From b761d31793e5aa1cc37de88fc5e9b02766990697 Mon Sep 17 00:00:00 2001 From: Andrew Dunai Date: Thu, 11 Jan 2018 15:43:39 +0200 Subject: [PATCH] Linting cleanup + context menu support + clearer ids. --- clay/gp.py | 117 +++++++++++++++++++++++++++++++++++++++-------- clay/playbar.py | 3 ++ clay/songlist.py | 92 ++++++++++++++++++++++--------------- 3 files changed, 156 insertions(+), 56 deletions(-) diff --git a/clay/gp.py b/clay/gp.py index 5f16fe6..6501ff0 100644 --- a/clay/gp.py +++ b/clay/gp.py @@ -76,19 +76,48 @@ class Track(object): TYPE_UPLOADED = 'uploaded' TYPE_STORE = 'store' - def __init__(self, track_id, title, artist, duration, track_type): - self._id = track_id + def __init__(self, id_, track_id, store_id, title, artist, duration): + self.id_ = id_ + self.track_id = track_id + self.store_id = store_id self.title = title self.artist = artist self.duration = duration - self.type = track_type @property def id(self): """ "id" or "track_id" of this track. """ - return self._id + if self.id_: + return self.id_ + if self.track_id: + return self.track_id + if self.store_id: + return self.store_id + raise Exception('None of "id", "track_id" and "store_id" were set for this track!') + + def __eq__(self, other): + if self.track_id: + return self.track_id == other.track_id + if self.store_id: + return self.store_id == other.store_id + if self.id_: + return self.store_id == other.id_ + return False + + @property + def type(self): + """ + Returns track type. + """ + if self.track_id: + return 'playlist' + if self.store_id: + return 'store' + if self.id_: + return 'uploaded' + raise Exception('None of "id", "track_id" and "store_id" were set for this track!') @classmethod def from_data(cls, data, many=False): @@ -99,21 +128,29 @@ class Track(object): if many: return [cls.from_data(one) for one in data] - if 'id' in data: - track_id = data['id'] - track_type = 'uploaded' - elif 'storeId' in data: - track_id = data['storeId'] - track_type = 'store' - else: - raise Exception('Track is missing both "id" and "storeId"! Where does it come from?') + if 'id' not in data and 'storeId' not in data and 'trackId' not in data: + raise Exception('Track is missing "id", "storeId" and "trackId"!') return Track( - track_id=track_id, + id_=data.get('id'), + track_id=data.get('trackId'), + store_id=data.get('storeId'), title=data['title'], artist=data['artist'], - duration=int(data['durationMillis']), - track_type=track_type + duration=int(data['durationMillis']) + ) + + def copy(self): + """ + Returns a copy of this instance. + """ + return Track( + id_=self.id_, + track_id=self.track_id, + store_id=self.store_id, + title=self.title, + artist=self.artist, + duration=self.duration ) def get_url(self, callback): @@ -151,6 +188,23 @@ class Track(object): add_to_my_library_async = asynchronous(add_to_my_library) + def remove_from_my_library(self): + """ + Remove a track from my library. + """ + return GP.get().remove_from_my_library(self) + + remove_from_my_library_async = asynchronous(remove_from_my_library) + + def __str__(self): + return u''.format( + self.artist, + self.title, + self.type + ) + + __repr__ = __str__ + class Artist(object): """ @@ -222,14 +276,20 @@ class Playlist(object): for tracks that are in both playlist and "my library"). """ results = [] - cached_tracks_map = GP.get().get_cached_tracks_map() for playlist_track in playlist_tracks: if 'track' in playlist_track: track = dict(playlist_track['track']) - track['id'] = playlist_track['trackId'] + # track['id'] = playlist_track['trackId'] track = Track.from_data(track) else: - track = cached_tracks_map[playlist_track['trackId']] + track = GP.get().get_track_by_id(playlist_track['trackId']).copy() + # track = cached_tracks_map[playlist_track['trackId']].copy() + # raise Exception('{} {} {}'.format(track.id_, track.store_id, track.track_id)) + track.track_id = playlist_track['trackId'] + # raise Exception(track) + # track.store_id = playlist_track.get('storeId') + # track.id_ = playlist_track.get('id') + # track['trackId'] = playlist_track['trackId'] results.append(track) return results @@ -360,7 +420,8 @@ class GP(object): """ if self.cached_tracks: return self.cached_tracks - self.cached_tracks = Track.from_data(self.mobile_client.get_all_songs(), True) + data = self.mobile_client.get_all_songs() + self.cached_tracks = Track.from_data(data, True) return self.cached_tracks get_all_tracks_async = asynchronous(get_all_tracks) @@ -397,6 +458,15 @@ class GP(object): """ return {track.id: track for track in self.cached_tracks} + def get_track_by_id(self, any_id): + """ + Return track by id, store_id or track_id. + """ + for track in self.cached_tracks: + if any_id in (track.id_, track.store_id, track.track_id): + return track + return None + def search(self, query): """ Find tracks and return an instance of :class:`.SearchResults`. @@ -415,6 +485,15 @@ class GP(object): self.invalidate_caches() return result + def remove_from_my_library(self, track): + """ + Remove a track from my library. + """ + result = self.mobile_client.delete_songs(track.id) + if result: + self.invalidate_caches() + return result + @property def is_authenticated(self): """ diff --git a/clay/playbar.py b/clay/playbar.py index 1cf3592..2c27733 100644 --- a/clay/playbar.py +++ b/clay/playbar.py @@ -18,6 +18,9 @@ class PlayBar(urwid.ProgressBar): self.player = Player.get() def get_text(self): + """ + Return text for display in this bar. + """ if self.track is None: return u'Idle' progress = self.player.get_play_progress_seconds() diff --git a/clay/songlist.py b/clay/songlist.py index adfcd0d..ec9463f 100644 --- a/clay/songlist.py +++ b/clay/songlist.py @@ -5,6 +5,7 @@ Components for song listing. import urwid from clay.notifications import NotificationArea from clay.player import Player +from clay.gp import GP class SongListItem(urwid.Pile): @@ -89,7 +90,7 @@ class SongListItem(urwid.Pile): ) ) self.line2.set_text( - u' {}'.format(self.track.artist) + u' {} ({})\n'.format(self.track.artist, self.track.type) ) if self.state == SongListItem.STATE_IDLE: self.content.set_attr('line1') @@ -163,47 +164,64 @@ class SongListBoxPopup(urwid.LineBox): def __init__(self, songitem): self.songitem = songitem + options = [ + urwid.AttrWrap(urwid.Text(songitem.full_title), 'panel') + ] + if not GP.get().get_track_by_id(songitem.track.id): + options.append(urwid.AttrWrap(urwid.Button( + 'Add to my library', on_press=self.add_to_my_library + ), 'panel', 'panel_focus')) + else: + options.append(urwid.AttrWrap(urwid.Button( + 'Remove from my library', on_press=self.remove_from_my_library + ), 'panel', 'panel_focus')) + options.append(urwid.AttrWrap(urwid.Divider('-'), 'panel_divider', 'panel_divider_focus')) + options.append(urwid.AttrWrap(urwid.Button( + 'Close', on_press=self.close + ), 'panel', 'panel_focus')) super(SongListBoxPopup, self).__init__( - urwid.Pile([ - urwid.AttrWrap(urwid.Text(songitem.full_title), 'panel'), - urwid.AttrWrap(urwid.Button( - 'Add to my library', on_press=self.add_to_my_library - ), 'panel', 'panel_focus'), - # urwid.AttrWrap(urwid.Button( - # 'Remove from my library', on_press=self.remove_from_my_library - # ), 'panel', 'panel_focus'), - # urwid.AttrWrap(urwid.Button('Remove from my library'), 'panel', 'panel_focus'), - # urwid.AttrWrap(urwid.Divider('-'), 'panel_divider', 'panel_divider_focus'), - # urwid.AttrWrap(urwid.Button('Add to player queue'), 'panel', 'panel_focus'), - # urwid.AttrWrap(urwid.Button('Remove from player queue'), 'panel', 'panel_focus'), - # urwid.AttrWrap(urwid.Divider('-'), 'panel_divider', 'panel_divider_focus'), - # urwid.AttrWrap(urwid.Button('Add to playlist'), 'panel', 'panel_focus'), - # urwid.AttrWrap(urwid.Button('Remove from playlist'), 'panel', 'panel_focus'), - # urwid.AttrWrap(urwid.Divider('-'), 'panel_divider', 'panel_divider_focus'), - # urwid.AttrWrap(urwid.Button('Like'), 'panel', 'panel_focus'), - # urwid.AttrWrap(urwid.Button('Dislike'), 'panel', 'panel_focus'), - ]) + urwid.Pile(options) ) - # raise(Exception(songitem.full_title)) def add_to_my_library(self, _): """ Add related track to my library. """ - self.songitem.track.add_to_my_library_async(callback=self.on_add_to_my_library) - urwid.emit_signal(self, 'close') + def on_add_to_my_library(result, error): + """ + Show notification with song addition result. + """ + if error or not result: + NotificationArea.notify('Error while adding track to my library: {}'.format( + str(error) if error else 'reason is unknown :(' + )) + else: + NotificationArea.notify('Track added to library!') + self.songitem.track.add_to_my_library_async(callback=on_add_to_my_library) + self.close() - @staticmethod - def on_add_to_my_library(result, error): + def remove_from_my_library(self, _): """ - Show notification with song addition result. + Removes related track to my library. """ - if error or not result: - NotificationArea.notify('Error while adding track to my library: {}'.format( - str(error) if error else 'reason is unknown :(' - )) - else: - NotificationArea.notify('Track added to library!') + def on_remove_from_my_library(result, error): + """ + Show notification with song removal result. + """ + if error or not result: + NotificationArea.notify('Error while removing track from my library: {}'.format( + str(error) if error else 'reason is unknown :(' + )) + else: + NotificationArea.notify('Track removed from library!') + self.songitem.track.remove_from_my_library_async(callback=on_remove_from_my_library) + self.close() + + def close(self, *_): + """ + Close this menu. + """ + urwid.emit_signal(self, 'close') class SongListBox(urwid.Frame): @@ -253,7 +271,7 @@ class SongListBox(urwid.Frame): current_index = None for index, track in enumerate(tracks): songitem = SongListItem(track) - if current_track is not None and current_track.id == track.id: + if current_track is not None and current_track == track: songitem.set_state(SongListItem.STATE_LOADING) if current_index is None: current_index = index @@ -341,9 +359,9 @@ class SongListBox(urwid.Frame): for i, songitem in enumerate(self.walker): if isinstance(songitem, urwid.Text): continue - if songitem.track.id == track.id: + if songitem.track == track: songitem.set_state(SongListItem.STATE_LOADING) - self.set_focus(i) + self.walker.set_focus(i) elif songitem.state != SongListItem.STATE_IDLE: songitem.set_state(SongListItem.STATE_IDLE) @@ -359,7 +377,7 @@ class SongListBox(urwid.Frame): for songitem in self.walker: if isinstance(songitem, urwid.Text): continue - if songitem.track.id == current_track.id: + if songitem.track == current_track: songitem.set_state( SongListItem.STATE_LOADING if is_loading @@ -409,7 +427,7 @@ class SongListBox(urwid.Frame): if key == 'meta m' and self.is_context_menu_visible: self.hide_context_menu() return None - return super(SongListBox).keypress(size, key) + return super(SongListBox, self).keypress(size, key) def mouse_event(self, size, event, button, col, row, focus): """