merge in next_gen branch
This commit is contained in:
parent
0a54012cf5
commit
fda07698db
5 changed files with 516 additions and 0 deletions
424
toxygen/app.py
Normal file
424
toxygen/app.py
Normal file
|
@ -0,0 +1,424 @@
|
|||
from middleware import threads
|
||||
import middleware.callbacks as callbacks
|
||||
from PyQt5 import QtWidgets, QtGui, QtCore
|
||||
import ui.password_screen as password_screen
|
||||
import updater.updater as updater
|
||||
import os
|
||||
from middleware.tox_factory import tox_factory
|
||||
import wrapper.toxencryptsave as tox_encrypt_save
|
||||
import user_data.toxes
|
||||
from user_data.settings import Settings
|
||||
from ui.login_screen import LoginScreen
|
||||
from user_data.profile_manager import ProfileManager
|
||||
from plugin_support.plugin_support import PluginLoader
|
||||
from ui.main_screen import MainWindow
|
||||
from ui import tray
|
||||
import utils.ui as util_ui
|
||||
import utils.util as util
|
||||
from contacts.profile import Profile
|
||||
from file_transfers.file_transfers_handler import FileTransfersHandler
|
||||
from contacts.contact_provider import ContactProvider
|
||||
from contacts.friend_factory import FriendFactory
|
||||
from contacts.group_factory import GroupFactory
|
||||
from contacts.contacts_manager import ContactsManager
|
||||
from av.calls_manager import CallsManager
|
||||
from history.database import Database
|
||||
from ui.widgets_factory import WidgetsFactory
|
||||
from smileys.smileys import SmileyLoader
|
||||
from ui.items_factories import MessagesItemsFactory, ContactItemsFactory
|
||||
from messenger.messenger import Messenger
|
||||
from network.tox_dns import ToxDns
|
||||
from history.history import History
|
||||
from file_transfers.file_transfers_messages_service import FileTransfersMessagesService
|
||||
from groups.groups_service import GroupsService
|
||||
from ui.create_profile_screen import CreateProfileScreen
|
||||
from common.provider import Provider
|
||||
from contacts.group_peer_factory import GroupPeerFactory
|
||||
from user_data.backup_service import BackupService
|
||||
import styles.style # TODO: dynamic loading
|
||||
|
||||
|
||||
class App:
|
||||
|
||||
def __init__(self, version, path_to_profile=None, uri=None):
|
||||
self._version = version
|
||||
self._app = self._settings = self._profile_manager = self._plugin_loader = self._messenger = None
|
||||
self._tox = self._ms = self._init = self._main_loop = self._av_loop = None
|
||||
self._uri = self._toxes = self._tray = self._file_transfer_handler = self._contacts_provider = None
|
||||
self._friend_factory = self._calls_manager = self._contacts_manager = self._smiley_loader = None
|
||||
self._group_peer_factory = self._tox_dns = self._backup_service = None
|
||||
self._group_factory = self._groups_service = self._profile = None
|
||||
if uri is not None and uri.startswith('tox:'):
|
||||
self._uri = uri[4:]
|
||||
self._path = path_to_profile
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Public methods
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def main(self):
|
||||
"""
|
||||
Main function of app. loads login screen if needed and starts main screen
|
||||
"""
|
||||
self._app = QtWidgets.QApplication([])
|
||||
self._load_icon()
|
||||
|
||||
if util.get_platform() == 'Linux':
|
||||
QtCore.QCoreApplication.setAttribute(QtCore.Qt.AA_X11InitThreads)
|
||||
|
||||
self._load_base_style()
|
||||
|
||||
if not self._select_and_load_profile():
|
||||
return
|
||||
|
||||
if self._try_to_update():
|
||||
return
|
||||
|
||||
self._load_app_styles()
|
||||
self._load_app_translations()
|
||||
|
||||
self._create_dependencies()
|
||||
self._start_threads()
|
||||
|
||||
if self._uri is not None:
|
||||
self._ms.add_contact(self._uri)
|
||||
|
||||
self._app.lastWindowClosed.connect(self._app.quit)
|
||||
|
||||
self._execute_app()
|
||||
|
||||
self._stop_app()
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# App executing
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def _execute_app(self):
|
||||
while True:
|
||||
try:
|
||||
self._app.exec_()
|
||||
except Exception as ex:
|
||||
util.log('Unhandled exception: ' + str(ex))
|
||||
else:
|
||||
break
|
||||
|
||||
def _stop_app(self):
|
||||
self._plugin_loader.stop()
|
||||
self._stop_threads()
|
||||
self._file_transfer_handler.stop()
|
||||
self._tray.hide()
|
||||
self._save_profile()
|
||||
self._settings.close()
|
||||
self._kill_toxav()
|
||||
self._kill_tox()
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# App loading
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def _load_base_style(self):
|
||||
with open(util.join_path(util.get_styles_directory(), 'dark_style.qss')) as fl:
|
||||
style = fl.read()
|
||||
self._app.setStyleSheet(style)
|
||||
|
||||
def _load_app_styles(self):
|
||||
# application color scheme
|
||||
if self._settings['theme'] == 'dark':
|
||||
return
|
||||
for theme in self._settings.built_in_themes().keys():
|
||||
if self._settings['theme'] != theme:
|
||||
continue
|
||||
theme_path = self._settings.built_in_themes()[theme]
|
||||
file_path = util.join_path(util.get_styles_directory(), theme_path)
|
||||
with open(file_path) as fl:
|
||||
style = fl.read()
|
||||
self._app.setStyleSheet(style)
|
||||
break
|
||||
|
||||
def _load_login_screen_translations(self):
|
||||
current_language, supported_languages = self._get_languages()
|
||||
if current_language not in supported_languages:
|
||||
return
|
||||
lang_path = supported_languages[current_language]
|
||||
translator = QtCore.QTranslator()
|
||||
translator.load(util.get_translations_directory() + lang_path)
|
||||
self._app.installTranslator(translator)
|
||||
self._app.translator = translator
|
||||
|
||||
def _load_icon(self):
|
||||
icon_file = os.path.join(util.get_images_directory(), 'icon.png')
|
||||
self._app.setWindowIcon(QtGui.QIcon(icon_file))
|
||||
|
||||
@staticmethod
|
||||
def _get_languages():
|
||||
current_locale = QtCore.QLocale()
|
||||
curr_language = current_locale.languageToString(current_locale.language())
|
||||
supported_languages = Settings.supported_languages()
|
||||
|
||||
return curr_language, supported_languages
|
||||
|
||||
def _load_app_translations(self):
|
||||
lang = Settings.supported_languages()[self._settings['language']]
|
||||
translator = QtCore.QTranslator()
|
||||
translator.load(os.path.join(util.get_translations_directory(), lang))
|
||||
self._app.installTranslator(translator)
|
||||
self._app.translator = translator
|
||||
|
||||
def _select_and_load_profile(self):
|
||||
encrypt_save = tox_encrypt_save.ToxEncryptSave()
|
||||
self._toxes = user_data.toxes.ToxES(encrypt_save)
|
||||
|
||||
if self._path is not None: # toxygen was started with path to profile
|
||||
self._load_existing_profile(self._path)
|
||||
else:
|
||||
auto_profile = Settings.get_auto_profile()
|
||||
if auto_profile is None: # no default profile
|
||||
result = self._select_profile()
|
||||
if result is None:
|
||||
return False
|
||||
if result.is_new_profile(): # create new profile
|
||||
if not self._create_new_profile(result.profile_path):
|
||||
return False
|
||||
else: # load existing profile
|
||||
self._load_existing_profile(result.profile_path)
|
||||
self._path = result.profile_path
|
||||
else: # default profile
|
||||
self._path = auto_profile
|
||||
self._load_existing_profile(auto_profile)
|
||||
|
||||
if Settings.is_active_profile(self._path): # profile is in use
|
||||
profile_name = util.get_profile_name_from_path(self._path)
|
||||
title = util_ui.tr('Profile {}').format(profile_name)
|
||||
text = util_ui.tr(
|
||||
'Other instance of Toxygen uses this profile or profile was not properly closed. Continue?')
|
||||
reply = util_ui.question(text, title)
|
||||
if not reply:
|
||||
return False
|
||||
|
||||
self._settings.set_active_profile()
|
||||
|
||||
return True
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Threads
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def _start_threads(self, initial_start=True):
|
||||
# init thread
|
||||
self._init = threads.InitThread(self._tox, self._plugin_loader, self._settings, initial_start)
|
||||
self._init.start()
|
||||
|
||||
# starting threads for tox iterate and toxav iterate
|
||||
self._main_loop = threads.ToxIterateThread(self._tox)
|
||||
self._main_loop.start()
|
||||
self._av_loop = threads.ToxAVIterateThread(self._tox.AV)
|
||||
self._av_loop.start()
|
||||
|
||||
if initial_start:
|
||||
threads.start_file_transfer_thread()
|
||||
|
||||
def _stop_threads(self, is_app_closing=True):
|
||||
self._init.stop_thread()
|
||||
|
||||
self._av_loop.stop_thread()
|
||||
self._main_loop.stop_thread()
|
||||
|
||||
if is_app_closing:
|
||||
threads.stop_file_transfer_thread()
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Profiles
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def _select_profile(self):
|
||||
self._load_login_screen_translations()
|
||||
ls = LoginScreen()
|
||||
profiles = ProfileManager.find_profiles()
|
||||
ls.update_select(profiles)
|
||||
ls.show()
|
||||
self._app.exec_()
|
||||
|
||||
return ls.result
|
||||
|
||||
def _load_existing_profile(self, profile_path):
|
||||
self._profile_manager = ProfileManager(self._toxes, profile_path)
|
||||
data = self._profile_manager.open_profile()
|
||||
if self._toxes.is_data_encrypted(data):
|
||||
data = self._enter_password(data)
|
||||
self._settings = Settings(self._toxes, profile_path.replace('.tox', '.json'))
|
||||
self._tox = self._create_tox(data)
|
||||
|
||||
def _create_new_profile(self, profile_name):
|
||||
result = self._get_create_profile_screen_result()
|
||||
if result is None:
|
||||
return False
|
||||
if result.save_into_default_folder:
|
||||
profile_path = util.join_path(Settings.get_default_path(), profile_name + '.tox')
|
||||
else:
|
||||
profile_path = util.join_path(util.curr_directory(__file__), profile_name + '.tox')
|
||||
if os.path.isfile(profile_path):
|
||||
util_ui.message_box(util_ui.tr('Profile with this name already exists'),
|
||||
util_ui.tr('Error'))
|
||||
return False
|
||||
name = profile_name or 'toxygen_user'
|
||||
self._tox = tox_factory()
|
||||
self._tox.self_set_name(name if name else 'Toxygen User')
|
||||
self._tox.self_set_status_message('Toxing on Toxygen')
|
||||
self._path = profile_path
|
||||
if result.password:
|
||||
self._toxes.set_password(result.password)
|
||||
self._settings = Settings(self._toxes, self._path.replace('.tox', '.json'))
|
||||
self._profile_manager = ProfileManager(self._toxes, profile_path)
|
||||
try:
|
||||
self._save_profile()
|
||||
except Exception as ex:
|
||||
print(ex)
|
||||
util.log('Profile creation exception: ' + str(ex))
|
||||
text = util_ui.tr('Profile saving error! Does Toxygen have permission to write to this directory?')
|
||||
util_ui.message_box(text, util_ui.tr('Error'))
|
||||
|
||||
return False
|
||||
current_language, supported_languages = self._get_languages()
|
||||
if current_language in supported_languages:
|
||||
self._settings['language'] = current_language
|
||||
self._settings.save()
|
||||
|
||||
return True
|
||||
|
||||
def _get_create_profile_screen_result(self):
|
||||
cps = CreateProfileScreen()
|
||||
cps.show()
|
||||
self._app.exec_()
|
||||
|
||||
return cps.result
|
||||
|
||||
def _save_profile(self, data=None):
|
||||
data = data or self._tox.get_savedata()
|
||||
self._profile_manager.save_profile(data)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Other private methods
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def _enter_password(self, data):
|
||||
"""
|
||||
Show password screen
|
||||
"""
|
||||
p = password_screen.PasswordScreen(self._toxes, data)
|
||||
p.show()
|
||||
self._app.lastWindowClosed.connect(self._app.quit)
|
||||
self._app.exec_()
|
||||
if p.result is not None:
|
||||
return p.result
|
||||
self._force_exit()
|
||||
|
||||
def _reset(self):
|
||||
"""
|
||||
Create new tox instance (new network settings)
|
||||
:return: tox instance
|
||||
"""
|
||||
self._contacts_manager.reset_contacts_statuses()
|
||||
self._stop_threads(False)
|
||||
data = self._tox.get_savedata()
|
||||
self._save_profile(data)
|
||||
self._kill_toxav()
|
||||
self._kill_tox()
|
||||
# create new tox instance
|
||||
self._tox = self._create_tox(data)
|
||||
self._start_threads(False)
|
||||
|
||||
tox_savers = [self._friend_factory, self._group_factory, self._plugin_loader, self._contacts_manager,
|
||||
self._contacts_provider, self._messenger, self._file_transfer_handler, self._groups_service,
|
||||
self._profile]
|
||||
for tox_saver in tox_savers:
|
||||
tox_saver.set_tox(self._tox)
|
||||
|
||||
self._calls_manager.set_toxav(self._tox.AV)
|
||||
self._contacts_manager.update_friends_numbers()
|
||||
self._contacts_manager.update_groups_lists()
|
||||
self._contacts_manager.update_groups_numbers()
|
||||
|
||||
self._init_callbacks()
|
||||
|
||||
def _create_dependencies(self):
|
||||
self._backup_service = BackupService(self._settings, self._profile_manager)
|
||||
self._smiley_loader = SmileyLoader(self._settings)
|
||||
self._tox_dns = ToxDns(self._settings)
|
||||
self._ms = MainWindow(self._settings, self._tray)
|
||||
db = Database(self._path.replace('.tox', '.db'), self._toxes)
|
||||
|
||||
contact_items_factory = ContactItemsFactory(self._settings, self._ms)
|
||||
self._friend_factory = FriendFactory(self._profile_manager, self._settings,
|
||||
self._tox, db, contact_items_factory)
|
||||
self._group_factory = GroupFactory(self._profile_manager, self._settings, self._tox, db, contact_items_factory)
|
||||
self._group_peer_factory = GroupPeerFactory(self._tox, self._profile_manager, db, contact_items_factory)
|
||||
self._contacts_provider = ContactProvider(self._tox, self._friend_factory, self._group_factory,
|
||||
self._group_peer_factory)
|
||||
self._profile = Profile(self._profile_manager, self._tox, self._ms, self._contacts_provider, self._reset)
|
||||
self._init_profile()
|
||||
self._plugin_loader = PluginLoader(self._settings, self)
|
||||
history = None
|
||||
messages_items_factory = MessagesItemsFactory(self._settings, self._plugin_loader, self._smiley_loader,
|
||||
self._ms, lambda m: history.delete_message(m))
|
||||
history = History(self._contacts_provider, db, self._settings, self._ms, messages_items_factory)
|
||||
self._contacts_manager = ContactsManager(self._tox, self._settings, self._ms, self._profile_manager,
|
||||
self._contacts_provider, history, self._tox_dns,
|
||||
messages_items_factory)
|
||||
history.set_contacts_manager(self._contacts_manager)
|
||||
self._calls_manager = CallsManager(self._tox.AV, self._settings, self._ms, self._contacts_manager)
|
||||
self._messenger = Messenger(self._tox, self._plugin_loader, self._ms, self._contacts_manager,
|
||||
self._contacts_provider, messages_items_factory, self._profile,
|
||||
self._calls_manager)
|
||||
file_transfers_message_service = FileTransfersMessagesService(self._contacts_manager, messages_items_factory,
|
||||
self._profile, self._ms)
|
||||
self._file_transfer_handler = FileTransfersHandler(self._tox, self._settings, self._contacts_provider,
|
||||
file_transfers_message_service, self._profile)
|
||||
messages_items_factory.set_file_transfers_handler(self._file_transfer_handler)
|
||||
widgets_factory = None
|
||||
widgets_factory_provider = Provider(lambda: widgets_factory)
|
||||
self._groups_service = GroupsService(self._tox, self._contacts_manager, self._contacts_provider, self._ms,
|
||||
widgets_factory_provider)
|
||||
widgets_factory = WidgetsFactory(self._settings, self._profile, self._profile_manager, self._contacts_manager,
|
||||
self._file_transfer_handler, self._smiley_loader, self._plugin_loader,
|
||||
self._toxes, self._version, self._groups_service, history,
|
||||
self._contacts_provider)
|
||||
self._tray = tray.init_tray(self._profile, self._settings, self._ms, self._toxes)
|
||||
self._ms.set_dependencies(widgets_factory, self._tray, self._contacts_manager, self._messenger, self._profile,
|
||||
self._plugin_loader, self._file_transfer_handler, history, self._calls_manager,
|
||||
self._groups_service, self._toxes)
|
||||
|
||||
self._tray.show()
|
||||
self._ms.show()
|
||||
|
||||
self._init_callbacks()
|
||||
|
||||
def _try_to_update(self):
|
||||
updating = updater.start_update_if_needed(self._version, self._settings)
|
||||
if updating:
|
||||
self._save_profile()
|
||||
self._settings.close()
|
||||
self._kill_toxav()
|
||||
self._kill_tox()
|
||||
return updating
|
||||
|
||||
def _create_tox(self, data):
|
||||
return tox_factory(data, self._settings)
|
||||
|
||||
def _force_exit(self):
|
||||
raise SystemExit()
|
||||
|
||||
def _init_callbacks(self):
|
||||
callbacks.init_callbacks(self._tox, self._profile, self._settings, self._plugin_loader, self._contacts_manager,
|
||||
self._calls_manager, self._file_transfer_handler, self._ms, self._tray,
|
||||
self._messenger, self._groups_service, self._contacts_provider)
|
||||
|
||||
def _init_profile(self):
|
||||
if not self._profile.has_avatar():
|
||||
self._profile.reset_avatar(self._settings['identicons'])
|
||||
|
||||
def _kill_toxav(self):
|
||||
self._calls_manager.set_toxav(None)
|
||||
self._tox.AV.kill()
|
||||
|
||||
def _kill_tox(self):
|
||||
self._tox.kill()
|
0
toxygen/smileys/__init__.py
Normal file
0
toxygen/smileys/__init__.py
Normal file
74
toxygen/smileys/smileys.py
Normal file
74
toxygen/smileys/smileys.py
Normal file
|
@ -0,0 +1,74 @@
|
|||
from utils import util
|
||||
import json
|
||||
import os
|
||||
from collections import OrderedDict
|
||||
from PyQt5 import QtCore
|
||||
|
||||
|
||||
class SmileyLoader:
|
||||
"""
|
||||
Class which loads smileys packs and insert smileys into messages
|
||||
"""
|
||||
|
||||
def __init__(self, settings):
|
||||
super().__init__()
|
||||
self._settings = settings
|
||||
self._curr_pack = None # current pack name
|
||||
self._smileys = {} # smileys dict. key - smiley (str), value - path to image (str)
|
||||
self._list = [] # smileys list without duplicates
|
||||
self.load_pack()
|
||||
|
||||
def load_pack(self):
|
||||
"""
|
||||
Loads smiley pack
|
||||
"""
|
||||
pack_name = self._settings['smiley_pack']
|
||||
if self._settings['smileys'] and self._curr_pack != pack_name:
|
||||
self._curr_pack = pack_name
|
||||
path = util.join_path(self.get_smileys_path(), 'config.json')
|
||||
try:
|
||||
with open(path, encoding='utf8') as fl:
|
||||
self._smileys = json.loads(fl.read())
|
||||
fl.seek(0)
|
||||
tmp = json.loads(fl.read(), object_pairs_hook=OrderedDict)
|
||||
print('Smiley pack {} loaded'.format(pack_name))
|
||||
keys, values, self._list = [], [], []
|
||||
for key, value in tmp.items():
|
||||
value = util.join_path(self.get_smileys_path(), value)
|
||||
if value not in values:
|
||||
keys.append(key)
|
||||
values.append(value)
|
||||
self._list = list(zip(keys, values))
|
||||
except Exception as ex:
|
||||
self._smileys = {}
|
||||
self._list = []
|
||||
print('Smiley pack {} was not loaded. Error: {}'.format(pack_name, ex))
|
||||
|
||||
def get_smileys_path(self):
|
||||
return util.join_path(util.get_smileys_directory(), self._curr_pack) if self._curr_pack is not None else None
|
||||
|
||||
@staticmethod
|
||||
def get_packs_list():
|
||||
d = util.get_smileys_directory()
|
||||
return [x[1] for x in os.walk(d)][0]
|
||||
|
||||
def get_smileys(self):
|
||||
return list(self._list)
|
||||
|
||||
def add_smileys_to_text(self, text, edit):
|
||||
"""
|
||||
Adds smileys to text
|
||||
:param text: message
|
||||
:param edit: MessageEdit instance
|
||||
:return text with smileys
|
||||
"""
|
||||
if not self._settings['smileys'] or not len(self._smileys):
|
||||
return text
|
||||
arr = text.split(' ')
|
||||
for i in range(len(arr)):
|
||||
if arr[i] in self._smileys:
|
||||
file_name = self._smileys[arr[i]] # image name
|
||||
arr[i] = '<img title=\"{}\" src=\"{}\" />'.format(arr[i], file_name)
|
||||
if file_name.endswith('.gif'): # animated smiley
|
||||
edit.addAnimation(QtCore.QUrl(file_name), self.get_smileys_path() + file_name)
|
||||
return ' '.join(arr)
|
0
toxygen/stickers/__init__.py
Normal file
0
toxygen/stickers/__init__.py
Normal file
18
toxygen/stickers/stickers.py
Normal file
18
toxygen/stickers/stickers.py
Normal file
|
@ -0,0 +1,18 @@
|
|||
import os
|
||||
import utils.util as util
|
||||
|
||||
|
||||
def load_stickers():
|
||||
"""
|
||||
:return list of stickers
|
||||
"""
|
||||
result = []
|
||||
d = util.get_stickers_directory()
|
||||
keys = [x[1] for x in os.walk(d)][0]
|
||||
for key in keys:
|
||||
path = util.join_path(d, key)
|
||||
files = filter(lambda f: f.endswith('.png'), os.listdir(path))
|
||||
files = map(lambda f: util.join_path(path, f), files)
|
||||
result.extend(files)
|
||||
|
||||
return result
|
Loading…
Reference in a new issue