diff --git a/database.py b/database.py index 5b404b2..384ff59 100644 --- a/database.py +++ b/database.py @@ -26,6 +26,7 @@ class Condition: if self._order_by: sql += f" ORDEY BY {self._order_by}" + print(sql) return sql def or_equal(self, column, equals_to, case_sensitive=True): @@ -330,7 +331,7 @@ class MusicDatabase: return self._result_to_dict(results) def query_music_by_id(self, _id): - return self.query_music(Condition().and_equal("id", _id)) + return self.query_music(Condition().and_equal("id", _id))[0] def query_music_by_keywords(self, keywords): condition = Condition() diff --git a/interface.py b/interface.py index f10a5e6..9a34f73 100644 --- a/interface.py +++ b/interface.py @@ -58,6 +58,7 @@ class ReverseProxied(object): web = Flask(__name__) +web.config['TEMPLATES_AUTO_RELOAD'] = True log = logging.getLogger("bot") user = 'Remote Control' @@ -361,20 +362,25 @@ def build_library_query_condition(form): try: condition = Condition() + types = form['type'].split(",") + sub_cond = Condition() + for type in types: + sub_cond.or_equal("type", type) + condition.and_sub_condition(sub_cond) + if form['type'] == 'file': folder = form['dir'] if not folder.endswith('/') and folder: folder += '/' sub_cond = Condition() + count = 0 for file in var.cache.files: if file.startswith(folder): + count += 1 sub_cond.or_equal("id", var.cache.file_id_lookup[file]) + if count > 900: + break condition.and_sub_condition(sub_cond) - condition.and_equal("type", "file") - elif form['type'] == 'url': - condition.and_equal("type", "url") - elif form['type'] == 'radio': - condition.and_equal("type", "radio") tags = form['tags'].split(",") for tag in tags: @@ -402,73 +408,80 @@ def library(): if request.form: log.debug("web: Post request from %s: %s" % (request.remote_addr, str(request.form))) - condition = build_library_query_condition(request.form) + if request.form['action'] in ['add', 'query', 'delete']: + condition = build_library_query_condition(request.form) - total_count = var.music_db.query_music_count(condition) - if not total_count: - abort(404) + total_count = var.music_db.query_music_count(condition) + if not total_count: + abort(404) - page_count = math.ceil(total_count / ITEM_PER_PAGE) + page_count = math.ceil(total_count / ITEM_PER_PAGE) - current_page = int(request.form['page']) if 'page' in request.form else 1 - if current_page <= page_count: - condition.offset((current_page - 1) * ITEM_PER_PAGE) - else: - abort(404) + current_page = int(request.form['page']) if 'page' in request.form else 1 + if current_page <= page_count: + condition.offset((current_page - 1) * ITEM_PER_PAGE) + else: + abort(404) - condition.limit(ITEM_PER_PAGE) - items = dicts_to_items(var.bot, var.music_db.query_music(condition)) + condition.limit(ITEM_PER_PAGE) + items = dicts_to_items(var.bot, var.music_db.query_music(condition)) - if 'action' in request.form and request.form['action'] == 'add': - for item in items: - music_wrapper = get_cached_wrapper(item, user) - var.playlist.append(music_wrapper) + if request.form['action'] == 'add': + for item in items: + music_wrapper = get_cached_wrapper(item, user) + 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()) + return redirect("./", code=302) + elif request.form['action'] == 'delete': + for item in items: + var.playlist.remove_by_id(item.id) + item = var.cache.get_item_by_id(var.bot, item.id) + + if os.path.isfile(item.uri()): + log.info("web: delete file " + item.uri()) + os.remove(item.uri()) + + var.cache.free_and_delete(item.id) + + if len(os.listdir(var.music_folder + request.form['dir'])) == 0: + os.rmdir(var.music_folder + request.form['dir']) + + return redirect("./", code=302) + else: + results = [] + for item in items: + result = {} + result['id'] = item.id + result['title'] = item.title + result['type'] = item.display_type() + result['tags'] = [(tag, tag_color(tag)) for tag in item.tags] + if item.thumbnail: + result['thumb'] = f"data:image/PNG;base64,{item.thumbnail}" + else: + result['thumb'] = "static/image/unknown-album.png" + + if item.type == 'file': + result['path'] = item.path + result['artist'] = item.artist + else: + result['path'] = item.url + result['artist'] = "??" + + results.append(result) + + return jsonify({ + 'items': results, + 'total_pages': page_count, + 'active_page': current_page + }) + elif request.form['action'] == 'edit_tags': + item = var.music_db.query_music_by_id(request.form['id']) + item['tags'] = list(dict.fromkeys(request.form['tags'].split(","))) # remove duplicated items + var.music_db.insert_music(item) return redirect("./", code=302) - elif 'action' in request.form and request.form['action'] == 'delete': - for item in items: - var.playlist.remove_by_id(item.id) - item = var.cache.get_item_by_id(var.bot, item.id) - if os.path.isfile(item.uri()): - log.info("web: delete file " + item.uri()) - os.remove(item.uri()) - - var.cache.free_and_delete(item.id) - - if len(os.listdir(var.music_folder + request.form['dir'])) == 0: - os.rmdir(var.music_folder + request.form['dir']) - - return redirect("./", code=302) - else: - results = [] - for item in items: - result = {} - result['id'] = item.id - result['title'] = item.title - result['type'] = item.display_type() - result['tags'] = [(tag, tag_color(tag)) for tag in item.tags] - if item.thumbnail: - result['thumb'] = f"data:image/PNG;base64,{item.thumbnail}" - else: - result['thumb'] = "static/image/unknown-album.png" - - if item.type == 'file': - result['path'] = item.path - result['artist'] = item.artist - else: - result['path'] = item.url - result['artist'] = "??" - - results.append(result) - - return jsonify({ - 'items': results, - 'total_pages': page_count, - 'active_page': current_page - }) else: abort(400) diff --git a/media/cache.py b/media/cache.py index 209268a..5297925 100644 --- a/media/cache.py +++ b/media/cache.py @@ -73,9 +73,8 @@ class MusicCache(dict): return items def fetch(self, bot, id): - music_dicts = self.db.query_music_by_id(id) - if music_dicts: - music_dict = music_dicts[0] + music_dict = self.db.query_music_by_id(id) + if music_dict: self[id] = dict_to_item(bot, music_dict) return self[id] else: diff --git a/templates/index.html b/templates/index.html index cdcb7b1..9aa4176 100644 --- a/templates/index.html +++ b/templates/index.html @@ -105,26 +105,12 @@
-
- -
-
-

Tags

-
-
- {% for tag in tags_color_lookup.keys() %} - - {{ tag }} - {% endfor %} +
+
-
-
-
@@ -215,6 +201,7 @@
Path/to/the/file
+ No tag Tag
@@ -253,7 +240,30 @@ Rescan Files - + + +
+ +
@@ -348,6 +358,41 @@ + + + @@ -501,7 +546,9 @@ } , 3000); // ------ Browser ------ - var filter_type = 'file'; + var filter_file = true; + var filter_url = false; + var filter_radio = false; var filter_dir = $("#filter-dir"); var filter_keywords = $("#filter-keywords"); var filter_btn_file = $("#filter-type-file"); @@ -509,19 +556,35 @@ var filter_btn_radio = $("#filter-type-radio"); function setFilterType(type){ - filter_type = type; - filter_btn_file.removeClass("btn-primary").addClass("btn-secondary"); - filter_btn_url.removeClass("btn-primary").addClass("btn-secondary"); - filter_btn_radio.removeClass("btn-primary").addClass("btn-secondary"); - filter_dir.prop("disabled", true); + filter_types = []; if(type === "file"){ - filter_btn_file.removeClass("btn-secondary").addClass("btn-primary"); - filter_dir.prop("disabled", false); + if(filter_btn_file.hasClass("btn-primary")){ + filter_btn_file.removeClass("btn-primary").addClass("btn-secondary"); + filter_dir.prop("disabled", true); + filter_file = false; + }else{ + filter_btn_file.removeClass("btn-secondary").addClass("btn-primary"); + filter_dir.prop("disabled", false); + filter_file = true; + } }else if(type === "url"){ - filter_btn_url.removeClass("btn-secondary").addClass("btn-primary"); + if(filter_btn_url.hasClass("btn-primary")) { + filter_btn_url.removeClass("btn-primary").addClass("btn-secondary"); + filter_url = false; + }else{ + filter_btn_url.removeClass("btn-secondary").addClass("btn-primary"); + filter_url = true; + } }else if(type === "radio"){ - filter_btn_radio.removeClass("btn-secondary").addClass("btn-primary"); + if(filter_btn_radio.hasClass("btn-primary")) { + filter_btn_radio.removeClass("btn-primary").addClass("btn-secondary"); + filter_radio = false; + }else{ + filter_btn_radio.removeClass("btn-secondary").addClass("btn-primary"); + filter_types.push('radio') + filter_radio = true; + } } updateResults(); } @@ -558,7 +621,7 @@ request('post', { 'delete_item_from_library': $(e.currentTarget).parent().parent().find(".library-item-id").val() }); - updateResults(); + updateResults(active_page); } ); @@ -596,9 +659,13 @@ var thumb_element = $(".library-item-thumb"); var type_element = $(".library-item-type"); var path_element = $(".library-item-path"); + + var tag_edit_element = $(".library-item-edit"); var notag_element = $(".library-item-notag"); var tag_element = $(".library-item-tag"); + var add_tag_modal = $("#addTagModal"); + function addResultItem(item){ id_element.val(item.id); title_element.html(item.title); @@ -612,6 +679,14 @@ var tags = item_copy.find(".library-item-tags"); tags.empty(); + + var tag_edit_copy = tag_edit_element.clone(); + tag_edit_copy.click(function(){ + addTagModalPrepare(item.id, item.title, item.tags); + add_tag_modal.modal('show'); + }); + tag_edit_copy.appendTo(tags); + if(item.tags.length > 0){ item.tags.forEach(function (tag_tuple){ tag_copy = tag_element.clone(); @@ -625,7 +700,7 @@ } item_copy.appendTo(lib_group); - item_copy.slideDown(); + item_copy.show(); } function getFilters(dest_page=1){ @@ -635,8 +710,13 @@ tags_list.push(tag.innerHTML); }); + filter_types = []; + if(filter_file){ filter_types.push("file"); } + if(filter_url){ filter_types.push("url"); } + if(filter_radio){ filter_types.push("radio"); } + return { - type: filter_type, + type: filter_types.join(','), dir: filter_dir.val(), tags: tags_list.join(","), keywords: filter_keywords.val(), @@ -646,8 +726,10 @@ var lib_loading = $("#library-item-loading"); var lib_empty = $("#library-item-empty"); + var active_page = 1; function updateResults(dest_page=1){ + active_page = dest_page; data = getFilters(dest_page); data.action = "query"; @@ -755,7 +837,7 @@ if(total_pages > 25){ i = (active_page - 12 >= 1) ? active_page - 12 : 1; - _i = total_pages - 24; + _i = total_pages - 23; i = (i < _i) ? i : _i; page_li_copy = page_li.clone(); page_no_copy = page_no.clone(); @@ -803,6 +885,67 @@ }); } + var add_tag_modal_title = $("#addTagModalTitle"); + var add_tag_modal_item_id = $("#addTagModalItemId"); + var add_tag_modal_tags = $("#addTagModalTags"); + var add_tag_modal_input = $("#addTagModalInput"); + var modal_tag = $(".modal-tag"); + var modal_tag_text = $(".modal-tag-text"); + + function addTagModalPrepare(_id, _title, _tag_tuples){ + add_tag_modal_title.html("Edit tags for " + _title); + add_tag_modal_item_id.val(_id); + add_tag_modal_tags.empty(); + _tag_tuples.forEach(function(tag_tuple){ + modal_tag_text.html(tag_tuple[0]); + var tag_copy = modal_tag.clone(); + var modal_tag_remove = tag_copy.find(".modal-tag-remove"); + modal_tag_remove.click(function(e){ + $(e.currentTarget).parent().remove(); + }); + tag_copy.show(); + tag_copy.appendTo(add_tag_modal_tags); + modal_tag_text.html(""); + }); + } + + function addTagModalAdd(){ + new_tags = add_tag_modal_input.val().split(",").map(function(str){return str.trim()}); + new_tags.forEach(function(tag){ + modal_tag_text.html(tag); + var tag_copy = modal_tag.clone(); + var modal_tag_remove = tag_copy.find(".modal-tag-remove"); + modal_tag_remove.click(function(e){ + $(e.currentTarget).parent().remove(); + }); + tag_copy.show(); + tag_copy.appendTo(add_tag_modal_tags); + modal_tag_text.html(""); + }); + add_tag_modal_input.val(""); + } + + function addTagModalSubmit(){ + var all_tags = $(".modal-tag-text"); + tags = []; + all_tags.each(function(i, element){ + if(element.innerHTML){ + tags.push(element.innerHTML); + } + }); + + $.ajax({ + type: 'POST', + url : 'library', + data: { + action: 'edit_tags', + id: add_tag_modal_item_id.val(), + tags: tags.join(",") + } + }); + updateResults(active_page); + } + themeInit(); updateResults();