diff --git a/picard/acoustid/__init__.py b/picard/acoustid/__init__.py index 8b93f18bf..b487fcdf7 100644 --- a/picard/acoustid/__init__.py +++ b/picard/acoustid/__init__.py @@ -42,11 +42,12 @@ def get_score(node): class AcoustIDClient(QtCore.QObject): - def __init__(self): + def __init__(self, acoustid_api): super().__init__() self._queue = deque() self._running = 0 self._max_processes = 2 + self._acoustid_api = acoustid_api # The second condition is checked because in case of a packaged build of picard # the temp directory that pyinstaller decompresses picard into changes on every @@ -155,7 +156,7 @@ class AcoustIDClient(QtCore.QObject): else: fp_type, recordingid = result params['recordingid'] = recordingid - self.tagger.acoustid_api.query_acoustid(partial(self._on_lookup_finished, next_func, file), **params) + self._acoustid_api.query_acoustid(partial(self._on_lookup_finished, next_func, file), **params) def _on_fpcalc_finished(self, next_func, file, exit_code, exit_status): process = self.sender() diff --git a/picard/acoustid/manager.py b/picard/acoustid/manager.py index edb373c25..95bb558b1 100644 --- a/picard/acoustid/manager.py +++ b/picard/acoustid/manager.py @@ -42,9 +42,10 @@ class AcoustIDManager(QtCore.QObject): # some leeway. BATCH_SUBMIT_COUNT = 240 - def __init__(self): + def __init__(self, acoustid_api): super().__init__() self._fingerprints = {} + self._acoustid_api = acoustid_api def add(self, file, recordingid): if not hasattr(file, 'acoustid_fingerprint'): @@ -110,7 +111,7 @@ class AcoustIDManager(QtCore.QObject): echo=None ) next_func = partial(self._batch_submit, submissions) - self.tagger.acoustid_api.submit_acoustid_fingerprints(fingerprints, + self._acoustid_api.submit_acoustid_fingerprints(fingerprints, partial(self._batch_submit_finished, submission_batch, next_func)) def _batch_submit_finished(self, submissions, next_func, document, http, error): diff --git a/picard/tagger.py b/picard/tagger.py index d1eb4abeb..b9f6c6d85 100644 --- a/picard/tagger.py +++ b/picard/tagger.py @@ -229,13 +229,14 @@ class Tagger(QtWidgets.QApplication): self.webservice = WebService() self.mb_api = MBAPIHelper(self.webservice) - self.acoustid_api = AcoustIdAPIHelper(self.webservice) load_user_collections() # Initialize fingerprinting - self._acoustid = acoustid.AcoustIDClient() + acoustid_api = AcoustIdAPIHelper(self.webservice) + self._acoustid = acoustid.AcoustIDClient(acoustid_api) self._acoustid.init() + self.acoustidmanager = AcoustIDManager(acoustid_api) # Load plugins self.pluginmanager = PluginManager() @@ -250,7 +251,6 @@ class Tagger(QtWidgets.QApplication): os.makedirs(USER_PLUGIN_DIR) self.pluginmanager.load_plugins_from_directory(USER_PLUGIN_DIR) - self.acoustidmanager = AcoustIDManager() self.browser_integration = BrowserIntegration() self.files = {} diff --git a/test/test_acoustidmanager.py b/test/test_acoustidmanager.py index 0b5c2f60b..e70e5f288 100644 --- a/test/test_acoustidmanager.py +++ b/test/test_acoustidmanager.py @@ -1,18 +1,50 @@ -from unittest.mock import MagicMock +from unittest.mock import ( + MagicMock, + Mock, +) from test.picardtestcase import PicardTestCase +from picard import config from picard.acoustid.manager import AcoustIDManager from picard.file import File +def mock_succeed_submission(*args, **kwargs): + # Run the callback + args[1]({}, None, None) + + +def mock_fail_submission(*args, **kwargs): + # Run the callback with error arguments + args[1]({}, MagicMock(), True) + + class AcoustIDManagerTest(PicardTestCase): def setUp(self): super().setUp() - self.acoustidmanager = AcoustIDManager() + config.setting = { + "clear_existing_tags": False, + "compare_ignore_tags": [] + } + self.mock_api_helper = MagicMock() + self.mock_api_helper.submit_acoustid_fingerprints = Mock(wraps=mock_succeed_submission) + self.acoustidmanager = AcoustIDManager(self.mock_api_helper) self.tagger.window = MagicMock() self.tagger.window.enable_submit = MagicMock() + def _add_unsubmitted_files(self, count): + files = [] + for i in range(0, count): + file = File('foo%d.flac' % i) + files.append(file) + file.acoustid_fingerprint = 'foo' + file.acoustid_length = 120 + self.acoustidmanager.add(file, None) + self.acoustidmanager.update(file, '00000000-0000-0000-0000-%012d' % i) + self.assertFalse(self.acoustidmanager.is_submitted(file)) + return files + def test_add_invalid(self): file = File('foo.flac') self.acoustidmanager.add(file, '00000000-0000-0000-0000-000000000001') @@ -51,3 +83,28 @@ class AcoustIDManagerTest(PicardTestCase): self.assertFalse(self.acoustidmanager.is_submitted(file)) self.acoustidmanager.update(file, '') self.assertTrue(self.acoustidmanager.is_submitted(file)) + + def test_submit_single_batch(self): + f = self._add_unsubmitted_files(1)[0] + self.acoustidmanager.submit() + self.assertEqual(self.mock_api_helper.submit_acoustid_fingerprints.call_count, 1) + self.assertEqual( + f.acoustid_fingerprint, + self.mock_api_helper.submit_acoustid_fingerprints.call_args[0][0][0].fingerprint + ) + + def test_submit_multi_batch(self): + files = self._add_unsubmitted_files(5 * AcoustIDManager.BATCH_SUBMIT_COUNT + 1) + self.acoustidmanager.submit() + self.assertEqual(self.mock_api_helper.submit_acoustid_fingerprints.call_count, 6) + for f in files: + self.assertTrue(self.acoustidmanager.is_submitted(f)) + + def test_submit_multi_batch_failure(self): + self.mock_api_helper.submit_acoustid_fingerprints = Mock(wraps=mock_fail_submission) + files = self._add_unsubmitted_files(5 * AcoustIDManager.BATCH_SUBMIT_COUNT + 1) + self.acoustidmanager.submit() + self.assertEqual(self.mock_api_helper.submit_acoustid_fingerprints.call_count, 6) + for f in files: + self.assertFalse(self.acoustidmanager.is_submitted(f)) +