import variables as var
from librb import radiobrowser
from database import SettingsDatabase, MusicDatabase
+from media.item import item_builders, item_loaders, item_id_generators, dict_to_item, dicts_to_items
from media.playlist import get_item_wrapper, get_item_wrapper_by_id, get_item_wrappers_by_tags
from media.file import FileItem
from media.url_from_playlist import PlaylistURLItem, get_playlist_info
bot.register_command(constants.commands('add_tag'), cmd_add_tag)
bot.register_command(constants.commands('remove_tag'), cmd_remove_tag)
bot.register_command(constants.commands('find_tagged'), cmd_find_tagged)
+ bot.register_command(constants.commands('search'), cmd_search_library)
+ bot.register_command(constants.commands('add_from_shortlist'), cmd_shortlist)
bot.register_command(constants.commands('drop_database'), cmd_drop_database, True)
bot.register_command(constants.commands('rescan'), cmd_refresh_cache, True)
bot.send_msg(msg, text)
+# ---------------- Variables -----------------
+
+song_shortlist = []
+
# ---------------- Commands ------------------
def cmd_play_file(bot, user, text, command, parameter, do_not_refresh_cache=False):
- global log
+ global log, song_shortlist
# if parameter is {index}
if parameter.isdigit():
- files = var.library.files
+ files = var.cache.files
if int(parameter) < len(files):
- music_wrapper = get_item_wrapper_by_id(bot, var.library.file_id_lookup[files[int(parameter)]], user)
+ music_wrapper = get_item_wrapper_by_id(bot, var.cache.file_id_lookup[files[int(parameter)]], user)
var.playlist.append(music_wrapper)
log.info("cmd: add to playlist: " + music_wrapper.format_debug_string())
bot.send_msg(constants.strings('file_added', item=music_wrapper.format_song_string()), text)
# bot.send_msg(constants.strings('no_file'), text)
# return
- if parameter in var.library.files:
+ if parameter in var.cache.files:
music_wrapper = get_item_wrapper(bot, type='file', path=parameter, user=user)
var.playlist.append(music_wrapper)
log.info("cmd: add to playlist: " + music_wrapper.format_debug_string())
return
# if parameter is {folder}
- files = var.library.dir.get_files(parameter)
+ files = var.cache.dir.get_files(parameter)
if files:
msgs = [constants.strings('multiple_file_added')]
count = 0
for file in files:
count += 1
- music_wrapper = get_item_wrapper_by_id(bot, var.library.file_id_lookup[file],user)
+ music_wrapper = get_item_wrapper_by_id(bot, var.cache.file_id_lookup[file], user)
var.playlist.append(music_wrapper)
log.info("cmd: add to playlist: " + music_wrapper.format_debug_string())
msgs.append("{} ({})".format(music_wrapper.item().title, music_wrapper.item().path))
else:
# try to do a partial match
- files = var.library.files
- matches = [(index, file) for index, file in enumerate(files) if parameter.lower() in file.lower()]
+ files = var.cache.files
+ matches = [ file for file in files if parameter.lower() in file.lower()]
if len(matches) == 1:
- file = matches[0][1]
- music_wrapper = get_item_wrapper_by_id(bot, var.library.file_id_lookup[file],user)
+ file = matches[0]
+ music_wrapper = get_item_wrapper_by_id(bot, var.cache.file_id_lookup[file], user)
var.playlist.append(music_wrapper)
log.info("cmd: add to playlist: " + music_wrapper.format_debug_string())
bot.send_msg(constants.strings('file_added', item=music_wrapper.format_song_string()), text)
return
elif len(matches) > 1:
msgs = [ constants.strings('multiple_matches') ]
- for match in matches:
- music_wrapper = get_item_wrapper_by_id(bot, var.library.file_id_lookup[match[1]], user)
- msgs.append("<b>{:0>3d}</b> - <b>{:s}</b> ({:s})".format(
- match[0], music_wrapper.item().title, match[1]))
+ song_shortlist = []
+ for index, match in enumerate(matches):
+ id = var.cache.file_id_lookup[match]
+ music_dict = var.music_db.query_music_by_id(id)
+ item = dict_to_item(bot, music_dict)
+
+ song_shortlist.append(music_dict)
+
+ msgs.append("<b>{:d}</b> - <b>{:s}</b> ({:s})".format(
+ index + 1, item.title, match))
send_multi_lines(bot, msgs, text)
return
if do_not_refresh_cache:
bot.send_msg(constants.strings("no_file"), text)
else:
- var.library.build_dir_cache(bot)
+ var.cache.build_dir_cache(bot)
cmd_play_file(bot, user, text, command, parameter, do_not_refresh_cache=True)
music_folder = var.music_folder
if parameter:
- files = var.library.files
+ files = var.cache.files
msgs = [ constants.strings('multiple_file_added') + "<ul>"]
count = 0
try:
match = re.search(parameter, file)
if match and match[0]:
count += 1
- music_wrapper = get_item_wrapper_by_id(bot, var.library.file_id_lookup[file], user)
+ music_wrapper = get_item_wrapper_by_id(bot, var.cache.file_id_lookup[file], user)
music_wrappers.append(music_wrapper)
log.info("cmd: add to playlist: " + music_wrapper.format_debug_string())
msgs.append("<li><b>{}</b> ({})</li>".format(music_wrapper.item().title,
if do_not_refresh_cache:
bot.send_msg(constants.strings("no_file"), text)
else:
- var.library.build_dir_cache(bot)
+ var.cache.build_dir_cache(bot)
cmd_play_file_match(bot, user, text, command, parameter, do_not_refresh_cache=True)
except re.error as e:
url = radiobrowser.geturl_byid(parameter)
if url != "-1":
log.info('cmd: Found url: ' + url)
- music_wrapper = get_item_wrapper(bot, type='radio', url=url, name=stationname)
+ music_wrapper = get_item_wrapper(bot, type='radio', url=url, name=stationname, user=user)
var.playlist.append(music_wrapper)
log.info("cmd: add to playlist: " + music_wrapper.format_debug_string())
bot.async_download_next()
yt_last_page = 0 # TODO: if we keep adding global variables, we need to consider sealing all commands up into classes.
def cmd_yt_search(bot, user, text, command, parameter):
- global log, yt_last_result, yt_last_page
+ global log, yt_last_result, yt_last_page, song_shortlist
item_per_page = 5
if parameter:
if parameter.startswith("-n"):
yt_last_page += 1
if len(yt_last_result) > yt_last_page * item_per_page:
+ song_shortlist = [{'type': 'url',
+ 'url': "https://www.youtube.com/watch?v=" + result[0],
+ 'title': result[1]
+ } for result in yt_last_result[yt_last_page * item_per_page: item_per_page]]
msg = _yt_format_result(yt_last_result, yt_last_page * item_per_page, item_per_page)
bot.send_msg(constants.strings('yt_result', result_table=msg), text)
else:
if results:
yt_last_result = results
yt_last_page = 0
+ song_shortlist = [{'type': 'url', 'url': "https://www.youtube.com/watch?v=" + result[0]}
+ for result in results[0: item_per_page]]
msg = _yt_format_result(results, 0, item_per_page)
bot.send_msg(constants.strings('yt_result', result_table=msg), text)
else:
msg = '<table><tr><th width="10%">Index</th><th>Title</th><th width="20%">Uploader</th></tr>'
for index, item in enumerate(results[start:start+count]):
msg += '<tr><td>{index:d}</td><td>{title}</td><td>{uploader}</td></tr>'.format(
- index=index + 1 + start, title=item[1], uploader=item[2])
+ index=index + 1, title=item[1], uploader=item[2])
msg += '</table>'
return msg
global log, yt_last_result, yt_last_page
if parameter:
- if parameter.isdigit() and 0 <= int(parameter) - 1 < len(yt_last_result):
- url = "https://www.youtube.com/watch?v=" + yt_last_result[int(parameter) - 1][0]
+ results = util.youtube_search(parameter)
+ if results:
+ yt_last_result = results
+ yt_last_page = 0
+ url = "https://www.youtube.com/watch?v=" + yt_last_result[0][0]
cmd_play_url(bot, user, text, command, url)
else:
- results = util.youtube_search(parameter)
- if results:
- yt_last_result = results
- yt_last_page = 0
- url = "https://www.youtube.com/watch?v=" + yt_last_result[0][0]
- cmd_play_url(bot, user, text, command, url)
- else:
- bot.send_msg(constants.strings('yt_query_error'))
+ bot.send_msg(constants.strings('yt_query_error'))
else:
bot.send_msg(constants.strings('bad_parameter', command=command), text)
def cmd_list_file(bot, user, text, command, parameter):
global log
- files = var.library.files
+ files = var.cache.files
msgs = [ "<br> <b>Files available:</b>" if not parameter else "<br> <b>Matched files:</b>" ]
try:
count = 0
bot.send_msg(constants.strings('bad_parameter', command=command), text)
def cmd_find_tagged(bot, user, text, command, parameter):
+ global song_shortlist
+
if not parameter:
bot.send_msg(constants.strings('bad_parameter', command=command))
return
tags = parameter.split(",")
tags = list(map(lambda t: t.strip(), tags))
- music_wrappers = get_item_wrappers_by_tags(bot, tags, user)
- for music_wrapper in music_wrappers:
+
+ music_dicts = var.music_db.query_music_by_tags(tags)
+ song_shortlist = music_dicts
+ items = dicts_to_items(bot, music_dicts)
+
+ for i, item in enumerate(items):
count += 1
- msgs.append("<li><b>{}</b> (<i>{}</i>)</li>".format(music_wrapper.item().title, ", ".join(music_wrapper.item().tags)))
+ msgs.append("<li><b>{:d}</b> - <b>{}</b> (<i>{}</i>)</li>".format(i+1, item.title, ", ".join(item.tags)))
+
+ if count != 0:
+ msgs.append("</ul>")
+ msgs.append(constants.strings("shortlist_instruction"))
+ send_multi_lines(bot, msgs, text, "")
+ else:
+ bot.send_msg(constants.strings("no_file"), text)
+
+def cmd_search_library(bot, user, text, command, parameter):
+ global song_shortlist
+ if not parameter:
+ bot.send_msg(constants.strings('bad_parameter', command=command))
+ return
+
+ msgs = [constants.strings('multiple_file_found') + "<ul>"]
+ count = 0
+
+ _keywords = parameter.split(" ")
+ keywords = []
+ for kw in _keywords:
+ if kw:
+ keywords.append(kw)
+
+ music_dicts = var.music_db.query_music_by_keywords(keywords)
+ items = dicts_to_items(bot, music_dicts)
+ song_shortlist = music_dicts
+
+ for item in items:
+ count += 1
+ if len(item.tags) > 0:
+ msgs.append("<li><b>{:d}</b> - [{}] <b>{}</b> (<i>{}</i>)</li>".format(count, item.display_type(), item.title, ", ".join(item.tags)))
+ else:
+ msgs.append("<li><b>{:d}</b> - [{}] <b>{}</b> </li>".format(count, item.display_type(), item.title, ", ".join(item.tags)))
if count != 0:
msgs.append("</ul>")
+ msgs.append(constants.strings("shortlist_instruction"))
send_multi_lines(bot, msgs, text, "")
else:
bot.send_msg(constants.strings("no_file"), text)
+
+def cmd_shortlist(bot, user, text, command, parameter):
+ global song_shortlist
+ indexes = []
+ try:
+ indexes = [ int(i) for i in parameter.split(" ") ]
+ except ValueError:
+ bot.send_msg(constants.strings('bad_parameter', command=command), text)
+ return
+
+ if len(indexes) > 1:
+ msgs = [constants.strings('multiple_file_added') + "<ul>"]
+ for index in indexes:
+ if 1 <= index <= len(song_shortlist):
+ kwargs = song_shortlist[index - 1]
+ kwargs['user'] = user
+ music_wrapper = get_item_wrapper(bot, **kwargs)
+ var.playlist.append(music_wrapper)
+ log.info("cmd: add to playlist: " + music_wrapper.format_debug_string())
+ msgs.append("<li><b>{}</b></li>".format(music_wrapper.item().title))
+ song_shortlist = []
+ else:
+ bot.send_msg(constants.strings('bad_parameter', command=command), text)
+ return
+
+ msgs.append("</ul>")
+ send_multi_lines(bot, msgs, text, "")
+ song_shortlist = []
+ return
+ elif len(indexes) == 1:
+ index = indexes[0]
+ if 1 <= index <= len(song_shortlist):
+ kwargs = song_shortlist[index - 1]
+ kwargs['user'] = user
+ music_wrapper = get_item_wrapper(bot, **kwargs)
+ var.playlist.append(music_wrapper)
+ log.info("cmd: add to playlist: " + music_wrapper.format_debug_string())
+ bot.send_msg(constants.strings('file_added', item=music_wrapper.format_song_string()), text)
+ song_shortlist = []
+ return
+
+ bot.send_msg(constants.strings('bad_parameter', command=command), text)
+
+
def cmd_drop_database(bot, user, text, command, parameter):
global log
def cmd_refresh_cache(bot, user, text, command, parameter):
global log
if bot.is_admin(user):
- var.library.build_dir_cache(bot)
+ var.cache.build_dir_cache(bot)
log.info("command: Local file cache refreshed.")
bot.send_msg(constants.strings('cache_refreshed'), text)
else:
add_tag = addtag
remove_tag = untag
find_tagged = findtagged, ft
+search = search
+add_from_shortlist = shortlist, sl
user_ban = userban
user_unban = userunban
current_mode = Current playback mode is <i>{mode}</i>.
change_mode = Playback mode set to <i>{mode}</i> by {user}.
repeat = Repeat {song} for {n} times.
-yt_result = Youtube query result: {result_table} Use <i>!ytplay</i> {{index}} to play the item you want. <br />
+yt_result = Youtube query result: {result_table} Use <i>!sl {{indexes}}</i> to play the item you want. <br />
<i>!ytquery -n</i> for the next page.
yt_no_more = No more results!
yt_query_error = Unable to query youtube!
removed_tags_from_all = Removed tags <i>{tags}</i> from songs on the playlist.
cleared_tags = Removed all tags from <b>{song}</b>.
cleared_tags_from_all = Removed all tags from songs on the playlist.
+shortlist_instruction = Use <i>!sl {indexes}</i> to play the item you want.
help = <h3>Commands</h3>
<b>Control</b>
<li> <b>!<u>ur</u>l </b> {url} - add Youtube or SoundCloud music </li>
<li> <b>!<u>playl</u>ist </b> {url} [{offset}] - add all items in a Youtube or SoundCloud playlist, and start with the {offset}-th item </li>
<li> <b>!<u>t</u>ag </b> {tags} - add all items with tags {tags}, tags separated by ",". </li>
+ <li> <b>!<u>sh</u>ortlist </b> (or <b>!sl</b>) {indexes} - add {indexes}-th item on the shortlist. </li>
<li> <b>!rm </b> {num} - remove the num-th song on the playlist </li>
<li> <b>!<u>rep</u>eat </b> [{num}] - repeat current song {num} (1 by default) times.</li>
<li> <b>!<u>ran</u>dom </b> - randomize the playlist.</li>
<li> <b>!<u>yp</u>lay </b> {index/keywords} - play an item from the list returned by <i>!ytquery</i>, or add the
first search result of {keywords} into the playlist.</li>
</ul>
- <b>Tag</b>
+ <b>Music Library</b>
<ul>
+ <li> <b>!<u>se</u>arch </b> {keywords} - find item with {keywords} in the music library, keywords separated by space.</li>
<li> <b>!<u>addt</u>ag </b> {index} {tags} - add {tags} to {index}-th item on the playlist, tags separated by ",". </li>
<li> <b>!<u>addt</u>ag </b> * {tags} - add {tags} to all items on the playlist. </li>
<li> <b>!<u>un</u>tag </b> {index/*} {tags} - remove {tags} from {index}-th item on the playlist. </li>
else:
return None
+ def query_music_by_keywords(self, keywords):
+ condition = []
+ filler = []
+
+ for keyword in keywords:
+ condition.append('title LIKE ?')
+ filler.append("%{:s}%".format(keyword))
+
+
+ condition_str = " AND ".join(condition)
+
+ conn = sqlite3.connect(self.db_path)
+ cursor = conn.cursor()
+ results = cursor.execute("SELECT id, type, title, metadata, tags FROM music "
+ "WHERE %s" % condition_str, filler).fetchall()
+ conn.close()
+
+ if len(results) > 0:
+ music_dicts = []
+ for result in results:
+ music_dict = json.loads(result[3])
+ music_dict['type'] = result[1]
+ music_dict['title'] = result[2]
+ music_dict['id'] = result[0]
+ music_dict['tags'] = result[4].strip(",").split(",")
+ if not music_dict['tags'][0]:
+ music_dict['tags'] = []
+
+ music_dicts.append(music_dict)
+
+ return music_dicts
+ else:
+ return None
+
def query_music_by_tags(self, tags):
condition = []
filler = []
def build_path_tags_lookup():
path_lookup = {}
- items = var.library.file_id_lookup.items()
+ items = var.cache.file_id_lookup.items()
for path, id in items:
path_lookup[path] = var.music_db.query_tags_by_id(id)
@web.route("/", methods=['GET'])
@requires_auth
def index():
- while var.library.dir_lock.locked():
+ while var.cache.dir_lock.locked():
time.sleep(0.1)
tags_color_lookup = build_tags_color_lookup()
path_tags_lookup = build_path_tags_lookup()
return render_template('index.html',
- all_files=var.library.files,
+ all_files=var.cache.files,
tags_lookup=path_tags_lookup,
tags_color_lookup=tags_color_lookup,
- music_library=var.library.dir,
+ music_library=var.cache.dir,
os=os,
playlist=var.playlist,
user=var.user,
if 'add_file_bottom' in request.form and ".." not in request.form['add_file_bottom']:
path = var.music_folder + request.form['add_file_bottom']
if os.path.isfile(path):
- music_wrapper = get_item_wrapper_by_id(var.bot, var.library.file_id_lookup[request.form['add_file_bottom']], user)
+ music_wrapper = get_item_wrapper_by_id(var.bot, var.cache.file_id_lookup[request.form['add_file_bottom']], user)
var.playlist.append(music_wrapper)
log.info('web: add to playlist(bottom): ' + music_wrapper.format_debug_string())
elif 'add_file_next' in request.form and ".." not in request.form['add_file_next']:
path = var.music_folder + request.form['add_file_next']
if os.path.isfile(path):
- music_wrapper = get_item_wrapper_by_id(var.bot, var.library.file_id_lookup[request.form['add_file_next']], user)
+ music_wrapper = get_item_wrapper_by_id(var.bot, var.cache.file_id_lookup[request.form['add_file_next']], user)
var.playlist.insert(var.playlist.current_index + 1, music_wrapper)
log.info('web: add to playlist(next): ' + music_wrapper.format_debug_string())
folder += '/'
if os.path.isdir(var.music_folder + folder):
- dir = var.library.dir
+ dir = var.cache.dir
if 'add_folder_recursively' in request.form:
files = dir.get_files_recursively(folder)
else:
music_wrappers = list(map(
lambda file:
- get_item_wrapper_by_id(var.bot, var.library.file_id_lookup[folder + file], user),
+ get_item_wrapper_by_id(var.bot, var.cache.file_id_lookup[folder + file], user),
files))
var.playlist.extend(music_wrappers)
var.db.set('playlist', 'playback_mode', "autoplay")
log.info("web: playback mode changed to autoplay.")
if action == "rescan":
- var.library.build_dir_cache(var.bot)
+ var.cache.build_dir_cache(var.bot)
log.info("web: Local file cache refreshed.")
elif action == "stop":
var.bot.stop()
log.info('web: Download of file %s requested from %s:' % (requested_file, request.remote_addr))
if '../' not in requested_file:
folder_path = var.music_folder
- files = var.library.files
+ files = var.cache.files
if requested_file in files:
filepath = os.path.join(folder_path, requested_file)
--- /dev/null
+import logging
+from database import MusicDatabase
+import json
+import threading
+
+from media.item import item_builders, item_loaders, item_id_generators, dict_to_item, dicts_to_items
+from database import MusicDatabase
+import variables as var
+import util
+
+
+class MusicCache(dict):
+ def __init__(self, db: MusicDatabase):
+ super().__init__()
+ self.db = db
+ self.log = logging.getLogger("bot")
+ self.dir = None
+ self.files = []
+ self.dir_lock = threading.Lock()
+
+ def get_item_by_id(self, bot, id): # Why all these functions need a bot? Because it need the bot to send message!
+ if id in self:
+ return self[id]
+
+ # if not cached, query the database
+ item = self.fetch(bot, id)
+ if item is not None:
+ self[id] = item
+ self.log.debug("library: music found in database: %s" % item.format_debug_string())
+ return item
+ else:
+ print(id)
+ raise KeyError("Unable to fetch item from the database! Please try to refresh the cache by !recache.")
+
+
+ def get_item(self, bot, **kwargs):
+ # kwargs should provide type and id, and parameters to build the item if not existed in the library.
+ # if cached
+ if 'id' in kwargs:
+ id = kwargs['id']
+ else:
+ id = item_id_generators[kwargs['type']](**kwargs)
+
+ if id in self:
+ return self[id]
+
+ # if not cached, query the database
+ item = self.fetch(bot, id)
+ if item is not None:
+ self[id] = item
+ self.log.debug("library: music found in database: %s" % item.format_debug_string())
+ return item
+
+ # if not in the database, build one
+ self[id] = item_builders[kwargs['type']](bot, **kwargs) # newly built item will not be saved immediately
+ return self[id]
+
+ def get_items_by_tags(self, bot, tags):
+ music_dicts = self.db.query_music_by_tags(tags)
+ items = []
+ if music_dicts:
+ for music_dict in music_dicts:
+ id = music_dict['id']
+ self[id] = dict_to_item(bot, music_dict)
+ items.append(self[id])
+
+ return items
+
+ def fetch(self, bot, id):
+ music_dicts = self.db.query_music(id=id)
+ if music_dicts:
+ music_dict = music_dicts[0]
+ self[id] = dict_to_item(bot, music_dict)
+ return self[id]
+ else:
+ return None
+
+ def save(self, id):
+ self.log.debug("library: music save into database: %s" % self[id].format_debug_string())
+ self.db.insert_music(self[id].to_dict())
+
+ def delete(self, id):
+ try:
+ item = self.get_item_by_id(None, id)
+ self.log.debug("library: DELETE item from the database: %s" % item.format_debug_string())
+
+ if item.type == 'file' and item.path in self.file_id_lookup:
+ if item.path in self.file_id_lookup:
+ del self.file_id_lookup[item.path]
+ self.files.remove(item.path)
+ self.save_dir_cache()
+
+ if item.id in self:
+ del self[item.id]
+ self.db.delete_music(id=item.id)
+ except KeyError:
+ return
+
+ def free(self, id):
+ if id in self:
+ self.log.debug("library: cache freed for item: %s" % self[id].format_debug_string())
+ del self[id]
+
+ def free_all(self):
+ self.log.debug("library: all cache freed")
+ self.clear()
+
+ def build_dir_cache(self, bot):
+ self.dir_lock.acquire()
+ self.log.info("library: rebuild directory cache")
+ self.files = []
+ self.file_id_lookup = {}
+ files = util.get_recursive_file_list_sorted(var.music_folder)
+ self.dir = util.Dir(var.music_folder)
+ for file in files:
+ item = self.get_item(bot, type='file', path=file)
+ if item.validate():
+ self.dir.add_file(file)
+ self.files.append(file)
+ self.log.debug("library: music save into database: %s" % item.format_debug_string())
+ self.db.insert_music(item.to_dict())
+ self.file_id_lookup[file] = item.id
+
+ self.save_dir_cache()
+ self.dir_lock.release()
+
+ def save_dir_cache(self):
+ var.db.set("dir_cache", "files", json.dumps(self.file_id_lookup))
+
+ def load_dir_cache(self, bot):
+ self.dir_lock.acquire()
+ self.log.info("library: load directory cache from database")
+ loaded = json.loads(var.db.get("dir_cache", "files"))
+ self.files = loaded.keys()
+ self.file_id_lookup = loaded
+ self.dir = util.Dir(var.music_folder)
+ for file, id in loaded.items():
+ self.dir.add_file(file)
+ self.dir_lock.release()
+
def format_song_string(self, user):
return constants.strings("file_item",
title=self.title,
- artist=self.artist,
+ artist=self.artist if self.artist else '??',
user=user
)
item_loaders['base'] = example_loader
item_id_generators['base'] = example_id_generator
+def dicts_to_items(bot, music_dicts):
+ items = []
+ for music_dict in music_dicts:
+ type = music_dict['type']
+ items.append(item_loaders[type](bot, music_dict))
+ return items
+
+def dict_to_item(bot, music_dict):
+ type = music_dict['type']
+ return item_loaders[type](bot, music_dict)
+
+
class BaseItem:
def __init__(self, bot, from_dict=None):
self.bot = bot
+++ /dev/null
-import logging
-from database import MusicDatabase
-import json
-import threading
-
-from media.item import item_builders, item_loaders, item_id_generators
-from database import MusicDatabase
-import variables as var
-import util
-
-
-class MusicLibrary(dict):
- def __init__(self, db: MusicDatabase):
- super().__init__()
- self.db = db
- self.log = logging.getLogger("bot")
- self.dir = None
- self.files = []
- self.dir_lock = threading.Lock()
-
- def get_item_by_id(self, bot, id): # Why all these functions need a bot? Because it need the bot to send message!
- if id in self:
- return self[id]
-
- # if not cached, query the database
- item = self.fetch(bot, id)
- if item is not None:
- self[id] = item
- self.log.debug("library: music found in database: %s" % item.format_debug_string())
- return item
- else:
- print(id)
- raise KeyError("Unable to fetch item from the database! Please try to refresh the cache by !recache.")
-
-
- def get_item(self, bot, **kwargs):
- # kwargs should provide type and id, and parameters to build the item if not existed in the library.
- # if cached
- id = item_id_generators[kwargs['type']](**kwargs)
- if id in self:
- return self[id]
-
- # if not cached, query the database
- item = self.fetch(bot, id)
- if item is not None:
- self[id] = item
- self.log.debug("library: music found in database: %s" % item.format_debug_string())
- return item
-
- # if not in the database, build one
- self[id] = item_builders[kwargs['type']](bot, **kwargs) # newly built item will not be saved immediately
- return self[id]
-
- def get_items_by_tags(self, bot, tags):
- music_dicts = self.db.query_music_by_tags(tags)
- items = []
- if music_dicts:
- for music_dict in music_dicts:
- id = music_dict['id']
- type = music_dict['type']
- self[id] = item_loaders[type](bot, music_dict)
- items.append(self[id])
-
- return items
-
- def fetch(self, bot, id):
- music_dicts = self.db.query_music(id=id)
- if music_dicts:
- music_dict = music_dicts[0]
- type = music_dict['type']
- self[id] = item_loaders[type](bot, music_dict)
- return self[id]
- else:
- return None
-
- def save(self, id):
- self.log.debug("library: music save into database: %s" % self[id].format_debug_string())
- self.db.insert_music(self[id].to_dict())
-
- def delete(self, id):
- try:
- item = self.get_item_by_id(None, id)
- self.log.debug("library: DELETE item from the database: %s" % item.format_debug_string())
-
- if item.type == 'file' and item.path in self.file_id_lookup:
- if item.path in self.file_id_lookup:
- del self.file_id_lookup[item.path]
- self.files.remove(item.path)
- self.save_dir_cache()
-
- if item.id in self:
- del self[item.id]
- self.db.delete_music(id=item.id)
- except KeyError:
- return
-
- def free(self, id):
- if id in self:
- self.log.debug("library: cache freed for item: %s" % self[id].format_debug_string())
- del self[id]
-
- def free_all(self):
- self.log.debug("library: all cache freed")
- self.clear()
-
- def build_dir_cache(self, bot):
- self.dir_lock.acquire()
- self.log.info("library: rebuild directory cache")
- self.files = []
- self.file_id_lookup = {}
- files = util.get_recursive_file_list_sorted(var.music_folder)
- self.dir = util.Dir(var.music_folder)
- for file in files:
- item = self.get_item(bot, type='file', path=file)
- if item.validate():
- self.dir.add_file(file)
- self.files.append(file)
- self.log.debug("library: music save into database: %s" % item.format_debug_string())
- self.db.insert_music(item.to_dict())
- self.file_id_lookup[file] = item.id
-
- self.save_dir_cache()
- self.dir_lock.release()
-
- def save_dir_cache(self):
- var.db.set("dir_cache", "files", json.dumps(self.file_id_lookup))
-
- def load_dir_cache(self, bot):
- self.dir_lock.acquire()
- self.log.info("library: load directory cache from database")
- loaded = json.loads(var.db.get("dir_cache", "files"))
- self.files = loaded.keys()
- self.file_id_lookup = loaded
- self.dir = util.Dir(var.music_folder)
- for file, id in loaded.items():
- self.dir.add_file(file)
- self.dir_lock.release()
-
from media.url_from_playlist import PlaylistURLItem
from media.radio import RadioItem
from database import MusicDatabase
-from media.library import MusicLibrary
+from media.cache import MusicCache
class PlaylistItemWrapper:
def __init__(self, lib, id, type, user):
return self.item().display_type()
+# Remember!!! Using these three get wrapper functions will automatically add items into the cache!
def get_item_wrapper(bot, **kwargs):
- item = var.library.get_item(bot, **kwargs)
+ item = var.cache.get_item(bot, **kwargs)
if 'user' not in kwargs:
raise KeyError("Which user added this song?")
- return PlaylistItemWrapper(var.library, item.id, kwargs['type'], kwargs['user'])
+ return PlaylistItemWrapper(var.cache, item.id, kwargs['type'], kwargs['user'])
def get_item_wrapper_by_id(bot, id, user):
- item = var.library.get_item_by_id(bot, id)
+ item = var.cache.get_item_by_id(bot, id)
if item:
- return PlaylistItemWrapper(var.library, item.id, item.type, user)
+ return PlaylistItemWrapper(var.cache, item.id, item.type, user)
else:
return None
def get_item_wrappers_by_tags(bot, tags, user):
- items = var.library.get_items_by_tags(bot, tags)
+ items = var.cache.get_items_by_tags(bot, tags)
ret = []
for item in items:
- ret.append(PlaylistItemWrapper(var.library, item.id, item.type, user))
+ ret.append(PlaylistItemWrapper(var.cache, item.id, item.type, user))
return ret
def get_playlist(mode, _list=None, index=None):
counter += 1
if counter == 0:
- var.library.free(removed.id)
+ var.cache.free(removed.id)
return removed
def remove_by_id(self, id):
def clear(self):
self.version += 1
self.current_index = -1
- var.library.free_all()
+ var.cache.free_all()
super().clear()
def save(self):
self.log.debug("playlist: validating %s" % item.format_debug_string())
if not item.validate() or item.is_failed():
self.log.debug("playlist: validating failed.")
- var.library.delete(item.id)
+ var.cache.delete(item.id)
self.remove_by_id(item.id)
self.log.debug("playlist: validating finished.")
import media.radio
import media.system
from media.playlist import BasePlaylist
-from media.library import MusicLibrary
+from media.cache import MusicCache
class MumbleBot:
break
else:
var.playlist.remove_by_id(next.id)
- var.library.delete(next.id)
+ var.cache.delete(next.id)
# =======================
self.send_msg(constants.strings('download_in_progress', item=current.format_short_string()))
else:
var.playlist.remove_by_id(current.id)
- var.library.delete(current.id)
+ var.cache.delete(current.id)
else:
self._loop_status = 'Empty queue'
else:
else:
var.music_db = MusicDatabase(":memory:")
- var.library = MusicLibrary(var.music_db)
+ var.cache = MusicCache(var.music_db)
# load playback mode
playback_mode = None
if var.config.get("bot", "refresh_cache_on_startup", fallback=True)\
or not var.db.has_option("dir_cache", "files"):
- var.library.build_dir_cache(var.bot)
+ var.cache.build_dir_cache(var.bot)
else:
- var.library.load_dir_cache(var.bot)
+ var.cache.load_dir_cache(var.bot)
# load playlist
if var.config.getboolean('bot', 'save_playlist', fallback=True):
bot = None
playlist = None
-library = None
+cache = None
user = ""
is_proxified = False