Merge branch 'file_transfers'
Conflicts: src/mainscreen.py
This commit is contained in:
commit
4c1a02c9a3
15 changed files with 520 additions and 43 deletions
|
@ -4,6 +4,7 @@ from settings import Settings
|
||||||
from profile import Profile
|
from profile import Profile
|
||||||
from toxcore_enums_and_consts import *
|
from toxcore_enums_and_consts import *
|
||||||
from tox import bin_to_string
|
from tox import bin_to_string
|
||||||
|
from ctypes import c_char_p, cast, pointer
|
||||||
|
|
||||||
|
|
||||||
class InvokeEvent(QtCore.QEvent):
|
class InvokeEvent(QtCore.QEvent):
|
||||||
|
@ -28,6 +29,10 @@ _invoker = Invoker()
|
||||||
def invoke_in_main_thread(fn, *args, **kwargs):
|
def invoke_in_main_thread(fn, *args, **kwargs):
|
||||||
QtCore.QCoreApplication.postEvent(_invoker, InvokeEvent(fn, *args, **kwargs))
|
QtCore.QCoreApplication.postEvent(_invoker, InvokeEvent(fn, *args, **kwargs))
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Callbacks - current user
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
def self_connection_status(tox_link):
|
def self_connection_status(tox_link):
|
||||||
"""
|
"""
|
||||||
|
@ -45,6 +50,11 @@ def self_connection_status(tox_link):
|
||||||
return wrapped
|
return wrapped
|
||||||
|
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Callbacks - friends
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
def friend_status(tox, friend_num, new_status, user_data):
|
def friend_status(tox, friend_num, new_status, user_data):
|
||||||
"""
|
"""
|
||||||
Check friend's status (none, busy, away)
|
Check friend's status (none, busy, away)
|
||||||
|
@ -64,6 +74,8 @@ def friend_connection_status(tox, friend_num, new_status, user_data):
|
||||||
friend = profile.get_friend_by_number(friend_num)
|
friend = profile.get_friend_by_number(friend_num)
|
||||||
if new_status == TOX_CONNECTION['NONE']:
|
if new_status == TOX_CONNECTION['NONE']:
|
||||||
invoke_in_main_thread(friend.set_status, None)
|
invoke_in_main_thread(friend.set_status, None)
|
||||||
|
elif friend.status is None:
|
||||||
|
invoke_in_main_thread(profile.send_avatar, friend_num)
|
||||||
invoke_in_main_thread(profile.update_filtration)
|
invoke_in_main_thread(profile.update_filtration)
|
||||||
|
|
||||||
|
|
||||||
|
@ -116,6 +128,62 @@ def friend_request(tox, public_key, message, message_size, user_data):
|
||||||
tox_id = bin_to_string(public_key, TOX_PUBLIC_KEY_SIZE)
|
tox_id = bin_to_string(public_key, TOX_PUBLIC_KEY_SIZE)
|
||||||
invoke_in_main_thread(profile.process_friend_request, tox_id, message.decode('utf-8'))
|
invoke_in_main_thread(profile.process_friend_request, tox_id, message.decode('utf-8'))
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Callbacks - file transfers
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def tox_file_recv(window, tray):
|
||||||
|
def wrapped(tox, friend_number, file_number, file_type, size, file_name, file_name_size, user_data):
|
||||||
|
profile = Profile.get_instance()
|
||||||
|
settings = Settings.get_instance()
|
||||||
|
if file_type == TOX_FILE_KIND['DATA']:
|
||||||
|
print 'file'
|
||||||
|
file_name = file_name[:file_name_size]
|
||||||
|
invoke_in_main_thread(profile.incoming_file_transfer,
|
||||||
|
friend_number,
|
||||||
|
file_number,
|
||||||
|
size,
|
||||||
|
file_name)
|
||||||
|
if not window.isActiveWindow():
|
||||||
|
friend = profile.get_friend_by_number(friend_number)
|
||||||
|
if settings['notifications']:
|
||||||
|
invoke_in_main_thread(tray_notification, 'File from ' + friend.name, file_name, tray)
|
||||||
|
if settings['sound_notifications']:
|
||||||
|
sound_notification(SOUND_NOTIFICATION['FILE_TRANSFER'])
|
||||||
|
else: # AVATAR
|
||||||
|
print 'Avatar'
|
||||||
|
invoke_in_main_thread(profile.incoming_avatar,
|
||||||
|
friend_number,
|
||||||
|
file_number,
|
||||||
|
size)
|
||||||
|
return wrapped
|
||||||
|
|
||||||
|
|
||||||
|
def file_recv_chunk(tox, friend_number, file_number, position, chunk, length, user_data):
|
||||||
|
invoke_in_main_thread(Profile.get_instance().incoming_chunk,
|
||||||
|
friend_number,
|
||||||
|
file_number,
|
||||||
|
position,
|
||||||
|
chunk[:length] if length else None)
|
||||||
|
|
||||||
|
|
||||||
|
def file_chunk_request(tox, friend_number, file_number, position, size, user_data):
|
||||||
|
Profile.get_instance().outgoing_chunk(
|
||||||
|
friend_number,
|
||||||
|
file_number,
|
||||||
|
position,
|
||||||
|
size)
|
||||||
|
|
||||||
|
|
||||||
|
def file_recv_control(tox, friend_number, file_number, file_control, user_data):
|
||||||
|
# TODO: process
|
||||||
|
pass
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# Callbacks - initialization
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
def init_callbacks(tox, window, tray):
|
def init_callbacks(tox, window, tray):
|
||||||
"""
|
"""
|
||||||
|
@ -124,10 +192,16 @@ def init_callbacks(tox, window, tray):
|
||||||
:param window: main window
|
:param window: main window
|
||||||
:param tray: tray (for notifications)
|
:param tray: tray (for notifications)
|
||||||
"""
|
"""
|
||||||
|
tox.callback_self_connection_status(self_connection_status(tox), 0)
|
||||||
|
|
||||||
tox.callback_friend_status(friend_status, 0)
|
tox.callback_friend_status(friend_status, 0)
|
||||||
tox.callback_friend_message(friend_message(window, tray), 0)
|
tox.callback_friend_message(friend_message(window, tray), 0)
|
||||||
tox.callback_self_connection_status(self_connection_status(tox), 0)
|
|
||||||
tox.callback_friend_connection_status(friend_connection_status, 0)
|
tox.callback_friend_connection_status(friend_connection_status, 0)
|
||||||
tox.callback_friend_name(friend_name, 0)
|
tox.callback_friend_name(friend_name, 0)
|
||||||
tox.callback_friend_status_message(friend_status_message, 0)
|
tox.callback_friend_status_message(friend_status_message, 0)
|
||||||
tox.callback_friend_request(friend_request, 0)
|
tox.callback_friend_request(friend_request, 0)
|
||||||
|
|
||||||
|
tox.callback_file_recv(tox_file_recv(window, tray), 0)
|
||||||
|
tox.callback_file_recv_chunk(file_recv_chunk, 0)
|
||||||
|
tox.callback_file_chunk_request(file_chunk_request, 0)
|
||||||
|
tox.callback_file_recv_control(file_recv_control, 0)
|
||||||
|
|
154
src/file_transfers.py
Normal file
154
src/file_transfers.py
Normal file
|
@ -0,0 +1,154 @@
|
||||||
|
from toxcore_enums_and_consts import TOX_FILE_KIND, TOX_FILE_CONTROL
|
||||||
|
from os.path import basename, getsize, exists
|
||||||
|
from os import remove
|
||||||
|
from time import time
|
||||||
|
from tox import Tox
|
||||||
|
import profile
|
||||||
|
from PySide import QtCore
|
||||||
|
|
||||||
|
|
||||||
|
TOX_FILE_TRANSFER_STATE = {
|
||||||
|
'RUNNING': 0,
|
||||||
|
'PAUSED': 1,
|
||||||
|
'CANCELED': 2,
|
||||||
|
'FINISHED': 3,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class StateSignal(QtCore.QObject):
|
||||||
|
signal = QtCore.Signal(int, float)
|
||||||
|
|
||||||
|
|
||||||
|
class FileTransfer(QtCore.QObject):
|
||||||
|
|
||||||
|
def __init__(self, path, tox, friend_number, size, file_number=None):
|
||||||
|
QtCore.QObject.__init__(self)
|
||||||
|
self._path = path
|
||||||
|
self._tox = tox
|
||||||
|
self._friend_number = friend_number
|
||||||
|
self.state = TOX_FILE_TRANSFER_STATE['RUNNING']
|
||||||
|
self._file_number = file_number
|
||||||
|
self._creation_time = time()
|
||||||
|
self._size = float(size)
|
||||||
|
self._done = 0
|
||||||
|
self._state_changed = StateSignal()
|
||||||
|
|
||||||
|
def set_tox(self, tox):
|
||||||
|
self._tox = tox
|
||||||
|
|
||||||
|
def set_state_changed_handler(self, handler):
|
||||||
|
self._state_changed.signal.connect(handler)
|
||||||
|
|
||||||
|
def get_file_number(self):
|
||||||
|
return self._file_number
|
||||||
|
|
||||||
|
def get_friend_number(self):
|
||||||
|
return self._friend_number
|
||||||
|
|
||||||
|
def cancel(self):
|
||||||
|
self.send_control(TOX_FILE_CONTROL['CANCEL'])
|
||||||
|
self._file.close()
|
||||||
|
self._state_changed.signal.emit(self.state, self._done / self._size)
|
||||||
|
|
||||||
|
def send_control(self, control):
|
||||||
|
if self._tox.file_control(self._friend_number, self._file_number, control):
|
||||||
|
self.state = control
|
||||||
|
self._state_changed.signal.emit(self.state, self._done / self._size if self._size else 0)
|
||||||
|
|
||||||
|
def get_file_id(self):
|
||||||
|
return self._tox.file_get_file_id(self._friend_number, self._file_number)
|
||||||
|
|
||||||
|
def file_seek(self):
|
||||||
|
# TODO implement or not implement
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class SendTransfer(FileTransfer):
|
||||||
|
|
||||||
|
def __init__(self, path, tox, friend_number, kind=TOX_FILE_KIND['DATA'], file_id=None):
|
||||||
|
if path is not None:
|
||||||
|
self._file = open(path, 'rb')
|
||||||
|
size = getsize(path)
|
||||||
|
else:
|
||||||
|
size = 0
|
||||||
|
super(SendTransfer, self).__init__(path, tox, friend_number, size)
|
||||||
|
self._file_number = tox.file_send(friend_number, kind, size, file_id,
|
||||||
|
basename(path).encode('utf-8') if path else '')
|
||||||
|
|
||||||
|
def send_chunk(self, position, size):
|
||||||
|
if size:
|
||||||
|
self._file.seek(position)
|
||||||
|
data = self._file.read(size)
|
||||||
|
self._tox.file_send_chunk(self._friend_number, self._file_number, position, data)
|
||||||
|
self._done += size
|
||||||
|
self._state_changed.signal.emit(self.state, self._done / self._size)
|
||||||
|
else:
|
||||||
|
self._file.close()
|
||||||
|
self.state = TOX_FILE_TRANSFER_STATE['FINISHED']
|
||||||
|
self._state_changed.signal.emit(self.state, 1)
|
||||||
|
|
||||||
|
|
||||||
|
class SendAvatar(SendTransfer):
|
||||||
|
|
||||||
|
def __init__(self, path, tox, friend_number):
|
||||||
|
if path is None:
|
||||||
|
hash = None
|
||||||
|
else:
|
||||||
|
with open(path, 'rb') as fl:
|
||||||
|
hash = Tox.hash(fl.read())
|
||||||
|
super(SendAvatar, self).__init__(path, tox, friend_number, TOX_FILE_KIND['AVATAR'], hash)
|
||||||
|
|
||||||
|
|
||||||
|
class ReceiveTransfer(FileTransfer):
|
||||||
|
|
||||||
|
def __init__(self, path, tox, friend_number, size, file_number):
|
||||||
|
super(ReceiveTransfer, self).__init__(path, tox, friend_number, size, file_number)
|
||||||
|
self._file = open(self._path, 'wb')
|
||||||
|
self._file.truncate(0)
|
||||||
|
self._file_size = 0
|
||||||
|
|
||||||
|
def cancel(self):
|
||||||
|
super(ReceiveTransfer, self).cancel()
|
||||||
|
remove(self._path)
|
||||||
|
|
||||||
|
def write_chunk(self, position, data):
|
||||||
|
if data is None:
|
||||||
|
self._file.close()
|
||||||
|
self.state = TOX_FILE_TRANSFER_STATE['FINISHED']
|
||||||
|
self._state_changed.signal.emit(self.state, self._done / self._size if self._size else 0)
|
||||||
|
else:
|
||||||
|
data = ''.join(chr(x) for x in data)
|
||||||
|
if self._file_size < position:
|
||||||
|
self._file.seek(0, 2)
|
||||||
|
self._file.write('\0' * (position - self._file_size))
|
||||||
|
self._file.seek(position)
|
||||||
|
self._file.write(data)
|
||||||
|
self._file.flush()
|
||||||
|
l = len(data)
|
||||||
|
if position + l > self._file_size:
|
||||||
|
self._file_size = position + l
|
||||||
|
self._done += l
|
||||||
|
self._state_changed.signal.emit(self.state, self._done / self._size)
|
||||||
|
|
||||||
|
|
||||||
|
class ReceiveAvatar(ReceiveTransfer):
|
||||||
|
|
||||||
|
def __init__(self, tox, friend_number, size, file_number):
|
||||||
|
path = profile.ProfileHelper.get_path() + '/avatars/{}.png'.format(tox.friend_get_public_key(friend_number))
|
||||||
|
super(ReceiveAvatar, self).__init__(path, tox, friend_number, size, file_number)
|
||||||
|
if exists(path):
|
||||||
|
if not size:
|
||||||
|
remove(path)
|
||||||
|
self.send_control(TOX_FILE_CONTROL['CANCEL'])
|
||||||
|
self.state = TOX_FILE_TRANSFER_STATE['CANCELED']
|
||||||
|
else:
|
||||||
|
hash = self.get_file_id()
|
||||||
|
with open(path, 'rb') as fl:
|
||||||
|
existing_hash = Tox.hash(fl.read())
|
||||||
|
if hash == existing_hash:
|
||||||
|
self.send_control(TOX_FILE_CONTROL['CANCEL'])
|
||||||
|
self.state = TOX_FILE_TRANSFER_STATE['CANCELED']
|
||||||
|
else:
|
||||||
|
self.send_control(TOX_FILE_CONTROL['RESUME'])
|
||||||
|
else:
|
||||||
|
self.send_control(TOX_FILE_CONTROL['RESUME'])
|
BIN
src/images/accept.png
Executable file
BIN
src/images/accept.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 469 B |
BIN
src/images/decline.png
Executable file
BIN
src/images/decline.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
|
@ -1,5 +1,8 @@
|
||||||
from toxcore_enums_and_consts import *
|
from toxcore_enums_and_consts import *
|
||||||
from PySide import QtGui, QtCore
|
from PySide import QtGui, QtCore
|
||||||
|
import profile
|
||||||
|
from file_transfers import TOX_FILE_TRANSFER_STATE
|
||||||
|
from util import curr_directory
|
||||||
|
|
||||||
|
|
||||||
class MessageEdit(QtGui.QPlainTextEdit):
|
class MessageEdit(QtGui.QPlainTextEdit):
|
||||||
|
@ -154,3 +157,97 @@ class StatusCircle(QtGui.QWidget):
|
||||||
paint.setPen(color)
|
paint.setPen(color)
|
||||||
paint.drawEllipse(center, rad_x + 3, rad_y + 3)
|
paint.drawEllipse(center, rad_x + 3, rad_y + 3)
|
||||||
paint.end()
|
paint.end()
|
||||||
|
|
||||||
|
|
||||||
|
class FileTransferItem(QtGui.QListWidget):
|
||||||
|
def __init__(self, file_name, size, time, user, friend_number, file_number, show_accept, parent=None):
|
||||||
|
QtGui.QListWidget.__init__(self, parent)
|
||||||
|
self.resize(QtCore.QSize(600, 50))
|
||||||
|
self.setStyleSheet('QListWidget { background-color: green; }')
|
||||||
|
|
||||||
|
self.name = QtGui.QLabel(self)
|
||||||
|
self.name.setGeometry(QtCore.QRect(0, 15, 95, 20))
|
||||||
|
self.name.setTextFormat(QtCore.Qt.PlainText)
|
||||||
|
font = QtGui.QFont()
|
||||||
|
font.setFamily("Times New Roman")
|
||||||
|
font.setPointSize(11)
|
||||||
|
font.setBold(True)
|
||||||
|
self.name.setFont(font)
|
||||||
|
self.name.setObjectName("name")
|
||||||
|
self.name.setText(user if len(user) <= 14 else user[:11] + '...')
|
||||||
|
self.name.setStyleSheet('QLabel { color: black; }')
|
||||||
|
|
||||||
|
self.time = QtGui.QLabel(self)
|
||||||
|
self.time.setGeometry(QtCore.QRect(550, 0, 50, 50))
|
||||||
|
font.setPointSize(10)
|
||||||
|
font.setBold(False)
|
||||||
|
self.time.setFont(font)
|
||||||
|
self.time.setObjectName("time")
|
||||||
|
self.time.setText(time)
|
||||||
|
self.time.setStyleSheet('QLabel { color: black; }')
|
||||||
|
|
||||||
|
self.cancel = QtGui.QPushButton(self)
|
||||||
|
self.cancel.setGeometry(QtCore.QRect(500, 0, 50, 50))
|
||||||
|
pixmap = QtGui.QPixmap(curr_directory() + '/images/decline.png')
|
||||||
|
icon = QtGui.QIcon(pixmap)
|
||||||
|
self.cancel.setIcon(icon)
|
||||||
|
self.cancel.setIconSize(QtCore.QSize(50, 50))
|
||||||
|
self.cancel.clicked.connect(lambda: self.cancel_transfer(friend_number, file_number))
|
||||||
|
|
||||||
|
self.accept = QtGui.QPushButton(self)
|
||||||
|
self.accept.setGeometry(QtCore.QRect(450, 0, 50, 50))
|
||||||
|
pixmap = QtGui.QPixmap(curr_directory() + '/images/accept.png')
|
||||||
|
icon = QtGui.QIcon(pixmap)
|
||||||
|
self.accept.setIcon(icon)
|
||||||
|
self.accept.setIconSize(QtCore.QSize(50, 50))
|
||||||
|
self.accept.clicked.connect(lambda: self.accept_transfer(friend_number, file_number, size))
|
||||||
|
self.accept.setVisible(show_accept)
|
||||||
|
|
||||||
|
self.pb = QtGui.QProgressBar(self)
|
||||||
|
self.pb.setGeometry(QtCore.QRect(100, 15, 100, 20))
|
||||||
|
self.pb.setValue(0)
|
||||||
|
|
||||||
|
self.file_name = QtGui.QLabel(self)
|
||||||
|
self.file_name.setGeometry(QtCore.QRect(210, 0, 230, 50))
|
||||||
|
font.setPointSize(12)
|
||||||
|
self.file_name.setFont(font)
|
||||||
|
self.file_name.setObjectName("time")
|
||||||
|
file_size = size / 1024
|
||||||
|
if not file_size:
|
||||||
|
file_size = '<1KB'
|
||||||
|
elif file_size >= 1024:
|
||||||
|
file_size = '{}MB'.format(file_size / 1024)
|
||||||
|
else:
|
||||||
|
file_size = '{}KB'.format(file_size)
|
||||||
|
file_data = u'{} {}'.format(file_size, file_name)
|
||||||
|
self.file_name.setText(file_data if len(file_data) <= 27 else file_data[:24] + '...')
|
||||||
|
self.file_name.setStyleSheet('QLabel { color: black; }')
|
||||||
|
self.saved_name = file_name
|
||||||
|
|
||||||
|
def cancel_transfer(self, friend_number, file_number):
|
||||||
|
pr = profile.Profile.get_instance()
|
||||||
|
pr.cancel_transfer(friend_number, file_number)
|
||||||
|
self.setStyleSheet('QListWidget { background-color: red; }')
|
||||||
|
self.cancel.setVisible(False)
|
||||||
|
self.accept.setVisible(False)
|
||||||
|
self.pb.setVisible(False)
|
||||||
|
|
||||||
|
def accept_transfer(self, friend_number, file_number, size):
|
||||||
|
directory = QtGui.QFileDialog.getExistingDirectory()
|
||||||
|
if directory:
|
||||||
|
pr = profile.Profile.get_instance()
|
||||||
|
pr.accept_transfer(self, directory + '/' + self.saved_name, friend_number, file_number, size)
|
||||||
|
self.accept.setVisible(False)
|
||||||
|
|
||||||
|
@QtCore.Slot(int, float)
|
||||||
|
def update(self, state, progress):
|
||||||
|
self.pb.setValue(int(progress * 100))
|
||||||
|
if state == TOX_FILE_TRANSFER_STATE['CANCELED']:
|
||||||
|
self.setStyleSheet('QListWidget { background-color: red; }')
|
||||||
|
self.cancel.setVisible(False)
|
||||||
|
self.accept.setVisible(False)
|
||||||
|
self.pb.setVisible(False)
|
||||||
|
elif state == TOX_FILE_TRANSFER_STATE['FINISHED']:
|
||||||
|
self.pb.setVisible(False)
|
||||||
|
self.cancel.setVisible(False)
|
||||||
|
|
||||||
|
|
|
@ -102,6 +102,7 @@ class MainWindow(QtGui.QMainWindow):
|
||||||
self.sendMessageButton.setGeometry(QtCore.QRect(550, 10, 60, 110))
|
self.sendMessageButton.setGeometry(QtCore.QRect(550, 10, 60, 110))
|
||||||
self.sendMessageButton.setObjectName("sendMessageButton")
|
self.sendMessageButton.setObjectName("sendMessageButton")
|
||||||
self.sendMessageButton.clicked.connect(self.send_message)
|
self.sendMessageButton.clicked.connect(self.send_message)
|
||||||
|
|
||||||
pixmap = QtGui.QPixmap(curr_directory() + '/images/send.png')
|
pixmap = QtGui.QPixmap(curr_directory() + '/images/send.png')
|
||||||
icon = QtGui.QIcon(pixmap)
|
icon = QtGui.QIcon(pixmap)
|
||||||
self.sendMessageButton.setIcon(icon)
|
self.sendMessageButton.setIcon(icon)
|
||||||
|
@ -114,6 +115,9 @@ class MainWindow(QtGui.QMainWindow):
|
||||||
icon = QtGui.QIcon(pixmap)
|
icon = QtGui.QIcon(pixmap)
|
||||||
self.screenshotButton.setIcon(icon)
|
self.screenshotButton.setIcon(icon)
|
||||||
self.screenshotButton.setIconSize(QtCore.QSize(90, 40))
|
self.screenshotButton.setIconSize(QtCore.QSize(90, 40))
|
||||||
|
|
||||||
|
self.fileTransferButton.clicked.connect(self.send_file)
|
||||||
|
self.screenshotButton.clicked.connect(self.send_screenshot)
|
||||||
QtCore.QMetaObject.connectSlotsByName(Form)
|
QtCore.QMetaObject.connectSlotsByName(Form)
|
||||||
|
|
||||||
def setup_left_bottom(self, Form):
|
def setup_left_bottom(self, Form):
|
||||||
|
@ -277,51 +281,75 @@ class MainWindow(QtGui.QMainWindow):
|
||||||
self.int_s.show()
|
self.int_s.show()
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
# Messages
|
# Messages and file transfers
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
def send_message(self):
|
def send_message(self):
|
||||||
text = self.messageEdit.toPlainText()
|
text = self.messageEdit.toPlainText()
|
||||||
self.profile.send_message(text)
|
self.profile.send_message(text)
|
||||||
|
|
||||||
|
def send_file(self):
|
||||||
|
if self.profile.is_active_online(): # active friend exists and online
|
||||||
|
name = QtGui.QFileDialog.getOpenFileName(self, 'Choose file')
|
||||||
|
if name[0]:
|
||||||
|
self.profile.send_file(name[0])
|
||||||
|
|
||||||
|
def send_screenshot(self):
|
||||||
|
# TODO: add screenshots support
|
||||||
|
if self.profile.is_active_online(): # active friend exists and online
|
||||||
|
pass
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
# Functions which called when user open context menu in friends list
|
# Functions which called when user open context menu in friends list
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
def friend_right_click(self, pos):
|
def friend_right_click(self, pos):
|
||||||
item = self.friends_list.itemAt(pos)
|
item = self.friends_list.itemAt(pos)
|
||||||
|
num = self.friends_list.indexFromItem(item).row()
|
||||||
|
friend = Profile.get_instance().get_friend_by_number(num)
|
||||||
|
settings = Settings.get_instance()
|
||||||
|
allowed = friend.tox_id in settings['auto_accept_from_friends']
|
||||||
|
auto = 'Disallow auto accept' if allowed else 'Allow auto accept'
|
||||||
if item is not None:
|
if item is not None:
|
||||||
self.listMenu = QtGui.QMenu()
|
self.listMenu = QtGui.QMenu()
|
||||||
set_alias_item = self.listMenu.addAction('Set alias')
|
set_alias_item = self.listMenu.addAction('Set alias')
|
||||||
clear_history_item = self.listMenu.addAction('Clear history')
|
clear_history_item = self.listMenu.addAction('Clear history')
|
||||||
copy_key_item = self.listMenu.addAction('Copy public key')
|
copy_key_item = self.listMenu.addAction('Copy public key')
|
||||||
|
auto_accept_item = self.listMenu.addAction(auto)
|
||||||
remove_item = self.listMenu.addAction('Remove friend')
|
remove_item = self.listMenu.addAction('Remove friend')
|
||||||
self.connect(set_alias_item, QtCore.SIGNAL("triggered()"), lambda: self.set_alias(item))
|
self.connect(set_alias_item, QtCore.SIGNAL("triggered()"), lambda: self.set_alias(num))
|
||||||
self.connect(remove_item, QtCore.SIGNAL("triggered()"), lambda: self.remove_friend(item))
|
self.connect(remove_item, QtCore.SIGNAL("triggered()"), lambda: self.remove_friend(num))
|
||||||
self.connect(copy_key_item, QtCore.SIGNAL("triggered()"), lambda: self.copy_friend_key(item))
|
self.connect(copy_key_item, QtCore.SIGNAL("triggered()"), lambda: self.copy_friend_key(num))
|
||||||
self.connect(clear_history_item, QtCore.SIGNAL("triggered()"), lambda: self.clear_history(item))
|
self.connect(clear_history_item, QtCore.SIGNAL("triggered()"), lambda: self.clear_history(num))
|
||||||
|
self.connect(auto_accept_item, QtCore.SIGNAL("triggered()"), lambda: self.auto_accept(num, not allowed))
|
||||||
parent_position = self.friends_list.mapToGlobal(QtCore.QPoint(0, 0))
|
parent_position = self.friends_list.mapToGlobal(QtCore.QPoint(0, 0))
|
||||||
self.listMenu.move(parent_position + pos)
|
self.listMenu.move(parent_position + pos)
|
||||||
self.listMenu.show()
|
self.listMenu.show()
|
||||||
|
|
||||||
def set_alias(self, item):
|
def set_alias(self, num):
|
||||||
num = self.friends_list.indexFromItem(item).row()
|
|
||||||
self.profile.set_alias(num)
|
self.profile.set_alias(num)
|
||||||
|
|
||||||
def remove_friend(self, item):
|
def remove_friend(self, num):
|
||||||
num = self.friends_list.indexFromItem(item).row()
|
|
||||||
self.profile.delete_friend(num)
|
self.profile.delete_friend(num)
|
||||||
|
|
||||||
def copy_friend_key(self, item):
|
def copy_friend_key(self, num):
|
||||||
num = self.friends_list.indexFromItem(item).row()
|
|
||||||
tox_id = self.profile.friend_public_key(num)
|
tox_id = self.profile.friend_public_key(num)
|
||||||
clipboard = QtGui.QApplication.clipboard()
|
clipboard = QtGui.QApplication.clipboard()
|
||||||
clipboard.setText(tox_id)
|
clipboard.setText(tox_id)
|
||||||
|
|
||||||
def clear_history(self, item):
|
def clear_history(self, num):
|
||||||
num = self.friends_list.indexFromItem(item).row()
|
|
||||||
self.profile.clear_history(num)
|
self.profile.clear_history(num)
|
||||||
|
|
||||||
|
def auto_accept(self, num, value):
|
||||||
|
settings = Settings.get_instance()
|
||||||
|
tox_id = self.profile.friend_public_key(num)
|
||||||
|
if value:
|
||||||
|
settings['auto_accept_from_friends'].append(tox_id)
|
||||||
|
else:
|
||||||
|
index = settings['auto_accept_from_friends'].index(tox_id)
|
||||||
|
del settings['auto_accept_from_friends'][index]
|
||||||
|
settings.save()
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
# Functions which called when user click somewhere else
|
# Functions which called when user click somewhere else
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
25
src/menu.py
25
src/menu.py
|
@ -1,7 +1,7 @@
|
||||||
from PySide import QtCore, QtGui
|
from PySide import QtCore, QtGui
|
||||||
from settings import Settings
|
from settings import Settings
|
||||||
from profile import Profile, ProfileHelper
|
from profile import Profile, ProfileHelper
|
||||||
from util import get_style
|
from util import get_style, curr_directory
|
||||||
|
|
||||||
|
|
||||||
class CenteredWidget(QtGui.QWidget):
|
class CenteredWidget(QtGui.QWidget):
|
||||||
|
@ -165,7 +165,7 @@ class ProfileSettings(CenteredWidget):
|
||||||
Profile.get_instance().reset_avatar()
|
Profile.get_instance().reset_avatar()
|
||||||
|
|
||||||
def set_avatar(self):
|
def set_avatar(self):
|
||||||
name = QtGui.QFileDialog.getOpenFileName(self, 'Open file')
|
name = QtGui.QFileDialog.getOpenFileName(self, 'Open file', None, 'Image Files (*.png)')
|
||||||
print name
|
print name
|
||||||
if name[0]:
|
if name[0]:
|
||||||
with open(name[0], 'rb') as f:
|
with open(name[0], 'rb') as f:
|
||||||
|
@ -174,6 +174,7 @@ class ProfileSettings(CenteredWidget):
|
||||||
|
|
||||||
def export_profile(self):
|
def export_profile(self):
|
||||||
directory = QtGui.QFileDialog.getExistingDirectory() + '/'
|
directory = QtGui.QFileDialog.getExistingDirectory() + '/'
|
||||||
|
if directory != '/':
|
||||||
ProfileHelper.export_profile(directory)
|
ProfileHelper.export_profile(directory)
|
||||||
settings = Settings.get_instance()
|
settings = Settings.get_instance()
|
||||||
settings.export(directory)
|
settings.export(directory)
|
||||||
|
@ -275,15 +276,21 @@ class PrivacySettings(CenteredWidget):
|
||||||
self.fileautoaccept.setGeometry(QtCore.QRect(40, 60, 271, 22))
|
self.fileautoaccept.setGeometry(QtCore.QRect(40, 60, 271, 22))
|
||||||
self.fileautoaccept.setObjectName("fileautoaccept")
|
self.fileautoaccept.setObjectName("fileautoaccept")
|
||||||
self.typingNotifications = QtGui.QCheckBox(self)
|
self.typingNotifications = QtGui.QCheckBox(self)
|
||||||
self.typingNotifications.setGeometry(QtCore.QRect(40, 90, 350, 31))
|
self.typingNotifications.setGeometry(QtCore.QRect(40, 90, 350, 30))
|
||||||
self.typingNotifications.setBaseSize(QtCore.QSize(350, 200))
|
|
||||||
self.typingNotifications.setObjectName("typingNotifications")
|
self.typingNotifications.setObjectName("typingNotifications")
|
||||||
|
self.auto_path = QtGui.QLabel(self)
|
||||||
|
self.auto_path.setGeometry(QtCore.QRect(40, 120, 350, 30))
|
||||||
|
self.path = QtGui.QPlainTextEdit(self)
|
||||||
|
self.path.setGeometry(QtCore.QRect(10, 160, 330, 30))
|
||||||
|
self.change_path = QtGui.QPushButton(self)
|
||||||
|
self.change_path.setGeometry(QtCore.QRect(230, 120, 100, 30))
|
||||||
self.retranslateUi()
|
self.retranslateUi()
|
||||||
settings = Settings.get_instance()
|
settings = Settings.get_instance()
|
||||||
self.typingNotifications.setChecked(settings['typing_notifications'])
|
self.typingNotifications.setChecked(settings['typing_notifications'])
|
||||||
self.fileautoaccept.setChecked(settings['allow_auto_accept'])
|
self.fileautoaccept.setChecked(settings['allow_auto_accept'])
|
||||||
self.saveHistory.setChecked(settings['save_history'])
|
self.saveHistory.setChecked(settings['save_history'])
|
||||||
|
self.path.setPlainText(settings['auto_accept_path'] or curr_directory())
|
||||||
|
self.change_path.clicked.connect(self.new_path)
|
||||||
QtCore.QMetaObject.connectSlotsByName(self)
|
QtCore.QMetaObject.connectSlotsByName(self)
|
||||||
|
|
||||||
def retranslateUi(self):
|
def retranslateUi(self):
|
||||||
|
@ -291,6 +298,8 @@ class PrivacySettings(CenteredWidget):
|
||||||
self.saveHistory.setText(QtGui.QApplication.translate("privacySettings", "Save chat history", None, QtGui.QApplication.UnicodeUTF8))
|
self.saveHistory.setText(QtGui.QApplication.translate("privacySettings", "Save chat history", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
self.fileautoaccept.setText(QtGui.QApplication.translate("privacySettings", "Allow file auto accept", None, QtGui.QApplication.UnicodeUTF8))
|
self.fileautoaccept.setText(QtGui.QApplication.translate("privacySettings", "Allow file auto accept", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
self.typingNotifications.setText(QtGui.QApplication.translate("privacySettings", "Send typing notifications", None, QtGui.QApplication.UnicodeUTF8))
|
self.typingNotifications.setText(QtGui.QApplication.translate("privacySettings", "Send typing notifications", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
|
self.auto_path.setText(QtGui.QApplication.translate("privacySettings", "Auto accept default path:", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
|
self.change_path.setText(QtGui.QApplication.translate("privacySettings", "Change", None, QtGui.QApplication.UnicodeUTF8))
|
||||||
|
|
||||||
def closeEvent(self, event):
|
def closeEvent(self, event):
|
||||||
settings = Settings.get_instance()
|
settings = Settings.get_instance()
|
||||||
|
@ -299,8 +308,14 @@ class PrivacySettings(CenteredWidget):
|
||||||
if settings['save_history'] and not self.saveHistory.isChecked(): # clear history
|
if settings['save_history'] and not self.saveHistory.isChecked(): # clear history
|
||||||
Profile.get_instance().clear_history()
|
Profile.get_instance().clear_history()
|
||||||
settings['save_history'] = self.saveHistory.isChecked()
|
settings['save_history'] = self.saveHistory.isChecked()
|
||||||
|
settings['auto_accept_path'] = self.path.toPlainText()
|
||||||
settings.save()
|
settings.save()
|
||||||
|
|
||||||
|
def new_path(self):
|
||||||
|
directory = QtGui.QFileDialog.getExistingDirectory() + '/'
|
||||||
|
if directory != '/':
|
||||||
|
self.path.setPlainText(directory)
|
||||||
|
|
||||||
|
|
||||||
class NotificationsSettings(CenteredWidget):
|
class NotificationsSettings(CenteredWidget):
|
||||||
"""Notifications settings form"""
|
"""Notifications settings form"""
|
||||||
|
|
|
@ -2,6 +2,7 @@ from PySide import QtGui
|
||||||
from PySide.phonon import Phonon
|
from PySide.phonon import Phonon
|
||||||
from util import curr_directory
|
from util import curr_directory
|
||||||
# TODO: make app icon active
|
# TODO: make app icon active
|
||||||
|
# TODO: add all sound notifications
|
||||||
|
|
||||||
|
|
||||||
SOUND_NOTIFICATION = {
|
SOUND_NOTIFICATION = {
|
||||||
|
@ -19,9 +20,10 @@ def tray_notification(title, text, tray):
|
||||||
|
|
||||||
|
|
||||||
def sound_notification(t):
|
def sound_notification(t):
|
||||||
# TODO: add other sound notifications
|
|
||||||
if t == SOUND_NOTIFICATION['MESSAGE']:
|
if t == SOUND_NOTIFICATION['MESSAGE']:
|
||||||
f = curr_directory() + '/sounds/message.wav'
|
f = curr_directory() + '/sounds/message.wav'
|
||||||
|
elif t == SOUND_NOTIFICATION['FILE_TRANSFER']:
|
||||||
|
f = curr_directory() + '/sounds/file.wav'
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
m = Phonon.MediaSource(f)
|
m = Phonon.MediaSource(f)
|
||||||
|
|
117
src/profile.py
117
src/profile.py
|
@ -1,4 +1,4 @@
|
||||||
from list_items import MessageItem, ContactItem
|
from list_items import MessageItem, ContactItem, FileTransferItem
|
||||||
from PySide import QtCore, QtGui
|
from PySide import QtCore, QtGui
|
||||||
from tox import Tox
|
from tox import Tox
|
||||||
import os
|
import os
|
||||||
|
@ -8,6 +8,7 @@ from ctypes import *
|
||||||
from util import curr_time, log, Singleton, curr_directory, convert_time
|
from util import curr_time, log, Singleton, curr_directory, convert_time
|
||||||
from tox_dns import tox_dns
|
from tox_dns import tox_dns
|
||||||
from history import *
|
from history import *
|
||||||
|
from file_transfers import *
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
|
||||||
|
@ -56,7 +57,7 @@ class ProfileHelper(object):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def export_profile(new_path):
|
def export_profile(new_path):
|
||||||
new_path += ProfileHelper._path.split('/')[-1]
|
new_path += os.path.basename(ProfileHelper._path)
|
||||||
with open(ProfileHelper._path, 'rb') as fin:
|
with open(ProfileHelper._path, 'rb') as fin:
|
||||||
data = fin.read()
|
data = fin.read()
|
||||||
with open(new_path, 'wb') as fout:
|
with open(new_path, 'wb') as fout:
|
||||||
|
@ -161,7 +162,7 @@ class Contact(object):
|
||||||
"""
|
"""
|
||||||
Tries to load avatar of contact or uses default avatar
|
Tries to load avatar of contact or uses default avatar
|
||||||
"""
|
"""
|
||||||
avatar_path = (Settings.get_default_path() + 'avatars/{}.png').format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2])
|
avatar_path = (ProfileHelper.get_path() + 'avatars/{}.png').format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2])
|
||||||
if not os.path.isfile(avatar_path): # load default image
|
if not os.path.isfile(avatar_path): # load default image
|
||||||
avatar_path = curr_directory() + '/images/avatar.png'
|
avatar_path = curr_directory() + '/images/avatar.png'
|
||||||
pixmap = QtGui.QPixmap(QtCore.QSize(64, 64))
|
pixmap = QtGui.QPixmap(QtCore.QSize(64, 64))
|
||||||
|
@ -170,17 +171,25 @@ class Contact(object):
|
||||||
self._widget.avatar_label.repaint()
|
self._widget.avatar_label.repaint()
|
||||||
|
|
||||||
def reset_avatar(self):
|
def reset_avatar(self):
|
||||||
avatar_path = (Settings.get_default_path() + 'avatars/{}.png').format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2])
|
avatar_path = (ProfileHelper.get_path() + 'avatars/{}.png').format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2])
|
||||||
if os.path.isfile(avatar_path):
|
if os.path.isfile(avatar_path):
|
||||||
os.remove(avatar_path)
|
os.remove(avatar_path)
|
||||||
self.load_avatar()
|
self.load_avatar()
|
||||||
|
|
||||||
def set_avatar(self, avatar):
|
def set_avatar(self, avatar):
|
||||||
avatar_path = (Settings.get_default_path() + 'avatars/{}.png').format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2])
|
avatar_path = (ProfileHelper.get_path() + 'avatars/{}.png').format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2])
|
||||||
with open(avatar_path, 'wb') as f:
|
with open(avatar_path, 'wb') as f:
|
||||||
f.write(avatar)
|
f.write(avatar)
|
||||||
self.load_avatar()
|
self.load_avatar()
|
||||||
|
|
||||||
|
# def get_avatar_hash(self):
|
||||||
|
# avatar_path = (ProfileHelper.get_path() + 'avatars/{}.png').format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2])
|
||||||
|
# if not os.path.isfile(avatar_path): # load default image
|
||||||
|
# return 0
|
||||||
|
# with open(avatar_path, 'rb') as fl:
|
||||||
|
# data = fl.read()
|
||||||
|
# return Tox.hash(data)
|
||||||
|
|
||||||
|
|
||||||
class Friend(Contact):
|
class Friend(Contact):
|
||||||
"""
|
"""
|
||||||
|
@ -318,6 +327,7 @@ class Profile(Contact, Singleton):
|
||||||
self._screen = screen
|
self._screen = screen
|
||||||
self._messages = screen.messages
|
self._messages = screen.messages
|
||||||
self._tox = tox
|
self._tox = tox
|
||||||
|
self._file_transfers = {} # dict of file transfers. key - tuple (friend_number, file_number)
|
||||||
settings = Settings.get_instance()
|
settings = Settings.get_instance()
|
||||||
self._show_online = settings['show_online_friends']
|
self._show_online = settings['show_online_friends']
|
||||||
screen.online_contacts.setChecked(self._show_online)
|
screen.online_contacts.setChecked(self._show_online)
|
||||||
|
@ -497,7 +507,7 @@ class Profile(Contact, Singleton):
|
||||||
self._messages.scrollToBottom()
|
self._messages.scrollToBottom()
|
||||||
self._friends[self._active_friend].append_message((message.decode('utf-8'),
|
self._friends[self._active_friend].append_message((message.decode('utf-8'),
|
||||||
MESSAGE_OWNER['FRIEND'],
|
MESSAGE_OWNER['FRIEND'],
|
||||||
time.time(),
|
int(time.time()),
|
||||||
message_type))
|
message_type))
|
||||||
else:
|
else:
|
||||||
friend = filter(lambda x: x.number == friend_num, self._friends)[0]
|
friend = filter(lambda x: x.number == friend_num, self._friends)[0]
|
||||||
|
@ -537,6 +547,7 @@ class Profile(Contact, Singleton):
|
||||||
Save history to db
|
Save history to db
|
||||||
"""
|
"""
|
||||||
print 'In save'
|
print 'In save'
|
||||||
|
if hasattr(self, '_history'):
|
||||||
if Settings.get_instance()['save_history']:
|
if Settings.get_instance()['save_history']:
|
||||||
for friend in self._friends:
|
for friend in self._friends:
|
||||||
messages = friend.get_corr_for_saving()
|
messages = friend.get_corr_for_saving()
|
||||||
|
@ -560,7 +571,7 @@ class Profile(Contact, Singleton):
|
||||||
self._history.export(directory)
|
self._history.export(directory)
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
# Factories for friend and message items
|
# Factories for friend, message and file transfer items
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
def create_friend_item(self):
|
def create_friend_item(self):
|
||||||
|
@ -578,11 +589,21 @@ class Profile(Contact, Singleton):
|
||||||
def create_message_item(self, text, time, name, message_type):
|
def create_message_item(self, text, time, name, message_type):
|
||||||
item = MessageItem(text, time, name, message_type, self._messages)
|
item = MessageItem(text, time, name, message_type, self._messages)
|
||||||
elem = QtGui.QListWidgetItem(self._messages)
|
elem = QtGui.QListWidgetItem(self._messages)
|
||||||
elem.setSizeHint(QtCore.QSize(500, item.getHeight()))
|
elem.setSizeHint(QtCore.QSize(600, item.getHeight()))
|
||||||
self._messages.addItem(elem)
|
self._messages.addItem(elem)
|
||||||
self._messages.setItemWidget(elem, item)
|
self._messages.setItemWidget(elem, item)
|
||||||
self._messages.repaint()
|
self._messages.repaint()
|
||||||
|
|
||||||
|
def create_file_transfer_item(self, file_name, size, friend_number, file_number, show_accept):
|
||||||
|
friend = self.get_friend_by_number(friend_number)
|
||||||
|
item = FileTransferItem(file_name, size, curr_time(), friend.name, friend_number, file_number, show_accept)
|
||||||
|
elem = QtGui.QListWidgetItem(self._messages)
|
||||||
|
elem.setSizeHint(QtCore.QSize(600, 50))
|
||||||
|
self._messages.addItem(elem)
|
||||||
|
self._messages.setItemWidget(elem, item)
|
||||||
|
self._messages.repaint()
|
||||||
|
return item
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
# Work with friends (remove, set alias, get public key)
|
# Work with friends (remove, set alias, get public key)
|
||||||
# -----------------------------------------------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
@ -701,6 +722,86 @@ class Profile(Contact, Singleton):
|
||||||
self.status = None
|
self.status = None
|
||||||
for friend in self._friends:
|
for friend in self._friends:
|
||||||
friend.status = None
|
friend.status = None
|
||||||
|
# TODO: FT reset
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
# File transfers support
|
||||||
|
# -----------------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def incoming_file_transfer(self, friend_number, file_number, size, file_name):
|
||||||
|
settings = Settings.get_instance()
|
||||||
|
friend = self.get_friend_by_number(friend_number)
|
||||||
|
if settings['allow_auto_accept'] and friend.tox_id in settings['auto_accept_from_friends']:
|
||||||
|
path = settings['auto_accept_path'] or curr_directory()
|
||||||
|
self.accept_transfer(path + '/' + file_name.decode('utf-8'), friend_number, file_number)
|
||||||
|
self.create_file_transfer_item(file_name.decode('utf-8'), size, friend_number, file_number, False)
|
||||||
|
else:
|
||||||
|
self.create_file_transfer_item(file_name.decode('utf-8'), size, friend_number, file_number, True)
|
||||||
|
|
||||||
|
def cancel_transfer(self, friend_number, file_number):
|
||||||
|
if (friend_number, file_number) in self._file_transfers:
|
||||||
|
tr = self._file_transfers[(friend_number, file_number)]
|
||||||
|
tr.cancel()
|
||||||
|
del self._file_transfers[(friend_number, file_number)]
|
||||||
|
|
||||||
|
def accept_transfer(self, item, path, friend_number, file_number, size):
|
||||||
|
rt = ReceiveTransfer(path, self._tox, friend_number, size, file_number)
|
||||||
|
self._file_transfers[(friend_number, file_number)] = rt
|
||||||
|
self._tox.file_control(friend_number, file_number, TOX_FILE_CONTROL['RESUME'])
|
||||||
|
rt.set_state_changed_handler(item.update)
|
||||||
|
|
||||||
|
def incoming_avatar(self, friend_number, file_number, size):
|
||||||
|
"""
|
||||||
|
Friend changed avatar
|
||||||
|
:param friend_number: friend number
|
||||||
|
:param file_number: file number
|
||||||
|
:param size: size of avatar or 0 (default avatar)
|
||||||
|
"""
|
||||||
|
ra = ReceiveAvatar(self._tox, friend_number, size, file_number)
|
||||||
|
if ra.state != TOX_FILE_TRANSFER_STATE['CANCELED']:
|
||||||
|
self._file_transfers[(friend_number, file_number)] = ra
|
||||||
|
else:
|
||||||
|
self.get_friend_by_number(friend_number).load_avatar()
|
||||||
|
|
||||||
|
def incoming_chunk(self, friend_number, file_number, position, data):
|
||||||
|
if (friend_number, file_number) in self._file_transfers:
|
||||||
|
transfer = self._file_transfers[(friend_number, file_number)]
|
||||||
|
transfer.write_chunk(position, data)
|
||||||
|
if transfer.state:
|
||||||
|
if type(transfer) is ReceiveAvatar:
|
||||||
|
self.get_friend_by_number(friend_number).load_avatar()
|
||||||
|
del self._file_transfers[(friend_number, file_number)]
|
||||||
|
|
||||||
|
def send_avatar(self, friend_number):
|
||||||
|
avatar_path = (ProfileHelper.get_path() + 'avatars/{}.png').format(self._tox_id[:TOX_PUBLIC_KEY_SIZE * 2])
|
||||||
|
if not os.path.isfile(avatar_path): # reset image
|
||||||
|
avatar_path = None
|
||||||
|
sa = SendAvatar(avatar_path, self._tox, friend_number)
|
||||||
|
self._file_transfers[(friend_number, sa.get_file_number())] = sa
|
||||||
|
|
||||||
|
def send_file(self, path):
|
||||||
|
friend_number = self.get_active_number()
|
||||||
|
st = SendTransfer(path, self._tox, friend_number)
|
||||||
|
self._file_transfers[(friend_number, st.get_file_number())] = st
|
||||||
|
item = self.create_file_transfer_item(os.path.basename(path), os.path.getsize(path), friend_number, st.get_file_number(), False)
|
||||||
|
st.set_state_changed_handler(item.update)
|
||||||
|
|
||||||
|
def outgoing_chunk(self, friend_number, file_number, position, size):
|
||||||
|
if (friend_number, file_number) in self._file_transfers:
|
||||||
|
transfer = self._file_transfers[(friend_number, file_number)]
|
||||||
|
transfer.send_chunk(position, size)
|
||||||
|
if transfer.state:
|
||||||
|
del self._file_transfers[(friend_number, file_number)]
|
||||||
|
|
||||||
|
def reset_avatar(self):
|
||||||
|
super(Profile, self).reset_avatar()
|
||||||
|
for friend in filter(lambda x: x.status is not None, self._friends):
|
||||||
|
self.send_avatar(friend.number)
|
||||||
|
|
||||||
|
def set_avatar(self, data):
|
||||||
|
super(Profile, self).set_avatar(data)
|
||||||
|
for friend in filter(lambda x: x.status is not None, self._friends):
|
||||||
|
self.send_avatar(friend.number)
|
||||||
|
|
||||||
|
|
||||||
def tox_factory(data=None, settings=None):
|
def tox_factory(data=None, settings=None):
|
||||||
|
|
|
@ -53,6 +53,7 @@ class Settings(Singleton, dict):
|
||||||
'save_history': False,
|
'save_history': False,
|
||||||
'allow_inline': True,
|
'allow_inline': True,
|
||||||
'allow_auto_accept': False,
|
'allow_auto_accept': False,
|
||||||
|
'auto_accept_path': None,
|
||||||
'show_online_friends': False,
|
'show_online_friends': False,
|
||||||
'auto_accept_from_friends': [],
|
'auto_accept_from_friends': [],
|
||||||
'friends_aliases': [],
|
'friends_aliases': [],
|
||||||
|
|
BIN
src/sounds/file.wav
Normal file
BIN
src/sounds/file.wav
Normal file
Binary file not shown.
BIN
src/sounds/message.wav
Normal file
BIN
src/sounds/message.wav
Normal file
Binary file not shown.
|
@ -1240,3 +1240,8 @@ MessageEdit:hover
|
||||||
{
|
{
|
||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QListWidget QPushButton
|
||||||
|
{
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from ctypes import c_char_p, Structure, CDLL, c_bool, addressof, c_int, c_size_t, POINTER, c_uint16, c_void_p, c_uint64
|
from ctypes import c_char_p, Structure, CDLL, c_bool, addressof, c_int, c_size_t, POINTER, c_uint16, c_void_p, c_uint64
|
||||||
from ctypes import create_string_buffer, ArgumentError, CFUNCTYPE, c_uint32, sizeof
|
from ctypes import create_string_buffer, ArgumentError, CFUNCTYPE, c_uint32, sizeof, c_uint8
|
||||||
from platform import system
|
from platform import system
|
||||||
from toxcore_enums_and_consts import *
|
from toxcore_enums_and_consts import *
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ class LibToxCore(object):
|
||||||
|
|
||||||
|
|
||||||
def string_to_bin(tox_id):
|
def string_to_bin(tox_id):
|
||||||
return c_char_p(tox_id.decode('hex'))
|
return c_char_p(tox_id.decode('hex')) if tox_id is not None else None
|
||||||
|
|
||||||
|
|
||||||
def bin_to_string(raw_id, length):
|
def bin_to_string(raw_id, length):
|
||||||
|
@ -1351,7 +1351,7 @@ class Tox(object):
|
||||||
pointer (c_void_p) to user_data
|
pointer (c_void_p) to user_data
|
||||||
:param user_data: pointer (c_void_p) to user data
|
:param user_data: pointer (c_void_p) to user data
|
||||||
"""
|
"""
|
||||||
c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_uint64, c_char_p, c_size_t, c_void_p)
|
c_callback = CFUNCTYPE(None, c_void_p, c_uint32, c_uint32, c_uint64, POINTER(c_uint8), c_size_t, c_void_p)
|
||||||
self.file_recv_chunk_cb = c_callback(callback)
|
self.file_recv_chunk_cb = c_callback(callback)
|
||||||
self.libtoxcore.tox_callback_file_recv_chunk(self._tox_pointer, self.file_recv_chunk_cb, user_data)
|
self.libtoxcore.tox_callback_file_recv_chunk(self._tox_pointer, self.file_recv_chunk_cb, user_data)
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ import time
|
||||||
from platform import system
|
from platform import system
|
||||||
|
|
||||||
|
|
||||||
program_version = '0.0.1 (alpha)'
|
program_version = '0.0.2 (alpha)'
|
||||||
|
|
||||||
|
|
||||||
def log(data):
|
def log(data):
|
||||||
|
|
Loading…
Reference in a new issue