diff options
Diffstat (limited to 'devtools/client/projecteditor/lib/project.js')
-rw-r--r-- | devtools/client/projecteditor/lib/project.js | 246 |
1 files changed, 246 insertions, 0 deletions
diff --git a/devtools/client/projecteditor/lib/project.js b/devtools/client/projecteditor/lib/project.js new file mode 100644 index 000000000..8e0a8802d --- /dev/null +++ b/devtools/client/projecteditor/lib/project.js @@ -0,0 +1,246 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +const { Cu } = require("chrome"); +const { Class } = require("sdk/core/heritage"); +const { EventTarget } = require("sdk/event/target"); +const { emit } = require("sdk/event/core"); +const { scope, on, forget } = require("devtools/client/projecteditor/lib/helpers/event"); +const prefs = require("sdk/preferences/service"); +const { LocalStore } = require("devtools/client/projecteditor/lib/stores/local"); +const { OS } = Cu.import("resource://gre/modules/osfile.jsm", {}); +const { Task } = require("devtools/shared/task"); +const promise = require("promise"); +const { TextEncoder, TextDecoder } = require("sdk/io/buffer"); +const url = require("sdk/url"); + +const gDecoder = new TextDecoder(); +const gEncoder = new TextEncoder(); + +/** + * A Project keeps track of the opened folders using LocalStore + * objects. Resources are generally requested from the project, + * even though the Store is actually keeping track of them. + * + * + * This object emits the following events: + * - "refresh-complete": After all stores have been refreshed from disk. + * - "store-added": When a store has been added to the project. + * - "store-removed": When a store has been removed from the project. + * - "resource-added": When a resource has been added to one of the stores. + * - "resource-removed": When a resource has been removed from one of the stores. + */ +var Project = Class({ + extends: EventTarget, + + /** + * Intialize the Project. + * + * @param Object options + * Options to be passed into Project.load function + */ + initialize: function (options) { + this.localStores = new Map(); + + this.load(options); + }, + + destroy: function () { + // We are removing the store because the project never gets persisted. + // There may need to be separate destroy functionality that doesn't remove + // from project if this is saved to DB. + this.removeAllStores(); + }, + + toString: function () { + return "[Project] " + this.name; + }, + + /** + * Load a project given metadata about it. + * + * @param Object options + * Information about the project, containing: + * id: An ID (currently unused, but could be used for saving) + * name: The display name of the project + * directories: An array of path strings to load + */ + load: function (options) { + this.id = options.id; + this.name = options.name || "Untitled"; + + let paths = new Set(options.directories.map(name => OS.Path.normalize(name))); + + for (let [path, store] of this.localStores) { + if (!paths.has(path)) { + this.removePath(path); + } + } + + for (let path of paths) { + this.addPath(path); + } + }, + + /** + * Refresh all project stores from disk + * + * @returns Promise + * A promise that resolves when everything has been refreshed. + */ + refresh: function () { + return Task.spawn(function* () { + for (let [path, store] of this.localStores) { + yield store.refresh(); + } + emit(this, "refresh-complete"); + }.bind(this)); + }, + + + /** + * Fetch a resource from the backing storage system for the store. + * + * @param string path + * The path to fetch + * @param Object options + * "create": bool indicating whether to create a file if it does not exist. + * @returns Promise + * A promise that resolves with the Resource. + */ + resourceFor: function (path, options) { + let store = this.storeContaining(path); + return store.resourceFor(path, options); + }, + + /** + * Get every resource used inside of the project. + * + * @returns Array<Resource> + * A list of all Resources in all Stores. + */ + allResources: function () { + let resources = []; + for (let store of this.allStores()) { + resources = resources.concat(store.allResources()); + } + return resources; + }, + + /** + * Get every Path used inside of the project. + * + * @returns generator-iterator<Store> + * A list of all Stores + */ + allStores: function* () { + for (let [path, store] of this.localStores) { + yield store; + } + }, + + /** + * Get every file path used inside of the project. + * + * @returns Array<string> + * A list of all file paths + */ + allPaths: function () { + return [...this.localStores.keys()]; + }, + + /** + * Get the store that contains a path. + * + * @returns Store + * The store, if any. Will return null if no store + * contains the given path. + */ + storeContaining: function (path) { + let containingStore = null; + for (let store of this.allStores()) { + if (store.contains(path)) { + // With nested projects, the final containing store will be returned. + containingStore = store; + } + } + return containingStore; + }, + + /** + * Add a store at the current path. If a store already exists + * for this path, then return it. + * + * @param string path + * @returns LocalStore + */ + addPath: function (path) { + if (!this.localStores.has(path)) { + this.addLocalStore(new LocalStore(path)); + } + return this.localStores.get(path); + }, + + /** + * Remove a store for a given path. + * + * @param string path + */ + removePath: function (path) { + this.removeLocalStore(this.localStores.get(path)); + }, + + + /** + * Add the given Store to the project. + * Fires a 'store-added' event on the project. + * + * @param Store store + */ + addLocalStore: function (store) { + store.canPair = true; + this.localStores.set(store.path, store); + + // Originally StoreCollection.addStore + on(this, store, "resource-added", (resource) => { + emit(this, "resource-added", resource); + }); + on(this, store, "resource-removed", (resource) => { + emit(this, "resource-removed", resource); + }); + + emit(this, "store-added", store); + }, + + + /** + * Remove all of the Stores belonging to the project. + */ + removeAllStores: function () { + for (let store of this.allStores()) { + this.removeLocalStore(store); + } + }, + + /** + * Remove the given Store from the project. + * Fires a 'store-removed' event on the project. + * + * @param Store store + */ + removeLocalStore: function (store) { + // XXX: tree selection should be reset if active element is affected by + // the store being removed + if (store) { + this.localStores.delete(store.path); + forget(this, store); + emit(this, "store-removed", store); + store.destroy(); + } + } +}); + +exports.Project = Project; |