aboutsummaryrefslogtreecommitdiffstats
path: root/lisp
diff options
context:
space:
mode:
authorMichael Albinus2018-07-18 16:51:56 +0200
committerMichael Albinus2018-07-18 16:51:56 +0200
commit7a258fa0bb9f9e7600229b95ed54ee5ee0bc9a0c (patch)
tree9654a226674f2153c703512f1298adc1866d1ceb /lisp
parentcb50077b1eb7c1467f2f200e01599b391d025bfa (diff)
downloademacs-7a258fa0bb9f9e7600229b95ed54ee5ee0bc9a0c.tar.gz
emacs-7a258fa0bb9f9e7600229b95ed54ee5ee0bc9a0c.zip
Adapt shadowfile.el for Tramp (Bug#4526, Bug#4846)
* etc/NEWS: Mention changes in shadowfile.el. * lisp/shadowfile.el (top): Require 'tramp instead of 'ange-ftp. (shadow-cluster): New defstruct. (shadow-make-cluster, shadow-cluster-name, shadow-cluster-primary) (shadow-cluster-regexp, shadow-get-user) (shadow-parse-fullname): Remove. (shadow-info-file, shadow-todo-file, shadow-system-name) (shadow-homedir, shadow-regexp-superquote, shadow-suffix) (shadow-set-cluster, shadow-get-cluster, shadow-site-name) (shadow-name-site, shadow-site-primary, shadow-site-cluster) (shadow-read-site, shadow-parse-name, shadow-make-fullname) (shadow-replace-name-component, shadow-local-file) (shadow-expand-cluster-in-file-name, shadow-contract-file-name) (shadow-same-site, shadow-file-match, shadow-define-cluster) (shadow-define-literal-group, shadow-define-regexp-group) (shadow-make-group, shadow-shadows-of-1, shadow-read-files) (shadow-write-info-file, shadow-write-todo-file) (shadow-initialize): Adapt variables and functions. * test/lisp/shadowfile-tests.el: New file.
Diffstat (limited to 'lisp')
-rw-r--r--lisp/shadowfile.el462
1 files changed, 230 insertions, 232 deletions
diff --git a/lisp/shadowfile.el b/lisp/shadowfile.el
index 0095d6959ef..e1a9b8e1d98 100644
--- a/lisp/shadowfile.el
+++ b/lisp/shadowfile.el
@@ -25,37 +25,38 @@
25;; This package helps you to keep identical copies of files in more than one 25;; This package helps you to keep identical copies of files in more than one
26;; place - possibly on different machines. When you save a file, it checks 26;; place - possibly on different machines. When you save a file, it checks
27;; whether it is on the list of files with "shadows", and if so, it tries to 27;; whether it is on the list of files with "shadows", and if so, it tries to
28;; copy it when you exit Emacs (or use the shadow-copy-files command). 28;; copy it when you exit Emacs (or use the `shadow-copy-files' command).
29 29
30;; Installation & Use: 30;; Installation & Use:
31 31
32;; Add clusters (if necessary) and file groups with shadow-define-cluster, 32;; Add clusters (if necessary) and file groups with `shadow-define-cluster',
33;; shadow-define-literal-group, and shadow-define-regexp-group (see the 33;; `shadow-define-literal-group', and `shadow-define-regexp-group' (see the
34;; documentation for these functions for information on how and when to use 34;; documentation for these functions for information on how and when to use
35;; them). After doing this once, everything should be automatic. 35;; them). After doing this once, everything should be automatic.
36 36
37;; The lists of clusters and shadows are saved in a ~/.emacs.d/shadows 37;; The lists of clusters and shadows are saved in `shadow-info-file',
38;; (`shadow-info-file') file, so that they can be remembered from one 38;; so that they can be remembered from one Emacs session to another,
39;; Emacs session to another, even (as much as possible) if the Emacs 39;; even (as much as possible) if the Emacs session terminates
40;; session terminates abnormally. The files needing to be copied are 40;; abnormally. The files needing to be copied are stored in
41;; stored in `shadow-todo-file'; if a file cannot be copied for any 41;; `shadow-todo-file'; if a file cannot be copied for any reason, it
42;; reason, it will stay on the list to be tried again next time. The 42;; will stay on the list to be tried again next time. The
43;; `shadow-info-file' file should itself have shadows on all your accounts 43;; `shadow-info-file' file should itself have shadows on all your
44;; so that the information in it is consistent everywhere, but 44;; accounts so that the information in it is consistent everywhere,
45;; `shadow-todo-file' is local information and should have no shadows. 45;; but `shadow-todo-file' is local information and should have no
46;; shadows.
46 47
47;; If you do not want to copy a particular file, you can answer "no" and 48;; If you do not want to copy a particular file, you can answer "no" and
48;; be asked again next time you hit C-x 4 s or exit Emacs. If you do not 49;; be asked again next time you hit "C-x 4 s" or exit Emacs. If you do not
49;; want to be asked again, use shadow-cancel, and you will not be asked 50;; want to be asked again, use "M-x shadow-cancel", and you will not be asked
50;; until you change the file and save it again. If you do not want to 51;; until you change the file and save it again. If you do not want to
51;; shadow that file ever again, you can edit it out of the shadows 52;; shadow that file ever again, you can edit it out of the shadows
52;; buffer. Anytime you edit the shadows buffer, you must type M-x 53;; buffer. Anytime you edit the shadows buffer, you must type "M-x
53;; shadow-read-files to load in the new information, or your changes will 54;; shadow-read-files" to load in the new information, or your changes will
54;; be overwritten! 55;; be overwritten!
55 56
56;; Bugs & Warnings: 57;; Bugs & Warnings:
57;; 58;;
58;; - It is bad to have two emacses both running shadowfile at the same 59;; - It is bad to have two Emacsen both running shadowfile at the same
59;; time. It tries to detect this condition, but is not always successful. 60;; time. It tries to detect this condition, but is not always successful.
60;; 61;;
61;; - You have to be careful not to edit a file in two locations 62;; - You have to be careful not to edit a file in two locations
@@ -64,19 +65,16 @@
64;; 65;;
65;; - It ought to check modification times of both files to make sure 66;; - It ought to check modification times of both files to make sure
66;; it is doing the right thing. This will have to wait until 67;; it is doing the right thing. This will have to wait until
67;; file-newer-than-file-p works between machines. 68;; `file-newer-than-file-p' works between machines.
68;; 69;;
69;; - It will not make directories for you, it just fails to copy files 70;; - It will not make directories for you, it just fails to copy files
70;; that belong in non-existent directories. 71;; that belong in non-existent directories.
71;;
72;; Please report any bugs to me (boris@gnu.org). Also let me know
73;; if you have suggestions or would like to be informed of updates.
74 72
75 73
76;;; Code: 74;;; Code:
77 75
78(require 'cl-lib) 76(require 'cl-lib)
79(require 'ange-ftp) 77(require 'tramp)
80 78
81;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 79;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
82;;; Variables 80;;; Variables
@@ -107,35 +105,35 @@ files that have been changed and need to be copied to other systems."
107 :type 'boolean 105 :type 'boolean
108 :group 'shadow) 106 :group 'shadow)
109 107
110;; FIXME in a sense, this changed in 24.4 (addition of locate-user-emacs-file), 108(defcustom shadow-info-file (locate-user-emacs-file "shadows" ".shadows")
111;; but due to the weird way this variable is initialized to nil, it didn't
112;; literally change. Same for shadow-todo-file.
113(defcustom shadow-info-file nil
114 "File to keep shadow information in. 109 "File to keep shadow information in.
115The `shadow-info-file' should be shadowed to all your accounts to 110The `shadow-info-file' should be shadowed to all your accounts to
116ensure consistency. Default: ~/.emacs.d/shadows" 111ensure consistency. Default: ~/.emacs.d/shadows"
117 :type '(choice (const nil) file) 112 :type 'file
118 :group 'shadow) 113 :group 'shadow
114 :version "26.2")
119 115
120(defcustom shadow-todo-file nil 116(defcustom shadow-todo-file
117 (locate-user-emacs-file "shadow_todo" ".shadow_todo")
121 "File to store the list of uncopied shadows in. 118 "File to store the list of uncopied shadows in.
122This means that if a remote system is down, or for any reason you cannot or 119This means that if a remote system is down, or for any reason you cannot or
123decide not to copy your shadow files at the end of one Emacs session, it will 120decide not to copy your shadow files at the end of one Emacs session, it will
124remember and ask you again in your next Emacs session. 121remember and ask you again in your next Emacs session.
125This file must NOT be shadowed to any other system, it is host-specific. 122This file must NOT be shadowed to any other system, it is host-specific.
126Default: ~/.emacs.d/shadow_todo" 123Default: ~/.emacs.d/shadow_todo"
127 :type '(choice (const nil) file) 124 :type 'file
128 :group 'shadow) 125 :group 'shadow
126 :version "26.2")
129 127
130 128
131;;; The following two variables should in most cases initialize themselves 129;;; The following two variables should in most cases initialize themselves
132;;; correctly. They are provided as variables in case the defaults are wrong 130;;; correctly. They are provided as variables in case the defaults are wrong
133;;; on your machine (and for efficiency). 131;;; on your machine (and for efficiency).
134 132
135(defvar shadow-system-name (system-name) 133(defvar shadow-system-name (concat "/" (system-name) ":")
136 "The complete hostname of this machine.") 134 "The identification for local files on this machine.")
137 135
138(defvar shadow-homedir nil 136(defvar shadow-homedir "~"
139 "Your home directory on this machine.") 137 "Your home directory on this machine.")
140 138
141;;; 139;;;
@@ -186,12 +184,12 @@ created by `shadow-define-regexp-group'.")
186 (car list)) 184 (car list))
187 185
188(defun shadow-regexp-superquote (string) 186(defun shadow-regexp-superquote (string)
189 "Like `regexp-quote', but includes the ^ and $. 187 "Like `regexp-quote', but includes the \\` and \\'.
190This makes sure regexp matches nothing but STRING." 188This makes sure regexp matches nothing but STRING."
191 (concat "^" (regexp-quote string) "$")) 189 (concat "\\`" (regexp-quote string) "\\'"))
192 190
193(defun shadow-suffix (prefix string) 191(defun shadow-suffix (prefix string)
194 "If PREFIX begins STRING, return the rest. 192 "If PREFIX begins with STRING, return the rest.
195Return value is non-nil if PREFIX and STRING are `string=' up to the length of 193Return value is non-nil if PREFIX and STRING are `string=' up to the length of
196PREFIX." 194PREFIX."
197 (let ((lp (length prefix)) 195 (let ((lp (length prefix))
@@ -204,70 +202,66 @@ PREFIX."
204;;; Clusters and sites 202;;; Clusters and sites
205;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 203;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
206 204
207;;; I use the term `site' to refer to a string which may be the name of a 205;;; I use the term `site' to refer to a string which may be the
208;;; cluster or a literal hostname. All user-level commands should accept 206;;; cluster identification "/name:", a remote identification
209;;; either. 207;;; "/method:user@host:", or "/system-name:' (the value of
210 208;;; `shadow-system-name') for the location of local files. All
211(defun shadow-make-cluster (name primary regexp) 209;;; user-level commands should accept either.
212 "Create a shadow cluster.
213It is called NAME, uses the PRIMARY hostname and REGEXP matching all
214hosts in the cluster. The variable `shadow-clusters' associates the
215names of clusters to these structures. This function is for program
216use: to create clusters interactively, use `shadow-define-cluster'
217instead."
218 (list name primary regexp))
219
220(defmacro shadow-cluster-name (cluster)
221 "Return the name of the CLUSTER."
222 (list 'elt cluster 0))
223 210
224(defmacro shadow-cluster-primary (cluster) 211(cl-defstruct (shadow-cluster (:type list) :named) name primary regexp)
225 "Return the primary hostname of a CLUSTER."
226 (list 'elt cluster 1))
227
228(defmacro shadow-cluster-regexp (cluster)
229 "Return the regexp matching hosts in a CLUSTER."
230 (list 'elt cluster 2))
231 212
232(defun shadow-set-cluster (name primary regexp) 213(defun shadow-set-cluster (name primary regexp)
233 "Put cluster NAME on the list of clusters. 214 "Put cluster NAME on the list of clusters.
234Replace old definition, if any. PRIMARY and REGEXP are the 215Replace old definition, if any. PRIMARY and REGEXP are the
235information defining the cluster. For interactive use, call 216information defining the cluster. For interactive use, call
236`shadow-define-cluster' instead." 217`shadow-define-cluster' instead."
237 (let ((rest (cl-remove-if (lambda (x) (equal name (car x))) 218 (let ((rest (cl-remove-if (lambda (x) (equal name (shadow-cluster-name x)))
238 shadow-clusters))) 219 shadow-clusters)))
239 (setq shadow-clusters 220 (setq shadow-clusters
240 (cons (shadow-make-cluster name primary regexp) 221 (cons (make-shadow-cluster :name name :primary primary :regexp regexp)
241 rest)))) 222 rest))))
242 223
243(defmacro shadow-get-cluster (name) 224(defun shadow-get-cluster (name)
244 "Return cluster named NAME, or nil." 225 "Return cluster named NAME, or nil."
245 (list 'assoc name 'shadow-clusters)) 226 (shadow-find
227 (lambda (x) (string-equal (shadow-cluster-name x) name))
228 shadow-clusters))
229
230;;; SITES
231
232(defun shadow-site-name (site)
233 "Return name if SITE has the form \"/name:\", otherwise SITE."
234 (if (string-match "\\`/\\(\\w+\\):\\'" site)
235 (match-string 1 site) site))
236
237(defun shadow-name-site (name)
238 "Return \"/name:\" if NAME has word syntax, otherwise NAME."
239 (if (string-match "\\`\\w+\\'" name)
240 (format "/%s:"name) name))
246 241
247(defun shadow-site-primary (site) 242(defun shadow-site-primary (site)
248 "If SITE is a cluster, return primary host, otherwise return SITE." 243 "If SITE is a cluster, return primary identification, otherwise return SITE."
249 (let ((c (shadow-get-cluster site))) 244 (let ((cluster (shadow-get-cluster (shadow-site-name site))))
250 (if c 245 (if cluster
251 (shadow-cluster-primary c) 246 (shadow-cluster-primary cluster)
252 site))) 247 site)))
253 248
254;;; SITES
255
256(defun shadow-site-cluster (site) 249(defun shadow-site-cluster (site)
257 "Given a SITE (hostname or cluster name), return cluster it is in, or nil." 250 "Given a SITE, return cluster it is in, or nil."
258 (or (assoc site shadow-clusters) 251 (or (shadow-get-cluster (shadow-site-name site))
259 (shadow-find 252 (shadow-find
260 (function (lambda (x) 253 (lambda (x)
261 (string-match (shadow-cluster-regexp x) 254 (string-match (shadow-cluster-regexp x) (shadow-name-site site)))
262 site)))
263 shadow-clusters))) 255 shadow-clusters)))
264 256
265(defun shadow-read-site () 257(defun shadow-read-site ()
266 "Read a cluster name or hostname from the minibuffer." 258 "Read a cluster name or host identification from the minibuffer."
267 (let ((ans (completing-read "Host or cluster name [RET when done]: " 259 (let ((ans (completing-read "Host identification or cluster name: "
268 shadow-clusters))) 260 shadow-clusters)))
269 (if (equal "" ans) 261 (when (or (shadow-get-cluster (shadow-site-name ans))
270 nil 262 (string-equal ans shadow-system-name)
263 (string-equal ans (shadow-site-name shadow-system-name))
264 (setq ans (file-remote-p ans)))
271 ans))) 265 ans)))
272 266
273(defun shadow-site-match (site1 site2) 267(defun shadow-site-match (site1 site2)
@@ -281,63 +275,95 @@ be matched against the primary of SITE2."
281 (string-match (shadow-cluster-regexp cluster1) primary2) 275 (string-match (shadow-cluster-regexp cluster1) primary2)
282 (string-equal site1 primary2))))) 276 (string-equal site1 primary2)))))
283 277
284(defun shadow-get-user (site)
285 "Return the default username for a SITE."
286 (ange-ftp-get-user (shadow-site-primary site)))
287
288;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 278;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
289;;; Filename manipulation 279;;; Filename manipulation
290;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 280;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
291 281
292(defun shadow-parse-fullname (fullname)
293 "Parse FULLNAME into (site user path) list.
294Leave it alone if it already is one. Return nil if the argument is
295not a full ange-ftp pathname."
296 (if (listp fullname)
297 fullname
298 (ange-ftp-ftp-name fullname)))
299
300(defun shadow-parse-name (name) 282(defun shadow-parse-name (name)
301 "Parse any NAME into (site user name) list. 283 "Parse any NAME into a `tramp-file-name' structure.
302Argument can be a simple name, full ange-ftp name, or already a hup list." 284Argument can be a simple name, remote file name, or already a
303 (or (shadow-parse-fullname name) 285`tramp-file-name' structure."
304 (list shadow-system-name 286 (cond
305 (user-login-name) 287 ((null name) nil)
306 name))) 288 ((tramp-file-name-p name) name)
307 289 ((file-remote-p name) (tramp-dissect-file-name name))
308(defsubst shadow-make-fullname (host user name) 290 ((shadow-local-file name)
309 "Make an ange-ftp style fullname out of HOST, USER (optional), and NAME. 291 (make-tramp-file-name
310This is probably not as general as it ought to be." 292 :host (shadow-site-name shadow-system-name)
311 (concat "/" 293 :localname (shadow-local-file name)))
312 (if user (concat user "@")) 294 ;; Cluster name.
313 host ":" 295 ((string-match "^/\\([^:/]+\\):\\([^:]*\\)$" name)
314 name)) 296 (let ((name (match-string 1 name))
297 (file (match-string 2 name)))
298 (when (shadow-get-cluster name)
299 (make-tramp-file-name :host name :localname file))))))
300
301(defsubst shadow-make-fullname (hup &optional host name)
302 "Make a Tramp style fullname out of HUP, a `tramp-file-name' structure.
303Replace HOST, and NAME when non-nil."
304 (let ((hup (copy-tramp-file-name hup)))
305 (when host (setf (tramp-file-name-host hup) host))
306 (when name (setf (tramp-file-name-localname hup) name))
307 (if (null (tramp-file-name-method hup))
308 (format
309 "/%s:%s" (tramp-file-name-host hup) (tramp-file-name-localname hup))
310 (tramp-make-tramp-file-name
311 (tramp-file-name-method hup)
312 (tramp-file-name-user hup)
313 (tramp-file-name-domain hup)
314 (tramp-file-name-host hup)
315 (tramp-file-name-port hup)
316 (tramp-file-name-localname hup)
317 (tramp-file-name-hop hup)))))
315 318
316(defun shadow-replace-name-component (fullname newname) 319(defun shadow-replace-name-component (fullname newname)
317 "Return FULLNAME with the name component changed to NEWNAME." 320 "Return FULLNAME with the name component changed to NEWNAME."
318 (let ((hup (shadow-parse-fullname fullname))) 321 (concat (file-remote-p fullname) newname))
319 (shadow-make-fullname (nth 0 hup) (nth 1 hup) newname)))
320 322
321(defun shadow-local-file (file) 323(defun shadow-local-file (file)
322 "If FILE is at this site, remove /user@host part. 324 "If FILE is not remote, return it.
323If refers to a different system or a different user on this system, 325If it refers to a different system, return nil."
324return nil." 326 (cond
325 (let ((hup (shadow-parse-fullname file))) 327 ((null file) nil)
326 (cond ((null hup) file) 328 ;; `tramp-file-name' structure.
327 ((and (shadow-site-match (nth 0 hup) shadow-system-name) 329 ((and (tramp-file-name-p file) (null (tramp-file-name-method file)))
328 (string-equal (nth 1 hup) (user-login-name))) 330 (tramp-file-name-localname file))
329 (nth 2 hup)) 331 ((tramp-file-name-p file) nil)
330 (t nil)))) 332 ;; Local host name.
333 ((string-match
334 (format "^%s\\([^:]*\\)$" (regexp-quote shadow-system-name)) file)
335 (match-string 1 file))
336 ;; Cluster name.
337 ((and (string-match "^/\\([^:/]+\\):\\([^:]*\\)$" file)
338 (shadow-get-cluster (match-string 1 file)))
339 (let ((file (match-string 2 file))
340 (primary
341 (shadow-cluster-primary
342 (shadow-get-cluster (match-string 1 file)))))
343 (when (string-equal primary shadow-system-name) (setq primary nil))
344 (shadow-local-file (concat primary file))))
345 ;; Local name.
346 ((null (file-remote-p file)) file)))
331 347
332(defun shadow-expand-cluster-in-file-name (file) 348(defun shadow-expand-cluster-in-file-name (file)
333 "If hostname part of FILE is a cluster, expand it to cluster's primary hostname. 349 "If hostname part of FILE is a cluster, expand it to cluster's primary hostname.
334Will return the name bare if it is a local file." 350Will return the name bare if it is a local file."
335 (let ((hup (shadow-parse-name file))) 351 (when (stringp file)
336 (cond ((null hup) file) 352 (cond
337 ((shadow-local-file hup)) 353 ;; Local file.
338 ((shadow-make-fullname (shadow-site-primary (nth 0 hup)) 354 ((shadow-local-file file))
339 (nth 1 hup) 355 ;; Cluster name.
340 (nth 2 hup)))))) 356 ((string-match "^\\(/[^:/]+:\\)[^:]*$" file)
357 (let ((primary
358 (save-match-data
359 (shadow-cluster-primary
360 (shadow-get-cluster
361 (shadow-site-name (match-string 1 file)))))))
362 (if (not primary)
363 file
364 (setq file (replace-match primary nil nil file 1))
365 (or (shadow-local-file file) file))))
366 (t file))))
341 367
342(defun shadow-expand-file-name (file &optional default) 368(defun shadow-expand-file-name (file &optional default)
343 "Expand file name and get FILE's true name." 369 "Expand file name and get FILE's true name."
@@ -352,46 +378,50 @@ true."
352 (homedir (if (shadow-local-file hup) 378 (homedir (if (shadow-local-file hup)
353 shadow-homedir 379 shadow-homedir
354 (file-name-as-directory 380 (file-name-as-directory
355 (nth 2 (shadow-parse-fullname 381 (file-local-name
356 (expand-file-name 382 (expand-file-name (shadow-make-fullname hup nil "~"))))))
357 (shadow-make-fullname 383 (suffix (shadow-suffix homedir (tramp-file-name-localname hup)))
358 (nth 0 hup) (nth 1 hup) "~"))))))) 384 (cluster (shadow-site-cluster (shadow-make-fullname hup nil ""))))
359 (suffix (shadow-suffix homedir (nth 2 hup))) 385 (when cluster
360 (cluster (shadow-site-cluster (nth 0 hup)))) 386 (setf (tramp-file-name-method hup) nil
387 (tramp-file-name-host hup) (shadow-cluster-name cluster)))
361 (shadow-make-fullname 388 (shadow-make-fullname
362 (if cluster 389 hup nil
363 (shadow-cluster-name cluster)
364 (nth 0 hup))
365 (nth 1 hup)
366 (if suffix 390 (if suffix
367 (concat "~/" suffix) 391 (concat "~/" suffix)
368 (nth 2 hup))))) 392 (tramp-file-name-localname hup)))))
369 393
370(defun shadow-same-site (pattern file) 394(defun shadow-same-site (pattern file)
371 "True if the site of PATTERN and of FILE are on the same site. 395 "True if the site of PATTERN and of FILE are on the same site.
372If usernames are supplied, they must also match exactly. PATTERN and FILE may 396PATTERN and FILE may be Tramp vectors, or remote file names.
373be lists of host, user, name, or ange-ftp file names. FILE may also be just a 397FILE may also be just a local filename."
374local filename." 398 (let ((pattern-sup (shadow-parse-name pattern))
375 (let ((pattern-sup (shadow-parse-fullname pattern))
376 (file-sup (shadow-parse-name file))) 399 (file-sup (shadow-parse-name file)))
377 (and 400 (and
378 (shadow-site-match (nth 0 pattern-sup) (nth 0 file-sup)) 401 (shadow-site-match
379 (or (null (nth 1 pattern-sup)) 402 (tramp-file-name-host pattern-sup) (tramp-file-name-host file-sup))
380 (string-equal (nth 1 pattern-sup) (nth 1 file-sup)))))) 403 (or (null (tramp-file-name-user pattern-sup))
404 (string-equal
405 (tramp-file-name-user pattern-sup)
406 (tramp-file-name-user file-sup))))))
381 407
382(defun shadow-file-match (pattern file &optional regexp) 408(defun shadow-file-match (pattern file &optional regexp)
383 "Return t if PATTERN matches FILE. 409 "Return t if PATTERN matches FILE.
384If REGEXP is supplied and non-nil, the file part of the pattern is a regular 410If REGEXP is supplied and non-nil, the file part of the pattern is a regular
385expression, otherwise it must match exactly. The sites and usernames must 411expression, otherwise it must match exactly. The sites must
386match---see `shadow-same-site'. The pattern must be in full ange-ftp format, 412match---see `shadow-same-site'. The pattern must be in full Tramp format,
387but the file can be any valid filename. This function does not do any 413but the file can be any valid filename. This function does not do any
388filename expansion or contraction, you must do that yourself first." 414filename expansion or contraction, you must do that yourself first."
389 (let* ((pattern-sup (shadow-parse-fullname pattern)) 415 (let* ((pattern-sup (shadow-parse-name pattern))
390 (file-sup (shadow-parse-name file))) 416 (file-sup (shadow-parse-name file)))
391 (and (shadow-same-site pattern-sup file-sup) 417 (and (shadow-same-site pattern-sup file-sup)
392 (if regexp 418 (if regexp
393 (string-match (nth 2 pattern-sup) (nth 2 file-sup)) 419 (string-match
394 (string-equal (nth 2 pattern-sup) (nth 2 file-sup)))))) 420 (tramp-file-name-localname pattern-sup)
421 (tramp-file-name-localname file-sup))
422 (string-equal
423 (tramp-file-name-localname pattern-sup)
424 (tramp-file-name-localname file-sup))))))
395 425
396;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 426;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
397;;; User-level Commands 427;;; User-level Commands
@@ -405,30 +435,34 @@ one of them is sufficient to update the file on all of them. Clusters are
405defined by a name, the network address of a primary host (the one we copy 435defined by a name, the network address of a primary host (the one we copy
406files to), and a regular expression that matches the hostnames of all the 436files to), and a regular expression that matches the hostnames of all the
407sites in the cluster." 437sites in the cluster."
408 (interactive (list (completing-read "Cluster name: " shadow-clusters () ()))) 438 (interactive (list (completing-read "Cluster name: " shadow-clusters)))
409 (let* ((old (shadow-get-cluster name)) 439 (let* ((old (shadow-get-cluster name))
410 (primary (read-string "Primary host: " 440 (primary (let (try-primary)
411 (if old (shadow-cluster-primary old) 441 (while (not
412 name))) 442 (or
413 (regexp (let (try-regexp) 443 (string-equal
414 (while (not 444 (setq try-primary
415 (string-match
416 (setq try-regexp
417 (read-string 445 (read-string
418 "Regexp matching all host names: " 446 "Primary host: "
419 (if old (shadow-cluster-regexp old) 447 (if old (shadow-cluster-primary old)
420 (shadow-regexp-superquote primary)))) 448 name)))
421 primary)) 449 shadow-system-name)
422 (message "Regexp doesn't include the primary host!") 450 (file-remote-p try-primary)))
423 (sit-for 2)) 451 (message "Not a valid primary!")
424 try-regexp)) 452 (sit-for 2))
425; (username (read-no-blanks-input 453 try-primary))
426; (format "Username (default %s): " 454 (regexp (let (try-regexp)
427; (shadow-get-user primary)) 455 (while (not
428; (if old (or (shadow-cluster-username old) "") 456 (string-match
429; (user-login-name)))) 457 (setq try-regexp
430 ) 458 (read-string
431; (if (string-equal "" username) (setq username nil)) 459 "Regexp matching all host names: "
460 (if old (shadow-cluster-regexp old)
461 (shadow-regexp-superquote primary))))
462 primary))
463 (message "Regexp doesn't include the primary host!")
464 (sit-for 2))
465 try-regexp)))
432 (shadow-set-cluster name primary regexp))) 466 (shadow-set-cluster name primary regexp)))
433 467
434;;;###autoload 468;;;###autoload
@@ -438,20 +472,14 @@ It may have different filenames on each site. When this file is edited, the
438new version will be copied to each of the other locations. Sites can be 472new version will be copied to each of the other locations. Sites can be
439specific hostnames, or names of clusters (see `shadow-define-cluster')." 473specific hostnames, or names of clusters (see `shadow-define-cluster')."
440 (interactive) 474 (interactive)
441 (let* ((hup (shadow-parse-fullname 475 (let* ((hup (shadow-parse-name
442 (shadow-contract-file-name (buffer-file-name)))) 476 (shadow-contract-file-name (buffer-file-name))))
443 (name (nth 2 hup)) 477 (name (tramp-file-name-localname hup))
444 user site group) 478 site group)
445 (while (setq site (shadow-read-site)) 479 (while (setq site (shadow-read-site))
446 (setq user (read-string (format "Username (default %s): " 480 (setq name (read-string "Filename: " name)
447 (shadow-get-user site))) 481 hup (shadow-parse-name (shadow-contract-file-name name))
448 name (read-string "Filename: " name)) 482 group (cons (shadow-make-fullname hup site) group)))
449 (setq group (cons (shadow-make-fullname site
450 (if (string-equal "" user)
451 (shadow-get-user site)
452 user)
453 name)
454 group)))
455 (setq shadow-literal-groups (cons group shadow-literal-groups))) 483 (setq shadow-literal-groups (cons group shadow-literal-groups)))
456 (shadow-write-info-file)) 484 (shadow-write-info-file))
457 485
@@ -468,19 +496,12 @@ function). Each site can be either a hostname or the name of a cluster (see
468 "Filename regexp: " 496 "Filename regexp: "
469 (if (buffer-file-name) 497 (if (buffer-file-name)
470 (shadow-regexp-superquote 498 (shadow-regexp-superquote
471 (nth 2 499 (file-local-name (buffer-file-name))))))
472 (shadow-parse-name 500 site sites)
473 (shadow-contract-file-name
474 (buffer-file-name))))))))
475 site sites usernames)
476 (while (setq site (shadow-read-site)) 501 (while (setq site (shadow-read-site))
477 (setq sites (cons site sites)) 502 (setq sites (cons site sites)))
478 (setq usernames
479 (cons (read-string (format "Username for %s: " site)
480 (shadow-get-user site))
481 usernames)))
482 (setq shadow-regexp-groups 503 (setq shadow-regexp-groups
483 (cons (shadow-make-group regexp sites usernames) 504 (cons (shadow-make-group regexp sites)
484 shadow-regexp-groups)) 505 shadow-regexp-groups))
485 (shadow-write-info-file))) 506 (shadow-write-info-file)))
486 507
@@ -537,14 +558,14 @@ permanently, remove the group from `shadow-literal-groups' or
537;;; Internal functions 558;;; Internal functions
538;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 559;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
539 560
540(defun shadow-make-group (regexp sites usernames) 561(defun shadow-make-group (regexp sites)
541 "Make a description of a file group--- 562 "Make a description of a file group---
542actually a list of regexp ange-ftp file names---from REGEXP (name of file to 563actually a list of regexp Tramp file names---from REGEXP (name of file to
543be shadowed), list of SITES, and corresponding list of USERNAMES for each 564be shadowed), and list of SITES"
544site."
545 (if sites 565 (if sites
546 (cons (shadow-make-fullname (car sites) (car usernames) regexp) 566 (cons (shadow-make-fullname
547 (shadow-make-group regexp (cdr sites) (cdr usernames))) 567 (shadow-parse-name (shadow-site-primary (car sites))) nil regexp)
568 (shadow-make-group regexp (cdr sites)))
548 nil)) 569 nil))
549 570
550(defun shadow-copy-file (s) 571(defun shadow-copy-file (s)
@@ -601,7 +622,9 @@ Consider them as regular expressions if third arg REGEXP is true."
601 (car groups)))) 622 (car groups))))
602 (append (cond ((equal nonmatching (car groups)) nil) 623 (append (cond ((equal nonmatching (car groups)) nil)
603 (regexp 624 (regexp
604 (let ((realname (nth 2 (shadow-parse-fullname file)))) 625 (let ((realname
626 (tramp-file-name-localname
627 (shadow-parse-name file))))
605 (mapcar 628 (mapcar
606 (function 629 (function
607 (lambda (x) 630 (lambda (x)
@@ -636,9 +659,8 @@ PAIR must be `eq' to one of the elements of that list."
636Thus restores shadowfile's state from your last Emacs session. 659Thus restores shadowfile's state from your last Emacs session.
637Return t unless files were locked; then return nil." 660Return t unless files were locked; then return nil."
638 (interactive) 661 (interactive)
639 (if (and (fboundp 'file-locked-p) 662 (if (or (stringp (file-locked-p shadow-info-file))
640 (or (stringp (file-locked-p shadow-info-file)) 663 (stringp (file-locked-p shadow-todo-file)))
641 (stringp (file-locked-p shadow-todo-file))))
642 (progn 664 (progn
643 (message "Shadowfile is running in another Emacs; can't have two.") 665 (message "Shadowfile is running in another Emacs; can't have two.")
644 (beep) 666 (beep)
@@ -647,7 +669,7 @@ Return t unless files were locked; then return nil."
647 (save-current-buffer 669 (save-current-buffer
648 (when shadow-info-file 670 (when shadow-info-file
649 (set-buffer (setq shadow-info-buffer 671 (set-buffer (setq shadow-info-buffer
650 (find-file-noselect shadow-info-file))) 672 (find-file-noselect shadow-info-file 'nowarn)))
651 (when (and (not (buffer-modified-p)) 673 (when (and (not (buffer-modified-p))
652 (file-newer-than-file-p (make-auto-save-file-name) 674 (file-newer-than-file-p (make-auto-save-file-name)
653 shadow-info-file)) 675 shadow-info-file))
@@ -680,6 +702,7 @@ defined, the old hashtable info is invalid."
680 (if (not shadow-info-buffer) 702 (if (not shadow-info-buffer)
681 (setq shadow-info-buffer (find-file-noselect shadow-info-file))) 703 (setq shadow-info-buffer (find-file-noselect shadow-info-file)))
682 (set-buffer shadow-info-buffer) 704 (set-buffer shadow-info-buffer)
705 (setq buffer-read-only nil)
683 (delete-region (point-min) (point-max)) 706 (delete-region (point-min) (point-max))
684 (shadow-insert-var 'shadow-clusters) 707 (shadow-insert-var 'shadow-clusters)
685 (shadow-insert-var 'shadow-literal-groups) 708 (shadow-insert-var 'shadow-literal-groups)
@@ -692,6 +715,7 @@ With non-nil argument also saves the buffer."
692 (if (not shadow-todo-buffer) 715 (if (not shadow-todo-buffer)
693 (setq shadow-todo-buffer (find-file-noselect shadow-todo-file))) 716 (setq shadow-todo-buffer (find-file-noselect shadow-todo-file)))
694 (set-buffer shadow-todo-buffer) 717 (set-buffer shadow-todo-buffer)
718 (setq buffer-read-only nil)
695 (delete-region (point-min) (point-max)) 719 (delete-region (point-min) (point-max))
696 (shadow-insert-var 'shadow-files-to-copy) 720 (shadow-insert-var 'shadow-files-to-copy)
697 (if save (shadow-save-todo-file)))) 721 (if save (shadow-save-todo-file))))
@@ -765,24 +789,6 @@ look for files that have been changed and need to be copied to other systems."
765 (kill-emacs))) 789 (kill-emacs)))
766 790
767;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 791;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
768;;; Lucid Emacs compatibility
769;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
770
771;; This is on hold until someone tells me about a working version of
772;; map-ynp for Lucid Emacs.
773
774;(when (string-match "Lucid" emacs-version)
775; (require 'symlink-fix)
776; (require 'ange-ftp)
777; (require 'map-ynp)
778; (if (not (fboundp 'file-truename))
779; (fset 'shadow-expand-file-name
780; (symbol-function 'symlink-expand-file-name)))
781; (if (not (fboundp 'ange-ftp-ftp-name))
782; (fset 'ange-ftp-ftp-name
783; (symbol-function 'ange-ftp-ftp-name))))
784
785;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
786;;; Hook us up 792;;; Hook us up
787;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; 793;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
788 794
@@ -790,18 +796,10 @@ look for files that have been changed and need to be copied to other systems."
790(defun shadow-initialize () 796(defun shadow-initialize ()
791 "Set up file shadowing." 797 "Set up file shadowing."
792 (interactive) 798 (interactive)
793 (if (null shadow-homedir) 799 (setq shadow-homedir
794 (setq shadow-homedir 800 (file-name-as-directory (shadow-expand-file-name shadow-homedir))
795 (file-name-as-directory (shadow-expand-file-name "~")))) 801 shadow-info-file (shadow-expand-file-name shadow-info-file)
796 (if (null shadow-info-file) 802 shadow-todo-file (shadow-expand-file-name shadow-todo-file))
797 (setq shadow-info-file
798 ;; FIXME: Move defaults to their defcustom.
799 (shadow-expand-file-name
800 (locate-user-emacs-file "shadows" ".shadows"))))
801 (if (null shadow-todo-file)
802 (setq shadow-todo-file
803 (shadow-expand-file-name
804 (locate-user-emacs-file "shadow_todo" ".shadow_todo"))))
805 (if (not (shadow-read-files)) 803 (if (not (shadow-read-files))
806 (progn 804 (progn
807 (message "Shadowfile information files not found - aborting") 805 (message "Shadowfile information files not found - aborting")