Refactoring.

This commit is contained in:
Andrew Dunai 2017-12-29 00:24:58 +02:00
parent 7aa91dc104
commit 4607dc6ca1
8 changed files with 142 additions and 105 deletions

21
app.py
View file

@ -1,4 +1,5 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
@ -43,16 +44,16 @@ PALETTE = [
class PlayProgress(urwid.ProgressBar):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
super(PlayProgress, self).__init__(*args, **kwargs)
self.track = None
def get_text(self):
if self.track is None:
return 'Idle'
return u'Idle'
progress = player.get_play_progress_seconds()
total = player.get_length_seconds()
return ' {} {} - {} [{:02d}:{:02d} / {:02d}:{:02d}] {} {}'.format(
'>' if player.is_playing else 'X',
return u' {} {} - {} [{:02d}:{:02d} / {:02d}:{:02d}] {} {}'.format(
u'|>' if player.is_playing else u'||',
# '\u25B6' if player.is_playing else '\u25A0',
self.track.artist,
self.track.title,
@ -60,11 +61,11 @@ class PlayProgress(urwid.ProgressBar):
progress % 60,
total // 60,
total % 60,
'S' if player.get_is_random() else ' ',
'R' if player.get_is_repeat_one() else ' '
u'S' if player.get_is_random() else ' ',
u'R' if player.get_is_repeat_one() else ' '
# '⋍' if player.get_is_random() else ' ',
# '⟲' if player.get_is_repeat_one() else ' '
).encode('utf-8')
)
def set_track(self, track):
self.track = track
@ -78,7 +79,7 @@ class AppWidget(urwid.Frame):
def __init__(self, page_class):
self.page_class = page_class
# self.attrwrap = urwid.AttrWrap(urwid.Text(), 'panel')
super().__init__(
super(AppWidget.Tab, self).__init__(
self.get_title()
)
@ -133,7 +134,7 @@ class AppWidget(urwid.Frame):
self.seekbar
])
self.current_page = StartUp(self)
super().__init__(
super(AppWidget, self).__init__(
header=self.header,
footer=self.panel,
body=self.current_page
@ -207,7 +208,7 @@ class AppWidget(urwid.Frame):
elif key == 'ctrl q':
sys.exit(0)
else:
super().keypress(size, key)
super(AppWidget, self).keypress(size, key)
def main():

90
gp.py
View file

@ -1,6 +1,8 @@
from gmusicapi.clients import Mobileclient
from threading import Thread, Lock
gp = None
def async(fn):
def wrapper(*args, **kwargs):
@ -12,6 +14,7 @@ def async(fn):
result = fn(*args, **kwargs)
except Exception as e:
callback(None, e, **extra)
raise
else:
callback(result, None, **extra)
@ -32,13 +35,68 @@ def synchronized(fn):
return wrapper
class Track(object):
def __init__(self, id, title, artist, duration):
self.id = id
self.title = title
self.artist = artist
self.duration = duration
@classmethod
def from_data(cls, data, many=False):
if many:
return [cls.from_data(one) for one in data]
return Track(
id=data['id'],
title=data['title'],
artist=data['artist'],
duration=int(data['durationMillis'])
)
def get_url(self, callback):
gp.get_stream_url(self.id, callback=callback, extra=dict(track=self))
class Playlist(object):
def __init__(self, id, name, tracks):
self.id = id
self.name = name
self.tracks = tracks
@classmethod
def from_data(cls, data, many=False):
if many:
return [cls.from_data(one) for one in data]
return Playlist(
id=data['id'],
name=data['name'],
tracks=cls.playlist_items_to_tracks(data['tracks'])
)
@classmethod
def playlist_items_to_tracks(self, playlist_tracks):
results = []
cached_tracks_map = gp.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 = Track.from_data(track)
else:
track = cached_tracks_map[playlist_track['trackId']]
results.append(track)
return results
class GP(object):
def __init__(self):
self.mc = Mobileclient()
self.invalidate_caches()
def invalidate_caches(self):
self.cached_songs = None
self.cached_tracks = None
self.cached_playlists = None
@async
@ -51,11 +109,11 @@ class GP(object):
@async
@synchronized
def get_all_songs(self):
if self.cached_songs:
return self.cached_songs
self.cached_songs = self.mc.get_all_songs()
return self.cached_songs
def get_all_tracks(self):
if self.cached_tracks:
return self.cached_tracks
self.cached_tracks = Track.from_data(self.mc.get_all_songs(), True)
return self.cached_tracks
@async
def get_stream_url(self, id):
@ -66,18 +124,18 @@ class GP(object):
def get_all_user_playlist_contents(self):
if self.cached_playlists:
return self.cached_playlists
if not self.cached_songs:
self.cached_songs = self.mc.get_all_songs()
cached_songs_map = {track['id']: track for track in self.cached_songs}
self.cached_playlists = self.mc.get_all_user_playlist_contents()
for playlist in self.cached_playlists:
for song in playlist['tracks']:
if 'track' not in song:
song['track'] = cached_songs_map[song['trackId']]
else:
song['track']['id'] = song['trackId']
if not self.cached_tracks:
self.cached_tracks = self.mc.get_all_tracks()
self.cached_playlists = Playlist.from_data(
self.mc.get_all_user_playlist_contents(),
True
)
return self.cached_playlists
def get_cached_tracks_map(self):
return {track.id: track for track in self.cached_tracks}
gp = GP()

View file

@ -1,7 +1,6 @@
import urwid
from gp import gp
from songlist import SongListBox
from player import Track
class MyLibrary(urwid.Columns):
@ -12,16 +11,16 @@ class MyLibrary(urwid.Columns):
self.app = app
self.songlist = SongListBox(app)
gp.get_all_songs(callback=self.on_get_all_songs)
gp.get_all_tracks(callback=self.on_get_all_songs)
return super().__init__([
return super(MyLibrary, self).__init__([
self.songlist
])
def on_get_all_songs(self, results, error):
def on_get_all_songs(self, tracks, error):
if error:
self.app.set_page('Error', error)
return
self.songlist.populate(Track.from_data(results, many=True))
self.songlist.populate(tracks)
self.app.redraw()

View file

@ -1,17 +1,17 @@
# -*- coding: utf-8 -*-
import urwid
from gp import gp
from player import Track
from songlist import SongListBox
class PlaylistListItem(urwid.Columns):
class MyPlaylistListItem(urwid.Columns):
signals = ['activate']
def __init__(self, data):
self.data = data
def __init__(self, playlist):
self.playlist = playlist
self.text = urwid.SelectableIcon('{} ({})'.format(
self.data['name'],
len(self.data['tracks'])
self.playlist.name,
len(self.playlist.tracks)
), cursor_position=3)
self.text.set_layout('left', 'clip', None)
self.content = urwid.AttrWrap(
@ -28,14 +28,10 @@ class PlaylistListItem(urwid.Columns):
return super().keypress(size, key)
def get_tracks(self):
return Track.from_data([
item['track']
for item
in self.data['tracks']
], many=True)
return self.playlist.tracks
class PlaylistListBox(urwid.ListBox):
class MyPlaylistListBox(urwid.ListBox):
signals = ['activate']
def __init__(self, app):
@ -59,18 +55,18 @@ class PlaylistListBox(urwid.ListBox):
items = []
for playlist in playlists:
playlistlistitem = PlaylistListItem(playlist)
myplaylistlistitem = MyPlaylistListItem(playlist)
urwid.connect_signal(
playlistlistitem, 'activate', self.item_activated
myplaylistlistitem, 'activate', self.item_activated
)
items.append(playlistlistitem)
items.append(myplaylistlistitem)
self.walker[:] = items
self.app.redraw()
def item_activated(self, playlistlistitem):
urwid.emit_signal(self, 'activate', playlistlistitem)
def item_activated(self, myplaylistlistitem):
urwid.emit_signal(self, 'activate', myplaylistlistitem)
class MyPlaylists(urwid.Columns):
@ -80,21 +76,21 @@ class MyPlaylists(urwid.Columns):
def __init__(self, app):
self.app = app
self.playlistlist = PlaylistListBox(app)
self.myplaylistlist = MyPlaylistListBox(app)
self.songlist = SongListBox(app)
self.songlist.populate([])
urwid.connect_signal(
self.playlistlist, 'activate', self.playlistlistitem_activated
self.myplaylistlist, 'activate', self.myplaylistlistitem_activated
)
return super().__init__([
self.playlistlist,
self.myplaylistlist,
self.songlist
])
def playlistlistitem_activated(self, playlistlistitem):
def myplaylistlistitem_activated(self, myplaylistlistitem):
self.songlist.populate(
playlistlistitem.get_tracks()
myplaylistlistitem.get_tracks()
)

View file

@ -22,7 +22,7 @@ class StartUp(urwid.Filler):
'Please set your credentials on the settings page.'
)
super().__init__(
super(StartUp, self).__init__(
urwid.Pile([
urwid.Padding(
urwid.AttrWrap(urwid.BigText(
@ -59,7 +59,7 @@ class StartUp(urwid.Filler):
class Error(urwid.Filler):
def __init__(self, app, error):
super().__init__(
super(Error, self).__init__(
urwid.Text('Error:\n\n{}'.format(str(error))),
valign='top'
)

View file

@ -5,34 +5,9 @@ import json
import vlc
from eventhook import EventHook
import gp
from gp import gp
class Track(object):
def __init__(self, id, title, artist, duration):
self.id = id
self.title = title
self.artist = artist
self.duration = duration
@classmethod
def from_data(cls, data, many=False):
if many:
return [cls.from_data(one) for one in data]
return Track(
id=data['id'],
title=data['title'],
artist=data['artist'],
duration=int(data['durationMillis'])
)
def get_url(self, callback):
gp.get_stream_url(self.id, callback=callback, extra=dict(track=self))
class Playlist(object):
class Queue(object):
def __init__(self):
self.random = False
self.repeat_one = False
@ -109,10 +84,10 @@ class Player(object):
self._media_position_changed
)
self.playlist = Playlist()
self.queue = Queue()
def broadcast_state(self):
track = self.playlist.get_current_track()
track = self.queue.get_current_track()
if track is None:
data = dict(
playing=False,
@ -145,29 +120,29 @@ class Player(object):
self.get_play_progress()
)
def load_playlist(self, data, current_index):
self.playlist.load(data, current_index)
def load_queue(self, data, current_index):
self.queue.load(data, current_index)
self._play()
def get_is_random(self):
return self.playlist.random
return self.queue.random
def get_is_repeat_one(self):
return self.playlist.repeat_one
return self.queue.repeat_one
def set_random(self, value):
self.playlist.random = value
self.queue.random = value
self.playback_flags_changed.fire()
def set_repeat_one(self, value):
self.playlist.repeat_one = value
self.queue.repeat_one = value
self.playback_flags_changed.fire()
def get_queue(self):
return self.playlist.get_tracks()
return self.queue.get_tracks()
def _play(self):
track = self.playlist.get_current_track()
track = self.queue.get_current_track()
if track is None:
return
track.get_url(callback=self._play_ready)
@ -200,14 +175,14 @@ class Player(object):
return int(self.mp.get_length() // 1000)
def next(self, force=False):
self.playlist.next(force)
self.queue.next(force)
self._play()
def get_current_track(self):
return self.playlist.get_current_track()
return self.queue.get_current_track()
# def prev(self):
# self.playlist.prev()
# self.queue.prev()
# self._play()
def seek(self, delta):

View file

@ -1,4 +1,5 @@
import os
import errno
import yaml
import appdirs
@ -51,7 +52,13 @@ class Settings(urwid.Columns):
@classmethod
def get_config_filename(cls):
filedir = appdirs.user_config_dir('clay', 'Clay')
os.makedirs(filedir, exist_ok=True)
try:
os.makedirs(filedir)
except OSError as e:
if e.errno != errno.EEXIST:
raise
path = os.path.join(filedir, 'config.json')
if not os.path.exists(path):
with open(path, 'w') as f:

View file

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
import urwid
from player import player
@ -12,9 +13,9 @@ class SongListItem(urwid.Pile):
STATE_ICONS = {
0: ' ',
1: '\u2505',
2: '\u25B6',
3: '\u25A0'
1: u'\u2505',
2: u'\u25B6',
3: u'\u25A0'
}
def __init__(self, track, index):
@ -36,7 +37,7 @@ class SongListItem(urwid.Pile):
'line1_focus'
)
super().__init__([
super(SongListItem, self).__init__([
self.content
])
self.update_text()
@ -50,8 +51,8 @@ class SongListItem(urwid.Pile):
def update_text(self):
self.line1.set_text(
'{index:3d} {icon} {title} [{minutes:02d}:{seconds:02d}]'.format(
index=self.index,
u'{index:3d} {icon} {title} [{minutes:02d}:{seconds:02d}]'.format(
index=self.index + 1,
icon=self.get_state_icon(self.state),
title=self.track.title,
minutes=self.track.duration // (1000 * 60),
@ -59,7 +60,7 @@ class SongListItem(urwid.Pile):
)
)
self.line2.set_text(
' {}'.format(self.track.artist)
u' {}'.format(self.track.artist)
)
if self.state == SongListItem.STATE_IDLE:
self.content.set_attr('line1')
@ -72,7 +73,7 @@ class SongListItem(urwid.Pile):
if key == 'enter':
urwid.emit_signal(self, 'activate', self)
return
return super().keypress(size, key)
return super(SongListItem, self).keypress(size, key)
# def render(self, size, focus=False):
# # if focus:
@ -99,7 +100,7 @@ class SongListBox(urwid.ListBox):
player.track_changed += self.track_changed
player.media_state_changed += self.media_state_changed
return super().__init__(self.walker)
return super(SongListBox, self).__init__(self.walker)
def tracks_to_songlist(self, tracks):
current_track = player.get_current_track()
@ -113,7 +114,7 @@ class SongListBox(urwid.ListBox):
return items
def item_activated(self, songitem):
player.load_playlist(self.tracks, songitem.index)
player.load_queue(self.tracks, songitem.index)
def track_changed(self, track):
for songitem in self.walker: