From 8d987ade6a6c952cf61c7bdf39168ca5e68b532f Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Mon, 20 Jun 2022 21:11:37 +0200 Subject: [PATCH] PICARD-2504: Make MB web service query limit configurable --- picard/browser/filelookup.py | 9 ++-- picard/cluster.py | 4 +- picard/file.py | 6 +-- picard/ui/options/advanced.py | 6 ++- picard/ui/searchdialog/album.py | 9 ++-- picard/ui/searchdialog/artist.py | 11 ++-- picard/ui/searchdialog/track.py | 9 ++-- picard/ui/ui_options_advanced.py | 66 +++++++++++++++--------- test/test_browser.py | 3 ++ ui/options_advanced.ui | 86 ++++++++++++++++++++++---------- 10 files changed, 139 insertions(+), 70 deletions(-) diff --git a/picard/browser/filelookup.py b/picard/browser/filelookup.py index 327f5c2dd..7f09f6da7 100644 --- a/picard/browser/filelookup.py +++ b/picard/browser/filelookup.py @@ -35,10 +35,8 @@ import re from PyQt5 import QtCore from picard import log -from picard.const import ( - PICARD_URLS, - QUERY_LIMIT, -) +from picard.config import get_config +from picard.const import PICARD_URLS from picard.disc import Disc from picard.util import ( build_qurl, @@ -172,8 +170,9 @@ class FileLookup(object): def search_entity(self, type_, query, adv=False, mbid_matched_callback=None, force_browser=False): if not force_browser and self.mbid_lookup(query, type_, mbid_matched_callback=mbid_matched_callback): return True + config = get_config() params = { - 'limit': QUERY_LIMIT, + 'limit': config.setting['query_limit'], 'type': type_, 'query': query, } diff --git a/picard/cluster.py b/picard/cluster.py index c9fb17f51..37ff649df 100644 --- a/picard/cluster.py +++ b/picard/cluster.py @@ -45,7 +45,6 @@ import re from PyQt5 import QtCore from picard.config import get_config -from picard.const import QUERY_LIMIT from picard.file import File from picard.metadata import ( Metadata, @@ -282,11 +281,12 @@ class Cluster(FileList): N_("Looking up the metadata for cluster %(album)s..."), {'album': self.metadata['album']} ) + config = get_config() self.lookup_task = self.tagger.mb_api.find_releases(self._lookup_finished, artist=self.metadata['albumartist'], release=self.metadata['album'], tracks=str(len(self.files)), - limit=QUERY_LIMIT) + limit=config.setting['query_limit']) def clear_lookup_task(self): if self.lookup_task: diff --git a/picard/file.py b/picard/file.py index 7de338054..7d143f3a7 100644 --- a/picard/file.py +++ b/picard/file.py @@ -4,7 +4,7 @@ # # Copyright (C) 2004 Robert Kaye # Copyright (C) 2006-2009, 2011-2013, 2017 Lukáš Lalinský -# Copyright (C) 2007-2011, 2015, 2018-2021 Philipp Wolfer +# Copyright (C) 2007-2011, 2015, 2018-2022 Philipp Wolfer # Copyright (C) 2008 Gary van der Merwe # Copyright (C) 2008-2009 Nikolai Prokoschenko # Copyright (C) 2009 Carlin Mangar @@ -62,7 +62,6 @@ from picard import ( log, ) from picard.config import get_config -from picard.const import QUERY_LIMIT from picard.const.sys import ( IS_MACOS, IS_WIN, @@ -876,6 +875,7 @@ class File(QtCore.QObject, Item): self.clear_lookup_task() metadata = self.metadata self.set_pending() + config = get_config() self.lookup_task = self.tagger.mb_api.find_tracks( partial(self._lookup_finished, File.LOOKUP_METADATA), track=metadata['title'], @@ -885,7 +885,7 @@ class File(QtCore.QObject, Item): tracks=metadata['totaltracks'], qdur=str(metadata.length // 2000), isrc=metadata['isrc'], - limit=QUERY_LIMIT) + limit=config.setting['query_limit']) def clear_lookup_task(self): if self.lookup_task: diff --git a/picard/ui/options/advanced.py b/picard/ui/options/advanced.py index 8e0c49e66..6da9ecb2b 100644 --- a/picard/ui/options/advanced.py +++ b/picard/ui/options/advanced.py @@ -4,7 +4,7 @@ # # Copyright (C) 2006-2007 Lukáš Lalinský # Copyright (C) 2013-2015, 2018, 2020-2021 Laurent Monin -# Copyright (C) 2014, 2019-2021 Philipp Wolfer +# Copyright (C) 2014, 2019-2022 Philipp Wolfer # Copyright (C) 2016-2017 Sambhav Kothari # # This program is free software; you can redistribute it and/or @@ -29,6 +29,7 @@ from picard.config import ( TextOption, get_config, ) +from picard.const import QUERY_LIMIT from picard.ui.options import ( OptionsPage, @@ -51,6 +52,7 @@ class AdvancedOptionsPage(OptionsPage): BoolOption("setting", "ignore_hidden_files", False), BoolOption("setting", "recursively_add_files", True), IntOption("setting", "ignore_track_duration_difference_under", 2), + IntOption("setting", "query_limit", QUERY_LIMIT), BoolOption("setting", "completeness_ignore_videos", False), BoolOption("setting", "completeness_ignore_pregap", False), BoolOption("setting", "completeness_ignore_data", False), @@ -70,6 +72,7 @@ class AdvancedOptionsPage(OptionsPage): self.ui.ignore_hidden_files.setChecked(config.setting["ignore_hidden_files"]) self.ui.recursively_add_files.setChecked(config.setting["recursively_add_files"]) self.ui.ignore_track_duration_difference_under.setValue(config.setting["ignore_track_duration_difference_under"]) + self.ui.query_limit.setValue(config.setting["query_limit"]) self.ui.completeness_ignore_videos.setChecked(config.setting["completeness_ignore_videos"]) self.ui.completeness_ignore_pregap.setChecked(config.setting["completeness_ignore_pregap"]) self.ui.completeness_ignore_data.setChecked(config.setting["completeness_ignore_data"]) @@ -83,6 +86,7 @@ class AdvancedOptionsPage(OptionsPage): config.setting["ignore_hidden_files"] = self.ui.ignore_hidden_files.isChecked() config.setting["recursively_add_files"] = self.ui.recursively_add_files.isChecked() config.setting["ignore_track_duration_difference_under"] = self.ui.ignore_track_duration_difference_under.value() + config.setting["query_limit"] = self.ui.query_limit.value() config.setting["completeness_ignore_videos"] = self.ui.completeness_ignore_videos.isChecked() config.setting["completeness_ignore_pregap"] = self.ui.completeness_ignore_pregap.isChecked() config.setting["completeness_ignore_data"] = self.ui.completeness_ignore_data.isChecked() diff --git a/picard/ui/searchdialog/album.py b/picard/ui/searchdialog/album.py index a95d356d4..555b59893 100644 --- a/picard/ui/searchdialog/album.py +++ b/picard/ui/searchdialog/album.py @@ -31,11 +31,13 @@ from PyQt5 import ( from PyQt5.QtCore import pyqtSignal from picard import log -from picard.config import Option +from picard.config import ( + Option, + get_config, +) from picard.const import ( CAA_HOST, CAA_PORT, - QUERY_LIMIT, ) from picard.coverart.image import CaaThumbnailCoverArtImage from picard.mbjson import ( @@ -168,11 +170,12 @@ class AlbumSearchDialog(SearchDialog): self.retry_params = Retry(self.search, text) self.search_box_text(text) self.show_progress() + config = get_config() self.tagger.mb_api.find_releases(self.handle_reply, query=text, search=True, advanced_search=self.use_advanced_search, - limit=QUERY_LIMIT) + limit=config.setting['query_limit']) def show_similar_albums(self, cluster): """Perform search by using existing metadata information diff --git a/picard/ui/searchdialog/artist.py b/picard/ui/searchdialog/artist.py index 0570b0854..e8b6ab1d1 100644 --- a/picard/ui/searchdialog/artist.py +++ b/picard/ui/searchdialog/artist.py @@ -4,7 +4,7 @@ # # Copyright (C) 2016 Rahul Raturi # Copyright (C) 2018, 2020-2021 Laurent Monin -# Copyright (C) 2018-2021 Philipp Wolfer +# Copyright (C) 2018-2022 Philipp Wolfer # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License @@ -23,8 +23,10 @@ from PyQt5 import QtCore -from picard.config import Option -from picard.const import QUERY_LIMIT +from picard.config import ( + Option, + get_config, +) from picard.mbjson import artist_to_metadata from picard.metadata import Metadata @@ -64,11 +66,12 @@ class ArtistSearchDialog(SearchDialog): self.retry_params = Retry(self.search, text) self.search_box_text(text) self.show_progress() + config = get_config() self.tagger.mb_api.find_artists(self.handle_reply, query=text, search=True, advanced_search=self.use_advanced_search, - limit=QUERY_LIMIT) + limit=config.setting['query_limit']) def retry(self): self.retry_params.function(self.retry_params.query) diff --git a/picard/ui/searchdialog/track.py b/picard/ui/searchdialog/track.py index 3b87e3ce7..19e3b10fc 100644 --- a/picard/ui/searchdialog/track.py +++ b/picard/ui/searchdialog/track.py @@ -24,8 +24,10 @@ from PyQt5 import QtCore -from picard.config import Option -from picard.const import QUERY_LIMIT +from picard.config import ( + Option, + get_config, +) from picard.file import File from picard.mbjson import ( countries_from_node, @@ -76,11 +78,12 @@ class TrackSearchDialog(SearchDialog): self.retry_params = Retry(self.search, text) self.search_box_text(text) self.show_progress() + config = get_config() self.tagger.mb_api.find_tracks(self.handle_reply, query=text, search=True, advanced_search=self.use_advanced_search, - limit=QUERY_LIMIT) + limit=config.setting['query_limit']) def show_similar_tracks(self, file_): """Perform search using existing metadata information diff --git a/picard/ui/ui_options_advanced.py b/picard/ui/ui_options_advanced.py index 68237e4ea..d79bcd68a 100644 --- a/picard/ui/ui_options_advanced.py +++ b/picard/ui/ui_options_advanced.py @@ -10,7 +10,7 @@ from PyQt5 import QtCore, QtGui, QtWidgets class Ui_AdvancedOptionsPage(object): def setupUi(self, AdvancedOptionsPage): AdvancedOptionsPage.setObjectName("AdvancedOptionsPage") - AdvancedOptionsPage.resize(570, 435) + AdvancedOptionsPage.resize(570, 455) self.vboxlayout = QtWidgets.QVBoxLayout(AdvancedOptionsPage) self.vboxlayout.setObjectName("vboxlayout") self.groupBox = QtWidgets.QGroupBox(AdvancedOptionsPage) @@ -18,9 +18,40 @@ class Ui_AdvancedOptionsPage(object): self.gridlayout = QtWidgets.QGridLayout(self.groupBox) self.gridlayout.setSpacing(2) self.gridlayout.setObjectName("gridlayout") - self.recursively_add_files = QtWidgets.QCheckBox(self.groupBox) - self.recursively_add_files.setObjectName("recursively_add_files") - self.gridlayout.addWidget(self.recursively_add_files, 5, 0, 1, 1) + self.label_ignore_regex = QtWidgets.QLabel(self.groupBox) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.label_ignore_regex.sizePolicy().hasHeightForWidth()) + self.label_ignore_regex.setSizePolicy(sizePolicy) + self.label_ignore_regex.setWordWrap(True) + self.label_ignore_regex.setObjectName("label_ignore_regex") + self.gridlayout.addWidget(self.label_ignore_regex, 1, 0, 1, 1) + self.horizontalLayout_2 = QtWidgets.QHBoxLayout() + self.horizontalLayout_2.setObjectName("horizontalLayout_2") + self.label_request_limit = QtWidgets.QLabel(self.groupBox) + sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Preferred) + sizePolicy.setHorizontalStretch(0) + sizePolicy.setVerticalStretch(0) + sizePolicy.setHeightForWidth(self.label_request_limit.sizePolicy().hasHeightForWidth()) + self.label_request_limit.setSizePolicy(sizePolicy) + self.label_request_limit.setObjectName("label_request_limit") + self.horizontalLayout_2.addWidget(self.label_request_limit) + self.query_limit = QtWidgets.QSpinBox(self.groupBox) + self.query_limit.setAccelerated(True) + self.query_limit.setMinimum(1) + self.query_limit.setMaximum(100) + self.query_limit.setProperty("value", 50) + self.query_limit.setObjectName("query_limit") + self.horizontalLayout_2.addWidget(self.query_limit) + self.gridlayout.addLayout(self.horizontalLayout_2, 8, 0, 1, 1) + self.regex_error = QtWidgets.QLabel(self.groupBox) + self.regex_error.setText("") + self.regex_error.setObjectName("regex_error") + self.gridlayout.addWidget(self.regex_error, 3, 0, 1, 1) + self.ignore_regex = QtWidgets.QLineEdit(self.groupBox) + self.ignore_regex.setObjectName("ignore_regex") + self.gridlayout.addWidget(self.ignore_regex, 2, 0, 1, 1) self.horizontalLayout = QtWidgets.QHBoxLayout() self.horizontalLayout.setSizeConstraint(QtWidgets.QLayout.SetDefaultConstraint) self.horizontalLayout.setObjectName("horizontalLayout") @@ -48,25 +79,12 @@ class Ui_AdvancedOptionsPage(object): self.ignore_track_duration_difference_under.setObjectName("ignore_track_duration_difference_under") self.horizontalLayout.addWidget(self.ignore_track_duration_difference_under) self.gridlayout.addLayout(self.horizontalLayout, 6, 0, 2, 1) - self.label_ignore_regex = QtWidgets.QLabel(self.groupBox) - sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred) - sizePolicy.setHorizontalStretch(0) - sizePolicy.setVerticalStretch(0) - sizePolicy.setHeightForWidth(self.label_ignore_regex.sizePolicy().hasHeightForWidth()) - self.label_ignore_regex.setSizePolicy(sizePolicy) - self.label_ignore_regex.setWordWrap(True) - self.label_ignore_regex.setObjectName("label_ignore_regex") - self.gridlayout.addWidget(self.label_ignore_regex, 1, 0, 1, 1) - self.regex_error = QtWidgets.QLabel(self.groupBox) - self.regex_error.setText("") - self.regex_error.setObjectName("regex_error") - self.gridlayout.addWidget(self.regex_error, 3, 0, 1, 1) + self.recursively_add_files = QtWidgets.QCheckBox(self.groupBox) + self.recursively_add_files.setObjectName("recursively_add_files") + self.gridlayout.addWidget(self.recursively_add_files, 5, 0, 1, 1) self.ignore_hidden_files = QtWidgets.QCheckBox(self.groupBox) self.ignore_hidden_files.setObjectName("ignore_hidden_files") self.gridlayout.addWidget(self.ignore_hidden_files, 4, 0, 1, 1) - self.ignore_regex = QtWidgets.QLineEdit(self.groupBox) - self.ignore_regex.setObjectName("ignore_regex") - self.gridlayout.addWidget(self.ignore_regex, 2, 0, 1, 1) self.vboxlayout.addWidget(self.groupBox) self.groupBox_completeness = QtWidgets.QGroupBox(AdvancedOptionsPage) self.groupBox_completeness.setObjectName("groupBox_completeness") @@ -105,7 +123,8 @@ class Ui_AdvancedOptionsPage(object): AdvancedOptionsPage.setTabOrder(self.ignore_regex, self.ignore_hidden_files) AdvancedOptionsPage.setTabOrder(self.ignore_hidden_files, self.recursively_add_files) AdvancedOptionsPage.setTabOrder(self.recursively_add_files, self.ignore_track_duration_difference_under) - AdvancedOptionsPage.setTabOrder(self.ignore_track_duration_difference_under, self.completeness_ignore_videos) + AdvancedOptionsPage.setTabOrder(self.ignore_track_duration_difference_under, self.query_limit) + AdvancedOptionsPage.setTabOrder(self.query_limit, self.completeness_ignore_videos) AdvancedOptionsPage.setTabOrder(self.completeness_ignore_videos, self.completeness_ignore_pregap) AdvancedOptionsPage.setTabOrder(self.completeness_ignore_pregap, self.completeness_ignore_data) AdvancedOptionsPage.setTabOrder(self.completeness_ignore_data, self.completeness_ignore_silence) @@ -113,9 +132,10 @@ class Ui_AdvancedOptionsPage(object): def retranslateUi(self, AdvancedOptionsPage): _translate = QtCore.QCoreApplication.translate self.groupBox.setTitle(_("Advanced options")) - self.recursively_add_files.setText(_("Include sub-folders when adding files from folder")) - self.label_track_duration_diff.setText(_("Ignore track duration difference under this number of seconds")) self.label_ignore_regex.setText(_("Ignore file paths matching the following regular expression:")) + self.label_request_limit.setText(_("Max. number of entities to return per MusicBrainz query")) + self.label_track_duration_diff.setText(_("Ignore track duration difference under this number of seconds")) + self.recursively_add_files.setText(_("Include sub-folders when adding files from folder")) self.ignore_hidden_files.setText(_("Ignore hidden files")) self.groupBox_completeness.setTitle(_("Ignore the following tracks when determining whether a release is complete")) self.completeness_ignore_videos.setText(_("Video tracks")) diff --git a/test/test_browser.py b/test/test_browser.py index ad6808a94..990c7cbda 100644 --- a/test/test_browser.py +++ b/test/test_browser.py @@ -196,6 +196,7 @@ class BrowserLookupTest(PicardTestCase): @patch.object(webbrowser2, 'open') def test_search_entity(self, mock_open): + self.set_config_values({'query_limit': 25}) result = self.lookup.search_entity('foo', 'search:123') self.assertTrue(result) url = mock_open.call_args[0][0] @@ -209,6 +210,7 @@ class BrowserLookupTest(PicardTestCase): @patch.object(webbrowser2, 'open') def test_search_entity_advanced(self, mock_open): + self.set_config_values({'query_limit': 25}) result = self.lookup.search_entity('foo', 'search:123', adv=True) self.assertTrue(result) url = mock_open.call_args[0][0] @@ -232,6 +234,7 @@ class BrowserLookupTest(PicardTestCase): @patch.object(webbrowser2, 'open') def test_search_entity_mbid_lookup_force_browser(self, mock_open): + self.set_config_values({'query_limit': 25}) with patch.object(self.lookup, 'mbid_lookup') as mock_lookup: entity = 'artist' mbid = '4836aa50-a9ae-490a-983b-cfc8efca92de' diff --git a/ui/options_advanced.ui b/ui/options_advanced.ui index ff7bc50c7..7f91979eb 100644 --- a/ui/options_advanced.ui +++ b/ui/options_advanced.ui @@ -7,7 +7,7 @@ 0 0 570 - 435 + 455 @@ -20,13 +20,65 @@ 2 - - + + + + + 0 + 0 + + - Include sub-folders when adding files from folder + Ignore file paths matching the following regular expression: + + + true + + + + + + + 0 + 0 + + + + Max. number of entities to return per MusicBrainz query + + + + + + + true + + + 1 + + + 100 + + + 50 + + + + + + + + + + + + + + + @@ -78,26 +130,10 @@ - - - - - 0 - 0 - - + + - Ignore file paths matching the following regular expression: - - - true - - - - - - - + Include sub-folders when adding files from folder @@ -108,9 +144,6 @@ - - - @@ -188,6 +221,7 @@ ignore_hidden_files recursively_add_files ignore_track_duration_difference_under + query_limit completeness_ignore_videos completeness_ignore_pregap completeness_ignore_data