From f73b6ede1c0235a37a7b344bc768e83c46417228 Mon Sep 17 00:00:00 2001 From: Glenn Ruehle Date: Tue, 18 Jun 2013 09:34:45 -0700 Subject: [PATCH 001/163] Start sketching out FileSystem --- src/file/FileSystem.js | 101 +++++++++++++++++++++++ src/file/FileSystemImpl.js | 0 src/file/impls/AppshellFileSystem.js | 0 src/file/impls/DropboxFileSystem.js | 0 src/file/impls/GoogleDriveFileSystem.js | 0 src/file/impls/LocalStorageFileSystem.js | 0 src/file/impls/NodeFileSystem.js | 0 7 files changed, 101 insertions(+) create mode 100644 src/file/FileSystem.js create mode 100644 src/file/FileSystemImpl.js create mode 100644 src/file/impls/AppshellFileSystem.js create mode 100644 src/file/impls/DropboxFileSystem.js create mode 100644 src/file/impls/GoogleDriveFileSystem.js create mode 100644 src/file/impls/LocalStorageFileSystem.js create mode 100644 src/file/impls/NodeFileSystem.js diff --git a/src/file/FileSystem.js b/src/file/FileSystem.js new file mode 100644 index 00000000000..26eeae15783 --- /dev/null +++ b/src/file/FileSystem.js @@ -0,0 +1,101 @@ +/* + * 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, regexp: true, indent: 4, maxerr: 50 */ +/*global define */ + +define(function (require, exports, module) { + "use strict"; + + // FileSystemImpl + var _impl; + + function setFileSystemImpl(impl) { + _impl = impl; + } + + /** + * Return a File object for the specified path. + * + * @param {string} path Path of file. + * TODO: @param options. At least create. Probably permissions. Possibly others. + * + * @return {$.Promise} Promise that will be resolved with the File object, + * or rejected if an error occurred. + */ + function getFileForPath(path, options) { + return _impl.getFileForPath(path, options); + } + + /** + * Return an File object that does *not* exist on disk. Any attempts to write to this + * file will result in a Save As dialog. + * + * TODO: @param options. Mode? Others? + * + * @return {$.Promise} Promise that will be resolved with the File object, + * or rejected if an error occurred. + */ + function newUnsavedFile(options) { + return _impl.newUnsavedFile(options); + } + + /** + * Return a Directory object for the specified path. + * + * @param {string} path Path of directory. Pass NULL to get the root directory. + * TODO: @param options. At least create. Probably permissions. Possibly others. + * + * @return {$.Promise} Promise that will be resolved with the Directory object, + * or rejected if an error occurred. + */ + function getDirectoryForPath(path, options) { + return _impl.getDirectoryForPath(path, options); + } + + /** + * Show an "Open" dialog and return the file(s)/directories selected by the user. + * + * TODO: args. See NativeFileSystem.showOpenDialog for examples + * + * @return {$.Promise} Promise that will be resolved with the selected file(s)/directories, + * or rejected if an error occurred. + */ + function showOpenDialog(options) { + return _impl.showOpenDialog(options); + } + + /** + * Show a "Save" dialog and return the path of the file to save. + * + * TODO: args. See NativeFileSystem.showSaveDialog for examples + * + * @return {$.Promise} Promise that will be resolved with the name of the file to save, + * or rejected if an error occurred. + */ + function showSaveDialog(options) { + return _impl.showSaveDialog(options); + } +}); + diff --git a/src/file/FileSystemImpl.js b/src/file/FileSystemImpl.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/file/impls/AppshellFileSystem.js b/src/file/impls/AppshellFileSystem.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/file/impls/DropboxFileSystem.js b/src/file/impls/DropboxFileSystem.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/file/impls/GoogleDriveFileSystem.js b/src/file/impls/GoogleDriveFileSystem.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/file/impls/LocalStorageFileSystem.js b/src/file/impls/LocalStorageFileSystem.js new file mode 100644 index 00000000000..e69de29bb2d diff --git a/src/file/impls/NodeFileSystem.js b/src/file/impls/NodeFileSystem.js new file mode 100644 index 00000000000..e69de29bb2d From 26c31d35f6daf9ceac601de7befa298afd8418fd Mon Sep 17 00:00:00 2001 From: Glenn Ruehle Date: Thu, 20 Jun 2013 07:05:06 -0700 Subject: [PATCH 002/163] Add FileIndex --- src/file/FileIndex.js | 73 ++++++++++++++++++++++++++++++++++++++++++ src/file/FileSystem.js | 16 ++++++++- 2 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 src/file/FileIndex.js diff --git a/src/file/FileIndex.js b/src/file/FileIndex.js new file mode 100644 index 00000000000..bb0fa9d2dc7 --- /dev/null +++ b/src/file/FileIndex.js @@ -0,0 +1,73 @@ +/* + * 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, regexp: true, indent: 4, maxerr: 50 */ +/*global define */ + +define(function (require, exports, module) { + "use strict"; + + // Root path + var _rootPath; + + // Master index + var _index; + + /** + * Clear the file index cache. + */ + function clear() { + _index = {}; + } + + /** + * Set the root for the file index cache. This clears the current cache + * and starts a new indexing worker thread. + * + * @param {string} rootPath The new root path. + */ + function setRoot(rootPath) { + // Clear existing index + clear(); + + // Set root + _rootPath = rootPath; + + // TODO: Start indexing on worker thread + } + + /** + * Returns the cached entry for the specified path, or undefined + * if the path has not been cached. + * + * @param {string} path The path of the entry to return. + * @return {File|Directory} The entry for the path, or undefined if it hasn't + * been cached yet. + */ + function getEntry(path) { + return _index[path]; + } + + +}); diff --git a/src/file/FileSystem.js b/src/file/FileSystem.js index 26eeae15783..bb1150d4e8a 100644 --- a/src/file/FileSystem.js +++ b/src/file/FileSystem.js @@ -23,11 +23,13 @@ /*jslint vars: true, plusplus: true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50 */ -/*global define */ +/*global define, $ */ define(function (require, exports, module) { "use strict"; + var FileIndex = require("file/FileIndex"); + // FileSystemImpl var _impl; @@ -45,6 +47,12 @@ define(function (require, exports, module) { * or rejected if an error occurred. */ function getFileForPath(path, options) { + var cachedEntry = FileIndex.getEntryForPath(path); + + if (cachedEntry) { + return new $.Deferred().resolve(cachedEntry).promise(); + } + return _impl.getFileForPath(path, options); } @@ -71,6 +79,12 @@ define(function (require, exports, module) { * or rejected if an error occurred. */ function getDirectoryForPath(path, options) { + var cachedEntry = FileIndex.getEntryForPath(path); + + if (cachedEntry) { + return new $.Deferred().resolve(cachedEntry).promise(); + } + return _impl.getDirectoryForPath(path, options); } From 4a4471cb26ce0adfe0c6b941257c42ad99ad94e2 Mon Sep 17 00:00:00 2001 From: Glenn Ruehle Date: Thu, 20 Jun 2013 07:06:59 -0700 Subject: [PATCH 003/163] Move files to new filesystem folder --- src/{file => filesystem}/FileIndex.js | 0 src/{file => filesystem}/FileSystem.js | 0 src/{file => filesystem}/FileSystemImpl.js | 0 src/{file => filesystem}/impls/AppshellFileSystem.js | 0 src/{file => filesystem}/impls/DropboxFileSystem.js | 0 src/{file => filesystem}/impls/GoogleDriveFileSystem.js | 0 src/{file => filesystem}/impls/LocalStorageFileSystem.js | 0 src/{file => filesystem}/impls/NodeFileSystem.js | 0 8 files changed, 0 insertions(+), 0 deletions(-) rename src/{file => filesystem}/FileIndex.js (100%) rename src/{file => filesystem}/FileSystem.js (100%) rename src/{file => filesystem}/FileSystemImpl.js (100%) rename src/{file => filesystem}/impls/AppshellFileSystem.js (100%) rename src/{file => filesystem}/impls/DropboxFileSystem.js (100%) rename src/{file => filesystem}/impls/GoogleDriveFileSystem.js (100%) rename src/{file => filesystem}/impls/LocalStorageFileSystem.js (100%) rename src/{file => filesystem}/impls/NodeFileSystem.js (100%) diff --git a/src/file/FileIndex.js b/src/filesystem/FileIndex.js similarity index 100% rename from src/file/FileIndex.js rename to src/filesystem/FileIndex.js diff --git a/src/file/FileSystem.js b/src/filesystem/FileSystem.js similarity index 100% rename from src/file/FileSystem.js rename to src/filesystem/FileSystem.js diff --git a/src/file/FileSystemImpl.js b/src/filesystem/FileSystemImpl.js similarity index 100% rename from src/file/FileSystemImpl.js rename to src/filesystem/FileSystemImpl.js diff --git a/src/file/impls/AppshellFileSystem.js b/src/filesystem/impls/AppshellFileSystem.js similarity index 100% rename from src/file/impls/AppshellFileSystem.js rename to src/filesystem/impls/AppshellFileSystem.js diff --git a/src/file/impls/DropboxFileSystem.js b/src/filesystem/impls/DropboxFileSystem.js similarity index 100% rename from src/file/impls/DropboxFileSystem.js rename to src/filesystem/impls/DropboxFileSystem.js diff --git a/src/file/impls/GoogleDriveFileSystem.js b/src/filesystem/impls/GoogleDriveFileSystem.js similarity index 100% rename from src/file/impls/GoogleDriveFileSystem.js rename to src/filesystem/impls/GoogleDriveFileSystem.js diff --git a/src/file/impls/LocalStorageFileSystem.js b/src/filesystem/impls/LocalStorageFileSystem.js similarity index 100% rename from src/file/impls/LocalStorageFileSystem.js rename to src/filesystem/impls/LocalStorageFileSystem.js diff --git a/src/file/impls/NodeFileSystem.js b/src/filesystem/impls/NodeFileSystem.js similarity index 100% rename from src/file/impls/NodeFileSystem.js rename to src/filesystem/impls/NodeFileSystem.js From 619dd453f7697e58f6bddf0b5206c9074a0c98c1 Mon Sep 17 00:00:00 2001 From: Glenn Ruehle Date: Thu, 20 Jun 2013 07:23:38 -0700 Subject: [PATCH 004/163] Add FileSystemEntry, File, and Directory classes. --- src/filesystem/Directory.js | 30 ++++++++++++++ src/filesystem/File.js | 30 ++++++++++++++ src/filesystem/FileSystem.js | 12 +++++- src/filesystem/FileSystemEntry.js | 65 +++++++++++++++++++++++++++++++ 4 files changed, 136 insertions(+), 1 deletion(-) create mode 100644 src/filesystem/Directory.js create mode 100644 src/filesystem/File.js create mode 100644 src/filesystem/FileSystemEntry.js diff --git a/src/filesystem/Directory.js b/src/filesystem/Directory.js new file mode 100644 index 00000000000..6847d7368ed --- /dev/null +++ b/src/filesystem/Directory.js @@ -0,0 +1,30 @@ +/* + * 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, regexp: true, indent: 4, maxerr: 50 */ +/*global define, $ */ + +define(function (require, exports, module) { + "use strict"; +}); diff --git a/src/filesystem/File.js b/src/filesystem/File.js new file mode 100644 index 00000000000..6847d7368ed --- /dev/null +++ b/src/filesystem/File.js @@ -0,0 +1,30 @@ +/* + * 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, regexp: true, indent: 4, maxerr: 50 */ +/*global define, $ */ + +define(function (require, exports, module) { + "use strict"; +}); diff --git a/src/filesystem/FileSystem.js b/src/filesystem/FileSystem.js index bb1150d4e8a..8dcbf92a30c 100644 --- a/src/filesystem/FileSystem.js +++ b/src/filesystem/FileSystem.js @@ -36,7 +36,17 @@ define(function (require, exports, module) { function setFileSystemImpl(impl) { _impl = impl; } - + + /** + * Set the root directory for the project. This clears any existing file cache + * and starts indexing on a new worker. + * + * @param {string} rootPath The new project root. + */ + function setProjectRoot(rootPath) { + FileIndex.setRoot(rootPath); + } + /** * Return a File object for the specified path. * diff --git a/src/filesystem/FileSystemEntry.js b/src/filesystem/FileSystemEntry.js new file mode 100644 index 00000000000..a393d18ec03 --- /dev/null +++ b/src/filesystem/FileSystemEntry.js @@ -0,0 +1,65 @@ +/* + * 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, regexp: true, indent: 4, maxerr: 50 */ +/*global define, $ */ + +/* TODO: Document this class/module. */ + +define(function (require, exports, module) { + "use strict"; + + /** + * Constructor + * @param {string} path The path for this entry + */ + function FileSystemEntry(path) { + this._path = path; + } + + /** + * The path of this entry. + * @type {string} + */ + FileSystemEntry.prototype._path = null; + + /** + * Returns true if this entry is a file. + * @return {boolean} + */ + FileSystemEntry.prototype.isFile = function () { + return false; + }; + + /** + * Returns true if this entry is a directory. + * @return {boolean} + */ + FileSystemEntry.prototype.isDirectory = function () { + return false; + }; + + // Export this class + exports = FileSystemEntry; +}); From 714732b97d6da528977ff64fd46dfa3dc0187a27 Mon Sep 17 00:00:00 2001 From: Glenn Ruehle Date: Mon, 24 Jun 2013 10:50:50 -0700 Subject: [PATCH 005/163] Get basic file reading and writing working --- src/brackets.js | 1 + src/filesystem/File.js | 92 +++++++++++++++ src/filesystem/FileIndex.js | 12 +- src/filesystem/FileSystem.js | 31 ++++- src/filesystem/FileSystemEntry.js | 9 +- src/filesystem/FileSystemImpl.js | 23 ++++ src/filesystem/impls/AppshellFileSystem.js | 128 +++++++++++++++++++++ 7 files changed, 287 insertions(+), 9 deletions(-) diff --git a/src/brackets.js b/src/brackets.js index 5068a19c494..baa27a551d2 100644 --- a/src/brackets.js +++ b/src/brackets.js @@ -77,6 +77,7 @@ define(function (require, exports, module) { CodeHintManager = require("editor/CodeHintManager"), PerfUtils = require("utils/PerfUtils"), FileIndexManager = require("project/FileIndexManager"), + FileSystem = require("filesystem/FileSystem"), QuickOpen = require("search/QuickOpen"), Menus = require("command/Menus"), FileUtils = require("file/FileUtils"), diff --git a/src/filesystem/File.js b/src/filesystem/File.js index 6847d7368ed..d54b8a2367e 100644 --- a/src/filesystem/File.js +++ b/src/filesystem/File.js @@ -27,4 +27,96 @@ define(function (require, exports, module) { "use strict"; + + var FileSystemEntry = require("filesystem/FileSystemEntry"); + + function File(fullPath, impl) { + FileSystemEntry.call(this, fullPath); + this._impl = impl; + } + + File.prototype = Object.create(FileSystemEntry.prototype); + File.prototype.constructor = File; + File.prototype.parentClass = FileSystemEntry.prototype; + + /** + * Low level file system implementation. + */ + File.prototype._impl = null; + + /** + * Cached stat object for this file. + */ + File.prototype._stat = null; + + File.prototype.isFile = function () { + return true; + }; + + /** + * Returns the stats for the file. + * + * @return {$.Promise} Promise that is resolved with the file's stats, or rejected + * if an error occurred. + */ + File.prototype.stat = function () { + var result = new $.Deferred(); + + if (this._stat) { + result.resolve(this._stat); + } else { + this._impl.stat(this._path, function (err, stat) { + if (err) { + result.reject(err); + } else { + this._stat = stat; + result.resolve(this._stat); + } + }.bind(this)); + } + + return result.promise(); + }; + + /** + * Read a file as text. + * + * @param {string=} encoding Encoding for reading. Defaults to UTF-8. + * + * @return {$.Promise} Promise that is resolved with the text and stats from the file, + * or rejected if an error occurred. + */ + File.prototype.readAsText = function (encoding) { + var result = new $.Deferred(); + + this._impl.readFile(this._path, encoding ? {encoding: encoding} : {}, function (err, data, stat) { + if (err) { + result.reject(err); + } else { + this._stat = stat; + result.resolve(data, stat); + } + }.bind(this)); + + return result.promise(); + }; + + /** + * Write a file. + * + * @param {string} data Data to write. + * @param {string=} encoding Encoding for data. Defaults to UTF-8. + * + * @return {$.Promise} Promise that is resolved with the file's new stats when the + * writing is complete, or rejected if an error occurred. + */ + File.prototype.write = function (data, encoding) { + var result = new $.Deferred(); + + //this._impl.writeFile(this._path, encoding + return result.promise(); + }; + + // Export this class + module.exports = File; }); diff --git a/src/filesystem/FileIndex.js b/src/filesystem/FileIndex.js index bb0fa9d2dc7..ab6b69af054 100644 --- a/src/filesystem/FileIndex.js +++ b/src/filesystem/FileIndex.js @@ -32,7 +32,7 @@ define(function (require, exports, module) { var _rootPath; // Master index - var _index; + var _index = {}; /** * Clear the file index cache. @@ -57,6 +57,10 @@ define(function (require, exports, module) { // TODO: Start indexing on worker thread } + function addEntry(entry) { + _index[entry.getPath()] = entry; + } + /** * Returns the cached entry for the specified path, or undefined * if the path has not been cached. @@ -69,5 +73,9 @@ define(function (require, exports, module) { return _index[path]; } - + // Export public API + exports.clear = clear; + exports.setRoot = setRoot; + exports.addEntry = addEntry; + exports.getEntry = getEntry; }); diff --git a/src/filesystem/FileSystem.js b/src/filesystem/FileSystem.js index 8dcbf92a30c..45076bbc4cc 100644 --- a/src/filesystem/FileSystem.js +++ b/src/filesystem/FileSystem.js @@ -28,10 +28,12 @@ define(function (require, exports, module) { "use strict"; - var FileIndex = require("file/FileIndex"); + var Directory = require("filesystem/Directory"), + File = require("filesystem/File"), + FileIndex = require("filesystem/FileIndex"); // FileSystemImpl - var _impl; + var _impl = require("filesystem/impls/AppshellFileSystem"); // Temp - force appshell impl for now function setFileSystemImpl(impl) { _impl = impl; @@ -57,13 +59,19 @@ define(function (require, exports, module) { * or rejected if an error occurred. */ function getFileForPath(path, options) { - var cachedEntry = FileIndex.getEntryForPath(path); + var cachedEntry = FileIndex.getEntry(path); if (cachedEntry) { return new $.Deferred().resolve(cachedEntry).promise(); } - return _impl.getFileForPath(path, options); + var file = new File(path, _impl); + + // TODO: Options + + FileIndex.addEntry(file); + + return new $.Deferred().resolve(file).promise(); } /** @@ -76,7 +84,9 @@ define(function (require, exports, module) { * or rejected if an error occurred. */ function newUnsavedFile(options) { - return _impl.newUnsavedFile(options); + // TODO: Implement me + + // return _impl.newUnsavedFile(options); } /** @@ -89,7 +99,7 @@ define(function (require, exports, module) { * or rejected if an error occurred. */ function getDirectoryForPath(path, options) { - var cachedEntry = FileIndex.getEntryForPath(path); + var cachedEntry = FileIndex.getEntry(path); if (cachedEntry) { return new $.Deferred().resolve(cachedEntry).promise(); @@ -121,5 +131,14 @@ define(function (require, exports, module) { function showSaveDialog(options) { return _impl.showSaveDialog(options); } + + // Export public API + exports.setFileSystemImpl = setFileSystemImpl; + exports.setProjectRoot = setProjectRoot; + exports.getFileForPath = getFileForPath; + exports.newUnsavedFile = newUnsavedFile; + exports.getDirectoryForPath = getDirectoryForPath; + exports.showOpenDialog = showOpenDialog; + exports.showSaveDialog = showSaveDialog; }); diff --git a/src/filesystem/FileSystemEntry.js b/src/filesystem/FileSystemEntry.js index a393d18ec03..39143228544 100644 --- a/src/filesystem/FileSystemEntry.js +++ b/src/filesystem/FileSystemEntry.js @@ -44,6 +44,13 @@ define(function (require, exports, module) { */ FileSystemEntry.prototype._path = null; + /** + * Returns the path for this file entry. + */ + FileSystemEntry.prototype.getPath = function () { + return this._path; + }; + /** * Returns true if this entry is a file. * @return {boolean} @@ -61,5 +68,5 @@ define(function (require, exports, module) { }; // Export this class - exports = FileSystemEntry; + module.exports = FileSystemEntry; }); diff --git a/src/filesystem/FileSystemImpl.js b/src/filesystem/FileSystemImpl.js index e69de29bb2d..114f9651f6d 100644 --- a/src/filesystem/FileSystemImpl.js +++ b/src/filesystem/FileSystemImpl.js @@ -0,0 +1,23 @@ +// Methods that must be implemented by a FileSystemImpl +// +// TODO: ERROR CODES!!!!!!! +// +// stat { +// isFile(): boolean, +// isDirectory(): boolean, +// mtime: Date, +// } +// +// +// showOpenDialog(TODO: args) +// showSaveDialog(TODO: args) +// [isNetworkDrive(path, callback)] +// readdir(path, callback) +// mkdir(path, [mode], callback) +// rename(oldPath, newPath, callback) +// stat(path, callback) +// readFile(path, [options], callback) +// writeFile(path, data, [options], callback) +// chmod(path, mode, callback) +// unlink(path, callback) +// [moveToTrash(path, callback)] \ No newline at end of file diff --git a/src/filesystem/impls/AppshellFileSystem.js b/src/filesystem/impls/AppshellFileSystem.js index e69de29bb2d..7a71df58851 100644 --- a/src/filesystem/impls/AppshellFileSystem.js +++ b/src/filesystem/impls/AppshellFileSystem.js @@ -0,0 +1,128 @@ +/* + * 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, regexp: true, indent: 4, maxerr: 50 */ +/*global define, appshell */ + +define(function (require, exports, module) { + "use strict"; + + function showOpenDialog() { + // TODO + } + + function showSaveDialog() { + // TODO + } + + function isNetworkDrive(path, callback) { + appshell.fs.isNetworkDrive(path, callback); + } + + function readdir(path, callback) { + appshell.fs.readdir(path, callback); + } + + function mkdir(path, mode, callback) { + if (typeof mode === "function") { + callback = mode; + mode = parseInt("0777", 8); + } + appshell.fs.makedir(path, mode, callback); + } + + function rename(oldPath, newPath, callback) { + appshell.fs.rename(oldPath, newPath, callback); + } + + function stat(path, callback) { + appshell.fs.stat(path, callback); + } + + function readFile(path, options, callback) { + var encoding = "utf8"; + + if (typeof options === "function") { + callback = options; + } else { + encoding = options.encoding || "utf8"; + } + + appshell.fs.readFile(path, encoding, function (err, data) { + if (err) { + callback(err, null); + } else { + stat(path, function (err, stat) { + callback(err, data, stat); + }); + } + }); + } + + function writeFile(path, data, options, callback) { + var encoding = "utf8"; + + if (typeof options === "function") { + callback = options; + } else { + encoding = options.encoding || "utf8"; + } + + appshell.fs.writeFile(path, data, encoding, function (err) { + if (err) { + callback(err); + } else { + stat(path, function (err, stat) { + callback(err, stat); + }); + } + }); + } + + function chmod(path, mode, callback) { + appshell.fs.chmod(path, mode, callback); + } + + function unlink(path, callback) { + appshell.fs.unlink(path, callback); + } + + function moveToTrash(path, callback) { + appshell.fs.moveToTrash(path, callback); + } + + // Export public API + exports.showOpenDialog = showOpenDialog; + exports.showSavedialog = showSaveDialog; + exports.isNetworkDrive = isNetworkDrive; + exports.readdir = readdir; + exports.mkdir = mkdir; + exports.rename = rename; + exports.stat = stat; + exports.readFile = readFile; + exports.writeFile = writeFile; + exports.chmod = chmod; + exports.unlink = unlink; + exports.moveToTrash = moveToTrash; +}); \ No newline at end of file From 1fa91449186afdab9ecc4e764f81d74faae9c463 Mon Sep 17 00:00:00 2001 From: Glenn Ruehle Date: Tue, 25 Jun 2013 09:44:15 -0700 Subject: [PATCH 006/163] Add Directory implementation. Make more stuff work. --- src/filesystem/Directory.js | 48 ++++++++ src/filesystem/File.js | 38 +----- src/filesystem/FileSystem.js | 98 ++++++++++------ src/filesystem/FileSystemEntry.js | 127 ++++++++++++++++++++- src/filesystem/FileSystemImpl.js | 1 + src/filesystem/impls/AppshellFileSystem.js | 52 ++++++++- 6 files changed, 285 insertions(+), 79 deletions(-) diff --git a/src/filesystem/Directory.js b/src/filesystem/Directory.js index 6847d7368ed..e59e0e17628 100644 --- a/src/filesystem/Directory.js +++ b/src/filesystem/Directory.js @@ -27,4 +27,52 @@ define(function (require, exports, module) { "use strict"; + + var FileSystemEntry = require("filesystem/FileSystemEntry"); + + function Directory(fullPath, impl) { + FileSystemEntry.call(this, fullPath, impl); + this._impl = impl; + } + + Directory.prototype = Object.create(FileSystemEntry.prototype); + Directory.prototype.constructor = Directory; + Directory.prototype.parentClass = FileSystemEntry.prototype; + + /** + * The contents of this directory. This "private" property is used by FileSystem. + * @type {Array} + */ + Directory.prototype._contents = null; + + Directory.prototype.isDirectory = function () { + return true; + }; + + /** + * Create a directory + * + * @param {int=} mode The mode for the directory. + * + * @return {$.Promise} Promise that is resolved with the stat from the new directory. + */ + Directory.prototype.create = function (mode) { + var result = new $.Deferred(); + + // TODO: support mode + + this._impl.mkdir(this._path, function (err, stat) { + if (err) { + result.reject(err); + } else { + this._stat = stat; + result.resolve(stat); + } + }.bind(this)); + + return result.promise(); + }; + + // Export this class + module.exports = Directory; }); diff --git a/src/filesystem/File.js b/src/filesystem/File.js index d54b8a2367e..a51b5121a71 100644 --- a/src/filesystem/File.js +++ b/src/filesystem/File.js @@ -31,53 +31,17 @@ define(function (require, exports, module) { var FileSystemEntry = require("filesystem/FileSystemEntry"); function File(fullPath, impl) { - FileSystemEntry.call(this, fullPath); - this._impl = impl; + FileSystemEntry.call(this, fullPath, impl); } File.prototype = Object.create(FileSystemEntry.prototype); File.prototype.constructor = File; File.prototype.parentClass = FileSystemEntry.prototype; - /** - * Low level file system implementation. - */ - File.prototype._impl = null; - - /** - * Cached stat object for this file. - */ - File.prototype._stat = null; - File.prototype.isFile = function () { return true; }; - /** - * Returns the stats for the file. - * - * @return {$.Promise} Promise that is resolved with the file's stats, or rejected - * if an error occurred. - */ - File.prototype.stat = function () { - var result = new $.Deferred(); - - if (this._stat) { - result.resolve(this._stat); - } else { - this._impl.stat(this._path, function (err, stat) { - if (err) { - result.reject(err); - } else { - this._stat = stat; - result.resolve(this._stat); - } - }.bind(this)); - } - - return result.promise(); - }; - /** * Read a file as text. * diff --git a/src/filesystem/FileSystem.js b/src/filesystem/FileSystem.js index 45076bbc4cc..584906103ca 100644 --- a/src/filesystem/FileSystem.js +++ b/src/filesystem/FileSystem.js @@ -53,35 +53,25 @@ define(function (require, exports, module) { * Return a File object for the specified path. * * @param {string} path Path of file. - * TODO: @param options. At least create. Probably permissions. Possibly others. * - * @return {$.Promise} Promise that will be resolved with the File object, - * or rejected if an error occurred. + * @return {File} The File object. This file may not yet exist on disk. */ - function getFileForPath(path, options) { - var cachedEntry = FileIndex.getEntry(path); + function getFileForPath(path) { + var file = FileIndex.getEntry(path); - if (cachedEntry) { - return new $.Deferred().resolve(cachedEntry).promise(); + if (!file) { + file = new File(path, _impl); + FileIndex.addEntry(file); } - - var file = new File(path, _impl); - - // TODO: Options - - FileIndex.addEntry(file); - - return new $.Deferred().resolve(file).promise(); + + return file; } /** * Return an File object that does *not* exist on disk. Any attempts to write to this * file will result in a Save As dialog. * - * TODO: @param options. Mode? Others? - * - * @return {$.Promise} Promise that will be resolved with the File object, - * or rejected if an error occurred. + * @return {File} The File object. */ function newUnsavedFile(options) { // TODO: Implement me @@ -93,21 +83,58 @@ define(function (require, exports, module) { * Return a Directory object for the specified path. * * @param {string} path Path of directory. Pass NULL to get the root directory. - * TODO: @param options. At least create. Probably permissions. Possibly others. * - * @return {$.Promise} Promise that will be resolved with the Directory object, - * or rejected if an error occurred. + * @return {Directory} The Directory object. This directory may not yet exist on disk. */ - function getDirectoryForPath(path, options) { - var cachedEntry = FileIndex.getEntry(path); + function getDirectoryForPath(path) { + var directory = FileIndex.getEntry(path); - if (cachedEntry) { - return new $.Deferred().resolve(cachedEntry).promise(); + if (!directory) { + directory = new Directory(path, _impl); + FileIndex.addEntry(directory); } - - return _impl.getDirectoryForPath(path, options); + + return directory; } + /** + * Read the contents of a Directory. + * + * @param {Directory} directory Directory whose contents you want to get + * + * @return {$.Promise} Promise that is resolved with the contents of the directory. + * Contents is an Array of File and Directory objects. + */ + function getDirectoryContents(directory) { + var i, entryPath, entry, result = new $.Deferred(); + + if (directory._contents) { + result.resolve(directory._contents); + return; + } + + _impl.readdir(directory.getPath(), function (err, contents, stats) { + directory._contents = []; + + // Instantiate content objects + for (i = 0; i < stats.length; i++) { + entryPath = directory.getPath() + "/" + contents[i]; + + if (stats[i].isFile()) { + entry = getFileForPath(entryPath); + } else { + entry = getDirectoryForPath(entryPath); + } + + directory._contents.push(entry); + } + + result.resolve(directory._contents); + }.bind(this)); + + return result.promise(); + }; + /** * Show an "Open" dialog and return the file(s)/directories selected by the user. * @@ -133,12 +160,13 @@ define(function (require, exports, module) { } // Export public API - exports.setFileSystemImpl = setFileSystemImpl; - exports.setProjectRoot = setProjectRoot; - exports.getFileForPath = getFileForPath; - exports.newUnsavedFile = newUnsavedFile; - exports.getDirectoryForPath = getDirectoryForPath; - exports.showOpenDialog = showOpenDialog; - exports.showSaveDialog = showSaveDialog; + exports.setFileSystemImpl = setFileSystemImpl; + exports.setProjectRoot = setProjectRoot; + exports.getFileForPath = getFileForPath; + exports.newUnsavedFile = newUnsavedFile; + exports.getDirectoryForPath = getDirectoryForPath; + exports.getDirectoryContents = getDirectoryContents; + exports.showOpenDialog = showOpenDialog; + exports.showSaveDialog = showSaveDialog; }); diff --git a/src/filesystem/FileSystemEntry.js b/src/filesystem/FileSystemEntry.js index 39143228544..f68130cee92 100644 --- a/src/filesystem/FileSystemEntry.js +++ b/src/filesystem/FileSystemEntry.js @@ -34,10 +34,21 @@ define(function (require, exports, module) { * Constructor * @param {string} path The path for this entry */ - function FileSystemEntry(path) { + function FileSystemEntry(path, impl) { this._path = path; + this._impl = impl; } + /** + * Cached stat object for this file. + */ + FileSystemEntry.prototype._stat = null; + + /** + * Low level file system implementation. + */ + FileSystemEntry.prototype._impl = null; + /** * The path of this entry. * @type {string} @@ -67,6 +78,120 @@ define(function (require, exports, module) { return false; }; + /** + * Returns true if the entry exists on disk. + * + * @return {$.Promise} Promise that is resolved with true if the entry exists, + * or false if it doesn't. + */ + FileSystemEntry.prototype.exists = function () { + var result = new $.Deferred(); + + // If we have _stat, the entry must exist + if (this._stat) { + result.resolve(true); + } else { + // No _stat object yet, query the system + this._impl.exists(this._path, function (val) { + result.resolve(val); + }); + } + + return result.promise(); + }; + + /** + * Returns the stats for the entry. + * + * @return {$.Promise} Promise that is resolved with the entries stats, or rejected + * if an error occurred. + */ + FileSystemEntry.prototype.stat = function () { + var result = new $.Deferred(); + + if (this._stat) { + result.resolve(this._stat); + } else { + this._impl.stat(this._path, function (err, stat) { + if (err) { + result.reject(err); + } else { + this._stat = stat; + result.resolve(this._stat); + } + }.bind(this)); + } + + return result.promise(); + }; + + /** + * Rename this entry. + * + * @param {String} newName New name for this entry. + * + * @return {$.Promise} Promise that is resolved with the entries stats, or rejected + * if an error occurred. + */ + FileSystemEntry.prototype.rename = function (newName) { + var result = new $.Deferred(); + + this._impl.rename(this._path, newName, function (err) { + if (err) { + result.reject(err); + } else { + result.resolve(); + } + }); + + return result.promise(); + }; + + /** + * Unlink (delete) this entry. + * + * @return {$.Promise} Promise that is resolved if the unlink succeeded, or rejected + * if an error occurred. + */ + FileSystemEntry.prototype.unlink = function () { + var result = new $.Deferred(); + + this._impl.unlink(function (err) { + if (err) { + result.reject(err); + } else { + result.resolve(); + } + }); + + return result.promise(); + }; + + /** + * Move this entry to the trash. If the underlying file system doesn't support move + * to trash, the item is permanently deleted. + * + * @return {$.Promise} Promise that is resolved if the moveToTrash succeeded, or rejected + * if an error occurred. + */ + FileSystemEntry.prototype.moveToTrash = function () { + if (!this._impl.moveToTrash) { + return this.unlink(); + } + + var result = new $.Deferred(); + + this._impl.moveToTrash(function (err) { + if (err) { + result.reject(err); + } else { + result.resolve(); + } + }); + + return result.promise(); + }; + // Export this class module.exports = FileSystemEntry; }); diff --git a/src/filesystem/FileSystemImpl.js b/src/filesystem/FileSystemImpl.js index 114f9651f6d..f5d4b0c5e5a 100644 --- a/src/filesystem/FileSystemImpl.js +++ b/src/filesystem/FileSystemImpl.js @@ -12,6 +12,7 @@ // showOpenDialog(TODO: args) // showSaveDialog(TODO: args) // [isNetworkDrive(path, callback)] +// exists(path, callback) // readdir(path, callback) // mkdir(path, [mode], callback) // rename(oldPath, newPath, callback) diff --git a/src/filesystem/impls/AppshellFileSystem.js b/src/filesystem/impls/AppshellFileSystem.js index 7a71df58851..cff54072378 100644 --- a/src/filesystem/impls/AppshellFileSystem.js +++ b/src/filesystem/impls/AppshellFileSystem.js @@ -36,12 +36,47 @@ define(function (require, exports, module) { // TODO } + function stat(path, callback) { + appshell.fs.stat(path, callback); + } + function isNetworkDrive(path, callback) { appshell.fs.isNetworkDrive(path, callback); } + function exists(path, callback) { + stat(path, function (err) { + if (err) { + callback(false); + } else { + callback(true); + } + }); + } + function readdir(path, callback) { - appshell.fs.readdir(path, callback); + var stats = []; + + appshell.fs.readdir(path, function (err, contents) { + var i, count = contents.length; + + if (err) { + callback(err); + return; + } + + contents.forEach(function (val, idx) { + stat(path + "/" + val, function (err, stat) { + if (!err) { + stats[idx] = stat; + } + count--; + if (count <= 0) { + callback(err, contents, stats); + } + }); + }); + }); } function mkdir(path, mode, callback) { @@ -49,17 +84,21 @@ define(function (require, exports, module) { callback = mode; mode = parseInt("0777", 8); } - appshell.fs.makedir(path, mode, callback); + appshell.fs.makedir(path, mode, function (err) { + if (err) { + callback(err); + } else { + stat(path, function (err, stat) { + callback(err, stat); + }); + } + }); } function rename(oldPath, newPath, callback) { appshell.fs.rename(oldPath, newPath, callback); } - function stat(path, callback) { - appshell.fs.stat(path, callback); - } - function readFile(path, options, callback) { var encoding = "utf8"; @@ -116,6 +155,7 @@ define(function (require, exports, module) { exports.showOpenDialog = showOpenDialog; exports.showSavedialog = showSaveDialog; exports.isNetworkDrive = isNetworkDrive; + exports.exists = exists; exports.readdir = readdir; exports.mkdir = mkdir; exports.rename = rename; From cb6b986db803b906b58fe961aa18b79737eb2e4d Mon Sep 17 00:00:00 2001 From: Glenn Ruehle Date: Thu, 27 Jun 2013 17:45:39 -0700 Subject: [PATCH 007/163] Set indexing when root path is set. --- src/filesystem/FileIndex.js | 19 ++---------- src/filesystem/FileSystem.js | 57 ++++++++++++++++++++++++++++-------- 2 files changed, 46 insertions(+), 30 deletions(-) diff --git a/src/filesystem/FileIndex.js b/src/filesystem/FileIndex.js index ab6b69af054..b02ce26aee0 100644 --- a/src/filesystem/FileIndex.js +++ b/src/filesystem/FileIndex.js @@ -27,9 +27,6 @@ define(function (require, exports, module) { "use strict"; - - // Root path - var _rootPath; // Master index var _index = {}; @@ -42,21 +39,10 @@ define(function (require, exports, module) { } /** - * Set the root for the file index cache. This clears the current cache - * and starts a new indexing worker thread. + * Add an entry. * - * @param {string} rootPath The new root path. + * @param {FileSystemEntry} entry The entry to add. */ - function setRoot(rootPath) { - // Clear existing index - clear(); - - // Set root - _rootPath = rootPath; - - // TODO: Start indexing on worker thread - } - function addEntry(entry) { _index[entry.getPath()] = entry; } @@ -75,7 +61,6 @@ define(function (require, exports, module) { // Export public API exports.clear = clear; - exports.setRoot = setRoot; exports.addEntry = addEntry; exports.getEntry = getEntry; }); diff --git a/src/filesystem/FileSystem.js b/src/filesystem/FileSystem.js index 584906103ca..c452c2dc2b5 100644 --- a/src/filesystem/FileSystem.js +++ b/src/filesystem/FileSystem.js @@ -39,16 +39,6 @@ define(function (require, exports, module) { _impl = impl; } - /** - * Set the root directory for the project. This clears any existing file cache - * and starts indexing on a new worker. - * - * @param {string} rootPath The new project root. - */ - function setProjectRoot(rootPath) { - FileIndex.setRoot(rootPath); - } - /** * Return a File object for the specified path. * @@ -108,7 +98,13 @@ define(function (require, exports, module) { function getDirectoryContents(directory) { var i, entryPath, entry, result = new $.Deferred(); + if (directory._contentsPromise) { + // Existing promise for this directory's contents. Return it. + return directory._contentsPromise; + } + if (directory._contents) { + // Return cached directory contents result.resolve(directory._contents); return; } @@ -129,11 +125,14 @@ define(function (require, exports, module) { directory._contents.push(entry); } + directory._contentsPromise = null; result.resolve(directory._contents); - }.bind(this)); + }); + + directory._contentsPromise = result.promise(); return result.promise(); - }; + } /** * Show an "Open" dialog and return the file(s)/directories selected by the user. @@ -159,14 +158,46 @@ define(function (require, exports, module) { return _impl.showSaveDialog(options); } + /** + * @private + * Recursively scan and index all entries in a directory + */ + function _scanDirectory(directoryPath) { + var directory = getDirectoryForPath(directoryPath); + + getDirectoryContents(directory).done(function (entries) { + var i; + + for (i = 0; i < entries.length; i++) { + if (entries[i].isDirectory()) { + _scanDirectory(entries[i].getPath()); + } + } + }); + } + + /** + * Set the root directory for the project. This clears any existing file cache + * and starts indexing on a new worker. + * + * @param {string} rootPath The new project root. + */ + function setProjectRoot(rootPath) { + // Clear file index + FileIndex.clear(); + + // Start indexing from the new root path + _scanDirectory(rootPath); + } + // Export public API exports.setFileSystemImpl = setFileSystemImpl; - exports.setProjectRoot = setProjectRoot; exports.getFileForPath = getFileForPath; exports.newUnsavedFile = newUnsavedFile; exports.getDirectoryForPath = getDirectoryForPath; exports.getDirectoryContents = getDirectoryContents; exports.showOpenDialog = showOpenDialog; exports.showSaveDialog = showSaveDialog; + exports.setProjectRoot = setProjectRoot; }); From 5fcf4259d2571f204886d812a9ab4d4edd23a2b6 Mon Sep 17 00:00:00 2001 From: Glenn Ruehle Date: Fri, 28 Jun 2013 11:27:00 -0700 Subject: [PATCH 008/163] Cache file contents. Hook up Find in Files. --- src/filesystem/File.js | 31 +++++++++---- src/filesystem/FileIndex.js | 12 +++++ src/filesystem/FileSystem.js | 10 +++++ src/project/ProjectManager.js | 7 ++- src/search/FindInFiles.js | 82 +++++++++++++++++------------------ 5 files changed, 91 insertions(+), 51 deletions(-) diff --git a/src/filesystem/File.js b/src/filesystem/File.js index a51b5121a71..76dba8cd0eb 100644 --- a/src/filesystem/File.js +++ b/src/filesystem/File.js @@ -38,6 +38,16 @@ define(function (require, exports, module) { File.prototype.constructor = File; File.prototype.parentClass = FileSystemEntry.prototype; + /** + * Contents of this file. + */ + File.prototype._contents = null; + + /** + * Override to return true. + * + * @return {boolean} True -- this is a file + */ File.prototype.isFile = function () { return true; }; @@ -53,14 +63,19 @@ define(function (require, exports, module) { File.prototype.readAsText = function (encoding) { var result = new $.Deferred(); - this._impl.readFile(this._path, encoding ? {encoding: encoding} : {}, function (err, data, stat) { - if (err) { - result.reject(err); - } else { - this._stat = stat; - result.resolve(data, stat); - } - }.bind(this)); + if (this._contents && this._stat) { + result.resolve(this._contents, this._stat); + } else { + this._impl.readFile(this._path, encoding ? {encoding: encoding} : {}, function (err, data, stat) { + if (err) { + result.reject(err); + } else { + this._stat = stat; + this._contents = data; + result.resolve(data, stat); + } + }.bind(this)); + } return result.promise(); }; diff --git a/src/filesystem/FileIndex.js b/src/filesystem/FileIndex.js index b02ce26aee0..6f01d484aab 100644 --- a/src/filesystem/FileIndex.js +++ b/src/filesystem/FileIndex.js @@ -31,6 +31,9 @@ define(function (require, exports, module) { // Master index var _index = {}; + // Array of all files in the index + var _allFiles = []; + /** * Clear the file index cache. */ @@ -45,6 +48,10 @@ define(function (require, exports, module) { */ function addEntry(entry) { _index[entry.getPath()] = entry; + + if (entry.isFile()) { + _allFiles.push(entry); + } } /** @@ -59,8 +66,13 @@ define(function (require, exports, module) { return _index[path]; } + function getAllFiles() { + return _allFiles; // TODO: Return a copy? + } + // Export public API exports.clear = clear; exports.addEntry = addEntry; exports.getEntry = getEntry; + exports.getAllFiles = getAllFiles; }); diff --git a/src/filesystem/FileSystem.js b/src/filesystem/FileSystem.js index c452c2dc2b5..81a6a73bd9e 100644 --- a/src/filesystem/FileSystem.js +++ b/src/filesystem/FileSystem.js @@ -134,6 +134,15 @@ define(function (require, exports, module) { return result.promise(); } + /** + * Return all indexed files + * + * @return {Array} Array containing all indexed files. + */ + function getFileList() { + return FileIndex.getAllFiles(); + } + /** * Show an "Open" dialog and return the file(s)/directories selected by the user. * @@ -196,6 +205,7 @@ define(function (require, exports, module) { exports.newUnsavedFile = newUnsavedFile; exports.getDirectoryForPath = getDirectoryForPath; exports.getDirectoryContents = getDirectoryContents; + exports.getFileList = getFileList; exports.showOpenDialog = showOpenDialog; exports.showSaveDialog = showSaveDialog; exports.setProjectRoot = setProjectRoot; diff --git a/src/project/ProjectManager.js b/src/project/ProjectManager.js index 0203b3eb7da..de76f4fb14c 100644 --- a/src/project/ProjectManager.js +++ b/src/project/ProjectManager.js @@ -68,7 +68,8 @@ define(function (require, exports, module) { NativeFileError = require("file/NativeFileError"), Urls = require("i18n!nls/urls"), KeyEvent = require("utils/KeyEvent"), - Async = require("utils/Async"); + Async = require("utils/Async"), + FileSystem = require("filesystem/FileSystem"); /** @@ -837,6 +838,8 @@ define(function (require, exports, module) { // close all the old files DocumentManager.closeAll(); + + FileSystem.setProjectRoot(rootPath); } // Clear project path map @@ -896,7 +899,7 @@ define(function (require, exports, module) { } }); resultRenderTree.fail(function () { - PerfUtils.terminateMeasurement(perfTimerName); +// PerfUtils.terminateMeasurement(perfTimerName); result.reject(); }); resultRenderTree.always(function () { diff --git a/src/search/FindInFiles.js b/src/search/FindInFiles.js index f287b636b50..8550ba30121 100644 --- a/src/search/FindInFiles.js +++ b/src/search/FindInFiles.js @@ -52,8 +52,8 @@ define(function (require, exports, module) { DocumentManager = require("document/DocumentManager"), EditorManager = require("editor/EditorManager"), PanelManager = require("view/PanelManager"), - FileIndexManager = require("project/FileIndexManager"), - FileUtils = require("file/FileUtils"), + FileSystem = require("filesystem/FileSystem"), + //FileUtils = require("file/FileUtils"), KeyEvent = require("utils/KeyEvent"), AppInit = require("utils/AppInit"), StatusBar = require("widgets/StatusBar"), @@ -390,18 +390,18 @@ define(function (require, exports, module) { } /** - * @param {!FileInfo} fileInfo File in question + * @param {!File} file File in question * @param {?Entry} scope Search scope, or null if whole project * @return {boolean} */ - function inScope(fileInfo, scope) { + function inScope(file, scope) { if (scope) { if (scope.isDirectory) { // Dirs always have trailing slash, so we don't have to worry about being // a substring of another dir name - return fileInfo.fullPath.indexOf(scope.fullPath) === 0; + return file.getPath().indexOf(scope.fullPath) === 0; } else { - return fileInfo.fullPath === scope.fullPath; + return file.getPath() === scope.fullPath; } } return true; @@ -434,44 +434,42 @@ define(function (require, exports, module) { return; } StatusBar.showBusyIndicator(true); - FileIndexManager.getFileInfoList("all") - .done(function (fileListResult) { - Async.doInParallel(fileListResult, function (fileInfo) { - var result = new $.Deferred(); - - if (!inScope(fileInfo, scope)) { - result.resolve(); - } else { - // Search one file - DocumentManager.getDocumentForPath(fileInfo.fullPath) - .done(function (doc) { - var matches = _getSearchMatches(doc.getText(), queryExpr); - - if (matches && matches.length) { - searchResults.push({ - fullPath: fileInfo.fullPath, - matches: matches - }); - } - result.resolve(); - }) - .fail(function (error) { - // Error reading this file. This is most likely because the file isn't a text file. - // Resolve here so we move on to the next file. - result.resolve(); + var fileList = FileSystem.getFileList(); + Async.doInParallel(fileList, function (file) { + var result = new $.Deferred(); + + if (!inScope(file, scope)) { + result.resolve(); + } else { + // Search one file + file.readAsText() + .done(function (contents) { + var matches = _getSearchMatches(contents, queryExpr); + + if (matches && matches.length) { + searchResults.push({ + fullPath: file.getPath(), + matches: matches }); - } - return result.promise(); - }) - .done(function () { - // Done searching all files: show results - _showSearchResults(searchResults, query, scope); - StatusBar.hideBusyIndicator(); + } + result.resolve(); }) - .fail(function () { - console.log("find in files failed."); - StatusBar.hideBusyIndicator(); + .fail(function (err) { + // Error reading this file. This is most likely because the file isn't a text file. + // Resolve here so we move on to the next file. + result.resolve(); }); + } + return result.promise(); + }) + .done(function () { + // Done searching all files: show results + _showSearchResults(searchResults, query, scope); + StatusBar.hideBusyIndicator(); + }) + .fail(function () { + console.log("find in files failed."); + StatusBar.hideBusyIndicator(); }); } }); @@ -494,6 +492,7 @@ define(function (require, exports, module) { } function _pathDeletedHandler(event, path) { + /* TODO: Handle path deleted if (searchResultsPanel.isVisible()) { // Update the search results searchResults.forEach(function (item, idx) { @@ -503,6 +502,7 @@ define(function (require, exports, module) { }); _showSearchResults(searchResults, currentQuery, currentScope); } + */ } From bf68b8688623aa990954090b9aee79e89f4e9695 Mon Sep 17 00:00:00 2001 From: Glenn Ruehle Date: Fri, 28 Jun 2013 17:03:38 -0700 Subject: [PATCH 009/163] Add file open dialogs and hook them up. --- src/document/DocumentCommandHandlers.js | 5 +- src/filesystem/FileIndex.js | 1 + src/filesystem/FileSystem.js | 99 +++++++++++-- src/filesystem/FileSystemImpl.js | 4 +- src/filesystem/impls/AppshellFileSystem.js | 15 +- src/filesystem/impls/DropboxFileSystem.js | 154 ++++++++++++++++++++ src/filesystem/impls/dropbox/dropbox.min.js | 5 + src/project/ProjectManager.js | 11 +- 8 files changed, 266 insertions(+), 28 deletions(-) create mode 100644 src/filesystem/impls/dropbox/dropbox.min.js diff --git a/src/document/DocumentCommandHandlers.js b/src/document/DocumentCommandHandlers.js index fd290651abb..ddb3f418151 100644 --- a/src/document/DocumentCommandHandlers.js +++ b/src/document/DocumentCommandHandlers.js @@ -34,6 +34,7 @@ define(function (require, exports, module) { var AppInit = require("utils/AppInit"), CommandManager = require("command/CommandManager"), Commands = require("command/Commands"), + FileSystem = require("filesystem/FileSystem"), NativeFileSystem = require("file/NativeFileSystem").NativeFileSystem, ProjectManager = require("project/ProjectManager"), DocumentManager = require("document/DocumentManager"), @@ -208,8 +209,8 @@ define(function (require, exports, module) { _defaultOpenDialogFullPath = ProjectManager.getProjectRoot().fullPath; } // Prompt the user with a dialog - NativeFileSystem.showOpenDialog(true, false, Strings.OPEN_FILE, _defaultOpenDialogFullPath, - null, function (paths) { + FileSystem.showOpenDialog(true, false, Strings.OPEN_FILE, _defaultOpenDialogFullPath, null) + .done(function (paths) { if (paths.length > 0) { // Add all files to the working set without verifying that // they still exist on disk (for faster opening) diff --git a/src/filesystem/FileIndex.js b/src/filesystem/FileIndex.js index 6f01d484aab..72c5018cbcd 100644 --- a/src/filesystem/FileIndex.js +++ b/src/filesystem/FileIndex.js @@ -39,6 +39,7 @@ define(function (require, exports, module) { */ function clear() { _index = {}; + _allFiles = []; } /** diff --git a/src/filesystem/FileSystem.js b/src/filesystem/FileSystem.js index 81a6a73bd9e..a2487e5b2a2 100644 --- a/src/filesystem/FileSystem.js +++ b/src/filesystem/FileSystem.js @@ -32,11 +32,35 @@ define(function (require, exports, module) { File = require("filesystem/File"), FileIndex = require("filesystem/FileIndex"); + var appshellFileSystem = require("filesystem/impls/AppshellFileSystem"), + dropboxFileSystem = require("filesystem/impls/DropboxFileSystem"); + // FileSystemImpl - var _impl = require("filesystem/impls/AppshellFileSystem"); // Temp - force appshell impl for now + var _impl; + /** + * Set the low-level implementation for file i/o + * + * @param {FileSystemImpl} impl File system implementation + */ function setFileSystemImpl(impl) { _impl = impl; + _impl.init(); + + FileIndex.clear(); + } + + /** + * Returns false for files and directories that are not commonly useful to display. + * + * @param {string} path File or directory to filter + * @return boolean true if the file should be displayed + */ + var _exclusionListRegEx = /\.pyc$|^\.git$|^\.gitignore$|^\.gitmodules$|^\.svn$|^\.DS_Store$|^Thumbs\.db$|^\.hg$|^CVS$|^\.cvsignore$|^\.gitattributes$|^\.hgtags$|^\.hgignore$/; + function shouldShow(path) { + var name = path.substr(path.lastIndexOf("/") + 1); + + return !name.match(_exclusionListRegEx); } /** @@ -116,13 +140,15 @@ define(function (require, exports, module) { for (i = 0; i < stats.length; i++) { entryPath = directory.getPath() + "/" + contents[i]; - if (stats[i].isFile()) { - entry = getFileForPath(entryPath); - } else { - entry = getDirectoryForPath(entryPath); + if (shouldShow(entryPath)) { + if (stats[i].isFile()) { + entry = getFileForPath(entryPath); + } else { + entry = getDirectoryForPath(entryPath); + } + + directory._contents.push(entry); } - - directory._contents.push(entry); } directory._contentsPromise = null; @@ -146,25 +172,62 @@ define(function (require, exports, module) { /** * Show an "Open" dialog and return the file(s)/directories selected by the user. * - * TODO: args. See NativeFileSystem.showOpenDialog for examples + * @param {boolean} allowMultipleSelection Allows selecting more than one file at a time + * @param {boolean} chooseDirectories Allows directories to be opened + * @param {string} title The title of the dialog + * @param {string} initialPath The folder opened inside the window initially. If initialPath + * is not set, or it doesn't exist, the window would show the last + * browsed folder depending on the OS preferences + * @param {Array.} fileTypes List of extensions that are allowed to be opened. A null value + * allows any extension to be selected. * * @return {$.Promise} Promise that will be resolved with the selected file(s)/directories, * or rejected if an error occurred. */ - function showOpenDialog(options) { - return _impl.showOpenDialog(options); + function showOpenDialog(allowMultipleSelection, + chooseDirectories, + title, + initialPath, + fileTypes) { + + var result = new $.Deferred(); + + _impl.showOpenDialog(allowMultipleSelection, chooseDirectories, title, initialPath, fileTypes, function (err, data) { + if (err) { + result.reject(err); + } else { + result.resolve(data); + } + }); + + return result.promise(); } /** * Show a "Save" dialog and return the path of the file to save. * - * TODO: args. See NativeFileSystem.showSaveDialog for examples + * @param {string} title The title of the dialog. + * @param {string} initialPath The folder opened inside the window initially. If initialPath + * is not set, or it doesn't exist, the window would show the last + * browsed folder depending on the OS preferences. + * @param {string} proposedNewFilename Provide a new file name for the user. This could be based on + * on the current file name plus an additional suffix * * @return {$.Promise} Promise that will be resolved with the name of the file to save, * or rejected if an error occurred. */ - function showSaveDialog(options) { - return _impl.showSaveDialog(options); + function showSaveDialog(title, initialPath, proposedNewFilename) { + var result = new $.Deferred(); + + _impl.showSaveDialog(title, initialPath, proposedNewFilename, function (err, selection) { + if (err) { + result.reject(err); + } else { + result.resolve(selection); + } + }); + + return result.promise(); } /** @@ -192,6 +255,12 @@ define(function (require, exports, module) { * @param {string} rootPath The new project root. */ function setProjectRoot(rootPath) { + if (rootPath && rootPath.length > 1) { + if (rootPath[rootPath.length - 1] === "/") { + rootPath = rootPath.substr(0, rootPath.length - 1); + } + } + // Clear file index FileIndex.clear(); @@ -199,8 +268,12 @@ define(function (require, exports, module) { _scanDirectory(rootPath); } + // Set initial file system + setFileSystemImpl(appshellFileSystem); + // Export public API exports.setFileSystemImpl = setFileSystemImpl; + exports.shouldShow = shouldShow; exports.getFileForPath = getFileForPath; exports.newUnsavedFile = newUnsavedFile; exports.getDirectoryForPath = getDirectoryForPath; diff --git a/src/filesystem/FileSystemImpl.js b/src/filesystem/FileSystemImpl.js index f5d4b0c5e5a..3820980b2c3 100644 --- a/src/filesystem/FileSystemImpl.js +++ b/src/filesystem/FileSystemImpl.js @@ -9,8 +9,8 @@ // } // // -// showOpenDialog(TODO: args) -// showSaveDialog(TODO: args) +// showOpenDialog(allowMultipleSelection, chooseDirectories, title, initialPath, fileTypes, function (err, data)) +// showSaveDialog((title, initialPath, proposedNewFilename, callback)) // [isNetworkDrive(path, callback)] // exists(path, callback) // readdir(path, callback) diff --git a/src/filesystem/impls/AppshellFileSystem.js b/src/filesystem/impls/AppshellFileSystem.js index cff54072378..d4ddaa417bf 100644 --- a/src/filesystem/impls/AppshellFileSystem.js +++ b/src/filesystem/impls/AppshellFileSystem.js @@ -28,12 +28,16 @@ define(function (require, exports, module) { "use strict"; - function showOpenDialog() { - // TODO + function init() { + // Nothing to do... } - function showSaveDialog() { - // TODO + function showOpenDialog(allowMultipleSelection, chooseDirectories, title, initialPath, fileTypes, callback) { + appshell.fs.showOpenDialog(allowMultipleSelection, chooseDirectories, title, initialPath, fileTypes, callback); + } + + function showSaveDialog(title, initialPath, proposedNewFilename, callback) { + appshell.fs.showSaveDialog(title, initialPath, proposedNewFilename, callback); } function stat(path, callback) { @@ -152,8 +156,9 @@ define(function (require, exports, module) { } // Export public API + exports.init = init; exports.showOpenDialog = showOpenDialog; - exports.showSavedialog = showSaveDialog; + exports.showSaveDialog = showSaveDialog; exports.isNetworkDrive = isNetworkDrive; exports.exists = exists; exports.readdir = readdir; diff --git a/src/filesystem/impls/DropboxFileSystem.js b/src/filesystem/impls/DropboxFileSystem.js index e69de29bb2d..f4af58b91b7 100644 --- a/src/filesystem/impls/DropboxFileSystem.js +++ b/src/filesystem/impls/DropboxFileSystem.js @@ -0,0 +1,154 @@ +/* + * 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, regexp: true, indent: 4, maxerr: 50 */ +/*global define, Dropbox */ + +define(function (require, exports, module) { + "use strict"; + + require("filesystem/impls/dropbox/dropbox.min.js"); + + var client; + + function init() { + if (!client) { + client = new Dropbox.Client({ + key: "sWR9wXcpXIA=|c5GZu+WL9XhxhReZMsg7QvspGpVZ80iF+Cin/xbKrQ==", + sandbox: false + }); + client.authDriver(new Dropbox.Drivers.Redirect({rememberUser: true})); + client.authenticate(function (err, client) { + // TODO: Handle errors + }); + } + } + + function showOpenDialog() { + // TODO + } + + function showSaveDialog() { + // TODO + } + + function _convertStat(stat) { + return { + isFile: function () { + return stat.isFile; + }, + isDirectory: function () { + return stat.isDirectory; + }, + mtime: stat && stat.modifiedAt + }; + } + + function stat(path, callback) { + client.stat(path, function (error, data) { + callback(error, _convertStat(data)); + }); + } + + function exists(path, callback) { + stat(path, function (err) { + if (err) { + callback(false); + } else { + callback(true); + } + }); + } + + function readdir(path, callback) { + var stats = []; + + client.readdir(path, function (err, contents, dirStat, stats) { + var i, convertedStats = []; + + if (!err) { + for (i = 0; i < stats.length; i++) { + convertedStats[i] = _convertStat(stats[i]); + } + } + + callback(err, contents, convertedStats); + }); + } + + function mkdir(path, mode, callback) { + if (typeof mode === "function") { + callback = mode; + } + client.mkdir(path, function (err, stat) { + callback(err, stat); + }); + } + + function rename(oldPath, newPath, callback) { + client.move(oldPath, newPath, callback); + } + + function readFile(path, options, callback) { + if (typeof options === "function") { + callback = options; + } + + client.readFile(path, function (err, data, stat) { + callback(err, data, stat); + }); + } + + function writeFile(path, data, options, callback) { + if (typeof options === "function") { + callback = options; + } + + client.writeFile(path, data, function (err, stat) { + callback(err, stat); + }); + } + + function chmod(path, mode, callback) { + // TODO: IMPLEMENT ME + } + + function unlink(path, callback) { + client.unlink(path, callback); + } + + // Export public API + exports.init = init; + exports.showOpenDialog = showOpenDialog; + exports.showSavedialog = showSaveDialog; + exports.exists = exists; + exports.readdir = readdir; + exports.mkdir = mkdir; + exports.rename = rename; + exports.stat = stat; + exports.readFile = readFile; + exports.writeFile = writeFile; + exports.chmod = chmod; + exports.unlink = unlink; +}); \ No newline at end of file diff --git a/src/filesystem/impls/dropbox/dropbox.min.js b/src/filesystem/impls/dropbox/dropbox.min.js new file mode 100644 index 00000000000..43c43a4fe4d --- /dev/null +++ b/src/filesystem/impls/dropbox/dropbox.min.js @@ -0,0 +1,5 @@ +(function(){var t,e,r,n,o,i,s,a,h,u,l,p,c,d,f,y,v,m,g,w,b,S,_,E,R,C,x,T,A,k,O,U=[].indexOf||function(t){for(var e=0,r=this.length;r>e;e++)if(e in this&&this[e]===t)return e;return-1},D={}.hasOwnProperty,P=function(t,e){function r(){this.constructor=t}for(var n in e)D.call(e,n)&&(t[n]=e[n]);return r.prototype=e.prototype,t.prototype=new r,t.__super__=e.prototype,t};if(t=function(){return null},t.Util={},t.EventSource=function(){function t(t){this._cancelable=t&&t.cancelable,this._listeners=[]}return t.prototype.addListener=function(t){if("function"!=typeof t)throw new TypeError("Invalid listener type; expected function");return 0>U.call(this._listeners,t)&&this._listeners.push(t),this},t.prototype.removeListener=function(t){var e,r,n,o,i,s;if(this._listeners.indexOf)r=this._listeners.indexOf(t),-1!==r&&this._listeners.splice(r,1);else for(s=this._listeners,e=o=0,i=s.length;i>o;e=++o)if(n=s[e],n===t){this._listeners.splice(e,1);break}return this},t.prototype.dispatch=function(t){var e,r,n,o,i;for(i=this._listeners,n=0,o=i.length;o>n;n++)if(e=i[n],r=e(t),this._cancelable&&r===!1)return!1;return!0},t}(),t.ApiError=function(){function t(t,e,r){var n,o;if(this.method=e,this.url=r,this.status=t.status,t.responseType)try{n=t.response||t.responseText}catch(i){o=i;try{n=t.responseText}catch(i){o=i,n=null}}else try{n=t.responseText}catch(i){o=i,n=null}if(n)try{this.responseText=""+n,this.response=JSON.parse(n)}catch(i){o=i,this.response=null}else this.responseText="(no response)",this.response=null}return t.prototype.status=void 0,t.prototype.method=void 0,t.prototype.url=void 0,t.prototype.responseText=void 0,t.prototype.response=void 0,t.NETWORK_ERROR=0,t.INVALID_PARAM=400,t.INVALID_TOKEN=401,t.OAUTH_ERROR=403,t.NOT_FOUND=404,t.INVALID_METHOD=405,t.RATE_LIMITED=503,t.OVER_QUOTA=507,t.prototype.toString=function(){return"Dropbox API error "+this.status+" from "+this.method+" "+this.url+" :: "+this.responseText},t.prototype.inspect=function(){return""+this},t}(),t.AuthDriver=function(){function e(){}return e.prototype.url=function(e){return"https://some.url?dboauth_token="+t.Xhr.urlEncode(e)},e.prototype.doAuthorize=function(t,e,r,n){return n("oauth-token")},e.prototype.onAuthStateChange=function(t,e){return e()},e}(),t.Drivers={},"function"==typeof d&&"function"==typeof w?(d=function(t){return window.atob(t)},w=function(t){return window.btoa(t)}):"undefined"==typeof window&&"undefined"==typeof self||"undefined"==typeof navigator||"string"!=typeof navigator.userAgent?(d=function(t){var e,r;return e=new Buffer(t,"base64"),function(){var t,n,o;for(o=[],r=t=0,n=e.length;n>=0?n>t:t>n;r=n>=0?++t:--t)o.push(String.fromCharCode(e[r]));return o}().join("")},w=function(t){var e,r;return e=new Buffer(function(){var e,n,o;for(o=[],r=e=0,n=t.length;n>=0?n>e:e>n;r=n>=0?++e:--e)o.push(t.charCodeAt(r));return o}()),e.toString("base64")}):(y="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",b=function(t,e,r){var n,o;for(o=3-e,t<<=8*o,n=3;n>=o;)r.push(y.charAt(63&t>>6*n)),n-=1;for(n=e;3>n;)r.push("="),n+=1;return null},f=function(t,e,r){var n,o;for(o=4-e,t<<=6*o,n=2;n>=o;)r.push(String.fromCharCode(255&t>>8*n)),n-=1;return null},w=function(t){var e,r,n,o,i,s;for(o=[],e=0,r=0,n=i=0,s=t.length;s>=0?s>i:i>s;n=s>=0?++i:--i)e=e<<8|t.charCodeAt(n),r+=1,3===r&&(b(e,r,o),e=r=0);return r>0&&b(e,r,o),o.join("")},d=function(t){var e,r,n,o,i,s,a;for(i=[],e=0,n=0,o=s=0,a=t.length;(a>=0?a>s:s>a)&&(r=t.charAt(o),"="!==r);o=a>=0?++s:--s)e=e<<6|y.indexOf(r),n+=1,4===n&&(f(e,n,i),e=n=0);return n>0&&f(e,n,i),i.join("")}),t.Util.atob=d,t.Util.btoa=w,t.Client=function(){function e(e){var r=this;this.sandbox=e.sandbox||!1,this.apiServer=e.server||this.defaultApiServer(),this.authServer=e.authServer||this.defaultAuthServer(),this.fileServer=e.fileServer||this.defaultFileServer(),this.downloadServer=e.downloadServer||this.defaultDownloadServer(),this.onXhr=new t.EventSource({cancelable:!0}),this.onError=new t.EventSource,this.onAuthStateChange=new t.EventSource,this.xhrOnErrorHandler=function(t,e){return r.handleXhrError(t,e)},this.oauth=new t.Oauth(e),this.driver=null,this.filter=null,this.uid=null,this.authState=null,this.authError=null,this._credentials=null,this.setCredentials(e),this.setupUrls()}return e.prototype.authDriver=function(t){return this.driver=t,this},e.prototype.onXhr=null,e.prototype.onError=null,e.prototype.onAuthStateChange=null,e.prototype.dropboxUid=function(){return this.uid},e.prototype.credentials=function(){return this._credentials||this.computeCredentials(),this._credentials},e.prototype.authenticate=function(t,e){var r,o,i,s=this;if(e||"function"!=typeof t||(e=t,t=null),r=t&&"interactive"in t?t.interactive:!0,!this.driver&&this.authState!==n.DONE)throw Error("Call authDriver to set an authentication driver");if(this.authState===n.ERROR)throw Error("Client got in an error state. Call reset() to reuse it!");return o=null,i=function(){var t;if(o!==s.authState&&(o=s.authState,s.driver&&s.driver.onAuthStateChange))return s.driver.onAuthStateChange(s,i),void 0;switch(s.authState){case n.RESET:return r?s.requestToken(function(t,e){var r,o;return t?(s.authError=t,s.authState=n.ERROR):(r=e.oauth_token,o=e.oauth_token_secret,s.oauth.setToken(r,o),s.authState=n.REQUEST),s._credentials=null,s.onAuthStateChange.dispatch(s),i()}):(e&&e(null,s),void 0);case n.REQUEST:return r?(t=s.authorizeUrl(s.oauth.token),s.driver.doAuthorize(t,s.oauth.token,s.oauth.tokenSecret,function(){return s.authState=n.AUTHORIZED,s._credentials=null,s.onAuthStateChange.dispatch(s),i()})):(e&&e(null,s),void 0);case n.AUTHORIZED:return s.getAccessToken(function(t,e){return t?(s.authError=t,s.authState=n.ERROR):(s.oauth.setToken(e.oauth_token,e.oauth_token_secret),s.uid=e.uid,s.authState=n.DONE),s._credentials=null,s.onAuthStateChange.dispatch(s),i()});case n.DONE:e&&e(null,s);break;case n.SIGNED_OFF:return s.authState=n.RESET,s.reset(),i();case n.ERROR:e&&e(s.authError,s)}},i(),this},e.prototype.isAuthenticated=function(){return this.authState===n.DONE},e.prototype.signOut=function(e){var r,o=this;return r=new t.Xhr("POST",this.urls.signOut),r.signWithOauth(this.oauth),this.dispatchXhr(r,function(t){return t?(e&&e(t),void 0):(o.authState=n.RESET,o.reset(),o.authState=n.SIGNED_OFF,o.onAuthStateChange.dispatch(o),o.driver&&o.driver.onAuthStateChange?o.driver.onAuthStateChange(o,function(){return e?e(t):void 0}):e?e(t):void 0)})},e.prototype.signOff=function(t){return this.signOut(t)},e.prototype.getUserInfo=function(e,r){var n,o;return r||"function"!=typeof e||(r=e,e=null),n=!1,e&&e.httpCache&&(n=!0),o=new t.Xhr("GET",this.urls.accountInfo),o.signWithOauth(this.oauth,n),this.dispatchXhr(o,function(e,n){return r(e,t.UserInfo.parse(n),n)})},e.prototype.readFile=function(e,r,n){var o,i,s,a,h,u,l;return n||"function"!=typeof r||(n=r,r=null),i={},u="text",a=null,o=!1,r&&(r.versionTag?i.rev=r.versionTag:r.rev&&(i.rev=r.rev),r.arrayBuffer?u="arraybuffer":r.blob?u="blob":r.buffer?u="buffer":r.binary&&(u="b"),r.length?(null!=r.start?(h=r.start,s=r.start+r.length-1):(h="",s=r.length),a="bytes="+h+"-"+s):null!=r.start&&(a="bytes="+r.start+"-"),r.httpCache&&(o=!0)),l=new t.Xhr("GET",""+this.urls.getFile+"/"+this.urlEncodePath(e)),l.setParams(i).signWithOauth(this.oauth,o),l.setResponseType(u),a&&(a&&l.setHeader("Range",a),l.reportResponseHeaders()),this.dispatchXhr(l,function(e,r,o,i){var s;return s=i?t.RangeInfo.parse(i["content-range"]):null,n(e,r,t.Stat.parse(o),s)})},e.prototype.writeFile=function(e,r,n,o){var i;return o||"function"!=typeof n||(o=n,n=null),i=t.Xhr.canSendForms&&"object"==typeof r,i?this.writeFileUsingForm(e,r,n,o):this.writeFileUsingPut(e,r,n,o)},e.prototype.writeFileUsingForm=function(e,r,n,o){var i,s,a,h;return a=e.lastIndexOf("/"),-1===a?(i=e,e=""):(i=e.substring(a),e=e.substring(0,a)),s={file:i},n&&(n.noOverwrite&&(s.overwrite="false"),n.lastVersionTag?s.parent_rev=n.lastVersionTag:(n.parentRev||n.parent_rev)&&(s.parent_rev=n.parentRev||n.parent_rev)),h=new t.Xhr("POST",""+this.urls.postFile+"/"+this.urlEncodePath(e)),h.setParams(s).signWithOauth(this.oauth).setFileField("file",i,r,"application/octet-stream"),delete s.file,this.dispatchXhr(h,function(e,r){return o?o(e,t.Stat.parse(r)):void 0})},e.prototype.writeFileUsingPut=function(e,r,n,o){var i,s;return i={},n&&(n.noOverwrite&&(i.overwrite="false"),n.lastVersionTag?i.parent_rev=n.lastVersionTag:(n.parentRev||n.parent_rev)&&(i.parent_rev=n.parentRev||n.parent_rev)),s=new t.Xhr("POST",""+this.urls.putFile+"/"+this.urlEncodePath(e)),s.setBody(r).setParams(i).signWithOauth(this.oauth),this.dispatchXhr(s,function(e,r){return o?o(e,t.Stat.parse(r)):void 0})},e.prototype.resumableUploadStep=function(e,r,n){var o,i;return r?(o={offset:r.offset},r.tag&&(o.upload_id=r.tag)):o={offset:0},i=new t.Xhr("POST",this.urls.chunkedUpload),i.setBody(e).setParams(o).signWithOauth(this.oauth),this.dispatchXhr(i,function(e,r){return e&&e.status===t.ApiError.INVALID_PARAM&&e.response&&e.response.upload_id&&e.response.offset?n(null,t.UploadCursor.parse(e.response)):n(e,t.UploadCursor.parse(r))})},e.prototype.resumableUploadFinish=function(e,r,n,o){var i,s;return o||"function"!=typeof n||(o=n,n=null),i={upload_id:r.tag},n&&(n.lastVersionTag?i.parent_rev=n.lastVersionTag:(n.parentRev||n.parent_rev)&&(i.parent_rev=n.parentRev||n.parent_rev),n.noOverwrite&&(i.overwrite="false")),s=new t.Xhr("POST",""+this.urls.commitChunkedUpload+"/"+this.urlEncodePath(e)),s.setParams(i).signWithOauth(this.oauth),this.dispatchXhr(s,function(e,r){return o?o(e,t.Stat.parse(r)):void 0})},e.prototype.stat=function(e,r,n){var o,i,s;return n||"function"!=typeof r||(n=r,r=null),i={},o=!1,r&&(null!=r.version&&(i.rev=r.version),(r.removed||r.deleted)&&(i.include_deleted="true"),r.readDir&&(i.list="true",r.readDir!==!0&&(i.file_limit=""+r.readDir)),r.cacheHash&&(i.hash=r.cacheHash),r.httpCache&&(o=!0)),i.include_deleted||(i.include_deleted="false"),i.list||(i.list="false"),s=new t.Xhr("GET",""+this.urls.metadata+"/"+this.urlEncodePath(e)),s.setParams(i).signWithOauth(this.oauth,o),this.dispatchXhr(s,function(e,r){var o,i,s;return s=t.Stat.parse(r),o=(null!=r?r.contents:void 0)?function(){var e,n,o,s;for(o=r.contents,s=[],e=0,n=o.length;n>e;e++)i=o[e],s.push(t.Stat.parse(i));return s}():void 0,n(e,s,o)})},e.prototype.readdir=function(t,e,r){var n;return r||"function"!=typeof e||(r=e,e=null),n={readDir:!0},e&&(null!=e.limit&&(n.readDir=e.limit),e.versionTag&&(n.versionTag=e.versionTag),(e.removed||e.deleted)&&(n.removed=e.removed||e.deleted),e.httpCache&&(n.httpCache=e.httpCache)),this.stat(t,n,function(t,e,n){var o,i;return o=n?function(){var t,e,r;for(r=[],t=0,e=n.length;e>t;t++)i=n[t],r.push(i.name);return r}():null,r(t,o,e,n)})},e.prototype.metadata=function(t,e,r){return this.stat(t,e,r)},e.prototype.makeUrl=function(e,r,n){var o,i,s,a,h,u=this;return n||"function"!=typeof r||(n=r,r=null),i=r&&(r["long"]||r.longUrl||r.downloadHack)?{short_url:"false"}:{},e=this.urlEncodePath(e),s=""+this.urls.shares+"/"+e,o=!1,a=!1,r&&(r.downloadHack?(o=!0,a=!0):r.download&&(o=!0,s=""+this.urls.media+"/"+e)),h=new t.Xhr("POST",s).setParams(i).signWithOauth(this.oauth),this.dispatchXhr(h,function(e,r){return a&&(null!=r?r.url:void 0)&&(r.url=r.url.replace(u.authServer,u.downloadServer)),n(e,t.PublicUrl.parse(r,o))})},e.prototype.history=function(e,r,n){var o,i,s;return n||"function"!=typeof r||(n=r,r=null),i={},o=!1,r&&(null!=r.limit&&(i.rev_limit=r.limit),r.httpCache&&(o=!0)),s=new t.Xhr("GET",""+this.urls.revisions+"/"+this.urlEncodePath(e)),s.setParams(i).signWithOauth(this.oauth,o),this.dispatchXhr(s,function(e,r){var o,i;return i=r?function(){var e,n,i;for(i=[],e=0,n=r.length;n>e;e++)o=r[e],i.push(t.Stat.parse(o));return i}():void 0,n(e,i)})},e.prototype.revisions=function(t,e,r){return this.history(t,e,r)},e.prototype.thumbnailUrl=function(t,e){var r;return r=this.thumbnailXhr(t,e),r.paramsToUrl().url},e.prototype.readThumbnail=function(e,r,n){var o,i;return n||"function"!=typeof r||(n=r,r=null),o="b",r&&(r.blob&&(o="blob"),r.arrayBuffer&&(o="arraybuffer"),r.buffer&&(o="buffer")),i=this.thumbnailXhr(e,r),i.setResponseType(o),this.dispatchXhr(i,function(e,r,o){return n(e,r,t.Stat.parse(o))})},e.prototype.thumbnailXhr=function(e,r){var n,o;return n={},r&&(r.format?n.format=r.format:r.png&&(n.format="png"),r.size&&(n.size=r.size)),o=new t.Xhr("GET",""+this.urls.thumbnails+"/"+this.urlEncodePath(e)),o.setParams(n).signWithOauth(this.oauth)},e.prototype.revertFile=function(e,r,n){var o;return o=new t.Xhr("POST",""+this.urls.restore+"/"+this.urlEncodePath(e)),o.setParams({rev:r}).signWithOauth(this.oauth),this.dispatchXhr(o,function(e,r){return n?n(e,t.Stat.parse(r)):void 0})},e.prototype.restore=function(t,e,r){return this.revertFile(t,e,r)},e.prototype.findByName=function(e,r,n,o){var i,s,a;return o||"function"!=typeof n||(o=n,n=null),s={query:r},i=!1,n&&(null!=n.limit&&(s.file_limit=n.limit),(n.removed||n.deleted)&&(s.include_deleted=!0),n.httpCache&&(i=!0)),a=new t.Xhr("GET",""+this.urls.search+"/"+this.urlEncodePath(e)),a.setParams(s).signWithOauth(this.oauth,i),this.dispatchXhr(a,function(e,r){var n,i;return i=r?function(){var e,o,i;for(i=[],e=0,o=r.length;o>e;e++)n=r[e],i.push(t.Stat.parse(n));return i}():void 0,o(e,i)})},e.prototype.search=function(t,e,r,n){return this.findByName(t,e,r,n)},e.prototype.makeCopyReference=function(e,r){var n;return n=new t.Xhr("GET",""+this.urls.copyRef+"/"+this.urlEncodePath(e)),n.signWithOauth(this.oauth),this.dispatchXhr(n,function(e,n){return r(e,t.CopyReference.parse(n))})},e.prototype.copyRef=function(t,e){return this.makeCopyReference(t,e)},e.prototype.pullChanges=function(e,r){var n,o;return r||"function"!=typeof e||(r=e,e=null),n=e?e.cursorTag?{cursor:e.cursorTag}:{cursor:e}:{},o=new t.Xhr("POST",this.urls.delta),o.setParams(n).signWithOauth(this.oauth),this.dispatchXhr(o,function(e,n){return r(e,t.PulledChanges.parse(n))})},e.prototype.delta=function(t,e){return this.pullChanges(t,e)},e.prototype.mkdir=function(e,r){var n;return n=new t.Xhr("POST",this.urls.fileopsCreateFolder),n.setParams({root:this.fileRoot,path:this.normalizePath(e)}).signWithOauth(this.oauth),this.dispatchXhr(n,function(e,n){return r?r(e,t.Stat.parse(n)):void 0})},e.prototype.remove=function(e,r){var n;return n=new t.Xhr("POST",this.urls.fileopsDelete),n.setParams({root:this.fileRoot,path:this.normalizePath(e)}).signWithOauth(this.oauth),this.dispatchXhr(n,function(e,n){return r?r(e,t.Stat.parse(n)):void 0})},e.prototype.unlink=function(t,e){return this.remove(t,e)},e.prototype["delete"]=function(t,e){return this.remove(t,e)},e.prototype.copy=function(e,r,n){var o,i,s;return n||"function"!=typeof o||(n=o,o=null),i={root:this.fileRoot,to_path:this.normalizePath(r)},e instanceof t.CopyReference?i.from_copy_ref=e.tag:i.from_path=this.normalizePath(e),s=new t.Xhr("POST",this.urls.fileopsCopy),s.setParams(i).signWithOauth(this.oauth),this.dispatchXhr(s,function(e,r){return n?n(e,t.Stat.parse(r)):void 0})},e.prototype.move=function(e,r,n){var o,i;return n||"function"!=typeof o||(n=o,o=null),i=new t.Xhr("POST",this.urls.fileopsMove),i.setParams({root:this.fileRoot,from_path:this.normalizePath(e),to_path:this.normalizePath(r)}).signWithOauth(this.oauth),this.dispatchXhr(i,function(e,r){return n?n(e,t.Stat.parse(r)):void 0})},e.prototype.reset=function(){var t;return this.uid=null,this.oauth.setToken(null,""),t=this.authState,this.authState=n.RESET,t!==this.authState&&this.onAuthStateChange.dispatch(this),this.authError=null,this._credentials=null,this},e.prototype.setCredentials=function(t){var e;return e=this.authState,this.oauth.reset(t),this.uid=t.uid||null,this.authState=t.authState?t.authState:t.token?n.DONE:n.RESET,this.authError=null,this._credentials=null,e!==this.authState&&this.onAuthStateChange.dispatch(this),this},e.prototype.appHash=function(){return this.oauth.appHash()},e.prototype.setupUrls=function(){return this.fileRoot=this.sandbox?"sandbox":"dropbox",this.urls={requestToken:""+this.apiServer+"/1/oauth/request_token",authorize:""+this.authServer+"/1/oauth/authorize",accessToken:""+this.apiServer+"/1/oauth/access_token",signOut:""+this.apiServer+"/1/unlink_access_token",accountInfo:""+this.apiServer+"/1/account/info",getFile:""+this.fileServer+"/1/files/"+this.fileRoot,postFile:""+this.fileServer+"/1/files/"+this.fileRoot,putFile:""+this.fileServer+"/1/files_put/"+this.fileRoot,metadata:""+this.apiServer+"/1/metadata/"+this.fileRoot,delta:""+this.apiServer+"/1/delta",revisions:""+this.apiServer+"/1/revisions/"+this.fileRoot,restore:""+this.apiServer+"/1/restore/"+this.fileRoot,search:""+this.apiServer+"/1/search/"+this.fileRoot,shares:""+this.apiServer+"/1/shares/"+this.fileRoot,media:""+this.apiServer+"/1/media/"+this.fileRoot,copyRef:""+this.apiServer+"/1/copy_ref/"+this.fileRoot,thumbnails:""+this.fileServer+"/1/thumbnails/"+this.fileRoot,chunkedUpload:""+this.fileServer+"/1/chunked_upload",commitChunkedUpload:""+this.fileServer+"/1/commit_chunked_upload/"+this.fileRoot,fileopsCopy:""+this.apiServer+"/1/fileops/copy",fileopsCreateFolder:""+this.apiServer+"/1/fileops/create_folder",fileopsDelete:""+this.apiServer+"/1/fileops/delete",fileopsMove:""+this.apiServer+"/1/fileops/move"}},e.prototype.authState=null,e.ERROR=0,e.RESET=1,e.REQUEST=2,e.AUTHORIZED=3,e.DONE=4,e.SIGNED_OFF=5,e.prototype.urlEncodePath=function(e){return t.Xhr.urlEncodeValue(this.normalizePath(e)).replace(/%2F/gi,"/")},e.prototype.normalizePath=function(t){var e;if("/"===t.substring(0,1)){for(e=1;"/"===t.substring(e,e+1);)e+=1;return t.substring(e)}return t},e.prototype.requestToken=function(e){var r;return r=new t.Xhr("POST",this.urls.requestToken).signWithOauth(this.oauth),this.dispatchXhr(r,e)},e.prototype.authorizeUrl=function(e){var r,n;return r=this.driver.url(e),n=null===r?{oauth_token:e}:{oauth_token:e,oauth_callback:r},""+this.urls.authorize+"?"+t.Xhr.urlEncode(n)},e.prototype.getAccessToken=function(e){var r;return r=new t.Xhr("POST",this.urls.accessToken).signWithOauth(this.oauth),this.dispatchXhr(r,e)},e.prototype.dispatchXhr=function(t,e){var r;return t.setCallback(e),t.onError=this.xhrOnErrorHandler,t.prepare(),r=t.xhr,this.onXhr.dispatch(t)&&t.send(),r},e.prototype.handleXhrError=function(e,r){var o=this;return e.status===t.ApiError.INVALID_TOKEN&&this.authState===n.DONE&&(this.authError=e,this.authState=n.ERROR,this.onAuthStateChange.dispatch(this),this.driver&&this.driver.onAuthStateChange)?(this.driver.onAuthStateChange(this,function(){return o.onError.dispatch(e),r(e)}),null):(this.onError.dispatch(e),r(e),null)},e.prototype.defaultApiServer=function(){return"https://api.dropbox.com"},e.prototype.defaultAuthServer=function(){return this.apiServer.replace("api.","www.")},e.prototype.defaultFileServer=function(){return this.apiServer.replace("api.","api-content.")},e.prototype.defaultDownloadServer=function(){return this.apiServer.replace("api.","dl.")},e.prototype.computeCredentials=function(){var t;return t={key:this.oauth.key,sandbox:this.sandbox},this.oauth.secret&&(t.secret=this.oauth.secret),this.oauth.token&&(t.token=this.oauth.token,t.tokenSecret=this.oauth.tokenSecret),this.uid&&(t.uid=this.uid),this.authState!==n.ERROR&&this.authState!==n.RESET&&this.authState!==n.DONE&&this.authState!==n.SIGNED_OFF&&(t.authState=this.authState),this.apiServer!==this.defaultApiServer()&&(t.server=this.apiServer),this.authServer!==this.defaultAuthServer()&&(t.authServer=this.authServer),this.fileServer!==this.defaultFileServer()&&(t.fileServer=this.fileServer),this.downloadServer!==this.defaultDownloadServer()&&(t.downloadServer=this.downloadServer),this._credentials=t},e}(),n=t.Client,t.Drivers.BrowserBase=function(){function e(t){this.rememberUser=(null!=t?t.rememberUser:void 0)||!1,this.useQuery=(null!=t?t.useQuery:void 0)||!1,this.scope=(null!=t?t.scope:void 0)||"default",this.storageKey=null,this.dbTokenRe=RegExp("(#|\\?|&)dboauth_token=([^&#]+)(&|#|$)"),this.rejectedRe=RegExp("(#|\\?|&)not_approved=true(&|#|$)"),this.tokenRe=RegExp("(#|\\?|&)oauth_token=([^&#]+)(&|#|$)")}return e.prototype.onAuthStateChange=function(t,e){var r=this;switch(this.setStorageKey(t),t.authState){case n.RESET:return this.loadCredentials(function(n){return n?n.authState?(t.setCredentials(n),e()):r.rememberUser?(t.setCredentials(n),t.getUserInfo(function(n){return n?(t.reset(),r.forgetCredentials(e)):e()})):r.forgetCredentials(e):e()});case n.REQUEST:return this.storeCredentials(t.credentials(),e);case n.DONE:return this.rememberUser?this.storeCredentials(t.credentials(),e):this.forgetCredentials(e);case n.SIGNED_OFF:return this.forgetCredentials(e);case n.ERROR:return this.forgetCredentials(e);default:return e(),this}},e.prototype.setStorageKey=function(t){return this.storageKey="dropbox-auth:"+this.scope+":"+t.appHash(),this},e.prototype.storeCredentials=function(t,e){return localStorage.setItem(this.storageKey,JSON.stringify(t)),e(),this},e.prototype.loadCredentials=function(t){var e,r;if(r=localStorage.getItem(this.storageKey),!r)return t(null),this;try{t(JSON.parse(r))}catch(n){e=n,t(null)}return this},e.prototype.forgetCredentials=function(t){return localStorage.removeItem(this.storageKey),t(),this},e.prototype.computeUrl=function(t){var e,r,n,o;return o="_dropboxjs_scope="+encodeURIComponent(this.scope)+"&dboauth_token=",r=t,-1===r.indexOf("#")?e=null:(n=r.split("#",2),r=n[0],e=n[1]),this.useQuery?(r+=-1===r.indexOf("?")?"?"+o:"&"+o,e?[r,"#"+e]:[r,""]):[r+"#?"+o,""]},e.prototype.locationToken=function(e){var r,n,o;return r=e||t.Drivers.BrowserBase.currentLocation(),o="_dropboxjs_scope="+encodeURIComponent(this.scope)+"&",-1===("function"==typeof r.indexOf?r.indexOf(o):void 0)?null:this.rejectedRe.test(r)?(n=this.dbTokenRe.exec(r),n?decodeURIComponent(n[2]):null):(n=this.tokenRe.exec(r),n?decodeURIComponent(n[2]):null)},e.currentLocation=function(){return window.location.href},e}(),t.Drivers.Redirect=function(e){function r(e){var n;r.__super__.constructor.call(this,e),n=this.computeUrl(t.Drivers.BrowserBase.currentLocation()),this.receiverUrl1=n[0],this.receiverUrl2=n[1]}return P(r,e),r.prototype.onAuthStateChange=function(t,e){var o,i=this;return o=function(){return function(){return r.__super__.onAuthStateChange.call(i,t,e)}}(),this.setStorageKey(t),t.authState===n.RESET?this.loadCredentials(function(t){return t&&t.authState?t.token===i.locationToken()&&t.authState===n.REQUEST?(t.authState=n.AUTHORIZED,i.storeCredentials(t,o)):i.forgetCredentials(o):o()}):o()},r.prototype.url=function(t){return this.receiverUrl1+encodeURIComponent(t)+this.receiverUrl2},r.prototype.doAuthorize=function(t){return window.location.assign(t)},r}(t.Drivers.BrowserBase),t.Drivers.Popup=function(e){function r(t){var e;r.__super__.constructor.call(this,t),e=this.computeUrl(this.baseUrl(t)),this.receiverUrl1=e[0],this.receiverUrl2=e[1]}return P(r,e),r.prototype.onAuthStateChange=function(t,e){var o,i=this;return o=function(){return function(){return r.__super__.onAuthStateChange.call(i,t,e)}}(),this.setStorageKey(t),t.authState===n.RESET?this.loadCredentials(function(t){return t&&t.authState?i.forgetCredentials(o):o()}):o()},r.prototype.doAuthorize=function(t,e,r,n){return this.listenForMessage(e,n),this.openWindow(t)},r.prototype.url=function(t){return this.receiverUrl1+encodeURIComponent(t)+this.receiverUrl2},r.prototype.baseUrl=function(e){var r;if(e){if(e.receiverUrl)return e.receiverUrl;if(e.receiverFile)return r=t.Drivers.BrowserBase.currentLocation().split("/"),r[r.length-1]=e.receiverFile,r.join("/")}return t.Drivers.BrowserBase.currentLocation()},r.prototype.openWindow=function(t){return window.open(t,"_dropboxOauthSigninWindow",this.popupWindowSpec(980,700))},r.prototype.popupWindowSpec=function(t,e){var r,n,o,i,s,a,h,u,l,p;return s=null!=(h=window.screenX)?h:window.screenLeft,a=null!=(u=window.screenY)?u:window.screenTop,i=null!=(l=window.outerWidth)?l:document.documentElement.clientWidth,r=null!=(p=window.outerHeight)?p:document.documentElement.clientHeight,n=Math.round(s+(i-t)/2),o=Math.round(a+(r-e)/2.5),s>n&&(n=s),a>o&&(o=a),"width="+t+",height="+e+","+("left="+n+",top="+o)+"dialog=yes,dependent=yes,scrollbars=yes,location=yes"},r.prototype.listenForMessage=function(e,r){var n,o=this;return n=function(i){var s;return s=i.data?i.data:i,o.locationToken(s)===e?(e=null,window.removeEventListener("message",n),t.Drivers.Popup.onMessage.removeListener(n),r()):void 0},window.addEventListener("message",n,!1),t.Drivers.Popup.onMessage.addListener(n)},r.oauthReceiver=function(){return window.addEventListener("load",function(){var t,e,r;if(r=window.opener,window.parent!==window.top&&(r||(r=window.parent)),r){try{r.postMessage(window.location.href,"*")}catch(n){e=n}try{r.Dropbox.Drivers.Popup.onMessage.dispatch(window.location.href)}catch(n){t=n}}return window.close()})},r.onMessage=new t.EventSource,r}(t.Drivers.BrowserBase),e=null,r=null,"undefined"!=typeof chrome&&null!==chrome&&(chrome.runtime&&(chrome.runtime.onMessage&&(e=chrome.runtime.onMessage),chrome.runtime.sendMessage&&(r=function(t){return chrome.runtime.sendMessage(t)})),chrome.extension&&(chrome.extension.onMessage&&(e||(e=chrome.extension.onMessage)),chrome.extension.sendMessage&&(r||(r=function(t){return chrome.extension.sendMessage(t)}))),e||function(){var e,r;return r=function(e){return e.Dropbox?(t.Drivers.Chrome.prototype.onMessage=e.Dropbox.Drivers.Chrome.onMessage,t.Drivers.Chrome.prototype.sendMessage=e.Dropbox.Drivers.Chrome.sendMessage):(e.Dropbox=t,t.Drivers.Chrome.prototype.onMessage=new t.EventSource,t.Drivers.Chrome.prototype.sendMessage=function(e){return t.Drivers.Chrome.prototype.onMessage.dispatch(e)})},chrome.extension&&chrome.extension.getBackgroundPage&&(e=chrome.extension.getBackgroundPage())?r(e):chrome.runtime&&chrome.runtime.getBackgroundPage?chrome.runtime.getBackgroundPage(function(t){return r(t)}):void 0}()),t.Drivers.Chrome=function(n){function o(t){var e,r;o.__super__.constructor.call(this,t),e=t&&t.receiverPath||"chrome_oauth_receiver.html",this.rememberUser=!0,this.useQuery=!0,r=this.computeUrl(this.expandUrl(e)),this.receiverUrl=r[0],this.receiverUrl2=r[1],this.storageKey="dropbox_js_"+this.scope+"_credentials"}return P(o,n),o.prototype.onMessage=e,o.prototype.sendMessage=r,o.prototype.expandUrl=function(t){return chrome.runtime&&chrome.runtime.getURL?chrome.runtime.getURL(t):chrome.extension&&chrome.extension.getURL?chrome.extension.getURL(t):t},o.prototype.onAuthStateChange=function(e,r){var n=this;switch(e.authState){case t.Client.RESET:return this.loadCredentials(function(t){if(t){if(t.authState)return n.forgetCredentials(r);e.setCredentials(t)}return r()});case t.Client.DONE:return this.storeCredentials(e.credentials(),r);case t.Client.SIGNED_OFF:return this.forgetCredentials(r);case t.Client.ERROR:return this.forgetCredentials(r);default:return r()}},o.prototype.doAuthorize=function(t,e,r,n){var o,i,s,a,h=this;return(null!=(i=chrome.identity)?i.launchWebAuthFlow:void 0)?chrome.identity.launchWebAuthFlow({url:t,interactive:!0},function(t){return h.locationToken(t)===e?n():void 0}):(null!=(s=chrome.experimental)?null!=(a=s.identity)?a.launchWebAuthFlow:void 0:void 0)?chrome.experimental.identity.launchWebAuthFlow({url:t,interactive:!0},function(t){return h.locationToken(t)===e?n():void 0}):(o={handle:null},this.listenForMessage(e,o,n),this.openWindow(t,function(t){return o.handle=t}))},o.prototype.openWindow=function(t,e){return chrome.tabs&&chrome.tabs.create?(chrome.tabs.create({url:t,active:!0,pinned:!1},function(t){return e(t)}),this):this},o.prototype.closeWindow=function(t){return chrome.tabs&&chrome.tabs.remove&&t.id?(chrome.tabs.remove(t.id),this):chrome.app&&chrome.app.window&&t.close?(t.close(),this):this},o.prototype.url=function(t){return this.receiverUrl+encodeURIComponent(t)},o.prototype.listenForMessage=function(t,e,r){var n,o=this;return n=function(i,s){return s&&s.tab&&s.tab.url.substring(0,o.receiverUrl.length)!==o.receiverUrl||!i.dropbox_oauth_receiver_href?void 0:o.locationToken(i.dropbox_oauth_receiver_href)===t?(e.handle&&o.closeWindow(e.handle),o.onMessage.removeListener(n),r()):void 0},this.onMessage.addListener(n)},o.prototype.storeCredentials=function(t,e){var r;return r={},r[this.storageKey]=t,chrome.storage.local.set(r,e),this},o.prototype.loadCredentials=function(t){var e=this;return chrome.storage.local.get(this.storageKey,function(r){return t(r[e.storageKey]||null)}),this},o.prototype.forgetCredentials=function(t){return chrome.storage.local.remove(this.storageKey,t),this},o.oauthReceiver=function(){return window.addEventListener("load",function(){var e;return e=new t.Drivers.Chrome,e.sendMessage({dropbox_oauth_receiver_href:window.location.href}),window.close?window.close():void 0})},o}(t.Drivers.BrowserBase),t.Drivers.Cordova=function(t){function e(t){this.rememberUser=(null!=t?t.rememberUser:void 0)||!1,this.scope=(null!=t?t.scope:void 0)||"default"}return P(e,t),e.prototype.doAuthorize=function(t,e,r,n){var o,i,s,a;return i=window.open(t,"_blank","location=yes"),a=!1,o=/^[^/]*\/\/[^/]*\//.exec(t)[0],s=function(e){return e.url===t&&a===!1?(a=!0,void 0):e.url&&e.url.substring(0,o.length)!==o?(a=!1,void 0):"exit"===e.type||a?(i.removeEventListener("loadstop",s),i.removeEventListener("exit",s),"exit"!==e.type&&i.close(),n()):void 0},i.addEventListener("loadstop",s),i.addEventListener("exit",s)},e.prototype.url=function(){return null},e.prototype.onAuthStateChange=function(t,r){var o,i=this;return o=function(){return function(){return e.__super__.onAuthStateChange.call(i,t,r)}}(),this.setStorageKey(t),t.authState===n.RESET?this.loadCredentials(function(t){return t&&t.authState?i.forgetCredentials(o):o()}):o()},e}(t.Drivers.BrowserBase),t.Drivers.NodeServer=function(){function t(t){this.port=(null!=t?t.port:void 0)||8912,this.faviconFile=(null!=t?t.favicon:void 0)||null,this.fs=require("fs"),this.http=require("http"),this.open=require("open"),this.callbacks={},this.nodeUrl=require("url"),this.createApp()}return t.prototype.url=function(t){return"http://localhost:"+this.port+"/oauth_callback?dboauth_token="+encodeURIComponent(t)},t.prototype.doAuthorize=function(t,e,r,n){return this.callbacks[e]=n,this.openBrowser(t)},t.prototype.openBrowser=function(t){if(!t.match(/^https?:\/\//))throw Error("Not a http/https URL: "+t);return"BROWSER"in process.env?this.open(t,process.env.BROWSER):this.open(t)},t.prototype.createApp=function(){var t=this;return this.app=this.http.createServer(function(e,r){return t.doRequest(e,r)}),this.app.listen(this.port)},t.prototype.closeServer=function(){return this.app.close()},t.prototype.doRequest=function(t,e){var r,n,o,i,s=this;return i=this.nodeUrl.parse(t.url,!0),"/oauth_callback"===i.pathname&&("true"===i.query.not_approved?(n=!0,o=i.query.dboauth_token):(n=!1,o=i.query.oauth_token),this.callbacks[o]&&(this.callbacks[o](n),delete this.callbacks[o])),r="",t.on("data",function(t){return r+=t}),t.on("end",function(){return s.faviconFile&&"/favicon.ico"===i.pathname?s.sendFavicon(e):s.closeBrowser(e)})},t.prototype.closeBrowser=function(t){var e;return e='\n\n

Please close this window.

',t.writeHead(200,{"Content-Length":e.length,"Content-Type":"text/html"}),t.write(e),t.end()},t.prototype.sendFavicon=function(t){return this.fs.readFile(this.faviconFile,function(e,r){return t.writeHead(200,{"Content-Length":r.length,"Content-Type":"image/x-icon"}),t.write(r),t.end()})},t}(),v=function(t,e){return c(E(A(t),A(e),t.length,e.length))},m=function(t){return c(T(A(t),t.length))},"undefined"!=typeof require)try{S=require("crypto"),S.createHmac&&S.createHash&&(v=function(t,e){var r;return r=S.createHmac("sha1",e),r.update(t),r.digest("base64")},m=function(t){var e;return e=S.createHash("sha1"),e.update(t),e.digest("base64")})}catch(X){C=X}if(t.Util.hmac=v,t.Util.sha1=m,E=function(t,e,r,n){var o,i,s,a;return e.length>16&&(e=T(e,n)),s=function(){var t,r;for(r=[],i=t=0;16>t;i=++t)r.push(909522486^e[i]);return r}(),a=function(){var t,r;for(r=[],i=t=0;16>t;i=++t)r.push(1549556828^e[i]); +return r}(),o=T(s.concat(t),64+r),T(a.concat(o),84)},T=function(t,e){var r,n,o,i,s,a,h,u,l,c,d,f,y,v,m,g,w,b;for(t[e>>2]|=1<<31-((3&e)<<3),t[(e+8>>6<<4)+15]=e<<3,g=Array(80),r=1732584193,o=-271733879,s=-1732584194,h=271733878,l=-1009589776,f=0,m=t.length;m>f;){for(n=r,i=o,a=s,u=h,c=l,y=b=0;80>b;y=++b)g[y]=16>y?t[f+y]:x(g[y-3]^g[y-8]^g[y-14]^g[y-16],1),20>y?(d=o&s|~o&h,v=1518500249):40>y?(d=o^s^h,v=1859775393):60>y?(d=o&s|o&h|s&h,v=-1894007588):(d=o^s^h,v=-899497514),w=p(p(x(r,5),d),p(p(l,g[y]),v)),l=h,h=s,s=x(o,30),o=r,r=w;r=p(r,n),o=p(o,i),s=p(s,a),h=p(h,u),l=p(l,c),f+=16}return[r,o,s,h,l]},x=function(t,e){return t<>>32-e},p=function(t,e){var r,n;return n=(65535&t)+(65535&e),r=(t>>16)+(e>>16)+(n>>16),r<<16|65535&n},c=function(t){var e,r,n,o,i;for(o="",e=0,n=4*t.length;n>e;)r=e,i=(255&t[r>>2]>>(3-(3&r)<<3))<<16,r+=1,i|=(255&t[r>>2]>>(3-(3&r)<<3))<<8,r+=1,i|=255&t[r>>2]>>(3-(3&r)<<3),o+=O[63&i>>18],o+=O[63&i>>12],e+=1,o+=e>=n?"=":O[63&i>>6],e+=1,o+=e>=n?"=":O[63&i],e+=1;return o},O="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",A=function(t){var e,r,n,o,i;for(e=[],n=255,r=o=0,i=t.length;i>=0?i>o:o>i;r=i>=0?++o:--o)e[r>>2]|=(t.charCodeAt(r)&n)<<(3-(3&r)<<3);return e},t.Oauth=function(){function e(t){this.key=this.k=null,this.secret=this.s=null,this.token=null,this.tokenSecret=null,this._appHash=null,this.reset(t)}return e.prototype.reset=function(t){var e,r,n,o;if(t.secret)this.k=this.key=t.key,this.s=this.secret=t.secret,this._appHash=null;else if(t.key)this.key=t.key,this.secret=null,n=d(_(this.key).split("|",2)[1]),o=n.split("?",2),e=o[0],r=o[1],this.k=decodeURIComponent(e),this.s=decodeURIComponent(r),this._appHash=null;else if(!this.k)throw Error("No API key supplied");return t.token?this.setToken(t.token,t.tokenSecret):this.setToken(null,"")},e.prototype.setToken=function(e,r){if(e&&!r)throw Error("No secret supplied with the user token");return this.token=e,this.tokenSecret=r||"",this.hmacKey=t.Xhr.urlEncodeValue(this.s)+"&"+t.Xhr.urlEncodeValue(r),null},e.prototype.authHeader=function(e,r,n){var o,i,s,a,h,u;this.addAuthParams(e,r,n),i=[];for(s in n)a=n[s],"oauth_"===s.substring(0,6)&&i.push(s);for(i.sort(),o=[],h=0,u=i.length;u>h;h++)s=i[h],o.push(t.Xhr.urlEncodeValue(s)+'="'+t.Xhr.urlEncodeValue(n[s])+'"'),delete n[s];return"OAuth "+o.join(",")},e.prototype.addAuthParams=function(t,e,r){return this.boilerplateParams(r),r.oauth_signature=this.signature(t,e,r),r},e.prototype.boilerplateParams=function(e){return e.oauth_consumer_key=this.k,e.oauth_nonce=t.Oauth.nonce(),e.oauth_signature_method="HMAC-SHA1",this.token&&(e.oauth_token=this.token),e.oauth_timestamp=Math.floor(Date.now()/1e3),e.oauth_version="1.0",e},e.nonce=function(){return Math.random().toString(36)},e.prototype.signature=function(e,r,n){var o;return o=e.toUpperCase()+"&"+t.Xhr.urlEncodeValue(r)+"&"+t.Xhr.urlEncodeValue(t.Xhr.urlEncode(n)),v(o,this.hmacKey)},e.prototype.appHash=function(){return this._appHash?this._appHash:this._appHash=m(this.k).replace(/\=/g,"")},e}(),null==Date.now&&(Date.now=function(){return(new Date).getTime()}),_=function(t,e){var r,n,o,i,s,a,h,u,l,p,c,f;for(e?(e=[encodeURIComponent(t),encodeURIComponent(e)].join("?"),t=function(){var e,n,o;for(o=[],r=e=0,n=t.length/2;n>=0?n>e:e>n;r=n>=0?++e:--e)o.push(16*(15&t.charCodeAt(2*r))+(15&t.charCodeAt(2*r+1)));return o}()):(p=t.split("|",2),t=p[0],e=p[1],t=d(t),t=function(){var e,n,o;for(o=[],r=e=0,n=t.length;n>=0?n>e:e>n;r=n>=0?++e:--e)o.push(t.charCodeAt(r));return o}(),e=d(e)),i=function(){for(f=[],u=0;256>u;u++)f.push(u);return f}.apply(this),a=0,s=l=0;256>l;s=++l)a=(a+i[r]+t[s%t.length])%256,c=[i[a],i[s]],i[s]=c[0],i[a]=c[1];return s=a=0,o=function(){var t,r,o,u;for(u=[],h=t=0,r=e.length;r>=0?r>t:t>r;h=r>=0?++t:--t)s=(s+1)%256,a=(a+i[s])%256,o=[i[a],i[s]],i[s]=o[0],i[a]=o[1],n=i[(i[s]+i[a])%256],u.push(String.fromCharCode((n^e.charCodeAt(h))%256));return u}(),t=function(){var e,n,o;for(o=[],r=e=0,n=t.length;n>=0?n>e:e>n;r=n>=0?++e:--e)o.push(String.fromCharCode(t[r]));return o}(),[w(t.join("")),w(o.join(""))].join("|")},t.Util.encodeKey=_,t.PulledChanges=function(){function e(e){var r;this.blankSlate=e.reset||!1,this.cursorTag=e.cursor,this.shouldPullAgain=e.has_more,this.shouldBackOff=!this.shouldPullAgain,this.changes=e.cursor&&e.cursor.length?function(){var n,o,i,s;for(i=e.entries,s=[],n=0,o=i.length;o>n;n++)r=i[n],s.push(t.PullChange.parse(r));return s}():[]}return e.parse=function(e){return e&&"object"==typeof e?new t.PulledChanges(e):e},e.prototype.blankSlate=void 0,e.prototype.cursorTag=void 0,e.prototype.changes=void 0,e.prototype.shouldPullAgain=void 0,e.prototype.shouldBackOff=void 0,e.prototype.cursor=function(){return this.cursorTag},e}(),t.PullChange=function(){function e(e){this.path=e[0],this.stat=t.Stat.parse(e[1]),this.stat?this.wasRemoved=!1:(this.stat=null,this.wasRemoved=!0)}return e.parse=function(e){return e&&"object"==typeof e?new t.PullChange(e):e},e.prototype.path=void 0,e.prototype.wasRemoved=void 0,e.prototype.stat=void 0,e}(),t.RangeInfo=function(){function e(t){var e;(e=/^bytes (\d*)-(\d*)\/(.*)$/.exec(t))?(this.start=parseInt(e[1]),this.end=parseInt(e[2]),this.size="*"===e[3]?null:parseInt(e[3])):(this.start=0,this.end=0,this.size=null)}return e.parse=function(e){return"string"==typeof e?new t.RangeInfo(e):e},e.prototype.start=null,e.prototype.size=null,e.prototype.end=null,e}(),t.PublicUrl=function(){function e(t,e){this.url=t.url,this.expiresAt=new Date(Date.parse(t.expires)),this.isDirect=e===!0?!0:e===!1?!1:"direct"in t?t.direct:864e5>=Date.now()-this.expiresAt,this.isPreview=!this.isDirect,this._json=null}return e.parse=function(e,r){return e&&"object"==typeof e?new t.PublicUrl(e,r):e},e.prototype.url=null,e.prototype.expiresAt=null,e.prototype.isDirect=null,e.prototype.isPreview=null,e.prototype.json=function(){return this._json||(this._json={url:this.url,expires:""+this.expiresAt,direct:this.isDirect})},e}(),t.CopyReference=function(){function e(t){"object"==typeof t?(this.tag=t.copy_ref,this.expiresAt=new Date(Date.parse(t.expires)),this._json=t):(this.tag=t,this.expiresAt=new Date(1e3*Math.ceil(Date.now()/1e3)),this._json=null)}return e.parse=function(e){return!e||"object"!=typeof e&&"string"!=typeof e?e:new t.CopyReference(e)},e.prototype.tag=null,e.prototype.expiresAt=null,e.prototype.json=function(){return this._json||(this._json={copy_ref:this.tag,expires:""+this.expiresAt})},e}(),t.Stat=function(){function e(t){var e,r,n,o;switch(this._json=t,this.path=t.path,"/"!==this.path.substring(0,1)&&(this.path="/"+this.path),e=this.path.length-1,e>=0&&"/"===this.path.substring(e)&&(this.path=this.path.substring(0,e)),r=this.path.lastIndexOf("/"),this.name=this.path.substring(r+1),this.isFolder=t.is_dir||!1,this.isFile=!this.isFolder,this.isRemoved=t.is_deleted||!1,this.typeIcon=t.icon,this.modifiedAt=(null!=(n=t.modified)?n.length:void 0)?new Date(Date.parse(t.modified)):null,this.clientModifiedAt=(null!=(o=t.client_mtime)?o.length:void 0)?new Date(Date.parse(t.client_mtime)):null,t.root){case"dropbox":this.inAppFolder=!1;break;case"app_folder":this.inAppFolder=!0;break;default:this.inAppFolder=null}this.size=t.bytes||0,this.humanSize=t.size||"",this.hasThumbnail=t.thumb_exists||!1,this.isFolder?(this.versionTag=t.hash,this.mimeType=t.mime_type||"inode/directory"):(this.versionTag=t.rev,this.mimeType=t.mime_type||"application/octet-stream")}return e.parse=function(e){return e&&"object"==typeof e?new t.Stat(e):e},e.prototype.path=null,e.prototype.name=null,e.prototype.inAppFolder=null,e.prototype.isFolder=null,e.prototype.isFile=null,e.prototype.isRemoved=null,e.prototype.typeIcon=null,e.prototype.versionTag=null,e.prototype.mimeType=null,e.prototype.size=null,e.prototype.humanSize=null,e.prototype.hasThumbnail=null,e.prototype.modifiedAt=null,e.prototype.clientModifiedAt=null,e.prototype.json=function(){return this._json},e}(),t.UploadCursor=function(){function e(t){this.replace(t)}return e.parse=function(e){return!e||"object"!=typeof e&&"string"!=typeof e?e:new t.UploadCursor(e)},e.prototype.tag=null,e.prototype.offset=null,e.prototype.expiresAt=null,e.prototype.json=function(){return this._json||(this._json={upload_id:this.tag,offset:this.offset,expires:""+this.expiresAt})},e.prototype.replace=function(t){return"object"==typeof t?(this.tag=t.upload_id||null,this.offset=t.offset||0,this.expiresAt=new Date(Date.parse(t.expires)||Date.now()),this._json=t):(this.tag=t||null,this.offset=0,this.expiresAt=new Date(1e3*Math.floor(Date.now()/1e3)),this._json=null),this},e}(),t.UserInfo=function(){function e(t){var e;this._json=t,this.name=t.display_name,this.email=t.email,this.countryCode=t.country||null,this.uid=""+t.uid,t.public_app_url?(this.publicAppUrl=t.public_app_url,e=this.publicAppUrl.length-1,e>=0&&"/"===this.publicAppUrl.substring(e)&&(this.publicAppUrl=this.publicAppUrl.substring(0,e))):this.publicAppUrl=null,this.referralUrl=t.referral_link,this.quota=t.quota_info.quota,this.privateBytes=t.quota_info.normal||0,this.sharedBytes=t.quota_info.shared||0,this.usedQuota=this.privateBytes+this.sharedBytes}return e.parse=function(e){return e&&"object"==typeof e?new t.UserInfo(e):e},e.prototype.name=null,e.prototype.email=null,e.prototype.countryCode=null,e.prototype.uid=null,e.prototype.referralUrl=null,e.prototype.publicAppUrl=null,e.prototype.quota=null,e.prototype.usedQuota=null,e.prototype.privateBytes=null,e.prototype.sharedBytes=null,e.prototype.json=function(){return this._json},e}(),"undefined"==typeof XMLHttpRequest||"undefined"==typeof window&&"undefined"==typeof self||"undefined"==typeof navigator||"string"!=typeof navigator.userAgent?(h=require("xhr2"),a=!1,i=!1,s=!1):("undefined"==typeof XDomainRequest||"withCredentials"in new XMLHttpRequest?(h=XMLHttpRequest,a=!1,i="undefined"!=typeof FormData&&-1===navigator.userAgent.indexOf("Firefox")):(h=XDomainRequest,a=!0,i=!1),s=!0),"undefined"==typeof Uint8Array)o=null,l=!1,u=!1;else if(Object.getPrototypeOf?o=Object.getPrototypeOf(Object.getPrototypeOf(new Uint8Array(0))).constructor:Object.__proto__&&(o=new Uint8Array(0).__proto__.__proto__.constructor),"undefined"==typeof Blob)l=!1,u=!0;else{try{2===new Blob([new Uint8Array(2)]).size?(l=!0,u=!0):(u=!1,l=2===new Blob([new ArrayBuffer(2)]).size)}catch(X){g=X,u=!1,l=!1,"undefined"!=typeof WebKitBlobBuilder&&-1!==navigator.userAgent.indexOf("Android")&&(i=!1)}o===Object&&(u=!1)}if(t.Xhr=function(){function e(t,e){this.method=t,this.isGet="GET"===this.method,this.url=e,this.wantHeaders=!1,this.headers={},this.params=null,this.body=null,this.preflight=!(this.isGet||"POST"===this.method),this.signed=!1,this.completed=!1,this.responseType=null,this.callback=null,this.xhr=null,this.onError=null}return e.Request=h,e.ieXdr=a,e.canSendForms=i,e.doesPreflight=s,e.ArrayBufferView=o,e.sendArrayBufferView=u,e.wrapBlob=l,e.prototype.xhr=null,e.prototype.onError=null,e.prototype.setParams=function(t){if(this.signed)throw Error("setParams called after addOauthParams or addOauthHeader");if(this.params)throw Error("setParams cannot be called twice");return this.params=t,this},e.prototype.setCallback=function(t){return this.callback=t,this},e.prototype.signWithOauth=function(e,r){return t.Xhr.ieXdr?this.addOauthParams(e):this.preflight||!t.Xhr.doesPreflight?this.addOauthHeader(e):this.isGet&&r?this.addOauthHeader(e):this.addOauthParams(e)},e.prototype.addOauthParams=function(t){if(this.signed)throw Error("Request already has an OAuth signature");return this.params||(this.params={}),t.addAuthParams(this.method,this.url,this.params),this.signed=!0,this},e.prototype.addOauthHeader=function(t){if(this.signed)throw Error("Request already has an OAuth signature");return this.params||(this.params={}),this.signed=!0,this.setHeader("Authorization",t.authHeader(this.method,this.url,this.params))},e.prototype.setBody=function(t){if(this.isGet)throw Error("setBody cannot be called on GET requests");if(null!==this.body)throw Error("Request already has a body");return"string"==typeof t||"undefined"!=typeof FormData&&t instanceof FormData||(this.headers["Content-Type"]="application/octet-stream",this.preflight=!0),this.body=t,this},e.prototype.setResponseType=function(t){return this.responseType=t,this},e.prototype.setHeader=function(t,e){var r;if(this.headers[t])throw r=this.headers[t],Error("HTTP header "+t+" already set to "+r);if("Content-Type"===t)throw Error("Content-Type is automatically computed based on setBody");return this.preflight=!0,this.headers[t]=e,this},e.prototype.reportResponseHeaders=function(){return this.wantHeaders=!0},e.prototype.setFileField=function(e,r,n,o){var i,s,a,h;if(null!==this.body)throw Error("Request already has a body");if(this.isGet)throw Error("setFileField cannot be called on GET requests");if("object"==typeof n){"undefined"!=typeof ArrayBuffer&&(n instanceof ArrayBuffer?t.Xhr.sendArrayBufferView&&(n=new Uint8Array(n)):!t.Xhr.sendArrayBufferView&&0===n.byteOffset&&n.buffer instanceof ArrayBuffer&&(n=n.buffer)),o||(o="application/octet-stream");try{n=new Blob([n],{type:o})}catch(u){g=u,window.WebKitBlobBuilder&&(a=new WebKitBlobBuilder,a.append(n),(i=a.getBlob(o))&&(n=i))}"undefined"!=typeof File&&n instanceof File&&(n=new Blob([n],{type:n.type})),h=n instanceof Blob}else h=!1;return h?(this.body=new FormData,this.body.append(e,n,r)):(o||(o="application/octet-stream"),s=this.multipartBoundary(),this.headers["Content-Type"]="multipart/form-data; boundary="+s,this.body=["--",s,"\r\n",'Content-Disposition: form-data; name="',e,'"; filename="',r,'"\r\n',"Content-Type: ",o,"\r\n","Content-Transfer-Encoding: binary\r\n\r\n",n,"\r\n","--",s,"--","\r\n"].join(""))},e.prototype.multipartBoundary=function(){return[Date.now().toString(36),Math.random().toString(36)].join("----")},e.prototype.paramsToUrl=function(){var e;return this.params&&(e=t.Xhr.urlEncode(this.params),0!==e.length&&(this.url=[this.url,"?",e].join("")),this.params=null),this},e.prototype.paramsToBody=function(){if(this.params){if(null!==this.body)throw Error("Request already has a body");if(this.isGet)throw Error("paramsToBody cannot be called on GET requests");this.headers["Content-Type"]="application/x-www-form-urlencoded",this.body=t.Xhr.urlEncode(this.params),this.params=null}return this},e.prototype.prepare=function(){var e,r,n,o,i=this;if(r=t.Xhr.ieXdr,this.isGet||null!==this.body||r?(this.paramsToUrl(),null!==this.body&&"string"==typeof this.body&&(this.headers["Content-Type"]="text/plain; charset=utf8")):this.paramsToBody(),this.xhr=new t.Xhr.Request,r?(this.xhr.onload=function(){return i.onXdrLoad()},this.xhr.onerror=function(){return i.onXdrError()},this.xhr.ontimeout=function(){return i.onXdrError()},this.xhr.onprogress=function(){}):this.xhr.onreadystatechange=function(){return i.onReadyStateChange()},this.xhr.open(this.method,this.url,!0),!r){o=this.headers;for(e in o)D.call(o,e)&&(n=o[e],this.xhr.setRequestHeader(e,n))}return this.responseType&&("b"===this.responseType?this.xhr.overrideMimeType&&this.xhr.overrideMimeType("text/plain; charset=x-user-defined"):this.xhr.responseType=this.responseType),this},e.prototype.send=function(e){var r,n;if(this.callback=e||this.callback,null!==this.body){r=this.body,t.Xhr.sendArrayBufferView?r instanceof ArrayBuffer&&(r=new Uint8Array(r)):0===r.byteOffset&&r.buffer instanceof ArrayBuffer&&(r=r.buffer);try{this.xhr.send(r)}catch(o){if(n=o,t.Xhr.sendArrayBufferView||!t.Xhr.wrapBlob)throw n;r=new Blob([r],{type:"application/octet-stream"}),this.xhr.send(r)}}else this.xhr.send();return this},e.urlEncode=function(t){var e,r,n;e=[];for(r in t)n=t[r],e.push(this.urlEncodeValue(r)+"="+this.urlEncodeValue(n));return e.sort().join("&")},e.urlEncodeValue=function(t){return encodeURIComponent(""+t).replace(/\!/g,"%21").replace(/'/g,"%27").replace(/\(/g,"%28").replace(/\)/g,"%29").replace(/\*/g,"%2A")},e.urlDecode=function(t){var e,r,n,o,i,s;for(r={},s=t.split("&"),o=0,i=s.length;i>o;o++)n=s[o],e=n.split("="),r[decodeURIComponent(e[0])]=decodeURIComponent(e[1]);return r},e.prototype.onReadyStateChange=function(){var e,r,n,o,i,s,a,h,u,l,p,c;if(4!==this.xhr.readyState)return!0;if(this.completed)return!0;if(this.completed=!0,200>this.xhr.status||this.xhr.status>=300)return r=new t.ApiError(this.xhr,this.method,this.url),this.onError?this.onError(r,this.callback):this.callback(r),!0;if(this.wantHeaders?(e=this.xhr.getAllResponseHeaders(),i=e?t.Xhr.parseResponseHeaders(e):this.guessResponseHeaders(),u=i["x-dropbox-metadata"]):(i=void 0,u=this.xhr.getResponseHeader("x-dropbox-metadata")),null!=u?u.length:void 0)try{h=JSON.parse(u)}catch(d){a=d,h=void 0}else h=void 0;if(this.responseType){if("b"===this.responseType){for(o=null!=this.xhr.responseText?this.xhr.responseText:this.xhr.response,n=[],s=p=0,c=o.length;c>=0?c>p:p>c;s=c>=0?++p:--p)n.push(String.fromCharCode(255&o.charCodeAt(s)));l=n.join(""),this.callback(null,l,h,i)}else this.callback(null,this.xhr.response,h,i);return!0}switch(l=null!=this.xhr.responseText?this.xhr.responseText:this.xhr.response,this.xhr.getResponseHeader("Content-Type")){case"application/x-www-form-urlencoded":this.callback(null,t.Xhr.urlDecode(l),h,i);break;case"application/json":case"text/javascript":this.callback(null,JSON.parse(l),h,i);break;default:this.callback(null,l,h,i)}return!0},e.parseResponseHeaders=function(t){var e,r,n,o,i,s,a,h;for(n={},r=t.split("\n"),a=0,h=r.length;h>a;a++)o=r[a],e=o.indexOf(":"),i=o.substring(0,e).trim().toLowerCase(),s=o.substring(e+1).trim(),n[i]=s;return n},e.prototype.guessResponseHeaders=function(){var t,e,r,n,o,i;for(t={},i=["cache-control","content-language","content-range","content-type","expires","last-modified","pragma","x-dropbox-metadata"],n=0,o=i.length;o>n;n++)e=i[n],r=this.xhr.getResponseHeader(e),r&&(t[e]=r);return t},e.prototype.onXdrLoad=function(){var e,r,n;if(this.completed)return!0;if(this.completed=!0,n=this.xhr.responseText,e=this.wantHeaders?{"content-type":this.xhr.contentType}:void 0,r=void 0,this.responseType)return this.callback(null,n,r,e),!0;switch(this.xhr.contentType){case"application/x-www-form-urlencoded":this.callback(null,t.Xhr.urlDecode(n),r,e);break;case"application/json":case"text/javascript":this.callback(null,JSON.parse(n),r,e);break;default:this.callback(null,n,r,e)}return!0},e.prototype.onXdrError=function(){var e;return this.completed?!0:(this.completed=!0,e=new t.ApiError(this.xhr,this.method,this.url),this.onError?this.onError(e,this.callback):this.callback(e),!0)},e}(),"undefined"!=typeof module&&"exports"in module)module.exports=t;else if("undefined"!=typeof window&&null!==window)if(window.Dropbox)for(R in t)D.call(t,R)&&(k=t[R],window.Dropbox[R]=k);else window.Dropbox=t;else{if("undefined"==typeof self||null===self)throw Error("This library only supports node.js and modern browsers.");self.Dropbox=t}}).call(this); +/* +//@ sourceMappingURL=dropbox.min.map +*/ \ No newline at end of file diff --git a/src/project/ProjectManager.js b/src/project/ProjectManager.js index de76f4fb14c..3852b5d0c77 100644 --- a/src/project/ProjectManager.js +++ b/src/project/ProjectManager.js @@ -1060,8 +1060,8 @@ define(function (require, exports, module) { _loadProject(path, false).then(result.resolve, result.reject); } else { // Pop up a folder browse dialog - NativeFileSystem.showOpenDialog(false, true, Strings.CHOOSE_FOLDER, _projectRoot.fullPath, null, - function (files) { + FileSystem.showOpenDialog(false, true, Strings.CHOOSE_FOLDER, _projectRoot.fullPath, null) + .done(function (files) { // If length == 0, user canceled the dialog; length should never be > 1 if (files.length > 0) { // Load the new project into the folder tree @@ -1069,16 +1069,15 @@ define(function (require, exports, module) { } else { result.reject(); } - }, - function (error) { + }) + .fail(function (error) { Dialogs.showModalDialog( DefaultDialogs.DIALOG_ID_ERROR, Strings.ERROR_LOADING_PROJECT, StringUtils.format(Strings.OPEN_DIALOG_ERROR, error.name) ); result.reject(); - } - ); + }); } }) .fail(function () { From 868e45b7d30dd1d61b27afab4183bc342471069a Mon Sep 17 00:00:00 2001 From: Glenn Ruehle Date: Sat, 29 Jun 2013 09:51:54 -0700 Subject: [PATCH 010/163] Migrate brackets.js from NativeFileSystem to FileSystem --- src/brackets.js | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/brackets.js b/src/brackets.js index baa27a551d2..14dcc02acac 100644 --- a/src/brackets.js +++ b/src/brackets.js @@ -90,7 +90,6 @@ define(function (require, exports, module) { Async = require("utils/Async"), UpdateNotification = require("utils/UpdateNotification"), UrlParams = require("utils/UrlParams").UrlParams, - NativeFileSystem = require("file/NativeFileSystem").NativeFileSystem, PreferencesManager = require("preferences/PreferencesManager"), Resizer = require("utils/Resizer"), LiveDevelopmentMain = require("LiveDevelopment/main"), @@ -214,12 +213,16 @@ define(function (require, exports, module) { if (!params.get("skipSampleProjectLoad") && !prefs.getValue("afterFirstLaunch")) { prefs.setValue("afterFirstLaunch", "true"); if (ProjectManager.isWelcomeProjectPath(initialProjectPath)) { - var dirEntry = new NativeFileSystem.DirectoryEntry(initialProjectPath); - - dirEntry.getFile("index.html", {}, function (fileEntry) { - var promise = CommandManager.execute(Commands.FILE_ADD_TO_WORKING_SET, { fullPath: fileEntry.fullPath }); - promise.then(deferred.resolve, deferred.reject); - }, deferred.reject); + var indexFile = FileSystem.getFileForPath(initialProjectPath + "/index.html"); + + indexFile.exists().done(function (exists) { + if (exists) { + var promise = CommandManager.execute(Commands.FILE_ADD_TO_WORKING_SET, { fullPath: indexFile.getPath() }); + promise.then(deferred.resolve, deferred.reject); + } else { + deferred.reject(); + } + }); } else { deferred.resolve(); } From 13368c480e24884f8261a0b5629996209d8ddba0 Mon Sep 17 00:00:00 2001 From: Glenn Ruehle Date: Mon, 1 Jul 2013 19:01:22 -0700 Subject: [PATCH 011/163] Reorganize impl files --- src/filesystem/FileSystem.js | 4 ++-- src/filesystem/{ => impls}/FileSystemImpl.js | 6 +++++- src/filesystem/impls/GoogleDriveFileSystem.js | 0 src/filesystem/impls/LocalStorageFileSystem.js | 0 src/filesystem/impls/NodeFileSystem.js | 0 src/filesystem/impls/{ => appshell}/AppshellFileSystem.js | 0 src/filesystem/impls/{ => dropbox}/DropboxFileSystem.js | 5 +++++ 7 files changed, 12 insertions(+), 3 deletions(-) rename src/filesystem/{ => impls}/FileSystemImpl.js (83%) delete mode 100644 src/filesystem/impls/GoogleDriveFileSystem.js delete mode 100644 src/filesystem/impls/LocalStorageFileSystem.js delete mode 100644 src/filesystem/impls/NodeFileSystem.js rename src/filesystem/impls/{ => appshell}/AppshellFileSystem.js (100%) rename src/filesystem/impls/{ => dropbox}/DropboxFileSystem.js (97%) diff --git a/src/filesystem/FileSystem.js b/src/filesystem/FileSystem.js index a2487e5b2a2..b0c3bac7d4f 100644 --- a/src/filesystem/FileSystem.js +++ b/src/filesystem/FileSystem.js @@ -32,8 +32,8 @@ define(function (require, exports, module) { File = require("filesystem/File"), FileIndex = require("filesystem/FileIndex"); - var appshellFileSystem = require("filesystem/impls/AppshellFileSystem"), - dropboxFileSystem = require("filesystem/impls/DropboxFileSystem"); + var appshellFileSystem = require("filesystem/impls/appshell/AppshellFileSystem"), + dropboxFileSystem = require("filesystem/impls/dropbox/DropboxFileSystem"); // FileSystemImpl var _impl; diff --git a/src/filesystem/FileSystemImpl.js b/src/filesystem/impls/FileSystemImpl.js similarity index 83% rename from src/filesystem/FileSystemImpl.js rename to src/filesystem/impls/FileSystemImpl.js index 3820980b2c3..50377f18588 100644 --- a/src/filesystem/FileSystemImpl.js +++ b/src/filesystem/impls/FileSystemImpl.js @@ -21,4 +21,8 @@ // writeFile(path, data, [options], callback) // chmod(path, mode, callback) // unlink(path, callback) -// [moveToTrash(path, callback)] \ No newline at end of file +// [moveToTrash(path, callback)] +// initWatchers(callback) +// watchDirectory(path) +// unwatchDirectory(path) +// unwatchAllDirectories() \ No newline at end of file diff --git a/src/filesystem/impls/GoogleDriveFileSystem.js b/src/filesystem/impls/GoogleDriveFileSystem.js deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/filesystem/impls/LocalStorageFileSystem.js b/src/filesystem/impls/LocalStorageFileSystem.js deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/filesystem/impls/NodeFileSystem.js b/src/filesystem/impls/NodeFileSystem.js deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/filesystem/impls/AppshellFileSystem.js b/src/filesystem/impls/appshell/AppshellFileSystem.js similarity index 100% rename from src/filesystem/impls/AppshellFileSystem.js rename to src/filesystem/impls/appshell/AppshellFileSystem.js diff --git a/src/filesystem/impls/DropboxFileSystem.js b/src/filesystem/impls/dropbox/DropboxFileSystem.js similarity index 97% rename from src/filesystem/impls/DropboxFileSystem.js rename to src/filesystem/impls/dropbox/DropboxFileSystem.js index f4af58b91b7..ee69aae2028 100644 --- a/src/filesystem/impls/DropboxFileSystem.js +++ b/src/filesystem/impls/dropbox/DropboxFileSystem.js @@ -138,6 +138,11 @@ define(function (require, exports, module) { client.unlink(path, callback); } + // TEMP - REMOVE ME + exports.pullChanges = function (cursor, callback) { + client.pullChanges(cursor, callback); + }; + // Export public API exports.init = init; exports.showOpenDialog = showOpenDialog; From 8a56ceeb9135381c877dfa656c8598191307c4e5 Mon Sep 17 00:00:00 2001 From: Glenn Ruehle Date: Thu, 4 Jul 2013 11:06:14 -0700 Subject: [PATCH 012/163] Getting started with directory watchers. --- src/filesystem/FileSystem.js | 25 ++++ src/filesystem/impls/FileSystemImpl.js | 6 +- .../impls/appshell/AppshellFileSystem.js | 91 +++++++++++- .../impls/appshell/node/FileWatcherDomain.js | 135 ++++++++++++++++++ .../impls/dropbox/DropboxFileSystem.js | 16 +++ 5 files changed, 268 insertions(+), 5 deletions(-) create mode 100644 src/filesystem/impls/appshell/node/FileWatcherDomain.js diff --git a/src/filesystem/FileSystem.js b/src/filesystem/FileSystem.js index b0c3bac7d4f..7700ba46dc5 100644 --- a/src/filesystem/FileSystem.js +++ b/src/filesystem/FileSystem.js @@ -44,6 +44,11 @@ define(function (require, exports, module) { * @param {FileSystemImpl} impl File system implementation */ function setFileSystemImpl(impl) { + // Clear old watchers + if (_impl) { + _impl.unwatchAll(); + } + _impl = impl; _impl.init(); @@ -246,6 +251,22 @@ define(function (require, exports, module) { } } }); + _impl.watchPath(directoryPath); + } + + /** + * @private + * Callback for file/directory watchers. This is called by the low-level implementation + * whenever a directory or file is changed. There may be multiple calls for any given + * directory or file, so this callback collects messages over a 100ms period of time + * and then handles them all at once. + * + * @param {string} path The path that changed. This could be a file or a directory. + * @param {stat=} stat Optional stat for the item that changed. This param is not always + * passed. + */ + function _watcherCallback(path, stat) { + console.log("File/directory change: " + path + ", stat: " + stat); } /** @@ -264,6 +285,10 @@ define(function (require, exports, module) { // Clear file index FileIndex.clear(); + // Initialize watchers + _impl.unwatchAll(); + _impl.initWatchers(_watcherCallback); + // Start indexing from the new root path _scanDirectory(rootPath); } diff --git a/src/filesystem/impls/FileSystemImpl.js b/src/filesystem/impls/FileSystemImpl.js index 50377f18588..ee4f3162b7c 100644 --- a/src/filesystem/impls/FileSystemImpl.js +++ b/src/filesystem/impls/FileSystemImpl.js @@ -23,6 +23,6 @@ // unlink(path, callback) // [moveToTrash(path, callback)] // initWatchers(callback) -// watchDirectory(path) -// unwatchDirectory(path) -// unwatchAllDirectories() \ No newline at end of file +// watchPath(path) +// unwatchPath(path) +// unwatchAll() \ No newline at end of file diff --git a/src/filesystem/impls/appshell/AppshellFileSystem.js b/src/filesystem/impls/appshell/AppshellFileSystem.js index d4ddaa417bf..a6dab8c0b9b 100644 --- a/src/filesystem/impls/appshell/AppshellFileSystem.js +++ b/src/filesystem/impls/appshell/AppshellFileSystem.js @@ -23,13 +23,67 @@ /*jslint vars: true, plusplus: true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50 */ -/*global define, appshell */ +/*global define, appshell, $, window */ define(function (require, exports, module) { "use strict"; + + var FileUtils = require("file/FileUtils"), + NodeConnection = require("utils/NodeConnection"); + + /** + * @const + * Amount of time to wait before automatically rejecting the connection + * deferred. If we hit this timeout, we'll never have a node connection + * for the file watcher in this run of Brackets. + */ + var NODE_CONNECTION_TIMEOUT = 30000; // 30 seconds - TODO: share with StaticServer & Package? + + /** + * @private + * @type{jQuery.Deferred.} + * A deferred which is resolved with a NodeConnection or rejected if + * we are unable to connect to Node. + */ + var _nodeConnectionDeferred; + + var _watcherCallback, // Callback function for watcher events + _watcherMap; // Map of directory paths to watcher objects function init() { - // Nothing to do... + if (!_nodeConnectionDeferred) { + _nodeConnectionDeferred = new $.Deferred(); + + // TODO: This code is a copy of the AppInit function in extensibility/Package.js. This should be refactored + // into common code. + + + // Start up the node connection, which is held in the + // _nodeConnectionDeferred module variable. (Use + // _nodeConnectionDeferred.done() to access it. + var connectionTimeout = window.setTimeout(function () { + console.error("[AppshellFileSystem] Timed out while trying to connect to node"); + _nodeConnectionDeferred.reject(); + }, NODE_CONNECTION_TIMEOUT); + + var _nodeConnection = new NodeConnection(); + _nodeConnection.connect(true).then(function () { + var domainPath = FileUtils.getNativeBracketsDirectoryPath() + "/" + FileUtils.getNativeModuleDirectoryPath(module) + "/node/FileWatcherDomain"; + + _nodeConnection.loadDomains(domainPath, true) + .then( + function () { + window.clearTimeout(connectionTimeout); + _nodeConnectionDeferred.resolve(_nodeConnection); + }, + function () { // Failed to connect + console.error("[AppshellFileSystem] Failed to connect to node", arguments); + window.clearTimeout(connectionTimeout); + _nodeConnectionDeferred.reject(); + } + ); + }); + } } function showOpenDialog(allowMultipleSelection, chooseDirectories, title, initialPath, fileTypes, callback) { @@ -155,6 +209,35 @@ define(function (require, exports, module) { appshell.fs.moveToTrash(path, callback); } + function initWatchers(callback) { + _watcherCallback = callback; + // _watcherMap = {}; TODO: send message to node? + } + + function watchPath(path) { + _nodeConnectionDeferred.done(function (nodeConnection) { + if (nodeConnection.connected()) { + nodeConnection.domains.fileWatcher.watchPath(path); + } + }); + } + + function unwatchPath(path) { + _nodeConnectionDeferred.done(function (nodeConnection) { + if (nodeConnection.connected()) { + nodeConnection.domains.fileWatcher.unwatchPath(path); + } + }); + } + + function unwatchAll() { + _nodeConnectionDeferred.done(function (nodeConnection) { + if (nodeConnection.connected()) { + nodeConnection.domains.fileWatcher.unwatchAll(); + } + }); + } + // Export public API exports.init = init; exports.showOpenDialog = showOpenDialog; @@ -170,4 +253,8 @@ define(function (require, exports, module) { exports.chmod = chmod; exports.unlink = unlink; exports.moveToTrash = moveToTrash; + exports.initWatchers = initWatchers; + exports.watchPath = watchPath; + exports.unwatchPath = unwatchPath; + exports.unwatchAll = unwatchAll; }); \ No newline at end of file diff --git a/src/filesystem/impls/appshell/node/FileWatcherDomain.js b/src/filesystem/impls/appshell/node/FileWatcherDomain.js new file mode 100644 index 00000000000..05b98d3eabc --- /dev/null +++ b/src/filesystem/impls/appshell/node/FileWatcherDomain.js @@ -0,0 +1,135 @@ +/* + * 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, node: true, nomen: true, indent: 4, maxerr: 50 */ + +"use strict"; + +var fs = require("fs"); + +var _watcherMap = {}; + +function watchPath(path) { + _watcherMap[path] = fs.watch(path, function (event, filename) { + console.log("Change in: " + path + " event: " + event + " filename: " + filename); + }); +} + +function unwatchPath(path) { + var watcher = _watcherMap[path]; + + if (watcher) { + watcher.close(); + delete _watcherMap[path]; + } +} + +function unwatchAll() { + var path; + + for (path in _watcherMap) { + if (_watcherMap.hasOwnProperty(path)) { + unwatchPath(path); + } + } +} + +/** + * Initialize the "extensions" domain. + * The extensions domain handles downloading, unpacking/verifying, and installing extensions. + */ +function init(domainManager) { + if (!domainManager.hasDomain("fileWatcher")) { + domainManager.registerDomain("fileWatcher", {major: 0, minor: 1}); + } + domainManager.registerCommand( + "fileWatcher", + "watchPath", + watchPath, + false, + "Start watching a file or directory", + [{ + name: "path", + type: "string", + description: "absolute filesystem path of the file or directory to watch" + }] /*, + [{ + name: "errors", + type: "string|Array.", + description: "download error, if any; first string is error code (one of Errors.*); subsequent strings are additional info" + }, { + name: "metadata", + type: "{name: string, version: string}", + description: "all package.json metadata (null if there's no package.json)" + }] + */ + ); + domainManager.registerCommand( + "fileWatcher", + "unwatchPath", + unwatchPath, + false, + "Stop watching a file or directory", + [{ + name: "path", + type: "string", + description: "absolute filesystem path of the file or directory to unwatch" + }] /*, + [{ + name: "errors", + type: "string|Array.", + description: "download error, if any; first string is error code (one of Errors.*); subsequent strings are additional info" + }, { + name: "metadata", + type: "{name: string, version: string}", + description: "all package.json metadata (null if there's no package.json)" + }] + */ + ); + domainManager.registerCommand( + "fileWatcher", + "unwatchAll", + unwatchAll, + false, + "Stop watching all files and directories"/*, + [{ + name: "path", + type: "string", + description: "absolute filesystem path of the file or directory to unwatch" + }], + [{ + name: "errors", + type: "string|Array.", + description: "download error, if any; first string is error code (one of Errors.*); subsequent strings are additional info" + }, { + name: "metadata", + type: "{name: string, version: string}", + description: "all package.json metadata (null if there's no package.json)" + }] + */ + ); +} + +exports.init = init; + diff --git a/src/filesystem/impls/dropbox/DropboxFileSystem.js b/src/filesystem/impls/dropbox/DropboxFileSystem.js index ee69aae2028..d2372e3a3b9 100644 --- a/src/filesystem/impls/dropbox/DropboxFileSystem.js +++ b/src/filesystem/impls/dropbox/DropboxFileSystem.js @@ -138,6 +138,18 @@ define(function (require, exports, module) { client.unlink(path, callback); } + function initWatchers(callback) { + } + + function watchPath(path) { + } + + function unwatchPath(path) { + } + + function unwatchAll() { + } + // TEMP - REMOVE ME exports.pullChanges = function (cursor, callback) { client.pullChanges(cursor, callback); @@ -156,4 +168,8 @@ define(function (require, exports, module) { exports.writeFile = writeFile; exports.chmod = chmod; exports.unlink = unlink; + exports.initWatchers = initWatchers; + exports.watchPath = watchPath; + exports.unwatchPath = unwatchPath; + exports.unwatchAll = unwatchAll; }); \ No newline at end of file From 045f34654b2abc0f763d1b2fa89b949e88c5e84a Mon Sep 17 00:00:00 2001 From: Glenn Ruehle Date: Mon, 8 Jul 2013 10:10:41 -0700 Subject: [PATCH 013/163] Initial integration into core. --- src/LiveDevelopment/LiveDevelopment.js | 14 +- src/brackets.js | 11 +- src/document/ChangedDocumentTracker.js | 4 +- src/document/Document.js | 21 +- src/document/DocumentCommandHandlers.js | 139 ++++------ src/document/DocumentManager.js | 122 +++++---- src/editor/Editor.js | 2 +- src/editor/EditorManager.js | 12 +- src/editor/InlineTextEditor.js | 10 +- src/editor/MultiRangeInlineEditor.js | 2 +- src/extensibility/ExtensionManager.js | 3 +- .../ExtensionManagerViewModel.js | 6 +- src/extensibility/InstallExtensionDialog.js | 5 +- src/extensibility/Package.js | 11 +- src/extensions/default/DebugCommands/main.js | 29 +- src/extensions/default/JSLint/main.js | 6 +- .../JavaScriptCodeHints/ScopeManager.js | 135 ++++----- .../default/JavaScriptCodeHints/Session.js | 4 +- .../default/JavaScriptCodeHints/main.js | 4 +- .../default/JavaScriptCodeHints/unittests.js | 30 +- .../default/JavaScriptQuickEdit/main.js | 27 +- .../default/JavaScriptQuickEdit/unittests.js | 5 +- src/extensions/default/QuickView/main.js | 2 +- src/extensions/default/RecentProjects/main.js | 16 +- src/extensions/default/StaticServer/main.js | 2 +- src/extensions/default/UrlCodeHints/main.js | 19 +- .../default/WebPlatformDocs/main.js | 3 +- .../samples/InlineImageViewer/main.js | 4 +- src/file/FileUtils.js | 66 ++--- src/filesystem/File.js | 11 +- src/filesystem/FileIndex.js | 16 ++ src/filesystem/FileSystem.js | 116 +++++++- src/filesystem/FileSystemEntry.js | 7 + .../impls/appshell/AppshellFileSystem.js | 46 +++- .../impls/appshell/node/FileWatcherDomain.js | 53 +--- .../impls/dropbox/DropboxFileSystem.js | 2 +- src/language/CSSUtils.js | 11 +- src/language/HTMLInstrumentation.js | 20 +- src/language/JSUtils.js | 31 +-- src/project/FileSyncManager.js | 33 +-- src/project/FileViewController.js | 6 +- src/project/ProjectManager.js | 259 +++++++++--------- src/project/SidebarView.js | 4 +- src/project/WorkingSetSort.js | 4 +- src/project/WorkingSetView.js | 12 +- src/search/FindInFiles.js | 14 +- src/search/QuickOpen.js | 27 +- src/utils/BuildInfoUtils.js | 9 +- src/utils/ExtensionLoader.js | 120 ++++---- src/utils/NativeApp.js | 1 + src/utils/ViewUtils.js | 2 +- 51 files changed, 798 insertions(+), 720 deletions(-) diff --git a/src/LiveDevelopment/LiveDevelopment.js b/src/LiveDevelopment/LiveDevelopment.js index 7b76c5543c7..113fab5efc0 100644 --- a/src/LiveDevelopment/LiveDevelopment.js +++ b/src/LiveDevelopment/LiveDevelopment.js @@ -162,7 +162,7 @@ define(function LiveDevelopment(require, exports, module) { if (baseUrl !== "" && url.indexOf(baseUrl) === 0) { // Use base url to translate to local file path. // Need to use encoded project path because it's decoded below. - path = url.replace(baseUrl, encodeURI(ProjectManager.getProjectRoot().fullPath)); + path = url.replace(baseUrl, encodeURI(ProjectManager.getProjectRoot().getPath())); } else if (url.indexOf("file://") === 0) { // Convert a file URL to local file path @@ -187,7 +187,7 @@ define(function LiveDevelopment(require, exports, module) { if (baseUrl !== "" && ProjectManager.isWithinProject(path)) { // Map to server url. Base url is already encoded, so don't encode again. var encodedDocPath = encodeURI(path); - var encodedProjectPath = encodeURI(ProjectManager.getProjectRoot().fullPath); + var encodedProjectPath = encodeURI(ProjectManager.getProjectRoot().getPath()); url = encodedDocPath.replace(encodedProjectPath, baseUrl); } else { @@ -215,7 +215,7 @@ define(function LiveDevelopment(require, exports, module) { // FUTURE: some of these things should just be moved into core Document; others should // be in a LiveDevelopment-specific object attached to the doc. - matches = /^(.*\/)(.+\.([^.]+))$/.exec(doc.file.fullPath); + matches = /^(.*\/)(.+\.([^.]+))$/.exec(doc.file.getPath()); if (!matches) { return; } @@ -270,7 +270,7 @@ define(function LiveDevelopment(require, exports, module) { } var foundDoc; docsToSearch.some(function matchesPath(ele) { - if (ele.doc.file.fullPath === path) { + if (ele.doc.file.getPath() === path) { foundDoc = ele; return true; } @@ -284,7 +284,7 @@ define(function LiveDevelopment(require, exports, module) { if (!editor) { return null; } - return getLiveDocForPath(editor.document.file.fullPath); + return getLiveDocForPath(editor.document.file.getPath()); } /** @@ -353,7 +353,7 @@ define(function LiveDevelopment(require, exports, module) { enableInstrumentation = true; _serverProvider.setRequestFilterPaths( - ["/" + encodeURI(ProjectManager.makeProjectRelativeIfPossible(doc.file.fullPath))] + ["/" + encodeURI(ProjectManager.makeProjectRelativeIfPossible(doc.file.getPath()))] ); // Send custom HTTP response for the current live document @@ -931,7 +931,7 @@ define(function LiveDevelopment(require, exports, module) { function _prepareServer(doc) { var deferred = new $.Deferred(); - _serverProvider = LiveDevServerManager.getProvider(doc.file.fullPath); + _serverProvider = LiveDevServerManager.getProvider(doc.file.getPath()); if (!exports.config.experimental && !_serverProvider) { if (FileUtils.isServerHtmlFileExt(doc.extension)) { diff --git a/src/brackets.js b/src/brackets.js index 14dcc02acac..d97a3cf5aa4 100644 --- a/src/brackets.js +++ b/src/brackets.js @@ -76,7 +76,6 @@ define(function (require, exports, module) { CommandManager = require("command/CommandManager"), CodeHintManager = require("editor/CodeHintManager"), PerfUtils = require("utils/PerfUtils"), - FileIndexManager = require("project/FileIndexManager"), FileSystem = require("filesystem/FileSystem"), QuickOpen = require("search/QuickOpen"), Menus = require("command/Menus"), @@ -137,7 +136,7 @@ define(function (require, exports, module) { JSUtils : JSUtils, CommandManager : CommandManager, FileSyncManager : FileSyncManager, - FileIndexManager : FileIndexManager, + //FileIndexManager : FileIndexManager, Menus : Menus, KeyBindingManager : KeyBindingManager, CodeHintManager : CodeHintManager, @@ -303,8 +302,12 @@ define(function (require, exports, module) { // TODO: (issue 269) to support IE, need to listen to document instead (and even then it may not work when focus is in an input field?) $(window).focus(function () { - FileSyncManager.syncOpenDocuments(); - FileIndexManager.markDirty(); + FileSyncManager.syncOpenDocuments(); // TODO: FileSystem - remove now that we have file watchers? + }); + + $(FileSystem).on("change", function (item) { + // TODO: FileSystem - only sync when window has focus? + FileSyncManager.syncOpenDocuments(); // TODO: Batch multiple changes into a single sync operation }); // Prevent unhandled middle button clicks from triggering native behavior diff --git a/src/document/ChangedDocumentTracker.js b/src/document/ChangedDocumentTracker.js index b7e95bbc262..e90e9bbbf1c 100644 --- a/src/document/ChangedDocumentTracker.js +++ b/src/document/ChangedDocumentTracker.js @@ -53,7 +53,7 @@ define(function (require, exports, module) { $(DocumentManager).on("afterDocumentCreate", function (event, doc) { // Only track documents in the current project - if (ProjectManager.isWithinProject(doc.file.fullPath)) { + if (ProjectManager.isWithinProject(doc.file.getPath())) { self._addListener(doc); } }); @@ -97,7 +97,7 @@ define(function (require, exports, module) { ChangedDocumentTracker.prototype._onChange = function (event, doc) { // if it was already changed, and the client hasn't reset the tracker, // then leave it changed. - this._changedPaths[doc.file.fullPath] = true; + this._changedPaths[doc.file.getPath()] = true; }; /** diff --git a/src/document/Document.js b/src/document/Document.js index 810523c7de7..ed61eb43088 100644 --- a/src/document/Document.js +++ b/src/document/Document.js @@ -256,7 +256,7 @@ define(function (require, exports, module) { * @param {!Date} newTimestamp Timestamp of file at the time we read its new contents from disk. */ Document.prototype.refreshText = function (text, newTimestamp) { - var perfTimerName = PerfUtils.markStart("refreshText:\t" + (!this.file || this.file.fullPath)); + var perfTimerName = PerfUtils.markStart("refreshText:\t" + (!this.file || this.file.getPath())); if (this._masterEditor) { this._masterEditor._resetText(text); @@ -395,16 +395,15 @@ define(function (require, exports, module) { // TODO: (issue #295) fetching timestamp async creates race conditions (albeit unlikely ones) var thisDoc = this; - this.file.getMetadata( - function (metadata) { - thisDoc.diskTimestamp = metadata.modificationTime; + this.file.stat() + .done(function (stat) { + thisDoc.diskTimestamp = stat.mtime; $(exports).triggerHandler("_documentSaved", thisDoc); - }, - function (error) { - console.log("Error updating timestamp after saving file: " + thisDoc.file.fullPath); + }) + .fail(function (err) { + console.log("Error updating timestamp after saving file: " + thisDoc.file.getPath()); $(exports).triggerHandler("_documentSaved", thisDoc); - } - ); + }); }; /* (pretty toString(), to aid debugging) */ @@ -412,7 +411,7 @@ define(function (require, exports, module) { var dirtyInfo = (this.isDirty ? " (dirty!)" : " (clean)"); var editorInfo = (this._masterEditor ? " (Editable)" : " (Non-editable)"); var refInfo = " refs:" + this._refCount; - return "[Document " + this.file.fullPath + dirtyInfo + editorInfo + refInfo + "]"; + return "[Document " + this.file.getPath() + dirtyInfo + editorInfo + refInfo + "]"; }; /** @@ -429,7 +428,7 @@ define(function (require, exports, module) { */ Document.prototype._updateLanguage = function () { var oldLanguage = this.language; - this.language = LanguageManager.getLanguageForPath(this.file.fullPath); + this.language = LanguageManager.getLanguageForPath(this.file.getPath()); if (oldLanguage && oldLanguage !== this.language) { $(this).triggerHandler("languageChanged", [oldLanguage, this.language]); diff --git a/src/document/DocumentCommandHandlers.js b/src/document/DocumentCommandHandlers.js index ddb3f418151..6bab6dc4c5d 100644 --- a/src/document/DocumentCommandHandlers.js +++ b/src/document/DocumentCommandHandlers.js @@ -35,7 +35,6 @@ define(function (require, exports, module) { CommandManager = require("command/CommandManager"), Commands = require("command/Commands"), FileSystem = require("filesystem/FileSystem"), - NativeFileSystem = require("file/NativeFileSystem").NativeFileSystem, ProjectManager = require("project/ProjectManager"), DocumentManager = require("document/DocumentManager"), EditorManager = require("editor/EditorManager"), @@ -78,7 +77,7 @@ define(function (require, exports, module) { if (brackets.inBrowser) { if (currentDoc) { _$title.text(_currentTitlePath); - _$title.attr("title", currentDoc.file.fullPath); + _$title.attr("title", currentDoc.file.getPath()); // dirty dot is always in DOM so layout doesn't change, and visibility is toggled _$dirtydot.css("visibility", (currentDoc.isDirty) ? "visible" : "hidden"); } else { @@ -119,10 +118,10 @@ define(function (require, exports, module) { // TODO: This timer is causing a "Recursive tests with the same name are not supporte" // exception. This code should be removed (if not needed), or updated with a unique // timer name (if needed). - // var perfTimerName = PerfUtils.markStart("DocumentCommandHandlers._onCurrentDocumentChange():\t" + (!newDocument || newDocument.file.fullPath)); + // var perfTimerName = PerfUtils.markStart("DocumentCommandHandlers._onCurrentDocumentChange():\t" + (!newDocument || newDocument.file.getPath())); if (newDocument) { - var fullPath = newDocument.file.fullPath; + var fullPath = newDocument.file.getPath(); // In the main toolbar, show the project-relative path (if the file is inside the current project) // or the full absolute path (if it's not in the project). @@ -141,7 +140,7 @@ define(function (require, exports, module) { function handleDirtyChange(event, changedDoc) { var currentDoc = DocumentManager.getCurrentDocument(); - if (currentDoc && changedDoc.file.fullPath === currentDoc.file.fullPath) { + if (currentDoc && changedDoc.file.getPath() === currentDoc.file.getPath()) { updateTitle(); } } @@ -174,7 +173,7 @@ define(function (require, exports, module) { .fail(function (fileError) { FileUtils.showFileOpenError(fileError.name, fullPath).done(function () { // For performance, we do lazy checking of file existence, so it may be in working set - DocumentManager.removeFromWorkingSet(new NativeFileSystem.FileEntry(fullPath)); + DocumentManager.removeFromWorkingSet(FileSystem.getFileForPath(fullPath)); EditorManager.focusEditor(); result.reject(); }); @@ -206,7 +205,7 @@ define(function (require, exports, module) { //first time through, default to the current project path if (!_defaultOpenDialogFullPath) { - _defaultOpenDialogFullPath = ProjectManager.getProjectRoot().fullPath; + _defaultOpenDialogFullPath = ProjectManager.getProjectRoot().getPath(); } // Prompt the user with a dialog FileSystem.showOpenDialog(true, false, Strings.OPEN_FILE, _defaultOpenDialogFullPath, null) @@ -216,13 +215,13 @@ define(function (require, exports, module) { // they still exist on disk (for faster opening) var filesToOpen = []; paths.forEach(function (file) { - filesToOpen.push(new NativeFileSystem.FileEntry(file)); + filesToOpen.push(FileSystem.getFileForPath(file)); }); DocumentManager.addListToWorkingSet(filesToOpen); doOpen(paths[paths.length - 1]) .done(function (doc) { - var url = PathUtils.parseUrl(doc.file.fullPath); + var url = PathUtils.parseUrl(doc.file.getPath()); //reconstruct the url but use the directory and stop there _defaultOpenDialogFullPath = url.protocol + url.doubleSlash + url.authority + url.directory; @@ -326,7 +325,6 @@ define(function (require, exports, module) { function _getUntitledFileSuggestion(dir, baseFileName, fileExt, isFolder) { var result = new $.Deferred(); var suggestedName = baseFileName + fileExt; - var dirEntry = new NativeFileSystem.DirectoryEntry(dir); result.progress(function attemptNewName(suggestedName, nextIndexToUse) { if (nextIndexToUse > 99) { @@ -334,32 +332,18 @@ define(function (require, exports, module) { result.reject(); return; } - - //check this name - var successCallback = function (entry) { - //file exists, notify to the next progress - result.notify(baseFileName + "-" + nextIndexToUse + fileExt, nextIndexToUse + 1); - }; - var errorCallback = function (error) { - //most likely error is FNF, user is better equiped to handle the rest - result.resolve(suggestedName); - }; - if (isFolder) { - dirEntry.getDirectory( - suggestedName, - {}, - successCallback, - errorCallback - ); - } else { - dirEntry.getFile( - suggestedName, - {}, - successCallback, - errorCallback - ); - } + var path = dir + "/" + suggestedName; + var entry = isFolder ? FileSystem.getDirectoryForPath(path) : FileSystem.getFileForPath(path); + + entry.exists().done(function (exists) { + if (exists) { + //file exists, notify to the next progress + result.notify(baseFileName + "-" + nextIndexToUse + fileExt, nextIndexToUse + 1); + } else { + result.resolve(suggestedName); + } + }); }); //kick it off @@ -396,7 +380,7 @@ define(function (require, exports, module) { var baseDir, selected = ProjectManager.getSelectedItem() || ProjectManager.getProjectRoot(); - baseDir = selected.fullPath; + baseDir = selected.getPath(); if (selected.isFile) { baseDir = baseDir.substr(0, baseDir.lastIndexOf("/")); } @@ -453,37 +437,24 @@ define(function (require, exports, module) { var result = new $.Deferred(); function handleError(error, fileEntry) { - _showSaveFileError(error.name, fileEntry.fullPath) + _showSaveFileError(error.name, fileEntry.getPath()) .done(function () { result.reject(error); }); } if (docToSave && docToSave.isDirty) { - var fileEntry = docToSave.file; + var file = docToSave.file; var writeError = false; - fileEntry.createWriter( - function (writer) { - writer.onwriteend = function () { - // Per spec, onwriteend is called after onerror too - if (!writeError) { - docToSave.notifySaved(); - result.resolve(); - } - }; - writer.onerror = function (error) { - writeError = true; - handleError(error, fileEntry); - }; - - // We don't want normalized line endings, so it's important to pass true to getText() - writer.write(docToSave.getText(true)); - }, - function (error) { - handleError(error, fileEntry); - } - ); + file.write(docToSave.getText(true)) + .done(function () { + docToSave.notifySaved(); + result.resolve(); + }) + .fail(function (err) { + handleError(err, file); + }); } else { result.resolve(); } @@ -532,7 +503,7 @@ define(function (require, exports, module) { return Async.doSequentially( DocumentManager.getWorkingSet(), function (file) { - var doc = DocumentManager.getOpenDocumentForPath(file.fullPath); + var doc = DocumentManager.getOpenDocumentForPath(file.getPath()); if (doc) { return doSave(doc); } else { @@ -554,13 +525,13 @@ define(function (require, exports, module) { function doRevert(doc) { var result = new $.Deferred(); - FileUtils.readAsText(doc.file) + FileUtils.readAsText(doc.file.getPath()) .done(function (text, readTimestamp) { doc.refreshText(text, readTimestamp); result.resolve(); }) .fail(function (error) { - FileUtils.showFileOpenError(error.name, doc.file.fullPath) + FileUtils.showFileOpenError(error.name, doc.file.getPath()) .done(function () { result.reject(error); }); @@ -622,14 +593,12 @@ define(function (require, exports, module) { return doSave(doc); } // now save new document - var newPath = FileUtils.getDirectoryPath(path); // create empty file, FileUtils.writeText will create content. - brackets.fs.writeFile(path, "", NativeFileSystem._FSEncodings.UTF8, function (error) { - if (error) { - result.reject(error); - } else { + var newFile = FileSystem.getFileForPath(path); + newFile.write("") + .done(function (stat) { DocumentManager.getDocumentForPath(path).done(function (newDoc) { - FileUtils.writeText(newDoc.file, doc.getText()).done(function () { + FileUtils.writeText(newDoc.file.getPath(), doc.getText()).done(function () { ProjectManager.refreshFileTree().done(function () { // do not call doRevert unless the file is dirty. // doRevert on a file that is not dirty and not in the working set @@ -645,21 +614,25 @@ define(function (require, exports, module) { }); }); }); - } - }); + }) + .fail(function (err) { + result.reject(err); + }); } // In the future we'll have to check wether the document is an unsaved // untitled focument. If so, we should default to project root. // If the there is no project, default to desktop. if (doc) { - fullPath = doc.file.fullPath; + fullPath = doc.file.getPath(); saveAsDefaultPath = FileUtils.getDirectoryPath(fullPath); defaultName = PathUtils.parseUrl(fullPath).filename; - NativeFileSystem.showSaveDialog(Strings.SAVE_FILE_AS, saveAsDefaultPath, defaultName, - _doSaveAfterSaveDialog, - function (error) { - result.reject(error); + FileSystem.showSaveDialog(Strings.SAVE_FILE_AS, saveAsDefaultPath, defaultName) + .done(function (selection) { + _doSaveAfterSaveDialog(selection); + }) + .fail(function (err) { + result.reject(err); }); } else { result.reject(); @@ -746,11 +719,11 @@ define(function (require, exports, module) { return promise; } - var doc = DocumentManager.getOpenDocumentForPath(file.fullPath); + var doc = DocumentManager.getOpenDocumentForPath(file.getPath()); if (doc && doc.isDirty) { // Document is dirty: prompt to save changes before closing - var filename = PathUtils.parseUrl(doc.file.fullPath).filename; + var filename = PathUtils.parseUrl(doc.file.getPath()).filename; Dialogs.showModalDialog( DefaultDialogs.DIALOG_ID_SAVE_CLOSE, @@ -798,7 +771,7 @@ define(function (require, exports, module) { // Only reload from disk if we've executed the Close for real, // *and* if at least one other view still exists - if (!promptOnly && DocumentManager.getOpenDocumentForPath(file.fullPath)) { + if (!promptOnly && DocumentManager.getOpenDocumentForPath(file.getPath())) { doRevert(doc) .then(result.resolve, result.reject); } else { @@ -832,7 +805,7 @@ define(function (require, exports, module) { var unsavedDocs = []; DocumentManager.getWorkingSet().forEach(function (file) { - var doc = DocumentManager.getOpenDocumentForPath(file.fullPath); + var doc = DocumentManager.getOpenDocumentForPath(file.getPath()); if (doc && doc.isDirty) { unsavedDocs.push(doc); } @@ -860,7 +833,7 @@ define(function (require, exports, module) { message += "
    "; unsavedDocs.forEach(function (doc) { message += "
  • " + - StringUtils.breakableUrl(ProjectManager.makeProjectRelativeIfPossible(doc.file.fullPath)) + + StringUtils.breakableUrl(ProjectManager.makeProjectRelativeIfPossible(doc.file.getPath())) + "
  • "; }); message += "
"; @@ -1045,7 +1018,7 @@ define(function (require, exports, module) { var file = DocumentManager.getNextPrevFile(inc); if (file) { DocumentManager.beginDocumentNavigation(); - CommandManager.execute(Commands.FILE_OPEN, { fullPath: file.fullPath }); + CommandManager.execute(Commands.FILE_OPEN, { fullPath: file.getPath() }); // Listen for ending of Ctrl+Tab sequence if (!_addedNavKeyHandler) { @@ -1075,9 +1048,9 @@ define(function (require, exports, module) { function handleShowInOS() { var entry = ProjectManager.getSelectedItem(); if (entry) { - brackets.app.showOSFolder(entry.fullPath, function (err) { + brackets.app.showOSFolder(entry.getPath(), function (err) { if (err) { - console.error("Error showing '" + entry.fullPath + "' in OS folder:", err); + console.error("Error showing '" + entry.getPath() + "' in OS folder:", err); } }); } diff --git a/src/document/DocumentManager.js b/src/document/DocumentManager.js index 9b4c930a561..3683ba2a908 100644 --- a/src/document/DocumentManager.js +++ b/src/document/DocumentManager.js @@ -86,7 +86,7 @@ define(function (require, exports, module) { "use strict"; var DocumentModule = require("document/Document"), - NativeFileSystem = require("file/NativeFileSystem").NativeFileSystem, + FileSystem = require("filesystem/FileSystem"), ProjectManager = require("project/ProjectManager"), EditorManager = require("editor/EditorManager"), FileSyncManager = require("project/FileSyncManager"), @@ -150,7 +150,7 @@ define(function (require, exports, module) { var _documentNavPending = false; /** - * All documents with refCount > 0. Maps Document.file.fullPath -> Document. + * All documents with refCount > 0. Maps Document.file.getPath() -> Document. * @private * @type {Object.} */ @@ -185,7 +185,7 @@ define(function (require, exports, module) { list = list || _workingSet; return CollectionUtils.indexOf(list, function (file, i) { - return file.fullPath === fullPath; + return file.getPath() === fullPath; }); } @@ -224,17 +224,13 @@ define(function (require, exports, module) { */ function addToWorkingSet(file) { // If doc is already in working set, don't add it again - if (findInWorkingSet(file.fullPath) !== -1) { + if (findInWorkingSet(file.getPath()) !== -1) { return; } - - // Add to _workingSet making sure we store a different instance from the - // one in the Document. See issue #1971 for more details. - file = new NativeFileSystem.FileEntry(file.fullPath); _workingSet.push(file); // Add to MRU order: either first or last, depending on whether it's already the current doc or not - if (_currentDocument && _currentDocument.file.fullPath === file.fullPath) { + if (_currentDocument && _currentDocument.file.getPath() === file.getPath()) { _workingSetMRUOrder.unshift(file); } else { _workingSetMRUOrder.push(file); @@ -260,14 +256,14 @@ define(function (require, exports, module) { // Process only files not already in working set fileList.forEach(function (file, index) { // If doc is already in working set, don't add it again - if (findInWorkingSet(file.fullPath) === -1) { + if (findInWorkingSet(file.getPath()) === -1) { uniqueFileList.push(file); // Add _workingSet.push(file); // Add to MRU order: either first or last, depending on whether it's already the current doc or not - if (_currentDocument && _currentDocument.file.fullPath === file.fullPath) { + if (_currentDocument && _currentDocument.file.getPath() === file.getPath()) { _workingSetMRUOrder.unshift(file); } else { _workingSetMRUOrder.push(file); @@ -290,15 +286,15 @@ define(function (require, exports, module) { */ function removeFromWorkingSet(file) { // If doc isn't in working set, do nothing - var index = findInWorkingSet(file.fullPath); + var index = findInWorkingSet(file.getPath()); if (index === -1) { return; } // Remove _workingSet.splice(index, 1); - _workingSetMRUOrder.splice(findInWorkingSet(file.fullPath, _workingSetMRUOrder), 1); - _workingSetAddedOrder.splice(findInWorkingSet(file.fullPath, _workingSetAddedOrder), 1); + _workingSetMRUOrder.splice(findInWorkingSet(file.getPath(), _workingSetMRUOrder), 1); + _workingSetAddedOrder.splice(findInWorkingSet(file.getPath(), _workingSetAddedOrder), 1); // Dispatch event $(exports).triggerHandler("workingSetRemove", file); @@ -324,7 +320,7 @@ define(function (require, exports, module) { * @param {!Document} */ function _markMostRecent(doc) { - var mruI = findInWorkingSet(doc.file.fullPath, _workingSetMRUOrder); + var mruI = findInWorkingSet(doc.file.getPath(), _workingSetMRUOrder); if (mruI !== -1) { _workingSetMRUOrder.splice(mruI, 1); _workingSetMRUOrder.unshift(doc.file); @@ -399,7 +395,7 @@ define(function (require, exports, module) { } if (_currentDocument) { - var mruI = findInWorkingSet(_currentDocument.file.fullPath, _workingSetMRUOrder); + var mruI = findInWorkingSet(_currentDocument.file.getPath(), _workingSetMRUOrder); if (mruI === -1) { // If doc not in working set, return most recent working set item if (_workingSetMRUOrder.length > 0) { @@ -438,11 +434,11 @@ define(function (require, exports, module) { return; } - var perfTimerName = PerfUtils.markStart("setCurrentDocument:\t" + doc.file.fullPath); + var perfTimerName = PerfUtils.markStart("setCurrentDocument:\t" + doc.file.getPath()); // If file not within project tree, add it to working set right now (don't wait for it to // become dirty) - if (!ProjectManager.isWithinProject(doc.file.fullPath)) { + if (!ProjectManager.isWithinProject(doc.file.getPath())) { addToWorkingSet(doc.file); } @@ -487,20 +483,20 @@ define(function (require, exports, module) { function closeFullEditor(file, skipAutoSelect) { // If this was the current document shown in the editor UI, we're going to switch to a // different document (or none if working set has no other options) - if (_currentDocument && _currentDocument.file.fullPath === file.fullPath) { + if (_currentDocument && _currentDocument.file.getPath() === file.getPath()) { // Get next most recent doc in the MRU order var nextFile = getNextPrevFile(1); - if (nextFile && nextFile.fullPath === _currentDocument.file.fullPath) { + if (nextFile && nextFile.getPath() === _currentDocument.file.getPath()) { // getNextPrevFile() might return the file we're about to close if it's the only one open (due to wraparound) nextFile = null; } // Switch editor to next document (or blank it out) if (nextFile && !skipAutoSelect) { - CommandManager.execute(Commands.FILE_OPEN, { fullPath: nextFile.fullPath }) + CommandManager.execute(Commands.FILE_OPEN, { fullPath: nextFile.getPath() }) .done(function () { // (Now we're guaranteed that the current document is not the one we're closing) - console.assert(!(_currentDocument && _currentDocument.file.fullPath === file.fullPath)); + console.assert(!(_currentDocument && _currentDocument.file.getPath() === file.getPath())); }) .fail(function () { // File chosen to be switched to could not be opened, and the original file @@ -577,34 +573,40 @@ define(function (require, exports, module) { var result = new $.Deferred(), promise = result.promise(); - // log this document's Promise as pending - getDocumentForPath._pendingDocumentPromises[fullPath] = promise; - - // create a new document - var fileEntry = new NativeFileSystem.FileEntry(fullPath), - perfTimerName = PerfUtils.markStart("getDocumentForPath:\t" + fullPath); - - result.done(function () { - PerfUtils.addMeasurement(perfTimerName); - }).fail(function () { - PerfUtils.finalizeMeasurement(perfTimerName); - }); - - FileUtils.readAsText(fileEntry) - .always(function () { - // document is no longer pending - delete getDocumentForPath._pendingDocumentPromises[fullPath]; - }) - .done(function (rawText, readTimestamp) { - doc = new DocumentModule.Document(fileEntry, readTimestamp, rawText); + FileSystem.pathExists(fullPath) + .done(function () { + // log this document's Promise as pending + getDocumentForPath._pendingDocumentPromises[fullPath] = promise; + + // create a new document + var file = FileSystem.getFileForPath(fullPath), + perfTimerName = PerfUtils.markStart("getDocumentForPath:\t" + fullPath); + + result.done(function () { + PerfUtils.addMeasurement(perfTimerName); + }).fail(function () { + PerfUtils.finalizeMeasurement(perfTimerName); + }); + + FileUtils.readAsText(fullPath) + .always(function () { + // document is no longer pending + delete getDocumentForPath._pendingDocumentPromises[fullPath]; + }) + .done(function (rawText, readTimestamp) { + doc = new DocumentModule.Document(file, readTimestamp, rawText); + + // This is a good point to clean up any old dangling Documents + _gcDocuments(); - // This is a good point to clean up any old dangling Documents - _gcDocuments(); - - result.resolve(doc); + result.resolve(doc); + }) + .fail(function (fileError) { + result.reject(fileError); + }); }) - .fail(function (fileError) { - result.reject(fileError); + .fail(function () { + result.reject(); // TODO: FileSystem error not found }); return promise; @@ -653,7 +655,7 @@ define(function (require, exports, module) { closeFullEditor(file, skipAutoSelect); // Notify all other editors to close as well - var doc = getOpenDocumentForPath(file.fullPath); + var doc = getOpenDocumentForPath(file.getPath()); if (doc) { $(doc).triggerHandler("deleted"); } @@ -683,20 +685,20 @@ define(function (require, exports, module) { workingSet.forEach(function (file, index) { // flag the currently active editor - isActive = currentDoc && (file.fullPath === currentDoc.file.fullPath); + isActive = currentDoc && (file.getPath() === currentDoc.file.getPath()); // save editor UI state for just the working set - var viewState = EditorManager._getViewState(file.fullPath); + var viewState = EditorManager._getViewState(file.getPath()); files.push({ - file: file.fullPath, + file: file.getPath(), active: isActive, viewState: viewState }); }); // append file root to make file list unique for each project - _prefs.setValue("files_" + projectRoot.fullPath, files); + _prefs.setValue("files_" + projectRoot.getPath(), files); } /** @@ -706,7 +708,7 @@ define(function (require, exports, module) { function _projectOpen(e) { // file root is appended for each project var projectRoot = ProjectManager.getProjectRoot(), - files = _prefs.getValue("files_" + projectRoot.fullPath); + files = _prefs.getValue("files_" + projectRoot.getPath()); if (!files) { return; @@ -719,7 +721,7 @@ define(function (require, exports, module) { // Add all files to the working set without verifying that // they still exist on disk (for faster project switching) files.forEach(function (value, index) { - filesToOpen.push(new NativeFileSystem.FileEntry(value.file)); + filesToOpen.push(FileSystem.getFileForPath(value.file)); if (value.active) { activeFile = value.file; } @@ -734,7 +736,7 @@ define(function (require, exports, module) { // Initialize the active editor if (!activeFile && _workingSet.length > 0) { - activeFile = _workingSet[0].fullPath; + activeFile = _workingSet[0].getPath(); } if (activeFile) { @@ -842,22 +844,22 @@ define(function (require, exports, module) { // For compatibility $(DocumentModule) .on("_afterDocumentCreate", function (event, doc) { - if (_openDocuments[doc.file.fullPath]) { + if (_openDocuments[doc.file.getPath()]) { console.error("Document for this path already in _openDocuments!"); return true; } - _openDocuments[doc.file.fullPath] = doc; + _openDocuments[doc.file.getPath()] = doc; $(exports).triggerHandler("afterDocumentCreate", doc); }) .on("_beforeDocumentDelete", function (event, doc) { - if (!_openDocuments[doc.file.fullPath]) { + if (!_openDocuments[doc.file.getPath()]) { console.error("Document with references was not in _openDocuments!"); return true; } $(exports).triggerHandler("beforeDocumentDelete", doc); - delete _openDocuments[doc.file.fullPath]; + delete _openDocuments[doc.file.getPath()]; }) .on("_documentRefreshed", function (event, doc) { $(exports).triggerHandler("documentRefreshed", doc); diff --git a/src/editor/Editor.js b/src/editor/Editor.js index f1d34aba353..0667faa7228 100644 --- a/src/editor/Editor.js +++ b/src/editor/Editor.js @@ -680,7 +680,7 @@ define(function (require, exports, module) { * @param {!string} text */ Editor.prototype._resetText = function (text) { - var perfTimerName = PerfUtils.markStart("Editor._resetText()\t" + (!this.document || this.document.file.fullPath)); + var perfTimerName = PerfUtils.markStart("Editor._resetText()\t" + (!this.document || this.document.file.getPath())); var cursorPos = this.getCursorPos(), scrollPos = this.getScrollPos(); diff --git a/src/editor/EditorManager.js b/src/editor/EditorManager.js index a630c5c2301..25ae1c1c431 100644 --- a/src/editor/EditorManager.js +++ b/src/editor/EditorManager.js @@ -334,7 +334,7 @@ define(function (require, exports, module) { // If outgoing editor is no longer needed, dispose it var isCurrentDocument = (DocumentManager.getCurrentDocument() === document); - var isInWorkingSet = (DocumentManager.findInWorkingSet(document.file.fullPath) !== -1); + var isInWorkingSet = (DocumentManager.findInWorkingSet(document.file.getPath()) !== -1); if (!isCurrentDocument && !isInWorkingSet) { // Destroy the editor widget (which un-refs the Document and reverts it to read-only mode) editor.destroy(); @@ -422,7 +422,7 @@ define(function (require, exports, module) { /** Updates _viewStateCache from the given editor's actual current state */ function _saveEditorViewState(editor) { - _viewStateCache[editor.document.file.fullPath] = { + _viewStateCache[editor.document.file.getPath()] = { selection: editor.getSelection(), scrollPos: editor.getScrollPos() }; @@ -431,7 +431,7 @@ define(function (require, exports, module) { /** Updates the given editor's actual state from _viewStateCache, if any state stored */ function _restoreEditorViewState(editor) { // We want to ignore the current state of the editor, so don't call _getViewState() - var viewState = _viewStateCache[editor.document.file.fullPath]; + var viewState = _viewStateCache[editor.document.file.getPath()]; if (viewState) { if (viewState.selection) { editor.setSelection(viewState.selection.start, viewState.selection.end); @@ -444,7 +444,7 @@ define(function (require, exports, module) { /** Returns up-to-date view state for the given file, or null if file not open and no state cached */ function _getViewState(fullPath) { - if (_currentEditorsDocument && _currentEditorsDocument.file.fullPath === fullPath) { + if (_currentEditorsDocument && _currentEditorsDocument.file.getPath() === fullPath) { _saveEditorViewState(_currentEditor); } return _viewStateCache[fullPath]; @@ -525,7 +525,7 @@ define(function (require, exports, module) { var doc = DocumentManager.getCurrentDocument(), container = _editorHolder.get(0); - var perfTimerName = PerfUtils.markStart("EditorManager._onCurrentDocumentChange():\t" + (!doc || doc.file.fullPath)); + var perfTimerName = PerfUtils.markStart("EditorManager._onCurrentDocumentChange():\t" + (!doc || doc.file.getPath())); // Update the UI to show the right editor (or nothing), and also dispose old editor if no // longer needed. @@ -544,7 +544,7 @@ define(function (require, exports, module) { // didn't change: removing a document from the working set (via the "X" button). (This may // also cover the case where the document WAS current, if the editor-swap happens before the // removal from the working set. - var doc = DocumentManager.getOpenDocumentForPath(removedFile.fullPath); + var doc = DocumentManager.getOpenDocumentForPath(removedFile.getPath()); if (doc) { _destroyEditorIfUnneeded(doc); } diff --git a/src/editor/InlineTextEditor.js b/src/editor/InlineTextEditor.js index 5e44338980e..94a35a5f139 100644 --- a/src/editor/InlineTextEditor.js +++ b/src/editor/InlineTextEditor.js @@ -66,7 +66,7 @@ define(function (require, exports, module) { $dirtyIndicators.each(function (index, indicator) { $indicator = $(this); - if ($indicator.data("fullPath") === doc.file.fullPath) { + if ($indicator.data("fullPath") === doc.file.getPath()) { _showDirtyIndicator($indicator, doc.isDirty); } }); @@ -214,19 +214,19 @@ define(function (require, exports, module) { var $dirtyIndicatorDiv = $("
") .addClass("dirty-indicator") .width(0); // initialize indicator as hidden - $dirtyIndicatorDiv.data("fullPath", doc.file.fullPath); + $dirtyIndicatorDiv.data("fullPath", doc.file.getPath()); this.$lineNumber = $(""); // wrap filename & line number in clickable link with tooltip $filenameInfo.append($dirtyIndicatorDiv) - .append(doc.file.name + " : ") + .append(doc.file.getName() + " : ") .append(this.$lineNumber) - .attr("title", doc.file.fullPath); + .attr("title", doc.file.getPath()); // clicking filename jumps to full editor view $filenameInfo.click(function () { - CommandManager.execute(Commands.FILE_OPEN, { fullPath: doc.file.fullPath }) + CommandManager.execute(Commands.FILE_OPEN, { fullPath: doc.file.getPath() }) .done(function () { EditorManager.getCurrentFullEditor().setCursorPos(startLine, 0, true); }); diff --git a/src/editor/MultiRangeInlineEditor.js b/src/editor/MultiRangeInlineEditor.js index cb293f1155f..fe4e88303ed 100644 --- a/src/editor/MultiRangeInlineEditor.js +++ b/src/editor/MultiRangeInlineEditor.js @@ -74,7 +74,7 @@ define(function (require, exports, module) { SearchResultItem.prototype.$listItem = null; function _updateRangeLabel(listItem, range) { - var text = range.name + " " + range.textRange.document.file.name + " : " + (range.textRange.startLine + 1); + var text = range.name + " " + range.textRange.document.file.getName() + " : " + (range.textRange.startLine + 1); listItem.text(text); listItem.attr("title", text); } diff --git a/src/extensibility/ExtensionManager.js b/src/extensibility/ExtensionManager.js index 9b1ec276fc8..f3d9ab40e80 100644 --- a/src/extensibility/ExtensionManager.js +++ b/src/extensibility/ExtensionManager.js @@ -38,7 +38,6 @@ define(function (require, exports, module) { "use strict"; var FileUtils = require("file/FileUtils"), - NativeFileSystem = require("file/NativeFileSystem").NativeFileSystem, Package = require("extensibility/Package"), ExtensionLoader = require("utils/ExtensionLoader"), Strings = require("strings"), @@ -133,7 +132,7 @@ define(function (require, exports, module) { */ function _loadPackageJson(folder) { var result = new $.Deferred(); - FileUtils.readAsText(new NativeFileSystem.FileEntry(folder + "/package.json")) + FileUtils.readAsText(folder + "/package.json") .done(function (text) { try { var json = JSON.parse(text); diff --git a/src/extensibility/ExtensionManagerViewModel.js b/src/extensibility/ExtensionManagerViewModel.js index 0a6152a2a4a..346f0059889 100644 --- a/src/extensibility/ExtensionManagerViewModel.js +++ b/src/extensibility/ExtensionManagerViewModel.js @@ -29,6 +29,7 @@ define(function (require, exports, module) { "use strict"; var ExtensionManager = require("extensibility/ExtensionManager"), + FileSystem = require("filesystem/FileSystem"), Async = require("utils/Async"), Package = require("extensibility/Package"), registry_utils = require("extensibility/registry_utils"); @@ -131,7 +132,7 @@ define(function (require, exports, module) { Object.keys(this._idsToUpdate).forEach(function (id) { var filename = this._idsToUpdate[id].localPath; if (filename) { - brackets.fs.unlink(filename, function () { }); + FileSystem.getFileForPath(filename).unlink(); } }.bind(this)); }; @@ -381,8 +382,7 @@ define(function (require, exports, module) { return; } if (installationResult.localPath) { - brackets.fs.unlink(installationResult.localPath, function () { - }); + FileSystem.getFileForPath(installationResult.localPath).unlink(); } delete this._idsToUpdate[id]; $(this).triggerHandler("change", [id]); diff --git a/src/extensibility/InstallExtensionDialog.js b/src/extensibility/InstallExtensionDialog.js index cc0b0392635..b1e6322171a 100644 --- a/src/extensibility/InstallExtensionDialog.js +++ b/src/extensibility/InstallExtensionDialog.js @@ -35,6 +35,7 @@ define(function (require, exports, module) { Strings = require("strings"), Commands = require("command/Commands"), CommandManager = require("command/CommandManager"), + FileSystem = require("filesystem/FileSystem"), KeyEvent = require("utils/KeyEvent"), Package = require("extensibility/Package"), NativeApp = require("utils/NativeApp"), @@ -254,9 +255,7 @@ define(function (require, exports, module) { // and the user cancels, we can delete the downloaded file. if (this._installResult && this._installResult.localPath) { var filename = this._installResult.localPath; - brackets.fs.unlink(filename, function () { - // ignore the result - }); + FileSystem.getFileForPath(filename).unlink(); } this._enterState(STATE_CLOSED); } else if (this._state !== STATE_CANCELING_INSTALL) { diff --git a/src/extensibility/Package.js b/src/extensibility/Package.js index 32db9007f8f..f767a77f33a 100644 --- a/src/extensibility/Package.js +++ b/src/extensibility/Package.js @@ -36,6 +36,7 @@ define(function (require, exports, module) { StringUtils = require("utils/StringUtils"), Strings = require("strings"), ExtensionLoader = require("utils/ExtensionLoader"), + FileSystem = require("filesystem/FileSystem"), NodeConnection = require("utils/NodeConnection"); var Errors = { @@ -328,9 +329,7 @@ define(function (require, exports, module) { result.localPath = downloadResult.localPath; d.resolve(result); } else { - brackets.fs.unlink(downloadResult.localPath, function (err) { - // ignore errors - }); + FileSystem.getFileForPath(downloadResult.localPath).unlink(); if (result.errors && result.errors.length > 0) { // Validation errors - for now, only return the first one state = STATE_FAILED; @@ -349,9 +348,7 @@ define(function (require, exports, module) { .fail(function (err) { // File IO errors, internal error in install()/validate(), or extension startup crashed state = STATE_FAILED; - brackets.fs.unlink(downloadResult.localPath, function (err) { - // ignore errors - }); + FileSystem.getFileForPath(downloadResult.localPath).unlink(); d.reject(err); // TODO: needs to be err.message ? }); }) @@ -444,7 +441,7 @@ define(function (require, exports, module) { d.reject(error); }) .always(function () { - brackets.fs.unlink(path, function () { }); + FileSystem.getFileForPath(path).unlink(); }); return d.promise(); } diff --git a/src/extensions/default/DebugCommands/main.js b/src/extensions/default/DebugCommands/main.js index 065da8445ff..8d3916c5bea 100644 --- a/src/extensions/default/DebugCommands/main.js +++ b/src/extensions/default/DebugCommands/main.js @@ -34,7 +34,7 @@ define(function (require, exports, module) { Menus = brackets.getModule("command/Menus"), Editor = brackets.getModule("editor/Editor").Editor, FileUtils = brackets.getModule("file/FileUtils"), - NativeFileSystem = brackets.getModule("file/NativeFileSystem").NativeFileSystem, + FileSystem = brackets.getModule("filesystem/FileSystem"), ProjectManager = brackets.getModule("project/ProjectManager"), PerfUtils = brackets.getModule("utils/PerfUtils"), NativeApp = brackets.getModule("utils/NativeApp"), @@ -140,8 +140,9 @@ define(function (require, exports, module) { function _handleSwitchLanguage() { var stringsPath = FileUtils.getNativeBracketsDirectoryPath() + "/nls"; - NativeFileSystem.requestNativeFileSystem(stringsPath, function (fs) { - fs.root.createReader().readEntries(function (entries) { + + FileSystem.getDirectoryContents(FileSystem.getDirectoryForPath(stringsPath)) + .done(function (contents) { var $dialog, $submit, $select, @@ -170,12 +171,12 @@ define(function (require, exports, module) { languages.push({label: getLocalizedLabel("en"), language: "en"}); // inspect all children of dirEntry - entries.forEach(function (entry) { - if (entry.isDirectory) { - var match = entry.name.match(/^([a-z]{2})(-[a-z]{2})?$/); + contents.forEach(function (entry) { + if (entry.isDirectory()) { + var match = entry.getName().match(/^([a-z]{2})(-[a-z]{2})?$/); if (match) { - var language = entry.name, + var language = entry.getName(), label = match[1]; if (match[2]) { @@ -203,7 +204,6 @@ define(function (require, exports, module) { $select.on("change", setLanguage).val(curLocale); }); - }); } function _enableRunTestsMenuItem() { @@ -212,18 +212,15 @@ define(function (require, exports, module) { } // Check for the SpecRunner.html file - var fileEntry = new NativeFileSystem.FileEntry( + var file = FileSystem.getFileForPath( FileUtils.getNativeBracketsDirectoryPath() + "/../test/SpecRunner.html" ); - fileEntry.getMetadata( - function (metadata) { - // If we sucessfully got the metadata for the SpecRunner.html file, - // enable the menu item + file.exists().done(function (exists) { + if (exists) { CommandManager.get(DEBUG_RUN_UNIT_TESTS).setEnabled(true); - }, - function (error) {} /* menu already disabled, ignore errors */ - ); + } + }); } diff --git a/src/extensions/default/JSLint/main.js b/src/extensions/default/JSLint/main.js index 3f9d9e2460f..fd58f72afb6 100644 --- a/src/extensions/default/JSLint/main.js +++ b/src/extensions/default/JSLint/main.js @@ -116,10 +116,10 @@ define(function (require, exports, module) { var perfTimerDOM, perfTimerLint; - var language = currentDoc ? LanguageManager.getLanguageForPath(currentDoc.file.fullPath) : ""; + var language = currentDoc ? LanguageManager.getLanguageForPath(currentDoc.file.getPath()) : ""; if (_enabled && language && language.getId() === "javascript") { - perfTimerLint = PerfUtils.markStart("JSLint linting:\t" + (!currentDoc || currentDoc.file.fullPath)); + perfTimerLint = PerfUtils.markStart("JSLint linting:\t" + (!currentDoc || currentDoc.file.getPath())); var text = currentDoc.getText(); // If a line contains only whitespace, remove the whitespace @@ -136,7 +136,7 @@ define(function (require, exports, module) { var result = JSLINT(text, null); PerfUtils.addMeasurement(perfTimerLint); - perfTimerDOM = PerfUtils.markStart("JSLint DOM:\t" + (!currentDoc || currentDoc.file.fullPath)); + perfTimerDOM = PerfUtils.markStart("JSLint DOM:\t" + (!currentDoc || currentDoc.file.getPath())); if (!result) { // Remove the null errors for the template diff --git a/src/extensions/default/JavaScriptCodeHints/ScopeManager.js b/src/extensions/default/JavaScriptCodeHints/ScopeManager.js index 0ca66ed183a..2294ff85f88 100644 --- a/src/extensions/default/JavaScriptCodeHints/ScopeManager.js +++ b/src/extensions/default/JavaScriptCodeHints/ScopeManager.js @@ -36,12 +36,11 @@ define(function (require, exports, module) { var DocumentManager = brackets.getModule("document/DocumentManager"), LanguageManager = brackets.getModule("language/LanguageManager"), - NativeFileSystem = brackets.getModule("file/NativeFileSystem").NativeFileSystem, + FileSystem = brackets.getModule("filesystem/FileSystem"), ProjectManager = brackets.getModule("project/ProjectManager"), CollectionUtils = brackets.getModule("utils/CollectionUtils"), ExtensionUtils = brackets.getModule("utils/ExtensionUtils"), FileUtils = brackets.getModule("file/FileUtils"), - FileIndexManager = brackets.getModule("project/FileIndexManager"), HintUtils = require("HintUtils"), MessageIds = require("MessageIds"), Preferences = require("Preferences"); @@ -80,15 +79,11 @@ define(function (require, exports, module) { library; files.forEach(function (i) { - NativeFileSystem.resolveNativeFileSystemPath(path + i, function (fileEntry) { - FileUtils.readAsText(fileEntry).done(function (text) { - library = JSON.parse(text); - builtinLibraryNames.push(library["!name"]); - ternEnvironment.push(library); - }).fail(function (error) { - console.log("failed to read tern config file " + i); - }); - }, function (error) { + FileUtils.readAsText(path + i).done(function (text) { + library = JSON.parse(text); + builtinLibraryNames.push(library["!name"]); + ternEnvironment.push(library); + }).fail(function (error) { console.log("failed to read tern config file " + i); }); }); @@ -117,33 +112,28 @@ define(function (require, exports, module) { // Normally there is a project root, but for unit tests we need to // pass in a project root. if (pr) { - projectRootPath = pr.fullPath; + projectRootPath = pr.getPath(); } else if (!projectRootPath) { console.log("initPreferences: projectRootPath has no value"); } var path = projectRootPath + Preferences.FILE_NAME; - NativeFileSystem.resolveNativeFileSystemPath(path, function (fileEntry) { - FileUtils.readAsText(fileEntry).done(function (text) { - var configObj = null; - try { - configObj = JSON.parse(text); - } catch (e) { - // continue with null configObj which will result in - // default settings. - console.log("Error parsing preference file: " + path); - if (e instanceof SyntaxError) { - console.log(e.message); - } + FileUtils.readAsText(path).done(function (text) { + var configObj = null; + try { + configObj = JSON.parse(text); + } catch (e) { + // continue with null configObj which will result in + // default settings. + console.log("Error parsing preference file: " + path); + if (e instanceof SyntaxError) { + console.log(e.message); } - preferences = new Preferences(configObj); - deferredPreferences.resolve(); - }).fail(function (error) { - preferences = new Preferences(); - deferredPreferences.resolve(); - }); - }, function (error) { + } + preferences = new Preferences(configObj); + deferredPreferences.resolve(); + }).fail(function (error) { preferences = new Preferences(); deferredPreferences.resolve(); }); @@ -183,18 +173,17 @@ define(function (require, exports, module) { * @param {!function(string)=} errorCallback - Callback for errors (optional). */ function forEachFileInDirectory(dir, doneCallback, fileCallback, directoryCallback, errorCallback) { - var files = []; - - NativeFileSystem.resolveNativeFileSystemPath(dir, function (dirEntry) { - var reader = dirEntry.createReader(); + var directory = FileSystem.getDirectoryForPath(dir), + files = []; - reader.readEntries(function (entries) { - entries.slice(0, preferences.getMaxFileCount()).forEach(function (entry) { - var path = entry.fullPath, + FileSystem.getDirectoryContents(directory) + .done(function (contents) { + contents.slice(0, preferences.getMaxFileCount()).forEach(function (entry) { + var path = entry.getPath(), split = HintUtils.splitPath(path), file = split.file; - if (fileCallback && entry.isFile) { + if (fileCallback && entry.isFile()) { if (file.indexOf(".") > 0) { // ignore .dotfiles var languageID = LanguageManager.getLanguageForPath(path).getId(); @@ -202,26 +191,21 @@ define(function (require, exports, module) { fileCallback(path); } } - } else if (directoryCallback && entry.isDirectory) { + } else if (directoryCallback && entry.isDirectory()) { var dirName = HintUtils.splitPath(split.dir).file; if (dirName.indexOf(".") !== 0) { // ignore .dotfiles - directoryCallback(entry.fullPath); + directoryCallback(entry.getPath()); } } }); doneCallback(); - }, function (err) { + }) + .fail(function (err) { if (errorCallback) { errorCallback(err); } - console.log("Unable to refresh directory: " + err); + console.log("Directory \"%s\" does not exist", dir); }); - }, function (err) { - if (errorCallback) { - errorCallback(err); - } - console.log("Directory \"%s\" does not exist", dir); - }); } /** @@ -410,7 +394,7 @@ define(function (require, exports, module) { * has completed. */ function requestJumptoDef(session, document, offset) { - var path = document.file.fullPath, + var path = document.file.getPath(), fileInfo = {type: MessageIds.TERN_FILE_INFO_TYPE_FULL, name: path, offsetLines: 0, @@ -554,7 +538,7 @@ define(function (require, exports, module) { to = {line: endLine, ch: endCh}; return {type: MessageIds.TERN_FILE_INFO_TYPE_PART, - name: document.file.fullPath, + name: document.file.getPath(), offsetLines: from.line, text: document.getRange(from, to)}; } @@ -572,7 +556,7 @@ define(function (require, exports, module) { var start = session.getCursor(), end = start, document = session.editor.document, - path = document.file.fullPath, + path = document.file.getPath(), isHtmlFile = LanguageManager.getLanguageForPath(path).getId() === "html", result; @@ -782,7 +766,7 @@ define(function (require, exports, module) { * @return {jQuery.Promise} - the promise for the request */ function updateTernFile(document) { - var path = document.file.fullPath; + var path = document.file.getPath(); _postMessageByPass({ type : MessageIds.TERN_UPDATE_FILE_MSG, @@ -838,29 +822,28 @@ define(function (require, exports, module) { function findNameInProject() { // check for any files in project that end with the right path. var fileName = HintUtils.splitPath(name).file; - FileIndexManager.getFilenameMatches("all", fileName) - .done(function (files) { - var file; - files = files.filter(function (file) { - var pos = file.fullPath.length - name.length; - return pos === file.fullPath.lastIndexOf(name); - }); - - if (files.length === 1) { - file = files[0]; - } - if (file) { - getDocText(file.fullPath).fail(function () { - replyWith(name, ""); - }); - } else { - replyWith(name, ""); - } - - }) - .fail(function () { + + var files = FileSystem.getFileList(function (file) { + return file.getName() === fileName; + }); + + var file; + files = files.filter(function (file) { + var pos = file.getPath().length - name.length; + return pos === file.getPath().lastIndexOf(name); + }); + + if (files.length === 1) { + file = files[0]; + } + if (file) { + getDocText(file.getPath()).fail(function () { replyWith(name, ""); }); + } else { + replyWith(name, ""); + } + } getDocText(name).fail(function () { @@ -1104,7 +1087,7 @@ define(function (require, exports, module) { * @param {Document} previousDocument - the document the editor has changed from */ function doEditorChange(session, document, previousDocument) { - var path = document.file.fullPath, + var path = document.file.getPath(), split = HintUtils.splitPath(path), dir = split.dir, files = [], @@ -1115,7 +1098,7 @@ define(function (require, exports, module) { documentChanges = null; addFilesPromise = addFilesDeferred.promise(); - pr = ProjectManager.getProjectRoot() ? ProjectManager.getProjectRoot().fullPath : null; + pr = ProjectManager.getProjectRoot() ? ProjectManager.getProjectRoot().getPath() : null; // avoid re-initializing tern if possible. if (canSkipTernInitialization(path)) { diff --git a/src/extensions/default/JavaScriptCodeHints/Session.js b/src/extensions/default/JavaScriptCodeHints/Session.js index d45a009b1c9..2e896b4e8a5 100644 --- a/src/extensions/default/JavaScriptCodeHints/Session.js +++ b/src/extensions/default/JavaScriptCodeHints/Session.js @@ -43,7 +43,7 @@ define(function (require, exports, module) { */ function Session(editor) { this.editor = editor; - this.path = editor.document.file.fullPath; + this.path = editor.document.file.getPath(); this.ternHints = []; this.ternGuesses = null; this.fnType = null; @@ -593,7 +593,7 @@ define(function (require, exports, module) { * @return {string} - the "javascript" text that can be sent to Tern. */ Session.prototype.getJavascriptText = function () { - if (LanguageManager.getLanguageForPath(this.editor.document.file.fullPath).getId() === "html") { + if (LanguageManager.getLanguageForPath(this.editor.document.file.getPath()).getId() === "html") { // HTML file - need to send back only the bodies of the // \n

Please close this window.

',t.writeHead(200,{"Content-Length":e.length,"Content-Type":"text/html"}),t.write(e),t.end()},t.prototype.sendFavicon=function(t){return this.fs.readFile(this.faviconFile,function(e,r){return t.writeHead(200,{"Content-Length":r.length,"Content-Type":"image/x-icon"}),t.write(r),t.end()})},t}(),v=function(t,e){return c(E(A(t),A(e),t.length,e.length))},m=function(t){return c(T(A(t),t.length))},"undefined"!=typeof require)try{S=require("crypto"),S.createHmac&&S.createHash&&(v=function(t,e){var r;return r=S.createHmac("sha1",e),r.update(t),r.digest("base64")},m=function(t){var e;return e=S.createHash("sha1"),e.update(t),e.digest("base64")})}catch(X){C=X}if(t.Util.hmac=v,t.Util.sha1=m,E=function(t,e,r,n){var o,i,s,a;return e.length>16&&(e=T(e,n)),s=function(){var t,r;for(r=[],i=t=0;16>t;i=++t)r.push(909522486^e[i]);return r}(),a=function(){var t,r;for(r=[],i=t=0;16>t;i=++t)r.push(1549556828^e[i]); -return r}(),o=T(s.concat(t),64+r),T(a.concat(o),84)},T=function(t,e){var r,n,o,i,s,a,h,u,l,c,d,f,y,v,m,g,w,b;for(t[e>>2]|=1<<31-((3&e)<<3),t[(e+8>>6<<4)+15]=e<<3,g=Array(80),r=1732584193,o=-271733879,s=-1732584194,h=271733878,l=-1009589776,f=0,m=t.length;m>f;){for(n=r,i=o,a=s,u=h,c=l,y=b=0;80>b;y=++b)g[y]=16>y?t[f+y]:x(g[y-3]^g[y-8]^g[y-14]^g[y-16],1),20>y?(d=o&s|~o&h,v=1518500249):40>y?(d=o^s^h,v=1859775393):60>y?(d=o&s|o&h|s&h,v=-1894007588):(d=o^s^h,v=-899497514),w=p(p(x(r,5),d),p(p(l,g[y]),v)),l=h,h=s,s=x(o,30),o=r,r=w;r=p(r,n),o=p(o,i),s=p(s,a),h=p(h,u),l=p(l,c),f+=16}return[r,o,s,h,l]},x=function(t,e){return t<>>32-e},p=function(t,e){var r,n;return n=(65535&t)+(65535&e),r=(t>>16)+(e>>16)+(n>>16),r<<16|65535&n},c=function(t){var e,r,n,o,i;for(o="",e=0,n=4*t.length;n>e;)r=e,i=(255&t[r>>2]>>(3-(3&r)<<3))<<16,r+=1,i|=(255&t[r>>2]>>(3-(3&r)<<3))<<8,r+=1,i|=255&t[r>>2]>>(3-(3&r)<<3),o+=O[63&i>>18],o+=O[63&i>>12],e+=1,o+=e>=n?"=":O[63&i>>6],e+=1,o+=e>=n?"=":O[63&i],e+=1;return o},O="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",A=function(t){var e,r,n,o,i;for(e=[],n=255,r=o=0,i=t.length;i>=0?i>o:o>i;r=i>=0?++o:--o)e[r>>2]|=(t.charCodeAt(r)&n)<<(3-(3&r)<<3);return e},t.Oauth=function(){function e(t){this.key=this.k=null,this.secret=this.s=null,this.token=null,this.tokenSecret=null,this._appHash=null,this.reset(t)}return e.prototype.reset=function(t){var e,r,n,o;if(t.secret)this.k=this.key=t.key,this.s=this.secret=t.secret,this._appHash=null;else if(t.key)this.key=t.key,this.secret=null,n=d(_(this.key).split("|",2)[1]),o=n.split("?",2),e=o[0],r=o[1],this.k=decodeURIComponent(e),this.s=decodeURIComponent(r),this._appHash=null;else if(!this.k)throw Error("No API key supplied");return t.token?this.setToken(t.token,t.tokenSecret):this.setToken(null,"")},e.prototype.setToken=function(e,r){if(e&&!r)throw Error("No secret supplied with the user token");return this.token=e,this.tokenSecret=r||"",this.hmacKey=t.Xhr.urlEncodeValue(this.s)+"&"+t.Xhr.urlEncodeValue(r),null},e.prototype.authHeader=function(e,r,n){var o,i,s,a,h,u;this.addAuthParams(e,r,n),i=[];for(s in n)a=n[s],"oauth_"===s.substring(0,6)&&i.push(s);for(i.sort(),o=[],h=0,u=i.length;u>h;h++)s=i[h],o.push(t.Xhr.urlEncodeValue(s)+'="'+t.Xhr.urlEncodeValue(n[s])+'"'),delete n[s];return"OAuth "+o.join(",")},e.prototype.addAuthParams=function(t,e,r){return this.boilerplateParams(r),r.oauth_signature=this.signature(t,e,r),r},e.prototype.boilerplateParams=function(e){return e.oauth_consumer_key=this.k,e.oauth_nonce=t.Oauth.nonce(),e.oauth_signature_method="HMAC-SHA1",this.token&&(e.oauth_token=this.token),e.oauth_timestamp=Math.floor(Date.now()/1e3),e.oauth_version="1.0",e},e.nonce=function(){return Math.random().toString(36)},e.prototype.signature=function(e,r,n){var o;return o=e.toUpperCase()+"&"+t.Xhr.urlEncodeValue(r)+"&"+t.Xhr.urlEncodeValue(t.Xhr.urlEncode(n)),v(o,this.hmacKey)},e.prototype.appHash=function(){return this._appHash?this._appHash:this._appHash=m(this.k).replace(/\=/g,"")},e}(),null==Date.now&&(Date.now=function(){return(new Date).getTime()}),_=function(t,e){var r,n,o,i,s,a,h,u,l,p,c,f;for(e?(e=[encodeURIComponent(t),encodeURIComponent(e)].join("?"),t=function(){var e,n,o;for(o=[],r=e=0,n=t.length/2;n>=0?n>e:e>n;r=n>=0?++e:--e)o.push(16*(15&t.charCodeAt(2*r))+(15&t.charCodeAt(2*r+1)));return o}()):(p=t.split("|",2),t=p[0],e=p[1],t=d(t),t=function(){var e,n,o;for(o=[],r=e=0,n=t.length;n>=0?n>e:e>n;r=n>=0?++e:--e)o.push(t.charCodeAt(r));return o}(),e=d(e)),i=function(){for(f=[],u=0;256>u;u++)f.push(u);return f}.apply(this),a=0,s=l=0;256>l;s=++l)a=(a+i[r]+t[s%t.length])%256,c=[i[a],i[s]],i[s]=c[0],i[a]=c[1];return s=a=0,o=function(){var t,r,o,u;for(u=[],h=t=0,r=e.length;r>=0?r>t:t>r;h=r>=0?++t:--t)s=(s+1)%256,a=(a+i[s])%256,o=[i[a],i[s]],i[s]=o[0],i[a]=o[1],n=i[(i[s]+i[a])%256],u.push(String.fromCharCode((n^e.charCodeAt(h))%256));return u}(),t=function(){var e,n,o;for(o=[],r=e=0,n=t.length;n>=0?n>e:e>n;r=n>=0?++e:--e)o.push(String.fromCharCode(t[r]));return o}(),[w(t.join("")),w(o.join(""))].join("|")},t.Util.encodeKey=_,t.PulledChanges=function(){function e(e){var r;this.blankSlate=e.reset||!1,this.cursorTag=e.cursor,this.shouldPullAgain=e.has_more,this.shouldBackOff=!this.shouldPullAgain,this.changes=e.cursor&&e.cursor.length?function(){var n,o,i,s;for(i=e.entries,s=[],n=0,o=i.length;o>n;n++)r=i[n],s.push(t.PullChange.parse(r));return s}():[]}return e.parse=function(e){return e&&"object"==typeof e?new t.PulledChanges(e):e},e.prototype.blankSlate=void 0,e.prototype.cursorTag=void 0,e.prototype.changes=void 0,e.prototype.shouldPullAgain=void 0,e.prototype.shouldBackOff=void 0,e.prototype.cursor=function(){return this.cursorTag},e}(),t.PullChange=function(){function e(e){this.path=e[0],this.stat=t.Stat.parse(e[1]),this.stat?this.wasRemoved=!1:(this.stat=null,this.wasRemoved=!0)}return e.parse=function(e){return e&&"object"==typeof e?new t.PullChange(e):e},e.prototype.path=void 0,e.prototype.wasRemoved=void 0,e.prototype.stat=void 0,e}(),t.RangeInfo=function(){function e(t){var e;(e=/^bytes (\d*)-(\d*)\/(.*)$/.exec(t))?(this.start=parseInt(e[1]),this.end=parseInt(e[2]),this.size="*"===e[3]?null:parseInt(e[3])):(this.start=0,this.end=0,this.size=null)}return e.parse=function(e){return"string"==typeof e?new t.RangeInfo(e):e},e.prototype.start=null,e.prototype.size=null,e.prototype.end=null,e}(),t.PublicUrl=function(){function e(t,e){this.url=t.url,this.expiresAt=new Date(Date.parse(t.expires)),this.isDirect=e===!0?!0:e===!1?!1:"direct"in t?t.direct:864e5>=Date.now()-this.expiresAt,this.isPreview=!this.isDirect,this._json=null}return e.parse=function(e,r){return e&&"object"==typeof e?new t.PublicUrl(e,r):e},e.prototype.url=null,e.prototype.expiresAt=null,e.prototype.isDirect=null,e.prototype.isPreview=null,e.prototype.json=function(){return this._json||(this._json={url:this.url,expires:""+this.expiresAt,direct:this.isDirect})},e}(),t.CopyReference=function(){function e(t){"object"==typeof t?(this.tag=t.copy_ref,this.expiresAt=new Date(Date.parse(t.expires)),this._json=t):(this.tag=t,this.expiresAt=new Date(1e3*Math.ceil(Date.now()/1e3)),this._json=null)}return e.parse=function(e){return!e||"object"!=typeof e&&"string"!=typeof e?e:new t.CopyReference(e)},e.prototype.tag=null,e.prototype.expiresAt=null,e.prototype.json=function(){return this._json||(this._json={copy_ref:this.tag,expires:""+this.expiresAt})},e}(),t.Stat=function(){function e(t){var e,r,n,o;switch(this._json=t,this.path=t.path,"/"!==this.path.substring(0,1)&&(this.path="/"+this.path),e=this.path.length-1,e>=0&&"/"===this.path.substring(e)&&(this.path=this.path.substring(0,e)),r=this.path.lastIndexOf("/"),this.name=this.path.substring(r+1),this.isFolder=t.is_dir||!1,this.isFile=!this.isFolder,this.isRemoved=t.is_deleted||!1,this.typeIcon=t.icon,this.modifiedAt=(null!=(n=t.modified)?n.length:void 0)?new Date(Date.parse(t.modified)):null,this.clientModifiedAt=(null!=(o=t.client_mtime)?o.length:void 0)?new Date(Date.parse(t.client_mtime)):null,t.root){case"dropbox":this.inAppFolder=!1;break;case"app_folder":this.inAppFolder=!0;break;default:this.inAppFolder=null}this.size=t.bytes||0,this.humanSize=t.size||"",this.hasThumbnail=t.thumb_exists||!1,this.isFolder?(this.versionTag=t.hash,this.mimeType=t.mime_type||"inode/directory"):(this.versionTag=t.rev,this.mimeType=t.mime_type||"application/octet-stream")}return e.parse=function(e){return e&&"object"==typeof e?new t.Stat(e):e},e.prototype.path=null,e.prototype.name=null,e.prototype.inAppFolder=null,e.prototype.isFolder=null,e.prototype.isFile=null,e.prototype.isRemoved=null,e.prototype.typeIcon=null,e.prototype.versionTag=null,e.prototype.mimeType=null,e.prototype.size=null,e.prototype.humanSize=null,e.prototype.hasThumbnail=null,e.prototype.modifiedAt=null,e.prototype.clientModifiedAt=null,e.prototype.json=function(){return this._json},e}(),t.UploadCursor=function(){function e(t){this.replace(t)}return e.parse=function(e){return!e||"object"!=typeof e&&"string"!=typeof e?e:new t.UploadCursor(e)},e.prototype.tag=null,e.prototype.offset=null,e.prototype.expiresAt=null,e.prototype.json=function(){return this._json||(this._json={upload_id:this.tag,offset:this.offset,expires:""+this.expiresAt})},e.prototype.replace=function(t){return"object"==typeof t?(this.tag=t.upload_id||null,this.offset=t.offset||0,this.expiresAt=new Date(Date.parse(t.expires)||Date.now()),this._json=t):(this.tag=t||null,this.offset=0,this.expiresAt=new Date(1e3*Math.floor(Date.now()/1e3)),this._json=null),this},e}(),t.UserInfo=function(){function e(t){var e;this._json=t,this.name=t.display_name,this.email=t.email,this.countryCode=t.country||null,this.uid=""+t.uid,t.public_app_url?(this.publicAppUrl=t.public_app_url,e=this.publicAppUrl.length-1,e>=0&&"/"===this.publicAppUrl.substring(e)&&(this.publicAppUrl=this.publicAppUrl.substring(0,e))):this.publicAppUrl=null,this.referralUrl=t.referral_link,this.quota=t.quota_info.quota,this.privateBytes=t.quota_info.normal||0,this.sharedBytes=t.quota_info.shared||0,this.usedQuota=this.privateBytes+this.sharedBytes}return e.parse=function(e){return e&&"object"==typeof e?new t.UserInfo(e):e},e.prototype.name=null,e.prototype.email=null,e.prototype.countryCode=null,e.prototype.uid=null,e.prototype.referralUrl=null,e.prototype.publicAppUrl=null,e.prototype.quota=null,e.prototype.usedQuota=null,e.prototype.privateBytes=null,e.prototype.sharedBytes=null,e.prototype.json=function(){return this._json},e}(),"undefined"==typeof XMLHttpRequest||"undefined"==typeof window&&"undefined"==typeof self||"undefined"==typeof navigator||"string"!=typeof navigator.userAgent?(h=require("xhr2"),a=!1,i=!1,s=!1):("undefined"==typeof XDomainRequest||"withCredentials"in new XMLHttpRequest?(h=XMLHttpRequest,a=!1,i="undefined"!=typeof FormData&&-1===navigator.userAgent.indexOf("Firefox")):(h=XDomainRequest,a=!0,i=!1),s=!0),"undefined"==typeof Uint8Array)o=null,l=!1,u=!1;else if(Object.getPrototypeOf?o=Object.getPrototypeOf(Object.getPrototypeOf(new Uint8Array(0))).constructor:Object.__proto__&&(o=new Uint8Array(0).__proto__.__proto__.constructor),"undefined"==typeof Blob)l=!1,u=!0;else{try{2===new Blob([new Uint8Array(2)]).size?(l=!0,u=!0):(u=!1,l=2===new Blob([new ArrayBuffer(2)]).size)}catch(X){g=X,u=!1,l=!1,"undefined"!=typeof WebKitBlobBuilder&&-1!==navigator.userAgent.indexOf("Android")&&(i=!1)}o===Object&&(u=!1)}if(t.Xhr=function(){function e(t,e){this.method=t,this.isGet="GET"===this.method,this.url=e,this.wantHeaders=!1,this.headers={},this.params=null,this.body=null,this.preflight=!(this.isGet||"POST"===this.method),this.signed=!1,this.completed=!1,this.responseType=null,this.callback=null,this.xhr=null,this.onError=null}return e.Request=h,e.ieXdr=a,e.canSendForms=i,e.doesPreflight=s,e.ArrayBufferView=o,e.sendArrayBufferView=u,e.wrapBlob=l,e.prototype.xhr=null,e.prototype.onError=null,e.prototype.setParams=function(t){if(this.signed)throw Error("setParams called after addOauthParams or addOauthHeader");if(this.params)throw Error("setParams cannot be called twice");return this.params=t,this},e.prototype.setCallback=function(t){return this.callback=t,this},e.prototype.signWithOauth=function(e,r){return t.Xhr.ieXdr?this.addOauthParams(e):this.preflight||!t.Xhr.doesPreflight?this.addOauthHeader(e):this.isGet&&r?this.addOauthHeader(e):this.addOauthParams(e)},e.prototype.addOauthParams=function(t){if(this.signed)throw Error("Request already has an OAuth signature");return this.params||(this.params={}),t.addAuthParams(this.method,this.url,this.params),this.signed=!0,this},e.prototype.addOauthHeader=function(t){if(this.signed)throw Error("Request already has an OAuth signature");return this.params||(this.params={}),this.signed=!0,this.setHeader("Authorization",t.authHeader(this.method,this.url,this.params))},e.prototype.setBody=function(t){if(this.isGet)throw Error("setBody cannot be called on GET requests");if(null!==this.body)throw Error("Request already has a body");return"string"==typeof t||"undefined"!=typeof FormData&&t instanceof FormData||(this.headers["Content-Type"]="application/octet-stream",this.preflight=!0),this.body=t,this},e.prototype.setResponseType=function(t){return this.responseType=t,this},e.prototype.setHeader=function(t,e){var r;if(this.headers[t])throw r=this.headers[t],Error("HTTP header "+t+" already set to "+r);if("Content-Type"===t)throw Error("Content-Type is automatically computed based on setBody");return this.preflight=!0,this.headers[t]=e,this},e.prototype.reportResponseHeaders=function(){return this.wantHeaders=!0},e.prototype.setFileField=function(e,r,n,o){var i,s,a,h;if(null!==this.body)throw Error("Request already has a body");if(this.isGet)throw Error("setFileField cannot be called on GET requests");if("object"==typeof n){"undefined"!=typeof ArrayBuffer&&(n instanceof ArrayBuffer?t.Xhr.sendArrayBufferView&&(n=new Uint8Array(n)):!t.Xhr.sendArrayBufferView&&0===n.byteOffset&&n.buffer instanceof ArrayBuffer&&(n=n.buffer)),o||(o="application/octet-stream");try{n=new Blob([n],{type:o})}catch(u){g=u,window.WebKitBlobBuilder&&(a=new WebKitBlobBuilder,a.append(n),(i=a.getBlob(o))&&(n=i))}"undefined"!=typeof File&&n instanceof File&&(n=new Blob([n],{type:n.type})),h=n instanceof Blob}else h=!1;return h?(this.body=new FormData,this.body.append(e,n,r)):(o||(o="application/octet-stream"),s=this.multipartBoundary(),this.headers["Content-Type"]="multipart/form-data; boundary="+s,this.body=["--",s,"\r\n",'Content-Disposition: form-data; name="',e,'"; filename="',r,'"\r\n',"Content-Type: ",o,"\r\n","Content-Transfer-Encoding: binary\r\n\r\n",n,"\r\n","--",s,"--","\r\n"].join(""))},e.prototype.multipartBoundary=function(){return[Date.now().toString(36),Math.random().toString(36)].join("----")},e.prototype.paramsToUrl=function(){var e;return this.params&&(e=t.Xhr.urlEncode(this.params),0!==e.length&&(this.url=[this.url,"?",e].join("")),this.params=null),this},e.prototype.paramsToBody=function(){if(this.params){if(null!==this.body)throw Error("Request already has a body");if(this.isGet)throw Error("paramsToBody cannot be called on GET requests");this.headers["Content-Type"]="application/x-www-form-urlencoded",this.body=t.Xhr.urlEncode(this.params),this.params=null}return this},e.prototype.prepare=function(){var e,r,n,o,i=this;if(r=t.Xhr.ieXdr,this.isGet||null!==this.body||r?(this.paramsToUrl(),null!==this.body&&"string"==typeof this.body&&(this.headers["Content-Type"]="text/plain; charset=utf8")):this.paramsToBody(),this.xhr=new t.Xhr.Request,r?(this.xhr.onload=function(){return i.onXdrLoad()},this.xhr.onerror=function(){return i.onXdrError()},this.xhr.ontimeout=function(){return i.onXdrError()},this.xhr.onprogress=function(){}):this.xhr.onreadystatechange=function(){return i.onReadyStateChange()},this.xhr.open(this.method,this.url,!0),!r){o=this.headers;for(e in o)D.call(o,e)&&(n=o[e],this.xhr.setRequestHeader(e,n))}return this.responseType&&("b"===this.responseType?this.xhr.overrideMimeType&&this.xhr.overrideMimeType("text/plain; charset=x-user-defined"):this.xhr.responseType=this.responseType),this},e.prototype.send=function(e){var r,n;if(this.callback=e||this.callback,null!==this.body){r=this.body,t.Xhr.sendArrayBufferView?r instanceof ArrayBuffer&&(r=new Uint8Array(r)):0===r.byteOffset&&r.buffer instanceof ArrayBuffer&&(r=r.buffer);try{this.xhr.send(r)}catch(o){if(n=o,t.Xhr.sendArrayBufferView||!t.Xhr.wrapBlob)throw n;r=new Blob([r],{type:"application/octet-stream"}),this.xhr.send(r)}}else this.xhr.send();return this},e.urlEncode=function(t){var e,r,n;e=[];for(r in t)n=t[r],e.push(this.urlEncodeValue(r)+"="+this.urlEncodeValue(n));return e.sort().join("&")},e.urlEncodeValue=function(t){return encodeURIComponent(""+t).replace(/\!/g,"%21").replace(/'/g,"%27").replace(/\(/g,"%28").replace(/\)/g,"%29").replace(/\*/g,"%2A")},e.urlDecode=function(t){var e,r,n,o,i,s;for(r={},s=t.split("&"),o=0,i=s.length;i>o;o++)n=s[o],e=n.split("="),r[decodeURIComponent(e[0])]=decodeURIComponent(e[1]);return r},e.prototype.onReadyStateChange=function(){var e,r,n,o,i,s,a,h,u,l,p,c;if(4!==this.xhr.readyState)return!0;if(this.completed)return!0;if(this.completed=!0,200>this.xhr.status||this.xhr.status>=300)return r=new t.ApiError(this.xhr,this.method,this.url),this.onError?this.onError(r,this.callback):this.callback(r),!0;if(this.wantHeaders?(e=this.xhr.getAllResponseHeaders(),i=e?t.Xhr.parseResponseHeaders(e):this.guessResponseHeaders(),u=i["x-dropbox-metadata"]):(i=void 0,u=this.xhr.getResponseHeader("x-dropbox-metadata")),null!=u?u.length:void 0)try{h=JSON.parse(u)}catch(d){a=d,h=void 0}else h=void 0;if(this.responseType){if("b"===this.responseType){for(o=null!=this.xhr.responseText?this.xhr.responseText:this.xhr.response,n=[],s=p=0,c=o.length;c>=0?c>p:p>c;s=c>=0?++p:--p)n.push(String.fromCharCode(255&o.charCodeAt(s)));l=n.join(""),this.callback(null,l,h,i)}else this.callback(null,this.xhr.response,h,i);return!0}switch(l=null!=this.xhr.responseText?this.xhr.responseText:this.xhr.response,this.xhr.getResponseHeader("Content-Type")){case"application/x-www-form-urlencoded":this.callback(null,t.Xhr.urlDecode(l),h,i);break;case"application/json":case"text/javascript":this.callback(null,JSON.parse(l),h,i);break;default:this.callback(null,l,h,i)}return!0},e.parseResponseHeaders=function(t){var e,r,n,o,i,s,a,h;for(n={},r=t.split("\n"),a=0,h=r.length;h>a;a++)o=r[a],e=o.indexOf(":"),i=o.substring(0,e).trim().toLowerCase(),s=o.substring(e+1).trim(),n[i]=s;return n},e.prototype.guessResponseHeaders=function(){var t,e,r,n,o,i;for(t={},i=["cache-control","content-language","content-range","content-type","expires","last-modified","pragma","x-dropbox-metadata"],n=0,o=i.length;o>n;n++)e=i[n],r=this.xhr.getResponseHeader(e),r&&(t[e]=r);return t},e.prototype.onXdrLoad=function(){var e,r,n;if(this.completed)return!0;if(this.completed=!0,n=this.xhr.responseText,e=this.wantHeaders?{"content-type":this.xhr.contentType}:void 0,r=void 0,this.responseType)return this.callback(null,n,r,e),!0;switch(this.xhr.contentType){case"application/x-www-form-urlencoded":this.callback(null,t.Xhr.urlDecode(n),r,e);break;case"application/json":case"text/javascript":this.callback(null,JSON.parse(n),r,e);break;default:this.callback(null,n,r,e)}return!0},e.prototype.onXdrError=function(){var e;return this.completed?!0:(this.completed=!0,e=new t.ApiError(this.xhr,this.method,this.url),this.onError?this.onError(e,this.callback):this.callback(e),!0)},e}(),"undefined"!=typeof module&&"exports"in module)module.exports=t;else if("undefined"!=typeof window&&null!==window)if(window.Dropbox)for(R in t)D.call(t,R)&&(k=t[R],window.Dropbox[R]=k);else window.Dropbox=t;else{if("undefined"==typeof self||null===self)throw Error("This library only supports node.js and modern browsers.");self.Dropbox=t}}).call(this); -/* -//@ sourceMappingURL=dropbox.min.map -*/ \ No newline at end of file diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index e1e78e52986..f0a67b4e311 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -68,10 +68,6 @@ define({ "ERROR_IN_BROWSER_TITLE" : "Oops! {APP_NAME} doesn't run in browsers yet.", "ERROR_IN_BROWSER" : "{APP_NAME} is built in HTML, but right now it runs as a desktop app so you can use it to edit local files. Please use the application shell in the github.com/adobe/brackets-shell repo to run {APP_NAME}.", - // FileIndexManager error string - "ERROR_MAX_FILES_TITLE" : "Error Indexing Files", - "ERROR_MAX_FILES" : "The maximum number of files have been indexed. Actions that look up files in the index may function incorrectly.", - // Live Development error strings "ERROR_LAUNCHING_BROWSER_TITLE" : "Error launching browser", "ERROR_CANT_FIND_CHROME" : "The Google Chrome browser could not be found. Please make sure it is installed.", diff --git a/src/project/FileIndexManager.js b/src/project/FileIndexManager.js index 570c14eaa8d..748e7fd8983 100644 --- a/src/project/FileIndexManager.js +++ b/src/project/FileIndexManager.js @@ -36,9 +36,6 @@ define(function (require, exports, module) { var ProjectManager = require("project/ProjectManager"), FileUtils = require("file/FileUtils"); - // TODO: do we need anything like _showMaxFilesDialog() now? - // If not, remove strings ERROR_MAX_FILES, ERROR_MAX_FILES_TITLE - function _warn() { console.error("Warning: FileIndexManager is deprecated. Use ProjectManager.getAllFiles() instead"); } diff --git a/src/project/ProjectManager.js b/src/project/ProjectManager.js index 7b1f89c7cd7..90b6c7dc80e 100644 --- a/src/project/ProjectManager.js +++ b/src/project/ProjectManager.js @@ -372,7 +372,7 @@ define(function (require, exports, module) { if (entry.fullPath) { fullPath = entry.fullPath; - // Truncate project path prefix (inlcuding its last slash) AND remove trailing slash suffix + // Truncate project path prefix (including its last slash) AND remove trailing slash suffix // So "/foo/bar/projroot/abc/xyz/" -> "abc/xyz" shortPath = fullPath.slice(projectPathLength, -1); @@ -644,9 +644,7 @@ define(function (require, exports, module) { * @private * See shouldShow */ - function _shouldShowPath(path) { - var name = path.substr(path.lastIndexOf("/") + 1); - + function _shouldShowName(name) { return !name.match(_exclusionListRegEx); } @@ -657,7 +655,7 @@ define(function (require, exports, module) { * @return boolean true if the file should be displayed */ function shouldShow(entry) { - return _shouldShowPath(entry.fullPath); + return _shouldShowName(entry.name); } /** @@ -877,7 +875,7 @@ define(function (require, exports, module) { FileSystem.on("change", _fileSystemChange); FileSystem.on("rename", _fileSystemRename); - FileSystem.watch(FileSystem.getDirectoryForPath(rootPath), _shouldShowPath, function (err) { + FileSystem.watch(FileSystem.getDirectoryForPath(rootPath), _shouldShowName, function (err) { if (err) { console.log("Error watching project root: ", rootPath, err); } @@ -1163,7 +1161,7 @@ define(function (require, exports, module) { Dialogs.showModalDialog( DefaultDialogs.DIALOG_ID_ERROR, Strings.ERROR_LOADING_PROJECT, - StringUtils.format(Strings.OPEN_DIALOG_ERROR, err.name) + StringUtils.format(Strings.OPEN_DIALOG_ERROR, err) ); result.reject(); } diff --git a/src/search/FindInFiles.js b/src/search/FindInFiles.js index ded16d287fb..cdf023c2518 100644 --- a/src/search/FindInFiles.js +++ b/src/search/FindInFiles.js @@ -70,7 +70,7 @@ define(function (require, exports, module) { searchSummaryTemplate = require("text!htmlContent/search-summary.html"), searchResultsTemplate = require("text!htmlContent/search-results.html"); - /** @cost Constants used to define the maximum results show per page and found in a single file */ + /** @const Constants used to define the maximum results show per page and found in a single file */ var RESULTS_PER_PAGE = 100, FIND_IN_FILE_MAX = 300, diff --git a/src/utils/DragAndDrop.js b/src/utils/DragAndDrop.js index bd047fe3e16..3a5dbf3ee59 100644 --- a/src/utils/DragAndDrop.js +++ b/src/utils/DragAndDrop.js @@ -126,7 +126,7 @@ define(function (require, exports, module) { errorFiles.push(path); result.reject(); }); - } else if (!err && item.isDirectory() && filteredFiles.length === 1) { + } else if (!err && item.isDirectory && filteredFiles.length === 1) { // One folder was dropped, open it. ProjectManager.openProject(path) .done(function () { diff --git a/test/spec/FileSystem-test.js b/test/spec/FileSystem-test.js index a95c3f9a02b..bcf0f2098cd 100644 --- a/test/spec/FileSystem-test.js +++ b/test/spec/FileSystem-test.js @@ -344,9 +344,10 @@ define(function (require, exports, module) { it("should only call the impl once for simultaneous read requests", function () { var directory = fileSystem.getDirectoryForPath("/subdir/"), cb = getContentsCallback(), + cb2 = getContentsCallback(), cbCount = 0; - function delayedCallback() { + function delayedCallback(cb) { return function () { var args = arguments; setTimeout(function () { @@ -363,14 +364,16 @@ define(function (require, exports, module) { // Make sure cached data is cleared directory._contents = undefined; directory.getContents(cb); - directory.getContents(cb); + directory.getContents(cb2); expect(cb.wasCalled).toBeFalsy(); // Callback should *not* have been called yet }); - waitsFor(function () { return cb.wasCalled; }); - waits(100); // Make sure there is time for a second callback + waitsFor(function () { return cb.wasCalled && cb2.wasCalled; }); runs(function () { expect(cb.wasCalled).toBe(true); expect(cb.error).toBeFalsy(); + expect(cb2.wasCalled).toBe(true); + expect(cb2.error).toBeFalsy(); + expect(cb.contents).toEqual(cb2.contents); expect(cbCount).toBe(1); }); }); diff --git a/test/spec/MockFileSystemImpl.js b/test/spec/MockFileSystemImpl.js index 4a464afcf1f..8549142604a 100644 --- a/test/spec/MockFileSystemImpl.js +++ b/test/spec/MockFileSystemImpl.js @@ -27,7 +27,8 @@ define(function (require, exports, module) { "use strict"; - var FileSystemError = require("filesystem/FileSystemError"); + var FileSystemError = require("filesystem/FileSystemError"), + FileSystemStats = require("filesystem/FileSystemStats"); // Watcher callback function var _watcherCallback; @@ -100,11 +101,11 @@ define(function (require, exports, module) { stat = null; if (entry) { - stat = { - isDirectory: !entry.isFile, + stat = new FileSystemStats({ isFile: entry.isFile, - mtime: entry.mtime - }; + mtime: entry.mtime, + size: 0 + }); } return stat; @@ -285,11 +286,6 @@ define(function (require, exports, module) { notify(path); } - function chmod(path, mode, callback) { - // Not implemented - callback(null); - } - function unlink(path, callback) { var cb = _getCallback("unlink", path, callback), notify = _getNotification("unlink", path, _sendDirectoryWatcherNotification); @@ -327,7 +323,6 @@ define(function (require, exports, module) { exports.stat = stat; exports.readFile = readFile; exports.writeFile = writeFile; - exports.chmod = chmod; exports.unlink = unlink; exports.initWatchers = initWatchers; exports.watchPath = watchPath; diff --git a/test/spec/ProjectManager-test.js b/test/spec/ProjectManager-test.js index 0a21a9585b1..20a6c6e657e 100644 --- a/test/spec/ProjectManager-test.js +++ b/test/spec/ProjectManager-test.js @@ -560,7 +560,7 @@ define(function (require, exports, module) { it("should not show useless directory entries", function () { var shouldShow = ProjectManager.shouldShow; var makeEntry = function (name) { - return { fullPath: name }; + return { name: name }; }; expect(shouldShow(makeEntry(".git"))).toBe(false); diff --git a/test/spec/SpecRunnerUtils.js b/test/spec/SpecRunnerUtils.js index 2b32a067f67..c8ac9abc5f3 100644 --- a/test/spec/SpecRunnerUtils.js +++ b/test/spec/SpecRunnerUtils.js @@ -98,20 +98,12 @@ define(function (require, exports, module) { function chmod(path, mode) { var deferred = new $.Deferred(); - FileSystem.resolve(path, function (err, entry) { + brackets.fs.chmod(path, parseInt(mode, 8), function (err) { if (err) { deferred.reject(err); - return; - } - - entry.chmod(parseInt(mode, 8), function (err) { - if (err) { - deferred.reject(err); - return; - } - + } else { deferred.resolve(); - }); + } }); return deferred.promise(); @@ -238,8 +230,8 @@ define(function (require, exports, module) { runs(function () { var dir = FileSystem.getDirectoryForPath(getTempDirectory()).create(function (err) { - if (err) { - deferred.reject(); + if (err && err !== FileSystemError.ALREADY_EXISTS) { + deferred.reject(err); } else { deferred.resolve(); } @@ -255,13 +247,13 @@ define(function (require, exports, module) { function _stat(pathname) { var deferred = new $.Deferred(); - FileSystem.resolve(pathname, function (err, stats) { + FileSystem.resolve(pathname, function (err, entry) { if (err) { deferred.reject(err); return; } - deferred.resolve(stats); + deferred.resolve(entry); }); return deferred; From c0736375414e3dc53608912e721c1bbf5bcc1243 Mon Sep 17 00:00:00 2001 From: Glenn Ruehle Date: Fri, 1 Nov 2013 17:22:24 -0700 Subject: [PATCH 138/163] Add FileSystemEntry.parentPath getter. --- src/filesystem/FileSystemEntry.js | 19 +++++++++++++++---- test/spec/FileSystem-test.js | 16 ++++++++++++++++ 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/src/filesystem/FileSystemEntry.js b/src/filesystem/FileSystemEntry.js index 8a018c7a389..0c7864ddb9e 100644 --- a/src/filesystem/FileSystemEntry.js +++ b/src/filesystem/FileSystemEntry.js @@ -47,7 +47,7 @@ define(function (require, exports, module) { this._id = nextId++; } - // Add "fullPath", "name", "id", "isFile" and "isDirectory" getters + // Add "fullPath", "name", "parent", "id", "isFile" and "isDirectory" getters Object.defineProperties(FileSystemEntry.prototype, { "fullPath": { get: function () { return this._path; }, @@ -57,6 +57,10 @@ define(function (require, exports, module) { get: function () { return this._name; }, set: function () { throw new Error("Cannot set name"); } }, + "parentPath": { + get: function () { return this._parentPath; }, + set: function () { throw new Error("Cannot set parentPath"); } + }, "id": { get: function () { return this._id; }, set: function () { throw new Error("Cannot set id"); } @@ -97,6 +101,12 @@ define(function (require, exports, module) { */ FileSystemEntry.prototype._name = null; + /** + * The parent of this entry. + * @type {string} + */ + FileSystemEntry.prototype._parentPath = null; + /** * Whether or not the entry is a file * @type {boolean} @@ -117,10 +127,11 @@ define(function (require, exports, module) { FileSystemEntry.prototype._setPath = function (newPath) { var parts = newPath.split("/"); if (this.isDirectory) { - this._name = parts[parts.length - 2]; - } else { - this._name = parts[parts.length - 1]; + parts.pop(); // Remove the empty string after last trailing "/" } + this._name = parts[parts.length - 1]; + parts.pop(); // Remove name + this._parentPath = parts.join("/") + "/"; this._path = newPath; }; diff --git a/test/spec/FileSystem-test.js b/test/spec/FileSystem-test.js index bcf0f2098cd..59c9fbe70dd 100644 --- a/test/spec/FileSystem-test.js +++ b/test/spec/FileSystem-test.js @@ -182,6 +182,22 @@ define(function (require, exports, module) { }); }); + describe("parent and name properties", function () { + it("should have a name property", function () { + var file = fileSystem.getFileForPath("/subdir/file3.txt"), + directory = fileSystem.getDirectoryForPath("/subdir/foo/"); + + expect(file.name).toBe("file3.txt"); + expect(directory.name).toBe("foo"); + }); + it("should have a parentPath property", function () { + var file = fileSystem.getFileForPath("/subdir/file3.txt"), + directory = fileSystem.getDirectoryForPath("/subdir/foo/"); + + expect(file.parentPath).toBe("/subdir/"); + expect(directory.parentPath).toBe("/subdir/"); + }); + }); describe("Singleton enforcement", function () { it("should return the same File object for the same path", function () { From 3dde78806b38bfe51f0bdec426836832fb58eb3a Mon Sep 17 00:00:00 2001 From: Glenn Ruehle Date: Fri, 1 Nov 2013 17:23:27 -0700 Subject: [PATCH 139/163] Double quotes --- src/filesystem/File.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/filesystem/File.js b/src/filesystem/File.js index 1ef10531855..aa2e84e47c0 100644 --- a/src/filesystem/File.js +++ b/src/filesystem/File.js @@ -90,7 +90,7 @@ define(function (require, exports, module) { File.prototype.write = function (data, encoding, callback) { if (typeof (encoding) === "function") { callback = encoding; - encoding = 'utf8'; + encoding = "utf8"; } callback = callback || function () {}; From 34085c5378961159b3d80b58436b4e8dc51542d9 Mon Sep 17 00:00:00 2001 From: Ian Wehrman Date: Sun, 3 Nov 2013 17:25:40 -0800 Subject: [PATCH 140/163] Implement the clarified spec of FileSystemImpl.readdir and Directory.getContents suggested by @gruehle --- src/filesystem/Directory.js | 78 ++++++++++++------- .../impls/appshell/AppshellFileSystem.js | 7 +- src/project/ProjectManager.js | 6 +- 3 files changed, 55 insertions(+), 36 deletions(-) diff --git a/src/filesystem/Directory.js b/src/filesystem/Directory.js index 67b12c1ea83..a1d5ab89008 100644 --- a/src/filesystem/Directory.js +++ b/src/filesystem/Directory.js @@ -57,16 +57,23 @@ define(function (require, exports, module) { */ Directory.prototype._contents = null; + /** + * The statErrors for this directory. This "private" property is used by FileSystem. + * @type {object.} fullPaths are mapped to FileSystemError strings + */ + Directory.prototype._statErrors = null; + /** * Read the contents of a Directory. * * @param {Directory} directory Directory whose contents you want to get - * @param {function (?string, Array.=)} callback Callback that is passed - * an error code and the contents of the directory. + * @param {function (?string, Array.=, object.=)} callback + * Callback that is passed an error code or the stat-able contents of the directory + * along with a fullPath-to-FileSystemError string map of unstat-able entries and + * their stat errors. If there are no stat errors then the last parameter shall + * remain undefined. */ Directory.prototype.getContents = function (callback) { - var i, entryPath, entry; - if (this._contentsCallbacks) { // There is already a pending call for this directory's contents. // Push the new callback onto the stack and return. @@ -79,38 +86,53 @@ define(function (require, exports, module) { // Watchers aren't guaranteed to fire immediately, so it's possible this will be somewhat stale. But // unlike file contents, we're willing to tolerate directory contents being stale. It should at least // be up-to-date with respect to changes made internally (by this filesystem). - callback(null, this._contents); + callback(null, this._contents, this._statErrors); return; } this._contentsCallbacks = [callback]; this._impl.readdir(this.fullPath, function (err, contents, stats) { - this._contents = []; - - // Instantiate content objects - var len = stats ? stats.length : 0; - - for (i = 0; i < len; i++) { - entryPath = this.fullPath + contents[i]; + this._statErrors = undefined; + if (err) { + this._contents = undefined; + } else { + this._contents = []; - // Note: not all entries necessarily have associated stats. - // For now, silently ignore such entries. - if (stats[i] && this._fileSystem._indexFilter(entryPath, contents[i])) { - if (stats[i].isFile) { - entry = this._fileSystem.getFileForPath(entryPath); + contents.forEach(function (name, index) { + var entryPath = this.fullPath + name, + entry; + + if (this._fileSystem._indexFilter(entryPath, name)) { + var entryStats = stats[index]; - // If file already existed, its cache may now be invalid (a change to file content may be messaged EITHER as - // a watcher change directly on that file, OR as a watcher change to its parent dir) - // TODO: move this to FileSystem._handleWatchResult()? - entry._contents = undefined; - } else { - entry = this._fileSystem.getDirectoryForPath(entryPath); - } - entry._stat = stats[i]; + // Note: not all entries necessarily have associated stats. + if (typeof entryStats === "string") { + // entryStats is an error string + if (this._statErrors === undefined) { + this._statErrors = {}; + } + this._statErrors[entryPath] = entryStats; + } else { + // entryStats is a FileSystemStats object + if (entryStats.isFile) { + entry = this._fileSystem.getFileForPath(entryPath); + + // If file already existed, its cache may now be invalid (a change + // to file content may be messaged EITHER as a watcher change + // directly on that file, OR as a watcher change to its parent dir) + // TODO: move this to FileSystem._handleWatchResult()? + entry._contents = undefined; + } else { + entry = this._fileSystem.getDirectoryForPath(entryPath); + } + + entry._stat = entryStats; + this._contents.push(entry); + } - this._contents.push(entry); - } + } + }, this); } // Reset the callback list before we begin calling back so that @@ -121,7 +143,7 @@ define(function (require, exports, module) { // Invoke all saved callbacks currentCallbacks.forEach(function (cb) { - cb(err, this._contents); + cb(err, this._contents, this._statErrors); }.bind(this)); }.bind(this)); }; diff --git a/src/filesystem/impls/appshell/AppshellFileSystem.js b/src/filesystem/impls/appshell/AppshellFileSystem.js index fde15e4ba9d..a100ddeaaf7 100644 --- a/src/filesystem/impls/appshell/AppshellFileSystem.js +++ b/src/filesystem/impls/appshell/AppshellFileSystem.js @@ -172,8 +172,6 @@ define(function (require, exports, module) { } function readdir(path, callback) { - var stats = []; - appshell.fs.readdir(path, function (err, contents) { if (err) { callback(_mapError(err)); @@ -186,11 +184,10 @@ define(function (require, exports, module) { return; } + var stats = []; contents.forEach(function (val, idx) { stat(path + "/" + val, function (err, stat) { - if (!err) { - stats[idx] = stat; - } + stats[idx] = err || stat; count--; if (count <= 0) { callback(err, contents, stats); diff --git a/src/project/ProjectManager.js b/src/project/ProjectManager.js index bb5b80e96ef..9b7ba81f09a 100644 --- a/src/project/ProjectManager.js +++ b/src/project/ProjectManager.js @@ -771,9 +771,7 @@ define(function (require, exports, module) { // Fetch dirEntry's contents dirEntry.getContents(function (err, contents) { - if (contents) { - processEntries(contents); - } else { + if (err) { Dialogs.showModalDialog( DefaultDialogs.DIALOG_ID_ERROR, Strings.ERROR_LOADING_PROJECT, @@ -785,6 +783,8 @@ define(function (require, exports, module) { ); // Reject the render promise so we can move on. deferred.reject(); + } else { + processEntries(contents); } }); } From 2184802a2745824754dd31fb98f75db5f8b806fd Mon Sep 17 00:00:00 2001 From: Peter Flynn Date: Mon, 4 Nov 2013 11:57:58 -0800 Subject: [PATCH 141/163] Avoid duplicated implementations of filtering ProjectManager.getAllFiles() down to just CSS files - provide a new utility function to filter files based on Language id --- src/editor/CSSInlineEditor.js | 11 +---------- src/language/CSSUtils.js | 6 +----- src/project/FileIndexManager.js | 4 +--- src/project/ProjectManager.js | 17 +++++++++++++++-- 4 files changed, 18 insertions(+), 20 deletions(-) diff --git a/src/editor/CSSInlineEditor.js b/src/editor/CSSInlineEditor.js index e8daa7ee257..b0ab59eb9b3 100644 --- a/src/editor/CSSInlineEditor.js +++ b/src/editor/CSSInlineEditor.js @@ -51,16 +51,7 @@ define(function (require, exports, module) { _newRuleHandlers = []; function _getCSSFilesInProject() { - function cssFilter(entry) { - if (entry.isFile) { - var language = LanguageManager.getLanguageForPath(entry.fullPath); - if (language.getId() === "css") { - return true; - } - } - return false; - } - return ProjectManager.getAllFiles(cssFilter); + return ProjectManager.getAllFiles(ProjectManager.getLanguageFilter("css")); } /** diff --git a/src/language/CSSUtils.js b/src/language/CSSUtils.js index bb6abc0e43b..eca317edd52 100644 --- a/src/language/CSSUtils.js +++ b/src/language/CSSUtils.js @@ -948,10 +948,6 @@ define(function (require, exports, module) { function _findMatchingRulesInCSSFiles(selector, resultSelectors) { var result = new $.Deferred(); - function _cssFilter(entry) { - return PathUtils.filenameExtension(entry.fullPath).toLowerCase() === ".css"; - } - // Load one CSS file and search its contents function _loadFileAndScan(fullPath, selector) { var oneFileResult = new $.Deferred(); @@ -972,7 +968,7 @@ define(function (require, exports, module) { return oneFileResult.promise(); } - ProjectManager.getAllFiles(_cssFilter) + ProjectManager.getAllFiles(ProjectManager.getLanguageFilter("css")) .done(function (cssFiles) { // Load index of all CSS files; then process each CSS file in turn (see above) Async.doInParallel(cssFiles, function (fileInfo, number) { diff --git a/src/project/FileIndexManager.js b/src/project/FileIndexManager.js index 748e7fd8983..8ba5c524ff6 100644 --- a/src/project/FileIndexManager.js +++ b/src/project/FileIndexManager.js @@ -43,9 +43,7 @@ define(function (require, exports, module) { function _getFilter(indexName) { if (indexName === "css") { - return function (file) { - return FileUtils.getFileExtension(file.name) === "css"; - }; + return ProjectManager.getLanguageFilter("css"); } else if (indexName === "all") { return null; } else { diff --git a/src/project/ProjectManager.js b/src/project/ProjectManager.js index 9b7ba81f09a..2d1d10fe4bc 100644 --- a/src/project/ProjectManager.js +++ b/src/project/ProjectManager.js @@ -59,6 +59,7 @@ define(function (require, exports, module) { Commands = require("command/Commands"), Dialogs = require("widgets/Dialogs"), DefaultDialogs = require("widgets/DefaultDialogs"), + LanguageManager = require("language/LanguageManager"), Menus = require("command/Menus"), StringUtils = require("utils/StringUtils"), Strings = require("strings"), @@ -1600,8 +1601,8 @@ define(function (require, exports, module) { * files in the working set that are *not* under the project root. Files filtered * out by shouldShow() OR isBinaryFile() are excluded. * - * @param {function (File, number):boolean=} filter Optional filter function. - * See Array.filter() for details. + * @param {function (File, number):boolean=} filter Optional function to filter + * the file list (does not filter directory traversal). API matches Array.filter(). * @param {boolean=} includeWorkingSet If true, include files in the working set * that are not under the project root (*except* for untitled documents). * @@ -1650,6 +1651,17 @@ define(function (require, exports, module) { return deferred.promise(); } + /** + * Returns a filter for use with getAllFiles() that filters files based on LanguageManager language id + * @param {!string} languageId + * @return {!function(File):boolean} + */ + function getLanguageFilter(languageId) { + return function languageFilter(file) { + return (LanguageManager.getLanguageForPath(file.fullPath).getId() === languageId); + }; + } + /** * @private * Respond to a FileSystem change event. @@ -1726,4 +1738,5 @@ define(function (require, exports, module) { exports.showInTree = showInTree; exports.refreshFileTree = refreshFileTree; exports.getAllFiles = getAllFiles; + exports.getLanguageFilter = getLanguageFilter; }); From 779019d23e4f0374d12ec0fa6fc49f7040fbc643 Mon Sep 17 00:00:00 2001 From: Ian Wehrman Date: Mon, 4 Nov 2013 12:00:48 -0800 Subject: [PATCH 142/163] Adjust the implementation of Directory.getContents to also return an array of FileSystemStats objects for each of the returned directory contents. --- src/filesystem/Directory.js | 38 ++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/src/filesystem/Directory.js b/src/filesystem/Directory.js index a1d5ab89008..aaf5a957c19 100644 --- a/src/filesystem/Directory.js +++ b/src/filesystem/Directory.js @@ -56,22 +56,30 @@ define(function (require, exports, module) { * @type {Array} */ Directory.prototype._contents = null; + + /** + * The stats for the contents of this directory, such that this._contentsStats[i] + * corresponds to this._contents[i]. + * @type {Array.} + */ + Directory.prototype._contentsStats = null; /** - * The statErrors for this directory. This "private" property is used by FileSystem. + * The stats errors for the contents of this directory. * @type {object.} fullPaths are mapped to FileSystemError strings */ - Directory.prototype._statErrors = null; + Directory.prototype._contentsStatsErrors = null; /** * Read the contents of a Directory. * * @param {Directory} directory Directory whose contents you want to get - * @param {function (?string, Array.=, object.=)} callback - * Callback that is passed an error code or the stat-able contents of the directory - * along with a fullPath-to-FileSystemError string map of unstat-able entries and - * their stat errors. If there are no stat errors then the last parameter shall - * remain undefined. + * @param {function (?string, Array.=, Array.=, object.=)} callback + * Callback that is passed an error code or the stat-able contents + * of the directory along with the stats for these entries and a + * fullPath-to-FileSystemError string map of unstat-able entries + * and their stat errors. If there are no stat errors then the last + * parameter shall remain undefined. */ Directory.prototype.getContents = function (callback) { if (this._contentsCallbacks) { @@ -86,18 +94,21 @@ define(function (require, exports, module) { // Watchers aren't guaranteed to fire immediately, so it's possible this will be somewhat stale. But // unlike file contents, we're willing to tolerate directory contents being stale. It should at least // be up-to-date with respect to changes made internally (by this filesystem). - callback(null, this._contents, this._statErrors); + callback(null, this._contents, this._contentsStats, this._contentsStatsErrors); return; } this._contentsCallbacks = [callback]; this._impl.readdir(this.fullPath, function (err, contents, stats) { - this._statErrors = undefined; + this._contentsStatsErrors = undefined; + if (err) { this._contents = undefined; + this._contentsStats = undefined; } else { this._contents = []; + this._contentsStats = []; contents.forEach(function (name, index) { var entryPath = this.fullPath + name, @@ -109,10 +120,10 @@ define(function (require, exports, module) { // Note: not all entries necessarily have associated stats. if (typeof entryStats === "string") { // entryStats is an error string - if (this._statErrors === undefined) { - this._statErrors = {}; + if (this._contentsStatsErrors === undefined) { + this._contentsStatsErrors = {}; } - this._statErrors[entryPath] = entryStats; + this._contentsStatsErrors[entryPath] = entryStats; } else { // entryStats is a FileSystemStats object if (entryStats.isFile) { @@ -129,6 +140,7 @@ define(function (require, exports, module) { entry._stat = entryStats; this._contents.push(entry); + this._contentsStats.push(entryStats); } } @@ -143,7 +155,7 @@ define(function (require, exports, module) { // Invoke all saved callbacks currentCallbacks.forEach(function (cb) { - cb(err, this._contents, this._statErrors); + cb(err, this._contents, this._contentsStats, this._contentsStatsErrors); }.bind(this)); }.bind(this)); }; From 5967e2f2211a305b8e10d2ec7939f06f140b51b3 Mon Sep 17 00:00:00 2001 From: Ian Wehrman Date: Tue, 5 Nov 2013 13:14:07 -0800 Subject: [PATCH 143/163] Replace references to FileEntry and DirectoryEntry with references to File and Directory --- src/document/Document.js | 8 ++--- src/document/DocumentCommandHandlers.js | 21 +++++++------ src/document/DocumentManager.js | 40 ++++++++++++------------- src/file/FileUtils.js | 6 ++-- src/language/CodeInspection.js | 14 ++++----- src/project/ProjectManager.js | 30 +++++++++---------- src/project/WorkingSetSort.js | 6 ++-- src/project/WorkingSetView.js | 8 ++--- src/search/FindInFiles.js | 2 +- src/utils/ExtensionLoader.js | 3 +- src/utils/ViewUtils.js | 2 +- test/spec/SpecRunnerUtils.js | 10 +++---- 12 files changed, 74 insertions(+), 76 deletions(-) diff --git a/src/document/Document.js b/src/document/Document.js index f0da4b09dce..5b5088e934e 100644 --- a/src/document/Document.js +++ b/src/document/Document.js @@ -69,7 +69,7 @@ define(function (require, exports, module) { * 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. * - * @param {!FileEntry} file Need not lie within the project. + * @param {!File} file Need not lie within the project. * @param {!Date} initialTimestamp File's timestamp when we read it off disk. * @param {!string} rawText Text content of the file. */ @@ -90,9 +90,9 @@ define(function (require, exports, module) { Document.prototype._refCount = 0; /** - * The FileEntry for this document. Need not lie within the project. - * If Document is untitled, this is an InaccessibleFileEntry object. - * @type {!FileEntry} + * The File for this document. Need not lie within the project. + * If Document is untitled, this is an InMemoryFile object. + * @type {!File} */ Document.prototype.file = null; diff --git a/src/document/DocumentCommandHandlers.js b/src/document/DocumentCommandHandlers.js index d96933f7b9d..63237272f04 100644 --- a/src/document/DocumentCommandHandlers.js +++ b/src/document/DocumentCommandHandlers.js @@ -515,17 +515,16 @@ define(function (require, exports, module) { */ function doSave(docToSave) { var result = new $.Deferred(), - fileEntry = docToSave.file; + file = docToSave.file; function handleError(error) { - _showSaveFileError(error, fileEntry.fullPath) + _showSaveFileError(error, file.fullPath) .done(function () { result.reject(error); }); } if (docToSave && docToSave.isDirty) { - var file = docToSave.file; var writeError = false; FileUtils.writeText(file, docToSave.getText(true)) @@ -537,7 +536,7 @@ define(function (require, exports, module) { handleError(err, file); }); } else { - result.resolve(fileEntry); + result.resolve(file); } result.always(function () { EditorManager.focusEditor(); @@ -580,7 +579,7 @@ define(function (require, exports, module) { * @param {?{cursorPos:!Object, selection:!Object, scrollPos:!Object}} settings - properties of * the original document's editor that need to be carried over to the new document * i.e. scrollPos, cursorPos and text selection - * @return {$.Promise} a promise that is resolved with the saved file's FileEntry. Rejected in + * @return {$.Promise} a promise that is resolved with the saved document's File. Rejected in * case of IO error (after error dialog dismissed), or if the Save dialog was canceled. */ function _doSaveAs(doc, settings) { @@ -695,7 +694,7 @@ define(function (require, exports, module) { /** * Saves the given file. If no file specified, assumes the current document. * @param {?{doc: ?Document}} commandData Document to close, or null - * @return {$.Promise} resolved with the saved file's FileEntry (which MAY DIFFER from the doc + * @return {$.Promise} resolved with the saved document's File (which MAY DIFFER from the doc * passed in, if the doc was untitled). Rejected in case of IO error (after error dialog * dismissed), or if doc was untitled and the Save dialog was canceled (will be rejected with * USER_CANCELED object). @@ -820,7 +819,7 @@ define(function (require, exports, module) { * Closes the specified file: removes it from the working set, and closes the main editor if one * is open. Prompts user about saving changes first, if document is dirty. * - * @param {?{file: FileEntry, promptOnly:boolean}} commandData Optional bag of arguments: + * @param {?{file: File, promptOnly:boolean}} commandData Optional bag of arguments: * file - File to close; assumes the current document if not specified. * promptOnly - If true, only displays the relevant confirmation UI and does NOT actually * close the document. This is useful when chaining file-close together with other user @@ -842,10 +841,10 @@ define(function (require, exports, module) { } // utility function for handleFileClose: closes document & removes from working set - function doClose(fileEntry) { + function doClose(file) { if (!promptOnly) { // This selects a different document if the working set has any other options - DocumentManager.closeFullEditor(fileEntry); + DocumentManager.closeFullEditor(file); EditorManager.focusEditor(); } @@ -930,8 +929,8 @@ define(function (require, exports, module) { } else if (id === Dialogs.DIALOG_BTN_OK) { // "Save" case: wait until we confirm save has succeeded before closing handleFileSave({doc: doc}) - .done(function (newFileEntry) { - doClose(newFileEntry); + .done(function (newFile) { + doClose(newFile); result.resolve(); }) .fail(function () { diff --git a/src/document/DocumentManager.js b/src/document/DocumentManager.js index 2d921489fcc..4b50d1e73f0 100644 --- a/src/document/DocumentManager.js +++ b/src/document/DocumentManager.js @@ -63,13 +63,13 @@ * * To listen for working set changes, you must listen to *all* of these events: * - workingSetAdd -- When a file is added to the working set (see getWorkingSet()). The 2nd arg - * to the listener is the added FileEntry, and the 3rd arg is the index it was inserted at. + * to the listener is the added File, and the 3rd arg is the index it was inserted at. * - workingSetAddList -- When multiple files are added to the working set (e.g. project open, multiple file open). - * The 2nd arg to the listener is the array of added FileEntry objects. + * The 2nd arg to the listener is the array of added File objects. * - workingSetRemove -- When a file is removed from the working set (see getWorkingSet()). The - * 2nd arg to the listener is the removed FileEntry. + * 2nd arg to the listener is the removed File. * - workingSetRemoveList -- When multiple files are removed from the working set (e.g. project close). - * The 2nd arg to the listener is the array of removed FileEntry objects. + * The 2nd arg to the listener is the array of removed File objects. * - workingSetSort -- When the workingSet array is reordered without additions or removals. * Listener receives no arguments. * @@ -135,7 +135,7 @@ define(function (require, exports, module) { /** * @private - * @type {Array.} + * @type {Array.} * @see DocumentManager.getWorkingSet() */ var _workingSet = []; @@ -143,14 +143,14 @@ define(function (require, exports, module) { /** * @private * Contains the same set of items as _workingSet, but ordered by how recently they were _currentDocument (0 = most recent). - * @type {Array.} + * @type {Array.} */ var _workingSetMRUOrder = []; /** * @private * Contains the same set of items as _workingSet, but ordered in the way they where added to _workingSet (0 = last added). - * @type {Array.} + * @type {Array.} */ var _workingSetAddedOrder = []; @@ -177,7 +177,7 @@ define(function (require, exports, module) { * Which items belong in the working set is managed entirely by DocumentManager. Callers cannot * (yet) change this collection on their own. * - * @return {Array.} + * @return {Array.} */ function getWorkingSet() { return _.clone(_workingSet); @@ -187,7 +187,7 @@ define(function (require, exports, module) { * Returns the index of the file matching fullPath in the working set. * Returns -1 if not found. * @param {!string} fullPath - * @param {Array.=} list Pass this arg to search a different array of files. Internal + * @param {Array.=} list Pass this arg to search a different array of files. Internal * use only. * @returns {number} index */ @@ -231,7 +231,7 @@ define(function (require, exports, module) { * Adds the given file to the end of the working set list, if it is not already in the list * and it does not have a custom viewer. * Does not change which document is currently open in the editor. Completes synchronously. - * @param {!FileEntry} file + * @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 * (useful if suppressRedraw was used with removeFromWorkingSet() earlier) @@ -288,7 +288,7 @@ define(function (require, exports, module) { * Does not change which document is currently open in the editor. * More efficient than calling addToWorkingSet() (in a loop) for * a list of files because there's only 1 redraw at the end - * @param {!FileEntryArray} fileList + * @param {!Array.} fileList */ function addListToWorkingSet(fileList) { var uniqueFileList = []; @@ -325,7 +325,7 @@ define(function (require, exports, module) { * Warning: low level API - use FILE_CLOSE command in most cases. * Removes the given file from the working set list, if it was in the list. Does not change * the current editor even if it's for this file. Does not prompt for unsaved changes. - * @param {!FileEntry} file + * @param {!File} file * @param {boolean=} true to suppress redraw after removal */ function removeFromWorkingSet(file, suppressRedraw) { @@ -393,7 +393,7 @@ define(function (require, exports, module) { /** * Sorts _workingSet using the compare function - * @param {function(FileEntry, FileEntry): number} compareFn The function that will be used inside JavaScript's + * @param {function(File, File): number} compareFn The function that will be used inside JavaScript's * sort function. The return a value should be >0 (sort a to a lower index than b), =0 (leaves a and b * unchanged with respect to each other) or <0 (sort b to a lower index than a) and must always returns * the same value when given a specific pair of elements a and b as its two arguments. @@ -432,7 +432,7 @@ define(function (require, exports, module) { * 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. * @param {number} inc -1 for previous, +1 for next; no other values allowed - * @return {?FileEntry} null if working set empty + * @return {?File} null if working set empty */ function getNextPrevFile(inc) { if (inc !== -1 && inc !== +1) { @@ -526,7 +526,7 @@ define(function (require, exports, module) { * * This is a subset of notifyFileDeleted(). Use this for the user-facing Close command. * - * @param {!FileEntry} file + * @param {!File} file * @param {boolean} skipAutoSelect - if true, don't automatically open and select the next document */ function closeFullEditor(file, skipAutoSelect) { @@ -716,7 +716,7 @@ define(function (require, exports, module) { * to request the same document simultaneously before the initial request has completed. * In particular, this happens at app startup where the working set is created and the * intial active document is opened in an editor. This is essential to ensure that only - * 1 Document exists for any FileEntry. + * one Document exists for any File. * @private * @type {Object.} */ @@ -757,11 +757,11 @@ define(function (require, exports, module) { /** - * Creates an untitled document. The associated FileEntry has a fullPath + * Creates an untitled document. The associated File has a fullPath that * looks like /some-random-string/Untitled-counter.fileExt. * - * @param {number} counter - used in the name of the new Document's FileEntry - * @param {string} fileExt - file extension of the new Document's FileEntry + * @param {number} counter - used in the name of the new Document's File + * @param {string} fileExt - file extension of the new Document's File * @return {Document} - a new untitled Document */ function createUntitledDocument(counter, fileExt) { @@ -784,7 +784,7 @@ define(function (require, exports, module) { * FUTURE: Instead of an explicit notify, we should eventually listen for deletion events on some * sort of "project file model," making this just a private event handler. * - * @param {!FileEntry} file + * @param {!File} file * @param {boolean} skipAutoSelect - if true, don't automatically open/select the next document */ function notifyFileDeleted(file, skipAutoSelect) { diff --git a/src/file/FileUtils.js b/src/file/FileUtils.js index de6f967b503..7bb065d9c33 100644 --- a/src/file/FileUtils.js +++ b/src/file/FileUtils.js @@ -204,7 +204,7 @@ define(function (require, exports, module) { /** * Removes the trailing slash from a path, if it has one. * Warning: this differs from the format of most paths used in Brackets! Use paths ending in "/" - * normally, as this is the format used by DirectoryEntry.fullPath. + * normally, as this is the format used by Directory.fullPath. * * @param {string} path * @return {string} @@ -219,14 +219,14 @@ define(function (require, exports, module) { /** * Warning: Contrary to the name, this does NOT return a canonical path. The canonical format - * used by DirectoryEntry.fullPath actually DOES include the trailing "/" + * used by Directory.fullPath actually DOES include the trailing "/" * @deprecated * * @param {string} path * @return {string} */ function canonicalizeFolderPath(path) { - console.error("Warning: FileUtils.canonicalizeFolderPath() is deprecated. Use paths ending in '/' if possible, like DirectoryEntry.fullPath"); + console.error("Warning: FileUtils.canonicalizeFolderPath() is deprecated. Use paths ending in '/' if possible, like Directory.fullPath"); return stripTrailingSlash(path); } diff --git a/src/language/CodeInspection.js b/src/language/CodeInspection.js index a34acf42d7e..915d312aa20 100644 --- a/src/language/CodeInspection.js +++ b/src/language/CodeInspection.js @@ -150,26 +150,26 @@ define(function (require, exports, module) { * This method doesn't update the Brackets UI, just provides inspection results. * These results will reflect any unsaved changes present in the file that is currently opened. * - * @param {!File} fileEntry File that will be inspected for errors. + * @param {!File} file File that will be inspected for errors. * @param ?{{name:string, scanFile:function(string, string):?{!errors:Array, aborted:boolean}} provider * @return {$.Promise} a jQuery promise that will be resolved with ?{!errors:Array, aborted:boolean} */ - function inspectFile(fileEntry, provider) { + function inspectFile(file, provider) { var response = new $.Deferred(); - provider = provider || getProviderForPath(fileEntry.fullPath); + provider = provider || getProviderForPath(file.fullPath); if (!provider) { response.resolve(null); return response.promise(); } - DocumentManager.getDocumentText(fileEntry) + DocumentManager.getDocumentText(file) .done(function (fileText) { var result, - perfTimerInspector = PerfUtils.markStart("CodeInspection '" + provider.name + "':\t" + fileEntry.fullPath); + perfTimerInspector = PerfUtils.markStart("CodeInspection '" + provider.name + "':\t" + file.fullPath); try { - result = provider.scanFile(fileText, fileEntry.fullPath); + result = provider.scanFile(fileText, file.fullPath); } catch (err) { console.error("[CodeInspection] Provider " + provider.name + " threw an error: " + err); response.reject(err); @@ -180,7 +180,7 @@ define(function (require, exports, module) { response.resolve(result); }) .fail(function (err) { - console.error("[CodeInspection] Could not read file for inspection: " + fileEntry.fullPath); + console.error("[CodeInspection] Could not read file for inspection: " + file.fullPath); response.reject(err); }); diff --git a/src/project/ProjectManager.js b/src/project/ProjectManager.js index 2d1d10fe4bc..f8e62917cba 100644 --- a/src/project/ProjectManager.js +++ b/src/project/ProjectManager.js @@ -202,10 +202,10 @@ define(function (require, exports, module) { } /** - * Returns the FileEntry or DirectoryEntry corresponding to the item selected in the file tree, or null + * Returns the File or Directory corresponding to the item selected in the file tree, or null * if no item is selected in the tree (though the working set may still have a selection; use * getSelectedItem() to get the selection regardless of whether it's in the tree or working set). - * @return {?Entry} + * @return {?(File|Directory)} */ function _getTreeSelectedItem() { var selected = _projectTree.jstree("get_selected"); @@ -216,11 +216,11 @@ define(function (require, exports, module) { } /** - * Returns the FileEntry or DirectoryEntry corresponding to the item selected in the sidebar panel, whether in + * Returns the File or Directory corresponding to the item selected in the sidebar panel, whether in * the file tree OR in the working set; or null if no item is selected anywhere in the sidebar. * May NOT be identical to the current Document - a folder may be selected in the sidebar, or the sidebar may not * have the current document visible in the tree & working set. - * @return {?Entry} + * @return {?(File|Directory)} */ function getSelectedItem() { // Prefer file tree selection, else use working set selection @@ -276,7 +276,7 @@ define(function (require, exports, module) { /** * Returns the root folder of the currently loaded project, or null if no project is open (during * startup, or running outside of app shell). - * @return {DirectoryEntry} + * @return {Directory} */ function getProjectRoot() { return _projectRoot; @@ -766,7 +766,7 @@ define(function (require, exports, module) { dirEntry = _projectRoot; isProjectRoot = true; } else { - // All other nodes: the DirectoryEntry is saved as jQ data in the tree (by _convertEntriesToJSON()) + // All other nodes: the Directory is saved as jQ data in the tree (by _convertEntriesToJSON()) dirEntry = treeNode.data("entry"); } @@ -1028,7 +1028,7 @@ define(function (require, exports, module) { * Finds the tree node corresponding to the given file/folder (rejected if the path lies * outside the project, or if it doesn't exist). * - * @param {!Entry} entry FileEntry or DirectoryEntry to find + * @param {!(File|Directory)} entry File or Directory to find * @return {$.Promise} Resolved with jQ obj for the jsTree tree node; or rejected if not found */ function _findTreeNode(entry) { @@ -1045,7 +1045,7 @@ define(function (require, exports, module) { // We're going to traverse from root of tree, one segment at a time var pathSegments = projRelativePath.split("/"); if (entry.isDirectory) { - pathSegments.pop(); // DirectoryEntry always has a trailing "/" + pathSegments.pop(); // Directory always has a trailing "/" } function findInSubtree($nodes, segmentI) { @@ -1113,7 +1113,7 @@ define(function (require, exports, module) { * Expands tree nodes to show the given file or folder and selects it. Silently no-ops if the * path lies outside the project, or if it doesn't exist. * - * @param {!Entry} entry FileEntry or DirectoryEntry to show + * @param {!(File|Directory)} entry File or Directory to show * @return {$.Promise} Resolved when done; or rejected if not found */ function showInTree(entry) { @@ -1216,7 +1216,7 @@ define(function (require, exports, module) { * @param initialName {string} Initial name for the item * @param skipRename {boolean} If true, don't allow the user to rename the item * @param isFolder {boolean} If true, create a folder instead of a file - * @return {$.Promise} A promise object that will be resolved with the FileEntry + * @return {$.Promise} A promise object that will be resolved with the File * of the created object, or rejected if the user cancelled or entered an illegal * filename. */ @@ -1229,12 +1229,12 @@ define(function (require, exports, module) { result = new $.Deferred(), wasNodeOpen = true; - // get the FileEntry or DirectoryEntry + // get the File or Directory if (selection) { selectionEntry = selection.data("entry"); } - // move selection to parent DirectoryEntry + // move selection to parent Directory if (selectionEntry) { if (selectionEntry.isFile) { position = "after"; @@ -1254,7 +1254,7 @@ define(function (require, exports, module) { } } - // use the project root DirectoryEntry + // use the project root Directory if (!selectionEntry) { selectionEntry = getProjectRoot(); } @@ -1474,7 +1474,7 @@ define(function (require, exports, module) { /** * Initiates a rename of the selected item in the project tree, showing an inline editor * for input. Silently no-ops if the entry lies outside the tree or doesn't exist. - * @param {!Entry} entry FileEntry or DirectoryEntry to rename + * @param {!(File|Directory)} entry File or Directory to rename */ function renameItemInline(entry) { // First make sure the item in the tree is visible - jsTree's rename API doesn't do anything to ensure inline input is visible @@ -1539,7 +1539,7 @@ define(function (require, exports, module) { /** * Delete file or directore from project - * @param {!Entry} entry FileEntry or DirectoryEntry to delete + * @param {!(File|Directory)} entry File or Directory to delete */ function deleteItem(entry) { var result = new $.Deferred(); diff --git a/src/project/WorkingSetSort.js b/src/project/WorkingSetSort.js index d33430b6309..2f3e0beb240 100644 --- a/src/project/WorkingSetSort.js +++ b/src/project/WorkingSetSort.js @@ -174,7 +174,7 @@ define(function (require, exports, module) { * @private * * @param {string} commandID A valid command identifier. - * @param {function(FileEntry, FileEntry): number} compareFn A valid sort + * @param {function(File, File): number} compareFn A valid sort * function (see register for a longer explanation). * @param {string} events Space-separated DocumentManager possible events * ending with ".sort". @@ -190,7 +190,7 @@ define(function (require, exports, module) { return this._commandID; }; - /** @return {function(FileEntry, FileEntry): number} The compare function */ + /** @return {function(File, File): number} The compare function */ Sort.prototype.getCompareFn = function () { return this._compareFn; }; @@ -223,7 +223,7 @@ define(function (require, exports, module) { /** * Registers a working set sort method. * @param {(string|Command)} command A command ID or a command object - * @param {function(FileEntry, FileEntry): number} compareFn The function that + * @param {function(File, File): number} compareFn The function that * will be used inside JavaScript's sort function. The return a value * should be >0 (sort a to a lower index than b), =0 (leaves a and b * unchanged with respect to each other) or <0 (sort b to a lower index diff --git a/src/project/WorkingSetView.js b/src/project/WorkingSetView.js index 45e00be4889..03b9610a27d 100644 --- a/src/project/WorkingSetView.js +++ b/src/project/WorkingSetView.js @@ -93,7 +93,7 @@ define(function (require, exports, module) { /** * @private * Adds directory names to elements representing passed files in working tree - * @param {Array.} filesList - list of FileEntries with the same filename + * @param {Array.} filesList - list of Files with the same filename */ function _addDirectoryNamesToWorkingTreeFiles(filesList) { // filesList must have at least two files in it for this to make sense @@ -465,7 +465,7 @@ define(function (require, exports, module) { /** * Builds the UI for a new list item and inserts in into the end of the list * @private - * @param {FileEntry} file + * @param {File} file * @return {HTMLLIElement} newListItem */ function _createNewListItem(file) { @@ -517,7 +517,7 @@ define(function (require, exports, module) { /** * Finds the listItem item assocated with the file. Returns null if not found. * @private - * @param {!FileEntry} file + * @param {!File} file * @return {HTMLLIItem} */ function _findListItemFromFile(file) { @@ -607,7 +607,7 @@ define(function (require, exports, module) { /** * @private - * @param {FileEntry} file + * @param {File} file * @param {boolean=} suppressRedraw If true, suppress redraw */ function _handleFileRemoved(file, suppressRedraw) { diff --git a/src/search/FindInFiles.js b/src/search/FindInFiles.js index cdf023c2518..9265a6f265d 100644 --- a/src/search/FindInFiles.js +++ b/src/search/FindInFiles.js @@ -94,7 +94,7 @@ define(function (require, exports, module) { /** @type {RegExp} The current search query regular expression */ var currentQueryExpr = null; - /** @type {Array.} An array of the files where it should look or null/empty to search the entire project */ + /** @type {Array.} An array of the files where it should look or null/empty to search the entire project */ var currentScope = null; /** @type {boolean} True if the matches in a file reached FIND_IN_FILE_MAX */ diff --git a/src/utils/ExtensionLoader.js b/src/utils/ExtensionLoader.js index d7062dd695c..743bc010c77 100644 --- a/src/utils/ExtensionLoader.js +++ b/src/utils/ExtensionLoader.js @@ -326,8 +326,7 @@ define(function (require, exports, module) { // Load extensions before restoring the project - // Create a new DirectoryEntry and call getDirectory() on the user extension - // directory. If the directory doesn't exist, it will be created. + // Get a Directory for the user extension directory and create it if it doesn't exist. // Note that this is an async call and there are no success or failure functions passed // in. If the directory *doesn't* exist, it will be created. Extension loading may happen // before the directory is finished being created, but that is okay, since the extension diff --git a/src/utils/ViewUtils.js b/src/utils/ViewUtils.js index d67978fe2e6..f7e60af39b6 100644 --- a/src/utils/ViewUtils.js +++ b/src/utils/ViewUtils.js @@ -342,7 +342,7 @@ define(function (require, exports, module) { /** * HTML formats a file entry name for display in the sidebar. - * @param {!FileEntry} entry File entry to display + * @param {!File} entry File entry to display * @return {string} HTML formatted string */ function getFileEntryDisplay(entry) { diff --git a/test/spec/SpecRunnerUtils.js b/test/spec/SpecRunnerUtils.js index a1020ff8957..fe490288c53 100644 --- a/test/spec/SpecRunnerUtils.js +++ b/test/spec/SpecRunnerUtils.js @@ -140,7 +140,7 @@ define(function (require, exports, module) { /** - * Resolves a path string to a FileEntry or DirectoryEntry + * Resolves a path string to a File or Directory * @param {!string} path Path to a file or directory * @return {$.Promise} A promise resolved when the file/directory is found or * rejected when any error occurs. @@ -694,7 +694,7 @@ define(function (require, exports, module) { /** * Parses offsets from a file using offset markup (e.g. "{{1}}" for offset 1). - * @param {!FileEntry} entry File to open + * @param {!File} entry File to open * @return {$.Promise} A promise resolved with a record that contains parsed offsets, * the file text without offset markup, the original file content, and the corresponding * file entry. @@ -775,7 +775,7 @@ define(function (require, exports, module) { /** * Copy a file source path to a destination - * @param {!FileEntry} source Entry for the source file to copy + * @param {!File} source Entry for the source file to copy * @param {!string} destination Destination path to copy the source file * @param {?{parseOffsets:boolean}} options parseOffsets allows optional * offset markup parsing. File is written to the destination path @@ -801,7 +801,7 @@ define(function (require, exports, module) { offsets = parseInfo.offsets; } - // create the new FileEntry + // create the new File createTextFile(destination, text, FileSystem).done(function (entry) { deferred.resolve(entry, offsets, text); }).fail(function (err) { @@ -817,7 +817,7 @@ define(function (require, exports, module) { /** * Copy a directory source to a destination - * @param {!DirectoryEntry} source Entry for the source directory to copy + * @param {!Directory} source Directory for the source directory to copy * @param {!string} destination Destination path to copy the source directory * @param {?{parseOffsets:boolean, infos:Object, removePrefix:boolean}}} options * parseOffsets - allows optional offset markup parsing. File is written to the From 7561f75b39892fabaa2097791f433f1e459b9e3b Mon Sep 17 00:00:00 2001 From: Glenn Ruehle Date: Tue, 5 Nov 2013 13:59:03 -0800 Subject: [PATCH 144/163] Respond to review comments. --- src/document/Document.js | 3 +-- src/document/DocumentCommandHandlers.js | 14 +++++++------- src/editor/CSSInlineEditor.js | 1 - src/filesystem/FileSystem.js | 10 ++++++---- src/project/ProjectManager.js | 2 +- 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/document/Document.js b/src/document/Document.js index f0da4b09dce..47bd7f6abf5 100644 --- a/src/document/Document.js +++ b/src/document/Document.js @@ -423,11 +423,10 @@ define(function (require, exports, module) { this.file.stat(function (err, stat) { if (!err) { thisDoc.diskTimestamp = stat.mtime; - $(exports).triggerHandler("_documentSaved", thisDoc); } else { console.log("Error updating timestamp after saving file: " + thisDoc.file.fullPath); - $(exports).triggerHandler("_documentSaved", thisDoc); } + $(exports).triggerHandler("_documentSaved", thisDoc); }); }; diff --git a/src/document/DocumentCommandHandlers.js b/src/document/DocumentCommandHandlers.js index d96933f7b9d..15eb363abc3 100644 --- a/src/document/DocumentCommandHandlers.js +++ b/src/document/DocumentCommandHandlers.js @@ -399,7 +399,7 @@ define(function (require, exports, module) { entry.exists(function (exists) { if (exists) { //file exists, notify to the next progress - result.notify(baseFileName + "-" + _nextUntitledIndexToUse + fileExt, _nextUntitledIndexToUse + 1); + result.notify(baseFileName + "-" + _nextUntitledIndexToUse++ + fileExt); } else { result.resolve(suggestedName); } @@ -515,29 +515,29 @@ define(function (require, exports, module) { */ function doSave(docToSave) { var result = new $.Deferred(), - fileEntry = docToSave.file; + file = docToSave.file; function handleError(error) { - _showSaveFileError(error, fileEntry.fullPath) + _showSaveFileError(error, file.fullPath) .done(function () { result.reject(error); }); } - if (docToSave && docToSave.isDirty) { - var file = docToSave.file; + if (docToSave.isDirty) { var writeError = false; + // We don't want normalized line endings, so it's important to pass true to getText() FileUtils.writeText(file, docToSave.getText(true)) .done(function () { docToSave.notifySaved(); result.resolve(file); }) .fail(function (err) { - handleError(err, file); + handleError(err); }); } else { - result.resolve(fileEntry); + result.resolve(file); } result.always(function () { EditorManager.focusEditor(); diff --git a/src/editor/CSSInlineEditor.js b/src/editor/CSSInlineEditor.js index b0ab59eb9b3..a8a8b752221 100644 --- a/src/editor/CSSInlineEditor.js +++ b/src/editor/CSSInlineEditor.js @@ -37,7 +37,6 @@ define(function (require, exports, module) { EditorManager = require("editor/EditorManager"), Editor = require("editor/Editor").Editor, ProjectManager = require("project/ProjectManager"), - LanguageManager = require("language/LanguageManager"), HTMLUtils = require("language/HTMLUtils"), Menus = require("command/Menus"), MultiRangeInlineEditor = require("editor/MultiRangeInlineEditor"), diff --git a/src/filesystem/FileSystem.js b/src/filesystem/FileSystem.js index 58d7a8a58cb..76b4a371fb1 100644 --- a/src/filesystem/FileSystem.js +++ b/src/filesystem/FileSystem.js @@ -484,8 +484,9 @@ define(function (require, exports, module) { * browsed folder depending on the OS preferences * @param {Array.} fileTypes List of extensions that are allowed to be opened. A null value * allows any extension to be selected. - * @param {function (?string, Array.=)} callback Callback resolved with an error string or - * the selected file(s)/directories. + * @param {function (?string, Array.=)} callback Callback resolved with an error and the + * selected file(s)/directories. If the user cancels the open dialog, + * the error will be falsy and the file/directory array will be empty. */ FileSystem.prototype.showOpenDialog = function (allowMultipleSelection, chooseDirectories, @@ -506,8 +507,9 @@ define(function (require, exports, module) { * browsed folder depending on the OS preferences. * @param {string} proposedNewFilename Provide a new file name for the user. This could be based on * on the current file name plus an additional suffix - * @param {function (?string, string=)} callback Callback that is resolved with an error string or the - * name of the file to save. + * @param {function (?string, string=)} callback Callback that is resolved with an error and the + * name of the file to save. If the user cancels the save, the error + * will be falsy and the new name will be empty. */ FileSystem.prototype.showSaveDialog = function (title, initialPath, proposedNewFilename, callback) { this._impl.showSaveDialog(title, initialPath, proposedNewFilename, callback); diff --git a/src/project/ProjectManager.js b/src/project/ProjectManager.js index 2d1d10fe4bc..6ceb6675b71 100644 --- a/src/project/ProjectManager.js +++ b/src/project/ProjectManager.js @@ -934,7 +934,7 @@ define(function (require, exports, module) { _unwatchProjectRoot(); } - + // Clear project path map _projectInitialLoad = { previous : [], /* array of arrays containing full paths to open at each depth of the tree */ From 92ea58ba27b73521f40322cd7676e067146fff7e Mon Sep 17 00:00:00 2001 From: Ian Wehrman Date: Wed, 6 Nov 2013 11:17:08 -0800 Subject: [PATCH 145/163] Add FileSystemError.TOO_MANY_ENTRIES and a maxEntries option to FileSystemEntry.visit with default 30,000 --- src/filesystem/FileSystemEntry.js | 90 ++++++++++++++++++++++++------- src/filesystem/FileSystemError.js | 1 + 2 files changed, 71 insertions(+), 20 deletions(-) diff --git a/src/filesystem/FileSystemEntry.js b/src/filesystem/FileSystemEntry.js index 0c7864ddb9e..84c9db62570 100644 --- a/src/filesystem/FileSystemEntry.js +++ b/src/filesystem/FileSystemEntry.js @@ -28,6 +28,11 @@ define(function (require, exports, module) { "use strict"; + var FileSystemError = require("filesystem/FileSystemError"); + + var VISIT_DEFAULT_MAX_DEPTH = 10, + VISIT_DEFAULT_MAX_ENTRIES = 30000; + /* Counter to give every entry a unique id */ var nextId = 0; @@ -231,39 +236,36 @@ define(function (require, exports, module) { }; /** - * Visit this entry and its descendents with the supplied visitor function. + * Private helper function for FileSystemEntry.visit that requires sanitized options. * + * @private * @param {function(FileSystemEntry): boolean} visitor - A visitor function, which is * applied to descendent FileSystemEntry objects. If the function returns false for * a particular Directory entry, that directory's descendents will not be visited. - * @param {{failFast: boolean=, maxDepth: number=}=} options - An optional set of options. + * @param {{failFast: boolean, maxDepth: number, maxEntriesCounter: {value: number}}} options * @param {function(?string)=} callback Callback with single "error" parameter. */ - FileSystemEntry.prototype.visit = function (visitor, options, callback) { - var DEFAULT_MAX_DEPTH = 100; + FileSystemEntry.prototype._visitHelper = function (visitor, options, callback) { + var failFast = options.failFast, + maxDepth = options.maxDepth, + maxEntriesCounter = options.maxEntriesCounter; - if (typeof options === "function") { - callback = options; - options = {}; - } else if (options === undefined) { - options = {}; + if (maxEntriesCounter.value-- < 0 || maxDepth-- < 0) { + callback(failFast ? FileSystemError.TOO_MANY_ENTRIES : null); + return; } - callback = callback || function () {}; - - var maxDepth = typeof options.maxDepth === "number" ? options.maxDepth : DEFAULT_MAX_DEPTH, - continueTraversal = visitor(this) && maxDepth-- > 0; - - if (this.isFile || !continueTraversal) { + if (!visitor(this) || this.isFile) { callback(null); return; } this.getContents(function (err, entries) { var counter = entries ? entries.length : 0, - newOptions = { + nextOptions = { + failFast: failFast, maxDepth: maxDepth, - failFast: options.maxDepth + maxEntriesCounter: maxEntriesCounter }; if (err || counter === 0) { @@ -272,21 +274,69 @@ define(function (require, exports, module) { } entries.forEach(function (entry) { - entry.visit(visitor, newOptions, function (err) { - if (err && options.failFast) { + entry._visitHelper(visitor, nextOptions, function (err) { + if (err && failFast) { counter = 0; callback(err); return; } if (--counter === 0) { - callback(options.failFast ? err : null); + callback(failFast ? err : null); } }); }); }.bind(this)); }; + /** + * Visit this entry and its descendents with the supplied visitor function. + * + * @param {function(FileSystemEntry): boolean} visitor - A visitor function, which is + * applied to descendent FileSystemEntry objects. If the function returns false for + * a particular Directory entry, that directory's descendents will not be visited. + * @param {{failFast: boolean=, maxDepth: number=, maxEntries: number=}=} options - An optional set of options. + * @param {function(?string)=} callback Callback with single "error" parameter. + */ + FileSystemEntry.prototype.visit = function (visitor, options, callback) { + if (typeof options === "function") { + callback = options; + options = {}; + } else if (options === undefined) { + options = {}; + } + + if (options.failFast === undefined) { + options.failFast = false; + } + + 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._visitHelper(visitor, options, function (err) { + if (callback) { + if (err) { + callback(err); + return; + } + + if (options.maxEntriesCounter.value < 0) { + callback(FileSystemError.TOO_MANY_ENTRIES); + return; + } + + callback(null); + } + }); + }; + // Export this class module.exports = FileSystemEntry; }); diff --git a/src/filesystem/FileSystemError.js b/src/filesystem/FileSystemError.js index ed896ef5761..d54130b21b3 100644 --- a/src/filesystem/FileSystemError.js +++ b/src/filesystem/FileSystemError.js @@ -41,6 +41,7 @@ define(function (require, exports, module) { NOT_READABLE : "NotReadable", NOT_WRITABLE : "NotWritable", OUT_OF_SPACE : "OutOfSpace", + TOO_MANY_ENTRIES : "TooManyEntries", ALREADY_EXISTS : "AlreadyExists" // FUTURE: Add remote connection errors: timeout, not logged in, connection err, etc. }; From 7bff28ef0c1e14b0758a826d2294493fc1bda0b3 Mon Sep 17 00:00:00 2001 From: Ian Wehrman Date: Wed, 6 Nov 2013 11:38:57 -0800 Subject: [PATCH 146/163] Show what used to be the FileIndexManager too-many-files error dialog on project load if the filesystem watch fails with TOO_MANY_ENTRIES. --- src/filesystem/FileSystem.js | 1 + src/nls/root/strings.js | 4 ++++ src/project/ProjectManager.js | 16 ++++++++++++++-- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/filesystem/FileSystem.js b/src/filesystem/FileSystem.js index 76b4a371fb1..c2e9288d439 100644 --- a/src/filesystem/FileSystem.js +++ b/src/filesystem/FileSystem.js @@ -693,6 +693,7 @@ define(function (require, exports, module) { this._watchEntry(entry, watchedRoot, function (err) { if (err) { console.warn("Failed to watch root: ", entry.fullPath, err); + callback(err); return; } diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index e8b8974612e..f86cf620aa6 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -67,6 +67,10 @@ define({ // Application error strings "ERROR_IN_BROWSER_TITLE" : "Oops! {APP_NAME} doesn't run in browsers yet.", "ERROR_IN_BROWSER" : "{APP_NAME} is built in HTML, but right now it runs as a desktop app so you can use it to edit local files. Please use the application shell in the github.com/adobe/brackets-shell repo to run {APP_NAME}.", + + // ProjectManager max files error string + "ERROR_MAX_FILES_TITLE" : "Error Indexing Files", + "ERROR_MAX_FILES" : "The maximum number of files have been indexed. Actions that look up files in the index may function incorrectly.", // Live Development error strings "ERROR_LAUNCHING_BROWSER_TITLE" : "Error launching browser", diff --git a/src/project/ProjectManager.js b/src/project/ProjectManager.js index 2095be13dda..986def44183 100644 --- a/src/project/ProjectManager.js +++ b/src/project/ProjectManager.js @@ -873,13 +873,25 @@ define(function (require, exports, module) { return updateWelcomeProjectPath(_prefs.getValue("projectPath")); } + /** + * Error dialog when max files in index is hit + * @return {Dialog} + */ + function _showMaxFilesDialog() { + return Dialogs.showModalDialog( + DefaultDialogs.DIALOG_ID_ERROR, + Strings.ERROR_MAX_FILES_TITLE, + Strings.ERROR_MAX_FILES + ); + } + function _watchProjectRoot(rootPath) { FileSystem.on("change", _fileSystemChange); FileSystem.on("rename", _fileSystemRename); FileSystem.watch(FileSystem.getDirectoryForPath(rootPath), _shouldShowName, function (err) { - if (err) { - console.log("Error watching project root: ", rootPath, err); + if (err === FileSystemError.TOO_MANY_ENTRIES) { + _showMaxFilesDialog(); } }); } From cc52b50621045d7f8a9e3be52eba89f7eff0477e Mon Sep 17 00:00:00 2001 From: Ian Wehrman Date: Wed, 6 Nov 2013 15:35:48 -0800 Subject: [PATCH 147/163] Off-by-one error --- src/filesystem/FileSystemEntry.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/filesystem/FileSystemEntry.js b/src/filesystem/FileSystemEntry.js index 84c9db62570..9a8a8ee6cf5 100644 --- a/src/filesystem/FileSystemEntry.js +++ b/src/filesystem/FileSystemEntry.js @@ -250,7 +250,7 @@ define(function (require, exports, module) { maxDepth = options.maxDepth, maxEntriesCounter = options.maxEntriesCounter; - if (maxEntriesCounter.value-- < 0 || maxDepth-- < 0) { + if (maxEntriesCounter.value-- <= 0 || maxDepth-- < 0) { callback(failFast ? FileSystemError.TOO_MANY_ENTRIES : null); return; } From af294e10fe706680a1d158c92d413404472a801e Mon Sep 17 00:00:00 2001 From: Ian Wehrman Date: Wed, 6 Nov 2013 15:37:00 -0800 Subject: [PATCH 148/163] If the file is created during by a write, send a change notification for the parent and not the new file. --- test/spec/MockFileSystemImpl.js | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/test/spec/MockFileSystemImpl.js b/test/spec/MockFileSystemImpl.js index 8549142604a..69070e25029 100644 --- a/test/spec/MockFileSystemImpl.js +++ b/test/spec/MockFileSystemImpl.js @@ -272,18 +272,21 @@ define(function (require, exports, module) { options = null; } - var cb = _getCallback("writeFile", path, callback), - notify = _getNotification("writeFile", path, _sendWatcherNotification); - - if (!_data[path]) { - _data[path] = { - isFile: true - }; - } - _data[path].contents = data; - _data[path].mtime = Date.now(); - cb(null); - notify(path); + exists(path, function (exists) { + var cb = _getCallback("writeFile", path, callback), + notification = exists ? _sendWatcherNotification : _sendDirectoryWatcherNotification, + notify = _getNotification("writeFile", path, notification); + + if (!_data[path]) { + _data[path] = { + isFile: true + }; + } + _data[path].contents = data; + _data[path].mtime = Date.now(); + cb(null); + notify(path); + }); } function unlink(path, callback) { From d1b698c3090f855c3381f641752c08ee7c18ee0e Mon Sep 17 00:00:00 2001 From: Ian Wehrman Date: Wed, 6 Nov 2013 15:45:43 -0800 Subject: [PATCH 149/163] Add tests for FileSystemEntry.visit --- test/spec/FileSystem-test.js | 148 +++++++++++++++++++++++++++++++++++ 1 file changed, 148 insertions(+) diff --git a/test/spec/FileSystem-test.js b/test/spec/FileSystem-test.js index 59c9fbe70dd..daa909ff3dc 100644 --- a/test/spec/FileSystem-test.js +++ b/test/spec/FileSystem-test.js @@ -502,6 +502,154 @@ define(function (require, exports, module) { }); }); + describe("FileSystemEntry.visit", function () { + function getContentsCallback() { + var callback = function (err, contents) { + callback.error = err; + callback.contents = contents; + callback.wasCalled = true; + }; + return callback; + } + + beforeEach(function () { + function initEntry(entry, command, args) { + var cb = getContentsCallback(); + + args.push(cb); + runs(function () { + entry[command].apply(entry, args); + }); + waitsFor(function () { return cb.wasCalled; }); + runs(function () { + expect(cb.error).toBeFalsy(); + }); + } + + function initDir(path) { + initEntry(fileSystem.getDirectoryForPath(path), "create", []); + } + + function initFile(path) { + initEntry(fileSystem.getFileForPath(path), "write", ["abc"]); + } + + initDir("/visit/"); + initFile("/visit/file.txt"); + initDir("/visit/subdir1/"); + initDir("/visit/subdir2/"); + initFile("/visit/subdir1/subfile11.txt"); + initFile("/visit/subdir1/subfile12.txt"); + initFile("/visit/subdir2/subfile21.txt"); + initFile("/visit/subdir2/subfile22.txt"); + }); + + it("should visit all entries by default", function () { + var directory = fileSystem.getDirectoryForPath("/visit/"), + results = {}, + visitor = function (entry) { + results[entry.fullPath] = entry; + return true; + }; + + var cb = getContentsCallback(); + runs(function () { + directory.visit(visitor, cb); + }); + waitsFor(function () { return cb.wasCalled; }); + runs(function () { + expect(cb.error).toBeFalsy(); + expect(Object.keys(results).length).toBe(8); + expect(results["/visit/"]).toBeTruthy(); + expect(results["/visit/file.txt"]).toBeTruthy(); + expect(results["/visit/subdir1/"]).toBeTruthy(); + expect(results["/visit/subdir2/"]).toBeTruthy(); + expect(results["/visit/subdir1/subfile11.txt"]).toBeTruthy(); + expect(results["/visit/subdir1/subfile12.txt"]).toBeTruthy(); + expect(results["/visit/subdir2/subfile21.txt"]).toBeTruthy(); + expect(results["/visit/subdir2/subfile21.txt"]).toBeTruthy(); + expect(results["/"]).not.toBeTruthy(); + }); + }); + + it("should visit with a specified maximum depth", function () { + var directory = fileSystem.getDirectoryForPath("/visit/"), + results = {}, + visitor = function (entry) { + results[entry.fullPath] = entry; + return true; + }; + + var cb = getContentsCallback(); + runs(function () { + directory.visit(visitor, {maxDepth: 1}, cb); + }); + waitsFor(function () { return cb.wasCalled; }); + runs(function () { + expect(cb.error).toBeFalsy(); + expect(Object.keys(results).length).toBe(4); + expect(results["/visit/"]).toBeTruthy(); + expect(results["/visit/file.txt"]).toBeTruthy(); + expect(results["/visit/subdir1/"]).toBeTruthy(); + expect(results["/visit/subdir2/"]).toBeTruthy(); + expect(results["/visit/subdir1/subfile11.txt"]).not.toBeTruthy(); + expect(results["/visit/subdir1/subfile12.txt"]).not.toBeTruthy(); + expect(results["/visit/subdir2/subfile21.txt"]).not.toBeTruthy(); + expect(results["/visit/subdir2/subfile21.txt"]).not.toBeTruthy(); + expect(results["/"]).not.toBeTruthy(); + }); + }); + + it("should visit with a specified maximum number of entries", function () { + var directory = fileSystem.getDirectoryForPath("/visit/"), + results = {}, + visitor = function (entry) { + results[entry.fullPath] = entry; + return true; + }; + + var cb = getContentsCallback(); + runs(function () { + directory.visit(visitor, {maxEntries: 4}, cb); + }); + waitsFor(function () { return cb.wasCalled; }); + runs(function () { + expect(cb.error).toBe(FileSystemError.TOO_MANY_ENTRIES); + expect(Object.keys(results).length).toBe(4); + expect(results["/visit/"]).toBeTruthy(); + expect(results["/visit/file.txt"]).toBeTruthy(); + expect(results["/"]).not.toBeTruthy(); + }); + }); + + it("should visit only children of directories admitted by the filter", function () { + var directory = fileSystem.getDirectoryForPath("/visit/"), + results = {}, + visitor = function (entry) { + results[entry.fullPath] = entry; + return entry.name === "visit" || /1$/.test(entry.name); + }; + + var cb = getContentsCallback(); + runs(function () { + directory.visit(visitor, cb); + }); + waitsFor(function () { return cb.wasCalled; }); + runs(function () { + expect(cb.error).toBeFalsy(); + expect(Object.keys(results).length).toBe(6); + expect(results["/visit/"]).toBeTruthy(); + expect(results["/visit/file.txt"]).toBeTruthy(); + expect(results["/visit/subdir1/"]).toBeTruthy(); + expect(results["/visit/subdir2/"]).toBeTruthy(); + expect(results["/visit/subdir1/subfile11.txt"]).toBeTruthy(); + expect(results["/visit/subdir1/subfile12.txt"]).toBeTruthy(); + expect(results["/visit/subdir2/subfile21.txt"]).not.toBeTruthy(); + expect(results["/visit/subdir2/subfile21.txt"]).not.toBeTruthy(); + expect(results["/"]).not.toBeTruthy(); + }); + }); + }); describe("Event timing", function () { From 4d6c73de5163538fcde0d634e9739a130f0b8a42 Mon Sep 17 00:00:00 2001 From: Ian Wehrman Date: Wed, 6 Nov 2013 16:16:08 -0800 Subject: [PATCH 150/163] Only mark one category of LanguageManager tests as integration tests instead of the entire suite; remove some redundant cleanup --- test/spec/LanguageManager-test.js | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/test/spec/LanguageManager-test.js b/test/spec/LanguageManager-test.js index 9bde071a5cd..4b484b66e04 100644 --- a/test/spec/LanguageManager-test.js +++ b/test/spec/LanguageManager-test.js @@ -36,7 +36,6 @@ define(function (require, exports, module) { FileSystem = require("filesystem/FileSystem"); describe("LanguageManager", function () { - this.category = "integration"; beforeEach(function () { waitsForDone(LanguageManager.ready, "LanguageManager ready", 10000); @@ -403,6 +402,7 @@ define(function (require, exports, module) { }); describe("rename file extension", function () { + this.category = "integration"; it("should update the document's language when a file is renamed", function () { var tempDir = SpecRunnerUtils.getTempDirectory(), @@ -475,7 +475,6 @@ define(function (require, exports, module) { }); waitsForDone(renameDeferred.promise(), "old file rename"); - var unlinkDeferred = $.Deferred(); runs(function () { html = LanguageManager.getLanguage("html"); @@ -490,16 +489,7 @@ define(function (require, exports, module) { // cleanup doc.releaseRef(); - - oldFile.unlink(function (err) { - if (err) { - unlinkDeferred.reject(err); - } else { - unlinkDeferred.resolve(); - } - }); }); - waitsForDone(unlinkDeferred.promise(), "new file unlink"); SpecRunnerUtils.closeTestWindow(); From 0fd2e149dd70bc7ea63f494a6cd641ea8b20d16b Mon Sep 17 00:00:00 2001 From: Glenn Ruehle Date: Wed, 6 Nov 2013 16:32:15 -0800 Subject: [PATCH 151/163] Respond to more review comments. --- src/document/DocumentManager.js | 86 ++++++++++--------- src/document/InMemoryFile.js | 2 +- src/editor/ImageViewer.js | 2 +- src/extensions/default/DebugCommands/main.js | 4 +- .../default/JavaScriptCodeHints/unittests.js | 10 +-- .../default/JavaScriptQuickEdit/main.js | 26 +++--- src/extensions/default/RecentProjects/main.js | 8 +- src/language/CSSUtils.js | 4 +- src/project/ProjectManager.js | 17 ++-- src/search/FindInFiles.js | 7 ++ src/search/QuickOpen.js | 1 - src/utils/ExtensionLoader.js | 36 +++----- test/spec/CSSUtils-test.js | 2 +- test/spec/LiveDevelopment-test.js | 24 +++--- test/spec/ProjectManager-test.js | 2 +- test/spec/SpecRunnerUtils.js | 29 ++----- 16 files changed, 125 insertions(+), 135 deletions(-) diff --git a/src/document/DocumentManager.js b/src/document/DocumentManager.js index 4b50d1e73f0..f3afc93f9dc 100644 --- a/src/document/DocumentManager.js +++ b/src/document/DocumentManager.js @@ -630,8 +630,8 @@ define(function (require, exports, module) { var id; // Need to walk all open documents and check for matching path. We can't - // use getFileForPath(fullPath).id since that won't match untitled - // documents... + // use getFileForPath(fullPath).id since the file it returns won't match + // an Untitled document's InMemoryFile. for (id in _openDocuments) { if (_openDocuments.hasOwnProperty(id)) { if (_openDocuments[id].file.fullPath === fullPath) { @@ -658,56 +658,60 @@ define(function (require, exports, module) { * with a FileSystemError if the file is not yet open and can't be read from disk. */ function getDocumentForPath(fullPath) { - var file = FileSystem.getFileForPath(fullPath), - doc = getOpenDocumentForPath(fullPath), - pendingPromise = getDocumentForPath._pendingDocumentPromises[file.id]; + var doc = getOpenDocumentForPath(fullPath); if (doc) { // use existing document return new $.Deferred().resolve(doc).promise(); - } else if (pendingPromise) { - // wait for the result of a previous request - return pendingPromise; } else { - var result = new $.Deferred(), - promise = result.promise(); + // 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); - result.reject(); - return promise; + return new $.Deferred().reject().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 - delete getDocumentForPath._pendingDocumentPromises[file.id]; - }) - .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); - }); + var file = FileSystem.getFileForPath(fullPath), + pendingPromise = getDocumentForPath._pendingDocumentPromises[file.id]; - return promise; + 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 + delete getDocumentForPath._pendingDocumentPromises[file.id]; + }) + .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; + } } } diff --git a/src/document/InMemoryFile.js b/src/document/InMemoryFile.js index c38e54a38b8..5de57065111 100644 --- a/src/document/InMemoryFile.js +++ b/src/document/InMemoryFile.js @@ -27,7 +27,7 @@ /** * Represents a file that will never exist on disk - a placeholder backing file for untitled Documents. NO ONE - * other than DocumentManager should create insteances of InMemoryFile. It is valid to test for one (`instanceof + * other than DocumentManager should create instances of InMemoryFile. It is valid to test for one (`instanceof * InMemoryFile`), but it's better to check `doc.isUntitled` where possible. * * Attempts to read/write an InMemoryFile will always fail, and exists() always yields false. InMemoryFile.fullPath diff --git a/src/editor/ImageViewer.js b/src/editor/ImageViewer.js index b1a5b06aafe..a32fff31c45 100644 --- a/src/editor/ImageViewer.js +++ b/src/editor/ImageViewer.js @@ -106,7 +106,7 @@ define(function (require, exports, module) { $("#img-data").text(dimensionString); } else { var sizeString = ""; - if (stat && stat.size) { + if (stat.size) { sizeString = " — " + StringUtils.prettyPrintBytes(stat.size, 2); } $("#img-data").html(dimensionString + sizeString); diff --git a/src/extensions/default/DebugCommands/main.js b/src/extensions/default/DebugCommands/main.js index 1263791716e..8fefab7d794 100644 --- a/src/extensions/default/DebugCommands/main.js +++ b/src/extensions/default/DebugCommands/main.js @@ -142,7 +142,7 @@ define(function (require, exports, module) { function _handleSwitchLanguage() { var stringsPath = FileUtils.getNativeBracketsDirectoryPath() + "/nls"; - FileSystem.getDirectoryForPath(stringsPath).getContents(function (err, contents) { + FileSystem.getDirectoryForPath(stringsPath).getContents(function (err, entries) { if (!err) { var $dialog, $submit, @@ -172,7 +172,7 @@ define(function (require, exports, module) { languages.push({label: getLocalizedLabel("en"), language: "en"}); // inspect all children of dirEntry - contents.forEach(function (entry) { + entries.forEach(function (entry) { if (entry.isDirectory) { var match = entry.name.match(/^([a-z]{2})(-[a-z]{2})?$/); diff --git a/src/extensions/default/JavaScriptCodeHints/unittests.js b/src/extensions/default/JavaScriptCodeHints/unittests.js index abf36208b05..5cd4501dac4 100644 --- a/src/extensions/default/JavaScriptCodeHints/unittests.js +++ b/src/extensions/default/JavaScriptCodeHints/unittests.js @@ -744,12 +744,10 @@ define(function (require, exports, module) { end = { line: 6, ch: 8 }, endplus = { line: 6, ch: 12 }; - runs(function () { - testDoc.replaceRange("A1.prop", start, start); - testEditor.setCursorPos(middle); - var hintObj = expectHints(JSCodeHints.jsHintProvider); - selectHint(JSCodeHints.jsHintProvider, hintObj, "propA"); - }); + testDoc.replaceRange("A1.prop", start, start); + testEditor.setCursorPos(middle); + var hintObj = expectHints(JSCodeHints.jsHintProvider); + selectHint(JSCodeHints.jsHintProvider, hintObj, "propA"); runs(function () { expect(testEditor.getCursorPos()).toEqual(end); diff --git a/src/extensions/default/JavaScriptQuickEdit/main.js b/src/extensions/default/JavaScriptQuickEdit/main.js index bb1cf5010ca..0634c8bc6c5 100644 --- a/src/extensions/default/JavaScriptQuickEdit/main.js +++ b/src/extensions/default/JavaScriptQuickEdit/main.js @@ -75,17 +75,21 @@ define(function (require, exports, module) { PerfUtils.markStart(PerfUtils.JAVASCRIPT_FIND_FUNCTION); - ProjectManager.getAllFiles().done(function (files) { - JSUtils.findMatchingFunctions(functionName, files) - .done(function (functions) { - PerfUtils.addMeasurement(PerfUtils.JAVASCRIPT_FIND_FUNCTION); - result.resolve(functions); - }) - .fail(function () { - PerfUtils.finalizeMeasurement(PerfUtils.JAVASCRIPT_FIND_FUNCTION); - result.reject(); - }); - }); + ProjectManager.getAllFiles() + .done(function (files) { + JSUtils.findMatchingFunctions(functionName, files) + .done(function (functions) { + PerfUtils.addMeasurement(PerfUtils.JAVASCRIPT_FIND_FUNCTION); + result.resolve(functions); + }) + .fail(function () { + PerfUtils.finalizeMeasurement(PerfUtils.JAVASCRIPT_FIND_FUNCTION); + result.reject(); + }); + }) + .fail(function () { + result.reject(); + }); return result.promise(); } diff --git a/src/extensions/default/RecentProjects/main.js b/src/extensions/default/RecentProjects/main.js index c6f6ded1bb2..e204b3c0f3c 100644 --- a/src/extensions/default/RecentProjects/main.js +++ b/src/extensions/default/RecentProjects/main.js @@ -280,10 +280,10 @@ define(function (require, exports, module) { */ function _handleListEvents() { $dropdown - .on("click", "a", function (e) { - var $link = $(e.target).closest("a"), - id = $link.attr("id"), - path = $link.data("path"); + .on("click", "a", function () { + var $link = $(this), + id = $link.attr("id"), + path = $link.data("path"); if (path) { ProjectManager.openProject(path) diff --git a/src/language/CSSUtils.js b/src/language/CSSUtils.js index eca317edd52..9466e897997 100644 --- a/src/language/CSSUtils.js +++ b/src/language/CSSUtils.js @@ -22,8 +22,8 @@ */ -/*jslint vars: true, plusplus: true, regexp: true, devel: true, nomen: true, indent: 4, maxerr: 50 */ -/*global define, $, CodeMirror, _parseRuleList: true, PathUtils */ +/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50, regexp: true */ +/*global define, $, CodeMirror, _parseRuleList: true */ // JSLint Note: _parseRuleList() is cyclical dependency, not a global function. // It was added to this list to prevent JSLint warning about being used before being defined. diff --git a/src/project/ProjectManager.js b/src/project/ProjectManager.js index 2095be13dda..6ac0db31352 100644 --- a/src/project/ProjectManager.js +++ b/src/project/ProjectManager.js @@ -90,7 +90,6 @@ define(function (require, exports, module) { * http://www.gnu.org/software/tar/manual/html_section/exclude.html * @type {RegExp} */ - var _exclusionListRegEx = /\.pyc$|^\.git$|^\.gitignore$|^\.gitmodules$|^\.svn$|^\.DS_Store$|^Thumbs\.db$|^\.hg$|^CVS$|^\.cvsignore$|^\.gitattributes$|^\.hgtags$|^\.c9revisions|^\.SyncArchive|^\.SyncID|^\.SyncIgnore|^\.hgignore$/; /** @@ -771,7 +770,7 @@ define(function (require, exports, module) { } // Fetch dirEntry's contents - dirEntry.getContents(function (err, contents) { + dirEntry.getContents(function (err, contents, statsErrs) { if (err) { Dialogs.showModalDialog( DefaultDialogs.DIALOG_ID_ERROR, @@ -785,6 +784,10 @@ define(function (require, exports, module) { // Reject the render promise so we can move on. deferred.reject(); } else { + if (statsErrs) { + // some but not all entries failed to load, so render what we can + console.warn("Error reading a subset of folder " + dirEntry); + } processEntries(contents); } }); @@ -879,7 +882,7 @@ define(function (require, exports, module) { FileSystem.watch(FileSystem.getDirectoryForPath(rootPath), _shouldShowName, function (err) { if (err) { - console.log("Error watching project root: ", rootPath, err); + console.error("Error watching project root: ", rootPath, err); } }); } @@ -896,7 +899,7 @@ define(function (require, exports, module) { FileSystem.unwatch(_projectRoot, function (err) { if (err) { - console.log("Error unwatching project root: ", _projectRoot.fullPath, err); + console.error("Error unwatching project root: ", _projectRoot.fullPath, err); } }); } @@ -1367,10 +1370,10 @@ define(function (require, exports, module) { var file = FileSystem.getFileForPath(newItemPath); file.write("", function (err) { - if (!err) { - successCallback(file); - } else { + if (err) { errorCallback(err); + } else { + successCallback(file); } }); } diff --git a/src/search/FindInFiles.js b/src/search/FindInFiles.js index 9265a6f265d..2e8811d2a71 100644 --- a/src/search/FindInFiles.js +++ b/src/search/FindInFiles.js @@ -875,6 +875,13 @@ define(function (require, exports, module) { if (entry && entry.isDirectory) { var resultsChanged = false; + // This is a temporary watcher implementation that needs to be updated + // once we have our final watcher API. Specifically, we will be adding + // 'added' and 'removed' parameters to this function to easily determine + // which files/folders have been added or removed. + // + // In the meantime, do a quick check for directory changed events to see + // if any of the search results files have been deleted. if (searchResultsPanel.isVisible()) { entry.getContents(function (err, contents) { if (!err) { diff --git a/src/search/QuickOpen.js b/src/search/QuickOpen.js index 05693ba2e34..79594fb1382 100644 --- a/src/search/QuickOpen.js +++ b/src/search/QuickOpen.js @@ -841,7 +841,6 @@ define(function (require, exports, module) { // Start fetching the file list, which will be needed the first time the user enters an un-prefixed query. If file index // caches are out of date, this list might take some time to asynchronously build. See searchFileList() for how this is handled. - this._filenameMatcher.reset(); fileListPromise = ProjectManager.getAllFiles(true) .done(function (files) { fileList = files; diff --git a/src/utils/ExtensionLoader.js b/src/utils/ExtensionLoader.js index 743bc010c77..53d257b8121 100644 --- a/src/utils/ExtensionLoader.js +++ b/src/utils/ExtensionLoader.js @@ -192,29 +192,21 @@ define(function (require, exports, module) { function testExtension(name, config, entryPoint) { var result = new $.Deferred(), extensionPath = config.baseUrl + "/" + entryPoint + ".js"; - - var fileExists = false, statComplete = false; - var file = FileSystem.getFileForPath(extensionPath); - - file.stat(function (err, stat) { - if (!err) { - statComplete = true; - if (stat.isFile) { - // unit test file exists - var extensionRequire = brackets.libRequire.config({ - context: name, - baseUrl: config.baseUrl, - paths: $.extend({}, config.paths, globalConfig) - }); - // console.log("[Extension] loading unit test " + config.baseUrl); - extensionRequire([entryPoint], function () { - // console.log("[Extension] loaded unit tests " + config.baseUrl); - result.resolve(); - }); - } else { - result.reject(); - } + FileSystem.resolve(extensionPath, function (err, entry) { + if (!err && entry.isFile) { + // unit test file exists + var extensionRequire = brackets.libRequire.config({ + context: name, + baseUrl: config.baseUrl, + paths: $.extend({}, config.paths, globalConfig) + }); + + // console.log("[Extension] loading unit test " + config.baseUrl); + extensionRequire([entryPoint], function () { + // console.log("[Extension] loaded unit tests " + config.baseUrl); + result.resolve(); + }); } else { result.reject(); } diff --git a/test/spec/CSSUtils-test.js b/test/spec/CSSUtils-test.js index 39c1bd4caf5..b67be3f9047 100644 --- a/test/spec/CSSUtils-test.js +++ b/test/spec/CSSUtils-test.js @@ -22,7 +22,7 @@ */ /*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, indent: 4, maxerr: 50 */ -/*global brackets, define, describe, xdescribe, it, xit, expect, beforeEach, afterEach, waitsFor, runs, $, CodeMirror, beforeFirst, afterLast */ +/*global define, describe, xdescribe, it, xit, expect, beforeEach, afterEach, waitsFor, runs, $, CodeMirror, beforeFirst, afterLast */ define(function (require, exports, module) { "use strict"; diff --git a/test/spec/LiveDevelopment-test.js b/test/spec/LiveDevelopment-test.js index 935cf1ce4ed..b1af49ebabe 100644 --- a/test/spec/LiveDevelopment-test.js +++ b/test/spec/LiveDevelopment-test.js @@ -999,7 +999,7 @@ define(function (require, exports, module) { SpecRunnerUtils.closeTestWindow(); }); - function loadFileAndUpdateFileIndex(fileToLoadIntoEditor) { + function loadFile(fileToLoadIntoEditor) { runs(function () { waitsForDone(SpecRunnerUtils.openProjectFiles([fileToLoadIntoEditor]), "SpecRunnerUtils.openProjectFiles " + fileToLoadIntoEditor); }); @@ -1039,7 +1039,7 @@ define(function (require, exports, module) { indexFile = "sub/sub2/index.html"; SpecRunnerUtils.loadProjectInTestWindow(testPath + "/static-project-1"); - loadFileAndUpdateFileIndex(cssFile); + loadFile(cssFile); runs(function () { promise = LiveDevelopment._getInitialDocFromCurrent(); @@ -1064,7 +1064,7 @@ define(function (require, exports, module) { SpecRunnerUtils.loadProjectInTestWindow(testPath + "/static-project-2"); - loadFileAndUpdateFileIndex(cssFile); + loadFile(cssFile); runs(function () { promise = LiveDevelopment._getInitialDocFromCurrent(); @@ -1088,7 +1088,7 @@ define(function (require, exports, module) { indexFile = "index.html"; SpecRunnerUtils.loadProjectInTestWindow(testPath + "/static-project-3"); - loadFileAndUpdateFileIndex(cssFile); + loadFile(cssFile); runs(function () { promise = LiveDevelopment._getInitialDocFromCurrent(); @@ -1112,7 +1112,7 @@ define(function (require, exports, module) { indexFile = "sub/sub2/index.html"; SpecRunnerUtils.loadProjectInTestWindow(testPath + "/static-project-4"); - loadFileAndUpdateFileIndex(cssFile); + loadFile(cssFile); runs(function () { promise = LiveDevelopment._getInitialDocFromCurrent(); @@ -1136,7 +1136,7 @@ define(function (require, exports, module) { indexFile = "sub/index.html"; SpecRunnerUtils.loadProjectInTestWindow(testPath + "/static-project-5"); - loadFileAndUpdateFileIndex(cssFile); + loadFile(cssFile); runs(function () { promise = LiveDevelopment._getInitialDocFromCurrent(); @@ -1159,7 +1159,7 @@ define(function (require, exports, module) { var cssFile = "top2/test.css"; SpecRunnerUtils.loadProjectInTestWindow(testPath + "/static-project-6"); - loadFileAndUpdateFileIndex(cssFile); + loadFile(cssFile); runs(function () { promise = LiveDevelopment._getInitialDocFromCurrent(); @@ -1211,7 +1211,7 @@ define(function (require, exports, module) { indexFile = "sub/sub2/index.php"; SpecRunnerUtils.loadProjectInTestWindow(testPath + "/dynamic-project-1"); - loadFileAndUpdateFileIndex(cssFile); + loadFile(cssFile); runs(function () { ProjectManager.setBaseUrl("http://localhost:1111/"); @@ -1236,7 +1236,7 @@ define(function (require, exports, module) { indexFile = "sub/index.php"; SpecRunnerUtils.loadProjectInTestWindow(testPath + "/dynamic-project-2"); - loadFileAndUpdateFileIndex(cssFile); + loadFile(cssFile); runs(function () { ProjectManager.setBaseUrl("http://localhost:2222/"); @@ -1261,7 +1261,7 @@ define(function (require, exports, module) { indexFile = "index.php"; SpecRunnerUtils.loadProjectInTestWindow(testPath + "/dynamic-project-3"); - loadFileAndUpdateFileIndex(cssFile); + loadFile(cssFile); runs(function () { ProjectManager.setBaseUrl("http://localhost:3333/"); @@ -1286,7 +1286,7 @@ define(function (require, exports, module) { indexFile = "sub/index.php"; SpecRunnerUtils.loadProjectInTestWindow(testPath + "/dynamic-project-5"); - loadFileAndUpdateFileIndex(cssFile); + loadFile(cssFile); runs(function () { ProjectManager.setBaseUrl("http://localhost:5555/"); @@ -1310,7 +1310,7 @@ define(function (require, exports, module) { var cssFile = "top2/test.css"; SpecRunnerUtils.loadProjectInTestWindow(testPath + "/dynamic-project-6"); - loadFileAndUpdateFileIndex(cssFile); + loadFile(cssFile); runs(function () { ProjectManager.setBaseUrl("http://localhost:6666/"); diff --git a/test/spec/ProjectManager-test.js b/test/spec/ProjectManager-test.js index 20a6c6e657e..12f3499d805 100644 --- a/test/spec/ProjectManager-test.js +++ b/test/spec/ProjectManager-test.js @@ -23,7 +23,7 @@ /*jslint vars: true, plusplus: true, devel: true, browser: true, nomen: true, indent: 4, maxerr: 50 */ -/*global $, define, require, describe, it, xit, expect, beforeEach, afterEach, waitsFor, runs, waitsForDone, beforeFirst, afterLast */ +/*global $, define, require, describe, it, expect, beforeEach, afterEach, waitsFor, runs, waitsForDone, beforeFirst, afterLast */ define(function (require, exports, module) { "use strict"; diff --git a/test/spec/SpecRunnerUtils.js b/test/spec/SpecRunnerUtils.js index fe490288c53..ba397d76003 100644 --- a/test/spec/SpecRunnerUtils.js +++ b/test/spec/SpecRunnerUtils.js @@ -81,6 +81,7 @@ define(function (require, exports, module) { if (err === FileSystemError.NOT_FOUND && silent) { result.resolve(); } else { + console.error("Unable to remove " + fullPath, err); result.reject(err); } } @@ -241,24 +242,6 @@ define(function (require, exports, module) { waitsForDone(deferred, "Create temp directory", 500); } - /** - * @private - */ - function _stat(pathname) { - var deferred = new $.Deferred(); - - FileSystem.resolve(pathname, function (err, entry) { - if (err) { - deferred.reject(err); - return; - } - - deferred.resolve(entry); - }); - - return deferred; - } - function _resetPermissionsOnSpecialTempFolders() { var i, folders = [], @@ -271,19 +254,19 @@ define(function (require, exports, module) { promise = Async.doSequentially(folders, function (folder) { var deferred = new $.Deferred(); - _stat(folder) - .done(function () { + FileSystem.resolve(folder, function (err, entry) { + if (!err) { // Change permissions if the directory exists chmod(folder, "777").then(deferred.resolve, deferred.reject); - }) - .fail(function (err) { + } else { if (err === FileSystemError.NOT_FOUND) { // Resolve the promise since the folder to reset doesn't exist deferred.resolve(); } else { deferred.reject(); } - }); + } + }); return deferred.promise(); }, true); From 629bc8fbb6f2979c9d3a97910708fa7a51356b31 Mon Sep 17 00:00:00 2001 From: Ian Wehrman Date: Thu, 7 Nov 2013 09:59:38 -0800 Subject: [PATCH 152/163] Fix the failFast callback err logic in FSE.visit --- src/filesystem/FileSystemEntry.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/filesystem/FileSystemEntry.js b/src/filesystem/FileSystemEntry.js index 9a8a8ee6cf5..65dc02c2e92 100644 --- a/src/filesystem/FileSystemEntry.js +++ b/src/filesystem/FileSystemEntry.js @@ -269,7 +269,7 @@ define(function (require, exports, module) { }; if (err || counter === 0) { - callback(err); + callback(failFast ? err : null); return; } @@ -282,7 +282,7 @@ define(function (require, exports, module) { } if (--counter === 0) { - callback(failFast ? err : null); + callback(null); } }); }); From 4d834816fec0040be7df3424960555253f7eb7ac Mon Sep 17 00:00:00 2001 From: Ian Wehrman Date: Thu, 7 Nov 2013 10:08:50 -0800 Subject: [PATCH 153/163] Do not watch filtered directories --- src/filesystem/FileSystem.js | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/filesystem/FileSystem.js b/src/filesystem/FileSystem.js index c2e9288d439..20af3f40c6e 100644 --- a/src/filesystem/FileSystem.js +++ b/src/filesystem/FileSystem.js @@ -206,15 +206,18 @@ define(function (require, exports, module) { } var visitor = function (child) { - if (child.isDirectory || child === watchedRoot.entry) { - watchPaths.push(child.fullPath); - } + if (watchedRoot.filter(child.name)) { + if (child.isDirectory || child === watchedRoot.entry) { + watchPaths.push(child.fullPath); + } + + if (!shouldWatch) { + allChildren.push(child); + } - if (!shouldWatch) { - allChildren.push(child); + return true; } - - return watchedRoot.filter(child.name); + return false; }.bind(this); entry.visit(visitor, function (err) { From b34a1a3fcd68b3ebf6197f0c0aed59f80ede678a Mon Sep 17 00:00:00 2001 From: Ian Wehrman Date: Thu, 7 Nov 2013 10:09:43 -0800 Subject: [PATCH 154/163] Directory.getContents has four parameters, the last of which is statsErrors --- src/project/ProjectManager.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/project/ProjectManager.js b/src/project/ProjectManager.js index 12d70a98ce1..5b2a7415dc3 100644 --- a/src/project/ProjectManager.js +++ b/src/project/ProjectManager.js @@ -770,7 +770,7 @@ define(function (require, exports, module) { } // Fetch dirEntry's contents - dirEntry.getContents(function (err, contents, statsErrs) { + dirEntry.getContents(function (err, contents, stats, statsErrs) { if (err) { Dialogs.showModalDialog( DefaultDialogs.DIALOG_ID_ERROR, From 1b626bd9dddcf0c438161fe6e3fc4dccf97f9181 Mon Sep 17 00:00:00 2001 From: Ian Wehrman Date: Thu, 7 Nov 2013 10:14:19 -0800 Subject: [PATCH 155/163] Initialize _watchResults in the constructor --- src/filesystem/FileSystem.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/filesystem/FileSystem.js b/src/filesystem/FileSystem.js index 20af3f40c6e..2ac37eb0dfc 100644 --- a/src/filesystem/FileSystem.js +++ b/src/filesystem/FileSystem.js @@ -84,6 +84,8 @@ define(function (require, exports, module) { // Initialize the watch/unwatch request queue this._watchRequests = []; + + this._watchResults = []; } /** @@ -115,7 +117,7 @@ define(function (require, exports, module) { * Queue of arguments to invoke _handleWatchResult() with; triggered once _writeCount drops to zero * @type {!Array.<{path:string, stat:Object}>} */ - FileSystem.prototype._watchResults = []; + FileSystem.prototype._watchResults = null; /** Process all queued watcher results, by calling _handleWatchResult() on each */ FileSystem.prototype._triggerWatchCallbacksNow = function () { From 453f472afd3dc5c7585482328c277a2c6148baf0 Mon Sep 17 00:00:00 2001 From: Ian Wehrman Date: Thu, 7 Nov 2013 10:49:10 -0800 Subject: [PATCH 156/163] Wrap sensitive callbacks in a try-catch block --- src/filesystem/Directory.js | 8 ++- src/filesystem/File.js | 2 + src/filesystem/FileSystem.js | 34 +++++++----- src/filesystem/FileSystemEntry.js | 2 + .../impls/appshell/AppshellFileSystem.js | 54 ++++++++++++------- 5 files changed, 66 insertions(+), 34 deletions(-) diff --git a/src/filesystem/Directory.js b/src/filesystem/Directory.js index aaf5a957c19..2ba52ca33a4 100644 --- a/src/filesystem/Directory.js +++ b/src/filesystem/Directory.js @@ -155,8 +155,12 @@ define(function (require, exports, module) { // Invoke all saved callbacks currentCallbacks.forEach(function (cb) { - cb(err, this._contents, this._contentsStats, this._contentsStatsErrors); - }.bind(this)); + try { + cb(err, this._contents, this._contentsStats, this._contentsStatsErrors); + } catch (ex) { + console.warn("Unhandled exception in callback: ", ex); + } + }, this); }.bind(this)); }; diff --git a/src/filesystem/File.js b/src/filesystem/File.js index aa2e84e47c0..e18a3b8090e 100644 --- a/src/filesystem/File.js +++ b/src/filesystem/File.js @@ -104,6 +104,8 @@ define(function (require, exports, module) { // this._contents = data; } callback(err, stat); + } catch (ex) { + console.warn("Unhandled exception in callback: ", ex); } finally { this._fileSystem._endWrite(); // unblock generic change events } diff --git a/src/filesystem/FileSystem.js b/src/filesystem/FileSystem.js index 2ac37eb0dfc..21feb15d708 100644 --- a/src/filesystem/FileSystem.js +++ b/src/filesystem/FileSystem.js @@ -22,7 +22,7 @@ */ /*jslint vars: true, plusplus: true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50 */ -/*global define, $, setTimeout */ +/*global define, $ */ /** * FileSystem is a model object representing a complete file system. This object creates @@ -90,7 +90,7 @@ define(function (require, exports, module) { /** * The low-level file system implementation used by this object. - * This is set in the constructor and cannot be changed. + * This is set in the init() function and cannot be changed. */ FileSystem.prototype._impl = null; @@ -123,7 +123,7 @@ define(function (require, exports, module) { FileSystem.prototype._triggerWatchCallbacksNow = function () { this._watchResults.forEach(function (info) { this._handleWatchResult(info.path, info.stat); - }.bind(this)); + }, this); this._watchResults.length = 0; }; @@ -149,13 +149,15 @@ define(function (require, exports, module) { request.fn.call(null, function () { // Apply the given callback var callbackArgs = arguments; - setTimeout(function () { + try { request.cb.apply(null, callbackArgs); - }, 0); - - // Process the remaining watch/unwatch requests - this._watchRequests.shift(); - this._dequeueWatchRequest(); + } catch (ex) { + console.warn("Unhandled exception in callback: ", ex); + } finally { + // Process the remaining watch/unwatch requests + this._watchRequests.shift(); + this._dequeueWatchRequest(); + } }.bind(this)); } }; @@ -295,10 +297,16 @@ define(function (require, exports, module) { console.assert(!this._impl, "This FileSystem has already been initialized!"); this._impl = impl; - this._impl.init(callback); - - // Initialize watchers - this._impl.initWatchers(this._enqueueWatchResult.bind(this)); + this._impl.init(function (err) { + if (err) { + callback(err); + return; + } + + // Initialize watchers + this._impl.initWatchers(this._enqueueWatchResult.bind(this)); + callback(null); + }); }; /** diff --git a/src/filesystem/FileSystemEntry.js b/src/filesystem/FileSystemEntry.js index 65dc02c2e92..1e59b00a28c 100644 --- a/src/filesystem/FileSystemEntry.js +++ b/src/filesystem/FileSystemEntry.js @@ -188,6 +188,8 @@ define(function (require, exports, module) { this._fileSystem._entryRenamed(this._path, newFullPath, this.isDirectory); } callback(err); // notify caller + } catch (ex) { + console.warn("Unhandled exception in callback: ", ex); } finally { this._fileSystem._endWrite(); // unblock generic change events } diff --git a/src/filesystem/impls/appshell/AppshellFileSystem.js b/src/filesystem/impls/appshell/AppshellFileSystem.js index a100ddeaaf7..307afc20e88 100644 --- a/src/filesystem/impls/appshell/AppshellFileSystem.js +++ b/src/filesystem/impls/appshell/AppshellFileSystem.js @@ -207,10 +207,14 @@ define(function (require, exports, module) { callback(_mapError(err)); } else { stat(path, function (err, stat) { - callback(err, stat); - - // Fake a file-watcher result until real watchers respond quickly - _changeCallback(_parentPath(path)); + try { + callback(err, stat); + } catch (ex) { + console.warn("Unhandled exception in callback: ", ex); + } finally { + // Fake a file-watcher result until real watchers respond quickly + _changeCallback(_parentPath(path)); + } }); } }); @@ -273,13 +277,17 @@ define(function (require, exports, module) { callback(_mapError(err)); } else { stat(path, function (err, stat) { - callback(err, stat); - - // Fake a file-watcher result until real watchers respond quickly - if (alreadyExists) { - _changeCallback(path, stat); // existing file modified - } else { - _changeCallback(_parentPath(path)); // new file created + try { + callback(err, stat); + } catch (ex) { + console.warn("Unhandled exception in callback: ", ex); + } finally { + // Fake a file-watcher result until real watchers respond quickly + if (alreadyExists) { + _changeCallback(path, stat); // existing file modified + } else { + _changeCallback(_parentPath(path)); // new file created + } } }); } @@ -290,19 +298,27 @@ define(function (require, exports, module) { function unlink(path, callback) { appshell.fs.unlink(path, function (err) { - callback(_mapError(err)); - - // Fake a file-watcher result until real watchers respond quickly - _changeCallback(_parentPath(path)); + try { + callback(_mapError(err)); + } catch (ex) { + console.warn("Unhandled exception in callback: ", ex); + } finally { + // Fake a file-watcher result until real watchers respond quickly + _changeCallback(_parentPath(path)); + } }); } function moveToTrash(path, callback) { appshell.fs.moveToTrash(path, function (err) { - callback(_mapError(err)); - - // Fake a file-watcher result until real watchers respond quickly - _changeCallback(_parentPath(path)); + try { + callback(_mapError(err)); + } catch (ex) { + console.warn("Unhandled exception in callback: ", ex); + } finally { + // Fake a file-watcher result until real watchers respond quickly + _changeCallback(_parentPath(path)); + } }); } From 7b5042e0931e0ec991893a3daecb3e866e056638 Mon Sep 17 00:00:00 2001 From: Ian Wehrman Date: Thu, 7 Nov 2013 11:07:59 -0800 Subject: [PATCH 157/163] Unbreak FileSystem.init --- src/filesystem/FileSystem.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/filesystem/FileSystem.js b/src/filesystem/FileSystem.js index 21feb15d708..dc25184a5a7 100644 --- a/src/filesystem/FileSystem.js +++ b/src/filesystem/FileSystem.js @@ -291,11 +291,13 @@ define(function (require, exports, module) { }; /** - * @param {function(?string)} callback Callback resolved, possibly with an error string. + * @param {function(?string)=} callback Callback resolved, possibly with an error string. */ FileSystem.prototype.init = function (impl, callback) { console.assert(!this._impl, "This FileSystem has already been initialized!"); + callback = callback || function () {}; + this._impl = impl; this._impl.init(function (err) { if (err) { @@ -306,7 +308,7 @@ define(function (require, exports, module) { // Initialize watchers this._impl.initWatchers(this._enqueueWatchResult.bind(this)); callback(null); - }); + }.bind(this)); }; /** From d77cf167b747fc1a1a1f0a2bb27bdf106a7e64e4 Mon Sep 17 00:00:00 2001 From: Glenn Ruehle Date: Thu, 7 Nov 2013 12:24:50 -0800 Subject: [PATCH 158/163] More cleanup --- src/extensions/default/DebugCommands/main.js | 3 +++ src/extensions/default/JavaScriptCodeHints/ScopeManager.js | 2 ++ 2 files changed, 5 insertions(+) diff --git a/src/extensions/default/DebugCommands/main.js b/src/extensions/default/DebugCommands/main.js index 8fefab7d794..478e58f7211 100644 --- a/src/extensions/default/DebugCommands/main.js +++ b/src/extensions/default/DebugCommands/main.js @@ -218,6 +218,9 @@ define(function (require, exports, module) { file.exists(function (exists) { if (exists) { + // If the SpecRunner.html file exists, enable the menu item. + // (menu item is already disabled, so no need to disable if the + // file doesn't exist). CommandManager.get(DEBUG_RUN_UNIT_TESTS).setEnabled(true); } }); diff --git a/src/extensions/default/JavaScriptCodeHints/ScopeManager.js b/src/extensions/default/JavaScriptCodeHints/ScopeManager.js index 3c3a913c273..8a41494f3cb 100644 --- a/src/extensions/default/JavaScriptCodeHints/ScopeManager.js +++ b/src/extensions/default/JavaScriptCodeHints/ScopeManager.js @@ -89,6 +89,8 @@ define(function (require, exports, module) { }).fail(function (error) { console.log("failed to read tern config file " + i); }); + } else { + console.log("failed to read tern config file " + i); } }); }); From 7595fdc0272501e48f8728ee9a271a561a4a23d4 Mon Sep 17 00:00:00 2001 From: Ian Wehrman Date: Thu, 7 Nov 2013 14:07:05 -0800 Subject: [PATCH 159/163] Many miscellaneous fixes from Peter's and Glenn's reviews --- src/document/DocumentCommandHandlers.js | 8 ++ src/document/DocumentManager.js | 2 +- src/document/InMemoryFile.js | 8 +- src/file/FileUtils.js | 2 +- src/filesystem/Directory.js | 27 +++-- src/filesystem/File.js | 42 ++++--- src/filesystem/FileIndex.js | 4 +- src/filesystem/FileSystem.js | 110 +++++++++--------- src/filesystem/FileSystemEntry.js | 40 +++++-- src/filesystem/FileSystemStats.js | 2 +- .../impls/appshell/AppshellFileSystem.js | 27 ++--- src/language/JSUtils.js | 2 +- test/spec/FileSystem-test.js | 45 +++---- test/spec/LanguageManager-test.js | 4 +- test/spec/MockFileSystemImpl.js | 3 +- 15 files changed, 180 insertions(+), 146 deletions(-) diff --git a/src/document/DocumentCommandHandlers.js b/src/document/DocumentCommandHandlers.js index bb0e91443ea..0436516f163 100644 --- a/src/document/DocumentCommandHandlers.js +++ b/src/document/DocumentCommandHandlers.js @@ -776,6 +776,14 @@ define(function (require, exports, module) { }); } + /** + * Saves all unsaved documents. Returns a Promise that will be resolved once ALL the save + * operations have been completed. If ANY save operation fails, an error dialog is immediately + * shown and the other files wait to save until it is dismissed; after all files have been + * processed, the Promise is rejected if any ONE save operation failed. + * + * @return {$.Promise} + */ function saveAll() { return _saveFileList(DocumentManager.getWorkingSet()); } diff --git a/src/document/DocumentManager.js b/src/document/DocumentManager.js index f3afc93f9dc..887497c3c24 100644 --- a/src/document/DocumentManager.js +++ b/src/document/DocumentManager.js @@ -745,7 +745,7 @@ define(function (require, exports, module) { if (doc) { result.resolve(doc.getText()); } else { - file.readAsText(function (err, contents) { + file.read(function (err, contents) { if (err) { result.reject(err); } else { diff --git a/src/document/InMemoryFile.js b/src/document/InMemoryFile.js index 5de57065111..5fd275dbd54 100644 --- a/src/document/InMemoryFile.js +++ b/src/document/InMemoryFile.js @@ -59,12 +59,12 @@ define(function (require, exports, module) { * * Read a file as text. * - * @param {string=} encoding Encoding for reading. Defaults to UTF-8. + * @param {object=} options Currently unused. * @param {function (number, string, object)} callback */ - InMemoryFile.prototype.readAsText = function (encoding, callback) { - if (typeof (encoding) === "function") { - callback = encoding; + InMemoryFile.prototype.read = function (options, callback) { + if (typeof (options) === "function") { + callback = options; } callback(FileSystemError.NOT_FOUND); }; diff --git a/src/file/FileUtils.js b/src/file/FileUtils.js index 7bb065d9c33..4a128e0f95e 100644 --- a/src/file/FileUtils.js +++ b/src/file/FileUtils.js @@ -58,7 +58,7 @@ define(function (require, exports, module) { }); // Read file - file.readAsText(function (err, data, stat) { + file.read(function (err, data, stat) { if (!err) { result.resolve(data, stat.mtime); } else { diff --git a/src/filesystem/Directory.js b/src/filesystem/Directory.js index 2ba52ca33a4..293cf074e43 100644 --- a/src/filesystem/Directory.js +++ b/src/filesystem/Directory.js @@ -23,7 +23,7 @@ /*jslint vars: true, plusplus: true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50 */ -/*global define, $ */ +/*global define */ define(function (require, exports, module) { "use strict"; @@ -37,6 +37,8 @@ define(function (require, exports, module) { * This class should *not* be instantiated directly. Use FileSystem.getDirectoryForPath, * FileSystem.resolve, or Directory.getContents to create an instance of this class. * + * Note: Directory.fullPath always has a trailing slash. + * * See the FileSystem class for more details. * * @param {!string} fullPath The full path for this Directory. @@ -70,6 +72,17 @@ define(function (require, exports, module) { */ Directory.prototype._contentsStatsErrors = null; + /** + * Clear any cached data for this directory + * @private + */ + Directory.prototype._clearCachedData = function () { + this.parentClass._clearCachedData.apply(this); + this._contents = undefined; + this._contentsStats = undefined; + this._contentsStatsErrors = undefined; + }; + /** * Read the contents of a Directory. * @@ -101,14 +114,12 @@ define(function (require, exports, module) { this._contentsCallbacks = [callback]; this._impl.readdir(this.fullPath, function (err, contents, stats) { - this._contentsStatsErrors = undefined; - if (err) { - this._contents = undefined; - this._contentsStats = undefined; + this._clearCachedData(); } else { this._contents = []; this._contentsStats = []; + this._contentsStatsErrors = undefined; contents.forEach(function (name, index) { var entryPath = this.fullPath + name, @@ -133,7 +144,7 @@ define(function (require, exports, module) { // to file content may be messaged EITHER as a watcher change // directly on that file, OR as a watcher change to its parent dir) // TODO: move this to FileSystem._handleWatchResult()? - entry._contents = undefined; + entry._clearCachedData(); } else { entry = this._fileSystem.getDirectoryForPath(entryPath); } @@ -167,8 +178,8 @@ define(function (require, exports, module) { /** * Create a directory * - * @param {function (?string, FileSystemStats=)=} callback Callback resolved with an error - * string or the stat object for the created directory. + * @param {function (?string, FileSystemStats=)=} callback Callback resolved with a + * FileSystemError string or the stat object for the created directory. */ Directory.prototype.create = function (callback) { callback = callback || function () {}; diff --git a/src/filesystem/File.js b/src/filesystem/File.js index e18a3b8090e..f6e1200529c 100644 --- a/src/filesystem/File.js +++ b/src/filesystem/File.js @@ -23,7 +23,7 @@ /*jslint vars: true, plusplus: true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50 */ -/*global define, $ */ +/*global define */ define(function (require, exports, module) { "use strict"; @@ -58,19 +58,29 @@ define(function (require, exports, module) { File.prototype._contents = null; /** - * Read a file as text. + * Clear any cached data for this file + * @private + */ + File.prototype._clearCachedData = function () { + this.parentClass._clearCachedData.apply(this); + this._contents = undefined; + }; + + + /** + * Read a file. * - * @param {string=} encoding Encoding for reading. Defaults to UTF-8. + * @param {object=} options Currently unused. * @param {function (?string, string=, FileSystemStats=)} callback Callback that is passed the - * error code, the file's contents, and the file's new stats. + * FileSystemError string or the file's contents and its stats. */ - File.prototype.readAsText = function (encoding, callback) { - if (typeof (encoding) === "function") { - callback = encoding; - encoding = null; + File.prototype.read = function (options, callback) { + if (typeof (options) === "function") { + callback = options; + options = {}; } - this._impl.readFile(this._path, encoding ? {encoding: encoding} : {}, function (err, data, stat) { + this._impl.readFile(this._path, options, function (err, data, stat) { if (!err) { this._stat = stat; // this._contents = data; @@ -83,21 +93,21 @@ define(function (require, exports, module) { * Write a file. * * @param {string} data Data to write. - * @param {string=} encoding Encoding for data. Defaults to UTF-8. + * @param {object=} options Currently unused. * @param {!function (?string, FileSystemStats=)=} callback Callback that is passed the - * error code and the file's new stats if the write is sucessful. + * FileSystemError string or the file's new stats. */ - File.prototype.write = function (data, encoding, callback) { - if (typeof (encoding) === "function") { - callback = encoding; - encoding = "utf8"; + File.prototype.write = function (data, options, callback) { + if (typeof (options) === "function") { + callback = options; + options = {}; } callback = callback || function () {}; this._fileSystem._beginWrite(); - this._impl.writeFile(this._path, data, encoding ? {encoding: encoding} : {}, function (err, stat) { + this._impl.writeFile(this._path, data, options, function (err, stat) { try { if (!err) { this._stat = stat; diff --git a/src/filesystem/FileIndex.js b/src/filesystem/FileIndex.js index 2834622cb7b..e6af676c600 100644 --- a/src/filesystem/FileIndex.js +++ b/src/filesystem/FileIndex.js @@ -42,8 +42,10 @@ define(function (require, exports, module) { /** * Master index + * + * @type{object{string: File|Directory}} Maps a fullPath to a File or Directory object */ - FileIndex.prototype._index = {}; + FileIndex.prototype._index = null; /** * Clear the file index cache. diff --git a/src/filesystem/FileSystem.js b/src/filesystem/FileSystem.js index dc25184a5a7..2d0941885f2 100644 --- a/src/filesystem/FileSystem.js +++ b/src/filesystem/FileSystem.js @@ -188,6 +188,27 @@ define(function (require, exports, module) { */ 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. @@ -197,7 +218,7 @@ define(function (require, exports, module) { * non-strict descendent of watchedRoot.entry. * @param {Object} watchedRoot - See FileSystem._watchedRoots. * @param {function(?string)} callback - A function that is called once the - * watch is complete. + * watch is complete, possibly with a FileSystemError string. * @param {boolean} shouldWatch - Whether the entry should be watched (true) * or unwatched (false). */ @@ -270,7 +291,7 @@ define(function (require, exports, module) { * non-strict descendent of watchedRoot.entry. * @param {Object} watchedRoot - See FileSystem._watchedRoots. * @param {function(?string)} callback - A function that is called once the - * watch is complete. + * watch is complete, possibly with a FileSystemError string. */ FileSystem.prototype._watchEntry = function (entry, watchedRoot, callback) { this._watchOrUnwatchEntry(entry, watchedRoot, callback, true); @@ -284,14 +305,15 @@ define(function (require, exports, module) { * non-strict descendent of watchedRoot.entry. * @param {Object} watchedRoot - See FileSystem._watchedRoots. * @param {function(?string)} callback - A function that is called once the - * watch is complete. + * watch is complete, possibly with a FileSystemError string. */ FileSystem.prototype._unwatchEntry = function (entry, watchedRoot, callback) { this._watchOrUnwatchEntry(entry, watchedRoot, callback, false); }; /** - * @param {function(?string)=} callback Callback resolved, possibly with an error string. + * @param {function(?string)=} callback Callback resolved, possibly with a + * FileSystemError string. */ FileSystem.prototype.init = function (impl, callback) { console.assert(!this._impl, "This FileSystem has already been initialized!"); @@ -328,15 +350,8 @@ define(function (require, exports, module) { * of this filtering - but they will not be watched if the watch-root's filter excludes them. */ FileSystem.prototype._indexFilter = function (path, name) { - var parentRoot; - - Object.keys(this._watchedRoots).some(function (watchedPath) { - if (path.indexOf(watchedPath) === 0) { - parentRoot = this._watchedRoots[watchedPath]; - return true; - } - }, this); - + var parentRoot = this._findWatchedRootForPath(path); + if (parentRoot) { return parentRoot.filter(name); } @@ -365,8 +380,14 @@ define(function (require, exports, module) { } }; - FileSystem.isAbsolutePath = function (path) { - return (path[0] === "/" || path[1] === ":"); + /** + * Determines whether or not the supplied path is absolute, as opposed to relative. + * + * @param {!string} fullPath + * @return {boolean} True if the fullPath is absolute and false otherwise. + */ + FileSystem.isAbsolutePath = function (fullPath) { + return (fullPath[0] === "/" || fullPath[1] === ":"); }; /** @@ -414,7 +435,7 @@ define(function (require, exports, module) { /** * Return a File object for the specified path. * - * @param {string} path Path of file. + * @param {string} path Absolute path of file. * * @return {File} The File object. This file may not yet exist on disk. */ @@ -433,7 +454,7 @@ define(function (require, exports, module) { /** * Return a Directory object for the specified path. * - * @param {string} path Path of directory. Pass NULL to get the root directory. + * @param {string} path Absolute path of directory. * * @return {Directory} The Directory object. This directory may not yet exist on disk. */ @@ -454,7 +475,7 @@ define(function (require, exports, module) { * * @param {string} path The path to resolve * @param {function (?string, FileSystemEntry=, FileSystemStats=)} callback Callback resolved - * with an error string or with the entry for the provided path + * with a FileSystemError string or with the entry for the provided path. */ FileSystem.prototype.resolve = function (path, callback) { // No need to normalize path here: assume underlying stat() does it internally, @@ -499,9 +520,10 @@ define(function (require, exports, module) { * browsed folder depending on the OS preferences * @param {Array.} fileTypes List of extensions that are allowed to be opened. A null value * allows any extension to be selected. - * @param {function (?string, Array.=)} callback Callback resolved with an error and the - * selected file(s)/directories. If the user cancels the open dialog, - * the error will be falsy and the file/directory array will be empty. + * @param {function (?string, Array.=)} callback Callback resolved with a FileSystemError + * string or the selected file(s)/directories. If the user cancels the + * open dialog, the error will be falsy and the file/directory array will + * be empty. */ FileSystem.prototype.showOpenDialog = function (allowMultipleSelection, chooseDirectories, @@ -522,9 +544,9 @@ define(function (require, exports, module) { * browsed folder depending on the OS preferences. * @param {string} proposedNewFilename Provide a new file name for the user. This could be based on * on the current file name plus an additional suffix - * @param {function (?string, string=)} callback Callback that is resolved with an error and the - * name of the file to save. If the user cancels the save, the error - * will be falsy and the new name will be empty. + * @param {function (?string, string=)} callback Callback that is resolved with a FileSystemError + * string or the name of the file to save. If the user cancels the save, + * the error will be falsy and the name will be empty. */ FileSystem.prototype.showSaveDialog = function (title, initialPath, proposedNewFilename, callback) { this._impl.showSaveDialog(title, initialPath, proposedNewFilename, callback); @@ -536,7 +558,7 @@ define(function (require, exports, module) { * whenever a directory or file is changed. * * @param {string} path The path that changed. This could be a file or a directory. - * @param {stat=} stat Optional stat for the item that changed. This param is not always + * @param {FileSystemStats=} stat Optional stat for the item that changed. This param is not always * passed. */ FileSystem.prototype._handleWatchResult = function (path, stat) { @@ -546,17 +568,12 @@ define(function (require, exports, module) { $(this).trigger("change", entry); }.bind(this); - if (!this._index) { - return; - } - if (!path) { // This is a "wholesale" change event // Clear all caches (at least those that won't do a stat() double-check before getting used) this._index.visitAll(function (entry) { if (entry.isDirectory) { - entry._stat = undefined; - entry._contents = undefined; + entry._clearCachedData(); } }); @@ -571,8 +588,8 @@ define(function (require, exports, module) { if (entry.isFile) { // Update stat and clear contents, but only if out of date if (!stat || !entry._stat || (stat.mtime.getTime() !== entry._stat.mtime.getTime())) { + entry._clearCachedData(); entry._stat = stat; - entry._contents = undefined; } fireChangeEvent(entry); @@ -580,19 +597,10 @@ define(function (require, exports, module) { var oldContents = entry._contents || []; // Clear out old contents + entry._clearCachedData(); entry._stat = stat; - entry._contents = undefined; - - var watchedRootPath = null, - allWatchedRootPaths = Object.keys(this._watchedRoots), - isRootWatched = allWatchedRootPaths.some(function (rootPath) { - if (entry.fullPath.indexOf(rootPath) === 0) { - watchedRootPath = rootPath; - return true; - } - }), - watchedRoot = isRootWatched ? this._watchedRoots[watchedRootPath] : null; + var watchedRoot = this._findWatchedRootForPath(entry.fullPath); if (!watchedRoot) { console.warn("Received change notification for unwatched path: ", path); return; @@ -667,8 +675,8 @@ define(function (require, exports, module) { * a particular name should be watched or ignored. Paths that are ignored are also * filtered from Directory.getContents() results within this subtree. * @param {function(?string)=} callback - A function that is called when the watch has - * completed. If the watch fails, the function will have a non-null parameter - * that describes the error. + * completed. If the watch fails, the function will have a non-null FileSystemError + * string parametr. */ FileSystem.prototype.watch = function (entry, filter, callback) { var fullPath = entry.fullPath, @@ -679,13 +687,7 @@ define(function (require, exports, module) { callback = callback || function () {}; - var watchingParentRoot = Object.keys(this._watchedRoots).some(function (path) { - var watchedRoot = this._watchedRoots[path], - watchedPath = watchedRoot.entry.fullPath; - - return fullPath.indexOf(watchedPath) === 0; - }, this); - + var watchingParentRoot = this._findWatchedRootForPath(fullPath); if (watchingParentRoot) { callback("A parent of this root is already watched"); return; @@ -722,8 +724,8 @@ define(function (require, exports, module) { * @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 - * completed. If the unwatch fails, the function will have a non-null parameter - * that describes the error. + * completed. If the unwatch fails, the function will have a non-null FileSystemError + * string parameter. */ FileSystem.prototype.unwatch = function (entry, callback) { var fullPath = entry.fullPath, diff --git a/src/filesystem/FileSystemEntry.js b/src/filesystem/FileSystemEntry.js index 1e59b00a28c..7cc04988ce3 100644 --- a/src/filesystem/FileSystemEntry.js +++ b/src/filesystem/FileSystemEntry.js @@ -23,7 +23,7 @@ /*jslint vars: true, plusplus: true, devel: true, nomen: true, regexp: true, indent: 4, maxerr: 50 */ -/*global define, $ */ +/*global define */ define(function (require, exports, module) { "use strict"; @@ -51,7 +51,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": { @@ -86,11 +86,13 @@ define(function (require, exports, module) { /** * Cached stat object for this file. + * @type {?FileSystemStats} */ FileSystemEntry.prototype._stat = null; /** * Parent file system. + * @type {!FileSystem} */ FileSystemEntry.prototype._fileSystem = null; @@ -141,6 +143,14 @@ define(function (require, exports, module) { this._path = newPath; }; + /** + * Clear any cached data for this entry + * @private + */ + FileSystemEntry.prototype._clearCachedData = function () { + this._stat = undefined; + }; + /** * Helpful toString for debugging purposes */ @@ -160,8 +170,8 @@ define(function (require, exports, module) { /** * Returns the stats for the entry. * - * @param {function (?string, FileSystemStats=)} callback Callback with "error" and "stat" - * parameters. + * @param {function (?string, FileSystemStats=)} callback Callback with a + * FileSystemError string or FileSystemStats object. */ FileSystemEntry.prototype.stat = function (callback) { this._impl.stat(this._path, function (err, stat) { @@ -176,7 +186,8 @@ define(function (require, exports, module) { * Rename this entry. * * @param {string} newFullPath New path & name for this entry. - * @param {function (?string)=} callback Callback with a single "error" parameter. + * @param {function (?string)=} callback Callback with a single FileSystemError + * string parameter. */ FileSystemEntry.prototype.rename = function (newFullPath, callback) { callback = callback || function () {}; @@ -200,11 +211,14 @@ define(function (require, exports, module) { * Unlink (delete) this entry. For Directories, this will delete the directory * and all of its contents. * - * @param {function (?string)=} callback Callback with a single "error" parameter. + * @param {function (?string)=} callback Callback with a single FileSystemError + * string parameter. */ FileSystemEntry.prototype.unlink = function (callback) { callback = callback || function () {}; - this._stat = null; + + this._clearCachedData(); + this._impl.unlink(this._path, function (err) { if (!err) { this._fileSystem._index.removeEntry(this); @@ -218,7 +232,8 @@ define(function (require, exports, module) { * Move this entry to the trash. If the underlying file system doesn't support move * to trash, the item is permanently deleted. * - * @param {function (?string)=} callback Callback with a single "error" parameter. + * @param {function (?string)=} callback Callback with a single FileSystemError + * string parameter. */ FileSystemEntry.prototype.moveToTrash = function (callback) { callback = callback || function () {}; @@ -227,7 +242,8 @@ define(function (require, exports, module) { return; } - this._stat = null; + this._clearCachedData(); + this._impl.moveToTrash(this._path, function (err) { if (!err) { this._fileSystem._index.removeEntry(this); @@ -245,7 +261,7 @@ define(function (require, exports, module) { * applied to descendent FileSystemEntry objects. If the function returns false for * a particular Directory entry, that directory's descendents will not be visited. * @param {{failFast: boolean, maxDepth: number, maxEntriesCounter: {value: number}}} options - * @param {function(?string)=} callback Callback with single "error" parameter. + * @param {function(?string)=} callback Callback with single FileSystemError string parameter. */ FileSystemEntry.prototype._visitHelper = function (visitor, options, callback) { var failFast = options.failFast, @@ -297,8 +313,8 @@ define(function (require, exports, module) { * @param {function(FileSystemEntry): boolean} visitor - A visitor function, which is * applied to descendent FileSystemEntry objects. If the function returns false for * a particular Directory entry, that directory's descendents will not be visited. - * @param {{failFast: boolean=, maxDepth: number=, maxEntries: number=}=} options - An optional set of options. - * @param {function(?string)=} callback Callback with single "error" parameter. + * @param {{failFast: boolean=, maxDepth: number=, maxEntries: number=}=} options + * @param {function(?string)=} callback Callback with single FileSystemError string parameter. */ FileSystemEntry.prototype.visit = function (visitor, options, callback) { if (typeof options === "function") { diff --git a/src/filesystem/FileSystemStats.js b/src/filesystem/FileSystemStats.js index 3530b94d174..7c7da6d9063 100644 --- a/src/filesystem/FileSystemStats.js +++ b/src/filesystem/FileSystemStats.js @@ -26,7 +26,7 @@ /*global define */ /** - * The FileSystemStats represents a particular file's stats. + * The FileSystemStats represents a particular FileSystemEntry's stats. */ define(function (require, exports, module) { "use strict"; diff --git a/src/filesystem/impls/appshell/AppshellFileSystem.js b/src/filesystem/impls/appshell/AppshellFileSystem.js index 307afc20e88..40a31e5a538 100644 --- a/src/filesystem/impls/appshell/AppshellFileSystem.js +++ b/src/filesystem/impls/appshell/AppshellFileSystem.js @@ -190,7 +190,7 @@ define(function (require, exports, module) { stats[idx] = err || stat; count--; if (count <= 0) { - callback(err, contents, stats); + callback(null, contents, stats); } }); }); @@ -225,14 +225,13 @@ define(function (require, exports, module) { // No need to fake a file-watcher result here: FileSystem already updates index on rename() } + /* + * Note: if either the read or the stat call fails then neither the read data + * or stat will be passed back, and the call should be considered to have failed. + * If both calls fail, the error from the read call is passed back. + */ function readFile(path, options, callback) { - var encoding = "utf8"; - - if (typeof options === "function") { - callback = options; - } else { - encoding = options.encoding || "utf8"; - } + var encoding = options.encoding || "utf8"; // Execute the read and stat calls in parallel var done = false, data, stat, err; @@ -244,7 +243,7 @@ define(function (require, exports, module) { } if (done) { - callback(err, _data, stat); + callback(err, err ? null : _data, stat); } else { done = true; data = _data; @@ -253,7 +252,7 @@ define(function (require, exports, module) { exports.stat(path, function (_err, _stat) { if (done) { - callback(_err, data, _stat); + callback(_err, _err ? null : data, _stat); } else { done = true; stat = _stat; @@ -263,13 +262,7 @@ define(function (require, exports, module) { } function writeFile(path, data, options, callback) { - var encoding = "utf8"; - - if (typeof options === "function") { - callback = options; - } else { - encoding = options.encoding || "utf8"; - } + var encoding = options.encoding || "utf8"; exists(path, function (alreadyExists) { appshell.fs.writeFile(path, data, encoding, function (err) { diff --git a/src/language/JSUtils.js b/src/language/JSUtils.js index 0e66eb7804a..8d76c4dc2e1 100644 --- a/src/language/JSUtils.js +++ b/src/language/JSUtils.js @@ -235,7 +235,7 @@ define(function (require, exports, module) { file.stat(function (err, stat) { if (!err) { - result.resolve(fileInfo.JSUtils.timestamp === stat.mtime); + result.resolve(fileInfo.JSUtils.timestamp.getTime() === stat.mtime.getTime()); } else { result.reject(err); } diff --git a/test/spec/FileSystem-test.js b/test/spec/FileSystem-test.js index daa909ff3dc..426db24033e 100644 --- a/test/spec/FileSystem-test.js +++ b/test/spec/FileSystem-test.js @@ -22,7 +22,7 @@ */ /*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */ -/*global define, describe, it, expect, beforeEach, afterEach, waits, waitsFor, runs, $, setTimeout */ +/*global define, describe, it, expect, beforeEach, afterEach, waits, waitsFor, runs, $, window */ define(function (require, exports, module) { "use strict"; @@ -82,7 +82,7 @@ define(function (require, exports, module) { function generateCbWrapper(cb) { function cbReadyToRun() { var _args = arguments; - setTimeout(function () { + window.setTimeout(function () { cb.apply(null, _args); }, ms); } @@ -90,8 +90,15 @@ define(function (require, exports, module) { } return generateCbWrapper; } - - + + function getContentsCallback() { + var callback = function (err, contents) { + callback.error = err; + callback.contents = contents; + callback.wasCalled = true; + }; + return callback; + } describe("Path normalization", function () { // Auto-prepended to both origPath & normPath in all the test helpers below @@ -272,6 +279,8 @@ define(function (require, exports, module) { runs(function () { expect(cb.error).toBeFalsy(); expect(file.fullPath).toBe("/file1-renamed.txt"); + expect(fileSystem.getFileForPath("/file1-renamed.txt")).toBe(file); + expect(fileSystem.getFileForPath("/file1.txt")).not.toBe(file); }); }); @@ -320,15 +329,6 @@ define(function (require, exports, module) { describe("Read directory", function () { - function getContentsCallback() { - var callback = function (err, contents) { - callback.error = err; - callback.contents = contents; - callback.wasCalled = true; - }; - return callback; - } - it("should read a Directory", function () { var directory = fileSystem.getDirectoryForPath("/subdir/"), cb = getContentsCallback(); @@ -366,7 +366,7 @@ define(function (require, exports, module) { function delayedCallback(cb) { return function () { var args = arguments; - setTimeout(function () { + window.setTimeout(function () { cbCount++; cb.apply(null, args); }, 300); @@ -431,7 +431,7 @@ define(function (require, exports, module) { // Verify initial contents runs(function () { - file.readAsText(firstReadCB); + file.read(firstReadCB); }); waitsFor(function () { return firstReadCB.wasCalled; }); runs(function () { @@ -450,7 +450,7 @@ define(function (require, exports, module) { // Verify new contents runs(function () { - file.readAsText(secondReadCB); + file.read(secondReadCB); }); waitsFor(function () { return secondReadCB.wasCalled; }); runs(function () { @@ -464,7 +464,7 @@ define(function (require, exports, module) { cb = readCallback(); runs(function () { - file.readAsText(cb); + file.read(cb); }); waitsFor(function () { return cb.wasCalled; }); runs(function () { @@ -492,7 +492,7 @@ define(function (require, exports, module) { waitsFor(function () { return cb.wasCalled; }); runs(function () { expect(cb.error).toBeFalsy(); - file.readAsText(readCb); + file.read(readCb); }); waitsFor(function () { return readCb.wasCalled; }); runs(function () { @@ -503,15 +503,6 @@ define(function (require, exports, module) { }); describe("FileSystemEntry.visit", function () { - function getContentsCallback() { - var callback = function (err, contents) { - callback.error = err; - callback.contents = contents; - callback.wasCalled = true; - }; - return callback; - } - beforeEach(function () { function initEntry(entry, command, args) { var cb = getContentsCallback(); diff --git a/test/spec/LanguageManager-test.js b/test/spec/LanguageManager-test.js index 4b484b66e04..f7e295d0653 100644 --- a/test/spec/LanguageManager-test.js +++ b/test/spec/LanguageManager-test.js @@ -505,7 +505,7 @@ define(function (require, exports, module) { runs(function () { // Create a scheme script file - doc = SpecRunnerUtils.createMockActiveDocument({ filename: SpecRunnerUtils.getTempDirectory() + "/file.scheme" }); + doc = SpecRunnerUtils.createMockActiveDocument({ filename: "/file.scheme" }); // Initial language will be unknown (scheme is not a default language) unknown = LanguageManager.getLanguage("unknown"); @@ -555,7 +555,7 @@ define(function (require, exports, module) { promise; // Create a foo script file - doc = SpecRunnerUtils.createMockActiveDocument({ filename: SpecRunnerUtils.getTempDirectory() + "/test.foo" }); + doc = SpecRunnerUtils.createMockActiveDocument({ filename: "/test.foo" }); // Initial language will be unknown (foo is not a default language) unknown = LanguageManager.getLanguage("unknown"); diff --git a/test/spec/MockFileSystemImpl.js b/test/spec/MockFileSystemImpl.js index 69070e25029..30ed0851936 100644 --- a/test/spec/MockFileSystemImpl.js +++ b/test/spec/MockFileSystemImpl.js @@ -143,7 +143,8 @@ define(function (require, exports, module) { } function exists(path, callback) { - callback(!!_data[path]); + var cb = _getCallback("exists", path, callback); + cb(!!_data[path]); } function readdir(path, callback) { From 6cbe02cdd5cf89ad474701deec899f17f04f115e Mon Sep 17 00:00:00 2001 From: Ian Wehrman Date: Thu, 7 Nov 2013 14:34:30 -0800 Subject: [PATCH 160/163] Use FileSystem change/rename events instead of the old ProjectManager projectFilesChanged event --- src/extensions/default/UrlCodeHints/main.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/extensions/default/UrlCodeHints/main.js b/src/extensions/default/UrlCodeHints/main.js index af33eba7be5..14dd803c16a 100644 --- a/src/extensions/default/UrlCodeHints/main.js +++ b/src/extensions/default/UrlCodeHints/main.js @@ -759,11 +759,13 @@ define(function (require, exports, module) { // For unit testing exports.hintProvider = urlHints; }); - - $(ProjectManager).on("projectFilesChange", function (event, projectRoot) { + + function _clearCachedHints() { // Cache may or may not be stale. Main benefit of cache is to limit async lookups // during typing. File tree updates cannot happen during typing, so it's probably // not worth determining whether cache may still be valid. Just delete it. exports.hintProvider.cachedHints = null; - }); + } + FileSystem.on("change", _clearCachedHints); + FileSystem.on("rename", _clearCachedHints); }); From b5e5da5502918897982d2f02d3cb2e6bf178fe2b Mon Sep 17 00:00:00 2001 From: Ian Wehrman Date: Thu, 7 Nov 2013 15:08:43 -0800 Subject: [PATCH 161/163] Revert an unnecessary and inconsistent unit test change --- test/spec/ProjectManager-test.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/test/spec/ProjectManager-test.js b/test/spec/ProjectManager-test.js index 12f3499d805..91a9fc9c31b 100644 --- a/test/spec/ProjectManager-test.js +++ b/test/spec/ProjectManager-test.js @@ -302,7 +302,7 @@ define(function (require, exports, module) { it("should delete the selected folder and all items in it.", function () { var complete = false, - newFolderName = testPath + "/toDelete", + newFolderName = testPath + "/toDelete/", rootFolderName = newFolderName, rootFolderEntry, error, @@ -355,10 +355,9 @@ define(function (require, exports, module) { waitsFor(function () { return complete; }, "ProjectManager.createNewItem() timeout", 1000); runs(function () { - var newFolder; - - newFolderName += "/toDelete1/"; - newFolder = FileSystem.getDirectoryForPath(newFolderName); + newFolderName += "toDelete1/"; + + var newFolder = FileSystem.getDirectoryForPath(newFolderName); complete = false; newFolder.stat(function (err, _stat) { error = err; From 609d8731aefcd66f4ef54eb3c925ea0c2f3b8a6a Mon Sep 17 00:00:00 2001 From: Ian Wehrman Date: Thu, 7 Nov 2013 15:27:47 -0800 Subject: [PATCH 162/163] Replace try-catch-finally callback blocks in the filesystem with try-finally blocks when possible. --- src/filesystem/File.js | 2 -- src/filesystem/FileSystem.js | 2 -- src/filesystem/FileSystemEntry.js | 2 -- src/filesystem/impls/appshell/AppshellFileSystem.js | 8 -------- 4 files changed, 14 deletions(-) diff --git a/src/filesystem/File.js b/src/filesystem/File.js index f6e1200529c..5b5e1a084fe 100644 --- a/src/filesystem/File.js +++ b/src/filesystem/File.js @@ -114,8 +114,6 @@ define(function (require, exports, module) { // this._contents = data; } callback(err, stat); - } catch (ex) { - console.warn("Unhandled exception in callback: ", ex); } finally { this._fileSystem._endWrite(); // unblock generic change events } diff --git a/src/filesystem/FileSystem.js b/src/filesystem/FileSystem.js index 2d0941885f2..1df56986221 100644 --- a/src/filesystem/FileSystem.js +++ b/src/filesystem/FileSystem.js @@ -151,8 +151,6 @@ define(function (require, exports, module) { var callbackArgs = arguments; try { request.cb.apply(null, callbackArgs); - } catch (ex) { - console.warn("Unhandled exception in callback: ", ex); } finally { // Process the remaining watch/unwatch requests this._watchRequests.shift(); diff --git a/src/filesystem/FileSystemEntry.js b/src/filesystem/FileSystemEntry.js index 7cc04988ce3..8381c0c96e5 100644 --- a/src/filesystem/FileSystemEntry.js +++ b/src/filesystem/FileSystemEntry.js @@ -199,8 +199,6 @@ define(function (require, exports, module) { this._fileSystem._entryRenamed(this._path, newFullPath, this.isDirectory); } callback(err); // notify caller - } catch (ex) { - console.warn("Unhandled exception in callback: ", ex); } finally { this._fileSystem._endWrite(); // unblock generic change events } diff --git a/src/filesystem/impls/appshell/AppshellFileSystem.js b/src/filesystem/impls/appshell/AppshellFileSystem.js index 40a31e5a538..80b3768a954 100644 --- a/src/filesystem/impls/appshell/AppshellFileSystem.js +++ b/src/filesystem/impls/appshell/AppshellFileSystem.js @@ -209,8 +209,6 @@ define(function (require, exports, module) { stat(path, function (err, stat) { try { callback(err, stat); - } catch (ex) { - console.warn("Unhandled exception in callback: ", ex); } finally { // Fake a file-watcher result until real watchers respond quickly _changeCallback(_parentPath(path)); @@ -272,8 +270,6 @@ define(function (require, exports, module) { stat(path, function (err, stat) { try { callback(err, stat); - } catch (ex) { - console.warn("Unhandled exception in callback: ", ex); } finally { // Fake a file-watcher result until real watchers respond quickly if (alreadyExists) { @@ -293,8 +289,6 @@ define(function (require, exports, module) { appshell.fs.unlink(path, function (err) { try { callback(_mapError(err)); - } catch (ex) { - console.warn("Unhandled exception in callback: ", ex); } finally { // Fake a file-watcher result until real watchers respond quickly _changeCallback(_parentPath(path)); @@ -306,8 +300,6 @@ define(function (require, exports, module) { appshell.fs.moveToTrash(path, function (err) { try { callback(_mapError(err)); - } catch (ex) { - console.warn("Unhandled exception in callback: ", ex); } finally { // Fake a file-watcher result until real watchers respond quickly _changeCallback(_parentPath(path)); From 60c8cd0b94a0b5b675e78bfaeadbd3279290bab0 Mon Sep 17 00:00:00 2001 From: Ian Wehrman Date: Thu, 7 Nov 2013 16:07:12 -0800 Subject: [PATCH 163/163] Reset default FSE.visit maxDepth to 100 --- src/filesystem/FileSystemEntry.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/filesystem/FileSystemEntry.js b/src/filesystem/FileSystemEntry.js index 8381c0c96e5..0ebdd805f45 100644 --- a/src/filesystem/FileSystemEntry.js +++ b/src/filesystem/FileSystemEntry.js @@ -30,7 +30,7 @@ define(function (require, exports, module) { var FileSystemError = require("filesystem/FileSystemError"); - var VISIT_DEFAULT_MAX_DEPTH = 10, + var VISIT_DEFAULT_MAX_DEPTH = 100, VISIT_DEFAULT_MAX_ENTRIES = 30000; /* Counter to give every entry a unique id */