mirror of
https://github.com/vale981/clay
synced 2025-03-05 09:31:40 -05:00
Refactored all classes to use unified singleton pattern.
This commit is contained in:
parent
504362c47e
commit
fa8e7564a3
15 changed files with 149 additions and 245 deletions
49
clay/app.py
49
clay/app.py
|
@ -7,7 +7,7 @@ Main app entrypoint.
|
|||
"""
|
||||
|
||||
import sys
|
||||
sys.path.insert(0, '.') # noqa
|
||||
sys.path.insert(0, '.')
|
||||
|
||||
|
||||
import argparse
|
||||
|
@ -15,7 +15,7 @@ import os
|
|||
import urwid
|
||||
|
||||
from clay import meta
|
||||
from clay.player import Player
|
||||
from clay.player import player
|
||||
from clay.playbar import PlayBar
|
||||
from clay.pages.debug import DebugPage
|
||||
from clay.pages.mylibrary import MyLibraryPage
|
||||
|
@ -24,8 +24,8 @@ from clay.pages.playerqueue import QueuePage
|
|||
from clay.pages.search import SearchPage
|
||||
from clay.pages.settings import SettingsPage
|
||||
from clay.settings import settings
|
||||
from clay.notifications import NotificationArea
|
||||
from clay.gp import GP
|
||||
from clay.notifications import notification_area
|
||||
from clay.gp import gp
|
||||
|
||||
|
||||
def create_palette(transparent=False):
|
||||
|
@ -146,35 +146,24 @@ class AppWidget(urwid.Frame):
|
|||
self.current_page = None
|
||||
self.loop = None
|
||||
|
||||
NotificationArea.set_app(self)
|
||||
notification_area.set_app(self)
|
||||
self._login_notification = None
|
||||
|
||||
self._cancel_actions = []
|
||||
|
||||
self.header = urwid.Pile([
|
||||
# urwid.Divider('\u2500'),
|
||||
urwid.AttrWrap(urwid.Columns([
|
||||
('pack', tab)
|
||||
for tab
|
||||
in self.tabs
|
||||
], dividechars=0), 'panel'),
|
||||
NotificationArea.get()
|
||||
# urwid.Divider('\u2500')
|
||||
])
|
||||
self.playbar = PlayBar(self)
|
||||
# self.panel = urwid.Pile([
|
||||
# urwid.Columns([
|
||||
# urwid.Divider(u'\u2500'),
|
||||
# ]),
|
||||
# self.playbar
|
||||
# ])
|
||||
# self.current_page = self.pages[0]
|
||||
super(AppWidget, self).__init__(
|
||||
header=self.header,
|
||||
footer=self.playbar,
|
||||
body=urwid.Filler(urwid.Text('Loading...', align='center'))
|
||||
)
|
||||
# self.current_page.activate()
|
||||
|
||||
self.set_page('MyLibraryPage')
|
||||
self.log_in()
|
||||
|
@ -193,22 +182,22 @@ class AppWidget(urwid.Frame):
|
|||
if self._login_notification:
|
||||
self._login_notification.close()
|
||||
if use_token and authtoken:
|
||||
self._login_notification = NotificationArea.notify('Using cached auth token...')
|
||||
GP.get().use_authtoken_async(
|
||||
self._login_notification = notification_area.notify('Using cached auth token...')
|
||||
gp.use_authtoken_async(
|
||||
authtoken,
|
||||
device_id,
|
||||
callback=self.on_check_authtoken
|
||||
)
|
||||
elif username and password and device_id:
|
||||
self._login_notification = NotificationArea.notify('Logging in...')
|
||||
GP.get().login_async(
|
||||
self._login_notification = notification_area.notify('Logging in...')
|
||||
gp.login_async(
|
||||
username,
|
||||
password,
|
||||
device_id,
|
||||
callback=self.on_login
|
||||
)
|
||||
else:
|
||||
self._login_notification = NotificationArea.notify(
|
||||
self._login_notification = notification_area.notify(
|
||||
'Please set your credentials on the settings page.'
|
||||
)
|
||||
|
||||
|
@ -247,7 +236,7 @@ class AppWidget(urwid.Frame):
|
|||
return
|
||||
|
||||
with settings.edit() as config:
|
||||
config['authtoken'] = GP.get().get_authtoken()
|
||||
config['authtoken'] = gp.get_authtoken()
|
||||
|
||||
self._login_notification.close()
|
||||
|
||||
|
@ -317,42 +306,41 @@ class AppWidget(urwid.Frame):
|
|||
"""
|
||||
Seek to the start of the song.
|
||||
"""
|
||||
Player.get().seek_absolute(0)
|
||||
player.seek_absolute(0)
|
||||
|
||||
@staticmethod
|
||||
def play_pause():
|
||||
"""
|
||||
Toggle play/pause.
|
||||
"""
|
||||
Player.get().play_pause()
|
||||
player.play_pause()
|
||||
|
||||
@staticmethod
|
||||
def next_song():
|
||||
"""
|
||||
Play next song.
|
||||
"""
|
||||
Player.get().next(True)
|
||||
player.next(True)
|
||||
|
||||
@staticmethod
|
||||
def seek_backward():
|
||||
"""
|
||||
Seek 5% backward.
|
||||
"""
|
||||
Player.get().seek(-0.05)
|
||||
player.seek(-0.05)
|
||||
|
||||
@staticmethod
|
||||
def seek_forward():
|
||||
"""
|
||||
Seek 5% forward.
|
||||
"""
|
||||
Player.get().seek(0.05)
|
||||
player.seek(0.05)
|
||||
|
||||
@staticmethod
|
||||
def toggle_shuffle():
|
||||
"""
|
||||
Toggle random playback.
|
||||
"""
|
||||
player = Player.get()
|
||||
player.set_random(not player.get_is_random())
|
||||
|
||||
@staticmethod
|
||||
|
@ -360,7 +348,6 @@ class AppWidget(urwid.Frame):
|
|||
"""
|
||||
Toggle repeat mode.
|
||||
"""
|
||||
player = Player.get()
|
||||
player.set_repeat_one(not player.get_is_repeat_one())
|
||||
|
||||
@staticmethod
|
||||
|
@ -377,7 +364,7 @@ class AppWidget(urwid.Frame):
|
|||
try:
|
||||
action = self._cancel_actions.pop()
|
||||
except IndexError:
|
||||
NotificationArea.close_newest()
|
||||
notification_area.close_newest()
|
||||
else:
|
||||
action()
|
||||
|
||||
|
@ -427,7 +414,7 @@ def main():
|
|||
exit(0)
|
||||
|
||||
if args.with_x_keybinds:
|
||||
Player.get().enable_xorg_bindings()
|
||||
player.enable_xorg_bindings()
|
||||
|
||||
# Run the actual program
|
||||
app_widget = AppWidget()
|
||||
|
|
|
@ -3,7 +3,7 @@ Clipboard utils.
|
|||
"""
|
||||
from subprocess import Popen, PIPE
|
||||
|
||||
from clay.notifications import NotificationArea
|
||||
from clay.notifications import notification_area
|
||||
|
||||
|
||||
COMMANDS = [
|
||||
|
@ -24,7 +24,7 @@ def copy(text):
|
|||
if proc.returncode == 0:
|
||||
return True
|
||||
|
||||
NotificationArea.notify(
|
||||
notification_area.notify(
|
||||
'Failed to copy text to clipboard. '
|
||||
'Please install "xclip" or "xsel".'
|
||||
)
|
||||
|
|
38
clay/gp.py
38
clay/gp.py
|
@ -14,7 +14,7 @@ from uuid import UUID
|
|||
from gmusicapi.clients import Mobileclient
|
||||
|
||||
from clay.eventhook import EventHook
|
||||
from clay.log import Logger
|
||||
from clay.log import logger
|
||||
|
||||
|
||||
def asynchronous(func):
|
||||
|
@ -202,7 +202,7 @@ class Track(object):
|
|||
)
|
||||
# We need to find a track in Library by trackId.
|
||||
UUID(data['trackId'])
|
||||
track = GP.get().get_track_by_id(data['trackId'])
|
||||
track = gp.get_track_by_id(data['trackId'])
|
||||
return Track(
|
||||
title=track.title,
|
||||
artist=track.artist,
|
||||
|
@ -214,7 +214,7 @@ class Track(object):
|
|||
original_data=data
|
||||
)
|
||||
except Exception as error: # pylint: disable=bare-except
|
||||
Logger.get().error(
|
||||
logger.error(
|
||||
'Failed to parse track data: %s, failing data: %s',
|
||||
repr(error),
|
||||
data
|
||||
|
@ -248,11 +248,11 @@ class Track(object):
|
|||
self.cached_url = url
|
||||
callback(url, error, self)
|
||||
|
||||
if GP.get().is_subscribed:
|
||||
if gp.is_subscribed:
|
||||
track_id = self.store_id
|
||||
else:
|
||||
track_id = self.library_id
|
||||
GP.get().get_stream_url_async(track_id, callback=on_get_url)
|
||||
gp.get_stream_url_async(track_id, callback=on_get_url)
|
||||
|
||||
@synchronized
|
||||
def create_station(self):
|
||||
|
@ -261,7 +261,7 @@ class Track(object):
|
|||
|
||||
Returns :class:`.Station` instance.
|
||||
"""
|
||||
station_id = GP.get().mobile_client.create_station(
|
||||
station_id = gp.mobile_client.create_station(
|
||||
name=u'Station - {}'.format(self.title),
|
||||
track_id=self.store_id
|
||||
)
|
||||
|
@ -275,7 +275,7 @@ class Track(object):
|
|||
"""
|
||||
Add a track to my library.
|
||||
"""
|
||||
return GP.get().add_to_my_library(self)
|
||||
return gp.add_to_my_library(self)
|
||||
|
||||
add_to_my_library_async = asynchronous(add_to_my_library)
|
||||
|
||||
|
@ -283,7 +283,7 @@ class Track(object):
|
|||
"""
|
||||
Remove a track from my library.
|
||||
"""
|
||||
return GP.get().remove_from_my_library(self)
|
||||
return gp.remove_from_my_library(self)
|
||||
|
||||
remove_from_my_library_async = asynchronous(remove_from_my_library)
|
||||
|
||||
|
@ -348,7 +348,7 @@ class Station(object):
|
|||
Fetch tracks related to this station and
|
||||
populate it with :class:`Track` instances.
|
||||
"""
|
||||
data = GP.get().mobile_client.get_station_tracks(self.id, 100)
|
||||
data = gp.mobile_client.get_station_tracks(self.id, 100)
|
||||
self._tracks = Track.from_data(data, Track.SOURCE_STATION, many=True)
|
||||
self._tracks_loaded = True
|
||||
|
||||
|
@ -427,7 +427,7 @@ class Playlist(object):
|
|||
)
|
||||
|
||||
|
||||
class GP(object):
|
||||
class _GP(object):
|
||||
"""
|
||||
Interface to :class:`gmusicapi.Mobileclient`. Implements
|
||||
asynchronous API calls, caching and some other perks.
|
||||
|
@ -435,12 +435,9 @@ class GP(object):
|
|||
Singleton.
|
||||
"""
|
||||
# TODO: Switch to urwid signals for more explicitness?
|
||||
instance = None
|
||||
|
||||
caches_invalidated = EventHook()
|
||||
|
||||
def __init__(self):
|
||||
assert self.__class__.instance is None, 'Can be created only once!'
|
||||
# self.is_debug = os.getenv('CLAY_DEBUG')
|
||||
self.mobile_client = Mobileclient()
|
||||
self.mobile_client._make_call = self._make_call_proxy(
|
||||
|
@ -456,16 +453,6 @@ class GP(object):
|
|||
|
||||
self.auth_state_changed = EventHook()
|
||||
|
||||
@classmethod
|
||||
def get(cls):
|
||||
"""
|
||||
Create new :class:`.GP` instance or return existing one.
|
||||
"""
|
||||
if cls.instance is None:
|
||||
cls.instance = GP()
|
||||
|
||||
return cls.instance
|
||||
|
||||
def _make_call_proxy(self, func):
|
||||
"""
|
||||
Return a function that wraps *fn* and logs args & return values.
|
||||
|
@ -474,7 +461,7 @@ class GP(object):
|
|||
"""
|
||||
Wrapper function.
|
||||
"""
|
||||
Logger.get().debug('GP::{}(*{}, **{})'.format(
|
||||
logger.debug('GP::{}(*{}, **{})'.format(
|
||||
protocol.__name__,
|
||||
args,
|
||||
kwargs
|
||||
|
@ -640,3 +627,6 @@ class GP(object):
|
|||
Return True if user is subscribed on Google Play Music, false otherwise.
|
||||
"""
|
||||
return self.mobile_client.is_subscribed
|
||||
|
||||
|
||||
gp = _GP() # pylint: disable=invalid-name
|
||||
|
|
|
@ -7,14 +7,17 @@ import threading
|
|||
|
||||
from clay.settings import settings
|
||||
from clay.eventhook import EventHook
|
||||
from clay.notifications import NotificationArea
|
||||
from clay.log import Logger
|
||||
from clay.notifications import notification_area
|
||||
from clay.log import logger
|
||||
|
||||
|
||||
IS_INIT = False
|
||||
def report_error(error_msg):
|
||||
|
||||
|
||||
def report_error(exc):
|
||||
"Print an error message to the debug screen"
|
||||
Logger.get().error("{0}: {1}".format(error.__class__.__name__, error_msg))
|
||||
logger.error("{0}: {1}".format(exc.__class__.__name__, exc))
|
||||
|
||||
|
||||
try:
|
||||
# pylint: disable=import-error
|
||||
|
@ -36,7 +39,7 @@ else:
|
|||
IS_INIT = True
|
||||
|
||||
|
||||
class HotkeyManager(object):
|
||||
class _HotkeyManager(object):
|
||||
"""
|
||||
Manages configs.
|
||||
Runs Gtk main loop in a thread.
|
||||
|
@ -47,10 +50,7 @@ class HotkeyManager(object):
|
|||
'prev': 'XF86AudioPrev'
|
||||
}
|
||||
|
||||
instance = None
|
||||
|
||||
def __init__(self):
|
||||
assert self.__class__.instance is None, 'Can be created only once!'
|
||||
self.hotkeys = {}
|
||||
self.config = None
|
||||
|
||||
|
@ -64,20 +64,12 @@ class HotkeyManager(object):
|
|||
|
||||
threading.Thread(target=Gtk.main).start()
|
||||
else:
|
||||
Logger.get().debug("Not loading the global shortcuts.")
|
||||
NotificationArea.notify(ERROR_MESSAGE +
|
||||
", this means the global shortcuts will not work.\n" +
|
||||
"You can check the log for more details.")
|
||||
|
||||
@classmethod
|
||||
def get(cls):
|
||||
"""
|
||||
Create new :class:`.HotkeyManager` instance or return existing one.
|
||||
"""
|
||||
if cls.instance is None:
|
||||
cls.instance = HotkeyManager()
|
||||
|
||||
return cls.instance
|
||||
logger.debug("Not loading the global shortcuts.")
|
||||
notification_area.notify(
|
||||
ERROR_MESSAGE +
|
||||
", this means the global shortcuts will not work.\n" +
|
||||
"You can check the log for more details."
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def load_keys():
|
||||
|
@ -85,7 +77,7 @@ class HotkeyManager(object):
|
|||
Load hotkey config from settings.
|
||||
"""
|
||||
hotkeys = settings.get('hotkeys', {})
|
||||
for operation, default_key in HotkeyManager.DEFAULT_HOTKEYS.items():
|
||||
for operation, default_key in _HotkeyManager.DEFAULT_HOTKEYS.items():
|
||||
if operation not in hotkeys or not hotkeys[operation]:
|
||||
hotkeys[operation] = default_key
|
||||
return hotkeys
|
||||
|
@ -106,3 +98,6 @@ class HotkeyManager(object):
|
|||
"""
|
||||
assert key
|
||||
getattr(self, operation).fire()
|
||||
|
||||
|
||||
hotkey_manager = _HotkeyManager() # pylint: disable=invalid-name
|
||||
|
|
22
clay/log.py
22
clay/log.py
|
@ -8,7 +8,7 @@ from datetime import datetime
|
|||
from clay.eventhook import EventHook
|
||||
|
||||
|
||||
class LoggerRecord(object):
|
||||
class _LoggerRecord(object):
|
||||
"""
|
||||
Represents a logger record.
|
||||
"""
|
||||
|
@ -40,17 +40,14 @@ class LoggerRecord(object):
|
|||
return self._message % self._args
|
||||
|
||||
|
||||
class Logger(object):
|
||||
class _Logger(object):
|
||||
"""
|
||||
Global logger.
|
||||
|
||||
Allows subscribing to log events.
|
||||
"""
|
||||
instance = None
|
||||
|
||||
def __init__(self):
|
||||
assert self.__class__.instance is None, 'Can be created only once!'
|
||||
|
||||
self.logs = []
|
||||
self.logfile = open('/tmp/clay.log', 'w')
|
||||
|
||||
|
@ -58,23 +55,13 @@ class Logger(object):
|
|||
|
||||
self.on_log_event = EventHook()
|
||||
|
||||
@classmethod
|
||||
def get(cls):
|
||||
"""
|
||||
Create new :class:`.Logger` instance or return existing one.
|
||||
"""
|
||||
if cls.instance is None:
|
||||
cls.instance = Logger()
|
||||
|
||||
return cls.instance
|
||||
|
||||
def log(self, level, message, *args):
|
||||
"""
|
||||
Add log item.
|
||||
"""
|
||||
self._lock.acquire()
|
||||
try:
|
||||
logger_record = LoggerRecord(level, message, args)
|
||||
logger_record = _LoggerRecord(level, message, args)
|
||||
self.logs.append(logger_record)
|
||||
self.logfile.write('{} {:8} {}\n'.format(
|
||||
logger_record.formatted_timestamp,
|
||||
|
@ -117,3 +104,6 @@ class Logger(object):
|
|||
Return all logs.
|
||||
"""
|
||||
return self.logs
|
||||
|
||||
|
||||
logger = _Logger() # pylint: disable=invalid-name
|
||||
|
|
|
@ -4,7 +4,7 @@ Notification widgets.
|
|||
import urwid
|
||||
|
||||
|
||||
class Notification(urwid.Columns):
|
||||
class _Notification(urwid.Columns):
|
||||
"""
|
||||
Single notification widget.
|
||||
Can be updated or closed.
|
||||
|
@ -16,7 +16,7 @@ class Notification(urwid.Columns):
|
|||
self._id = notification_id
|
||||
self.text = urwid.Text('')
|
||||
self._set_text(message)
|
||||
super(Notification, self).__init__([
|
||||
super(_Notification, self).__init__([
|
||||
urwid.AttrWrap(
|
||||
urwid.Columns([
|
||||
self.text,
|
||||
|
@ -41,7 +41,7 @@ class Notification(urwid.Columns):
|
|||
message = '\n'.join([
|
||||
message[0]
|
||||
] + [' {}'.format(line) for line in message[1:]])
|
||||
self.text.set_text(Notification.TEMPLATE.format(message))
|
||||
self.text.set_text(_Notification.TEMPLATE.format(message))
|
||||
|
||||
def update(self, message):
|
||||
"""
|
||||
|
@ -50,7 +50,7 @@ class Notification(urwid.Columns):
|
|||
self._set_text(message)
|
||||
if not self.is_alive:
|
||||
self.area.append_notification(self)
|
||||
self.area.__class__.app.redraw()
|
||||
self.area.app.redraw()
|
||||
|
||||
@property
|
||||
def is_alive(self):
|
||||
|
@ -70,73 +70,36 @@ class Notification(urwid.Columns):
|
|||
if notification is self:
|
||||
self.area.contents.remove((notification, props))
|
||||
|
||||
if self.area.__class__.app is not None:
|
||||
self.area.__class__.app.redraw()
|
||||
if self.area.app is not None:
|
||||
self.area.app.redraw()
|
||||
|
||||
|
||||
class NotificationArea(urwid.Pile):
|
||||
class _NotificationArea(urwid.Pile):
|
||||
"""
|
||||
Notification area widget.
|
||||
"""
|
||||
instance = None
|
||||
app = None
|
||||
|
||||
def __init__(self):
|
||||
assert self.__class__.instance is None, 'Can be created only once!'
|
||||
self.app = None
|
||||
self.last_id = 0
|
||||
self.notifications = {}
|
||||
super(NotificationArea, self).__init__([])
|
||||
super(_NotificationArea, self).__init__([])
|
||||
|
||||
@classmethod
|
||||
def get(cls):
|
||||
"""
|
||||
Create new :class:`.NotificationArea` instance or return existing one.
|
||||
"""
|
||||
if cls.instance is None:
|
||||
cls.instance = NotificationArea()
|
||||
|
||||
return cls.instance
|
||||
|
||||
@classmethod
|
||||
def set_app(cls, app):
|
||||
def set_app(self, app):
|
||||
"""
|
||||
Set app instance.
|
||||
|
||||
Required for proper screen redraws when
|
||||
new notifications are created asynchronously.
|
||||
"""
|
||||
cls.app = app
|
||||
self.app = app
|
||||
|
||||
@classmethod
|
||||
def notify(cls, message):
|
||||
"""
|
||||
Create new notification with message.
|
||||
This is a class method.
|
||||
"""
|
||||
return cls.get().do_notify(message)
|
||||
|
||||
@classmethod
|
||||
def close_all(cls):
|
||||
"""
|
||||
Close all notfiications.
|
||||
This is a class method.
|
||||
"""
|
||||
cls.get().do_close_all()
|
||||
|
||||
@classmethod
|
||||
def close_newest(cls):
|
||||
"""
|
||||
Close newest notification.
|
||||
This is a class method.
|
||||
"""
|
||||
cls.get().do_close_newest()
|
||||
|
||||
def do_notify(self, message):
|
||||
def notify(self, message):
|
||||
"""
|
||||
Create new notification with message.
|
||||
"""
|
||||
self.last_id += 1
|
||||
notification = Notification(self, self.last_id, message)
|
||||
notification = _Notification(self, self.last_id, message)
|
||||
self.append_notification(notification)
|
||||
return notification
|
||||
|
||||
|
@ -150,20 +113,23 @@ class NotificationArea(urwid.Pile):
|
|||
('weight', 1)
|
||||
)
|
||||
)
|
||||
if self.__class__.app is not None:
|
||||
self.__class__.app.redraw()
|
||||
if self.app is not None:
|
||||
self.app.redraw()
|
||||
|
||||
def do_close_all(self):
|
||||
def close_all(self):
|
||||
"""
|
||||
Close all notifications.
|
||||
"""
|
||||
while self.contents:
|
||||
self.contents[0][0].close()
|
||||
|
||||
def do_close_newest(self):
|
||||
def close_newest(self):
|
||||
"""
|
||||
Close newest notification
|
||||
"""
|
||||
if not self.contents:
|
||||
return
|
||||
self.contents[-1][0].close()
|
||||
|
||||
|
||||
notification_area = _NotificationArea() # pylint: disable=invalid-name
|
||||
|
|
|
@ -4,9 +4,9 @@ Debug page.
|
|||
import urwid
|
||||
|
||||
from clay.pages.page import AbstractPage
|
||||
from clay.log import Logger
|
||||
from clay.log import logger
|
||||
from clay.clipboard import copy
|
||||
from clay.gp import GP
|
||||
from clay.gp import gp
|
||||
|
||||
|
||||
class DebugItem(urwid.AttrMap):
|
||||
|
@ -49,9 +49,9 @@ class DebugPage(urwid.Pile, AbstractPage):
|
|||
def __init__(self, app):
|
||||
self.app = app
|
||||
self.walker = urwid.SimpleListWalker([])
|
||||
for log_record in Logger.get().get_logs():
|
||||
for log_record in logger.get_logs():
|
||||
self._append_log(log_record)
|
||||
Logger.get().on_log_event += self._append_log
|
||||
logger.on_log_event += self._append_log
|
||||
self.listbox = urwid.ListBox(self.walker)
|
||||
|
||||
self.debug_data = urwid.Text('')
|
||||
|
@ -64,7 +64,7 @@ class DebugPage(urwid.Pile, AbstractPage):
|
|||
self.listbox
|
||||
])
|
||||
|
||||
GP.get().auth_state_changed += self.update
|
||||
gp.auth_state_changed += self.update
|
||||
|
||||
self.update()
|
||||
|
||||
|
@ -72,12 +72,11 @@ class DebugPage(urwid.Pile, AbstractPage):
|
|||
"""
|
||||
Update this widget.
|
||||
"""
|
||||
gpclient = GP.get()
|
||||
self.debug_data.set_text(
|
||||
'- Is authenticated: {}\n'
|
||||
'- Is subscribed: {}'.format(
|
||||
gpclient.is_authenticated,
|
||||
gpclient.is_subscribed if gpclient.is_authenticated else None
|
||||
gp.is_authenticated,
|
||||
gp.is_subscribed if gp.is_authenticated else None
|
||||
)
|
||||
)
|
||||
|
||||
|
|
|
@ -3,9 +3,9 @@ Library page.
|
|||
"""
|
||||
import urwid
|
||||
|
||||
from clay.gp import GP
|
||||
from clay.gp import gp
|
||||
from clay.songlist import SongListBox
|
||||
from clay.notifications import NotificationArea
|
||||
from clay.notifications import notification_area
|
||||
from clay.pages.page import AbstractPage
|
||||
|
||||
|
||||
|
@ -28,8 +28,8 @@ class MyLibraryPage(urwid.Columns, AbstractPage):
|
|||
self.songlist = SongListBox(app)
|
||||
self.notification = None
|
||||
|
||||
GP.get().auth_state_changed += self.get_all_songs
|
||||
GP.get().caches_invalidated += self.get_all_songs
|
||||
gp.auth_state_changed += self.get_all_songs
|
||||
gp.caches_invalidated += self.get_all_songs
|
||||
|
||||
super(MyLibraryPage, self).__init__([
|
||||
self.songlist
|
||||
|
@ -41,7 +41,7 @@ class MyLibraryPage(urwid.Columns, AbstractPage):
|
|||
Populate song list.
|
||||
"""
|
||||
if error:
|
||||
NotificationArea.notify('Failed to load my library: {}'.format(str(error)))
|
||||
notification_area.notify('Failed to load my library: {}'.format(str(error)))
|
||||
return
|
||||
# self.notification.close()
|
||||
self.songlist.populate(tracks)
|
||||
|
@ -51,12 +51,11 @@ class MyLibraryPage(urwid.Columns, AbstractPage):
|
|||
"""
|
||||
Called when auth state changes or GP caches are invalidated.
|
||||
"""
|
||||
if GP.get().is_authenticated:
|
||||
if gp.is_authenticated:
|
||||
self.songlist.set_placeholder(u'\n \uf01e Loading song list...')
|
||||
|
||||
GP.get().get_all_tracks_async(callback=self.on_get_all_songs)
|
||||
gp.get_all_tracks_async(callback=self.on_get_all_songs)
|
||||
self.app.redraw()
|
||||
# self.notification = NotificationArea.notify('Loading library...')
|
||||
|
||||
def activate(self):
|
||||
pass
|
||||
|
|
|
@ -3,9 +3,9 @@ Components for "My playlists" page.
|
|||
"""
|
||||
import urwid
|
||||
|
||||
from clay.gp import GP
|
||||
from clay.gp import gp
|
||||
from clay.songlist import SongListBox
|
||||
from clay.notifications import NotificationArea
|
||||
from clay.notifications import notification_area
|
||||
from clay.pages.page import AbstractPage
|
||||
|
||||
|
||||
|
@ -59,7 +59,7 @@ class MyPlaylistListBox(urwid.ListBox):
|
|||
])
|
||||
self.notification = None
|
||||
|
||||
GP.get().auth_state_changed += self.auth_state_changed
|
||||
gp.auth_state_changed += self.auth_state_changed
|
||||
|
||||
super(MyPlaylistListBox, self).__init__(self.walker)
|
||||
|
||||
|
@ -73,9 +73,7 @@ class MyPlaylistListBox(urwid.ListBox):
|
|||
urwid.Text(u'\n \uf01e Loading playlists...', align='center')
|
||||
]
|
||||
|
||||
GP.get().get_all_user_playlist_contents_async(callback=self.on_get_playlists)
|
||||
|
||||
# self.notification = NotificationArea.notify('Loading playlists...')
|
||||
gp.get_all_user_playlist_contents_async(callback=self.on_get_playlists)
|
||||
|
||||
def on_get_playlists(self, playlists, error):
|
||||
"""
|
||||
|
@ -83,7 +81,7 @@ class MyPlaylistListBox(urwid.ListBox):
|
|||
Populates list of playlists.
|
||||
"""
|
||||
if error:
|
||||
NotificationArea.notify('Failed to get playlists: {}'.format(str(error)))
|
||||
notification_area.notify('Failed to get playlists: {}'.format(str(error)))
|
||||
|
||||
items = []
|
||||
for playlist in playlists:
|
||||
|
|
|
@ -4,7 +4,7 @@ Components for "Queue" page.
|
|||
import urwid
|
||||
|
||||
from clay.songlist import SongListBox
|
||||
from clay.player import Player
|
||||
from clay.player import player
|
||||
from clay.pages.page import AbstractPage
|
||||
|
||||
|
||||
|
@ -24,7 +24,6 @@ class QueuePage(urwid.Columns, AbstractPage):
|
|||
self.app = app
|
||||
self.songlist = SongListBox(app)
|
||||
|
||||
player = Player.get()
|
||||
self.songlist.populate(player.get_queue_tracks())
|
||||
player.queue_changed += self.queue_changed
|
||||
player.track_appended += self.track_appended
|
||||
|
@ -39,7 +38,7 @@ class QueuePage(urwid.Columns, AbstractPage):
|
|||
Called when player queue is changed.
|
||||
Updates this queue widget.
|
||||
"""
|
||||
self.songlist.populate(Player.get().get_queue_tracks())
|
||||
self.songlist.populate(player.get_queue_tracks())
|
||||
|
||||
def track_appended(self, track):
|
||||
"""
|
||||
|
|
|
@ -3,9 +3,9 @@ Components for search page.
|
|||
"""
|
||||
import urwid
|
||||
|
||||
from clay.gp import GP
|
||||
from clay.gp import gp
|
||||
from clay.songlist import SongListBox
|
||||
from clay.notifications import NotificationArea
|
||||
from clay.notifications import notification_area
|
||||
from clay.pages.page import AbstractPage
|
||||
|
||||
|
||||
|
@ -76,14 +76,14 @@ class SearchPage(urwid.Pile, AbstractPage):
|
|||
self.songlist.set_placeholder(u' \U0001F50D Searching for "{}"...'.format(
|
||||
query
|
||||
))
|
||||
GP.get().search_async(query, callback=self.search_finished)
|
||||
gp.search_async(query, callback=self.search_finished)
|
||||
|
||||
def search_finished(self, results, error):
|
||||
"""
|
||||
Populate song list with search results.
|
||||
"""
|
||||
if error:
|
||||
NotificationArea.notify('Failed to search: {}'.format(str(error)))
|
||||
notification_area.notify('Failed to search: {}'.format(str(error)))
|
||||
else:
|
||||
self.songlist.populate(results.get_tracks())
|
||||
self.app.redraw()
|
||||
|
|
|
@ -5,7 +5,7 @@ import urwid
|
|||
|
||||
from clay.pages.page import AbstractPage
|
||||
from clay.settings import settings
|
||||
from clay.player import Player
|
||||
from clay.player import player
|
||||
|
||||
|
||||
class Slider(urwid.Widget):
|
||||
|
@ -95,7 +95,7 @@ class Slider(urwid.Widget):
|
|||
"""
|
||||
Update player equalizer & toggle redraw.
|
||||
"""
|
||||
Player.get().set_equalizer_value(self.index, self.value)
|
||||
player.set_equalizer_value(self.index, self.value)
|
||||
self._invalidate()
|
||||
|
||||
|
||||
|
@ -107,7 +107,7 @@ class Equalizer(urwid.Columns):
|
|||
self.bands = [
|
||||
Slider(index, freq)
|
||||
for index, freq
|
||||
in enumerate(Player.get().get_equalizer_freqs())
|
||||
in enumerate(player.get_equalizer_freqs())
|
||||
]
|
||||
super(Equalizer, self).__init__(
|
||||
self.bands
|
||||
|
|
|
@ -4,7 +4,7 @@ PlayBar widget.
|
|||
# pylint: disable=too-many-instance-attributes
|
||||
import urwid
|
||||
|
||||
from clay.player import Player
|
||||
from clay.player import player
|
||||
from clay import meta
|
||||
|
||||
|
||||
|
@ -98,7 +98,6 @@ class PlayBar(urwid.Pile):
|
|||
])
|
||||
self.update()
|
||||
|
||||
player = Player.get()
|
||||
player.media_position_changed += self.update
|
||||
player.media_state_changed += self.update
|
||||
player.track_changed += self.update
|
||||
|
@ -115,7 +114,6 @@ class PlayBar(urwid.Pile):
|
|||
"""
|
||||
Return the style for current playback state.
|
||||
"""
|
||||
player = Player.get()
|
||||
if player.is_loading or player.is_playing:
|
||||
return 'title-playing'
|
||||
return 'title-idle'
|
||||
|
@ -124,7 +122,6 @@ class PlayBar(urwid.Pile):
|
|||
"""
|
||||
Return text for display in this bar.
|
||||
"""
|
||||
player = Player.get()
|
||||
track = player.get_current_track()
|
||||
if track is None:
|
||||
return u'{} {}'.format(
|
||||
|
@ -153,7 +150,6 @@ class PlayBar(urwid.Pile):
|
|||
Called when something unrelated to completion value changes,
|
||||
e.g. current track or playback flags.
|
||||
"""
|
||||
player = Player.get()
|
||||
self.text.set_text(self.get_text())
|
||||
self.progressbar.set_progress(player.get_play_progress())
|
||||
self.progressbar.set_done_style(
|
||||
|
|
|
@ -14,12 +14,12 @@ except ImportError: # Python 2.x
|
|||
|
||||
from clay import vlc, meta
|
||||
from clay.eventhook import EventHook
|
||||
from clay.notifications import NotificationArea
|
||||
from clay.notifications import notification_area
|
||||
from clay.settings import settings
|
||||
from clay.log import Logger
|
||||
from clay.log import logger
|
||||
|
||||
|
||||
class Queue(object):
|
||||
class _Queue(object):
|
||||
"""
|
||||
Model that represents player queue (local playlist),
|
||||
i.e. list of tracks to be played.
|
||||
|
@ -112,15 +112,13 @@ class Queue(object):
|
|||
return self.tracks
|
||||
|
||||
|
||||
class Player(object):
|
||||
class _Player(object):
|
||||
"""
|
||||
Interface to libVLC. Uses Queue as a playback plan.
|
||||
Emits various events if playback state, tracks or play flags change.
|
||||
|
||||
Singleton.
|
||||
"""
|
||||
instance = None
|
||||
|
||||
media_position_changed = EventHook()
|
||||
media_state_changed = EventHook()
|
||||
track_changed = EventHook()
|
||||
|
@ -130,9 +128,6 @@ class Player(object):
|
|||
track_removed = EventHook()
|
||||
|
||||
def __init__(self):
|
||||
assert self.__class__.instance is None, 'Can be created only once!'
|
||||
self.logger = Logger.get()
|
||||
|
||||
self.instance = vlc.Instance()
|
||||
self.instance.set_user_agent(
|
||||
meta.APP_NAME,
|
||||
|
@ -164,26 +159,15 @@ class Player(object):
|
|||
|
||||
self._create_station_notification = None
|
||||
self._is_loading = False
|
||||
self.queue = Queue()
|
||||
|
||||
@classmethod
|
||||
def get(cls):
|
||||
"""
|
||||
Create new :class:`.Player` instance or return existing one.
|
||||
"""
|
||||
if cls.instance is None:
|
||||
cls.instance = Player()
|
||||
|
||||
return cls.instance
|
||||
self.queue = _Queue()
|
||||
|
||||
def enable_xorg_bindings(self):
|
||||
"""Enable the global X bindings using keybinder"""
|
||||
if os.environ.get("DISPLAY") is None:
|
||||
self.logger.debug("X11 isn't running so we can't load the global keybinds")
|
||||
logger.debug("X11 isn't running so we can't load the global keybinds")
|
||||
return
|
||||
|
||||
from clay.hotkeys import HotkeyManager
|
||||
hotkey_manager = HotkeyManager.get()
|
||||
from clay.hotkeys import hotkey_manager
|
||||
hotkey_manager.play_pause += self.play_pause
|
||||
hotkey_manager.next += self.next
|
||||
hotkey_manager.prev += lambda: self.seek_absolute(0)
|
||||
|
@ -248,7 +232,7 @@ class Player(object):
|
|||
Load queue & start playback.
|
||||
Fires :attr:`.queue_changed` event.
|
||||
|
||||
See :meth:`.Queue.load`.
|
||||
See :meth:`._Queue.load`.
|
||||
"""
|
||||
self.queue.load(data, current_index)
|
||||
self.queue_changed.fire()
|
||||
|
@ -259,7 +243,7 @@ class Player(object):
|
|||
Append track to queue.
|
||||
Fires :attr:`.track_appended` event.
|
||||
|
||||
See :meth:`.Queue.append`
|
||||
See :meth:`._Queue.append`
|
||||
"""
|
||||
self.queue.append(track)
|
||||
self.track_appended.fire(track)
|
||||
|
@ -270,7 +254,7 @@ class Player(object):
|
|||
Remove track from queue.
|
||||
Fires :attr:`.track_removed` event.
|
||||
|
||||
See :meth:`.Queue.remove`
|
||||
See :meth:`._Queue.remove`
|
||||
"""
|
||||
self.queue.remove(track)
|
||||
self.track_removed.fire(track)
|
||||
|
@ -280,7 +264,7 @@ class Player(object):
|
|||
Request creation of new station from some track.
|
||||
Runs in background.
|
||||
"""
|
||||
self._create_station_notification = NotificationArea.notify('Creating station...')
|
||||
self._create_station_notification = notification_area.notify('Creating station...')
|
||||
track.create_station_async(callback=self._create_station_from_track_ready)
|
||||
|
||||
def _create_station_from_track_ready(self, station, error):
|
||||
|
@ -331,7 +315,7 @@ class Player(object):
|
|||
|
||||
def get_queue_tracks(self):
|
||||
"""
|
||||
Return :attr:`.Queue.get_tracks`
|
||||
Return :attr:`._Queue.get_tracks`
|
||||
"""
|
||||
return self.queue.get_tracks()
|
||||
|
||||
|
@ -350,19 +334,19 @@ class Player(object):
|
|||
if settings.get('download_tracks', False) or settings.get_is_file_cached(track.filename):
|
||||
path = settings.get_cached_file_path(track.filename)
|
||||
if path is None:
|
||||
self.logger.debug('Track %s not in cache, downloading...', track.store_id)
|
||||
logger.debug('Track %s not in cache, downloading...', track.store_id)
|
||||
track.get_url(callback=self._download_track)
|
||||
else:
|
||||
self.logger.debug('Track %s in cache, playing', track.store_id)
|
||||
logger.debug('Track %s in cache, playing', track.store_id)
|
||||
self._play_ready(path, None, track)
|
||||
else:
|
||||
self.logger.debug('Starting to stream %s', track.store_id)
|
||||
logger.debug('Starting to stream %s', track.store_id)
|
||||
track.get_url(callback=self._play_ready)
|
||||
|
||||
def _download_track(self, url, error, track):
|
||||
if error:
|
||||
NotificationArea.notify('Failed to request media URL: {}'.format(str(error)))
|
||||
self.logger.error(
|
||||
notification_area.notify('Failed to request media URL: {}'.format(str(error)))
|
||||
logger.error(
|
||||
'Failed to request media URL for track %s: %s',
|
||||
track.original_data,
|
||||
str(error)
|
||||
|
@ -379,8 +363,8 @@ class Player(object):
|
|||
"""
|
||||
self._is_loading = False
|
||||
if error:
|
||||
NotificationArea.notify('Failed to request media URL: {}'.format(str(error)))
|
||||
self.logger.error(
|
||||
notification_area.notify('Failed to request media URL: {}'.format(str(error)))
|
||||
logger.error(
|
||||
'Failed to request media URL for track %s: %s',
|
||||
track.original_data,
|
||||
str(error)
|
||||
|
@ -436,7 +420,7 @@ class Player(object):
|
|||
def next(self, force=False):
|
||||
"""
|
||||
Advance to next track in queue.
|
||||
See :meth:`.Queue.next`.
|
||||
See :meth:`._Queue.next`.
|
||||
"""
|
||||
self.queue.next(force)
|
||||
self._play()
|
||||
|
@ -444,7 +428,7 @@ class Player(object):
|
|||
def get_current_track(self):
|
||||
"""
|
||||
Return currently played track.
|
||||
See :meth:`.Queue.get_current_track`.
|
||||
See :meth:`._Queue.get_current_track`.
|
||||
"""
|
||||
return self.queue.get_current_track()
|
||||
|
||||
|
@ -509,3 +493,6 @@ class Player(object):
|
|||
index
|
||||
) == 0
|
||||
self.media_player.set_equalizer(self.equalizer)
|
||||
|
||||
|
||||
player = _Player() # pylint: disable=invalid-name
|
||||
|
|
|
@ -12,9 +12,9 @@ except ImportError:
|
|||
# Python 2.3
|
||||
from string import letters as ascii_letters
|
||||
import urwid
|
||||
from clay.notifications import NotificationArea
|
||||
from clay.player import Player
|
||||
from clay.gp import GP
|
||||
from clay.notifications import notification_area
|
||||
from clay.player import player
|
||||
from clay.gp import gp
|
||||
from clay.clipboard import copy
|
||||
from clay.settings import settings
|
||||
|
||||
|
@ -113,7 +113,7 @@ class SongListItem(urwid.Pile):
|
|||
)
|
||||
)
|
||||
if settings.get_is_file_cached(self.track.filename):
|
||||
self.line1_right.set_text(u'\u25bc Cached')
|
||||
self.line1_right.set_text(u' \u25bc Cached')
|
||||
else:
|
||||
self.line1_right.set_text(u'')
|
||||
self.line2.set_text(
|
||||
|
@ -214,7 +214,7 @@ class SongListBoxPopup(urwid.LineBox):
|
|||
'panel_divider',
|
||||
'panel_divider_focus'
|
||||
))
|
||||
if not GP.get().get_track_by_id(songitem.track.id):
|
||||
if not gp.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',
|
||||
|
@ -241,7 +241,7 @@ class SongListBoxPopup(urwid.LineBox):
|
|||
'panel_divider',
|
||||
'panel_divider_focus'
|
||||
))
|
||||
if self.songitem.track in Player.get().get_queue_tracks():
|
||||
if self.songitem.track in player.get_queue_tracks():
|
||||
options.append(urwid.AttrWrap(
|
||||
urwid.Button('Remove from queue', on_press=self.remove_from_queue),
|
||||
'panel',
|
||||
|
@ -282,11 +282,11 @@ class SongListBoxPopup(urwid.LineBox):
|
|||
Show notification with song addition result.
|
||||
"""
|
||||
if error or not result:
|
||||
NotificationArea.notify('Error while adding track to my library: {}'.format(
|
||||
notification_area.notify('Error while adding track to my library: {}'.format(
|
||||
str(error) if error else 'reason is unknown :('
|
||||
))
|
||||
else:
|
||||
NotificationArea.notify('Track added to library!')
|
||||
notification_area.notify('Track added to library!')
|
||||
self.songitem.track.add_to_my_library_async(callback=on_add_to_my_library)
|
||||
self.close()
|
||||
|
||||
|
@ -299,11 +299,11 @@ class SongListBoxPopup(urwid.LineBox):
|
|||
Show notification with song removal result.
|
||||
"""
|
||||
if error or not result:
|
||||
NotificationArea.notify('Error while removing track from my library: {}'.format(
|
||||
notification_area.notify('Error while removing track from my library: {}'.format(
|
||||
str(error) if error else 'reason is unknown :('
|
||||
))
|
||||
else:
|
||||
NotificationArea.notify('Track removed from library!')
|
||||
notification_area.notify('Track removed from library!')
|
||||
self.songitem.track.remove_from_my_library_async(callback=on_remove_from_my_library)
|
||||
self.close()
|
||||
|
||||
|
@ -311,21 +311,21 @@ class SongListBoxPopup(urwid.LineBox):
|
|||
"""
|
||||
Appends related track to queue.
|
||||
"""
|
||||
Player.get().append_to_queue(self.songitem.track)
|
||||
player.append_to_queue(self.songitem.track)
|
||||
self.close()
|
||||
|
||||
def remove_from_queue(self, _):
|
||||
"""
|
||||
Removes related track from queue.
|
||||
"""
|
||||
Player.get().remove_from_queue(self.songitem.track)
|
||||
player.remove_from_queue(self.songitem.track)
|
||||
self.close()
|
||||
|
||||
def create_station(self, _):
|
||||
"""
|
||||
Create a station from this track.
|
||||
"""
|
||||
Player.get().create_station_from_track(self.songitem.track)
|
||||
player.create_station_from_track(self.songitem.track)
|
||||
self.close()
|
||||
|
||||
def copy_url(self, _):
|
||||
|
@ -355,7 +355,6 @@ class SongListBox(urwid.Frame):
|
|||
self.tracks = []
|
||||
self.walker = urwid.SimpleFocusListWalker([])
|
||||
|
||||
player = Player.get()
|
||||
player.track_changed += self.track_changed
|
||||
player.media_state_changed += self.media_state_changed
|
||||
|
||||
|
@ -443,7 +442,7 @@ class SongListBox(urwid.Frame):
|
|||
"""
|
||||
Convert list of track data items into list of :class:`.SongListItem` instances.
|
||||
"""
|
||||
current_track = Player.get().get_current_track()
|
||||
current_track = player.get_current_track()
|
||||
items = []
|
||||
current_index = None
|
||||
for index, track in enumerate(tracks):
|
||||
|
@ -476,7 +475,6 @@ class SongListBox(urwid.Frame):
|
|||
Toggles track playback state or loads entire playlist
|
||||
that contains current track into player queue.
|
||||
"""
|
||||
player = Player.get()
|
||||
if songitem.is_currently_played:
|
||||
player.play_pause()
|
||||
else:
|
||||
|
@ -488,7 +486,7 @@ class SongListBox(urwid.Frame):
|
|||
Called when specific item emits *append-requested* item.
|
||||
Appends track to player queue.
|
||||
"""
|
||||
Player.get().append_to_queue(songitem.track)
|
||||
player.append_to_queue(songitem.track)
|
||||
|
||||
@staticmethod
|
||||
def item_unappend_requested(songitem):
|
||||
|
@ -496,7 +494,7 @@ class SongListBox(urwid.Frame):
|
|||
Called when specific item emits *remove-requested* item.
|
||||
Removes track from player queue.
|
||||
"""
|
||||
Player.get().remove_from_queue(songitem.track)
|
||||
player.remove_from_queue(songitem.track)
|
||||
|
||||
@staticmethod
|
||||
def item_station_requested(songitem):
|
||||
|
@ -504,7 +502,7 @@ class SongListBox(urwid.Frame):
|
|||
Called when specific item emits *station-requested* item.
|
||||
Requests new station creation.
|
||||
"""
|
||||
Player.get().create_station_from_track(songitem.track)
|
||||
player.create_station_from_track(songitem.track)
|
||||
|
||||
def context_menu_requested(self, songitem):
|
||||
"""
|
||||
|
@ -551,7 +549,7 @@ class SongListBox(urwid.Frame):
|
|||
Called when player media state changes.
|
||||
Updates corresponding song item state (if found in this song list).
|
||||
"""
|
||||
current_track = Player.get().get_current_track()
|
||||
current_track = player.get_current_track()
|
||||
if current_track is None:
|
||||
return
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue