Merge branch 'files'
This commit is contained in:
commit
91db9c603a
|
|
@ -0,0 +1,33 @@
|
|||
@import url("openwebrx-header.css");
|
||||
@import url("openwebrx-globals.css");
|
||||
|
||||
html, body {
|
||||
height: unset;
|
||||
}
|
||||
|
||||
body {
|
||||
margin-bottom: 5rem;
|
||||
}
|
||||
|
||||
hr {
|
||||
background: #444;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 1em 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: separate;
|
||||
border-spacing: 15px;
|
||||
}
|
||||
|
||||
td {
|
||||
text-align: center;
|
||||
border: 3px dotted;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
}
|
||||
|
|
@ -158,8 +158,8 @@ input[type=range]:disabled {
|
|||
{
|
||||
height: 100%;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#openwebrx-scale-container
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
<HTML><HEAD>
|
||||
<TITLE>OpenWebRX+ Received Files</TITLE>
|
||||
<link rel="shortcut icon" type="image/x-icon" href="static/favicon.ico" />
|
||||
<link rel="stylesheet" href="static/css/bootstrap.min.css" />
|
||||
<link rel="stylesheet" type="text/css" href="static/css/files.css" />
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/showdown/1.9.0/showdown.min.js"></script>
|
||||
<script src="static/lib/jquery-3.2.1.min.js"></script>
|
||||
<script src="static/lib/Header.js"></script>
|
||||
</HEAD><BODY>
|
||||
${header}
|
||||
<div class="container">
|
||||
<h1>OpenWebRX+ Received Files</h1>
|
||||
<table>
|
||||
${rows}
|
||||
</table>
|
||||
</div>
|
||||
</BODY></HTML>
|
||||
|
|
@ -11,6 +11,7 @@
|
|||
<div class="button" data-toggle-panel="openwebrx-panel-log"><svg viewBox="0 0 80 80"><use xlink:href="${document_root}static/gfx/svg-defs.svg#panel-log"></use></svg><br/>Log</div>
|
||||
<div class="button" data-toggle-panel="openwebrx-panel-receiver"><svg viewBox="0 0 80 80"><use xlink:href="${document_root}static/gfx/svg-defs.svg#panel-receiver"></use></svg><br/>Receiver</div>
|
||||
<a class="button" href="${document_root}map" target="openwebrx-map"><svg viewBox="0 0 80 80"><use xlink:href="${document_root}static/gfx/svg-defs.svg#panel-map"></use></svg><br/>Map</a>
|
||||
<a class="button" href="${document_root}files" target="openwebrx-files"><svg viewBox="0 0 80 80"><use xlink:href="${document_root}static/gfx/svg-defs.svg#panel-log"></use></svg><br/>Files</a>
|
||||
<a class="button" href="${document_root}settings" target="openwebrx-settings"><svg viewBox="0 0 80 80"><use xlink:href="${document_root}static/gfx/svg-defs.svg#panel-settings"></use></svg><br/>Settings</a>
|
||||
|
||||
<!-- session timeout and use policy page display -->
|
||||
|
|
|
|||
|
|
@ -297,10 +297,12 @@ SstvMessagePanel.prototype.pushMessage = function(msg) {
|
|||
// $b.scrollTop($b[0].scrollHeight);
|
||||
}
|
||||
else if(msg.width>0 && msg.height>0 && !msg.hasOwnProperty('line')) {
|
||||
var h = '' + msg.timestamp + ' ' + msg.width + 'x' + msg.height +
|
||||
' ' + msg.sstvMode + '<br>';
|
||||
var c = '<canvas width="' + msg.width + '" height="' + msg.height +
|
||||
'" class="frame"></canvas>';
|
||||
var h = '<div>' + msg.timestamp + ' ' + msg.width + 'x' + msg.height +
|
||||
' ' + msg.sstvMode + '</div>';
|
||||
var c = '<div onclick="saveCanvas(\'' + msg.filename + '\');">' +
|
||||
'<canvas class="frame" id="' + msg.filename +
|
||||
'" width="' + msg.width + '" height="' + msg.height +
|
||||
'"></canvas></div>';
|
||||
// Append a new canvas
|
||||
$b.append($('<tr><td class="message">' + h + c + '</td></tr>'));
|
||||
$b.scrollTop($b[0].scrollHeight);
|
||||
|
|
|
|||
|
|
@ -90,6 +90,26 @@ function toggleRecording() {
|
|||
}
|
||||
}
|
||||
|
||||
function saveCanvas(canvas) {
|
||||
// Get canvas by its ID
|
||||
var c = document.getElementById(canvas);
|
||||
if (c == null) return;
|
||||
|
||||
// Create and click a link to the canvas data URL
|
||||
var a = document.createElement('a');
|
||||
a.href = c.toDataURL('image/png');
|
||||
a.style = 'display: none';
|
||||
a.download = canvas + ".png";
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
|
||||
// Get rid of the canvas data URL
|
||||
setTimeout(function() {
|
||||
document.body.removeChild(a);
|
||||
window.URL.revokeObjectURL(a.href);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
function zoomInOneStep() {
|
||||
zoom_set(zoom_level + 1);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,12 @@
|
|||
from owrx.config import ConfigError
|
||||
from configparser import ConfigParser
|
||||
import os
|
||||
import re
|
||||
from glob import glob
|
||||
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class CoreConfig(object):
|
||||
defaults = {
|
||||
|
|
@ -46,6 +50,28 @@ class CoreConfig(object):
|
|||
if not os.access(dir, os.W_OK):
|
||||
raise ConfigError(key, "{dir} is not writable".format(dir=dir))
|
||||
|
||||
# Get complete path to a stored file from its filename by
|
||||
# adding folder name
|
||||
def getStoredFilePath(self, filename):
|
||||
return self.get_temporary_directory() + "/" + filename
|
||||
|
||||
# Get list of stored files, sorted in reverse alphabetic order
|
||||
# (so that newer files appear first)
|
||||
def getStoredFiles(self):
|
||||
dir = self.get_temporary_directory()
|
||||
files = [f for f in os.listdir(dir) if re.match(r"SSTV-[0-9]+-[0-9]+\.bmp", f)]
|
||||
return sorted(files, reverse=True)
|
||||
|
||||
# Delete all stored files except for <keepN> newest ones
|
||||
def cleanStoredFiles(self, keepN):
|
||||
files = self.getStoredFiles()
|
||||
for f in files[keepN:]:
|
||||
logger.debug("Deleting stored file '%s'." % f)
|
||||
try:
|
||||
os.unlink(self.getStoredFilePath(f))
|
||||
except Exception as exptn:
|
||||
logger.debug(str(exptn))
|
||||
|
||||
def get_web_port(self):
|
||||
return self.web_port
|
||||
|
||||
|
|
@ -57,3 +83,4 @@ class CoreConfig(object):
|
|||
|
||||
def get_aprs_symbols_path(self):
|
||||
return self.aprs_symbols_path
|
||||
|
||||
|
|
|
|||
|
|
@ -162,6 +162,7 @@ defaultConfig = PropertyLayer(
|
|||
callsign_url="https://www.qrzcq.com/call/{}",
|
||||
usage_policy_url="policy",
|
||||
session_timeout=0,
|
||||
keep_files=20,
|
||||
decoding_queue_workers=2,
|
||||
decoding_queue_length=10,
|
||||
wsjt_decoding_depth=3,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
from owrx.controllers.template import WebpageController
|
||||
from owrx.controllers.assets import AssetsController
|
||||
from owrx.config.core import CoreConfig
|
||||
|
||||
class FileController(AssetsController):
|
||||
def getFilePath(self, file):
|
||||
return CoreConfig().getStoredFilePath(file)
|
||||
|
||||
|
||||
class FilesController(WebpageController):
|
||||
def template_variables(self):
|
||||
files = CoreConfig().getStoredFiles()
|
||||
rows = ""
|
||||
|
||||
for i in range(len(files)):
|
||||
# Start a row
|
||||
if i % 3 == 0:
|
||||
rows += '<tr>\n'
|
||||
# Print out individual tiles
|
||||
rows += ('<td class="file-tile">' +
|
||||
('<a href="/files/%s" download="%s">' % (files[i], files[i])) +
|
||||
('<img src="/files/%s" download="%s">' % (files[i], files[i])) +
|
||||
('<p align="center">%s</p>' % files[i]) +
|
||||
'</a></td>\n')
|
||||
# Finish a row
|
||||
if i % 3 == 2:
|
||||
rows += '</tr>\n'
|
||||
|
||||
# Finish final row
|
||||
if len(files) > 0 and len(files) % 3 != 0:
|
||||
rows += '</tr>\n'
|
||||
|
||||
variables = super().template_variables()
|
||||
variables["rows"] = rows
|
||||
return variables
|
||||
|
||||
def indexAction(self):
|
||||
self.serve_template("files.html", **self.template_variables())
|
||||
|
|
@ -70,18 +70,24 @@ class GeneralSettingsController(SettingsFormController):
|
|||
NumberInput(
|
||||
"max_clients",
|
||||
"Maximum number of clients",
|
||||
infotext="Number of people who can connect at the same time.",
|
||||
),
|
||||
NumberInput(
|
||||
"keep_files",
|
||||
"Maximum number of files",
|
||||
infotext="Number of received images and other files to keep.",
|
||||
),
|
||||
NumberInput(
|
||||
"session_timeout",
|
||||
"Session timeout",
|
||||
infotext="User session timeout in seconds (0 to disable timeout).",
|
||||
infotext="Client session timeout in seconds (0 to disable timeout).",
|
||||
append="secs",
|
||||
),
|
||||
TextInput(
|
||||
"usage_policy_url",
|
||||
"Usage policy URL",
|
||||
infotext="Specifies web page describing receiver usage policy "
|
||||
+ "and shown when the user session times out.",
|
||||
+ "and shown when a client session times out.",
|
||||
),
|
||||
),
|
||||
Section(
|
||||
|
|
|
|||
|
|
@ -19,7 +19,8 @@ class ReceiverDetails(PropertyFilter):
|
|||
"photo_title",
|
||||
"photo_desc",
|
||||
"usage_policy_url",
|
||||
"session_timeout",
|
||||
"session_timeout",
|
||||
"keep_files",
|
||||
)
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
from owrx.controllers.status import StatusController
|
||||
from owrx.controllers.template import IndexController, MapController, PolicyController
|
||||
from owrx.controllers.feature import FeatureController
|
||||
from owrx.controllers.file import FilesController, FileController
|
||||
from owrx.controllers.assets import OwrxAssetsController, AprsSymbolsController, CompiledAssetsController
|
||||
from owrx.controllers.websocket import WebSocketController
|
||||
from owrx.controllers.api import ApiController
|
||||
|
|
@ -96,6 +97,8 @@ class Router(object):
|
|||
StaticRoute("/map", MapController),
|
||||
StaticRoute("/policy", PolicyController),
|
||||
StaticRoute("/features", FeatureController),
|
||||
StaticRoute("/files", FilesController),
|
||||
RegexRoute("^/files/(SSTV-[0-9]+-[0-9]+\.bmp)$", FileController),
|
||||
StaticRoute("/api/features", ApiController),
|
||||
StaticRoute("/metrics", MetricsController, options={"action": "prometheusAction"}),
|
||||
StaticRoute("/metrics.json", MetricsController),
|
||||
|
|
|
|||
10
owrx/sstv.py
10
owrx/sstv.py
|
|
@ -1,4 +1,5 @@
|
|||
from owrx.config.core import CoreConfig
|
||||
from owrx.config import Config
|
||||
from csdr.module import ThreadModule
|
||||
from pycsdr.types import Format
|
||||
from datetime import datetime
|
||||
|
|
@ -80,7 +81,14 @@ class SstvParser(ThreadModule):
|
|||
if self.height==0 or self.line<self.height:
|
||||
logger.debug("Deleting short bitmap file '%s'." % self.fileName)
|
||||
os.unlink(self.fileName)
|
||||
except Exception:
|
||||
else:
|
||||
# Delete excessive files from storage
|
||||
logger.debug("Performing storage cleanup...")
|
||||
pm = Config.get()
|
||||
CoreConfig().cleanStoredFiles(pm["keep_files"])
|
||||
|
||||
except Exception as exptn:
|
||||
logger.debug(str(exptn))
|
||||
self.file = None
|
||||
|
||||
def newFile(self, fileName):
|
||||
|
|
|
|||
Loading…
Reference in New Issue