Repository: beetbox/beets

Issue:
Subsonicupdate plugin broken in multiple paces
### Problem

Running the latest git version of beets causes the subsonicupdate plugin to fail with a generic "Error: 1".
The current code is broken in 2 places:

1. `def __get_version(self):`
Code is trying to retrieve the version in order to decide whether to authenticate with token or password: a much better choice than my original plan of assuming everyone would use an updated version and/or prefer token over password due to additional security.
In the current code, the REST API is called by passing only parameters 'c' and 'f': this API fails because it's not complete and the plugin stops processing.

2. `def start_scan(self):`
Assuming the authentication would go through, this call would fail anyway since the current code is splitting the API version in 3 values and passing them separately, e.g. `&v=1&v=61&v=1`, rather than `&v=1.61.1`

I have always been running an ancient version of beets with my original code for the plugin and I noticed this just today as I set up a new machine and installed beets form git repo through AUR.
It also seems I am the only user of this plugin given no other feedback has been filed so far, so I can understand if this fix doesn't get high priority.

### Setup

* OS: Arch Linux
* Python version: 3.9.6
* beets version: 1.5.0
* Turning off plugins made problem go away (yes/no):  disabling subsonicupdate plugin will prevent the error from happening

### Additional Info
Subsonic API reference: http://www.subsonic.org/pages/api.jsp



Discussion:
Agh; both of those seem bad! Thanks for the extremely clear explanation of what needs to be fixed.
No problem. I will try and see if I can help on this but Python is not really my thing.
this is my fix for the plugin.
1) no more querying server for API version (you should know beforehand how to authenticate to the server in order to query it) -> added back "auth" option in config.yaml that will specify whether to authenticate with password or token
2) hardcoded API versions 1.12.0 (passwordh auth) and 1.13.0 (token auth): I know this is not pretty and hard-coding things is not a best practice but considering point 1) I don't see any other way around it. The API to scan library is present in both 1.12.0 and 1.13.0 so this should not impact functionality at all

Resolving patch:
diff --git a/beetsplug/subsonicupdate.py b/beetsplug/subsonicupdate.py
--- a/beetsplug/subsonicupdate.py
+++ b/beetsplug/subsonicupdate.py
@@ -20,6 +20,14 @@
         url: https://mydomain.com:443/subsonic
         user: username
         pass: password
+        auth: token
+For older Subsonic versions, token authentication
+is not supported, use password instead:
+    subsonic:
+        url: https://mydomain.com:443/subsonic
+        user: username
+        pass: password
+        auth: pass
 """
 from __future__ import division, absolute_import, print_function
 
@@ -34,7 +42,6 @@
 from beets.plugins import BeetsPlugin
 
 __author__ = 'https://github.com/maffo999'
-AUTH_TOKEN_VERSION = (1, 12)
 
 
 class SubsonicUpdate(BeetsPlugin):
@@ -45,30 +52,11 @@ def __init__(self):
             'user': 'admin',
             'pass': 'admin',
             'url': 'http://localhost:4040',
+            'auth': 'token',
         })
         config['subsonic']['pass'].redact = True
-        self._version = None
-        self._auth = None
         self.register_listener('import', self.start_scan)
 
-    @property
-    def version(self):
-        if self._version is None:
-            self._version = self.__get_version()
-        return self._version
-
-    @property
-    def auth(self):
-        if self._auth is None:
-            if self.version is not None:
-                if self.version > AUTH_TOKEN_VERSION:
-                    self._auth = "token"
-                else:
-                    self._auth = "password"
-            self._log.info(
-                u"using '{}' authentication method".format(self._auth))
-        return self._auth
-
     @staticmethod
     def __create_token():
         """Create salt and token from given password.
@@ -110,48 +98,30 @@ def __format_url(endpoint):
 
         return url + '/rest/{}'.format(endpoint)
 
-    def __get_version(self):
-        url = self.__format_url("ping.view")
-        payload = {
-            'c': 'beets',
-            'f': 'json'
-        }
-        try:
-            response = requests.get(url, params=payload)
-            if response.status_code == 200:
-                json = response.json()
-                version = json['subsonic-response']['version']
-                self._log.info(
-                    u'subsonic version:{0} '.format(version))
-                return tuple(int(s) for s in version.split('.'))
-            else:
-                self._log.error(u'Error: {0}', json)
-                return None
-        except Exception as error:
-            self._log.error(u'Error: {0}'.format(error))
-            return None
-
     def start_scan(self):
         user = config['subsonic']['user'].as_str()
-        url = self.__format_url("startScan.view")
+        auth = config['subsonic']['auth'].as_str()
+        url = self.__format_url("startScan")
+        self._log.debug(u'URL is {0}', url)
+        self._log.debug(u'auth type is {0}', config['subsonic']['auth'])
 
-        if self.auth == 'token':
+        if auth == "token":
             salt, token = self.__create_token()
             payload = {
                 'u': user,
                 't': token,
                 's': salt,
-                'v': self.version,  # Subsonic 6.1 and newer.
+                'v': '1.13.0',  # Subsonic 5.3 and newer
                 'c': 'beets',
                 'f': 'json'
             }
-        elif self.auth == 'password':
+        elif auth == "password":
             password = config['subsonic']['pass'].as_str()
             encpass = hexlify(password.encode()).decode()
             payload = {
                 'u': user,
                 'p': 'enc:{}'.format(encpass),
-                'v': self.version,
+                'v': '1.12.0',
                 'c': 'beets',
                 'f': 'json'
             }


Test patch: