diff --git a/toxygen/communication/callbacks.py b/toxygen/communication/callbacks.py index e27bd74..ac93173 100644 --- a/toxygen/communication/callbacks.py +++ b/toxygen/communication/callbacks.py @@ -102,10 +102,10 @@ def friend_status_message(profile): def friend_message(profile, settings, window, tray): - """ - New message from friend - """ def wrapped(tox, friend_number, message_type, message, size, user_data): + """ + New message from friend + """ message = str(message, 'utf-8') invoke_in_main_thread(profile.new_message, friend_number, message_type, message) if not window.isActiveWindow(): @@ -119,16 +119,17 @@ def friend_message(profile, settings, window, tray): return wrapped -def friend_request(tox, public_key, message, message_size, user_data): - """ - Called when user get new friend request - """ - print('Friend request') - profile = Profile.get_instance() - key = ''.join(chr(x) for x in public_key[:TOX_PUBLIC_KEY_SIZE]) - tox_id = bin_to_string(key, TOX_PUBLIC_KEY_SIZE) - if tox_id not in Settings.get_instance()['blocked']: - invoke_in_main_thread(profile.process_friend_request, tox_id, str(message, 'utf-8')) +def friend_request(contacts_manager): + def wrapped(tox, public_key, message, message_size, user_data): + """ + Called when user get new friend request + """ + print('Friend request') + key = ''.join(chr(x) for x in public_key[:TOX_PUBLIC_KEY_SIZE]) + tox_id = bin_to_string(key, TOX_PUBLIC_KEY_SIZE) + invoke_in_main_thread(contacts_manager.process_friend_request, tox_id, str(message, 'utf-8')) + + return wrapped def friend_typing(tox, friend_number, typing, user_data): diff --git a/toxygen/contacts/contact.py b/toxygen/contacts/contact.py index 96ff804..22191a7 100644 --- a/toxygen/contacts/contact.py +++ b/toxygen/contacts/contact.py @@ -1,4 +1,4 @@ -from db.database import * +from history.database import * from contacts import basecontact import util from messenger.messages import * @@ -124,8 +124,8 @@ class Contact(basecontact.BaseContact): # Message deletion # ----------------------------------------------------------------------------------------------------------------- - def delete_message(self, time): - elem = list(filter(lambda x: type(x) in (TextMessage, GroupChatMessage) and x.get_data()[2] == time, self._corr))[0] + def delete_message(self, message_id): + elem = list(filter(lambda x: type(x) in (TextMessage, GroupChatMessage) and x.message_id == message_id, self._corr))[0] tmp = list(filter(lambda x: x.get_type() <= 1, self._corr)) if elem in tmp[-self._unsaved_messages:] and self._unsaved_messages: self._unsaved_messages -= 1 diff --git a/toxygen/contacts/contacts_manager.py b/toxygen/contacts/contacts_manager.py index cbac1e1..7653b56 100644 --- a/toxygen/contacts/contacts_manager.py +++ b/toxygen/contacts/contacts_manager.py @@ -152,6 +152,10 @@ class ContactsManager: active_friend = property(get_active, set_active) + def update(self): + if self._active_friend + 1: + self.set_active(self._active_friend) + # ----------------------------------------------------------------------------------------------------------------- # Filtration # ----------------------------------------------------------------------------------------------------------------- @@ -389,6 +393,8 @@ class ContactsManager: :param tox_id: tox id of contact :param message: message """ + if tox_id in self._settings['blocked']: + return try: text = QtWidgets.QApplication.translate('MainWindow', 'User {} wants to add you to contact list. Message:\n{}') info = text.format(tox_id, message) diff --git a/toxygen/contacts/profile.py b/toxygen/contacts/profile.py index 483d96b..9d33f19 100644 --- a/toxygen/contacts/profile.py +++ b/toxygen/contacts/profile.py @@ -45,7 +45,6 @@ class Profile(basecontact.BaseContact, Singleton): self._show_avatars = settings['show_avatars'] self._paused_file_transfers = dict(settings['paused_file_transfers']) # key - file id, value: [path, friend number, is incoming, start position] - self._history = History(tox.self_get_public_key()) # connection to db # ----------------------------------------------------------------------------------------------------------------- @@ -128,10 +127,6 @@ class Profile(basecontact.BaseContact, Singleton): self._messages.scrollToBottom() self.set_active(None) - def update(self): - if self._active_friend + 1: - self.set_active(self._active_friend) - # ----------------------------------------------------------------------------------------------------------------- # Friend connection status callbacks # ----------------------------------------------------------------------------------------------------------------- @@ -242,29 +237,6 @@ class Profile(basecontact.BaseContact, Singleton): return messages - def split_and_send(self, number, message_type, message): - """ - Message splitting. Message length cannot be > TOX_MAX_MESSAGE_LENGTH - :param number: friend's number - :param message_type: type of message - :param message: message text - """ - while len(message) > TOX_MAX_MESSAGE_LENGTH: - size = TOX_MAX_MESSAGE_LENGTH * 4 // 5 - last_part = message[size:TOX_MAX_MESSAGE_LENGTH] - if b' ' in last_part: - index = last_part.index(b' ') - elif b',' in last_part: - index = last_part.index(b',') - elif b'.' in last_part: - index = last_part.index(b'.') - else: - index = TOX_MAX_MESSAGE_LENGTH - size - 1 - index += size + 1 - self._tox.friend_send_message(number, message_type, message[:index]) - message = message[index:] - self._tox.friend_send_message(number, message_type, message) - def new_message(self, friend_num, message_type, message): """ Current user gets new message @@ -286,30 +258,29 @@ class Profile(basecontact.BaseContact, Singleton): if not friend.visibility: self.update_filtration() - def send_message(self, text, friend_num=None): + def send_message_to_friend(self, text, friend_number=None): """ Send message :param text: message text - :param friend_num: num of friend + :param friend_number: number of friend """ - if not self.is_active_a_friend(): - self.send_gc_message(text) - return - if friend_num is None: - friend_num = self.get_active_number() + if friend_number is None: + friend_number = self.get_active_number() if text.startswith('/plugin '): - plugin_support.PluginLoader.get_instance().command(text[8:]) + self._plugin_loader.command(text[8:]) self._screen.messageEdit.clear() - elif text and friend_num + 1: + elif text and friend_number >= 0: if text.startswith('/me '): message_type = TOX_MESSAGE_TYPE['ACTION'] text = text[4:] else: message_type = TOX_MESSAGE_TYPE['NORMAL'] - friend = self.get_friend_by_number(friend_num) + friend = self.get_friend_by_number(friend_number) friend.inc_receipts() if friend.status is not None: - self.split_and_send(friend.number, message_type, text.encode('utf-8')) + messages = self.split_message(text.encode('utf-8')) + for message in messages: + self._tox.friend_send_message(friend_number, message_type, message) t = time.time() if friend.number == self.get_active_number() and self.is_active_a_friend(): self.create_message_item(text, t, MESSAGE_OWNER['NOT_SENT'], message_type) @@ -317,128 +288,12 @@ class Profile(basecontact.BaseContact, Singleton): self._messages.scrollToBottom() friend.append_message(TextMessage(text, MESSAGE_OWNER['NOT_SENT'], t, message_type)) - def delete_message(self, time): + def delete_message(self, message_id): friend = self.get_curr_friend() friend.delete_message(time) - self._history.delete_message(friend.tox_id, time) + self._history.delete_message(friend.tox_id, message_id) self.update() - # ----------------------------------------------------------------------------------------------------------------- - # History support - # ----------------------------------------------------------------------------------------------------------------- - - def save_history(self): - """ - Save history to db - """ - s = Settings.get_instance() - if hasattr(self, '_history'): - if s['save_history']: - for friend in filter(lambda x: type(x) is Friend, self._contacts): - if not self._history.friend_exists_in_db(friend.tox_id): - self._history.add_friend_to_db(friend.tox_id) - if not s['save_unsent_only']: - messages = friend.get_corr_for_saving() - else: - messages = friend.get_unsent_messages_for_saving() - self._history.delete_messages(friend.tox_id) - self._history.save_messages_to_db(friend.tox_id, messages) - unsent_messages = friend.get_unsent_messages() - unsent_time = unsent_messages[0].get_data()[2] if len(unsent_messages) else time.time() + 1 - self._history.update_messages(friend.tox_id, unsent_time) - self._history.save() - del self._history - - def clear_history(self, num=None, save_unsent=False): - """ - Clear chat history - """ - if num is not None: - friend = self._contacts[num] - friend.clear_corr(save_unsent) - if self._history.friend_exists_in_db(friend.tox_id): - self._history.delete_messages(friend.tox_id) - self._history.delete_friend_from_db(friend.tox_id) - else: # clear all history - for number in range(len(self._contacts)): - self.clear_history(number, save_unsent) - if num is None or num == self.get_active_number(): - self.update() - - def load_history(self): - """ - Tries to load next part of messages - """ - if not self._load_history: - return - self._load_history = False - friend = self.get_curr_friend() - friend.load_corr(False) - data = friend.get_corr() - if not data: - return - data.reverse() - data = data[self._messages.count():self._messages.count() + PAGE_SIZE] - for message in data: - if message.get_type() <= 1: # text message - data = message.get_data() - self.create_message_item(data[0], - data[2], - data[1], - data[3], - False) - elif message.get_type() == MESSAGE_TYPE['FILE_TRANSFER']: # file transfer - if message.get_status() is None: - self.create_unsent_file_item(message) - continue - item = self.create_file_transfer_item(message, False) - if message.get_status() in ACTIVE_FILE_TRANSFERS: # active file transfer - try: - ft = self._file_transfers[(message.get_friend_number(), message.get_file_number())] - ft.set_state_changed_handler(item.update_transfer_state) - ft.signal() - except: - print('Incoming not started transfer - no info found') - elif message.get_type() == MESSAGE_TYPE['INLINE']: # inline image - self.create_inline_item(message.get_data(), False) - else: # info message - data = message.get_data() - self.create_message_item(data[0], - data[2], - '', - data[3], - False) - self._load_history = True - - def export_db(self, directory): - self._history.export(directory) - - def export_history(self, num, as_text=True, _range=None): - friend = self._contacts[num] - if _range is None: - friend.load_all_corr() - corr = friend.get_corr() - elif _range[1] + 1: - corr = friend.get_corr()[_range[0]:_range[1] + 1] - else: - corr = friend.get_corr()[_range[0]:] - arr = [] - new_line = '\n' if as_text else '
' - for message in corr: - if type(message) is TextMessage: - data = message.get_data() - if as_text: - x = '[{}] {}: {}\n' - else: - x = '[{}] {}: {}
' - arr.append(x.format(convert_time(data[2]) if data[1] != MESSAGE_OWNER['NOT_SENT'] else 'Unsent', - friend.name if data[1] == MESSAGE_OWNER['FRIEND'] else self.name, - data[0])) - s = new_line.join(arr) - if not as_text: - s = '{}{}'.format(friend.name, s) - return s - # ----------------------------------------------------------------------------------------------------------------- # Friend, message and file transfer items creation # ----------------------------------------------------------------------------------------------------------------- diff --git a/toxygen/db/__init__.py b/toxygen/history/__init__.py similarity index 100% rename from toxygen/db/__init__.py rename to toxygen/history/__init__.py diff --git a/toxygen/db/database.py b/toxygen/history/database.py similarity index 99% rename from toxygen/db/database.py rename to toxygen/history/database.py index b414e8b..ff9ce8e 100644 --- a/toxygen/db/database.py +++ b/toxygen/history/database.py @@ -9,7 +9,7 @@ PAGE_SIZE = 42 TIMEOUT = 11 -SAVE_MESSAGES = 250 +SAVE_MESSAGES = 500 MESSAGE_OWNER = { 'ME': 0, diff --git a/toxygen/history/history_loader.py b/toxygen/history/history_loader.py new file mode 100644 index 0000000..86e61c5 --- /dev/null +++ b/toxygen/history/history_loader.py @@ -0,0 +1,134 @@ +from messenger.messages import * + + +class HistoryLoader: + + def __init__(self, db, settings): + self._db = db + self._settings = settings + + # ----------------------------------------------------------------------------------------------------------------- + # History support + # ----------------------------------------------------------------------------------------------------------------- + + def save_history(self): + """ + Save history to db + """ + if hasattr(self, '_history'): + if self._settings['save_history']: + for friend in filter(lambda x: type(x) is Friend, self._contacts): + if not self._history.friend_exists_in_db(friend.tox_id): + self._history.add_friend_to_db(friend.tox_id) + if not self._settings['save_unsent_only']: + messages = friend.get_corr_for_saving() + else: + messages = friend.get_unsent_messages_for_saving() + self._history.delete_messages(friend.tox_id) + self._history.save_messages_to_db(friend.tox_id, messages) + unsent_messages = friend.get_unsent_messages() + unsent_time = unsent_messages[0].get_data()[2] if len(unsent_messages) else time.time() + 1 + self._history.update_messages(friend.tox_id, unsent_time) + self._history.save() + del self._history + + def clear_history(self, friend, save_unsent=False): + """ + Clear chat history + """ + friend.clear_corr(save_unsent) + if self._db.friend_exists_in_db(friend.tox_id): + self._db.delete_messages(friend.tox_id) + self._db.delete_friend_from_db(friend.tox_id) + + def load_history(self): + """ + Tries to load next part of messages + """ + if not self._load_history: + return + self._load_history = False + friend = self.get_curr_friend() + friend.load_corr(False) + data = friend.get_corr() + if not data: + return + data.reverse() + data = data[self._messages.count():self._messages.count() + PAGE_SIZE] + for message in data: + if message.get_type() <= 1: # text message + data = message.get_data() + self.create_message_item(data[0], + data[2], + data[1], + data[3], + False) + elif message.get_type() == MESSAGE_TYPE['FILE_TRANSFER']: # file transfer + if message.get_status() is None: + self.create_unsent_file_item(message) + continue + item = self.create_file_transfer_item(message, False) + if message.get_status() in ACTIVE_FILE_TRANSFERS: # active file transfer + try: + ft = self._file_transfers[(message.get_friend_number(), message.get_file_number())] + ft.set_state_changed_handler(item.update_transfer_state) + ft.signal() + except: + print('Incoming not started transfer - no info found') + elif message.get_type() == MESSAGE_TYPE['INLINE']: # inline image + self.create_inline_item(message.get_data(), False) + else: # info message + data = message.get_data() + self.create_message_item(data[0], + data[2], + '', + data[3], + False) + self._load_history = True + + def export_history(self, friend, as_text=True, _range=None): + if _range is None: + friend.load_all_corr() + corr = friend.get_corr() + elif _range[1] + 1: + corr = friend.get_corr()[_range[0]:_range[1] + 1] + else: + corr = friend.get_corr()[_range[0]:] + + generator = TextHistoryGenerator() if as_text else HtmlHistoryGenerator + + return generator.generate(corr) + + +class HtmlHistoryGenerator: + + def generate(self, corr): + arr = [] + for message in corr: + if type(message) is TextMessage: + data = message.get_data() + x = '[{}] {}: {}
' + arr.append(x.format(convert_time(data[2]) if data[1] != MESSAGE_OWNER['NOT_SENT'] else 'Unsent', + friend.name if data[1] == MESSAGE_OWNER['FRIEND'] else self.name, + data[0])) + s = '
'.join(arr) + s = '{}{}'.format(friend.name, + s) + return s + + +class TextHistoryGenerator: + + def generate(self, corr): + arr = [] + for message in corr: + if type(message) is TextMessage: + data = message.get_data() + x = '[{}] {}: {}\n' + arr.append(x.format(convert_time(data[2]) if data[1] != MESSAGE_OWNER['NOT_SENT'] else 'Unsent', + friend.name if data[1] == MESSAGE_OWNER['FRIEND'] else self.name, + data[0])) + s = '\n'.join(arr) + + return s + diff --git a/toxygen/messenger/messages.py b/toxygen/messenger/messages.py index 8d9f4a3..4baa29a 100644 --- a/toxygen/messenger/messages.py +++ b/toxygen/messenger/messages.py @@ -5,18 +5,17 @@ MESSAGE_TYPE = { 'ACTION': 1, 'FILE_TRANSFER': 2, 'INLINE': 3, - 'INFO_MESSAGE': 4, - 'GC_TEXT': 5, - 'GC_ACTION': 6 + 'INFO_MESSAGE': 4 } class Message: - def __init__(self, message_type, owner, time): + def __init__(self, message_id, message_type, owner, time): self._time = time self._type = message_type self._owner = owner + self._message_id = message_id def get_type(self): return self._type @@ -27,14 +26,19 @@ class Message: def mark_as_sent(self): self._owner = 0 + def get_message_id(self): + return self._message_id + + message_id = property(get_message_id) + class TextMessage(Message): """ Plain text or action message """ - def __init__(self, message, owner, time, message_type): - super(TextMessage, self).__init__(message_type, owner, time) + def __init__(self, id, message, owner, time, message_type): + super(TextMessage, self).__init__(id, message_type, owner, time) self._message = message def get_data(self): @@ -43,8 +47,8 @@ class TextMessage(Message): class GroupChatMessage(TextMessage): - def __init__(self, message, owner, time, message_type, name): - super().__init__(message, owner, time, message_type) + def __init__(self, id, message, owner, time, message_type, name): + super().__init__(id, message, owner, time, message_type) self._user_name = name def get_data(self): @@ -56,7 +60,7 @@ class TransferMessage(Message): Message with info about file transfer """ - def __init__(self, owner, time, status, size, name, friend_number, file_number): + def __init__(self, id, owner, time, status, size, name, friend_number, file_number): super(TransferMessage, self).__init__(MESSAGE_TYPE['FILE_TRANSFER'], owner, time) self._status = status self._size = size @@ -83,8 +87,8 @@ class TransferMessage(Message): class UnsentFile(Message): - def __init__(self, path, data, time): - super(UnsentFile, self).__init__(MESSAGE_TYPE['FILE_TRANSFER'], 0, time) + def __init__(self, id, path, data, time): + super(UnsentFile, self).__init__(id, MESSAGE_TYPE['FILE_TRANSFER'], 0, time) self._data, self._path = data, path def get_data(self): @@ -99,8 +103,8 @@ class InlineImage(Message): Inline image """ - def __init__(self, data): - super(InlineImage, self).__init__(MESSAGE_TYPE['INLINE'], None, None) + def __init__(self, id, data): + super(InlineImage, self).__init__(id, MESSAGE_TYPE['INLINE'], None, None) self._data = data def get_data(self): @@ -109,5 +113,5 @@ class InlineImage(Message): class InfoMessage(TextMessage): - def __init__(self, message, time): - super(InfoMessage, self).__init__(message, None, time, MESSAGE_TYPE['INFO_MESSAGE']) + def __init__(self, id, message, time): + super(InfoMessage, self).__init__(id, message, None, time, MESSAGE_TYPE['INFO_MESSAGE'])