From d3f8fb1be7571496e5b518cd373babe7ac1efeea Mon Sep 17 00:00:00 2001 From: Steve Pletcher Date: Sat, 19 Dec 2015 14:14:38 -0500 Subject: [PATCH 1/2] Add collaborator.js --- src/config.json | 53 +++--- src/document/Document.js | 168 +++++++++--------- src/document/DocumentManager.js | 176 +++++++++--------- src/filesystem/FileSystem.js | 286 +++++++++++++++--------------- src/filesystem/FileSystemEntry.js | 167 ++++++++--------- 5 files changed, 431 insertions(+), 419 deletions(-) diff --git a/src/config.json b/src/config.json index eee1df205cb..9746a547a91 100644 --- a/src/config.json +++ b/src/config.json @@ -20,7 +20,7 @@ "extension_url": "https://s3.amazonaws.com/extend.brackets/{0}/{0}-{1}.zip", "linting.enabled_by_default": true, "build_timestamp": "", - "healthDataServerURL" : "https://healthdev.brackets.io/healthDataLog" + "healthDataServerURL": "https://health.brackets.io/healthDataLog" }, "name": "Brackets", "version": "1.4.0-0", @@ -35,42 +35,51 @@ "branch": "", "SHA": "" }, - "devDependencies": { - "grunt": "0.4.1", - "jasmine-node": "1.11.0", - "grunt-jasmine-node": "0.1.0", + "dependencies": { + "autoprefixer-core": "5.1.8", + "aws-sdk": "^2.2.9", + "grunt": "0.4.5", "grunt-cli": "0.1.9", - "phantomjs": "1.9.13", - "grunt-lib-phantomjs": "0.3.0", - "grunt-contrib-jshint": "0.6.0", - "grunt-contrib-watch": "0.4.3", - "grunt-contrib-jasmine": "0.4.2", - "grunt-template-jasmine-requirejs": "0.1.0", - "grunt-contrib-cssmin": "0.6.0", "grunt-contrib-clean": "0.4.1", + "grunt-contrib-concat": "0.3.0", + "grunt-contrib-compress": "git://github.com/sedge/grunt-contrib-compress.git#fix-gruntjs#61", "grunt-contrib-copy": "0.4.1", + "grunt-contrib-cssmin": "0.6.0", "grunt-contrib-htmlmin": "0.1.3", + "grunt-contrib-jasmine": "0.4.2", + "grunt-contrib-jshint": "0.6.0", "grunt-contrib-less": "0.8.2", + "grunt-postcss": "0.3.0", "grunt-contrib-requirejs": "0.4.1", "grunt-contrib-uglify": "0.2.0", - "grunt-contrib-concat": "0.3.0", + "grunt-contrib-watch": "0.4.3", + "grunt-git": "^0.3.4", + "grunt-jasmine-node": "0.1.0", + "grunt-lib-phantomjs": "0.3.0", + "grunt-npm": "git://github.com/sedge/grunt-npm.git#branchcheck", + "grunt-shell": "^1.1.2", "grunt-targethtml": "0.2.6", + "grunt-template-jasmine-requirejs": "0.1.0", + "grunt-text-replace": "^0.4.0", + "grunt-update-submodules": "^0.4.1", "grunt-usemin": "0.1.11", + "habitat": "^3.1.2", + "jasmine-node": "1.11.0", + "jshint": "2.1.4", "load-grunt-tasks": "0.2.0", + "phantomjs": "1.9.13", "q": "0.9.2", "semver": "^4.1.0", - "jshint": "2.1.4", "xmldoc": "^0.1.2", "grunt-cleanempty": "1.0.3" }, + "devDependencies": { + "http-server": "^0.8.0" + }, "scripts": { "postinstall": "grunt install", - "test": "grunt cla-check-pull test" + "test": "grunt test && grunt build-browser-compressed", + "start": "http-server -p 8000 --cors" }, - "licenses": [ - { - "type": "MIT", - "url": "https://github.com/adobe/brackets/blob/master/LICENSE" - } - ] -} + "license": "MIT" +} \ No newline at end of file diff --git a/src/document/Document.js b/src/document/Document.js index 31a36b0b696..5b601d3a410 100644 --- a/src/document/Document.js +++ b/src/document/Document.js @@ -1,24 +1,24 @@ /* * Copyright (c) 2012 Adobe Systems Incorporated. All rights reserved. - * + * * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: - * + * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. - * + * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. - * + * */ @@ -27,7 +27,7 @@ define(function (require, exports, module) { "use strict"; - + var EditorManager = require("editor/EditorManager"), EventDispatcher = require("utils/EventDispatcher"), FileUtils = require("file/FileUtils"), @@ -36,14 +36,14 @@ define(function (require, exports, module) { LanguageManager = require("language/LanguageManager"), CodeMirror = require("thirdparty/CodeMirror/lib/codemirror"), _ = require("thirdparty/lodash"); - + /** * Model for the contents of a single file and its current modification state. * See DocumentManager documentation for important usage notes. * * Document dispatches these events: * - * __change__ -- When the text of the editor changes (including due to undo/redo). + * __change__ -- When the text of the editor changes (including due to undo/redo). * * Passes ({Document}, {ChangeList}), where ChangeList is an array * of change record objects. Each change record looks like: @@ -51,7 +51,7 @@ define(function (require, exports, module) { * { from: start of change, expressed as {line: , ch: }, * to: end of change, expressed as {line: , ch: }, * text: array of lines of text to replace existing text } - * + * * The line and ch offsets are both 0-based. * * The ch offset in "from" is inclusive, but the ch offset in "to" is exclusive. For example, @@ -60,13 +60,13 @@ define(function (require, exports, module) { * * If "from" and "to" are undefined, then this is a replacement of the entire text content. * - * IMPORTANT: If you listen for the "change" event, you MUST also addRef() the document + * IMPORTANT: If you listen for the "change" event, you MUST also addRef() the document * (and releaseRef() it whenever you stop listening). You should also listen to the "deleted" * event. * * __deleted__ -- When the file for this document has been deleted. All views onto the document should * be closed. The document will no longer be editable or dispatch "change" events. - * + * * __languageChanged__ -- When the value of getLanguage() has changed. 2nd argument is the old value, * 3rd argument is the new value. * @@ -80,15 +80,15 @@ define(function (require, exports, module) { this._updateLanguage(); this.refreshText(rawText, initialTimestamp, true); } - + EventDispatcher.makeEventDispatcher(Document.prototype); - + /** * Number of clients who want this Document to stay alive. The Document is listed in * DocumentManager._openDocuments whenever refCount > 0. */ Document.prototype._refCount = 0; - + /** * The File for this document. Need not lie within the project. * If Document is untitled, this is an InMemoryFile object. @@ -101,27 +101,27 @@ define(function (require, exports, module) { * @type {!Language} */ Document.prototype.language = null; - + /** * Whether this document has unsaved changes or not. * When this changes on any Document, DocumentManager dispatches a "dirtyFlagChange" event. * @type {boolean} */ Document.prototype.isDirty = false; - + /** * Whether this document is currently being saved. * @type {boolean} */ Document.prototype.isSaving = false; - + /** * What we expect the file's timestamp to be on disk. If the timestamp differs from this, then * it means the file was modified by an app other than Brackets. * @type {!Date} */ Document.prototype.diskTimestamp = null; - + /** * The timestamp of the document at the point where the user last said to keep changes that conflict * with the current disk version. Can also be -1, indicating that the file was deleted on disk at the @@ -137,13 +137,13 @@ define(function (require, exports, module) { * @type {boolean} */ Document.prototype._refreshInProgress = false; - + /** * The text contents of the file, or null if our backing model is _masterEditor. * @type {?string} */ Document.prototype._text = null; - + /** * Editor object representing the full-size editor UI for this document. May be null if Document * has not yet been modified or been the currentDocument; in that case, our backing model is the @@ -151,18 +151,18 @@ define(function (require, exports, module) { * @type {?Editor} */ Document.prototype._masterEditor = null; - + /** * The content's line-endings style. If a Document is created on empty text, or text with * inconsistent line endings, defaults to the current platform's standard endings. * @type {FileUtils.LINE_ENDINGS_CRLF|FileUtils.LINE_ENDINGS_LF} */ Document.prototype._lineEndings = null; - + /** Add a ref to keep this Document alive */ Document.prototype.addRef = function () { //console.log("+++REF+++ "+this); - + if (this._refCount === 0) { //console.log("+++ adding to open list"); if (exports.trigger("_afterDocumentCreate", this)) { @@ -187,7 +187,7 @@ define(function (require, exports, module) { } } }; - + /** * Attach a backing Editor to the Document, enabling setText() to be called. Assumes Editor has * already been initialized with the value of getText(). ONLY Editor should call this (and only @@ -203,7 +203,7 @@ define(function (require, exports, module) { masterEditor.on("change", this._handleEditorChange.bind(this)); } }; - + /** * Detach the backing Editor from the Document, disallowing setText(). The text content is * stored back onto _text so other Document clients continue to have read-only access. ONLY @@ -218,7 +218,7 @@ define(function (require, exports, module) { this._masterEditor = null; } }; - + /** * Guarantees that _masterEditor is non-null. If needed, asks EditorManager to create a new master * editor bound to this Document (which in turn causes Document._makeEditable() to be called). @@ -229,7 +229,7 @@ define(function (require, exports, module) { EditorManager._createUnattachedMasterEditor(this); } }; - + /** * Returns the document's current contents; may not be saved to disk yet. Whenever this * value changes, the Document dispatches a "change" event. @@ -250,7 +250,7 @@ define(function (require, exports, module) { } } return codeMirrorText; - + } else { // Optimized path that doesn't require creating master editor if (useOriginalLineEndings) { @@ -260,12 +260,12 @@ define(function (require, exports, module) { } } }; - + /** Normalizes line endings the same way CodeMirror would */ Document.normalizeText = function (text) { return text.replace(/\r\n/g, "\n"); }; - + /** * Sets the contents of the document. Treated as an edit. Line endings will be rewritten to * match the document's current line-ending style. @@ -276,7 +276,7 @@ define(function (require, exports, module) { this._masterEditor._codeMirror.setValue(text); // _handleEditorChange() triggers "change" event }; - + /** * @private * Triggers the appropriate events when a change occurs: "change" on the Document instance @@ -284,10 +284,12 @@ define(function (require, exports, module) { * @param {Object} changeList Changelist in CodeMirror format */ Document.prototype._notifyDocumentChange = function (changeList) { + // [SJP] Every time document changes, THIS TRIGGERS. + // Any network syncing should happen HERE. this.trigger("change", this, changeList); exports.trigger("documentChange", this, changeList); }; - + /** * Sets the contents of the document. Treated as reloading the document from disk: the document * will be marked clean with a new timestamp, the undo/redo history is cleared, and we re-check @@ -303,13 +305,13 @@ define(function (require, exports, module) { // If clean, don't transiently mark dirty during refresh // (we'll still send change events though, of course) this._refreshInProgress = true; - + if (this._masterEditor) { this._masterEditor._resetText(text); // clears undo history too // _handleEditorChange() triggers "change" event for us } else { this._text = text; - + if (!initial) { // We fake a change record here that looks like CodeMirror's text change records, but // omits "from" and "to", by which we mean the entire text has changed. @@ -320,33 +322,33 @@ define(function (require, exports, module) { } } this._updateTimestamp(newTimestamp); - + // If Doc was dirty before refresh, reset it to clean now (don't always call, to avoid no-op dirtyFlagChange events) Since // _resetText() above already ensures Editor state is clean, it's safe to skip _markClean() as long as our own state is already clean too. if (this.isDirty) { this._markClean(); } this._refreshInProgress = false; - + // Sniff line-ending style this._lineEndings = FileUtils.sniffLineEndings(text); if (!this._lineEndings) { this._lineEndings = FileUtils.getPlatformLineEndings(); } - + exports.trigger("_documentRefreshed", this); PerfUtils.addMeasurement(perfTimerName); }; - + /** * Adds, replaces, or removes text. If a range is given, the text at that range is replaced with the * given new text; if text == "", then the entire range is effectively deleted. If 'end' is omitted, * then the new text is inserted at that point and all existing text is preserved. Line endings will * be rewritten to match the document's current line-ending style. - * + * * IMPORTANT NOTE: Because of #1688, do not use this in cases where you might be - * operating on a linked document (like the main document for an inline editor) + * operating on a linked document (like the main document for an inline editor) * during an outer CodeMirror operation (like a key event that's handled by the * editor itself). A common case of this is code hints in inline editors. In * such cases, use `editor._codeMirror.replaceRange()` instead. This should be @@ -356,7 +358,7 @@ define(function (require, exports, module) { * @param {!{line:number, ch:number}} start Start of range, inclusive (if 'to' specified) or insertion point (if not) * @param {?{line:number, ch:number}} end End of range, exclusive; optional * @param {?string} origin Optional string used to batch consecutive edits for undo. - * If origin starts with "+", then consecutive edits with the same origin will be batched for undo if + * If origin starts with "+", then consecutive edits with the same origin will be batched for undo if * they are close enough together in time. * If origin starts with "*", then all consecutive edit with the same origin will be batched for * undo. @@ -369,7 +371,7 @@ define(function (require, exports, module) { this._masterEditor._codeMirror.replaceRange(text, start, end, origin); // _handleEditorChange() triggers "change" event }; - + /** * Returns the characters in the given range. Line endings are normalized to '\n'. * @param {!{line:number, ch:number}} start Start of range, inclusive @@ -380,7 +382,7 @@ define(function (require, exports, module) { this._ensureMasterEditor(); return this._masterEditor._codeMirror.getRange(start, end); }; - + /** * Returns the text of the given line (excluding any line ending characters) * @param {number} Zero-based line number @@ -390,7 +392,7 @@ define(function (require, exports, module) { this._ensureMasterEditor(); return this._masterEditor._codeMirror.getLine(lineNum); }; - + /** * Batches a series of related Document changes. Repeated calls to replaceRange() should be wrapped in a * batch for efficiency. Begins the batch, calls doOperation(), ends the batch, and then returns. @@ -398,11 +400,11 @@ define(function (require, exports, module) { */ Document.prototype.batchOperation = function (doOperation) { this._ensureMasterEditor(); - + var self = this; self._masterEditor._codeMirror.operation(doOperation); }; - + /** * Handles changes from the master backing Editor. Changes are triggered either by direct edits * to that Editor's UI, OR by our setText()/refreshText() methods. @@ -416,17 +418,17 @@ define(function (require, exports, module) { // Sync isDirty from CodeMirror state var wasDirty = this.isDirty; this.isDirty = !editor._codeMirror.isClean(); - + // Notify if isDirty just changed (this also auto-adds us to working set if needed) if (wasDirty !== this.isDirty) { exports.trigger("_dirtyFlagChange", this); } } - + // Notify that Document's text has changed this._notifyDocumentChange(changeList); }; - + /** * @private */ @@ -437,7 +439,7 @@ define(function (require, exports, module) { } exports.trigger("_dirtyFlagChange", this); }; - + /** * @private */ @@ -446,8 +448,8 @@ define(function (require, exports, module) { // Clear the "keep changes" timestamp since it's no longer relevant. this.keepChangesTime = null; }; - - /** + + /** * Called when the document is saved (which currently happens in DocumentCommandHandlers). Marks the * document not dirty and notifies listeners of the save. */ @@ -455,9 +457,9 @@ define(function (require, exports, module) { if (!this._masterEditor) { console.log("### Warning: saving a Document that is not modifiable!"); } - + this._markClean(); - + // TODO: (issue #295) fetching timestamp async creates race conditions (albeit unlikely ones) var thisDoc = this; this.file.stat(function (err, stat) { @@ -469,11 +471,11 @@ define(function (require, exports, module) { exports.trigger("_documentSaved", thisDoc); }); }; - + /** - * Adjusts a given position taking a given replaceRange-type edit into account. + * Adjusts a given position taking a given replaceRange-type edit into account. * If the position is within the original edit range (start and end inclusive), - * it gets pushed to the end of the content that replaced the range. Otherwise, + * it gets pushed to the end of the content that replaced the range. Otherwise, * if it's after the edit, it gets adjusted so it refers to the same character * it did before the edit. * @param {!{line:number, ch: number}} pos The position to adjust. @@ -501,7 +503,7 @@ define(function (require, exports, module) { } return {line: line, ch: ch}; }; - + /** * Like _.each(), but if given a single item not in an array, acts as * if it were an array containing just that item. @@ -513,7 +515,7 @@ define(function (require, exports, module) { cb(itemOrArr, 0); } } - + /** * Helper function for edit operations that operate on multiple selections. Takes an "edit list" * that specifies a list of replaceRanges that should occur, but where all the positions are with @@ -533,15 +535,15 @@ define(function (require, exports, module) { * * @param {!Array.<{edit: {text: string, start:{line: number, ch: number}, end:?{line: number, ch: number}} * | Array.<{text: string, start:{line: number, ch: number}, end:?{line: number, ch: number}}>, - * selection: ?{start:{line:number, ch:number}, end:{line:number, ch:number}, + * selection: ?{start:{line:number, ch:number}, end:{line:number, ch:number}, * primary:boolean, reversed: boolean, isBeforeEdit: boolean}>} - * | ?Array.<{start:{line:number, ch:number}, end:{line:number, ch:number}, + * | ?Array.<{start:{line:number, ch:number}, end:{line:number, ch:number}, * primary:boolean, reversed: boolean, isBeforeEdit: boolean}>}>} edits * Specifies the list of edits to perform in a manner similar to CodeMirror's `replaceRange`. This array * will be mutated. * * `edit` is the edit to perform: - * `text` will replace the current contents of the range between `start` and `end`. + * `text` will replace the current contents of the range between `start` and `end`. * If `end` is unspecified, the text is inserted at `start`. * `start` and `end` should be positions relative to the document *ignoring* all other edit descriptions * (i.e., as if you were only performing this one edit on the document). @@ -560,7 +562,7 @@ define(function (require, exports, module) { * and then specify a resulting selection that shouldn't be fixed up for any of those edits (but should be * fixed up for edits related to other selections). It can also be useful if you have several selections * that should ignore the effects of a given edit because you've fixed them up already (this commonly happens - * with line-oriented edits where multiple cursors on the same line should be ignored, but still tracked). + * with line-oriented edits where multiple cursors on the same line should be ignored, but still tracked). * Within an edit group, edit positions must be specified relative to previous edits within that group. Also, * the total bounds of edit groups must not overlap (e.g. edits in one group can't surround an edit from another group). * @@ -570,7 +572,7 @@ define(function (require, exports, module) { */ Document.prototype.doMultipleEdits = function (edits, origin) { var self = this; - + // Sort the edits backwards, so we don't have to adjust the edit positions as we go along // (though we do have to adjust the selection positions). edits.sort(function (editDesc1, editDesc2) { @@ -586,11 +588,11 @@ define(function (require, exports, module) { return CodeMirror.cmpPos(edit2.start, edit1.start); } }); - + // Pull out the selections, in the same order as the edits. var result = _.cloneDeep(_.pluck(edits, "selection")); - - // Preflight the edits to specify "end" if unspecified and make sure they don't overlap. + + // Preflight the edits to specify "end" if unspecified and make sure they don't overlap. // (We don't want to do it during the actual edits, since we don't want to apply some of // the edits before we find out.) _.each(edits, function (editDesc, index) { @@ -612,7 +614,7 @@ define(function (require, exports, module) { } }); }); - + // Perform the edits. this.batchOperation(function () { _.each(edits, function (editDesc, index) { @@ -640,7 +642,7 @@ define(function (require, exports, module) { }); }); }); - + result = _.chain(result) .filter(function (item) { return item !== undefined; @@ -655,7 +657,7 @@ define(function (require, exports, module) { }); return result; }; - + /* (pretty toString(), to aid debugging) */ Document.prototype.toString = function () { var dirtyInfo = (this.isDirty ? " (dirty!)" : " (clean)"); @@ -663,7 +665,7 @@ define(function (require, exports, module) { var refInfo = " refs:" + this._refCount; return "[Document " + this.file.fullPath + dirtyInfo + editorInfo + refInfo + "]"; }; - + /** * Returns the language this document is written in. * The language returned is based on the file extension. @@ -672,7 +674,7 @@ define(function (require, exports, module) { Document.prototype.getLanguage = function () { return this.language; }; - + /** * Updates the language to match the current mapping given by LanguageManager */ @@ -683,22 +685,22 @@ define(function (require, exports, module) { this.trigger("languageChanged", oldLanguage, this.language); } }; - + /** Called when Document.file has been modified (due to a rename) */ Document.prototype._notifyFilePathChanged = function () { // File extension may have changed this._updateLanguage(); }; - + /** * Is this an untitled document? - * + * * @return {boolean} - whether or not the document is untitled */ Document.prototype.isUntitled = function () { return this.file instanceof InMemoryFile; }; - + // We dispatch events from the module level, and the instance level. Instance events are wired up // in the Document constructor. EventDispatcher.makeEventDispatcher(exports); diff --git a/src/document/DocumentManager.js b/src/document/DocumentManager.js index 264681127f4..bc5a538e14d 100644 --- a/src/document/DocumentManager.js +++ b/src/document/DocumentManager.js @@ -26,7 +26,7 @@ /*global define, $ */ /** - * DocumentManager maintains a list of currently 'open' Documents. The DocumentManager is responsible + * DocumentManager maintains a list of currently 'open' Documents. The DocumentManager is responsible * for coordinating document operations and dispatching certain document events. * * Document is the model for a file's contents; it dispatches events whenever those contents change. @@ -66,10 +66,10 @@ * only, but is also triggered for non-editor views e.g. image files). * * - fileNameChange -- When the name of a file or folder has changed. The 2nd arg is the old name. - * The 3rd arg is the new name. Generally, however, file objects have already been changed by the - * time this event is dispatched so code that relies on matching the filename to a file object + * The 3rd arg is the new name. Generally, however, file objects have already been changed by the + * time this event is dispatched so code that relies on matching the filename to a file object * will need to compare the newname. - * + * * - pathDeleted -- When a file or folder has been deleted. The 2nd arg is the path that was deleted. * * To listen for events, do something like this: (see EventDispatcher for details on this pattern) @@ -79,9 +79,9 @@ */ define(function (require, exports, module) { "use strict"; - + var _ = require("thirdparty/lodash"); - + var AppInit = require("utils/AppInit"), EventDispatcher = require("utils/EventDispatcher"), DocumentModule = require("document/Document"), @@ -122,7 +122,7 @@ define(function (require, exports, module) { */ function getOpenDocumentForPath(fullPath) { var id; - + // Need to walk all open documents and check for matching path. We can't // use getFileForPath(fullPath).id since the file it returns won't match // an Untitled document's InMemoryFile. @@ -135,22 +135,22 @@ define(function (require, exports, module) { } return null; } - + /** * Returns the Document that is currently open in the editor UI. May be null. * @return {?Document} */ function getCurrentDocument() { var file = MainViewManager.getCurrentlyViewedFile(MainViewManager.ACTIVE_PANE); - + if (file) { return getOpenDocumentForPath(file.fullPath); } - + return null; } - + /** * Returns a list of items in the working set in UI list order. May be 0-length, but never null. * @deprecated Use MainViewManager.getWorkingSet() instead @@ -175,7 +175,7 @@ define(function (require, exports, module) { DeprecationWarning.deprecationWarning("Use MainViewManager.findInWorkingSet() instead of DocumentManager.findInWorkingSet()", true); return MainViewManager.findInWorkingSet(MainViewManager.ACTIVE_PANE, fullPath); } - + /** * Returns all Documents that are 'open' in the UI somewhere (for now, this means open in an * inline editor and/or a full-size editor). Only these Documents can be modified, and only @@ -192,11 +192,11 @@ define(function (require, exports, module) { } return result; } - - + + /** * Adds the given file to the end of the working set list. - * @deprecated Use MainViewManager.addToWorkingSet() instead + * @deprecated Use MainViewManager.addToWorkingSet() instead * @param {!File} file * @param {number=} index Position to add to list (defaults to last); -1 is ignored * @param {boolean=} forceRedraw If true, a working set change notification is always sent @@ -206,11 +206,11 @@ define(function (require, exports, module) { DeprecationWarning.deprecationWarning("Use MainViewManager.addToWorkingSet() instead of DocumentManager.addToWorkingSet()", true); MainViewManager.addToWorkingSet(MainViewManager.ACTIVE_PANE, file, index, forceRedraw); } - + /** - * @deprecated Use MainViewManager.addListToWorkingSet() instead + * @deprecated Use MainViewManager.addListToWorkingSet() instead * Adds the given file list to the end of the working set list. - * If a file in the list has its own custom viewer, then it + * If a file in the list has its own custom viewer, then it * is not added into the working set. * Does not change which document is currently open in the editor. * More efficient than calling addToWorkingSet() (in a loop) for @@ -222,9 +222,9 @@ define(function (require, exports, module) { MainViewManager.addListToWorkingSet(MainViewManager.ACTIVE_PANE, fileList); } - + /** - * closes a list of files + * closes a list of files * @deprecated Use CommandManager.execute(Commands.FILE_CLOSE_LIST) instead * @param {!Array.} list - list of File objectgs to close */ @@ -232,7 +232,7 @@ define(function (require, exports, module) { DeprecationWarning.deprecationWarning("Use CommandManager.execute(Commands.FILE_CLOSE_LIST, {PaneId: MainViewManager.ALL_PANES, fileList: list}) instead of DocumentManager.removeListFromWorkingSet()", true); CommandManager.execute(Commands.FILE_CLOSE_LIST, {PaneId: MainViewManager.ALL_PANES, fileList: list}); } - + /** * closes all open files * @deprecated CommandManager.execute(Commands.FILE_CLOSE_ALL) instead @@ -243,7 +243,7 @@ define(function (require, exports, module) { } /** - * closes the specified file file + * closes the specified file file * @deprecated use CommandManager.execute(Commands.FILE_CLOSE, {File: file}) instead * @param {!File} file - the file to close */ @@ -251,27 +251,27 @@ define(function (require, exports, module) { DeprecationWarning.deprecationWarning("Use CommandManager.execute(Commands.FILE_CLOSE, {File: file} instead of DocumentManager.closeFullEditor()", true); CommandManager.execute(Commands.FILE_CLOSE, {File: file}); } - + /** * opens the specified document for editing in the currently active pane * @deprecated use CommandManager.execute(Commands.CMD_OPEN, {fullPath: doc.file.fullPath}) instead - * @param {!Document} document The Document to make current. + * @param {!Document} document The Document to make current. */ function setCurrentDocument(doc) { DeprecationWarning.deprecationWarning("Use CommandManager.execute(Commands.CMD_OPEN) instead of DocumentManager.setCurrentDocument()", true); CommandManager.execute(Commands.CMD_OPEN, {fullPath: doc.file.fullPath}); } - + /** - * freezes the Working Set MRU list + * freezes the Working Set MRU list * @deprecated use MainViewManager.beginTraversal() instead */ function beginDocumentNavigation() { DeprecationWarning.deprecationWarning("Use MainViewManager.beginTraversal() instead of DocumentManager.beginDocumentNavigation()", true); MainViewManager.beginTraversal(); } - + /** * ends document navigation and moves the current file to the front of the MRU list in the Working Set * @deprecated use MainViewManager.endTraversal() instead @@ -280,7 +280,7 @@ define(function (require, exports, module) { DeprecationWarning.deprecationWarning("Use MainViewManager.endTraversal() instead of DocumentManager.finalizeDocumentNavigation()", true); MainViewManager.endTraversal(); } - + /** * Get the next or previous file in the working set, in MRU order (relative to currentDocument). May * return currentDocument itself if working set is length 1. @@ -294,7 +294,7 @@ define(function (require, exports, module) { } return null; } - + /** * Cleans up any loose Documents whose only ref is its own master Editor, and that Editor is not * rooted in the UI anywhere. This can happen if the Editor is auto-created via Document APIs that @@ -310,8 +310,8 @@ define(function (require, exports, module) { } }); } - - + + /** * Gets an existing open Document for the given file, or creates a new one if the Document is * not currently open ('open' means referenced by the UI somewhere). Always use this method to @@ -322,7 +322,7 @@ define(function (require, exports, module) { * if you are going to display its contents in a piece of UI - then you must addRef() the Document * and listen for changes on it. (Note: opening the Document in an Editor automatically manages * refs and listeners for that Editor UI). - * + * * If all you need is the Document's getText() value, use the faster getDocumentText() instead. * * @param {!string} fullPath @@ -336,35 +336,35 @@ define(function (require, exports, module) { // use existing document return new $.Deferred().resolve(doc).promise(); } else { - + // Should never get here if the fullPath refers to an Untitled document if (fullPath.indexOf(_untitledDocumentPath) === 0) { console.error("getDocumentForPath called for non-open untitled document: " + fullPath); return new $.Deferred().reject().promise(); } - + var file = FileSystem.getFileForPath(fullPath), pendingPromise = getDocumentForPath._pendingDocumentPromises[file.id]; - + if (pendingPromise) { // wait for the result of a previous request return pendingPromise; } else { var result = new $.Deferred(), promise = result.promise(); - + // log this document's Promise as pending getDocumentForPath._pendingDocumentPromises[file.id] = promise; - + // create a new document var perfTimerName = PerfUtils.markStart("getDocumentForPath:\t" + fullPath); - + result.done(function () { PerfUtils.addMeasurement(perfTimerName); }).fail(function () { PerfUtils.finalizeMeasurement(perfTimerName); }); - + FileUtils.readAsText(file) .always(function () { // document is no longer pending @@ -372,21 +372,21 @@ define(function (require, exports, module) { }) .done(function (rawText, readTimestamp) { doc = new DocumentModule.Document(file, readTimestamp, rawText); - + // This is a good point to clean up any old dangling Documents _gcDocuments(); - + result.resolve(doc); }) .fail(function (fileError) { result.reject(fileError); }); - + return promise; } } } - + /** * Document promises that are waiting to be resolved. It is possible for multiple clients * to request the same document simultaneously before the initial request has completed. @@ -397,20 +397,20 @@ define(function (require, exports, module) { * @type {Object.} */ getDocumentForPath._pendingDocumentPromises = {}; - + /** * Gets the text of a Document (including any unsaved changes), or would-be Document if the * file is not actually open. More efficient than getDocumentForPath(). Use when you're reading * document(s) but don't need to hang onto a Document object. - * + * * If the file is open this is equivalent to calling getOpenDocumentForPath().getText(). If the * file is NOT open, this is like calling getDocumentForPath()...getText() but more efficient. * Differs from plain FileUtils.readAsText() in two ways: (a) line endings are still normalized * as in Document.getText(); (b) unsaved changes are returned if there are any. - * + * * @param {!File} file The file to get the text for. * @param {boolean=} checkLineEndings Whether to return line ending information. Default false (slightly more efficient). - * @return {$.Promise} + * @return {$.Promise} * A promise that is resolved with three parameters: * contents - string: the document's text * timestamp - Date: the last time the document was changed on disk (might not be the same as the last time it was changed in memory) @@ -438,8 +438,8 @@ define(function (require, exports, module) { } return result.promise(); } - - + + /** * Creates an untitled document. The associated File has a fullPath that * looks like /some-random-string/Untitled-counter.fileExt. @@ -453,12 +453,12 @@ define(function (require, exports, module) { fullPath = _untitledDocumentPath + "/" + filename, now = new Date(), file = new InMemoryFile(fullPath, FileSystem); - + FileSystem.addEntryForPathIfRequired(file, fullPath); return new DocumentModule.Document(file, now, ""); } - + /** * Reacts to a file being deleted: if there is a Document for this file, causes it to dispatch a * "deleted" event; ensures it's not the currentDocument; and removes this file from the working @@ -472,19 +472,19 @@ define(function (require, exports, module) { * * NOTE: This function is not for general consumption, is considered private and may be deprecated * without warning in a future release. - * + * * @param {!File} file */ function notifyFileDeleted(file) { // Notify all editors to close as well exports.trigger("pathDeleted", file.fullPath); - + var doc = getOpenDocumentForPath(file.fullPath); - + if (doc) { doc.trigger("deleted"); } - + // At this point, all those other views SHOULD have released the Doc if (doc && doc._refCount > 0) { console.warn("Deleted " + file.fullPath + " Document still has " + doc._refCount + " references. Did someone addRef() without listening for 'deleted'?"); @@ -498,15 +498,15 @@ define(function (require, exports, module) { * @param {string} fullPath The path of the file/folder that has been deleted */ function notifyPathDeleted(fullPath) { - // FileSyncManager.syncOpenDocuments() does all the work prompting + // FileSyncManager.syncOpenDocuments() does all the work prompting // the user to save any unsaved changes and then calls us back // via notifyFileDeleted FileSyncManager.syncOpenDocuments(Strings.FILE_DELETED_TITLE); - + if (!getOpenDocumentForPath(fullPath) && !MainViewManager.findInAllWorkingSets(fullPath).length) { // For images not open in the workingset, - // FileSyncManager.syncOpenDocuments() will + // FileSyncManager.syncOpenDocuments() will // not tell us to close those views exports.trigger("pathDeleted", fullPath); } @@ -520,19 +520,19 @@ define(function (require, exports, module) { * @param {string} newName The new name of the file/folder */ function notifyPathNameChanged(oldName, newName) { - // Notify all open documents + // Notify all open documents _.forEach(_openDocuments, function (doc) { - // TODO: Only notify affected documents? For now _notifyFilePathChange + // TODO: Only notify affected documents? For now _notifyFilePathChange // just updates the language if the extension changed, so it's fine // to call for all open docs. doc._notifyFilePathChanged(); }); - + // Send a "fileNameChange" event. This will trigger the views to update. exports.trigger("fileNameChange", oldName, newName); } - - + + /** * @private * Update document @@ -561,7 +561,7 @@ define(function (require, exports, module) { } }); } - + // For compatibility DocumentModule .on("_afterDocumentCreate", function (event, doc) { @@ -589,20 +589,20 @@ define(function (require, exports, module) { exports.trigger("dirtyFlagChange", doc); if (doc.isDirty) { MainViewManager.addToWorkingSet(MainViewManager.ACTIVE_PANE, doc.file); - + // We just dirtied a doc and added it to the active working set // this may have come from an internal dirtying so if it was // added to a working set that had no active document then - // open the document + // open the document // - // See: https://github.com/adobe/brackets/issues/9569 + // See: https://github.com/adobe/brackets/issues/9569 // // NOTE: Adding a file to the active working set may not actually add - // it to the active working set (e.g. the document was already + // it to the active working set (e.g. the document was already // opened to the inactive working set.) // // Check that it was actually added to the active working set - + if (!MainViewManager.getCurrentlyViewedFile() && MainViewManager.findInWorkingSet(MainViewManager.ACTIVE_PANE, doc.file.fullPath) !== -1) { CommandManager.execute(Commands.FILE_OPEN, {fullPath: doc.file.fullPath}); @@ -612,11 +612,11 @@ define(function (require, exports, module) { .on("_documentSaved", function (event, doc) { exports.trigger("documentSaved", doc); }); - + /** * @private * Examine each preference key for migration of the working set files. - * If the key has a prefix of "files_/", then it is a working set files + * If the key has a prefix of "files_/", then it is a working set files * preference from old preference model. * * @param {string} key The key of the preference to be examined @@ -630,17 +630,17 @@ define(function (require, exports, module) { var projectPath = key.substr(pathPrefix.length); return "user project.files " + projectPath; } - + return null; } - - + + // Set up event dispatch EventDispatcher.makeEventDispatcher(exports); - + // This deprecated event is dispatched manually from "currentFileChange" below EventDispatcher.markDeprecated(exports, "currentDocumentChange", "MainViewManager.currentFileChange"); - + // These deprecated events are automatically dispatched by DeprecationWarning, set up in AppInit.extensionsLoaded() EventDispatcher.markDeprecated(exports, "workingSetAdd", "MainViewManager.workingSetAdd"); EventDispatcher.markDeprecated(exports, "workingSetAddList", "MainViewManager.workingSetAddList"); @@ -648,7 +648,7 @@ define(function (require, exports, module) { EventDispatcher.markDeprecated(exports, "workingSetRemoveList", "MainViewManager.workingSetRemoveList"); EventDispatcher.markDeprecated(exports, "workingSetSort", "MainViewManager.workingSetSort"); - /* + /* * After extensionsLoaded, register the proxying listeners that convert newer events to * deprecated events. This ensures that extension listeners still run later than core * listeners. If we registered these proxies earlier, extensions would get the deprecated @@ -672,15 +672,15 @@ define(function (require, exports, module) { _proxyDeprecatedEvent("workingSetRemoveList"); _proxyDeprecatedEvent("workingSetSort"); }); - - + + PreferencesManager.convertPreferences(module, {"files_": "user"}, true, _checkPreferencePrefix); // Handle file saves that may affect preferences exports.on("documentSaved", function (e, doc) { PreferencesManager.fileChanged(doc.file.fullPath); }); - + MainViewManager.on("currentFileChange", function (e, newFile, newPaneId, oldFile) { var newDoc = null, oldDoc = null; @@ -688,15 +688,15 @@ define(function (require, exports, module) { if (newFile) { newDoc = getOpenDocumentForPath(newFile.fullPath); } - + if (oldFile) { oldDoc = getOpenDocumentForPath(oldFile.fullPath); } - + if (oldDoc) { oldDoc.off("languageChanged.DocumentManager"); } - + if (newDoc) { PreferencesManager._setCurrentLanguage(newDoc.getLanguage().getId()); newDoc.on("languageChanged.DocumentManager", function (e, oldLang, newLang) { @@ -706,15 +706,15 @@ define(function (require, exports, module) { } else { PreferencesManager._setCurrentLanguage(undefined); } - + if (newDoc !== oldDoc) { // Dispatch deprecated event with doc args (not File args as the new event uses) exports.trigger("currentDocumentChange", newDoc, oldDoc); } }); - - // Deprecated APIs + + // Deprecated APIs exports.getWorkingSet = getWorkingSet; exports.findInWorkingSet = findInWorkingSet; exports.addToWorkingSet = addToWorkingSet; @@ -727,7 +727,7 @@ define(function (require, exports, module) { exports.setCurrentDocument = setCurrentDocument; exports.closeFullEditor = closeFullEditor; exports.closeAll = closeAll; - + // Define public API exports.Document = DocumentModule.Document; exports.getDocumentForPath = getDocumentForPath; @@ -735,7 +735,7 @@ define(function (require, exports, module) { exports.getDocumentText = getDocumentText; exports.createUntitledDocument = createUntitledDocument; exports.getAllOpenDocuments = getAllOpenDocuments; - + // For internal use only exports.notifyPathNameChanged = notifyPathNameChanged; exports.notifyPathDeleted = notifyPathDeleted; diff --git a/src/filesystem/FileSystem.js b/src/filesystem/FileSystem.js index 009dc6fd606..2e429b631d9 100644 --- a/src/filesystem/FileSystem.js +++ b/src/filesystem/FileSystem.js @@ -1,24 +1,24 @@ /* * Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved. - * + * * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: - * + * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. - * + * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. - * + * */ /*jslint vars: true, plusplus: true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50 */ @@ -39,24 +39,24 @@ * * Use FileSystem.getFileForPath()/FileSystem.getDirectoryForPath() if you know the * file/directory already exists, or if you want to create a new entry. * * Use Directory.getContents() to return all entries for the specified Directory. - * + * * All paths passed *to* FileSystem APIs must be in the following format: * * The path separator is "/" regardless of platform * * Paths begin with "/" on Mac/Linux and "c:/" (or some other drive letter) on Windows - * + * * All paths returned *from* FileSystem APIs additionally meet the following guarantees: * * No ".." segments * * No consecutive "/"s * * Paths to a directory always end with a trailing "/" * (Because FileSystem normalizes paths automatically, paths passed *to* FileSystem do not need * to meet these requirements) - * + * * FileSystem dispatches the following events: * (NOTE: attach to these events via `FileSystem.on()` - not `$(FileSystem).on()`) - * + * * __change__ - Sent whenever there is a change in the file system. The handler - * is passed up to three arguments: the changed entry and, if that changed entry - * is a Directory, a list of entries added to the directory and a list of entries + * is passed up to three arguments: the changed entry and, if that changed entry + * is a Directory, a list of entries added to the directory and a list of entries * removed from the Directory. The entry argument can be: * * a File - the contents of the file have changed, and should be reloaded. * * a Directory - an immediate child of the directory has been added, removed, @@ -77,7 +77,7 @@ * has changed. Note that these events will only be sent for rename operations that happen * within the filesystem. If a file is renamed externally, a change event on the parent * directory will be sent instead. - * + * * FileSystem may perform caching. But it guarantees: * * File contents & metadata - reads are guaranteed to be up to date (cached data is not used * without first veryifying it is up to date). @@ -91,14 +91,14 @@ */ define(function (require, exports, module) { "use strict"; - + var Directory = require("filesystem/Directory"), File = require("filesystem/File"), FileIndex = require("filesystem/FileIndex"), FileSystemError = require("filesystem/FileSystemError"), WatchedRoot = require("filesystem/WatchedRoot"), EventDispatcher = require("utils/EventDispatcher"); - + /** * The FileSystem is not usable until init() signals its callback. * @constructor @@ -106,29 +106,29 @@ define(function (require, exports, module) { function FileSystem() { // Create a file index this._index = new FileIndex(); - + // Initialize the set of watched roots this._watchedRoots = {}; // Initialize the watch/unwatch request queue this._watchRequests = []; - + // Initialize the queue of pending external changes this._externalChanges = []; } EventDispatcher.makeEventDispatcher(FileSystem.prototype); - + /** - * The low-level file system implementation used by this object. + * The low-level file system implementation used by this object. * This is set in the init() function and cannot be changed. */ FileSystem.prototype._impl = null; - + /** * The FileIndex used by this object. This is initialized in the constructor. */ FileSystem.prototype._index = null; - + /** * Refcount of any pending filesystem mutation operations (e.g., writes, * unlinks, etc.). Used to ensure that external change events aren't processed @@ -138,19 +138,19 @@ define(function (require, exports, module) { * @type {number} */ FileSystem.prototype._activeChangeCount = 0; - + // For unit testing only FileSystem.prototype._getActiveChangeCount = function () { return this._activeChangeCount; }; - + /** * Queue of arguments with which to invoke _handleExternalChanges(); triggered * once _activeChangeCount drops to zero. * @type {!Array.<{path:?string, stat:FileSystemStats=}>} */ FileSystem.prototype._externalChanges = null; - + /** Process all queued watcher results, by calling _handleExternalChange() on each */ FileSystem.prototype._triggerExternalChangesNow = function () { this._externalChanges.forEach(function (info) { @@ -158,7 +158,7 @@ define(function (require, exports, module) { }, this); this._externalChanges.length = 0; }; - + /** * Receives a result from the impl's watcher callback, and either processes it * immediately (if _activeChangeCount is 0) or otherwise stores it for later @@ -172,21 +172,21 @@ define(function (require, exports, module) { this._triggerExternalChangesNow(); } }; - + /** * The queue of pending watch/unwatch requests. * @type {Array.<{fn: function(), cb: function()}>} */ FileSystem.prototype._watchRequests = null; - + /** * Dequeue and process all pending watch/unwatch requests */ FileSystem.prototype._dequeueWatchRequest = function () { if (this._watchRequests.length > 0) { var request = this._watchRequests[0]; - + request.fn.call(null, function () { // Apply the given callback var callbackArgs = arguments; @@ -200,7 +200,7 @@ define(function (require, exports, module) { }.bind(this)); } }; - + /** * Enqueue a new watch/unwatch request. * @@ -222,36 +222,36 @@ define(function (require, exports, module) { * The set of watched roots, encoded as a mapping from full paths to WatchedRoot * objects which contain a file entry, filter function, and an indication of * whether the watched root is inactive, starting up or fully active. - * + * * @type {Object.} */ FileSystem.prototype._watchedRoots = null; - + /** * Finds a parent watched root for a given path, or returns null if a parent * watched root does not exist. - * + * * @param {string} fullPath The child path for which a parent watched root is to be found * @return {?{entry: FileSystemEntry, filter: function(string) boolean}} The parent * watched root, if it exists, or null. */ FileSystem.prototype._findWatchedRootForPath = function (fullPath) { var watchedRoot = null; - + Object.keys(this._watchedRoots).some(function (watchedPath) { if (fullPath.indexOf(watchedPath) === 0) { watchedRoot = this._watchedRoots[watchedPath]; return true; } }, this); - + return watchedRoot; }; - + /** * Helper function to watch or unwatch a filesystem entry beneath a given * watchedRoot. - * + * * @private * @param {FileSystemEntry} entry - The FileSystemEntry to watch. Must be a * non-strict descendent of watchedRoot.entry. @@ -274,7 +274,7 @@ define(function (require, exports, module) { // no-ops if the impl supports recursiveWatch callback(null); } else { - // The impl will handle finding all subdirectories to watch. + // The impl will handle finding all subdirectories to watch. this._enqueueWatchRequest(function (requestCb) { impl[commandName].call(impl, entry.fullPath, requestCb); }.bind(this), callback); @@ -285,7 +285,7 @@ define(function (require, exports, module) { this._enqueueWatchRequest(function (requestCb) { // First construct a list of entries to watch or unwatch var entriesToWatch = []; - + var visitor = function (child) { if (watchedRoot.filter(child.name, child.parentPath)) { if (child.isDirectory || child === watchedRoot.entry) { @@ -295,27 +295,27 @@ define(function (require, exports, module) { } return false; }; - + entry.visit(visitor, function (err) { if (err) { // Unexpected error requestCb(err); return; } - + // Then watch or unwatched all these entries var count = entriesToWatch.length; if (count === 0) { requestCb(null); return; } - + var watchCallback = function () { if (--count === 0) { requestCb(null); } }; - + entriesToWatch.forEach(function (entry) { impl.watchPath(entry.fullPath, watchCallback); }); @@ -329,10 +329,10 @@ define(function (require, exports, module) { }, callback); } }; - + /** * Watch a filesystem entry beneath a given watchedRoot. - * + * * @private * @param {FileSystemEntry} entry - The FileSystemEntry to watch. Must be a * non-strict descendent of watchedRoot.entry. @@ -346,7 +346,7 @@ define(function (require, exports, module) { /** * Unwatch a filesystem entry beneath a given watchedRoot. - * + * * @private * @param {FileSystemEntry} entry - The FileSystemEntry to watch. Must be a * non-strict descendent of watchedRoot.entry. @@ -365,27 +365,27 @@ define(function (require, exports, module) { child._clearCachedData(true); } }.bind(this)); - + callback(err); }.bind(this), false); }; - + /** * Initialize this FileSystem instance. - * + * * @param {FileSystemImpl} impl The back-end implementation for this * FileSystem instance. */ FileSystem.prototype.init = function (impl) { console.assert(!this._impl, "This FileSystem has already been initialized!"); - + var changeCallback = this._enqueueExternalChange.bind(this), offlineCallback = this._unwatchAll.bind(this); - + this._impl = impl; this._impl.initWatchers(changeCallback, offlineCallback); }; - + /** * Close a file system. Clear all caches, indexes, and file watchers. */ @@ -393,31 +393,31 @@ define(function (require, exports, module) { this._impl.unwatchAll(); this._index.clear(); }; - + /** * Returns true if the given path should be automatically added to the index & watch list when one of its ancestors * is a watch-root. (Files are added automatically when the watch-root is first established, or later when a new * directory is created and its children enumerated). - * + * * Entries explicitly created via FileSystem.getFile/DirectoryForPath() are *always* added to the index regardless * of this filtering - but they will not be watched if the watch-root's filter excludes them. - * + * * @param {string} path Full path * @param {string} name Name portion of the path */ FileSystem.prototype._indexFilter = function (path, name) { var parentRoot = this._findWatchedRootForPath(path); - + if (parentRoot) { return parentRoot.filter(name, path); } - + // It might seem more sensible to return false (exclude) for files outside the watch roots, but // that would break usage of appFileSystem for 'system'-level things like enumerating extensions. // (Or in general, Directory.getContents() for any Directory outside the watch roots). return true; }; - + /** * Indicates that a filesystem-mutating operation has begun. As long as there * are changes taking place, change events from the external watchers are @@ -425,35 +425,35 @@ define(function (require, exports, module) { * because for mutating operations that originate from within the filesystem, * synthetic change events are fired that do not depend on external file * watchers, and we prefer the former over the latter for the following - * reasons: 1) there is no delay; and 2) they may have higher fidelity --- + * reasons: 1) there is no delay; and 2) they may have higher fidelity --- * e.g., a rename operation can be detected as such, instead of as a nearly * simultaneous addition and deletion. - * - * All operations that mutate the file system MUST begin with a call to + * + * All operations that mutate the file system MUST begin with a call to * _beginChange and must end with a call to _endChange. */ FileSystem.prototype._beginChange = function () { this._activeChangeCount++; //console.log("> beginChange -> " + this._activeChangeCount); }; - + /** - * Indicates that a filesystem-mutating operation has completed. See + * Indicates that a filesystem-mutating operation has completed. See * FileSystem._beginChange above. */ FileSystem.prototype._endChange = function () { this._activeChangeCount--; //console.log("< endChange -> " + this._activeChangeCount); - + if (this._activeChangeCount < 0) { console.error("FileSystem _activeChangeCount has fallen below zero!"); } - + if (!this._activeChangeCount) { this._triggerExternalChangesNow(); } }; - + /** * Determines whether or not the supplied path is absolute, as opposed to relative. * @@ -471,13 +471,13 @@ define(function (require, exports, module) { return path; } - + /* * Matches continguous groups of forward slashes * @const */ var _DUPLICATED_SLASH_RE = /\/{2,}/g; - + /** * Returns a canonical version of the path: no duplicated "/"es, no ".."s, * and directories guaranteed to end in a trailing "/" @@ -486,16 +486,16 @@ define(function (require, exports, module) { * @return {!string} */ FileSystem.prototype._normalizePath = function (path, isDirectory) { - + if (!FileSystem.isAbsolutePath(path)) { throw new Error("Paths must be absolute: '" + path + "'"); // expect only absolute paths } - + var isUNCPath = this._impl.normalizeUNCPaths && path.search(_DUPLICATED_SLASH_RE) === 0; - + // Remove duplicated "/"es path = path.replace(_DUPLICATED_SLASH_RE, "/"); - + // Remove ".." segments if (path.indexOf("..") !== -1) { var segments = path.split("/"), @@ -511,17 +511,17 @@ define(function (require, exports, module) { } path = segments.join("/"); } - + if (isDirectory) { // Make sure path DOES include trailing slash path = _ensureTrailingSlash(path); } - + if (isUNCPath) { // Restore the leading double slash that was removed previously path = "/" + path; } - + return path; }; @@ -549,7 +549,7 @@ define(function (require, exports, module) { * @private * @param {function(string, FileSystem)} EntryConstructor Constructor with * which to initialize new FileSystemEntry objects. - * @param {string} path Absolute path of file. + * @param {string} path Absolute path of file. * @return {File|Directory} The File or Directory object. This file may not * yet exist on disk. */ @@ -557,26 +557,26 @@ define(function (require, exports, module) { var isDirectory = EntryConstructor === Directory; path = this._normalizePath(path, isDirectory); var entry = this._index.getEntry(path); - + if (!entry) { entry = new EntryConstructor(path, this); this._index.addEntry(entry); } - + return entry; }; - + /** * Return a File object for the specified path. * - * @param {string} path Absolute path of file. + * @param {string} path Absolute path of file. * * @return {File} The File object. This file may not yet exist on disk. */ FileSystem.prototype.getFileForPath = function (path) { return this._getEntryForPath(File, path); }; - + /** * Return a Directory object for the specified path. * @@ -587,7 +587,7 @@ define(function (require, exports, module) { FileSystem.prototype.getDirectoryForPath = function (path) { return this._getEntryForPath(Directory, path); }; - + /** * Resolve a path. * @@ -603,14 +603,14 @@ define(function (require, exports, module) { normalizedPath = _ensureTrailingSlash(normalizedPath); item = this._index.getEntry(normalizedPath); } - + if (item) { item.stat(function (err, stat) { if (err) { callback(err); return; } - + callback(null, item, stat); }); } else { @@ -619,22 +619,22 @@ define(function (require, exports, module) { callback(err); return; } - + if (stat.isFile) { item = this.getFileForPath(path); } else { item = this.getDirectoryForPath(path); } - + if (item._isWatched()) { item._stat = stat; } - + callback(null, item, stat); }.bind(this)); } }; - + /** * Show an "Open" dialog and return the file(s)/directories selected by the user. * @@ -659,10 +659,10 @@ define(function (require, exports, module) { initialPath, fileTypes, callback) { - + this._impl.showOpenDialog(allowMultipleSelection, chooseDirectories, title, initialPath, fileTypes, callback); }; - + /** * Show a "Save" dialog and return the path of the file to save. * @@ -682,7 +682,7 @@ define(function (require, exports, module) { /** * Fire a rename event. Clients listen for these events using FileSystem.on. - * + * * @param {string} oldPath The entry's previous fullPath * @param {string} newPath The entry's current fullPath */ @@ -692,7 +692,7 @@ define(function (require, exports, module) { /** * Fire a change event. Clients listen for these events using FileSystem.on. - * + * * @param {File|Directory} entry The entry that has changed * @param {Array=} added If the entry is a directory, this * is a set of new entries in the directory. @@ -702,7 +702,7 @@ define(function (require, exports, module) { FileSystem.prototype._fireChangeEvent = function (entry, added, removed) { this.trigger("change", entry, added, removed); }; - + /** * @private * Notify the system when an entry name has changed. @@ -715,14 +715,14 @@ define(function (require, exports, module) { // Update all affected entries in the index this._index.entryRenamed(oldFullPath, newFullPath, isDirectory); }; - + /** * Notify the filesystem that the given directory has changed. Updates the filesystem's * internal state as a result of the change, and calls back with the set of added and * removed entries. Mutating FileSystemEntry operations should call this method before * applying the operation's callback, and pass along the resulting change sets in the * internal change event. - * + * * @param {Directory} directory The directory that has changed. * @param {function(Array=, Array=)} callback * The callback that will be applied to a set of added and a set of removed @@ -730,13 +730,13 @@ define(function (require, exports, module) { */ FileSystem.prototype._handleDirectoryChange = function (directory, callback) { var oldContents = directory._contents; - + directory._clearCachedData(); directory.getContents(function (err, contents) { var addedEntries = oldContents && contents.filter(function (entry) { return oldContents.indexOf(entry) === -1; }); - + var removedEntries = oldContents && oldContents.filter(function (entry) { return contents.indexOf(entry) === -1; }); @@ -750,7 +750,7 @@ define(function (require, exports, module) { entry._clearCachedData(true); } }.bind(this)); - + callback(addedEntries, removedEntries); return; } @@ -758,22 +758,22 @@ define(function (require, exports, module) { var addedCounter = addedEntries ? addedEntries.length : 0, removedCounter = removedEntries ? removedEntries.length : 0, counter = addedCounter + removedCounter; - + if (counter === 0) { callback(addedEntries, removedEntries); return; } - + var watchOrUnwatchCallback = function (err) { if (err) { console.error("FileSystem error in _handleDirectoryChange after watch/unwatch entries: " + err); } - + if (--counter === 0) { callback(addedEntries, removedEntries); } }; - + if (addedEntries) { addedEntries.forEach(function (entry) { this._watchEntry(entry, watchedRoot, watchOrUnwatchCallback); @@ -787,15 +787,15 @@ define(function (require, exports, module) { } }.bind(this)); }; - + /** * @private * Processes a result from the file/directory watchers. Watch results are sent from the low-level implementation - * whenever a directory or file is changed. + * whenever a directory or file is changed. * * @param {string} path The path that changed. This could be a file or a directory. * @param {FileSystemStats=} stat Optional stat for the item that changed. This param is not always - * passed. + * passed. */ FileSystem.prototype._handleExternalChange = function (path, stat) { @@ -805,13 +805,13 @@ define(function (require, exports, module) { // Passing 'true' for a similar reason as in _unwatchEntry() - see #7150 entry._clearCachedData(true); }); - + this._fireChangeEvent(null); return; } - + path = this._normalizePath(path, false); - + var entry = this._index.getEntry(path); if (entry) { var oldStat = entry._stat; @@ -825,7 +825,7 @@ define(function (require, exports, module) { } else { this._handleDirectoryChange(entry, function (added, removed) { entry._stat = stat; - + // We send a change even if added & removed are both zero-length. Something may still have changed, // e.g. a file may have been quickly removed & re-added before we got a chance to reread the directory // listing. @@ -834,7 +834,7 @@ define(function (require, exports, module) { } } }; - + /** * Clears all cached content. Because of the performance implications of this, this should only be used if * there is a suspicion that the file system has not been updated through the normal file watchers @@ -843,10 +843,10 @@ define(function (require, exports, module) { FileSystem.prototype.clearAllCaches = function () { this._handleExternalChange(null); }; - + /** * Start watching a filesystem root entry. - * + * * @param {FileSystemEntry} entry - The root entry to watch. If entry is a directory, * all subdirectories that aren't explicitly filtered will also be watched. * @param {function(string): boolean} filter - Returns true if a particular item should @@ -858,9 +858,9 @@ define(function (require, exports, module) { */ FileSystem.prototype.watch = function (entry, filter, callback) { var fullPath = entry.fullPath; - + callback = callback || function () {}; - + var watchingParentRoot = this._findWatchedRootForPath(fullPath); if (watchingParentRoot && (watchingParentRoot.status === WatchedRoot.STARTING || @@ -872,25 +872,25 @@ define(function (require, exports, module) { var watchingChildRoot = Object.keys(this._watchedRoots).some(function (path) { var watchedRoot = this._watchedRoots[path], watchedPath = watchedRoot.entry.fullPath; - + return watchedPath.indexOf(fullPath) === 0; }, this); - + if (watchingChildRoot && (watchingChildRoot.status === WatchedRoot.STARTING || watchingChildRoot.status === WatchedRoot.ACTIVE)) { callback("A child of this root is already watched"); return; } - + var watchedRoot = new WatchedRoot(entry, filter); - + this._watchedRoots[fullPath] = watchedRoot; // Enter the STARTING state early to indiate that watched Directory // objects may cache their contents. See FileSystemEntry._isWatched. watchedRoot.status = WatchedRoot.STARTING; - + this._watchEntry(entry, watchedRoot, function (err) { if (err) { console.warn("Failed to watch root: ", entry.fullPath, err); @@ -900,14 +900,14 @@ define(function (require, exports, module) { } watchedRoot.status = WatchedRoot.ACTIVE; - + callback(null); }.bind(this)); }; /** * Stop watching a filesystem root entry. - * + * * @param {FileSystemEntry} entry - The root entry to stop watching. The unwatch will * if the entry is not currently being watched. * @param {function(?string)=} callback - A function that is called when the unwatch has @@ -917,9 +917,9 @@ define(function (require, exports, module) { FileSystem.prototype.unwatch = function (entry, callback) { var fullPath = entry.fullPath, watchedRoot = this._watchedRoots[fullPath]; - + callback = callback || function () {}; - + if (!watchedRoot) { callback(FileSystemError.ROOT_NOT_WATCHED); return; @@ -928,16 +928,16 @@ define(function (require, exports, module) { // Mark this as inactive, but don't delete the entry until the unwatch is complete. // This is useful for making sure we don't try to concurrently watch overlapping roots. watchedRoot.status = WatchedRoot.INACTIVE; - + this._unwatchEntry(entry, watchedRoot, function (err) { delete this._watchedRoots[fullPath]; - + this._index.visitAll(function (child) { if (child.fullPath.indexOf(entry.fullPath) === 0) { this._index.removeEntry(child); } }.bind(this)); - + if (err) { console.warn("Failed to unwatch root: ", entry.fullPath, err); callback(err); @@ -947,7 +947,7 @@ define(function (require, exports, module) { callback(null); }.bind(this)); }; - + /** * Unwatch all watched roots. Calls unwatch on the underlying impl for each * watched root and ignores errors. @@ -955,7 +955,7 @@ define(function (require, exports, module) { */ FileSystem.prototype._unwatchAll = function () { console.warn("File watchers went offline!"); - + Object.keys(this._watchedRoots).forEach(function (path) { var watchedRoot = this._watchedRoots[path]; @@ -965,13 +965,13 @@ define(function (require, exports, module) { console.warn("Watching disabled for", watchedRoot.entry.fullPath); }); }, this); - + // Fire a wholesale change event, clearing all caches and request that // clients manually update their state. this._handleExternalChange(null); }; - + // The singleton instance var _instance; @@ -980,7 +980,7 @@ define(function (require, exports, module) { return func.apply(_instance, arguments); }; } - + // Export public methods as proxies to the singleton instance exports.init = _wrap(FileSystem.prototype.init); exports.close = _wrap(FileSystem.prototype.close); @@ -994,13 +994,13 @@ define(function (require, exports, module) { exports.watch = _wrap(FileSystem.prototype.watch); exports.unwatch = _wrap(FileSystem.prototype.unwatch); exports.clearAllCaches = _wrap(FileSystem.prototype.clearAllCaches); - + // Static public utility methods exports.isAbsolutePath = FileSystem.isAbsolutePath; - + // For testing only exports._getActiveChangeCount = _wrap(FileSystem.prototype._getActiveChangeCount); - + /** * Add an event listener for a FileSystem event. * @@ -1010,7 +1010,7 @@ define(function (require, exports, module) { exports.on = function (event, handler) { _instance.on(event, handler); }; - + /** * Remove an event listener for a FileSystem event. * @@ -1020,13 +1020,13 @@ define(function (require, exports, module) { exports.off = function (event, handler) { _instance.off(event, handler); }; - + // Export the FileSystem class as "private" for unit testing only. exports._FileSystem = FileSystem; - + // Create the singleton instance _instance = new FileSystem(); - + // Initialize the singleton instance _instance.init(require("fileSystemImpl")); }); diff --git a/src/filesystem/FileSystemEntry.js b/src/filesystem/FileSystemEntry.js index 73e9f293c62..fc51111fb89 100644 --- a/src/filesystem/FileSystemEntry.js +++ b/src/filesystem/FileSystemEntry.js @@ -1,34 +1,34 @@ /* * Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved. - * + * * Permission is hereby granted, free of charge, to any person obtaining a - * copy of this software and associated documentation files (the "Software"), - * to deal in the Software without restriction, including without limitation - * the rights to use, copy, modify, merge, publish, distribute, sublicense, - * and/or sell copies of the Software, and to permit persons to whom the + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: - * + * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. - * + * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. - * + * */ /*jslint vars: true, plusplus: true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50 */ /*global define */ /* - * To ensure cache coherence, current and future asynchronous state-changing - * operations of FileSystemEntry and its subclasses should implement the + * To ensure cache coherence, current and future asynchronous state-changing + * operations of FileSystemEntry and its subclasses should implement the * following high-level sequence of steps: - * + * * 1. Block external filesystem change events; * 2. Execute the low-level state-changing operation; * 3. Update the internal filesystem state, including caches; @@ -36,24 +36,24 @@ * 5. Fire an appropriate internal change notification; and * 6. Unblock external change events. * - * Note that because internal filesystem state is updated first, both the original + * Note that because internal filesystem state is updated first, both the original * caller and the change notification listeners observe filesystem state that is * current w.r.t. the operation. Furthermore, because external change events are * blocked before the operation begins, listeners will only receive the internal * change event for the operation and not additional (or possibly inconsistent) * external change events. - * + * * State-changing operations that block external filesystem change events must * take care to always subsequently unblock the external change events in all * control paths. It is safe to assume, however, that the underlying impl will * always apply the callback with some value. - + * Caches should be conservative. Consequently, the entry's cached data should * always be cleared if the underlying impl's operation fails. This is the case * event for read-only operations because an unexpected failure implies that the * system is in an unknown state. The entry should communicate this by failing * where appropriate, and should not use the cache to hide failure. - * + * * Only watched entries should make use of cached data because change events are * only expected for such entries, and change events are used to granularly * invalidate out-of-date caches. @@ -64,16 +64,16 @@ */ define(function (require, exports, module) { "use strict"; - + var FileSystemError = require("filesystem/FileSystemError"), WatchedRoot = require("filesystem/WatchedRoot"); - + var VISIT_DEFAULT_MAX_DEPTH = 100, VISIT_DEFAULT_MAX_ENTRIES = 30000; - + /* Counter to give every entry a unique id */ var nextId = 0; - + /** * Model for a file system entry. This is the base class for File and Directory, * and is never used directly. @@ -89,7 +89,7 @@ define(function (require, exports, module) { this._fileSystem = fileSystem; this._id = nextId++; } - + // Add "fullPath", "name", "parent", "id", "isFile" and "isDirectory" getters Object.defineProperties(FileSystemEntry.prototype, { "fullPath": { @@ -121,13 +121,13 @@ define(function (require, exports, module) { set: function () { throw new Error("Cannot set _impl"); } } }); - + /** * Cached stat object for this file. * @type {?FileSystemStats} */ FileSystemEntry.prototype._stat = null; - + /** * Parent file system. * @type {!FileSystem} @@ -139,7 +139,7 @@ define(function (require, exports, module) { * @type {string} */ FileSystemEntry.prototype._path = null; - + /** * The name of this entry. * @type {string} @@ -151,19 +151,19 @@ define(function (require, exports, module) { * @type {string} */ FileSystemEntry.prototype._parentPath = null; - + /** * Whether or not the entry is a file * @type {boolean} */ FileSystemEntry.prototype._isFile = false; - + /** * Whether or not the entry is a directory * @type {boolean} */ FileSystemEntry.prototype._isDirectory = false; - + /** * Cached copy of this entry's watched root * @type {entry: File|Directory, filter: function(FileSystemEntry):boolean, active: boolean} @@ -175,7 +175,7 @@ define(function (require, exports, module) { * @type {boolean} */ FileSystemEntry.prototype._watchedRootFilterResult = false; - + /** * Determines whether or not the entry is watched. * @param {boolean=} relaxed If falsey, the method will only return true if @@ -186,17 +186,17 @@ define(function (require, exports, module) { FileSystemEntry.prototype._isWatched = function (relaxed) { var watchedRoot = this._watchedRoot, filterResult = this._watchedRootFilterResult; - + if (!watchedRoot) { watchedRoot = this._fileSystem._findWatchedRootForPath(this._path); - + if (watchedRoot) { this._watchedRoot = watchedRoot; filterResult = watchedRoot.filter(this._name, this._parentPath); this._watchedRootFilterResult = filterResult; } } - + if (watchedRoot) { if (watchedRoot.status === WatchedRoot.ACTIVE || (relaxed && watchedRoot.status === WatchedRoot.STARTING)) { @@ -210,7 +210,7 @@ define(function (require, exports, module) { } return false; }; - + /** * Update the path for this entry * @private @@ -223,16 +223,16 @@ define(function (require, exports, module) { } this._name = parts[parts.length - 1]; parts.pop(); // Remove name - + if (parts.length > 0) { this._parentPath = parts.join("/") + "/"; } else { // root directories have no parent path this._parentPath = null; } - + this._path = newPath; - + var watchedRoot = this._watchedRoot; if (watchedRoot) { if (newPath.indexOf(watchedRoot.entry.fullPath) === 0) { @@ -245,7 +245,7 @@ define(function (require, exports, module) { } } }; - + /** * Clear any cached data for this entry * @private @@ -253,18 +253,18 @@ define(function (require, exports, module) { FileSystemEntry.prototype._clearCachedData = function () { this._stat = undefined; }; - + /** * Helpful toString for debugging purposes */ FileSystemEntry.prototype.toString = function () { return "[" + (this.isDirectory ? "Directory " : "File ") + this._path + "]"; }; - + /** * Check to see if the entry exists on disk. Note that there will NOT be an * error returned if the file does not exist on the disk; in that case the - * error parameter will be null and the boolean will be false. The error + * error parameter will be null and the boolean will be false. The error * parameter will only be truthy when an unexpected error was encountered * during the test, in which case the state of the entry should be considered * unknown. @@ -277,22 +277,22 @@ define(function (require, exports, module) { callback(null, true); return; } - + this._impl.exists(this._path, function (err, exists) { if (err) { this._clearCachedData(); callback(err); return; } - + if (!exists) { this._clearCachedData(); } - + callback(null, exists); }.bind(this)); }; - + /** * Returns the stats for the entry. * @@ -304,22 +304,22 @@ define(function (require, exports, module) { callback(null, this._stat); return; } - + this._impl.stat(this._path, function (err, stat) { if (err) { this._clearCachedData(); callback(err); return; } - + if (this._isWatched()) { this._stat = stat; } - + callback(null, stat); }.bind(this)); }; - + /** * Rename this entry. * @@ -328,24 +328,25 @@ define(function (require, exports, module) { * string parameter. */ FileSystemEntry.prototype.rename = function (newFullPath, callback) { + console.log(this); callback = callback || function () {}; - + // Block external change events until after the write has finished this._fileSystem._beginChange(); - + this._impl.rename(this._path, newFullPath, function (err) { var oldFullPath = this._path; - + try { if (err) { this._clearCachedData(); callback(err); return; } - + // Update internal filesystem state this._fileSystem._handleRename(this._path, newFullPath, this.isDirectory); - + try { // Notify the caller callback(null); @@ -359,7 +360,7 @@ define(function (require, exports, module) { } }.bind(this)); }; - + /** * Permanently delete this entry. For Directories, this will delete the directory * and all of its contents. For reversible delete, see moveToTrash(). @@ -369,10 +370,10 @@ define(function (require, exports, module) { */ FileSystemEntry.prototype.unlink = function (callback) { callback = callback || function () {}; - + // Block external change events until after the write has finished this._fileSystem._beginChange(); - + this._clearCachedData(); this._impl.unlink(this._path, function (err) { var parent = this._fileSystem.getDirectoryForPath(this.parentPath); @@ -380,19 +381,19 @@ define(function (require, exports, module) { // Update internal filesystem state this._fileSystem._handleDirectoryChange(parent, function (added, removed) { try { - // Notify the caller + // Notify the caller callback(err); } finally { // Notify change listeners this._fileSystem._fireChangeEvent(parent, added, removed); - + // Unblock external change events this._fileSystem._endChange(); } }.bind(this)); }.bind(this)); }; - + /** * Move this entry to the trash. If the underlying file system doesn't support move * to trash, the item is permanently deleted. @@ -410,7 +411,7 @@ define(function (require, exports, module) { // Block external change events until after the write has finished this._fileSystem._beginChange(); - + this._clearCachedData(); this._impl.moveToTrash(this._path, function (err) { var parent = this._fileSystem.getDirectoryForPath(this.parentPath); @@ -423,14 +424,14 @@ define(function (require, exports, module) { } finally { // Notify change listeners this._fileSystem._fireChangeEvent(parent, added, removed); - + // Unblock external change events this._fileSystem._endChange(); } }.bind(this)); }.bind(this)); }; - + /** * Private helper function for FileSystemEntry.visit that requires sanitized options. * @@ -446,37 +447,37 @@ define(function (require, exports, module) { FileSystemEntry.prototype._visitHelper = function (stats, visitedPaths, visitor, options, callback) { var maxDepth = options.maxDepth, maxEntriesCounter = options.maxEntriesCounter; - + if (maxEntriesCounter.value-- <= 0 || maxDepth-- < 0) { // The outer FileSystemEntry.visit call is responsible for applying // the main callback to FileSystemError.TOO_MANY_FILES in this case callback(null); return; } - + if (this.isDirectory) { var visitedPath = stats.realPath || this.fullPath; - + if (visitedPaths.hasOwnProperty(visitedPath)) { // Link cycle detected callback(null); return; } - + visitedPaths[visitedPath] = true; } - + if (!visitor(this) || this.isFile) { callback(null); return; } - + this.getContents(function (err, entries, entriesStats) { if (err) { callback(err); return; } - + var counter = entries.length; if (counter === 0) { callback(null); @@ -488,19 +489,19 @@ define(function (require, exports, module) { callback(null); } } - + var nextOptions = { maxDepth: maxDepth, maxEntriesCounter: maxEntriesCounter }; - + entries.forEach(function (entry, index) { var stats = entriesStats[index]; entry._visitHelper(stats, visitedPaths, visitor, nextOptions, helperCallback); }); }.bind(this)); }; - + /** * Visit this entry and its descendents with the supplied visitor function. * Correctly handles symbolic link cycles and options can be provided to limit @@ -508,7 +509,7 @@ define(function (require, exports, module) { * order is guaranteed; instead of relying on such an order, it is preferable * to use the visit function to build a list of visited entries, sort those * entries as desired, and then process them. Whenever possible, deep - * filesystem traversals should use this method. + * filesystem traversals should use this method. * * @param {function(FileSystemEntry): boolean} visitor - A visitor function, which is * applied to this entry and all descendent FileSystemEntry objects. If the function returns @@ -524,44 +525,44 @@ define(function (require, exports, module) { if (options === undefined) { options = {}; } - + callback = callback || function () {}; } - + if (options.maxDepth === undefined) { options.maxDepth = VISIT_DEFAULT_MAX_DEPTH; } - + if (options.maxEntries === undefined) { options.maxEntries = VISIT_DEFAULT_MAX_ENTRIES; } options.maxEntriesCounter = { value: options.maxEntries }; - + this.stat(function (err, stats) { if (err) { callback(err); return; } - + this._visitHelper(stats, {}, visitor, options, function (err) { if (callback) { if (err) { callback(err); return; } - + if (options.maxEntriesCounter.value < 0) { callback(FileSystemError.TOO_MANY_ENTRIES); return; } - + callback(null); } }.bind(this)); }.bind(this)); }; - + // Export this class module.exports = FileSystemEntry; }); From 00eb97dc03086ad278f5becd3ff211504dfc6ccb Mon Sep 17 00:00:00 2001 From: Steve Pletcher Date: Sat, 19 Dec 2015 14:20:32 -0500 Subject: [PATCH 2/2] Include new file --- src/utils/Collaborator.js | 99 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 src/utils/Collaborator.js diff --git a/src/utils/Collaborator.js b/src/utils/Collaborator.js new file mode 100644 index 00000000000..ea5c376eefb --- /dev/null +++ b/src/utils/Collaborator.js @@ -0,0 +1,99 @@ + /* + * Copyright (c) 2013 Adobe Systems Incorporated. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + */ + + +/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */ +/*global define, $, FileReader*/ + +define(function (require, exports, module) { + "use strict"; + + //pubSubService must implement: + //sub(eventName, callback) : subscribes, runs the callback when the named event is pub'd + //pub(eventName, parameters) : publishes the parameters (must be JSON object or string) to + // the specified event name + //unsub(eventName) : unsubscribes to the event + function Collaborator(pubSubService){ + this._pubSub = pubSubService; + } + + Collaborator.prototype.makeDocumentCollaborative = function(doc){ + doc._collab = this; + + this._pubSub.sub(documentChannel(doc, "change"), function(newText){ + doc.refreshText(newText, new Date()); + }); + + doc.on("change", function(changedDoc){ + this._pubSub.pub(documentChannel(changedDoc, "change"), changedDoc.getText()); + }); + }; + + Collaborator.prototype.makeFileSystemCollaborative = function(filesystem){ + var collab = this; + var handleNewFileSystem = function(eventName){ + return function(newFileSystem){ + var index = newFileSystem._index._index; // TODO map over this and pull out names and IDs only + collab._pubSub.pub(fileSystemChannel(newFileSystem, eventName), newFileSystem) + }; + }; + + filesystem.on("rename", function(oldName, newName){ + collab._pubSub.pub(fileSystemChannel(filesystem, "rename"), { + oldName: oldName, + newName: newName + }); + }); + + filesystem.on("change", function(entry, added, removed){ + collab._pubSub.pub(fileSystemChannel(filesystem, "rename"), { + entry: entry, + added: added, + removed: removed + }); + }); + + collab._pubSub.sub(fileSystemChannel(newFileSystem, "rename"), function(filechange){ + filesystem._handleRename(filechange.oldName, filechange.newName); + }); + + collab._pubSub.sub(fileSystemChannel(newFileSystem, "change"), function(filechange){ + filesystem._enqueueExternalChange(filechange.entry, filechange.added, filechange.removed); + }); + }; + + function fileSystemChannel(filesystem, subEvent){ + return channelName() + (subEvent ? ":" + subEvent : ""); + }; + + function documentChannel(doc, subEvent){ + return channelName(doc.file._path) + (subEvent ? ":" + subEvent : ""); + }; + + function channelName(uniquePart){ + return "collab:" + uniquePart; + } + + // Export public API + exports = Collaborator; +});