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.notifications.py
6  
7 Written by: Josh.5 <jsunnex@gmail.com>
8 Date: 03 Jul 2022, (7:49 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 import threading
33 import uuid
34 from queue import Queue
35  
36 from unmanic.libs.singleton import SingletonType
37  
38  
39 class Notifications(Queue, metaclass=SingletonType):
40 """
41 Handles messages passed to the frontend.
42  
43 Messages are sent as objects. These objects require the following fields:
44 - 'uuid' : A unique ID of the message. Prevent messages duplication
45 - 'type' : The type of message - 'error', 'warning', 'success', or 'info'
46 - 'icon' : The icon to display with the notification
47 - 'label' : A code to represent an I18n string for the frontend to display the notification label
48 - 'message' : The message string that is appended to the I18n string displayed on the frontend.
49 - 'link' : The link to be applied to the notification. Can be relative or a URL
50  
51 Examples:
52 notifications = Notifications()
53  
54 # Place a notification
55 notifications.add(
56 {
57 'uuid': 'failedTask',
58 'type': 'error',
59 'icon': 'report',
60 'label': 'failedTaskLabel',
61 'message': 'You have a new failed task in your completed tasks list',
62 'navigation': {
63 'push': '/unmanic/ui/dashboard',
64 'events': [
65 'completedTasksShowMore',
66 ],
67 },
68 })
69  
70 # Remove an item
71 notifications.remove('updateAvailable')
72  
73 # Read the current list of notifications
74 notifications.read_all_items()
75  
76 # Update an item in place
77 notifications.add(
78 {
79 'uuid': 'updateAvailable',
80 'type': 'info',
81 'icon': 'update',
82 'label': 'updateAvailableLabel',
83 'message': 'updateAvailableMessage',
84 'navigation': {
85 'url': 'https://docs.unmanic.app',
86 },
87 })
88  
89 """
90  
91 def _init(self, maxsize):
92 self.all_items = set()
93 Queue._init(self, maxsize)
94  
95 def __add_to_queue(self, item, block=True, timeout=None):
96 Queue.put(self, item, block, timeout)
97  
98 @staticmethod
99 def __validate_item(item):
100 # Ensure all required keys are present
101 for key in ['type', 'icon', 'label', 'message', 'navigation']:
102 if key not in item:
103 raise Exception("Frontend message item incorrectly formatted. Missing key: '{}'".format(key))
104  
105 # Ensure the given type is valid
106 if item.get('type') not in ['error', 'warning', 'success', 'info']:
107 raise Exception(
108 "Frontend message item's code must be in ['error', 'warning', 'success', 'info', 'status']. Received '{}'".format(
109 item.get('type')
110 )
111 )
112 return True
113  
114 def __get_all_items(self):
115 items = []
116 while not self.empty():
117 items.append(self.get())
118 return items
119  
120 def __requeue_items(self, items):
121 for item in items:
122 self.__add_to_queue(item)
123  
124 def add(self, item):
125 # Ensure received item is valid
126 self.__validate_item(item)
127 # Generate uuid if one is not provided
128 if not item.get('uuid'):
129 item['uuid'] = str(uuid.uuid4())
130 # If it is not already in message list, add it to the list and the queue
131 if item.get('uuid') not in self.all_items:
132 self.all_items.add(item.get('uuid'))
133 self.__add_to_queue(item)
134  
135 def remove(self, item_uuid):
136 """
137 Remove a single item from the notifications list given it's UUID
138  
139 :param item_uuid:
140 :return:
141 """
142 success = False
143 # Get all items out of queue
144 current_items = self.__get_all_items()
145 # Create list of items that will be queued again
146 requeue_items = []
147 for current_item in current_items:
148 if current_item.get('uuid') != item_uuid:
149 requeue_items.append(current_item)
150 # Remove the requested item UUID from the all_items set
151 lock = threading.RLock()
152 lock.acquire()
153 if item_uuid in self.all_items:
154 self.all_items.remove(item_uuid)
155 success = True
156 lock.release()
157 # Add all requeue_items items back into the queue
158 self.__requeue_items(requeue_items)
159 return success
160  
161 def read_all_items(self):
162 # Get all items out of queue
163 current_items = self.__get_all_items()
164 # Add all requeue_items items back into the queue
165 self.__requeue_items(current_items)
166 # Return items list
167 return current_items
168  
169 def update(self, item):
170 # Ensure received item is valid
171 self.__validate_item(item)
172 # Generate uuid if one is not provided
173 if not item.get('uuid'):
174 item['uuid'] = str(uuid.uuid4())
175 # If it is not already in message list, add it to the list and the queue
176 if item.get('uuid') not in self.all_items:
177 self.all_items.add(item.get('uuid'))
178 self.__add_to_queue(item)
179 else:
180 # Get all items out of queue
181 current_items = self.__get_all_items()
182 # Create list of items that will be queued again
183 # This will not include the item requested for update
184 lock = threading.RLock()
185 lock.acquire()
186 requeue_items = []
187 for current_item in current_items:
188 if current_item.get('uuid') != item.get('uuid'):
189 requeue_items.append(current_item)
190 continue
191 requeue_items.append(item)
192 # Add all requeue_items items back into the queue
193 self.__requeue_items(requeue_items)