history improvements

This commit is contained in:
ingvar1995 2018-03-12 00:32:46 +03:00
parent 20bb694c7e
commit 1bead7d55d
8 changed files with 189 additions and 189 deletions

View File

@ -102,10 +102,10 @@ def friend_status_message(profile):
def friend_message(profile, settings, window, tray): def friend_message(profile, settings, window, tray):
def wrapped(tox, friend_number, message_type, message, size, user_data):
""" """
New message from friend New message from friend
""" """
def wrapped(tox, friend_number, message_type, message, size, user_data):
message = str(message, 'utf-8') message = str(message, 'utf-8')
invoke_in_main_thread(profile.new_message, friend_number, message_type, message) invoke_in_main_thread(profile.new_message, friend_number, message_type, message)
if not window.isActiveWindow(): if not window.isActiveWindow():
@ -119,16 +119,17 @@ def friend_message(profile, settings, window, tray):
return wrapped return wrapped
def friend_request(tox, public_key, message, message_size, user_data): def friend_request(contacts_manager):
def wrapped(tox, public_key, message, message_size, user_data):
""" """
Called when user get new friend request Called when user get new friend request
""" """
print('Friend request') print('Friend request')
profile = Profile.get_instance()
key = ''.join(chr(x) for x in public_key[:TOX_PUBLIC_KEY_SIZE]) key = ''.join(chr(x) for x in public_key[:TOX_PUBLIC_KEY_SIZE])
tox_id = bin_to_string(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(contacts_manager.process_friend_request, tox_id, str(message, 'utf-8'))
invoke_in_main_thread(profile.process_friend_request, tox_id, str(message, 'utf-8'))
return wrapped
def friend_typing(tox, friend_number, typing, user_data): def friend_typing(tox, friend_number, typing, user_data):

View File

@ -1,4 +1,4 @@
from db.database import * from history.database import *
from contacts import basecontact from contacts import basecontact
import util import util
from messenger.messages import * from messenger.messages import *
@ -124,8 +124,8 @@ class Contact(basecontact.BaseContact):
# Message deletion # Message deletion
# ----------------------------------------------------------------------------------------------------------------- # -----------------------------------------------------------------------------------------------------------------
def delete_message(self, time): def delete_message(self, message_id):
elem = list(filter(lambda x: type(x) in (TextMessage, GroupChatMessage) and x.get_data()[2] == time, self._corr))[0] 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)) tmp = list(filter(lambda x: x.get_type() <= 1, self._corr))
if elem in tmp[-self._unsaved_messages:] and self._unsaved_messages: if elem in tmp[-self._unsaved_messages:] and self._unsaved_messages:
self._unsaved_messages -= 1 self._unsaved_messages -= 1

View File

@ -152,6 +152,10 @@ class ContactsManager:
active_friend = property(get_active, set_active) active_friend = property(get_active, set_active)
def update(self):
if self._active_friend + 1:
self.set_active(self._active_friend)
# ----------------------------------------------------------------------------------------------------------------- # -----------------------------------------------------------------------------------------------------------------
# Filtration # Filtration
# ----------------------------------------------------------------------------------------------------------------- # -----------------------------------------------------------------------------------------------------------------
@ -389,6 +393,8 @@ class ContactsManager:
:param tox_id: tox id of contact :param tox_id: tox id of contact
:param message: message :param message: message
""" """
if tox_id in self._settings['blocked']:
return
try: try:
text = QtWidgets.QApplication.translate('MainWindow', 'User {} wants to add you to contact list. Message:\n{}') text = QtWidgets.QApplication.translate('MainWindow', 'User {} wants to add you to contact list. Message:\n{}')
info = text.format(tox_id, message) info = text.format(tox_id, message)

View File

@ -45,7 +45,6 @@ class Profile(basecontact.BaseContact, Singleton):
self._show_avatars = settings['show_avatars'] self._show_avatars = settings['show_avatars']
self._paused_file_transfers = dict(settings['paused_file_transfers']) self._paused_file_transfers = dict(settings['paused_file_transfers'])
# key - file id, value: [path, friend number, is incoming, start position] # 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._messages.scrollToBottom()
self.set_active(None) self.set_active(None)
def update(self):
if self._active_friend + 1:
self.set_active(self._active_friend)
# ----------------------------------------------------------------------------------------------------------------- # -----------------------------------------------------------------------------------------------------------------
# Friend connection status callbacks # Friend connection status callbacks
# ----------------------------------------------------------------------------------------------------------------- # -----------------------------------------------------------------------------------------------------------------
@ -242,29 +237,6 @@ class Profile(basecontact.BaseContact, Singleton):
return messages 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): def new_message(self, friend_num, message_type, message):
""" """
Current user gets new message Current user gets new message
@ -286,30 +258,29 @@ class Profile(basecontact.BaseContact, Singleton):
if not friend.visibility: if not friend.visibility:
self.update_filtration() self.update_filtration()
def send_message(self, text, friend_num=None): def send_message_to_friend(self, text, friend_number=None):
""" """
Send message Send message
:param text: message text :param text: message text
:param friend_num: num of friend :param friend_number: number of friend
""" """
if not self.is_active_a_friend(): if friend_number is None:
self.send_gc_message(text) friend_number = self.get_active_number()
return
if friend_num is None:
friend_num = self.get_active_number()
if text.startswith('/plugin '): if text.startswith('/plugin '):
plugin_support.PluginLoader.get_instance().command(text[8:]) self._plugin_loader.command(text[8:])
self._screen.messageEdit.clear() self._screen.messageEdit.clear()
elif text and friend_num + 1: elif text and friend_number >= 0:
if text.startswith('/me '): if text.startswith('/me '):
message_type = TOX_MESSAGE_TYPE['ACTION'] message_type = TOX_MESSAGE_TYPE['ACTION']
text = text[4:] text = text[4:]
else: else:
message_type = TOX_MESSAGE_TYPE['NORMAL'] 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() friend.inc_receipts()
if friend.status is not None: 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() t = time.time()
if friend.number == self.get_active_number() and self.is_active_a_friend(): 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) self.create_message_item(text, t, MESSAGE_OWNER['NOT_SENT'], message_type)
@ -317,128 +288,12 @@ class Profile(basecontact.BaseContact, Singleton):
self._messages.scrollToBottom() self._messages.scrollToBottom()
friend.append_message(TextMessage(text, MESSAGE_OWNER['NOT_SENT'], t, message_type)) 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 = self.get_curr_friend()
friend.delete_message(time) friend.delete_message(time)
self._history.delete_message(friend.tox_id, time) self._history.delete_message(friend.tox_id, message_id)
self.update() 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 '<br>'
for message in corr:
if type(message) is TextMessage:
data = message.get_data()
if as_text:
x = '[{}] {}: {}\n'
else:
x = '[{}] <b>{}:</b> {}<br>'
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 = '<html><head><meta charset="UTF-8"><title>{}</title></head><body>{}</body></html>'.format(friend.name, s)
return s
# ----------------------------------------------------------------------------------------------------------------- # -----------------------------------------------------------------------------------------------------------------
# Friend, message and file transfer items creation # Friend, message and file transfer items creation
# ----------------------------------------------------------------------------------------------------------------- # -----------------------------------------------------------------------------------------------------------------

View File

@ -9,7 +9,7 @@ PAGE_SIZE = 42
TIMEOUT = 11 TIMEOUT = 11
SAVE_MESSAGES = 250 SAVE_MESSAGES = 500
MESSAGE_OWNER = { MESSAGE_OWNER = {
'ME': 0, 'ME': 0,

View File

@ -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 = '[{}] <b>{}:</b> {}<br>'
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 = '<br>'.join(arr)
s = '<html><head><meta charset="UTF-8"><title>{}</title></head><body>{}</body></html>'.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

View File

@ -5,18 +5,17 @@ MESSAGE_TYPE = {
'ACTION': 1, 'ACTION': 1,
'FILE_TRANSFER': 2, 'FILE_TRANSFER': 2,
'INLINE': 3, 'INLINE': 3,
'INFO_MESSAGE': 4, 'INFO_MESSAGE': 4
'GC_TEXT': 5,
'GC_ACTION': 6
} }
class Message: class Message:
def __init__(self, message_type, owner, time): def __init__(self, message_id, message_type, owner, time):
self._time = time self._time = time
self._type = message_type self._type = message_type
self._owner = owner self._owner = owner
self._message_id = message_id
def get_type(self): def get_type(self):
return self._type return self._type
@ -27,14 +26,19 @@ class Message:
def mark_as_sent(self): def mark_as_sent(self):
self._owner = 0 self._owner = 0
def get_message_id(self):
return self._message_id
message_id = property(get_message_id)
class TextMessage(Message): class TextMessage(Message):
""" """
Plain text or action message Plain text or action message
""" """
def __init__(self, message, owner, time, message_type): def __init__(self, id, message, owner, time, message_type):
super(TextMessage, self).__init__(message_type, owner, time) super(TextMessage, self).__init__(id, message_type, owner, time)
self._message = message self._message = message
def get_data(self): def get_data(self):
@ -43,8 +47,8 @@ class TextMessage(Message):
class GroupChatMessage(TextMessage): class GroupChatMessage(TextMessage):
def __init__(self, message, owner, time, message_type, name): def __init__(self, id, message, owner, time, message_type, name):
super().__init__(message, owner, time, message_type) super().__init__(id, message, owner, time, message_type)
self._user_name = name self._user_name = name
def get_data(self): def get_data(self):
@ -56,7 +60,7 @@ class TransferMessage(Message):
Message with info about file transfer 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) super(TransferMessage, self).__init__(MESSAGE_TYPE['FILE_TRANSFER'], owner, time)
self._status = status self._status = status
self._size = size self._size = size
@ -83,8 +87,8 @@ class TransferMessage(Message):
class UnsentFile(Message): class UnsentFile(Message):
def __init__(self, path, data, time): def __init__(self, id, path, data, time):
super(UnsentFile, self).__init__(MESSAGE_TYPE['FILE_TRANSFER'], 0, time) super(UnsentFile, self).__init__(id, MESSAGE_TYPE['FILE_TRANSFER'], 0, time)
self._data, self._path = data, path self._data, self._path = data, path
def get_data(self): def get_data(self):
@ -99,8 +103,8 @@ class InlineImage(Message):
Inline image Inline image
""" """
def __init__(self, data): def __init__(self, id, data):
super(InlineImage, self).__init__(MESSAGE_TYPE['INLINE'], None, None) super(InlineImage, self).__init__(id, MESSAGE_TYPE['INLINE'], None, None)
self._data = data self._data = data
def get_data(self): def get_data(self):
@ -109,5 +113,5 @@ class InlineImage(Message):
class InfoMessage(TextMessage): class InfoMessage(TextMessage):
def __init__(self, message, time): def __init__(self, id, message, time):
super(InfoMessage, self).__init__(message, None, time, MESSAGE_TYPE['INFO_MESSAGE']) super(InfoMessage, self).__init__(id, message, None, time, MESSAGE_TYPE['INFO_MESSAGE'])