file.cljs 11 KB

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