atom-nuclide
Version:
A unified developer experience for web and mobile development, built as a suite of features on top of Atom to provide hackability and the support of an active community.
370 lines (320 loc) • 14.7 kB
JavaScript
Object.defineProperty(exports, '__esModule', {
value: true
});
/*
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the license found in the LICENSE file in
* the root directory of this source tree.
*/
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { var callNext = step.bind(null, 'next'); var callThrow = step.bind(null, 'throw'); function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(callNext, callThrow); } } callNext(); }); }; }
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
var _assert2;
function _assert() {
return _assert2 = _interopRequireDefault(require('assert'));
}
var _commonsNodeStream2;
function _commonsNodeStream() {
return _commonsNodeStream2 = require('../../commons-node/stream');
}
var _ServerConnection2;
function _ServerConnection() {
return _ServerConnection2 = require('./ServerConnection');
}
var _atom2;
function _atom() {
return _atom2 = require('atom');
}
var _commonsNodeNuclideUri2;
function _commonsNodeNuclideUri() {
return _commonsNodeNuclideUri2 = _interopRequireDefault(require('../../commons-node/nuclideUri'));
}
var _RemoteConnectionConfigurationManager2;
function _RemoteConnectionConfigurationManager() {
return _RemoteConnectionConfigurationManager2 = require('./RemoteConnectionConfigurationManager');
}
var _nuclideLogging2;
function _nuclideLogging() {
return _nuclideLogging2 = require('../../nuclide-logging');
}
var logger = (0, (_nuclideLogging2 || _nuclideLogging()).getLogger)();
var FILE_WATCHER_SERVICE = 'FileWatcherService';
var FILE_SYSTEM_SERVICE = 'FileSystemService';
// key for https connection.
// A RemoteConnection represents a directory which has been opened in Nuclide on a remote machine.
// This corresponds to what atom calls a 'root path' in a project.
//
// TODO: The _entries and _hgRepositoryDescription should not be here.
// Nuclide behaves badly when remote directories are opened which are parent/child of each other.
// And there needn't be a 1:1 relationship between RemoteConnections and hg repos.
var RemoteConnection = (function () {
_createClass(RemoteConnection, null, [{
key: 'findOrCreate',
value: _asyncToGenerator(function* (config) {
var serverConnection = yield (_ServerConnection2 || _ServerConnection()).ServerConnection.getOrCreate(config);
var connection = new RemoteConnection(serverConnection, config.cwd, config.displayTitle);
return yield connection._initialize();
})
// Do NOT call this directly. Use findOrCreate instead.
}, {
key: '_emitter',
value: new (_atom2 || _atom()).Emitter(),
enumerable: true
}]);
function RemoteConnection(connection, cwd, displayTitle) {
_classCallCheck(this, RemoteConnection);
this._cwd = cwd;
this._subscriptions = new (_atom2 || _atom()).CompositeDisposable();
this._hgRepositoryDescription = null;
this._connection = connection;
this._displayTitle = displayTitle;
}
_createClass(RemoteConnection, [{
key: '_setHgRepoInfo',
// A workaround before Atom 2.0: Atom's Project::setPaths currently uses
// ::repositoryForDirectorySync, so we need the repo information to already be
// available when the new path is added. t6913624 tracks cleanup of this.
value: _asyncToGenerator(function* () {
var remotePath = this.getPathForInitialWorkingDirectory();
var _ref = this.getService('SourceControlService');
var getHgRepository = _ref.getHgRepository;
this._setHgRepositoryDescription((yield getHgRepository(remotePath)));
})
}, {
key: 'getUriOfRemotePath',
value: function getUriOfRemotePath(remotePath) {
return 'nuclide://' + this.getRemoteHostname() + remotePath;
}
}, {
key: 'getPathOfUri',
value: function getPathOfUri(uri) {
return (_commonsNodeNuclideUri2 || _commonsNodeNuclideUri()).default.parse(uri).path;
}
}, {
key: 'createDirectory',
value: function createDirectory(uri) {
var symlink = arguments.length <= 1 || arguments[1] === undefined ? false : arguments[1];
return this._connection.createDirectory(uri, this._hgRepositoryDescription, symlink);
}
// A workaround before Atom 2.0: see ::getHgRepoInfo of main.js.
}, {
key: '_setHgRepositoryDescription',
value: function _setHgRepositoryDescription(hgRepositoryDescription) {
this._hgRepositoryDescription = hgRepositoryDescription;
}
}, {
key: 'getHgRepositoryDescription',
value: function getHgRepositoryDescription() {
return this._hgRepositoryDescription;
}
}, {
key: 'createFile',
value: function createFile(uri) {
var symlink = arguments.length <= 1 || arguments[1] === undefined ? false : arguments[1];
return this._connection.createFile(uri, symlink);
}
}, {
key: '_initialize',
value: _asyncToGenerator(function* () {
var attemptShutdown = false;
// Must add first to prevent the ServerConnection from going away
// in a possible race.
this._connection.addConnection(this);
try {
var FileSystemService = this.getService(FILE_SYSTEM_SERVICE);
var resolvedPath = yield FileSystemService.resolveRealPath(this._cwd);
// Now that we know the real path, it's possible this collides with an existing connection.
// If so, we should just stop immediately.
if (resolvedPath !== this._cwd) {
var existingConnection = RemoteConnection.getByHostnameAndPath(this.getRemoteHostname(), resolvedPath);
(0, (_assert2 || _assert()).default)(this !== existingConnection);
if (existingConnection != null) {
this.close(attemptShutdown);
return existingConnection;
}
this._cwd = resolvedPath;
}
// A workaround before Atom 2.0: see ::getHgRepoInfo.
yield this._setHgRepoInfo();
RemoteConnection._emitter.emit('did-add', this);
this._watchRootProjectDirectory();
} catch (e) {
this.close(attemptShutdown);
throw e;
}
return this;
})
}, {
key: '_watchRootProjectDirectory',
value: function _watchRootProjectDirectory() {
var _this = this;
var rootDirectoryUri = this.getUriForInitialWorkingDirectory();
var rootDirectotyPath = this.getPathForInitialWorkingDirectory();
var FileWatcherService = this.getService(FILE_WATCHER_SERVICE);
(0, (_assert2 || _assert()).default)(FileWatcherService);
var watchDirectoryRecursive = FileWatcherService.watchDirectoryRecursive;
// Start watching the project for changes and initialize the root watcher
// for next calls to `watchFile` and `watchDirectory`.
var watchStream = watchDirectoryRecursive(rootDirectoryUri).refCount();
var subscription = watchStream.subscribe(function (watchUpdate) {
// Nothing needs to be done if the root directory was watched correctly.
// Let's just console log it anyway.
logger.info('Watcher Features Initialized for project: ' + rootDirectoryUri, watchUpdate);
}, _asyncToGenerator(function* (error) {
var warningMessageToUser = 'You just connected to a remote project ' + ('`' + rootDirectotyPath + '` but we recommend you remove this directory now ') + 'because crucial features like synced remote file editing, file search, ' + 'and Mercurial-related updates will not work.<br/>';
var loggedErrorMessage = error.message || error;
logger.error('Watcher failed to start - watcher features disabled! Error: ' + loggedErrorMessage);
var FileSystemService = _this.getService(FILE_SYSTEM_SERVICE);
if (yield FileSystemService.isNfs(rootDirectotyPath)) {
warningMessageToUser += 'This project directory: `' + rootDirectotyPath + '` is on <b>`NFS`</b> filesystem. ' + 'Nuclide works best with local (non-NFS) root directory.' + 'e.g. `/data/users/$USER`';
} else {
warningMessageToUser += '<b><a href="https://facebook.github.io/watchman/">Watchman</a> Error:</b>' + loggedErrorMessage;
}
// Add a persistent warning message to make sure the user sees it before dismissing.
atom.notifications.addWarning(warningMessageToUser, { dismissable: true });
}), function () {
// Nothing needs to be done if the root directory watch has ended.
logger.info('Watcher Features Ended for project: ' + rootDirectoryUri);
});
this._subscriptions.add(new (_commonsNodeStream2 || _commonsNodeStream()).DisposableSubscription(subscription));
}
}, {
key: 'close',
value: _asyncToGenerator(function* (shutdownIfLast) {
this._subscriptions.dispose();
yield this._connection.removeConnection(this, shutdownIfLast);
RemoteConnection._emitter.emit('did-close', this);
})
}, {
key: 'getConnection',
value: function getConnection() {
return this._connection;
}
}, {
key: 'getPort',
value: function getPort() {
return this._connection.getPort();
}
}, {
key: 'getRemoteHostname',
value: function getRemoteHostname() {
return this._connection.getRemoteHostname();
}
}, {
key: 'getDisplayTitle',
value: function getDisplayTitle() {
return this._displayTitle;
}
}, {
key: 'getUriForInitialWorkingDirectory',
value: function getUriForInitialWorkingDirectory() {
return this.getUriOfRemotePath(this.getPathForInitialWorkingDirectory());
}
}, {
key: 'getPathForInitialWorkingDirectory',
value: function getPathForInitialWorkingDirectory() {
return this._cwd;
}
}, {
key: 'getConfig',
value: function getConfig() {
return _extends({}, this._connection.getConfig(), { cwd: this._cwd, displayTitle: this._displayTitle });
}
}, {
key: 'getService',
value: function getService(serviceName) {
return this._connection.getService(serviceName);
}
}, {
key: 'isOnlyConnection',
value: function isOnlyConnection() {
return this._connection.getConnections().length === 1;
}
}], [{
key: '_createInsecureConnectionForTesting',
value: function _createInsecureConnectionForTesting(cwd, port) {
var config = {
host: 'localhost',
port: port,
cwd: cwd,
displayTitle: ''
};
return RemoteConnection.findOrCreate(config);
}
/**
* Create a connection by reusing the configuration of last successful connection associated with
* given host. If the server's certs has been updated or there is no previous successful
* connection, null (resolved by promise) is returned.
*/
}, {
key: 'createConnectionBySavedConfig',
value: _asyncToGenerator(function* (host, cwd, displayTitle) {
var connectionConfig = (0, (_RemoteConnectionConfigurationManager2 || _RemoteConnectionConfigurationManager()).getConnectionConfig)(host);
if (!connectionConfig) {
return null;
}
try {
var config = _extends({}, connectionConfig, { cwd: cwd, displayTitle: displayTitle });
return yield RemoteConnection.findOrCreate(config);
} catch (e) {
var log = e.name === 'VersionMismatchError' ? logger.warn : logger.error;
log('Failed to reuse connectionConfiguration for ' + host, e);
return null;
}
})
}, {
key: 'onDidAddRemoteConnection',
value: function onDidAddRemoteConnection(handler) {
return RemoteConnection._emitter.on('did-add', handler);
}
}, {
key: 'onDidCloseRemoteConnection',
value: function onDidCloseRemoteConnection(handler) {
return RemoteConnection._emitter.on('did-close', handler);
}
}, {
key: 'getForUri',
value: function getForUri(uri) {
var _default$parse = (_commonsNodeNuclideUri2 || _commonsNodeNuclideUri()).default.parse(uri);
var hostname = _default$parse.hostname;
var path = _default$parse.path;
if (hostname == null) {
return null;
}
return RemoteConnection.getByHostnameAndPath(hostname, path);
}
/**
* Get cached connection match the hostname and the path has the prefix of connection.cwd.
* @param hostname The connected server host name.
* @param path The absolute path that's has the prefix of cwd of the connection.
* If path is null, empty or undefined, then return the connection which matches
* the hostname and ignore the initial working directory.
*/
}, {
key: 'getByHostnameAndPath',
value: function getByHostnameAndPath(hostname, path) {
return RemoteConnection.getByHostname(hostname).filter(function (connection) {
return path.startsWith(connection.getPathForInitialWorkingDirectory());
})[0];
}
}, {
key: 'getByHostname',
value: function getByHostname(hostname) {
var server = (_ServerConnection2 || _ServerConnection()).ServerConnection.getByHostname(hostname);
return server == null ? [] : server.getConnections();
}
}]);
return RemoteConnection;
})();
exports.RemoteConnection = RemoteConnection;
// host nuclide server is running on.
// port to connect to.
// Path to remote directory user should start in upon connection.
// Name of the saved connection profile.
// certificate of certificate authority.
// client certificate for https connection.
// Path to remote directory user should start in upon connection.