summaryrefslogtreecommitdiff
path: root/modules
diff options
context:
space:
mode:
authorCraig Jennings <c@cjennings.net>2026-05-10 14:51:53 -0500
committerCraig Jennings <c@cjennings.net>2026-05-10 14:51:53 -0500
commit7833f4ff97edd46d62cea1eeb0405923ec2806cb (patch)
tree36db15ac6a9cff062d660a04b89754d3d5184492 /modules
parenta7071b13d5a3309e28ee03e33acc526d7eaed810 (diff)
downloaddotemacs-7833f4ff97edd46d62cea1eeb0405923ec2806cb.tar.gz
dotemacs-7833f4ff97edd46d62cea1eeb0405923ec2806cb.zip
refactor(org-refile): migrate to cj-cache helper
Phase 5 step 3 of utility-consolidation, second of two cache migrations. Same shape as the agenda migration: drop four state vars, replace the build-with-cache function with a thin wrapper around `cj/cache-value-or-rebuild', extract the slow scan into a pure-ish helper. Add `cj/--org-refile-scan-targets' as the slow filesystem walk (org-roam node enumeration plus 30,000+ todo.org files across code-dir and projects-dir). `cj/build-org-refile-targets' now reads as: detect background-build state, ask the cache helper for the value with the scan helper as BUILD-FN, route the original log lines through :on-hit / :on-build-success. Drop four module-level state vars: - `cj/org-refile-targets-cache' - `cj/org-refile-targets-cache-time' - `cj/org-refile-targets-cache-ttl' - `cj/org-refile-targets-building' Rewrite the existing test file to test wrapper behavior at the contract level (stub the scan helper, verify wrapper outcomes). 8 tests parallel the agenda test set: first-call builds, second-call uses cache, force-rebuild bypass, TTL expiration, empty scan, building-flag cleanup on success and error, and error propagation.
Diffstat (limited to 'modules')
-rw-r--r--modules/org-refile-config.el128
1 files changed, 51 insertions, 77 deletions
diff --git a/modules/org-refile-config.el b/modules/org-refile-config.el
index 05450338..8d989e4b 100644
--- a/modules/org-refile-config.el
+++ b/modules/org-refile-config.el
@@ -14,25 +14,15 @@
;;; Code:
(require 'system-lib)
+(require 'cj-cache)
;; ----------------------------- Org Refile Targets ----------------------------
;; sets refile targets
;; - adds project files in org-roam to the refile targets
;; - adds todo.org files in subdirectories of the code and project directories
-(defvar cj/org-refile-targets-cache nil
- "Cached refile targets to avoid expensive directory scanning.
-Set to nil to invalidate cache.")
-
-(defvar cj/org-refile-targets-cache-time nil
- "Time when refile targets cache was last built.")
-
-(defvar cj/org-refile-targets-cache-ttl 3600
- "Time-to-live for refile targets cache in seconds (default: 1 hour).")
-
-(defvar cj/org-refile-targets-building nil
- "Non-nil when refile targets are being built asynchronously.
-Prevents duplicate builds if user refiles before async build completes.")
+(defvar cj/--org-refile-targets-cache (cj/cache-make :ttl 3600)
+ "Cache state for the refile targets list. See `cj-cache.el'.")
(defun cj/org-refile-ensure-org-mode (file)
"Ensure FILE is a .org file and its buffer is in org-mode.
@@ -53,6 +43,37 @@ This prevents issues where:
(org-mode)))
buf))
+(defun cj/--org-refile-scan-targets ()
+ "Scan disk for the refile-targets list. Pure-ish: no caching, no logging.
+Returns the list to assign to `org-refile-targets'. Slow -- walks
+30,000+ files across `code-dir' and `projects-dir'."
+ (let ((new-files
+ (list
+ (cons inbox-file '(:maxlevel . 1))
+ (cons reference-file '(:maxlevel . 2))
+ (cons schedule-file '(:maxlevel . 1)))))
+ (when (and (fboundp 'cj/org-roam-list-notes-by-tag)
+ (fboundp 'org-roam-node-list))
+ (let* ((project-and-topic-files
+ (append (cj/org-roam-list-notes-by-tag "Project")
+ (cj/org-roam-list-notes-by-tag "Topic")))
+ (file-rule '(:maxlevel . 1)))
+ (dolist (file project-and-topic-files)
+ (unless (assoc file new-files)
+ (push (cons file file-rule) new-files)))))
+ (dolist (dir (list user-emacs-directory code-dir projects-dir))
+ (condition-case nil
+ (let* ((todo-files (directory-files-recursively
+ dir "^[Tt][Oo][Dd][Oo]\\.[Oo][Rr][Gg]$"
+ nil
+ (lambda (d) (not (string-match-p "airootfs" d)))))
+ (file-rule '(:maxlevel . 1)))
+ (dolist (file todo-files)
+ (unless (assoc file new-files)
+ (push (cons file file-rule) new-files))))
+ (permission-denied nil)))
+ (nreverse new-files)))
+
(defun cj/build-org-refile-targets (&optional force-rebuild)
"Build =org-refile-targets= with caching.
@@ -62,70 +83,23 @@ Otherwise, returns cached targets if available and not expired.
This function scans 30,000+ files across code/projects directories,
so caching improves performance from 15-20 seconds to instant."
(interactive "P")
- ;; Check if we can use cache
- (let ((cache-valid (and cj/org-refile-targets-cache
- cj/org-refile-targets-cache-time
- (not force-rebuild)
- (< (- (float-time) cj/org-refile-targets-cache-time)
- cj/org-refile-targets-cache-ttl))))
- (if cache-valid
- ;; Use cached targets (instant)
- (progn
- (setq org-refile-targets cj/org-refile-targets-cache)
- ;; Always show cache-hit message (interactive or background)
- (cj/log-silently "Using cached refile targets (%d files)"
- (length org-refile-targets)))
- ;; Check if async build is in progress
- (when cj/org-refile-targets-building
- (cj/log-silently "Waiting for background cache build to complete..."))
- ;; Rebuild from scratch (slow - scans 34,000+ files)
- (unwind-protect
- (progn
- (setq cj/org-refile-targets-building t)
- (let ((start-time (current-time))
- (new-files
- (list
- (cons inbox-file '(:maxlevel . 1))
- (cons reference-file '(:maxlevel . 2))
- (cons schedule-file '(:maxlevel . 1)))))
-
- ;; Extend with org-roam files if available AND org-roam is loaded
- (when (and (fboundp 'cj/org-roam-list-notes-by-tag)
- (fboundp 'org-roam-node-list))
- (let* ((project-and-topic-files
- (append (cj/org-roam-list-notes-by-tag "Project")
- (cj/org-roam-list-notes-by-tag "Topic")))
- (file-rule '(:maxlevel . 1)))
- (dolist (file project-and-topic-files)
- (unless (assoc file new-files)
- (push (cons file file-rule) new-files)))))
-
- ;; Add todo.org files from known directories
- ;; Skip directories that cause permission errors (e.g., archiso airootfs)
- (dolist (dir (list user-emacs-directory code-dir projects-dir))
- (condition-case nil
- (let* ((todo-files (directory-files-recursively
- dir "^[Tt][Oo][Dd][Oo]\\.[Oo][Rr][Gg]$"
- nil
- (lambda (d) (not (string-match-p "airootfs" d)))))
- (file-rule '(:maxlevel . 1)))
- (dolist (file todo-files)
- (unless (assoc file new-files)
- (push (cons file file-rule) new-files))))
- (permission-denied nil))) ;; Silently skip permission errors
-
- ;; Update targets and cache
- (setq new-files (nreverse new-files))
- (setq org-refile-targets new-files)
- (setq cj/org-refile-targets-cache new-files)
- (setq cj/org-refile-targets-cache-time (float-time))
-
- ;; Always show completion message (interactive or background)
- (cj/log-silently "Built refile targets: %d files in %.2f seconds"
- (length org-refile-targets)
- (- (float-time) (float-time start-time)))))
- ;; Always clear the building flag, even if build fails
- (setq cj/org-refile-targets-building nil)))))
+ (when (cj/cache-building-p cj/--org-refile-targets-cache)
+ (cj/log-silently "Waiting for background cache build to complete..."))
+ (let* ((start-time (current-time))
+ (targets
+ (cj/cache-value-or-rebuild
+ cj/--org-refile-targets-cache
+ #'cj/--org-refile-scan-targets
+ :force-rebuild force-rebuild
+ :on-hit (lambda (v)
+ (cj/log-silently "Using cached refile targets (%d files)"
+ (length v)))
+ :on-build-success
+ (lambda (v)
+ (cj/log-silently "Built refile targets: %d files in %.2f seconds"
+ (length v)
+ (- (float-time) (float-time start-time)))))))
+ (setq org-refile-targets targets)))
;; Build cache asynchronously after startup to avoid blocking Emacs
(run-with-idle-timer