mirror of
https://github.com/vale981/clay
synced 2025-03-04 17:11:41 -05:00
commit
e144f4bf36
6 changed files with 210 additions and 5 deletions
|
@ -18,6 +18,7 @@ from clay.playbar import PlayBar
|
|||
from clay.pages.debug import DebugPage
|
||||
from clay.pages.mylibrary import MyLibraryPage
|
||||
from clay.pages.myplaylists import MyPlaylistsPage
|
||||
from clay.pages.mystations import MyStationsPage
|
||||
from clay.pages.playerqueue import QueuePage
|
||||
from clay.pages.search import SearchPage
|
||||
from clay.pages.settings import SettingsPage
|
||||
|
@ -68,6 +69,7 @@ class AppWidget(urwid.Frame):
|
|||
DebugPage(self),
|
||||
MyLibraryPage(self),
|
||||
MyPlaylistsPage(self),
|
||||
MyStationsPage(self),
|
||||
QueuePage(self),
|
||||
SearchPage(self),
|
||||
SettingsPage(self)
|
||||
|
|
|
@ -37,6 +37,9 @@ hotkeys:
|
|||
playlist_page:
|
||||
start_playlist: enter
|
||||
|
||||
station_page:
|
||||
start_station: enter
|
||||
|
||||
debug_page:
|
||||
copy_message: enter
|
||||
|
||||
|
|
47
clay/gp.py
47
clay/gp.py
|
@ -12,6 +12,8 @@ from gmusicapi.clients import Mobileclient
|
|||
from clay.eventhook import EventHook
|
||||
from clay.log import logger
|
||||
|
||||
STATION_FETCH_LEN = 50
|
||||
|
||||
def asynchronous(func):
|
||||
"""
|
||||
Decorates a function to become asynchronous.
|
||||
|
@ -355,7 +357,8 @@ class Station(object):
|
|||
"""
|
||||
Model that represents specific station on Google Play Music.
|
||||
"""
|
||||
def __init__(self, station_id):
|
||||
def __init__(self, station_id, name):
|
||||
self.name = name
|
||||
self._id = station_id
|
||||
self._tracks = []
|
||||
self._tracks_loaded = False
|
||||
|
@ -372,9 +375,12 @@ class Station(object):
|
|||
Fetch tracks related to this station and
|
||||
populate it with :class:`Track` instances.
|
||||
"""
|
||||
data = gp.mobile_client.get_station_tracks(self.id, 100)
|
||||
data = gp.mobile_client.get_station_tracks(self.id, STATION_FETCH_LEN)
|
||||
self._tracks = Track.from_data(data, Track.SOURCE_STATION, many=True)
|
||||
self._tracks_loaded = True
|
||||
return self
|
||||
|
||||
load_tracks_async = asynchronous(load_tracks)
|
||||
|
||||
def get_tracks(self):
|
||||
"""
|
||||
|
@ -383,6 +389,20 @@ class Station(object):
|
|||
assert self._tracks_loaded, 'Must call ".load_tracks()" before ".get_tracks()"'
|
||||
return self._tracks
|
||||
|
||||
@classmethod
|
||||
def from_data(cls, data, many=False):
|
||||
"""
|
||||
Construct and return one or many :class:`.Station` instances
|
||||
from Google Play Music API response.
|
||||
"""
|
||||
if many:
|
||||
return [cls.from_data(one) for one in data if one['inLibrary']]
|
||||
|
||||
return Station(
|
||||
station_id=data['id'],
|
||||
name=data['name']
|
||||
)
|
||||
|
||||
|
||||
class SearchResults(object):
|
||||
"""
|
||||
|
@ -472,6 +492,7 @@ class _GP(object):
|
|||
# self._last_call_index = 0
|
||||
self.cached_tracks = None
|
||||
self.cached_playlists = None
|
||||
self.cached_stations = None
|
||||
|
||||
self.invalidate_caches()
|
||||
|
||||
|
@ -504,10 +525,11 @@ class _GP(object):
|
|||
|
||||
def invalidate_caches(self):
|
||||
"""
|
||||
Clear cached tracks & playlists.
|
||||
Clear cached tracks & playlists & stations.
|
||||
"""
|
||||
self.cached_tracks = None
|
||||
self.cached_playlists = None
|
||||
self.cached_stations = None
|
||||
self.caches_invalidated.fire()
|
||||
|
||||
@synchronized
|
||||
|
@ -576,6 +598,25 @@ class _GP(object):
|
|||
|
||||
get_stream_url_async = asynchronous(get_stream_url)
|
||||
|
||||
@synchronized
|
||||
def get_all_user_station_contents(self, **_):
|
||||
"""
|
||||
Return list of :class:`.Station` instances.
|
||||
"""
|
||||
if self.cached_stations:
|
||||
return self.cached_stations
|
||||
self.get_all_tracks()
|
||||
|
||||
self.cached_stations = Station.from_data(
|
||||
self.mobile_client.get_all_stations(),
|
||||
True
|
||||
)
|
||||
return self.cached_stations
|
||||
|
||||
get_all_user_station_contents_async = ( # pylint: disable=invalid-name
|
||||
asynchronous(get_all_user_station_contents)
|
||||
)
|
||||
|
||||
@synchronized
|
||||
def get_all_user_playlist_contents(self, **_):
|
||||
"""
|
||||
|
|
159
clay/pages/mystations.py
Normal file
159
clay/pages/mystations.py
Normal file
|
@ -0,0 +1,159 @@
|
|||
"""
|
||||
Components for "My stations" page.
|
||||
"""
|
||||
import urwid
|
||||
|
||||
from clay.gp import gp
|
||||
from clay.songlist import SongListBox
|
||||
from clay.notifications import notification_area
|
||||
from clay.pages.page import AbstractPage
|
||||
from clay.hotkeys import hotkey_manager
|
||||
|
||||
class MyStationListItem(urwid.Columns):
|
||||
"""
|
||||
One station in the list of stations.
|
||||
"""
|
||||
signals = ['activate']
|
||||
|
||||
def __init__(self, station):
|
||||
self.station = station
|
||||
self.text = urwid.SelectableIcon(u' \u2630 {} '.format(
|
||||
self.station.name
|
||||
), cursor_position=3)
|
||||
self.text.set_layout('left', 'clip', None)
|
||||
self.content = urwid.AttrWrap(
|
||||
self.text,
|
||||
'default',
|
||||
'selected'
|
||||
)
|
||||
super(MyStationListItem, self).__init__([self.content])
|
||||
|
||||
def keypress(self, size, key):
|
||||
"""
|
||||
Handle keypress.
|
||||
"""
|
||||
return hotkey_manager.keypress("station_page", self, super(MyStationListItem, self),
|
||||
size, key)
|
||||
|
||||
def start_station(self):
|
||||
"""
|
||||
Start playing the selected station
|
||||
"""
|
||||
urwid.emit_signal(self, 'activate', self)
|
||||
return None
|
||||
|
||||
|
||||
class MyStationListBox(urwid.ListBox):
|
||||
"""
|
||||
List of stations.
|
||||
"""
|
||||
signals = ['activate']
|
||||
|
||||
def __init__(self, app):
|
||||
self.app = app
|
||||
|
||||
self.walker = urwid.SimpleListWalker([
|
||||
urwid.Text('Not ready')
|
||||
])
|
||||
self.notification = None
|
||||
|
||||
gp.auth_state_changed += self.auth_state_changed
|
||||
|
||||
super(MyStationListBox, self).__init__(self.walker)
|
||||
|
||||
def auth_state_changed(self, is_auth):
|
||||
"""
|
||||
Called when auth state changes (e. g. user is logged in).
|
||||
Requests fetching of station.
|
||||
"""
|
||||
if is_auth:
|
||||
self.walker[:] = [
|
||||
urwid.Text(u'\n \uf01e Loading stations...', align='center')
|
||||
]
|
||||
|
||||
gp.get_all_user_station_contents_async(callback=self.on_get_stations)
|
||||
|
||||
def on_get_stations(self, stations, error):
|
||||
"""
|
||||
Called when a list of stations fetch completes.
|
||||
Populates list of stations.
|
||||
"""
|
||||
if error:
|
||||
notification_area.notify('Failed to get stations: {}'.format(str(error)))
|
||||
|
||||
items = []
|
||||
for station in stations:
|
||||
mystationlistitem = MyStationListItem(station)
|
||||
urwid.connect_signal(
|
||||
mystationlistitem, 'activate', self.item_activated
|
||||
)
|
||||
items.append(mystationlistitem)
|
||||
|
||||
self.walker[:] = items
|
||||
|
||||
self.app.redraw()
|
||||
|
||||
def item_activated(self, mystationlistitem):
|
||||
"""
|
||||
Called when a specific station is selected.
|
||||
Re-emits this event.
|
||||
"""
|
||||
urwid.emit_signal(self, 'activate', mystationlistitem)
|
||||
|
||||
|
||||
class MyStationsPage(urwid.Columns, AbstractPage):
|
||||
"""
|
||||
Stations page.
|
||||
|
||||
Contains two parts:
|
||||
|
||||
- List of stations (:class:`.MyStationBox`)
|
||||
- List of songs in selected station (:class:`clay:songlist:SongListBox`)
|
||||
"""
|
||||
@property
|
||||
def name(self):
|
||||
return 'Stations'
|
||||
|
||||
@property
|
||||
def key(self):
|
||||
return 3
|
||||
|
||||
def __init__(self, app):
|
||||
self.app = app
|
||||
|
||||
self.stationlist = MyStationListBox(app)
|
||||
self.songlist = SongListBox(app)
|
||||
self.songlist.set_placeholder('\n Select a station.')
|
||||
|
||||
urwid.connect_signal(
|
||||
self.stationlist, 'activate', self.mystationlistitem_activated
|
||||
)
|
||||
|
||||
super(MyStationsPage, self).__init__([
|
||||
self.stationlist,
|
||||
self.songlist
|
||||
])
|
||||
|
||||
def mystationlistitem_activated(self, mystationlistitem):
|
||||
"""
|
||||
Called when specific station is selected.
|
||||
Requests fetching of station tracks
|
||||
"""
|
||||
self.songlist.set_placeholder(u'\n \uf01e Loading station tracks...')
|
||||
mystationlistitem.station.load_tracks_async(callback=self.on_station_loaded)
|
||||
|
||||
def on_station_loaded(self, station, error):
|
||||
"""
|
||||
Called when station tracks fetch completes.
|
||||
Populates songlist with tracks from the selected station.
|
||||
"""
|
||||
if error:
|
||||
notification_area.notify('Failed to get station tracks: {}'.format(str(error)))
|
||||
|
||||
self.songlist.populate(
|
||||
station.get_tracks()
|
||||
)
|
||||
self.app.redraw()
|
||||
|
||||
def activate(self):
|
||||
pass
|
|
@ -18,7 +18,7 @@ class QueuePage(urwid.Columns, AbstractPage):
|
|||
|
||||
@property
|
||||
def key(self):
|
||||
return 3
|
||||
return 4
|
||||
|
||||
def __init__(self, app):
|
||||
self.app = app
|
||||
|
|
|
@ -58,7 +58,7 @@ class SearchPage(urwid.Pile, AbstractPage):
|
|||
|
||||
@property
|
||||
def key(self):
|
||||
return 4
|
||||
return 5
|
||||
|
||||
def __init__(self, app):
|
||||
self.app = app
|
||||
|
|
Loading…
Add table
Reference in a new issue