Publish Vulpes Preview 15 sources for desktop, Android and Tundra
Share Gaia assets and adapters, keep private profiles and development documents local, and update both public READMEs.
This commit is contained in:
parent
93e3a48004
commit
b38dea0597
25901 changed files with 9982925 additions and 369 deletions
543
gaia/apps/verticalhome/js/stores/item.js
Executable file
543
gaia/apps/verticalhome/js/stores/item.js
Executable file
|
|
@ -0,0 +1,543 @@
|
|||
'use strict';
|
||||
/* global ApplicationSource */
|
||||
/* global BookmarkSource */
|
||||
/* global configurator */
|
||||
/* global dispatchEvent */
|
||||
/* global GaiaGrid */
|
||||
|
||||
(function(exports) {
|
||||
|
||||
const DB_VERSION = 1;
|
||||
|
||||
const DB_NAME = 'verticalhome';
|
||||
|
||||
const DB_ITEM_STORE = 'items';
|
||||
const DB_SV_APP_STORE_NAME = 'svAppsInstalled';
|
||||
|
||||
const DATASTORE_NAME = 'verticalhome_items_migration';
|
||||
|
||||
const PREFS_STORE = 'vertical_preferences_store';
|
||||
|
||||
var db;
|
||||
var datastore;
|
||||
|
||||
function sort(entries, order) {
|
||||
|
||||
if (!order || !order.length) {
|
||||
return entries;
|
||||
}
|
||||
|
||||
var newEntries = [];
|
||||
function isEqual(lookFor, compareWith) {
|
||||
if (!compareWith.detail || !lookFor) {
|
||||
return false;
|
||||
}
|
||||
if (compareWith instanceof GaiaGrid.Mozapp) {
|
||||
if (!lookFor.manifestURL || !compareWith.detail.manifestURL) {
|
||||
return false;
|
||||
}
|
||||
if (compareWith.detail.entryPoint) {
|
||||
return lookFor.manifestURL === compareWith.detail.manifestURL &&
|
||||
lookFor.entry_point === compareWith.detail.entryPoint;
|
||||
} else {
|
||||
return lookFor.manifestURL === compareWith.detail.manifestURL;
|
||||
}
|
||||
} else if (compareWith instanceof GaiaGrid.Bookmark) {
|
||||
if (!lookFor.id || !compareWith.detail.id) {
|
||||
return false;
|
||||
}
|
||||
return lookFor.id === compareWith.detail.id;
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0, iLen = order.length; i < iLen; i++) {
|
||||
// Add all entries of current section
|
||||
for (var j = 0, jLen = order[i].length; j < jLen; j++) {
|
||||
var ind = entries.findIndex(
|
||||
isEqual.bind(null, order[i][j]));
|
||||
if (ind >= 0) {
|
||||
newEntries.push(entries.splice(ind,1)[0]);
|
||||
}
|
||||
}
|
||||
// If we have more sections add a divider
|
||||
if (i < iLen - 1) {
|
||||
newEntries.push(new GaiaGrid.Divider());
|
||||
}
|
||||
}
|
||||
// If entries is not empty yet, they could be unordered apps (added at end)
|
||||
// or SingleVariant apps, installed before vertical started, which we need
|
||||
// insert it in their correct position (ever if there was a first run with
|
||||
// SIM).
|
||||
if (entries.length > 0) {
|
||||
// We only reorder sv apps if SIM was present on first boot!
|
||||
if (configurator.isSimPresentOnFirstBoot) {
|
||||
sortUnclassifiedApps(newEntries, entries);
|
||||
} else {
|
||||
newEntries.push(new GaiaGrid.Divider());
|
||||
newEntries = newEntries.concat(entries);
|
||||
}
|
||||
}
|
||||
|
||||
for (i = 0, iLen = newEntries.length; i < iLen; i++) {
|
||||
if (newEntries[i].detail) {
|
||||
newEntries[i].detail.index = i;
|
||||
}
|
||||
}
|
||||
return newEntries;
|
||||
}
|
||||
|
||||
function sortUnclassifiedApps(dstEntries, orgEntries) {
|
||||
function compareApps(appA, appB) {
|
||||
var svAppA = null;
|
||||
var svAppB = null;
|
||||
|
||||
if (appA) {
|
||||
svAppA = configurator.getSingleVariantApp(appA.detail.manifestURL);
|
||||
}
|
||||
if (appB) {
|
||||
svAppB = configurator.getSingleVariantApp(appB.detail.manifestURL);
|
||||
}
|
||||
|
||||
var locA = svAppA && svAppA.location !== undefined && svAppA.location ||
|
||||
Number.MAX_VALUE;
|
||||
var locB = svAppB && svAppB.location !== undefined && svAppB.location ||
|
||||
Number.MAX_VALUE;
|
||||
|
||||
var relPos = locA - locB;
|
||||
return relPos ? (relPos > 0 ? 1 : -1) : 0;
|
||||
}
|
||||
|
||||
// To simplify the process, we order the apps first (SV apps in relative
|
||||
// order and at the beginning of the array)
|
||||
orgEntries.sort(compareApps);
|
||||
|
||||
// After the sorting process, if the first element is not a SV app then
|
||||
// we only need to add them at the end of dstEntries
|
||||
if (!configurator.getSingleVariantApp(orgEntries[0].detail.manifestURL)) {
|
||||
dstEntries.push(new GaiaGrid.Divider());
|
||||
dstEntries = dstEntries.concat(orgEntries);
|
||||
} else {
|
||||
var sepAdded = false;
|
||||
for (var i = 0, iLen = orgEntries.length; i < iLen; i++) {
|
||||
var app = orgEntries[i];
|
||||
var svApp = configurator.getSingleVariantApp(app.detail.manifestURL);
|
||||
if (!svApp) {
|
||||
if (!sepAdded) {
|
||||
dstEntries.push(new GaiaGrid.Divider());
|
||||
sepAdded = true;
|
||||
}
|
||||
dstEntries.push(app);
|
||||
} else {
|
||||
if (svApp.location !== undefined) {
|
||||
dstEntries.splice(svApp.location, 0, app);
|
||||
} else {
|
||||
dstEntries.push(app);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function loadTable(table, indexName, iterator, aNext) {
|
||||
newTxn(table, 'readonly', function(txn, store) {
|
||||
var index = store.index(indexName);
|
||||
index.openCursor().onsuccess = function onsuccess(event) {
|
||||
var cursor = event.target.result;
|
||||
if (!cursor) {
|
||||
return;
|
||||
}
|
||||
iterator(cursor.value);
|
||||
cursor.continue();
|
||||
};
|
||||
}, aNext);
|
||||
}
|
||||
|
||||
function newTxn(storeName, txnType, withTxnAndStore, successCb) {
|
||||
var txn = db.transaction([storeName], txnType);
|
||||
var store = txn.objectStore(storeName);
|
||||
|
||||
txn.oncomplete = function(event) {
|
||||
if (successCb) {
|
||||
successCb(event);
|
||||
}
|
||||
};
|
||||
|
||||
txn.onerror = function(event) {
|
||||
console.warn('Error during transaction.');
|
||||
};
|
||||
|
||||
withTxnAndStore(txn, store);
|
||||
}
|
||||
|
||||
function ItemStore(onsuccess) {
|
||||
var self = this;
|
||||
this.applicationSource = new ApplicationSource(this);
|
||||
this.bookmarkSource = new BookmarkSource(this);
|
||||
|
||||
this.sources = [this.applicationSource, this.bookmarkSource];
|
||||
|
||||
this.ready = false;
|
||||
|
||||
var isEmpty = false;
|
||||
self.gridOrder = null;
|
||||
|
||||
var request = window.indexedDB.open(DB_NAME, DB_VERSION);
|
||||
|
||||
request.onupgradeneeded = function _onupgradeneeded(event) {
|
||||
var db = event.target.result;
|
||||
|
||||
var oldVersion = event.oldVersion || 0;
|
||||
switch (oldVersion) {
|
||||
case 0:
|
||||
// Create the item store
|
||||
var objectStore = db.createObjectStore(DB_ITEM_STORE,
|
||||
{ keyPath: 'index'});
|
||||
|
||||
objectStore.createIndex('index', 'index', { unique: true });
|
||||
isEmpty = true;
|
||||
var objectSV = db.createObjectStore(DB_SV_APP_STORE_NAME,
|
||||
{ keyPath: 'manifestURL' });
|
||||
objectSV.createIndex('indexSV', 'indexSV', { unique: true });
|
||||
}
|
||||
};
|
||||
|
||||
request.onsuccess = function _onsuccess() {
|
||||
onsuccess && onsuccess(isEmpty);
|
||||
db = request.result;
|
||||
var cb = self.fetch.bind(self, self.synchronize.bind(self));
|
||||
|
||||
if (isEmpty) {
|
||||
window.addEventListener('configuration-ready', function onReady() {
|
||||
window.removeEventListener('configuration-ready', onReady);
|
||||
self.gridOrder = configurator.getGrid();
|
||||
self.populate(cb);
|
||||
});
|
||||
} else {
|
||||
self.initSources(cb);
|
||||
}
|
||||
|
||||
// Handle migration
|
||||
navigator.mozSetMessageHandler('connection', self.handleIAC.bind(self));
|
||||
};
|
||||
|
||||
window.addEventListener('gaiagrid-cached-icons-rendered', this);
|
||||
}
|
||||
|
||||
ItemStore.prototype = {
|
||||
|
||||
/**
|
||||
* Whether or not the grid has been rendered. We use this as an
|
||||
* indicator as to whether or not we should save items to indexedDB.
|
||||
* If we get items to save before this is true, we wait until the
|
||||
* cached-icons-rendered event to save.
|
||||
*/
|
||||
_renderedOnce: false,
|
||||
|
||||
/**
|
||||
* The saved arguments for the last deferred save call.
|
||||
*/
|
||||
_deferredSaveArgs: [],
|
||||
|
||||
/**
|
||||
* A list of all items. These are item objects (App, Bookmark, Divider)
|
||||
*/
|
||||
_allItems: [],
|
||||
|
||||
/**
|
||||
* Maintains the current index of the last grid item.
|
||||
*/
|
||||
nextPosition: 0,
|
||||
|
||||
/**
|
||||
* Fetches a list of all items in the store.
|
||||
*/
|
||||
all: function(success) {
|
||||
if (!this.ready) {
|
||||
window.addEventListener('databaseready', this.all.bind(this, success));
|
||||
return;
|
||||
}
|
||||
|
||||
success(this._allItems);
|
||||
},
|
||||
|
||||
saveTable: function(table, objArr, column, checkPersist, aNext) {
|
||||
newTxn(table, 'readwrite', function(txn, store) {
|
||||
store.clear();
|
||||
for (var i = 0, iLen = objArr.length; i < iLen; i++) {
|
||||
if (!checkPersist || (checkPersist && objArr[i].persistToDB)) {
|
||||
store.put(column?objArr[i][column]:objArr[i]);
|
||||
}
|
||||
}
|
||||
if (typeof aNext === 'function') {
|
||||
aNext();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
saveMigrationData: function(objArr, column, checkPersist) {
|
||||
return datastore.clear().then(() => {
|
||||
var chain = [];
|
||||
objArr.forEach((object) => {
|
||||
if (!checkPersist || object.persistToDB) {
|
||||
var data = column ? object[column] : object;
|
||||
chain.push(datastore.put(data, data.index + 1));
|
||||
}
|
||||
});
|
||||
return Promise.all(chain);
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {Object} object a single object to update.
|
||||
* @param {Function} callback fires when transaction finishes.
|
||||
*/
|
||||
saveItem: function(object, callback) {
|
||||
// intentional use of == meaning null or undefined.
|
||||
if (object.index == null) {
|
||||
console.error('Attempting to save object without `index`');
|
||||
return;
|
||||
}
|
||||
|
||||
newTxn(
|
||||
DB_ITEM_STORE,
|
||||
'readwrite',
|
||||
function(txn, store) {
|
||||
store.put(object);
|
||||
},
|
||||
callback
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Defers the save of items until after the grid is ready.
|
||||
*/
|
||||
deferredSave: function() {
|
||||
var args = Array.slice(arguments);
|
||||
|
||||
if (this._renderedOnce) {
|
||||
return this.save.apply(this, args);
|
||||
}
|
||||
|
||||
this._deferredSaveArgs = args;
|
||||
},
|
||||
|
||||
/**
|
||||
* Saves all icons to the database.
|
||||
*/
|
||||
save: function(entries, aNext) {
|
||||
entries = sort(entries, this.gridOrder);
|
||||
this.gridOrder = null;
|
||||
// The initial config is simply the list of apps
|
||||
this.saveTable(DB_ITEM_STORE, entries, 'detail', true, aNext);
|
||||
},
|
||||
|
||||
/**
|
||||
* Save reference to SingleVariant app previously installed
|
||||
*/
|
||||
savePrevInstalledSvApp: function(svApps, aNext) {
|
||||
this.saveTable(DB_SV_APP_STORE_NAME, svApps, null, false, aNext);
|
||||
},
|
||||
|
||||
/**
|
||||
* Fetches items from the database.
|
||||
* @param {Function} callback A function to call after fetching all items.
|
||||
*/
|
||||
fetch: function(callback) {
|
||||
var cached = {};
|
||||
var collected = [];
|
||||
|
||||
function iterator(value) {
|
||||
collected.push(value);
|
||||
}
|
||||
|
||||
function iteratorSV(value) {
|
||||
/* jshint validthis: true */
|
||||
this.applicationSource.addPreviouslyInstalledSvApp(value.manifestURL);
|
||||
}
|
||||
|
||||
loadTable(DB_SV_APP_STORE_NAME, 'indexSV', iteratorSV.bind(this));
|
||||
loadTable(DB_ITEM_STORE, 'index', iterator, finish.bind(this));
|
||||
|
||||
/**
|
||||
* Add to _allItems if the record is unique by comparing the identifier.
|
||||
*/
|
||||
function addIfUnique(item) {
|
||||
/* jshint validthis: true */
|
||||
if (!cached[item.identifier]) {
|
||||
cached[item.identifier] = true;
|
||||
this._allItems.push(item);
|
||||
}
|
||||
}
|
||||
|
||||
function finish() {
|
||||
/* jshint validthis: true */
|
||||
// Transforms DB results into item classes
|
||||
for (var i = 0, iLen = collected.length; i < iLen; i++) {
|
||||
var thisItem = collected[i];
|
||||
if (thisItem.type === 'app') {
|
||||
var itemObj = this.applicationSource.mapToApp(thisItem);
|
||||
addIfUnique.call(this, itemObj);
|
||||
} else if (thisItem.type === 'divider') {
|
||||
var divider = new GaiaGrid.Divider(thisItem);
|
||||
this._allItems.push(divider);
|
||||
} else if (thisItem.type === 'bookmark') {
|
||||
var bookmark = new GaiaGrid.Bookmark(thisItem);
|
||||
addIfUnique.call(this, bookmark);
|
||||
}
|
||||
}
|
||||
|
||||
this.notifyReady();
|
||||
|
||||
if (callback && typeof callback === 'function') {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* We have fetched data from our local database and displayed it,
|
||||
* but data inside of our application or bookmark store may be outdated.
|
||||
* We need to synchronize each source and delete/add records.
|
||||
*/
|
||||
synchronize: function() {
|
||||
this.sources.forEach(function eachSource(source) {
|
||||
source.synchronize();
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Initializes all sources.
|
||||
* @param {Function} callback The callback to fire after all sources init.
|
||||
*/
|
||||
initSources: function(callback) {
|
||||
var pending = this.sources.length;
|
||||
|
||||
var allEntries = [];
|
||||
var self = this;
|
||||
|
||||
var current = 0;
|
||||
function handleSource() {
|
||||
var source = self.sources[current];
|
||||
current++;
|
||||
source.populate(next);
|
||||
}
|
||||
handleSource();
|
||||
|
||||
function next(entries) {
|
||||
allEntries = allEntries.concat(entries);
|
||||
if (!(--pending)) {
|
||||
callback(allEntries);
|
||||
} else {
|
||||
handleSource();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Populates the database with the initial data.
|
||||
* @param {Function} callback Callback after database is populated.
|
||||
*/
|
||||
populate: function(callback) {
|
||||
this.initSources(function(entries) {
|
||||
this.deferredSave(entries, callback);
|
||||
}.bind(this));
|
||||
},
|
||||
|
||||
/**
|
||||
* Notifies consumers that the database is ready for queries to be makde.
|
||||
*/
|
||||
notifyReady: function() {
|
||||
this.ready = true;
|
||||
dispatchEvent(new CustomEvent('databaseready'));
|
||||
},
|
||||
|
||||
/**
|
||||
* Gets the next available position in the grid
|
||||
*/
|
||||
getNextPosition: function() {
|
||||
var nextPosition = this.nextPosition;
|
||||
this.nextPosition++;
|
||||
return nextPosition;
|
||||
},
|
||||
|
||||
handleIAC: function(connectionRequest) {
|
||||
if (connectionRequest.keyword !== 'verticalhome-migrate') {
|
||||
return;
|
||||
}
|
||||
|
||||
var port = connectionRequest.port;
|
||||
port.onmessage = (event) => {
|
||||
if (event.data.action !== 'migrate') {
|
||||
console.error('Unhandled migration action requested:',
|
||||
event.data.action);
|
||||
return;
|
||||
}
|
||||
|
||||
this.migrate(event.data.manifestURL);
|
||||
};
|
||||
port.start();
|
||||
},
|
||||
|
||||
migrate: function(manifestURL) {
|
||||
this.migratingTo = manifestURL;
|
||||
console.info('verticalhome migrating to ' + this.migratingTo);
|
||||
|
||||
if (this.migrating) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.migrating = true;
|
||||
(datastore ? Promise.resolve() :
|
||||
navigator.getDataStores(DATASTORE_NAME).
|
||||
then(stores => {
|
||||
datastore = stores[0];
|
||||
return Promise.resolve();
|
||||
},
|
||||
(e) => {
|
||||
return Promise.reject(e);
|
||||
})).
|
||||
then(() => {
|
||||
this.all(entries => {
|
||||
this.saveMigrationData(entries, 'detail', true).then(() => {
|
||||
this.migrating = false;
|
||||
window.navigator.mozSettings.createLock().set({
|
||||
'homescreen.manifestURL': this.migratingTo
|
||||
});
|
||||
navigator.getDataStores(PREFS_STORE).then(
|
||||
stores => {
|
||||
if (stores.length < 1) {
|
||||
console.error('Error opening verticalhome prefs datastore');
|
||||
return;
|
||||
}
|
||||
|
||||
stores[0].put('migrated', true).then(() => {
|
||||
window.close();
|
||||
}, e => {
|
||||
console.error('Error storing migration pref', e);
|
||||
window.close();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
},
|
||||
e => {
|
||||
console.error('Error retrieving datastore', e);
|
||||
});
|
||||
},
|
||||
|
||||
handleEvent: function(e) {
|
||||
switch(e.type) {
|
||||
case 'gaiagrid-cached-icons-rendered':
|
||||
this._renderedOnce = true;
|
||||
window.removeEventListener('gaiagrid-cached-icons-rendered', this);
|
||||
if (this._deferredSaveArgs.length) {
|
||||
this.save.apply(this, this._deferredSaveArgs);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exports.ItemStore = ItemStore;
|
||||
|
||||
}(window));
|
||||
Loading…
Reference in a new issue