kapsikkum-unmanic – Blame information for rev 1

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 office 1 #!/usr/bin/env python3
2 # -*- coding: utf-8 -*-
3  
4 """
5 unmanic.config.py
6  
7 Written by: Josh.5 <jsunnex@gmail.com>
8 Date: 06 Dec 2018, (7:21 AM)
9  
10 Copyright:
11 Copyright (C) Josh Sunnex - All Rights Reserved
12  
13 Permission is hereby granted, free of charge, to any person obtaining a copy
14 of this software and associated documentation files (the "Software"), to deal
15 in the Software without restriction, including without limitation the rights
16 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17 copies of the Software, and to permit persons to whom the Software is
18 furnished to do so, subject to the following conditions:
19  
20 The above copyright notice and this permission notice shall be included in all
21 copies or substantial portions of the Software.
22  
23 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
26 IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
27 DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
28 OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
29 OR OTHER DEALINGS IN THE SOFTWARE.
30  
31 """
32  
33 import os
34 import json
35  
36 from unmanic import metadata
37 from unmanic.libs import unlogger
38 from unmanic.libs import common
39 from unmanic.libs.singleton import SingletonType
40  
41 try:
42 from json.decoder import JSONDecodeError
43 except ImportError:
44 JSONDecodeError = ValueError
45  
46  
47 class Config(object, metaclass=SingletonType):
48 app_version = ''
49  
50 test = ''
51  
52 def __init__(self, config_path=None, **kwargs):
53 # Set the default UI Port
54 self.ui_port = 8888
55  
56 # Set default directories
57 home_directory = common.get_home_dir()
58 self.config_path = os.path.join(home_directory, '.unmanic', 'config')
59 self.log_path = os.path.join(home_directory, '.unmanic', 'logs')
60 self.plugins_path = os.path.join(home_directory, '.unmanic', 'plugins')
61 self.userdata_path = os.path.join(home_directory, '.unmanic', 'userdata')
62  
63 # Configure debugging
64 self.debugging = False
65  
66 # Configure first run (future feature)
67 self.first_run = False
68  
69 # Configure first run (future feature)
70 self.release_notes_viewed = None
71  
72 # Library Settings:
73 self.library_path = common.get_default_library_path()
74 self.enable_library_scanner = False
75 self.schedule_full_scan_minutes = 1440
76 self.follow_symlinks = True
77 self.concurrent_file_testers = 2
78 self.run_full_scan_on_start = False
79 self.clear_pending_tasks_on_restart = True
80 self.auto_manage_completed_tasks = False
81 self.max_age_of_completed_tasks = 91
82 self.always_keep_failed_tasks = True
83  
84 # Worker settings
85 self.cache_path = common.get_default_cache_path()
86  
87 # Link settings
88 self.installation_name = ''
89 self.remote_installations = []
90 self.distributed_worker_count_target = 0
91  
92 # Legacy config
93 # TODO: Remove this before next major version bump
94 self.number_of_workers = None
95 self.worker_event_schedules = None
96  
97 # Import env variables and override all previous settings.
98 self.__import_settings_from_env()
99  
100 # Import Unmanic path settings from command params
101 if kwargs.get('unmanic_path'):
102 self.set_config_item('config_path', os.path.join(kwargs.get('unmanic_path'), 'config'), save_settings=False)
103 self.set_config_item('plugins_path', os.path.join(kwargs.get('unmanic_path'), 'plugins'), save_settings=False)
104 self.set_config_item('userdata_path', os.path.join(kwargs.get('unmanic_path'), 'userdata'), save_settings=False)
105  
106 # Finally, re-read config from file and override all previous settings.
107 self.__import_settings_from_file(config_path)
108  
109 # Overwrite current settings with given args
110 if config_path:
111 self.set_config_item('config_path', config_path, save_settings=False)
112  
113 # Overwrite all other settings passed from command params
114 if kwargs.get('port'):
115 self.set_config_item('ui_port', kwargs.get('port'), save_settings=False)
116  
117 # Apply settings to the unmanic logger
118 self.__setup_unmanic_logger()
119  
120 def _log(self, message, message2='', level="info"):
121 """
122 Generic logging method. Can be implemented on any unmanic class
123  
124 :param message:
125 :param message2:
126 :param level:
127 :return:
128 """
129 unmanic_logging = unlogger.UnmanicLogger.__call__()
130 logger = unmanic_logging.get_logger(__class__.__name__)
131 if logger:
132 message = common.format_message(message, message2)
133 getattr(logger, level)(message)
134 else:
135 print("Unmanic.{} - ERROR!!! Failed to find logger".format(self.__name__))
136  
137 def get_config_as_dict(self):
138 """
139 Return a dictionary of configuration fields and their current values
140  
141 :return:
142 """
143 return self.__dict__
144  
145 def get_config_keys(self):
146 """
147 Return a list of configuration fields
148  
149 :return:
150 """
151 return self.get_config_as_dict().keys()
152  
153 def __setup_unmanic_logger(self):
154 """
155 Pass configuration to the global logger
156  
157 :return:
158 """
159 unmanic_logging = unlogger.UnmanicLogger.__call__()
160 unmanic_logging.setup_logger(self)
161  
162 def __import_settings_from_env(self):
163 """
164 Read configuration from environment variables.
165 This is useful for running in a docker container or for unit testing.
166  
167 :return:
168 """
169 for setting in self.get_config_keys():
170 if setting in os.environ:
171 self.set_config_item(setting, os.environ.get(setting), save_settings=False)
172  
173 def __import_settings_from_file(self, config_path=None):
174 """
175 Read configuration from the settings JSON file.
176  
177 :return:
178 """
179 # If config path was not passed as variable, use the default one
180 if not config_path:
181 config_path = self.get_config_path()
182 # Ensure the config path exists
183 if not os.path.exists(config_path):
184 os.makedirs(config_path)
185 settings_file = os.path.join(config_path, 'settings.json')
186 if os.path.exists(settings_file):
187 data = {}
188 try:
189 with open(settings_file) as infile:
190 data = json.load(infile)
191 except Exception as e:
192 self._log("Exception in reading saved settings from file:", message2=str(e), level="exception")
193 # Set data to Config class
194 self.set_bulk_config_items(data, save_settings=False)
195  
196 def __write_settings_to_file(self):
197 """
198 Dump current settings to the settings JSON file.
199  
200 :return:
201 """
202 if not os.path.exists(self.get_config_path()):
203 os.makedirs(self.get_config_path())
204 settings_file = os.path.join(self.get_config_path(), 'settings.json')
205 data = self.get_config_as_dict()
206 result = common.json_dump_to_file(data, settings_file)
207 if not result['success']:
208 for message in result['errors']:
209 self._log("Error:", message2=str(message), level="error")
210 raise Exception("Exception in writing settings to file")
211  
212 def get_config_item(self, key):
213 """
214 Get setting from either this class or the Settings model
215  
216 :param key:
217 :return:
218 """
219 # First attempt to fetch it from this class' get functions
220 if hasattr(self, "get_{}".format(key)):
221 getter = getattr(self, "get_{}".format(key))
222 if callable(getter):
223 return getter()
224  
225 def set_config_item(self, key, value, save_settings=True):
226 """
227 Assigns a value to a given configuration field.
228 This is applied to both this class.
229  
230 If 'save_settings' is set to False, then settings are only
231 assigned and not saved to file.
232  
233 :param key:
234 :param value:
235 :param save_settings:
236 :return:
237 """
238 # Get lowercase value of key
239 field_id = key.lower()
240 # Check if key is a valid setting
241 if field_id not in self.get_config_keys():
242 self._log("Attempting to save unknown key", message2=str(key), level="warning")
243 # Do not proceed if this is any key other than the database
244 return
245  
246 # If in a special config list, execute that command
247 if hasattr(self, "set_{}".format(key)):
248 setter = getattr(self, "set_{}".format(key))
249 if callable(setter):
250 setter(value)
251 else:
252 # Assign value directly to class attribute
253 setattr(self, key, value)
254  
255 # Save settings (if requested)
256 if save_settings:
257 try:
258 self.__write_settings_to_file()
259 except Exception as e:
260 self._log("Failed to write settings to file: ", message2=str(self.get_config_as_dict()), level="exception")
261  
262 def set_bulk_config_items(self, items, save_settings=True):
263 """
264 Write bulk config items to this class.
265  
266 :param items:
267 :param save_settings:
268 :return:
269 """
270 # Set values that match the settings model attributes
271 config_keys = self.get_config_keys()
272 for config_key in config_keys:
273 # Only import the item if it exists (Running a get here would default a missing var to None)
274 if config_key in items:
275 self.set_config_item(config_key, items[config_key], save_settings=save_settings)
276  
277 @staticmethod
278 def read_version():
279 """
280 Return the application's version number as a string
281  
282 :return:
283 """
284 return metadata.read_version_string('long')
285  
286 def read_system_logs(self, lines=None):
287 """
288 Return an array of system log lines
289  
290 :param lines:
291 :return:
292 """
293 log_lines = []
294 log_file = os.path.join(self.log_path, 'unmanic.log')
295 line_count = 0
296 for line in reversed(list(open(log_file))):
297 log_lines.insert(0, line.rstrip())
298 line_count += 1
299 if line_count == lines:
300 break
301 return log_lines
302  
303 def get_ui_port(self):
304 """
305 Get setting - ui_port
306  
307 :return:
308 """
309 return self.ui_port
310  
311 def get_cache_path(self):
312 """
313 Get setting - cache_path
314  
315 :return:
316 """
317 return self.cache_path
318  
319 def set_cache_path(self, cache_path):
320 """
321 Get setting - cache_path
322  
323 :return:
324 """
325 if cache_path == "":
326 self._log("Cache path cannot be empty. Resetting it to default", level="warning")
327 cache_path = common.get_default_cache_path()
328 self.cache_path = cache_path
329  
330 def get_config_path(self):
331 """
332 Get setting - config_path
333  
334 :return:
335 """
336 return self.config_path
337  
338 def get_debugging(self):
339 """
340 Get setting - debugging
341  
342 :return:
343 """
344 return self.debugging
345  
346 def set_debugging(self, value):
347 """
348 Set setting - debugging
349  
350 This requires an update to the logger object
351  
352 :return:
353 """
354 unmanic_logging = unlogger.UnmanicLogger.__call__()
355 if value:
356 unmanic_logging.enable_debugging()
357 else:
358 unmanic_logging.disable_debugging()
359 self.debugging = value
360  
361 def get_first_run(self):
362 """
363 Get setting - first_run
364  
365 :return:
366 """
367 return self.first_run
368  
369 def get_release_notes_viewed(self):
370 """
371 Get setting - release_notes_viewed
372  
373 :return:
374 """
375 return self.release_notes_viewed
376  
377 def get_library_path(self):
378 """
379 Get setting - library_path
380  
381 :return:
382 """
383 return self.library_path
384  
385 def get_clear_pending_tasks_on_restart(self):
386 """
387 Get setting - clear_pending_tasks_on_restart
388  
389 :return:
390 """
391 return self.clear_pending_tasks_on_restart
392  
393 def get_auto_manage_completed_tasks(self):
394 """
395 Get setting - auto_manage_completed_tasks
396  
397 :return:
398 """
399 return self.auto_manage_completed_tasks
400  
401 def get_max_age_of_completed_tasks(self):
402 """
403 Get setting - max_age_of_completed_tasks
404  
405 :return:
406 """
407 return self.max_age_of_completed_tasks
408  
409 def get_always_keep_failed_tasks(self):
410 """
411 Get setting - always_keep_failed_tasks
412  
413 :return:
414 """
415 return self.always_keep_failed_tasks
416  
417 def get_log_path(self):
418 """
419 Get setting - log_path
420  
421 :return:
422 """
423 return self.log_path
424  
425 def get_number_of_workers(self):
426 """
427 Get setting - number_of_workers
428  
429 :return:
430 """
431 return self.number_of_workers
432  
433 def get_worker_event_schedules(self):
434 """
435 Get setting - worker_event_schedules
436  
437 :return:
438 """
439 return self.worker_event_schedules
440  
441 def get_enable_library_scanner(self):
442 """
443 Get setting - enable_library_scanner
444  
445 :return:
446 """
447 return self.enable_library_scanner
448  
449 def get_run_full_scan_on_start(self):
450 """
451 Get setting - run_full_scan_on_start
452  
453 :return:
454 """
455 return self.run_full_scan_on_start
456  
457 def get_schedule_full_scan_minutes(self):
458 """
459 Get setting - schedule_full_scan_minutes
460  
461 :return:
462 """
463 return self.schedule_full_scan_minutes
464  
465 def get_follow_symlinks(self):
466 """
467 Get setting - follow_symlinks
468  
469 :return:
470 """
471 return self.follow_symlinks
472  
473 def get_concurrent_file_testers(self):
474 """
475 Get setting - concurrent_file_testers
476  
477 :return:
478 """
479 return self.concurrent_file_testers
480  
481 def get_plugins_path(self):
482 """
483 Get setting - config_path
484  
485 :return:
486 """
487 return self.plugins_path
488  
489 def get_userdata_path(self):
490 """
491 Get setting - userdata_path
492  
493 :return:
494 """
495 return self.userdata_path
496  
497 def get_installation_name(self):
498 """
499 Get setting - installation_name
500  
501 :return:
502 """
503 return self.installation_name
504  
505 def get_remote_installations(self):
506 """
507 Get setting - remote_installations
508  
509 :return:
510 """
511 remote_installations = []
512 for ri in self.remote_installations:
513 ri['distributed_worker_count_target'] = self.distributed_worker_count_target
514 remote_installations.append(ri)
515 return remote_installations
516  
517 def get_distributed_worker_count_target(self):
518 """
519 Get setting - distributed_worker_count_target
520  
521 :return:
522 """
523 return self.distributed_worker_count_target