summaryrefslogtreecommitdiffstats
path: root/devtools/client/projecteditor/lib/project.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/projecteditor/lib/project.js')
-rw-r--r--devtools/client/projecteditor/lib/project.js246
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;