update big NGC
BIN
docs/ubuntu.png
Executable file → Normal file
Before Width: | Height: | Size: 109 KiB After Width: | Height: | Size: 107 KiB |
BIN
docs/windows.png
Executable file → Normal file
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 71 KiB |
11
setup.py
|
@ -12,9 +12,9 @@ version = main.__version__ + '.0'
|
|||
|
||||
|
||||
if system() == 'Windows':
|
||||
MODULES = ['PyQt5', 'PyAudio', 'numpy', 'opencv-python', 'pydenticon']
|
||||
MODULES = ['PyQt5', 'PyAudio', 'numpy', 'opencv-python', 'pydenticon', 'cv2']
|
||||
else:
|
||||
MODULES = []
|
||||
MODULES = ['pydenticon']
|
||||
try:
|
||||
import pyaudio
|
||||
except ImportError:
|
||||
|
@ -32,9 +32,9 @@ else:
|
|||
except ImportError:
|
||||
MODULES.append('opencv-python')
|
||||
try:
|
||||
import pydenticon
|
||||
import coloredlogs
|
||||
except ImportError:
|
||||
MODULES.append('pydenticon')
|
||||
MODULES.append('coloredlogs')
|
||||
|
||||
|
||||
def get_packages():
|
||||
|
@ -84,6 +84,9 @@ setup(name='Toxygen',
|
|||
'Programming Language :: Python :: 3 :: Only',
|
||||
'Programming Language :: Python :: 3.5',
|
||||
'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
],
|
||||
entry_points={
|
||||
'console_scripts': ['toxygen=toxygen.main:main']
|
||||
|
|
744
toxygen/app.py
|
@ -1,14 +1,26 @@
|
|||
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||
import pyaudio
|
||||
import time
|
||||
import threading
|
||||
from wrapper.toxav_enums import *
|
||||
import cv2
|
||||
import itertools
|
||||
import numpy as np
|
||||
|
||||
from wrapper.toxav_enums import *
|
||||
from av import screen_sharing
|
||||
from av.call import Call
|
||||
import common.tox_save
|
||||
|
||||
from utils import ui as util_ui
|
||||
import tests.support_testing as ts
|
||||
from middleware.threads import invoke_in_main_thread
|
||||
from main import sleep
|
||||
from middleware.threads import BaseThread
|
||||
|
||||
global LOG
|
||||
import logging
|
||||
LOG = logging.getLogger('app.'+__name__)
|
||||
|
||||
TIMER_TIMEOUT = 30.0
|
||||
bSTREAM_CALLBACK = False
|
||||
|
||||
class AV(common.tox_save.ToxAvSave):
|
||||
|
||||
|
@ -16,6 +28,13 @@ class AV(common.tox_save.ToxAvSave):
|
|||
super().__init__(toxav)
|
||||
self._settings = settings
|
||||
self._running = True
|
||||
s = settings
|
||||
if 'video' not in s:
|
||||
LOG.warn("AV.__init__ 'video' not in s" )
|
||||
LOG.debug(f"AV.__init__ {s!r}" )
|
||||
elif 'device' not in s['video']:
|
||||
LOG.warn("AV.__init__ 'device' not in s.video" )
|
||||
LOG.debug(f"AV.__init__ {s['video']!r}" )
|
||||
|
||||
self._calls = {} # dict: key - friend number, value - Call instance
|
||||
|
||||
|
@ -25,17 +44,27 @@ class AV(common.tox_save.ToxAvSave):
|
|||
self._audio_running = False
|
||||
self._out_stream = None
|
||||
|
||||
self._audio_rate = 8000
|
||||
self._audio_channels = 1
|
||||
self._audio_duration = 60
|
||||
self._audio_sample_count = self._audio_rate * self._audio_channels * self._audio_duration // 1000
|
||||
self._audio_rate_pa = 48000
|
||||
self._audio_rate_tox = 48000
|
||||
self._audio_rate_pa = 48000
|
||||
self._audio_krate_tox_audio = self._audio_rate_tox // 1000
|
||||
self._audio_krate_tox_video = 5000
|
||||
self._audio_sample_count_pa = self._audio_rate_pa * self._audio_channels * self._audio_duration // 1000
|
||||
self._audio_sample_count_tox = self._audio_rate_tox * self._audio_channels * self._audio_duration // 1000
|
||||
|
||||
self._video = None
|
||||
self._video_thread = None
|
||||
self._video_running = False
|
||||
|
||||
self._video_width = 640
|
||||
self._video_height = 480
|
||||
self._video_width = 320
|
||||
self._video_height = 240
|
||||
|
||||
iOutput = self._settings._args.audio['output']
|
||||
self.lPaSampleratesO = ts.lSdSamplerates(iOutput)
|
||||
global oPYA
|
||||
oPYA = self._audio = pyaudio.PyAudio()
|
||||
|
||||
def stop(self):
|
||||
self._running = False
|
||||
|
@ -51,28 +80,71 @@ class AV(common.tox_save.ToxAvSave):
|
|||
|
||||
def __call__(self, friend_number, audio, video):
|
||||
"""Call friend with specified number"""
|
||||
self._toxav.call(friend_number, 32 if audio else 0, 5000 if video else 0)
|
||||
if friend_number in self._calls:
|
||||
LOG.warn(f"__call__ already has {friend_number}")
|
||||
return
|
||||
if self._audio_krate_tox_audio not in ts.lToxSampleratesK:
|
||||
LOG.warn(f"__call__ {self._audio_krate_tox_audio} not in {ts.lToxSampleratesK}")
|
||||
|
||||
try:
|
||||
self._toxav.call(friend_number,
|
||||
self._audio_krate_tox_audio if audio else 0,
|
||||
self._audio_krate_tox_video if video else 0)
|
||||
except ArgumentError as e:
|
||||
LOG.warn(f"_toxav.call already has {friend_number}")
|
||||
return
|
||||
self._calls[friend_number] = Call(audio, video)
|
||||
threading.Timer(30.0, lambda: self.finish_not_started_call(friend_number)).start()
|
||||
threading.Timer(TIMER_TIMEOUT,
|
||||
lambda: self.finish_not_started_call(friend_number)).start()
|
||||
|
||||
def accept_call(self, friend_number, audio_enabled, video_enabled):
|
||||
# obsolete
|
||||
return call_accept_call(self, friend_number, audio_enabled, video_enabled)
|
||||
|
||||
def call_accept_call(self, friend_number, audio_enabled, video_enabled):
|
||||
LOG.debug(f"call_accept_call from {friend_number} {self._running}" +
|
||||
f"{audio_enabled} {video_enabled}")
|
||||
# import pdb; pdb.set_trace() - gets into q Qt exec_ problem
|
||||
# ts.trepan_handler()
|
||||
|
||||
if self._audio_krate_tox_audio not in ts.lToxSampleratesK:
|
||||
LOG.warn(f"__call__ {self._audio_krate_tox_audio} not in {ts.lToxSampleratesK}")
|
||||
if self._running:
|
||||
self._calls[friend_number] = Call(audio_enabled, video_enabled)
|
||||
self._toxav.answer(friend_number, 32 if audio_enabled else 0, 5000 if video_enabled else 0)
|
||||
# audio_bit_rate: Audio bit rate in Kb/sec. Set this to 0 to disable audio sending.
|
||||
# video_bit_rate: Video bit rate in Kb/sec. Set this to 0 to disable video sending.
|
||||
try:
|
||||
self._toxav.answer(friend_number,
|
||||
self._audio_krate_tox_audio if audio_enabled else 0,
|
||||
self._audio_krate_tox_video if video_enabled else 0)
|
||||
except ArgumentError as e:
|
||||
LOG.debug(f"AV accept_call error from {friend_number} {self._running}" +
|
||||
f"{e}")
|
||||
raise
|
||||
if audio_enabled:
|
||||
# may raise
|
||||
self.start_audio_thread()
|
||||
if video_enabled:
|
||||
# may raise
|
||||
self.start_video_thread()
|
||||
|
||||
def finish_call(self, friend_number, by_friend=False):
|
||||
LOG.debug(f"finish_call {friend_number}")
|
||||
if not by_friend:
|
||||
self._toxav.call_control(friend_number, TOXAV_CALL_CONTROL['CANCEL'])
|
||||
if friend_number in self._calls:
|
||||
del self._calls[friend_number]
|
||||
try:
|
||||
# AttributeError: 'int' object has no attribute 'out_audio'
|
||||
if not len(list(filter(lambda c: c.out_audio, self._calls))):
|
||||
self.stop_audio_thread()
|
||||
if not len(list(filter(lambda c: c.out_video, self._calls))):
|
||||
self.stop_video_thread()
|
||||
except Exception as e:
|
||||
LOG.error(f"finish_call FixMe: {e}")
|
||||
# dunno
|
||||
self.stop_audio_thread()
|
||||
self.stop_video_thread()
|
||||
|
||||
def finish_not_started_call(self, friend_number):
|
||||
if friend_number in self:
|
||||
|
@ -84,6 +156,7 @@ class AV(common.tox_save.ToxAvSave):
|
|||
"""
|
||||
New call state
|
||||
"""
|
||||
LOG.debug(f"toxav_call_state_cb {friend_number}")
|
||||
call = self._calls[friend_number]
|
||||
call.is_active = True
|
||||
|
||||
|
@ -107,31 +180,80 @@ class AV(common.tox_save.ToxAvSave):
|
|||
"""
|
||||
Start audio sending
|
||||
"""
|
||||
global oPYA
|
||||
iInput = self._settings._args.audio['input']
|
||||
if self._audio_thread is not None:
|
||||
LOG.warn(f"start_audio_thread device={iInput}")
|
||||
return
|
||||
iInput = self._settings._args.audio['input']
|
||||
LOG.debug(f"start_audio_thread device={iInput}")
|
||||
lPaSamplerates = ts.lSdSamplerates(iInput)
|
||||
if not(len(lPaSamplerates)):
|
||||
e = f"No supported sample rates for device: audio[input]={iInput!r}"
|
||||
LOG.error(f"No supported sample rates {e}")
|
||||
raise RuntimeError(e)
|
||||
if not self._audio_rate_pa in lPaSamplerates:
|
||||
LOG.warn(f"{self._audio_rate_pa} not in {lPaSamplerates!r}")
|
||||
if False:
|
||||
self._audio_rate_pa = oPYA.get_device_info_by_index(iInput)['defaultSampleRate']
|
||||
else:
|
||||
LOG.warn(f"Setting audio_rate to: {lPaSamplerates[0]}")
|
||||
self._audio_rate_pa = lPaSamplerates[0]
|
||||
|
||||
self._audio_running = True
|
||||
try:
|
||||
LOG.debug( f"start_audio_thread framerate: {self._audio_rate_pa}" \
|
||||
+f" device: {iInput}"
|
||||
+f" supported: {lPaSamplerates!r}")
|
||||
if self._audio_rate_pa not in lPaSamplerates:
|
||||
LOG.warn(f"PAudio sampling rate was {self._audio_rate_pa} changed to {lPaSamplerates[0]}")
|
||||
self._audio_rate_pa = lPaSamplerates[0]
|
||||
|
||||
self._audio = pyaudio.PyAudio()
|
||||
self._audio_stream = self._audio.open(format=pyaudio.paInt16,
|
||||
rate=self._audio_rate,
|
||||
if bSTREAM_CALLBACK:
|
||||
self._audio_stream = oPYA.open(format=pyaudio.paInt16,
|
||||
rate=self._audio_rate_pa,
|
||||
channels=self._audio_channels,
|
||||
input=True,
|
||||
input_device_index=self._settings.audio['input'],
|
||||
frames_per_buffer=self._audio_sample_count * 10)
|
||||
input_device_index=iInput,
|
||||
frames_per_buffer=self._audio_sample_count_pa * 10,
|
||||
stream_callback=self.send_audio_data)
|
||||
self._audio_running = True
|
||||
self._audio_stream.start_stream()
|
||||
while self._audio_stream.is_active():
|
||||
sleep(0.1)
|
||||
self._audio_stream.stop_stream()
|
||||
self._audio_stream.close()
|
||||
|
||||
self._audio_thread = threading.Thread(target=self.send_audio)
|
||||
else:
|
||||
self._audio_stream = oPYA.open(format=pyaudio.paInt16,
|
||||
rate=self._audio_rate_pa,
|
||||
channels=self._audio_channels,
|
||||
input=True,
|
||||
input_device_index=iInput,
|
||||
frames_per_buffer=self._audio_sample_count_pa * 10)
|
||||
self._audio_running = True
|
||||
self._audio_thread = BaseThread(target=self.send_audio,
|
||||
name='_audio_thread')
|
||||
self._audio_thread.start()
|
||||
|
||||
except Exception as e:
|
||||
LOG.error(f"Starting self._audio.open {e}")
|
||||
LOG.debug(repr(dict(format=pyaudio.paInt16,
|
||||
rate=self._audio_rate_pa,
|
||||
channels=self._audio_channels,
|
||||
input=True,
|
||||
input_device_index=iInput,
|
||||
frames_per_buffer=self._audio_sample_count_pa * 10)))
|
||||
# catcher in place in calls_manager
|
||||
raise RuntimeError(e)
|
||||
else:
|
||||
LOG.debug(f"start_audio_thread {self._audio_stream!r}")
|
||||
|
||||
def stop_audio_thread(self):
|
||||
|
||||
if self._audio_thread is None:
|
||||
return
|
||||
|
||||
self._audio_running = False
|
||||
|
||||
self._audio_thread.join()
|
||||
|
||||
self._audio_thread = None
|
||||
self._audio_stream = None
|
||||
self._audio = None
|
||||
|
@ -144,21 +266,39 @@ class AV(common.tox_save.ToxAvSave):
|
|||
def start_video_thread(self):
|
||||
if self._video_thread is not None:
|
||||
return
|
||||
s = self._settings
|
||||
if 'video' not in s:
|
||||
LOG.warn("AV.__init__ 'video' not in s" )
|
||||
LOG.debug(f"start_video_thread {s!r}" )
|
||||
raise RuntimeError("start_video_thread not 'video' in s)" )
|
||||
elif 'device' not in s['video']:
|
||||
LOG.error("start_video_thread not 'device' in s['video']" )
|
||||
LOG.debug(f"start_video_thread {s['video']!r}" )
|
||||
raise RuntimeError("start_video_thread not 'device' ins s['video']" )
|
||||
self._video_width = s['video']['width']
|
||||
self._video_height = s['video']['height']
|
||||
|
||||
self._video_running = True
|
||||
self._video_width = s.video['width']
|
||||
self._video_height = s.video['height']
|
||||
LOG.info("start_video_thread " \
|
||||
+f" device: {s['video']['device']}" \
|
||||
+f" supported: {s['video']['width']} {s['video']['height']}")
|
||||
|
||||
if s.video['device'] == -1:
|
||||
self._video = screen_sharing.DesktopGrabber(self._settings.video['x'], self._settings.video['y'],
|
||||
self._settings.video['width'], self._settings.video['height'])
|
||||
s['video']['device'] = -1
|
||||
if s['video']['device'] == -1:
|
||||
self._video = screen_sharing.DesktopGrabber(s['video']['x'],
|
||||
s['video']['y'],
|
||||
s['video']['width'],
|
||||
s['video']['height'])
|
||||
else:
|
||||
self._video = cv2.VideoCapture(self._settings.video['device'])
|
||||
with ts.ignoreStdout():
|
||||
import cv2
|
||||
self._video = cv2.VideoCapture(s['video']['device'])
|
||||
self._video.set(cv2.CAP_PROP_FPS, 25)
|
||||
self._video.set(cv2.CAP_PROP_FRAME_WIDTH, self._video_width)
|
||||
self._video.set(cv2.CAP_PROP_FRAME_HEIGHT, self._video_height)
|
||||
|
||||
self._video_thread = threading.Thread(target=self.send_video)
|
||||
self._video_running = True
|
||||
self._video_thread = BaseThread(target=self.send_video,
|
||||
name='_video_thread')
|
||||
self._video_thread.start()
|
||||
|
||||
def stop_video_thread(self):
|
||||
|
@ -166,7 +306,17 @@ class AV(common.tox_save.ToxAvSave):
|
|||
return
|
||||
|
||||
self._video_running = False
|
||||
self._video_thread.join()
|
||||
i = 0
|
||||
while i < ts.iTHREAD_JOINS:
|
||||
self._video_thread.join(ts.iTHREAD_TIMEOUT)
|
||||
try:
|
||||
if not self._video_thread.is_alive(): break
|
||||
except:
|
||||
# AttributeError: 'NoneType' object has no attribute 'join'
|
||||
break
|
||||
i = i + 1
|
||||
else:
|
||||
LOG.warn("self._video_thread.is_alive BLOCKED")
|
||||
self._video_thread = None
|
||||
self._video = None
|
||||
|
||||
|
@ -180,58 +330,109 @@ class AV(common.tox_save.ToxAvSave):
|
|||
"""
|
||||
|
||||
if self._out_stream is None:
|
||||
self._out_stream = self._audio.open(format=pyaudio.paInt16,
|
||||
iOutput = self._settings._args.audio['output']
|
||||
if not rate in self.lPaSampleratesO:
|
||||
LOG.warn(f"{rate} not in {self.lPaSampleratesO!r}")
|
||||
if False:
|
||||
rate = oPYA.get_device_info_by_index(iOutput)['defaultSampleRate']
|
||||
LOG.warn(f"Setting audio_rate to: {self.lPaSampleratesO[0]}")
|
||||
rate = self.lPaSampleratesO[0]
|
||||
try:
|
||||
with ts.ignoreStderr():
|
||||
self._out_stream = oPYA.open(format=pyaudio.paInt16,
|
||||
channels=channels_count,
|
||||
rate=rate,
|
||||
output_device_index=self._settings.audio['output'],
|
||||
output_device_index=iOutput,
|
||||
output=True)
|
||||
except Exception as e:
|
||||
LOG.error(f"Error playing audio_chunk creating self._out_stream {e}")
|
||||
LOG.debug(f"audio_chunk output_device_index={self._settings._args.audio['input']} rate={rate} channels={channels_count}")
|
||||
invoke_in_main_thread(util_ui.message_box,
|
||||
str(e),
|
||||
util_ui.tr("Error Chunking audio"))
|
||||
# dunno
|
||||
self.stop()
|
||||
return
|
||||
|
||||
self._out_stream.write(samples)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# AV sending
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def send_audio_data(self, data, count, *largs, **kwargs):
|
||||
pcm = data
|
||||
# :param sampling_rate: Audio sampling rate used in this frame.
|
||||
if self._toxav is None:
|
||||
raise RuntimeError("_toxav not initialized")
|
||||
if self._audio_rate_tox not in ts.lToxSamplerates:
|
||||
LOG.warn(f"ToxAudio sampling rate was {self._audio_rate_tox} changed to {ts.lToxSamplerates[0]}")
|
||||
self._audio_rate_tox = ts.lToxSamplerates[0]
|
||||
|
||||
for friend_num in self._calls:
|
||||
if self._calls[friend_num].out_audio:
|
||||
try:
|
||||
# app.av.calls ERROR Error send_audio: One of the frame parameters was invalid. E.g. the resolution may be too small or too large, or the audio sampling rate may be unsupported
|
||||
self._toxav.audio_send_frame(friend_num,
|
||||
pcm,
|
||||
count,
|
||||
self._audio_channels,
|
||||
self._audio_rate_tox)
|
||||
except Exception as e:
|
||||
LOG.error(f"Error send_audio audio_send_frame: {e}")
|
||||
LOG.debug(f"send_audio self._audio_rate_tox={self._audio_rate_tox} self._audio_channels={self._audio_channels}")
|
||||
invoke_in_main_thread(util_ui.message_box,
|
||||
str(e),
|
||||
util_ui.tr("Error send_audio audio_send_frame"))
|
||||
pass
|
||||
|
||||
def send_audio(self):
|
||||
"""
|
||||
This method sends audio to friends
|
||||
"""
|
||||
|
||||
i=0
|
||||
count = self._audio_sample_count_tox
|
||||
LOG.debug(f"send_audio stream={self._audio_stream}")
|
||||
while self._audio_running:
|
||||
try:
|
||||
pcm = self._audio_stream.read(self._audio_sample_count)
|
||||
if pcm:
|
||||
for friend_num in self._calls:
|
||||
if self._calls[friend_num].out_audio:
|
||||
try:
|
||||
self._toxav.audio_send_frame(friend_num, pcm, self._audio_sample_count,
|
||||
self._audio_channels, self._audio_rate)
|
||||
pcm = self._audio_stream.read(count, exception_on_overflow=False)
|
||||
if not pcm:
|
||||
sleep(0.1)
|
||||
else:
|
||||
self.send_audio_data(pcm, count)
|
||||
except:
|
||||
pass
|
||||
except:
|
||||
pass
|
||||
|
||||
time.sleep(0.01)
|
||||
i += 1
|
||||
LOG.debug(f"send_audio {i}")
|
||||
sleep(0.01)
|
||||
|
||||
def send_video(self):
|
||||
"""
|
||||
This method sends video to friends
|
||||
"""
|
||||
LOG.debug(f"send_video thread={threading.current_thread()}"
|
||||
+f" self._video_running={self._video_running}"
|
||||
+f" device: {self._settings['video']['device']}" )
|
||||
while self._video_running:
|
||||
try:
|
||||
result, frame = self._video.read()
|
||||
if result:
|
||||
LOG.warn(f"send_video video_send_frame _video.read")
|
||||
else:
|
||||
height, width, channels = frame.shape
|
||||
for friend_num in self._calls:
|
||||
if self._calls[friend_num].out_video:
|
||||
try:
|
||||
y, u, v = self.convert_bgr_to_yuv(frame)
|
||||
self._toxav.video_send_frame(friend_num, width, height, y, u, v)
|
||||
except:
|
||||
except Exception as e:
|
||||
LOG.debug(f"send_video video_send_frame ERROR {e}")
|
||||
pass
|
||||
|
||||
except:
|
||||
pass
|
||||
|
||||
time.sleep(0.01)
|
||||
sleep(0.1)
|
||||
|
||||
def convert_bgr_to_yuv(self, frame):
|
||||
"""
|
||||
|
@ -264,11 +465,14 @@ class AV(common.tox_save.ToxAvSave):
|
|||
Y, U, V can be extracted using slices and joined in one list using itertools.chain.from_iterable()
|
||||
Function returns bytes(y), bytes(u), bytes(v), because it is required for ctypes
|
||||
"""
|
||||
with ts.ignoreStdout():
|
||||
import cv2
|
||||
frame = cv2.cvtColor(frame, cv2.COLOR_BGR2YUV_I420)
|
||||
|
||||
y = frame[:self._video_height, :]
|
||||
y = list(itertools.chain.from_iterable(y))
|
||||
|
||||
import numpy as np
|
||||
u = np.zeros((self._video_height // 2, self._video_width // 2), dtype=np.int)
|
||||
u[::2, :] = frame[self._video_height:self._video_height * 5 // 4, :self._video_width // 2]
|
||||
u[1::2, :] = frame[self._video_height:self._video_height * 5 // 4, self._video_width // 2:]
|
||||
|
|
|
@ -1,22 +1,30 @@
|
|||
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||
|
||||
import sys
|
||||
import threading
|
||||
import cv2
|
||||
|
||||
import av.calls
|
||||
from messenger.messages import *
|
||||
from ui import av_widgets
|
||||
import common.event as event
|
||||
import utils.ui as util_ui
|
||||
|
||||
global LOG
|
||||
import logging
|
||||
LOG = logging.getLogger('app.'+__name__)
|
||||
|
||||
class CallsManager:
|
||||
|
||||
def __init__(self, toxav, settings, screen, contacts_manager):
|
||||
def __init__(self, toxav, settings, main_screen, contacts_manager, app=None):
|
||||
self._call = av.calls.AV(toxav, settings) # object with data about calls
|
||||
self._call_widgets = {} # dict of incoming call widgets
|
||||
self._incoming_calls = set()
|
||||
self._settings = settings
|
||||
self._screen = screen
|
||||
self._main_screen = main_screen
|
||||
self._contacts_manager = contacts_manager
|
||||
self._call_started_event = event.Event() # friend_number, audio, video, is_outgoing
|
||||
self._call_finished_event = event.Event() # friend_number, is_declined
|
||||
self._app = app
|
||||
|
||||
def set_toxav(self, toxav):
|
||||
self._call.set_toxav(toxav)
|
||||
|
@ -45,10 +53,10 @@ class CallsManager:
|
|||
if not self._contacts_manager.is_active_a_friend():
|
||||
return
|
||||
if num not in self._call and self._contacts_manager.is_active_online(): # start call
|
||||
if not self._settings.audio['enabled']:
|
||||
if not self._settings['audio']['enabled']:
|
||||
return
|
||||
self._call(num, audio, video)
|
||||
self._screen.active_call()
|
||||
self._main_screen.active_call()
|
||||
self._call_started_event(num, audio, video, True)
|
||||
elif num in self._call: # finish or cancel call if you call with active friend
|
||||
self.stop_call(num, False)
|
||||
|
@ -57,13 +65,13 @@ class CallsManager:
|
|||
"""
|
||||
Incoming call from friend.
|
||||
"""
|
||||
if not self._settings.audio['enabled']:
|
||||
return
|
||||
LOG.debug(__name__ +f" incoming_call {friend_number}")
|
||||
# if not self._settings['audio']['enabled']: return
|
||||
friend = self._contacts_manager.get_friend_by_number(friend_number)
|
||||
self._call_started_event(friend_number, audio, video, False)
|
||||
self._incoming_calls.add(friend_number)
|
||||
if friend_number == self._contacts_manager.get_active_number():
|
||||
self._screen.incoming_call()
|
||||
self._main_screen.incoming_call()
|
||||
else:
|
||||
friend.actions = True
|
||||
text = util_ui.tr("Incoming video call") if video else util_ui.tr("Incoming audio call")
|
||||
|
@ -74,31 +82,73 @@ class CallsManager:
|
|||
def accept_call(self, friend_number, audio, video):
|
||||
"""
|
||||
Accept incoming call with audio or video
|
||||
Called from a thread
|
||||
"""
|
||||
self._call.accept_call(friend_number, audio, video)
|
||||
self._screen.active_call()
|
||||
|
||||
LOG.debug(f"CM accept_call from {friend_number} {audio} {video}")
|
||||
sys.stdout.flush()
|
||||
|
||||
try:
|
||||
self._call.call_accept_call(friend_number, audio, video)
|
||||
except Exception as e:
|
||||
LOG.error(f"accept_call _call.accept_call ERROR for {friend_number} {e}")
|
||||
self._main_screen.call_finished()
|
||||
if hasattr(self._main_screen, '_settings') and \
|
||||
'audio' in self._main_screen._settings and \
|
||||
'input' in self._main_screen._settings['audio']:
|
||||
iInput = self._settings['audio']['input']
|
||||
iOutput = self._settings['audio']['output']
|
||||
iVideo = self._settings['video']['device']
|
||||
LOG.debug(f"iInput={iInput} iOutput={iOutput} iVideo={iVideo}")
|
||||
elif hasattr(self._main_screen, '_settings') and \
|
||||
hasattr(self._main_screen._settings, 'audio') and \
|
||||
'input' not in self._main_screen._settings['audio']:
|
||||
LOG.warn(f"'audio' not in {self._main_screen._settings!r}")
|
||||
elif hasattr(self._main_screen, '_settings') and \
|
||||
hasattr(self._main_screen._settings, 'audio') and \
|
||||
'input' not in self._main_screen._settings['audio']:
|
||||
LOG.warn(f"'audio' not in {self._main_screen._settings!r}")
|
||||
else:
|
||||
LOG.warn(f"_settings not in self._main_screen")
|
||||
util_ui.message_box(str(e),
|
||||
util_ui.tr('ERROR Accepting call from {friend_number}'))
|
||||
else:
|
||||
self._main_screen.active_call()
|
||||
|
||||
finally:
|
||||
# does not terminate call - just the av_widget
|
||||
if friend_number in self._incoming_calls:
|
||||
self._incoming_calls.remove(friend_number)
|
||||
try:
|
||||
self._call_widgets[friend_number].close()
|
||||
del self._call_widgets[friend_number]
|
||||
except:
|
||||
# RuntimeError: wrapped C/C++ object of type IncomingCallWidget has been deleted
|
||||
|
||||
pass
|
||||
LOG.debug(f" closed self._call_widgets[{friend_number}]")
|
||||
|
||||
def stop_call(self, friend_number, by_friend):
|
||||
"""
|
||||
Stop call with friend
|
||||
"""
|
||||
LOG.debug(__name__+f" stop_call {friend_number}")
|
||||
if friend_number in self._incoming_calls:
|
||||
self._incoming_calls.remove(friend_number)
|
||||
is_declined = True
|
||||
else:
|
||||
is_declined = False
|
||||
self._screen.call_finished()
|
||||
is_video = self._call.is_video_call(friend_number)
|
||||
self._main_screen.call_finished()
|
||||
self._call.finish_call(friend_number, by_friend) # finish or decline call
|
||||
if friend_number in self._call_widgets:
|
||||
self._call_widgets[friend_number].close()
|
||||
del self._call_widgets[friend_number]
|
||||
|
||||
def destroy_window():
|
||||
#??? FixMed
|
||||
is_video = self._call.is_video_call(friend_number)
|
||||
if is_video:
|
||||
import cv2
|
||||
cv2.destroyWindow(str(friend_number))
|
||||
|
||||
threading.Timer(2.0, destroy_window).start()
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import numpy as np
|
||||
from PyQt5 import QtWidgets
|
||||
|
||||
|
||||
|
@ -17,6 +16,7 @@ class DesktopGrabber:
|
|||
pixmap = self._screen.grabWindow(0, self._x, self._y, self._width, self._height)
|
||||
image = pixmap.toImage()
|
||||
s = image.bits().asstring(self._width * self._height * 4)
|
||||
import numpy as np
|
||||
arr = np.fromstring(s, dtype=np.uint8).reshape((self._height, self._width, 4))
|
||||
|
||||
return True, arr
|
||||
|
|
|
@ -1,83 +1,49 @@
|
|||
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||
import random
|
||||
import urllib.request
|
||||
from utils.util import *
|
||||
from PyQt5 import QtNetwork, QtCore
|
||||
import json
|
||||
from PyQt5 import QtNetwork
|
||||
from PyQt5 import QtCore
|
||||
try:
|
||||
import requests
|
||||
except ImportError:
|
||||
requests = None
|
||||
try:
|
||||
import pycurl
|
||||
import certifi
|
||||
from io import BytesIO
|
||||
except ImportError:
|
||||
pycurl = None
|
||||
|
||||
from user_data.settings import get_user_config_path
|
||||
from tests.support_testing import download_url, _get_nodes_path
|
||||
|
||||
DEFAULT_NODES_COUNT = 4
|
||||
global LOG
|
||||
import logging
|
||||
LOG = logging.getLogger('app.'+'bootstrap')
|
||||
|
||||
|
||||
class Node:
|
||||
|
||||
def __init__(self, node):
|
||||
self._ip, self._port, self._tox_key = node['ipv4'], node['port'], node['public_key']
|
||||
self._priority = random.randint(1, 1000000) if node['status_tcp'] and node['status_udp'] else 0
|
||||
|
||||
def get_priority(self):
|
||||
return self._priority
|
||||
|
||||
priority = property(get_priority)
|
||||
|
||||
def get_data(self):
|
||||
return self._ip, self._port, self._tox_key
|
||||
|
||||
|
||||
def generate_nodes(nodes_count=DEFAULT_NODES_COUNT):
|
||||
with open(_get_nodes_path(), 'rt') as fl:
|
||||
json_nodes = json.loads(fl.read())['nodes']
|
||||
nodes = map(lambda json_node: Node(json_node), json_nodes)
|
||||
nodes = filter(lambda n: n.priority > 0, nodes)
|
||||
sorted_nodes = sorted(nodes, key=lambda x: x.priority)
|
||||
if nodes_count is not None:
|
||||
sorted_nodes = sorted_nodes[-DEFAULT_NODES_COUNT:]
|
||||
for node in sorted_nodes:
|
||||
yield node.get_data()
|
||||
|
||||
|
||||
def download_nodes_list(settings):
|
||||
url = 'https://nodes.tox.chat/json'
|
||||
def download_nodes_list(settings, oArgs):
|
||||
if not settings['download_nodes_list']:
|
||||
return
|
||||
return ''
|
||||
url = settings['download_nodes_url']
|
||||
path = _get_nodes_path(oArgs=oArgs)
|
||||
# dont download blindly so we can edit the file and not block on startup
|
||||
if os.path.isfile(path):
|
||||
with open(path, 'rt') as fl:
|
||||
result = fl.read()
|
||||
return result
|
||||
LOG.debug("downloading list of nodes")
|
||||
result = download_url(url, settings._app)
|
||||
if not result:
|
||||
LOG.warn("failed downloading list of nodes")
|
||||
return ''
|
||||
LOG.info("downloaded list of nodes")
|
||||
_save_nodes(result, settings._app)
|
||||
return result
|
||||
|
||||
if not settings['proxy_type']: # no proxy
|
||||
try:
|
||||
req = urllib.request.Request(url)
|
||||
req.add_header('Content-Type', 'application/json')
|
||||
response = urllib.request.urlopen(req)
|
||||
result = response.read()
|
||||
_save_nodes(result)
|
||||
except Exception as ex:
|
||||
log('TOX nodes loading error: ' + str(ex))
|
||||
else: # proxy
|
||||
netman = QtNetwork.QNetworkAccessManager()
|
||||
proxy = QtNetwork.QNetworkProxy()
|
||||
proxy.setType(
|
||||
QtNetwork.QNetworkProxy.Socks5Proxy if settings['proxy_type'] == 2 else QtNetwork.QNetworkProxy.HttpProxy)
|
||||
proxy.setHostName(settings['proxy_host'])
|
||||
proxy.setPort(settings['proxy_port'])
|
||||
netman.setProxy(proxy)
|
||||
try:
|
||||
request = QtNetwork.QNetworkRequest()
|
||||
request.setUrl(QtCore.QUrl(url))
|
||||
reply = netman.get(request)
|
||||
|
||||
while not reply.isFinished():
|
||||
QtCore.QThread.msleep(1)
|
||||
QtCore.QCoreApplication.processEvents()
|
||||
data = bytes(reply.readAll().data())
|
||||
_save_nodes(data)
|
||||
except Exception as ex:
|
||||
log('TOX nodes loading error: ' + str(ex))
|
||||
|
||||
|
||||
def _get_nodes_path():
|
||||
return join_path(curr_directory(__file__), 'nodes.json')
|
||||
|
||||
|
||||
def _save_nodes(nodes):
|
||||
def _save_nodes(nodes, app):
|
||||
if not nodes:
|
||||
return
|
||||
print('Saving nodes...')
|
||||
with open(_get_nodes_path(), 'wb') as fl:
|
||||
with open(_get_nodes_path(oArgs=app._args), 'wb') as fl:
|
||||
LOG.info("Saving nodes to " +_get_nodes_path())
|
||||
fl.write(nodes)
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||
from user_data.settings import *
|
||||
from PyQt5 import QtCore, QtGui
|
||||
from wrapper.toxcore_enums_and_consts import TOX_PUBLIC_KEY_SIZE
|
||||
|
|
|
@ -1,10 +1,17 @@
|
|||
from history.database import *
|
||||
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||
from history.database import TIMEOUT, \
|
||||
SAVE_MESSAGES, MESSAGE_AUTHOR
|
||||
|
||||
from contacts import basecontact, common
|
||||
from messenger.messages import *
|
||||
from contacts.contact_menu import *
|
||||
from file_transfers import file_transfers as ft
|
||||
import re
|
||||
|
||||
# LOG=util.log
|
||||
global LOG
|
||||
import logging
|
||||
LOG = logging.getLogger('app.'+__name__)
|
||||
|
||||
class Contact(basecontact.BaseContact):
|
||||
"""
|
||||
|
@ -139,7 +146,7 @@ class Contact(basecontact.BaseContact):
|
|||
and m.tox_message_id == tox_message_id, self._corr))[0]
|
||||
message.mark_as_sent()
|
||||
except Exception as ex:
|
||||
util.log('Mark as sent ex: ' + str(ex))
|
||||
LOG.error(f"Mark as sent: {ex!s}")
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Message deletion
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||
from PyQt5 import QtWidgets
|
||||
import utils.ui as util_ui
|
||||
|
||||
import utils.ui as util_ui
|
||||
from wrapper.toxcore_enums_and_consts import *
|
||||
|
||||
global LOG
|
||||
import logging
|
||||
LOG = logging.getLogger('app')
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Builder
|
||||
|
@ -99,8 +105,8 @@ class BaseContactMenuGenerator:
|
|||
(copy_menu_builder
|
||||
.with_name(util_ui.tr('Copy'))
|
||||
.with_action(util_ui.tr('Name'), lambda: main_screen.copy_text(self._contact.name))
|
||||
.with_action(util_ui.tr('Status message'), lambda: main_screen.copy_text(self._contact.status_message))
|
||||
.with_action(util_ui.tr('Public key'), lambda: main_screen.copy_text(self._contact.tox_id))
|
||||
.with_action(util_ui.tr("Status message"), lambda: main_screen.copy_text(self._contact.status_message))
|
||||
.with_action(util_ui.tr("Public key"), lambda: main_screen.copy_text(self._contact.tox_id))
|
||||
)
|
||||
|
||||
return copy_menu_builder
|
||||
|
@ -108,11 +114,11 @@ class BaseContactMenuGenerator:
|
|||
def _generate_history_menu_builder(self, history_loader, main_screen):
|
||||
history_menu_builder = ContactMenuBuilder()
|
||||
(history_menu_builder
|
||||
.with_name(util_ui.tr('Chat history'))
|
||||
.with_action(util_ui.tr('Clear history'), lambda: history_loader.clear_history(self._contact)
|
||||
.with_name(util_ui.tr("Chat history"))
|
||||
.with_action(util_ui.tr("Clear history"), lambda: history_loader.clear_history(self._contact)
|
||||
or main_screen.messages.clear())
|
||||
.with_action(util_ui.tr('Export as text'), lambda: history_loader.export_history(self._contact))
|
||||
.with_action(util_ui.tr('Export as HTML'), lambda: history_loader.export_history(self._contact, False))
|
||||
.with_action(util_ui.tr("Export as text"), lambda: history_loader.export_history(self._contact))
|
||||
.with_action(util_ui.tr("Export as HTML"), lambda: history_loader.export_history(self._contact, False))
|
||||
)
|
||||
|
||||
return history_menu_builder
|
||||
|
@ -127,16 +133,16 @@ class FriendMenuGenerator(BaseContactMenuGenerator):
|
|||
groups_menu_builder = self._generate_groups_menu(contacts_manager, groups_service)
|
||||
|
||||
allowed = self._contact.tox_id in settings['auto_accept_from_friends']
|
||||
auto = util_ui.tr('Disallow auto accept') if allowed else util_ui.tr('Allow auto accept')
|
||||
auto = util_ui.tr("Disallow auto accept") if allowed else util_ui.tr('Allow auto accept')
|
||||
|
||||
builder = ContactMenuBuilder()
|
||||
menu = (builder
|
||||
.with_action(util_ui.tr('Set alias'), lambda: main_screen.set_alias(number))
|
||||
.with_action(util_ui.tr("Set alias"), lambda: main_screen.set_alias(number))
|
||||
.with_submenu(history_menu_builder)
|
||||
.with_submenu(copy_menu_builder)
|
||||
.with_action(auto, lambda: main_screen.auto_accept(number, not allowed))
|
||||
.with_action(util_ui.tr('Remove friend'), lambda: main_screen.remove_friend(number))
|
||||
.with_action(util_ui.tr('Block friend'), lambda: main_screen.block_friend(number))
|
||||
.with_action(util_ui.tr("Remove friend"), lambda: main_screen.remove_friend(number))
|
||||
.with_action(util_ui.tr("Block friend"), lambda: main_screen.block_friend(number))
|
||||
.with_action(util_ui.tr('Notes'), lambda: main_screen.show_note(self._contact))
|
||||
.with_optional_submenu(plugins_menu_builder)
|
||||
.with_optional_submenu(groups_menu_builder)
|
||||
|
@ -165,11 +171,13 @@ class FriendMenuGenerator(BaseContactMenuGenerator):
|
|||
|
||||
def _generate_groups_menu(self, contacts_manager, groups_service):
|
||||
chats = contacts_manager.get_group_chats()
|
||||
LOG.debug(f"_generate_groups_menu len(chats)={len(chats)} or self._contact.status={self._contact.status}")
|
||||
if not len(chats) or self._contact.status is None:
|
||||
return None
|
||||
#? return None
|
||||
pass
|
||||
groups_menu_builder = ContactMenuBuilder()
|
||||
(groups_menu_builder
|
||||
.with_name(util_ui.tr('Invite to group'))
|
||||
.with_name(util_ui.tr("Invite to group"))
|
||||
.with_actions([(g.name, lambda: groups_service.invite_friend(self._contact.number, g.number)) for g in chats])
|
||||
)
|
||||
|
||||
|
@ -184,26 +192,26 @@ class GroupMenuGenerator(BaseContactMenuGenerator):
|
|||
|
||||
builder = ContactMenuBuilder()
|
||||
menu = (builder
|
||||
.with_action(util_ui.tr('Set alias'), lambda: main_screen.set_alias(number))
|
||||
.with_action(util_ui.tr("Set alias"), lambda: main_screen.set_alias(number))
|
||||
.with_submenu(copy_menu_builder)
|
||||
.with_submenu(history_menu_builder)
|
||||
.with_optional_action(util_ui.tr('Manage group'),
|
||||
.with_optional_action(util_ui.tr("Manage group"),
|
||||
lambda: groups_service.show_group_management_screen(self._contact),
|
||||
self._contact.is_self_founder())
|
||||
.with_optional_action(util_ui.tr('Group settings'),
|
||||
.with_optional_action(util_ui.tr("Group settings"),
|
||||
lambda: groups_service.show_group_settings_screen(self._contact),
|
||||
not self._contact.is_self_founder())
|
||||
.with_optional_action(util_ui.tr('Set topic'),
|
||||
.with_optional_action(util_ui.tr("Set topic"),
|
||||
lambda: groups_service.set_group_topic(self._contact),
|
||||
self._contact.is_self_moderator_or_founder())
|
||||
.with_action(util_ui.tr('Bans list'),
|
||||
lambda: groups_service.show_bans_list(self._contact))
|
||||
.with_action(util_ui.tr('Reconnect to group'),
|
||||
# .with_action(util_ui.tr("Bans list"),
|
||||
# lambda: groups_service.show_bans_list(self._contact))
|
||||
.with_action(util_ui.tr("Reconnect to group"),
|
||||
lambda: groups_service.reconnect_to_group(self._contact.number))
|
||||
.with_optional_action(util_ui.tr('Disconnect from group'),
|
||||
.with_optional_action(util_ui.tr("Disconnect from group"),
|
||||
lambda: groups_service.disconnect_from_group(self._contact.number),
|
||||
self._contact.status is not None)
|
||||
.with_action(util_ui.tr('Leave group'), lambda: groups_service.leave_group(self._contact.number))
|
||||
.with_action(util_ui.tr("Leave group"), lambda: groups_service.leave_group(self._contact.number))
|
||||
.with_action(util_ui.tr('Notes'), lambda: main_screen.show_note(self._contact))
|
||||
).build()
|
||||
|
||||
|
@ -218,10 +226,10 @@ class GroupPeerMenuGenerator(BaseContactMenuGenerator):
|
|||
|
||||
builder = ContactMenuBuilder()
|
||||
menu = (builder
|
||||
.with_action(util_ui.tr('Set alias'), lambda: main_screen.set_alias(number))
|
||||
.with_action(util_ui.tr("Set alias"), lambda: main_screen.set_alias(number))
|
||||
.with_submenu(copy_menu_builder)
|
||||
.with_submenu(history_menu_builder)
|
||||
.with_action(util_ui.tr('Quit chat'),
|
||||
.with_action(util_ui.tr("Quit chat"),
|
||||
lambda: contacts_manager.remove_group_peer(self._contact))
|
||||
.with_action(util_ui.tr('Notes'), lambda: main_screen.show_note(self._contact))
|
||||
).build()
|
||||
|
|
|
@ -15,8 +15,10 @@ class ContactProvider(tox_save.ToxSave):
|
|||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_friend_by_number(self, friend_number):
|
||||
try:
|
||||
public_key = self._tox.friend_get_public_key(friend_number)
|
||||
|
||||
except Exception as e:
|
||||
return None
|
||||
return self.get_friend_by_public_key(public_key)
|
||||
|
||||
def get_friend_by_public_key(self, public_key):
|
||||
|
@ -29,7 +31,10 @@ class ContactProvider(tox_save.ToxSave):
|
|||
return friend
|
||||
|
||||
def get_all_friends(self):
|
||||
try:
|
||||
friend_numbers = self._tox.self_get_friend_list()
|
||||
except Exception as e:
|
||||
return None
|
||||
friends = map(lambda n: self.get_friend_by_number(n), friend_numbers)
|
||||
|
||||
return list(friends)
|
||||
|
@ -39,13 +44,19 @@ class ContactProvider(tox_save.ToxSave):
|
|||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def get_all_groups(self):
|
||||
try:
|
||||
group_numbers = range(self._tox.group_get_number_groups())
|
||||
except Exception as e:
|
||||
return None
|
||||
groups = map(lambda n: self.get_group_by_number(n), group_numbers)
|
||||
|
||||
return list(groups)
|
||||
|
||||
def get_group_by_number(self, group_number):
|
||||
try:
|
||||
public_key = self._tox.group_get_chat_id(group_number)
|
||||
except Exception as e:
|
||||
return None
|
||||
|
||||
return self.get_group_by_public_key(public_key)
|
||||
|
||||
|
@ -67,7 +78,7 @@ class ContactProvider(tox_save.ToxSave):
|
|||
|
||||
def get_group_peer_by_id(self, group, peer_id):
|
||||
peer = group.get_peer_by_id(peer_id)
|
||||
|
||||
if peer:
|
||||
return self._get_group_peer(group, peer)
|
||||
|
||||
def get_group_peer_by_public_key(self, group, public_key):
|
||||
|
|
|
@ -1,9 +1,15 @@
|
|||
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||
from contacts.friend import Friend
|
||||
from contacts.group_chat import GroupChat
|
||||
from messenger.messages import *
|
||||
from common.tox_save import ToxSave
|
||||
from contacts.group_peer_contact import GroupPeerContact
|
||||
|
||||
# LOG=util.log
|
||||
global LOG
|
||||
import logging
|
||||
LOG = logging.getLogger('app.'+__name__)
|
||||
log = lambda x: LOG.info(x)
|
||||
|
||||
class ContactsManager(ToxSave):
|
||||
"""
|
||||
|
@ -129,8 +135,8 @@ class ContactsManager(ToxSave):
|
|||
self._set_current_contact_data(contact)
|
||||
self._active_contact_changed(contact)
|
||||
except Exception as ex: # no friend found. ignore
|
||||
util.log('Friend value: ' + str(value))
|
||||
util.log('Error in set active: ' + str(ex))
|
||||
LOG.warn(f"no friend found. Friend value: {value!s}")
|
||||
LOG.error('in set active: ' + str(ex))
|
||||
raise
|
||||
|
||||
active_contact = property(get_active, set_active)
|
||||
|
@ -235,6 +241,7 @@ class ContactsManager(ToxSave):
|
|||
def get_or_create_group_peer_contact(self, group_number, peer_id):
|
||||
group = self.get_group_by_number(group_number)
|
||||
peer = group.get_peer_by_id(peer_id)
|
||||
if peer: # broken
|
||||
if not self.check_if_contact_exists(peer.public_key):
|
||||
self.add_group_peer(group, peer)
|
||||
|
||||
|
@ -375,6 +382,7 @@ class ContactsManager(ToxSave):
|
|||
|
||||
def remove_group_peer_by_id(self, group, peer_id):
|
||||
peer = group.get_peer_by_id(peer_id)
|
||||
if peer: # broken
|
||||
if not self.check_if_contact_exists(peer.public_key):
|
||||
return
|
||||
contact = self.get_contact_by_tox_id(peer.public_key)
|
||||
|
@ -382,6 +390,7 @@ class ContactsManager(ToxSave):
|
|||
|
||||
def remove_group_peer(self, group_peer_contact):
|
||||
contact = self.get_contact_by_tox_id(group_peer_contact.tox_id)
|
||||
if contact:
|
||||
self._cleanup_contact_data(contact)
|
||||
num = self._contacts.index(contact)
|
||||
self._delete_contact(num)
|
||||
|
@ -432,7 +441,7 @@ class ContactsManager(ToxSave):
|
|||
self.save_profile()
|
||||
return True
|
||||
except Exception as ex: # wrong data
|
||||
util.log('Friend request failed with ' + str(ex))
|
||||
LOG.error('Friend request failed with ' + str(ex))
|
||||
return str(ex)
|
||||
|
||||
def process_friend_request(self, tox_id, message):
|
||||
|
@ -451,7 +460,7 @@ class ContactsManager(ToxSave):
|
|||
data = self._tox.get_savedata()
|
||||
self._profile_manager.save_profile(data)
|
||||
except Exception as ex: # something is wrong
|
||||
util.log('Accept friend request failed! ' + str(ex))
|
||||
LOG.error('Accept friend request failed! ' + str(ex))
|
||||
|
||||
def can_send_typing_notification(self):
|
||||
return self._settings['typing_notifications'] and not self.is_active_a_group_chat_peer()
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||
|
||||
from contacts import contact
|
||||
from contacts.contact_menu import GroupMenuGenerator
|
||||
import utils.util as util
|
||||
|
@ -6,6 +8,14 @@ from wrapper import toxcore_enums_and_consts as constants
|
|||
from common.tox_save import ToxSave
|
||||
from groups.group_ban import GroupBan
|
||||
|
||||
global LOG
|
||||
import logging
|
||||
LOG = logging.getLogger(__name__)
|
||||
def LOG_ERROR(l): print('ERROR_: '+l)
|
||||
def LOG_WARN(l): print('WARN_: '+l)
|
||||
def LOG_INFO(l): print('INFO_: '+l)
|
||||
def LOG_DEBUG(l): print('DEBUG_: '+l)
|
||||
def LOG_TRACE(l): pass # print('TRACE+ '+l)
|
||||
|
||||
class GroupChat(contact.Contact, ToxSave):
|
||||
|
||||
|
@ -73,6 +83,10 @@ class GroupChat(contact.Contact, ToxSave):
|
|||
return self.get_self_role() == constants.TOX_GROUP_ROLE['FOUNDER']
|
||||
|
||||
def add_peer(self, peer_id, is_current_user=False):
|
||||
if peer_id > self._peers_limit:
|
||||
LOG_WARN(f"add_peer id={peer_id} > {self._peers_limit}")
|
||||
return
|
||||
|
||||
peer = GroupChatPeer(peer_id,
|
||||
self._tox.group_peer_get_name(self._number, peer_id),
|
||||
self._tox.group_peer_get_status(self._number, peer_id),
|
||||
|
@ -86,25 +100,41 @@ class GroupChat(contact.Contact, ToxSave):
|
|||
self.remove_all_peers_except_self()
|
||||
else:
|
||||
peer = self.get_peer_by_id(peer_id)
|
||||
if peer: # broken
|
||||
self._peers.remove(peer)
|
||||
else:
|
||||
LOG_WARN(f"remove_peer empty peers for {peer_id}")
|
||||
|
||||
def get_peer_by_id(self, peer_id):
|
||||
peers = list(filter(lambda p: p.id == peer_id, self._peers))
|
||||
|
||||
if peers:
|
||||
#? broken
|
||||
return peers[0]
|
||||
else:
|
||||
LOG_WARN(f"get_peer_by_id empty peers for {peer_id}")
|
||||
return []
|
||||
|
||||
def get_peer_by_public_key(self, public_key):
|
||||
peers = list(filter(lambda p: p.public_key == public_key, self._peers))
|
||||
|
||||
# DEBUGc: group_moderation #0 mod_id=4294967295 event_type=3
|
||||
# WARN_: get_peer_by_id empty peers for 4294967295
|
||||
if peers:
|
||||
return peers[0]
|
||||
else:
|
||||
LOG_WARN(f"get_peer_by_public_key empty peers for {public_key}")
|
||||
return []
|
||||
|
||||
def remove_all_peers_except_self(self):
|
||||
self._peers = self._peers[:1]
|
||||
|
||||
def get_peers_names(self):
|
||||
peers_names = map(lambda p: p.name, self._peers)
|
||||
|
||||
if peers_names: # broken
|
||||
return list(peers_names)
|
||||
else:
|
||||
LOG_WARN(f"get_peers_names empty peers")
|
||||
#? broken
|
||||
return []
|
||||
|
||||
def get_peers(self):
|
||||
return self._peers[:]
|
||||
|
@ -112,16 +142,17 @@ class GroupChat(contact.Contact, ToxSave):
|
|||
peers = property(get_peers)
|
||||
|
||||
def get_bans(self):
|
||||
ban_ids = self._tox.group_ban_get_list(self._number)
|
||||
bans = []
|
||||
for ban_id in ban_ids:
|
||||
ban = GroupBan(ban_id,
|
||||
self._tox.group_ban_get_target(self._number, ban_id),
|
||||
self._tox.group_ban_get_time_set(self._number, ban_id))
|
||||
bans.append(ban)
|
||||
|
||||
return bans
|
||||
|
||||
return []
|
||||
# ban_ids = self._tox.group_ban_get_list(self._number)
|
||||
# bans = []
|
||||
# for ban_id in ban_ids:
|
||||
# ban = GroupBan(ban_id,
|
||||
# self._tox.group_ban_get_target(self._number, ban_id),
|
||||
# self._tox.group_ban_get_time_set(self._number, ban_id))
|
||||
# bans.append(ban)
|
||||
#
|
||||
# return bans
|
||||
#
|
||||
bans = property(get_bans)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||
from contacts import basecontact
|
||||
import random
|
||||
import threading
|
||||
import common.tox_save as tox_save
|
||||
from middleware.threads import invoke_in_main_thread
|
||||
|
||||
global LOG
|
||||
import logging
|
||||
LOG = logging.getLogger('app.'+__name__)
|
||||
|
||||
class Profile(basecontact.BaseContact, tox_save.ToxSave):
|
||||
"""
|
||||
|
@ -14,6 +18,7 @@ class Profile(basecontact.BaseContact, tox_save.ToxSave):
|
|||
:param tox: tox instance
|
||||
:param screen: ref to main screen
|
||||
"""
|
||||
assert tox
|
||||
basecontact.BaseContact.__init__(self,
|
||||
profile_manager,
|
||||
tox.self_get_name(),
|
||||
|
|
|
@ -1,11 +1,19 @@
|
|||
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||
from messenger.messages import *
|
||||
from ui.contact_items import *
|
||||
import utils.util as util
|
||||
from common.tox_save import ToxSave
|
||||
from tests.support_testing import assert_main_thread
|
||||
from copy import deepcopy
|
||||
|
||||
# LOG=util.log
|
||||
global LOG
|
||||
import logging
|
||||
LOG = logging.getLogger('app.'+__name__)
|
||||
log = lambda x: LOG.info(x)
|
||||
|
||||
class FileTransfersHandler(ToxSave):
|
||||
|
||||
lBlockAvatars = []
|
||||
def __init__(self, tox, settings, contact_provider, file_transfers_message_service, profile):
|
||||
super().__init__(tox)
|
||||
self._settings = settings
|
||||
|
@ -19,6 +27,7 @@ class FileTransfersHandler(ToxSave):
|
|||
# key = (friend number, file number), value - message id
|
||||
|
||||
profile.avatar_changed_event.add_callback(self._send_avatar_to_contacts)
|
||||
self. lBlockAvatars = []
|
||||
|
||||
def stop(self):
|
||||
self._settings['paused_file_transfers'] = self._paused_file_transfers if self._settings['resend_files'] else {}
|
||||
|
@ -37,6 +46,7 @@ class FileTransfersHandler(ToxSave):
|
|||
:param file_name: file name without path
|
||||
"""
|
||||
friend = self._get_friend_by_number(friend_number)
|
||||
if friend is None: return None
|
||||
auto = self._settings['allow_auto_accept'] and friend.tox_id in self._settings['auto_accept_from_friends']
|
||||
inline = is_inline(file_name) and self._settings['allow_inline']
|
||||
file_id = self._tox.file_get_file_id(friend_number, file_number)
|
||||
|
@ -85,7 +95,9 @@ class FileTransfersHandler(ToxSave):
|
|||
self._tox.file_control(friend_number, file_number, TOX_FILE_CONTROL['CANCEL'])
|
||||
|
||||
def cancel_not_started_transfer(self, friend_number, message_id):
|
||||
self._get_friend_by_number(friend_number).delete_one_unsent_file(message_id)
|
||||
friend = self._get_friend_by_number(friend_number)
|
||||
if friend is None: return None
|
||||
friend.delete_one_unsent_file(message_id)
|
||||
|
||||
def pause_transfer(self, friend_number, file_number, by_friend=False):
|
||||
"""
|
||||
|
@ -115,6 +127,7 @@ class FileTransfersHandler(ToxSave):
|
|||
"""
|
||||
path = self._generate_valid_path(path, from_position)
|
||||
friend = self._get_friend_by_number(friend_number)
|
||||
if friend is None: return None
|
||||
if not inline:
|
||||
rt = ReceiveTransfer(path, self._tox, friend_number, size, file_number, from_position)
|
||||
else:
|
||||
|
@ -145,6 +158,7 @@ class FileTransfersHandler(ToxSave):
|
|||
|
||||
def send_inline(self, data, file_name, friend_number, is_resend=False):
|
||||
friend = self._get_friend_by_number(friend_number)
|
||||
if friend is None: return None
|
||||
if friend.status is None and not is_resend:
|
||||
self._file_transfers_message_service.add_unsent_file_message(friend, file_name, data)
|
||||
return
|
||||
|
@ -162,11 +176,12 @@ class FileTransfersHandler(ToxSave):
|
|||
:param file_id: file id of transfer
|
||||
"""
|
||||
friend = self._get_friend_by_number(friend_number)
|
||||
if friend is None: return None
|
||||
if friend.status is None and not is_resend:
|
||||
self._file_transfers_message_service.add_unsent_file_message(friend, path, None)
|
||||
return
|
||||
elif friend.status is None and is_resend:
|
||||
print('Error in sending')
|
||||
LOG.error('Error in sending')
|
||||
return
|
||||
st = SendTransfer(path, self._tox, friend_number, TOX_FILE_KIND['DATA'], file_id)
|
||||
file_name = os.path.basename(path)
|
||||
|
@ -186,23 +201,27 @@ class FileTransfersHandler(ToxSave):
|
|||
|
||||
def transfer_finished(self, friend_number, file_number):
|
||||
transfer = self._file_transfers[(friend_number, file_number)]
|
||||
friend = self._get_friend_by_number(friend_number)
|
||||
if friend is None: return None
|
||||
t = type(transfer)
|
||||
if t is ReceiveAvatar:
|
||||
self._get_friend_by_number(friend_number).load_avatar()
|
||||
friend.load_avatar()
|
||||
elif t is ReceiveToBuffer or (t is SendFromBuffer and self._settings['allow_inline']): # inline image
|
||||
print('inline')
|
||||
LOG.debug('inline')
|
||||
inline = InlineImageMessage(transfer.data)
|
||||
message_id = self._insert_inline_before[(friend_number, file_number)]
|
||||
del self._insert_inline_before[(friend_number, file_number)]
|
||||
index = self._get_friend_by_number(friend_number).insert_inline(message_id, inline)
|
||||
if friend is None: return None
|
||||
index = friend.insert_inline(message_id, inline)
|
||||
self._file_transfers_message_service.add_inline_message(transfer, index)
|
||||
del self._file_transfers[(friend_number, file_number)]
|
||||
|
||||
def send_files(self, friend_number):
|
||||
try:
|
||||
friend = self._get_friend_by_number(friend_number)
|
||||
if friend is None: return None
|
||||
friend.remove_invalid_unsent_files()
|
||||
files = friend.get_unsent_files()
|
||||
try:
|
||||
for fl in files:
|
||||
data, path = fl.data, fl.path
|
||||
if data is not None:
|
||||
|
@ -211,6 +230,7 @@ class FileTransfersHandler(ToxSave):
|
|||
self.send_file(path, friend_number, True)
|
||||
friend.clear_unsent_files()
|
||||
for key in self._paused_file_transfers.keys():
|
||||
# RuntimeError: dictionary changed size during iteration
|
||||
(path, ft_friend_number, is_incoming, start_position) = self._paused_file_transfers[key]
|
||||
if not os.path.exists(path):
|
||||
del self._paused_file_transfers[key]
|
||||
|
@ -218,12 +238,16 @@ class FileTransfersHandler(ToxSave):
|
|||
self.send_file(path, friend_number, True, key)
|
||||
del self._paused_file_transfers[key]
|
||||
except Exception as ex:
|
||||
print('Exception in file sending: ' + str(ex))
|
||||
LOG.error('Exception in file sending: ' + str(ex))
|
||||
|
||||
def friend_exit(self, friend_number):
|
||||
for friend_num, file_num in self._file_transfers.keys():
|
||||
# RuntimeError: dictionary changed size during iteration
|
||||
lMayChangeDynamically = self._file_transfers.copy()
|
||||
for friend_num, file_num in lMayChangeDynamically:
|
||||
if friend_num != friend_number:
|
||||
continue
|
||||
if (friend_num, file_num) not in self._file_transfers:
|
||||
continue
|
||||
ft = self._file_transfers[(friend_num, file_num)]
|
||||
if type(ft) is SendTransfer:
|
||||
self._paused_file_transfers[ft.file_id] = [ft.path, friend_num, False, -1]
|
||||
|
@ -240,8 +264,16 @@ class FileTransfersHandler(ToxSave):
|
|||
:param friend_number: number of friend who should get new avatar
|
||||
:param avatar_path: path to avatar or None if reset
|
||||
"""
|
||||
if (avatar_path, friend_number,) in self.lBlockAvatars:
|
||||
return
|
||||
|
||||
try:
|
||||
sa = SendAvatar(avatar_path, self._tox, friend_number)
|
||||
self._file_transfers[(friend_number, sa.file_number)] = sa
|
||||
except Exception as e:
|
||||
# ArgumentError('This client is currently not connected to the friend.')
|
||||
LOG.error(f"send_avatar {e}")
|
||||
self.lBlockAvatars.append( (avatar_path, friend_number,) )
|
||||
|
||||
def incoming_avatar(self, friend_number, file_number, size):
|
||||
"""
|
||||
|
@ -251,6 +283,7 @@ class FileTransfersHandler(ToxSave):
|
|||
:param size: size of avatar or 0 (default avatar)
|
||||
"""
|
||||
friend = self._get_friend_by_number(friend_number)
|
||||
if friend is None: return None
|
||||
ra = ReceiveAvatar(friend.get_contact_avatar_path(), self._tox, friend_number, size, file_number)
|
||||
if ra.state != FILE_TRANSFER_STATE['CANCELLED']:
|
||||
self._file_transfers[(friend_number, file_number)] = ra
|
||||
|
@ -259,6 +292,7 @@ class FileTransfersHandler(ToxSave):
|
|||
friend.reset_avatar(self._settings['identicons'])
|
||||
|
||||
def _send_avatar_to_contacts(self, _):
|
||||
# from a callback
|
||||
friends = self._get_all_friends()
|
||||
for friend in filter(self._is_friend_online, friends):
|
||||
self.send_avatar(friend.number)
|
||||
|
@ -269,6 +303,7 @@ class FileTransfersHandler(ToxSave):
|
|||
|
||||
def _is_friend_online(self, friend_number):
|
||||
friend = self._get_friend_by_number(friend_number)
|
||||
if friend is None: return None
|
||||
|
||||
return friend.status is not None
|
||||
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||
|
||||
import common.tox_save as tox_save
|
||||
import utils.ui as util_ui
|
||||
from groups.peers_list import PeersListGenerator
|
||||
from groups.group_invite import GroupInvite
|
||||
import wrapper.toxcore_enums_and_consts as constants
|
||||
from wrapper.toxcore_enums_and_consts import *
|
||||
|
||||
|
||||
class GroupsService(tox_save.ToxSave):
|
||||
|
@ -65,7 +68,17 @@ class GroupsService(tox_save.ToxSave):
|
|||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def invite_friend(self, friend_number, group_number):
|
||||
if self._tox.friend_get_connection_status(friend_number) == TOX_CONNECTION['NONE']:
|
||||
title = f"Error in group_invite_friend {friend_number}"
|
||||
e = f"Friend not connected friend_number={friend_number}"
|
||||
util_ui.message_box(title +'\n' +str(e), title)
|
||||
return
|
||||
|
||||
try:
|
||||
self._tox.group_invite_friend(group_number, friend_number)
|
||||
except Exception as e:
|
||||
title = f"Error in group_invite_friend {group_number} {friend_number}"
|
||||
util_ui.message_box(title +'\n' +str(e), title)
|
||||
|
||||
def process_group_invite(self, friend_number, group_name, invite_data):
|
||||
friend = self._get_friend_by_number(friend_number)
|
||||
|
@ -188,6 +201,7 @@ class GroupsService(tox_save.ToxSave):
|
|||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def show_bans_list(self, group):
|
||||
return
|
||||
widgets_factory = self._get_widgets_factory()
|
||||
self._screen = widgets_factory.create_groups_bans_screen(group)
|
||||
self._screen.show()
|
||||
|
|
|
@ -1,7 +1,13 @@
|
|||
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||
from sqlite3 import connect
|
||||
import os.path
|
||||
import utils.util as util
|
||||
|
||||
# LOG=util.log
|
||||
global LOG
|
||||
import logging
|
||||
LOG = logging.getLogger('app.'+__name__)
|
||||
log = lambda x: LOG.info(x)
|
||||
|
||||
TIMEOUT = 11
|
||||
|
||||
|
@ -24,18 +30,29 @@ CONTACT_TYPE = {
|
|||
class Database:
|
||||
|
||||
def __init__(self, path, toxes):
|
||||
self._path, self._toxes = path, toxes
|
||||
self._path = path
|
||||
self._toxes = toxes
|
||||
self._name = os.path.basename(path)
|
||||
if os.path.exists(path):
|
||||
|
||||
def open(self):
|
||||
path = self._path
|
||||
toxes = self._toxes
|
||||
if not os.path.exists(path):
|
||||
LOG.warn('Db not found: ' +path)
|
||||
return
|
||||
try:
|
||||
with open(path, 'rb') as fin:
|
||||
data = fin.read()
|
||||
except Exception as ex:
|
||||
LOG.error('Db reading error: ' +path +' ' +str(ex))
|
||||
raise
|
||||
try:
|
||||
if toxes.is_data_encrypted(data):
|
||||
data = toxes.pass_decrypt(data)
|
||||
with open(path, 'wb') as fout:
|
||||
fout.write(data)
|
||||
except Exception as ex:
|
||||
util.log('Db reading error: ' + str(ex))
|
||||
LOG.error('Db writing error: ' +path +' ' + str(ex))
|
||||
os.remove(path)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
@ -72,9 +89,11 @@ class Database:
|
|||
' message_type INTEGER'
|
||||
')')
|
||||
db.commit()
|
||||
except:
|
||||
print('Database is locked!')
|
||||
return True
|
||||
except Exception as e:
|
||||
LOG("ERROR: " +self._name +' Database exception! ' +str(e))
|
||||
db.rollback()
|
||||
return False
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
@ -84,9 +103,11 @@ class Database:
|
|||
cursor = db.cursor()
|
||||
cursor.execute('DROP TABLE id' + tox_id + ';')
|
||||
db.commit()
|
||||
except:
|
||||
print('Database is locked!')
|
||||
return True
|
||||
except Exception as e:
|
||||
LOG("ERROR: " +self._name +' Database exception! ' +str(e))
|
||||
db.rollback()
|
||||
return False
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
@ -98,9 +119,11 @@ class Database:
|
|||
'(message, author_name, author_type, unix_time, message_type) ' +
|
||||
'VALUES (?, ?, ?, ?, ?, ?);', messages_iter)
|
||||
db.commit()
|
||||
except:
|
||||
print('Database is locked!')
|
||||
return True
|
||||
except Exception as e:
|
||||
LOG("ERROR: " +self._name +' Database exception! ' +str(e))
|
||||
db.rollback()
|
||||
return False
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
@ -111,9 +134,11 @@ class Database:
|
|||
cursor.execute('UPDATE id' + tox_id + ' SET author = 0 '
|
||||
'WHERE id = ' + str(message_id) + ' AND author = 2;')
|
||||
db.commit()
|
||||
except:
|
||||
print('Database is locked!')
|
||||
return True
|
||||
except Exception as e:
|
||||
LOG("ERROR: " +self._name +' Database exception! ' +str(e))
|
||||
db.rollback()
|
||||
return False
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
@ -123,9 +148,11 @@ class Database:
|
|||
cursor = db.cursor()
|
||||
cursor.execute('DELETE FROM id' + tox_id + ' WHERE id = ' + str(unique_id) + ';')
|
||||
db.commit()
|
||||
except:
|
||||
print('Database is locked!')
|
||||
return True
|
||||
except Exception as e:
|
||||
LOG("ERROR: " +self._name +' Database exception! ' +str(e))
|
||||
db.rollback()
|
||||
return False
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
@ -135,9 +162,11 @@ class Database:
|
|||
cursor = db.cursor()
|
||||
cursor.execute('DELETE FROM id' + tox_id + ';')
|
||||
db.commit()
|
||||
except:
|
||||
print('Database is locked!')
|
||||
return True
|
||||
except Exception as e:
|
||||
LOG("ERROR: " +self._name +' Database exception! ' +str(e))
|
||||
db.rollback()
|
||||
return False
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||
from history.history_logs_generators import *
|
||||
|
||||
|
||||
|
@ -26,7 +27,8 @@ class History:
|
|||
"""
|
||||
Save history to db
|
||||
"""
|
||||
if self._settings['save_db']:
|
||||
# me a mistake? was _db not _history
|
||||
if self._settings['save_history'] or self._settings['save_db']:
|
||||
for friend in self._contact_provider.get_all_friends():
|
||||
self._db.add_friend_to_db(friend.tox_id)
|
||||
if not self._settings['save_unsent_only']:
|
||||
|
|
BIN
toxygen/images/accept.png
Executable file → Normal file
Before Width: | Height: | Size: 114 KiB After Width: | Height: | Size: 116 KiB |
BIN
toxygen/images/accept_audio.png
Executable file → Normal file
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 6.3 KiB |
BIN
toxygen/images/accept_video.png
Executable file → Normal file
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 12 KiB |
BIN
toxygen/images/avatar.png
Executable file → Normal file
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 329 B After Width: | Height: | Size: 433 B |
Before Width: | Height: | Size: 609 B After Width: | Height: | Size: 556 B |
BIN
toxygen/images/call.png
Executable file → Normal file
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 816 B |
BIN
toxygen/images/call_video.png
Executable file → Normal file
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 1.2 KiB |
BIN
toxygen/images/decline.png
Executable file → Normal file
Before Width: | Height: | Size: 118 KiB After Width: | Height: | Size: 119 KiB |
BIN
toxygen/images/decline_call.png
Executable file → Normal file
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 10 KiB |
BIN
toxygen/images/file.png
Executable file → Normal file
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 1.2 KiB |
BIN
toxygen/images/finish_call.png
Executable file → Normal file
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 816 B |
BIN
toxygen/images/finish_call_video.png
Executable file → Normal file
Before Width: | Height: | Size: 3 KiB After Width: | Height: | Size: 461 B |
Before Width: | Height: | Size: 4 KiB After Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 11 KiB |
BIN
toxygen/images/icon_new_messages.png
Executable file → Normal file
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 911 B |
Before Width: | Height: | Size: 231 B After Width: | Height: | Size: 400 B |
Before Width: | Height: | Size: 405 B After Width: | Height: | Size: 474 B |
BIN
toxygen/images/incoming_call.png
Executable file → Normal file
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 816 B |
BIN
toxygen/images/incoming_call_video.png
Executable file → Normal file
Before Width: | Height: | Size: 3 KiB After Width: | Height: | Size: 461 B |
BIN
toxygen/images/menu.png
Executable file → Normal file
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 159 B After Width: | Height: | Size: 325 B |
Before Width: | Height: | Size: 445 B After Width: | Height: | Size: 489 B |
Before Width: | Height: | Size: 201 B After Width: | Height: | Size: 376 B |
Before Width: | Height: | Size: 351 B After Width: | Height: | Size: 454 B |
BIN
toxygen/images/pause.png
Executable file → Normal file
Before Width: | Height: | Size: 306 B After Width: | Height: | Size: 427 B |
BIN
toxygen/images/resume.png
Executable file → Normal file
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 1.4 KiB |
BIN
toxygen/images/screenshot.png
Executable file → Normal file
Before Width: | Height: | Size: 481 B After Width: | Height: | Size: 656 B |
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 865 B |
BIN
toxygen/images/send.png
Executable file → Normal file
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.5 KiB |
BIN
toxygen/images/smiley.png
Executable file → Normal file
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 2.3 KiB |
BIN
toxygen/images/sticker.png
Executable file → Normal file
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 94 KiB |
BIN
toxygen/images/typing.png
Executable file → Normal file
Before Width: | Height: | Size: 5.6 KiB After Width: | Height: | Size: 2.5 KiB |
417
toxygen/main.py
|
@ -1,51 +1,424 @@
|
|||
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||
import sys
|
||||
import os
|
||||
import app
|
||||
from user_data.settings import *
|
||||
import utils.util as util
|
||||
import argparse
|
||||
import logging
|
||||
import signal
|
||||
|
||||
import faulthandler
|
||||
faulthandler.enable()
|
||||
|
||||
import warnings
|
||||
warnings.filterwarnings('ignore')
|
||||
|
||||
import tests.support_testing as ts
|
||||
try:
|
||||
from trepan.interfaces import server as Mserver
|
||||
from trepan.api import debug
|
||||
except:
|
||||
print('trepan3 TCP server NOT enabled.')
|
||||
else:
|
||||
import signal
|
||||
try:
|
||||
signal.signal(signal.SIGUSR1, ts.trepan_handler)
|
||||
print('trepan3 TCP server enabled on port 6666.')
|
||||
except: pass
|
||||
|
||||
from user_data.settings import *
|
||||
from user_data.settings import Settings
|
||||
from user_data import settings
|
||||
import utils.util as util
|
||||
from tests import omain
|
||||
with ts.ignoreStderr():
|
||||
import pyaudio
|
||||
|
||||
__maintainer__ = 'Ingvar'
|
||||
__version__ = '0.5.0'
|
||||
__version__ = '0.5.0+'
|
||||
|
||||
from PyQt5 import QtCore
|
||||
import gevent
|
||||
if 'QtCore' in sys.modules:
|
||||
def qt_sleep(fSec):
|
||||
if fSec > .001:
|
||||
QtCore.QThread.msleep(int(fSec*1000.0))
|
||||
QtCore.QCoreApplication.processEvents()
|
||||
sleep = qt_sleep
|
||||
elif 'gevent' in sys.modules:
|
||||
sleep = gevent.sleep
|
||||
else:
|
||||
import time
|
||||
sleep = time.sleep
|
||||
|
||||
def reset():
|
||||
Settings.reset_auto_profile()
|
||||
|
||||
def clean():
|
||||
"""Removes libs folder"""
|
||||
directory = util.get_libs_directory()
|
||||
util.remove(directory)
|
||||
|
||||
|
||||
def reset():
|
||||
Settings.reset_auto_profile()
|
||||
|
||||
|
||||
def print_toxygen_version():
|
||||
print('Toxygen v' + __version__)
|
||||
print('Toxygen ' + __version__)
|
||||
|
||||
def setup_default_audio():
|
||||
# need:
|
||||
audio = ts.get_audio()
|
||||
# unfinished
|
||||
global oPYA
|
||||
oPYA = pyaudio.PyAudio()
|
||||
audio['output_devices'] = dict()
|
||||
i = oPYA.get_device_count()
|
||||
while i > 0:
|
||||
i -= 1
|
||||
if oPYA.get_device_info_by_index(i)['maxOutputChannels'] == 0:
|
||||
continue
|
||||
audio['output_devices'][i] = oPYA.get_device_info_by_index(i)['name']
|
||||
i = oPYA.get_device_count()
|
||||
audio['input_devices'] = dict()
|
||||
while i > 0:
|
||||
i -= 1
|
||||
if oPYA.get_device_info_by_index(i)['maxInputChannels'] == 0:
|
||||
continue
|
||||
audio['input_devices'][i] = oPYA.get_device_info_by_index(i)['name']
|
||||
return audio
|
||||
|
||||
def main():
|
||||
def setup_video(oArgs):
|
||||
video = setup_default_video()
|
||||
if oArgs.video_input == '-1':
|
||||
video['device'] = video['output_devices'][1]
|
||||
else:
|
||||
video['device'] = oArgs.video_input
|
||||
return video
|
||||
|
||||
def setup_audio(oArgs):
|
||||
global oPYA
|
||||
audio = setup_default_audio()
|
||||
for k,v in audio['input_devices'].items():
|
||||
if v == 'default' and 'input' not in audio :
|
||||
audio['input'] = k
|
||||
if v == getattr(oArgs, 'audio_input'):
|
||||
audio['input'] = k
|
||||
LOG.debug(f"Setting audio['input'] {k} = {v} {k}")
|
||||
break
|
||||
for k,v in audio['output_devices'].items():
|
||||
if v == 'default' and 'output' not in audio:
|
||||
audio['output'] = k
|
||||
if v == getattr(oArgs, 'audio_output'):
|
||||
audio['output'] = k
|
||||
LOG.debug(f"Setting audio['output'] {k} = {v} " +str(k))
|
||||
break
|
||||
|
||||
if hasattr(oArgs, 'mode') and getattr(oArgs, 'mode') > 1:
|
||||
audio['enabled'] = True
|
||||
audio['audio_enabled'] = True
|
||||
audio['video_enabled'] = True
|
||||
elif hasattr(oArgs, 'mode') and getattr(oArgs, 'mode') > 0:
|
||||
audio['enabled'] = True
|
||||
audio['audio_enabled'] = False
|
||||
audio['video_enabled'] = True
|
||||
else:
|
||||
audio['enabled'] = False
|
||||
audio['audio_enabled'] = False
|
||||
audio['video_enabled'] = False
|
||||
|
||||
return audio
|
||||
|
||||
i = getattr(oArgs, 'audio_output')
|
||||
if i >= 0:
|
||||
try:
|
||||
elt = oPYA.get_device_info_by_index(i)
|
||||
if i >= 0 and ( 'maxOutputChannels' not in elt or \
|
||||
elt['maxOutputChannels'] == 0):
|
||||
LOG.warn(f"Audio output device has no output channels: {i}")
|
||||
oArgs.audio_output = -1
|
||||
except OSError as e:
|
||||
LOG.warn("Audio output device error looking for maxOutputChannels: " \
|
||||
+str(i) +' ' +str(e))
|
||||
oArgs.audio_output = -1
|
||||
|
||||
if getattr(oArgs, 'audio_output') < 0:
|
||||
LOG.info("Choose an output device:")
|
||||
i = oPYA.get_device_count()
|
||||
while i > 0:
|
||||
i -= 1
|
||||
if oPYA.get_device_info_by_index(i)['maxOutputChannels'] == 0:
|
||||
continue
|
||||
LOG.info(str(i) \
|
||||
+' ' +oPYA.get_device_info_by_index(i)['name'] \
|
||||
+' ' +str(oPYA.get_device_info_by_index(i)['defaultSampleRate'])
|
||||
)
|
||||
return 0
|
||||
|
||||
i = getattr(oArgs, 'audio_input')
|
||||
if i >= 0:
|
||||
try:
|
||||
elt = oPYA.get_device_info_by_index(i)
|
||||
if i >= 0 and ( 'maxInputChannels' not in elt or \
|
||||
elt['maxInputChannels'] == 0):
|
||||
LOG.warn(f"Audio input device has no input channels: {i}")
|
||||
setattr(oArgs, 'audio_input', -1)
|
||||
except OSError as e:
|
||||
LOG.warn("Audio input device error looking for maxInputChannels: " \
|
||||
+str(i) +' ' +str(e))
|
||||
setattr(oArgs, 'audio_input', -1)
|
||||
if getattr(oArgs, 'audio_input') < 0:
|
||||
LOG.info("Choose an input device:")
|
||||
i = oPYA.get_device_count()
|
||||
while i > 0:
|
||||
i -= 1
|
||||
if oPYA.get_device_info_by_index(i)['maxInputChannels'] == 0:
|
||||
continue
|
||||
LOG.info(str(i) \
|
||||
+' ' +oPYA.get_device_info_by_index(i)['name']
|
||||
+' ' +str(oPYA.get_device_info_by_index(i)['defaultSampleRate'])
|
||||
)
|
||||
return 0
|
||||
|
||||
def setup_default_video():
|
||||
default_video = ["-1"]
|
||||
default_video.extend(ts.get_video_indexes())
|
||||
LOG.info(f"Video input choices: {default_video!r}")
|
||||
video = {'device': -1, 'width': 320, 'height': 240, 'x': 0, 'y': 0}
|
||||
video['output_devices'] = default_video
|
||||
return video
|
||||
|
||||
def main_parser():
|
||||
import cv2
|
||||
if not os.path.exists('/proc/sys/net/ipv6'):
|
||||
bIpV6 = 'False'
|
||||
else:
|
||||
bIpV6 = 'True'
|
||||
lIpV6Choices=[bIpV6, 'False']
|
||||
|
||||
audio = setup_default_audio()
|
||||
default_video = setup_default_video()
|
||||
|
||||
logfile = os.path.join(os.environ.get('TMPDIR', '/tmp'), 'toxygen.log')
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--version', action='store_true', help='Prints Toxygen version')
|
||||
parser.add_argument('--clean', action='store_true', help='Delete toxcore libs from libs folder')
|
||||
parser.add_argument('--reset', action='store_true', help='Reset default profile')
|
||||
parser.add_argument('--uri', help='Add specified Tox ID to friends')
|
||||
parser.add_argument('profile', nargs='?', default=None, help='Path to Tox profile')
|
||||
args = parser.parse_args()
|
||||
parser.add_argument('--uri', type=str, default='',
|
||||
help='Add specified Tox ID to friends')
|
||||
parser.add_argument('--logfile', default=logfile,
|
||||
help='Filename for logging')
|
||||
parser.add_argument('--loglevel', type=int, default=logging.INFO,
|
||||
help='Threshold for logging (lower is more) default: 20')
|
||||
parser.add_argument('--proxy_host', '--proxy-host', type=str,
|
||||
# oddball - we want to use '' as a setting
|
||||
default='0.0.0.0',
|
||||
help='proxy host')
|
||||
parser.add_argument('--proxy_port', '--proxy-port', default=0, type=int,
|
||||
help='proxy port')
|
||||
parser.add_argument('--proxy_type', '--proxy-type', default=0, type=int,
|
||||
choices=[0,1,2],
|
||||
help='proxy type 1=https, 2=socks')
|
||||
parser.add_argument('--tcp_port', '--tcp-port', default=0, type=int,
|
||||
help='tcp port')
|
||||
parser.add_argument('--auto_accept_path', '--auto-accept-path', type=str,
|
||||
default=os.path.join(os.environ['HOME'], 'Downloads'),
|
||||
help="auto_accept_path")
|
||||
parser.add_argument('--mode', type=int, default=2,
|
||||
help='Mode: 0=chat 1=chat+audio 2=chat+audio+video default: 0')
|
||||
parser.add_argument('--font', type=str, default="Courier",
|
||||
help='Message font')
|
||||
parser.add_argument('--message_font_size', type=int, default=15,
|
||||
help='Font size in pixels')
|
||||
parser.add_argument('--local_discovery_enabled',type=str,
|
||||
default='False', choices=['True','False'],
|
||||
help='Look on the local lan')
|
||||
parser.add_argument('--udp_enabled',type=str,
|
||||
default='True', choices=['True','False'],
|
||||
help='En/Disable udp')
|
||||
parser.add_argument('--ipv6_enabled',type=str,
|
||||
default=bIpV6, choices=lIpV6Choices,
|
||||
help='En/Disable ipv6')
|
||||
parser.add_argument('--compact_mode',type=str,
|
||||
default='True', choices=['True','False'],
|
||||
help='Compact mode')
|
||||
parser.add_argument('--allow_inline',type=str,
|
||||
default='False', choices=['True','False'],
|
||||
help='Dis/Enable allow_inline')
|
||||
parser.add_argument('--notifications',type=str,
|
||||
default='True', choices=['True','False'],
|
||||
help='Dis/Enable notifications')
|
||||
parser.add_argument('--sound_notifications',type=str,
|
||||
default='True', choices=['True','False'],
|
||||
help='Enable sound notifications')
|
||||
parser.add_argument('--calls_sound',type=str,
|
||||
default='True', choices=['True','False'],
|
||||
help='Enable calls_sound')
|
||||
parser.add_argument('--core_logging',type=str,
|
||||
default='False', choices=['True','False'],
|
||||
help='Dis/Enable Toxcore notifications')
|
||||
parser.add_argument('--hole_punching_enabled',type=str,
|
||||
default='False', choices=['True','False'],
|
||||
help='En/Enable hole punching')
|
||||
parser.add_argument('--dht_announcements_enabled',type=str,
|
||||
default='True', choices=['True','False'],
|
||||
help='En/Disable DHT announcements')
|
||||
parser.add_argument('--save_history',type=str,
|
||||
default='True', choices=['True','False'],
|
||||
help='En/Disable save history')
|
||||
parser.add_argument('--update', type=int, default=0,
|
||||
choices=[0,0],
|
||||
help='Update program (broken)')
|
||||
parser.add_argument('--download_nodes_list',type=str,
|
||||
default='False', choices=['True','False'],
|
||||
help='Download nodes list')
|
||||
parser.add_argument('--nodes_json', type=str,
|
||||
default='')
|
||||
parser.add_argument('--download_nodes_url', type=str,
|
||||
default='https://nodes.tox.chat/json')
|
||||
parser.add_argument('--network', type=str,
|
||||
choices=['main', 'new', 'local', 'newlocal'],
|
||||
default='new')
|
||||
parser.add_argument('--video_input', type=str,
|
||||
default=-1,
|
||||
choices=default_video['output_devices'],
|
||||
help="Video input device number - /dev/video?")
|
||||
parser.add_argument('--audio_input', type=str,
|
||||
default=oPYA.get_default_input_device_info()['name'],
|
||||
choices=audio['input_devices'].values(),
|
||||
help="Audio input device name - aplay -L for help")
|
||||
parser.add_argument('--audio_output', type=str,
|
||||
default=oPYA.get_default_output_device_info()['index'],
|
||||
choices=audio['output_devices'].values(),
|
||||
help="Audio output device number - -1 for help")
|
||||
parser.add_argument('--theme', type=str, default='default',
|
||||
choices=['dark', 'default'],
|
||||
help='Theme - style of UI')
|
||||
parser.add_argument('--sleep', type=str, default='time',
|
||||
# could expand this to tk, gtk, gevent...
|
||||
choices=['qt','gevent','time'],
|
||||
help='Sleep method - one of qt, gevent , time')
|
||||
supported_languages = settings.supported_languages()
|
||||
parser.add_argument('--language', type=str, default='English',
|
||||
choices=supported_languages,
|
||||
help='Languages')
|
||||
parser.add_argument('profile', type=str, nargs='?', default=None,
|
||||
help='Path to Tox profile')
|
||||
return parser
|
||||
|
||||
if args.version:
|
||||
# clean out the unchanged settings so these can override the profile
|
||||
lKEEP_SETTINGS = ['uri',
|
||||
'profile',
|
||||
'loglevel',
|
||||
'logfile',
|
||||
'mode',
|
||||
'audio',
|
||||
'video',
|
||||
'ipv6_enabled',
|
||||
'udp_enabled',
|
||||
'local_discovery_enabled',
|
||||
'theme',
|
||||
'network',
|
||||
'message_font_size',
|
||||
'font',
|
||||
'save_history',
|
||||
'language',
|
||||
'update',
|
||||
'proxy_host',
|
||||
'proxy_type',
|
||||
'proxy_port',
|
||||
'core_logging',
|
||||
'audio',
|
||||
'video'
|
||||
] # , 'nodes_json'
|
||||
lBOOLEANS = [
|
||||
'local_discovery_enabled',
|
||||
'udp_enabled',
|
||||
'ipv6_enabled',
|
||||
'compact_mode',
|
||||
'allow_inline',
|
||||
'notifications',
|
||||
'sound_notifications',
|
||||
'hole_punching_enabled',
|
||||
'dht_announcements_enabled',
|
||||
'save_history',
|
||||
'download_nodes_list'
|
||||
'core_logging',
|
||||
]
|
||||
|
||||
class A(): pass
|
||||
|
||||
global oAPP
|
||||
oAPP = None
|
||||
def main(lArgs):
|
||||
global oPYA
|
||||
from argparse import Namespace
|
||||
parser = main_parser()
|
||||
default_ns = parser.parse_args([])
|
||||
oArgs = parser.parse_args(lArgs)
|
||||
|
||||
if oArgs.version:
|
||||
print_toxygen_version()
|
||||
return
|
||||
return 0
|
||||
|
||||
if args.clean:
|
||||
if oArgs.clean:
|
||||
clean()
|
||||
return
|
||||
return 0
|
||||
|
||||
if args.reset:
|
||||
if oArgs.reset:
|
||||
reset()
|
||||
return
|
||||
return 0
|
||||
|
||||
toxygen = app.App(__version__, args.profile, args.uri)
|
||||
toxygen.main()
|
||||
# if getattr(oArgs, 'network') in ['newlocal', 'localnew']: oArgs.network = 'new'
|
||||
|
||||
# clean out the unchanged settings so these can override the profile
|
||||
for key in default_ns.__dict__.keys():
|
||||
if key in lKEEP_SETTINGS: continue
|
||||
if not hasattr(oArgs, key): continue
|
||||
if getattr(default_ns, key) == getattr(oArgs, key):
|
||||
delattr(oArgs, key)
|
||||
|
||||
for key in lBOOLEANS:
|
||||
if not hasattr(oArgs, key): continue
|
||||
val = getattr(oArgs, key)
|
||||
if type(val) == bool: continue
|
||||
if val in ['False', 'false', '0']:
|
||||
setattr(oArgs, key, False)
|
||||
else:
|
||||
setattr(oArgs, key, True)
|
||||
|
||||
aArgs = A()
|
||||
for key in oArgs.__dict__.keys():
|
||||
setattr(aArgs, key, getattr(oArgs, key))
|
||||
setattr(aArgs, 'video', setup_video(oArgs))
|
||||
aArgs.video = setup_video(oArgs)
|
||||
assert 'video' in aArgs.__dict__
|
||||
|
||||
setattr(aArgs, 'audio', setup_audio(oArgs))
|
||||
aArgs.audio = setup_audio(oArgs)
|
||||
assert 'audio' in aArgs.__dict__
|
||||
|
||||
oArgs = aArgs
|
||||
toxygen = app.App(__version__, oArgs)
|
||||
global oAPP
|
||||
oAPP = toxygen
|
||||
i = toxygen.iMain()
|
||||
return i
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
iRet = 0
|
||||
try:
|
||||
iRet = main(sys.argv[1:])
|
||||
except KeyboardInterrupt:
|
||||
iRet = 0
|
||||
except SystemExit as e:
|
||||
iRet = e
|
||||
except Exception as e:
|
||||
import traceback
|
||||
sys.stderr.write(f"Exception from main {e}" \
|
||||
+'\n' + traceback.format_exc() +'\n' )
|
||||
iRet = 1
|
||||
|
||||
# Exception ignored in: <module 'threading' from '/usr/lib/python3.9/threading.py'>
|
||||
# File "/usr/lib/python3.9/threading.py", line 1428, in _shutdown
|
||||
# lock.acquire()
|
||||
# gevent.exceptions.LoopExit as e:
|
||||
# This operation would block forever
|
||||
sys.stderr.write('Calling sys.exit' +'\n')
|
||||
with ts.ignoreStdout():
|
||||
sys.exit(iRet)
|
||||
|
|
|
@ -38,8 +38,8 @@ class Message:
|
|||
|
||||
MESSAGE_ID = 0
|
||||
|
||||
def __init__(self, message_type, author, time):
|
||||
self._time = time
|
||||
def __init__(self, message_type, author, iTime):
|
||||
self._time = iTime
|
||||
self._type = message_type
|
||||
self._author = author
|
||||
self._widget = None
|
||||
|
@ -66,6 +66,7 @@ class Message:
|
|||
message_id = property(get_message_id)
|
||||
|
||||
def get_widget(self, *args):
|
||||
# FixMe
|
||||
self._widget = self._create_widget(*args)
|
||||
|
||||
return self._widget
|
||||
|
@ -81,6 +82,7 @@ class Message:
|
|||
self._widget.mark_as_sent()
|
||||
|
||||
def _create_widget(self, *args):
|
||||
# overridden
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
|
@ -95,8 +97,8 @@ class TextMessage(Message):
|
|||
Plain text or action message
|
||||
"""
|
||||
|
||||
def __init__(self, message, owner, time, message_type, message_id=0):
|
||||
super().__init__(message_type, owner, time)
|
||||
def __init__(self, message, owner, iTime, message_type, message_id=0):
|
||||
super().__init__(message_type, owner, iTime)
|
||||
self._message = message
|
||||
self._id = message_id
|
||||
|
||||
|
@ -119,8 +121,8 @@ class TextMessage(Message):
|
|||
|
||||
class OutgoingTextMessage(TextMessage):
|
||||
|
||||
def __init__(self, message, owner, time, message_type, tox_message_id=0):
|
||||
super().__init__(message, owner, time, message_type)
|
||||
def __init__(self, message, owner, iTime, message_type, tox_message_id=0):
|
||||
super().__init__(message, owner, iTime, message_type)
|
||||
self._tox_message_id = tox_message_id
|
||||
|
||||
def get_tox_message_id(self):
|
||||
|
@ -134,8 +136,8 @@ class OutgoingTextMessage(TextMessage):
|
|||
|
||||
class GroupChatMessage(TextMessage):
|
||||
|
||||
def __init__(self, id, message, owner, time, message_type, name):
|
||||
super().__init__(id, message, owner, time, message_type)
|
||||
def __init__(self, id, message, owner, iTime, message_type, name):
|
||||
super().__init__(id, message, owner, iTime, message_type)
|
||||
self._user_name = name
|
||||
|
||||
|
||||
|
@ -144,8 +146,8 @@ class TransferMessage(Message):
|
|||
Message with info about file transfer
|
||||
"""
|
||||
|
||||
def __init__(self, author, time, state, size, file_name, friend_number, file_number):
|
||||
super().__init__(MESSAGE_TYPE['FILE_TRANSFER'], author, time)
|
||||
def __init__(self, author, iTime, state, size, file_name, friend_number, file_number):
|
||||
super().__init__(MESSAGE_TYPE['FILE_TRANSFER'], author, iTime)
|
||||
self._state = state
|
||||
self._size = size
|
||||
self._file_name = file_name
|
||||
|
@ -185,10 +187,10 @@ class TransferMessage(Message):
|
|||
|
||||
file_name = property(get_file_name)
|
||||
|
||||
def transfer_updated(self, state, percentage, time):
|
||||
def transfer_updated(self, state, percentage, iTime):
|
||||
self._state = state
|
||||
if self._widget is not None:
|
||||
self._widget.update_transfer_state(state, percentage, time)
|
||||
self._widget.update_transfer_state(state, percentage, iTime)
|
||||
|
||||
def _create_widget(self, *args):
|
||||
return FileTransferItem(self, *args)
|
||||
|
@ -196,9 +198,9 @@ class TransferMessage(Message):
|
|||
|
||||
class UnsentFileMessage(TransferMessage):
|
||||
|
||||
def __init__(self, path, data, time, author, size, friend_number):
|
||||
def __init__(self, path, data, iTime, author, size, friend_number):
|
||||
file_name = os.path.basename(path)
|
||||
super().__init__(author, time, FILE_TRANSFER_STATE['UNSENT'], size, file_name, friend_number, -1)
|
||||
super().__init__(author, iTime, FILE_TRANSFER_STATE['UNSENT'], size, file_name, friend_number, -1)
|
||||
self._data, self._path = data, path
|
||||
|
||||
def get_data(self):
|
||||
|
@ -235,5 +237,5 @@ class InlineImageMessage(Message):
|
|||
|
||||
class InfoMessage(TextMessage):
|
||||
|
||||
def __init__(self, message, time):
|
||||
super().__init__(message, None, time, MESSAGE_TYPE['INFO_MESSAGE'])
|
||||
def __init__(self, message, iTime):
|
||||
super().__init__(message, None, iTime, MESSAGE_TYPE['INFO_MESSAGE'])
|
||||
|
|
|
@ -1,6 +1,12 @@
|
|||
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||
import common.tox_save as tox_save
|
||||
from messenger.messages import *
|
||||
from tests.support_testing import assert_main_thread
|
||||
|
||||
global LOG
|
||||
import logging
|
||||
LOG = logging.getLogger('app.'+__name__)
|
||||
log = lambda x: LOG.info(x)
|
||||
|
||||
class Messenger(tox_save.ToxSave):
|
||||
|
||||
|
@ -76,6 +82,7 @@ class Messenger(tox_save.ToxSave):
|
|||
|
||||
if not text or friend_number < 0:
|
||||
return
|
||||
assert_main_thread()
|
||||
|
||||
friend = self._get_friend_by_number(friend_number)
|
||||
messages = self._split_message(text.encode('utf-8'))
|
||||
|
@ -106,7 +113,7 @@ class Messenger(tox_save.ToxSave):
|
|||
message_id = self._tox.friend_send_message(friend_number, message.type, message.text.encode('utf-8'))
|
||||
message.tox_message_id = message_id
|
||||
except Exception as ex:
|
||||
util.log('Sending pending messages failed with ' + str(ex))
|
||||
LOG.warn('Sending pending messages failed with ' + str(ex))
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Messaging - groups
|
||||
|
@ -142,6 +149,9 @@ class Messenger(tox_save.ToxSave):
|
|||
t = util.get_unix_time()
|
||||
group = self._get_group_by_number(group_number)
|
||||
peer = group.get_peer_by_id(peer_id)
|
||||
if not peer:
|
||||
LOG.warn('FixMe new_group_message group.get_peer_by_id ' + str(peer_id))
|
||||
return
|
||||
text_message = TextMessage(message, MessageAuthor(peer.name, MESSAGE_AUTHOR['GC_PEER']), t, message_type)
|
||||
self._add_message(text_message, group)
|
||||
|
||||
|
@ -158,6 +168,7 @@ class Messenger(tox_save.ToxSave):
|
|||
|
||||
if not text or group_number < 0 or peer_id < 0:
|
||||
return
|
||||
assert_main_thread()
|
||||
|
||||
group_peer_contact = self._contacts_manager.get_or_create_group_peer_contact(group_number, peer_id)
|
||||
group = self._get_group_by_number(group_number)
|
||||
|
@ -182,6 +193,9 @@ class Messenger(tox_save.ToxSave):
|
|||
t = util.get_unix_time()
|
||||
group = self._get_group_by_number(group_number)
|
||||
peer = group.get_peer_by_id(peer_id)
|
||||
if not peer:
|
||||
LOG.warn('FixMe new_group_private_message group.get_peer_by_id ' + str(peer_id))
|
||||
return
|
||||
text_message = TextMessage(message, MessageAuthor(peer.name, MESSAGE_AUTHOR['GC_PEER']),
|
||||
t, message_type)
|
||||
group_peer_contact = self._contacts_manager.get_or_create_group_peer_contact(group_number, peer_id)
|
||||
|
@ -291,10 +305,12 @@ class Messenger(tox_save.ToxSave):
|
|||
self._create_info_message_item(message)
|
||||
|
||||
def _create_info_message_item(self, message):
|
||||
assert_main_thread()
|
||||
self._items_factory.create_message_item(message)
|
||||
self._screen.messages.scrollToBottom()
|
||||
|
||||
def _add_message(self, text_message, contact):
|
||||
assert_main_thread()
|
||||
if self._contacts_manager.is_contact_active(contact): # add message to list
|
||||
self._create_message_item(text_message)
|
||||
self._screen.messages.scrollToBottom()
|
||||
|
|
|
@ -1,15 +1,40 @@
|
|||
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||
import os
|
||||
import threading
|
||||
from PyQt5 import QtGui
|
||||
from wrapper.toxcore_enums_and_consts import *
|
||||
from wrapper.toxav_enums import *
|
||||
from wrapper.tox import bin_to_string
|
||||
import utils.ui as util_ui
|
||||
import utils.util as util
|
||||
import cv2
|
||||
import numpy as np
|
||||
from middleware.threads import invoke_in_main_thread, execute
|
||||
from notifications.tray import tray_notification
|
||||
from notifications.sound import *
|
||||
import threading
|
||||
from datetime import datetime
|
||||
|
||||
iMAX_INT32 = 4294967295
|
||||
def LOG_ERROR(l): print('ERRORc: '+l)
|
||||
def LOG_WARN(l): print('WARNc: '+l)
|
||||
def LOG_INFO(l): print('INFOc: '+l)
|
||||
def LOG_DEBUG(l): print('DEBUGc: '+l)
|
||||
def LOG_TRACE(l): pass # print('TRACE+ '+l)
|
||||
|
||||
global aTIMES
|
||||
aTIMES=dict()
|
||||
def bTooSoon(key, sSlot, fSec=10.0):
|
||||
# rate limiting
|
||||
global aTIMES
|
||||
if sSlot not in aTIMES:
|
||||
aTIMES[sSlot] = dict()
|
||||
OTIME = aTIMES[sSlot]
|
||||
now = datetime.now()
|
||||
if key not in OTIME:
|
||||
OTIME[key] = now
|
||||
return False
|
||||
delta = now - OTIME[key]
|
||||
OTIME[key] = now
|
||||
if delta.total_seconds() < fSec: return True
|
||||
return False
|
||||
|
||||
# TODO: refactoring. Use contact provider instead of manager
|
||||
|
||||
|
@ -17,15 +42,48 @@ import threading
|
|||
# Callbacks - current user
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
global iBYTES
|
||||
iBYTES=0
|
||||
def sProcBytes(sFile=None):
|
||||
global iBYTES
|
||||
if sFile is None:
|
||||
pid = os.getpid()
|
||||
sFile = f"/proc/{pid}/net/softnet_stat"
|
||||
if os.path.exists(sFile):
|
||||
total = 0
|
||||
with open(sFile, 'r') as iFd:
|
||||
for elt in iFd.readlines():
|
||||
i = elt.find(' ')
|
||||
p = int(elt[:i], 16)
|
||||
total = total + p
|
||||
if iBYTES == 0:
|
||||
iBYTES = total
|
||||
return ''
|
||||
diff = total - iBYTES
|
||||
s = f' {diff // 1024} Kbytes'
|
||||
else:
|
||||
s = ''
|
||||
return s
|
||||
|
||||
def self_connection_status(tox, profile):
|
||||
"""
|
||||
Current user changed connection status (offline, TCP, UDP)
|
||||
"""
|
||||
pid = os.getpid()
|
||||
sFile = '/proc/'+str(pid) +'/net/softnet_stat'
|
||||
sSlot = 'self connection status'
|
||||
def wrapped(tox_link, connection, user_data):
|
||||
print('Connection status: ', str(connection))
|
||||
key = f"connection {connection}"
|
||||
if bTooSoon(key, sSlot, 10): return
|
||||
s = sProcBytes(sFile)
|
||||
try:
|
||||
status = tox.self_get_status() if connection != TOX_CONNECTION['NONE'] else None
|
||||
if status:
|
||||
LOG_DEBUG(f"self_connection_status: connection={connection} status={status}" +' '+s)
|
||||
invoke_in_main_thread(profile.set_status, status)
|
||||
except Exception as e:
|
||||
LOG_ERROR(f"self_connection_status: {e}")
|
||||
pass
|
||||
|
||||
return wrapped
|
||||
|
||||
|
@ -36,13 +94,17 @@ def self_connection_status(tox, profile):
|
|||
|
||||
|
||||
def friend_status(contacts_manager, file_transfer_handler, profile, settings):
|
||||
sSlot = 'friend status'
|
||||
def wrapped(tox, friend_number, new_status, user_data):
|
||||
"""
|
||||
Check friend's status (none, busy, away)
|
||||
"""
|
||||
print("Friend's #{} status changed!".format(friend_number))
|
||||
LOG_DEBUG(f"Friend's #{friend_number} status changed")
|
||||
key = f"friend_number {friend_number}"
|
||||
if bTooSoon(key, sSlot, 10): return
|
||||
friend = contacts_manager.get_friend_by_number(friend_number)
|
||||
if friend.status is None and settings['sound_notifications'] and profile.status != TOX_USER_STATUS['BUSY']:
|
||||
if friend.status is None and settings['sound_notifications'] and \
|
||||
profile.status != TOX_USER_STATUS['BUSY']:
|
||||
sound_notification(SOUND_NOTIFICATION['FRIEND_CONNECTION_STATUS'])
|
||||
invoke_in_main_thread(friend.set_status, new_status)
|
||||
|
||||
|
@ -61,7 +123,7 @@ def friend_connection_status(contacts_manager, profile, settings, plugin_loader,
|
|||
"""
|
||||
Check friend's connection status (offline, udp, tcp)
|
||||
"""
|
||||
print("Friend #{} connection status: {}".format(friend_number, new_status))
|
||||
LOG_DEBUG(f"Friend #{friend_number} connection status: {new_status}")
|
||||
friend = contacts_manager.get_friend_by_number(friend_number)
|
||||
if new_status == TOX_CONNECTION['NONE']:
|
||||
invoke_in_main_thread(friend.set_status, None)
|
||||
|
@ -79,11 +141,14 @@ def friend_connection_status(contacts_manager, profile, settings, plugin_loader,
|
|||
|
||||
|
||||
def friend_name(contacts_provider, messenger):
|
||||
sSlot = 'friend_name'
|
||||
def wrapped(tox, friend_number, name, size, user_data):
|
||||
"""
|
||||
Friend changed his name
|
||||
"""
|
||||
print('New name friend #' + str(friend_number))
|
||||
key = f"friend_number={friend_number}"
|
||||
if bTooSoon(key, sSlot, 60): return
|
||||
LOG_DEBUG(f'New name friend #' + str(friend_number))
|
||||
friend = contacts_provider.get_friend_by_number(friend_number)
|
||||
old_name = friend.name
|
||||
new_name = str(name, 'utf-8')
|
||||
|
@ -92,16 +157,19 @@ def friend_name(contacts_provider, messenger):
|
|||
|
||||
return wrapped
|
||||
|
||||
|
||||
def friend_status_message(contacts_manager, messenger):
|
||||
sSlot = 'status_message'
|
||||
def wrapped(tox, friend_number, status_message, size, user_data):
|
||||
"""
|
||||
:return: function for callback friend_status_message. It updates friend's status message
|
||||
and calls window repaint
|
||||
"""
|
||||
friend = contacts_manager.get_friend_by_number(friend_number)
|
||||
key = f"friend_number={friend_number}"
|
||||
if bTooSoon(key, sSlot, 10): return
|
||||
|
||||
invoke_in_main_thread(friend.set_status_message, str(status_message, 'utf-8'))
|
||||
print('User #{} has new status message'.format(friend_number))
|
||||
LOG_DEBUG(f'User #{friend_number} has new status message')
|
||||
invoke_in_main_thread(messenger.send_messages, friend_number)
|
||||
|
||||
return wrapped
|
||||
|
@ -112,6 +180,7 @@ def friend_message(messenger, contacts_manager, profile, settings, window, tray)
|
|||
"""
|
||||
New message from friend
|
||||
"""
|
||||
LOG_DEBUG(f"friend_message #{friend_number}")
|
||||
message = str(message, 'utf-8')
|
||||
invoke_in_main_thread(messenger.new_message, friend_number, message_type, message)
|
||||
if not window.isActiveWindow():
|
||||
|
@ -121,6 +190,7 @@ def friend_message(messenger, contacts_manager, profile, settings, window, tray)
|
|||
if settings['sound_notifications'] and profile.status != TOX_USER_STATUS['BUSY']:
|
||||
sound_notification(SOUND_NOTIFICATION['MESSAGE'])
|
||||
icon = os.path.join(util.get_images_directory(), 'icon_new_messages.png')
|
||||
if tray:
|
||||
invoke_in_main_thread(tray.setIcon, QtGui.QIcon(icon))
|
||||
|
||||
return wrapped
|
||||
|
@ -131,7 +201,7 @@ def friend_request(contacts_manager):
|
|||
"""
|
||||
Called when user get new friend request
|
||||
"""
|
||||
print('Friend request')
|
||||
LOG_DEBUG(f'Friend request')
|
||||
key = ''.join(chr(x) for x in public_key[:TOX_PUBLIC_KEY_SIZE])
|
||||
tox_id = bin_to_string(key, TOX_PUBLIC_KEY_SIZE)
|
||||
invoke_in_main_thread(contacts_manager.process_friend_request, tox_id, str(message, 'utf-8'))
|
||||
|
@ -140,9 +210,12 @@ def friend_request(contacts_manager):
|
|||
|
||||
|
||||
def friend_typing(messenger):
|
||||
sSlot = "friend_typing"
|
||||
def wrapped(tox, friend_number, typing, user_data):
|
||||
key = f"friend_number={friend_number}"
|
||||
if bTooSoon(key, sSlot, 10): return
|
||||
LOG_DEBUG(f"friend_typing #{friend_number}")
|
||||
invoke_in_main_thread(messenger.friend_typing, friend_number, typing)
|
||||
|
||||
return wrapped
|
||||
|
||||
|
||||
|
@ -164,7 +237,7 @@ def tox_file_recv(window, tray, profile, file_transfer_handler, contacts_manager
|
|||
"""
|
||||
def wrapped(tox, friend_number, file_number, file_type, size, file_name, file_name_size, user_data):
|
||||
if file_type == TOX_FILE_KIND['DATA']:
|
||||
print('File')
|
||||
LOG_DEBUG(f'file_transfer_handler File')
|
||||
try:
|
||||
file_name = str(file_name[:file_name_size], 'utf-8')
|
||||
except:
|
||||
|
@ -184,7 +257,7 @@ def tox_file_recv(window, tray, profile, file_transfer_handler, contacts_manager
|
|||
icon = util.join_path(util.get_images_directory(), 'icon_new_messages.png')
|
||||
invoke_in_main_thread(tray.setIcon, QtGui.QIcon(icon))
|
||||
else: # avatar
|
||||
print('Avatar')
|
||||
LOG_DEBUG(f'file_transfer_handler Avatar')
|
||||
invoke_in_main_thread(file_transfer_handler.incoming_avatar,
|
||||
friend_number,
|
||||
file_number,
|
||||
|
@ -259,15 +332,17 @@ def lossy_packet(plugin_loader):
|
|||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
def call_state(calls_manager):
|
||||
def wrapped(toxav, friend_number, mask, user_data):
|
||||
def wrapped(iToxav, friend_number, mask, user_data):
|
||||
"""
|
||||
New call state
|
||||
"""
|
||||
print(friend_number, mask)
|
||||
LOG_DEBUG(f"call_state #{friend_number}")
|
||||
if mask == TOXAV_FRIEND_CALL_STATE['FINISHED'] or mask == TOXAV_FRIEND_CALL_STATE['ERROR']:
|
||||
invoke_in_main_thread(calls_manager.stop_call, friend_number, True)
|
||||
else:
|
||||
calls_manager.toxav_call_state_cb(friend_number, mask)
|
||||
# guessing was calls_manager.
|
||||
#? incoming_call
|
||||
calls_manager._call.toxav_call_state_cb(friend_number, mask)
|
||||
|
||||
return wrapped
|
||||
|
||||
|
@ -277,7 +352,7 @@ def call(calls_manager):
|
|||
"""
|
||||
Incoming call from friend
|
||||
"""
|
||||
print(friend_number, audio, video)
|
||||
LOG_DEBUG(f"Incoming call from {friend_number} {audio} {video}")
|
||||
invoke_in_main_thread(calls_manager.incoming_call, audio, video, friend_number)
|
||||
|
||||
return wrapped
|
||||
|
@ -288,7 +363,9 @@ def callback_audio(calls_manager):
|
|||
"""
|
||||
New audio chunk
|
||||
"""
|
||||
calls_manager.call.audio_chunk(
|
||||
LOG_DEBUG(f"callback_audio #{friend_number}")
|
||||
# guessing was .call
|
||||
calls_manager._call.audio_chunk(
|
||||
bytes(samples[:audio_samples_per_channel * 2 * audio_channels_count]),
|
||||
audio_channels_count,
|
||||
rate)
|
||||
|
@ -324,6 +401,9 @@ def video_receive_frame(toxav, friend_number, width, height, y, u, v, ystride, u
|
|||
|
||||
It can be created from initial y, u, v using slices
|
||||
"""
|
||||
LOG_DEBUG(f"video_receive_frame from {friend_number}")
|
||||
import cv2
|
||||
import numpy as np
|
||||
try:
|
||||
y_size = abs(max(width, abs(ystride)))
|
||||
u_size = abs(max(width // 2, abs(ustride)))
|
||||
|
@ -349,7 +429,8 @@ def video_receive_frame(toxav, friend_number, width, height, y, u, v, ystride, u
|
|||
|
||||
invoke_in_main_thread(cv2.imshow, str(friend_number), frame)
|
||||
except Exception as ex:
|
||||
print(ex)
|
||||
LOG_ERROR(f"video_receive_frame {ex} #{friend_number}")
|
||||
pass
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Callbacks - groups
|
||||
|
@ -361,16 +442,21 @@ def group_message(window, tray, tox, messenger, settings, profile):
|
|||
New message in group chat
|
||||
"""
|
||||
def wrapped(tox_link, group_number, peer_id, message_type, message, length, user_data):
|
||||
LOG_DEBUG(f"group_message #{group_number}")
|
||||
message = str(message[:length], 'utf-8')
|
||||
invoke_in_main_thread(messenger.new_group_message, group_number, message_type, message, peer_id)
|
||||
if window.isActiveWindow():
|
||||
return
|
||||
bl = settings['notify_all_gc'] or profile.name in message
|
||||
name = tox.group_peer_get_name(group_number, peer_id)
|
||||
if settings['notifications'] and profile.status != TOX_USER_STATUS['BUSY'] and (not settings.locked) and bl:
|
||||
invoke_in_main_thread(tray_notification, name, message, tray, window)
|
||||
if settings['sound_notifications'] and bl and profile.status != TOX_USER_STATUS['BUSY']:
|
||||
if settings['sound_notifications'] and bl and \
|
||||
profile.status != TOX_USER_STATUS['BUSY']:
|
||||
sound_notification(SOUND_NOTIFICATION['MESSAGE'])
|
||||
if False and settings['tray_icon']:
|
||||
if settings['notifications'] and \
|
||||
profile.status != TOX_USER_STATUS['BUSY'] and \
|
||||
(not settings.locked) and bl:
|
||||
invoke_in_main_thread(tray_notification, name, message, tray, window)
|
||||
icon = util.join_path(util.get_images_directory(), 'icon_new_messages.png')
|
||||
invoke_in_main_thread(tray.setIcon, QtGui.QIcon(icon))
|
||||
|
||||
|
@ -382,6 +468,7 @@ def group_private_message(window, tray, tox, messenger, settings, profile):
|
|||
New private message in group chat
|
||||
"""
|
||||
def wrapped(tox_link, group_number, peer_id, message_type, message, length, user_data):
|
||||
LOG_DEBUG(f"group_private_message #{group_number}")
|
||||
message = str(message[:length], 'utf-8')
|
||||
invoke_in_main_thread(messenger.new_group_private_message, group_number, message_type, message, peer_id)
|
||||
if window.isActiveWindow():
|
||||
|
@ -400,13 +487,15 @@ def group_private_message(window, tray, tox, messenger, settings, profile):
|
|||
|
||||
def group_invite(window, settings, tray, profile, groups_service, contacts_provider):
|
||||
def wrapped(tox, friend_number, invite_data, length, group_name, group_name_length, user_data):
|
||||
LOG_DEBUG(f"group_invite friend_number={friend_number}")
|
||||
group_name = str(bytes(group_name[:group_name_length]), 'utf-8')
|
||||
invoke_in_main_thread(groups_service.process_group_invite,
|
||||
friend_number, group_name,
|
||||
bytes(invite_data[:length]))
|
||||
if window.isActiveWindow():
|
||||
return
|
||||
if settings['notifications'] and profile.status != TOX_USER_STATUS['BUSY'] and not settings.locked:
|
||||
if settings['notifications'] and \
|
||||
profile.status != TOX_USER_STATUS['BUSY'] and not settings.locked:
|
||||
friend = contacts_provider.get_friend_by_number(friend_number)
|
||||
title = util_ui.tr('New invite to group chat')
|
||||
text = util_ui.tr('{} invites you to group "{}"').format(friend.name, group_name)
|
||||
|
@ -419,6 +508,7 @@ def group_invite(window, settings, tray, profile, groups_service, contacts_provi
|
|||
|
||||
def group_self_join(contacts_provider, contacts_manager, groups_service):
|
||||
def wrapped(tox, group_number, user_data):
|
||||
LOG_DEBUG(f"group_self_join #{group_number}")
|
||||
group = contacts_provider.get_group_by_number(group_number)
|
||||
invoke_in_main_thread(group.set_status, TOX_USER_STATUS['NONE'])
|
||||
invoke_in_main_thread(groups_service.update_group_info, group)
|
||||
|
@ -428,8 +518,15 @@ def group_self_join(contacts_provider, contacts_manager, groups_service):
|
|||
|
||||
|
||||
def group_peer_join(contacts_provider, groups_service):
|
||||
sSlot = "group_peer_join"
|
||||
def wrapped(tox, group_number, peer_id, user_data):
|
||||
key = f"group_peer_join #{group_number} peer_id={peer_id}"
|
||||
if bTooSoon(key, sSlot, 20): return
|
||||
group = contacts_provider.get_group_by_number(group_number)
|
||||
if peer_id > group._peers_limit:
|
||||
LOG_ERROR(key +f" {peer_id} > {group._peers_limit}")
|
||||
return
|
||||
LOG_DEBUG(key)
|
||||
group.add_peer(peer_id)
|
||||
invoke_in_main_thread(groups_service.generate_peers_list)
|
||||
invoke_in_main_thread(groups_service.update_group_info, group)
|
||||
|
@ -439,28 +536,41 @@ def group_peer_join(contacts_provider, groups_service):
|
|||
|
||||
def group_peer_exit(contacts_provider, groups_service, contacts_manager):
|
||||
def wrapped(tox, group_number, peer_id, message, length, user_data):
|
||||
LOG_DEBUG(f"group_peer_exit #{group_number} peer_id={peer_id}")
|
||||
group = contacts_provider.get_group_by_number(group_number)
|
||||
group.remove_peer(peer_id)
|
||||
invoke_in_main_thread(groups_service.generate_peers_list)
|
||||
|
||||
return wrapped
|
||||
|
||||
|
||||
def group_peer_name(contacts_provider, groups_service):
|
||||
def wrapped(tox, group_number, peer_id, name, length, user_data):
|
||||
LOG_DEBUG(f"group_peer_name #{group_number} peer_id={peer_id}")
|
||||
group = contacts_provider.get_group_by_number(group_number)
|
||||
peer = group.get_peer_by_id(peer_id)
|
||||
if peer:
|
||||
peer.name = str(name[:length], 'utf-8')
|
||||
invoke_in_main_thread(groups_service.generate_peers_list)
|
||||
else:
|
||||
# FixMe: known signal to revalidate roles...
|
||||
#_peers = [(p._name, p._peer_id) for p in group.get_peers()]
|
||||
LOG_TRACE(f"remove_peer group {group!r} has no peer_id={peer_id} in _peers!r")
|
||||
return
|
||||
|
||||
return wrapped
|
||||
|
||||
|
||||
def group_peer_status(contacts_provider, groups_service):
|
||||
def wrapped(tox, group_number, peer_id, peer_status, user_data):
|
||||
LOG_DEBUG(f"group_peer_status #{group_number} peer_id={peer_id}")
|
||||
group = contacts_provider.get_group_by_number(group_number)
|
||||
peer = group.get_peer_by_id(peer_id)
|
||||
if peer:
|
||||
peer.status = peer_status
|
||||
else:
|
||||
# _peers = [(p._name, p._peer_id) for p in group.get_peers()]
|
||||
LOG_TRACE(f"remove_peer group {group!r} has no peer_id={peer_id} in _peers!r")
|
||||
# TODO: add info message
|
||||
invoke_in_main_thread(groups_service.generate_peers_list)
|
||||
|
||||
return wrapped
|
||||
|
@ -468,32 +578,62 @@ def group_peer_status(contacts_provider, groups_service):
|
|||
|
||||
def group_topic(contacts_provider):
|
||||
def wrapped(tox, group_number, peer_id, topic, length, user_data):
|
||||
LOG_DEBUG(f"group_topic #{group_number} peer_id={peer_id}")
|
||||
group = contacts_provider.get_group_by_number(group_number)
|
||||
if group:
|
||||
topic = str(topic[:length], 'utf-8')
|
||||
invoke_in_main_thread(group.set_status_message, topic)
|
||||
else:
|
||||
_peers = [(p._name, p._peer_id) for p in group.get_peers()]
|
||||
LOG_WARN(f"group_topic {group!r} has no peer_id={peer_id} in {_peers!r}")
|
||||
# TODO: add info message
|
||||
|
||||
return wrapped
|
||||
|
||||
|
||||
def group_moderation(groups_service, contacts_provider, contacts_manager, messenger):
|
||||
|
||||
def update_peer_role(group, mod_peer_id, peer_id, new_role):
|
||||
peer = group.get_peer_by_id(peer_id)
|
||||
if peer:
|
||||
peer.role = new_role
|
||||
# TODO: add info message
|
||||
else:
|
||||
# FixMe: known signal to revalidate roles...
|
||||
# _peers = [(p._name, p._peer_id) for p in group.get_peers()]
|
||||
LOG_TRACE(f"update_peer_role group {group!r} has no peer_id={peer_id} in _peers!r")
|
||||
# TODO: add info message
|
||||
|
||||
def remove_peer(group, mod_peer_id, peer_id, is_ban):
|
||||
peer = group.get_peer_by_id(peer_id)
|
||||
if peer:
|
||||
contacts_manager.remove_group_peer_by_id(group, peer_id)
|
||||
group.remove_peer(peer_id)
|
||||
else:
|
||||
# FixMe: known signal to revalidate roles...
|
||||
#_peers = [(p._name, p._peer_id) for p in group.get_peers()]
|
||||
LOG_TRACE(f"remove_peer group {group!r} has no peer_id={peer_id} in _peers!r")
|
||||
# TODO: add info message
|
||||
|
||||
# source_peer_number, target_peer_number,
|
||||
def wrapped(tox, group_number, mod_peer_id, peer_id, event_type, user_data):
|
||||
if mod_peer_id == iMAX_INT32 or peer_id == iMAX_INT32:
|
||||
# FixMe: known signal to revalidate roles...
|
||||
return
|
||||
LOG_DEBUG(f"group_moderation #{group_number} mod_id={mod_peer_id} peer_id={peer_id} event_type={event_type}")
|
||||
group = contacts_provider.get_group_by_number(group_number)
|
||||
mod_peer = group.get_peer_by_id(mod_peer_id)
|
||||
if not mod_peer:
|
||||
#_peers = [(p._name, p._peer_id) for p in group.get_peers()]
|
||||
LOG_TRACE(f"remove_peer group {group!r} has no mod_peer_id={mod_peer_id} in _peers!r")
|
||||
return
|
||||
peer = group.get_peer_by_id(peer_id)
|
||||
if not peer:
|
||||
# FixMe: known signal to revalidate roles...
|
||||
#_peers = [(p._name, p._peer_id) for p in group.get_peers()]
|
||||
LOG_TRACE(f"remove_peer group {group!r} has no peer_id={peer_id} in _peers!r")
|
||||
return
|
||||
|
||||
if event_type == TOX_GROUP_MOD_EVENT['KICK']:
|
||||
remove_peer(group, mod_peer_id, peer_id, False)
|
||||
elif event_type == TOX_GROUP_MOD_EVENT['BAN']:
|
||||
remove_peer(group, mod_peer_id, peer_id, True)
|
||||
elif event_type == TOX_GROUP_MOD_EVENT['OBSERVER']:
|
||||
update_peer_role(group, mod_peer_id, peer_id, TOX_GROUP_ROLE['OBSERVER'])
|
||||
elif event_type == TOX_GROUP_MOD_EVENT['USER']:
|
||||
|
@ -509,6 +649,7 @@ def group_moderation(groups_service, contacts_provider, contacts_manager, messen
|
|||
def group_password(contacts_provider):
|
||||
|
||||
def wrapped(tox_link, group_number, password, length, user_data):
|
||||
LOG_DEBUG(f"group_password #{group_number}")
|
||||
password = str(password[:length], 'utf-8')
|
||||
group = contacts_provider.get_group_by_number(group_number)
|
||||
group.password = password
|
||||
|
@ -519,6 +660,7 @@ def group_password(contacts_provider):
|
|||
def group_peer_limit(contacts_provider):
|
||||
|
||||
def wrapped(tox_link, group_number, peer_limit, user_data):
|
||||
LOG_DEBUG(f"group_peer_limit #{group_number}")
|
||||
group = contacts_provider.get_group_by_number(group_number)
|
||||
group.peer_limit = peer_limit
|
||||
|
||||
|
@ -528,6 +670,7 @@ def group_peer_limit(contacts_provider):
|
|||
def group_privacy_state(contacts_provider):
|
||||
|
||||
def wrapped(tox_link, group_number, privacy_state, user_data):
|
||||
LOG_DEBUG(f"group_privacy_state #{group_number}")
|
||||
group = contacts_provider.get_group_by_number(group_number)
|
||||
group.is_private = privacy_state == TOX_GROUP_PRIVACY_STATE['PRIVATE']
|
||||
|
||||
|
@ -540,7 +683,7 @@ def group_privacy_state(contacts_provider):
|
|||
|
||||
def init_callbacks(tox, profile, settings, plugin_loader, contacts_manager,
|
||||
calls_manager, file_transfer_handler, main_window, tray, messenger, groups_service,
|
||||
contacts_provider):
|
||||
contacts_provider, ms=None):
|
||||
"""
|
||||
Initialization of all callbacks.
|
||||
:param tox: Tox instance
|
||||
|
@ -557,6 +700,10 @@ def init_callbacks(tox, profile, settings, plugin_loader, contacts_manager,
|
|||
:param groups_service: GroupsService instance
|
||||
:param contacts_provider: ContactsProvider instance
|
||||
"""
|
||||
global LOG
|
||||
import logging
|
||||
LOG = logging.getLogger('app.'+__name__)
|
||||
|
||||
# self callbacks
|
||||
tox.callback_self_connection_status(self_connection_status(tox, profile))
|
||||
|
||||
|
|
|
@ -1,10 +1,40 @@
|
|||
from bootstrap.bootstrap import *
|
||||
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||
import sys
|
||||
import threading
|
||||
import queue
|
||||
from utils import util
|
||||
import time
|
||||
from PyQt5 import QtCore
|
||||
|
||||
from bootstrap.bootstrap import *
|
||||
from bootstrap.bootstrap import download_nodes_list
|
||||
import tests.support_testing as ts
|
||||
from utils import util
|
||||
|
||||
if 'QtCore' in sys.modules:
|
||||
def qt_sleep(fSec):
|
||||
if fSec > .001:
|
||||
QtCore.QThread.msleep(int(fSec*1000.0))
|
||||
QtCore.QCoreApplication.processEvents()
|
||||
sleep = qt_sleep
|
||||
elif 'gevent' in sys.modules:
|
||||
import gevent
|
||||
sleep = gevent.sleep
|
||||
else:
|
||||
import time
|
||||
sleep = time.sleep
|
||||
import time
|
||||
sleep = time.sleep
|
||||
|
||||
# LOG=util.log
|
||||
global LOG
|
||||
import logging
|
||||
LOG = logging.getLogger('app.'+'threads')
|
||||
# log = lambda x: LOG.info(x)
|
||||
|
||||
def LOG_ERROR(l): print('ERRORt: '+l)
|
||||
def LOG_WARN(l): print('WARNt: '+l)
|
||||
def LOG_INFO(l): print('INFOt: '+l)
|
||||
def LOG_DEBUG(l): print('DEBUGt: '+l)
|
||||
def LOG_TRACE(l): pass # print('TRACE+ '+l)
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Base threads
|
||||
|
@ -12,25 +42,45 @@ from PyQt5 import QtCore
|
|||
|
||||
class BaseThread(threading.Thread):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
def __init__(self, name=None, target=None):
|
||||
self._stop_thread = False
|
||||
if name:
|
||||
super().__init__(name=name, target=target)
|
||||
else:
|
||||
super().__init__(target=target)
|
||||
|
||||
def stop_thread(self):
|
||||
def stop_thread(self, timeout=-1):
|
||||
self._stop_thread = True
|
||||
self.join()
|
||||
|
||||
if timeout < 0:
|
||||
timeout = ts.iTHREAD_TIMEOUT
|
||||
i = 0
|
||||
while i < ts.iTHREAD_JOINS:
|
||||
self.join(timeout)
|
||||
if not self.is_alive(): break
|
||||
i = i + 1
|
||||
else:
|
||||
LOG_WARN(f"BaseThread {self.name} BLOCKED")
|
||||
|
||||
class BaseQThread(QtCore.QThread):
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self, name=None):
|
||||
# NO name=name
|
||||
super().__init__()
|
||||
self._stop_thread = False
|
||||
self.name = str(id(self))
|
||||
|
||||
def stop_thread(self):
|
||||
def stop_thread(self, timeout=-1):
|
||||
self._stop_thread = True
|
||||
self.wait()
|
||||
|
||||
if timeout < 0:
|
||||
timeout = ts.iTHREAD_TIMEOUT
|
||||
i = 0
|
||||
while i < ts.iTHREAD_JOINS:
|
||||
self.wait(timeout)
|
||||
if not self.isRunning(): break
|
||||
i = i + 1
|
||||
sleep(ts.iTHREAD_TIMEOUT)
|
||||
else:
|
||||
LOG_WARN(f"BaseQThread {self.name} BLOCKED")
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
# Toxcore threads
|
||||
|
@ -38,45 +88,51 @@ class BaseQThread(QtCore.QThread):
|
|||
|
||||
class InitThread(BaseThread):
|
||||
|
||||
def __init__(self, tox, plugin_loader, settings, is_first_start):
|
||||
super().__init__()
|
||||
self._tox, self._plugin_loader, self._settings = tox, plugin_loader, settings
|
||||
def __init__(self, tox, plugin_loader, settings, app, is_first_start):
|
||||
super().__init__(name='InitThread')
|
||||
self._tox = tox
|
||||
self._plugin_loader = plugin_loader
|
||||
self._settings = settings
|
||||
self._app = app
|
||||
self._is_first_start = is_first_start
|
||||
|
||||
def run(self):
|
||||
LOG_DEBUG('InitThread run: ')
|
||||
try:
|
||||
if self._is_first_start:
|
||||
# download list of nodes if needed
|
||||
download_nodes_list(self._settings)
|
||||
# start plugins
|
||||
if self._settings['download_nodes_list']:
|
||||
LOG_INFO('downloading list of nodes')
|
||||
download_nodes_list(self._settings, oArgs=self._app._args)
|
||||
|
||||
if False:
|
||||
lNodes = ts.generate_nodes()
|
||||
LOG_INFO(f"bootstrapping {len(lNodes)!s} nodes")
|
||||
for data in lNodes:
|
||||
if self._stop_thread:
|
||||
return
|
||||
self._tox.bootstrap(*data)
|
||||
self._tox.add_tcp_relay(*data)
|
||||
else:
|
||||
LOG_INFO(f"calling test_net nodes")
|
||||
threading.Timer(1.0,
|
||||
self._app.test_net,
|
||||
args=list(),
|
||||
kwargs=dict(lElts=None, oThread=self, iMax=2)
|
||||
).start()
|
||||
|
||||
if self._is_first_start:
|
||||
LOG_INFO('starting plugins')
|
||||
self._plugin_loader.load()
|
||||
|
||||
# bootstrap
|
||||
try:
|
||||
for data in generate_nodes():
|
||||
if self._stop_thread:
|
||||
return
|
||||
self._tox.bootstrap(*data)
|
||||
self._tox.add_tcp_relay(*data)
|
||||
except:
|
||||
except Exception as e:
|
||||
LOG_DEBUG(f"InitThread run: ERROR {e}")
|
||||
pass
|
||||
|
||||
for _ in range(10):
|
||||
for _ in range(ts.iTHREAD_JOINS):
|
||||
if self._stop_thread:
|
||||
return
|
||||
time.sleep(1)
|
||||
|
||||
while not self._tox.self_get_connection_status():
|
||||
try:
|
||||
for data in generate_nodes(None):
|
||||
if self._stop_thread:
|
||||
sleep(ts.iTHREAD_SLEEP)
|
||||
return
|
||||
self._tox.bootstrap(*data)
|
||||
self._tox.add_tcp_relay(*data)
|
||||
except:
|
||||
pass
|
||||
finally:
|
||||
time.sleep(5)
|
||||
|
||||
|
||||
class ToxIterateThread(BaseQThread):
|
||||
|
||||
|
@ -85,21 +141,27 @@ class ToxIterateThread(BaseQThread):
|
|||
self._tox = tox
|
||||
|
||||
def run(self):
|
||||
LOG_DEBUG('ToxIterateThread run: ')
|
||||
while not self._stop_thread:
|
||||
try:
|
||||
iMsec = self._tox.iteration_interval()
|
||||
self._tox.iterate()
|
||||
time.sleep(self._tox.iteration_interval() / 1000)
|
||||
except Exception as e:
|
||||
# Fatal Python error: Segmentation fault
|
||||
LOG_ERROR('ToxIterateThread run: {e}')
|
||||
sleep(iMsec / 1000)
|
||||
|
||||
|
||||
class ToxAVIterateThread(BaseQThread):
|
||||
|
||||
def __init__(self, toxav):
|
||||
super().__init__()
|
||||
self._toxav = toxav
|
||||
|
||||
def run(self):
|
||||
LOG_DEBUG('ToxAVIterateThread run: ')
|
||||
while not self._stop_thread:
|
||||
self._toxav.iterate()
|
||||
time.sleep(self._toxav.iteration_interval() / 1000)
|
||||
sleep(self._toxav.iteration_interval() / 1000)
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------------------------------------------
|
||||
|
@ -109,7 +171,7 @@ class ToxAVIterateThread(BaseQThread):
|
|||
class FileTransfersThread(BaseQThread):
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
super().__init__('FileTransfers')
|
||||
self._queue = queue.Queue()
|
||||
self._timeout = 0.01
|
||||
|
||||
|
@ -124,14 +186,12 @@ class FileTransfersThread(BaseQThread):
|
|||
except queue.Empty:
|
||||
pass
|
||||
except queue.Full:
|
||||
util.log('Queue is full in _thread')
|
||||
LOG_WARN('Queue is full in _thread')
|
||||
except Exception as ex:
|
||||
util.log('Exception in _thread: ' + str(ex))
|
||||
LOG_ERROR('in _thread: ' + str(ex))
|
||||
|
||||
|
||||
_thread = FileTransfersThread()
|
||||
|
||||
|
||||
def start_file_transfer_thread():
|
||||
_thread.start()
|
||||
|
||||
|
|
|
@ -1,27 +1,70 @@
|
|||
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||
import user_data.settings
|
||||
import wrapper.tox
|
||||
import wrapper.toxcore_enums_and_consts as enums
|
||||
import ctypes
|
||||
import traceback
|
||||
import os
|
||||
|
||||
global LOG
|
||||
import logging
|
||||
LOG = logging.getLogger('app.'+'tox_factory')
|
||||
def LOG_DEBUG(l): print('DEBUGf: '+l)
|
||||
def LOG_LOG(l): print('TRACf: '+l)
|
||||
|
||||
def tox_factory(data=None, settings=None):
|
||||
from ctypes import *
|
||||
from utils import util
|
||||
from utils import ui as util_ui
|
||||
|
||||
def tox_log_cb(iTox, level, file, line, func, message, *args):
|
||||
"""
|
||||
* @param level The severity of the log message.
|
||||
* @param file The source file from which the message originated.
|
||||
* @param line The source line from which the message originated.
|
||||
* @param func The function from which the message originated.
|
||||
* @param message The log message.
|
||||
* @param user_data The user data pointer passed to tox_new in options.
|
||||
"""
|
||||
file = str(file, 'UTF-8')
|
||||
func = str(func, 'UTF-8')
|
||||
message = str(message, 'UTF-8')
|
||||
if file == 'network.c' and line == 660: return
|
||||
# root WARNING 3network.c#944:b'send_packet'attempted to send message with network family 10 (probably IPv6) on IPv4 socket
|
||||
if file == 'network.c' and line == 944: return
|
||||
message = f"{file}#{line}:{func} {message}"
|
||||
LOG_LOG(# 'TRAC: ' +
|
||||
message)
|
||||
|
||||
def tox_factory(data=None, settings=None, args=None, app=None):
|
||||
"""
|
||||
:param data: user data from .tox file. None = no saved data, create new profile
|
||||
:param settings: current profile settings. None = default settings will be used
|
||||
:return: new tox instance
|
||||
"""
|
||||
if settings is None:
|
||||
if not settings:
|
||||
LOG.warn("tox_factory using get_default_settings")
|
||||
settings = user_data.settings.Settings.get_default_settings()
|
||||
else:
|
||||
user_data.settings.clean_settings(settings)
|
||||
|
||||
try:
|
||||
tox_options = wrapper.tox.Tox.options_new()
|
||||
tox_options.contents.ipv6_enabled = settings['ipv6_enabled']
|
||||
tox_options.contents.udp_enabled = settings['udp_enabled']
|
||||
tox_options.contents.proxy_type = settings['proxy_type']
|
||||
tox_options.contents.proxy_type = int(settings['proxy_type'])
|
||||
if type(settings['proxy_host']) == str:
|
||||
tox_options.contents.proxy_host = bytes(settings['proxy_host'],'UTF-8')
|
||||
tox_options.contents.proxy_port = settings['proxy_port']
|
||||
elif type(settings['proxy_host']) == bytes:
|
||||
tox_options.contents.proxy_host = settings['proxy_host']
|
||||
else:
|
||||
tox_options.contents.proxy_host = b''
|
||||
tox_options.contents.proxy_port = int(settings['proxy_port'])
|
||||
tox_options.contents.start_port = settings['start_port']
|
||||
tox_options.contents.end_port = settings['end_port']
|
||||
tox_options.contents.tcp_port = settings['tcp_port']
|
||||
tox_options.contents.local_discovery_enabled = settings['lan_discovery']
|
||||
tox_options.contents.local_discovery_enabled = settings['local_discovery_enabled']
|
||||
tox_options.contents.dht_announcements_enabled = settings['dht_announcements_enabled']
|
||||
tox_options.contents.hole_punching_enabled = settings['hole_punching_enabled']
|
||||
if data: # load existing profile
|
||||
tox_options.contents.savedata_type = enums.TOX_SAVEDATA_TYPE['TOX_SAVE']
|
||||
tox_options.contents.savedata_data = ctypes.c_char_p(data)
|
||||
|
@ -31,4 +74,29 @@ def tox_factory(data=None, settings=None):
|
|||
tox_options.contents.savedata_data = None
|
||||
tox_options.contents.savedata_length = 0
|
||||
|
||||
return wrapper.tox.Tox(tox_options)
|
||||
# overrides
|
||||
tox_options.contents.local_discovery_enabled = False
|
||||
tox_options.contents.ipv6_enabled = False
|
||||
tox_options.contents.hole_punching_enabled = False
|
||||
|
||||
LOG.debug("wrapper.tox.Tox settings: " +repr(settings))
|
||||
|
||||
if tox_options._options_pointer:
|
||||
c_callback = CFUNCTYPE(None, c_void_p, c_int, c_char_p, c_int, c_char_p, c_char_p, c_void_p)
|
||||
tox_options.self_logger_cb = c_callback(tox_log_cb)
|
||||
wrapper.tox.Tox.libtoxcore.tox_options_set_log_callback(
|
||||
tox_options._options_pointer,
|
||||
tox_options.self_logger_cb)
|
||||
else:
|
||||
logging.warn("No tox_options._options_pointer to add self_logger_cb" )
|
||||
|
||||
retval = wrapper.tox.Tox(tox_options)
|
||||
except Exception as e:
|
||||
if app and hasattr(app, '_log'):
|
||||
app._log(f"ERROR: wrapper.tox.Tox failed: {e}")
|
||||
LOG.warn(traceback.format_exc())
|
||||
raise
|
||||
|
||||
if app and hasattr(app, '_log'):
|
||||
app._log("DEBUG: wrapper.tox.Tox succeeded")
|
||||
return retval
|
||||
|
|
|
@ -1,20 +1,40 @@
|
|||
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||
import json
|
||||
import urllib.request
|
||||
import utils.util as util
|
||||
from PyQt5 import QtNetwork, QtCore
|
||||
try:
|
||||
import requests
|
||||
except ImportError:
|
||||
requests = None
|
||||
|
||||
global LOG
|
||||
import logging
|
||||
LOG = logging.getLogger('app.'+__name__)
|
||||
|
||||
class ToxDns:
|
||||
|
||||
def __init__(self, settings):
|
||||
def __init__(self, settings, log=None):
|
||||
self._settings = settings
|
||||
self._log = log
|
||||
|
||||
@staticmethod
|
||||
def _send_request(url, data):
|
||||
if requests:
|
||||
LOG.info('send_request loading with requests: ' + str(url))
|
||||
headers = dict()
|
||||
headers['Content-Type'] = 'application/json'
|
||||
req = requests.get(url, headers=headers)
|
||||
if req.status_code < 300:
|
||||
retval = req.content
|
||||
else:
|
||||
raise LookupError(str(req.status_code))
|
||||
else:
|
||||
req = urllib.request.Request(url)
|
||||
req.add_header('Content-Type', 'application/json')
|
||||
response = urllib.request.urlopen(req, bytes(json.dumps(data), 'utf-8'))
|
||||
res = json.loads(str(response.read(), 'utf-8'))
|
||||
retval = response.read()
|
||||
res = json.loads(str(retval, 'utf-8'))
|
||||
if not res['c']:
|
||||
return res['tox_id']
|
||||
else:
|
||||
|
@ -29,12 +49,25 @@ class ToxDns:
|
|||
site = email.split('@')[1]
|
||||
data = {"action": 3, "name": "{}".format(email)}
|
||||
urls = ('https://{}/api'.format(site), 'http://{}/api'.format(site))
|
||||
if not self._settings['proxy_type']: # no proxy
|
||||
if requests:
|
||||
for url in urls:
|
||||
LOG.info('TOX nodes loading with requests: ' + str(url))
|
||||
try:
|
||||
headers = dict()
|
||||
headers['Content-Type'] = 'application/json'
|
||||
req = requests.get(url, headers=headers)
|
||||
if req.status_code < 300:
|
||||
result = req.content
|
||||
return result
|
||||
except Exception as ex:
|
||||
LOG.error('ERROR: TOX DNS loading error with requests: ' + str(ex))
|
||||
|
||||
elif not self._settings['proxy_type']: # no proxy
|
||||
for url in urls:
|
||||
try:
|
||||
return self._send_request(url, data)
|
||||
except Exception as ex:
|
||||
util.log('TOX DNS ERROR: ' + str(ex))
|
||||
LOG.error('ERROR: TOX DNS ' + str(ex))
|
||||
else: # proxy
|
||||
netman = QtNetwork.QNetworkAccessManager()
|
||||
proxy = QtNetwork.QNetworkProxy()
|
||||
|
@ -60,6 +93,6 @@ class ToxDns:
|
|||
if not result['c']:
|
||||
return result['tox_id']
|
||||
except Exception as ex:
|
||||
util.log('TOX DNS ERROR: ' + str(ex))
|
||||
LOG.error('ERROR: TOX DNS ' + str(ex))
|
||||
|
||||
return None # error
|
||||
|
|
|
@ -3,6 +3,9 @@ import wave
|
|||
import pyaudio
|
||||
import os.path
|
||||
|
||||
global LOG
|
||||
import logging
|
||||
LOG = logging.getLogger('app.'+__name__)
|
||||
|
||||
SOUND_NOTIFICATION = {
|
||||
'MESSAGE': 0,
|
||||
|
@ -25,9 +28,19 @@ class AudioFile:
|
|||
|
||||
def play(self):
|
||||
data = self.wf.readframes(self.chunk)
|
||||
try:
|
||||
while data:
|
||||
self.stream.write(data)
|
||||
data = self.wf.readframes(self.chunk)
|
||||
except Exception as e:
|
||||
LOG.error(f"Error during AudioFile play {e!s}")
|
||||
LOG.debug("Error during AudioFile play " \
|
||||
+' rate=' +str(self.wf.getframerate()) \
|
||||
+ 'format=' +str(self.p.get_format_from_width(self.wf.getsampwidth())) \
|
||||
+' channels=' +str(self.wf.getnchannels()) \
|
||||
)
|
||||
|
||||
raise
|
||||
|
||||
def close(self):
|
||||
self.stream.close()
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
|
||||
import utils.util as util
|
||||
import os
|
||||
import importlib
|
||||
|
@ -5,6 +6,14 @@ import inspect
|
|||
import plugins.plugin_super_class as pl
|
||||
import sys
|
||||
|
||||
# LOG=util.log
|
||||
global LOG
|
||||
import logging
|
||||
LOG = logging.getLogger('plugin_support')
|
||||
def trace(msg, *args, **kwargs): LOG._log(0, msg, [])
|
||||
LOG.trace = trace
|
||||
|
||||
log = lambda x: LOG.info(x)
|
||||
|
||||
class Plugin:
|
||||
|
||||
|
@ -46,38 +55,49 @@ class PluginLoader:
|
|||
"""
|
||||
path = util.get_plugins_directory()
|
||||
if not os.path.exists(path):
|
||||
util.log('Plugin dir not found')
|
||||
self._app._LOG('WARN: Plugin directory not found: ' + path)
|
||||
return
|
||||
else:
|
||||
|
||||
sys.path.append(path)
|
||||
files = [f for f in os.listdir(path) if os.path.isfile(os.path.join(path, f))]
|
||||
for fl in files:
|
||||
if fl in ('plugin_super_class.py', '__init__.py') or not fl.endswith('.py'):
|
||||
continue
|
||||
name = fl[:-3] # module name without .py
|
||||
base_name = fl[:-3] # module name without .py
|
||||
try:
|
||||
module = importlib.import_module(name) # import plugin
|
||||
except ImportError:
|
||||
util.log('Import error in module ' + name)
|
||||
module = importlib.import_module(base_name) # import plugin
|
||||
LOG.trace('Imported module: ' +base_name +' file: ' +fl)
|
||||
except ImportError as e:
|
||||
LOG.warn(f"Import error: {e}" +' file: ' +fl)
|
||||
continue
|
||||
except Exception as ex:
|
||||
util.log('Exception in module ' + name + ' Exception: ' + str(ex))
|
||||
LOG.error('importing ' + base_name + ' Exception: ' + str(ex))
|
||||
continue
|
||||
for elem in dir(module):
|
||||
obj = getattr(module, elem)
|
||||
# looking for plugin class in module
|
||||
if not inspect.isclass(obj) or not hasattr(obj, 'is_plugin') or not obj.is_plugin:
|
||||
continue
|
||||
print('Plugin', elem)
|
||||
try: # create instance of plugin class
|
||||
instance = obj(self._app)
|
||||
is_active = instance.get_short_name() in self._settings['plugins']
|
||||
instance = obj(self._app) # name, short_name, app
|
||||
# needed by bday...
|
||||
instance._profile=self._app._ms._profile
|
||||
instance._settings=self._settings
|
||||
short_name = instance.get_short_name()
|
||||
is_active = short_name in self._settings['plugins']
|
||||
if is_active:
|
||||
try:
|
||||
instance.start()
|
||||
self._app.LOG('INFO: Started Plugin ' +short_name)
|
||||
except Exception as e:
|
||||
self._app.LOG.error(f"Starting Plugin ' +short_name +' {e}")
|
||||
# else: LOG.info('Defined Plugin ' +short_name)
|
||||
except Exception as ex:
|
||||
util.log('Exception in module ' + name + ' Exception: ' + str(ex))
|
||||
LOG.error('in module ' + short_name + ' Exception: ' + str(ex))
|
||||
continue
|
||||
self._plugins[instance.get_short_name()] = Plugin(instance, is_active)
|
||||
short_name = instance.get_short_name()
|
||||
self._plugins[short_name] = Plugin(instance, is_active)
|
||||
LOG.info('Added plugin: ' +short_name +' from file: ' +fl)
|
||||
break
|
||||
|
||||
def callback_lossless(self, friend_number, data):
|
||||
|
@ -126,7 +146,13 @@ class PluginLoader:
|
|||
"""
|
||||
Return window or None for specified plugin
|
||||
"""
|
||||
try:
|
||||
if key in self._plugins and hasattr(self._plugins[key], 'instance'):
|
||||
return self._plugins[key].instance.get_window()
|
||||
except Exception as e:
|
||||
self._app.LOG('WARN: ' +key +' _plugins no slot instance: ' +str(e))
|
||||
|
||||
return None
|
||||
|
||||
def toggle_plugin(self, key):
|
||||
"""
|
||||
|
@ -162,6 +188,7 @@ class PluginLoader:
|
|||
for plugin in self._plugins.values():
|
||||
if not plugin.is_active:
|
||||
continue
|
||||
|
||||
try:
|
||||
result.extend(plugin.instance.get_menu(num))
|
||||
except:
|
||||
|
@ -173,6 +200,10 @@ class PluginLoader:
|
|||
for plugin in self._plugins.values():
|
||||
if not plugin.is_active:
|
||||
continue
|
||||
if not hasattr(plugin.instance, 'get_message_menu'):
|
||||
name = plugin.instance.get_short_name()
|
||||
self._app.LOG('WARN: get_message_menu not found: ' + name)
|
||||
continue
|
||||
try:
|
||||
result.extend(plugin.instance.get_message_menu(menu, selected_text))
|
||||
except:
|
||||
|
@ -189,6 +220,11 @@ class PluginLoader:
|
|||
del self._plugins[key]
|
||||
|
||||
def reload(self):
|
||||
print('Reloading plugins')
|
||||
path = util.get_plugins_directory()
|
||||
if not os.path.exists(path):
|
||||
self._app.LOG('WARN: Plugin directory not found: ' + path)
|
||||
return
|
||||
|
||||
self.stop()
|
||||
self._app.LOG('INFO: Reloading plugins from ' +path)
|
||||
self.load()
|
||||
|
|
|
@ -4,6 +4,11 @@ import os
|
|||
from collections import OrderedDict
|
||||
from PyQt5 import QtCore
|
||||
|
||||
# LOG=util.log
|
||||
global LOG
|
||||
import logging
|
||||
LOG = logging.getLogger('app.'+__name__)
|
||||
log = lambda x: LOG.info(x)
|
||||
|
||||
class SmileyLoader:
|
||||
"""
|
||||
|
@ -31,7 +36,7 @@ class SmileyLoader:
|
|||
self._smileys = json.loads(fl.read())
|
||||
fl.seek(0)
|
||||
tmp = json.loads(fl.read(), object_pairs_hook=OrderedDict)
|
||||
print('Smiley pack {} loaded'.format(pack_name))
|
||||
LOG.info('Smiley pack {} loaded'.format(pack_name))
|
||||
keys, values, self._list = [], [], []
|
||||
for key, value in tmp.items():
|
||||
value = util.join_path(self.get_smileys_path(), value)
|
||||
|
@ -42,7 +47,7 @@ class SmileyLoader:
|
|||
except Exception as ex:
|
||||
self._smileys = {}
|
||||
self._list = []
|
||||
print('Smiley pack {} was not loaded. Error: {}'.format(pack_name, ex))
|
||||
LOG.error('Smiley pack {} was not loaded. Error: {}'.format(pack_name, str(ex)))
|
||||
|
||||
def get_smileys_path(self):
|
||||
return util.join_path(util.get_smileys_directory(), self._curr_pack) if self._curr_pack is not None else None
|
||||
|
|
BIN
toxygen/stickers/tox/black.png
Executable file → Normal file
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 1.9 KiB |
BIN
toxygen/stickers/tox/red.png
Executable file → Normal file
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.6 KiB |
BIN
toxygen/stickers/tox/tox_logo.png
Executable file → Normal file
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 16 KiB |
BIN
toxygen/stickers/tox/tox_logo_1.png
Executable file → Normal file
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
BIN
toxygen/stickers/tox/white.png
Executable file → Normal file
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.4 KiB |
BIN
toxygen/styles/rc/Hmovetoolbar.png
Executable file → Normal file
Before Width: | Height: | Size: 220 B After Width: | Height: | Size: 322 B |
BIN
toxygen/styles/rc/Hsepartoolbar.png
Executable file → Normal file
Before Width: | Height: | Size: 172 B After Width: | Height: | Size: 331 B |
BIN
toxygen/styles/rc/Vmovetoolbar.png
Executable file → Normal file
Before Width: | Height: | Size: 228 B After Width: | Height: | Size: 361 B |
BIN
toxygen/styles/rc/Vsepartoolbar.png
Executable file → Normal file
Before Width: | Height: | Size: 187 B After Width: | Height: | Size: 348 B |
BIN
toxygen/styles/rc/branch_closed-on.png
Executable file → Normal file
Before Width: | Height: | Size: 147 B After Width: | Height: | Size: 276 B |
BIN
toxygen/styles/rc/branch_closed.png
Executable file → Normal file
Before Width: | Height: | Size: 160 B After Width: | Height: | Size: 305 B |
BIN
toxygen/styles/rc/branch_open-on.png
Executable file → Normal file
Before Width: | Height: | Size: 150 B After Width: | Height: | Size: 273 B |
BIN
toxygen/styles/rc/branch_open.png
Executable file → Normal file
Before Width: | Height: | Size: 166 B After Width: | Height: | Size: 311 B |
BIN
toxygen/styles/rc/checkbox_checked.png
Executable file → Normal file
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 411 B |
BIN
toxygen/styles/rc/checkbox_checked_disabled.png
Executable file → Normal file
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 411 B |
BIN
toxygen/styles/rc/checkbox_checked_focus.png
Executable file → Normal file
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 411 B |
BIN
toxygen/styles/rc/checkbox_indeterminate.png
Executable file → Normal file
Before Width: | Height: | Size: 493 B After Width: | Height: | Size: 580 B |
BIN
toxygen/styles/rc/checkbox_indeterminate_disabled.png
Executable file → Normal file
Before Width: | Height: | Size: 492 B After Width: | Height: | Size: 580 B |
BIN
toxygen/styles/rc/checkbox_indeterminate_focus.png
Executable file → Normal file
Before Width: | Height: | Size: 514 B After Width: | Height: | Size: 580 B |
BIN
toxygen/styles/rc/checkbox_unchecked.png
Executable file → Normal file
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 358 B |
BIN
toxygen/styles/rc/checkbox_unchecked_disabled.png
Executable file → Normal file
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 358 B |
BIN
toxygen/styles/rc/checkbox_unchecked_focus.png
Executable file → Normal file
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 358 B |
BIN
toxygen/styles/rc/close-hover.png
Executable file → Normal file
Before Width: | Height: | Size: 598 B After Width: | Height: | Size: 542 B |
BIN
toxygen/styles/rc/close-pressed.png
Executable file → Normal file
Before Width: | Height: | Size: 598 B After Width: | Height: | Size: 542 B |
BIN
toxygen/styles/rc/close.png
Executable file → Normal file
Before Width: | Height: | Size: 586 B After Width: | Height: | Size: 680 B |
BIN
toxygen/styles/rc/down_arrow.png
Executable file → Normal file
Before Width: | Height: | Size: 165 B After Width: | Height: | Size: 310 B |
BIN
toxygen/styles/rc/down_arrow_disabled.png
Executable file → Normal file
Before Width: | Height: | Size: 166 B After Width: | Height: | Size: 311 B |
BIN
toxygen/styles/rc/left_arrow.png
Executable file → Normal file
Before Width: | Height: | Size: 166 B After Width: | Height: | Size: 311 B |
BIN
toxygen/styles/rc/left_arrow_disabled.png
Executable file → Normal file
Before Width: | Height: | Size: 166 B After Width: | Height: | Size: 311 B |
BIN
toxygen/styles/rc/radio_checked.png
Executable file → Normal file
Before Width: | Height: | Size: 933 B After Width: | Height: | Size: 930 B |
BIN
toxygen/styles/rc/radio_checked_disabled.png
Executable file → Normal file
Before Width: | Height: | Size: 933 B After Width: | Height: | Size: 930 B |
BIN
toxygen/styles/rc/radio_checked_focus.png
Executable file → Normal file
Before Width: | Height: | Size: 933 B After Width: | Height: | Size: 930 B |
BIN
toxygen/styles/rc/radio_unchecked.png
Executable file → Normal file
Before Width: | Height: | Size: 724 B After Width: | Height: | Size: 786 B |
BIN
toxygen/styles/rc/radio_unchecked_disabled.png
Executable file → Normal file
Before Width: | Height: | Size: 724 B After Width: | Height: | Size: 786 B |
BIN
toxygen/styles/rc/radio_unchecked_focus.png
Executable file → Normal file
Before Width: | Height: | Size: 724 B After Width: | Height: | Size: 786 B |
BIN
toxygen/styles/rc/right_arrow.png
Executable file → Normal file
Before Width: | Height: | Size: 160 B After Width: | Height: | Size: 305 B |
BIN
toxygen/styles/rc/right_arrow_disabled.png
Executable file → Normal file
Before Width: | Height: | Size: 160 B After Width: | Height: | Size: 305 B |
BIN
toxygen/styles/rc/sizegrip.png
Executable file → Normal file
Before Width: | Height: | Size: 129 B After Width: | Height: | Size: 285 B |