(in-package :cl-xdg-trash) (declaim (ftype (function () integer) getuid)) (defun getuid () "Return the current user's UID." (osicat-posix:getuid)) (declaim (ftype (function (&key (:homedir (or pathname string null))) pathname) xdg-data-home)) (defun xdg-data-home (&key homedir) (let ((env (uiop:getenv "XDG_DATA_HOME"))) (cond (env (uiop:parse-native-namestring (pathname env) :ensure-directory t)) ((not homedir) (merge-pathnames #P".local/share/" (user-homedir-pathname))) ((pathnamep homedir) (merge-pathnames #P".local/share/" (uiop:ensure-directory-pathname homedir))) (t (merge-pathnames #P".local/share/" (uiop:parse-native-namestring homedir :ensure-directory t)))))) (declaim (ftype (function (&key (:homedir (or pathname string null))) pathname) user-home-trash-directory)) (defun user-home-trash-directory (&key homedir) (merge-pathnames #P"Trash/" (xdg-data-home :homedir homedir))) (declaim (ftype (function ((or string pathname)) list) find-trash-dirs-for-toplevel)) (defun find-trash-dirs-for-toplevel (toplevel) "List the trash directories that exist under TOPLEVEL." (let ((top-path (ensure-nonwild-pathname toplevel :ensure-directory t)) found) (let ((dir (merge-pathnames #P".Trash" top-path))) (when (uiop:directory-exists-p dir) (push dir found))) (let ((uid (getuid))) (when uid (let ((dir (merge-pathnames (pathname (format nil ".Trash-~D" uid)) top-path))) (when (uiop:directory-exists-p dir) (push dir found))))) found)) (declaim (ftype (function () list) list-toplevel-trash-directories)) (defun list-toplevel-trash-directories () "List all known trash directories other than the user's trash directory." (mapcan 'find-trash-dirs-for-toplevel (list-mountpoints))) (declaim (ftype (function () list) list-trash-directories)) (defun list-trash-directories () "List all known trash directories." (cons (user-home-trash-directory) (list-toplevel-trash-directories))) (declaim (ftype (function ((or pathname string) &key (:include-self t)) t) path-in-home-directory-p)) (defun path-in-home-directory-p (path &key include-self) "Return non-nil if PATH is in the user's home directory (or is the user's home directory)." (let ((path (ensure-nonwild-pathname path :ensure-directory t)) (home (user-homedir-pathname))) (or (and include-self (uiop:pathname-equal path home)) (uiop:subpathp path home)))) (declaim (ftype (function ((or pathname string)) pathname) trash-directory-for-file)) (defun trash-directory-for-file (path) "Return the trash directory into which PATH should be trashed." (let* ((res-path (ensure-nonwild-pathname path)) (root (find-filesystem-root res-path))) (or (and (path-in-home-directory-p res-path) (uiop:pathname-equal (find-filesystem-root (user-homedir-pathname)) root) (car (find-trash-dirs-for-toplevel root))) (user-home-trash-directory)))) (declaim (ftype (function ((or pathname string) &optional t) t) trash-file)) (defun trash-file (path &optional (update-size-cache t)) "Move PATH to the trash. Specifically, move it to the proper trash as specified by the XDG standard. If UPDATE-SIZE-CACHE is non-nil (the default) also update the directory size cache." (let* ((path (merge-pathnames (ensure-nonwild-pathname path) (uiop:getcwd))) (trash-directory (trash-directory-for-file path)) (trashinfo (make-trashinfo-for trash-directory path)) (files-dir (ensure-directories-exist (merge-pathnames #P"files/" trash-directory) :verbose nil))) (osicat-posix:rename (uiop:native-namestring path) (uiop:native-namestring (merge-pathnames (make-pathname :name (trashinfo-name trashinfo)) files-dir))) (when update-size-cache (trashed-file-size trash-directory (trashinfo-name trashinfo))))) (declaim (ftype (function ((or pathname string list)) list) normalize-trash-directories) (inline normalize-trash-directories)) (defun normalize-trash-directories (trash-directories) "Normalize TRASH-DIRECTORIES to a non-wild list of pathnames." (if (or (stringp trash-directories) (pathnamep trash-directories)) (list (ensure-nonwild-pathname trash-directories :ensure-directory t)) (mapcar #'(lambda (elt) (ensure-nonwild-pathname elt :ensure-directory t)) trash-directories))) (declaim (ftype (function (pathname) list) list-trashed-files-for-directory)) (defun list-trasheds-file-for-directory (trash-directory) "Return a list of trashinfo objects for every trashed file in TRASH-DIRECTORY." (let ((info-dir (merge-pathnames #P"info/" trash-directory))) (mapcan #'(lambda (path) (let ((name (file-or-dir-namestring path))) (when (uiop:string-suffix-p name ".trashinfo") (list (parse-trashinfo-file trash-directory (subseq name 0 (- (length name) (length ".trashinfo")))))))) (uiop:directory-files info-dir)))) (declaim (ftype (function (&optional (or pathname string list)) list) normalize-trash-directories)) (defun list-trashed-files (&optional (trash-directories (list-trash-directories))) "Return a list of trashinfo objects for each trashed file in TRASH-DIRECTORIES. TRASH-DIRECTORIES can also be a single path." (mapcan #'list-trashed-file-for-directory (normalize-trash-directories trash-directories))) (declaim (ftype (function (trashinfo &optional t) t) restore-file)) (defun restore-file (trashinfo &optional (update-size-cache t)) "Restore the file pointed to by TRASHINFO. If UPDATE-SIZE-CACHE is non-nil (the default), also update the directory size cache." (osicat-posix:rename (uiop:native-namestring (trashinfo-trashed-file trashinfo)) (uiop:native-namestring (trashinfo-original-path trashinfo))) (handler-bind ;; attempt to re-trash the file in case of error ((t #'(lambda (e) (osicat-posix:rename (uiop:native-namestring (trashinfo-original-path trashinfo)) (uiop:native-namestring (trashinfo-trashed-file trashinfo))) (signal e)))) (delete-file (trashinfo-info-file trashinfo)) (when update-size-cache (trashed-file-size (trashinfo-trash-directory trashinfo) (trashinfo-name trashinfo)))))