diff --git a/src/main/frontend/db.cljs b/src/main/frontend/db.cljs index e96b861b0a..7d92630c88 100644 --- a/src/main/frontend/db.cljs +++ b/src/main/frontend/db.cljs @@ -73,73 +73,11 @@ [logseq.db.default built-in-pages-names built-in-pages]) -;; persisting DBs between page reloads -(defn persist! [repo] - (let [key (datascript-db repo) - db (get-db repo)] - (when db - (let [db-str (if db (db->string db) "")] - (p/let [_ (db-persist/save-graph! key db-str)]))))) - -(defonce persistent-jobs (atom {})) - -(defn clear-repo-persistent-job! - [repo] - (when-let [old-job (get @persistent-jobs repo)] - (js/clearTimeout old-job))) - -(defn persist-if-idle! - [repo] - (clear-repo-persistent-job! repo) - (let [job (js/setTimeout - (fn [] - (if (and (state/input-idle? repo) - (state/db-idle? repo) - ;; It's ok to not persist here since new changes - ;; will be notified when restarting the app. - (not (state/whiteboard-route?))) - (persist! repo) - ;; (state/set-db-persisted! repo true) - - (persist-if-idle! repo))) - 3000)] - (swap! persistent-jobs assoc repo job))) - -;; only save when user's idle - -(defonce *db-listener (atom nil)) - -(defn- repo-listen-to-tx! - [repo conn] - (d/listen! conn :persistence - (fn [tx-report] - (when (not (:new-graph? (:tx-meta tx-report))) ; skip initial txs - (if (util/electron?) - (when-not (:dbsync? (:tx-meta tx-report)) - ;; sync with other windows if needed - (p/let [graph-has-other-window? (ipc/ipc "graphHasOtherWindow" repo)] - (when graph-has-other-window? - (ipc/ipc "dbsync" repo {:data (db->string (:tx-data tx-report))})))) - (do - (state/set-last-transact-time! repo (util/time-ms)) - (persist-if-idle! repo))) - - (when-let [db-listener @*db-listener] - (db-listener repo tx-report)))))) - -(defn listen-and-persist! - [repo] - (when-let [conn (get-db repo false)] - (d/unlisten! conn :persistence) - (repo-listen-to-tx! repo conn))) - (defn start-db-conn! ([repo] (start-db-conn! repo {})) ([repo option] - (conn/start! repo - (assoc option - :listen-handler listen-and-persist!)))) + (conn/start! repo option))) (defn new-block-id [] diff --git a/src/main/frontend/db/listener.cljs b/src/main/frontend/db/listener.cljs new file mode 100644 index 0000000000..138ed5cf2d --- /dev/null +++ b/src/main/frontend/db/listener.cljs @@ -0,0 +1,70 @@ +(ns frontend.db.listener + "DB listeners" + (:require [frontend.db.conn :as conn] + [frontend.db.utils :as db-utils] + [frontend.db.persist :as db-persist] + [frontend.state :as state] + [frontend.util :as util] + [promesa.core :as p] + [electron.ipc :as ipc] + [datascript.core :as d])) + +;; persisting DBs between page reloads +(defn persist! [repo] + (let [key (conn/datascript-db repo) + db (conn/get-db repo)] + (when db + (let [db-str (if db (db-utils/db->string db) "")] + (p/let [_ (db-persist/save-graph! key db-str)]))))) + +(defonce persistent-jobs (atom {})) + +(defn clear-repo-persistent-job! + [repo] + (when-let [old-job (get @persistent-jobs repo)] + (js/clearTimeout old-job))) + +(defn persist-if-idle! + [repo] + (clear-repo-persistent-job! repo) + (let [job (js/setTimeout + (fn [] + (if (and (state/input-idle? repo) + (state/db-idle? repo) + ;; It's ok to not persist here since new changes + ;; will be notified when restarting the app. + (not (state/whiteboard-route?))) + (persist! repo) + ;; (state/set-db-persisted! repo true) + + (persist-if-idle! repo))) + 3000)] + (swap! persistent-jobs assoc repo job))) + +;; only save when user's idle + +(defonce *db-listener (atom nil)) + +(defn repo-listen-to-tx! + [repo conn] + (d/listen! conn :persistence + (fn [tx-report] + (when (not (:new-graph? (:tx-meta tx-report))) ; skip initial txs + (if (util/electron?) + (when-not (:dbsync? (:tx-meta tx-report)) + ;; sync with other windows if needed + (p/let [graph-has-other-window? (ipc/ipc "graphHasOtherWindow" repo)] + (when graph-has-other-window? + (ipc/ipc "dbsync" repo {:data (db-utils/db->string (:tx-data tx-report))})))) + (do + (state/set-last-transact-time! repo (util/time-ms)) + (persist-if-idle! repo))) + + (when-let [db-listener @*db-listener] + (db-listener repo tx-report)))))) + +(defn listen-and-persist! + [repo] + (when-let [conn (conn/get-db repo false)] + (d/unlisten! conn :persistence) + (repo-listen-to-tx! repo conn))) diff --git a/src/main/frontend/db/restore.cljs b/src/main/frontend/db/restore.cljs index 5cf8ca2d25..cafba82a4f 100644 --- a/src/main/frontend/db/restore.cljs +++ b/src/main/frontend/db/restore.cljs @@ -18,8 +18,8 @@ [promesa.core :as p] [frontend.util :as util] [cljs-time.core :as t] - [clojure.set :as set])) - + [clojure.set :as set] + [frontend.db.listener :as db-listener])) (defn- old-schema? "Requires migration if the schema version is older than db-schema/version" @@ -120,22 +120,25 @@ data) (concat unloaded-pages) (remove nil?)))] - (d/unlisten! conn :persistence) (state/set-state! [repo :restore/unloaded-blocks] unloaded-block-ids) (state/set-state! [repo :restore/unloaded-pages :unloaded-pages] (set unloaded-pages)) (p/loop [data (get-loading-data repo *data per-length)] + (d/unlisten! conn :persistence) (cond (or (not= repo (state/get-current-repo)) ; switched to another graph (empty? data)) (do (state/set-state! [repo :restore/unloaded-blocks] nil) (state/set-state! [repo :restore/unloaded-pages] nil) + (db-listener/repo-listen-to-tx! repo conn) (let [end (util/time-ms)] (println "[debug] load others from SQLite: " (int (/ (- end start) 1000)) " seconds."))) (not (state/input-idle? repo {:diff 6000})) ; wait until input is idle - (p/do! (p/delay 5000) - (p/recur (get-loading-data repo *data per-length))) + (p/do! + (db-listener/repo-listen-to-tx! repo conn) + (p/delay 5000) + (p/recur (get-loading-data repo *data per-length))) :else (let [datoms (->> data @@ -154,6 +157,7 @@ (util/profile (str "DB transact! " (count datoms) " datoms") (d/transact! conn datoms {:skip-persist? true})) (state/update-state! [repo :restore/unloaded-blocks] (fn [ids] (set/difference ids (set (map #(gobj/get % "uuid") data))))) + (db-listener/repo-listen-to-tx! repo conn) (p/let [_ (p/delay 0)] (p/recur (get-loading-data repo *data per-length)))))))) diff --git a/src/main/frontend/handler.cljs b/src/main/frontend/handler.cljs index a70c0d9d4d..6fd543add5 100644 --- a/src/main/frontend/handler.cljs +++ b/src/main/frontend/handler.cljs @@ -44,7 +44,8 @@ [lambdaisland.glogi :as log] [promesa.core :as p] [frontend.mobile.core :as mobile] - [frontend.db.react :as db-react])) + [frontend.db.react :as db-react] + [frontend.db.listener :as db-listener])) (defn set-global-error-notification! [] @@ -86,7 +87,7 @@ (-> (db-restore/restore-graph! repo) (p/then (fn [] - (db/listen-and-persist! repo) + (db-listener/listen-and-persist! repo) ;; try to load custom css only for current repo (ui-handler/add-style-if-exists!) @@ -197,7 +198,7 @@ (state/set-component! :editor/box editor/box) (command-palette/register-global-shortcut-commands)) -(reset! db/*db-listener outliner-db/after-transact-pipelines) +(reset! db-listener/*db-listener outliner-db/after-transact-pipelines) (defn start! [render] diff --git a/src/main/frontend/handler/editor.cljs b/src/main/frontend/handler/editor.cljs index 16ed8314ac..f9b79f4e0f 100644 --- a/src/main/frontend/handler/editor.cljs +++ b/src/main/frontend/handler/editor.cljs @@ -56,7 +56,8 @@ [logseq.graph-parser.util.block-ref :as block-ref] [logseq.graph-parser.util.page-ref :as page-ref] [promesa.core :as p] - [rum.core :as rum])) + [rum.core :as rum] + [frontend.db.listener :as db-listener])) ;; FIXME: should support multiple images concurrently uploading @@ -1816,12 +1817,6 @@ new-value (string/replace value full_text new-full-text)] (save-block-aux! block new-value {}))) -(defn- mark-last-input-time! - [repo] - (when repo - (state/set-editor-last-input-time! repo (util/time-ms)) - (db/clear-repo-persistent-job! repo))) - (defonce *auto-save-timeout (atom nil)) (defn edit-box-on-change! [e _block id] @@ -1830,7 +1825,7 @@ (state/set-edit-content! id value false) (when @*auto-save-timeout (js/clearTimeout @*auto-save-timeout)) - (mark-last-input-time! repo) + (editor-property/mark-last-input-time! repo) (reset! *auto-save-timeout (js/setTimeout (fn [] @@ -2690,7 +2685,7 @@ top-block? (= (:block/left block) (:block/page block)) single-block? (inside-of-single-block (.-target e)) root-block? (= (:block.temp/container block) (str (:block/uuid block)))] - (mark-last-input-time! repo) + (editor-property/mark-last-input-time! repo) (cond (not= selected-start selected-end) (do diff --git a/src/main/frontend/handler/editor/property.cljs b/src/main/frontend/handler/editor/property.cljs index 7afd6de4b9..e5e344030c 100644 --- a/src/main/frontend/handler/editor/property.cljs +++ b/src/main/frontend/handler/editor/property.cljs @@ -10,7 +10,8 @@ [frontend.util.drawer :as drawer] [frontend.util.property :as property] [goog.object :as gobj] - [logseq.graph-parser.util :as gp-util])) + [logseq.graph-parser.util :as gp-util] + [frontend.db.listener :as db-listener])) (defn clear-selection! [] @@ -35,6 +36,12 @@ (or "") (subs 0 pos)))) +(defn mark-last-input-time! + [repo] + (when repo + (state/set-editor-last-input-time! repo (util/time-ms)) + (db-listener/clear-repo-persistent-job! repo))) + (defn edit-block! ([block pos id] (edit-block! block pos id nil)) @@ -69,7 +76,9 @@ (drawer/remove-logbook))] (clear-selection!) (if edit-input-id - (state/set-editing! edit-input-id content block text-range) + (do + (state/set-editing! edit-input-id content block text-range) + (mark-last-input-time! (state/get-current-repo))) ;; Block may not be rendered yet (js/setTimeout (fn [] (edit-block! block pos id (update opts :retry-times inc))) 10)))))))) diff --git a/src/main/frontend/handler/events.cljs b/src/main/frontend/handler/events.cljs index e1ed4322c1..ec1fae59a6 100644 --- a/src/main/frontend/handler/events.cljs +++ b/src/main/frontend/handler/events.cljs @@ -70,7 +70,8 @@ [logseq.db.schema :as db-schema] [logseq.graph-parser.config :as gp-config] [promesa.core :as p] - [rum.core :as rum])) + [rum.core :as rum] + [frontend.db.listener :as db-listener])) ;; TODO: should we move all events here? @@ -130,7 +131,7 @@ (defmethod handle :graph/added [[_ repo {:keys [empty-graph?]}]] (db/set-key-value repo :ast/version db-schema/ast-version) (search-handler/rebuild-indices!) - (db/persist! repo) + (db-listener/persist! repo) (plugin-handler/hook-plugin-app :graph-after-indexed {:repo repo :empty-graph? empty-graph?}) (when (state/setups-picker?) (if empty-graph? @@ -562,8 +563,8 @@ (catch :default e (js/console.error e))) (state/set-current-repo! current-repo) - (db/listen-and-persist! current-repo) - (db/persist-if-idle! current-repo) + (db-listener/listen-and-persist! current-repo) + (db-listener/persist-if-idle! current-repo) (repo-config-handler/restore-repo-config! current-repo) (.watch mobile-util/fs-watcher #js {:path current-repo-dir}) (when graph-switch-f (graph-switch-f current-repo true)) diff --git a/src/main/frontend/handler/repo.cljs b/src/main/frontend/handler/repo.cljs index 0a4b3188f3..7f4281c2d3 100644 --- a/src/main/frontend/handler/repo.cljs +++ b/src/main/frontend/handler/repo.cljs @@ -33,7 +33,8 @@ [medley.core :as medley] [logseq.common.path :as path] [logseq.common.config :as common-config] - [frontend.db.react :as react])) + [frontend.db.react :as react] + [frontend.db.listener :as db-listener])) ;; Project settings should be checked in two situations: ;; 1. User changes the config.edn directly in logseq.com (fn: alter-file) @@ -359,7 +360,7 @@ (defn start-repo-db-if-not-exists! [repo] (state/set-current-repo! repo) - (db/start-db-conn! repo)) + (db/start-db-conn! repo {:listen-handler db-listener/listen-and-persist!})) (defn- setup-local-repo-if-not-exists-impl! [] @@ -369,7 +370,7 @@ repo-dir (config/get-repo-dir repo)] (p/do! (fs/mkdir-if-not-exists repo-dir) ;; create memory://local (state/set-current-repo! repo) - (db/start-db-conn! repo) + (db/start-db-conn! repo {:listen-handler db-listener/listen-and-persist!}) (when-not config/publishing? (let [dummy-notes (t :tutorial/dummy-notes)] (create-dummy-notes-page repo dummy-notes))) @@ -407,7 +408,7 @@ (when (config/global-config-enabled?) (global-config-handler/restore-global-config!)) ;; Don't have to unlisten the old listener, as it will be destroyed with the conn - (db/listen-and-persist! repo) + (db-listener/listen-and-persist! repo) (ui-handler/add-style-if-exists!) (state/set-db-restoring! false))) @@ -447,7 +448,7 @@ (p/do! (when before (before)) - (db/persist! repo) + (db-listener/persist! repo) (when on-success (on-success))) (p/catch (fn [error] diff --git a/src/main/frontend/handler/web/nfs.cljs b/src/main/frontend/handler/web/nfs.cljs index d7252b1e46..84ca9181d2 100644 --- a/src/main/frontend/handler/web/nfs.cljs +++ b/src/main/frontend/handler/web/nfs.cljs @@ -20,7 +20,8 @@ [goog.object :as gobj] [lambdaisland.glogi :as log] [logseq.graph-parser.util :as gp-util] - [promesa.core :as p])) + [promesa.core :as p] + [frontend.db.listener :as db-listener])) (defn remove-ignore-files [files dir-name nfs?] @@ -138,7 +139,7 @@ (state/add-repo! {:url repo :nfs? true}) (state/set-loading-files! repo false) (when ok-handler (ok-handler {:url repo})) - (db/persist-if-idle! repo)))))) + (db-listener/persist-if-idle! repo)))))) (p/catch (fn [error] (log/error :nfs/load-files-error repo) (log/error :exception error))))))) @@ -275,7 +276,7 @@ (search/reset-indice! repo) (db/remove-conn! repo) (db/clear-query-state!) - (db/start-db-conn! repo) + (db/start-db-conn! repo {:listen-handler db-listener/listen-and-persist!}) (reload-dir! repo {:re-index? true :ok-handler ok-handler}))))