mirror of
https://github.com/classilla/tenfourfox.git
synced 2026-03-11 07:42:00 +00:00
218 lines
6.3 KiB
JavaScript
218 lines
6.3 KiB
JavaScript
/* -*- 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 { Cc, Ci, Cu, ChromeWorker } = require("chrome");
|
|
const { Class } = require("sdk/core/heritage");
|
|
const { OS } = Cu.import("resource://gre/modules/osfile.jsm", {});
|
|
const { emit } = require("sdk/event/core");
|
|
const { Store } = require("devtools/client/projecteditor/lib/stores/base");
|
|
const { Task } = Cu.import("resource://gre/modules/Task.jsm", {});
|
|
const promise = require("promise");
|
|
const { on, forget } = require("devtools/client/projecteditor/lib/helpers/event");
|
|
const { FileResource } = require("devtools/client/projecteditor/lib/stores/resource");
|
|
const {Services} = Cu.import("resource://gre/modules/Services.jsm");
|
|
const {setTimeout, clearTimeout} = Cu.import("resource://gre/modules/Timer.jsm", {});
|
|
|
|
const CHECK_LINKED_DIRECTORY_DELAY = 5000;
|
|
const SHOULD_LIVE_REFRESH = true;
|
|
// XXX: Ignores should be customizable
|
|
const IGNORE_REGEX = /(^\.)|(\~$)|(^node_modules$)/;
|
|
|
|
/**
|
|
* A LocalStore object maintains a collection of Resource objects
|
|
* from the file system.
|
|
*
|
|
* This object emits the following events:
|
|
* - "resource-added": When a resource is added
|
|
* - "resource-removed": When a resource is removed
|
|
*/
|
|
var LocalStore = Class({
|
|
extends: Store,
|
|
|
|
defaultCategory: "js",
|
|
|
|
initialize: function(path) {
|
|
this.initStore();
|
|
this.path = OS.Path.normalize(path);
|
|
this.rootPath = this.path;
|
|
this.displayName = this.path;
|
|
this.root = this._forPath(this.path);
|
|
this.notifyAdd(this.root);
|
|
this.refreshLoop = this.refreshLoop.bind(this);
|
|
this.refreshLoop();
|
|
},
|
|
|
|
destroy: function() {
|
|
clearTimeout(this._refreshTimeout);
|
|
|
|
if (this._refreshDeferred) {
|
|
this._refreshDeferred.reject("destroy");
|
|
}
|
|
if (this.worker) {
|
|
this.worker.terminate();
|
|
}
|
|
|
|
this._refreshTimeout = null;
|
|
this._refreshDeferred = null;
|
|
this.worker = null;
|
|
|
|
if (this.root) {
|
|
forget(this, this.root);
|
|
this.root.destroy();
|
|
}
|
|
},
|
|
|
|
toString: function() { return "[LocalStore:" + this.path + "]" },
|
|
|
|
/**
|
|
* Return a FileResource object for the given path. If a FileInfo
|
|
* is provided the resource will use it, otherwise the FileResource
|
|
* might not have full information until the next refresh.
|
|
*
|
|
* The following parameters are passed into the FileResource constructor
|
|
* See resource.js for information about them
|
|
*
|
|
* @param String path
|
|
* @param FileInfo info
|
|
* @returns Resource
|
|
*/
|
|
_forPath: function(path, info=null) {
|
|
if (this.resources.has(path)) {
|
|
return this.resources.get(path);
|
|
}
|
|
|
|
let resource = FileResource(this, path, info);
|
|
this.resources.set(path, resource);
|
|
return resource;
|
|
},
|
|
|
|
/**
|
|
* Return a promise that resolves to a fully-functional FileResource
|
|
* within this project. This will hit the disk for stat info.
|
|
* options:
|
|
*
|
|
* create: If true, a resource will be created even if the underlying
|
|
* file doesn't exist.
|
|
*/
|
|
resourceFor: function(path, options) {
|
|
path = OS.Path.normalize(path);
|
|
|
|
if (this.resources.has(path)) {
|
|
return promise.resolve(this.resources.get(path));
|
|
}
|
|
|
|
if (!this.contains(path)) {
|
|
return promise.reject(new Error(path + " does not belong to " + this.path));
|
|
}
|
|
|
|
return Task.spawn(function*() {
|
|
let parent = yield this.resourceFor(OS.Path.dirname(path));
|
|
|
|
let info;
|
|
try {
|
|
info = yield OS.File.stat(path);
|
|
} catch (ex if ex instanceof OS.File.Error && ex.becauseNoSuchFile) {
|
|
if (!options.create) {
|
|
throw ex;
|
|
}
|
|
}
|
|
|
|
let resource = this._forPath(path, info);
|
|
parent.addChild(resource);
|
|
return resource;
|
|
}.bind(this));
|
|
},
|
|
|
|
refreshLoop: function() {
|
|
// XXX: Once Bug 958280 adds a watch function, will not need to forever loop here.
|
|
this.refresh().then(() => {
|
|
if (SHOULD_LIVE_REFRESH) {
|
|
this._refreshTimeout = setTimeout(this.refreshLoop,
|
|
CHECK_LINKED_DIRECTORY_DELAY);
|
|
}
|
|
});
|
|
},
|
|
|
|
_refreshTimeout: null,
|
|
_refreshDeferred: null,
|
|
|
|
/**
|
|
* Refresh the directory structure.
|
|
*/
|
|
refresh: function(path=this.rootPath) {
|
|
if (this._refreshDeferred) {
|
|
return this._refreshDeferred.promise;
|
|
}
|
|
this._refreshDeferred = promise.defer();
|
|
|
|
let worker = this.worker = new ChromeWorker("chrome://devtools/content/projecteditor/lib/helpers/readdir.js");
|
|
let start = Date.now();
|
|
|
|
worker.onmessage = evt => {
|
|
// console.log("Directory read finished in " + ( Date.now() - start ) +"ms", evt);
|
|
for (path in evt.data) {
|
|
let info = evt.data[path];
|
|
info.path = path;
|
|
|
|
let resource = this._forPath(path, info);
|
|
resource.info = info;
|
|
if (info.isDir) {
|
|
let newChildren = new Set();
|
|
for (let childPath of info.children) {
|
|
childInfo = evt.data[childPath];
|
|
newChildren.add(this._forPath(childPath, childInfo));
|
|
}
|
|
resource.setChildren(newChildren);
|
|
}
|
|
resource.info.children = null;
|
|
}
|
|
|
|
worker = null;
|
|
this._refreshDeferred.resolve();
|
|
this._refreshDeferred = null;
|
|
};
|
|
worker.onerror = ex => {
|
|
console.error(ex);
|
|
worker = null;
|
|
this._refreshDeferred.reject(ex);
|
|
this._refreshDeferred = null;
|
|
}
|
|
worker.postMessage({ path: this.rootPath, ignore: IGNORE_REGEX });
|
|
return this._refreshDeferred.promise;
|
|
},
|
|
|
|
/**
|
|
* Returns true if the given path would be a child of the store's
|
|
* root directory.
|
|
*/
|
|
contains: function(path) {
|
|
path = OS.Path.normalize(path);
|
|
let thisPath = OS.Path.split(this.rootPath);
|
|
let thatPath = OS.Path.split(path)
|
|
|
|
if (!(thisPath.absolute && thatPath.absolute)) {
|
|
throw new Error("Contains only works with absolute paths.");
|
|
}
|
|
|
|
if (thisPath.winDrive && (thisPath.winDrive != thatPath.winDrive)) {
|
|
return false;
|
|
}
|
|
|
|
if (thatPath.components.length <= thisPath.components.length) {
|
|
return false;
|
|
}
|
|
|
|
for (let i = 0; i < thisPath.components.length; i++) {
|
|
if (thisPath.components[i] != thatPath.components[i]) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
});
|
|
exports.LocalStore = LocalStore;
|
|
|