grasp/server/grasp_server.py

165 lines
4.7 KiB
Python
Raw Normal View History

2019-06-29 08:18:09 -07:00
#!/usr/bin/env python3
2019-01-07 14:53:08 -08:00
import argparse
from http.server import BaseHTTPRequestHandler, HTTPServer
import json
2019-01-07 14:53:08 -08:00
import os
2019-01-13 07:34:13 -08:00
import logging
2019-01-07 14:53:08 -08:00
from pathlib import Path
import re
2020-04-12 08:39:45 -07:00
from typing import List, Optional, Dict, Any
2019-01-04 13:27:24 -08:00
from org_tools import as_org, empty, DEFAULT_TEMPLATE, Config
2019-01-04 13:27:24 -08:00
2020-04-12 08:39:45 -07:00
CAPTURE_PATH_VAR = 'GRASP_CAPTURE_PATH'
2019-01-13 07:34:13 -08:00
CAPTURE_TEMPLATE_VAR = 'GRASP_CAPTURE_TEMPLATE'
2020-04-12 08:39:45 -07:00
CAPTURE_CONFIG_VAR = 'GRASP_CAPTURE_CONFIG'
2019-01-07 14:53:08 -08:00
2019-01-13 07:34:13 -08:00
def get_logger():
return logging.getLogger('grasp-server')
2020-04-12 08:39:45 -07:00
2019-01-13 07:34:13 -08:00
def append_org(
path: Path,
org: str
):
logger = get_logger()
# TODO perhaps should be an error?...
if not path.exists():
logger.warning("path %s didn't exist!", path)
# https://stackoverflow.com/a/13232181
if len(org.encode('utf8')) > 4096:
logger.warning("writing out %s might be non-atomic", org)
with path.open('a') as fo:
fo.write(org)
2020-04-12 08:39:45 -07:00
from functools import lru_cache
@lru_cache(1)
def capture_config() -> Optional[Config]:
cvar = os.environ.get(CAPTURE_CONFIG_VAR)
if cvar is None:
return None
globs: Dict[str, Any] = {}
exec(Path(cvar).read_text(), globs)
ConfigClass = globs['Config']
return ConfigClass()
2019-01-04 13:27:24 -08:00
def capture(
2019-01-13 07:34:13 -08:00
url: str,
title,
selection,
comment,
tag_str,
2019-01-04 13:27:24 -08:00
):
2019-01-13 07:34:13 -08:00
logger = get_logger()
# protect strings against None
def safe(s: Optional[str]) -> str:
if s is None:
return ''
else:
return s
2019-01-07 14:53:08 -08:00
capture_path = Path(os.environ[CAPTURE_PATH_VAR]).expanduser()
2019-01-13 07:34:13 -08:00
org_template = os.environ[CAPTURE_TEMPLATE_VAR]
2020-04-12 08:39:45 -07:00
config = capture_config()
2019-01-13 07:34:13 -08:00
logger.info('capturing %s to %s', (url, title, selection, comment, tag_str), capture_path)
url = safe(url)
title = safe(title)
selection = safe(selection)
comment = safe(comment)
tag_str = safe(tag_str)
2019-01-07 14:53:08 -08:00
tags: List[str] = []
2019-01-06 12:44:00 -08:00
if not empty(tag_str):
2019-06-29 06:44:54 -07:00
tags = re.split(r'[\s,]', tag_str)
2019-01-06 12:44:00 -08:00
tags = [t for t in tags if not empty(t)] # just in case
2019-01-04 15:57:34 -08:00
2019-01-13 07:34:13 -08:00
org = as_org(
url=url,
title=title,
selection=selection,
comment=comment,
tags=tags,
org_template=org_template,
2020-04-12 08:39:45 -07:00
config=config,
2019-01-13 07:34:13 -08:00
)
append_org(
path=capture_path,
org=org,
)
response = {
2019-01-07 15:56:06 -08:00
'path': str(capture_path),
'status': 'ok',
2019-01-13 07:34:13 -08:00
}
return json.dumps(response).encode('utf8')
2019-01-07 14:53:08 -08:00
class GraspRequestHandler(BaseHTTPRequestHandler):
def handle_POST(self):
2019-01-13 07:34:13 -08:00
logger = get_logger()
content_length = int(self.headers['Content-Length'])
post_data = self.rfile.read(content_length)
payload = json.loads(post_data.decode('utf8'))
2019-01-13 07:34:13 -08:00
logger.info("incoming request %s", payload)
res = capture(**payload)
self.send_response(200)
self.send_header('Content-Type', 'application/json')
self.end_headers()
self.wfile.write(res)
def respond_error(self, message: str):
self.send_response(500)
self.send_header('Content-Type', 'text/html')
self.end_headers()
self.wfile.write(message.encode('utf8'))
def do_POST(self):
2019-01-13 07:34:13 -08:00
logger = get_logger()
try:
self.handle_POST()
except Exception as e:
2019-01-13 07:34:13 -08:00
logger.error("Error during processing")
logger.exception(e)
self.respond_error(message=str(e))
2020-04-12 08:39:45 -07:00
def run(port: str, capture_path: str, template: str, config: Optional[Path]):
2019-01-13 07:34:13 -08:00
logger = get_logger()
logger.info("Using template %s", template)
2019-01-13 07:34:13 -08:00
2019-01-07 14:53:08 -08:00
# not sure if there is a simpler way to communicate with the server...
os.environ[CAPTURE_PATH_VAR] = capture_path
2019-01-13 07:34:13 -08:00
os.environ[CAPTURE_TEMPLATE_VAR] = template
2020-04-12 08:39:45 -07:00
if config is not None:
os.environ[CAPTURE_CONFIG_VAR] = str(config)
httpd = HTTPServer(('', int(port)), GraspRequestHandler)
2019-01-13 07:34:13 -08:00
logger.info(f"Starting httpd on port {port}")
httpd.serve_forever()
2019-01-07 14:53:08 -08:00
2019-06-29 08:18:09 -07:00
2019-01-07 14:53:08 -08:00
def setup_parser(p):
p.add_argument('--port', type=str, default='12212', help='Port for communicating with extension')
p.add_argument('--path', type=str, default='~/capture.org', help='File to capture into')
p.add_argument('--template', type=str, default=DEFAULT_TEMPLATE, help=f"""
2019-01-13 07:34:13 -08:00
{as_org.__doc__}
""")
2020-04-12 08:39:45 -07:00
abspath = lambda p: str(Path(p).absolute())
p.add_argument('--config', type=abspath, required=False, help='Optional dynamic config')
2019-01-07 14:53:08 -08:00
2019-06-29 08:18:09 -07:00
2019-01-07 14:53:08 -08:00
def main():
2019-01-13 07:34:13 -08:00
logging.basicConfig(level=logging.DEBUG, format='%(asctime)s %(name)-12s %(levelname)-8s %(message)s')
p = argparse.ArgumentParser('grasp server', formatter_class=lambda prog: argparse.ArgumentDefaultsHelpFormatter(prog, width=100)) # type: ignore
2019-01-07 14:53:08 -08:00
setup_parser(p)
args = p.parse_args()
2020-04-12 08:39:45 -07:00
run(args.port, args.path, args.template, args.config)
2019-01-07 14:53:08 -08:00
if __name__ == '__main__':
main()