From c4cb66a759548b458b11d4a5429c534f06fd5e5f Mon Sep 17 00:00:00 2001 From: Andrew Dunai Date: Thu, 1 Feb 2018 17:25:52 +0200 Subject: [PATCH] Added track caching option & more debugging. --- README.md | 1 + clay/gp.py | 7 +++++++ clay/meta.py | 2 +- clay/pages/debug.py | 27 +++++++++++++++++++++++++++ clay/pages/settings.py | 9 ++++++++- clay/player.py | 37 ++++++++++++++++++++++++++++++++++++- clay/settings.py | 28 ++++++++++++++++++++++++++++ 7 files changed, 108 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 57cc72c..71a7657 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,7 @@ Documentation is ![available here](http://clay.readthedocs.io/en/latest/). - Notifications - Audio equalizer - Global hotkeys +- Song file caching - Configuration UI - Token caching for faster authorizations - Song operations (add to library, start station etc.) diff --git a/clay/gp.py b/clay/gp.py index 200de61..0456e13 100644 --- a/clay/gp.py +++ b/clay/gp.py @@ -615,3 +615,10 @@ class GP(object): Return True if user is authenticated on Google Play Music, false otherwise. """ return self.mobile_client.is_authenticated() + + @property + def is_subscribed(self): + """ + Return True if user is subscribed on Google Play Music, false otherwise. + """ + return self.mobile_client.is_subscribed diff --git a/clay/meta.py b/clay/meta.py index 39b3c62..47b660b 100644 --- a/clay/meta.py +++ b/clay/meta.py @@ -7,7 +7,7 @@ except ImportError: codename = None APP_NAME = 'Clay Player' -VERSION = '0.5.6' +VERSION = '0.6.0' if codename is not None: VERSION_WITH_CODENAME = VERSION + '-' + codename(separator='-', id=VERSION) else: diff --git a/clay/pages/debug.py b/clay/pages/debug.py index c7d444f..a500e9e 100644 --- a/clay/pages/debug.py +++ b/clay/pages/debug.py @@ -6,6 +6,7 @@ import urwid from clay.pages.page import AbstractPage from clay.log import Logger from clay.clipboard import copy +from clay.gp import GP class DebugItem(urwid.AttrMap): @@ -52,12 +53,38 @@ class DebugPage(urwid.Pile, AbstractPage): self._append_log(log_record) Logger.get().on_log_event += self._append_log self.listbox = urwid.ListBox(self.walker) + + self.debug_data = urwid.Text('') + super(DebugPage, self).__init__([ + ('pack', self.debug_data), + ('pack', urwid.Text('')), ('pack', urwid.Text('Hit "Enter" to copy selected message to clipboard.')), + ('pack', urwid.Divider(u'\u2550')), self.listbox ]) + GP.get().auth_state_changed += self.update + + self.update() + + def update(self, *_): + """ + Update this widget. + """ + gpclient = GP.get() + self.debug_data.set_text( + '- Is authenticated: {}\n' + '- Is subscribed: {}'.format( + gpclient.is_authenticated, + gpclient.is_subscribed if gpclient.is_authenticated else None + ) + ) + def _append_log(self, log_record): + """ + Add log record to list. + """ self.walker.insert(0, urwid.Divider(u'\u2500')) self.walker.insert(0, DebugItem(log_record)) diff --git a/clay/pages/settings.py b/clay/pages/settings.py index ae97758..773e459 100644 --- a/clay/pages/settings.py +++ b/clay/pages/settings.py @@ -138,6 +138,10 @@ class SettingsPage(urwid.Columns, AbstractPage): self.device_id = urwid.Edit( edit_text=config.get('device_id', '') ) + self.download_tracks = urwid.CheckBox( + 'Download tracks before playback', + state=config.get('download_tracks', False) + ) self.equalizer = Equalizer() super(SettingsPage, self).__init__([urwid.ListBox(urwid.SimpleListWalker([ urwid.Text('Settings'), @@ -151,6 +155,8 @@ class SettingsPage(urwid.Columns, AbstractPage): urwid.Text('Device ID'), urwid.AttrWrap(self.device_id, 'input', 'input_focus'), urwid.Divider(' '), + self.download_tracks, + urwid.Divider(' '), urwid.AttrWrap(urwid.Button( 'Save', on_press=self.on_save ), 'input', 'input_focus'), @@ -165,7 +171,8 @@ class SettingsPage(urwid.Columns, AbstractPage): Settings.set_config(dict( username=self.username.edit_text, password=self.password.edit_text, - device_id=self.device_id.edit_text + device_id=self.device_id.edit_text, + download_tracks=self.download_tracks.state )) self.app.set_page('MyLibraryPage') self.app.log_in() diff --git a/clay/player.py b/clay/player.py index cfc0c84..54df857 100644 --- a/clay/player.py +++ b/clay/player.py @@ -5,12 +5,18 @@ Media player built using libVLC. # pylint: disable=too-many-public-methods from random import randint import json +try: # Python 3.x + from urllib.request import urlopen +except ImportError: # Python 2.x + from urllib2 import urlopen from clay import vlc from clay.eventhook import EventHook from clay.notifications import NotificationArea from clay.hotkeys import HotkeyManager +from clay.settings import Settings from clay import meta +from clay.log import Logger class Queue(object): @@ -329,10 +335,34 @@ class Player(object): if track is None: return self._is_loading = True - track.get_url(callback=self._play_ready) self.broadcast_state() self.track_changed.fire(track) + if Settings.get_config().get('download_tracks', False): + path = Settings.get_cached_file_path(track.store_id + '.mp3') + if path is None: + Logger.get().debug('Track %s not in cache, downloading...', track.store_id) + track.get_url(callback=self._download_track) + else: + Logger.get().debug('Track %s in cache, playing', track.store_id) + self._play_ready(path, None, track) + else: + Logger.get().debug('Starting to stream %s', track.store_id) + track.get_url(callback=self._play_ready) + + def _download_track(self, url, error, track): + if error: + NotificationArea.notify('Failed to request media URL: {}'.format(str(error))) + Logger.get().error( + 'Failed to request media URL for track %s: %s', + track.store_id, + str(error) + ) + return + response = urlopen(url) + path = Settings.save_file_to_cache(track.store_id + '.mp3', response.read()) + self._play_ready(path, None, track) + def _play_ready(self, url, error, track): """ Called once track's media stream URL request completes. @@ -341,6 +371,11 @@ class Player(object): self._is_loading = False if error: NotificationArea.notify('Failed to request media URL: {}'.format(str(error))) + Logger.get().error( + 'Failed to request media URL for track %s: %s', + track.store_id, + str(error) + ) return assert track media = vlc.Media(url) diff --git a/clay/settings.py b/clay/settings.py index 1c235a6..9677d9f 100644 --- a/clay/settings.py +++ b/clay/settings.py @@ -25,6 +25,12 @@ class Settings(object): if error.errno != errno.EEXIST: raise + try: + os.makedirs(appdirs.user_cache_dir('clay', 'Clay')) + except OSError as error: + if error.errno != errno.EEXIST: + raise + path = os.path.join(filedir, 'config.yaml') if not os.path.exists(path): with open(path, 'w') as settings: @@ -48,3 +54,25 @@ class Settings(object): config.update(new_config) with open(Settings.get_config_filename(), 'w') as settings: settings.write(yaml.dump(config, default_flow_style=False)) + + @classmethod + def get_cached_file_path(cls, filename): + """ + Get full path to cached file. + """ + cache_dir = appdirs.user_cache_dir('clay', 'Clay') + path = os.path.join(cache_dir, filename) + if os.path.exists(path): + return path + return None + + @classmethod + def save_file_to_cache(cls, filename, content): + """ + Save content into file in cache. + """ + cache_dir = appdirs.user_cache_dir('clay', 'Clay') + path = os.path.join(cache_dir, filename) + with open(path, 'wb') as cachefile: + cachefile.write(content) + return path