Jelajahi Sumber

enhance: auto backup every hour

Tienson Qin 1 tahun lalu
induk
melakukan
ad67958d17

+ 2 - 1
deps/db/src/logseq/db/sqlite/common_db.cljs

@@ -255,7 +255,8 @@
         idents (mapcat (fn [id]
                          (when-let [e (d/entity db id)]
                            (d/datoms db :eavt (:db/id e))))
-                       [:logseq.kv/db-type :logseq.kv/graph-uuid :logseq.property/empty-placeholder])
+                       [:logseq.kv/db-type :logseq.kv/graph-uuid :logseq.property/empty-placeholder
+                        :logseq.kv/graph-backup-folder])
         favorites (when db-graph? (get-favorites db))
         views (when db-graph? (get-views-data db))
         latest-journals (get-latest-journals db 1)

+ 17 - 17
src/main/frontend/components/export.cljs

@@ -18,8 +18,6 @@
             [logseq.shui.ui :as shui]
             [promesa.core :as p]
             [frontend.idb :as idb]
-            [logseq.db.sqlite.common-db :as sqlite-common-db]
-            [frontend.persist-db :as persist-db]
             [frontend.handler.notification :as notification]))
 
 (rum/defcs auto-backup < rum/reactive
@@ -36,14 +34,16 @@
        [:div.flex.flex-row.items-center.gap-1.text-sm
         [:div.opacity-50 (str "Backup folder:")]
         backup-folder
-        ;; TODO: support changing folder
-        ;; (shui/button
-        ;;  {:variant :ghost
-        ;;   :class "!px-1 !py-1"
-        ;;   :title "Change backup folder"
-        ;;   :size :sm}
-        ;;  (ui/icon "edit"))
-        ]
+        (shui/button
+         {:variant :ghost
+          :class "!px-1 !py-1"
+          :title "Change backup folder"
+          :on-click (fn []
+                      (p/do!
+                        (db/transact! [[:db/retractEntity :logseq.kv/graph-backup-folder]])
+                        (reset! *backup-folder nil)))
+          :size :sm}
+         (ui/icon "edit"))]
        (shui/button
         {:variant :default
          :on-click (fn []
@@ -54,17 +54,17 @@
                        (db/transact! [(ldb/kv :logseq.kv/graph-backup-folder folder-name)])
                        (reset! *backup-folder folder-name)))}
         "Set backup folder first"))
+     [:div.opacity-50.text-sm
+      "Backup will be created every hour."]
+
      (when backup-folder
        (shui/button
         {:variant :default
          :on-click (fn []
-                     (p/let [handle (idb/get-item (str "file-handle/" backup-folder))]
-                       (when handle
-                         (p/let [graph-dir-handle (.getDirectoryHandle handle (sqlite-common-db/sanitize-db-name repo) #js {:create true})
-                                 backup-handle (.getFileHandle graph-dir-handle "backup.db" #js {:create true})
-                                 data (persist-db/<export-db repo {:return-data? true})
-                                 _ (utils/writeFile backup-handle data)]
-                           (notification/show! "Backup successfully!" :success)))))}
+                     (p/let [result (export/backup-db-graph repo)]
+                       (when result
+                         (notification/show! "Backup successfully!" :success))
+                       (export/auto-db-backup! repo {:backup-now? false})))}
         "Backup now"))]))
 
 

+ 12 - 8
src/main/frontend/handler/events.cljs

@@ -83,7 +83,8 @@
             [frontend.modules.outliner.pipeline :as pipeline]
             [frontend.date :as date]
             [logseq.db :as ldb]
-            [frontend.persist-db :as persist-db]))
+            [frontend.persist-db :as persist-db]
+            [frontend.handler.export :as export]))
 
 ;; TODO: should we move all events here?
 
@@ -196,6 +197,7 @@
      (repo-handler/refresh-repos!))))
 
 (defmethod handle :graph/switch [[_ graph opts]]
+  (export/cancel-db-backup!)
   (persist-db/export-current-graph!)
   (state/set-state! :db/async-query-loading #{})
   (state/set-state! :db/async-queries {})
@@ -432,15 +434,17 @@
       (when (and (not dir-exists?)
                  (not util/nfs?))
         (state/pub-event! [:graph/dir-gone dir]))))
-  (p/do!
-   (state/pub-event! [:graph/sync-context])
+  (let [db-based? (config/db-based-graph? repo)]
+    (p/do!
+     (state/pub-event! [:graph/sync-context])
     ;; re-render-root is async and delegated to rum, so we need to wait for main ui to refresh
-   (when (mobile-util/native-ios?)
-     (js/setTimeout #(mobile/mobile-postinit) 1000))
+     (when (mobile-util/native-ios?)
+       (js/setTimeout #(mobile/mobile-postinit) 1000))
     ;; FIXME: an ugly implementation for redirecting to page on new window is restored
-   (repo-handler/graph-ready! repo)
-   (when-not (config/db-based-graph? repo)
-     (fs-watcher/load-graph-files! repo))))
+     (repo-handler/graph-ready! repo)
+     (if db-based?
+       (export/auto-db-backup! repo {:backup-now? true})
+       (fs-watcher/load-graph-files! repo)))))
 
 (defmethod handle :notification/show [[_ {:keys [content status clear?]}]]
   (notification/show! content status clear?))

+ 42 - 1
src/main/frontend/handler/export.cljs

@@ -19,7 +19,11 @@
    [promesa.core :as p]
    [frontend.persist-db :as persist-db]
    [cljs-bean.core :as bean]
-   [frontend.handler.export.common :as export-common-handler])
+   [frontend.handler.export.common :as export-common-handler]
+   [logseq.db.sqlite.common-db :as sqlite-common-db]
+   [logseq.db :as ldb]
+   [frontend.idb :as idb]
+   ["/frontend/utils" :as utils])
   (:import
    [goog.string StringBuffer]))
 
@@ -219,3 +223,40 @@
       (.setAttribute anchor "href" data-str)
       (.setAttribute anchor "download" (file-name (str repo "_roam") :json))
       (.click anchor))))
+
+(defn backup-db-graph
+  [repo]
+  (when (and repo (= repo (state/get-current-repo)))
+    (when-let [backup-folder (ldb/get-key-value (db/get-db repo) :logseq.kv/graph-backup-folder)]
+      (p/let [handle (idb/get-item (str "file-handle/" backup-folder))
+              repo-name (sqlite-common-db/sanitize-db-name repo)]
+        (if handle
+          (p/let [graph-dir-handle (.getDirectoryHandle handle repo-name #js {:create true})
+                  backup-handle (.getFileHandle graph-dir-handle "backup.db" #js {:create true})
+                  data (persist-db/<export-db repo {:return-data? true})
+                  _ (utils/writeFile backup-handle data)]
+            (println "Successfully created a backup for" repo-name "at" (str (js/Date.)) ".")
+            true)
+          (p/do!
+            ;; handle cleared
+           (notification/show! "DB backup failed, please go to Export and specify a backup folder." :error)
+           false))))))
+
+(defonce *backup-interval (atom nil))
+(defn cancel-db-backup!
+  []
+  (when-let [i @*backup-interval]
+    (js/clearInterval i)))
+
+(defn auto-db-backup!
+  [repo {:keys [backup-now?]
+         :or {backup-now? true}}]
+  (when (ldb/get-key-value (db/get-db repo) :logseq.kv/graph-backup-folder)
+    (when (and (config/db-based-graph? repo) util/web-platform? (utils/nfsSupported))
+      (cancel-db-backup!)
+
+      (when backup-now? (backup-db-graph repo))
+
+    ;; run backup every hour
+      (let [interval (js/setInterval #(backup-db-graph repo) (* 1 60 60 1000))]
+        (reset! *backup-interval interval)))))