Skip to content

Commit

Permalink
Enable opening of remote(http|https) files in Brackets (adobe#14153)
Browse files Browse the repository at this point in the history
* Enable opening of remote(http|https) files in Brackets

* Adding jasmine tests

* Hooking to QuickOpen for remote file open

* Remove console statements

* Address code review comments

* Providing additional checks

* Unifying file existense check in MRU

* Include Remote file entries in MROF display list by stubbing existence check

* Handle image viewer and pixel mapping
  • Loading branch information
swmitra authored and navch committed May 4, 2018
1 parent 1d3f813 commit 421ed1a
Show file tree
Hide file tree
Showing 9 changed files with 534 additions and 14 deletions.
5 changes: 3 additions & 2 deletions src/document/DocumentManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -323,10 +323,11 @@ define(function (require, exports, module) {
* If all you need is the Document's getText() value, use the faster getDocumentText() instead.
*
* @param {!string} fullPath
* @param {!object} fileObj actual File|RemoteFile or some other protocol adapter handle
* @return {$.Promise} A promise object that will be resolved with the Document, or rejected
* with a FileSystemError if the file is not yet open and can't be read from disk.
*/
function getDocumentForPath(fullPath) {
function getDocumentForPath(fullPath, fileObj) {
var doc = getOpenDocumentForPath(fullPath);

if (doc) {
Expand All @@ -342,7 +343,7 @@ define(function (require, exports, module) {
return promise;
}

var file = FileSystem.getFileForPath(fullPath),
var file = fileObj || FileSystem.getFileForPath(fullPath),
pendingPromise = getDocumentForPath._pendingDocumentPromises[file.id];

if (pendingPromise) {
Expand Down
2 changes: 1 addition & 1 deletion src/editor/ImageViewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ define(function (require, exports, module) {
*/
function ImageView(file, $container) {
this.file = file;
this.$el = $(Mustache.render(ImageViewTemplate, {fullPath: FileUtils.encodeFilePath(file.fullPath),
this.$el = $(Mustache.render(ImageViewTemplate, {fullPath: file.encodedPath || 'file:///' + FileUtils.encodeFilePath(file.fullPath),
now: new Date().valueOf()}));

$container.append(this.$el);
Expand Down
221 changes: 221 additions & 0 deletions src/extensions/default/RemoteFileAdapter/RemoteFile.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
/*
* Copyright (c) 2018 - present 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.
*
*/

define(function (require, exports, module) {
"use strict";

var FileSystemError = brackets.getModule("filesystem/FileSystemError"),
FileSystemStats = brackets.getModule("filesystem/FileSystemStats");

var SESSION_START_TIME = new Date();

/**
* Create a new file stat. See the FileSystemStats class for more details.
*
* @param {!string} fullPath The full path for this File.
* @return {FileSystemStats} stats.
*/
function _getStats(uri) {
return new FileSystemStats({
isFile: true,
mtime: SESSION_START_TIME.toISOString(),
size: 0,
realPath: uri,
hash: uri
});
}

/**
* Model for a RemoteFile.
*
* This class should *not* be instantiated directly. Use FileSystem.getFileForPath
*
* See the FileSystem class for more details.
*
* @constructor
* @param {!string} fullPath The full path for this File.
* @param {!FileSystem} fileSystem The file system associated with this File.
*/
function RemoteFile(protocol, fullPath, fileSystem) {
this._isFile = true;
this._isDirectory = false;
this._path = fullPath;
this._stat = _getStats(fullPath);
this._id = fullPath;
this._name = fullPath.split('/').pop();
this._fileSystem = fileSystem;
this.donotWatch = true;
this.protocol = protocol;
this.encodedPath = fullPath;
}

// Add "fullPath", "name", "parent", "id", "isFile" and "isDirectory" getters
Object.defineProperties(RemoteFile.prototype, {
"fullPath": {
get: function () { return this._path; },
set: function () { throw new Error("Cannot set fullPath"); }
},
"name": {
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"); }
},
"isFile": {
get: function () { return this._isFile; },
set: function () { throw new Error("Cannot set isFile"); }
},
"isDirectory": {
get: function () { return this._isDirectory; },
set: function () { throw new Error("Cannot set isDirectory"); }
},
"_impl": {
get: function () { return this._fileSystem._impl; },
set: function () { throw new Error("Cannot set _impl"); }
}
});

/**
* Helpful toString for debugging and equality check purposes
*/
RemoteFile.prototype.toString = function () {
return "[RemoteFile " + this._path + "]";
};

/**
* Returns the stats for the remote entry.
*
* @param {function (?string, FileSystemStats=)} callback Callback with a
* FileSystemError string or FileSystemStats object.
*/
RemoteFile.prototype.stat = function (callback) {
if (this._stat) {
callback(null, this._stat);
} else {
callback(FileSystemError.NOT_FOUND);
}
};

RemoteFile.prototype.constructor = RemoteFile;

/**
* Cached contents of this file. This value is nullable but should NOT be undefined.
* @private
* @type {?string}
*/
RemoteFile.prototype._contents = null;


/**
* @private
* @type {?string}
*/
RemoteFile.prototype._encoding = "utf8";

/**
* @private
* @type {?bool}
*/
RemoteFile.prototype._preserveBOM = false;


/**
* Clear any cached data for this file. Note that this explicitly does NOT
* clear the file's hash.
* @private
*/
RemoteFile.prototype._clearCachedData = function () {
// no-op
};

/**
* Reads a remote file.
*
* @param {Object=} options Currently unused.
* @param {function (?string, string=, FileSystemStats=)} callback Callback that is passed the
* FileSystemError string or the file's contents and its stats.
*/
RemoteFile.prototype.read = function (options, callback) {
if (typeof (options) === "function") {
callback = options;
}
this._encoding = "utf8";

if (this._contents !== null && this._stat) {
callback(null, this._contents, this._encoding, this._stat);
return;
}

var self = this;
$.ajax({
url: this.fullPath
})
.done(function (data) {
self._contents = data;
callback(null, data, self._encoding, self._stat);
})
.fail(function (e) {
callback(FileSystemError.NOT_FOUND);
});
};

/**
* Write a file.
*
* @param {string} data Data to write.
* @param {object=} options Currently unused.
* @param {function (?string, FileSystemStats=)=} callback Callback that is passed the
* FileSystemError string or the file's new stats.
*/
RemoteFile.prototype.write = function (data, encoding, callback) {
if (typeof (encoding) === "function") {
callback = encoding;
}
callback(FileSystemError.NOT_FOUND);
};

RemoteFile.prototype.exists = function (callback) {
callback(null, true);
};

RemoteFile.prototype.unlink = function (callback) {
callback(FileSystemError.NOT_FOUND);
};

RemoteFile.prototype.rename = function (newName, callback) {
callback(FileSystemError.NOT_FOUND);
};

RemoteFile.prototype.moveToTrash = function (callback) {
callback(FileSystemError.NOT_FOUND);
};

// Export this class
module.exports = RemoteFile;
});
72 changes: 72 additions & 0 deletions src/extensions/default/RemoteFileAdapter/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* Copyright (c) 2018 - present 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.
*
*/

define(function (require, exports, module) {
"use strict";

var AppInit = brackets.getModule("utils/AppInit"),
FileSystem = brackets.getModule("filesystem/FileSystem"),
QuickOpen = brackets.getModule("search/QuickOpen"),
PathUtils = brackets.getModule("thirdparty/path-utils/path-utils"),
CommandManager = brackets.getModule("command/CommandManager"),
Commands = brackets.getModule("command/Commands"),
RemoteFile = require("RemoteFile");

var HTTP_PROTOCOL = "http:",
HTTPS_PROTOCOL = "https:";

AppInit.htmlReady(function () {
var protocolAdapter = {
priority: 0, // Default priority
fileImpl: RemoteFile,
canRead: function (filePath) {
return true; // Always claim true, we are the default adpaters
}
};

// Register the custom object as HTTP and HTTPS protocol adapter
FileSystem.registerProtocolAdapter(HTTP_PROTOCOL, protocolAdapter);
FileSystem.registerProtocolAdapter(HTTPS_PROTOCOL, protocolAdapter);

// Register as quick open plugin for file URI's having HTTP:|HTTPS: protocol
QuickOpen.addQuickOpenPlugin(
{
name: "Remote file URI input",
languageIds: [], // for all language modes
search: function () {
return $.Deferred().resolve([arguments[0]]);
},
match: function (query) {
var protocol = PathUtils.parseUrl(query).protocol;
return [HTTP_PROTOCOL, HTTPS_PROTOCOL].indexOf(protocol) !== -1;
},
itemFocus: function (query) {
}, // no op
itemSelect: function () {
CommandManager.execute(Commands.FILE_OPEN, {fullPath: arguments[0]});
}
}
);
});

});
Loading

0 comments on commit 421ed1a

Please sign in to comment.