toxygen/toxygen/user_data/settings.py

416 lines
14 KiB
Python

# -*- mode: python; indent-tabs-mode: nil; py-indent-offset: 4; coding: utf-8 -*-
import os
from platform import system
import json
from pprint import pprint
from utils.util import *
from utils.util import log, join_path
from common.event import Event
import utils.ui as util_ui
import utils.util as util_utils
import user_data
import wrapper_tests.support_testing as ts
global LOG
import logging
LOG = logging.getLogger('settings')
def merge_args_into_settings(args, settings):
if args:
print(repr(args.__dict__.keys()))
if not hasattr(args, 'audio'):
LOG.warn('No audio ' +repr(args))
settings['audio'] = getattr(args, 'audio')
if not hasattr(args, 'video'):
LOG.warn('No video ' +repr(args))
settings['video'] = getattr(args, 'video')
for key in settings.keys():
# proxy_type proxy_port proxy_host
not_key = 'not_' +key
if hasattr(args, key):
val = getattr(args, key)
if type(val) == bytes:
# proxy_host - ascii?
# filenames - ascii?
val = str(val, 'UTF-8')
settings[key] = val
elif hasattr(args, not_key):
val = not getattr(args, not_key)
settings[key] = val
clean_settings(settings)
return
def clean_settings(self):
# failsafe to ensure C tox is bytes and Py settings is str
# overrides
self['mirror_mode'] = False
# REQUIRED!!
if not os.path.exists('/proc/sys/net/ipv6'):
LOG.warn('Disabling IPV6 because /proc/sys/net/ipv6 does not exist')
self['ipv6_enabled'] = False
if 'proxy_type' in self and self['proxy_type'] == 0:
self['proxy_host'] = ''
self['proxy_port'] = 0
if 'proxy_type' in self and self['proxy_type'] != 0 and \
'proxy_host' in self and self['proxy_host'] != '' and \
'proxy_port' in self and self['proxy_port'] != 0:
if 'udp_enabled' in self and self['udp_enabled']:
# We don't currently support UDP over proxy.
LOG.info("UDP enabled and proxy set: disabling UDP")
self['udp_enabled'] = False
if 'local_discovery_enabled' in self and self['local_discovery_enabled']:
LOG.info("local_discovery_enabled enabled and proxy set: disabling local_discovery_enabled")
self['local_discovery_enabled'] = False
if 'dht_announcements_enabled' in self and self['dht_announcements_enabled']:
LOG.info("dht_announcements_enabled enabled and proxy set: disabling dht_announcements_enabled")
self['dht_announcements_enabled'] = False
if 'auto_accept_path' in self and \
type(self['auto_accept_path']) == bytes:
self['auto_accept_path'] = str(self['auto_accept_path'], 'UTF-8')
for key in Settings.get_default_settings():
if key not in self: continue
if type(self[key]) == bytes:
LOG.warn('bytes setting in: ' +key \
+' ' + repr(self[key]))
# ascii?
# self[key] = str(self[key], 'utf-8')
LOG.debug("Cleaned settings")
def get_user_config_path():
system = util_utils.get_platform()
if system == 'Windows':
return os.path.join(os.getenv('APPDATA'), 'Tox/')
elif system == 'Darwin':
return os.path.join(os.getenv('HOME'), 'Library/Application Support/Tox/')
else:
return os.path.join(os.getenv('HOME'), '.config/tox/')
def supported_languages():
return {
'English': 'en_EN',
'French': 'fr_FR',
'Russian': 'ru_RU',
'Ukrainian': 'uk_UA'
}
def built_in_themes():
return {
'dark': 'dark_style.qss',
'default': 'style.qss'
}
def get_global_settings_path():
return os.path.join(get_base_directory(), 'toxygen.json')
def is_active_profile(profile_path):
sFile = profile_path + '.lock'
if not os.path.isfile(sFile):
return False
try:
import psutil
except Exception as e:
return True
with open(sFile, 'rb') as iFd:
sPid = iFd.read()
if sPid and int(sPid.strip()) in psutil.pids():
return True
LOG.debug('Unlinking stale lock file ' +sFile)
try:
os.unlink(sFile)
except:
pass
return False
class Settings(dict):
"""
Settings of current profile + global app settings
"""
def __init__(self, toxes, path, app):
self._path = path
self._profile_path = path.replace('.json', '.tox')
self._toxes = toxes
self._app = app
self._args = app._oArgs
self._oArgs = app._oArgs
self._log = lambda l: LOG.log(self._oArgs.loglevel, l)
self._settings_saved_event = Event()
if path and os.path.isfile(path):
try:
with open(path, 'rb') as fl:
data = fl.read()
if toxes.is_data_encrypted(data):
data = toxes.pass_decrypt(data)
info = json.loads(str(data, 'utf-8'))
LOG.debug('Parsed settings from: ' + str(path))
except Exception as ex:
title = 'Error opening/parsing settings file: '
text = title + path
LOG.error(title +str(ex))
util_ui.message_box(text, title)
info = Settings.get_default_settings(app._oArgs)
user_data.settings.clean_settings(info)
else:
LOG.debug('get_default_settings for: ' + repr(path))
info = Settings.get_default_settings(app._oArgs)
if not os.path.exists(path):
merge_args_into_settings(app._oArgs, info)
else:
aC = self._changed(app._oArgs, info)
if aC:
title = 'Override profile with commandline - '
if path:
title += os.path.basename(path)
text = 'Override profile with command-line settings? \n'
# text += '\n'.join([str(key) +'=' +str(val) for
# key,val in self._changed(app._oArgs).items()])
text += repr(aC)
reply = util_ui.question(text, title)
if reply:
merge_args_into_settings(app._oArgs, info)
info['audio'] = getattr(app._oArgs, 'audio')
info['video'] = getattr(app._oArgs, 'video')
super().__init__(info)
self._upgrade()
LOG.info('Parsed settings from: ' + str(path))
ex = f"self=id(self) {self!r}"
LOG.debug(ex)
self.save()
self.locked = False
self.closing = False
self.unlockScreen = False
# -----------------------------------------------------------------------------------------------------------------
# Properties
# -----------------------------------------------------------------------------------------------------------------
def get_settings_saved_event(self):
return self._settings_saved_event
settings_saved_event = property(get_settings_saved_event)
# -----------------------------------------------------------------------------------------------------------------
# Public methods
# -----------------------------------------------------------------------------------------------------------------
def save(self):
text = json.dumps(self)
if self._toxes.has_password():
text = bytes(self._toxes.pass_encrypt(bytes(text, 'utf-8')))
else:
text = bytes(text, 'utf-8')
tmp = self._path + str(os.getpid())
try:
with open(tmp, 'wb') as fl:
fl.write(text)
os.rename(tmp, self._path)
except Exception as e:
LOG.warn(f'Error saving to {self._path} ' +str(e))
else:
self._settings_saved_event(text)
def close(self):
path = self._profile_path + '.lock'
if os.path.isfile(path):
os.remove(path)
def set_active_profile(self):
"""
Mark current profile as active
"""
path = self._profile_path + '.lock'
try:
import shutil
except:
pass
else:
shutil.copy2(self._profile_path, path)
# need to open this with the same perms as _profile_path
# copy profile_path and then write?
with open(path, 'wb') as fl:
fl.write(bytes(str(os.getpid()), 'ascii'))
def export(self, path):
text = json.dumps(self)
name = os.path.basename(self._path)
with open(join_path(path, str(name)), 'w') as fl:
fl.write(text)
def update_path(self, new_path):
self._path = new_path
self.save()
# -----------------------------------------------------------------------------------------------------------------
# Static methods
# -----------------------------------------------------------------------------------------------------------------
@staticmethod
def get_auto_profile():
p = get_global_settings_path()
if not os.path.isfile(p):
return None
with open(p) as fl:
data = fl.read()
try:
auto = json.loads(data)
except Exception as ex:
LOG.warn(f"json.loads {data}: {ex!s}")
auto = {}
if 'profile_path' in auto:
path = str(auto['profile_path'])
if not os.path.isabs(path):
path = join_path(path, curr_directory(__file__))
if os.path.isfile(path):
return path
@staticmethod
def supported_languages():
# backwards
return supported_languages()
@staticmethod
def set_auto_profile(path):
p = get_global_settings_path()
if os.path.isfile(p):
with open(p) as fl:
data = fl.read()
data = json.loads(data)
else:
data = {}
data['profile_path'] = str(path)
with open(p, 'w') as fl:
fl.write(json.dumps(data))
@staticmethod
def reset_auto_profile():
p = get_global_settings_path()
if os.path.isfile(p):
with open(p) as fl:
data = fl.read()
data = json.loads(data)
else:
data = {}
if 'profile_path' in data:
del data['profile_path']
with open(p, 'w') as fl:
fl.write(json.dumps(data))
@staticmethod
def get_default_settings(args=None):
"""
Default profile settings
"""
retval = {
# FixMe: match? /var/local/src/c-toxcore/toxcore/tox.h
'ipv6_enabled': True,
'udp_enabled': True,
'trace_enabled': False,
'local_discovery_enabled': True,
'dht_announcements_enabled': True,
'proxy_type': 0,
'proxy_host': '',
'proxy_port': 0,
'start_port': 0,
'end_port': 0,
'tcp_port': 0,
'local_discovery_enabled': True,
'hole_punching_enabled': False,
# tox_log_cb *log_callback;
'experimental_thread_safety': False,
# operating_system
'theme': 'default',
'notifications': False,
'sound_notifications': False,
'language': 'English',
'calls_sound': False, # was True
'save_history': True,
'save_unsent_only': False,
'allow_inline': True,
'allow_auto_accept': True,
'auto_accept_path': None,
'sorting': 0,
'auto_accept_from_friends': [],
'paused_file_transfers': {},
'resend_files': True,
'friends_aliases': [],
'show_avatars': False,
'typing_notifications': False,
'blocked': [],
'plugins': [],
'notes': {},
'smileys': True,
'smiley_pack': 'default',
'mirror_mode': False,
'width': 920,
'height': 500,
'x': 400,
'y': 400,
'message_font_size': 14,
'unread_color': 'red',
'compact_mode': False,
'identicons': True,
'show_welcome_screen': True,
'close_app': 0,
'font': 'Times New Roman',
'update': 0,
'group_notifications': True,
'download_nodes_list': False, #
'download_nodes_url': 'https://nodes.tox.chat/json',
'notify_all_gc': False,
'backup_directory': None,
'audio': {'input': -1,
'output': -1,
'enabled': True},
'video': {'device': -1,
'width': 320,
'height': 240,
'x': 0,
'y': 0},
'current_nodes': None,
'network': 'new',
'tray_icon': False,
}
return retval
# -----------------------------------------------------------------------------------------------------------------
# Private methods
# -----------------------------------------------------------------------------------------------------------------
def _upgrade(self):
default = Settings.get_default_settings()
for key in default:
if key not in self:
print(key)
self[key] = default[key]
def _changed(self, aArgs, info):
aRet = dict()
default = Settings.get_default_settings()
for key in default:
if key in ['audio', 'video']: continue
if key not in aArgs.__dict__: continue
val = aArgs.__dict__[key]
if val in ['0.0.0.0']: continue
if key in aArgs.__dict__ and key not in info:
# dunno = network
continue
if key in aArgs.__dict__ and info[key] != val:
aRet[key] = val
return aRet