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.library.py
6  
7 Written by: Josh.5 <jsunnex@gmail.com>
8 Date: 06 Feb 2022, (12:11 PM)
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 import random
33  
34 from unmanic.config import Config
35 from unmanic.libs import common
36 from unmanic.libs.unmodels import EnabledPlugins, Libraries, LibraryPluginFlow, Plugins, Tags, Tasks
37  
38  
39 def generate_random_library_name():
40 names = [
41 "Willes", "Here", "Helry", "Vyncent", "Burgwy", "Homas Yournet", "Roguy Eldys", "George Ewes", "Hearda",
42 "Mathye Gedde", "Wynfre", "Gauwill", "Aldhert", "Ryany", "Reward", "Atwulf", "Amer", "Alten Yourner", "Reda", "Oled",
43 "Anthohn Dene", "Rarder", "Artin Borne", "Eadwean", "Freyny Loray", "Breda", "Gauwalt Nynsell", "Lodwy", "Exam",
44 "Alters Corby", "Wilhye", "Gery", "Raffin", "Ceolbehrt", "Jamath", "George Sone", "Geoffrey Nette", "Eadund", "Dunne",
45 "Gilda", "Aered", "Lafa", "Eadulf", "Eanmaed", "Cyni", "Draffin", "Nichye", "Reder", "Aldwid", "Conbad", "Munda",
46 "Willex", "Ichohn", "Orkold", "Gyleon", "Ealard", "Helmund", "Nother", "Bertio", "Phamund Erett", "Cuthre", "Aewald",
47 "Aehehrt", "Folke", "Ales", "Chury Kypwe", "Liamund", "Rewalt Wyne", "Arryn", "Charlip", "Georguy", "Lare", "Aenward",
48 "Eanwald", "Ashwid", "Britheard", "Cholas", "Eolhed", "Anwulf", "Eorcorht", "Piersym", "Godre", "Edward", "Dreder",
49 "Geoffry", "Wyny", "Hardwy", "Witio", "Grewis", "Chilew", "Gare", "Arnwulf", "Masym Arren", "Iged", "Uwan", "Coenwy",
50 "Saefa", "Thiles", "Cyne", "Exard", "Ichas Horne", "Rewilh Morley", "Edmur Ferry", "Wine", "Ered", "Lacio", "Elres",
51 "Gaenbyrtf", "Stomund", "Riffin Maley", "Thiliam Save", "Walda", "Giles Drighte", "Robern Finchey", "Wulfa", "James",
52 "Stiny Fane", "Driffin", "Andrers", "Beorhtio", "Balda", "Warder", "Bealdu", "Dene", "Andren", "Stephye", "Ealcar",
53 "Richye Corby", "Ament Anes", "Tharry", "Germund", "Ralphye Payney"
54 ]
55 adjectives = [
56 "awesome", "adorable", "abounding", "aspiring", "beloved", "blue", "blissful", "creamy", "cavernous", "content",
57 "droopy", "excited", "enchanted", "enormous", "extroverted", "exciting", "gullible", "gaseous", "grumpy", "giant",
58 "handsome", "hefty", "harmless", "happy", "hairy", "humdrum", "invincible", "illiterate", "inexperienced", "impolite",
59 "illustrious", "impartial", "innocent", "jovial", "juvenile", "joyful", "jumpy", "jagged", "joyous", "kooky", "large",
60 "likeable", "mountainous", "momentous", "minty", "nocturnal", "nautical", "organic", "overcooked", "productive",
61 "plush", "polished", "queasy", "quirky", "quintessential", "reminiscent", "remarkable", "ragged", "rowdy", "soggy",
62 "sudden", "scandalous", "secretive", "spry", "squiggly", "smooth", "sulky", "scented", "spicy", "sticky", "slushy",
63 "symptomatic", "tart", "turbulent", "tiresome", "typical", "xyloid", "xanthic", "zealous", "zany",
64 ]
65 return "{name}, the {adjective} library".format(name=random.choice(names), adjective=random.choice(adjectives))
66  
67  
68 class Library(object):
69 """
70 Library
71  
72 Contains all data pertaining to a library
73  
74 """
75  
76 def __init__(self, library_id: int):
77 # Ensure library ID is not 0
78 if library_id < 1:
79 raise Exception("Library ID cannot be less than 1")
80 self.model = Libraries.get_or_none(id=library_id)
81 if not self.model:
82 raise Exception("Unable to fetch library with ID {}".format(library_id))
83  
84 @staticmethod
85 def get_all_libraries():
86 """
87 Return a list of all libraries
88  
89 :return:
90 """
91 # Fetch default library path from
92 from unmanic.config import Config
93 default_library_path = Config().get_library_path()
94 if not default_library_path:
95 default_library_path = common.get_default_library_path()
96  
97 # Fetch all libraries from DB
98 configured_libraries = Libraries.select()
99  
100 # Ensure that at least the default path was added.
101 # If the libraries path is empty, then we should add the default path
102 if not configured_libraries:
103 default_library = {
104 'id': 1,
105 'name': generate_random_library_name(),
106 'path': default_library_path,
107 'locked': False,
108 "enable_remote_only": False,
109 "enable_scanner": False,
110 "enable_inotify": False,
111 'tags': [],
112 }
113 Libraries.create(**default_library)
114 return [default_library]
115  
116 # Loop over results
117 default_library = []
118 libraries = []
119 for lib in configured_libraries:
120 # Always update the default library path
121 if lib.id == 1 and lib.path != default_library_path:
122 lib.path = default_library_path
123 lib.save()
124 # Create library config dictionary
125 library_config = {
126 'id': lib.id,
127 'name': lib.name,
128 'path': lib.path,
129 'locked': lib.locked,
130 'enable_remote_only': lib.enable_remote_only,
131 'enable_scanner': lib.enable_scanner,
132 'enable_inotify': lib.enable_inotify,
133 'tags': [],
134 }
135 # Append tags
136 for tag in lib.tags.order_by(Tags.name):
137 library_config['tags'].append(tag.name)
138  
139 # Keep the default library separate
140 if lib.id == 1:
141 default_library.append(library_config)
142 continue
143 libraries.append(library_config)
144  
145 # Return the list of libraries sorted by name
146 return default_library + sorted(libraries, key=lambda d: d['name'])
147  
148 @staticmethod
149 def within_library_count_limits(frontend_messages=None):
150 # Fetch level from session
151 from unmanic.libs.session import Session
152 s = Session()
153 s.register_unmanic()
154 if s.level > 1:
155 return True
156  
157 # Fetch all enabled plugins
158 library_count = Libraries.select().count()
159  
160 # Ensure enabled plugins are within limits
161 # Function was returned above if the user was logged in and able to use infinite
162 if library_count > s.library_count:
163 # If the frontend messages queue was included in request, append a message
164 if frontend_messages:
165 frontend_messages.put(
166 {
167 'id': 'libraryEnabledLimits',
168 'type': 'error',
169 'code': 'libraryEnabledLimits',
170 'message': '',
171 'timeout': 0
172 }
173 )
174 return False
175 # If the frontend messages queue was included in request, remove the notification as we are currently within limits
176 if frontend_messages:
177 frontend_messages.remove_item('libraryEnabledLimits')
178 return True
179  
180 @staticmethod
181 def create(data: dict):
182 """
183 Create a new library
184  
185 :param data:
186 :return:
187 """
188 # Ensure ID is removed from data for a create
189 if 'id' in data:
190 del data['id']
191 new_library = Libraries.create(**data)
192 return Library(new_library.id)
193  
194 @staticmethod
195 def export(library_id):
196 from unmanic.libs.plugins import PluginsHandler
197  
198 # Read the library
199 library_config = Library(library_id)
200  
201 # Get list of enabled plugins with their settings
202 enabled_plugins = []
203 for enabled_plugin in library_config.get_enabled_plugins(include_settings=True):
204 enabled_plugins.append({
205 'plugin_id': enabled_plugin.get('plugin_id'),
206 'has_config': enabled_plugin.get('has_config'),
207 'settings': enabled_plugin.get('settings'),
208 })
209  
210 # Create plugin flow
211 plugin_flow = {}
212  
213 plugin_handler = PluginsHandler()
214 for plugin_type in plugin_handler.get_plugin_types_with_flows():
215 plugin_flow[plugin_type] = []
216 flow = plugin_handler.get_enabled_plugin_flows_for_plugin_type(plugin_type, library_id)
217 for f in flow:
218 plugin_flow[plugin_type].append(f.get('plugin_id'))
219  
220 return {
221 "plugins": {
222 "enabled_plugins": enabled_plugins,
223 "plugin_flow": plugin_flow,
224 },
225 "library_config": {
226 "name": library_config.get_name(),
227 "path": library_config.get_path(),
228 'enable_remote_only': library_config.get_enable_remote_only(),
229 'enable_scanner': library_config.get_enable_scanner(),
230 'enable_inotify': library_config.get_enable_inotify(),
231 'tags': library_config.get_tags(),
232 },
233 }
234  
235 def __remove_enabled_plugins(self):
236 """
237 Remove all enabled plugins
238  
239 :return:
240 """
241 query = EnabledPlugins.delete()
242 query = query.where(EnabledPlugins.library_id == self.model.id)
243 return query.execute()
244  
245 def __trim_plugin_flow(self, plugin_ids: list):
246 """
247 Trim the plugin flow removing entries not in the given plugin ids list
248  
249 :param plugin_ids:
250 :return:
251 """
252 query = LibraryPluginFlow.delete()
253 query = query.where((LibraryPluginFlow.library_id == self.model.id) & (LibraryPluginFlow.plugin_id.not_in(plugin_ids)))
254 return query.execute()
255  
256 def __remove_associated_tasks(self):
257 """
258 Remove all tasks associated with a library
259  
260 :return:
261 """
262 Tasks.delete().where(Tasks.library_id == self.model.id).execute()
263  
264 def get_id(self):
265 return self.model.id
266  
267 def get_name(self):
268 return self.model.name
269  
270 def set_name(self, value):
271 self.model.name = value
272  
273 def get_path(self):
274 return self.model.path
275  
276 def set_path(self, value):
277 self.model.path = value
278  
279 def get_locked(self):
280 return self.model.locked
281  
282 def set_locked(self, value):
283 self.model.locked = value
284  
285 def get_enable_remote_only(self):
286 return self.model.enable_remote_only
287  
288 def set_enable_remote_only(self, value):
289 self.model.enable_remote_only = value
290  
291 def get_enable_scanner(self):
292 return self.model.enable_scanner
293  
294 def set_enable_scanner(self, value):
295 self.model.enable_scanner = value
296  
297 def get_enable_inotify(self):
298 return self.model.enable_inotify
299  
300 def set_enable_inotify(self, value):
301 self.model.enable_inotify = value
302  
303 def get_priority_score(self):
304 return self.model.priority_score
305  
306 def set_priority_score(self, value):
307 self.model.priority_score = value
308  
309 def get_tags(self):
310 return_tags = []
311 for tag in self.model.tags.order_by(Tags.name):
312 return_tags.append(tag.name)
313 return return_tags
314  
315 def set_tags(self, value):
316 # Create any missing tags
317 for tag_name in value:
318 # Do not update any current tags with on_conflict_replace() as this will also change their IDs
319 # Instead, just ignore them
320 Tags.insert(name=tag_name).on_conflict_ignore().execute()
321 # Create a SELECT query for all tags with the listed names
322 tags_select_query = Tags.select().where(Tags.name.in_(value))
323 # Clear out the current linking table of tags linked to this library
324 # Add new links for each tag that was fetched matching the provided names
325 self.model.tags.add(tags_select_query, clear_existing=True)
326  
327 def get_enabled_plugins(self, include_settings=False):
328 """
329 Get all enabled plugins for this library
330  
331 :return:
332 """
333 # Fetch enabled plugins for this library
334 query = self.model.enabled_plugins.select(Plugins, EnabledPlugins.library_id)
335 query = query.join(Plugins, join_type='LEFT OUTER JOIN', on=(EnabledPlugins.plugin_id == Plugins.id))
336 query = query.order_by(Plugins.name)
337  
338 from unmanic.libs.unplugins import PluginExecutor
339 plugin_executor = PluginExecutor()
340  
341 # Extract required data
342 enabled_plugins = []
343 for enabled_plugin in query.dicts():
344 # Check if plugin is able to be configured
345 has_config = False
346 plugin_settings, plugin_settings_meta = plugin_executor.get_plugin_settings(enabled_plugin.get('plugin_id'),
347 library_id=self.model.id)
348 if plugin_settings:
349 has_config = True
350 # Add plugin to list of enabled plugins
351 item = {
352 'plugin_id': enabled_plugin.get('plugin_id'),
353 'name': enabled_plugin.get('name'),
354 'description': enabled_plugin.get('description'),
355 'icon': enabled_plugin.get('icon'),
356 'has_config': has_config,
357 }
358 if include_settings:
359 item['settings'] = plugin_settings
360 enabled_plugins.append(item)
361  
362 return enabled_plugins
363  
364 def get_plugin_flow(self):
365 """
366 Fetch the plugin flow for a library
367  
368 :return:
369 """
370 plugin_flow = {}
371 from unmanic.libs.plugins import PluginsHandler
372 plugin_handler = PluginsHandler()
373 from unmanic.libs.unplugins import PluginExecutor
374 plugin_ex = PluginExecutor()
375 for plugin_type in plugin_ex.get_all_plugin_types():
376 # Ignore types without flows
377 if not plugin_type.get('has_flow'):
378 continue
379  
380 # Create list of plugins in this plugin type
381 plugin_flow[plugin_type.get('id')] = []
382 plugin_modules = plugin_handler.get_enabled_plugin_modules_by_type(plugin_type.get('id'), library_id=self.model.id)
383 for plugin_module in plugin_modules:
384 plugin_flow[plugin_type.get('id')].append(
385 {
386 "plugin_id": plugin_module.get("plugin_id"),
387 "name": plugin_module.get("name", ""),
388 "author": plugin_module.get("author", ""),
389 "description": plugin_module.get("description", ""),
390 "version": plugin_module.get("version", ""),
391 "icon": plugin_module.get("icon", ""),
392 }
393 )
394  
395 return plugin_flow
396  
397 def __set_default_plugin_flow_priority(self, plugin_list):
398 from unmanic.libs.unplugins import PluginExecutor
399 plugin_executor = PluginExecutor()
400 from unmanic.libs.plugins import PluginsHandler
401 plugin_handler = PluginsHandler()
402  
403 # Fetch current items
404 configured_plugin_ids = []
405 query = LibraryPluginFlow.select().where(LibraryPluginFlow.library_id == self.model.id)
406 for flow_item in query:
407 configured_plugin_ids.append(flow_item.plugin_id.plugin_id)
408  
409 for plugin in plugin_list:
410 # Ignore already configured plugins
411 if plugin.get('plugin_id') in configured_plugin_ids:
412 continue
413 plugin_info = plugin_handler.get_plugin_info(plugin.get('plugin_id'))
414 plugin_priorities = plugin_info.get('priorities')
415 if plugin_priorities:
416 # Fetch the plugin info back from the DB
417 plugin_info = Plugins.select().where(Plugins.plugin_id == plugin.get("plugin_id")).first()
418 # Fetch all plugin types in this plugin
419 plugin_types_in_plugin = plugin_executor.get_all_plugin_types_in_plugin(plugin.get("plugin_id"))
420 # Loop over the plugin types in this plugin
421 for plugin_type in plugin_types_in_plugin:
422 # get the plugin runner function name for this runner
423 plugin_type_meta = plugin_executor.get_plugin_type_meta(plugin_type)
424 runner_string = plugin_type_meta.plugin_runner()
425 if plugin_priorities.get(runner_string) and int(plugin_priorities.get(runner_string, 0)) > 0:
426 # If the runner has a priority set and that value is greater than 0 (default that wont set anything),
427 # Save the priority
428 PluginsHandler.set_plugin_flow_position_for_single_plugin(
429 plugin_info,
430 plugin_type,
431 self.model.id,
432 plugin_priorities.get(runner_string)
433 )
434  
435 def set_enabled_plugins(self, plugin_list: list):
436 """
437 Update the list of enabled plugins
438  
439 :param plugin_list:
440 :return:
441 """
442 # Remove all enabled plugins
443 self.__remove_enabled_plugins()
444  
445 # Add new repos
446 data = []
447 plugin_ids = []
448 for plugin_info in plugin_list:
449 plugin = Plugins.get(plugin_id=plugin_info.get('plugin_id'))
450 plugin_ids.append(plugin.id)
451 if plugin:
452 data.append({
453 "library_id": self.model.id,
454 "plugin_id": plugin,
455 "plugin_name": plugin.name,
456 })
457  
458 # Delete all plugin flows for plugins not to be enabled for this library
459 self.__trim_plugin_flow(plugin_ids)
460  
461 # Insert plugins
462 EnabledPlugins.insert_many(data).execute()
463  
464 # Add default flow for newly added plugins
465 self.__set_default_plugin_flow_priority(plugin_list)
466  
467 def save(self):
468 """
469 Save the data for this library
470  
471 :return:
472 """
473 # Save changes made to model
474 save_result = self.model.save()
475  
476 # If this is the default library path, save to config.library_path object also
477 if self.get_id() == 1:
478 config = Config()
479 config.set_config_item('library_path', self.get_path())
480  
481 return save_result
482  
483 def delete(self):
484 """
485 Delete the current library
486  
487 :return:
488 """
489 # Ensure we can never delete library ID 1 (the default library)
490 if self.get_id() == 1:
491 raise Exception("Unable to remove the default library")
492  
493 # Ensure we are not trying to delete a locked library
494 if self.get_locked():
495 raise Exception("Unable to remove a locked library")
496  
497 # Remove all enabled plugins
498 self.__remove_enabled_plugins()
499  
500 # Remove all plugin flows
501 self.__trim_plugin_flow([])
502  
503 # Delete all tasks with matching library_id
504 self.__remove_associated_tasks()
505  
506 # Remove the library entry
507 return self.model.delete_instance(recursive=True)