feat: directory cache

This commit is contained in:
Terry Geng 2020-03-07 15:12:22 +08:00
parent 8e6a639e57
commit 4fce3b956e
8 changed files with 174 additions and 107 deletions

View File

@ -9,8 +9,8 @@ import media.system
import util import util
import variables as var import variables as var
from librb import radiobrowser from librb import radiobrowser
from database import SettingsDatabase from database import SettingsDatabase, MusicDatabase
from media.playlist import get_item_wrapper from media.playlist import get_item_wrapper, get_item_wrapper_by_id
from media.file import FileItem from media.file import FileItem
from media.url_from_playlist import PlaylistURLItem, get_playlist_info from media.url_from_playlist import PlaylistURLItem, get_playlist_info
from media.url import URLItem from media.url import URLItem
@ -54,21 +54,22 @@ def register_all_commands(bot):
bot.register_command(constants.commands('random'), cmd_random) bot.register_command(constants.commands('random'), cmd_random)
bot.register_command(constants.commands('repeat'), cmd_repeat) bot.register_command(constants.commands('repeat'), cmd_repeat)
bot.register_command(constants.commands('mode'), cmd_mode) bot.register_command(constants.commands('mode'), cmd_mode)
bot.register_command(constants.commands('drop_database'), cmd_drop_database) bot.register_command(constants.commands('drop_database'), cmd_drop_database, True)
bot.register_command(constants.commands('recache'), cmd_refresh_cache, True)
# Just for debug use # Just for debug use
bot.register_command('rtrms', cmd_real_time_rms) bot.register_command('rtrms', cmd_real_time_rms, True)
bot.register_command('loop', cmd_loop_state) bot.register_command('loop', cmd_loop_state, True)
bot.register_command('item', cmd_item) bot.register_command('item', cmd_item, True)
def send_multi_lines(bot, lines, text): def send_multi_lines(bot, lines, text, linebreak="<br />"):
global log global log
msg = "" msg = ""
br = "" br = ""
for newline in lines: for newline in lines:
msg += br msg += br
br = "<br>" br = linebreak
if (len(msg) + len(newline)) > (bot.mumble.get_max_message_length() - 4) != 0: # 4 == len("<br>") if (len(msg) + len(newline)) > (bot.mumble.get_max_message_length() - 4) != 0: # 4 == len("<br>")
bot.send_msg(msg, text) bot.send_msg(msg, text)
msg = "" msg = ""
@ -163,106 +164,113 @@ def cmd_pause(bot, user, text, command, parameter):
bot.send_msg(constants.strings('paused')) bot.send_msg(constants.strings('paused'))
def cmd_play_file(bot, user, text, command, parameter): def cmd_play_file(bot, user, text, command, parameter, do_not_refresh_cache=False):
global log global log
# if parameter is {index} # if parameter is {index}
if parameter.isdigit(): if parameter.isdigit():
files = util.get_recursive_file_list_sorted(var.music_folder) files = var.library.files
if int(parameter) < len(files): if int(parameter) < len(files):
filename = files[int(parameter)].replace(var.music_folder, '') music_wrapper = get_item_wrapper_by_id(bot, var.library.file_id_lookup[files[int(parameter)]], user)
music_wrapper = get_item_wrapper(bot, type='file', path=filename, user=user)
var.playlist.append(music_wrapper) var.playlist.append(music_wrapper)
log.info("cmd: add to playlist: " + music_wrapper.format_debug_string()) log.info("cmd: add to playlist: " + music_wrapper.format_debug_string())
bot.send_msg(constants.strings('file_added', item=music_wrapper.format_song_string(user)), text) bot.send_msg(constants.strings('file_added', item=music_wrapper.format_song_string()), text)
return
# if parameter is {path} # if parameter is {path}
else: else:
# sanitize "../" and so on # sanitize "../" and so on
path = os.path.abspath(os.path.join(var.music_folder, parameter)) # path = os.path.abspath(os.path.join(var.music_folder, parameter))
if not path.startswith(os.path.abspath(var.music_folder)): # if not path.startswith(os.path.abspath(var.music_folder)):
bot.send_msg(constants.strings('no_file'), text) # bot.send_msg(constants.strings('no_file'), text)
return # return
if os.path.isfile(path): if parameter in var.library.files:
music_wrapper = get_item_wrapper(bot, type='file', path=parameter, user=user) music_wrapper = get_item_wrapper(bot, type='file', path=parameter, user=user)
var.playlist.append(music_wrapper) var.playlist.append(music_wrapper)
log.info("cmd: add to playlist: " + music_wrapper.format_debug_string()) log.info("cmd: add to playlist: " + music_wrapper.format_debug_string())
bot.send_msg(constants.strings('file_added', item=music_wrapper.format_song_string(user)), text) bot.send_msg(constants.strings('file_added', item=music_wrapper.format_song_string()), text)
return return
# if parameter is {folder} # if parameter is {folder}
elif os.path.isdir(path): files = var.library.dir.get_files(parameter)
if parameter != '.' and parameter != './': if files:
if not parameter.endswith("/"):
parameter += "/"
else:
parameter = ""
files = util.get_recursive_file_list_sorted(var.music_folder)
music_library = util.Dir(var.music_folder)
for file in files:
music_library.add_file(file)
files = music_library.get_files(parameter)
msgs = [constants.strings('multiple_file_added')] msgs = [constants.strings('multiple_file_added')]
count = 0 count = 0
for file in files: for file in files:
count += 1 count += 1
music_wrapper = get_item_wrapper(bot, type='file', path=file, user=user) music_wrapper = get_item_wrapper_by_id(bot, var.library.file_id_lookup[file],user)
var.playlist.append(music_wrapper) var.playlist.append(music_wrapper)
log.info("cmd: add to playlist: " + music_wrapper.format_debug_string()) log.info("cmd: add to playlist: " + music_wrapper.format_debug_string())
msgs.append("{} ({})".format(music_wrapper.item().title, music_wrapper.item().path)) msgs.append("{} ({})".format(music_wrapper.item().title, music_wrapper.item().path))
if count != 0: if count != 0:
send_multi_lines(bot, msgs, text) send_multi_lines(bot, msgs, text)
else: return
bot.send_msg(constants.strings('no_file'), text)
else: else:
# try to do a partial match # try to do a partial match
files = util.get_recursive_file_list_sorted(var.music_folder) files = var.library.files
matches = [(index, file) for index, file in enumerate(files) if parameter.lower() in file.lower()] matches = [(index, file) for index, file in enumerate(files) if parameter.lower() in file.lower()]
if len(matches) == 0: if len(matches) == 1:
bot.send_msg(constants.strings('no_file'), text)
elif len(matches) == 1:
file = matches[0][1] file = matches[0][1]
music_wrapper = get_item_wrapper(bot, type='file', path=file, user=user) music_wrapper = get_item_wrapper_by_id(bot, var.library.file_id_lookup[file],user)
var.playlist.append(music_wrapper) var.playlist.append(music_wrapper)
log.info("cmd: add to playlist: " + music_wrapper.format_debug_string()) log.info("cmd: add to playlist: " + music_wrapper.format_debug_string())
bot.send_msg(constants.strings('file_added', item=music_wrapper.format_song_string(user)), text) bot.send_msg(constants.strings('file_added', item=music_wrapper.format_song_string()), text)
else: return
msgs = [ constants.strings('multiple_matches')] elif len(matches) > 1:
msgs = [ constants.strings('multiple_matches') ]
for match in matches: for match in matches:
msgs.append("<b>{:0>3d}</b> - {:s}".format(match[0], match[1])) 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]))
send_multi_lines(bot, msgs, text) 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)
cmd_play_file(bot, user, text, command, parameter, do_not_refresh_cache=True)
def cmd_play_file_match(bot, user, text, command, parameter): def cmd_play_file_match(bot, user, text, command, parameter, do_not_refresh_cache=False):
global log global log
music_folder = var.music_folder music_folder = var.music_folder
if parameter: if parameter:
files = util.get_recursive_file_list_sorted(music_folder) files = var.library.files
msgs = [ constants.strings('multiple_file_added')] msgs = [ constants.strings('multiple_file_added') + "<ul>"]
count = 0 count = 0
try: try:
music_wrappers = [] music_wrappers = []
for file in files: for file in files:
match = re.search(parameter, file) match = re.search(parameter, file)
if match: if match and match[0]:
count += 1 count += 1
music_wrapper = get_item_wrapper(bot, type='file', path=file, user=user) music_wrapper = get_item_wrapper_by_id(bot, var.library.file_id_lookup[file], user)
music_wrappers.append(music_wrapper) music_wrappers.append(music_wrapper)
log.info("cmd: add to playlist: " + music_wrapper.format_debug_string()) log.info("cmd: add to playlist: " + music_wrapper.format_debug_string())
msgs.append("{} ({})".format(music_wrapper.item().title, music_wrapper.item().path)) msgs.append("<li><b>{}</b> ({})</li>".format(music_wrapper.item().title,
file[:match.span()[0]]
+ "<b style='color:pink'>"
+ file[match.span()[0]: match.span()[1]]
+ "</b>"
+ file[match.span()[1]:]
))
if count != 0: if count != 0:
msgs.append("</ul>")
var.playlist.extend(music_wrappers) var.playlist.extend(music_wrappers)
send_multi_lines(bot, msgs, text) send_multi_lines(bot, msgs, text, "")
else: else:
bot.send_msg(constants.strings('no_file'), text) if do_not_refresh_cache:
bot.send_msg(constants.strings("no_file"), text)
else:
var.library.build_dir_cache(bot)
cmd_play_file_match(bot, user, text, command, parameter, do_not_refresh_cache=True)
except re.error as e: except re.error as e:
msg = constants.strings('wrong_pattern', error=str(e)) msg = constants.strings('wrong_pattern', error=str(e))
@ -678,9 +686,7 @@ def cmd_remove(bot, user, text, command, parameter):
def cmd_list_file(bot, user, text, command, parameter): def cmd_list_file(bot, user, text, command, parameter):
global log global log
folder_path = var.music_folder files = var.library.files
files = util.get_recursive_file_list_sorted(folder_path)
msgs = [ "<br> <b>Files available:</b>" if not parameter else "<br> <b>Matched files:</b>" ] msgs = [ "<br> <b>Files available:</b>" if not parameter else "<br> <b>Matched files:</b>" ]
try: try:
count = 0 count = 0
@ -714,7 +720,7 @@ def cmd_queue(bot, user, text, command, parameter):
for i, music in enumerate(var.playlist): for i, music in enumerate(var.playlist):
newline = '' newline = ''
if i == var.playlist.current_index: if i == var.playlist.current_index:
newline = '<b>{} ▶ ({}) {} ◀</b>'.format(i + 1, music.display_type(), newline = "<b style='color:orange'>{} ({}) {} </b>".format(i + 1, music.display_type(),
music.format_short_string()) music.format_short_string())
else: else:
newline = '<b>{}</b> ({}) {}'.format(i + 1, music.display_type(), newline = '<b>{}</b> ({}) {}'.format(i + 1, music.display_type(),
@ -745,7 +751,7 @@ def cmd_repeat(bot, user, text, command, parameter):
) )
log.info("bot: add to playlist: " + music.format_debug_string) log.info("bot: add to playlist: " + music.format_debug_string)
bot.send_msg(constants.strings("repeat", song=music.format_song_string, n=str(repeat)), text) bot.send_msg(constants.strings("repeat", song=music.format_song_string(), n=str(repeat)), text)
def cmd_mode(bot, user, text, command, parameter): def cmd_mode(bot, user, text, command, parameter):
global log global log
@ -770,8 +776,17 @@ def cmd_drop_database(bot, user, text, command, parameter):
var.db.drop_table() var.db.drop_table()
var.db = SettingsDatabase(var.dbfile) var.db = SettingsDatabase(var.dbfile)
var.music_db.drop_table()
var.music_db = MusicDatabase(var.dbfile)
log.info("command: database dropped.")
bot.send_msg(constants.strings('database_dropped'), text) bot.send_msg(constants.strings('database_dropped'), text)
def cmd_refresh_cache(bot, user, text, command, parameter):
global log
var.library.build_dir_cache(bot)
log.info("command: cache refreshed.")
bot.send_msg(constants.strings('cache_refreshed'), text)
# Just for debug use # Just for debug use
def cmd_real_time_rms(bot, user, text, command, parameter): def cmd_real_time_rms(bot, user, text, command, parameter):
bot._display_rms = not bot._display_rms bot._display_rms = not bot._display_rms

View File

@ -61,6 +61,13 @@ announce_current_music = True
allow_other_channel_message = False allow_other_channel_message = False
allow_private_message = True allow_private_message = True
# 'save_music_library': If this is set True, the bot will save the metadata of music into the database.
save_music_library = True
# 'refresh_cache_on_startup': If this is set true, the bot will refresh its music directory cache when starting up.
# But it won't reload metadata from each files. If set to False, it will used the cache last time.
refresh_cache_on_startup = True
# If save_playlist is set True, the bot will save current # If save_playlist is set True, the bot will save current
# playlist before quitting and reload it the next time it start. # playlist before quitting and reload it the next time it start.
save_playlist = True save_playlist = True
@ -159,6 +166,7 @@ ducking_threshold = duckthres
ducking_volume = duckv ducking_volume = duckv
drop_database = dropdatabase drop_database = dropdatabase
recache = recache
[strings] [strings]
current_volume = Current volume: {volume}. current_volume = Current volume: {volume}.
@ -178,19 +186,19 @@ bad_url = Bad URL requested.
preconfigurated_radio = Preconfigurated Radio available: preconfigurated_radio = Preconfigurated Radio available:
unable_download = Error while downloading music... unable_download = Error while downloading music...
which_command = Do you mean <br /> {commands} which_command = Do you mean <br /> {commands}
multiple_matches = Track not found! Possible candidates: multiple_matches = File not found! Possible candidates:
queue_contents = Items on the playlist: queue_contents = Items on the playlist:
queue_empty = Playlist is empty! queue_empty = Playlist is empty!
invalid_index = Invalid index <i>{index}</i>. Use '!queue' to see your playlist. invalid_index = Invalid index <i>{index}</i>. Use '!queue' to see your playlist.
now_playing = Playing <br />{item} now_playing = Playing {item}
radio = Radio radio = Radio
file = File file = File
url_from_playlist = URL url_from_playlist = URL
url = URL url = URL
radio_item = <a href="{url}">{title}</a> <i>from</i> {name} <i>added by</i> {user} radio_item = <a href="{url}"><b>{title}</b></a> <i>from</i> {name} <i>added by</i> {user}
file_item = {artist} - {title} <i>added by</i> {user} file_item = <b>{artist} - {title}</b> <i>added by</i> {user}
url_from_playlist_item = <a href="{url}">{title}</a> <i>from playlist</i> <a href="{playlist_url}">{playlist}</a> <i>added by</i> {user} url_from_playlist_item = <a href="{url}"><b>{title}</b></a> <i>from playlist</i> <a href="{playlist_url}">{playlist}</a> <i>added by</i> {user}
url_item = <a href="{url}">{title}</a> <i>added by</i> {user} url_item = <a href="{url}"><b>{title}</b></a> <i>added by</i> {user}
not_in_my_channel = You're not in my channel, command refused! not_in_my_channel = You're not in my channel, command refused!
pm_not_allowed = Private message aren't allowed. pm_not_allowed = Private message aren't allowed.
too_long = {song} is too long, removed from playlist! too_long = {song} is too long, removed from playlist!
@ -216,6 +224,7 @@ yt_result = Youtube query result: {result_table} Use <i>!ytplay</i> {{index}} to
yt_no_more = No more results! yt_no_more = No more results!
yt_query_error = Unable to query youtube! yt_query_error = Unable to query youtube!
playlist_fetching_failed = Unable to fetch the playlist! playlist_fetching_failed = Unable to fetch the playlist!
cache_refreshed = Cache refreshed!
help = <h3>Commands</h3> help = <h3>Commands</h3>
<b>Control</b> <b>Control</b>
@ -266,7 +275,9 @@ admin_help = <h3>Admin command</h3>
<li><b>!<u>useru</u>nban </b> {user} - unban a user</li> <li><b>!<u>useru</u>nban </b> {user} - unban a user</li>
<li><b>!<u>urlb</u>an </b> {url} - ban an url</li> <li><b>!<u>urlb</u>an </b> {url} - ban an url</li>
<li><b>!<u>urlu</u>nban </b> {url} - unban an url</li> <li><b>!<u>urlu</u>nban </b> {url} - unban an url</li>
<li><b>!dropdatabase</b> - clear the entire database, YOU SHOULD KNOW WHAT YOU ARE DOING.</li> <li><b>!<u>urlu</u>nban </b> {url} - unban an url</li>
<li><b>!recache </b> {url} - rebuild local music file cache</li>
<li><b>!dropdatabase</b> - clear the entire database, you will lose all settings and music library.</li>
</ul> </ul>

View File

@ -74,6 +74,10 @@ port = 64738
# 'save_music_library': If this is set True, the bot will save the metadata of music into the database. # 'save_music_library': If this is set True, the bot will save the metadata of music into the database.
#save_music_library = True #save_music_library = True
# 'refresh_cache_on_startup': If this is set true, the bot will refresh its music directory cache when starting up.
# But it won't reload metadata from each files. If set to False, it will used the cache last time.
#refresh_cache_on_startup = True
# 'save_playlist': If save_playlist is set True, the bot will save current playlist before quitting # 'save_playlist': If save_playlist is set True, the bot will save current playlist before quitting
# and reload it the next time it start. It requires save_music_library to be True to function. # and reload it the next time it start. It requires save_music_library to be True to function.
#save_playlist = True #save_playlist = True

View File

@ -238,3 +238,10 @@ class MusicDatabase:
"WHERE %s" % condition_str, filler) "WHERE %s" % condition_str, filler)
conn.commit() conn.commit()
conn.close() conn.close()
def drop_table(self):
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("DROP TABLE music")
conn.close()

View File

@ -4,22 +4,15 @@ from functools import wraps
from flask import Flask, render_template, request, redirect, send_file, Response, jsonify, abort from flask import Flask, render_template, request, redirect, send_file, Response, jsonify, abort
import variables as var import variables as var
import util import util
from datetime import datetime
import os import os
import os.path import os.path
import shutil import shutil
import random
from werkzeug.utils import secure_filename from werkzeug.utils import secure_filename
import errno import errno
import media import media
from media.playlist import get_item_wrapper from media.playlist import get_item_wrapper, get_item_wrapper_by_id
from media.file import FileItem
from media.url_from_playlist import PlaylistURLItem, get_playlist_info
from media.url import URLItem
from media.radio import RadioItem
import logging import logging
import time import time
import constants
class ReverseProxied(object): class ReverseProxied(object):
@ -101,16 +94,9 @@ def requires_auth(f):
@web.route("/", methods=['GET']) @web.route("/", methods=['GET'])
@requires_auth @requires_auth
def index(): def index():
folder_path = var.music_folder
files = util.get_recursive_file_list_sorted(var.music_folder)
music_library = util.Dir(folder_path)
for file in files:
music_library.add_file(file)
return render_template('index.html', return render_template('index.html',
all_files=files, all_files=var.library.files,
music_library=music_library, music_library=var.library.dir,
os=os, os=os,
playlist=var.playlist, playlist=var.playlist,
user=var.user, user=var.user,
@ -157,14 +143,13 @@ def status():
def post(): def post():
global log global log
folder_path = var.music_folder
if request.method == 'POST': if request.method == 'POST':
if request.form: if request.form:
log.debug("web: Post request from %s: %s" % ( request.remote_addr, str(request.form))) log.debug("web: Post request from %s: %s" % ( request.remote_addr, str(request.form)))
if 'add_file_bottom' in request.form and ".." not in request.form['add_file_bottom']: if 'add_file_bottom' in request.form and ".." not in request.form['add_file_bottom']:
path = var.music_folder + request.form['add_file_bottom'] path = var.music_folder + request.form['add_file_bottom']
if os.path.isfile(path): if os.path.isfile(path):
music_wrapper = get_item_wrapper(var.bot, type='file', path=request.form['add_file_bottom'], user=user) music_wrapper = get_item_wrapper_by_id(var.bot, var.library.file_id_lookup[request.form['add_file_bottom']], user)
var.playlist.append(music_wrapper) var.playlist.append(music_wrapper)
log.info('web: add to playlist(bottom): ' + music_wrapper.format_debug_string()) log.info('web: add to playlist(bottom): ' + music_wrapper.format_debug_string())
@ -172,7 +157,7 @@ def post():
elif 'add_file_next' in request.form and ".." not in request.form['add_file_next']: elif 'add_file_next' in request.form and ".." not in request.form['add_file_next']:
path = var.music_folder + request.form['add_file_next'] path = var.music_folder + request.form['add_file_next']
if os.path.isfile(path): if os.path.isfile(path):
music_wrapper = get_item_wrapper(var.bot, type='file', path=request.form['add_file_next'], user=user) music_wrapper = get_item_wrapper_by_id(var.bot, var.library.file_id_lookup[request.form['add_file_next']], user)
var.playlist.insert(var.playlist.current_index + 1, music_wrapper) var.playlist.insert(var.playlist.current_index + 1, music_wrapper)
log.info('web: add to playlist(next): ' + music_wrapper.format_debug_string()) log.info('web: add to playlist(next): ' + music_wrapper.format_debug_string())
@ -186,19 +171,15 @@ def post():
folder += '/' folder += '/'
if os.path.isdir(var.music_folder + folder): if os.path.isdir(var.music_folder + folder):
dir = var.library.dir
files = util.get_recursive_file_list_sorted(var.music_folder)
music_library = util.Dir(folder_path)
for file in files:
music_library.add_file(file)
if 'add_folder_recursively' in request.form: if 'add_folder_recursively' in request.form:
files = music_library.get_files_recursively(folder) files = dir.get_files_recursively(folder)
else: else:
files = music_library.get_files(folder) files = dir.get_files(folder)
music_wrappers = list(map( music_wrappers = list(map(
lambda file: get_item_wrapper(var.bot, type='file', path=file, user=user), lambda file:
get_item_wrapper_by_id(var.bot, var.library.file_id_lookup[folder + file], user),
files)) files))
var.playlist.extend(music_wrappers) var.playlist.extend(music_wrappers)
@ -370,7 +351,7 @@ def download():
log.info('web: Download of file %s requested from %s:' % (requested_file, request.remote_addr)) log.info('web: Download of file %s requested from %s:' % (requested_file, request.remote_addr))
if '../' not in requested_file: if '../' not in requested_file:
folder_path = var.music_folder folder_path = var.music_folder
files = util.get_recursive_file_list_sorted(var.music_folder) files = var.library.files
if requested_file in files: if requested_file in files:
filepath = os.path.join(folder_path, requested_file) filepath = os.path.join(folder_path, requested_file)

View File

@ -1,13 +1,11 @@
import logging import logging
from database import MusicDatabase from database import MusicDatabase
import json
from media.item import item_builders, item_loaders, item_id_generators from media.item import item_builders, item_loaders, item_id_generators
from media.file import FileItem
from media.url import URLItem
from media.url_from_playlist import PlaylistURLItem
from media.radio import RadioItem
from database import MusicDatabase from database import MusicDatabase
import variables as var import variables as var
import util
class MusicLibrary(dict): class MusicLibrary(dict):
@ -15,8 +13,10 @@ class MusicLibrary(dict):
super().__init__() super().__init__()
self.db = db self.db = db
self.log = logging.getLogger("bot") self.log = logging.getLogger("bot")
self.dir = None
self.files = []
def get_item_by_id(self, bot, id): 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: if id in self:
return self[id] return self[id]
@ -26,6 +26,9 @@ class MusicLibrary(dict):
self[id] = item self[id] = item
self.log.debug("library: music found in database: %s" % item.format_debug_string()) self.log.debug("library: music found in database: %s" % item.format_debug_string())
return item return item
else:
raise KeyError("Unable to fetch item from the database! Please try to refresh the cache by !recache.")
def get_item(self, bot, **kwargs): def get_item(self, bot, **kwargs):
# kwargs should provide type and id, and parameters to build the item if not existed in the library. # kwargs should provide type and id, and parameters to build the item if not existed in the library.
@ -59,8 +62,13 @@ class MusicLibrary(dict):
self.log.debug("library: music save into database: %s" % self[id].format_debug_string()) self.log.debug("library: music save into database: %s" % self[id].format_debug_string())
self.db.insert_music(self[id].to_dict()) self.db.insert_music(self[id].to_dict())
def delete(self, id): def delete(self, item):
self.db.delete_music(id=id) if item.type == 'file' and item.path in self.file_id_lookup:
del self.file_id_lookup[item.path]
self.files.remove(item.path)
self.save_dir_cache()
self.db.delete_music(id=item.id)
def free(self, id): def free(self, id):
if id in self: if id in self:
@ -68,3 +76,31 @@ class MusicLibrary(dict):
def free_all(self): def free_all(self):
self.clear() self.clear()
def build_dir_cache(self, bot):
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.file_id_lookup[file] = item.id
self.save_dir_cache()
def save_dir_cache(self):
var.db.set("dir_cache", "files", json.dumps(self.file_id_lookup))
def load_dir_cache(self, bot):
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)

View File

@ -298,6 +298,7 @@ class BasePlaylist(list):
if not item.validate() or item.is_failed(): if not item.validate() or item.is_failed():
self.log.debug("playlist: validating failed.") self.log.debug("playlist: validating failed.")
self.remove_by_id(item.id) self.remove_by_id(item.id)
var.library.delete(item.item())
self.log.debug("playlist: validating finished.") self.log.debug("playlist: validating finished.")
self.validating_thread_lock.release() self.validating_thread_lock.release()
@ -422,6 +423,10 @@ class AutoPlaylist(BasePlaylist):
# self.refresh() # self.refresh()
# return self # return self
def clear(self):
super().clear()
self.refresh()
def next(self): def next(self):
if len(self) == 0: if len(self) == 0:
return False return False

View File

@ -184,12 +184,12 @@ class MumbleBot:
else: else:
self.log.debug("update: no new version found.") self.log.debug("update: no new version found.")
def register_command(self, cmd, handle): def register_command(self, cmd, handle, no_partial_match=False):
cmds = cmd.split(",") cmds = cmd.split(",")
for command in cmds: for command in cmds:
command = command.strip() command = command.strip()
if command: if command:
self.cmd_handle[command] = handle self.cmd_handle[command] = { 'handle': handle, 'partial_match': not no_partial_match}
self.log.debug("bot: command added: " + command) self.log.debug("bot: command added: " + command)
def set_comment(self): def set_comment(self):
@ -254,19 +254,19 @@ class MumbleBot:
try: try:
if command in self.cmd_handle: if command in self.cmd_handle:
command_exc = command command_exc = command
self.cmd_handle[command](self, user, text, command, parameter) self.cmd_handle[command]['handle'](self, user, text, command, parameter)
else: else:
# try partial match # try partial match
cmds = self.cmd_handle.keys() cmds = self.cmd_handle.keys()
matches = [] matches = []
for cmd in cmds: for cmd in cmds:
if cmd.startswith(command): if cmd.startswith(command) and self.cmd_handle[cmd]['partial_match']:
matches.append(cmd) matches.append(cmd)
if len(matches) == 1: if len(matches) == 1:
self.log.info("bot: {:s} matches {:s}".format(command, matches[0])) self.log.info("bot: {:s} matches {:s}".format(command, matches[0]))
command_exc = matches[0] command_exc = matches[0]
self.cmd_handle[command_exc](self, user, text, command_exc, parameter) self.cmd_handle[command_exc]['handle'](self, user, text, command_exc, parameter)
elif len(matches) > 1: elif len(matches) > 1:
self.mumble.users[text.actor].send_text_message( self.mumble.users[text.actor].send_text_message(
constants.strings('which_command', commands="<br>".join(matches))) constants.strings('which_command', commands="<br>".join(matches)))
@ -347,6 +347,7 @@ class MumbleBot:
break break
else: else:
var.playlist.remove_by_id(next.id) var.playlist.remove_by_id(next.id)
var.library.delete(next.item())
# ======================= # =======================
@ -406,6 +407,7 @@ class MumbleBot:
self.send_msg(constants.strings('download_in_progress', item=current.format_short_string())) self.send_msg(constants.strings('download_in_progress', item=current.format_short_string()))
else: else:
var.playlist.remove_by_id(current.id) var.playlist.remove_by_id(current.id)
var.library.delete(current.item())
else: else:
self._loop_status = 'Empty queue' self._loop_status = 'Empty queue'
else: else:
@ -654,6 +656,12 @@ if __name__ == '__main__':
var.bot = MumbleBot(args) var.bot = MumbleBot(args)
command.register_all_commands(var.bot) command.register_all_commands(var.bot)
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)
else:
var.library.load_dir_cache(var.bot)
# load playlist # load playlist
if var.config.getboolean('bot', 'save_playlist', fallback=True): if var.config.getboolean('bot', 'save_playlist', fallback=True):
var.bot_logger.info("bot: load playlist from previous session") var.bot_logger.info("bot: load playlist from previous session")