Added MyStationsPage to pages

This commit is contained in:
fluctuz 2018-03-31 22:37:49 +02:00
parent 08a60e8d95
commit 7ae0c93dec
2 changed files with 203 additions and 3 deletions

View file

@ -12,6 +12,8 @@ from gmusicapi.clients import Mobileclient
from clay.eventhook import EventHook from clay.eventhook import EventHook
from clay.log import logger from clay.log import logger
STATION_FETCH_LEN = 50
def asynchronous(func): def asynchronous(func):
""" """
Decorates a function to become asynchronous. Decorates a function to become asynchronous.
@ -355,7 +357,8 @@ class Station(object):
""" """
Model that represents specific station on Google Play Music. 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._id = station_id
self._tracks = [] self._tracks = []
self._tracks_loaded = False self._tracks_loaded = False
@ -372,9 +375,12 @@ class Station(object):
Fetch tracks related to this station and Fetch tracks related to this station and
populate it with :class:`Track` instances. 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 = Track.from_data(data, Track.SOURCE_STATION, many=True)
self._tracks_loaded = True self._tracks_loaded = True
return self
load_tracks_async = asynchronous(load_tracks)
def get_tracks(self): def get_tracks(self):
""" """
@ -383,6 +389,20 @@ class Station(object):
assert self._tracks_loaded, 'Must call ".load_tracks()" before ".get_tracks()"' assert self._tracks_loaded, 'Must call ".load_tracks()" before ".get_tracks()"'
return self._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): class SearchResults(object):
""" """
@ -472,6 +492,7 @@ class _GP(object):
# self._last_call_index = 0 # self._last_call_index = 0
self.cached_tracks = None self.cached_tracks = None
self.cached_playlists = None self.cached_playlists = None
self.cached_stations = None
self.invalidate_caches() self.invalidate_caches()
@ -504,10 +525,11 @@ class _GP(object):
def invalidate_caches(self): def invalidate_caches(self):
""" """
Clear cached tracks & playlists. Clear cached tracks & playlists & stations.
""" """
self.cached_tracks = None self.cached_tracks = None
self.cached_playlists = None self.cached_playlists = None
self.cached_stations = None
self.caches_invalidated.fire() self.caches_invalidated.fire()
@synchronized @synchronized
@ -576,6 +598,25 @@ class _GP(object):
get_stream_url_async = asynchronous(get_stream_url) 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 @synchronized
def get_all_user_playlist_contents(self, **_): def get_all_user_playlist_contents(self, **_):
""" """

159
clay/pages/mystations.py Normal file
View 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