from functools import wraps from urllib.parse import unquote import json import logging import os import queue import shutil import threading import time import requests from tornado.httpserver import HTTPServer from tornado.ioloop import IOLoop from tornado.web import StaticFileHandler, Application, HTTPError import tornado.gen import tornado.web from action import Camera logger = logging.getLogger(__name__) STATIC_PATH = 'static' PORT = 8000 ADDRESS = '127.0.0.1' with open('camera.json') as fd: CAMERA = json.load(fd) state = { 'time': {}, 'status': 'Idle' } def run_async(func): @wraps(func) def async_func(*args, **kwargs): func_hl = Thread(target=func, args=args, kwargs=kwargs) func_hl.start() return func_hl return async_func def _to_json(python_object): if isinstance(python_object, datetime.datetime): if python_object.year < 1900: tt = python_object.timetuple() return '%d-%02d-%02dT%02d:%02d%02dZ' % tuple(list(tt)[:6]) return python_object.strftime('%Y-%m-%dT%H:%M:%SZ') raise TypeError('%s %s is not JSON serializable' % (repr(python_object), type(python_object))) def json_dumps(obj): return json.dumps(obj, indent=4, default=_to_json, ensure_ascii=False, sort_keys=True).encode() class ControlQueue: shutdown = False def worker(self): while True: item = self.q.get() if item is None or self.shutdown: break state['status'] = 'Active' self.camera.sequence(**item) if self.camera.abort: self.camera.abort = False state['status'] = 'Idle' def __init__(self): self.q = queue.Queue() self.camera = Camera(**CAMERA) self._worker = threading.Thread(target=self.worker) self._worker.start() def put(self, filename): self.q.put(filename) def join(self): self.shutdown = True self.camera.abort = True # block until all tasks are done self.q.join() self.q.put(None) self._worker.join() class API(object): def __call__(self, method, kwargs): return getattr(self, method)(**kwargs) def getPresets(self, **data): result = {} result['presets'] = ctl.camera.get_presets(True) return result def camera(self, **data): result = {} for key, value in data.items(): getattr(ctl.camera, key)(**value) return result def run(self, **data): result = {} ctl.put(data) return result def stop(self, **data): result = {} ctl.camera.abort = True ctl.camera.continuous(ctl.camera.STOP) return result def status(self, **data): result = {} result['time'] = ctl.camera.segment_times result['status'] = state['status'] if result['status'] == 'Active': if ctl.camera.sequence_start: result['duration'] = int(time.time() - ctl.camera.sequence_start) else: result['duration'] = '...' result['next'] = ctl.camera.next_target else: result['duration'] = ctl.camera.sequence_time result['position'] = ctl.camera.last_position return result #@run_async def api_task(request, callback): api = API() response = { 'result': api(request['method'], request.get('params', {})) } callback(response) class RPCHandler(tornado.web.RequestHandler): def get(self): self.write(BANNER) @tornado.gen.coroutine def post(self): error = None request = None try: request = json.loads(self.request.body.decode()) if request['method'][0] == '_' or not hasattr(API, request['method']): raise Exception('unknown method') except: error = {'error': {'code': -32700, 'message': 'Parse error'}} if not error: try: response = yield tornado.gen.Task(api_task, request) except: logger.error("ERROR: %s", request, exc_info=True) error = {'error': {'code': -32000, 'message': 'Server error'}} if error: response = error if request and 'id' in request: response['id'] = request['id'] response['jsonrpc'] = '2.0' response = json_dumps(response) self.write(response) def prepare(encoding): if not os.path.exists(settings['prefix']): print('please create "%s" and start again' % settings['prefix']) sys.exit(1) index = os.path.join(settings['prefix'], 'index.html') if not os.path.exists(index): try: with open(index, 'w') as fd: fd.write(BANNER_PUBLIC) except: print('can not write to "%s"' % settings['prefix']) sys.exit(1) load_files(encoding) registered = False while not registered: try: register_server() except: logging.error('failed to register') time.sleep(10) registered = True class MainHandler(tornado.web.RequestHandler): def get(self, path): path = os.path.join(STATIC_PATH, 'index.html') with open(path) as fd: content = fd.read() self.set_header('Content-Type', 'text/html') self.set_header('Content-Length', str(len(content))) self.set_header('Cache-Control', 'no-cache, no-store, must-revalidate') self.set_header('Pragma', 'no-cache') self.set_header('Expires', '0') self.write(content) def main(): global ctl ctl = ControlQueue() handlers = [ (r'/api/', RPCHandler), (r'/static/(.*)', StaticFileHandler, {'path': STATIC_PATH}), (r"(.*)", MainHandler), ] options = { 'debug': False, 'gzip': True, } app = Application(handlers, **options) app.listen(PORT, ADDRESS) main = IOLoop.instance() try: main.start() except: print('shutting down...') ctl.join() if __name__ == '__main__': main()