Skip to content

Commit a119c38

Browse files
committed
Merge pull request #217 from tamcap/remoteFileDelete
Remote file and folder delete
2 parents 7aa1b4b + fc7abbe commit a119c38

File tree

8 files changed

+125
-42
lines changed

8 files changed

+125
-42
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ Create file `.remote-sync.json` in your project root with these settings:
3333
* `ignore` — Array of [minimatch](https://github.com/isaacs/minimatch) patterns of files to ignore
3434
* `uploadOnSave` — Whether or not to upload the current file when saved, default: false
3535
* `uploadMirrors` — transport mirror config array when upload
36+
* `deleteLocal` - whether or not to delete the local file / folder after remote delete
3637

3738
SCP example:
3839
```json

index.coffee

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@ $ = null
66

77
getEventPath = (e)->
88
$ ?= require('atom-space-pen-views').$
9-
9+
1010
target = $(e.target).closest('.file, .directory, .tab')[0]
1111
target ?= atom.workspace.getActiveTextEditor()
12-
12+
1313
fullPath = target?.getPath?()
1414
return [] unless fullPath
15-
15+
1616
[projectPath, relativePath] = atom.project.relativizePath(fullPath)
1717
return [projectPath, fullPath]
1818

@@ -27,7 +27,7 @@ initProject = (projectPaths)->
2727
for projectPath in disposes
2828
projectDict[projectPath].dispose()
2929
delete projectDict[projectPath]
30-
30+
3131
for projectPath in projectPaths
3232
try
3333
projectPath = fs.realpathSync(projectPath)
@@ -37,22 +37,22 @@ initProject = (projectPaths)->
3737
RemoteSync ?= require "./lib/RemoteSync"
3838
obj = RemoteSync.create(projectPath)
3939
projectDict[projectPath] = obj if obj
40-
40+
4141
handleEvent = (e, cmd)->
4242
[projectPath, fullPath] = getEventPath(e)
4343
return unless projectPath
44-
44+
4545
projectObj = projectDict[fs.realpathSync(projectPath)]
4646
projectObj[cmd]?(fs.realpathSync(fullPath))
47-
47+
4848
reload = (projectPath)->
4949
projectDict[projectPath]?.dispose()
5050
projectDict[projectPath] = RemoteSync.create(projectPath)
5151

5252
configure = (e)->
5353
[projectPath] = getEventPath(e)
5454
return unless projectPath
55-
55+
5656
projectPath = fs.realpathSync(projectPath)
5757
RemoteSync ?= require "./lib/RemoteSync"
5858
RemoteSync.configure projectPath, -> reload(projectPath)
@@ -72,51 +72,53 @@ module.exports =
7272
configFileName:
7373
type: 'string'
7474
default: '.remote-sync.json'
75-
75+
7676
activate: (state) ->
7777
projectDict = {}
7878
initProject(atom.project.getPaths())
79-
79+
8080
CompositeDisposable ?= require('atom').CompositeDisposable
8181
disposables = new CompositeDisposable
82-
82+
8383
disposables.add atom.commands.add('atom-workspace', {
8484
'remote-sync:upload-folder': (e)-> handleEvent(e, "uploadFolder")
8585
'remote-sync:upload-file': (e)-> handleEvent(e, "uploadFile")
86+
'remote-sync:delete-file': (e)-> handleEvent(e, "deleteFile")
87+
'remote-sync:delete-folder': (e)-> handleEvent(e, "deleteFile")
8688
'remote-sync:download-file': (e)-> handleEvent(e, "downloadFile")
8789
'remote-sync:download-folder': (e)-> handleEvent(e, "downloadFolder")
8890
'remote-sync:diff-file': (e)-> handleEvent(e, "diffFile")
8991
'remote-sync:diff-folder': (e)-> handleEvent(e, "diffFolder")
9092
'remote-sync:upload-git-change': (e)-> handleEvent(e, "uploadGitChange")
9193
'remote-sync:configure': configure
9294
})
93-
95+
9496
disposables.add atom.project.onDidChangePaths (projectPaths)->
9597
initProject(projectPaths)
96-
98+
9799
disposables.add atom.workspace.observeTextEditors (editor) ->
98100
onDidSave = editor.onDidSave (e) ->
99101
fullPath = e.path
100102
[projectPath, relativePath] = atom.project.relativizePath(fullPath)
101103
return unless projectPath
102-
104+
103105
projectPath = fs.realpathSync(projectPath)
104106
projectObj = projectDict[projectPath]
105107
return unless projectObj
106-
108+
107109
if fs.realpathSync(fullPath) == fs.realpathSync(projectObj.configPath)
108110
projectObj = reload(projectPath)
109-
111+
110112
return unless projectObj.host.uploadOnSave
111113
projectObj.uploadFile(fs.realpathSync(fullPath))
112-
113-
114+
115+
114116
onDidDestroy = editor.onDidDestroy ->
115117
disposables.remove onDidSave
116118
disposables.remove onDidDestroy
117119
onDidDestroy.dispose()
118120
onDidSave.dispose()
119-
121+
120122
disposables.add onDidSave
121123
disposables.add onDidDestroy
122124

lib/RemoteSync.coffee

Lines changed: 33 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -25,44 +25,58 @@ getLogger = ->
2525
class RemoteSync
2626
constructor: (@projectPath, @configPath) ->
2727
Host ?= require './model/host'
28-
28+
2929
@host = new Host(@configPath)
3030
@initIgnore(@host)
31-
31+
3232
initIgnore: (host)->
3333
ignore = host.ignore?.split(",")
3434
host.isIgnore = (filePath, relativizePath) =>
3535
return false unless ignore
36-
36+
3737
relativizePath = @projectPath unless relativizePath
3838
filePath = path.relative relativizePath, filePath
3939

4040
minimatch ?= require "minimatch"
4141
for pattern in ignore
4242
return true if minimatch filePath, pattern, { matchBase: true, dot: true }
4343
return false
44-
44+
4545
isIgnore: (filePath, relativizePath)->
4646
return @host.isIgnore(filePath, relativizePath)
47-
47+
4848
dispose: ->
4949
if @transport
5050
@transport.dispose()
5151
@transport = null
5252

53+
deleteFile: (filePath) ->
54+
return if @isIgnore(filePath)
55+
56+
if not uploadCmd
57+
UploadListener = require "./UploadListener"
58+
uploadCmd = new UploadListener getLogger()
59+
60+
uploadCmd.handleDelete(filePath, @getTransport())
61+
for t in @getUploadMirrors()
62+
uploadCmd.handleDelete(filePath, t)
63+
64+
if @host.deleteLocal
65+
fs.removeSync(filePath)
66+
5367
downloadFolder: (localPath, targetPath, callback)->
5468
DownloadCmd ?= require './commands/DownloadAllCommand'
5569
DownloadCmd.run(getLogger(), @getTransport(),
5670
localPath, targetPath, callback)
57-
71+
5872
downloadFile: (localPath)->
5973
realPath = path.relative(@projectPath, localPath)
6074
realPath = path.join(@host.target, realPath).replace(/\\/g, "/")
6175
@getTransport().download(realPath)
62-
76+
6377
uploadFile: (filePath) ->
6478
return if @isIgnore(filePath)
65-
79+
6680
if not uploadCmd
6781
UploadListener = require "./UploadListener"
6882
uploadCmd = new UploadListener getLogger()
@@ -74,7 +88,7 @@ class RemoteSync
7488
uploadFolder: (dirPath)->
7589
fs.traverseTree dirPath, @uploadFile.bind(@), =>
7690
return not @isIgnore(dirPath)
77-
91+
7892
uploadGitChange: (dirPath)->
7993
repos = atom.project.getRepositories()
8094
curRepo = null
@@ -85,15 +99,15 @@ class RemoteSync
8599
curRepo = repo
86100
break
87101
return unless curRepo
88-
102+
89103
isChangedPath = (path)->
90104
status = curRepo.getCachedPathStatus(path)
91105
return curRepo.isStatusModified(status) or curRepo.isStatusNew(status)
92-
106+
93107
fs.traverseTree dirPath, (path)=>
94108
@uploadFile(path) if isChangedPath(path)
95109
, (path)=> return not @isIgnore(path)
96-
110+
97111
createTransport: (host)->
98112
if host.transport is 'scp' or host.transport is 'sftp'
99113
ScpTransport ?= require "./transports/ScpTransport"
@@ -129,13 +143,13 @@ class RemoteSync
129143

130144
@getTransport().download realPath, targetPath, =>
131145
@diff localPath, targetPath
132-
146+
133147
diffFolder: (localPath)->
134148
os = require "os" if not os
135149
targetPath = path.join os.tmpDir(), "remote-sync"
136150
@downloadFolder localPath, targetPath, =>
137151
@diff localPath, targetPath
138-
152+
139153
diff: (localPath, targetPath) ->
140154
targetPath = path.join(targetPath, path.relative(@projectPath, localPath))
141155
diffCmd = atom.config.get('remote-sync.difftoolCommand')
@@ -146,22 +160,22 @@ class RemoteSync
146160
Command error: #{err}
147161
command: #{diffCmd} #{localPath} #{targetPath}
148162
"""
149-
150-
module.exports =
163+
164+
module.exports =
151165
create: (projectPath)->
152166
configPath = path.join projectPath, atom.config.get('remote-sync.configFileName')
153167
return unless fs.existsSync configPath
154168
return new RemoteSync(projectPath, configPath)
155-
169+
156170
configure: (projectPath, callback)->
157171
HostView ?= require './view/host-view'
158172
Host ?= require './model/host'
159173
EventEmitter ?= require("events").EventEmitter
160-
174+
161175
emitter = new EventEmitter()
162176
emitter.on "configured", callback
163177

164178
configPath = path.join projectPath, atom.config.get('remote-sync.configFileName')
165179
host = new Host(configPath, emitter)
166180
view = new HostView(host)
167-
view.attach()
181+
view.attach()

lib/UploadListener.coffee

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,19 @@ class UploadListener
1313
localFilePath: localFilePath
1414
transport: transport
1515

16+
handleDelete: (localFilePath, transport) ->
17+
if not @queueDelete
18+
async = require "async" if not async
19+
@queueDelete = async.queue(@deleteFile.bind(@), 1)
20+
21+
@queueDelete.push
22+
localFilePath: localFilePath
23+
transport: transport
24+
25+
deleteFile: (task, callback) ->
26+
{localFilePath, transport} = task
27+
transport.delete localFilePath, callback
28+
1629
uploadFile: (task, callback) ->
1730
{localFilePath, transport} = task
1831
transport.upload localFilePath, callback

lib/transports/FtpTransport.coffee

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,27 @@ class FtpTransport
1313
@logger.error err if err
1414
@connection = null
1515

16+
delete: (localFilePath, callback) ->
17+
targetFilePath = path.join(@settings.target,
18+
path.relative(@projectPath, localFilePath))
19+
.replace(/\\/g, "/")
20+
21+
errorHandler = (err) =>
22+
@logger.error err
23+
callback()
24+
25+
@_getConnection (err, c) =>
26+
return errorHandler err if err
27+
28+
end = @logger.log "Remote delete: #{targetFilePath} ..."
29+
30+
c.delete targetFilePath, (err) ->
31+
return errorHandler err if err
32+
33+
end()
34+
35+
callback()
36+
1637
upload: (localFilePath, callback) ->
1738
targetFilePath = path.join(@settings.target,
1839
path.relative(@projectPath, localFilePath))

lib/transports/ScpTransport.coffee

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,30 @@ class ScpTransport
1212
@connection.end()
1313
@connection = null
1414

15+
delete: (localFilePath, callback) ->
16+
targetFilePath = path.join(@settings.target,
17+
path.relative(@projectPath, localFilePath))
18+
.replace(/\\/g, "/")
19+
20+
errorHandler = (err) =>
21+
@logger.error err
22+
callback()
23+
24+
@_getConnection (err, c) =>
25+
return errorHandler err if err
26+
27+
end = @logger.log "Remote delete: #{targetFilePath} ..."
28+
29+
c.sftp (err, sftp) ->
30+
return errorHandler err if err
31+
32+
c.exec "rm -rf \"#{targetFilePath}\"", (err) ->
33+
return errorHandler err if err
34+
35+
end()
36+
sftp.end()
37+
callback()
38+
1539
upload: (localFilePath, callback) ->
1640
fs = require "fs" if not fs
1741
targetFilePath = path.join(@settings.target,

lib/view/host-view.coffee

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,12 @@ class ConfigView extends View
4646
@div class: 'block', outlet: 'ftpPasswordBlock', style: 'display:none', =>
4747
@label 'Password'
4848

49-
@label " uploadOnSave", =>
50-
@input type: 'checkbox', outlet: 'uploadOnSave'
49+
@div class:'block', =>
50+
@label " uploadOnSave", =>
51+
@input type: 'checkbox', outlet: 'uploadOnSave'
52+
53+
@label " Delete local file/folder upon remote delete", =>
54+
@input type: 'checkbox', outlet: 'deleteLocal'
5155

5256
@div class: 'block pull-right', =>
5357
@button class: 'inline-block-tight btn', outlet: 'cancelButton', click: 'close', 'Cancel'
@@ -91,6 +95,7 @@ class ConfigView extends View
9195
$(editor).view().setText(@host[dataName] or "")
9296

9397
@uploadOnSave.prop('checked', @host.uploadOnSave)
98+
@deleteLocal.prop('checked', @host.deleteLocal)
9499
$(":contains('"+@host.transport.toUpperCase()+"')", @transportGroup).click() if @host.transport
95100
if @host.transport is "scp"
96101
$('.btn-group .btn', @authenticationButtonsBlock).each (i, btn)=>
@@ -107,6 +112,7 @@ class ConfigView extends View
107112

108113
confirm: ->
109114
@host.uploadOnSave = @uploadOnSave.prop('checked')
115+
@host.deleteLocal = @deleteLocal.prop('checked')
110116
@find(".editor").each (i, editor)=>
111117
dataName = $(editor).prev().text().split(" ")[0].toLowerCase()
112118
view = $(editor).view()

menus/context.cson

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@
55
{label: 'Upload File', command: 'remote-sync:upload-file'}
66
{label: 'Download File', command: 'remote-sync:download-file'}
77
{label: 'Diff File', command: 'remote-sync:diff-file'}
8+
{label: 'Delete File', command: 'remote-sync:delete-file'}
89
]
910
]
10-
11+
1112
'.tree-view.full-menu .header.list-item':[
1213
label: 'Remote Sync',
1314
submenu:[
@@ -16,5 +17,6 @@
1617
{label: 'Upload Folder Change', command: 'remote-sync:upload-git-change'}
1718
{label: 'Download Folder', command: 'remote-sync:download-folder'}
1819
{label: 'Diff Folder', command: 'remote-sync:diff-folder'}
20+
{label: 'Delete Folder', command: 'remote-sync:delete-folder'}
1921
]
20-
]
22+
]

0 commit comments

Comments
 (0)