mirror of
https://github.com/vale981/clay
synced 2025-03-05 09:31:40 -05:00
Linting cleanup + context menu support + clearer ids.
This commit is contained in:
parent
a6db19087e
commit
b761d31793
3 changed files with 156 additions and 56 deletions
117
clay/gp.py
117
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'<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):
|
||||
"""
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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):
|
||||
"""
|
||||
|
|
Loading…
Add table
Reference in a new issue