diff --git a/owrx/controllers.py b/owrx/controllers.py deleted file mode 100644 index e3aa664..0000000 --- a/owrx/controllers.py +++ /dev/null @@ -1,210 +0,0 @@ -import os -import mimetypes -import json -import pkg_resources -from datetime import datetime -from string import Template -from owrx.websocket import WebSocketConnection -from owrx.config import PropertyManager -from owrx.client import ClientRegistry -from owrx.connection import WebSocketMessageHandler -from owrx.version import openwebrx_version -from owrx.feature import FeatureDetector -from owrx.metrics import Metrics -from owrx.sdr import SdrService -from abc import ABC, abstractmethod - -import logging - -logger = logging.getLogger(__name__) - - -class Controller(ABC): - def __init__(self, handler, request): - self.handler = handler - self.request = request - - def send_response(self, content, code=200, content_type="text/html", last_modified: datetime = None, max_age=None): - self.handler.send_response(code) - if content_type is not None: - self.handler.send_header("Content-Type", content_type) - if last_modified is not None: - self.handler.send_header("Last-Modified", last_modified.strftime("%a, %d %b %Y %H:%M:%S GMT")) - if max_age is not None: - self.handler.send_header("Cache-Control", "max-age: {0}".format(max_age)) - self.handler.end_headers() - if type(content) == str: - content = content.encode() - self.handler.wfile.write(content) - - @abstractmethod - def handle_request(self): - pass - - -class StatusController(Controller): - def handle_request(self): - pm = PropertyManager.getSharedInstance() - # TODO keys that have been left out since they are no longer simple strings: sdr_hw, bands, antenna - vars = { - "status": "active", - "name": pm["receiver_name"], - "op_email": pm["receiver_admin"], - "users": ClientRegistry.getSharedInstance().clientCount(), - "users_max": pm["max_clients"], - "gps": pm["receiver_gps"], - "asl": pm["receiver_asl"], - "loc": pm["receiver_location"], - "sw_version": openwebrx_version, - "avatar_ctime": os.path.getctime("htdocs/gfx/openwebrx-avatar.png"), - } - self.send_response("\n".join(["{key}={value}".format(key=key, value=value) for key, value in vars.items()])) - - -class StatusJsonController(Controller): - def getProfileStats(self, profile): - return { - "name": profile["name"], - "center_freq": profile["center_freq"], - "sample_rate": profile["samp_rate"], - } - - def getReceiverStats(self, receiver): - stats = { - "name": receiver.getName(), - # TODO would be better to have types from the config here - "type": type(receiver).__name__, - "profiles": [self.getProfileStats(p) for p in receiver.getProfiles().values()] - } - return stats - - def handle_request(self): - pm = PropertyManager.getSharedInstance() - - gps = pm["receiver_gps"] - status = { - "receiver": { - "name": pm["receiver_name"], - "admin": pm["receiver_admin"], - "gps": {"lat": gps[0], "lon": gps[1]}, - "asl": pm["receiver_asl"], - "location": pm["receiver_location"], - }, - "max_clients": pm["max_clients"], - "version": openwebrx_version, - "sdrs": [self.getReceiverStats(r) for r in SdrService.getSources().values()] - } - self.send_response(json.dumps(status), content_type="application/json") - - -class AssetsController(Controller): - def getModified(self, file): - return None - - def openFile(self, file): - pass - - def serve_file(self, file, content_type=None): - try: - modified = self.getModified(file) - - if modified is not None and "If-Modified-Since" in self.handler.headers: - client_modified = datetime.strptime( - self.handler.headers["If-Modified-Since"], "%a, %d %b %Y %H:%M:%S %Z" - ) - if modified <= client_modified: - self.send_response("", code=304) - return - - f = self.openFile(file) - data = f.read() - f.close() - - if content_type is None: - (content_type, encoding) = mimetypes.MimeTypes().guess_type(file) - self.send_response(data, content_type=content_type, last_modified=modified, max_age=3600) - except FileNotFoundError: - self.send_response("file not found", code=404) - - def handle_request(self): - filename = self.request.matches.group(1) - self.serve_file(filename) - - -class OwrxAssetsController(AssetsController): - def openFile(self, file): - return pkg_resources.resource_stream("htdocs", file) - - -class AprsSymbolsController(AssetsController): - def __init__(self, handler, request): - pm = PropertyManager.getSharedInstance() - path = pm["aprs_symbols_path"] - if not path.endswith("/"): - path += "/" - self.path = path - super().__init__(handler, request) - - def getFilePath(self, file): - return self.path + file - - def getModified(self, file): - return datetime.fromtimestamp(os.path.getmtime(self.getFilePath(file))) - - def openFile(self, file): - return open(self.getFilePath(file), "rb") - - -class TemplateController(Controller): - def render_template(self, file, **vars): - file_content = pkg_resources.resource_string("htdocs", file).decode("utf-8") - template = Template(file_content) - - return template.safe_substitute(**vars) - - def serve_template(self, file, **vars): - self.send_response(self.render_template(file, **vars), content_type="text/html") - - def default_variables(self): - return {} - - -class WebpageController(TemplateController): - def template_variables(self): - header = self.render_template("include/header.include.html") - return {"header": header} - - -class IndexController(WebpageController): - def handle_request(self): - self.serve_template("index.html", **self.template_variables()) - - -class MapController(WebpageController): - def handle_request(self): - # TODO check if we have a google maps api key first? - self.serve_template("map.html", **self.template_variables()) - - -class FeatureController(WebpageController): - def handle_request(self): - self.serve_template("features.html", **self.template_variables()) - - -class ApiController(Controller): - def handle_request(self): - data = json.dumps(FeatureDetector().feature_report()) - self.send_response(data, content_type="application/json") - - -class MetricsController(Controller): - def handle_request(self): - data = json.dumps(Metrics.getSharedInstance().getMetrics()) - self.send_response(data, content_type="application/json") - - -class WebSocketController(Controller): - def handle_request(self): - conn = WebSocketConnection(self.handler, WebSocketMessageHandler()) - # enter read loop - conn.handle() diff --git a/owrx/controllers/__init__.py b/owrx/controllers/__init__.py new file mode 100644 index 0000000..04f7954 --- /dev/null +++ b/owrx/controllers/__init__.py @@ -0,0 +1,25 @@ +from abc import ABC, abstractmethod +from datetime import datetime + + +class Controller(ABC): + def __init__(self, handler, request): + self.handler = handler + self.request = request + + def send_response(self, content, code=200, content_type="text/html", last_modified: datetime = None, max_age=None): + self.handler.send_response(code) + if content_type is not None: + self.handler.send_header("Content-Type", content_type) + if last_modified is not None: + self.handler.send_header("Last-Modified", last_modified.strftime("%a, %d %b %Y %H:%M:%S GMT")) + if max_age is not None: + self.handler.send_header("Cache-Control", "max-age: {0}".format(max_age)) + self.handler.end_headers() + if type(content) == str: + content = content.encode() + self.handler.wfile.write(content) + + @abstractmethod + def handle_request(self): + pass diff --git a/owrx/controllers/api.py b/owrx/controllers/api.py new file mode 100644 index 0000000..122363a --- /dev/null +++ b/owrx/controllers/api.py @@ -0,0 +1,9 @@ +from . import Controller +from owrx.feature import FeatureDetector +import json + + +class ApiController(Controller): + def handle_request(self): + data = json.dumps(FeatureDetector().feature_report()) + self.send_response(data, content_type="application/json") diff --git a/owrx/controllers/assets.py b/owrx/controllers/assets.py new file mode 100644 index 0000000..a3b093d --- /dev/null +++ b/owrx/controllers/assets.py @@ -0,0 +1,64 @@ +from . import Controller +from owrx.config import PropertyManager +from datetime import datetime +import mimetypes +import os +import pkg_resources + + +class AssetsController(Controller): + def getModified(self, file): + return None + + def openFile(self, file): + pass + + def serve_file(self, file, content_type=None): + try: + modified = self.getModified(file) + + if modified is not None and "If-Modified-Since" in self.handler.headers: + client_modified = datetime.strptime( + self.handler.headers["If-Modified-Since"], "%a, %d %b %Y %H:%M:%S %Z" + ) + if modified <= client_modified: + self.send_response("", code=304) + return + + f = self.openFile(file) + data = f.read() + f.close() + + if content_type is None: + (content_type, encoding) = mimetypes.MimeTypes().guess_type(file) + self.send_response(data, content_type=content_type, last_modified=modified, max_age=3600) + except FileNotFoundError: + self.send_response("file not found", code=404) + + def handle_request(self): + filename = self.request.matches.group(1) + self.serve_file(filename) + + +class OwrxAssetsController(AssetsController): + def openFile(self, file): + return pkg_resources.resource_stream("htdocs", file) + + +class AprsSymbolsController(AssetsController): + def __init__(self, handler, request): + pm = PropertyManager.getSharedInstance() + path = pm["aprs_symbols_path"] + if not path.endswith("/"): + path += "/" + self.path = path + super().__init__(handler, request) + + def getFilePath(self, file): + return self.path + file + + def getModified(self, file): + return datetime.fromtimestamp(os.path.getmtime(self.getFilePath(file))) + + def openFile(self, file): + return open(self.getFilePath(file), "rb") diff --git a/owrx/controllers/metrics.py b/owrx/controllers/metrics.py new file mode 100644 index 0000000..33f05b1 --- /dev/null +++ b/owrx/controllers/metrics.py @@ -0,0 +1,9 @@ +from . import Controller +from owrx.metrics import Metrics +import json + + +class MetricsController(Controller): + def handle_request(self): + data = json.dumps(Metrics.getSharedInstance().getMetrics()) + self.send_response(data, content_type="application/json") diff --git a/owrx/controllers/status.py b/owrx/controllers/status.py new file mode 100644 index 0000000..3b7f9ac --- /dev/null +++ b/owrx/controllers/status.py @@ -0,0 +1,62 @@ +from . import Controller +from owrx.client import ClientRegistry +from owrx.version import openwebrx_version +from owrx.sdr import SdrService +from owrx.config import PropertyManager +import os +import json + + +class StatusController(Controller): + def handle_request(self): + pm = PropertyManager.getSharedInstance() + # TODO keys that have been left out since they are no longer simple strings: sdr_hw, bands, antenna + vars = { + "status": "active", + "name": pm["receiver_name"], + "op_email": pm["receiver_admin"], + "users": ClientRegistry.getSharedInstance().clientCount(), + "users_max": pm["max_clients"], + "gps": pm["receiver_gps"], + "asl": pm["receiver_asl"], + "loc": pm["receiver_location"], + "sw_version": openwebrx_version, + "avatar_ctime": os.path.getctime("htdocs/gfx/openwebrx-avatar.png"), + } + self.send_response("\n".join(["{key}={value}".format(key=key, value=value) for key, value in vars.items()])) + + +class StatusJsonController(Controller): + def getProfileStats(self, profile): + return { + "name": profile["name"], + "center_freq": profile["center_freq"], + "sample_rate": profile["samp_rate"], + } + + def getReceiverStats(self, receiver): + stats = { + "name": receiver.getName(), + # TODO would be better to have types from the config here + "type": type(receiver).__name__, + "profiles": [self.getProfileStats(p) for p in receiver.getProfiles().values()] + } + return stats + + def handle_request(self): + pm = PropertyManager.getSharedInstance() + + gps = pm["receiver_gps"] + status = { + "receiver": { + "name": pm["receiver_name"], + "admin": pm["receiver_admin"], + "gps": {"lat": gps[0], "lon": gps[1]}, + "asl": pm["receiver_asl"], + "location": pm["receiver_location"], + }, + "max_clients": pm["max_clients"], + "version": openwebrx_version, + "sdrs": [self.getReceiverStats(r) for r in SdrService.getSources().values()] + } + self.send_response(json.dumps(status), content_type="application/json") diff --git a/owrx/controllers/template.py b/owrx/controllers/template.py new file mode 100644 index 0000000..cbcf116 --- /dev/null +++ b/owrx/controllers/template.py @@ -0,0 +1,40 @@ +from . import Controller +import pkg_resources +from string import Template +from abc import ABCMeta + + +class TemplateController(Controller, metaclass=ABCMeta): + def render_template(self, file, **vars): + file_content = pkg_resources.resource_string("htdocs", file).decode("utf-8") + template = Template(file_content) + + return template.safe_substitute(**vars) + + def serve_template(self, file, **vars): + self.send_response(self.render_template(file, **vars), content_type="text/html") + + def default_variables(self): + return {} + + +class WebpageController(TemplateController, metaclass=ABCMeta): + def template_variables(self): + header = self.render_template("include/header.include.html") + return {"header": header} + + +class IndexController(WebpageController): + def handle_request(self): + self.serve_template("index.html", **self.template_variables()) + + +class MapController(WebpageController): + def handle_request(self): + # TODO check if we have a google maps api key first? + self.serve_template("map.html", **self.template_variables()) + + +class FeatureController(WebpageController): + def handle_request(self): + self.serve_template("features.html", **self.template_variables()) diff --git a/owrx/controllers/websocket.py b/owrx/controllers/websocket.py new file mode 100644 index 0000000..2886b54 --- /dev/null +++ b/owrx/controllers/websocket.py @@ -0,0 +1,10 @@ +from . import Controller +from owrx.websocket import WebSocketConnection +from owrx.connection import WebSocketMessageHandler + + +class WebSocketController(Controller): + def handle_request(self): + conn = WebSocketConnection(self.handler, WebSocketMessageHandler()) + # enter read loop + conn.handle() diff --git a/owrx/http.py b/owrx/http.py index 4783a27..94d8521 100644 --- a/owrx/http.py +++ b/owrx/http.py @@ -1,15 +1,19 @@ -from owrx.controllers import ( - StatusController, +from owrx.controllers.status import ( StatusJsonController, - IndexController, - OwrxAssetsController, - WebSocketController, - MapController, - FeatureController, - ApiController, - MetricsController, - AprsSymbolsController, + StatusController ) +from owrx.controllers.template import ( + IndexController, + MapController, + FeatureController +) +from owrx.controllers.assets import ( + OwrxAssetsController, + AprsSymbolsController +) +from owrx.controllers.websocket import WebSocketController +from owrx.controllers.api import ApiController +from owrx.controllers.metrics import MetricsController from http.server import BaseHTTPRequestHandler from urllib.parse import urlparse, parse_qs import re diff --git a/setup.py b/setup.py index dd4fe70..c32d972 100644 --- a/setup.py +++ b/setup.py @@ -11,7 +11,7 @@ except ImportError: setup( name="OpenWebRX", version=str(strictversion), - packages=find_namespace_packages(include=["owrx", "owrx.source", "owrx.service", "csdr", "htdocs"]), + packages=find_namespace_packages(include=["owrx", "owrx.source", "owrx.service", "owrx.controllers", "csdr", "htdocs"]), package_data={"htdocs": [f[len("htdocs/") :] for f in glob("htdocs/**/*", recursive=True)]}, entry_points={"console_scripts": ["openwebrx=owrx.__main__:main"]}, # use the github page for now