---
trashcan
(1) Doesn't respect make-auto-save-file-name
(2) Doesn't preserve the marked files (*) in dired buffers when files are
(3) Richard Stallman told me that code that is to be distributed with Emacs
&rest
(defun trashcan--is-a-windows-system ()
(memq system-type '(windows-nt ms-dos)))
(defun trashcan--is-a-unix-system ()
(not (trashcan--is-a-windows-system)))
(defvar trashcan-dirname (if (trashcan--is-a-windows-system) "TRASHCAN" ".Trash")
"This variable specifies what directory to move files into with the
\"x\" key in dired mode. Do not add any prefix to the directory such
as \"~/\" or \"/\".
If this is a Windows system, the trashcan directories are located at
the following regexp:
(concat \"^[a-zA-Z]:/\" (regexp-quote trashcan-dirname))
If this is a Unix system, there is one trashcan directory for
each user and are located at the following places:
(concat \"~/\" trashcan-dirname)
Also there are trashcan directories in the following regexp:
\"/media/[0-9A-Za-z]+/[0-9A-Za-z]+\" for one trashcan directory
in each Lubuntu drive.
In Windows, DO NOT give this the same name as the windows RECYCLER
directory as this will confuse the hell out of Windows.
")
(defvar trashcan-patch-delete-stuff-p t
"This variable if set causes the functions delete-file and
delete-directory to be patched to use the trashcan directories
instead of deleting files permenantely."
)
(defun trashcan--split (file)
(setq file (expand-file-name file))
(if (string-match "^[a-zA-Z]:/" file)
(cons (substring file 0 3) (substring file 3))
(if (string-match "/home/www/C/" file)
(setq file (cons "/media/www/C1TB/"
(substring file (match-end 0))))
(if (string-match "/media/\\([0-9A-Za-z]+\\)/\\([0-9A-Za-z]+/\\)" file)
(cons (substring file 0 (match-end 2))
(substring file (match-end 2))
)
(if (string-match (concat "\\(" (expand-file-name "~/") "\\)") file)
(cons (expand-file-name "~/")
(substring file (match-end 1))
)
))))
)
(defun trashcan--encode (file)
(let* ((s (trashcan--split file))
(d (car s))
(f (cdr s)))
(let ((i 0))
(while (< i (length f))
(if (eq ?/ (aref f i))
(aset f i ?!))
(incf i)))
(let ((new (concat d trashcan-dirname "/" f)))
(if (file-exists-p new)
(let ((count 1)
(result nil))
(while (file-exists-p (setq result (concat new "." (format "%d" count))))
(incf count))
result)
new))))
(defun trashcan--decode (file)
(cond
((string-match (concat "^[a-zA-Z]:/" (regexp-quote trashcan-dirname)) file)
(let ((d (substring file 0 3))
(f (substring file (+ 4 (length trashcan-dirname))))
(i 0))
(while (< i (length f))
(if (eq ?! (aref f i))
(aset f i ?/))
(incf i))
(concat d f)))
((string-match (concat "^/media/[A-Za-z0-9]+/\\([A-Za-z0-9]+/\\)" (regexp-quote trashcan-dirname) "/\\(.*\\)$") file)
(let ((y (substring file 0 (match-end 1)))
(x (substring file (match-beginning 2) (match-end 2)))
(i 0))
(while (< i (length x))
(if (eq ?! (aref x i))
(aset x i ?/))
(incf i))
(concat y x))
)
(t
(assert (string-match (concat (expand-file-name "~/") (regexp-quote trashcan-dirname) "/\\(.*\\)$") file))
(let ((x (substring file (match-beginning 1) (match-end 1)))
(i 0))
(while (< i (length x))
(if (eq ?! (aref x i))
(aset x i ?/))
(incf i))
(concat "~/" x))
)
)
)
(defmacro trashcan--walk-buffers (sexp)
(save-window-excursion
(let ((trashcan--walk-buffers--ptr (buffer-list)))
(while trashcan--walk-buffers--ptr
(set-buffer (car trashcan--walk-buffers--ptr))
(eval sexp)
(setq trashcan--walk-buffers--ptr (cdr trashcan--walk-buffers--ptr))))))
(defun trashcan--delete-dangerous (file-or-directory)
"Is better than the built-in function delete-file in that it also deletes directories,
therefore is more dangerous than delete-file"
(if (file-exists-p file-or-directory)
(shell-command (concat "rm -rvf \"" file-or-directory "\""))
))
(defun trashcan--in-windows-trashcan (filename)
"Returns the relevant windows trashcan directory or nil if there isn't one"
(setq filename (expand-file-name filename))
(let ((dirname (file-name-directory filename)))
(if (string-match (concat "^\\([a-zA-Z]:/" (regexp-quote trashcan-dirname) "\\)") dirname)
(substring dirname (match-beginning 1) (match-end 1)))))
(defun trashcan--in-unix-trashcan (filename)
"Returns the relevant unix trashcan directory or nil if there isn't one"
(setq filename (expand-file-name filename))
(if (not (string-match "/$" filename))
(setq filename (concat filename "/")))
(setq filename (expand-file-name filename))
(when (string-match "^/home/www/C/TRASHCAN/" filename)
(setq filename (concat "/media/www/C1TB/TRASHCAN/" (substring filename (match-end 0)))))
(let ((dirname (file-name-directory filename))
(dir-regexp (concat "/media/\\([A-Za-z0-9]+/\\)\\([A-Z0-9]+/\\)" (regexp-quote trashcan-dirname) "/")))
(if (string-match dir-regexp filename)
(progn
(setq s (concat (substring filename 0 (match-end 2)) trashcan-dirname "/"))
(make-directory (file-name-directory s) 'PARENTS)
s)
(if (string-match (concat "^" (expand-file-name "~/") (regexp-quote trashcan-dirname) "/") dirname)
(progn
(make-directory (concat "~/" trashcan-dirname "/") 'PARENTS)
(concat (expand-file-name "~/") trashcan-dirname "/"))))))
(defun trashcan--in-trashcan (filename)
(or (trashcan--in-windows-trashcan filename)
(trashcan--in-unix-trashcan filename)))
(defun trashcan--after-permanent-deletion ()
(let (dirname)
(cond
((setq dirname (trashcan--in-windows-trashcan default-directory)))
((setq dirname (trashcan--in-unix-trashcan default-directory)))
(t
(error "Should never happen")))
(trashcan--walk-buffers
(if (or (and (buffer-file-name)
(string-match (concat "^" dirname) default-directory)
(y-or-n-p (concat "Kill buffer " (buffer-file-name) " too? ")))
(and (eq major-mode 'dired-mode) (not (file-exists-p default-directory))))
(kill-buffer nil)))))
(setq trashcan--global-refresh-count 1)
(defun trashcan--rename-to-trash (file-list)
(let ((dir nil))
(let ((ptr file-list))
(while ptr
(let* ((new-name (trashcan--encode (car ptr)))
(fnd (file-name-directory new-name)))
(if (not (file-exists-p fnd))
(make-directory fnd 'PARENTS))
(setq dir fnd)
(rename-file (car ptr) new-name))
(setq ptr (cdr ptr)))
(setq ptr file-list)
(incf trashcan--global-refresh-count)
(if (not (boundp 'trashcan--refresh-count))
(setq-default trashcan--refresh-count nil))
(while ptr
(trashcan--walk-buffers
(progn
(make-local-variable 'trashcan--refresh-count)
(if (and (buffer-file-name)
(string-match (concat "^" (regexp-quote (car ptr))) (buffer-file-name))
(not (eq trashcan--global-refresh-count trashcan--refresh-count)))
(set-visited-file-name (trashcan--encode (car ptr)) 'NO-QUERY))
(setq trashcan--refresh-count trashcan--global-refresh-count)))
(let ((dirname (file-name-directory (car ptr))))
(trashcan--walk-buffers
(progn
(make-local-variable 'trashcan--refresh-count)
(if (and (eq major-mode 'dired-mode)
(string-match (concat "^" (regexp-quote dirname) "/?$") default-directory)
(not (eq trashcan--global-refresh-count trashcan--refresh-count)))
(revert-buffer))
(set (make-local-variable 'trashcan--refresh-count) trashcan--global-refresh-count))))
(setq ptr (cdr ptr))))
(if (trashcan--is-a-windows-system)
(setq dir (downcase dir)))
(trashcan--walk-buffers
(if (and (eq major-mode 'dired-mode) (not (file-exists-p (expand-file-name default-directory))))
(kill-buffer nil)))
(trashcan--walk-buffers
(if (and (eq major-mode 'dired-mode) (string=
(if (trashcan--is-a-windows-system)
(downcase default-directory)
default-directory) dir))
(revert-buffer)))))
(require 'dired)
(defadvice dired-internal-do-deletions (around trashcan-stub activate)
"This function replaces the function of the same name in the standard Emacs file dired.el"
(if (not (eq major-mode 'dired-mode))
(error "You must be in dired mode to execute dired-internal-do-deletions"))
(if (string-match "^/[a-z0-9]*:" (car (car l)))
ad-do-it)
(let ((ptr l))
(while ptr
(if (or (string-match "/\\./?$" (caar ptr)) (string-match "/\\.\\./?$" (caar ptr)))
(error "You cannot delete the directories . or .."))
(setq ptr (cdr ptr))))
(let ((ptr l))
(while ptr
(if (or (string-match (concat "^[a-zA-Z]:/" (regexp-quote trashcan-dirname) "/?$") (caar ptr))
(string-match (concat "/media/www/\\([a-zA-Z0-9]+\\)/" (regexp-quote trashcan-dirname) "/?$") (caar ptr))
(string-match (concat "^" (expand-file-name "~/") (regexp-quote trashcan-dirname) "/?$") (caar ptr)))
(progn
(error (concat "You cannot move a trashcan directory (%s) into a trashcan directory "
"(Try \"rm -r\" instead)")
trashcan-dirname)))
(setq ptr (cdr ptr))))
(let ((in-trash (trashcan--in-trashcan default-directory))
(files (mapcar (function car) l)))
(setq files (nreverse (mapcar (function dired-make-relative) files)))
(if in-trash
(if (dired-mark-pop-up " *Deletions*"
'delete
files
dired-deletion-confirmer
(format "Permanently Delete %s " (dired-mark-prompt arg files)))
(let ((ptr l))
(while ptr
(trashcan--delete-dangerous (caar ptr))
(message "Deleted file %s " (caar ptr))
(setq ptr (cdr ptr)))
(revert-buffer)
(trashcan--after-permanent-deletion)))
(if (dired-mark-pop-up " *Deletions*"
'delete
files
dired-deletion-confirmer
(format "Move to trashcan %s " (dired-mark-prompt arg files)))
(let ((ptr l)
(list nil))
(while ptr
(setq list (cons (caar ptr) list))
(setq ptr (cdr ptr)))
(trashcan--rename-to-trash list)
(revert-buffer))
))))
(defun trashcan--make-absolute (filename)
(setq filename (expand-file-name filename))
(if (string-match "/$" filename)
(setq filename (substring filename 0 (1- (length filename)))))
(if (not (or (string-match "^[a-zA-Z]:/" filename)
(string-match "^/" filename)))
(concat (expand-file-name default-directory) filename)
filename))
(defadvice delete-file (around trashcan-stub activate)
"Adds trashcan functionality to delete-file. If given an autosave
file, it behaves like the default setting of delete-file. See the
variable trashcan-patch-delete-stuff-p"
(if (or (not trashcan-patch-delete-stuff-p)
(string-match "^#.*#$" (file-name-nondirectory filename))
(and (file-name-directory filename)
(not (or (string-match (concat "^" (expand-file-name "~") "/") (file-name-directory filename))
(string-match "^~/" (file-name-directory filename))))))
(progn
ad-do-it)
(setq filename (trashcan--make-absolute filename))
(if (trashcan--in-trashcan filename)
(progn
(trashcan--delete-dangerous filename)
(trashcan--after-permanent-deletion))
(trashcan--rename-to-trash (list filename)))))
(defadvice delete-directory (around trashcan-stub activate)
"Adds trashcan functionality to delete-directory. If given an
autosave file, it behaves like the default setting of delete-file.
See the variable trashcan-patch-delete-stuff-p"
(if (or (not trashcan-patch-delete-stuff-p)
(string-match "^#.*#$" (file-name-nondirectory directory))
(and (file-name-directory directory)
(not (or (string-match (concat "^" (expand-file-name "~") "/") (file-name-directory directory))
(string-match "^~/" (file-name-directory directory))))))
ad-do-it
(setq directory (trashcan--make-absolute directory))
(if (trashcan--in-trashcan directory)
(progn
(trashcan--delete-dangerous directory)
(trashcan--after-permanent-deletion))
(trashcan--rename-to-trash (list directory)))))
(defun trashcan-restore ()
(interactive)
(if (not (trashcan--in-trashcan default-directory))
(error "You must be in the trashcan directory (%s) to execute this command" trashcan-dirname))
(let* ((list (dired-get-marked-files))
(ptr list))
(while ptr
(let* ((source (car ptr))
(target (trashcan--decode source))
(fnd (file-name-directory target)))
(if (file-exists-p target)
(error "File %s already exists" target))
(make-directory fnd 'PARENTS)
(rename-file source target)
(trashcan--walk-buffers
(if (and (buffer-file-name) (string-match (concat "^" (regexp-quote source)) (buffer-file-name)))
(let ((n (substring (buffer-file-name) (length source))))
(set-visited-file-name (concat target n) 'NO-QUERY))))
(trashcan--walk-buffers
(if (and (eq major-mode 'dired-mode) (string= fnd (expand-file-name default-directory)))
(revert-buffer)))
(trashcan--walk-buffers
(if (and (eq major-mode 'dired-mode) (not (file-exists-p (expand-file-name default-directory))))
(kill-buffer nil)))
)
(setq ptr (cdr ptr))))
(trashcan--walk-buffers
(if (and (eq major-mode 'dired-mode) (trashcan--in-trashcan default-directory))
(revert-buffer))))
(defun trashcan-empty ()
"Careful when using this command as it cannot be undone"
(interactive)
(cond
((not (trashcan--in-trashcan default-directory))
(error "You must be in the trashcan to execute this command"))
((not (eq major-mode 'dired-mode))
(error "You must be in dired mode to execute this command"))
(t
(if (yes-or-no-p "Really empty trashcan? ")
(let (dirname)
(cond
((setq dirname (trashcan--in-windows-trashcan default-directory)))
((setq dirname (trashcan--in-unix-trashcan default-directory)))
(t
(error "Should never happen")))
(save-window-excursion
(trashcan--delete-dangerous dirname))
(make-directory dirname 'PARENTS)
(revert-buffer)
(trashcan--after-permanent-deletion))))))
(provide 'trashcan)