Skip to content

Commit

Permalink
feat: add handle_request method (#355)
Browse files Browse the repository at this point in the history
  • Loading branch information
cosven committed Mar 2, 2020
1 parent 98b6ade commit ed4931b
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 133 deletions.
131 changes: 21 additions & 110 deletions feeluown/cli.py
Expand Up @@ -6,11 +6,10 @@
from contextlib import contextmanager
from socket import socket, AF_INET, SOCK_STREAM

from fuocore.cmds import exec_cmd, Cmd
from fuocore.cmds.helpers import show_song
from fuocore.protocol import Parser
from fuocore.serializers import serialize
from fuocore.protocol import Request, Response
from feeluown.consts import CACHE_DIR
from feeluown.server import handle_request


OUTPUT_CACHE_FILEPATH = os.path.join(CACHE_DIR, 'cli.out')
Expand Down Expand Up @@ -98,81 +97,6 @@ def setup_cli_argparse(parser):
cmd_handler_mapping = {}


class Request:
def __init__(self, cmd, *args, options=None, options_str=None, heredoc=None):
"""cli request object
:param string cmd: cmd name (e.g. search)
:param list args: cmd arguments
:param string options_str: cmd options
:param string heredoc:
>>> req = Request('search',
... 'linkin park',
... options_str='[type=pl,source=xiami]',)
>>> req.raw
'search "linkin park" [type=pl,source=xiami]'
>>> req.to_cmd().options
'{"type": "pl", "source": "xiami"}'
"""
self.cmd = cmd
self.args = args
self.options = options if options else {}
self.options_str = options_str
self.heredoc = heredoc

@property
def raw(self):
"""generate syntactically correct request"""

def escape(value):
# if value is not furi/float/integer, than we surround the value
# with double quotes
from fuocore.protocol.lexer import furi_re, integer_re, float_re

regex_list = (furi_re, float_re, integer_re)
for regex in regex_list:
if regex.match(value):
break
else:
value = '"{}"'.format(value)
return value

options_str = self.options_str
raw = '{cmd} {args_str} {options_str} #: {req_options_str}'.format(
cmd=self.cmd,
args_str=' '.join((escape(arg) for arg in self.args)),
options_str=(options_str if options_str else ''),
req_options_str=", ".join("{}={}".format(k, v)
for k, v in self.options.items())
)
if self.heredoc is not None:
raw += ' <<EOF\n{}\nEOF\n\n'.format(self.heredoc)
return raw

def to_cmd(self):
if self.options_str:
options = Parser(self.options_str).parse_cmd_options()
else:
options = {}
return Cmd(self.cmd, *self.args, options=options)

def __str__(self):
return '{} {}'.format(self.cmd, self.args)


class Response:
def __init__(self, code, content):
self.code = code
self.content = content

@classmethod
def from_text(cls, text):
if text.endswith('OK\n'):
return Response(code='OK', content='\n'.join(text.split('\n')[1:-2]))
return Response('Oops', content='An error occured in server.')


class Client(object):
def __init__(self, sock):
self.sock = sock
Expand All @@ -187,10 +111,7 @@ def send(self, req):
while len(buf) < int(length) + 2:
buf.extend(rfile.readline())
text = bytes(buf[:-2]).decode('utf-8')
if code.lower() == 'ok':
return Response(code='OK', content=text)
else:
return Response(code='Oops', content=text)
return Response(ok=code.lower() == 'ok', text=text)

def close(self):
self.sock.close()
Expand Down Expand Up @@ -226,7 +147,6 @@ class BaseHandler(metaclass=HandlerMeta):
def __init__(self, args):
self.args = args

self._req = Request(args.cmd)
options_list = ["format"]
args_dict = vars(args)
req_options = {option: args_dict.get(option) for option in options_list
Expand All @@ -241,10 +161,10 @@ def get_req(self):

def process_resp(self, resp):
if resp.code == 'OK':
if resp.content:
print(resp.content)
if resp.text:
print(resp.text)
else:
print_error(resp.content)
print_error(resp.text)


class SimpleHandler(BaseHandler):
Expand All @@ -262,24 +182,22 @@ class HandlerWithWriteListCache(BaseHandler):
def before_request(self):
cmd = self.args.cmd
if cmd == 'search':
self._req.args = (self.args.keyword, )
options_str = self.args.options
if not options_str:
return
if options_str.startswith('[') and options_str.endswith(']'):
self._req.options_str = options_str
else:
self._req.options_str = '[{}]'.format(options_str)
self._req.cmd_args = (self.args.keyword, )
options_str = self.args.options or ''
option_kv_list = options_str.split(',')
for option_kv in option_kv_list:
k, v = option_kv.split('=')
self._req.cmd_options[k] = v

def process_resp(self, resp):
if resp.code != 'OK' or not resp.content:
if resp.code != 'OK' or not resp.text:
super().process_resp(resp)
return
format = self._req.options.get('format', 'plain')
if format != 'plain':
print(resp.content)
print(resp.text)
return
lines = resp.content.split('\n')
lines = resp.text.split('\n')
with open(OUTPUT_CACHE_FILEPATH, 'w') as f:
padding_width = len(str(len(lines)))
tpl = '{:%dd} {}' % padding_width
Expand All @@ -305,7 +223,7 @@ def before_request(self):
uri = line.split('#')[0].strip()
break
i += 1
self._req.args = (uri, )
self._req.cmd_args = (uri, )


class AddHandler(BaseHandler):
Expand All @@ -318,7 +236,7 @@ def before_request(self):
furi_list.append(line.strip())
else:
furi_list = [self.args.uri]
self._req.args = (' '.join(furi_list), )
self._req.cmd_args = (' '.join(furi_list), )


class ExecHandler(BaseHandler):
Expand All @@ -327,10 +245,10 @@ class ExecHandler(BaseHandler):
def before_request(self):
code = self.args.code
if code is None:
code = sys.stdin.read()
self._req.heredoc = code
body = sys.stdin.read()
self._req.set_heredoc_body(body)
else:
self._req.args = (code, )
self._req.cmd_args = (code, )


class OnceClient:
Expand All @@ -339,14 +257,7 @@ def __init__(self, app):

def send(self, req):
app = self._app
success, body = exec_cmd(req.to_cmd(),
library=app.library,
player=app.player,
playlist=app.playlist,
live_lyric=app.live_lyric)
msg = serialize(req.options.get('format', 'plain'), body, brief=False)
code = 'OK' if success else 'Oops'
return Response(code=code, content=msg)
return handle_request(req, app)


def dispatch(args, client):
Expand Down
31 changes: 17 additions & 14 deletions feeluown/server.py
Expand Up @@ -8,6 +8,22 @@
logger = logging.getLogger(__name__)


def handle_request(req, app, ctx=None):
"""
:type req: fuocore.protocol.Request
"""
cmd = Cmd(req.cmd, *req.cmd_args, options=req.cmd_options)
ok, body = exec_cmd(
cmd,
library=app.library,
player=app.player,
playlist=app.playlist,
live_lyric=app.live_lyric)
format = req.options.get('format', 'plain')
msg = serialize(format, body, brief=False)
return Response(ok=ok, text=msg, req=req)


class FuoServer:
def __init__(self, app):
self._app = app
Expand All @@ -24,17 +40,4 @@ def protocol_factory(self):
loop=self._loop)

def handle_req(self, req, session=None):
"""
:type req: fuocore.protocol.Request
"""
cmd = Cmd(req.cmd, *req.cmd_args, options=req.cmd_options)
success, body = exec_cmd(
cmd,
library=self._app.library,
player=self._app.player,
playlist=self._app.playlist,
live_lyric=self._app.live_lyric)
code = 'ok' if success else 'oops'
format = req.options.get('format', 'plain')
msg = serialize(format, body, brief=False)
return Response(code=code, msg=msg, req=req)
return handle_request(req, self._app, ctx=session)
47 changes: 44 additions & 3 deletions fuocore/protocol/data_structure.py
@@ -1,3 +1,11 @@
def options_to_str(options):
"""
TODO: support complex value, such as list
"""
return ",".join("{}={}".format(k, v)
for k, v in options.items())


class Request:
"""fuo 协议请求对象"""

Expand All @@ -14,13 +22,46 @@ def __init__(self, cmd, cmd_args=None,
self.heredoc_word = heredoc_word

def set_heredoc_body(self, body):
assert self.has_heredoc is True and self.heredoc_word
self.cmd_args = [body]

@property
def raw(self):
"""generate syntactically correct request"""

def escape(value):
# if value is not furi/float/integer, than we surround the value
# with double quotes
from fuocore.protocol.lexer import furi_re, integer_re, float_re

regex_list = (furi_re, float_re, integer_re)
for regex in regex_list:
if regex.match(value):
break
else:
value = '"{}"'.format(value)
return value

# TODO: allow heredoc and args appear at the same time
args_str = '' if self.has_heredoc else \
' '.join((escape(arg) for arg in self.cmd_args))
raw = '{cmd} {args_str} {options_str} #: {req_options_str}'.format(
cmd=self.cmd,
args_str=args_str,
options_str=options_to_str(self.cmd_options),
req_options_str=options_to_str(self.options),
)
if self.has_heredoc:
raw += ' <<{}\n{}\n{}\n\n'.format(self.heredoc_word,
self.cmd_args[0],
self.heredoc_word)
return raw


class Response:
def __init__(self, code, msg, req=None, options=None):
self.code = code
self.msg = msg
def __init__(self, ok=True, text='', req=None, options=None):
self.code = 'OK' if ok else 'Oops'
self.text = text
self.options = options or {}

self.req = req
4 changes: 2 additions & 2 deletions fuocore/protocol/server_protocol.py
Expand Up @@ -79,7 +79,7 @@ async def read_request(self):

async def write_response(self, resp):
# TODO: 区分客户端和服务端错误(比如客户端错误后面加 ! 标记)
msg_bytes = bytes(resp.msg, 'utf-8')
msg_bytes = bytes(resp.text, 'utf-8')
response_line = ('ACK {} {}\r\n'
.format(resp.code, len(msg_bytes)))
self._writer.write(bytes(response_line, 'utf-8'))
Expand All @@ -99,7 +99,7 @@ async def start(self):
req = await self.read_request()
except RequestError as e:
msg = 'bad reqeust!\r\n' + str(e)
bad_request_resp = Response('oops', msg)
bad_request_resp = Response(ok=False, text=msg)
await self.write_response(bad_request_resp)
else:
if req is None: # client close the connection
Expand Down
8 changes: 4 additions & 4 deletions integration-tests/run.py
Expand Up @@ -23,7 +23,8 @@ def create_client():


def register_dummy_provider():
req = Request('exec', heredoc='''
req = Request('exec', has_heredoc=True, heredoc_word='EOF')
req.set_heredoc_body('''
from fuocore.provider import dummy_provider
app.library.register(dummy_provider)
''')
Expand All @@ -40,9 +41,8 @@ def collect():

def test_show_providers_with_json_format():
with create_client() as client:
resp = client.send(Request('show', 'fuo://',
options={'format': 'json'}))
providers = json.loads(resp.content)
resp = client.send(Request('show', ['fuo://'], options={'format': 'json'}))
providers = json.loads(resp.text)
for provider in providers:
if provider['identifier'] == 'dummy':
break
Expand Down

0 comments on commit ed4931b

Please sign in to comment.