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
815
gaia/shared/js/smart-screen/card_manager.js
Executable file
815
gaia/shared/js/smart-screen/card_manager.js
Executable file
|
|
@ -0,0 +1,815 @@
|
|||
/* global evt, SharedUtils, Promise, PipedPromise, Application, CardStore,
|
||||
Deck, Folder, AsyncSemaphore, AppBookmark */
|
||||
|
||||
(function(exports) {
|
||||
'use strict';
|
||||
|
||||
var CardManager = function() {
|
||||
};
|
||||
|
||||
CardManager.STATES = Object.freeze({
|
||||
'READY': 'READY',
|
||||
'SYNCING': 'SYNCING'
|
||||
});
|
||||
|
||||
CardManager.prototype = evt({
|
||||
HIDDEN_ROLES: ['system', 'homescreen', 'addon', 'langpack'],
|
||||
TABLE_NAME: 'home_cards',
|
||||
|
||||
// Only two modes available: readonly and readwrite (default)
|
||||
// 'readwrite' mode is for Smart-Home app only
|
||||
// This '_mode' variable only affects CardStore.
|
||||
_mode: 'readwrite',
|
||||
|
||||
_manifestURLOfCardStore: undefined,
|
||||
|
||||
_cardStore: undefined,
|
||||
|
||||
_cardList: [],
|
||||
|
||||
installedApps: {},
|
||||
|
||||
_asyncSemaphore: undefined,
|
||||
|
||||
_isHiddenApp: function cm_isHiddenApp(role) {
|
||||
if (!role) {
|
||||
return false;
|
||||
}
|
||||
return (this.HIDDEN_ROLES.indexOf(role) !== -1);
|
||||
},
|
||||
|
||||
_isCardListLoaded: function cm_isCardListLoaded() {
|
||||
return this._cardList && this._cardList.length > 0;
|
||||
},
|
||||
|
||||
_serializeCard: function cm_serializeCard(card) {
|
||||
return card && card.serialize();
|
||||
},
|
||||
|
||||
_deserializeCardEntry: function cm_deserializeCardEntry(cardEntry) {
|
||||
var cardInstance;
|
||||
switch (cardEntry.type) {
|
||||
case 'Application':
|
||||
cardInstance = Application.deserialize(cardEntry, this.installedApps);
|
||||
break;
|
||||
case 'Deck':
|
||||
cardInstance = Deck.deserialize(cardEntry, this.installedApps);
|
||||
break;
|
||||
case 'AppBookmark':
|
||||
cardInstance = AppBookmark.deserialize(cardEntry);
|
||||
break;
|
||||
case 'Folder':
|
||||
cardInstance = Folder.deserialize(cardEntry);
|
||||
// Save the folder into card store whenever we receives
|
||||
// folder-changed event
|
||||
cardInstance.on('folder-changed', this._onFolderChange.bind(this));
|
||||
break;
|
||||
}
|
||||
return cardInstance;
|
||||
},
|
||||
|
||||
writeFolderInCardStore: function cm_writeFolderInCardStore(folder) {
|
||||
var that = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
if (folder instanceof Folder) {
|
||||
that._asyncSemaphore.v();
|
||||
var cardEntriesInFolder =
|
||||
folder.getCardList().map(that._serializeCard.bind(that));
|
||||
that._cardStore.saveData(folder.folderId,
|
||||
cardEntriesInFolder).then(function() {
|
||||
folder.state = Folder.STATES.NORMAL;
|
||||
}).then(function() {
|
||||
that._asyncSemaphore.p();
|
||||
resolve();
|
||||
}, reject);
|
||||
} else {
|
||||
reject();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// XXX: Call this function when you need to write cardList back to datastore
|
||||
// Because when we write cardList to datastore, we need to seperate
|
||||
// first level cards and cards under folder into sperated records in
|
||||
// datastore. A better way whould pull out logic related to cardList
|
||||
// into a standalone module. We will do this later.
|
||||
writeCardlistInCardStore: function cm_writeCardlistInCardStore(options) {
|
||||
var that = this;
|
||||
var emptyFolderIndices = [];
|
||||
return new Promise(function(resolve, reject) {
|
||||
var saveDataPromises = [];
|
||||
var newCardList;
|
||||
that._asyncSemaphore.v();
|
||||
// The cards inside of folder are not saved nested in cardList
|
||||
// but we explicit save them under key of folderId.
|
||||
// Here we save content of each folder one by one
|
||||
newCardList = that._cardList.filter(function(card, index) {
|
||||
if (card instanceof Folder) {
|
||||
if (card.getCardList().length > 0) {
|
||||
saveDataPromises.push(that.writeFolderInCardStore(card));
|
||||
} else {
|
||||
emptyFolderIndices.push(index);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
if (options && options.cleanEmptyFolder) {
|
||||
that._cardList = newCardList;
|
||||
}
|
||||
Promise.all(saveDataPromises).then(function() {
|
||||
resolve();
|
||||
});
|
||||
}).then(function() {
|
||||
var cardEntries =
|
||||
that._cardList.map(that._serializeCard.bind(that));
|
||||
return that._cardStore.saveData('cardList', cardEntries);
|
||||
}).then(function() {
|
||||
that._asyncSemaphore.p();
|
||||
if (options && options.cleanEmptyFolder) {
|
||||
that.fire('card-removed', emptyFolderIndices);
|
||||
}
|
||||
}).catch(function() {
|
||||
that._asyncSemaphore.p();
|
||||
});
|
||||
},
|
||||
|
||||
_loadDefaultCardList: function cm_loadDefaultCardList() {
|
||||
var that = this;
|
||||
return this._getPipedPromise('_loadDefaultCardList',
|
||||
function(resolve, reject) {
|
||||
var defaultCardListFile = 'shared/resources/default-cards.json';
|
||||
that._asyncSemaphore.v();
|
||||
that._loadFile({
|
||||
url: defaultCardListFile,
|
||||
responseType: 'json'
|
||||
}).then(function onFulfill(config) {
|
||||
that._cardList =
|
||||
config.card_list.map(function(cardEntry) {
|
||||
var card = that._deserializeCardEntry(cardEntry);
|
||||
if (card instanceof Folder && card.isDeserializing()) {
|
||||
card.loadCardsInFolder({
|
||||
from: 'config',
|
||||
cardEntry: cardEntry,
|
||||
deserializer: that._deserializeCardEntry.bind(that)
|
||||
});
|
||||
}
|
||||
return card;
|
||||
});
|
||||
// write cardList into data store for the first time
|
||||
that.writeCardlistInCardStore().then(resolve, reject);
|
||||
}).then(function() {
|
||||
that._asyncSemaphore.p();
|
||||
}).catch(function onReject(error) {
|
||||
var reason ='request ' + defaultCardListFile +
|
||||
' got reject ' + error;
|
||||
that._asyncSemaphore.p();
|
||||
reject(reason);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
_onCardStoreChange: function cm_onCardStoreChange(evt) {
|
||||
var that = this;
|
||||
if (evt.operation === 'updated') {
|
||||
that._asyncSemaphore.v();
|
||||
// When we receives 'cardlist-changed' in readonly mode, it means
|
||||
// Smart-Home app has change cardList. We'd better re-fetch cardList
|
||||
// as a whole.
|
||||
if (this._mode === 'readonly') {
|
||||
this._cardList = [];
|
||||
}
|
||||
this._reloadCardList().then(function() {
|
||||
that._asyncSemaphore.p();
|
||||
that.fire('cardlist-changed');
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
_onFolderChange: function cm_onFolderChange(folder) {
|
||||
if (folder.isDetached()) {
|
||||
this.writeCardlistInCardStore();
|
||||
} else {
|
||||
this.writeFolderInCardStore(folder);
|
||||
if (folder.isEmpty()) {
|
||||
this.writeCardlistInCardStore();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_initCardStoreIfNeeded: function cm_initCardStore() {
|
||||
if (!this._cardStore) {
|
||||
this._cardStore = new CardStore(
|
||||
this.TABLE_NAME, this._mode, this._manifestURLOfCardStore);
|
||||
this._cardStore.on('change', this._onCardStoreChange.bind(this));
|
||||
}
|
||||
},
|
||||
|
||||
// XXX: DO NOT call this directly except for _reloadCardList.
|
||||
// Because it is not protected by AsyncSemaphore. We should change it as
|
||||
// returning a `Promise`
|
||||
_loadCardListFromCardStore:
|
||||
function cm_loadCardListFromCardStore(cardListFromCardStore) {
|
||||
var that = this;
|
||||
cardListFromCardStore.forEach(function(cardEntry) {
|
||||
var found = that.findCardFromCardList({cardEntry: cardEntry});
|
||||
if (!found) {
|
||||
var card = that._deserializeCardEntry(cardEntry);
|
||||
// The cards inside of folder are not saved nested in
|
||||
// datastore 'cardList'. But we explicit save them under key
|
||||
// of folderId. So we need to retrieve them by their folderId
|
||||
// and put them back to folders where they belong.
|
||||
if (card instanceof Folder && card.isDeserializing()) {
|
||||
card.loadCardsInFolder({
|
||||
from: 'datastore',
|
||||
datastore: that._cardStore,
|
||||
deserializer: that._deserializeCardEntry.bind(that)
|
||||
});
|
||||
}
|
||||
that._cardList.push(card);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
_reloadCardList: function cm_loadCardList() {
|
||||
var that = this;
|
||||
return this._getPipedPromise('_reloadCardList',
|
||||
function(resolve, reject) {
|
||||
that._asyncSemaphore.v();
|
||||
that._initCardStoreIfNeeded();
|
||||
resolve(that._cardStore.getData('cardList'));
|
||||
}).then(function(cardListFromCardStore) {
|
||||
if (cardListFromCardStore) {
|
||||
// XXX: Change _loadCardListFromCardStore as returning a `Promise`
|
||||
that._loadCardListFromCardStore(cardListFromCardStore);
|
||||
} else {
|
||||
// no cardList in datastore, load default from config file
|
||||
return Promise.resolve(that._loadDefaultCardList());
|
||||
}
|
||||
}).then(function() {
|
||||
that._asyncSemaphore.p();
|
||||
}).catch(function(reason) {
|
||||
console.warn('Unable to reload cardList due to ' + reason);
|
||||
that._asyncSemaphore.p();
|
||||
});
|
||||
},
|
||||
|
||||
_loadFile: function cm_loadIcon(request) {
|
||||
// _loadFile could accept reentrance, so it should return
|
||||
// new Promise on each invokation
|
||||
return new Promise(function(resolve, reject) {
|
||||
var url = request.url;
|
||||
var responseType = request.responseType || 'text';
|
||||
if (typeof url === 'string') {
|
||||
try {
|
||||
var xhr = new XMLHttpRequest({mozAnon: true, mozSystem: true});
|
||||
xhr.open('GET', url, true);
|
||||
xhr.responseType = responseType;
|
||||
xhr.onload = function onload(evt) {
|
||||
if (xhr.status !== 0 && xhr.status !== 200) {
|
||||
reject(xhr.statusText);
|
||||
} else {
|
||||
resolve(xhr.response);
|
||||
}
|
||||
};
|
||||
xhr.ontimeout = xhr.onerror = function onErrorOrTimeout() {
|
||||
reject();
|
||||
};
|
||||
xhr.send();
|
||||
} catch (e) {
|
||||
reject(e.message);
|
||||
}
|
||||
} else {
|
||||
reject('invalid request');
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
_bestMatchingIcon:
|
||||
function cm_bestMatchingIcon(app, manifest, preferredSize) {
|
||||
var max = 0;
|
||||
var closestSize = 0;
|
||||
preferredSize = preferredSize || Number.MAX_VALUE;
|
||||
|
||||
for (var size in manifest.icons) {
|
||||
size = parseInt(size, 10);
|
||||
if (size > max) {
|
||||
max = size;
|
||||
}
|
||||
if (!closestSize && size >= preferredSize) {
|
||||
closestSize = size;
|
||||
}
|
||||
}
|
||||
|
||||
if (!closestSize) {
|
||||
closestSize = max;
|
||||
}
|
||||
|
||||
var url = manifest.icons[closestSize];
|
||||
if (!url) {
|
||||
return;
|
||||
}
|
||||
if (url.indexOf('data:') === 0 ||
|
||||
url.indexOf('app://') === 0 ||
|
||||
url.indexOf('http://') === 0 ||
|
||||
url.indexOf('https://') === 0) {
|
||||
return url;
|
||||
}
|
||||
if (url.charAt(0) != '/') {
|
||||
console.warn('`' + manifest.name + '` app icon is invalid. ' +
|
||||
'Manifest `icons` attribute should contain URLs -or- ' +
|
||||
'absolute paths from the origin field.');
|
||||
return '';
|
||||
}
|
||||
|
||||
if (app.origin.slice(-1) === '/') {
|
||||
return app.origin.slice(0, -1) + url;
|
||||
}
|
||||
|
||||
return [app.origin + url, closestSize];
|
||||
},
|
||||
|
||||
_onAppInstall: function cm_onAppInstall(evt) {
|
||||
var app = evt.application;
|
||||
var manifest = app.manifest || app.updateManifest;
|
||||
if (!app.launch || !manifest || !manifest.icons ||
|
||||
this._isHiddenApp(manifest.role)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var message =
|
||||
this.installedApps[app.manifestURL] ? 'update' : 'install';
|
||||
this.installedApps[app.manifestURL] = app;
|
||||
this.fire(message, this.getAppEntries(app.manifestURL));
|
||||
},
|
||||
|
||||
_onAppUninstall: function cm_onAppUninstall(evt) {
|
||||
var app = evt.application;
|
||||
|
||||
// XXX: Delete uninstalled app cards if any. The deletion should be
|
||||
// performed only once, so we let only the owner of data store to do it.
|
||||
// Notice the application object will be destroyed after uninstall event
|
||||
// is done, and we won't be able to read any data inside card.nativeApp
|
||||
// anymore (thus cause failure to FindCardFromCardList and etc.).
|
||||
// So we let CardManager do it consciously to prevent zombie app cards.
|
||||
// A possible way to improve it is to decouple AppMgmt and its app objects
|
||||
// completely with CardManager and leave them inside applications.js.
|
||||
if (this._cardStore.isOwner) {
|
||||
var card = this.findCardFromCardList({
|
||||
manifestURL: app.manifestURL
|
||||
});
|
||||
card && this.removeCard(card);
|
||||
}
|
||||
|
||||
if (this.installedApps[app.manifestURL]) {
|
||||
this.fire('uninstall', this.getAppEntries(app.manifestURL));
|
||||
delete this.installedApps[app.manifestURL];
|
||||
}
|
||||
},
|
||||
|
||||
insertNewFolder: function cm_insertFolder(name, index) {
|
||||
var newFolder = new Folder({
|
||||
name: name,
|
||||
state: Folder.STATES.DETACHED
|
||||
});
|
||||
|
||||
this._asyncSemaphore.wait(function() {
|
||||
if (typeof index !== 'number') {
|
||||
index = this._cardList.length;
|
||||
}
|
||||
this._cardList.splice(index, 0, newFolder);
|
||||
// Notice that we are not saving card list yet
|
||||
// Because newFolder is empty, it is meaningless to save it
|
||||
// But we need to hook `folder-changed` event handler in case
|
||||
// we need to save it when its content changed
|
||||
newFolder.on('folder-changed', this._onFolderChange.bind(this));
|
||||
this.fire('card-inserted', newFolder, index);
|
||||
}, this);
|
||||
|
||||
return newFolder;
|
||||
},
|
||||
|
||||
insertCard: function cm_insertCard(options) {
|
||||
var that = this;
|
||||
this._asyncSemaphore.wait(function() {
|
||||
var newCard;
|
||||
var position;
|
||||
|
||||
if (options.cardEntry) {
|
||||
newCard = this._deserializeCardEntry(options.cardEntry);
|
||||
} else {
|
||||
newCard = options.card;
|
||||
}
|
||||
|
||||
// prevent same card from being inserted twice
|
||||
if (newCard && newCard.nativeApp) {
|
||||
var sameCardExisting = that.findCardFromCardList({
|
||||
manifestURL: newCard.nativeApp.manifestURL,
|
||||
launchURL: newCard.launchURL
|
||||
});
|
||||
if (sameCardExisting) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof options.index === 'number') {
|
||||
position = options.index;
|
||||
} else if (!newCard.group) {
|
||||
position = this._cardList.length;
|
||||
} else {
|
||||
// If the given card belongs to a deck (has type), we assume the deck
|
||||
// spans a group with all its bookmarks following deck icon itself,
|
||||
// and the given card should be put at the end of the group.
|
||||
position = -1;
|
||||
for(var idx = 0; idx < this._cardList.length; idx++) {
|
||||
var card = this._cardList[idx];
|
||||
if(position === -1 && !(card instanceof Deck)) {
|
||||
// Only Decks are admitted as the start of the group.
|
||||
continue;
|
||||
} else if (position !== -1 && card.group !== newCard.group) {
|
||||
// We've exceeded the end of the group.
|
||||
break;
|
||||
} else if (card.group === newCard.group) {
|
||||
// We're still inside the group.
|
||||
position = idx;
|
||||
}
|
||||
}
|
||||
position += 1;
|
||||
// No corresponding deck found; insert at bottom.
|
||||
if (position === 0) {
|
||||
position = this._cardList.length;
|
||||
}
|
||||
}
|
||||
|
||||
this._cardList.splice(position, 0, newCard);
|
||||
this.writeCardlistInCardStore().then(function() {
|
||||
that.fire('card-inserted', newCard, position, options.overFolder);
|
||||
});
|
||||
}, this);
|
||||
},
|
||||
|
||||
removeCard: function cm_removeCard(item) {
|
||||
this._asyncSemaphore.wait(function() {
|
||||
var that = this;
|
||||
var index =
|
||||
(typeof item === 'number') ? item : this._cardList.indexOf(item);
|
||||
|
||||
if (index >= 0) {
|
||||
this._cardList.splice(index, 1);
|
||||
this.writeCardlistInCardStore().then(function() {
|
||||
that.fire('card-removed', [index]);
|
||||
});
|
||||
} else {
|
||||
// the card is not in _cardList, then it could probably be in folder
|
||||
this._cardList.forEach(function(card) {
|
||||
if (card instanceof Folder) {
|
||||
// don't bother to fire card-removed event because the folder
|
||||
// itself will fire this event
|
||||
card.removeCard(item);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, this);
|
||||
},
|
||||
|
||||
updateCard: function cm_updateCard(item, index) {
|
||||
var that = this;
|
||||
this._asyncSemaphore.wait(function() {
|
||||
if (typeof index === 'undefined') {
|
||||
index = this._cardList.findIndex(function(elem) {
|
||||
return elem.cardId === item.cardId;
|
||||
});
|
||||
}
|
||||
if (index >= 0) {
|
||||
// Most of the time, 'item' is directly reference to card in _cardList
|
||||
// and by the time we reach here, the card ('item') is already
|
||||
// updated. So don't bother to update it again.
|
||||
if (this._cardList[index] !== item) {
|
||||
this._cardList[index] = item;
|
||||
}
|
||||
this.writeCardlistInCardStore().then(function() {
|
||||
that.fire('card-updated', that._cardList[index], index);
|
||||
});
|
||||
}
|
||||
}, this);
|
||||
},
|
||||
|
||||
// XXX: Note that if you put card index as parameters for item1 and item2
|
||||
// Please make sure they are "index of _cardList"
|
||||
swapCard: function cm_switchCard(item1, item2) {
|
||||
this._asyncSemaphore.wait(function() {
|
||||
var that = this;
|
||||
var idx1, idx2;
|
||||
idx1 = (typeof item1 === 'number') ?
|
||||
idx1 = item1 :
|
||||
this._cardList.indexOf(item1);
|
||||
idx2 = (typeof item2 === 'number') ?
|
||||
idx2 = item2 :
|
||||
this._cardList.indexOf(item2);
|
||||
var tmp = this._cardList[idx1];
|
||||
this._cardList[idx1] = this._cardList[idx2];
|
||||
this._cardList[idx2] = tmp;
|
||||
|
||||
this.writeCardlistInCardStore().then(function() {
|
||||
that.fire('card-swapped',
|
||||
that._cardList[idx1], that._cardList[idx2], idx1, idx2);
|
||||
});
|
||||
}, this);
|
||||
},
|
||||
|
||||
init: function cm_init(mode) {
|
||||
var that = this;
|
||||
var appMgmt = navigator.mozApps.mgmt;
|
||||
|
||||
this._asyncSemaphore = new AsyncSemaphore();
|
||||
// protect from async access to cardList
|
||||
this._asyncSemaphore.v();
|
||||
|
||||
this._mode = mode || 'readwrite';
|
||||
// If we are running in readonly mode, we need to tell card store what
|
||||
// manifestURL of the datastore we are going to use, because we are not
|
||||
// using card store of current app.
|
||||
if (this._mode === 'readonly') {
|
||||
this._manifestURLOfCardStore =
|
||||
'app://smart-home.gaiamobile.org/manifest.webapp';
|
||||
}
|
||||
|
||||
return this._getPipedPromise('init', function(resolve, reject) {
|
||||
var request = appMgmt.getAll();
|
||||
request.onsuccess = function onsuccess(event) {
|
||||
event.target.result.forEach(function eachApp(app) {
|
||||
var manifest = app.manifest;
|
||||
if (!app.launch || !manifest || !manifest.icons ||
|
||||
that._isHiddenApp(manifest.role)) {
|
||||
return;
|
||||
}
|
||||
that.installedApps[app.manifestURL] = app;
|
||||
});
|
||||
|
||||
resolve(that._reloadCardList());
|
||||
};
|
||||
request.onerror = function() {
|
||||
reject();
|
||||
that._asyncSemaphore.p();
|
||||
};
|
||||
appMgmt.addEventListener('install', that);
|
||||
appMgmt.addEventListener('uninstall', that);
|
||||
}).then(function() {
|
||||
that._asyncSemaphore.p();
|
||||
});
|
||||
},
|
||||
|
||||
uninit: function cm_uninit() {
|
||||
var appMgmt = navigator.mozApps.mgmt;
|
||||
appMgmt.removeEventListener('install', this);
|
||||
appMgmt.removeEventListener('uninstall', this);
|
||||
|
||||
this._cardList = [];
|
||||
this._cardStore.off('change');
|
||||
this._cardStore = undefined;
|
||||
this.installedApps = {};
|
||||
},
|
||||
|
||||
getAppEntries: function cm_getAppEntries(manifestURL) {
|
||||
if (!manifestURL || !this.installedApps[manifestURL]) {
|
||||
return [];
|
||||
}
|
||||
|
||||
var manifest = this.installedApps[manifestURL].manifest ||
|
||||
this.installedApps[manifestURL].updateManifest;
|
||||
var entryPoints = manifest.entry_points;
|
||||
var entries = [];
|
||||
var removable = this.installedApps[manifestURL].removable;
|
||||
|
||||
if (!entryPoints || manifest.type !== 'certified') {
|
||||
entries.push({
|
||||
manifestURL: manifestURL,
|
||||
entryPoint: '',
|
||||
name: manifest.name,
|
||||
removable: removable,
|
||||
type: 'Application'
|
||||
});
|
||||
} else {
|
||||
for (var entryPoint in entryPoints) {
|
||||
if (entryPoints[entryPoint].icons) {
|
||||
entries.push({
|
||||
manifestURL: manifestURL,
|
||||
entryPoint: entryPoint,
|
||||
name: entryPoints[entryPoint].name,
|
||||
removable: removable,
|
||||
type: 'Application'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
return entries;
|
||||
},
|
||||
|
||||
getEntryManifest: function cm_getEntryManifest(manifestURL, entryPoint) {
|
||||
if (!manifestURL || !this.installedApps[manifestURL]) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var manifest = this.installedApps[manifestURL].manifest ||
|
||||
this.installedApps[manifestURL].updateManifest;
|
||||
|
||||
if (entryPoint) {
|
||||
var entry_manifest = manifest.entry_points[entryPoint];
|
||||
return entry_manifest || null;
|
||||
}
|
||||
|
||||
return manifest;
|
||||
},
|
||||
|
||||
getIconBlob: function cm_getIconBlob(options) {
|
||||
var manifestURL = options.manifestURL;
|
||||
var entryPoint = options.entryPoint;
|
||||
var preferredSize = options.preferredSize;
|
||||
var that = this;
|
||||
return new Promise(function(resolve, reject) {
|
||||
var entry_manifest = that.getEntryManifest(manifestURL, entryPoint);
|
||||
if (!entry_manifest) {
|
||||
reject('No manifest');
|
||||
}
|
||||
|
||||
var iconData = that._bestMatchingIcon(
|
||||
that.installedApps[manifestURL], entry_manifest, preferredSize);
|
||||
if (!iconData) {
|
||||
reject('No url');
|
||||
return;
|
||||
}
|
||||
|
||||
that._loadFile({
|
||||
url: iconData[0],
|
||||
responseType: 'blob'
|
||||
}).then(function onFulfill(blob) {
|
||||
resolve([blob, iconData[1]]);
|
||||
}, function onReject(error) {
|
||||
reject('Error on loading blob of ' + manifestURL);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
_getLocalizedName: function cm_getLocalizedName(manifestURL, lang) {
|
||||
if (!manifestURL || !lang) {
|
||||
return;
|
||||
}
|
||||
|
||||
var manifest = this.getEntryManifest(manifestURL);
|
||||
var locales = manifest.locales;
|
||||
var localizedName = locales && locales[lang] &&
|
||||
(locales[lang].short_name || locales[lang].name);
|
||||
return localizedName || manifest.short_name || manifest.name;
|
||||
},
|
||||
|
||||
// Resolve to one of the following forms:
|
||||
// 1. {raw: 'localized name'}
|
||||
// 2. {id: 'l10nid'}
|
||||
// 3. undefined
|
||||
resolveCardName: function cm_resolveCardName(card, lang) {
|
||||
var name;
|
||||
if (!card || !lang) {
|
||||
return name;
|
||||
}
|
||||
|
||||
// Priority is:
|
||||
// 1. user given name
|
||||
// 2. l10nId if any
|
||||
// 3. localized application/deck name if it is an application/deck
|
||||
if (card.name && card.name.raw) {
|
||||
name = {
|
||||
raw: card.name.raw
|
||||
};
|
||||
} else if (card.name && card.name.id) {
|
||||
name = card.name;
|
||||
} else if (card.nativeApp &&
|
||||
(card instanceof Application || card instanceof Deck)) {
|
||||
name = {
|
||||
raw: this._getLocalizedName(card.nativeApp.manifestURL, lang)
|
||||
};
|
||||
}
|
||||
return name;
|
||||
},
|
||||
|
||||
getCardList: function cm_getCardList() {
|
||||
var that = this;
|
||||
return this._getPipedPromise('getCardList', function(resolve, reject) {
|
||||
that._asyncSemaphore.wait(function() {
|
||||
resolve(that._reloadCardList());
|
||||
}, that);
|
||||
}).then(function() {
|
||||
return Promise.resolve(that._cardList);
|
||||
});
|
||||
},
|
||||
|
||||
getFilteredCardList: function cm_getFilteredCardList(filterName) {
|
||||
return this.getCardList().then(function(allCards) {
|
||||
var filteredCards = [];
|
||||
if (filterName === 'all') {
|
||||
filteredCards = allCards;
|
||||
} else if (filterName) {
|
||||
allCards.forEach(function(card) {
|
||||
if (card.group === filterName) {
|
||||
filteredCards.push(card);
|
||||
}
|
||||
});
|
||||
}
|
||||
return Promise.resolve(filteredCards);
|
||||
});
|
||||
},
|
||||
|
||||
// TODO: need to be protected by semaphore
|
||||
// TODO: some comparison are based on card type. We may move these part to
|
||||
// card class themselves.
|
||||
// There are three types of query:
|
||||
// 1. query by cardId
|
||||
// 2. query by manifestURL and optionally launchURL
|
||||
// 3. query by bookmark url
|
||||
// 4. query by cardEntry (i.e. serialized card)
|
||||
findCardFromCardList: function cm_findCardFromCardList(query) {
|
||||
var found;
|
||||
this._cardList.some(function(card) {
|
||||
if (card instanceof Folder) {
|
||||
// XXX: findCard() shares almost the same logic as
|
||||
// findCardFromCardList. We should consolidate them. See
|
||||
// http://bugzil.la/1156726
|
||||
found = card.findCard(query);
|
||||
if (found) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (card.cardId === query.cardId) {
|
||||
found = card;
|
||||
return true;
|
||||
} else if (query.manifestURL && card.nativeApp &&
|
||||
card.nativeApp.manifestURL === query.manifestURL) {
|
||||
|
||||
// if we specify launchURL in query, then we must compare
|
||||
// launchURL first
|
||||
if (query.launchURL) {
|
||||
if (card.launchURL === query.launchURL) {
|
||||
found = card;
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
found = card;
|
||||
return true;
|
||||
}
|
||||
} else if (query.url) {
|
||||
if (query.url === card.url) {
|
||||
found = card;
|
||||
return true;
|
||||
}
|
||||
} else if (query.cardEntry) {
|
||||
// XXX: this could be bad at performance because we serialize card
|
||||
// in every loop. We might need improvement on this query.
|
||||
if (JSON.stringify(card.serialize()) ===
|
||||
JSON.stringify(query.cardEntry)) {
|
||||
found = card;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
});
|
||||
return found;
|
||||
},
|
||||
|
||||
findContainingFolder: function cm_findContainingFolder(query) {
|
||||
var folder;
|
||||
this._cardList.some(function(card) {
|
||||
if (card instanceof Folder) {
|
||||
if (card.findCard(query)) {
|
||||
folder = card;
|
||||
}
|
||||
}
|
||||
});
|
||||
return folder;
|
||||
},
|
||||
|
||||
isPinned: function cm_isPinned(options) {
|
||||
var that = this;
|
||||
return this._getPipedPromise('isPinned', function(resolve, reject) {
|
||||
that._asyncSemaphore.wait(function() {
|
||||
resolve(that._reloadCardList());
|
||||
}, that);
|
||||
}).then(function() {
|
||||
return Promise.resolve(!!that.findCardFromCardList(options));
|
||||
});
|
||||
},
|
||||
|
||||
handleEvent: function cr_handleEvent(evt) {
|
||||
switch(evt.type) {
|
||||
case 'install':
|
||||
this._onAppInstall(evt);
|
||||
break;
|
||||
case 'uninstall':
|
||||
this._onAppUninstall(evt);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
SharedUtils.addMixin(CardManager, PipedPromise);
|
||||
|
||||
exports.CardManager = CardManager;
|
||||
}(window));
|
||||
Loading…
Reference in a new issue