1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234
|
#!/usr/bin/env python3
"""
Syncthing-GTK - Configuration
Configuration object implementation; Uses JSON.
Config file is by default in ~/.config/syncthing-gtk/config.json
or other ~/.config equivalent
"""
from datetime import datetime
from syncthing_gtk.tools import IS_WINDOWS, get_config_dir, is_portable
import dateutil.parser
import os, sys, json, logging
log = logging.getLogger("Configuration")
LONG_AGO = datetime.fromtimestamp(1)
class _Configuration(object):
"""
Configuration object implementation.
Use like dict to save / access values
"""
# Dict with keys that are required in configuration file
# and default values for those keys.
# Format: key : (type, default)
REQUIRED_KEYS = {
"autostart_daemon" : (int, 2), # 0 - wait for daemon, 1 - autostart, 2 - ask
"autokill_daemon" : (int, 2), # 0 - never kill, 1 - always kill, 2 - ask
"daemon_priority" : (int, 0), # uses nice values
"max_cpus" : (int, 0), # 0 for all cpus
"syncthing_binary" : (str, "/usr/bin/syncthing"),
"syncthing_arguments" : (str, ""),
"minimize_on_start" : (bool, False),
"folder_as_path" : (bool, True),
"use_old_header" : (bool, False),
"icons_in_menu" : (bool, True),
"animate_icon" : (bool, True),
"notification_for_update" : (bool, True),
"notification_for_folder" : (bool, False),
"notification_for_error" : (bool, True),
"st_autoupdate" : (bool, False),
"last_updatecheck" : (datetime, LONG_AGO),
"window_position" : (tuple, None),
"infobox_style" : (str, 'font_weight="bold" font_size="large"'),
"icon_theme" : (str, 'syncthing'),
"force_dark_theme" : (bool, False), # Windows-only
"language" : (str, ""), # Windows-only
"file_browser" : (str, "explore"), # Windows-only
}
# Overrides some default values on Windows
WINDOWS_OVERRIDE = {
"syncthing_binary" : (str, "C:\\Program Files\\Syncthing\\syncthing.exe"),
"autokill_daemon" : (int, 1),
"use_old_header" : (bool, False),
"st_autoupdate" : (bool, True),
}
def __init__(self):
try:
self.load()
except Exception as e:
log.warning("Failed to load configuration; Creating new one.")
log.warning("Reason: %s", (e,))
self.create()
# Convert objects serialized as string back to object
self.convert_values()
# Check if everything is in place, add default value
# where value is missing
if self.check_values():
# check_values returns True if any default value is added
log.info("Saving configuration...")
self.save()
def load(self):
# Check & create directory
if not os.path.exists(self.get_config_dir()):
try:
os.makedirs(self.get_config_dir())
except Exception as e:
log.error("Cannot create configuration directory")
log.exception(e)
sys.exit(1)
# Load json
with open(self.get_config_file(), "r") as conf:
self.values = json.loads(conf.read())
def get_default_value(self, key):
if IS_WINDOWS:
return self.WINDOWS_OVERRIDE.get(key, self.REQUIRED_KEYS[key])[-1]
return self.REQUIRED_KEYS[key][-1]
def get_config_dir(self):
return os.path.join(get_config_dir(), "syncthing-gtk")
def get_config_file(self):
return os.path.join(self.get_config_dir(), "config.json")
def create(self):
""" Creates new, empty configuration """
self.values = {}
self.check_values()
self.save()
def check_values(self):
"""
Check if all required values are in place and fill by default
whatever is missing.
Returns True if anything gets changed.
"""
needs_to_save = False
for key in Configuration.REQUIRED_KEYS:
tp, default = Configuration.REQUIRED_KEYS[key]
if not self.check_type(key, tp):
log.verbose("Configuration key %s is missing. Using default", key)
if IS_WINDOWS and key in Configuration.WINDOWS_OVERRIDE:
tp, default = Configuration.WINDOWS_OVERRIDE[key]
self.values[key] = default
needs_to_save = True
return needs_to_save
def convert_values(self):
"""
Converts all objects serialized as string back to object
"""
for key in Configuration.REQUIRED_KEYS:
if key in self.values:
tp, trash = Configuration.REQUIRED_KEYS[key]
try:
if tp == datetime and type(self.values[key]) == str:
# Parse datetime
self.values[key] = dateutil.parser.parse(self.values[key])
elif tp == tuple and type(self.values[key]) == list:
# Convert list to tuple
self.values[key] = tuple(self.values[key])
elif tp == bool and type(self.values[key]) == int:
# Convert bools
self.values[key] = bool(self.values[key])
except Exception as e:
log.warning("Failed to parse configuration value '%s'. Using default.", key)
log.warning(e)
# Value will be re-created by check_values method
del self.values[key]
def check_type(self, key, tp):
"""
Returns True if value is set and type match.
Auto-converts objects serialized as string back to objects
"""
if not key in self.values:
return False
# Handle special cases
if type(self.values[key]) == str and tp == str:
return True
if tp in (tuple,) and self.values[key] == None:
return True
# Return value
return type(self.values[key]) == tp
def save(self):
""" Saves configuration file """
with open(self.get_config_file(), "w") as conf:
conf.write(
json.dumps(
self.values,
sort_keys=True,
indent=4,
separators=(',', ': '),
default=serializer
)
)
def __iter__(self):
for k in self.values:
yield k
def get(self, key):
return self.values[key]
def set(self, key, value):
self.values[key] = value
self.save()
__getitem__ = get
__setitem__ = set
def __delitem__(self, key):
del self.values[key]
def __contains__(self, key):
""" Returns true if there is such value """
return key in self.values
def serializer(obj):
""" Handles serialization where json can't do it by itself """
if hasattr(obj, "isoformat"):
# datetime object
return obj.isoformat()
raise TypeError("Can't serialize object of type %s" % (type(obj),))
def Configuration(*a, **b):
if IS_WINDOWS and not is_portable():
from syncthing_gtk.windows import WinConfiguration
return WinConfiguration(*a, **b)
return _Configuration(*a, **b)
Configuration.REQUIRED_KEYS = _Configuration.REQUIRED_KEYS
Configuration.WINDOWS_OVERRIDE = _Configuration.WINDOWS_OVERRIDE
def migrate_fs_watch(stgtk_config, st_config):
"""
Migrates filesystem watch config from ST-GTK configuration
to Syncthing configuration and posts it to daemon.
Returns True if anything was changed.
Called automatically if old fs watch setting is found in ST-GTK config.
"""
# TODO: This can be removed later
if "use_inotify" not in stgtk_config:
return False
changed = False
folder_by_id = { x["id"]: x for x in st_config['folders'] }
for rid in stgtk_config["use_inotify"]:
if rid in folder_by_id:
folder_by_id[rid]["fsWatcherDelayS"] = 10
folder_by_id[rid]["fsWatcherEnabled"] = True
changed = True
del stgtk_config["use_inotify"]
return changed
|