initial commit - rewriting. ngc wrapper added, project structure updated
This commit is contained in:
parent
bb2a857ecf
commit
2de4eea357
57 changed files with 2420 additions and 957 deletions
0
toxygen/contacts/__init__.py
Normal file
0
toxygen/contacts/__init__.py
Normal file
118
toxygen/contacts/basecontact.py
Normal file
118
toxygen/contacts/basecontact.py
Normal file
|
@ -0,0 +1,118 @@
|
|||
from user_data.settings import *
|
||||
from PyQt5 import QtCore, QtGui
|
||||
from wrapper.toxcore_enums_and_consts import TOX_PUBLIC_KEY_SIZE
|
||||
|
||||
|
||||
class BaseContact:
|
||||
"""
|
||||
Class encapsulating TOX contact
|
||||
Properties: name (alias of contact or name), status_message, status (connection status)
|
||||
widget - widget for update, tox id (or public key)
|
||||
Base class for all contacts.
|
||||
"""
|
||||
|
||||
def __init__(self, name, status_message, widget, tox_id):
|
||||
"""
|
||||
:param name: name, example: 'Toxygen user'
|
||||
:param status_message: status message, example: 'Toxing on Toxygen'
|
||||
:param widget: ContactItem instance
|
||||
:param tox_id: tox id of contact
|
||||
"""
|
||||
self._name, self._status_message = name, status_message
|
||||
self._status, self._widget = None, widget
|
||||
self._tox_id = tox_id
|
||||
self.init_widget()
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Name - current name or alias of user
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_name(self):
|
||||
return self._name
|
||||
|
||||
def set_name(self, value):
|
||||
self._name = str(value, 'utf-8')
|
||||
self._widget.name.setText(self._name)
|
||||
self._widget.name.repaint()
|
||||
|
||||
name = property(get_name, set_name)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Status message
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_status_message(self):
|
||||
return self._status_message
|
||||
|
||||
def set_status_message(self, value):
|
||||
self._status_message = str(value, 'utf-8')
|
||||
self._widget.status_message.setText(self._status_message)
|
||||
self._widget.status_message.repaint()
|
||||
|
||||
status_message = property(get_status_message, set_status_message)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Status
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_status(self):
|
||||
return self._status
|
||||
|
||||
def set_status(self, value):
|
||||
self._status = value
|
||||
self._widget.connection_status.update(value)
|
||||
|
||||
status = property(get_status, set_status)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# TOX ID. WARNING: for friend it will return public key, for profile - full address
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_tox_id(self):
|
||||
return self._tox_id
|
||||
|
||||
tox_id = property(get_tox_id)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Avatars
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def load_avatar(self):
|
||||
"""
|
||||
Tries to load avatar of contact or uses default avatar
|
||||
"""
|
||||
prefix = ProfileManager.get_path() + 'avatars/'
|
||||
avatar_path = prefix + '{}.png'.format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2])
|
||||
if not os.path.isfile(avatar_path) or not os.path.getsize(avatar_path): # load default image
|
||||
avatar_path = curr_directory() + '/images/avatar.png'
|
||||
width = self._widget.avatar_label.width()
|
||||
pixmap = QtGui.QPixmap(avatar_path)
|
||||
self._widget.avatar_label.setPixmap(pixmap.scaled(width, width, QtCore.Qt.KeepAspectRatio,
|
||||
QtCore.Qt.SmoothTransformation))
|
||||
self._widget.avatar_label.repaint()
|
||||
|
||||
def reset_avatar(self):
|
||||
avatar_path = (ProfileManager.get_path() + 'avatars/{}.png').format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2])
|
||||
if os.path.isfile(avatar_path):
|
||||
os.remove(avatar_path)
|
||||
self.load_avatar()
|
||||
|
||||
def set_avatar(self, avatar):
|
||||
avatar_path = (ProfileManager.get_path() + 'avatars/{}.png').format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2])
|
||||
with open(avatar_path, 'wb') as f:
|
||||
f.write(avatar)
|
||||
self.load_avatar()
|
||||
|
||||
def get_pixmap(self):
|
||||
return self._widget.avatar_label.pixmap()
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Widgets
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def init_widget(self):
|
||||
if self._widget is not None:
|
||||
self._widget.name.setText(self._name)
|
||||
self._widget.status_message.setText(self._status_message)
|
||||
self._widget.connection_status.update(self._status)
|
||||
self.load_avatar()
|
287
toxygen/contacts/contact.py
Normal file
287
toxygen/contacts/contact.py
Normal file
|
@ -0,0 +1,287 @@
|
|||
from db.history import *
|
||||
from contacts import basecontact
|
||||
import util
|
||||
from messenger.messages import *
|
||||
import file_transfers as ft
|
||||
import re
|
||||
|
||||
|
||||
class Contact(basecontact.BaseContact):
|
||||
"""
|
||||
Class encapsulating TOX contact
|
||||
Properties: number, message getter, history etc. Base class for friend and gc classes
|
||||
"""
|
||||
|
||||
def __init__(self, message_getter, number, name, status_message, widget, tox_id):
|
||||
"""
|
||||
:param message_getter: gets messages from db
|
||||
:param number: number of friend.
|
||||
"""
|
||||
super().__init__(name, status_message, widget, tox_id)
|
||||
self._number = number
|
||||
self._new_messages = False
|
||||
self._visible = True
|
||||
self._alias = False
|
||||
self._message_getter = message_getter
|
||||
self._corr = []
|
||||
self._unsaved_messages = 0
|
||||
self._history_loaded = self._new_actions = False
|
||||
self._curr_text = self._search_string = ''
|
||||
self._search_index = 0
|
||||
|
||||
def __del__(self):
|
||||
self.set_visibility(False)
|
||||
del self._widget
|
||||
if hasattr(self, '_message_getter'):
|
||||
del self._message_getter
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# History support
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def load_corr(self, first_time=True):
|
||||
"""
|
||||
:param first_time: friend became active, load first part of messages
|
||||
"""
|
||||
if (first_time and self._history_loaded) or (not hasattr(self, '_message_getter')):
|
||||
return
|
||||
if self._message_getter is None:
|
||||
return
|
||||
data = list(self._message_getter.get(PAGE_SIZE))
|
||||
if data is not None and len(data):
|
||||
data.reverse()
|
||||
else:
|
||||
return
|
||||
data = list(map(lambda tupl: TextMessage(*tupl), data))
|
||||
self._corr = data + self._corr
|
||||
self._history_loaded = True
|
||||
|
||||
def load_all_corr(self):
|
||||
"""
|
||||
Get all chat history from db for current friend
|
||||
"""
|
||||
if self._message_getter is None:
|
||||
return
|
||||
data = list(self._message_getter.get_all())
|
||||
if data is not None and len(data):
|
||||
data.reverse()
|
||||
data = list(map(lambda tupl: TextMessage(*tupl), data))
|
||||
self._corr = data + self._corr
|
||||
self._history_loaded = True
|
||||
|
||||
def get_corr_for_saving(self):
|
||||
"""
|
||||
Get data to save in db
|
||||
:return: list of unsaved messages or []
|
||||
"""
|
||||
messages = list(filter(lambda x: x.get_type() <= 1, self._corr))
|
||||
return list(map(lambda x: x.get_data(), messages[-self._unsaved_messages:])) if self._unsaved_messages else []
|
||||
|
||||
def get_corr(self):
|
||||
return self._corr[:]
|
||||
|
||||
def append_message(self, message):
|
||||
"""
|
||||
:param message: text or file transfer message
|
||||
"""
|
||||
self._corr.append(message)
|
||||
if message.get_type() <= 1:
|
||||
self._unsaved_messages += 1
|
||||
|
||||
def get_last_message_text(self):
|
||||
messages = list(filter(lambda x: x.get_type() <= 1 and x.get_owner() != MESSAGE_OWNER['FRIEND'], self._corr))
|
||||
if messages:
|
||||
return messages[-1].get_data()[0]
|
||||
else:
|
||||
return ''
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Unsent messages
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_unsent_messages(self):
|
||||
"""
|
||||
:return list of unsent messages
|
||||
"""
|
||||
messages = filter(lambda x: x.get_owner() == MESSAGE_OWNER['NOT_SENT'], self._corr)
|
||||
return list(messages)
|
||||
|
||||
def get_unsent_messages_for_saving(self):
|
||||
"""
|
||||
:return list of unsent messages for saving
|
||||
"""
|
||||
messages = filter(lambda x: x.get_type() <= 1 and x.get_owner() == MESSAGE_OWNER['NOT_SENT'], self._corr)
|
||||
return list(map(lambda x: x.get_data(), messages))
|
||||
|
||||
def mark_as_sent(self):
|
||||
try:
|
||||
message = list(filter(lambda x: x.get_owner() == MESSAGE_OWNER['NOT_SENT'], self._corr))[0]
|
||||
message.mark_as_sent()
|
||||
except Exception as ex:
|
||||
util.log('Mark as sent ex: ' + str(ex))
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# 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]
|
||||
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
|
||||
self._corr.remove(elem)
|
||||
self._message_getter.delete_one()
|
||||
self._search_index = 0
|
||||
|
||||
def delete_old_messages(self):
|
||||
"""
|
||||
Delete old messages (reduces RAM usage if messages saving is not enabled)
|
||||
"""
|
||||
def save_message(x):
|
||||
if x.get_type() == 2 and (x.get_status() >= 2 or x.get_status() is None):
|
||||
return True
|
||||
return x.get_owner() == MESSAGE_OWNER['NOT_SENT']
|
||||
|
||||
old = filter(save_message, self._corr[:-SAVE_MESSAGES])
|
||||
self._corr = list(old) + self._corr[-SAVE_MESSAGES:]
|
||||
text_messages = filter(lambda x: x.get_type() <= 1, self._corr)
|
||||
self._unsaved_messages = min(self._unsaved_messages, len(list(text_messages)))
|
||||
self._search_index = 0
|
||||
|
||||
def clear_corr(self, save_unsent=False):
|
||||
"""
|
||||
Clear messages list
|
||||
"""
|
||||
if hasattr(self, '_message_getter'):
|
||||
del self._message_getter
|
||||
self._search_index = 0
|
||||
# don't delete data about active file transfer
|
||||
if not save_unsent:
|
||||
self._corr = list(filter(lambda x: x.get_type() == 2 and
|
||||
x.get_status() in ft.ACTIVE_FILE_TRANSFERS, self._corr))
|
||||
self._unsaved_messages = 0
|
||||
else:
|
||||
self._corr = list(filter(lambda x: (x.get_type() == 2 and x.get_status() in ft.ACTIVE_FILE_TRANSFERS)
|
||||
or (x.get_type() <= 1 and x.get_owner() == MESSAGE_OWNER['NOT_SENT']),
|
||||
self._corr))
|
||||
self._unsaved_messages = len(self.get_unsent_messages())
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Chat history search
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def search_string(self, search_string):
|
||||
self._search_string, self._search_index = search_string, 0
|
||||
return self.search_prev()
|
||||
|
||||
def search_prev(self):
|
||||
while True:
|
||||
l = len(self._corr)
|
||||
for i in range(self._search_index - 1, -l - 1, -1):
|
||||
if self._corr[i].get_type() > 1:
|
||||
continue
|
||||
message = self._corr[i].get_data()[0]
|
||||
if re.search(self._search_string, message, re.IGNORECASE) is not None:
|
||||
self._search_index = i
|
||||
return i
|
||||
self._search_index = -l
|
||||
self.load_corr(False)
|
||||
if len(self._corr) == l:
|
||||
return None # not found
|
||||
|
||||
def search_next(self):
|
||||
if not self._search_index:
|
||||
return None
|
||||
for i in range(self._search_index + 1, 0):
|
||||
if self._corr[i].get_type() > 1:
|
||||
continue
|
||||
message = self._corr[i].get_data()[0]
|
||||
if re.search(self._search_string, message, re.IGNORECASE) is not None:
|
||||
self._search_index = i
|
||||
return i
|
||||
return None # not found
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Current text - text from message area
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_curr_text(self):
|
||||
return self._curr_text
|
||||
|
||||
def set_curr_text(self, value):
|
||||
self._curr_text = value
|
||||
|
||||
curr_text = property(get_curr_text, set_curr_text)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Alias support
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def set_name(self, value):
|
||||
"""
|
||||
Set new name or ignore if alias exists
|
||||
:param value: new name
|
||||
"""
|
||||
if not self._alias:
|
||||
super().set_name(value)
|
||||
|
||||
def set_alias(self, alias):
|
||||
self._alias = bool(alias)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Visibility in friends' list
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_visibility(self):
|
||||
return self._visible
|
||||
|
||||
def set_visibility(self, value):
|
||||
self._visible = value
|
||||
|
||||
visibility = property(get_visibility, set_visibility)
|
||||
|
||||
def set_widget(self, widget):
|
||||
self._widget = widget
|
||||
self.init_widget()
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Unread messages and other actions from friend
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_actions(self):
|
||||
return self._new_actions
|
||||
|
||||
def set_actions(self, value):
|
||||
self._new_actions = value
|
||||
self._widget.connection_status.update(self.status, value)
|
||||
|
||||
actions = property(get_actions, set_actions) # unread messages, incoming files, av calls
|
||||
|
||||
def get_messages(self):
|
||||
return self._new_messages
|
||||
|
||||
def inc_messages(self):
|
||||
self._new_messages += 1
|
||||
self._new_actions = True
|
||||
self._widget.connection_status.update(self.status, True)
|
||||
self._widget.messages.update(self._new_messages)
|
||||
|
||||
def reset_messages(self):
|
||||
self._new_actions = False
|
||||
self._new_messages = 0
|
||||
self._widget.messages.update(self._new_messages)
|
||||
self._widget.connection_status.update(self.status, False)
|
||||
|
||||
messages = property(get_messages)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Friend's number (can be used in toxcore)
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_number(self):
|
||||
return self._number
|
||||
|
||||
def set_number(self, value):
|
||||
self._number = value
|
||||
|
||||
number = property(get_number, set_number)
|
75
toxygen/contacts/friend.py
Normal file
75
toxygen/contacts/friend.py
Normal file
|
@ -0,0 +1,75 @@
|
|||
from contacts import contact
|
||||
from messenger.messages import *
|
||||
import os
|
||||
|
||||
|
||||
class Friend(contact.Contact):
|
||||
"""
|
||||
Friend in list of friends.
|
||||
"""
|
||||
|
||||
def __init__(self, message_getter, number, name, status_message, widget, tox_id):
|
||||
super().__init__(message_getter, number, name, status_message, widget, tox_id)
|
||||
self._receipts = 0
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# File transfers support
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def update_transfer_data(self, file_number, status, inline=None):
|
||||
"""
|
||||
Update status of active transfer and load inline if needed
|
||||
"""
|
||||
try:
|
||||
tr = list(filter(lambda x: x.get_type() == MESSAGE_TYPE['FILE_TRANSFER'] and x.is_active(file_number),
|
||||
self._corr))[0]
|
||||
tr.set_status(status)
|
||||
i = self._corr.index(tr)
|
||||
if inline: # inline was loaded
|
||||
self._corr.insert(i, inline)
|
||||
return i - len(self._corr)
|
||||
except:
|
||||
pass
|
||||
|
||||
def get_unsent_files(self):
|
||||
messages = filter(lambda x: type(x) is UnsentFile, self._corr)
|
||||
return messages
|
||||
|
||||
def clear_unsent_files(self):
|
||||
self._corr = list(filter(lambda x: type(x) is not UnsentFile, self._corr))
|
||||
|
||||
def remove_invalid_unsent_files(self):
|
||||
def is_valid(message):
|
||||
if type(message) is not UnsentFile:
|
||||
return True
|
||||
if message.get_data()[1] is not None:
|
||||
return True
|
||||
return os.path.exists(message.get_data()[0])
|
||||
self._corr = list(filter(is_valid, self._corr))
|
||||
|
||||
def delete_one_unsent_file(self, time):
|
||||
self._corr = list(filter(lambda x: not (type(x) is UnsentFile and x.get_data()[2] == time), self._corr))
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# History support
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_receipts(self):
|
||||
return self._receipts
|
||||
|
||||
receipts = property(get_receipts) # read receipts
|
||||
|
||||
def inc_receipts(self):
|
||||
self._receipts += 1
|
||||
|
||||
def dec_receipt(self):
|
||||
if self._receipts:
|
||||
self._receipts -= 1
|
||||
self.mark_as_sent()
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Full status
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_full_status(self):
|
||||
return self._status_message
|
51
toxygen/contacts/group_chat.py
Normal file
51
toxygen/contacts/group_chat.py
Normal file
|
@ -0,0 +1,51 @@
|
|||
from contacts import contact
|
||||
import util
|
||||
from PyQt5 import QtGui, QtCore
|
||||
from wrapper import toxcore_enums_and_consts as constants
|
||||
|
||||
|
||||
# TODO: ngc
|
||||
|
||||
class GroupChat(contact.Contact):
|
||||
|
||||
def __init__(self, name, status_message, widget, tox, group_number):
|
||||
super().__init__(None, group_number, name, status_message, widget, None)
|
||||
self._tox = tox
|
||||
self.set_status(constants.TOX_USER_STATUS['NONE'])
|
||||
|
||||
def set_name(self, name):
|
||||
self._tox.group_set_title(self._number, name)
|
||||
super().set_name(name)
|
||||
|
||||
def send_message(self, message):
|
||||
self._tox.group_message_send(self._number, message.encode('utf-8'))
|
||||
|
||||
def new_title(self, title):
|
||||
super().set_name(title)
|
||||
|
||||
def load_avatar(self):
|
||||
path = util.curr_directory() + '/images/group.png'
|
||||
width = self._widget.avatar_label.width()
|
||||
pixmap = QtGui.QPixmap(path)
|
||||
self._widget.avatar_label.setPixmap(pixmap.scaled(width, width, QtCore.Qt.KeepAspectRatio,
|
||||
QtCore.Qt.SmoothTransformation))
|
||||
self._widget.avatar_label.repaint()
|
||||
|
||||
def remove_invalid_unsent_files(self):
|
||||
pass
|
||||
|
||||
def get_names(self):
|
||||
peers_count = self._tox.group_number_peers(self._number)
|
||||
names = []
|
||||
for i in range(peers_count):
|
||||
name = self._tox.group_peername(self._number, i)
|
||||
names.append(name)
|
||||
names = sorted(names, key=lambda n: n.lower())
|
||||
return names
|
||||
|
||||
def get_full_status(self):
|
||||
names = self.get_names()
|
||||
return '\n'.join(names)
|
||||
|
||||
def get_peer_name(self, peer_number):
|
||||
return self._tox.group_peername(self._number, peer_number)
|
1452
toxygen/contacts/profile.py
Normal file
1452
toxygen/contacts/profile.py
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue