fs.cljs 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. (ns frontend.fs
  2. "System-component-like ns that provides common file operations for all
  3. platforms by delegating to implementations of the fs protocol"
  4. (:require [cljs-bean.core :as bean]
  5. [clojure.string :as string]
  6. [electron.ipc :as ipc]
  7. [frontend.config :as config]
  8. [frontend.fs.capacitor-fs :as capacitor-fs]
  9. [frontend.fs.memory-fs :as memory-fs]
  10. [frontend.fs.node :as node]
  11. [frontend.fs.protocol :as protocol]
  12. [frontend.mobile.util :as mobile-util]
  13. [frontend.state :as state]
  14. [frontend.util :as util]
  15. [lambdaisland.glogi :as log]
  16. [logseq.common.path :as path]
  17. [logseq.common.util :as common-util]
  18. [promesa.core :as p]))
  19. (defonce memory-backend (memory-fs/->MemoryFs))
  20. (defonce node-backend (node/->Node))
  21. (defonce mobile-backend (capacitor-fs/->Capacitorfs))
  22. (defn- get-native-backend
  23. "Native FS backend of current platform"
  24. []
  25. (cond
  26. (util/electron?)
  27. node-backend
  28. (mobile-util/native-platform?)
  29. mobile-backend))
  30. (defn get-fs
  31. [dir & {:keys [repo rpath]}]
  32. (let [repo (or repo (state/get-current-repo))
  33. bfs-local? (and dir
  34. (or (string/starts-with? dir (str "/" config/demo-repo))
  35. (string/starts-with? dir config/demo-repo)))
  36. db-assets? (and
  37. (config/db-based-graph? repo)
  38. rpath
  39. (string/starts-with? rpath "assets/"))]
  40. (cond
  41. (and db-assets? (util/electron?))
  42. node-backend
  43. db-assets?
  44. memory-backend
  45. (nil? dir) ;; global file op, use native backend
  46. (get-native-backend)
  47. (string/starts-with? dir "memory://")
  48. memory-backend
  49. (and (util/electron?) (not bfs-local?))
  50. node-backend
  51. :else
  52. mobile-backend)))
  53. (defn mkdir!
  54. [dir]
  55. (protocol/mkdir! (get-fs dir) dir))
  56. (defn mkdir-recur!
  57. [dir]
  58. (protocol/mkdir-recur! (get-fs dir) dir))
  59. (defn readdir
  60. "list all absolute paths in dir, absolute"
  61. [dir & {:keys [path-only?]}]
  62. (when-not path-only?
  63. (js/console.error "BUG: (deprecation) path-only? is always true"))
  64. (p/let [result (protocol/readdir (get-fs dir) dir)
  65. result (bean/->clj result)]
  66. (map common-util/path-normalize result)))
  67. (defn unlink!
  68. "Should move the path to logseq/recycle instead of deleting it."
  69. [repo fpath opts]
  70. ;; TODO(andelf): better handle backup here, instead of fs impl
  71. (protocol/unlink! (get-fs fpath) repo fpath opts))
  72. (defn rmdir!
  73. "Remove the directory recursively.
  74. Warning: only run it for browser cache."
  75. [dir]
  76. (when-let [fs (get-fs dir)]
  77. (when (= fs memory-backend)
  78. (protocol/rmdir! fs dir))))
  79. (defn write-plain-text-file!
  80. "Use it only for plain-text files, not binary"
  81. [repo dir rpath content opts]
  82. (when content
  83. (let [path (common-util/path-normalize rpath)
  84. fs-record (get-fs dir {:repo repo
  85. :rpath rpath})]
  86. (->
  87. (p/let [opts (assoc opts
  88. :error-handler
  89. (fn [error]
  90. (state/pub-event! [:capture-error {:error error
  91. :payload {:type :write-file/failed
  92. :fs (type fs-record)
  93. :user-agent (when js/navigator js/navigator.userAgent)
  94. :content-length (count content)}}])))
  95. _ (protocol/write-file! fs-record repo dir path content opts)])
  96. (p/catch (fn [error]
  97. (log/error :file/write-failed {:dir dir
  98. :path path
  99. :error error})
  100. ;; Disable this temporarily
  101. ;; (js/alert "Current file can't be saved! Please copy its content to your local file system and click the refresh button.")
  102. ))))))
  103. (defn read-file
  104. ([dir path]
  105. (let [fs (get-fs dir)
  106. options (if (= fs memory-backend)
  107. {:encoding "utf8"}
  108. {})]
  109. (read-file dir path options)))
  110. ([dir path options]
  111. (protocol/read-file (get-fs dir) dir path options)))
  112. (defn rename!
  113. "Rename files, incoming relative path, converted to absolute path"
  114. [repo old-path new-path]
  115. (let [new-path (common-util/path-normalize new-path)]
  116. (cond
  117. ; See https://github.com/isomorphic-git/lightning-fs/issues/41
  118. (= old-path new-path)
  119. (p/resolved nil)
  120. :else
  121. (let [repo-dir (config/get-repo-dir repo)
  122. old-fpath (path/path-join repo-dir old-path)
  123. new-fpath (path/path-join repo-dir new-path)]
  124. (protocol/rename! (get-fs old-fpath) repo old-fpath new-fpath)))))
  125. (defn stat
  126. ([fpath]
  127. (protocol/stat (get-fs fpath) fpath))
  128. ([dir path]
  129. (let [fpath (path/path-join dir path)]
  130. (protocol/stat (get-fs dir) fpath))))
  131. (defn mkdir-if-not-exists
  132. [dir]
  133. (when dir
  134. (util/p-handle
  135. (stat dir)
  136. (fn [_stat])
  137. (fn [_error]
  138. (mkdir-recur! dir)))))
  139. (defn copy!
  140. "Only used by Logseq Sync"
  141. [repo old-path new-path]
  142. (cond
  143. (= old-path new-path)
  144. (p/resolved nil)
  145. :else
  146. (let [[old-path new-path]
  147. (map #(if (or (util/electron?) (mobile-util/native-platform?))
  148. %
  149. (str (config/get-repo-dir repo) "/" %))
  150. [old-path new-path])
  151. new-dir (path/dirname new-path)]
  152. (p/let [_ (mkdir-if-not-exists new-dir)]
  153. (protocol/copy! (get-fs old-path) repo old-path new-path)))))
  154. (defn open-dir
  155. [dir]
  156. (let [record (get-native-backend)]
  157. (p/let [result (protocol/open-dir record dir)]
  158. (when result
  159. (let [{:keys [path files]} result
  160. dir path
  161. files (mapv (fn [entry]
  162. (assoc entry :path (path/relative-path dir (:path entry))))
  163. files)]
  164. {:path path :files files})))))
  165. (defn get-files
  166. "List all files in the directory, recursively.
  167. Wrap as {:path string :files []}, using relative path"
  168. [dir]
  169. (let [fs-record (get-native-backend)]
  170. (p/let [files (protocol/get-files fs-record dir)]
  171. (println ::get-files (count files) "files")
  172. (let [files (mapv (fn [entry]
  173. (assoc entry :path (path/relative-path dir (:path entry))))
  174. files)]
  175. {:path dir :files files}))))
  176. (defn watch-dir!
  177. ([dir] (watch-dir! dir {}))
  178. ([dir options] (protocol/watch-dir! (get-fs dir) dir options)))
  179. (defn unwatch-dir!
  180. [dir]
  181. (protocol/unwatch-dir! (get-fs dir) dir))
  182. ;; FIXME: counterintuitive return value
  183. (defn create-if-not-exists
  184. "Create a file if it doesn't exist. return false on written, true on already exists"
  185. ([repo dir path]
  186. (create-if-not-exists repo dir path ""))
  187. ([repo dir path initial-content]
  188. (-> (p/let [_stat (stat dir path)]
  189. true)
  190. (p/catch
  191. (fn [_error]
  192. (p/let [_ (write-plain-text-file! repo dir path initial-content nil)]
  193. false))))))
  194. (defn file-exists?
  195. ([fpath]
  196. (util/p-handle
  197. (stat fpath)
  198. (fn [stat'] (not (nil? stat')))
  199. (fn [_e] false)))
  200. ([dir path]
  201. (util/p-handle
  202. (stat dir path)
  203. (fn [stat'] (not (nil? stat')))
  204. (fn [_e] false))))
  205. (defn asset-href-exists?
  206. "href is from `make-asset-url`, so it's most likely a full-path"
  207. [href]
  208. (p/let [repo-dir (config/get-repo-dir (state/get-current-repo))
  209. rpath (path/relative-path repo-dir href)
  210. exist? (file-exists? repo-dir rpath)]
  211. exist?))
  212. (defn asset-path-normalize
  213. [path]
  214. (cond
  215. (util/electron?)
  216. (path/url-to-path path)
  217. (mobile-util/native-platform?)
  218. path
  219. :else
  220. path))
  221. (defn dir-exists?
  222. [dir]
  223. (file-exists? dir ""))
  224. (defn backup-db-file!
  225. [repo path db-content disk-content]
  226. (cond
  227. (util/electron?)
  228. (ipc/ipc "backupDbFile" (config/get-local-dir repo) path db-content disk-content)
  229. (mobile-util/native-platform?)
  230. (capacitor-fs/backup-file repo :backup-dir path db-content)
  231. ;; TODO: nfs
  232. ))