branch: elpa/gnosis
commit 4f1204b95a665f0d3e27c3717f4740d1d0a25656
Author: Thanos Apollo <[email protected]>
Commit: Thanos Apollo <[email protected]>

    [Feature] Add gnosis-import-deck-async.
    
    * Split FILE into chunks and import each chunk.
---
 gnosis.el | 104 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 104 insertions(+)

diff --git a/gnosis.el b/gnosis.el
index 782151fb7d..3eac214887 100644
--- a/gnosis.el
+++ b/gnosis.el
@@ -2122,6 +2122,110 @@ before importing into it."
       (org-mode)
       (gnosis-save-deck (gnosis-export-parse--deck-name)))))
 
+(defun gnosis--import-split-chunks (text chunk-size)
+  "Split org TEXT into chunks of CHUNK-SIZE themata.
+
+Return a list of strings, each containing up to CHUNK-SIZE
+`* Thema' headings."
+  (let ((headings '())
+        (start 0))
+    ;; Find all `* Thema' positions
+    (while (string-match "^\\* Thema" text start)
+      (push (match-beginning 0) headings)
+      (setf start (1+ (match-beginning 0))))
+    (setq headings (nreverse headings))
+    (let ((chunks '())
+          (total (length headings)))
+      (cl-loop for i from 0 below total by chunk-size
+               for beg = (nth i headings)
+               for end-idx = (min (+ i chunk-size) total)
+               for end = (if (< end-idx total)
+                             (nth end-idx headings)
+                           (length text))
+               do (push (substring text beg end) chunks))
+      (nreverse chunks))))
+
+(defun gnosis--import-chunk (header chunk deck-id id-cache)
+  "Import a single CHUNK of org text.
+
+HEADER is the #+DECK line to prepend.  DECK-ID is the resolved
+deck id.  ID-CACHE is the shared `gnosis--id-cache' hash table.
+Returns a list of error strings (nil on full success)."
+  (let ((gc-cons-threshold most-positive-fixnum)
+        (gnosis--id-cache id-cache)
+        (errors nil))
+    (with-temp-buffer
+      (insert header "\n" chunk)
+      (org-mode)
+      (let ((themata (gnosis-export-parse-themata)))
+        (emacsql-with-transaction gnosis-db
+          (cl-loop for thema in themata
+                   for err = (gnosis-save-thema thema deck-id)
+                   when err do (push err errors)))))
+    (nreverse errors)))
+
+;;;###autoload
+(defun gnosis-import-deck-async (file &optional chunk-size)
+  "Import gnosis deck from FILE asynchronously in chunks.
+
+CHUNK-SIZE controls how many themata to process per batch
+\(default 500).  Uses `run-with-timer' between chunks so Emacs
+stays responsive.  Progress is reported in the echo area."
+  (interactive "fFile: ")
+  (let* ((chunk-size (or chunk-size 500))
+         (text (with-temp-buffer
+                 (insert-file-contents file)
+                 (buffer-string)))
+         ;; Extract header (everything before first `* Thema')
+         (header-end (or (string-match "^\\* Thema" text) 0))
+         (header (string-trim-right (substring text 0 header-end)))
+         (deck-name (with-temp-buffer
+                      (insert header)
+                      (org-mode)
+                      (gnosis-export-parse--deck-name)))
+         (deck-id (progn
+                    (when (and (gnosis-get 'id 'decks `(= name ,deck-name))
+                               (not (y-or-n-p
+                                     (format "Deck '%s' already exists.  
Import into it? "
+                                             deck-name))))
+                      (user-error "Aborted"))
+                    (gnosis-get-deck-id deck-name)))
+         (id-cache (let ((ht (make-hash-table :test 'equal)))
+                     (dolist (id (gnosis-select 'id 'themata nil t) ht)
+                       (puthash id t ht))))
+         (chunks (gnosis--import-split-chunks text chunk-size))
+         (total-chunks (length chunks))
+         ;; Count total themata from the text
+         (total-themata (with-temp-buffer
+                          (insert text)
+                          (count-matches "^\\* Thema" (point-min) 
(point-max))))
+         (imported 0)
+         (all-errors '()))
+    (message "Importing %d themata in %d chunks..." total-themata total-chunks)
+    (cl-labels
+        ((process-next (remaining chunk-n)
+           (if (null remaining)
+               ;; Done
+               (if all-errors
+                   (message "Import complete: %d themata, %d errors"
+                            imported (length all-errors))
+                 (message "Import complete: %d themata for deck '%s'"
+                          imported deck-name))
+             (let* ((chunk (car remaining))
+                    (errors (gnosis--import-chunk header chunk deck-id 
id-cache))
+                    ;; Count headings in this chunk
+                    (n (with-temp-buffer
+                         (insert chunk)
+                         (count-matches "^\\* Thema" (point-min) 
(point-max)))))
+               (setq imported (+ imported n))
+               (when errors
+                 (setq all-errors (append all-errors errors)))
+               (message "Importing... %d/%d themata (chunk %d/%d)"
+                        imported total-themata chunk-n total-chunks)
+               (run-with-timer 0.01 nil
+                               #'process-next (cdr remaining) (1+ chunk-n))))))
+      (process-next chunks 1))))
+
 ;;;###autoload
 (defun gnosis-add-thema (deck type &optional keimenon hypothesis
                              answer parathema tags example)

Reply via email to