diff --git a/doc/org-manual.org b/doc/org-manual.org index 59591894d..5e69ef074 100644 --- a/doc/org-manual.org +++ b/doc/org-manual.org @@ -8004,15 +8004,15 @@ mentioning. When attaching files to a heading it will be assigned a tag according to what is set here. -- ~org-attach-id-to-path-function~ :: - #+vindex: org-attach-id-to-path-function +- ~org-attach-id-to-path-function-list~ :: + #+vindex: org-attach-id-to-path-function-list When =ID= is used for attachments, the ID is parsed into a part of a - directory-path. See ~org-attach-id-folder-format~ for the default - function. Define a new one and add it to - ~org-attach-id-to-path-function~ if you want the folder structure - any other way. Note that modifying this makes org-attach dependent - on your function also for opening attachments, not only setting - them! + directory-path. See ~org-attach-id-uuid-folder-format~ for the + default function. Define a new one and add it as first element in + ~org-attach-id-to-path-function-list~ if you want the folder + structure in any other way. All functions in this list will be + tried when resolving existing ID's into paths, to maintain backward + compatability with existing folders in your system. - ~org-attach-expert~ :: #+vindex: org-attach-expert diff --git a/etc/ORG-NEWS b/etc/ORG-NEWS index 0e07326cb..bdcfc24fd 100644 --- a/etc/ORG-NEWS +++ b/etc/ORG-NEWS @@ -178,8 +178,9 @@ precedence and will be used. One can now also choose to build attachment-directory-paths in a customized way. This is an advanced topic, but in some case it makes sense to parse an ID in a different way than the default one. Create -your own function and use it is ~org-attach-id-to-path-function~ if -you want to customize the ID-based folder structure. +your own function and add it to the beginning of +~org-attach-id-to-path-function~list~ if you want to customize the ID +based folder structure. If you've used ATTACH_DIR properties to manage attachments, use the following code to rename that property to DIR which supports the same @@ -411,6 +412,11 @@ the attachment dispatcher. If one chooses, it is now possible to create ID's based on timestamp (ISO8601) instead of UUID by changing org-id-method to ts. +For an improved folder structure when using timestamp as ID, make sure +to promote ~org-attach-id-ts-folder-format~ to the first element of +~org-attach-id-to-path-function-list~ in your configuration at the +same time. + *** New customization: ~org-id-locations-relative~ New customization to make the persisting of org-id-locations between sessions to store links to files as relative instead of absolute. The diff --git a/lisp/org-attach.el b/lisp/org-attach.el index 2138a2807..bc49be7fe 100644 --- a/lisp/org-attach.el +++ b/lisp/org-attach.el @@ -152,19 +152,33 @@ When set to `query', ask the user instead." (const :tag "Always delete attachments" t) (const :tag "Query the user" query))) -(defun org-attach-id-folder-format (id) - "Translate an ID into a folder-path. +(defun org-attach-id-uuid-folder-format (id) + "Translate an UUID ID into a folder-path. Default format for how Org translates ID properties to a path for -attachments." +attachments. Useful if ID is generated with UUID." (format "%s/%s" (substring id 0 2) (substring id 2))) -(defcustom org-attach-id-to-path-function #'org-attach-id-folder-format - "Function parsing the ID parameter into a folder-path." +(defun org-attach-id-ts-folder-format (id) + "Translate an ID based on a timestamp to a folder-path. +Useful way of translation if ID is generated based on ISO8601 +timestamp. Splits the attachment folder hierarchy into +year-month, the rest." + (format "%s/%s" + (substring id 0 6) + (substring id 6))) + +(defcustom org-attach-id-to-path-function-list '(org-attach-id-uuid-folder-format + org-attach-id-ts-folder-format) + "List of functions parsing an ID string into a folder-path. +The first function in this list defines the preferred function +which will be used when creating new attachment folders. All +functions of this list will be tried when looking for existing +attachment folders based on ID." :group 'org-attach :package-version '(Org . "9.3") - :type 'function) + :type '(repeat (function :tag "Function with ID as input"))) (defvar org-attach-after-change-hook nil "Hook to be called when files have been added or removed to the attachment folder.") @@ -301,7 +315,7 @@ is run. If NO-FS-CHECK is non-nil, the function returns the path to the attachment even if it has not yet been initialized in the filesystem. -If no attachment directory exist, return nil." +If no attachment directory can be derived, return nil." (let (attach-dir id) (cond (create-if-not-exists-p @@ -314,7 +328,7 @@ If no attachment directory exist, return nil." (org-attach-check-absolute-path attach-dir)) ((setq id (org-entry-get nil "ID" org-attach-use-inheritance)) (org-attach-check-absolute-path nil) - (setq attach-dir (org-attach-dir-from-id id)))) + (setq attach-dir (org-attach-dir-from-id id 'try-all)))) (if no-fs-check attach-dir (when (and attach-dir (file-directory-p attach-dir)) @@ -346,11 +360,27 @@ If the attachment by some reason cannot be created an error will be raised." (make-directory attach-dir t)) attach-dir)) -(defun org-attach-dir-from-id (id) - "Returns a file name based on `org-attach-id-dir' and ID." - (expand-file-name - (funcall org-attach-id-to-path-function id) - (expand-file-name org-attach-id-dir))) +(defun org-attach-dir-from-id (id &optional try-all) + "Returns a folder path based on `org-attach-id-dir' and ID. +If TRY-ALL is non-nil, try all id-to-path functions in +`org-attach-id-to-path-function-list' and return the first path +that exist in the filesystem, or the first one if none exist. +Otherwise only use the first function in that list." + (let ((attach-dir-preferred (expand-file-name + (funcall (car org-attach-id-to-path-function-list) id) + (expand-file-name org-attach-id-dir)))) + (if try-all + (let ((attach-dir attach-dir-preferred) + (fun-list (cdr org-attach-id-to-path-function-list))) + (while (and fun-list (not (file-directory-p attach-dir))) + (setq attach-dir (expand-file-name + (funcall (car fun-list) id) + (expand-file-name org-attach-id-dir))) + (setq fun-list (cdr fun-list))) + (if (file-directory-p attach-dir) + attach-dir + attach-dir-preferred)) + attach-dir-preferred))) (defun org-attach-check-absolute-path (dir) "Check if we have enough information to root the attachment directory.