watcher_handler.cljs 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. (ns frontend.fs.watcher-handler
  2. "Main ns that handles file watching events from electron's main process"
  3. (:require [clojure.set :as set]
  4. [clojure.string :as string]
  5. [frontend.config :as config]
  6. [frontend.db :as db]
  7. [frontend.db.async :as db-async]
  8. [frontend.db.file-based.model :as file-model]
  9. [frontend.db.model :as model]
  10. [frontend.fs :as fs]
  11. [frontend.handler.file-based.file :as file-handler]
  12. [frontend.handler.file-based.property :as file-property-handler]
  13. [frontend.handler.global-config :as global-config-handler]
  14. [frontend.handler.notification :as notification]
  15. [frontend.handler.page :as page-handler]
  16. [frontend.handler.ui :as ui-handler]
  17. [frontend.state :as state]
  18. [frontend.util.fs :as fs-util]
  19. [lambdaisland.glogi :as log]
  20. [logseq.common.config :as common-config]
  21. [logseq.common.path :as path]
  22. [logseq.common.util.block-ref :as block-ref]
  23. [promesa.core :as p]))
  24. ;; all IPC paths must be normalized! (via common-util/path-normalize)
  25. (defn- set-missing-block-ids!
  26. "For every referred block in the content, fix their block ids in files if missing."
  27. [content]
  28. (when (string? content)
  29. (let [missing-blocks (->> (block-ref/get-all-block-ref-ids content)
  30. (distinct)
  31. (keep model/get-block-by-uuid)
  32. (filter (fn [block]
  33. (not= (str (:id (:block/properties block)))
  34. (str (:block/uuid block))))))]
  35. (when (seq missing-blocks)
  36. (file-property-handler/batch-set-block-property-aux!
  37. (mapv
  38. (fn [b] [(:block/uuid b) :id (str (:block/uuid b))])
  39. missing-blocks))))))
  40. (defn- handle-add-and-change!
  41. [repo path content db-content ctime mtime backup?]
  42. (let [config (state/get-config repo)
  43. path-hidden-patterns (:hidden config)]
  44. (when-not (or (and (seq path-hidden-patterns)
  45. (common-config/hidden? path path-hidden-patterns))
  46. ;; File not changed
  47. (= content db-content))
  48. (p/let [;; save the previous content in a versioned bak file to avoid data overwritten.
  49. _ (when backup?
  50. (-> (when-let [repo-dir (config/get-local-dir repo)]
  51. (file-handler/backup-file! repo-dir path db-content content))
  52. (p/catch #(js/console.error "❌ Bak Error: " path %))))
  53. _ (file-handler/alter-file repo path content {:re-render-root? true
  54. :from-disk? true
  55. :fs/event :fs/local-file-change
  56. :ctime ctime
  57. :mtime mtime})]
  58. (set-missing-block-ids! content)))))
  59. (defn handle-changed!
  60. [type {:keys [dir path content stat global-dir] :as payload}]
  61. (let [repo (state/get-current-repo)]
  62. (when dir
  63. (let [;; Global directory events don't know their originating repo so we rely
  64. ;; on the client to correctly identify it
  65. repo (cond
  66. global-dir repo
  67. :else (config/get-local-repo dir))
  68. repo-dir (config/get-local-dir repo)
  69. {:keys [mtime ctime]} stat
  70. ext (keyword (path/file-ext path))]
  71. (when (contains? #{:org :md :markdown :css :js :edn :excalidraw :tldr} ext)
  72. (p/let [db-content (db-async/<get-file repo path)
  73. exists-in-db? (not (nil? db-content))
  74. db-content (or db-content "")]
  75. (when (or content (contains? #{"unlink" "unlinkDir" "addDir"} type))
  76. (cond
  77. (and (= "unlinkDir" type) dir)
  78. (state/pub-event! [:graph/dir-gone dir])
  79. (and (= "addDir" type) dir)
  80. (state/pub-event! [:graph/dir-back repo dir])
  81. (contains? (:file/unlinked-dirs @state/state) dir)
  82. nil
  83. (and (= "add" type)
  84. (not= (string/trim content) (string/trim db-content)))
  85. (let [backup? (not (string/blank? db-content))]
  86. (handle-add-and-change! repo path content db-content ctime mtime backup?))
  87. (and (= "change" type)
  88. (= dir repo-dir)
  89. (not (common-config/local-asset? path)))
  90. (handle-add-and-change! repo path content db-content ctime mtime (not global-dir)) ;; no backup for global dir
  91. (and (= "unlink" type)
  92. exists-in-db?)
  93. (p/let [dir-exists? (fs/file-exists? dir "")]
  94. (when dir-exists?
  95. (when-let [page-name (file-model/get-file-page path)]
  96. (println "Delete page: " page-name ", file path: " path ".")
  97. (page-handler/<delete! page-name #()))))
  98. ;; global config handling
  99. (and (= "change" type)
  100. (= dir (global-config-handler/global-config-dir)))
  101. (when (= path "config.edn")
  102. (file-handler/alter-global-file
  103. (global-config-handler/global-config-path) content {:from-disk? true}))
  104. (and (= "change" type)
  105. (not exists-in-db?))
  106. (js/console.error "Can't get file in the db: " path)
  107. (and (contains? #{"add" "change" "unlink"} type)
  108. (string/ends-with? path "logseq/custom.css"))
  109. (do
  110. (println "reloading custom.css")
  111. (ui-handler/add-style-if-exists!))
  112. (contains? #{"add" "change" "unlink"} type)
  113. nil
  114. :else
  115. (log/error :fs/watcher-no-handler {:type type
  116. :payload payload})))))
  117. ;; return nil, otherwise the entire db will be transferred by ipc
  118. nil))))
  119. (defn load-graph-files!
  120. "This fn replaces the former initial fs watcher"
  121. [graph]
  122. (when graph
  123. (let [repo-dir (config/get-repo-dir graph)]
  124. ;; read all files in the repo dir, notify if readdir error
  125. (p/let [db-files' (db-async/<get-files graph)
  126. db-files (map first db-files')
  127. [files deleted-files]
  128. (-> (fs/readdir repo-dir :path-only? true)
  129. (p/chain (fn [files]
  130. (->> files
  131. (map #(path/relative-path repo-dir %))
  132. (remove #(fs-util/ignored-path? repo-dir %))
  133. (sort-by (fn [f] [(not (string/starts-with? f "logseq/"))
  134. (not (string/starts-with? f "journals/"))
  135. (not (string/starts-with? f "pages/"))
  136. (string/lower-case f)]))))
  137. (fn [files]
  138. (let [deleted-files (set/difference (set db-files) (set files))]
  139. [files
  140. deleted-files])))
  141. (p/catch (fn [error]
  142. (when-not (config/demo-graph? graph)
  143. (js/console.error "reading" graph)
  144. (state/pub-event! [:notification/show
  145. {:content (str "The graph " graph " can not be read:" error)
  146. :status :error
  147. :clear? false}]))
  148. [nil nil])))
  149. ;; notifies user when large initial change set is detected
  150. ;; NOTE: this is an estimation, not accurate
  151. notification-uid (when (or (> (abs (- (count db-files) (count files)))
  152. 100)
  153. (> (count deleted-files)
  154. 100))
  155. (prn ::init-watcher-large-change-set)
  156. (notification/show! "Loading changes from disk..."
  157. :info
  158. false))]
  159. (prn ::initial-watcher repo-dir {:deleted (count deleted-files)
  160. :total (count files)})
  161. (p/do!
  162. (when (seq deleted-files)
  163. (p/all (map (fn [path]
  164. (when-let [page-name (file-model/get-file-page path)]
  165. (println "Delete page: " page-name ", file path: " path ".")
  166. (page-handler/<delete! page-name #())))
  167. deleted-files)))
  168. (-> (p/delay 500) ;; workaround for notification ui not showing
  169. (p/then #(p/all (map (fn [file-rpath]
  170. (p/let [stat (fs/stat repo-dir file-rpath)
  171. content (fs/read-file repo-dir file-rpath)
  172. type (if (db/file-exists? graph file-rpath)
  173. "change"
  174. "add")]
  175. (handle-changed! type
  176. {:dir repo-dir
  177. :path file-rpath
  178. :content content
  179. :stat stat})))
  180. files)))
  181. (p/then (fn []
  182. (when notification-uid
  183. (prn ::init-notify)
  184. (notification/clear! notification-uid)
  185. (state/pub-event! [:notification/show {:content (str "The graph " graph " is loaded.")
  186. :status :success
  187. :clear? true}]))))
  188. (p/catch (fn [error]
  189. (js/console.dir error)))))))))