Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
451 lines
11 KiB
JavaScript
Executable file
451 lines
11 KiB
JavaScript
Executable file
'use strict';
|
|
/* jshint moz:true */
|
|
/* global Promise */
|
|
/* global indexedDB */
|
|
|
|
/*
|
|
* DownloadStore.js: Allows to store download finished ordered by timestamp
|
|
*
|
|
* - Adding a new download:
|
|
*
|
|
* var req = DownloadStore.add(download);
|
|
*
|
|
* req.onsuccess = function() {
|
|
* console.log('Complete download stored successfully');
|
|
* };
|
|
*
|
|
* req.onerror = function(e) {
|
|
* console.error(e);
|
|
* };
|
|
*
|
|
* - Getting all downloads:
|
|
*
|
|
* var reqStore = DownloadStore.getAll();
|
|
*
|
|
* reqStore.onsuccess = function(ev) {
|
|
* ev.target.result.forEach(function(download) {
|
|
* // Do something cool with the download
|
|
* });
|
|
* };
|
|
*
|
|
* reqStore.onerror = function(e) {
|
|
* console.error(e);
|
|
* };
|
|
*
|
|
* - Removing a existing download:
|
|
*
|
|
* var req = DownloadStore.remove(download);
|
|
*
|
|
* req.onsuccess = function() {
|
|
* console.log('Deleted download successfully');
|
|
* };
|
|
*
|
|
* req.onerror = function(e) {
|
|
* console.error(e);
|
|
* };
|
|
*
|
|
* WARNING:
|
|
*
|
|
* The system app should add this to its manifest file
|
|
*
|
|
* "datastores-owned": {
|
|
* "download_store": {
|
|
* "description": "Stores download finished"
|
|
* }
|
|
* }
|
|
*
|
|
* The settings app should add this to its manifest file
|
|
*
|
|
* "datastores-access": {
|
|
* "download_store": {
|
|
* "description": "Stores download finished"
|
|
* }
|
|
* }
|
|
*
|
|
*/
|
|
|
|
/* exported DownloadStore */
|
|
var DownloadStore = (function() {
|
|
var datastore;
|
|
|
|
// Datastore name declared on the manifest.webapp
|
|
const DATASTORE_NAME = 'download_store';
|
|
|
|
var downloadListDB;
|
|
var downloadList = [];
|
|
var lastRevisionId;
|
|
|
|
const LIST_DB = 'download_list';
|
|
const LIST_STORE = 'download_list';
|
|
|
|
const LIST_KEY = 0;
|
|
const REVISION_KEY = 1;
|
|
|
|
/*
|
|
* Request auxiliary object to support asynchronous calls
|
|
*/
|
|
var Request = function() {
|
|
this.done = function(result) {
|
|
if (typeof this.onsuccess === 'function') {
|
|
this.result = result;
|
|
window.setTimeout(function() {
|
|
this.onsuccess({
|
|
target: this
|
|
});
|
|
}.bind(this), 0);
|
|
}
|
|
};
|
|
|
|
this.failed = function(error) {
|
|
if (typeof this.onerror === 'function') {
|
|
this.error = error;
|
|
window.setTimeout(function() {
|
|
this.onerror({
|
|
target: this
|
|
});
|
|
}.bind(this), 0);
|
|
}
|
|
};
|
|
};
|
|
|
|
function promisify(request) {
|
|
return new Promise(function(resolve, reject) {
|
|
request.onsuccess = (event) => {resolve(event.target.result);};
|
|
request.onerror = (event) => {reject(event.target.errorCode);};
|
|
});
|
|
}
|
|
|
|
// Indicates the initialization state
|
|
var readyState;
|
|
|
|
function init(success, fail) {
|
|
if (readyState === 'initialized') {
|
|
success();
|
|
return;
|
|
}
|
|
|
|
if (readyState === 'initializing') {
|
|
document.addEventListener('ds-initialized', function oninitalized() {
|
|
document.removeEventListener('ds-initialized', oninitalized);
|
|
success();
|
|
});
|
|
return;
|
|
}
|
|
|
|
readyState = 'initializing';
|
|
|
|
if (!navigator.getDataStores) {
|
|
var messageError = 'Data store: DataStore API is not working';
|
|
console.error(messageError);
|
|
fail({
|
|
message: messageError
|
|
});
|
|
return;
|
|
}
|
|
|
|
navigator.getDataStores(DATASTORE_NAME).then(function(ds) {
|
|
var messageError = 'Download Store: Cannot get access to the DataStore';
|
|
if (ds.length < 1) {
|
|
console.error(messageError);
|
|
throw ({
|
|
message: messageError
|
|
});
|
|
}
|
|
|
|
datastore = ds[0];
|
|
// Checking the length as the index should be there
|
|
}).then(function() {
|
|
return openDownloadListIndexedDB();
|
|
}).then(function(db) {
|
|
downloadListDB = db;
|
|
|
|
var listStore = downloadListDB.transaction(LIST_STORE, 'readonly')
|
|
.objectStore(LIST_STORE);
|
|
|
|
return Promise.all([
|
|
promisify(listStore.get(LIST_KEY))
|
|
.then((value) => {downloadList = value;}),
|
|
promisify(listStore.get(REVISION_KEY))
|
|
.then((value) => {lastRevisionId = value;})
|
|
]);
|
|
}).then(function() {
|
|
readyState = 'initialized';
|
|
|
|
// Remove the legacy index element, if it exists. See Bug 1180250.
|
|
const LEGACY_INDEX_ID = 1;
|
|
if (downloadList && downloadList.includes(LEGACY_INDEX_ID)) {
|
|
return promisify(get(LEGACY_INDEX_ID)).then((download) => {
|
|
if ('byTimestamp' in download) {
|
|
downloadList.splice(downloadList.indexOf(LEGACY_INDEX_ID), 1);
|
|
}
|
|
});
|
|
}
|
|
}).then(function() {
|
|
notifyOpenSuccess(success);
|
|
}).catch(function(e) {
|
|
console.error('Error while opening the Download Store: ', e.message);
|
|
fail(e);
|
|
});
|
|
}
|
|
|
|
function openDownloadListIndexedDB() {
|
|
return new Promise(function(resolve, reject) {
|
|
var request = indexedDB.open(LIST_DB);
|
|
|
|
request.onsuccess = function(event) {
|
|
var db = event.target.result;
|
|
resolve(db);
|
|
};
|
|
|
|
request.onerror = function(event) {
|
|
reject(event.target.errorCode);
|
|
};
|
|
|
|
request.onupgradeneeded = function(event) {
|
|
var db = event.target.result;
|
|
|
|
promisify(db.createObjectStore(LIST_STORE)).then(() => {
|
|
var listStore = db.transaction(LIST_STORE, 'readwrite')
|
|
.objectStore(LIST_STORE);
|
|
|
|
return Promise.all([
|
|
promisify(listStore.add([], LIST_KEY)),
|
|
promisify(listStore.add(0, REVISION_KEY))
|
|
]);
|
|
}).catch(reject);
|
|
};
|
|
|
|
request.onblocked = function(event) {
|
|
reject(event.target.errorCode);
|
|
};
|
|
});
|
|
}
|
|
|
|
function notifyOpenSuccess(cb) {
|
|
window.setTimeout(cb, 0);
|
|
document.dispatchEvent(new CustomEvent('ds-initialized'));
|
|
}
|
|
|
|
// These fields will be stored in our datastore
|
|
var fieldsToPropagate = ['url', 'path', 'totalBytes', 'contentType',
|
|
'startTime', 'state', 'storageName', 'storagePath'];
|
|
|
|
function cookDownload(download) {
|
|
var ret = Object.create(null);
|
|
|
|
fieldsToPropagate.forEach(function(field) {
|
|
ret[field] = download[field];
|
|
});
|
|
|
|
// This is the timestamp when the download finished
|
|
ret.finalizeTime = new Date();
|
|
|
|
return ret;
|
|
}
|
|
|
|
function defaultError(req) {
|
|
return defaultErrorCb.bind(null, req);
|
|
}
|
|
|
|
function defaultErrorCb(request, error) {
|
|
request.failed(error);
|
|
}
|
|
|
|
function defaultSuccess(req) {
|
|
return defaultSuccessCb.bind(null, req);
|
|
}
|
|
|
|
function defaultSuccessCb(request, result) {
|
|
request.done(result);
|
|
}
|
|
|
|
function updateDownloadList() {
|
|
var cursor = datastore.sync(lastRevisionId);
|
|
|
|
function cursorResolve(task) {
|
|
switch (task.operation) {
|
|
case 'done':
|
|
lastRevisionId = task.revisionId;
|
|
|
|
var listStore = downloadListDB.transaction(LIST_STORE, 'readwrite')
|
|
.objectStore(LIST_STORE);
|
|
|
|
return Promise.all([
|
|
promisify(listStore.put(downloadList, LIST_KEY)),
|
|
promisify(listStore.put(lastRevisionId, REVISION_KEY))
|
|
]);
|
|
|
|
case 'clear':
|
|
downloadList = [];
|
|
break;
|
|
|
|
case 'add':
|
|
downloadList.push(task.id);
|
|
break;
|
|
|
|
case 'update':
|
|
// This doesn't matter for us.
|
|
break;
|
|
|
|
case 'remove':
|
|
var i = downloadList.indexOf(task.id);
|
|
if (i >= 0) {
|
|
downloadList.splice(i, 1);
|
|
}
|
|
break;
|
|
}
|
|
|
|
return cursor.next().then(cursorResolve);
|
|
}
|
|
|
|
return cursor.next().then(cursorResolve);
|
|
}
|
|
|
|
function doAdd(download, req) {
|
|
var downloadCooked = cookDownload(download);
|
|
// Adding our cooked download
|
|
datastore.add(downloadCooked).then(function(id) {
|
|
// Enriched object with the id provided by the datastore
|
|
downloadCooked.id = id;
|
|
datastore.put(downloadCooked, id)
|
|
.then(function() { req.done(downloadCooked); },
|
|
defaultError(req));
|
|
}, defaultError(req));
|
|
}
|
|
|
|
function add(download) {
|
|
var req = new Request();
|
|
|
|
window.setTimeout(function() {
|
|
init(doAdd.bind(null, download, req), req.failed.bind(req));
|
|
});
|
|
|
|
return req;
|
|
}
|
|
|
|
function doGet(id, req) {
|
|
datastore.get(id).then(function(download) { req.done(download); },
|
|
defaultError(req));
|
|
}
|
|
|
|
function get(id) {
|
|
var req = new Request();
|
|
|
|
window.setTimeout(function() {
|
|
init(doGet.bind(null, id, req), req.failed.bind(req));
|
|
});
|
|
|
|
return req;
|
|
}
|
|
|
|
function doGetAll(req) {
|
|
updateDownloadList().then(function() {
|
|
datastore.get.apply(datastore, downloadList).then(defaultSuccess(req),
|
|
defaultError(req));
|
|
}, defaultError(req));
|
|
}
|
|
|
|
function getAll() {
|
|
var req = new Request();
|
|
|
|
window.setTimeout(function() {
|
|
init(doGetAll.bind(null, req), req.failed.bind(req));
|
|
});
|
|
|
|
return req;
|
|
}
|
|
|
|
function doRemove(id, req) {
|
|
// Removing the download object from datastore
|
|
datastore.remove(id).then(function(success) {
|
|
if (success) {
|
|
defaultSuccess(req);
|
|
} else {
|
|
req.failed({
|
|
message: 'The download with id: ' + id + 'does not exist'
|
|
});
|
|
}
|
|
}, defaultError(req));
|
|
}
|
|
|
|
function remove(download) {
|
|
var req = new Request();
|
|
|
|
window.setTimeout(function() {
|
|
init(doRemove.bind(null, download.id, req), req.failed.bind(req));
|
|
});
|
|
|
|
return req;
|
|
}
|
|
|
|
function doAddListener(listener) {
|
|
datastore.addEventListener('change', listener);
|
|
}
|
|
|
|
function addListener(listener) {
|
|
var req = new Request();
|
|
|
|
window.setTimeout(function() {
|
|
init(doAddListener.bind(null, listener), req.failed.bind(req));
|
|
});
|
|
|
|
return req;
|
|
}
|
|
|
|
function doRemoveListener(listener) {
|
|
datastore.removeEventListener('change', listener);
|
|
}
|
|
|
|
function removeListener(listener) {
|
|
var req = new Request();
|
|
|
|
window.setTimeout(function() {
|
|
init(doRemoveListener.bind(null, listener), req.failed.bind(req));
|
|
});
|
|
|
|
return req;
|
|
}
|
|
|
|
return {
|
|
/*
|
|
* This method returns the download with the specified id.
|
|
*/
|
|
get: get,
|
|
|
|
/*
|
|
* This method returns an array of complete downloads
|
|
*/
|
|
getAll: getAll,
|
|
|
|
/*
|
|
* It adds a new download object and returns the new datastore
|
|
* download object
|
|
*
|
|
* @param{Object} Download object provided by the API
|
|
*
|
|
*/
|
|
add: add,
|
|
|
|
/*
|
|
* It removes a download object
|
|
*
|
|
* @param{Object} The download object to be removed from the datastore
|
|
*
|
|
*/
|
|
remove: remove,
|
|
|
|
/*
|
|
* Add a listener that will receive datastore change events.
|
|
*
|
|
* @param{Function} The listener to be added.
|
|
*/
|
|
addListener: addListener,
|
|
|
|
/*
|
|
* Remove a listener previously added via addListener.
|
|
*
|
|
* @param{Function} The listener to be removed.
|
|
*/
|
|
removeListener: removeListener
|
|
};
|
|
}());
|