diff options
Diffstat (limited to 'org-fc-cache.el')
-rw-r--r-- | org-fc-cache.el | 181 |
1 files changed, 181 insertions, 0 deletions
diff --git a/org-fc-cache.el b/org-fc-cache.el new file mode 100644 index 0000000..17ab403 --- /dev/null +++ b/org-fc-cache.el @@ -0,0 +1,181 @@ +;;; org-fc-cache.el --- Cache for org-fc -*- lexical-binding: t; -*- + +;; Copyright (C) 2020 Leon Rische + +;; Author: Leon Rische <emacs@leonrische.me> +;; Url: https://www.leonrische.me/pages/org_flashcards.html +;; Package-requires: ((emacs "26.3") (org "9.3")) +;; Version: 0.0.1 + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see <https://www.gnu.org/licenses/>. + +;;; Commentary: +;; +;; Even with the AWK based indexer, indexing cards before each review +;; gets slow if there are a lot of files / cards. +;; +;; After running the indexer one time, file checksums are used to +;; determine which cache entries need to be updated, assuming only a +;; small subset of the flashcard files is changed between reviews, +;; this is much faster than building the full index each time. + +;;; Code: + +(require 'org-fc) + +;;; Queue / Processing of Files + +(defvar org-fc-cache + (make-hash-table :test #'equal) + "Cache mapping filenames to card lists.") + +(defun org-fc-cache-update () + "Make sure the cache is up to date." + (let* ((hashes (org-fc-cache-hashes org-fc-directories)) + (changed + (cl-remove-if + (lambda (file) + (string= + (plist-get (gethash file org-fc-cache) :hash) + (gethash file hashes))) + (hash-table-keys hashes)))) + ;; Update changed files + (dolist (new (org-fc-awk-index-files changed)) + (let* ((path (plist-get new :path)) + (hash (gethash path hashes))) + (puthash + path + (plist-put new :hash hash) + org-fc-cache))) + ;; Remove deleted files + (dolist (file (hash-table-values org-fc-cache)) + (unless (gethash file hashes) + (remhash file org-fc-cache))))) + +;;; Filtering Entries + +(defun org-fc-cache-index (paths &optional filter) + "Find cards in PATHS matching an optional FILTER. +FILTER is assumed to be a predicate function taking a single card +as its input." + (org-fc-cache-update) + ;; Make sure paths are absolute & canonical + ;; Keys of the hash table can be assumed to be absolute & canonical. + (setq paths (mapcar #'expand-file-name paths)) + (let (res) + (maphash + (lambda (path file) + (when (cl-some (lambda (p) (string-prefix-p p path)) paths) + ;; Use push instead of `nconc' because `nconc' would break + ;; the entries of the hash table. + (if filter + (dolist (card (cl-remove-if-not filter (plist-get file :cards))) + (push (plist-put card :path path) res)) + (dolist (card (plist-get file :cards)) + (push (plist-put card :path path) res))))) + org-fc-cache) + res)) + +;; TODO: Check for awk errors +;; TODO: This should go into the awk file +(defun org-fc-awk-index-files (files) + "Generate a list of all cards and positions in FILES. +Unlike `org-fc-awk-index-paths', files are included directly in +the AWK command and directories are not supported." + (mapcar + (lambda (file) + (plist-put file :cards + (mapcar + (lambda (card) + (plist-put + card :tags + (org-fc-awk-combine-tags + (plist-get card :inherited-tags) + (plist-get card :local-tags)))) + (plist-get file :cards)))) + (read + (shell-command-to-string + (org-fc-awk--command + "awk/index.awk" + :variables (org-fc-awk--indexer-variables) + :input (mapconcat #'identity files " ")))))) + +;;; Cache Mode + +(defun org-fc-cache--enable () + "Enable org-fc-cache. +Initializes the cache and adds hooks." + (message "building org-fc cache...") + (org-fc-cache-update) + (add-hook 'org-fc-before-setup-hook #'org-fc-cache-coherence-check) + (setq org-fc-index-function #'org-fc-cache-index) + (message "org-fc cache enabled")) + +(defun org-fc-cache--disable () + "Disable org-fc-cache. +Resets the cache and removes hooks." + (setq org-fc-cache (make-hash-table :test #'equal)) + (remove-hook 'org-fc-before-setup-hook #'org-fc-cache-coherence-check) + (setq org-fc-index-function #'org-fc-awk-index) + (message "org-fc cache disabled")) + +(define-minor-mode org-fc-cache-mode + "Minor mode for caching org-fc card data. + +This mode sets up several hooks to ensure the case updated when files change, +are renamed or deleted." + :lighter "org-fc cache" + :group 'org-fc + :require 'org-fc + :global t + (if org-fc-cache-mode + (org-fc-cache--enable) + (org-fc-cache--disable))) + +;;; Coherence Check + +;; TODO: There already is a similar check in org-fc, +;; those should be combined. +(defun org-fc-cache-coherence-check () + "Check if the entry at point is coherent with its cache representation. +This is especially relevant w.r.t a card's due date / suspension state before review." + (org-fc-review-with-current-item cur + (if (org-fc-suspended-entry-p) + (error "Trying to review a suspended card")) + (let* ((position (plist-get cur :position)) + (review-data (org-fc-get-review-data)) + (row (assoc position review-data #'string=)) + (due (parse-iso8601-time-string (nth 4 row)))) + (unless (time-less-p due (current-time)) + (error "Trying to review a non-due card"))))) + +;;; Hashing + +(defun org-fc-cache-hashes (directories) + "Compute hashsums of all org files in DIRECTORIES." + (let ((output (shell-command-to-string + (org-fc-awk--pipe + (org-fc-awk--find directories) + (org-fc-awk--xargs "sha1sum")))) + (table (make-hash-table :test #'equal))) + (dolist (line (split-string output "\n" t)) + (let ((parts (split-string line " "))) + (puthash (cadr parts) (car parts) table))) + table)) + +;;; Footer + +(provide 'org-fc-cache) + +;;; org-fc-cache.el ends here |