Linting cleanup + context menu support + clearer ids.

This commit is contained in:
Andrew Dunai 2018-01-11 15:43:39 +02:00
parent a6db19087e
commit b761d31793
3 changed files with 156 additions and 56 deletions

View file

@ -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'<Track "{} - {}" from {}>'.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):
"""

View file

@ -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()

View file

@ -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):
"""