mirror of
https://github.com/betaflight/betaflight-configurator.git
synced 2025-07-15 20:35:23 +03:00
initial implementation of firmware cache
This commit is contained in:
parent
c5ae5d07a6
commit
bd1dbf21d0
5 changed files with 249 additions and 41 deletions
|
@ -34,7 +34,8 @@
|
||||||
{"usbDevices": [
|
{"usbDevices": [
|
||||||
{"vendorId": 1155, "productId": 57105}
|
{"vendorId": 1155, "productId": 57105}
|
||||||
]},
|
]},
|
||||||
"webview"
|
"webview",
|
||||||
|
"unlimitedStorage"
|
||||||
],
|
],
|
||||||
"sockets": {
|
"sockets": {
|
||||||
"tcp": {
|
"tcp": {
|
||||||
|
|
|
@ -36,6 +36,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"i18next": "^10.3.0",
|
"i18next": "^10.3.0",
|
||||||
"i18next-xhr-backend": "^1.5.1",
|
"i18next-xhr-backend": "^1.5.1",
|
||||||
|
"lru_map": "^0.3.3",
|
||||||
"marked": "^0.3.12"
|
"marked": "^0.3.12"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
183
src/js/FirmwareCache.js
Normal file
183
src/js/FirmwareCache.js
Normal file
|
@ -0,0 +1,183 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Caching of previously downloaded firmwares and release descriptions
|
||||||
|
*
|
||||||
|
* Depends on LRUMap for which the docs can be found here:
|
||||||
|
* https://github.com/rsms/js-lru
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {object} Descriptor Release descriptor object
|
||||||
|
* @property {string} releaseUrl
|
||||||
|
* @property {string} name
|
||||||
|
* @property {string} version
|
||||||
|
* @property {string} url
|
||||||
|
* @property {string} file
|
||||||
|
* @property {string} target
|
||||||
|
* @property {string} date
|
||||||
|
* @property {string} notes
|
||||||
|
* @property {string} status
|
||||||
|
* @see buildBoardOptions() in {@link release_checker.js}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {object} CacheItem
|
||||||
|
* @property {Descriptor} release
|
||||||
|
* @property {string} [hexdata]
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages caching of downloaded firmware files
|
||||||
|
*/
|
||||||
|
let FirmwareCache = (function() {
|
||||||
|
|
||||||
|
let MetadataStorage = (function() {
|
||||||
|
let CACHEKEY = "firmware-cache-metadata";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Array} data LRU key-value pairs
|
||||||
|
*/
|
||||||
|
function persist(data) {
|
||||||
|
let obj = {};
|
||||||
|
obj[CACHEKEY] = data;
|
||||||
|
chrome.storage.local.set(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Function} callback
|
||||||
|
*/
|
||||||
|
function load(callback) {
|
||||||
|
chrome.storage.local.get(CACHEKEY, obj => {
|
||||||
|
let entries = typeof obj === "object" && obj.hasOwnProperty(CACHEKEY)
|
||||||
|
? obj[CACHEKEY]
|
||||||
|
: [];
|
||||||
|
callback(entries);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
persist: persist,
|
||||||
|
load: load,
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
let metadataCache = new LRUMap(100);
|
||||||
|
let metadataLoaded = false;
|
||||||
|
|
||||||
|
metadataCache.shift = function() {
|
||||||
|
// remove hexdata for oldest release
|
||||||
|
let oldest = LRUMap.prototype.shift.call(this);
|
||||||
|
if (oldest !== undefined) {
|
||||||
|
/** @type {CacheItem} */
|
||||||
|
let cached = oldest[1];
|
||||||
|
let hexdataKey = withHexdataPrefix(keyOf(cached.release));
|
||||||
|
chrome.storage.local.remove(hexdataKey,
|
||||||
|
() => console.debug("Hex data removed: " + hexdataKey));
|
||||||
|
}
|
||||||
|
return oldest;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Descriptor} release
|
||||||
|
* @returns {string} A key used for caching the metadata for a release
|
||||||
|
*/
|
||||||
|
function keyOf(release) {
|
||||||
|
return release.file;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} key
|
||||||
|
* @returns {string} A key for storing the hex data for a release
|
||||||
|
*/
|
||||||
|
function withHexdataPrefix(key) {
|
||||||
|
return "hex:" + key;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Descriptor} release
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
function has(release) {
|
||||||
|
if (!metadataLoaded) {
|
||||||
|
console.warn("Cache not yet loaded");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return metadataCache.has(keyOf(release));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Descriptor} release
|
||||||
|
* @param {string} hexdata
|
||||||
|
*/
|
||||||
|
function put(release, hexdata) {
|
||||||
|
if (!metadataLoaded) {
|
||||||
|
console.warn("Cache not yet loaded");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (has(release)) {
|
||||||
|
console.debug("Firmware is already cached: " + keyOf(release));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let key = keyOf(release);
|
||||||
|
let hexdataKey = withHexdataPrefix(key);
|
||||||
|
metadataCache.set(key, {
|
||||||
|
release: release,
|
||||||
|
});
|
||||||
|
MetadataStorage.persist(metadataCache.toJSON());
|
||||||
|
let obj = {};
|
||||||
|
obj[hexdataKey] = hexdata;
|
||||||
|
chrome.storage.local.set(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Descriptor} release
|
||||||
|
* @param {Function} callback
|
||||||
|
* @returns {(CacheItem|undefined)}
|
||||||
|
*/
|
||||||
|
function get(release, callback) {
|
||||||
|
if (!metadataLoaded) {
|
||||||
|
console.warn("Cache not yet loaded");
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
let key = keyOf(release);
|
||||||
|
/** @type {CacheItem} */
|
||||||
|
let cached = metadataCache.get(key);
|
||||||
|
if (cached !== undefined) {
|
||||||
|
let hexdataKey = withHexdataPrefix(key);
|
||||||
|
chrome.storage.local.get(hexdataKey, function(obj) {
|
||||||
|
cached.hexdata = typeof obj === "object" && obj.hasOwnProperty(hexdataKey)
|
||||||
|
? obj[hexdataKey]
|
||||||
|
: null;
|
||||||
|
callback(cached);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {Array} entries
|
||||||
|
*/
|
||||||
|
function onEntriesLoaded(entries) {
|
||||||
|
let pairs = [];
|
||||||
|
for (let entry of entries) {
|
||||||
|
pairs.push([entry.key, entry.value]);
|
||||||
|
}
|
||||||
|
metadataCache.assign(pairs);
|
||||||
|
metadataLoaded = true;
|
||||||
|
console.info("Firmware cache loaded; number of entries: " + entries.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
has: has,
|
||||||
|
put: put,
|
||||||
|
get: get,
|
||||||
|
load: () => {
|
||||||
|
MetadataStorage.load(onEntriesLoaded);
|
||||||
|
},
|
||||||
|
flush: () => {
|
||||||
|
MetadataStorage.persist(metadataCache.toJSON());
|
||||||
|
metadataCache.clear();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
})();
|
|
@ -17,6 +17,8 @@ TABS.firmware_flasher.initialize = function (callback) {
|
||||||
parsed_hex = false; // parsed raw hex in array format
|
parsed_hex = false; // parsed raw hex in array format
|
||||||
|
|
||||||
$('#content').load("./tabs/firmware_flasher.html", function () {
|
$('#content').load("./tabs/firmware_flasher.html", function () {
|
||||||
|
FirmwareCache.load();
|
||||||
|
|
||||||
function parse_hex(str, callback) {
|
function parse_hex(str, callback) {
|
||||||
// parsing hex in different thread
|
// parsing hex in different thread
|
||||||
var worker = new Worker('./js/workers/hex_parser.js');
|
var worker = new Worker('./js/workers/hex_parser.js');
|
||||||
|
@ -30,6 +32,54 @@ TABS.firmware_flasher.initialize = function (callback) {
|
||||||
worker.postMessage(str);
|
worker.postMessage(str);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function process_hex(data, summary) {
|
||||||
|
intel_hex = data;
|
||||||
|
|
||||||
|
parse_hex(intel_hex, function (data) {
|
||||||
|
parsed_hex = data;
|
||||||
|
|
||||||
|
if (parsed_hex) {
|
||||||
|
if (!FirmwareCache.has(summary)) {
|
||||||
|
FirmwareCache.put(summary, intel_hex);
|
||||||
|
console.info("Release put to cache: " + summary.file);
|
||||||
|
}
|
||||||
|
|
||||||
|
var url;
|
||||||
|
|
||||||
|
$('span.progressLabel').html('<a class="save_firmware" href="#" title="Save Firmware">Loaded Online Firmware: (' + parsed_hex.bytes_total + ' bytes)</a>');
|
||||||
|
|
||||||
|
$('a.flash_firmware').removeClass('disabled');
|
||||||
|
|
||||||
|
$('div.release_info .target').text(summary.target);
|
||||||
|
$('div.release_info .name').text(summary.version).prop('href', summary.releaseUrl);
|
||||||
|
$('div.release_info .date').text(summary.date);
|
||||||
|
$('div.release_info .status').text(summary.status);
|
||||||
|
$('div.release_info .file').text(summary.file).prop('href', summary.url);
|
||||||
|
|
||||||
|
var formattedNotes = summary.notes.replace(/#(\d+)/g, '[#$1](https://github.com/betaflight/betaflight/pull/$1)');
|
||||||
|
formattedNotes = marked(formattedNotes);
|
||||||
|
$('div.release_info .notes').html(formattedNotes);
|
||||||
|
$('div.release_info .notes').find('a').each(function() {
|
||||||
|
$(this).attr('target', '_blank');
|
||||||
|
});
|
||||||
|
|
||||||
|
$('div.release_info').slideDown();
|
||||||
|
|
||||||
|
} else {
|
||||||
|
$('span.progressLabel').text(i18n.getMessage('firmwareFlasherHexCorrupted'));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function onLoadSuccess(data, summary) {
|
||||||
|
summary = typeof summary === "object"
|
||||||
|
? summary
|
||||||
|
: $('select[name="firmware_version"] option:selected').data('summary');
|
||||||
|
process_hex(data, summary);
|
||||||
|
$("a.load_remote_file").removeClass('disabled');
|
||||||
|
$("a.load_remote_file").text(i18n.getMessage('firmwareFlasherButtonLoadOnline'));
|
||||||
|
};
|
||||||
|
|
||||||
function buildBoardOptions(releaseData) {
|
function buildBoardOptions(releaseData) {
|
||||||
if (!releaseData) {
|
if (!releaseData) {
|
||||||
$('select[name="board"]').empty().append('<option value="0">Offline</option>');
|
$('select[name="board"]').empty().append('<option value="0">Offline</option>');
|
||||||
|
@ -226,7 +276,15 @@ TABS.firmware_flasher.initialize = function (callback) {
|
||||||
$('select[name="firmware_version"]').change(function(evt){
|
$('select[name="firmware_version"]').change(function(evt){
|
||||||
$('div.release_info').slideUp();
|
$('div.release_info').slideUp();
|
||||||
$('a.flash_firmware').addClass('disabled');
|
$('a.flash_firmware').addClass('disabled');
|
||||||
if (evt.target.value=="0") {
|
let release = $("option:selected", evt.target).data("summary");
|
||||||
|
let isCached = FirmwareCache.has(release);
|
||||||
|
if (evt.target.value=="0" || isCached) {
|
||||||
|
if (isCached) {
|
||||||
|
FirmwareCache.get(release, cached => {
|
||||||
|
console.info("Release found in cache: " + release.file);
|
||||||
|
onLoadSuccess(cached.hexdata, release);
|
||||||
|
});
|
||||||
|
}
|
||||||
$("a.load_remote_file").addClass('disabled');
|
$("a.load_remote_file").addClass('disabled');
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
@ -241,40 +299,6 @@ TABS.firmware_flasher.initialize = function (callback) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
function process_hex(data, summary) {
|
|
||||||
intel_hex = data;
|
|
||||||
|
|
||||||
parse_hex(intel_hex, function (data) {
|
|
||||||
parsed_hex = data;
|
|
||||||
|
|
||||||
if (parsed_hex) {
|
|
||||||
var url;
|
|
||||||
|
|
||||||
$('span.progressLabel').html('<a class="save_firmware" href="#" title="Save Firmware">Loaded Online Firmware: (' + parsed_hex.bytes_total + ' bytes)</a>');
|
|
||||||
|
|
||||||
$('a.flash_firmware').removeClass('disabled');
|
|
||||||
|
|
||||||
$('div.release_info .target').text(summary.target);
|
|
||||||
$('div.release_info .name').text(summary.version).prop('href', summary.releaseUrl);
|
|
||||||
$('div.release_info .date').text(summary.date);
|
|
||||||
$('div.release_info .status').text(summary.status);
|
|
||||||
$('div.release_info .file').text(summary.file).prop('href', summary.url);
|
|
||||||
|
|
||||||
var formattedNotes = summary.notes.replace(/#(\d+)/g, '[#$1](https://github.com/betaflight/betaflight/pull/$1)');
|
|
||||||
formattedNotes = marked(formattedNotes);
|
|
||||||
$('div.release_info .notes').html(formattedNotes);
|
|
||||||
$('div.release_info .notes').find('a').each(function() {
|
|
||||||
$(this).attr('target', '_blank');
|
|
||||||
});
|
|
||||||
|
|
||||||
$('div.release_info').slideDown();
|
|
||||||
|
|
||||||
} else {
|
|
||||||
$('span.progressLabel').text(i18n.getMessage('firmwareFlasherHexCorrupted'));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function failed_to_load() {
|
function failed_to_load() {
|
||||||
$('span.progressLabel').text(i18n.getMessage('firmwareFlasherFailedToLoadOnlineFirmware'));
|
$('span.progressLabel').text(i18n.getMessage('firmwareFlasherFailedToLoadOnlineFirmware'));
|
||||||
$('a.flash_firmware').addClass('disabled');
|
$('a.flash_firmware').addClass('disabled');
|
||||||
|
@ -286,11 +310,7 @@ TABS.firmware_flasher.initialize = function (callback) {
|
||||||
if (summary) { // undefined while list is loading or while running offline
|
if (summary) { // undefined while list is loading or while running offline
|
||||||
$("a.load_remote_file").text(i18n.getMessage('firmwareFlasherButtonDownloading'));
|
$("a.load_remote_file").text(i18n.getMessage('firmwareFlasherButtonDownloading'));
|
||||||
$("a.load_remote_file").addClass('disabled');
|
$("a.load_remote_file").addClass('disabled');
|
||||||
$.get(summary.url, function (data) {
|
$.get(summary.url, onLoadSuccess).fail(failed_to_load);
|
||||||
process_hex(data, summary);
|
|
||||||
$("a.load_remote_file").removeClass('disabled');
|
|
||||||
$("a.load_remote_file").text(i18n.getMessage('firmwareFlasherButtonLoadOnline'));
|
|
||||||
}).fail(failed_to_load);
|
|
||||||
} else {
|
} else {
|
||||||
$('span.progressLabel').text(i18n.getMessage('firmwareFlasherFailedToLoadOnlineFirmware'));
|
$('span.progressLabel').text(i18n.getMessage('firmwareFlasherFailedToLoadOnlineFirmware'));
|
||||||
}
|
}
|
||||||
|
@ -507,6 +527,7 @@ TABS.firmware_flasher.initialize = function (callback) {
|
||||||
|
|
||||||
TABS.firmware_flasher.cleanup = function (callback) {
|
TABS.firmware_flasher.cleanup = function (callback) {
|
||||||
PortHandler.flush_callbacks();
|
PortHandler.flush_callbacks();
|
||||||
|
FirmwareCache.flush();
|
||||||
|
|
||||||
// unbind "global" events
|
// unbind "global" events
|
||||||
$(document).unbind('keypress');
|
$(document).unbind('keypress');
|
||||||
|
|
|
@ -35,6 +35,7 @@
|
||||||
<link type="text/css" rel="stylesheet" href="./css/dropdown-lists/css/style_lists.css" media="all"/>
|
<link type="text/css" rel="stylesheet" href="./css/dropdown-lists/css/style_lists.css" media="all"/>
|
||||||
<link type="text/css" rel="stylesheet" href="./js/libraries/switchery/switchery.css" media="all"/>
|
<link type="text/css" rel="stylesheet" href="./js/libraries/switchery/switchery.css" media="all"/>
|
||||||
<link rel="stylesheet" type="text/css" href="./js/libraries/jbox/jBox.css"/>
|
<link rel="stylesheet" type="text/css" href="./js/libraries/jbox/jBox.css"/>
|
||||||
|
<script type="text/javascript" src="./node_modules/lru_map/lru.js"></script>
|
||||||
<script type="text/javascript" src="./node_modules/i18next/i18next.js"></script>
|
<script type="text/javascript" src="./node_modules/i18next/i18next.js"></script>
|
||||||
<script type="text/javascript" src="./node_modules/i18next-xhr-backend/i18nextXHRBackend.js"></script>
|
<script type="text/javascript" src="./node_modules/i18next-xhr-backend/i18nextXHRBackend.js"></script>
|
||||||
<script type="text/javascript" src="./node_modules/marked/marked.min.js"></script>
|
<script type="text/javascript" src="./node_modules/marked/marked.min.js"></script>
|
||||||
|
@ -96,6 +97,7 @@
|
||||||
<script type="text/javascript" src="./js/tabs/cli.js"></script>
|
<script type="text/javascript" src="./js/tabs/cli.js"></script>
|
||||||
<script type="text/javascript" src="./js/tabs/logging.js"></script>
|
<script type="text/javascript" src="./js/tabs/logging.js"></script>
|
||||||
<script type="text/javascript" src="./js/tabs/onboard_logging.js"></script>
|
<script type="text/javascript" src="./js/tabs/onboard_logging.js"></script>
|
||||||
|
<script type="text/javascript" src="./js/FirmwareCache.js"></script>
|
||||||
<script type="text/javascript" src="./js/tabs/firmware_flasher.js"></script>
|
<script type="text/javascript" src="./js/tabs/firmware_flasher.js"></script>
|
||||||
<script type="text/javascript" src="./js/tabs/failsafe.js"></script>
|
<script type="text/javascript" src="./js/tabs/failsafe.js"></script>
|
||||||
<script type="text/javascript" src="./js/LogoManager.js"></script>
|
<script type="text/javascript" src="./js/LogoManager.js"></script>
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue