Refactored all classes to use unified singleton pattern.

This commit is contained in:
Andrew Dunai 2018-02-14 20:39:59 +02:00
parent 504362c47e
commit fa8e7564a3
15 changed files with 149 additions and 245 deletions

View file

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

View file

@ -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".'
)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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