michael@0: const {Cc,Ci,Cu} = require("chrome"); michael@0: const ObservableObject = require("devtools/shared/observable-object"); michael@0: const promise = require("devtools/toolkit/deprecated-sync-thenables"); michael@0: michael@0: const {EventEmitter} = Cu.import("resource://gre/modules/devtools/event-emitter.js"); michael@0: const {generateUUID} = Cc['@mozilla.org/uuid-generator;1'].getService(Ci.nsIUUIDGenerator); michael@0: michael@0: /** michael@0: * IndexedDB wrapper that just save project objects michael@0: * michael@0: * The only constraint is that project objects have to have michael@0: * a unique `location` object. michael@0: */ michael@0: michael@0: const global = this; michael@0: const IDB = { michael@0: _db: null, michael@0: michael@0: open: function () { michael@0: let deferred = promise.defer(); michael@0: michael@0: var idbManager = Cc["@mozilla.org/dom/indexeddb/manager;1"] michael@0: .getService(Ci.nsIIndexedDatabaseManager); michael@0: idbManager.initWindowless(global); michael@0: michael@0: let request = global.indexedDB.open("AppProjects", 5); michael@0: request.onerror = function(event) { michael@0: deferred.reject("Unable to open AppProjects indexedDB. " + michael@0: "Error code: " + event.target.errorCode); michael@0: }; michael@0: request.onupgradeneeded = function(event) { michael@0: let db = event.target.result; michael@0: db.createObjectStore("projects", { keyPath: "location" }); michael@0: }; michael@0: michael@0: request.onsuccess = function() { michael@0: let db = IDB._db = request.result; michael@0: let objectStore = db.transaction("projects").objectStore("projects"); michael@0: let projects = [] michael@0: objectStore.openCursor().onsuccess = function(event) { michael@0: let cursor = event.target.result; michael@0: if (cursor) { michael@0: if (cursor.value.location) { michael@0: // We need to make sure this object has a `.location` property. michael@0: // The UI depends on this property. michael@0: // This should not be needed as we make sure to register valid michael@0: // projects, but in the past (before bug 924568), we might have michael@0: // registered invalid objects. michael@0: projects.push(cursor.value); michael@0: } michael@0: cursor.continue(); michael@0: } else { michael@0: deferred.resolve(projects); michael@0: } michael@0: }; michael@0: }; michael@0: michael@0: return deferred.promise; michael@0: }, michael@0: michael@0: add: function(project) { michael@0: let deferred = promise.defer(); michael@0: michael@0: if (!project.location) { michael@0: // We need to make sure this object has a `.location` property. michael@0: deferred.reject("Missing location property on project object."); michael@0: } else { michael@0: let transaction = IDB._db.transaction(["projects"], "readwrite"); michael@0: let objectStore = transaction.objectStore("projects"); michael@0: let request = objectStore.add(project); michael@0: request.onerror = function(event) { michael@0: deferred.reject("Unable to add project to the AppProjects indexedDB: " + michael@0: this.error.name + " - " + this.error.message ); michael@0: }; michael@0: request.onsuccess = function() { michael@0: deferred.resolve(); michael@0: }; michael@0: } michael@0: michael@0: return deferred.promise; michael@0: }, michael@0: michael@0: update: function(project) { michael@0: let deferred = promise.defer(); michael@0: michael@0: var transaction = IDB._db.transaction(["projects"], "readwrite"); michael@0: var objectStore = transaction.objectStore("projects"); michael@0: var request = objectStore.put(project); michael@0: request.onerror = function(event) { michael@0: deferred.reject("Unable to update project to the AppProjects indexedDB: " + michael@0: this.error.name + " - " + this.error.message ); michael@0: }; michael@0: request.onsuccess = function() { michael@0: deferred.resolve(); michael@0: }; michael@0: michael@0: return deferred.promise; michael@0: }, michael@0: michael@0: remove: function(location) { michael@0: let deferred = promise.defer(); michael@0: michael@0: let request = IDB._db.transaction(["projects"], "readwrite") michael@0: .objectStore("projects") michael@0: .delete(location); michael@0: request.onsuccess = function(event) { michael@0: deferred.resolve(); michael@0: }; michael@0: request.onerror = function() { michael@0: deferred.reject("Unable to delete project to the AppProjects indexedDB: " + michael@0: this.error.name + " - " + this.error.message ); michael@0: }; michael@0: michael@0: return deferred.promise; michael@0: } michael@0: }; michael@0: michael@0: const store = new ObservableObject({ projects:[] }); michael@0: michael@0: let loadDeferred = promise.defer(); michael@0: michael@0: IDB.open().then(function (projects) { michael@0: store.object.projects = projects; michael@0: AppProjects.emit("ready", store.object.projects); michael@0: loadDeferred.resolve(); michael@0: }); michael@0: michael@0: const AppProjects = { michael@0: load: function() { michael@0: return loadDeferred.promise; michael@0: }, michael@0: michael@0: addPackaged: function(folder) { michael@0: let project = { michael@0: type: "packaged", michael@0: location: folder.path, michael@0: // We need a unique id, that is the app origin, michael@0: // in order to identify the app when being installed on the device. michael@0: // The packaged app local path is a valid id, but only on the client. michael@0: // This origin will be used to generate the true id of an app: michael@0: // its manifest URL. michael@0: // If the app ends up specifying an explicit origin in its manifest, michael@0: // we will override this random UUID on app install. michael@0: packagedAppOrigin: generateUUID().toString().slice(1, -1) michael@0: }; michael@0: return IDB.add(project).then(function () { michael@0: store.object.projects.push(project); michael@0: // return the added objects (proxified) michael@0: return store.object.projects[store.object.projects.length - 1]; michael@0: }); michael@0: }, michael@0: michael@0: addHosted: function(manifestURL) { michael@0: let project = { michael@0: type: "hosted", michael@0: location: manifestURL michael@0: }; michael@0: return IDB.add(project).then(function () { michael@0: store.object.projects.push(project); michael@0: // return the added objects (proxified) michael@0: return store.object.projects[store.object.projects.length - 1]; michael@0: }); michael@0: }, michael@0: michael@0: update: function (project) { michael@0: return IDB.update({ michael@0: type: project.type, michael@0: location: project.location, michael@0: packagedAppOrigin: project.packagedAppOrigin michael@0: }).then(() => project); michael@0: }, michael@0: michael@0: remove: function(location) { michael@0: return IDB.remove(location).then(function () { michael@0: let projects = store.object.projects; michael@0: for (let i = 0; i < projects.length; i++) { michael@0: if (projects[i].location == location) { michael@0: projects.splice(i, 1); michael@0: return; michael@0: } michael@0: } michael@0: throw new Error("Unable to find project in AppProjects store"); michael@0: }); michael@0: }, michael@0: michael@0: get: function(location) { michael@0: let projects = store.object.projects; michael@0: for (let i = 0; i < projects.length; i++) { michael@0: if (projects[i].location == location) { michael@0: return projects[i]; michael@0: } michael@0: } michael@0: return null; michael@0: }, michael@0: michael@0: store: store michael@0: }; michael@0: michael@0: EventEmitter.decorate(AppProjects); michael@0: michael@0: exports.AppProjects = AppProjects; michael@0: