Added underlying modulation to bookmarks, refactored bookmarks.

This commit is contained in:
Marat Fayzullin 2024-10-28 14:32:01 -04:00
parent 309274a6af
commit ce9d93d4f3
9 changed files with 239 additions and 83 deletions

View File

@ -82,7 +82,8 @@ h1 {
/* col-1 */ /* col-1 */
.bookmarks table [data-editor="name"] { .bookmarks table [data-editor="name"] {
width: 35%; white-space: nowrap;
width: 29%;
} }
/* col-2 */ /* col-2 */
@ -101,11 +102,24 @@ h1 {
} }
/* col-4 */ /* col-4 */
.bookmarks table [data-editor="description"] { .bookmarks table [data-editor="underlying"] {
width: 35%; white-space: nowrap;
min-width: 160px;
width: 10%;
} }
/* col-5 */ /* col-5 */
.bookmarks table [data-editor="description"] {
width: 29%;
}
/* col-6 */
.bookmarks table [data-editor="scannable"] {
width: 2%;
text-align: center;
}
/* col-7 */
.bookmarks table tr td:last-child, .bookmarks table tr th:last-child { .bookmarks table tr td:last-child, .bookmarks table tr th:last-child {
text-align: right; text-align: right;
width: 10%; width: 10%;

View File

@ -1515,14 +1515,13 @@ img.openwebrx-mirror-img
background-color: #575757; background-color: #575757;
padding: 10px; padding: 10px;
color: white; color: white;
position: fixed;
font-size: 10pt; font-size: 10pt;
border-radius: 15px; border-radius: 15px;
-moz-border-radius: 15px; -moz-border-radius: 15px;
position: fixed; position: fixed;
left: 50%; left: 50%;
top: 50%; top: 50%;
transform: translate(-50%, 0); transform: translate(-50%, -50%);
} }
.openwebrx-dialog .form-field { .openwebrx-dialog .form-field {

View File

@ -378,12 +378,16 @@
</div> </div>
<div class="form-field"> <div class="form-field">
<label for="frequency">Frequency:</label> <label for="frequency">Frequency:</label>
<input type="number" id="frequency" name="frequency"> <input type="number" id="frequency" name="frequency" min="1">
</div> </div>
<div class="form-field"> <div class="form-field">
<label for="modulation">Modulation:</label> <label for="modulation">Modulation:</label>
<select name="modulation" id="modulation"></select> <select name="modulation" id="modulation"></select>
</div> </div>
<div class="form-field">
<label for="underlying">Underlying:</label>
<select name="underlying" id="underlying"></select>
</div>
<div class="form-field"> <div class="form-field">
<label for="name">Description:</label> <label for="name">Description:</label>
<input type="text" id="description" name="description"> <input type="text" id="description" name="description">

View File

@ -2,7 +2,7 @@ function BookmarkBar() {
var me = this; var me = this;
me.modesToScan = ['lsb', 'usb', 'cw', 'am', 'sam', 'nfm']; me.modesToScan = ['lsb', 'usb', 'cw', 'am', 'sam', 'nfm'];
me.localBookmarks = new BookmarkLocalStorage(); me.localBookmarks = new BookmarkLocalStorage();
me.$container = $("#openwebrx-bookmarks-container"); me.$container = $('#openwebrx-bookmarks-container');
me.bookmarks = {}; me.bookmarks = {};
me.$container.on('click', '.bookmark', function(e){ me.$container.on('click', '.bookmark', function(e){
@ -11,9 +11,7 @@ function BookmarkBar() {
var b = $bookmark.data(); var b = $bookmark.data();
if (!b || !b.frequency || !b.modulation) return; if (!b || !b.frequency || !b.modulation) return;
me.getDemodulator().set_offset_frequency(b.frequency - center_freq); me.getDemodulator().set_offset_frequency(b.frequency - center_freq);
if (b.modulation) {
me.getDemodulatorPanel().setMode(b.modulation, b.underlying); me.getDemodulatorPanel().setMode(b.modulation, b.underlying);
}
$bookmark.addClass('selected'); $bookmark.addClass('selected');
stopScanner(); stopScanner();
}); });
@ -41,7 +39,7 @@ function BookmarkBar() {
me.showEditDialog(); me.showEditDialog();
}); });
me.$dialog = $("#openwebrx-dialog-bookmark"); me.$dialog = $('#openwebrx-dialog-bookmark');
me.$dialog.find('.openwebrx-button[data-action=cancel]').click(function(){ me.$dialog.find('.openwebrx-button[data-action=cancel]').click(function(){
me.$dialog.hide(); me.$dialog.hide();
}); });
@ -108,25 +106,60 @@ BookmarkBar.prototype.render = function(){
BookmarkBar.prototype.showEditDialog = function(bookmark) { BookmarkBar.prototype.showEditDialog = function(bookmark) {
if (!bookmark) { if (!bookmark) {
var mode = this.getDemodulator().get_secondary_demod() || this.getDemodulator().get_modulation(); var mode1 = this.getDemodulator().get_secondary_demod()
var mode2 = this.getDemodulator().get_modulation();
// if no secondary demod, use the primary one
if (!mode1) { mode1 = mode2; mode2 = ''; }
bookmark = { bookmark = {
name: "", name: '',
frequency: center_freq + this.getDemodulator().get_offset_frequency(), frequency: center_freq + this.getDemodulator().get_offset_frequency(),
modulation: mode, modulation: mode1,
description: "", underlying: mode2,
scannable : this.modesToScan.indexOf(mode) >= 0 description: '',
scannable : this.modesToScan.indexOf(mode1) >= 0
} }
this.sanitizeBookmark(bookmark);
} }
this.$dialog.bookmarkDialog().setValues(bookmark); this.$dialog.bookmarkDialog().setValues(bookmark);
this.$dialog.show(); this.$dialog.show();
this.$dialog.find('#name').focus(); this.$dialog.find('#name').focus();
}; };
BookmarkBar.prototype.sanitizeBookmark = function(b) {
// must have name, frequency, and modulation
if (!b.name || !b.frequency || !b.modulation)
return "Must have name, frequency, and modulation.";
// must have non-empty name
b.name = b.name.trim();
if (b.name.length <= 0) return "Must have a non-empty name.";
// must have positive frequency
b.frequency = Number(b.frequency);
if (b.frequency <= 0) return "Frequency must be positive.";
// must have valid modulation
var mode = Modes.findByModulation(b.modulation);
if (!mode) return "Must have valid modulation."
// check that underlying demodulator is valid
if (!b.underlying)
b.underlying = '';
else if (!mode.underlying)
return "Must not have underlying modulation.";
else if (mode.underlying.indexOf(b.underlying) < 0)
return "Must have valid underlying modulation.";
return null;
};
BookmarkBar.prototype.storeBookmark = function() { BookmarkBar.prototype.storeBookmark = function() {
var me = this; var me = this;
var bookmark = this.$dialog.bookmarkDialog().getValues(); var bookmark = this.$dialog.bookmarkDialog().getValues();
if (!bookmark) return; if (!bookmark) return;
bookmark.frequency = Number(bookmark.frequency);
var error = this.sanitizeBookmark(bookmark);
if (error) { alert(error); return; }
var bookmarks = me.localBookmarks.getBookmarks(); var bookmarks = me.localBookmarks.getBookmarks();

View File

@ -9,9 +9,18 @@ $.fn.bookmarkDialog = function() {
}).join('')); }).join(''));
return this; return this;
}, },
setUnderlying: function(modes) {
$el.find('#underlying').html('<option value="">None</option>' +
modes.filter(function(m) {
return m.isAvailable() && !m.underlying && m.type === 'analog';
}).map(function(m) {
return '<option value="' + m.modulation + '">' + m.name + '</option>';
}).join(''));
return this;
},
setValues: function(bookmark) { setValues: function(bookmark) {
var $form = $el.find('form'); var $form = $el.find('form');
['name', 'frequency', 'modulation', 'description', 'scannable'].forEach(function(key){ ['name', 'frequency', 'modulation', 'underlying', 'description', 'scannable'].forEach(function(key) {
var $input = $form.find('#' + key); var $input = $form.find('#' + key);
if ($input.is(':checkbox')) { if ($input.is(':checkbox')) {
$input.prop('checked', bookmark[key]); $input.prop('checked', bookmark[key]);
@ -25,7 +34,7 @@ $.fn.bookmarkDialog = function() {
getValues: function() { getValues: function() {
var bookmark = {}; var bookmark = {};
var valid = true; var valid = true;
['name', 'frequency', 'modulation', 'description', 'scannable'].forEach(function(key){ ['name', 'frequency', 'modulation', 'underlying', 'description', 'scannable'].forEach(function(key) {
var $input = $el.find('#' + key); var $input = $el.find('#' + key);
valid = valid && $input[0].checkValidity(); valid = valid && $input[0].checkValidity();
bookmark[key] = $input.is(':checkbox')? $input.is(':checked') : $input.val(); bookmark[key] = $input.is(':checkbox')? $input.is(':checked') : $input.val();
@ -38,4 +47,4 @@ $.fn.bookmarkDialog = function() {
return bookmark; return bookmark;
} }
} }
} };

View File

@ -5,7 +5,9 @@ var Modes = {
setModes:function(json){ setModes:function(json){
this.modes = json.map(function(m){ return new Mode(m); }); this.modes = json.map(function(m){ return new Mode(m); });
this.updatePanels(); this.updatePanels();
$('#openwebrx-dialog-bookmark').bookmarkDialog().setModes(this.modes); var bookmarkDialog = $('#openwebrx-dialog-bookmark').bookmarkDialog();
bookmarkDialog.setUnderlying(this.modes);
bookmarkDialog.setModes(this.modes);
}, },
getModes:function(){ getModes:function(){
return this.modes; return this.modes;

View File

@ -177,8 +177,8 @@ ModulationEditor.prototype = new Editor();
ModulationEditor.prototype.getInputHtml = function() { ModulationEditor.prototype.getInputHtml = function() {
return '<select class="form-control form-control-sm">' + return '<select class="form-control form-control-sm">' +
$.map(this.modes, function(name, modulation) { $.map(this.modes, function(mode, name) {
return '<option value="' + modulation + '">' + name + '</option>'; return '<option value="' + name + '">' + mode.name + '</option>';
}).join('') + }).join('') +
'</select>'; '</select>';
}; };
@ -188,6 +188,30 @@ ModulationEditor.prototype.getHtml = function() {
return $option.html(); return $option.html();
}; };
function UnderlyingEditor(table) {
Editor.call(this, table);
this.modes = table.data('modes');
}
UnderlyingEditor.prototype = new Editor();
UnderlyingEditor.prototype.getInputHtml = function() {
return '<select class="form-control form-control-sm">' +
'<option value="">None</option>' +
$.map(this.modes, function(mode, name) {
if (mode.analog && !mode.underlying.length)
return '<option value="' + name + '">' + mode.name + '</option>';
else
return '';
}).join('') +
'</select>';
};
UnderlyingEditor.prototype.getHtml = function() {
var $option = this.input.find('option:selected')
return $option? $option.html() : '';
};
function DescriptionEditor(table) { function DescriptionEditor(table) {
Editor.call(this, table); Editor.call(this, table);
} }
@ -220,11 +244,16 @@ ScannableEditor.prototype.getHtml = function() {
return this.getValue()? '&check;' : ''; return this.getValue()? '&check;' : '';
}; };
var renderModulation = function(m, modes) {
return !m? 'None' : m in modes? modes[m].name : m;
}
$.fn.bookmarktable = function() { $.fn.bookmarktable = function() {
var editors = { var editors = {
name: NameEditor, name: NameEditor,
frequency: FrequencyEditor, frequency: FrequencyEditor,
modulation: ModulationEditor, modulation: ModulationEditor,
underlying: UnderlyingEditor,
description: DescriptionEditor, description: DescriptionEditor,
scannable: ScannableEditor scannable: ScannableEditor
}; };
@ -255,6 +284,8 @@ $.fn.bookmarktable = function() {
}).done(function() { }).done(function() {
$cell.data('value', editor.getValue()); $cell.data('value', editor.getValue());
$cell.html(editor.getHtml()); $cell.html(editor.getHtml());
}).fail(function() {
$cell.html(html);
}); });
}; };
@ -337,6 +368,9 @@ $.fn.bookmarktable = function() {
data: JSON.stringify([data]), data: JSON.stringify([data]),
contentType: 'application/json', contentType: 'application/json',
method: 'POST' method: 'POST'
}).fail(function(data) {
// adding failed, reenable inputs
$.map(inputs, function(input, name) { input.disable(false); });
}).done(function(data) { }).done(function(data) {
if (data.length && data.length === 1 && 'bookmark_id' in data[0]) { if (data.length && data.length === 1 && 'bookmark_id' in data[0]) {
row.attr('data-id', data[0]['bookmark_id']); row.attr('data-id', data[0]['bookmark_id']);
@ -347,16 +381,16 @@ $.fn.bookmarktable = function() {
td.data('value', input.getValue()); td.data('value', input.getValue());
td.html(input.getHtml()); td.html(input.getHtml());
}); });
}
// remove inputs
var $cell = row.find('td').last(); var $cell = row.find('td').last();
var $group = $cell.find('.btn-group'); var $group = $cell.find('.btn-group');
if ($group.length) { if ($group.length) {
$group.remove; $group.remove;
$cell.html('<div class="btn btn-sm btn-danger bookmark-delete">delete</div>'); $cell.html('<div class="btn btn-sm btn-danger bookmark-delete">delete</div>');
} }
}
}); });
}); });
$table.append(row); $table.append(row);
@ -372,16 +406,13 @@ $.fn.bookmarktable = function() {
var modes = $table.data('modes'); var modes = $table.data('modes');
var $list = $('<table class="table table-sm">'); var $list = $('<table class="table table-sm">');
$list.append(bookmarks.map(function(b) { $list.append(bookmarks.map(function(b) {
var modulation = b.modulation;
if (modulation in modes) {
modulation = modes[modulation];
}
var row = $( var row = $(
'<tr>' + '<tr>' +
'<td><input class="form-check-input select" type="checkbox"></td>' + '<td><input class="form-check-input select" type="checkbox">&nbsp;</td>' +
'<td>' + b.name + '</td>' + '<td>' + b.name + '</td>' +
'<td class="frequency">' + renderFrequency(b.frequency) + '</td>' + '<td class="frequency">' + renderFrequency(b.frequency) + '</td>' +
'<td>' + modulation + '</td>' + '<td>' + renderModulation(b.modulation, modes) + '</td>' +
// '<td>' + renderModulation(b.underlying, modes) + '</td>' +
'</tr>' '</tr>'
); );
row.data('bookmark', b); row.data('bookmark', b);
@ -407,31 +438,30 @@ $.fn.bookmarktable = function() {
data: JSON.stringify(selected), data: JSON.stringify(selected),
contentType: 'application/json', contentType: 'application/json',
method: 'POST' method: 'POST'
}).fail(function(data) {
// import failed
$table.find('.emptytext').remove();
}).done(function(data) { }).done(function(data) {
$table.find('.emptytext').remove(); $table.find('.emptytext').remove();
var modes = $table.data('modes'); var modes = $table.data('modes');
if (data.length && data.length == selected.length) { if (data.length && data.length == selected.length) {
$table.append(data.map(function(obj, index) { $table.append(data.map(function(obj, index) {
var bookmark = selected[index]; var b = selected[index];
var modulation_name = bookmark.modulation; // provide reasonable defaults for missing fields
if (modulation_name in modes) { if (!('underlying' in b)) b.underlying = '';
modulation_name = modes[modulation_name]; if (!('description' in b)) b.description = '';
} if (!('scannable' in b)) {
// provide reasonable default for missing fields
if (!('description' in bookmark)) {
bookmark.description = '';
}
if (!('scannable' in bookmark)) {
var modesToScan = ['lsb', 'usb', 'cw', 'am', 'sam', 'nfm']; var modesToScan = ['lsb', 'usb', 'cw', 'am', 'sam', 'nfm'];
bookmark.scannable = modesToScan.indexOf(bookmark.modulation) >= 0; b.scannable = modesToScan.indexOf(b.modulation) >= 0;
} }
return $( return $(
'<tr data-id="' + obj.bookmark_id + '">' + '<tr data-id="' + obj.bookmark_id + '">' +
'<td data-editor="name" data-value="' + bookmark.name + '">' + bookmark.name + '</td>' + '<td data-editor="name" data-value="' + b.name + '">' + b.name + '</td>' +
'<td data-editor="frequency" data-value="' + bookmark.frequency + '" class="frequency">' + renderFrequency(bookmark.frequency) +'</td>' + '<td data-editor="frequency" data-value="' + b.frequency + '" class="frequency">' + renderFrequency(b.frequency) +'</td>' +
'<td data-editor="modulation" data-value="' + bookmark.modulation + '">' + modulation_name + '</td>' + '<td data-editor="modulation" data-value="' + b.modulation + '">' + renderModulation(b.modulation, modes) + '</td>' +
'<td data-editor="description" data-value="' + bookmark.description + '">' + bookmark.description + '</td>' + '<td data-editor="underlying" data-value="' + b.underlying + '">' + renderModulation(b.underlying, modes) + '</td>' +
'<td data-editor="scannable" data-value="' + bookmark.scannable + '">' + (bookmark.scannable? '&check;':'') + '</td>' + '<td data-editor="description" data-value="' + b.description + '">' + b.description + '</td>' +
'<td data-editor="scannable" data-value="' + b.scannable + '">' + (b.scannable? '&check;':'') + '</td>' +
'<td>' + '<td>' +
'<button type="button" class="btn btn-sm btn-danger bookmark-delete">delete</button>' + '<button type="button" class="btn btn-sm btn-danger bookmark-delete">delete</button>' +
'</td>' + '</td>' +

View File

@ -16,6 +16,7 @@ class Bookmark(object):
self.name = j["name"] self.name = j["name"]
self.frequency = j["frequency"] self.frequency = j["frequency"]
self.modulation = j["modulation"] self.modulation = j["modulation"]
self.underlying = j["underlying"] if "underlying" in j else ""
self.description = j["description"] if "description" in j else "" self.description = j["description"] if "description" in j else ""
self.srcFile = srcFile self.srcFile = srcFile
# By default, only scan modulations that make sense to scan # By default, only scan modulations that make sense to scan
@ -33,6 +34,9 @@ class Bookmark(object):
def getModulation(self): def getModulation(self):
return self.modulation return self.modulation
def getUnderlying(self):
return self.underlying
def getDescription(self): def getDescription(self):
return self.description return self.description
@ -47,6 +51,7 @@ class Bookmark(object):
"name": self.getName(), "name": self.getName(),
"frequency": self.getFrequency(), "frequency": self.getFrequency(),
"modulation": self.getModulation(), "modulation": self.getModulation(),
"underlying": self.getUnderlying(),
"description": self.getDescription(), "description": self.getDescription(),
"scannable": self.isScannable(), "scannable": self.isScannable(),
} }

View File

@ -2,7 +2,7 @@ from owrx.controllers.template import WebpageController
from owrx.controllers.admin import AuthorizationMixin from owrx.controllers.admin import AuthorizationMixin
from owrx.controllers.settings import SettingsBreadcrumb from owrx.controllers.settings import SettingsBreadcrumb
from owrx.bookmarks import Bookmark, Bookmarks from owrx.bookmarks import Bookmark, Bookmarks
from owrx.modes import Modes from owrx.modes import Modes, AnalogMode
from owrx.breadcrumb import Breadcrumb, BreadcrumbItem, BreadcrumbMixin from owrx.breadcrumb import Breadcrumb, BreadcrumbItem, BreadcrumbMixin
import json import json
import math import math
@ -24,7 +24,7 @@ class BookmarksController(AuthorizationMixin, BreadcrumbMixin, WebpageController
def render_table(self): def render_table(self):
bookmarks = Bookmarks.getSharedInstance().getEditableBookmarks() bookmarks = Bookmarks.getSharedInstance().getEditableBookmarks()
emptyText = """ emptyText = """
<tr class="emptytext"><td colspan="4"> <tr class="emptytext"><td colspan="7">
No bookmarks in storage. You can add new bookmarks using the buttons below. No bookmarks in storage. You can add new bookmarks using the buttons below.
</td></tr> </td></tr>
""" """
@ -35,6 +35,7 @@ class BookmarksController(AuthorizationMixin, BreadcrumbMixin, WebpageController
<th>Name</th> <th>Name</th>
<th class="frequency">Frequency</th> <th class="frequency">Frequency</th>
<th>Modulation</th> <th>Modulation</th>
<th>Underlying</th>
<th>Description</th> <th>Description</th>
<th>Scan</th> <th>Scan</th>
<th>Actions</th> <th>Actions</th>
@ -43,7 +44,11 @@ class BookmarksController(AuthorizationMixin, BreadcrumbMixin, WebpageController
</table> </table>
""".format( """.format(
bookmarks="".join(self.render_bookmark(b) for b in bookmarks) if bookmarks else emptyText, bookmarks="".join(self.render_bookmark(b) for b in bookmarks) if bookmarks else emptyText,
modes=json.dumps({m.modulation: m.name for m in Modes.getAvailableModes()}), modes=json.dumps({m.modulation: {
"name" : m.name,
"analog" : isinstance(m, AnalogMode),
"underlying" : m.underlying if hasattr(m, "underlying") else []
} for m in Modes.getAvailableModes()}),
) )
def render_bookmark(self, bookmark: Bookmark): def render_bookmark(self, bookmark: Bookmark):
@ -65,13 +70,18 @@ class BookmarksController(AuthorizationMixin, BreadcrumbMixin, WebpageController
suffix = suffixes[exp] suffix = suffixes[exp]
return "{num:g} {suffix}Hz".format(num=num, suffix=suffix) return "{num:g} {suffix}Hz".format(num=num, suffix=suffix)
mode = Modes.findByModulation(bookmark.getModulation())
scan = bookmark.isScannable() scan = bookmark.isScannable()
name1 = bookmark.getModulation()
name2 = bookmark.getUnderlying()
mode1 = Modes.findByModulation(name1)
mode2 = Modes.findByModulation(name2)
return """ return """
<tr data-id="{id}"> <tr data-id="{id}">
<td data-editor="name" data-value="{name}">{name}</td> <td data-editor="name" data-value="{name}">{name}</td>
<td data-editor="frequency" data-value="{frequency}" class="frequency">{rendered_frequency}</td> <td data-editor="frequency" data-value="{frequency}" class="frequency">{rendered_frequency}</td>
<td data-editor="modulation" data-value="{modulation}">{modulation_name}</td> <td data-editor="modulation" data-value="{modulation}">{modulation_name}</td>
<td data-editor="underlying" data-value="{underlying}">{underlying_name}</td>
<td data-editor="description" data-value="{description}">{description}</td> <td data-editor="description" data-value="{description}">{description}</td>
<td data-editor="scannable" data-value="{scannable}">{scannable_check}</td> <td data-editor="scannable" data-value="{scannable}">{scannable_check}</td>
<td> <td>
@ -84,8 +94,10 @@ class BookmarksController(AuthorizationMixin, BreadcrumbMixin, WebpageController
# TODO render frequency in si units # TODO render frequency in si units
frequency=bookmark.getFrequency(), frequency=bookmark.getFrequency(),
rendered_frequency=render_frequency(bookmark.getFrequency()), rendered_frequency=render_frequency(bookmark.getFrequency()),
modulation=bookmark.getModulation() if mode is None else mode.modulation, modulation=name1 if mode1 is None else mode1.modulation,
modulation_name=bookmark.getModulation() if mode is None else mode.name, underlying=name2 if mode2 is None else mode2.modulation,
modulation_name=name1 if mode1 is None else mode1.name,
underlying_name="None" if not name2 else name2 if mode2 is None else mode2.name,
description=bookmark.getDescription(), description=bookmark.getDescription(),
scannable="true" if scan else "false", scannable="true" if scan else "false",
scannable_check="&check;" if scan else "", scannable_check="&check;" if scan else "",
@ -98,6 +110,45 @@ class BookmarksController(AuthorizationMixin, BreadcrumbMixin, WebpageController
except StopIteration: except StopIteration:
return None return None
def _sanitizeBookmark(self, data):
try:
# Must have name, frequency, modulation
if "name" not in data or "frequency" not in data or "modulation" not in data:
return "Bookmark missing required fields"
# Name must not be empty
data["name"] = data["name"].strip()
if len(data["name"]) == 0:
return "Empty bookmark name"
# Frequency must be integer
if not isinstance(data["frequency"], int):
data["frequency"] = int(data["frequency"])
# Frequency must be >0
if data["frequency"] <= 0:
return "Frequency must be positive"
# Get both modes
mode1 = Modes.findByModulation(data["modulation"]) if "modulation" in data else None
mode2 = Modes.findByModulation(data["underlying"]) if "underlying" in data else None
# Unknown main mode
if mode1 is None:
return "Invalid modulation"
# No underlying mode
if mode2 is None:
data["underlying"] = ""
else:
# Main mode has no underlying mode or underlying mode incorrect
if not hasattr(mode1, "underlying") or mode2.modulation not in mode1.underlying:
return "Incorrect underlying modulation"
# Underlying mode is at the default value
#if mode2.modulation == mode1.underlying[0]:
# data["underlying"] = ""
except Exception as e:
# Something else went horribly wrong
return str(e)
# Everything ok
return None
def update(self): def update(self):
bookmark_id = int(self.request.matches.group(1)) bookmark_id = int(self.request.matches.group(1))
bookmark = self._findBookmark(bookmark_id) bookmark = self._findBookmark(bookmark_id)
@ -105,18 +156,26 @@ class BookmarksController(AuthorizationMixin, BreadcrumbMixin, WebpageController
self.send_response("{}", content_type="application/json", code=404) self.send_response("{}", content_type="application/json", code=404)
return return
try: try:
newd = {}
data = json.loads(self.get_body().decode("utf-8")) data = json.loads(self.get_body().decode("utf-8"))
for key in ["name", "frequency", "modulation", "description", "scannable"]: for key in ["name", "frequency", "modulation", "underlying", "description", "scannable"]:
if key in data: if key in data:
value = data[key] newd[key] = data[key]
if key == "frequency": elif hasattr(bookmark, key):
value = int(value) newd[key] = getattr(bookmark, key)
setattr(bookmark, key, value) # Make sure everything is correct
error = self._sanitizeBookmark(newd)
if error is not None:
raise ValueError(error)
# Update and store bookmark
for key in newd:
setattr(bookmark, key, newd[key])
Bookmarks.getSharedInstance().store() Bookmarks.getSharedInstance().store()
# TODO this should not be called explicitly... bookmarks don't have any event capability right now, though # TODO this should not be called explicitly... bookmarks don't have any event capability right now, though
Bookmarks.getSharedInstance().notifySubscriptions(bookmark) Bookmarks.getSharedInstance().notifySubscriptions(bookmark)
self.send_response("{}", content_type="application/json", code=200) self.send_response("{}", content_type="application/json", code=200)
except json.JSONDecodeError: except (json.JSONDecodeError, ValueError) as e:
logger.warning("Failed updating bookmark: " + str(e))
self.send_response("{}", content_type="application/json", code=400) self.send_response("{}", content_type="application/json", code=400)
def new(self): def new(self):
@ -125,12 +184,12 @@ class BookmarksController(AuthorizationMixin, BreadcrumbMixin, WebpageController
def create(bookmark_data): def create(bookmark_data):
# sanitize # sanitize
data = {} data = {}
for key in ["name", "frequency", "modulation", "description", "scannable"]: for key in ["name", "frequency", "modulation", "underlying", "description", "scannable"]:
if key in bookmark_data: if key in bookmark_data:
if key == "frequency":
data[key] = int(bookmark_data[key])
else:
data[key] = bookmark_data[key] data[key] = bookmark_data[key]
error = self._sanitizeBookmark(data)
if error is not None:
raise ValueError(error)
bookmark = Bookmark(data) bookmark = Bookmark(data)
bookmarks.addBookmark(bookmark) bookmarks.addBookmark(bookmark)
return {"bookmark_id": id(bookmark)} return {"bookmark_id": id(bookmark)}
@ -140,7 +199,8 @@ class BookmarksController(AuthorizationMixin, BreadcrumbMixin, WebpageController
result = [create(b) for b in data] result = [create(b) for b in data]
bookmarks.store() bookmarks.store()
self.send_response(json.dumps(result), content_type="application/json", code=200) self.send_response(json.dumps(result), content_type="application/json", code=200)
except json.JSONDecodeError: except (json.JSONDecodeError, ValueError) as e:
logger.warning("Failed creating bookmark: " + str(e))
self.send_response("{}", content_type="application/json", code=400) self.send_response("{}", content_type="application/json", code=400)
def delete(self): def delete(self):