diff --git a/handlers/base.py b/handlers/base.py index c478c2c..3fe53d9 100644 --- a/handlers/base.py +++ b/handlers/base.py @@ -1,8 +1,13 @@ +import logging + from abc import ABCMeta, abstractmethod from configparser import ConfigParser from fahrplan.model.schedule import Schedule +from hacks import noexcept +from util import write_output +log = logging.getLogger(__name__) class HandlerBase(metaclass=ABCMeta): def __init__(self, name: str, config: ConfigParser, global_config: ConfigParser): @@ -18,6 +23,12 @@ def run(self) -> Schedule: class ExportHandler(HandlerBase, metaclass=ABCMeta): - @abstractmethod + content_type = None + + @noexcept(log) def run(self, schedule: Schedule) -> bool: + return write_output(self.config["path"], self.export(schedule)) + + @abstractmethod + def export(self, schedule: Schedule) -> str: pass diff --git a/handlers/export_handlers/basic_xml.py b/handlers/export_handlers/basic_xml.py index 32cf5ba..d977d69 100644 --- a/handlers/export_handlers/basic_xml.py +++ b/handlers/export_handlers/basic_xml.py @@ -10,8 +10,7 @@ class BasicXMLExportHandler(ExportHandler): - @noexcept(log) - def run(self, schedule: Schedule) -> bool: - path = self.config["path"] - content = schedule.to_xml(extended=False) - return write_output(path, content) + content_type = "application/xml" + + def export(self, schedule: Schedule) -> str: + return schedule.to_xml(extended=False) diff --git a/handlers/export_handlers/extended_xml.py b/handlers/export_handlers/extended_xml.py index e55dbea..ea793be 100644 --- a/handlers/export_handlers/extended_xml.py +++ b/handlers/export_handlers/extended_xml.py @@ -10,8 +10,7 @@ class ExtendedXMLExportHandler(ExportHandler): - @noexcept(log) - def run(self, schedule: Schedule) -> bool: - path = self.config["path"] - content = schedule.to_xml(extended=True) - return write_output(path, content) + content_type = "application/xml" + + def export(self, schedule: Schedule) -> str: + return schedule.to_xml(extended=True) diff --git a/handlers/export_handlers/frab_json.py b/handlers/export_handlers/frab_json.py index fb54ef2..f7d2d71 100644 --- a/handlers/export_handlers/frab_json.py +++ b/handlers/export_handlers/frab_json.py @@ -4,19 +4,17 @@ from ..base import ExportHandler from fahrplan.model.schedule import Schedule from fahrplan.datetime import format_duration, format_date, format_datetime, format_time -from hacks import noexcept -from util import write_output log = logging.getLogger(__name__) class FrabJsonExportHandler(ExportHandler): - @noexcept(log) - def run(self, schedule: Schedule) -> bool: - path = self.config["path"] + content_type = "application/json" + + def export(self, schedule: Schedule) -> str: content = self.get_data(schedule) - return write_output(path, json.dumps({"schedule": content}, ensure_ascii=False, sort_keys=True, indent=2)) + return json.dumps({"schedule": content}, ensure_ascii=False, sort_keys=True, indent=2) def get_data(self, schedule): """ diff --git a/webschedule.py b/webschedule.py new file mode 100755 index 0000000..f07ff34 --- /dev/null +++ b/webschedule.py @@ -0,0 +1,107 @@ +#!/usr/bin/env python3 + +import logging + +from argparse import ArgumentParser, FileType +from configparser import ConfigParser +from functools import reduce + +from fahrplan.model.schedule import Schedule + +import schedule +from functools import partial +from http.server import HTTPServer, BaseHTTPRequestHandler +from urllib.parse import urlparse + +log = logging.getLogger(__name__) + +class HTTPRequestHandler(BaseHTTPRequestHandler): + def __init__(self, config, import_handlers, export_handlers, *args, **kwargs): + self.config = config + self.import_handlers = import_handlers + self.export_handlers = { h.config["path"]: h for h in export_handlers } + super().__init__(*args, **kwargs) + + def do_GET(self): + log.info(f'Get {self.path}') + url = urlparse(self.path) + path = url.path[1:] + + if path not in self.export_handlers: + self.send_error(404, 'Path not found') + return + + try: + schedule = self.make_schedule() + if schedule is None: + self.send_error(204) + return + handler = self.export_handlers[path] + body = handler.export(schedule) + if body is not None and not isinstance(body, bytes): + body = body.encode() + content_type = handler.content_type + except Exception as err: + self.send_error(500, str(err)) + raise + + self.send_response(200) + self.send_header('Content-Type', content_type) + self.send_header('Content-Length', len(body)) + self.end_headers() + self.wfile.write(body) + + def make_schedule(self): + imported_schedules = [] + log.info('Running import handlers') + for handler in self.import_handlers: + log.info(f'Running import handler "{handler.name}".') + imported_schedules.append(handler.run()) + log.debug('Finished running import handlers') + + if not any(imported_schedules): + return None + + log.info('Merging schedules.') + final_schedule = reduce(Schedule.merge, imported_schedules) + log.debug('Finished merging schedules.') + + return final_schedule + +def main(): + ap = ArgumentParser() + ap.add_argument('--verbose', '-v', action='count') + ap.add_argument('--quiet', '-q', action='count') + ap.add_argument('--config', '-c', nargs='?', type=FileType('r'), default='./config.ini') + ap.add_argument('--logfile', '-l') + ap.add_argument('--debug', '-d', action='store_true') + ap.add_argument('--interface', '-i', default='localhost') + ap.add_argument('--port', '-p', type=int, default=8080) + args = ap.parse_args() + + schedule.configure_logging(args) + + log.info(f'Using config file "{args.config.name}".') + config = ConfigParser() + config.read_file(args.config) + log.debug('Basic initialization done.') + + import_handlers = schedule.initialize_import_handlers(config) + if not import_handlers: + log.critical("No import handlers to run, aborting.") + sys.exit(1) + + export_handlers = schedule.initialize_export_handlers(config) + if not export_handlers: + log.critical("No export handlers to run, aborting.") + sys.exit(1) + + req_handler = partial(HTTPRequestHandler, config, import_handlers, export_handlers) + httpd = HTTPServer((args.interface, args.port), req_handler) + try: + httpd.serve_forever() + except KeyboardInterrupt: + pass + +if __name__ == '__main__': + main()