├── .gitignore ├── .travis.yml ├── LICENSE ├── Makefile ├── README.md ├── keymaps └── sftp-deployment.cson ├── lib ├── DeploymentManager.js ├── configs │ ├── Config.js │ ├── ConfigFactory.js │ ├── FtpConfig.js │ └── SftpConfig.js ├── connections │ ├── Connection.js │ ├── ConnectionFactory.js │ ├── FtpConnection.js │ └── SftpConnection.js ├── exceptions │ ├── ConfigurationFileNotReadableException.js │ ├── ConfigurationFileSyntaxErrorException.js │ ├── ConnectionErrorException.js │ ├── DirectoryCreationErrorException.js │ ├── DownloadErrorException.js │ ├── Exception.js │ ├── NoConfigurationFileFoundException.js │ ├── RemoteDirectoryCreationErrorException.js │ ├── RemoteDirectoryNotReadableException.js │ ├── RemoteSystemErrorException.js │ ├── TransfertErrorException.js │ └── UploadErrorException.js ├── filesystem │ ├── Directory.js │ ├── File.js │ ├── FileManager.js │ └── Node.js ├── observers │ ├── ConsoleObserver.js │ ├── MessageObserver.js │ └── Observer.js ├── queue │ ├── Action.js │ └── Queue.js └── sftp-deployment.js ├── menus └── sftp-deployment.cson ├── package.json ├── spec └── FileSpec.js └── styles └── sftp-deployment.less /.gitignore: -------------------------------------------------------------------------------- 1 | sftp-config.json 2 | node_modules 3 | build 4 | coverage 5 | .DS_store 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | node_js: 2 | - "0.10" 3 | language: node_js 4 | env: 5 | - COVERALLS_REPO_TOKEN="ckvmICpF1SAKEEXWuEr7zFuV34fKi01LR" 6 | before_script: 7 | - npm install -g istanbul 8 | - npm install -g mocha 9 | script: make test-cov 10 | after_success: 11 | - make coveralls 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Ellipsis Team 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SRC = lib 2 | TESTS = spec/*Spec.js 3 | REPORTER = spec 4 | COVERAGE_REPORT = ./coverage/lcov.info 5 | COVERALLS = ./node_modules/coveralls/bin/coveralls.js 6 | 7 | test: test-mocha 8 | 9 | test-mocha: 10 | @NODE_ENV=test mocha \ 11 | --timeout 200 \ 12 | --reporter $(REPORTER) \ 13 | $(TESTS) 14 | 15 | test-cov: istanbul 16 | 17 | istanbul: 18 | istanbul cover _mocha -- -R spec --recursive $(SRC) $(TESTS) 19 | 20 | coveralls: 21 | cat $(COVERAGE_REPORT) | $(COVERALLS) 22 | 23 | clean: 24 | rm -rf ./coverage 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/amoussard/sftp-deployment.svg?branch=evol%2F3-upload-selection)](https://travis-ci.org/amoussard/sftp-deployment) 2 | [![Coverage Status](https://img.shields.io/coveralls/amoussard/sftp-deployment.svg)](https://coveralls.io/r/amoussard/sftp-deployment) 3 | 4 | #SFTP-Deployment for Atom.io 5 | 6 | Spend less time managing file transfers and more time coding. FTP and SFTP support for Atom.io to send and receive files directly in your server. 7 | 8 | SFTP-Deployment is a package for Atom.io using [SSH2 client](https://github.com/mscdex/ssh2) and [Node FTP](https://github.com/mscdex/node-ftp) modules written in pure Javascript for [node.js](http://nodejs.org/). 9 | 10 | ![SFTP-deployment](https://atom.io/assets/packages-d6c259ff67b995961012620be1e26678.gif "SFTP-deployment") 11 | 12 | ##Features 13 | 14 | ###Workflows 15 | * Upload/Download current file 16 | * Upload open files (tabs) 17 | * Upload/Download selection from Tree View 18 | 19 | ###Compatibility 20 | * Supports FTP and SFTP servers 21 | * Password SSH support 22 | * Works on Windows, OS X and Linux 23 | 24 | ###Integration 25 | * Menu entries and command palette control 26 | * File-based configuration (JSON) 27 | * Colorized output panel with options for automatic hiding 28 | 29 | ## Installation 30 | 31 | 1. Search `sftp` or `ftp` in the atom package manager 32 | 2. Since the installation is successful, you can generate the configuration file with the command 33 | * `cmd-shift-p` and search `mapToRemote` 34 | * Packages menu -> FTP/SFTP -> Map to Remote... 35 | * Create your own 36 | 3. Set your ftp/sftp configuration in this file 37 | 4. Use it! 38 | 39 | The configuration file **MUST** always be in the root directory of your project. 40 | 41 | ###Example of configuration file: 42 | 43 | ####SFTP with user/password : 44 | ``` 45 | { 46 | "type": "sftp", 47 | "host": "example.com", 48 | "user": "username", 49 | "password": "password", 50 | "port": "22", 51 | "remotePath": "/example/path" 52 | } 53 | ``` 54 | 55 | ####SFTP protocol with private key : 56 | ``` 57 | { 58 | "type": "sftp", 59 | "host": "example.com", 60 | "user": "username", 61 | "port": "22", 62 | "remotePath": "/example/path", 63 | "sshKeyFile": "~/.ssh/id_rsa", 64 | "passphrase": "your_passphrase" 65 | } 66 | ``` 67 | The passphrase is optional, only if your key require it. 68 | 69 | ####FTP protocol : 70 | ``` 71 | { 72 | "type": "ftp", 73 | "host": "example.com", 74 | "username": "username", 75 | "password": "password", 76 | "port": "21", 77 | "remotePath": "/example/path" 78 | } 79 | ``` 80 | 81 | ##Next Versions 82 | 83 | ###Workflows 84 | * Upload just the changes since your last commit 85 | * See upload/download progress 86 | * Synchronize in both directions 87 | 88 | ###Integration 89 | * Keyboard shortcuts 90 | * Secure password and passphrase entry 91 | 92 | ##Versions 93 | * `1.0.1` 94 | * Fix after Atom update 95 | * `1.0.0` 96 | * Full refactoring of the package 97 | * Improve stability 98 | * Best error management 99 | * Upload on save 100 | * Upload/Download selection of files and directories in tree-view 101 | * `0.4.0` 102 | * Upload/Download of folders 103 | * Refactoring of code 104 | * `0.3.0` 105 | * Refactoring of the code 106 | * Notifications/message system 107 | * FTP support 108 | * ST3 package syntax support 109 | * bugfix 110 | * `0.1.0` Build the first atom package 111 | -------------------------------------------------------------------------------- /keymaps/sftp-deployment.cson: -------------------------------------------------------------------------------- 1 | # Keybindings require three things to be fully defined: A selector that is 2 | # matched against the focused element, the keystroke and the command to 3 | # execute. 4 | # 5 | # Below is a basic keybinding which registers on all platforms by applying to 6 | # the root workspace element. 7 | 8 | # For more detailed documentation see 9 | # https://atom.io/docs/latest/advanced/keymaps 10 | '.atom-workspace': 11 | 'ctrl-alt-o': 'sftp-deployment:toggle' 12 | -------------------------------------------------------------------------------- /lib/DeploymentManager.js: -------------------------------------------------------------------------------- 1 | ////////////////// 2 | // Requires 3 | ////////////////// 4 | 5 | var fs = require('fs'); 6 | var path = require('path'); 7 | var util = require('util'); 8 | 9 | var ConfigFactory = require('./configs/ConfigFactory'); 10 | var ConnectionFactory = require('./connections/ConnectionFactory'); 11 | var Connection = require('./connections/Connection'); 12 | var oFileManager = require('./filesystem/FileManager'); 13 | 14 | var OperationalError = require('bluebird').Promise.OperationalError; 15 | var NoConfigurationFileFoundException = require('./exceptions/NoConfigurationFileFoundException'); 16 | var ConfigurationFileNotReadableException = require('./exceptions/ConfigurationFileNotReadableException'); 17 | var ConfigurationFileSyntaxErrorException = require('./exceptions/ConfigurationFileSyntaxErrorException'); 18 | var ConnectionErrorException = require('./exceptions/ConnectionErrorException'); 19 | var UploadErrorException = require('./exceptions/UploadErrorException'); 20 | var DownloadErrorException = require('./exceptions/DownloadErrorException'); 21 | var RemoteDirectoryCreationErrorException = require('./exceptions/RemoteDirectoryCreationErrorException'); 22 | var RemoteSystemErrorException = require('./exceptions/RemoteSystemErrorException'); 23 | var DirectoryCreationErrorException = require('./exceptions/DirectoryCreationErrorException'); 24 | var TransfertErrorException = require('./exceptions/TransfertErrorException'); 25 | var RemoteDirectoryNotReadableException = require('./exceptions/RemoteDirectoryNotReadableException'); 26 | 27 | var self; 28 | 29 | ////////////////// 30 | // Ctor 31 | ////////////////// 32 | 33 | function DeploymentManager() { 34 | this.observers = []; 35 | this.configurationFileName = "deployment-config.json"; 36 | 37 | self = this; 38 | } 39 | 40 | ////////////////// 41 | // Methods 42 | ////////////////// 43 | 44 | DeploymentManager.prototype.registerObserver = function(observer) { 45 | this.observers.push(observer); 46 | }; 47 | 48 | DeploymentManager.prototype.notifyObservers = function(value, data) { 49 | this.observers.forEach(function (observer) { 50 | observer.notify(value, data); 51 | }); 52 | }; 53 | 54 | DeploymentManager.prototype.upload = function(files) { 55 | var configFactory = new ConfigFactory(); 56 | var connectionFactory = new ConnectionFactory(); 57 | var configPath = path.join( 58 | atom.project.rootDirectories[0].path, 59 | self.configurationFileName 60 | ); 61 | 62 | return configFactory.loadConfig(configPath) 63 | .then(function(config) { 64 | if (!config.uploadConfigFile) { 65 | files = files.filter(function (el) { 66 | return el.relativePath !== self.configurationFileName; 67 | }); 68 | } 69 | return connectionFactory.openConnection(config); 70 | }) 71 | .then(function(connection) { 72 | return connection.upload(files); 73 | }) 74 | .then(function(connection) { 75 | return connection.close(); 76 | }) 77 | .then(function(success) { 78 | if (success) { 79 | self.notifyObservers('upload_file_success'); 80 | return; 81 | } 82 | self.notifyObservers('upload_file_error'); 83 | }) 84 | .catch(NoConfigurationFileFoundException, function(e) { 85 | self.notifyObservers('no_configuration_file_found'); 86 | }) 87 | .catch(ConfigurationFileNotReadableException, function(e) { 88 | self.notifyObservers('configuration_file_not_readable'); 89 | }) 90 | .catch(ConfigurationFileSyntaxErrorException, function(e) { 91 | self.notifyObservers('configuration_file_syntax_error', e); 92 | }) 93 | .catch(ConnectionErrorException, function(e) { 94 | self.notifyObservers('connection_error', e); 95 | }) 96 | .catch(UploadErrorException, function(e) { 97 | self.notifyObservers('upload_file_error', e); 98 | }) 99 | .catch(RemoteDirectoryCreationErrorException, function(e) { 100 | self.notifyObservers('remote_directory_creation_error', e); 101 | }) 102 | .catch(RemoteSystemErrorException, function(e) { 103 | self.notifyObservers('remote_system_error', e); 104 | }) 105 | .catch(TransfertErrorException, function(e) { 106 | self.notifyObservers('transfert_file_error', e); 107 | }) 108 | .catch(function(e) { 109 | console.error(e); 110 | }); 111 | }; 112 | 113 | DeploymentManager.prototype.download = function(nodes) { 114 | var configFactory = new ConfigFactory(); 115 | var connectionFactory = new ConnectionFactory(); 116 | var configPath = path.join( 117 | atom.project.rootDirectories[0].path, 118 | self.configurationFileName 119 | ); 120 | var conn = null; 121 | 122 | return configFactory.loadConfig(configPath) 123 | .then(function(config) { 124 | return connectionFactory.openConnection(config); 125 | }) 126 | .then(function(connection) { 127 | conn = connection; 128 | return connection.getTargetFiles(nodes); 129 | }) 130 | .then(function(files) { 131 | return conn.download(files); 132 | }) 133 | .then(function(connection) { 134 | return connection.close(); 135 | }) 136 | .then(function(success) { 137 | if (success) { 138 | self.notifyObservers('download_file_success'); 139 | return; 140 | } 141 | self.notifyObservers('download_file_error'); 142 | }) 143 | .catch(NoConfigurationFileFoundException, function(e) { 144 | self.notifyObservers('no_configuration_file_found'); 145 | }) 146 | .catch(ConfigurationFileNotReadableException, function(e) { 147 | self.notifyObservers('configuration_file_not_readable'); 148 | }) 149 | .catch(ConfigurationFileSyntaxErrorException, function(e) { 150 | self.notifyObservers('configuration_file_syntax_error', e); 151 | }) 152 | .catch(ConnectionErrorException, function(e) { 153 | self.notifyObservers('connection_error', e); 154 | }) 155 | .catch(DownloadErrorException, function(e) { 156 | self.notifyObservers('download_file_error', e); 157 | }) 158 | .catch(DirectoryCreationErrorException, function(e) { 159 | self.notifyObservers('directory_creation_error', e); 160 | }) 161 | .catch(TransfertErrorException, function(e) { 162 | self.notifyObservers('transfert_file_error', e); 163 | }) 164 | .catch(RemoteDirectoryNotReadableException, function(e) { 165 | self.notifyObservers('remote_directory_not_readable', e); 166 | }) 167 | .catch(function(e) { 168 | console.error(e); 169 | }); 170 | }; 171 | 172 | /** 173 | * Generate a default configuration file 174 | * Ask which protocol the user want to use 175 | */ 176 | DeploymentManager.prototype.generateConfigFile = function() { 177 | var configFactory = new ConfigFactory(); 178 | 179 | if (atom.project.rootDirectories 180 | && atom.project.rootDirectories.length < 1) { 181 | self.notifyObservers("project_not_found"); 182 | return; 183 | } 184 | 185 | var configPath = path.join( 186 | atom.project.rootDirectories[0].path, 187 | self.configurationFileName 188 | ); 189 | 190 | atom.confirm({ 191 | message: "Which type of configuration you want to generate ?", 192 | detailedMessage: "Be careful, this will overwrite the existent configuration file !", 193 | buttons: { 194 | "SFTP": function () { 195 | configFactory 196 | .createSftpConfig() 197 | .save(configPath) 198 | .then(function() { 199 | self.notifyObservers("configuration_file_creation_success"); 200 | }) 201 | .catch(function(e) { 202 | self.notifyObservers("configuration_file_creation_error", e); 203 | }); 204 | }, 205 | "FTP": function () { 206 | configFactory 207 | .createFtpConfig() 208 | .save(configPath) 209 | .then(function() { 210 | self.notifyObservers("configuration_file_creation_success"); 211 | }) 212 | .catch(function(e) { 213 | self.notifyObservers("configuration_file_creation_error", e); 214 | }); 215 | }, 216 | "Cancel": null 217 | } 218 | }); 219 | }; 220 | 221 | /** 222 | * Upload the open tab file 223 | */ 224 | DeploymentManager.prototype.uploadCurrentFile = function() { 225 | var configFactory = new ConfigFactory(); 226 | var configPath = path.join( 227 | atom.project.rootDirectories[0].path, 228 | self.configurationFileName 229 | ); 230 | 231 | configFactory.loadConfig(configPath) 232 | .then(function(config) { 233 | oFileManager.getCurrentFile() 234 | .then(function(file) { 235 | if (!config.uploadConfigFile && file.relativePath == self.configurationFileName) { 236 | return; 237 | }else { 238 | self.notifyObservers('begin_transfert'); 239 | self.upload([file]); 240 | } 241 | }); 242 | }); 243 | }; 244 | 245 | /** 246 | * Upload on save 247 | */ 248 | DeploymentManager.prototype.uploadOnSave = function() { 249 | var configFactory = new ConfigFactory(); 250 | var configPath = path.join( 251 | atom.project.rootDirectories[0].path, 252 | self.configurationFileName 253 | ); 254 | 255 | configFactory.loadConfig(configPath) 256 | .then(function(config) { 257 | if (config.getUploadOnSave()) { 258 | return self.uploadCurrentFile(); 259 | } 260 | }); 261 | }; 262 | 263 | /** 264 | * Download the open tab file 265 | */ 266 | DeploymentManager.prototype.downloadCurrentFile = function() { 267 | atom.confirm({ 268 | message: "You will download the current file, be careful, it will be overwritten.", 269 | buttons: { 270 | "Overwrite": function () { 271 | self.notifyObservers('begin_transfert'); 272 | oFileManager.getCurrentFile() 273 | .then(function(file) { 274 | self.download([file]); 275 | }); 276 | }, 277 | "Cancel": null 278 | } 279 | }); 280 | }; 281 | 282 | /** 283 | * Upload open tab files 284 | */ 285 | DeploymentManager.prototype.uploadOpenFiles = function() { 286 | var configFactory = new ConfigFactory(); 287 | var configPath = path.join( 288 | atom.project.rootDirectories[0].path, 289 | self.configurationFileName 290 | ); 291 | 292 | configFactory.loadConfig(configPath) 293 | .then(function(config) { 294 | oFileManager.getOpenFiles() 295 | .then(function(files) { 296 | if (!config.uploadConfigFile) { 297 | files = files.filter(function (file) { 298 | return file.relativePath != self.configurationFileName; 299 | }); 300 | if (files.length == 0) { 301 | return; 302 | } 303 | } 304 | self.notifyObservers('begin_transfert'); 305 | self.upload(files); 306 | }); 307 | }); 308 | }; 309 | 310 | /** 311 | * Upload a tree-view selection 312 | */ 313 | DeploymentManager.prototype.uploadSelection = function() { 314 | var configFactory = new ConfigFactory(); 315 | var configPath = path.join( 316 | atom.project.rootDirectories[0].path, 317 | self.configurationFileName 318 | ); 319 | 320 | configFactory.loadConfig(configPath) 321 | .then(function(config) { 322 | oFileManager.getSelection() 323 | .then(function(files) { 324 | if (!config.uploadConfigFile) { 325 | files = files.filter(function (file) { 326 | return file.relativePath != self.configurationFileName; 327 | }); 328 | if (files.length == 0) { 329 | return; 330 | } 331 | } 332 | self.notifyObservers('begin_transfert'); 333 | self.upload(files); 334 | }); 335 | }); 336 | }; 337 | 338 | /** 339 | * Download a tree-view selection 340 | */ 341 | DeploymentManager.prototype.downloadSelection = function() { 342 | atom.confirm({ 343 | message: "You will download all your selection, be careful, it will be overwritted.", 344 | buttons: { 345 | "Overwrite": function () { 346 | self.notifyObservers('begin_transfert'); 347 | oFileManager.getSelection() 348 | .then(function(nodes) { 349 | self.download(nodes); 350 | }); 351 | }, 352 | "Cancel": null 353 | } 354 | }); 355 | }; 356 | 357 | module.exports = DeploymentManager; 358 | -------------------------------------------------------------------------------- /lib/configs/Config.js: -------------------------------------------------------------------------------- 1 | ////////////////// 2 | // Requires 3 | ////////////////// 4 | 5 | var Promise = require('bluebird'); 6 | var fs = Promise.promisifyAll(require('fs')); 7 | var path = require('path'); 8 | 9 | ////////////////// 10 | // Ctor 11 | ////////////////// 12 | 13 | function Config() { 14 | /** 15 | * @type {String} 16 | */ 17 | this.type = undefined; 18 | 19 | /** 20 | * @type {String} 21 | */ 22 | this.host = ""; 23 | 24 | /** 25 | * @type {String} 26 | */ 27 | this.username = ""; 28 | 29 | /** 30 | * @type {String} 31 | */ 32 | this.password = ""; 33 | 34 | /** 35 | * @type {Number} 36 | */ 37 | this.port = -1; 38 | 39 | /** 40 | * @type {String} 41 | */ 42 | this.remotePath = ""; 43 | 44 | /** 45 | * @type {Bool} 46 | */ 47 | this.uploadOnSave = false; 48 | 49 | /** 50 | * @type {Bool} 51 | */ 52 | this.uploadConfigFile = false; 53 | } 54 | 55 | ////////////////// 56 | // Methods 57 | ////////////////// 58 | 59 | /** 60 | * Type setter 61 | * @param {String} _type 62 | */ 63 | Config.prototype.setType = function (_type) { 64 | this.type = _type; 65 | }; 66 | 67 | /** 68 | * Type getter 69 | */ 70 | Config.prototype.getType = function () { 71 | return this.type; 72 | }; 73 | 74 | /** 75 | * Host setter 76 | * @param {String} _host 77 | */ 78 | Config.prototype.setHost = function (_host) { 79 | this.host = _host; 80 | }; 81 | 82 | /** 83 | * Host getter 84 | */ 85 | Config.prototype.getHost = function () { 86 | return this.host; 87 | }; 88 | 89 | /** 90 | * Username setter 91 | * @param {String} _username 92 | */ 93 | Config.prototype.setUsername = function (_username) { 94 | this.username = _username; 95 | }; 96 | 97 | /** 98 | * Username getter 99 | */ 100 | Config.prototype.getUsername = function () { 101 | return this.username; 102 | }; 103 | 104 | /** 105 | * Password setter 106 | * @param {String} _password 107 | */ 108 | Config.prototype.setPassword = function (_password) { 109 | this.password = _password; 110 | }; 111 | 112 | /** 113 | * Password getter 114 | */ 115 | Config.prototype.getPassword = function () { 116 | return this.password; 117 | }; 118 | 119 | /** 120 | * Port setter 121 | * @param {Number} _port 122 | */ 123 | Config.prototype.setPort = function (_port) { 124 | this.port = _port; 125 | }; 126 | 127 | /** 128 | * Port getter 129 | */ 130 | Config.prototype.getPort = function () { 131 | return this.port; 132 | }; 133 | 134 | /** 135 | * Remote path setter 136 | * @param {String} _remotePath 137 | */ 138 | Config.prototype.setRemotePath = function (_remotePath) { 139 | this.remotePath = _remotePath; 140 | }; 141 | 142 | /** 143 | * Remote path getter 144 | */ 145 | Config.prototype.getRemotePath = function () { 146 | return this.remotePath; 147 | }; 148 | 149 | /** 150 | * Upload Save setter 151 | * @param {Bool} _uploadOnSave 152 | */ 153 | Config.prototype.setUploadOnSave = function (_uploadOnSave) { 154 | this.uploadOnSave = _uploadOnSave; 155 | }; 156 | 157 | /** 158 | * Upload Save getter 159 | */ 160 | Config.prototype.getUploadOnSave = function () { 161 | return this.uploadOnSave; 162 | }; 163 | 164 | /** 165 | * Upload Config File setter 166 | * @param {Bool} _uploadOnSave 167 | */ 168 | Config.prototype.setUploadConfigFile = function (_uploadConfigFile) { 169 | this.uploadConfigFile = _uploadConfigFile; 170 | }; 171 | 172 | /** 173 | * Upload Config File getter 174 | */ 175 | Config.prototype.getUploadConfigFile = function () { 176 | return this.uploadConfigFile; 177 | }; 178 | 179 | function replacer(key, value) { 180 | return value !== null ? value : undefined; 181 | } 182 | 183 | Config.prototype.save = function (path) { 184 | return fs.writeFileAsync(path, JSON.stringify(this, replacer, 4)); 185 | } 186 | 187 | module.exports = Config; 188 | -------------------------------------------------------------------------------- /lib/configs/ConfigFactory.js: -------------------------------------------------------------------------------- 1 | ////////////////// 2 | // Requires 3 | ////////////////// 4 | 5 | var Promise = require('bluebird'); 6 | var fs = Promise.promisifyAll(require('fs')); 7 | var path = require('path'); 8 | var OperationalError = require('bluebird').Promise.OperationalError; 9 | 10 | var SftpConfig = require("./SftpConfig"); 11 | var FtpConfig = require("./FtpConfig"); 12 | var NoConfigurationFileFoundException = require("./../exceptions/NoConfigurationFileFoundException"); 13 | var ConfigurationFileNotReadableException = require("./../exceptions/ConfigurationFileNotReadableException"); 14 | var ConfigurationFileSyntaxErrorException = require("./../exceptions/ConfigurationFileSyntaxErrorException"); 15 | 16 | ////////////////// 17 | // Ctor 18 | ////////////////// 19 | 20 | function ConfigFactory() { 21 | } 22 | 23 | ////////////////// 24 | // Methods 25 | ////////////////// 26 | 27 | ConfigFactory.prototype.parseConfigFile = function(content) { 28 | var deferred = Promise.pending(); 29 | 30 | try { 31 | var configData = JSON.parse(content); 32 | deferred.fulfill(configData); 33 | } catch(e) { 34 | if (e.name === 'SyntaxError') { 35 | deferred.reject(new ConfigurationFileSyntaxErrorException(e.message)); 36 | } else { 37 | deferred.reject(e); 38 | } 39 | } 40 | 41 | return deferred.promise; 42 | } 43 | 44 | ConfigFactory.prototype.createConfig = function(configData) { 45 | var deferred = Promise.pending(); 46 | 47 | try { 48 | var type = configData.type; 49 | type = type.charAt(0).toUpperCase() + type.substring(1); 50 | var getter = 'create' + type + 'Config'; 51 | var config = this[getter](); 52 | 53 | for (var key in configData) { 54 | if (config[key] !== undefined) { 55 | config[key] = configData[key]; 56 | } 57 | } 58 | deferred.resolve(config); 59 | } catch(e) { 60 | deferred.reject(e); 61 | } 62 | 63 | return deferred.promise; 64 | } 65 | 66 | /** 67 | * Create configuration from a file 68 | * @param {String} file 69 | * @param {Function} callback 70 | */ 71 | ConfigFactory.prototype.loadConfig = function(configPath) { 72 | var deferred = Promise.pending(); 73 | var config = null; 74 | var self = this; 75 | 76 | return fs.readFileAsync(configPath, 'utf8') 77 | .then(function(content) { 78 | return self.parseConfigFile(content); 79 | }) 80 | .then(function(configData) { 81 | return self.createConfig(configData); 82 | }) 83 | .catch(OperationalError, function(e) { 84 | if (e.code === 'ENOENT') { 85 | throw new NoConfigurationFileFoundException(); 86 | } else if (e.code === 'EACCES') { 87 | throw new ConfigurationFileNotReadableException(); 88 | } else { 89 | throw e; 90 | } 91 | }); 92 | }; 93 | 94 | /** 95 | * @return {SftpConfig} 96 | */ 97 | ConfigFactory.prototype.createSftpConfig = function () { 98 | return new SftpConfig(); 99 | }; 100 | 101 | /** 102 | * @return {FtpConfig} 103 | */ 104 | ConfigFactory.prototype.createFtpConfig = function () { 105 | return new FtpConfig(); 106 | }; 107 | 108 | module.exports = ConfigFactory; 109 | -------------------------------------------------------------------------------- /lib/configs/FtpConfig.js: -------------------------------------------------------------------------------- 1 | ////////////////// 2 | // Requires 3 | ////////////////// 4 | 5 | var Config = require('./Config'); 6 | var util = require('util'); 7 | 8 | ////////////////// 9 | // Ctor 10 | ////////////////// 11 | 12 | function FtpConfig() { 13 | Config.apply(this, Array.prototype.slice.call(arguments)); 14 | 15 | /** 16 | * @type {Number} 17 | */ 18 | this.port = 21; 19 | 20 | /** 21 | * @type {String} 22 | */ 23 | this.type = "ftp"; 24 | } 25 | 26 | util.inherits(FtpConfig, Config); 27 | 28 | module.exports = FtpConfig; 29 | -------------------------------------------------------------------------------- /lib/configs/SftpConfig.js: -------------------------------------------------------------------------------- 1 | ////////////////// 2 | // Requires 3 | ////////////////// 4 | 5 | var util = require('util'); 6 | var fs = require('fs'); 7 | var path = require('path'); 8 | var expandHomeDir = require('expand-home-dir') 9 | 10 | var Config = require('./Config'); 11 | 12 | ////////////////// 13 | // Ctor 14 | ////////////////// 15 | 16 | function SftpConfig() { 17 | Config.apply(this, Array.prototype.slice.call(arguments)); 18 | 19 | /** 20 | * @type {Number} 21 | */ 22 | this.port = 22; 23 | 24 | /** 25 | * @type {String} 26 | */ 27 | this.type = "sftp"; 28 | 29 | /** 30 | * @type {String} 31 | */ 32 | this.sshKeyFile = null; 33 | 34 | /** 35 | * @type {String} 36 | */ 37 | this.passphrase = null; 38 | } 39 | 40 | util.inherits(SftpConfig, Config); 41 | 42 | ////////////////// 43 | // Methods 44 | ////////////////// 45 | 46 | /** 47 | * Ssh key file setter 48 | * @param {String} _key 49 | */ 50 | SftpConfig.prototype.setSshKeyFile = function (_key) { 51 | this.sshKeyFile = _key; 52 | }; 53 | 54 | /** 55 | * Ssh key file getter 56 | */ 57 | SftpConfig.prototype.getSshKeyFile = function () { 58 | return fs.readFileSync(expandHomeDir(this.sshKeyFile), 'utf8'); 59 | }; 60 | 61 | /** 62 | * Passphrase setter 63 | * @param {String} _passphrase 64 | */ 65 | SftpConfig.prototype.setPassphrase = function (_passphrase) { 66 | this.passphrase = _passphrase; 67 | }; 68 | 69 | /** 70 | * Passphrase getter 71 | */ 72 | SftpConfig.prototype.getPassphrase = function () { 73 | return this.passphrase; 74 | }; 75 | 76 | module.exports = SftpConfig; 77 | -------------------------------------------------------------------------------- /lib/connections/Connection.js: -------------------------------------------------------------------------------- 1 | ////////////////// 2 | // Requires 3 | ////////////////// 4 | 5 | var Promise = require('bluebird'); 6 | var exec = require('child_process').exec; 7 | 8 | var Queue = require('./../queue/Queue'); 9 | var Action = require('./../queue/Action'); 10 | var Directory = require('./../filesystem/Directory'); 11 | 12 | var ConnectionErrorException = require('./../exceptions/ConnectionErrorException'); 13 | var DirectoryCreationErrorException = require('./../exceptions/DirectoryCreationErrorException'); 14 | var RemoteDirectoryNotReadableException = require('./../exceptions/RemoteDirectoryNotReadableException'); 15 | 16 | ////////////////// 17 | // Ctor 18 | ////////////////// 19 | 20 | function Connection(config) { 21 | this.config = config ? config : null; 22 | this.connection = null; 23 | this.queue = new Queue(3); 24 | } 25 | 26 | ////////////////// 27 | // Methods 28 | ////////////////// 29 | 30 | Connection.prototype.getConnectionInformations = function() { 31 | throw "Method non implemented"; 32 | } 33 | 34 | Connection.prototype.createRemoteDirectory = function(directoryPath) { 35 | throw "Method non implemented"; 36 | } 37 | 38 | /** 39 | * @TODO: Change exec to proper way 40 | */ 41 | Connection.prototype.createDirectory = function(directoryPath) { 42 | var deferred = Promise.pending(); 43 | 44 | try { 45 | exec( 46 | 'mkdir -p ' + directoryPath, 47 | function (err) { 48 | if (err) { 49 | deferred.reject(new DirectoryCreationErrorException(directoryPath)); 50 | return; 51 | } 52 | 53 | deferred.resolve(directoryPath); 54 | } 55 | ); 56 | } catch(e) { 57 | deferred.reject(e); 58 | } 59 | 60 | return deferred.promise; 61 | } 62 | 63 | Connection.prototype.uploadFile = function(file) { 64 | throw "Method non implemented"; 65 | } 66 | 67 | Connection.prototype.downloadFile = function(file) { 68 | throw "Method non implemented"; 69 | } 70 | 71 | Connection.prototype.connect = function() { 72 | var self = this; 73 | var deferred = Promise.pending(); 74 | 75 | this.connection.on('ready', function() { 76 | deferred.resolve(self); 77 | }); 78 | 79 | this.connection.on('error', function(err) { 80 | deferred.reject(new ConnectionErrorException(err.message)); 81 | }); 82 | 83 | try { 84 | this.connection.connect(this.getConnectionInformations()); 85 | } catch(e) { 86 | deferred.reject(new ConnectionErrorException(e.message)); 87 | } 88 | 89 | return deferred.promise; 90 | } 91 | 92 | Connection.prototype.close = function() { 93 | var deferred = Promise.pending(); 94 | 95 | try { 96 | this.connection.end(); 97 | deferred.resolve(true); 98 | } catch(e) { 99 | deferred.reject(e); 100 | } 101 | 102 | return deferred.promise; 103 | } 104 | 105 | /** 106 | * @param {File[]} files 107 | */ 108 | Connection.prototype.upload = function(files) { 109 | var self = this; 110 | var deferred = Promise.pending(); 111 | 112 | try { 113 | for (var i in files) { 114 | this.queue.addAction(new Action( 115 | self, 116 | self.uploadFile, 117 | [files[i]] 118 | )); 119 | } 120 | 121 | this.queue.execute( 122 | function() { 123 | deferred.resolve(self); 124 | }, 125 | function(e) { 126 | deferred.reject(e); 127 | } 128 | ); 129 | } catch(e) { 130 | deferred.reject(e); 131 | } 132 | 133 | return deferred.promise; 134 | } 135 | 136 | /** 137 | * @param {File[]} aFiles 138 | */ 139 | Connection.prototype.download = function(files) { 140 | var self = this; 141 | var deferred = Promise.pending(); 142 | 143 | try { 144 | for (var i in files) { 145 | this.queue.addAction(new Action( 146 | self, 147 | self.downloadFile, 148 | [files[i]] 149 | )); 150 | } 151 | 152 | this.queue.execute( 153 | function() { 154 | deferred.resolve(self); 155 | }, 156 | function(e) { 157 | deferred.reject(e); 158 | } 159 | ); 160 | } catch(e) { 161 | deferred.reject(e); 162 | } 163 | 164 | return deferred.promise; 165 | } 166 | 167 | Connection.prototype.extractDistantFiles = function(node, files) { 168 | } 169 | 170 | /** 171 | * @param {File[]} aFiles 172 | */ 173 | Connection.prototype.getTargetFiles = function(nodes) { 174 | var self = this; 175 | var deferred = Promise.pending(); 176 | 177 | var files = []; 178 | var calls = []; 179 | 180 | try { 181 | for (var i in nodes) { 182 | var node = nodes[i]; 183 | calls.push(this.extractDistantFiles(node, files)); 184 | } 185 | 186 | Promise.all(calls) 187 | .then(function() { 188 | deferred.resolve(files); 189 | }) 190 | .catch(RemoteDirectoryNotReadableException, function(e) { 191 | deferred.reject(e); 192 | }); 193 | } catch(e) { 194 | deferred.reject(e); 195 | } 196 | 197 | return deferred.promise; 198 | } 199 | 200 | module.exports = Connection; 201 | -------------------------------------------------------------------------------- /lib/connections/ConnectionFactory.js: -------------------------------------------------------------------------------- 1 | ////////////////// 2 | // Requires 3 | ////////////////// 4 | 5 | var Promise = require('bluebird'); 6 | 7 | var SftpConnection = require("./SftpConnection"); 8 | var FtpConnection = require("./FtpConnection"); 9 | 10 | ////////////////// 11 | // Ctor 12 | ////////////////// 13 | 14 | function ConnectionFactory() { 15 | } 16 | 17 | ////////////////// 18 | // Methods 19 | ////////////////// 20 | 21 | ConnectionFactory.prototype.createConnection = function(config) { 22 | var deferred = Promise.pending(); 23 | 24 | try { 25 | var type = config.type; 26 | type = type.charAt(0).toUpperCase() + type.substring(1); 27 | var getter = 'create' + type + 'Connection'; 28 | var connection = this[getter](config); 29 | 30 | deferred.resolve(connection); 31 | } catch(e) { 32 | deferred.reject(e); 33 | } 34 | 35 | return deferred.promise; 36 | }; 37 | 38 | /** 39 | * @return {SftpConnection} 40 | */ 41 | ConnectionFactory.prototype.openConnection = function (config) { 42 | return this.createConnection(config) 43 | .then(function(connection) { 44 | return connection.connect(); 45 | }); 46 | }; 47 | 48 | /** 49 | * @return {SftpConnection} 50 | */ 51 | ConnectionFactory.prototype.createSftpConnection = function (config) { 52 | return new SftpConnection(config); 53 | }; 54 | 55 | /** 56 | * @return {FtpConnection} 57 | */ 58 | ConnectionFactory.prototype.createFtpConnection = function (config) { 59 | return new FtpConnection(config); 60 | }; 61 | 62 | module.exports = ConnectionFactory; 63 | -------------------------------------------------------------------------------- /lib/connections/FtpConnection.js: -------------------------------------------------------------------------------- 1 | ////////////////// 2 | // Requires 3 | ////////////////// 4 | 5 | var util = require('util'); 6 | var path = require('path'); 7 | var fs = require('fs'); 8 | var FTPConnection = require('ftp'); 9 | var Promise = require('bluebird'); 10 | 11 | var Connection = require('./Connection'); 12 | var Directory = require('./../filesystem/Directory'); 13 | var File = require('./../filesystem/File'); 14 | 15 | var UploadErrorException = require('./../exceptions/UploadErrorException'); 16 | var DownloadErrorException = require('./../exceptions/DownloadErrorException'); 17 | var RemoteDirectoryCreationErrorException = require('./../exceptions/RemoteDirectoryCreationErrorException'); 18 | var TransfertErrorException = require('./../exceptions/TransfertErrorException'); 19 | var RemoteDirectoryNotReadableException = require('./../exceptions/RemoteDirectoryNotReadableException'); 20 | 21 | ////////////////// 22 | // Ctor 23 | ////////////////// 24 | 25 | function FtpConnection(config) { 26 | Connection.apply(this, [config]); 27 | 28 | this.connection = new FTPConnection(); 29 | } 30 | 31 | util.inherits(FtpConnection, Connection); 32 | 33 | ////////////////// 34 | // Methods 35 | ////////////////// 36 | 37 | FtpConnection.prototype.getConnectionInformations = function() { 38 | var self = this; 39 | 40 | return { 41 | host: self.config.host, 42 | port: self.config.port ? self.config.port : 21, 43 | user: self.config.username, 44 | password: self.config.password 45 | }; 46 | } 47 | 48 | 49 | FtpConnection.prototype.createRemoteDirectory = function(directoryPath) { 50 | var deferred = Promise.pending(); 51 | 52 | try { 53 | this.connection.mkdir( 54 | directoryPath, 55 | true, 56 | function (err) { 57 | if (err) { 58 | deferred.reject(new RemoteDirectoryCreationErrorException(directoryPath)); 59 | } 60 | 61 | deferred.resolve(directoryPath); 62 | } 63 | ); 64 | } catch(e) { 65 | deferred.reject(e); 66 | } 67 | 68 | return deferred.promise; 69 | } 70 | 71 | FtpConnection.prototype.put = function(sourceFile, destinationFile) { 72 | var deferred = Promise.pending(); 73 | 74 | try { 75 | this.connection.put(sourceFile, destinationFile, function(err) { 76 | if (err) { 77 | if (err.code === 550) { 78 | deferred.reject(new UploadErrorException(sourceFile, 'Permission denied')); 79 | } else { 80 | deferred.reject(err); 81 | } 82 | return; 83 | } 84 | 85 | deferred.resolve(); 86 | }); 87 | } catch(e) { 88 | deferred.reject(e); 89 | } 90 | 91 | return deferred.promise; 92 | } 93 | 94 | FtpConnection.prototype.get = function(sourceFile, destinationFile) { 95 | var deferred = Promise.pending(); 96 | 97 | try { 98 | this.connection.get(sourceFile, function(err, stream) { 99 | if (err) { 100 | if (err.code === 550) { 101 | deferred.reject(new DownloadErrorException(destinationFile, 'Permission denied')); 102 | } else if (err.code === 425) { 103 | deferred.reject(new TransfertErrorException(sourceFile, err.message)); 104 | } else { 105 | deferred.reject(err); 106 | } 107 | return; 108 | } 109 | 110 | var outFileStream = fs.createWriteStream(destinationFile); 111 | outFileStream.once('error', function(e) { 112 | deferred.reject(new DownloadErrorException(destinationFile, 'Permission denied')); 113 | }); 114 | stream.once('close', function() { 115 | deferred.resolve(); 116 | }); 117 | stream.pipe(outFileStream); 118 | }); 119 | } catch(e) { 120 | deferred.reject(e); 121 | } 122 | 123 | return deferred.promise; 124 | } 125 | 126 | FtpConnection.prototype.uploadFile = function(file) { 127 | var deferred = Promise.pending(); 128 | var self = this; 129 | var destinationFile = path.join( 130 | this.config.getRemotePath(), 131 | file.getRelativePath() 132 | ).replace(/\\/g, '/'); 133 | 134 | try { 135 | this.createRemoteDirectory(path.dirname(destinationFile)) 136 | .then(function(directory) { 137 | return self.put(file.getPath(), destinationFile); 138 | }) 139 | .then(function() { 140 | deferred.resolve(file); 141 | }) 142 | .catch(function(e) { 143 | deferred.reject(e); 144 | }); 145 | } catch(e) { 146 | deferred.reject(e); 147 | } 148 | 149 | return deferred.promise; 150 | } 151 | 152 | FtpConnection.prototype.downloadFile = function(file) { 153 | var deferred = Promise.pending(); 154 | var self = this; 155 | var sourceFile = path.join( 156 | this.config.getRemotePath(), 157 | file.getRelativePath() 158 | ).replace(/\\/g, '/'); 159 | 160 | try { 161 | this.createDirectory(file.getDirectory()) 162 | .then(function(directory) { 163 | return self.get(sourceFile, file.getPath()); 164 | }) 165 | .then(function() { 166 | deferred.resolve(file); 167 | }) 168 | .catch(function(e) { 169 | deferred.reject(e); 170 | }); 171 | } catch(e) { 172 | deferred.reject(e); 173 | } 174 | 175 | return deferred.promise; 176 | } 177 | 178 | FtpConnection.prototype.extractDistantFiles = function(node, files) { 179 | var deferred = Promise.pending(); 180 | var self = this; 181 | 182 | try { 183 | if (node instanceof Directory) { 184 | var remotePath = path.join( 185 | this.config.getRemotePath(), 186 | node.getRelativePath() 187 | ).replace(/\\/g, '/'); 188 | 189 | this.connection.list(remotePath, function(err, nodes) { 190 | if (err) { 191 | deferred.reject(new RemoteDirectoryNotReadableException(remotePath)); 192 | } 193 | 194 | var calls = []; 195 | 196 | for (var i in nodes) { 197 | if (nodes[i].type === 'd') { 198 | calls.push(self.extractDistantFiles(new Directory(path.join(node.getRelativePath(), nodes[i].name), true), files)); 199 | } else { 200 | calls.push(self.extractDistantFiles(new File(path.join(node.getRelativePath(), nodes[i].name), true), files)); 201 | } 202 | } 203 | 204 | Promise.all(calls).then(function() { 205 | deferred.resolve(); 206 | }) 207 | .catch(function(e) { 208 | deferred.reject(e); 209 | }); 210 | }) 211 | } else { 212 | files.push(node); 213 | deferred.resolve(); 214 | } 215 | } catch(e) { 216 | deferred.reject(e); 217 | } 218 | 219 | return deferred.promise; 220 | } 221 | 222 | module.exports = FtpConnection; 223 | -------------------------------------------------------------------------------- /lib/connections/SftpConnection.js: -------------------------------------------------------------------------------- 1 | ////////////////// 2 | // Requires 3 | ////////////////// 4 | 5 | var Promise = require('bluebird'); 6 | var util = require('util'); 7 | var path = require('path'); 8 | var SshConnection = require('ssh2'); 9 | 10 | var Connection = require('./Connection'); 11 | var Directory = require('./../filesystem/Directory'); 12 | var File = require('./../filesystem/File'); 13 | 14 | var UploadErrorException = require('./../exceptions/UploadErrorException'); 15 | var DownloadErrorException = require('./../exceptions/DownloadErrorException'); 16 | var RemoteDirectoryCreationErrorException = require('./../exceptions/RemoteDirectoryCreationErrorException'); 17 | var RemoteSystemErrorException = require('./../exceptions/RemoteSystemErrorException'); 18 | var TransfertErrorException = require('./../exceptions/TransfertErrorException'); 19 | var RemoteDirectoryNotReadableException = require('./../exceptions/RemoteDirectoryNotReadableException'); 20 | 21 | ////////////////// 22 | // Ctor 23 | ////////////////// 24 | 25 | function SftpConnection(config) { 26 | Connection.apply(this, [config]); 27 | 28 | this.connection = new SshConnection(); 29 | } 30 | 31 | util.inherits(SftpConnection, Connection); 32 | 33 | ////////////////// 34 | // Methods 35 | ////////////////// 36 | 37 | SftpConnection.prototype.getConnectionInformations = function() { 38 | var self = this; 39 | 40 | var informations = { 41 | host: self.config.host, 42 | port: self.config.port ? self.config.port : 22, 43 | username: self.config.getUsername() 44 | } 45 | 46 | if (self.config.getPassword()) { 47 | informations.password = self.config.getPassword(); 48 | } else { 49 | informations.privateKey = self.config.getSshKeyFile(); 50 | informations.passphrase = self.config.getPassphrase(); 51 | } 52 | 53 | return informations; 54 | } 55 | 56 | SftpConnection.prototype.createRemoteDirectory = function(directoryPath) { 57 | var deferred = Promise.pending(); 58 | 59 | try { 60 | var success = this.connection.exec( 61 | 'mkdir -p ' + directoryPath, 62 | function (err, stream) { 63 | if (err) { 64 | deferred.reject(new RemoteDirectoryCreationErrorException(directoryPath)); 65 | } 66 | 67 | stream.on('close', function() { 68 | deferred.resolve(directoryPath); 69 | }); 70 | stream.stderr.on('data', function(data) { 71 | deferred.reject(new RemoteSystemErrorException(data.toString())); 72 | }); 73 | } 74 | ); 75 | } catch(e) { 76 | deferred.reject(e); 77 | } 78 | 79 | return deferred.promise; 80 | } 81 | 82 | SftpConnection.prototype.openSftp = function() { 83 | var deferred = Promise.pending(); 84 | 85 | try { 86 | this.connection.sftp(function(err, sftp) { 87 | if (err) { 88 | deferred.reject(err); 89 | } 90 | 91 | deferred.resolve(sftp); 92 | }); 93 | } catch(e) { 94 | deferred.reject(e); 95 | } 96 | 97 | return deferred.promise; 98 | } 99 | 100 | SftpConnection.prototype.fastPut = function(sftp, sourceFile, destinationFile) { 101 | var deferred = Promise.pending(); 102 | 103 | try { 104 | sftp.fastPut(sourceFile, destinationFile, function(err) { 105 | if (err) { 106 | deferred.reject(new UploadErrorException(sourceFile, err.message)); 107 | } 108 | 109 | deferred.resolve(sftp); 110 | }); 111 | } catch(e) { 112 | deferred.reject(e); 113 | } 114 | 115 | return deferred.promise; 116 | } 117 | 118 | SftpConnection.prototype.fastGet = function(sftp, sourceFile, destinationFile) { 119 | var deferred = Promise.pending(); 120 | 121 | try { 122 | sftp.fastGet(sourceFile, destinationFile, function(err) { 123 | if (err) { 124 | if (err.code === 'EACCES') { 125 | deferred.reject(new DownloadErrorException(destinationFile, 'Permission denied')); 126 | } else { 127 | deferred.reject(err); 128 | } 129 | } 130 | 131 | deferred.resolve(sftp); 132 | }); 133 | } catch(e) { 134 | deferred.reject(e); 135 | } 136 | 137 | return deferred.promise; 138 | } 139 | 140 | SftpConnection.prototype.uploadFile = function(file) { 141 | var deferred = Promise.pending(); 142 | var self = this; 143 | var destinationFile = path.join( 144 | this.config.getRemotePath(), 145 | file.getRelativePath() 146 | ).replace(/\\/g, '/'); 147 | 148 | try { 149 | this.createRemoteDirectory(path.dirname(destinationFile)) 150 | .then(function(directory) { 151 | return self.openSftp(); 152 | }) 153 | .then(function(sftp) { 154 | return self.fastPut(sftp, file.getPath(), destinationFile); 155 | }) 156 | .then(function(sftp) { 157 | sftp.end(); 158 | deferred.resolve(file); 159 | }) 160 | .catch(function(e) { 161 | deferred.reject(e); 162 | }); 163 | } catch(e) { 164 | deferred.reject(e); 165 | } 166 | 167 | return deferred.promise; 168 | } 169 | 170 | SftpConnection.prototype.downloadFile = function(file) { 171 | var deferred = Promise.pending(); 172 | var self = this; 173 | var sourceFile = path.join( 174 | this.config.getRemotePath(), 175 | file.getRelativePath() 176 | ).replace(/\\/g, '/'); 177 | 178 | try { 179 | this.createDirectory(file.getDirectory()) 180 | .then(function(directory) { 181 | return self.openSftp(); 182 | }) 183 | .then(function(sftp) { 184 | return self.fastGet(sftp, sourceFile, file.getPath()); 185 | }) 186 | .then(function(sftp) { 187 | sftp.end(); 188 | deferred.resolve(file); 189 | }) 190 | .catch(function(e) { 191 | deferred.reject(e); 192 | }); 193 | } catch(e) { 194 | deferred.reject(e); 195 | } 196 | 197 | return deferred.promise; 198 | } 199 | 200 | SftpConnection.prototype.getListDir = function(sftp, node, files) { 201 | var deferred = Promise.pending(); 202 | var self = this; 203 | 204 | try { 205 | if (node instanceof Directory) { 206 | var remotePath = path.join( 207 | this.config.getRemotePath(), 208 | node.getRelativePath() 209 | ).replace(/\\/g, '/'); 210 | 211 | sftp.readdir(remotePath, function(err, nodes) { 212 | if (err) { 213 | deferred.reject(new RemoteDirectoryNotReadableException(remotePath)); 214 | } 215 | 216 | var calls = []; 217 | 218 | for (var i in nodes) { 219 | if (nodes[i].attrs.isDirectory()) { 220 | calls.push(self.getListDir(sftp, new Directory(path.join(node.getRelativePath(), nodes[i].filename), true), files)); 221 | } else { 222 | calls.push(self.getListDir(sftp, new File(path.join(node.getRelativePath(), nodes[i].filename), true), files)); 223 | } 224 | } 225 | 226 | Promise.all(calls) 227 | .then(function() { 228 | deferred.resolve(); 229 | }) 230 | .catch(function(e) { 231 | deferred.reject(e); 232 | }); 233 | }); 234 | } else { 235 | files.push(node); 236 | deferred.resolve(); 237 | } 238 | } catch(e) { 239 | deferred.reject(e); 240 | } 241 | 242 | return deferred.promise; 243 | } 244 | 245 | SftpConnection.prototype.extractDistantFiles = function(node, files) { 246 | var deferred = Promise.pending(); 247 | var self = this; 248 | 249 | try { 250 | var sftp = null; 251 | if (node instanceof Directory) { 252 | this.openSftp() 253 | .then(function(_sftp) { 254 | sftp = _sftp; 255 | return self.getListDir(_sftp, node, files); 256 | }) 257 | .then(function(nodes) { 258 | sftp.end(); 259 | var calls = []; 260 | 261 | for (var i in nodes) { 262 | calls.push(this.extractDistantFiles(nodes[i], files)); 263 | } 264 | 265 | Promise.all(calls) 266 | .then(function() { 267 | deferred.resolve(); 268 | }) 269 | .catch(function(e) { 270 | deferred.reject(e); 271 | }); 272 | }) 273 | .catch(function(e) { 274 | deferred.reject(e); 275 | }); 276 | } else { 277 | files.push(node); 278 | deferred.resolve(); 279 | } 280 | } catch(e) { 281 | deferred.reject(e); 282 | } 283 | 284 | return deferred.promise; 285 | } 286 | 287 | module.exports = SftpConnection; 288 | -------------------------------------------------------------------------------- /lib/exceptions/ConfigurationFileNotReadableException.js: -------------------------------------------------------------------------------- 1 | var util = require('util'); 2 | 3 | var Exception = require('./Exception'); 4 | 5 | ////////////////// 6 | // Ctor 7 | ////////////////// 8 | 9 | function ConfigurationFileNotReadableException() { 10 | Exception.apply(this, ['The configuration file is not readable.']); 11 | } 12 | 13 | util.inherits(ConfigurationFileNotReadableException, Exception); 14 | 15 | module.exports = ConfigurationFileNotReadableException; 16 | -------------------------------------------------------------------------------- /lib/exceptions/ConfigurationFileSyntaxErrorException.js: -------------------------------------------------------------------------------- 1 | var util = require('util'); 2 | 3 | var Exception = require('./Exception'); 4 | 5 | ////////////////// 6 | // Ctor 7 | ////////////////// 8 | 9 | function ConfigurationFileSyntaxErrorException(message) { 10 | Exception.apply( 11 | this, 12 | ['The configuration file is not well formatted : ' + message] 13 | ); 14 | } 15 | 16 | util.inherits(ConfigurationFileSyntaxErrorException, Exception); 17 | 18 | module.exports = ConfigurationFileSyntaxErrorException; 19 | -------------------------------------------------------------------------------- /lib/exceptions/ConnectionErrorException.js: -------------------------------------------------------------------------------- 1 | var util = require('util'); 2 | 3 | var Exception = require('./Exception'); 4 | 5 | ////////////////// 6 | // Ctor 7 | ////////////////// 8 | 9 | function ConnectionErrorException(message) { 10 | Exception.apply( 11 | this, 12 | ['Cannot established connection : ' + message] 13 | ); 14 | } 15 | 16 | util.inherits(ConnectionErrorException, Exception); 17 | 18 | module.exports = ConnectionErrorException; 19 | -------------------------------------------------------------------------------- /lib/exceptions/DirectoryCreationErrorException.js: -------------------------------------------------------------------------------- 1 | var util = require('util'); 2 | 3 | var Exception = require('./Exception'); 4 | 5 | ////////////////// 6 | // Ctor 7 | ////////////////// 8 | 9 | function DirectoryCreationErrorException(directory) { 10 | Exception.apply( 11 | this, 12 | ['Cannot create directory "' + directory + '"'] 13 | ); 14 | } 15 | 16 | util.inherits(DirectoryCreationErrorException, Exception); 17 | 18 | module.exports = DirectoryCreationErrorException; 19 | -------------------------------------------------------------------------------- /lib/exceptions/DownloadErrorException.js: -------------------------------------------------------------------------------- 1 | var util = require('util'); 2 | 3 | var Exception = require('./Exception'); 4 | 5 | ////////////////// 6 | // Ctor 7 | ////////////////// 8 | 9 | function DownloadErrorException(file, message) { 10 | Exception.apply( 11 | this, 12 | ['Cannot download file "' + file + '" : ' + message] 13 | ); 14 | } 15 | 16 | util.inherits(DownloadErrorException, Exception); 17 | 18 | module.exports = DownloadErrorException; 19 | -------------------------------------------------------------------------------- /lib/exceptions/Exception.js: -------------------------------------------------------------------------------- 1 | ////////////////// 2 | // Ctor 3 | ////////////////// 4 | 5 | function Exception(message) { 6 | this.code = null; 7 | this.message = message; 8 | } 9 | 10 | Exception.prototype = Object.create(Error.prototype); 11 | 12 | ////////////////// 13 | // Methods 14 | ////////////////// 15 | 16 | /** 17 | * Path getter 18 | * @return {String} 19 | */ 20 | Exception.prototype.getMessage = function () { 21 | return this.message; 22 | }; 23 | 24 | /** 25 | * Relative path getter 26 | * @return {String} 27 | */ 28 | Exception.prototype.getCode = function () { 29 | return this.code; 30 | }; 31 | 32 | module.exports = Exception; 33 | -------------------------------------------------------------------------------- /lib/exceptions/NoConfigurationFileFoundException.js: -------------------------------------------------------------------------------- 1 | var util = require('util'); 2 | 3 | var Exception = require('./Exception'); 4 | 5 | ////////////////// 6 | // Ctor 7 | ////////////////// 8 | 9 | function NoConfigurationFileFoundException() { 10 | Exception.apply(this, ['No configuration file found.']); 11 | } 12 | 13 | util.inherits(NoConfigurationFileFoundException, Exception); 14 | 15 | module.exports = NoConfigurationFileFoundException; 16 | -------------------------------------------------------------------------------- /lib/exceptions/RemoteDirectoryCreationErrorException.js: -------------------------------------------------------------------------------- 1 | var util = require('util'); 2 | 3 | var Exception = require('./Exception'); 4 | 5 | ////////////////// 6 | // Ctor 7 | ////////////////// 8 | 9 | function RemoteDirectoryCreationErrorException(directory) { 10 | Exception.apply( 11 | this, 12 | ['Cannot create remote directory "' + directory + '"'] 13 | ); 14 | } 15 | 16 | util.inherits(RemoteDirectoryCreationErrorException, Exception); 17 | 18 | module.exports = RemoteDirectoryCreationErrorException; 19 | -------------------------------------------------------------------------------- /lib/exceptions/RemoteDirectoryNotReadableException.js: -------------------------------------------------------------------------------- 1 | var util = require('util'); 2 | 3 | var Exception = require('./Exception'); 4 | 5 | ////////////////// 6 | // Ctor 7 | ////////////////// 8 | 9 | function RemoteDirectoryNotReadableException(directory) { 10 | Exception.apply(this, ['The remote directory "' + directory + '" is not readable.']); 11 | } 12 | 13 | util.inherits(RemoteDirectoryNotReadableException, Exception); 14 | 15 | module.exports = RemoteDirectoryNotReadableException; 16 | -------------------------------------------------------------------------------- /lib/exceptions/RemoteSystemErrorException.js: -------------------------------------------------------------------------------- 1 | var util = require('util'); 2 | 3 | var Exception = require('./Exception'); 4 | 5 | ////////////////// 6 | // Ctor 7 | ////////////////// 8 | 9 | function RemoteSystemErrorException(msg) { 10 | Exception.apply( 11 | this, 12 | ['Remote System Error: "' + msg + '"'] 13 | ); 14 | } 15 | 16 | util.inherits(RemoteSystemErrorException, Exception); 17 | 18 | module.exports = RemoteSystemErrorException; 19 | -------------------------------------------------------------------------------- /lib/exceptions/TransfertErrorException.js: -------------------------------------------------------------------------------- 1 | var util = require('util'); 2 | 3 | var Exception = require('./Exception'); 4 | 5 | ////////////////// 6 | // Ctor 7 | ////////////////// 8 | 9 | function TransfertErrorException(file, message) { 10 | Exception.apply( 11 | this, 12 | ['Transfert error with file "' + file + '" : ' + message] 13 | ); 14 | } 15 | 16 | util.inherits(TransfertErrorException, Exception); 17 | 18 | module.exports = TransfertErrorException; 19 | -------------------------------------------------------------------------------- /lib/exceptions/UploadErrorException.js: -------------------------------------------------------------------------------- 1 | var util = require('util'); 2 | 3 | var Exception = require('./Exception'); 4 | 5 | ////////////////// 6 | // Ctor 7 | ////////////////// 8 | 9 | function UploadErrorException(file, message) { 10 | Exception.apply( 11 | this, 12 | ['Cannot upload file "' + file + '" : ' + message] 13 | ); 14 | } 15 | 16 | util.inherits(UploadErrorException, Exception); 17 | 18 | module.exports = UploadErrorException; 19 | -------------------------------------------------------------------------------- /lib/filesystem/Directory.js: -------------------------------------------------------------------------------- 1 | ////////////////// 2 | // Requires 3 | ////////////////// 4 | 5 | var path = require('path'); 6 | var util = require('util'); 7 | 8 | var Node = require('./Node'); 9 | 10 | ////////////////// 11 | // Ctor 12 | ////////////////// 13 | 14 | function Directory(_path, relative) { 15 | Node.apply(this, [_path, relative]); 16 | } 17 | util.inherits(Directory, Node); 18 | 19 | module.exports = Directory; 20 | -------------------------------------------------------------------------------- /lib/filesystem/File.js: -------------------------------------------------------------------------------- 1 | ////////////////// 2 | // Requires 3 | ////////////////// 4 | 5 | var path = require('path'); 6 | var util = require('util'); 7 | 8 | var Node = require('./Node'); 9 | 10 | ////////////////// 11 | // Ctor 12 | ////////////////// 13 | 14 | function File(_path, relative) { 15 | Node.apply(this, [_path, relative]); 16 | } 17 | util.inherits(File, Node); 18 | 19 | ////////////////// 20 | // Methods 21 | ////////////////// 22 | 23 | /** 24 | * Filename getter 25 | * @return {String} 26 | */ 27 | File.prototype.getFilename = function () { 28 | return path.basename(this.path); 29 | }; 30 | 31 | /** 32 | * Directory getter 33 | * @return {String} 34 | */ 35 | File.prototype.getDirectory = function () { 36 | return path.dirname(this.path); 37 | }; 38 | 39 | module.exports = File; 40 | -------------------------------------------------------------------------------- /lib/filesystem/FileManager.js: -------------------------------------------------------------------------------- 1 | ////////////////// 2 | // Requires 3 | ////////////////// 4 | 5 | var Promise = require('bluebird'); 6 | var util = require('util'); 7 | var fs = require('fs'); 8 | var path = require('path'); 9 | 10 | var File = require('./File'); 11 | var Directory = require('./Directory'); 12 | 13 | ////////////////// 14 | // Ctor 15 | ////////////////// 16 | 17 | function FileManager() { 18 | } 19 | 20 | ////////////////// 21 | // Methods 22 | ////////////////// 23 | 24 | /** 25 | * Get the file of current open tab 26 | */ 27 | FileManager.prototype.getCurrentFile = function() { 28 | var deferred = Promise.pending(); 29 | 30 | try { 31 | deferred.fulfill(new File(atom.workspace.paneContainer.activePane.activeItem.buffer.file.path)); 32 | } catch(e) { 33 | deferred.reject(e); 34 | } 35 | 36 | return deferred.promise; 37 | }; 38 | 39 | /** 40 | * Get the files of open tabs 41 | * @return {File[]} 42 | */ 43 | FileManager.prototype.getOpenFiles = function() { 44 | var deferred = Promise.pending(); 45 | 46 | try { 47 | var files = []; 48 | var items = atom.workspace.getActivePane().getItems(); 49 | for (var i in items) { 50 | files.push(new File(items[i].buffer.file.path)); 51 | } 52 | deferred.fulfill(files); 53 | } catch(e) { 54 | deferred.reject(e); 55 | } 56 | 57 | return deferred.promise; 58 | }; 59 | 60 | var treatPath = function(_path, results, deep) { 61 | var stats = fs.statSync(_path); 62 | 63 | if (stats.isDirectory()) { 64 | if (deep) { 65 | var files = fs.readdirSync(_path); 66 | for (var i in files) { 67 | treatPath(path.join(_path, files[i]), results, deep); 68 | } 69 | } else { 70 | results.push(new Directory(_path)); 71 | } 72 | } else { 73 | if (results.indexOf(_path) < 0) { 74 | results.push(new File(_path)); 75 | } 76 | } 77 | } 78 | 79 | /** 80 | * Get the files of open tabs 81 | * @return {File[]} 82 | */ 83 | FileManager.prototype.getSelection = function(deep) { 84 | var deferred = Promise.pending(); 85 | 86 | try { 87 | var selectedPaths = atom.workspace.getLeftPanels()[0].getItem().selectedPaths(); 88 | var files = []; 89 | for (var i in selectedPaths) { 90 | treatPath(selectedPaths[i], files, deep); 91 | } 92 | deferred.resolve(files); 93 | } catch(e) { 94 | deferred.reject(e); 95 | } 96 | 97 | return deferred.promise; 98 | }; 99 | 100 | module.exports = new FileManager(); 101 | -------------------------------------------------------------------------------- /lib/filesystem/Node.js: -------------------------------------------------------------------------------- 1 | ////////////////// 2 | // Requires 3 | ////////////////// 4 | 5 | var path = require('path'); 6 | 7 | ////////////////// 8 | // Ctor 9 | ////////////////// 10 | 11 | function Node(_path, relative) { 12 | if (atom.project.rootDirectories.length < 1) { 13 | throw "project_not_found"; 14 | } 15 | 16 | if (relative) { 17 | this.path = path.join( 18 | atom.project.rootDirectories[0].path, 19 | _path 20 | ); 21 | this.relativePath = _path; 22 | } else { 23 | this.path = _path; 24 | this.relativePath = path.relative( 25 | atom.project.rootDirectories[0].path, 26 | _path 27 | ); 28 | } 29 | } 30 | 31 | ////////////////// 32 | // Methods 33 | ////////////////// 34 | 35 | /** 36 | * Path getter 37 | * @return {String} 38 | */ 39 | Node.prototype.getPath = function () { 40 | return this.path; 41 | }; 42 | 43 | /** 44 | * Relative path getter 45 | * @return {String} 46 | */ 47 | Node.prototype.getRelativePath = function () { 48 | return this.relativePath; 49 | }; 50 | 51 | module.exports = Node; 52 | -------------------------------------------------------------------------------- /lib/observers/ConsoleObserver.js: -------------------------------------------------------------------------------- 1 | function MessageObserver() { 2 | } 3 | 4 | MessageObserver.prototype.notify = function(value, data) { 5 | switch (value) { 6 | case "project_not_found": 7 | console.log("Create a project before trying to create a configuration file"); 8 | break; 9 | case "configuration_file_creation_success": 10 | console.log("The configuration file was created with success"); 11 | break; 12 | case "configuration_file_creation_error": 13 | console.log("A error occured during configuration file creation : " + data); 14 | break; 15 | case "configuration_file_doesnt_exist": 16 | console.log("The configuration file doesn't exist"); 17 | break; 18 | case "configuration_file_exists": 19 | console.log("The configuration file exists"); 20 | break; 21 | case "configuration_ready": 22 | console.log("The configuration is ready"); 23 | console.log(data); 24 | break; 25 | case "connection_ready": 26 | console.log("The connection is ready"); 27 | console.log(data); 28 | break; 29 | case "connection_error": 30 | console.log(data.message); 31 | break; 32 | case "connection_sftp_end": 33 | case "connection_ftp_end": 34 | console.log("The sftp connection ends"); 35 | break; 36 | case "sftp_mkdir": 37 | case "ftp_mkdir": 38 | console.log("Creation of directory : " + data); 39 | break; 40 | case "sftp_mkdir_upload_file_error": 41 | case "ftp_mkdir_upload_file_error": 42 | console.log("Creation of directory " + data + " fail"); 43 | break; 44 | case "upload_file": 45 | case "sftp_upload_file": 46 | case "ftp_upload_file": 47 | console.log("Upload of " + data); 48 | break; 49 | case "upload_file_error": 50 | console.log(data.message); 51 | break; 52 | case "upload_file_success": 53 | case "sftp_upload_file_success": 54 | case "ftp_upload_file_success": 55 | console.log("Upload success"); 56 | break; 57 | case "download_file": 58 | case "sftp_download_file": 59 | case "ftp_download_file": 60 | console.log("Download of " + data); 61 | break; 62 | case "download_file_error": 63 | case "sftp_download_file_error": 64 | case "ftp_download_file_error": 65 | console.log(data.message); 66 | break; 67 | case "download_file_success": 68 | case "sftp_download_file_success": 69 | case "ftp_download_file_success": 70 | console.log("Download success"); 71 | break; 72 | default: 73 | break; 74 | } 75 | }; 76 | 77 | module.exports = MessageObserver; 78 | -------------------------------------------------------------------------------- /lib/observers/MessageObserver.js: -------------------------------------------------------------------------------- 1 | function MessageObserver() { 2 | } 3 | 4 | var createMessage = function(message, classes) { 5 | var atomModule = require('atom-space-pen-views'); 6 | var $ = atomModule.$; 7 | 8 | var workspace = $('.workspace'); 9 | var sftpMessages = workspace.find('.sftp-messages ul'); 10 | if (sftpMessages.length === 0) { 11 | $('.workspace').append('
'); 12 | sftpMessages = workspace.find('.sftp-messages ul'); 13 | } 14 | 15 | var id = 'sftp-message-' + sftpMessages.length; 16 | sftpMessages.append('
  • ' + message + '
  • '); 17 | 18 | setTimeout(function() { 19 | var message = $('#' + id); 20 | var messages = $('.sftp-messages ul').children('.message'); 21 | message.remove(); 22 | if (sftpMessages.find('.message').length === 0) { 23 | sftpMessages.parent().remove(); 24 | } 25 | }, 3000); 26 | }; 27 | 28 | var createErrorMessage = function(message) { 29 | hideLoader(); 30 | createMessage(message, 'error'); 31 | }; 32 | 33 | var createWarningMessage = function(message) { 34 | hideLoader(); 35 | createMessage(message, 'warning'); 36 | }; 37 | 38 | var createSuccessMessage = function(message) { 39 | hideLoader(); 40 | createMessage(message, 'success'); 41 | }; 42 | 43 | var showLoader = function() { 44 | var atomModule = require('atom-space-pen-views'); 45 | var $ = atomModule.$; 46 | 47 | var workspace = $('.workspace'); 48 | var sftpLoader = workspace.find('.sftp-loader'); 49 | if (sftpLoader.length === 0) { 50 | workspace.append( 51 | '
    ' 52 | + '' 53 | + 'Loading...' 54 | + '
    '); 55 | sftpLoader = workspace.find('.sftp-loader'); 56 | } 57 | sftpLoader.show(); 58 | } 59 | 60 | var hideLoader = function() { 61 | var atomModule = require('atom-space-pen-views'); 62 | var $ = atomModule.$; 63 | 64 | var sftpLoader = $('.workspace').find('.sftp-loader'); 65 | if (sftpLoader.length !== 0) { 66 | sftpLoader.hide(); 67 | } 68 | } 69 | 70 | MessageObserver.prototype.notify = function(value, data) { 71 | switch (value) { 72 | case 'begin_transfert': 73 | showLoader(); 74 | break; 75 | case 'project_not_found': 76 | createErrorMessage('Create a project before trying to create a configuration file', 'project_not_found'); 77 | break; 78 | case 'configuration_file_creation_success': 79 | createSuccessMessage('The configuration file was created with success'); 80 | break; 81 | case 'configuration_file_creation_error': 82 | createErrorMessage('A error occured during configuration file creation'); 83 | break; 84 | case 'no_configuration_file_found': 85 | createErrorMessage('The configuration file doesn\'t exist'); 86 | break; 87 | case 'configuration_file_not_readable': 88 | createErrorMessage('The configuration file is not readable'); 89 | break; 90 | case 'configuration_file_not_readable': 91 | createErrorMessage('The configuration file is not readable'); 92 | break; 93 | case 'configuration_file_syntax_error': 94 | createErrorMessage(data.message); 95 | break; 96 | case 'connection_error': 97 | createErrorMessage(data.message); 98 | break; 99 | case 'remote_directory_creation_error': 100 | createErrorMessage(data.message); 101 | break; 102 | case 'remote_directory_not_readable': 103 | createErrorMessage(data.message); 104 | break; 105 | case 'directory_creation_error': 106 | createErrorMessage(data.message); 107 | break; 108 | case 'remote_system_error': 109 | createErrorMessage(data.message); 110 | break; 111 | case 'upload_file_error': 112 | createErrorMessage(data.message); 113 | break; 114 | case 'transfert_file_error': 115 | createErrorMessage(data.message); 116 | break; 117 | case 'upload_file_success': 118 | createSuccessMessage('Upload success'); 119 | break; 120 | case 'download_file_error': 121 | createErrorMessage(data.message); 122 | break; 123 | case 'download_file_success': 124 | createSuccessMessage('Download success'); 125 | break; 126 | default: 127 | break; 128 | } 129 | }; 130 | 131 | module.exports = MessageObserver; 132 | -------------------------------------------------------------------------------- /lib/observers/Observer.js: -------------------------------------------------------------------------------- 1 | function Observer() { 2 | } 3 | 4 | Observer.prototype.notify = function() { 5 | throw "You can't use this class directly"; 6 | }; 7 | 8 | module.exports = Observer; 9 | -------------------------------------------------------------------------------- /lib/queue/Action.js: -------------------------------------------------------------------------------- 1 | function Action(object, call, args) { 2 | this.object = object; 3 | this.call = call; 4 | this.args = args; 5 | } 6 | 7 | Action.prototype.execute = function() { 8 | return this.call.apply(this.object, this.args); 9 | }; 10 | 11 | module.exports = Action; 12 | -------------------------------------------------------------------------------- /lib/queue/Queue.js: -------------------------------------------------------------------------------- 1 | function Queue(maxActive) { 2 | this.queue = []; 3 | this.actives = []; 4 | this.maxActive = maxActive; 5 | this.endCallback = function() {}; 6 | this.errorCallback = function() {}; 7 | 8 | for (var i = 0; i < this.maxActive; i++) { 9 | this.actives[i] = null; 10 | } 11 | } 12 | 13 | Queue.prototype.init = function(endCallback, errorCallback) { 14 | this.endCallback = endCallback; 15 | this.errorCallback = errorCallback; 16 | }; 17 | 18 | Queue.prototype.end = function() { 19 | this.endCallback(); 20 | }; 21 | 22 | Queue.prototype.addAction = function(action) { 23 | this.queue.push(action); 24 | }; 25 | 26 | Queue.prototype.isFinished = function() { 27 | for (var i = 0; i < this.maxActive; i++) { 28 | if (this.actives[i] !== null) { 29 | return false; 30 | } 31 | } 32 | 33 | return (this.queue.length === 0); 34 | } 35 | 36 | Queue.prototype.nextSlotAvailable = function() { 37 | for (var i = 0; i < this.maxActive; i++) { 38 | if (this.actives[i] === null) { 39 | return i; 40 | } 41 | } 42 | 43 | return false; 44 | } 45 | 46 | Queue.prototype.next = function() { 47 | var self = this; 48 | var index = this.nextSlotAvailable(); 49 | 50 | if (index !== false) { 51 | var action = this.queue.shift(action); 52 | 53 | if (action) { 54 | self.actives[index] = action; 55 | action.execute() 56 | .then(function(v) { 57 | self.actives[index] = null; 58 | self.next(); 59 | }) 60 | .catch(function(e) { 61 | self.errorCallback(e); 62 | self.next(); 63 | self.actives[index] = null; 64 | }); 65 | 66 | return; 67 | } 68 | } 69 | 70 | if (this.isFinished()) { 71 | this.end(); 72 | } 73 | }; 74 | 75 | Queue.prototype.execute = function(endCallback, errorCallback) { 76 | this.init(endCallback, errorCallback); 77 | 78 | while (this.nextSlotAvailable() !== false && this.queue.length > 0) { 79 | this.next(); 80 | } 81 | }; 82 | 83 | module.exports = Queue; 84 | -------------------------------------------------------------------------------- /lib/sftp-deployment.js: -------------------------------------------------------------------------------- 1 | var DeploymentManager = require('./DeploymentManager'); 2 | var MessageObserver = require('./observers/MessageObserver'); 3 | // DEV MODE 4 | //var ConsoleObserver = require('./observers/ConsoleObserver'); 5 | var manager = new DeploymentManager(); 6 | manager.registerObserver(new MessageObserver()); 7 | // DEV MODE 8 | //manager.registerObserver(new ConsoleObserver()); 9 | 10 | /** 11 | * Declare command palette to Atom 12 | * @type {Object} 13 | */ 14 | module.exports = { 15 | 'config': { 16 | 'uploadOnSave': { 17 | 'title': 'Upload on save', 18 | 'description': 'When enabled, remote files will be automatically uploaded when saved', 19 | 'type': 'boolean', 20 | 'default': true 21 | }, 22 | 'messagePanel': { 23 | 'title': 'Display message panel', 24 | 'type': 'boolean', 25 | 'default': true 26 | }, 27 | 'sshPrivateKeyPath': { 28 | 'title': 'Path to private SSH key', 29 | 'type': 'string', 30 | 'default': '~/.ssh/id_rsa' 31 | }, 32 | 'messagePanelTimeout': { 33 | 'title': 'Timeout for message panel', 34 | 'type': 'integer', 35 | 'default': 6000 36 | } 37 | }, 38 | 39 | activate: function() { 40 | atom.commands.add( 41 | 'atom-workspace', 42 | 'sftp-deployment:mapToRemote', 43 | manager.generateConfigFile 44 | ); 45 | atom.commands.add( 46 | 'atom-workspace', 47 | 'sftp-deployment:uploadCurrentFile', 48 | manager.uploadCurrentFile 49 | ); 50 | atom.commands.add( 51 | 'atom-workspace', 52 | 'sftp-deployment:downloadCurrentFile', 53 | manager.downloadCurrentFile 54 | ); 55 | atom.commands.add( 56 | 'atom-workspace', 57 | 'sftp-deployment:uploadOpenFiles', 58 | manager.uploadOpenFiles 59 | ); 60 | atom.commands.add( 61 | 'atom-workspace', 62 | 'sftp-deployment:uploadSelection', 63 | manager.uploadSelection 64 | ); 65 | atom.commands.add( 66 | 'atom-workspace', 67 | 'sftp-deployment:downloadSelection', 68 | manager.downloadSelection 69 | ); 70 | atom.commands.add( 71 | 'atom-workspace', 72 | 'core:save', 73 | manager.uploadOnSave 74 | ); 75 | }, 76 | 77 | // handleEvent: function(textEditor) { 78 | // console.log(textEditor); 79 | // textEditor.buffer.onDidSave(function() { 80 | // console.log('test'); 81 | // return manager.uploadOnSave(); 82 | // }); 83 | // } 84 | }; 85 | -------------------------------------------------------------------------------- /menus/sftp-deployment.cson: -------------------------------------------------------------------------------- 1 | 'context-menu': 2 | 'atom-text-editor': [ 3 | { 'type': 'separator' } 4 | { 'label': 'FTP/SFTP', 'submenu': [ 5 | { 'label': 'Upload current file', 'command': 'sftp-deployment:uploadCurrentFile' } 6 | { 'label': 'Download current file', 'command': 'sftp-deployment:downloadCurrentFile' } 7 | ]} 8 | { 'type': 'separator' } 9 | ] 10 | 'atom-pane .tab': [ 11 | { 'type': 'separator' } 12 | { 'label': 'FTP/SFTP', 'submenu': [ 13 | { 'label': 'Upload open files', 'command': 'sftp-deployment:uploadOpenFiles' } 14 | ]} 15 | { 'type': 'separator' } 16 | ] 17 | 'atom-pane .tab.active': [ 18 | { 'type': 'separator' } 19 | { 'label': 'FTP/SFTP', 'submenu': [ 20 | { 'label': 'Upload current file', 'command': 'sftp-deployment:uploadCurrentFile' } 21 | { 'label': 'Download current file', 'command': 'sftp-deployment:downloadCurrentFile' } 22 | ]} 23 | { 'type': 'separator' } 24 | ] 25 | '.tree-view': [ 26 | { 'type': 'separator' } 27 | { 'label': 'FTP/SFTP', 'submenu': [ 28 | { 'label': 'Upload selection', 'command': 'sftp-deployment:uploadSelection' } 29 | { 'label': 'Download selection', 'command': 'sftp-deployment:downloadSelection' } 30 | ]} 31 | { 'type': 'separator' } 32 | ] 33 | 34 | 'menu': [ 35 | { 36 | 'label': 'Packages' 37 | 'submenu': [ 38 | 'label': 'FTP/SFTP' 39 | 'submenu': [ 40 | { 'label': 'Map to remote...', 'command': 'sftp-deployment:mapToRemote' } 41 | { 'type': 'separator' } 42 | { 'label': 'Upload current file', 'command': 'sftp-deployment:uploadCurrentFile' } 43 | { 'label': 'Upload open files', 'command': 'sftp-deployment:uploadOpenFiles' } 44 | { 'type': 'separator' } 45 | { 'label': 'Download current file', 'command': 'sftp-deployment:downloadCurrentFile' } 46 | ] 47 | ] 48 | } 49 | ] 50 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "SFTP-deployment", 3 | "description": "A package which allow you to upload and download files with FTP/SFTP protocol", 4 | "version": "1.0.2", 5 | "author": { 6 | "name": "Ellipsis Team", 7 | "email": "axel.moussard@gmail.com" 8 | }, 9 | "repository": "https://github.com/amoussard/sftp-deployment", 10 | "bugs": { 11 | "url": "https://github.com/amoussard/sftp-deployment/issues" 12 | }, 13 | "main": "./lib/sftp-deployment.js", 14 | "activationCommands": { 15 | "atom-workspace": [ 16 | "sftp-deployment:mapToRemote", 17 | "sftp-deployment:uploadCurrentFile", 18 | "sftp-deployment:uploadOpenFiles", 19 | "sftp-deployment:downloadCurrentFile", 20 | "sftp-deployment:uploadSelection", 21 | "sftp-deployment:downloadSelection", 22 | "core:save" 23 | ] 24 | }, 25 | "engines": { 26 | "atom": ">0.50.0" 27 | }, 28 | "scripts": { 29 | "test": "make test" 30 | }, 31 | "dependencies": { 32 | "ssh2": "^0.4.4", 33 | "ftp": "^0.3.9", 34 | "MD5": "^1.2.1", 35 | "util": "*", 36 | "node-dir": "*", 37 | "bluebird": "^2.9.16", 38 | "expand-home-dir": "^0.0.2", 39 | "atom-space-pen-views": "^2.0.3" 40 | }, 41 | "devDependencies": { 42 | "should": "latest", 43 | "coveralls": "2.3.0" 44 | }, 45 | "keywords": [ 46 | "atom", 47 | "package", 48 | "sftp", 49 | "ftp", 50 | "remote", 51 | "deployment" 52 | ], 53 | "license": "MIT" 54 | } 55 | -------------------------------------------------------------------------------- /spec/FileSpec.js: -------------------------------------------------------------------------------- 1 | var assert = require('assert'); 2 | 3 | var File = require('../lib/filesystem/File'); 4 | 5 | describe('File', function() { 6 | var file = new File('/home/amoussard/test/directory/file.txt'); 7 | 8 | describe('#getPath()', function() { 9 | it('should return the complete path', function() { 10 | assert.equal('/home/amoussard/test/directory/file.txt', file.getPath()); 11 | }); 12 | }); 13 | 14 | describe('#getFilename()', function(){ 15 | it('should return the name of the file', function() { 16 | assert.equal('file.txt', file.getFilename()); 17 | }); 18 | }); 19 | 20 | describe('#getDirectory()', function(){ 21 | it('should return the name of the directory', function() { 22 | assert.equal('/home/amoussard/test/directory', file.getDirectory()); 23 | }); 24 | }); 25 | 26 | }); 27 | -------------------------------------------------------------------------------- /styles/sftp-deployment.less: -------------------------------------------------------------------------------- 1 | // The ui-variables file is provided by base themes provided by Atom. 2 | // 3 | // See https://github.com/atom/atom-dark-ui/blob/master/stylesheets/ui-variables.less 4 | // for a full listing of what's available. 5 | @import "ui-variables"; 6 | 7 | .sftp-deployment{} 8 | 9 | .sftp-messages, 10 | .sftp-loader { 11 | margin: 0; 12 | padding: 0; 13 | background: #333333; 14 | border: 1px solid rgba(0, 0, 0, 0.5); 15 | position: absolute; 16 | right: 0.5em; 17 | top: 3.5em; 18 | z-index: 100; 19 | } 20 | 21 | .sftp-messages ul{ 22 | margin: 0; 23 | padding: 0; 24 | position: relative; 25 | list-style-type: none; 26 | } 27 | 28 | .message{ 29 | color: white; 30 | margin: 0.3em; 31 | padding: 1em; 32 | background: #27292B; 33 | } 34 | 35 | .message.success{ color: #26C26A; } 36 | .message.warning{ color: #FF982D; } 37 | .message.error{ color: #FF2D2D; } 38 | 39 | .sftp-loader { 40 | display: none; 41 | } 42 | --------------------------------------------------------------------------------