1
0
Fork 0
mirror of https://github.com/betaflight/betaflight-configurator.git synced 2025-07-19 14:25:14 +03:00

Merge pull request #1013 from atomgomba/feature-firmware-caching

Initial implementation of firmware cache
This commit is contained in:
Michael Keller 2018-05-03 14:19:59 +12:00 committed by GitHub
commit 86020812a1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 301 additions and 42 deletions

View file

@ -34,7 +34,8 @@
{"usbDevices": [
{"vendorId": 1155, "productId": 57105}
]},
"webview"
"webview",
"unlimitedStorage"
],
"sockets": {
"tcp": {

View file

@ -36,6 +36,7 @@
"dependencies": {
"i18next": "^10.3.0",
"i18next-xhr-backend": "^1.5.1",
"lru_map": "^0.3.3",
"marked": "^0.3.12"
},
"devDependencies": {

216
src/js/FirmwareCache.js Normal file
View file

@ -0,0 +1,216 @@
'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 onPutToCacheCallback,
onRemoveFromCacheCallback;
let JournalStorage = (function () {
let CACHEKEY = "firmware-cache-journal";
/**
* @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 journal = new LRUMap(100),
journalLoaded = false;
journal.shift = function () {
// remove cached data for oldest release
let oldest = LRUMap.prototype.shift.call(this);
if (oldest === undefined) {
return undefined;
}
let key = oldest[0];
let cacheKey = withCachePrefix(key);
chrome.storage.local.get(cacheKey, obj => {
/** @type {CacheItem} */
let cached = typeof obj === "object" && obj.hasOwnProperty(cacheKey)
? obj[cacheKey]
: null;
chrome.storage.local.remove(cacheKey, () => {
onRemoveFromCache(cached.release);
console.debug("Cache data removed: " + cacheKey);
});
});
return oldest;
};
/**
* @param {Descriptor} release
* @returns {string} A key used to store a release in the journal
*/
function keyOf(release) {
return release.file;
}
/**
* @param {string} key
* @returns {string} A key for storing cached data for a release
*/
function withCachePrefix(key) {
return "cache:" + key;
}
/**
* @param {Descriptor} release
* @returns {boolean}
*/
function has(release) {
if (!journalLoaded) {
console.warn("Cache not yet loaded");
return false;
}
return journal.has(keyOf(release));
}
/**
* @param {Descriptor} release
* @param {string} hexdata
*/
function put(release, hexdata) {
if (!journalLoaded) {
console.warn("Cache journal not yet loaded");
return;
}
let key = keyOf(release);
if (has(release)) {
console.debug("Firmware is already cached: " + key);
return;
}
journal.set(key, true);
JournalStorage.persist(journal.toJSON());
let obj = {};
obj[withCachePrefix(key)] = {
release: release,
hexdata: hexdata,
};
chrome.storage.local.set(obj, () => {
console.info("Release put to cache: " + key);
onPutToCache(release);
});
}
/**
* @param {Descriptor} release
* @param {Function} callback
*/
function get(release, callback) {
if (!journalLoaded) {
console.warn("Cache journal not yet loaded");
return undefined;
}
let key = keyOf(release);
if (!has(release)) {
console.debug("Firmware is not cached: " + key);
return;
}
let cacheKey = withCachePrefix(key);
chrome.storage.local.get(cacheKey, obj => {
/** @type {CacheItem} */
let cached = typeof obj === "object" && obj.hasOwnProperty(cacheKey)
? obj[cacheKey]
: null;
callback(cached);
});
}
/**
* @param {Descriptor} release
*/
function onPutToCache(release) {
if (typeof onPutToCacheCallback === "function") {
onPutToCacheCallback(release);
}
}
/**
* @param {Descriptor} release
*/
function onRemoveFromCache(release) {
if (typeof onRemoveFromCacheCallback === "function") {
onRemoveFromCacheCallback(release);
}
}
/**
* @param {Array} entries
*/
function onEntriesLoaded(entries) {
let pairs = [];
for (let entry of entries) {
pairs.push([entry.key, entry.value]);
}
journal.assign(pairs);
journalLoaded = true;
console.info("Firmware cache journal loaded; number of entries: " + entries.length);
}
return {
has: has,
put: put,
get: get,
onPutToCache: callback => onPutToCacheCallback = callback,
onRemoveFromCache: callback => onRemoveFromCacheCallback = callback,
load: () => {
JournalStorage.load(onEntriesLoaded);
},
flush: () => {
JournalStorage.persist(journal.toJSON());
journal.clear();
},
};
})();

View file

@ -16,7 +16,23 @@ TABS.firmware_flasher.initialize = function (callback) {
var intel_hex = false, // standard intel hex in string format
parsed_hex = false; // parsed raw hex in array format
/**
* Change boldness of firmware option depending on cache status
*
* @param {Descriptor} release
*/
function onFirmwareCacheUpdate(release) {
$("option[value='{0}']".format(release.version))
.css("font-weight", FirmwareCache.has(release)
? "bold"
: "normal");
}
$('#content').load("./tabs/firmware_flasher.html", function () {
FirmwareCache.load();
FirmwareCache.onPutToCache(onFirmwareCacheUpdate);
FirmwareCache.onRemoveFromCache(onFirmwareCacheUpdate);
function parse_hex(str, callback) {
// parsing hex in different thread
var worker = new Worker('./js/workers/hex_parser.js');
@ -30,6 +46,53 @@ TABS.firmware_flasher.initialize = function (callback) {
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);
}
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) {
if (!releaseData) {
$('select[name="board"]').empty().append('<option value="0">Offline</option>');
@ -160,7 +223,12 @@ TABS.firmware_flasher.initialize = function (callback) {
descriptor.target,
descriptor.date,
descriptor.status
)).data('summary', descriptor);
))
.css("font-weight", FirmwareCache.has(descriptor)
? "bold"
: "normal"
)
.data('summary', descriptor);
versions_e.append(select_e);
});
@ -226,7 +294,15 @@ TABS.firmware_flasher.initialize = function (callback) {
$('select[name="firmware_version"]').change(function(evt){
$('div.release_info').slideUp();
$('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');
}
else {
@ -241,40 +317,6 @@ TABS.firmware_flasher.initialize = function (callback) {
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() {
$('span.progressLabel').text(i18n.getMessage('firmwareFlasherFailedToLoadOnlineFirmware'));
$('a.flash_firmware').addClass('disabled');
@ -286,11 +328,7 @@ TABS.firmware_flasher.initialize = function (callback) {
if (summary) { // undefined while list is loading or while running offline
$("a.load_remote_file").text(i18n.getMessage('firmwareFlasherButtonDownloading'));
$("a.load_remote_file").addClass('disabled');
$.get(summary.url, function (data) {
process_hex(data, summary);
$("a.load_remote_file").removeClass('disabled');
$("a.load_remote_file").text(i18n.getMessage('firmwareFlasherButtonLoadOnline'));
}).fail(failed_to_load);
$.get(summary.url, onLoadSuccess).fail(failed_to_load);
} else {
$('span.progressLabel').text(i18n.getMessage('firmwareFlasherFailedToLoadOnlineFirmware'));
}
@ -507,6 +545,7 @@ TABS.firmware_flasher.initialize = function (callback) {
TABS.firmware_flasher.cleanup = function (callback) {
PortHandler.flush_callbacks();
FirmwareCache.flush();
// unbind "global" events
$(document).unbind('keypress');

View file

@ -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="./js/libraries/switchery/switchery.css" media="all"/>
<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-xhr-backend/i18nextXHRBackend.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/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/failsafe.js"></script>
<script type="text/javascript" src="./js/LogoManager.js"></script>