]> git.0d.be Git - botaradio.git/blob - media/cache.py
feat: Lartza's urlban idea #91, fixed private message
[botaradio.git] / media / cache.py
1 import logging
2 import os
3
4 from database import MusicDatabase
5 import json
6 import threading
7
8 from media.item import item_builders, item_loaders, item_id_generators, dict_to_item, dicts_to_items
9 from database import MusicDatabase
10 import variables as var
11 import util
12
13
14 class MusicCache(dict):
15     def __init__(self, db: MusicDatabase):
16         super().__init__()
17         self.db = db
18         self.log = logging.getLogger("bot")
19         self.dir = None
20         self.files = []
21         self.dir_lock = threading.Lock()
22
23     def get_item_by_id(self, bot, id): # Why all these functions need a bot? Because it need the bot to send message!
24         if id in self:
25             return self[id]
26
27         # if not cached, query the database
28         item = self.fetch(bot, id)
29         if item is not None:
30             self[id] = item
31             self.log.debug("library: music found in database: %s" % item.format_debug_string())
32             return item
33         else:
34             return None
35             #print(id)
36             #raise KeyError("Unable to fetch item from the database! Please try to refresh the cache by !recache.")
37
38
39     def get_item(self, bot, **kwargs):
40         # kwargs should provide type and id, and parameters to build the item if not existed in the library.
41         # if cached
42         if 'id' in kwargs:
43             id = kwargs['id']
44         else:
45             id = item_id_generators[kwargs['type']](**kwargs)
46
47         if id in self:
48             return self[id]
49
50         # if not cached, query the database
51         item = self.fetch(bot, id)
52         if item is not None:
53             self[id] = item
54             self.log.debug("library: music found in database: %s" % item.format_debug_string())
55             return item
56
57         # if not in the database, build one
58         self[id] = item_builders[kwargs['type']](bot, **kwargs) # newly built item will not be saved immediately
59         return self[id]
60
61     def get_items_by_tags(self, bot, tags):
62         music_dicts = self.db.query_music_by_tags(tags)
63         items = []
64         if music_dicts:
65             for music_dict in music_dicts:
66                 id = music_dict['id']
67                 self[id] = dict_to_item(bot, music_dict)
68                 items.append(self[id])
69
70         return items
71
72     def fetch(self, bot, id):
73         music_dicts = self.db.query_music(id=id)
74         if music_dicts:
75             music_dict = music_dicts[0]
76             self[id] = dict_to_item(bot, music_dict)
77             return self[id]
78         else:
79             return None
80
81     def save(self, id):
82         self.log.debug("library: music save into database: %s" % self[id].format_debug_string())
83         self.db.insert_music(self[id].to_dict())
84
85     def free_and_delete(self, id):
86         item = self.get_item_by_id(None, id)
87         if item:
88             self.log.debug("library: DELETE item from the database: %s" % item.format_debug_string())
89
90             if item.type == 'file' and item.path in self.file_id_lookup:
91                 if item.path in self.file_id_lookup:
92                     del self.file_id_lookup[item.path]
93                 self.files.remove(item.path)
94                 self.save_dir_cache()
95             elif item.type == 'url':
96                 os.remove(item.path)
97
98             if item.id in self:
99                 del self[item.id]
100             self.db.delete_music(id=item.id)
101
102     def free(self, id):
103         if id in self:
104             self.log.debug("library: cache freed for item: %s" % self[id].format_debug_string())
105             del self[id]
106
107     def free_all(self):
108         self.log.debug("library: all cache freed")
109         self.clear()
110
111     def build_dir_cache(self, bot):
112         self.dir_lock.acquire()
113         self.log.info("library: rebuild directory cache")
114         self.files = []
115         self.file_id_lookup = {}
116         files = util.get_recursive_file_list_sorted(var.music_folder)
117         self.dir = util.Dir(var.music_folder)
118         for file in files:
119             item = self.fetch(bot, item_id_generators['file'](path=file))
120             if not item:
121                 item = item_builders['file'](bot, path=file)
122                 self.log.debug("library: music save into database: %s" % item.format_debug_string())
123                 self.db.insert_music(item.to_dict())
124
125             self.dir.add_file(file)
126             self.files.append(file)
127             self.file_id_lookup[file] = item.id
128
129         self.save_dir_cache()
130         self.dir_lock.release()
131
132     def save_dir_cache(self):
133         var.db.set("dir_cache", "files", json.dumps(self.file_id_lookup))
134
135     def load_dir_cache(self, bot):
136         self.dir_lock.acquire()
137         self.log.info("library: load directory cache from database")
138         loaded = json.loads(var.db.get("dir_cache", "files"))
139         self.files = loaded.keys()
140         self.file_id_lookup = loaded
141         self.dir = util.Dir(var.music_folder)
142         for file, id in loaded.items():
143             self.dir.add_file(file)
144         self.dir_lock.release()
145
146
147 class CachedItemWrapper:
148     def __init__(self, lib, id, type, user):
149         self.lib = lib
150         self.id = id
151         self.user = user
152         self.type = type
153         self.log = logging.getLogger("bot")
154         self.version = 0
155
156     def item(self):
157         return self.lib[self.id]
158
159     def to_dict(self):
160         dict = self.item().to_dict()
161         dict['user'] = self.user
162         return dict
163
164     def validate(self):
165         ret = self.item().validate()
166         if ret and self.item().version > self.version:
167             self.version = self.item().version
168             self.lib.save(self.id)
169         return ret
170
171     def prepare(self):
172         ret = self.item().prepare()
173         if ret and self.item().version > self.version:
174             self.version = self.item().version
175             self.lib.save(self.id)
176         return ret
177
178     def async_prepare(self):
179         th = threading.Thread(
180             target=self.prepare, name="Prepare-" + self.id[:7])
181         self.log.info(
182             "%s: start preparing item in thread: " % self.item().type + self.format_debug_string())
183         th.daemon = True
184         th.start()
185         return th
186
187     def uri(self):
188         return self.item().uri()
189
190     def add_tags(self, tags):
191         self.item().add_tags(tags)
192         if self.item().version > self.version:
193             self.version = self.item().version
194             self.lib.save(self.id)
195
196     def remove_tags(self, tags):
197         self.item().remove_tags(tags)
198         if self.item().version > self.version:
199             self.version = self.item().version
200             self.lib.save(self.id)
201
202     def clear_tags(self):
203         self.item().clear_tags()
204         if self.item().version > self.version:
205             self.version = self.item().version
206             self.lib.save(self.id)
207
208     def is_ready(self):
209         return self.item().is_ready()
210
211     def is_failed(self):
212         return self.item().is_failed()
213
214     def format_current_playing(self):
215         return self.item().format_current_playing(self.user)
216
217     def format_song_string(self):
218         return self.item().format_song_string(self.user)
219
220     def format_short_string(self):
221         return self.item().format_short_string()
222
223     def format_debug_string(self):
224         return self.item().format_debug_string()
225
226     def display_type(self):
227         return self.item().display_type()
228
229
230 # Remember!!! Get wrapper functions will automatically add items into the cache!
231 def get_cached_wrapper_from_scrap(bot, **kwargs):
232     item = var.cache.get_item(bot, **kwargs)
233     if 'user' not in kwargs:
234         raise KeyError("Which user added this song?")
235     return CachedItemWrapper(var.cache, item.id, kwargs['type'], kwargs['user'])
236
237
238 def get_cached_wrapper_from_dict(bot, dict_from_db, user):
239     item = dict_to_item(bot, dict_from_db)
240     var.cache[dict_from_db['id']] = item
241     return CachedItemWrapper(var.cache, item.id, item.type, user)
242
243
244 def get_cached_wrapper_by_id(bot, id, user):
245     item = var.cache.get_item_by_id(bot, id)
246     if item:
247         return CachedItemWrapper(var.cache, item.id, item.type, user)
248     else:
249         return None
250
251
252 def get_cached_wrappers_by_tags(bot, tags, user):
253     items = var.cache.get_items_by_tags(bot, tags)
254     ret = []
255     for item in items:
256         ret.append(CachedItemWrapper(var.cache, item.id, item.type, user))
257     return ret