file.cljs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. (ns frontend.handler.file-based.file
  2. "Provides util handler fns for file graph files"
  3. (:refer-clojure :exclude [load-file])
  4. (:require [electron.ipc :as ipc]
  5. [frontend.config :as config]
  6. [frontend.db :as db]
  7. [frontend.db.file-based.model :as file-model]
  8. [frontend.fs :as fs]
  9. [frontend.handler.common.config-edn :as config-edn-common-handler]
  10. [frontend.handler.global-config :as global-config-handler]
  11. [frontend.handler.repo-config :as repo-config-handler]
  12. [frontend.handler.ui :as ui-handler]
  13. [frontend.schema.handler.global-config :as global-config-schema]
  14. [frontend.schema.handler.repo-config :as repo-config-schema]
  15. [frontend.state :as state]
  16. [frontend.util :as util]
  17. [lambdaisland.glogi :as log]
  18. [logseq.common.config :as common-config]
  19. [logseq.common.path :as path]
  20. [logseq.common.util :as common-util]
  21. [promesa.core :as p]))
  22. ;; TODO: extract all git ops using a channel
  23. (defn load-file
  24. [repo-url path]
  25. (->
  26. (p/let [content (fs/read-file (config/get-repo-dir repo-url) path)]
  27. content)
  28. (p/catch
  29. (fn [e]
  30. (println "Load file failed: " path)
  31. (js/console.error e)))))
  32. (defn reset-file!
  33. [repo file-path content opts]
  34. (state/<invoke-db-worker :thread-api/reset-file repo file-path content opts))
  35. (defn- load-multiple-files
  36. [repo-url paths]
  37. (doall
  38. (mapv #(load-file repo-url %) paths)))
  39. (defn- keep-formats
  40. [files formats]
  41. (filter
  42. (fn [file]
  43. (let [format (common-util/get-format file)]
  44. (contains? formats format)))
  45. files))
  46. (defn- only-text-formats
  47. [files]
  48. (keep-formats files (common-config/text-formats)))
  49. (defn- only-image-formats
  50. [files]
  51. (keep-formats files (common-config/img-formats)))
  52. (defn load-files-contents!
  53. [repo-url files ok-handler]
  54. (let [images (only-image-formats files)
  55. files (only-text-formats files)]
  56. (-> (p/all (load-multiple-files repo-url files))
  57. (p/then (fn [contents]
  58. (let [file-contents (cond->
  59. (zipmap files contents)
  60. (seq images)
  61. (merge (zipmap images (repeat (count images) ""))))
  62. file-contents (for [[file content] file-contents]
  63. {:file/path (common-util/path-normalize file)
  64. :file/content content})]
  65. (ok-handler file-contents))))
  66. (p/catch (fn [error]
  67. (log/error :fs/load-files-error repo-url)
  68. (log/error :exception error))))))
  69. (defn backup-file!
  70. "Backup db content to bak directory"
  71. [repo-url path db-content content]
  72. (when (util/electron?)
  73. (ipc/ipc "backupDbFile" repo-url path db-content content)))
  74. (defn- detect-deprecations
  75. [path content]
  76. (when (or (= path "logseq/config.edn")
  77. (= (path/dirname path) (global-config-handler/safe-global-config-dir)))
  78. (config-edn-common-handler/detect-deprecations path content {:db-graph? false})))
  79. (defn- validate-file
  80. "Returns true if valid and if false validator displays error message. Files
  81. that are not validated just return true"
  82. [path content]
  83. (cond
  84. (= path "logseq/config.edn")
  85. (config-edn-common-handler/validate-config-edn path content repo-config-schema/Config-edn)
  86. (= (path/dirname path) (global-config-handler/safe-global-config-dir))
  87. (config-edn-common-handler/validate-config-edn path content global-config-schema/Config-edn)
  88. :else
  89. true))
  90. (defn- write-file-aux!
  91. [repo path content write-file-options]
  92. (let [original-content (db/get-file repo path)
  93. path-dir (config/get-repo-dir repo)
  94. write-file-options' (merge write-file-options
  95. (when original-content {:old-content original-content}))]
  96. (fs/write-plain-text-file! repo path-dir path content write-file-options')))
  97. (defn alter-global-file
  98. "Does pre-checks on a global file, writes if it's not already written
  99. (:from-disk? is not set) and then does post-checks. Currently only handles
  100. global config.edn but can be extended as needed"
  101. [path content {:keys [from-disk?]}]
  102. (if (and path (= path (global-config-handler/safe-global-config-path)))
  103. (do
  104. (detect-deprecations path content)
  105. (when (validate-file path content)
  106. (-> (p/let [_ (when-not from-disk?
  107. (fs/write-plain-text-file! "" nil path content {:skip-compare? true}))]
  108. (p/do! (global-config-handler/restore-global-config!)
  109. (state/pub-event! [:shortcut/refresh])))
  110. (p/catch (fn [error]
  111. (state/pub-event! [:notification/show
  112. {:content (str "Failed to write to file " path ", error: " error)
  113. :status :error}])
  114. (log/error :write/failed error)
  115. (state/pub-event! [:capture-error
  116. {:error error
  117. :payload {:type :write-file/failed-for-alter-file}}]))))))
  118. (log/error :msg "alter-global-file does not support this file" :file path)))
  119. (defn alter-file
  120. "Write any in-DB file, e.g. repo config, page, whiteboard, etc."
  121. [repo path content {:keys [reset? re-render-root? from-disk? skip-compare? new-graph? verbose
  122. ctime mtime]
  123. :fs/keys [event]
  124. :or {reset? true
  125. re-render-root? false
  126. from-disk? false
  127. skip-compare? false}}]
  128. (let [path (common-util/path-normalize path)
  129. config-file? (= path "logseq/config.edn")
  130. _ (when config-file?
  131. (detect-deprecations path content))
  132. config-valid? (and config-file? (validate-file path content))]
  133. (when (or config-valid? (not config-file?)) ; non-config file or valid config
  134. (let [opts {:new-graph? new-graph?
  135. :from-disk? from-disk?
  136. :fs/event event
  137. :ctime ctime
  138. :mtime mtime}]
  139. (-> (p/let [result (if reset?
  140. (p/do!
  141. (when-let [page-id (file-model/get-file-page-id path)]
  142. (db/transact! repo
  143. [[:db/retract page-id :block/alias]
  144. [:db/retract page-id :block/tags]]
  145. opts))
  146. (reset-file!
  147. repo path content (merge opts
  148. ;; To avoid skipping the `:or` bounds for keyword destructuring
  149. (when (some? verbose) {:verbose verbose}))))
  150. (db/set-file-content! repo path content opts))
  151. _ (when-not from-disk?
  152. (write-file-aux! repo path content {:skip-compare? skip-compare?}))]
  153. (when re-render-root? (ui-handler/re-render-root!))
  154. (cond
  155. (= path "logseq/custom.css")
  156. (do
  157. ;; ui-handler will load css from db and config
  158. (db/set-file-content! repo path content)
  159. (ui-handler/add-style-if-exists!))
  160. (= path "logseq/config.edn")
  161. (p/let [_ (repo-config-handler/restore-repo-config! repo content)]
  162. (state/pub-event! [:shortcut/refresh])))
  163. result)
  164. (p/catch
  165. (fn [error]
  166. (println "Write file failed, path: " path ", content: " content)
  167. (log/error :write/failed error)
  168. (state/pub-event! [:capture-error
  169. {:error error
  170. :payload {:type :write-file/failed-for-alter-file}}]))))))))
  171. (defn set-file-content!
  172. [repo path new-content]
  173. (alter-file repo path new-content {:reset? false
  174. :re-render-root? false}))
  175. (defn- alter-files-handler!
  176. [repo files {:keys [finish-handler]} file->content]
  177. (let [write-file-f (fn [[path content]]
  178. (when path
  179. (let [path (common-util/path-normalize path)
  180. original-content (get file->content path)]
  181. (-> (fs/write-plain-text-file! repo (config/get-repo-dir repo) path content
  182. {:old-content original-content})
  183. (p/catch (fn [error]
  184. (state/pub-event! [:notification/show
  185. {:content (str "Failed to save the file " path ". Error: "
  186. (str error))
  187. :status :error
  188. :clear? false}])
  189. (state/pub-event! [:capture-error
  190. {:error error
  191. :payload {:type :write-file/failed}}])
  192. (log/error :write-file/failed {:path path
  193. :content content
  194. :error error})))))))
  195. finish-handler (fn []
  196. (when finish-handler
  197. (finish-handler)))]
  198. (-> (p/all (map write-file-f files))
  199. (p/then (fn []
  200. (finish-handler)))
  201. (p/catch (fn [error]
  202. (println "Alter files failed:")
  203. (js/console.error error))))))
  204. (defn alter-files
  205. [repo files {:keys [reset? update-db?]
  206. :or {reset? false
  207. update-db? true}
  208. :as opts}]
  209. ;; old file content
  210. (let [file->content (let [paths (map first files)]
  211. (zipmap paths
  212. (map (fn [path] (db/get-file repo path)) paths)))]
  213. ;; update db
  214. (when update-db?
  215. (p/all
  216. (map
  217. (fn [[path content]]
  218. (if reset?
  219. (reset-file! repo path content {})
  220. (db/set-file-content! repo path content)))
  221. files)))
  222. (alter-files-handler! repo files opts file->content)))
  223. (defn watch-for-current-graph-dir!
  224. []
  225. (when-let [repo (state/get-current-repo)]
  226. (when-let [dir (config/get-repo-dir repo)]
  227. ;; An unwatch shouldn't be needed on startup. However not having this
  228. ;; after an app refresh can cause stale page data to load
  229. (fs/unwatch-dir! dir)
  230. (fs/watch-dir! dir))))