file.cljs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. (ns frontend.handler.file
  2. (:refer-clojure :exclude [load-file])
  3. (:require ["/frontend/utils" :as utils]
  4. [borkdude.rewrite-edn :as rewrite]
  5. [cljs.core.async.interop :refer [<p!]]
  6. [clojure.core.async :as async]
  7. [frontend.config :as config]
  8. [frontend.db :as db]
  9. [frontend.fs :as fs]
  10. [frontend.fs.nfs :as nfs]
  11. [frontend.handler.common :as common-handler]
  12. [frontend.handler.ui :as ui-handler]
  13. [frontend.state :as state]
  14. [frontend.util :as util]
  15. [logseq.graph-parser.util :as gp-util]
  16. [lambdaisland.glogi :as log]
  17. [promesa.core :as p]
  18. [frontend.mobile.util :as mobile]
  19. [logseq.graph-parser.config :as gp-config]
  20. [logseq.graph-parser :as graph-parser]))
  21. ;; TODO: extract all git ops using a channel
  22. (defn load-file
  23. [repo-url path]
  24. (->
  25. (p/let [content (fs/read-file (config/get-repo-dir repo-url) path)]
  26. content)
  27. (p/catch
  28. (fn [e]
  29. (println "Load file failed: " path)
  30. (js/console.error e)))))
  31. (defn load-multiple-files
  32. [repo-url paths]
  33. (doall
  34. (mapv #(load-file repo-url %) paths)))
  35. (defn- keep-formats
  36. [files formats]
  37. (filter
  38. (fn [file]
  39. (let [format (gp-util/get-format file)]
  40. (contains? formats format)))
  41. files))
  42. (defn- only-text-formats
  43. [files]
  44. (keep-formats files (gp-config/text-formats)))
  45. (defn- only-image-formats
  46. [files]
  47. (keep-formats files (gp-config/img-formats)))
  48. (defn restore-config!
  49. ([repo-url project-changed-check?]
  50. (restore-config! repo-url nil project-changed-check?))
  51. ([repo-url config-content _project-changed-check?]
  52. (let [config-content (if config-content config-content
  53. (common-handler/get-config repo-url))]
  54. (when config-content
  55. (common-handler/reset-config! repo-url config-content)))))
  56. (defn load-files-contents!
  57. [repo-url files ok-handler]
  58. (let [images (only-image-formats files)
  59. files (only-text-formats files)]
  60. (-> (p/all (load-multiple-files repo-url files))
  61. (p/then (fn [contents]
  62. (let [file-contents (cond->
  63. (zipmap files contents)
  64. (seq images)
  65. (merge (zipmap images (repeat (count images) ""))))
  66. file-contents (for [[file content] file-contents]
  67. {:file/path (gp-util/path-normalize file)
  68. :file/content content})]
  69. (ok-handler file-contents))))
  70. (p/catch (fn [error]
  71. (log/error :nfs/load-files-error repo-url)
  72. (log/error :exception error))))))
  73. (defn- page-exists-in-another-file
  74. "Conflict of files towards same page"
  75. [repo-url page file]
  76. (when-let [page-name (:block/name page)]
  77. (let [current-file (:file/path (db/get-page-file repo-url page-name))]
  78. (when (not= file current-file)
  79. current-file))))
  80. (defn- get-delete-blocks [repo-url first-page file]
  81. (let [delete-blocks (->
  82. (concat
  83. (db/delete-file-blocks! repo-url file)
  84. (when first-page (db/delete-page-blocks repo-url (:block/name first-page))))
  85. (distinct))]
  86. (when-let [current-file (page-exists-in-another-file repo-url first-page file)]
  87. (when (not= file current-file)
  88. (let [error (str "Page already exists with another file: " current-file ", current file: " file)]
  89. (state/pub-event! [:notification/show
  90. {:content error
  91. :status :error
  92. :clear? false}]))))
  93. delete-blocks))
  94. (defn reset-file!
  95. ([repo-url file content]
  96. (reset-file! repo-url file content {}))
  97. ([repo-url file content {:keys [verbose] :as options}]
  98. (let [electron-local-repo? (and (util/electron?)
  99. (config/local-db? repo-url))
  100. file (cond
  101. (and electron-local-repo?
  102. util/win32?
  103. (utils/win32 file))
  104. file
  105. (and electron-local-repo? (or
  106. util/win32?
  107. (not= "/" (first file))))
  108. (str (config/get-repo-dir repo-url) "/" file)
  109. (and (mobile/native-android?) (not= "/" (first file)))
  110. file
  111. (and (mobile/native-ios?) (not= "/" (first file)))
  112. file
  113. :else
  114. file)
  115. file (gp-util/path-normalize file)
  116. new? (nil? (db/entity [:file/path file]))]
  117. (:tx
  118. (graph-parser/parse-file
  119. (db/get-db repo-url false)
  120. file
  121. content
  122. (merge (dissoc options :verbose)
  123. {:new? new?
  124. :delete-blocks-fn (partial get-delete-blocks repo-url)
  125. :extract-options (merge
  126. {:user-config (state/get-config)
  127. :date-formatter (state/get-date-formatter)
  128. :page-name-order (state/page-name-order)
  129. :block-pattern (config/get-block-pattern (gp-util/get-format file))
  130. :supported-formats (gp-config/supported-formats)}
  131. (when (some? verbose) {:verbose verbose}))}))))))
  132. ;; TODO: Remove this function in favor of `alter-files`
  133. (defn alter-file
  134. [repo path content {:keys [reset? re-render-root? from-disk? skip-compare? new-graph? verbose]
  135. :or {reset? true
  136. re-render-root? false
  137. from-disk? false
  138. skip-compare? false}}]
  139. (let [original-content (db/get-file repo path)
  140. write-file! (if from-disk?
  141. #(p/resolved nil)
  142. #(fs/write-file! repo (config/get-repo-dir repo) path content
  143. (assoc (when original-content {:old-content original-content})
  144. :skip-compare? skip-compare?)))
  145. opts {:new-graph? new-graph?
  146. :from-disk? from-disk?}]
  147. (if reset?
  148. (do
  149. (when-let [page-id (db/get-file-page-id path)]
  150. (db/transact! repo
  151. [[:db/retract page-id :block/alias]
  152. [:db/retract page-id :block/tags]]
  153. opts))
  154. (reset-file! repo path content (merge opts
  155. (when (some? verbose) {:verbose verbose}))))
  156. (db/set-file-content! repo path content opts))
  157. (util/p-handle (write-file!)
  158. (fn [_]
  159. (when (= path (config/get-config-path repo))
  160. (restore-config! repo true))
  161. (when (= path (config/get-custom-css-path repo))
  162. (ui-handler/add-style-if-exists!))
  163. (when re-render-root? (ui-handler/re-render-root!)))
  164. (fn [error]
  165. (println "Write file failed, path: " path ", content: " content)
  166. (log/error :write/failed error)))))
  167. (defn set-file-content!
  168. [repo path new-content]
  169. (alter-file repo path new-content {:reset? false
  170. :re-render-root? false}))
  171. (defn alter-files
  172. [repo files {:keys [reset? update-db?]
  173. :or {reset? false
  174. update-db? true}
  175. :as opts}]
  176. ;; old file content
  177. (let [file->content (let [paths (map first files)]
  178. (zipmap paths
  179. (map (fn [path] (db/get-file repo path)) paths)))]
  180. ;; update db
  181. (when update-db?
  182. (doseq [[path content] files]
  183. (if reset?
  184. (reset-file! repo path content)
  185. (db/set-file-content! repo path content))))
  186. (when-let [chan (state/get-file-write-chan)]
  187. (let [chan-callback (:chan-callback opts)]
  188. (async/put! chan [repo files opts file->content])
  189. (when chan-callback
  190. (chan-callback))))))
  191. (defn alter-files-handler!
  192. [repo files {:keys [finish-handler chan]} file->content]
  193. (let [write-file-f (fn [[path content]]
  194. (when path
  195. (let [original-content (get file->content path)]
  196. (-> (p/let [_ (or
  197. (util/electron?)
  198. (nfs/check-directory-permission! repo))]
  199. (fs/write-file! repo (config/get-repo-dir repo) path content
  200. {:old-content original-content}))
  201. (p/catch (fn [error]
  202. (state/pub-event! [:notification/show
  203. {:content (str "Failed to save the file " path ". Error: "
  204. (str error))
  205. :status :error
  206. :clear? false}])
  207. (state/pub-event! [:instrument {:type :write-file/failed
  208. :payload {:path path
  209. :content-length (count content)
  210. :error-str (str error)
  211. :error error}}])
  212. (log/error :write-file/failed {:path path
  213. :content content
  214. :error error})))))))
  215. finish-handler (fn []
  216. (when finish-handler
  217. (finish-handler))
  218. (ui-handler/re-render-file!))]
  219. (-> (p/all (map write-file-f files))
  220. (p/then (fn []
  221. (finish-handler)
  222. (when chan
  223. (async/put! chan true))))
  224. (p/catch (fn [error]
  225. (println "Alter files failed:")
  226. (js/console.error error)
  227. (async/put! chan false))))))
  228. (defn run-writes-chan!
  229. []
  230. (let [chan (state/get-file-write-chan)]
  231. (async/go-loop []
  232. (let [args (async/<! chan)]
  233. ;; return a channel
  234. (try
  235. (<p! (apply alter-files-handler! args))
  236. (catch js/Error e
  237. (log/error :file/write-failed e))))
  238. (recur))
  239. chan))
  240. (defn watch-for-current-graph-dir!
  241. []
  242. (when-let [repo (state/get-current-repo)]
  243. (when-let [dir (config/get-repo-dir repo)]
  244. (fs/unwatch-dir! dir)
  245. (fs/watch-dir! dir))))
  246. (defn create-metadata-file
  247. [repo-url encrypted?]
  248. (let [repo-dir (config/get-repo-dir repo-url)
  249. path (str config/app-name "/" config/metadata-file)
  250. file-path (str "/" path)
  251. default-content (if encrypted? "{:db/encrypted? true}" "{}")]
  252. (p/let [_ (fs/mkdir-if-not-exists (str repo-dir "/" config/app-name))
  253. file-exists? (fs/create-if-not-exists repo-url repo-dir file-path default-content)]
  254. (when-not file-exists?
  255. (reset-file! repo-url path default-content)))))
  256. (defn create-pages-metadata-file
  257. [repo-url]
  258. (let [repo-dir (config/get-repo-dir repo-url)
  259. path (str config/app-name "/" config/pages-metadata-file)
  260. file-path (str "/" path)
  261. default-content "{}"]
  262. (p/let [_ (fs/mkdir-if-not-exists (str repo-dir "/" config/app-name))
  263. file-exists? (fs/create-if-not-exists repo-url repo-dir file-path default-content)]
  264. (when-not file-exists?
  265. (reset-file! repo-url path default-content)))))
  266. (defn edn-file-set-key-value
  267. [path k v]
  268. (when-let [repo (state/get-current-repo)]
  269. (when-let [content (db/get-file path)]
  270. (common-handler/read-config content)
  271. (let [result (common-handler/parse-config content)
  272. ks (if (vector? k) k [k])
  273. new-result (rewrite/assoc-in result ks v)
  274. new-content (str new-result)]
  275. (set-file-content! repo path new-content)))))