Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cmds refactor #382

Merged
merged 2 commits into from
Jul 2, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 3 additions & 10 deletions fuocore/cmds/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
import logging

from fuocore.excs import FuoException
from fuocore.router import ShowCmdException

from .base import cmd_handler_mapping
from .excs import CmdException

from .help import HelpHandler # noqa
from .status import StatusHandler # noqa
Expand All @@ -16,10 +14,6 @@
logger = logging.getLogger(__name__)


class CmdException(FuoException):
pass


class Cmd:
def __init__(self, action, *args, options=None):
self.action = action
Expand Down Expand Up @@ -68,9 +62,8 @@ def exec_cmd(cmd, *args, app):
playlist=playlist,
live_lyric=live_lyric)
rv = handler.handle(cmd)
except ShowCmdException:
logger.exception('handle cmd({}) error'.format(cmd))
return False, 'show command not correct'
except CmdException as e:
return False, str(e)
except Exception:
logger.exception('handle cmd({}) error'.format(cmd))
return False, 'internal server error'
Expand Down
5 changes: 5 additions & 0 deletions fuocore/cmds/excs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from fuocore.excs import FuoException


class CmdException(FuoException):
pass
4 changes: 2 additions & 2 deletions fuocore/cmds/playlist.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from fuocore.models import ModelType
from fuocore.models.uri import resolve, reverse
from fuocore.utils import reader_to_list, to_reader
from fuocore.utils import to_readall_reader
from .base import AbstractHandler


Expand Down Expand Up @@ -31,7 +31,7 @@ def add(self, furi_list):
if obj_type == ModelType.song:
playlist.add(obj)
elif obj_type == ModelType.playlist:
songs = reader_to_list(to_reader(obj, "songs"))
songs = to_readall_reader(obj, "songs").readall()
for song in songs:
playlist.add(song)

Expand Down
193 changes: 60 additions & 133 deletions fuocore/cmds/show.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,16 @@
show fuo://local/songs/1 # 显示一首歌的详细信息
"""
import logging
from functools import wraps
from urllib.parse import urlparse

from fuocore.utils import reader_to_list, to_reader
from fuocore.router import Router
from fuocore.utils import to_readall_reader
from fuocore.router import Router, NotFound
from fuocore.models.uri import NS_TYPE_MAP, TYPE_NS_MAP
from fuocore.models import ModelType

from .base import AbstractHandler
from .excs import CmdException

logger = logging.getLogger(__name__)

Expand All @@ -34,156 +38,79 @@ def handle(self, cmd):
r = urlparse(furi)
path = '/{}{}'.format(r.netloc, r.path)
logger.debug('请求 path: {}'.format(path))
rv = router.dispatch(path, {'library': self.library})
try:
rv = router.dispatch(path, {'library': self.library})
except NotFound:
raise CmdException(f'path {path} not found')
return rv


def noexception_handler_default(obj_name, obj_identifier, obj):
if obj is None:
return "{} identified by {} is not found"\
.format(obj_name, obj_identifier)
return obj


def noexception_handler_lyric(obj_name, obj_identifier, obj):
song, sid = obj, obj_identifier
if song is None:
return "{} identified by {} is not found"\
.format(obj_name, sid)

if song.lyric is None:
return "no lyric for this song, enjoy it ~"

return song.lyric.content


def noexception_handler_user(obj_name, obj_identifier, obj):
user, uid = obj, obj_identifier
if user is not None:
return user
elif uid == 'me':
return "User is not logged in in current session(plugin)"
else:
return "No {} with uid {} ".format(obj_name, uid)


def noexception_handler_readerlist(obj_name, obj_identifier, obj):
if obj is None:
return "No {} found by {} "\
.format(obj_name, obj_identifier)

# quick and dirty implement
if obj_name == 'playlists':
return reader_to_list(to_reader(obj, "songs"))
else:
return reader_to_list(to_reader(obj, "albums"))


def get_from_provider(
req,
provider,
obj_identifier,
obj_name,
handler=noexception_handler_default):
provider_path_name = provider
provider = req.ctx['library'].get(provider)

if provider is None:
return "No such provider : {}".format(provider_path_name)

try:
if obj_name == 'songs':
obj = provider.Song.get(obj_identifier)
elif obj_name == 'artists':
obj = provider.Artist.get(obj_identifier)
elif obj_name == 'ablums':
obj = provider.Album.get(obj_identifier)
elif obj_name == 'playlists':
obj = provider.Playlist.get(obj_identifier)
elif obj_name == 'users':
if obj_identifier == 'me':
obj = provider._user
else:
obj = provider.User.get(obj_identifier)
else:
obj = None
except Exception:
return "resource-{} identified by {} is unavailable in {}"\
.format(obj_name, obj_identifier, provider.name)
else:
return handler(obj_name, obj_identifier, obj)
def get_model_or_raise(provider, model_type, model_id):
ns = TYPE_NS_MAP[model_type]
model_cls = provider.get_model_cls(model_type)
model = model_cls.get(model_id)
if model is None:
raise CmdException(
f'{ns}:{model_id} not found in provider:{provider.identifier}')
return model


def use_provider(func):
@wraps(func)
def wrapper(req, **kwargs):
provider_id = kwargs.pop('provider')
provider = req.ctx['library'].get(provider_id)
if provider is None:
raise CmdException(f'provider:{provider_id} not found')
return func(req, provider, **kwargs)
return wrapper


def create_model_handler(ns, model_type):
@route(f'/<provider>/{ns}/<model_id>')
@use_provider
def handle(req, provider, model_id):
# special cases:
# fuo://<provider>/users/me -> show current logged user
if model_type == ModelType.user:
if model_id == 'me':
user = getattr(provider, '_user', None)
if user is None:
raise CmdException(
f'log in provider:{provider.identifier} first')
return user
model = get_model_or_raise(provider, model_type, model_id)
return model


@route('/')
def list_providers(req):
return req.ctx['library'].list()


@route('/<provider>/songs/<sid>')
def song_detail(req, provider, sid):
return get_from_provider(req, provider, sid, "songs")
for ns, model_type in NS_TYPE_MAP.items():
create_model_handler(ns, model_type)


@route('/<provider>/songs/<sid>/lyric')
@use_provider
def lyric(req, provider, sid):
return get_from_provider(req, provider, sid, "songs", noexception_handler_lyric)


@route('/<provider>/artists/<aid>')
def artist_detail(req, provider, aid):
return get_from_provider(req, provider, aid, "artists")


@route('/<provider>/albums/<bid>')
def album_detail(req, provider, bid):
return get_from_provider(req, provider, bid, "albums")


'''
------------------------------------
Original Route -- get User by uid
example : fuo show fuo://<provider>/users/12345678
------------------------------------
Issue #317
Description: fuo show nehancement -- show info about current user
example : fuo show fuo://<provider>/users/me
'''


@route('/<provider>/users/<uid>')
def user_detail(req, provider, uid):
return get_from_provider(req, provider, uid, "users", noexception_handler_user)


@route('/<provider>/playlists/<pid>')
def playlist_detail(req, provider, pid):
return get_from_provider(req, provider, pid, "playlists")
song = get_model_or_raise(provider, ModelType.song, sid)
if song.lyric is not None:
return song.lyric.content
return ''


@route('/<provider>/playlists/<pid>/songs')
@use_provider
def playlist_songs(req, provider, pid):
return get_from_provider(
req,
provider,
pid,
"playlists",
noexception_handler_readerlist
)


'''
Issue #317
Description: fuo show enhancement -- show all albums of an artist identified by aid
example : fuo show fuo://<provider>/artists/<aid>/albums
'''
playlist = get_model_or_raise(provider, ModelType.playlist, pid)
return to_readall_reader(playlist, 'songs').readall()


@route('/<provider>/artists/<aid>/albums')
@use_provider
def albums_of_artist(req, provider, aid):
return get_from_provider(
req,
provider,
aid,
"artists",
noexception_handler_readerlist
)
"""show all albums of an artist identified by artist id"""
artist = get_model_or_raise(provider, ModelType.artist, aid)
return to_readall_reader(artist, 'albums').readall()
29 changes: 6 additions & 23 deletions fuocore/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,10 @@
from urllib.parse import parse_qsl, urlsplit


class ShowCmdException(Exception):
pass


# Rule not found
class NotFound(ShowCmdException):
def __init__(self, rule):
self.rule = rule

def __str__(self):
# use backstrace lib (the format_exc/print_exc) causes fail
# maybe the coroutine issue ?
uri_msg = "Bad Uri Exception: fuo://{}".format(self.rule)
return uri_msg
class NotFound(Exception):
"""
No matched rule for path
"""


def match(url, rules):
Expand All @@ -42,8 +32,7 @@ def match(url, rules):
query = dict(parse_qsl(qs))
params = match.groupdict()
return rule, params, query

raise NotFound(path)
raise NotFound(f"No matched rule for {path}")


def _validate_rule(rule):
Expand Down Expand Up @@ -109,13 +98,7 @@ def wrapper(*args, **kwargs):
return decorator

def dispatch(self, uri, ctx):
try:
rule, params, query = match(uri, self.rules)
except NotFound:
# pass it to the exception handle procedure in __init__.py
raise
return None

rule, params, query = match(uri, self.rules)
handler = self.handlers[rule]
req = Request(uri=uri, rule=rule, params=params, query=query, ctx=ctx)
return handler(req, **params)
29 changes: 16 additions & 13 deletions fuocore/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from functools import wraps
from itertools import filterfalse

from fuocore.reader import Reader, RandomSequentialReader, SequentialReader
from fuocore.reader import wrap as reader_wrap


logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -89,29 +89,32 @@ def get_osx_theme():
return 1 if theme == 'Dark' else -1


def reader_to_list(reader):
if not isinstance(reader, Reader):
raise TypeError
if reader.allow_random_read:
return reader.readall()
return list(reader)


def to_reader(model, field):
flag_attr = 'allow_create_{}_g'.format(field)
method_attr = 'create_{}_g'.format(field)

flag_g = getattr(model.meta, flag_attr)

if flag_g:
return SequentialReader.wrap(getattr(model, method_attr)())
return reader_wrap(getattr(model, method_attr)())

value = getattr(model, field, None)
if value is None:
return RandomSequentialReader.from_list([])
return reader_wrap([])
if isinstance(value, (list, tuple)):
return RandomSequentialReader.from_list(value)
return SequentialReader.wrap(iter(value)) # TypeError if not iterable
return reader_wrap(value)
return reader_wrap(iter(value)) # TypeError if not iterable


def to_readall_reader(*args, **kwargs):
"""
hack: set SequentialReader reader's count to 1000 if it is None
so that we can call readall method.
"""
reader = to_reader(*args, **kwargs)
if reader.count is None:
reader.count = 1000
return reader


class DedupList(list):
Expand Down
Loading