diff --git a/beets/util/confit.py b/beets/util/confit.py index c9051de53..de22e0adf 100644 --- a/beets/util/confit.py +++ b/beets/util/confit.py @@ -1105,12 +1105,19 @@ class Filename(Template): they are relative to the current working directory. This helps attain the expected behavior when using command-line options. """ - def __init__(self, default=REQUIRED, cwd=None, relative_to=None): + def __init__(self, default=REQUIRED, cwd=None, relative_to=None, + in_app_dir=False): """ `relative_to` is the name of a sibling value that is being validated at the same time. + + `in_app_dir` indicates whether the path should be resolved + inside the application's config directory (even when the setting + does not come from a file). """ super(Filename, self).__init__(default) - self.cwd, self.relative_to = cwd, relative_to + self.cwd = cwd + self.relative_to = relative_to + self.in_app_dir = in_app_dir def __repr__(self): args = [] @@ -1124,6 +1131,9 @@ class Filename(Template): if self.relative_to is not None: args.append('relative_to=' + repr(self.relative_to)) + if self.in_app_dir: + args.append('in_app_dir=True') + return 'Filename({0})'.format(', '.join(args)) def resolve_relative_to(self, view, template): @@ -1198,7 +1208,7 @@ class Filename(Template): path, ) - elif source.filename: + elif source.filename or self.in_app_dir: # From defaults: relative to the app's directory. path = os.path.join(view.root().config_dir(), path) diff --git a/beetsplug/discogs.py b/beetsplug/discogs.py index 554c6023d..f025c9775 100644 --- a/beetsplug/discogs.py +++ b/beetsplug/discogs.py @@ -17,6 +17,7 @@ discogs-client library. """ from beets.autotag.hooks import AlbumInfo, TrackInfo, Distance from beets.plugins import BeetsPlugin +from beets.util import confit from discogs_client import Release, Client from discogs_client.exceptions import DiscogsAPIError from requests.exceptions import ConnectionError @@ -24,6 +25,7 @@ import beets import logging import re import time +import json log = logging.getLogger('beets') @@ -41,32 +43,51 @@ class DiscogsPlugin(BeetsPlugin): self.config.add({ 'apikey': 'rAzVUQYRaoFjeBjyWuWZ', 'apisecret': 'plxtUTqoCzwxZpqdPysCwGuBSmZNdZVy', + 'tokenfile': 'discogs_token.json', 'source_weight': 0.5, }) c_key = self.config['apikey'].get(unicode) c_secret = self.config['apisecret'].get(unicode) + + # Get the OAuth token from a file or log in. try: - token = self.config['token'].get(unicode) - secret = self.config['secret'].get(unicode) - except beets.confit.NotFoundError: + with open(self._tokenfile()) as f: + tokendata = json.load(f) + except IOError: + # No token yet. Generate one. token, secret = self.authenticate(c_key, c_secret) + else: + token = tokendata['token'] + secret = tokendata['secret'] self.discogs_client = Client(USER_AGENT, c_key, c_secret, token, secret) + def _tokenfile(self): + """Get the path to the JSON file for storing the OAuth token. + """ + return self.config['tokenfile'].get(confit.Filename(in_app_dir=True)) + def authenticate(self, c_key, c_secret): + # Get the link for the OAuth page. auth_client = Client(USER_AGENT, c_key, c_secret) _, _, url = auth_client.get_authorize_url() beets.ui.print_("To authenticate with Discogs, visit:") beets.ui.print_(url) + + # Ask for the code and validate it. code = beets.ui.input_("Enter the code:") try: token, secret = auth_client.get_access_token(code) except DiscogsAPIError: raise beets.ui.UserError('Discogs authorization failed') - beets.ui.print_("token: %s\nsecret: %s" % (token, secret)) - beets.ui.print_("Add the above to beets config!") + + # Save the token for later use. + log.debug('Discogs token {0}, secret {1}'.format(token, secret)) + with open(self._tokenfile(), 'w') as f: + json.dump({'token': token, 'secret': secret}, f) + return token, secret def album_distance(self, items, album_info, mapping):