Merge pull request #21 from Fluctuz/master

Added page for stations
This commit is contained in:
Andrew Dunai 2018-04-06 11:22:38 +03:00 committed by GitHub
commit e144f4bf36
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 210 additions and 5 deletions

View file

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

View file

@ -37,6 +37,9 @@ hotkeys:
playlist_page:
start_playlist: enter
station_page:
start_station: enter
debug_page:
copy_message: enter

View file

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

View file

@ -18,7 +18,7 @@ class QueuePage(urwid.Columns, AbstractPage):
@property
def key(self):
return 3
return 4
def __init__(self, app):
self.app = app

View file

@ -58,7 +58,7 @@ class SearchPage(urwid.Pile, AbstractPage):
@property
def key(self):
return 4
return 5
def __init__(self, app):
self.app = app