mirror of
https://github.com/vale981/clay
synced 2025-03-05 09:31:40 -05:00
Added debug page.
This commit is contained in:
parent
b48b147542
commit
801d691930
9 changed files with 267 additions and 31 deletions
|
@ -160,12 +160,8 @@ I'll try to figure it out ASAP.
|
|||
|
||||
Most issues can be reproduced only with specific data coming from Google Play Music servers.
|
||||
|
||||
You can set `CLAY_DEBUG` environment variable to `1` before launching Clay player.
|
||||
This will dump all Google API calls & responses to the `/tmp/clay-api-log.json` file.
|
||||
|
||||
You can attach this file when opening an issue here, but **keep in mind that
|
||||
this file MAY contain some personal data, access tokens and/or Google account credentials
|
||||
that may allow others to use your account**.
|
||||
Use "debug" tab within app to select the error and hit "Enter" to copy it into clipboard.
|
||||
This will help me to investigate this issue.
|
||||
|
||||
# Credits
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ import urwid
|
|||
|
||||
from clay.player import Player
|
||||
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.playerqueue import QueuePage
|
||||
|
@ -120,6 +121,7 @@ class AppWidget(urwid.Frame):
|
|||
|
||||
def __init__(self):
|
||||
self.pages = [
|
||||
DebugPage(self),
|
||||
MyLibraryPage(self),
|
||||
MyPlaylistsPage(self),
|
||||
QueuePage(self),
|
||||
|
|
31
clay/clipboard.py
Normal file
31
clay/clipboard.py
Normal file
|
@ -0,0 +1,31 @@
|
|||
"""
|
||||
Clipboard utils.
|
||||
"""
|
||||
from subprocess import Popen, PIPE
|
||||
|
||||
from clay.notifications import NotificationArea
|
||||
|
||||
|
||||
COMMANDS = [
|
||||
('xclip', '-selection', 'clipboard'),
|
||||
('xsel', '-bi'),
|
||||
]
|
||||
|
||||
|
||||
def copy(text):
|
||||
"""
|
||||
Copy text to clipboard.
|
||||
|
||||
Return True on success.
|
||||
"""
|
||||
for cmd in COMMANDS:
|
||||
proc = Popen(cmd, stdin=PIPE)
|
||||
proc.communicate(text.encode('utf-8'))
|
||||
if proc.returncode == 0:
|
||||
return True
|
||||
|
||||
NotificationArea.notify(
|
||||
'Failed to copy text to clipboard. '
|
||||
'Please install "xclip" or "xsel".'
|
||||
)
|
||||
return False
|
42
clay/gp.py
42
clay/gp.py
|
@ -6,15 +6,15 @@ Google Play Music integration via gmusicapi.
|
|||
# pylint: disable=too-many-instance-attributes
|
||||
# pylint: disable=too-many-return-statements
|
||||
# pylint: disable=protected-access
|
||||
# pylint: disable=no-self-use
|
||||
from __future__ import print_function
|
||||
import os
|
||||
import json
|
||||
from threading import Thread, Lock
|
||||
from uuid import UUID
|
||||
|
||||
from gmusicapi.clients import Mobileclient
|
||||
|
||||
from clay.eventhook import EventHook
|
||||
from clay.log import Logger
|
||||
|
||||
|
||||
def asynchronous(func):
|
||||
|
@ -199,7 +199,12 @@ class Track(object):
|
|||
album_name=track.album_name,
|
||||
album_url=track.album_url
|
||||
)
|
||||
except Exception: # pylint: disable=bare-except
|
||||
except Exception as error: # pylint: disable=bare-except
|
||||
Logger.get().error(
|
||||
'Failed to parse track data: %s, failing data: %s',
|
||||
repr(error),
|
||||
data
|
||||
)
|
||||
# TODO: Fix this.
|
||||
# print('Failed to create track from data.')
|
||||
# print('Failing payload was:')
|
||||
|
@ -418,12 +423,12 @@ class GP(object):
|
|||
|
||||
def __init__(self):
|
||||
assert self.__class__.instance is None, 'Can be created only once!'
|
||||
self.is_debug = os.getenv('CLAY_DEBUG')
|
||||
# self.is_debug = os.getenv('CLAY_DEBUG')
|
||||
self.mobile_client = Mobileclient()
|
||||
if self.is_debug:
|
||||
self.mobile_client._make_call = self._make_call_proxy(self.mobile_client._make_call)
|
||||
self.debug_file = open('/tmp/clay-api-log.json', 'w')
|
||||
self._last_call_index = 0
|
||||
# if self.is_debug:
|
||||
# self.mobile_client._make_call = self._make_call_proxy(self.mobile_client._make_call)
|
||||
# self.debug_file = open('/tmp/clay-api-log.json', 'w')
|
||||
# self._last_call_index = 0
|
||||
self.cached_tracks = None
|
||||
self.cached_playlists = None
|
||||
|
||||
|
@ -449,15 +454,20 @@ class GP(object):
|
|||
"""
|
||||
Wrapper function.
|
||||
"""
|
||||
Logger.get().debug('GP::{}(*{}, **{})'.format(
|
||||
protocol.__name__,
|
||||
args,
|
||||
kwargs
|
||||
))
|
||||
result = func(protocol, *args, **kwargs)
|
||||
self._last_call_index += 1
|
||||
call_index = self._last_call_index
|
||||
self.debug_file.write(json.dumps([
|
||||
call_index,
|
||||
protocol.__name__, args, kwargs,
|
||||
result
|
||||
]) + '\n')
|
||||
self.debug_file.flush()
|
||||
# self._last_call_index += 1
|
||||
# call_index = self._last_call_index
|
||||
# self.debug_file.write(json.dumps([
|
||||
# call_index,
|
||||
# protocol.__name__, args, kwargs,
|
||||
# result
|
||||
# ]) + '\n')
|
||||
# self.debug_file.flush()
|
||||
return result
|
||||
return _make_call
|
||||
|
||||
|
|
119
clay/log.py
Normal file
119
clay/log.py
Normal file
|
@ -0,0 +1,119 @@
|
|||
"""
|
||||
Logger implementation.
|
||||
"""
|
||||
# pylint: disable=too-few-public-methods
|
||||
from threading import Lock
|
||||
from datetime import datetime
|
||||
|
||||
from clay.eventhook import EventHook
|
||||
|
||||
|
||||
class LoggerRecord(object):
|
||||
"""
|
||||
Represents a logger record.
|
||||
"""
|
||||
def __init__(self, verbosity, message, args):
|
||||
self._timestamp = datetime.now()
|
||||
self._verbosity = verbosity
|
||||
self._message = message
|
||||
self._args = args
|
||||
|
||||
@property
|
||||
def formatted_timestamp(self):
|
||||
"""
|
||||
Return timestamp.
|
||||
"""
|
||||
return str(self._timestamp)
|
||||
|
||||
@property
|
||||
def verbosity(self):
|
||||
"""
|
||||
Return verbosity.
|
||||
"""
|
||||
return self._verbosity
|
||||
|
||||
@property
|
||||
def formatted_message(self):
|
||||
"""
|
||||
Return formatted message.
|
||||
"""
|
||||
return self._message % self._args
|
||||
|
||||
|
||||
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')
|
||||
|
||||
self._lock = Lock()
|
||||
|
||||
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)
|
||||
self.logs.append(logger_record)
|
||||
self.logfile.write('{} {:8} {}\n'.format(
|
||||
logger_record.formatted_timestamp,
|
||||
logger_record.verbosity,
|
||||
logger_record.formatted_message
|
||||
))
|
||||
self.logfile.flush()
|
||||
self.on_log_event.fire(logger_record)
|
||||
finally:
|
||||
self._lock.release()
|
||||
|
||||
def debug(self, message, *args):
|
||||
"""
|
||||
Add debug log item.
|
||||
"""
|
||||
self.log('DEBUG', message, *args)
|
||||
|
||||
def info(self, message, *args):
|
||||
"""
|
||||
Add info log item.
|
||||
"""
|
||||
self.log('INFO', message, *args)
|
||||
|
||||
def warn(self, message, *args):
|
||||
"""
|
||||
Add warning log item.
|
||||
"""
|
||||
self.log('WARNING', message, *args)
|
||||
|
||||
warning = warn
|
||||
|
||||
def error(self, message, *args):
|
||||
"""
|
||||
Add error log item.
|
||||
"""
|
||||
self.log('ERROR', message, *args)
|
||||
|
||||
def get_logs(self):
|
||||
"""
|
||||
Return all logs.
|
||||
"""
|
||||
return self.logs
|
|
@ -7,7 +7,7 @@ except ImportError:
|
|||
codename = None
|
||||
|
||||
APP_NAME = 'Clay Player'
|
||||
VERSION = '0.5.5'
|
||||
VERSION = '0.5.6'
|
||||
if codename is not None:
|
||||
VERSION_WITH_CODENAME = VERSION + '-' + codename(separator='-', id=VERSION)
|
||||
else:
|
||||
|
|
82
clay/pages/debug.py
Normal file
82
clay/pages/debug.py
Normal file
|
@ -0,0 +1,82 @@
|
|||
"""
|
||||
Debug page.
|
||||
"""
|
||||
import urwid
|
||||
|
||||
from clay.pages.page import AbstractPage
|
||||
from clay.log import Logger
|
||||
from clay.clipboard import copy
|
||||
|
||||
|
||||
class DebugItem(urwid.AttrMap):
|
||||
"""
|
||||
Represents a single debug log item.
|
||||
"""
|
||||
def selectable(self):
|
||||
return True
|
||||
|
||||
def __init__(self, log_record):
|
||||
self.log_record = log_record
|
||||
|
||||
self.columns = urwid.Columns([
|
||||
('pack', urwid.Text(self.log_record.verbosity.ljust(8))),
|
||||
urwid.Text(
|
||||
(
|
||||
self.log_record.formatted_timestamp +
|
||||
'\n' +
|
||||
self.log_record.formatted_message
|
||||
)
|
||||
)
|
||||
])
|
||||
|
||||
super(DebugItem, self).__init__(self.columns, 'panel', 'panel_focus')
|
||||
|
||||
def keypress(self, _, key):
|
||||
"""
|
||||
Handle heypress.
|
||||
"""
|
||||
if key == 'enter':
|
||||
copy(self.log_record.formatted_message)
|
||||
return None
|
||||
return key
|
||||
|
||||
|
||||
class DebugPage(urwid.Pile, AbstractPage):
|
||||
"""
|
||||
Represents debug page.
|
||||
"""
|
||||
def __init__(self, app):
|
||||
self.app = app
|
||||
self.walker = urwid.SimpleListWalker([])
|
||||
for log_record in Logger.get().get_logs():
|
||||
self._append_log(log_record)
|
||||
Logger.get().on_log_event += self._append_log
|
||||
self.listbox = urwid.ListBox(self.walker)
|
||||
super(DebugPage, self).__init__([
|
||||
('pack', urwid.Text('Hit "Enter" to copy selected message to clipboard.')),
|
||||
self.listbox
|
||||
])
|
||||
|
||||
def _append_log(self, log_record):
|
||||
self.walker.insert(0, urwid.Divider(u'\u2500'))
|
||||
self.walker.insert(0, DebugItem(log_record))
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""
|
||||
Return page name.
|
||||
"""
|
||||
return "Debug"
|
||||
|
||||
@property
|
||||
def key(self):
|
||||
"""
|
||||
Return page key (``int``), used for hotkeys.
|
||||
"""
|
||||
return 0
|
||||
|
||||
def activate(self):
|
||||
"""
|
||||
Notify page that it is activated.
|
||||
"""
|
||||
pass
|
|
@ -7,12 +7,14 @@ class AbstractPage(object):
|
|||
"""
|
||||
Represents app page.
|
||||
"""
|
||||
@property
|
||||
def name(self):
|
||||
"""
|
||||
Return page name.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@property
|
||||
def key(self):
|
||||
"""
|
||||
Return page key (``int``), used for hotkeys.
|
||||
|
|
|
@ -4,7 +4,6 @@ Components for song listing.
|
|||
# pylint: disable=too-many-arguments
|
||||
# pylint: disable=too-many-instance-attributes
|
||||
# pylint: disable=too-many-public-methods
|
||||
import os
|
||||
from string import digits
|
||||
try:
|
||||
# Python 3.x
|
||||
|
@ -16,6 +15,7 @@ import urwid
|
|||
from clay.notifications import NotificationArea
|
||||
from clay.player import Player
|
||||
from clay.gp import GP
|
||||
from clay.clipboard import copy
|
||||
|
||||
|
||||
class SongListItem(urwid.Pile):
|
||||
|
@ -321,13 +321,7 @@ class SongListBoxPopup(urwid.LineBox):
|
|||
"""
|
||||
Copy URL to clipboard.
|
||||
"""
|
||||
code = os.system('echo "{}" | xclipa -selection clipboard'.format(
|
||||
self.songitem.track.cached_url
|
||||
))
|
||||
if code != 0:
|
||||
NotificationArea.notify(
|
||||
'Failed to copy URL to clipboard, do you have "xclip" installed?'
|
||||
)
|
||||
copy(self.songitem.track.cached_url)
|
||||
self.close()
|
||||
|
||||
def close(self, *_):
|
||||
|
|
Loading…
Add table
Reference in a new issue