diff --git a/.gitignore b/.gitignore index b512c09..4cab0da 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ -node_modules \ No newline at end of file +node_modules +.vscode +ftp-test \ No newline at end of file diff --git a/extension.js b/extension.js index 062fb0b..f36b3df 100644 --- a/extension.js +++ b/extension.js @@ -7,23 +7,23 @@ global.STATUS_TIMEOUT = 3000; // this method is called when your extension is activated // your extension is activated the very first time the command is executed function activate(context) { - + var syncHelper, currentConfig; var ftpConfig = require('./modules/ftp-config'); var getSyncHelper = function() { var oldConfig = currentConfig; currentConfig = ftpConfig.getSyncConfig(); - + if(!syncHelper) syncHelper = require('./modules/sync-helper')(); - else if(ftpConfig.connectionChanged(oldConfig)) + else if(ftpConfig.connectionChanged(oldConfig)) syncHelper.disconnect(); - + syncHelper.useConfig(currentConfig) - + return syncHelper; } - + var initCommand = vscode.commands.registerCommand('extension.ftpsyncinit', require('./modules/init-command')); var syncCommand = vscode.commands.registerCommand('extension.ftpsyncupload', function() { require('./modules/sync-command')(true, getSyncHelper) }); var downloadCommand = vscode.commands.registerCommand('extension.ftpsyncdownload', function() { require('./modules/sync-command')(false, getSyncHelper) }); @@ -31,6 +31,7 @@ function activate(context) { var singleCommand = vscode.commands.registerTextEditorCommand('extension.ftpsyncsingle', function(editor) { require('./modules/sync-single-command')(editor, getSyncHelper) }); var uploadcurrentCommand = vscode.commands.registerCommand("extension.ftpsyncuploadselected", function(fileUrl) { require('./modules/uploadcurrent-command')(fileUrl, getSyncHelper) }); var downloadcurrentCommand = vscode.commands.registerCommand("extension.ftpsyncdownloadselected", function(fileUrl) { require('./modules/downloadcurrent-command')(fileUrl, getSyncHelper) }); + var listcurrentCommand = vscode.commands.registerCommand("extension.ftpsynclistselected", function(fileUrl) { require('./modules/list-command')(fileUrl, getSyncHelper) }); var onSave = require('./modules/on-save'); var currentConfig = getSyncHelper().getConfig(); @@ -53,7 +54,7 @@ function activate(context) { vscode.workspace.onDidSaveTextDocument(function(file) { onSave(file, getSyncHelper); }); - + context.subscriptions.push(initCommand); context.subscriptions.push(syncCommand); context.subscriptions.push(downloadCommand); @@ -61,6 +62,7 @@ function activate(context) { context.subscriptions.push(singleCommand); context.subscriptions.push(uploadcurrentCommand); context.subscriptions.push(downloadcurrentCommand); + context.subscriptions.push(listcurrentCommand); } exports.activate = activate; @@ -68,4 +70,4 @@ exports.activate = activate; function deactivate() { fsw.dispose(); } -exports.deactivate = deactivate; \ No newline at end of file +exports.deactivate = deactivate; diff --git a/modules/downloadcurrent-command.js b/modules/downloadcurrent-command.js index 1b9919f..4419c85 100644 --- a/modules/downloadcurrent-command.js +++ b/modules/downloadcurrent-command.js @@ -4,30 +4,43 @@ var ftpconfig = require("./ftp-config"); var path = require("path"); var isIgnored = require("./is-ignored"); -module.exports = function(fileUrl, getFtpSync) { - if(!vscode.workspace.rootPath) { - vscode.window.showErrorMessage("Ftp-sync: Cannot init ftp-sync without opened folder"); - return; - } - - if(fileUrl.fsPath.indexOf(vscode.workspace.rootPath) < 0) { - vscode.window.showErrorMessage("Ftp-sync: Selected file is not a part of the workspace."); - return; - } - - var config = ftpconfig.getConfig(); - if(isIgnored(fileUrl.fsPath, config.allow, config.ignore)) { - vscode.window.showErrorMessage("Ftp-sync: Selected file is ignored."); - return; - } - - var fileName = path.basename(fileUrl.fsPath); - var downloadStatus = vscode.window.setStatusBarMessage("Ftp-sync: Downloading " + fileName + " from FTP server...", STATUS_TIMEOUT); - getFtpSync().downloadFile(fileUrl.fsPath, vscode.workspace.rootPath, function(err) { - downloadStatus.dispose(); - if(err) - vscode.window.showErrorMessage("Ftp-sync: Downloading " + fileName + " failed: " + err); - else - vscode.window.setStatusBarMessage("Ftp-sync: " + fileName + " downloaded successfully!", STATUS_TIMEOUT); - }) -} +module.exports = function (fileUrl, getFtpSync) { + + var filePath = fileUrl ? fileUrl.fsPath : undefined; + + //We aren't getting a file, trying to take the current one + if (!filePath) { + filePath = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.document.fileName : undefined; + } + + if (!filePath) { + vscode.window.showErrorMessage("Ftp-sync: No file selected"); + return; + } + + if (!vscode.workspace.rootPath) { + vscode.window.showErrorMessage("Ftp-sync: Cannot init ftp-sync without opened folder"); + return; + } + + if (filePath.indexOf(vscode.workspace.rootPath) < 0) { + vscode.window.showErrorMessage("Ftp-sync: Selected file is not a part of the workspace."); + return; + } + + var config = ftpconfig.getConfig(); + if (isIgnored(filePath, config.allow, config.ignore)) { + vscode.window.showErrorMessage("Ftp-sync: Selected file is ignored."); + return; + } + + var fileName = path.basename(filePath); + var downloadStatus = vscode.window.setStatusBarMessage("Ftp-sync: Downloading " + fileName + " from FTP server...", STATUS_TIMEOUT); + getFtpSync().downloadFile(filePath, vscode.workspace.rootPath, function (err) { + downloadStatus.dispose(); + if (err) + vscode.window.showErrorMessage("Ftp-sync: Downloading " + fileName + " failed: " + err); + else + vscode.window.setStatusBarMessage("Ftp-sync: " + fileName + " downloaded successfully!", STATUS_TIMEOUT); + }) +} \ No newline at end of file diff --git a/modules/list-command.js b/modules/list-command.js new file mode 100644 index 0000000..6bdee5b --- /dev/null +++ b/modules/list-command.js @@ -0,0 +1,146 @@ +/* global STATUS_TIMEOUT */ +const vscode = require('vscode'); +const ftpconfig = require('./ftp-config'); +const path = require('path'); +const isIgnored = require('./is-ignored'); +const output = require('./output'); +const downloadFn = require('./downloadcurrent-command'); +const uploadFn = require('./uploadcurrent-command'); + +module.exports = function(fileUrl, getFtpSync) { + var filePath = fileUrl ? fileUrl.fsPath : undefined; + + //We aren't getting a file, trying to take the current one + if(!filePath) { + filePath = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.document.fileName : undefined; + } + + if(!filePath) { + vscode.window.showErrorMessage("Ftp-sync: No file selected"); + return; + } + + if (!vscode.workspace.rootPath) { + vscode.window.showErrorMessage('Ftp-sync: Cannot init ftp-sync without opened folder'); + return; + } + + if (filePath.indexOf(vscode.workspace.rootPath) < 0) { + vscode.window.showErrorMessage('Ftp-sync: Selected file is not a part of the workspace.'); + return; + } + + var config = ftpconfig.getConfig(); + if (isIgnored(filePath, config.allow, config.ignore)) { + vscode.window.showErrorMessage('Ftp-sync: Selected file is ignored.'); + return; + } + + let remotePath = getFatherPath(filePath.replace(vscode.workspace.rootPath, config.remotePath)); + function listAllFiles(filesRemotePath) { + getFtpSync().ListRemoteFilesByPath(filesRemotePath, function(err, files) { + if (err) { + // console.error('err:', err); + vscode.window.showErrorMessage('Ftp-sync: Listing failed: ' + err); + } else { + vscode.window.setStatusBarMessage('Ftp-sync: Listing successfully!', STATUS_TIMEOUT); + // console.log('files:', files); + showFiles(files, filesRemotePath); + } + }); + } + function deleteFn(filePath) { + getFtpSync().deleteRemoteFile(filePath).then(result => { + vscode.window.setStatusBarMessage('Ftp-sync: Delete successfully!', STATUS_TIMEOUT); + }).catch(err => { + vscode.window.showErrorMessage('Ftp-sync: Delete failed: ' + err); + }) + } + listAllFiles(remotePath); + // show remotePath files + function showFiles(files, filesRemotePath) { + const pickOptions = files.map(file => ({label: getLabel(file), description: file.path, file, isDir: file.isDir})); + const pickResult = vscode.window.showQuickPick([ + { + label: '../', + description: '. UP a folder', + backPath: getFatherPath(filesRemotePath) + } + ].concat(pickOptions), {placeHolder: 'Select a folder or file'}); + + pickResult.then(function(result) { + if (!result) { + return; + } + // console.log('sel file:', result); + if (result.backPath) { + listAllFiles(result.backPath); + } else if (result.isDir) { + listAllFiles(result.file.path); + } else { + showFileActions(result.file); + } + }); + } + // show Actions + function showFileActions(file) { + const pickOptions = [ + { + label: '../', + description: '. UP a folder', + backPath: getFatherPath(file.path) + }, { + label: 'DownLoad', + description: 'DownLoad this file', + file, + action: 'download' + }, { + label: 'Upload', + description: 'Upload this file', + file, + action: 'upload' + }, { + label: 'Delete', + description: 'Delete this file', + file, + action: 'delete' + } + ]; + const pickResult = vscode.window.showQuickPick(pickOptions); + + pickResult.then(function(result) { + // console.log('sel Actions:', result); + if (!result) { + return; + } + if (result.backPath) { + listAllFiles(result.backPath); + } else if (result.action === 'download') { + downloadFn(getLocalPath(result.file.path), getFtpSync); + } else if (result.action === 'upload') { + uploadFn(getLocalPath(result.file.path), getFtpSync); + } else if (result.action === 'delete') { + deleteFn(result.file.path); + } + }); + } +}; +function getLabel(file) { + const name = file.name && file.name.indexOf('/') === 0 + ? file.name.slice(1) + : file.name; + return file.isDir + ? `${name}/` + : name + +} +function getLocalPath(fileRemotePath) { + return { + fsPath: fileRemotePath.replace(ftpconfig.getConfig().remotePath, vscode.workspace.rootPath) + }; +} +function getFatherPath(son) { + let father = son.split('/'); + father = father.slice(0, father.length - 1).join('/'); + return father; +} diff --git a/modules/scp-wrapper.js b/modules/scp-wrapper.js new file mode 100644 index 0000000..ef1d5b4 --- /dev/null +++ b/modules/scp-wrapper.js @@ -0,0 +1,109 @@ +module.exports = function () { + var self = this; + + var Mode = require('stat-mode'); + var Client = require('scp2'); + var path = require("path"); + var client = Client; + var low = require('scp2').Client; + var config = {}; + var sftp; + self.connect = function (ftpConfig) { + + try { + var privateKey = ftpConfig.privateKeyPath ? require('fs').readFileSync(ftpConfig.privateKeyPath) : undefined; + } catch (err) { + process.nextTick(function () { + onErrorHandler(err); + }); + return; + } + + config = { + host: ftpConfig.host, + port: ftpConfig.port, + username: ftpConfig.user, + password: ftpConfig.password, + privateKey: privateKey, + passphrase: ftpConfig.passphrase, + agent: ftpConfig.agent + }; + + client.defaults(config); + + setTimeout(function() { + client.emit('ready'); + }); + } + + self.onready = function (callback) { + client.once('ready', callback); + } + + var onErrorHandler; + self.onerror = function (callback) { + onErrorHandler = callback; + client.once('error', callback); + } + + self.pasv = function (callback) { + callback(); + } + + self.goSftp = function (callback) { + client.sftp(function (err, sftpClient) { + if (!err && !sftp) { + sftp = sftpClient; + } + callback(err); + }) + } + + self.end = function () { + client.end(); + } + + self.onclose = function (callback) { + client.once('close', callback); + } + + self.list = function (remote, callback) { + client.sftp(function (err, sftpClient) { + sftpClient.readdir(remote, function (err, result) { + if (!err) result = result.map(f => { + return { + name: f.filename, + type: new Mode(f.attrs).isDirectory() ? "d" : "f", //TODO: determine if it's a file or not + size: f.attrs.size + } + }); + callback(err, result); + }); + }); + } + + self.get = function (remote, local, callback) { + client.scp(Object.assign({}, config, { path: remote }), local, callback); + } + + self.put = function (local, remote, callback) { + client.scp(local, Object.assign({}, config, { path: remote }), callback) + } + + self.mkdir = function (remote, callback) { + client.mkdir(remote, callback); + } + + self.delete = function (remote, callback) { + client.sftp(function (err, sftpClient) { + sftpClient.unlink(remote, callback) + }); + } + + self.rmdir = function (remote, callback) { + client.sftp(function (err, sftpClient) { + sftpClient.rmdir(remote, callback) + }); + } + +} \ No newline at end of file diff --git a/modules/sync-helper.js b/modules/sync-helper.js index 5cb4a93..083d291 100644 --- a/modules/sync-helper.js +++ b/modules/sync-helper.js @@ -8,6 +8,7 @@ var isIgnored = require('./is-ignored'); var output = require("./output"); var FtpWrapper = require("./ftp-wrapper"); var SftpWrapper = require("./sftp-wrapper"); +var ScpWrapper = require("./scp-wrapper"); var vscode = require("vscode"); var ftp; @@ -16,17 +17,18 @@ var ftp; var openListRemoteFilesRequsts = 0; // get timestamp -var getCurrentTime = function() { +var getCurrentTime = function () { var currentdate = new Date(); - return currentdate.getDate() + "/" + (currentdate.getMonth()+1) + "/" + currentdate.getFullYear() + " @ " + currentdate.getHours() + ":" + currentdate.getMinutes() + ":" + currentdate.getSeconds(); + return currentdate.getDate() + "/" + ( + currentdate.getMonth() + 1) + "/" + currentdate.getFullYear() + " @ " + currentdate.getHours() + ":" + currentdate.getMinutes() + ":" + currentdate.getSeconds(); } //add options -var listRemoteFiles = function(remotePath, callback, originalRemotePath, options) { - output(getCurrentTime() + " > [ftp-sync] listRemoteFiles: " + remotePath); +var listRemoteFiles = function (remotePath, callback, originalRemotePath, options) { + output(getCurrentTime() + " > [ftp-sync] listRemoteFiles: " + remotePath); remotePath = upath.toUnix(remotePath); - if(!originalRemotePath) { + if (!originalRemotePath) { originalRemotePath = remotePath; // Overwrite original callback to execute only if all open request are finish @@ -42,13 +44,13 @@ var listRemoteFiles = function(remotePath, callback, originalRemotePath, options // Add a new open request openListRemoteFilesRequsts += 1; - ftp.list(remotePath, function(err, remoteFiles) { + ftp.list(remotePath, function (err, remoteFiles) { // The request is finish so remove it openListRemoteFilesRequsts -= 1; - if(err) { - if(err.code == 450) + if (err) { + if (err.code == 450) callback(null, []); else callback(err); @@ -58,92 +60,193 @@ var listRemoteFiles = function(remotePath, callback, originalRemotePath, options var result = []; var subdirs = []; - if(remoteFiles.length == 0) + if (remoteFiles.length == 0) callback(null, result); - remoteFiles.forEach(function(fileInfo) { + remoteFiles.forEach(function (fileInfo) { //when listing remoteFiles by onPrepareRemoteProgress, ignore remoteFiles - if (isIgnored(path.join(remotePath, fileInfo.name), ftpConfig.allow, ftpConfig.ignore)) return; + if (isIgnored(path.join(remotePath, fileInfo.name), ftpConfig.allow, ftpConfig.ignore)) + return; - if(fileInfo.name == "." || fileInfo.name == "..") return; + if (fileInfo.name == "." || fileInfo.name == "..") + return; var remoteItemPath = upath.toUnix(path.join(remotePath, fileInfo.name)); - if(fileInfo.type != 'd') + if (fileInfo.type != 'd') result.push({ name: remoteItemPath, size: fileInfo.size, isDir: false }) - else if(fileInfo.type == 'd') { + else if (fileInfo.type == 'd') { subdirs.push(fileInfo); - result.push({ name: remoteItemPath, isDir: true }); + result.push({ + name: remoteItemPath, + isDir: true + }); } }); - var finish = function() { - result.forEach(function(item) { - if(_.startsWith(item.name, originalRemotePath)) - item.name = item.name.replace(originalRemotePath, ""); - if(item.name[0] == "/") item.name = item.name.substr(1); - if(onPrepareRemoteProgress) onPrepareRemoteProgress(item.name); + var finish = function () { + result.forEach(function (item) { + if (_.startsWith(item.name, originalRemotePath)) + item.name = item.name.replace(originalRemotePath, ""); + if (item.name[0] == "/") + item.name = item.name.substr(1); + if (onPrepareRemoteProgress) + onPrepareRemoteProgress(item.name); + }); + result = _.sortBy(result, function (item) { + return item.name }); - result = _.sortBy(result, function(item) { return item.name }); callback(null, result); } - var listNextSubdir = function() { + var listNextSubdir = function () { var subdir = subdirs.shift(); var subPath = upath.toUnix(path.join(remotePath, subdir.name)); - listRemoteFiles(subPath, function(err, subResult) { - if(err) { - callback(err); + listRemoteFiles(subPath, function (err, subResult) { + if (err) { + callback(err); return; } result = _.union(result, subResult) - if(subdirs.length == 0) + if (subdirs.length == 0) finish(); else listNextSubdir(); }, originalRemotePath, options); } - if(subdirs.length == 0) + if (subdirs.length == 0) finish(); else listNextSubdir(); }); } +// list remote files, deep = 1 +const listOneDeepRemoteFiles = function (remotePath, callback) { + output(getCurrentTime() + ' > [ftp-sync] listRemoteFiles: ' + remotePath); + remotePath = upath.toUnix(remotePath); + ftp.list(remotePath, function (err, remoteFiles) { + if (err) { + if (err.code == 450) + callback(null, []); + else + callback(err); + return; + } + let result = []; + + if (remoteFiles.length == 0) { + callback(null, result); + return + } + + remoteFiles.forEach(function (fileInfo) { + // when listing remoteFiles by onPrepareRemoteProgress, ignore remoteFiles + if (isIgnored(path.join(remotePath, fileInfo.name), ftpConfig.allow, ftpConfig.ignore)) + return; + + if (fileInfo.name == '.' || fileInfo.name == '..') + return; + var remoteItemPath = upath.toUnix(path.join(remotePath, fileInfo.name)); + if (fileInfo.type != 'd') + result.push({ + name: remoteItemPath, + size: fileInfo.size, + isDir: false + }); + else if (fileInfo.type == 'd') { + result.push({ + name: remoteItemPath, + isDir: true + }); + } + }); + const finish = function () { + result.forEach(function (item) { + if (_.startsWith(item.name, remotePath)) { + item.path = item.name; + item.name = item.name.replace(remotePath, ''); + } + }); + result = _.sortBy(result, function (item) { + return item.name; + }); + callback(null, result); + }; + finish(); + }); +}; +// the entry of list request +const ListRemoteFilesByPath = function (remotePath, callback) { + connect(function (err) { + if (err) { + callback(err); + return; + } + listOneDeepRemoteFiles(remotePath, callback); + }); +}; +const deleteRemoteFile = function (remoteFilePath) { + return new Promise((resolve, reject) => { + connect(function (err) { + if (err) { + reject(err); + return; + } + output(getCurrentTime() + " > [ftp-sync] deletRemoteFile: " + remoteFilePath); + ftp.delete(remoteFilePath, function (err) { + if (err) + reject(err); + else + resolve({ + success: true, + path: remoteFilePath + }); + }); + }); + }) +}; //add options -var listLocalFiles = function(localPath, callback, options) { - output(getCurrentTime() + " > [ftp-sync] listLocalFiles:" + localPath); +var listLocalFiles = function (localPath, callback, options) { + output(getCurrentTime() + " > [ftp-sync] listLocalFiles:" + localPath); var files = []; - fswalk.walk(localPath, function(basedir, filename, stat, next) { + fswalk.walk(localPath, function (basedir, filename, stat, next) { var filePath = path.join(basedir, filename); //when listing localFiles by onPrepareLocalProgress, ignore localfile - if (isIgnored(filePath, ftpConfig.allow, ftpConfig.ignore)) return next(); + if (isIgnored(filePath, ftpConfig.allow, ftpConfig.ignore)) + return next(); filePath = filePath.replace(localPath, ""); filePath = upath.toUnix(filePath); - if(filePath[0] == "/") filePath = filePath.substr(1); + if (filePath[0] == "/") + filePath = filePath.substr(1); - if(onPrepareLocalProgress) onPrepareLocalProgress(filePath); + if (onPrepareLocalProgress) + onPrepareLocalProgress(filePath); files.push({ name: filePath, size: stat.size, isDir: stat.isDirectory() }); next(); - }, function(err) { + }, function (err) { callback(err, files); }); } -var prepareSyncObject = function(remoteFiles, localFiles, options, callback) { - var from = options.upload ? localFiles : remoteFiles; - var to = options.upload ? remoteFiles : localFiles; +var prepareSyncObject = function (remoteFiles, localFiles, options, callback) { + var from = options.upload ? + localFiles : + remoteFiles; + var to = options.upload ? + remoteFiles : + localFiles; - var skipIgnores = function(file) { + var skipIgnores = function (file) { return isIgnored(path.join(options.remotePath, file.name), ftpConfig.allow, ftpConfig.ignore); } @@ -156,40 +259,48 @@ var prepareSyncObject = function(remoteFiles, localFiles, options, callback) { var filesToRemove = []; var dirsToRemove = []; - if(options.mode == "force") - from.forEach(function(fromFile) { - var toEquivalent = to.find(function(toFile) { return toFile.name == fromFile.name }); - if(toEquivalent && !fromFile.isDir) + if (options.mode == "force") + from.forEach(function (fromFile) { + var toEquivalent = to.find(function (toFile) { + return toFile.name == fromFile.name + }); + if (toEquivalent && !fromFile.isDir) filesToUpdate.push(fromFile.name); - if(!toEquivalent) { - if(fromFile.isDir) + if (!toEquivalent) { + if (fromFile.isDir) dirsToAdd.push(fromFile.name) else filesToAdd.push(fromFile.name); } }); else - from.forEach(function(fromFile) { - var toEquivalent = to.find(function(toFile) { return toFile.name == fromFile.name }); - if(!toEquivalent && !fromFile.isDir) filesToAdd.push(fromFile.name); - if(!toEquivalent && fromFile.isDir) dirsToAdd.push(fromFile.name); - if(toEquivalent) toEquivalent.wasOnFrom = true; - if(toEquivalent && toEquivalent.size != fromFile.size && !fromFile.isDir) + from.forEach(function (fromFile) { + var toEquivalent = to.find(function (toFile) { + return toFile.name == fromFile.name + }); + if (!toEquivalent && !fromFile.isDir) + filesToAdd.push(fromFile.name); + if (!toEquivalent && fromFile.isDir) + dirsToAdd.push(fromFile.name); + if (toEquivalent) + toEquivalent.wasOnFrom = true; + if (toEquivalent && toEquivalent.size != fromFile.size && !fromFile.isDir) filesToUpdate.push(fromFile.name); }); - if(options.mode == "full") - to.filter(function(toFile) { return !toFile.wasOnFrom }) - .forEach(function(toFile) { - if(toFile.isDir) - dirsToRemove.push(toFile.name) - else - filesToRemove.push(toFile.name); - }); + if (options.mode == "full") + to.filter(function (toFile) { + return !toFile.wasOnFrom + }).forEach(function (toFile) { + if (toFile.isDir) + dirsToRemove.push(toFile.name) + else + filesToRemove.push(toFile.name); + }); callback(null, { _readMe: "Review list of sync operations, then use Ftp-sync: Commit command to accept changes", - _warning: "This file should not be saved, reopened review file won't work!", + _warning: "This file should not be saved, reopened review file won't work!", filesToUpdate: filesToUpdate, filesToAdd: filesToAdd, dirsToAdd: dirsToAdd, @@ -198,244 +309,274 @@ var prepareSyncObject = function(remoteFiles, localFiles, options, callback) { }); } -var totalOperations = function(sync) { - return sync.filesToUpdate.length - + sync.filesToAdd.length - + sync.dirsToAdd.length - + sync.filesToRemove.length - + sync.dirsToRemove.length +var totalOperations = function (sync) { + return sync.filesToUpdate.length + sync.filesToAdd.length + sync.dirsToAdd.length + sync.filesToRemove.length + sync.dirsToRemove.length } -var onPrepareRemoteProgress, onPrepareLocalProgress, onSyncProgress; +var onPrepareRemoteProgress, + onPrepareLocalProgress, + onSyncProgress; var connected = false; - -var connect = function(callback) { - //output(getCurrentTime() + " > [sync-helper] connect"); - if(connected == false) - { +var connect = function (callback) { + output(getCurrentTime() + " > [sync-helper] connect"); + if (connected == false) { // If password and private key path are required but missing from the // config file, prompt the user for a password and then connect - if((ftpConfig.protocol == "sftp" && !ftpConfig.password && !ftpConfig.privateKeyPath) - || !ftpConfig.password) { + if (((ftpConfig.protocol == "sftp" || ftpConfig.protocol == "scp") && !ftpConfig.password && !ftpConfig.privateKeyPath) || + !ftpConfig.password) { vscode.window.showInputBox({ prompt: '[ftp-sync] Password for "' + ftpConfig.host + '"', password: true - }).then(function(password) { - ftp.connect(Object.assign({}, ftpConfig, { password: password })); + }).then(function (password) { + ftp.connect(Object.assign({}, ftpConfig, { + password: password + })); }); - } - else { // Otherwise just connect + } else { // Otherwise just connect ftp.connect(ftpConfig); } - ftp.onready(function() { - connected = true; - if(!ftpConfig.passive && ftpConfig.protocol != "sftp") - callback(); - else if(ftpConfig.protocol == "sftp") - ftp.goSftp(callback); - else if(ftpConfig.passive) - ftp.pasv(callback); - }); + ftp.onready(function () { + connected = true; + if (!ftpConfig.passive && ftpConfig.protocol != "sftp") + callback(); + else if (ftpConfig.protocol == "sftp") + ftp.goSftp(callback); + else if (ftpConfig.passive) + ftp.pasv(callback); + }); ftp.onerror(callback); - ftp.onclose(function(err) { - output(getCurrentTime() + " > [ftp-sync] connection closed"); - connected = false; - }); - } - else + ftp.onclose(function (err) { + output(getCurrentTime() + " > [ftp-sync] connection closed"); + connected = false; + }); + } else callback(); } - -var prepareSync = function(options, callback) { - connect(function(err) { - if(err) callback(err); - else listRemoteFiles(options.remotePath, function(err, remoteFiles) { - if(err) callback(err); - else listLocalFiles(options.localPath, function(err, localFiles) { - if(err) callback(err); - else prepareSyncObject(remoteFiles, localFiles, options, callback); - }, options) - }, null, options); +var prepareSync = function (options, callback) { + connect(function (err) { + if (err) + callback(err); + else + listRemoteFiles(options.remotePath, function (err, remoteFiles) { + if (err) + callback(err); + else + listLocalFiles(options.localPath, function (err, localFiles) { + if (err) + callback(err); + else + prepareSyncObject(remoteFiles, localFiles, options, callback); + }, options) + }, null, options); }); } - -var executeSyncLocal = function(sync, options, callback) { - if(onSyncProgress != null) +var executeSyncLocal = function (sync, options, callback) { + if (onSyncProgress != null) onSyncProgress(sync.startTotal - totalOperations(sync), sync.startTotal); - var replaceFile = function(fileToReplace) { + var replaceFile = function (fileToReplace) { var local = path.join(options.localPath, fileToReplace); var remote = upath.toUnix(path.join(options.remotePath, fileToReplace)); output(getCurrentTime() + " > [ftp-sync] syncLocal replace: " + remote); - ftp.get(remote, local, function(err) { - if(err) callback(err); - else executeSyncLocal(sync, options, callback); + ftp.get(remote, local, function (err) { + if (err) + callback(err); + else + executeSyncLocal(sync, options, callback); }); } - if(sync.dirsToAdd.length > 0) { + if (sync.dirsToAdd.length > 0) { var dirToAdd = sync.dirsToAdd.pop(); var localPath = path.join(options.localPath, dirToAdd); output(getCurrentTime() + " > [ftp-sync] syncLocal createDir: " + dirToAdd); - mkdirp(localPath, function(err) { - if(err) callback(err); else executeSyncLocal(sync, options, callback); + mkdirp(localPath, function (err) { + if (err) + callback(err); + else + executeSyncLocal(sync, options, callback); }); - } else if(sync.filesToAdd.length > 0) { + } else if (sync.filesToAdd.length > 0) { var fileToAdd = sync.filesToAdd.pop(); replaceFile(fileToAdd); - } else if(sync.filesToUpdate.length > 0) { + } else if (sync.filesToUpdate.length > 0) { var fileToUpdate = sync.filesToUpdate.pop(); replaceFile(fileToUpdate); - } else if(sync.filesToRemove.length > 0) { + } else if (sync.filesToRemove.length > 0) { var fileToRemove = sync.filesToRemove.pop(); var localPath = path.join(options.localPath, fileToRemove); output(getCurrentTime() + " > [ftp-sync] syncLocal removeFile: " + fileToRemove); - fs.unlink(localPath, function(err) { - if(err) callback(err); else executeSyncLocal(sync, options, callback); + fs.unlink(localPath, function (err) { + if (err) + callback(err); + else + executeSyncLocal(sync, options, callback); }); - } else if(sync.dirsToRemove.length > 0) { + } else if (sync.dirsToRemove.length > 0) { var dirToRemove = sync.dirsToRemove.pop(); var localPath = path.join(options.localPath, dirToRemove); output(getCurrentTime() + " > [ftp-sync] syncLocal removeDir: " + dirToAdd); - fs.rmdir(localPath, function(err) { - if(err) callback(err); else executeSyncLocal(sync, options, callback); + fs.rmdir(localPath, function (err) { + if (err) + callback(err); + else + executeSyncLocal(sync, options, callback); }); } else { callback(); } } -var executeSyncRemote = function(sync, options, callback) { - if(onSyncProgress != null) +var executeSyncRemote = function (sync, options, callback) { + if (onSyncProgress != null) onSyncProgress(sync.startTotal - totalOperations(sync), sync.startTotal); - var replaceFile = function(fileToReplace) { + var replaceFile = function (fileToReplace) { var local = path.join(options.localPath, fileToReplace); var remote = upath.toUnix(path.join(options.remotePath, fileToReplace)); output(getCurrentTime() + " > [ftp-sync] syncRemote replace: " + local); - ftp.put(local, remote, function(err) { - if(err) callback(err); else executeSyncRemote(sync, options, callback); + ftp.put(local, remote, function (err) { + if (err) + callback(err); + else + executeSyncRemote(sync, options, callback); }); } - if(sync.dirsToAdd.length > 0) { + if (sync.dirsToAdd.length > 0) { var dirToAdd = sync.dirsToAdd.shift(); var remotePath = upath.toUnix(path.join(options.remotePath, dirToAdd)); output(getCurrentTime() + " > [ftp-sync] syncRemote createDir: " + dirToAdd); - ftp.mkdir(remotePath, function(err) { - if(err) callback(err); else executeSyncRemote(sync, options, callback); + ftp.mkdir(remotePath, function (err) { + if (err) + callback(err); + else + executeSyncRemote(sync, options, callback); }) - } else if(sync.filesToAdd.length > 0) { + } else if (sync.filesToAdd.length > 0) { var fileToAdd = sync.filesToAdd.shift(); replaceFile(fileToAdd); - } else if(sync.filesToUpdate.length > 0) { + } else if (sync.filesToUpdate.length > 0) { var fileToUpdate = sync.filesToUpdate.shift(); replaceFile(fileToUpdate); - } else if(sync.filesToRemove.length > 0) { + } else if (sync.filesToRemove.length > 0) { var fileToRemove = sync.filesToRemove.pop(); var remotePath = upath.toUnix(path.join(options.remotePath, fileToRemove)); output(getCurrentTime() + " > [ftp-sync] syncRemote removeFile: " + fileToRemove); - ftp.delete(remotePath, function(err) { - if(err) callback(err); else executeSyncRemote(sync, options, callback); + ftp.delete(remotePath, function (err) { + if (err) + callback(err); + else + executeSyncRemote(sync, options, callback); }); - } else if(sync.dirsToRemove.length > 0) { + } else if (sync.dirsToRemove.length > 0) { var dirToRemove = sync.dirsToRemove.pop(); var remotePath = upath.toUnix(path.join(options.remotePath, dirToRemove)); output(getCurrentTime() + " > [ftp-sync] syncRemote removeDir: " + dirToRemove); - ftp.rmdir(remotePath, function(err) { - if(err) callback(err); else executeSyncRemote(sync, options, callback); + ftp.rmdir(remotePath, function (err) { + if (err) + callback(err); + else + executeSyncRemote(sync, options, callback); }); } else { callback(); } } -var ensureDirExists = function(remoteDir, callback) { - ftp.list(path.posix.join(remoteDir, ".."), function(err, list) { - if(err) - ensureDirExists(path.posix.join(remoteDir, ".."), function() { - ensureDirExists(remoteDir, callback); - }); - else if(_.any(list, f => f.name == path.basename(remoteDir))) - callback(); - else - ftp.mkdir(remoteDir, function(err) { - if(err) callback(err) - else callback(); - }) - }); +var ensureDirExists = function (remoteDir, callback) { + ftp.list(path.posix.join(remoteDir, ".."), function (err, list) { + if (err) { + ensureDirExists(path.posix.join(remoteDir, ".."), function () { + ensureDirExists(remoteDir, callback); + }); + } else if (_.some(list, f => f.name == path.basename(remoteDir))) { + callback(); + } else { + ftp.mkdir(remoteDir, function (err) { + if (err) callback(err) + else callback(); + }); + } + }); } -var uploadFile = function(localPath, rootPath, callback) { - output(getCurrentTime() + " > [sync-helper] uploading: " + path.parse(localPath).base); +var uploadFile = function (localPath, rootPath, callback) { + output(getCurrentTime() + " > [sync-helper] uploading: " + path.parse(localPath).base); var remotePath = upath.toUnix(path.join(ftpConfig.remote, localPath.replace(rootPath, ''))); var remoteDir = upath.toUnix(path.dirname(remotePath)); - connect(function(err) { - if(err) { + connect(function (err) { + if (err) { callback(err); return; - } - var putFile = function() { - ftp.put(localPath, remotePath, function(err) { - callback(err); + } + var putFile = function () { + ftp.put(localPath, remotePath, function (err) { + callback(err); + }) + } + if (remoteDir != ".") + ensureDirExists(remoteDir, function (err) { + if (err) + callback(err); + else + putFile(); }) - } - if(remoteDir != ".") - ensureDirExists(remoteDir, function(err) { - if(err) callback(err); - else putFile(); - }) - else - putFile(); + else + putFile(); }) } -var downloadFile = function(localPath, rootPath, callback) { - output(getCurrentTime() + " > [sync-helper] downloading: " + path.parse(localPath).base); - var remotePath = upath.toUnix(path.join(ftpConfig.remote, localPath.replace(rootPath, ''))); - var remoteDir = upath.toUnix(path.dirname(remotePath)); - connect(function(err) { - if(err) callback(err); - var getFile = function() { - ftp.get(remotePath, localPath, function(err) { - callback(err); - }) - } - if(remoteDir != ".") - ensureDirExists(remoteDir, function(err) { - if(err) callback(err); - else getFile(); - }) - else - getFile(); - }) +var downloadFile = function (localPath, rootPath, callback) { + output(getCurrentTime() + " > [sync-helper] downloading: " + path.parse(localPath).base); + var remotePath = upath.toUnix(path.join(ftpConfig.remote, localPath.replace(rootPath, ''))); + var remoteDir = upath.toUnix(path.dirname(remotePath)); + connect(function (err) { + if (err) + callback(err); + var getFile = function () { + ftp.get(remotePath, localPath, function (err) { + callback(err); + }) + } + if (remoteDir != ".") + ensureDirExists(remoteDir, function (err) { + if (err) + callback(err); + else + getFile(); + }) + else + getFile(); + }) } -var executeSync = function(sync, options, callback) { - output(getCurrentTime() + " > [ftp-sync] sync starting"); +var executeSync = function (sync, options, callback) { + output(getCurrentTime() + " > [ftp-sync] sync starting"); sync.startTotal = totalOperations(sync); - connect(function(err) { - if(err) callback(err); - else if(options.upload) + connect(function (err) { + if (err) + callback(err); + else if (options.upload) executeSyncRemote(sync, options, callback); else executeSyncLocal(sync, options, callback); @@ -444,33 +585,35 @@ var executeSync = function(sync, options, callback) { var ftpConfig; var helper = { - useConfig: function(config) { - if(!ftpConfig || ftpConfig.protocol != config.protocol) - ftp = config.protocol == "sftp" ? new SftpWrapper() : new FtpWrapper(); + useConfig: function (config) { + if (!ftpConfig || ftpConfig.protocol != config.protocol) + ftp = config.protocol == "sftp" ? new SftpWrapper() : config.protocol == "scp" ? new ScpWrapper() : new FtpWrapper(); ftpConfig = config; }, - getConfig: function() { + getConfig: function () { return ftpConfig; }, prepareSync: prepareSync, + ListRemoteFilesByPath: ListRemoteFilesByPath, + deleteRemoteFile: deleteRemoteFile, executeSync: executeSync, totalOperations: totalOperations, uploadFile: uploadFile, downloadFile: downloadFile, - disconnect: function() { + disconnect: function () { ftp.end(); }, - onPrepareRemoteProgress: function(callback) { + onPrepareRemoteProgress: function (callback) { onPrepareRemoteProgress = callback; }, - onPrepareLocalProgress: function(callback) { + onPrepareLocalProgress: function (callback) { onPrepareLocalProgress = callback; }, - onSyncProgress: function(callback) { + onSyncProgress: function (callback) { onSyncProgress = callback; } } -module.exports = function(config) { +module.exports = function (config) { return helper; -} +} \ No newline at end of file diff --git a/modules/uploadcurrent-command.js b/modules/uploadcurrent-command.js index 33d8e52..f54f450 100644 --- a/modules/uploadcurrent-command.js +++ b/modules/uploadcurrent-command.js @@ -5,26 +5,38 @@ var path = require("path"); var isIgnored = require("./is-ignored"); module.exports = function(fileUrl, getFtpSync) { + var filePath = fileUrl ? fileUrl.fsPath : undefined; + + //We aren't getting a file, trying to take the current one + if(!filePath) { + filePath = vscode.window.activeTextEditor ? vscode.window.activeTextEditor.document.fileName : undefined; + } + + if(!filePath) { + vscode.window.showErrorMessage("Ftp-sync: No file selected"); + return; + } + if(!vscode.workspace.rootPath) { vscode.window.showErrorMessage("Ftp-sync: Cannot init ftp-sync without opened folder"); return; } - if(fileUrl.fsPath.indexOf(vscode.workspace.rootPath) < 0) { + if(filePath.indexOf(vscode.workspace.rootPath) < 0) { vscode.window.showErrorMessage("Ftp-sync: Selected file is not a part of the workspace."); return; } var config = ftpconfig.getConfig(); - if(isIgnored(fileUrl.fsPath, config.allow, config.ignore)) { + if(isIgnored(filePath, config.allow, config.ignore)) { vscode.window.showErrorMessage("Ftp-sync: Selected file is ignored."); return; } - var fileName = path.basename(fileUrl.fsPath); + var fileName = path.basename(filePath); var uploadingStatus = vscode.window.setStatusBarMessage("Ftp-sync: Uploading " + fileName + " to FTP server...", STATUS_TIMEOUT); - getFtpSync().uploadFile(fileUrl.fsPath, vscode.workspace.rootPath, function(err) { + getFtpSync().uploadFile(filePath, vscode.workspace.rootPath, function(err) { uploadingStatus.dispose(); if(err) vscode.window.showErrorMessage("Ftp-sync: Uploading " + fileName + " failed: " + err); diff --git a/package.json b/package.json index 6248a90..8209fa2 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,10 @@ { "command": "extension.ftpsyncdownloadselected", "title": "Ftp-sync: Download File" + }, + { + "command": "extension.ftpsynclistselected", + "title": "Ftp-sync: Browse Remote..." } ], "menus": { @@ -72,6 +76,11 @@ "command": "extension.ftpsyncdownloadselected", "group": "extension", "when": "!explorerResourceIsFolder" + }, + { + "command": "extension.ftpsynclistselected", + "group": "extension", + "when": "!explorerResourceIsFolder" } ] } @@ -83,9 +92,10 @@ "fs-cp": "^1.3.1", "fs-walk": "0.0.1", "ftp": "^0.3.10", - "lodash": "3.10.1", + "lodash": "~4.11.1", "mkdirp": "^0.5.1", - "ssh2": "^0.4.13", + "scp2": "^0.5.0", + "ssh2": "^0.6", "stat-mode": "^0.2.1", "upath": "0.1.6" }