initial commit - rewriting. ngc wrapper added, project structure updated

This commit is contained in:
ingvar1995 2018-01-26 23:21:46 +03:00
parent bb2a857ecf
commit 2de4eea357
57 changed files with 2420 additions and 957 deletions

View file

View 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
View 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)

View 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

View 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

File diff suppressed because it is too large Load diff