Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 460 lines (393 sloc) 14.354 kB
75bc88c Rename dired-synchronize to dired-sync.
Sébastien Gross authored
1 ;;; dired-sync.el --- sync directories within dired
a65f055 First commit.
Sébastien Gross authored
2
3 ;; Copyright © 2010 Sebastien Gross <seb•ɑƬ•chezwam•ɖɵʈ•org>
4
5 ;; Author: Sebastien Gross <seb•ɑƬ•chezwam•ɖɵʈ•org>
6 ;; Keywords: emacs, dired, rsync
7 ;; Created: 2010-12-02
669924d Change dired-sync to use dired-sync-with-files.
Sébastien Gross authored
8 ;; Last changed: 2010-12-07 11:13:23
a65f055 First commit.
Sébastien Gross authored
9 ;; Licence: WTFPL, grab your copy here: http://sam.zoy.org/wtfpl/
10
11 ;; This file is NOT part of GNU Emacs.
12
e44c84c Add todo.
Sébastien Gross authored
13 ;;; History:
14 ;; * 0.1 - first release
15
16 ;;; TODO
17 ;; * move both rsync / ssh commands to variables.
18
a65f055 First commit.
Sébastien Gross authored
19 ;;; Commentary:
20 ;;
c1f3432 Add some explanations on how it works.
Sébastien Gross authored
21 ;; dired-sync provide a simple and easy way to synchronize directories from
22 ;; dired. This tool is based upon both rsync(1) and ssh(1).
23 ;;
24 ;; To install `dired-sync' you simply need to drop dired-sync.el in you
25 ;; load-path and bind (suggested) C-s S key to `dired-sync':
26 ;;
27 ;; (when (require 'dired-sync nil t)
28 ;; (define-key dired-mode-map (kbd "C-c S") 'dired-sync))
29 ;;
30 ;; There are 3 types of directories synchronizations as explained bellow.
31 ;;
32 ;; * local / local
33 ;; This is the easiest way. rsync(1) would be enough.
34 ;;
35 ;; * local / remote or remote / local
36 ;; This is also a simple way, the only requirement is a working ssh
37 ;; connection to the remote host.
38 ;;
39 ;; * remote / remote
40 ;; This is a bit more complexe since there are 2 types of remote / remote
41 ;; syncs.
42 ;; - source server can reach destination server
43 ;; In that case `dired-sync' would optimize files synchronisation by
44 ;; running rsync(1) on the source server through a ssh connection.
45 ;; - source server cannot reach destination server
46 ;; This is the more complexe case. `dired-sync' would create a ssh
47 ;; tunnel from source to destination using your local machine as a
48 ;; jumphost.
49 ;; Be aware this mode is greedy regarding bandwidth consumption since
50 ;; data are transfered twice: from the source server to localhost AND
51 ;; from localhost to destination server.
52 ;; If a direct connection could not be established from source to
53 ;; destination, `dired-sync' would automatically fall back to the tunneled
54 ;; sync mode.
55 ;;
56 ;; `dired-sync' is heavily based on ssh(1) configuration hence your
57 ;; ~/.ssh/config file should be as accurate as possible. It doesn't matter
58 ;; how many jumphosts you need to use to reach both source and destination
59 ;; as long as they are declared in you ssh configuration file.
60 ;;
61 ;; Source Destination
62 ;;
63 ;; +---------+ (if possible) +---------+
64 ;; | HostA |<- - - - - - - - - >| HostB |
65 ;; | UserA | | UserB |
66 ;; +---------+ +---------+
67 ;; ^ ^
68 ;; +---------+ +---------+
69 ;; |JumphostA| |JumphostB|
70 ;; | UserJHA | | UserJHB |
71 ;; +---------+ +---------+
72 ;; ^ ^
73 ;; | +---------+ |
74 ;; -----------|localhost|----------
75 ;; +---------+
76 ;;
77 ;; To use that configuration, you ~/.ssh/config may be something like:
78 ;;
79 ;; Host *
80 ;; ForwardAgent yes
81 ;; RhostsRSAAuthentication yes
82 ;; RSAAuthentication yes
83 ;; HashKnownHosts yes
84 ;; IdentityFile ~/.ssh/id_rsa
85 ;; TCPKeepAlive yes
86 ;; ServerAliveInterval 30
87 ;; Port 22
88 ;; Protocol 2,1
89 ;;
90 ;; Host jumphostA
91 ;; User userJHA
92 ;; HostName jumphostA.example.com
93 ;;
94 ;; Host hostA
95 ;; User userA
96 ;; HostName jumphostA.internal.example.com
97 ;; ProxyCommand ssh -q -t jumphostA nc -w 1 %h %p
98 ;;
99 ;; Host jumphostB
100 ;; User userJHB
101 ;; HostName jumphostB.other-example.com
102 ;;
103 ;; Host hostB
104 ;; User userB
105 ;; HostName jumphostV.internal.other-example.com
106 ;; ProxyCommand ssh -q -t jumphostB nc -w 1 %h %p
107 ;;
4503985 Add commands to reach hostA and hostB.
Sébastien Gross authored
108 ;;
109 ;; then you would be able to connect to hostA by simply typing:
110 ;;
111 ;; ssh hostA
112 ;;
113 ;; Or opening a dired buffer to hostB: C-X C-f /scp:hostB:
114 ;;
c1f3432 Add some explanations on how it works.
Sébastien Gross authored
115
a65f055 First commit.
Sébastien Gross authored
116 ;;; Code:
117
118
75bc88c Rename dired-synchronize to dired-sync.
Sébastien Gross authored
119 (defcustom dired-sync-bin "rsync"
120 "Path to sync tool."
a65f055 First commit.
Sébastien Gross authored
121 :type 'string
75bc88c Rename dired-synchronize to dired-sync.
Sébastien Gross authored
122 :group 'dired-sync)
a65f055 First commit.
Sébastien Gross authored
123
37cebcf Add specials and devices options to rsync.
Sébastien Gross authored
124 (defcustom dired-sync-args '("--delete" "-a" "-D" "-i")
75bc88c Rename dired-synchronize to dired-sync.
Sébastien Gross authored
125 "Args for sync tool."
a65f055 First commit.
Sébastien Gross authored
126 :type 'list
75bc88c Rename dired-synchronize to dired-sync.
Sébastien Gross authored
127 :group 'dired-sync)
a65f055 First commit.
Sébastien Gross authored
128
3e0e5bc Add command delay variable.
Sébastien Gross authored
129 (defcustom dired-sync-timeout 10
130 "Timeout (in seconds) when performing ssh login tests."
a65f055 First commit.
Sébastien Gross authored
131 :type 'integer
75bc88c Rename dired-synchronize to dired-sync.
Sébastien Gross authored
132 :group 'dired-sync)
a65f055 First commit.
Sébastien Gross authored
133
3e0e5bc Add command delay variable.
Sébastien Gross authored
134 (defcustom dired-sync-command-delay 10
135 "Delay (in seconds) between commands when synchronizing 2
136 tunneled remote hosts."
137 :type 'integer
138 :group 'dired-sync)
139
f1069f5 Add dired-sync-commands variable.
Sébastien Gross authored
140 (defcustom dired-sync-commands
141 '(:get-user-local
4f5d2c2 Update dired-sync-commands to handle parameters and add :do-sync-loca…
Sébastien Gross authored
142 (lambda (&rest ignore) "whoami")
143 :get-user-remote (lambda (&optional d-host &rest ignore)
144 (let ((dst-host (or d-host dst-host)))
145 (concat
146 "ssh -q -o StrictHostKeyChecking=no "
147 "-o PasswordAuthentication=no "
148 "-o UserKnownHostsFile=/dev/null "
149 dst-host " whoami")))
150 :do-sync-local-local (lambda (&optional s-path d-path
151 &rest ignore)
152 (let ((src-path (or s-path
153 src-path-quote))
154 (dst-path (or d-path
155 dst-path-quote))))
156 (list
157 (list "rsync" "--delete" "-a" "-D" "-i"
158 src-path dst-path)
159 nil))
160
161
162
163 )
f1069f5 Add dired-sync-commands variable.
Sébastien Gross authored
164 "PLIST containing commands used to perform synchronization.
165
166 Variables defined in `dired-sync-with-files' could be used.
167
168 :get-user-local
169
170 Shell function to be used to retrieve local username. Please
171 note that local is relative to source host.
172
173 :get-user-remote
174
175 Shell function to be used to retrieve remote username from
176 source host. This function is used to check source-destination
177 connectivity."
178 :type 'plist
179 :group 'dired-sync)
180
181
3e0e5bc Add command delay variable.
Sébastien Gross authored
182
c48449c Add dired-sync-with-files macro.
Sébastien Gross authored
183 (defmacro dired-sync-with-files (src dst &rest body)
184 "Execute BODY after converting both SRC and DST to variables according
185 `dired-sync-parse-uri' PLIST definition.
186
187 Variables can be accessed anywhere in BODY.
188
189 Variables are: src-file, src-host, src-user, src-path,
e1d5d21 Add :path-quote information in dired-sync-parse-uri.
Sébastien Gross authored
190 src-path-quote, src-tunnel-port, dst-file, dst-host, dst-user,
191 dst-path, dst-path-quote, dst-tunnel-port.
c48449c Add dired-sync-with-files macro.
Sébastien Gross authored
192
193 See `dired-sync-parse-uri' for further information."
194 `(let* ((src ,src)
195 (src-file (plist-get ,src :file))
196 (src-host (plist-get ,src :host))
197 (src-user (plist-get ,src :user))
198 (src-path (plist-get ,src :path))
e1d5d21 Add :path-quote information in dired-sync-parse-uri.
Sébastien Gross authored
199 (src-path-quote (plist-get ,src :path-quote))
c48449c Add dired-sync-with-files macro.
Sébastien Gross authored
200 (src-tunnel-port (plist-get ,src :tunnel-port))
201 (dst ,dst)
202 (dst-file (plist-get ,dst :file))
203 (dst-host (plist-get ,dst :host))
204 (dst-user (plist-get ,dst :user))
205 (dst-path (plist-get ,dst :path))
e1d5d21 Add :path-quote information in dired-sync-parse-uri.
Sébastien Gross authored
206 (dst-path-quote (plist-get ,dst :path-quote))
c48449c Add dired-sync-with-files macro.
Sébastien Gross authored
207 (dst-tunnel-port (plist-get ,dst :tunnel-port)))
208 ,@body))
209
a65f055 First commit.
Sébastien Gross authored
210
cd00afa Changed dired-sync-get-user to use both dired-sync-commands and dired…
Sébastien Gross authored
211 (defun dired-sync-get-user ()
212 "Return username on SRC-HOST when connecting using ssh.
213
214 If DST-HOST is defined, try to connect to DST-HOST using SRC-HOST
215 as a proxy.
a65f055 First commit.
Sébastien Gross authored
216
cd00afa Changed dired-sync-get-user to use both dired-sync-commands and dired…
Sébastien Gross authored
217 If an error occurs, returns nil.
a65f055 First commit.
Sébastien Gross authored
218
cd00afa Changed dired-sync-get-user to use both dired-sync-commands and dired…
Sébastien Gross authored
219 Both SRC-HOST and DST-HOST should be provided using
220 `dired-sync-with-files' macro."
a65f055 First commit.
Sébastien Gross authored
221 (let ((err (get-buffer-create "*err*"))
222 (out (get-buffer-create "*out*"))
cd00afa Changed dired-sync-get-user to use both dired-sync-commands and dired…
Sébastien Gross authored
223 (default-directory src-file)
a65f055 First commit.
Sébastien Gross authored
224 (cmd
cd00afa Changed dired-sync-get-user to use both dired-sync-commands and dired…
Sébastien Gross authored
225 (if dst-host
226 (funcall (plist-get dired-sync-commands :get-user-remote))
227 (funcall (plist-get dired-sync-commands :get-user-local))))
a65f055 First commit.
Sébastien Gross authored
228 in-s out-s)
229 (with-timeout
cd00afa Changed dired-sync-get-user to use both dired-sync-commands and dired…
Sébastien Gross authored
230 (dired-sync-timeout
231 (message
232 (format
233 "dired-sync-get-user timeout on %s : %s" src-host cmd)))
a65f055 First commit.
Sébastien Gross authored
234 (shell-command cmd out err))
235 (set-buffer out)
236 ;; Just keep the last line in case of error such as
237 ;; cd: 1149: can't cd to /path/to
238 (point-max)
239 (setq out-s (buffer-substring-no-properties (point-at-bol) (point-at-eol)))
240 (kill-buffer out)
241 (set-buffer err)
242 (setq err-s (buffer-string))
243 (kill-buffer err)
244 (when (string= "" out-s) (setq out-s nil))
245 out-s))
4b75fdf Remove some debug messages.
Sébastien Gross authored
246
a65f055 First commit.
Sébastien Gross authored
247
248
75bc88c Rename dired-synchronize to dired-sync.
Sébastien Gross authored
249 (defun dired-sync-parse-uri (file)
2d499b7 Add better doc string for dired-sync-parse-uri.
Sébastien Gross authored
250 "Parse FILE which syntax is given by Info node `(tramp) Filename Syntax'.
a65f055 First commit.
Sébastien Gross authored
251
252 Returned value is a PLIST with following properties.
253
254 :file
255
2d499b7 Add better doc string for dired-sync-parse-uri.
Sébastien Gross authored
256 A copy of original FILE value.
a65f055 First commit.
Sébastien Gross authored
257
258 :host
259
2d499b7 Add better doc string for dired-sync-parse-uri.
Sébastien Gross authored
260 The remote hostname returned by `tramp-file-real-host'. nil
261 if file is local.
a65f055 First commit.
Sébastien Gross authored
262
263 :user
264
2d499b7 Add better doc string for dired-sync-parse-uri.
Sébastien Gross authored
265 The remove user (login) name if FILE is remote, nil if user
266 is not specified in FILE, or if FILE is local. Value is
267 retrieved using `tramp-file-name-user'.
a65f055 First commit.
Sébastien Gross authored
268
269 :path
270
2d499b7 Add better doc string for dired-sync-parse-uri.
Sébastien Gross authored
271 The full pathname retrieved using
272 `tramp-file-name-localname'.
a65f055 First commit.
Sébastien Gross authored
273
e1d5d21 Add :path-quote information in dired-sync-parse-uri.
Sébastien Gross authored
274 :path-quote
275
276 A shell quoted version of the :path as returned by
277 `shell-quote-argument'.
278
a65f055 First commit.
Sébastien Gross authored
279 :tunnel-port
280
2d499b7 Add better doc string for dired-sync-parse-uri.
Sébastien Gross authored
281 Random port used to for ssh tunnel setup."
a65f055 First commit.
Sébastien Gross authored
282 (let* ((file (expand-file-name file))
283 (file-vec (or (ignore-errors (tramp-dissect-file-name file))
284 (tramp-dissect-file-name (concat "/:" file) 1)))
e1d5d21 Add :path-quote information in dired-sync-parse-uri.
Sébastien Gross authored
285 (host (tramp-file-name-real-host file-vec))
a65f055 First commit.
Sébastien Gross authored
286 (user (tramp-file-name-user file-vec))
e1d5d21 Add :path-quote information in dired-sync-parse-uri.
Sébastien Gross authored
287 (path (tramp-file-name-localname file-vec))
a65f055 First commit.
Sébastien Gross authored
288 (method (tramp-file-name-method file-vec))
cd00afa Changed dired-sync-get-user to use both dired-sync-commands and dired…
Sébastien Gross authored
289 (ret (list :file file :user user :method method
290 :host host :path path
291 :path-quote (shell-quote-argument path)
292 :tunnel-port nil)))
a65f055 First commit.
Sébastien Gross authored
293 (when (and host (not user))
cd00afa Changed dired-sync-get-user to use both dired-sync-commands and dired…
Sébastien Gross authored
294 (setq ret (plist-put ret :user (dired-sync-with-files ret nil
295 (dired-sync-get-user2))))
296 (setq ret (plist-put ret :tunnel-port
297 (+ 1024 (random (- 32767 1024))))))
298 ret))
a65f055 First commit.
Sébastien Gross authored
299
300
301
75bc88c Rename dired-synchronize to dired-sync.
Sébastien Gross authored
302 (defun dired-sync-read-src-dst (&optional source destination)
ae52b92 Add some comments.
Sébastien Gross authored
303 "Read both source and detination directories from minibuffer if not provided.
304
e1d5d21 Add :path-quote information in dired-sync-parse-uri.
Sébastien Gross authored
305 If called from a `dired-mode' buffer, use `default-directory' for
306 SOURCE."
75bc88c Rename dired-synchronize to dired-sync.
Sébastien Gross authored
307 (let* ((src (dired-sync-parse-uri
a65f055 First commit.
Sébastien Gross authored
308 (or source
309 (if (eq major-mode 'dired-mode) default-directory nil)
75bc88c Rename dired-synchronize to dired-sync.
Sébastien Gross authored
310 (read-file-name "Sync source: " nil nil t nil))))
311 (dst (dired-sync-parse-uri
a65f055 First commit.
Sébastien Gross authored
312 (or destination
313 (read-file-name
75bc88c Rename dired-synchronize to dired-sync.
Sébastien Gross authored
314 (format "Sync %s to: " (plist-get src :file)))
a65f055 First commit.
Sébastien Gross authored
315 nil nil t nil 'file-directory-p)))
316 direct)
4893911 Changed dired-sync-read-src-dst to use both dired-sync-commands and d…
Sébastien Gross authored
317 (dired-sync-with-files
318 src dst
319
320 ;; remove tailing / for source file.
321 ;; Prevent from copying all source files into destination without
322 ;; creating a new directory
323 (unless (string= "/" src-path)
324 (setq src (plist-put src :file
325 (replace-regexp-in-string "/*$" ""
326 src-file)))
327 (setq src (plist-put src :path
328 (replace-regexp-in-string "/*$" ""
329 src-path)))
330 (setq src (plist-put src :path-quote
331 (replace-regexp-in-string "/*$" ""
332 src-path-quote))))
333
334 ;; try to get e direct link between the hosts
335 (when (and src-host dst-host)
336 (setq direct
337 (dired-sync-get-user
338 ;; Change to / on remote host to prevent from remote dir not
339 ;; found errors.
340 (format "/%s:/" src-host)
341 ;; connecter on remote host using appropriated user.
342 (format "%s@%s" dst-user dst-host))))
343 (setq src (plist-put src :direct direct))
344 (setq dst (plist-put dst :direct direct))
345 (list :src src :dst dst))))
a65f055 First commit.
Sébastien Gross authored
346
347
75bc88c Rename dired-synchronize to dired-sync.
Sébastien Gross authored
348 (defun dired-sync (&optional source destination)
349 "sync 2 directories using `dired-sync-bin'."
a65f055 First commit.
Sébastien Gross authored
350 (interactive)
75bc88c Rename dired-synchronize to dired-sync.
Sébastien Gross authored
351 (let* ((files (dired-sync-read-src-dst source destination))
a65f055 First commit.
Sébastien Gross authored
352 (src (plist-get files :src))
353 (dst (plist-get files :dst))
354 cmd1 cmd2)
669924d Change dired-sync to use dired-sync-with-files.
Sébastien Gross authored
355 (dired-sync-with-files
356 src dst
357 (cond
358 ;; both files are remote and src cannot connect to dst
359 ((and
360 (plist-get src :host)
361 (plist-get dst :host)
362 (not (plist-get src :direct)))
363 (setq cmd1 `("ssh" "-L"
364 ,(format "%d:127.0.0.1:22" (plist-get dst :tunnel-port))
365 ,(plist-get dst :host)))
366 (setq cmd2 `("ssh" "-A" "-R"
367 ,(format "%d:127.0.0.1:%d" (plist-get src :tunnel-port)
368 (plist-get dst :tunnel-port))
369 ,(plist-get src :host)
370 ,(concat dired-sync-bin " "
371 (mapconcat 'concat dired-sync-args " ")
372 (format " -e 'ssh -A -p %d "
373 (plist-get src :tunnel-port))
374 "-o StrictHostKeyChecking=no "
375 "-o UserKnownHostsFile=/dev/null' "
376 (format "%s %s@localhost:%s"
377 (plist-get src :path)
378 (plist-get dst :user)
379 (plist-get dst :path))))))
380
381 ;; both files are remote and src cannot connect to dst
382 ((and
383 (plist-get src :host)
384 (plist-get dst :host))
385 (setq cmd1 `("ssh" ,(plist-get src :host)
386 ,(concat dired-sync-bin " "
387 (mapconcat 'concat dired-sync-args " ")
388 " -e ssh "
389 (shell-quote-argument (plist-get src :path))
390 (format " %s@%s:%s" (plist-get dst :user)
391 (plist-get dst :host)
392 (shell-quote-argument (plist-get dst :path)))))
393 cmd2 nil))
394 ;; one file is remote
395 ((or
396 (plist-get src :host)
397 (plist-get dst :host))
398 (setq cmd1 (apply 'append `((,dired-sync-bin)
399 ,dired-sync-args
400 ("-e") ("ssh")
401 (,(if (plist-get src :host)
402 (format "%s:%s" (plist-get src :host)
403 (plist-get src :path))
404 (plist-get src :file)))
405 (,(if (plist-get dst :host)
406 (format "%s:%s" (plist-get dst :host)
407 (plist-get dst :path))
408 (plist-get dst :file)))))
409 cmd2 nil))
410
411 ;; all files are local
412 (t
413 (setq cmd1 (funcall (plist-get dired-sync-commands :do-sync-local-local)))
414 (setq cmd2 (cadr cmd1))
415 (setq cmd1 (car cmd1))))
416
417
418 (message (concat "C1: " (mapconcat 'append cmd1 " ")))
419 (message (concat "C2: " (mapconcat 'append cmd2 " ")))
420
421 (let* ((p1-str (format "dired-sync %s to %s"
422 (plist-get src :file)
423 (plist-get dst :file)))
424 (p1-buf (format "*%s*" p1-str))
425 (p1 (apply 'start-process p1-str p1-buf (car cmd1) (cdr cmd1)))
426 (p2-str (format "%s (syncing)" p1-str))
427 (p2-buf (format "*%s*" p2-str))
428 p2)
429
430 (process-put p1 :buf p1-buf)
431 (unless cmd2
432 (set-process-sentinel p1 'dired-sync-proc-sentinel))
433 (when cmd2
434 ;;make sur shh tunnel is up
435 (sit-for dired-sync-command-delay)
436 (setq p2 (apply 'start-process p2-str p2-buf (car cmd2) (cdr cmd2)))
437 (process-put p2 :related p1)
438 (process-put p2 :buf p2-buf)
439 (set-process-sentinel p2 'dired-sync-proc-sentinel)))
440
441 t)))
a65f055 First commit.
Sébastien Gross authored
442
443
75bc88c Rename dired-synchronize to dired-sync.
Sébastien Gross authored
444 (defun dired-sync-proc-sentinel (proc change)
a65f055 First commit.
Sébastien Gross authored
445 (when (eq (process-status proc) 'exit)
446 (let ((status (process-exit-status proc))
447 (buf (process-get proc :buf))
448 (related (process-get proc :related)))
449 (if (not (eq 0 status))
450 (progn
451 (when (process-buffer proc)
452 (set-window-buffer (selected-window) buf))
75bc88c Rename dired-synchronize to dired-sync.
Sébastien Gross authored
453 (error "dired-sync failled"))
454 (message "dired-sync success")
a65f055 First commit.
Sébastien Gross authored
455 (kill-buffer buf))
456 (when related
457 (kill-process related)))))
458
75bc88c Rename dired-synchronize to dired-sync.
Sébastien Gross authored
459 (provide 'dired-sync)
Something went wrong with that request. Please try again.