2020-02-23 16:22:13 +00:00
|
|
|
from . import Controller
|
2021-02-11 18:31:44 +00:00
|
|
|
from owrx.config.core import CoreConfig
|
2020-06-21 20:35:40 +00:00
|
|
|
from datetime import datetime, timezone
|
2020-02-23 16:22:13 +00:00
|
|
|
import mimetypes
|
|
|
|
import os
|
|
|
|
import pkg_resources
|
2020-05-02 11:35:42 +00:00
|
|
|
from abc import ABCMeta, abstractmethod
|
2020-09-04 13:44:25 +00:00
|
|
|
import gzip
|
|
|
|
|
|
|
|
import logging
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
class GzipMixin(object):
|
2021-02-08 22:29:24 +00:00
|
|
|
def send_response(self, content, code=200, headers=None, content_type="text/html", *args, **kwargs):
|
2020-09-04 13:44:25 +00:00
|
|
|
if self.zipable(content_type) and "accept-encoding" in self.request.headers:
|
2021-01-20 16:01:46 +00:00
|
|
|
accepted = [s.strip().lower() for s in self.request.headers["accept-encoding"].split(",")]
|
2020-09-04 13:44:25 +00:00
|
|
|
if "gzip" in accepted:
|
|
|
|
if type(content) == str:
|
|
|
|
content = content.encode()
|
|
|
|
content = self.gzip(content)
|
|
|
|
if headers is None:
|
|
|
|
headers = {}
|
|
|
|
headers["Content-Encoding"] = "gzip"
|
2021-02-08 22:29:24 +00:00
|
|
|
super().send_response(content, code, headers=headers, content_type=content_type, *args, **kwargs)
|
2020-09-04 13:44:25 +00:00
|
|
|
|
|
|
|
def zipable(self, content_type):
|
2021-05-14 21:10:17 +00:00
|
|
|
types = ["application/javascript", "text/css", "text/html", "image/svg+xml"]
|
2020-09-04 13:44:25 +00:00
|
|
|
return content_type in types
|
|
|
|
|
|
|
|
def gzip(self, content):
|
|
|
|
return gzip.compress(content)
|
2020-02-23 16:22:13 +00:00
|
|
|
|
2020-05-02 11:35:42 +00:00
|
|
|
|
2020-09-04 13:44:25 +00:00
|
|
|
class ModificationAwareController(Controller, metaclass=ABCMeta):
|
2020-06-21 20:35:40 +00:00
|
|
|
@abstractmethod
|
|
|
|
def getModified(self, file):
|
|
|
|
pass
|
2021-01-20 16:01:46 +00:00
|
|
|
|
2020-06-21 20:35:40 +00:00
|
|
|
def wasModified(self, file):
|
|
|
|
try:
|
2020-07-01 17:10:46 +00:00
|
|
|
modified = self.getModified(file).replace(microsecond=0)
|
2021-01-20 16:01:46 +00:00
|
|
|
|
2020-06-21 20:35:40 +00:00
|
|
|
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"
|
|
|
|
).replace(tzinfo=timezone.utc)
|
|
|
|
if modified <= client_modified:
|
|
|
|
return False
|
|
|
|
except FileNotFoundError:
|
|
|
|
pass
|
2021-01-20 16:01:46 +00:00
|
|
|
|
2020-06-21 20:35:40 +00:00
|
|
|
return True
|
|
|
|
|
|
|
|
|
2020-09-04 13:44:25 +00:00
|
|
|
class AssetsController(GzipMixin, ModificationAwareController, metaclass=ABCMeta):
|
2020-02-23 16:22:13 +00:00
|
|
|
def getModified(self, file):
|
2020-07-01 17:10:46 +00:00
|
|
|
return datetime.fromtimestamp(os.path.getmtime(self.getFilePath(file)), timezone.utc)
|
2020-02-23 16:22:13 +00:00
|
|
|
|
|
|
|
def openFile(self, file):
|
2020-05-02 11:35:42 +00:00
|
|
|
return open(self.getFilePath(file), "rb")
|
|
|
|
|
|
|
|
@abstractmethod
|
|
|
|
def getFilePath(self, file):
|
2020-02-23 16:22:13 +00:00
|
|
|
pass
|
|
|
|
|
|
|
|
def serve_file(self, file, content_type=None):
|
|
|
|
try:
|
|
|
|
modified = self.getModified(file)
|
|
|
|
|
2020-06-21 20:35:40 +00:00
|
|
|
if not self.wasModified(file):
|
|
|
|
self.send_response("", code=304)
|
|
|
|
return
|
2020-02-23 16:22:13 +00:00
|
|
|
|
|
|
|
f = self.openFile(file)
|
|
|
|
data = f.read()
|
|
|
|
f.close()
|
|
|
|
|
|
|
|
if content_type is None:
|
2021-02-10 23:20:17 +00:00
|
|
|
(content_type, encoding) = mimetypes.guess_type(self.getFilePath(file))
|
2020-02-23 16:22:13 +00:00
|
|
|
self.send_response(data, content_type=content_type, last_modified=modified, max_age=3600)
|
|
|
|
except FileNotFoundError:
|
|
|
|
self.send_response("file not found", code=404)
|
|
|
|
|
2020-02-23 17:32:37 +00:00
|
|
|
def indexAction(self):
|
2020-02-23 16:22:13 +00:00
|
|
|
filename = self.request.matches.group(1)
|
|
|
|
self.serve_file(filename)
|
|
|
|
|
|
|
|
|
|
|
|
class OwrxAssetsController(AssetsController):
|
2020-05-02 11:35:42 +00:00
|
|
|
def getFilePath(self, file):
|
2021-02-08 23:12:53 +00:00
|
|
|
mappedFiles = {
|
|
|
|
"gfx/openwebrx-avatar.png": "receiver_avatar",
|
|
|
|
"gfx/openwebrx-top-photo.jpg": "receiver_top_photo",
|
|
|
|
}
|
2021-02-10 20:29:46 +00:00
|
|
|
if file in mappedFiles and ("mapped" not in self.request.query or self.request.query["mapped"][0] != "false"):
|
|
|
|
config = CoreConfig()
|
2021-05-07 14:57:54 +00:00
|
|
|
for ext in ["png", "jpg", "webp"]:
|
2021-02-10 23:20:17 +00:00
|
|
|
user_file = "{}/{}.{}".format(config.get_data_directory(), mappedFiles[file], ext)
|
|
|
|
if os.path.exists(user_file) and os.path.isfile(user_file):
|
|
|
|
return user_file
|
2020-05-02 11:35:42 +00:00
|
|
|
return pkg_resources.resource_filename("htdocs", file)
|
2020-02-23 16:22:13 +00:00
|
|
|
|
|
|
|
|
|
|
|
class AprsSymbolsController(AssetsController):
|
2020-02-27 17:43:44 +00:00
|
|
|
def __init__(self, handler, request, options):
|
2021-02-06 23:21:57 +00:00
|
|
|
path = CoreConfig().get_aprs_symbols_path()
|
2020-02-23 16:22:13 +00:00
|
|
|
if not path.endswith("/"):
|
|
|
|
path += "/"
|
|
|
|
self.path = path
|
2020-02-27 17:43:44 +00:00
|
|
|
super().__init__(handler, request, options)
|
2020-02-23 16:22:13 +00:00
|
|
|
|
|
|
|
def getFilePath(self, file):
|
|
|
|
return self.path + file
|
|
|
|
|
|
|
|
|
2020-09-04 13:44:25 +00:00
|
|
|
class CompiledAssetsController(GzipMixin, ModificationAwareController):
|
2020-05-02 11:35:42 +00:00
|
|
|
profiles = {
|
|
|
|
"receiver.js": [
|
2020-09-17 19:33:11 +00:00
|
|
|
"lib/chroma.min.js",
|
2020-05-02 11:35:42 +00:00
|
|
|
"openwebrx.js",
|
|
|
|
"lib/jquery-3.2.1.min.js",
|
2020-11-04 21:32:13 +00:00
|
|
|
"lib/jquery.nanoscroller.min.js",
|
2020-05-08 22:11:20 +00:00
|
|
|
"lib/Header.js",
|
2020-05-02 11:35:42 +00:00
|
|
|
"lib/Demodulator.js",
|
|
|
|
"lib/DemodulatorPanel.js",
|
2021-03-30 16:19:23 +00:00
|
|
|
"lib/BookmarkLocalStorage.js",
|
2020-05-02 11:35:42 +00:00
|
|
|
"lib/BookmarkBar.js",
|
2020-05-03 21:56:22 +00:00
|
|
|
"lib/BookmarkDialog.js",
|
2020-05-02 11:35:42 +00:00
|
|
|
"lib/AudioEngine.js",
|
|
|
|
"lib/ProgressBar.js",
|
|
|
|
"lib/Measurement.js",
|
|
|
|
"lib/FrequencyDisplay.js",
|
2020-12-09 20:19:22 +00:00
|
|
|
"lib/MessagePanel.js",
|
2020-05-02 11:35:42 +00:00
|
|
|
"lib/Js8Threads.js",
|
|
|
|
"lib/Modes.js",
|
2021-01-16 18:40:22 +00:00
|
|
|
"lib/MetaPanel.js",
|
2020-05-02 11:57:19 +00:00
|
|
|
],
|
2020-05-10 14:12:37 +00:00
|
|
|
"map.js": [
|
|
|
|
"lib/jquery-3.2.1.min.js",
|
|
|
|
"lib/chroma.min.js",
|
|
|
|
"lib/Header.js",
|
|
|
|
"map.js",
|
|
|
|
],
|
2020-05-31 18:25:41 +00:00
|
|
|
"settings.js": [
|
|
|
|
"lib/jquery-3.2.1.min.js",
|
2021-03-03 20:51:33 +00:00
|
|
|
"lib/bootstrap.bundle.min.js",
|
2021-03-30 23:22:39 +00:00
|
|
|
"lib/location-picker.min.js",
|
2020-05-31 18:25:41 +00:00
|
|
|
"lib/Header.js",
|
2021-02-15 19:19:43 +00:00
|
|
|
"lib/settings/MapInput.js",
|
2021-02-08 22:29:24 +00:00
|
|
|
"lib/settings/ImageUpload.js",
|
2021-03-30 16:19:23 +00:00
|
|
|
"lib/BookmarkLocalStorage.js",
|
2021-02-13 22:53:16 +00:00
|
|
|
"lib/settings/BookmarkTable.js",
|
2021-02-15 19:19:43 +00:00
|
|
|
"lib/settings/WsjtDecodingDepthsInput.js",
|
2021-02-16 17:35:18 +00:00
|
|
|
"lib/settings/WaterfallDropdown.js",
|
2021-02-19 20:07:13 +00:00
|
|
|
"lib/settings/GainInput.js",
|
2021-02-22 22:49:28 +00:00
|
|
|
"lib/settings/OptionalSection.js",
|
2021-02-24 16:12:23 +00:00
|
|
|
"lib/settings/SchedulerInput.js",
|
2021-02-27 22:14:41 +00:00
|
|
|
"lib/settings/ExponentialInput.js",
|
2022-12-10 18:50:26 +00:00
|
|
|
"lib/settings/LogMessages.js",
|
2020-05-31 18:25:41 +00:00
|
|
|
"settings.js",
|
2021-01-20 16:01:46 +00:00
|
|
|
],
|
2020-05-02 11:35:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
def indexAction(self):
|
|
|
|
profileName = self.request.matches.group(1)
|
|
|
|
if profileName not in CompiledAssetsController.profiles:
|
|
|
|
self.send_response("profile not found", code=404)
|
2020-05-02 14:59:27 +00:00
|
|
|
return
|
2020-05-02 11:35:42 +00:00
|
|
|
|
|
|
|
files = CompiledAssetsController.profiles[profileName]
|
|
|
|
files = [pkg_resources.resource_filename("htdocs", f) for f in files]
|
|
|
|
|
|
|
|
modified = self.getModified(files)
|
|
|
|
|
2020-06-21 20:35:40 +00:00
|
|
|
if not self.wasModified(files):
|
|
|
|
self.send_response("", code=304)
|
|
|
|
return
|
2020-05-02 11:35:42 +00:00
|
|
|
|
|
|
|
contents = [self.getContents(f) for f in files]
|
|
|
|
|
2021-02-10 23:20:17 +00:00
|
|
|
(content_type, encoding) = mimetypes.guess_type(profileName)
|
2020-05-02 11:35:42 +00:00
|
|
|
self.send_response("\n".join(contents), content_type=content_type, last_modified=modified, max_age=3600)
|
|
|
|
|
|
|
|
def getContents(self, file):
|
|
|
|
with open(file) as f:
|
|
|
|
return f.read()
|
|
|
|
|
|
|
|
def getModified(self, files):
|
2020-07-01 17:10:46 +00:00
|
|
|
modified = [os.path.getmtime(f) for f in files]
|
|
|
|
return datetime.fromtimestamp(max(*modified), timezone.utc)
|