blob: 792dc06322996af5c932257564eb85da9cc0ba26 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/**
* @fileoverview Utility functions for creating and operating on the packaged
* AppWindow API.
*/
const appUtil = {};
/**
* Save app launch data to the local storage.
*/
appUtil.saveAppState = () => {
if (!window.appState) {
return;
}
const items = {};
items[window.appID] = JSON.stringify(window.appState);
chrome.storage.local.set(items, () => {
if (chrome.runtime.lastError) {
console.error(
'Failed to save app state: ' + chrome.runtime.lastError.message);
}
});
};
/**
* Updates the app state.
*
* @param {?string} currentDirectoryURL Currently opened directory as an URL.qq
* If null the value is left unchanged.
* @param {?string} selectionURL Currently selected entry as an URL. If null the
* value is left unchanged.
* @param {string|Object=} opt_param Additional parameters, to be stored. If
* null, then left unchanged.
*/
appUtil.updateAppState = (currentDirectoryURL, selectionURL, opt_param) => {
window.appState = window.appState || {};
if (opt_param !== undefined && opt_param !== null) {
window.appState.params = opt_param;
}
if (currentDirectoryURL !== null) {
window.appState.currentDirectoryURL = currentDirectoryURL;
}
if (selectionURL !== null) {
window.appState.selectionURL = selectionURL;
}
appUtil.saveAppState();
};
/**
* AppCache is a persistent timestamped key-value storage backed by
* HTML5 local storage.
*
* It is not designed for frequent access. In order to avoid costly
* localStorage iteration all data is kept in a single localStorage item.
* There is no in-memory caching, so concurrent access is _almost_ safe.
*
* TODO(kaznacheev) Reimplement this based on Indexed DB.
*/
appUtil.AppCache = () => {};
/**
* Local storage key.
*/
appUtil.AppCache.KEY = 'AppCache';
/**
* Max number of items.
*/
appUtil.AppCache.CAPACITY = 100;
/**
* Default lifetime.
*/
appUtil.AppCache.LIFETIME = 30 * 24 * 60 * 60 * 1000; // 30 days.
/**
* @param {string} key Key.
* @param {function(number)} callback Callback accepting a value.
*/
appUtil.AppCache.getValue = (key, callback) => {
appUtil.AppCache.read_(map => {
const entry = map[key];
callback(entry && entry.value);
});
};
/**
* Updates the cache.
*
* @param {string} key Key.
* @param {?(string|number)} value Value. Remove the key if value is null.
* @param {number=} opt_lifetime Maximum time to keep an item (in milliseconds).
*/
appUtil.AppCache.update = (key, value, opt_lifetime) => {
appUtil.AppCache.read_(map => {
if (value != null) {
map[key] = {
value: value,
expire: Date.now() + (opt_lifetime || appUtil.AppCache.LIFETIME)
};
} else if (key in map) {
delete map[key];
} else {
return; // Nothing to do.
}
appUtil.AppCache.cleanup_(map);
appUtil.AppCache.write_(map);
});
};
/**
* @param {function(Object)} callback Callback accepting a map of timestamped
* key-value pairs.
* @private
*/
appUtil.AppCache.read_ = callback => {
chrome.storage.local.get(appUtil.AppCache.KEY, values => {
const json = values[appUtil.AppCache.KEY];
if (json) {
try {
callback(/** @type {Object} */ (JSON.parse(json)));
} catch (e) {
// The local storage item somehow got messed up, start fresh.
}
}
callback({});
});
};
/**
* @param {Object} map A map of timestamped key-value pairs.
* @private
*/
appUtil.AppCache.write_ = map => {
const items = {};
items[appUtil.AppCache.KEY] = JSON.stringify(map);
chrome.storage.local.set(items);
};
/**
* Remove over-capacity and obsolete items.
*
* @param {Object} map A map of timestamped key-value pairs.
* @private
*/
appUtil.AppCache.cleanup_ = map => {
// Sort keys by ascending timestamps.
const keys = [];
for (const key in map) {
if (map.hasOwnProperty(key)) {
keys.push(key);
}
}
keys.sort((a, b) => {
return map[a].expire - map[b].expire;
});
const cutoff = Date.now();
let obsolete = 0;
while (obsolete < keys.length && map[keys[obsolete]].expire < cutoff) {
obsolete++;
}
const overCapacity = Math.max(0, keys.length - appUtil.AppCache.CAPACITY);
const itemsToDelete = Math.max(obsolete, overCapacity);
for (let i = 0; i != itemsToDelete; i++) {
delete map[keys[i]];
}
};