nfs.cljs 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  1. (ns frontend.fs.nfs
  2. (:require [frontend.fs.protocol :as protocol]
  3. [frontend.util :as util]
  4. [clojure.string :as string]
  5. [frontend.idb :as idb]
  6. [promesa.core :as p]
  7. [lambdaisland.glogi :as log]
  8. [goog.object :as gobj]
  9. [frontend.db :as db]
  10. [frontend.config :as config]
  11. [frontend.state :as state]
  12. [frontend.handler.notification :as notification]
  13. ["/frontend/utils" :as utils]
  14. [frontend.encrypt :as encrypt]))
  15. ;; We need to cache the file handles in the memory so that
  16. ;; the browser will not keep asking permissions.
  17. (defonce nfs-file-handles-cache (atom {}))
  18. (defn get-nfs-file-handle
  19. [handle-path]
  20. (get @nfs-file-handles-cache handle-path))
  21. (defn add-nfs-file-handle!
  22. [handle-path handle]
  23. (swap! nfs-file-handles-cache assoc handle-path handle))
  24. (defn remove-nfs-file-handle!
  25. [handle-path]
  26. (swap! nfs-file-handles-cache dissoc handle-path))
  27. (defn nfs-saved-handler
  28. [repo path file]
  29. (when-let [last-modified (gobj/get file "lastModified")]
  30. ;; TODO: extract
  31. (let [path (if (= \/ (first path))
  32. (subs path 1)
  33. path)]
  34. ;; Bad code
  35. (db/set-file-last-modified-at! repo path last-modified))))
  36. (defn verify-permission
  37. [repo handle read-write?]
  38. (let [repo (or repo (state/get-current-repo))]
  39. (p/then
  40. (utils/verifyPermission handle read-write?)
  41. (fn []
  42. (state/set-state! [:nfs/user-granted? repo] true)
  43. true))))
  44. (defn check-directory-permission!
  45. [repo]
  46. (when (config/local-db? repo)
  47. (p/let [handle (idb/get-item (str "handle/" repo))]
  48. (when handle
  49. (verify-permission repo handle true)))))
  50. (defn- contents-matched?
  51. [disk-content db-content]
  52. (when (and (string? disk-content) (string? db-content))
  53. (if (encrypt/encrypted-db? (state/get-current-repo))
  54. (p/let [decrypted-content (encrypt/decrypt disk-content)]
  55. (= (string/trim decrypted-content) (string/trim db-content)))
  56. (p/resolved (= (string/trim disk-content) (string/trim db-content))))))
  57. (defrecord ^:large-vars/cleanup-todo Nfs []
  58. protocol/Fs
  59. (mkdir! [_this dir]
  60. (let [parts (->> (string/split dir "/")
  61. (remove string/blank?))
  62. root (->> (butlast parts)
  63. (string/join "/"))
  64. new-dir (last parts)
  65. root-handle (str "handle/" root)]
  66. (->
  67. (p/let [handle (idb/get-item root-handle)
  68. _ (when handle (verify-permission nil handle true))]
  69. (when (and handle new-dir
  70. (not (string/blank? new-dir)))
  71. (p/let [handle (.getDirectoryHandle ^js handle new-dir
  72. #js {:create true})
  73. handle-path (str root-handle "/" new-dir)
  74. _ (idb/set-item! handle-path handle)]
  75. (add-nfs-file-handle! handle-path handle)
  76. (println "Stored handle: " (str root-handle "/" new-dir)))))
  77. (p/catch (fn [error]
  78. (js/console.debug "mkdir error: " error ", dir: " dir)
  79. (throw error))))))
  80. (readdir [_this dir]
  81. (let [prefix (str "handle/" dir)
  82. cached-files (keys @nfs-file-handles-cache)]
  83. (p/resolved
  84. (->> (filter #(string/starts-with? % (str prefix "/")) cached-files)
  85. (map (fn [path]
  86. (string/replace path prefix "")))))))
  87. (unlink! [this repo path _opts]
  88. (let [[dir basename] (util/get-dir-and-basename path)
  89. handle-path (str "handle" path)]
  90. (->
  91. (p/let [recycle-dir (str "/" repo (util/format "/%s/%s" config/app-name config/recycle-dir))
  92. _ (protocol/mkdir! this recycle-dir)
  93. handle (idb/get-item handle-path)
  94. file (.getFile handle)
  95. content (.text file)
  96. handle (idb/get-item (str "handle" dir))
  97. _ (idb/remove-item! handle-path)
  98. file-name (-> (string/replace path (str "/" repo "/") "")
  99. (string/replace "/" "_")
  100. (string/replace "\\" "_"))
  101. new-path (str recycle-dir "/" file-name)
  102. _ (protocol/write-file! this repo
  103. "/"
  104. new-path
  105. content nil)]
  106. (when handle
  107. (.removeEntry ^js handle basename))
  108. (remove-nfs-file-handle! handle-path))
  109. (p/catch (fn [error]
  110. (log/error :unlink/path {:path path
  111. :error error}))))))
  112. (rmdir! [_this _dir]
  113. ;; TOO dangerious, we should never implement this
  114. nil)
  115. (read-file [_this dir path _options]
  116. (let [handle-path (str "handle" dir "/" path)]
  117. (p/let [handle (idb/get-item handle-path)
  118. local-file (and handle (.getFile handle))]
  119. (and local-file (.text local-file)))))
  120. (write-file! [_this repo dir path content opts]
  121. (let [parts (string/split path "/")
  122. basename (last parts)
  123. sub-dir (->> (butlast parts)
  124. (remove string/blank?)
  125. (string/join "/"))
  126. sub-dir-handle-path (str "handle/"
  127. (subs dir 1)
  128. (when sub-dir
  129. (str "/" sub-dir)))
  130. handle-path (if (= "/" (last sub-dir-handle-path))
  131. (subs sub-dir-handle-path 0 (dec (count sub-dir-handle-path)))
  132. sub-dir-handle-path)
  133. handle-path (string/replace handle-path "//" "/")
  134. basename-handle-path (str handle-path "/" basename)]
  135. (p/let [file-handle (idb/get-item basename-handle-path)]
  136. ;; check file-handle available, remove it when got 'NotFoundError'
  137. (p/let [test-get-file (when file-handle
  138. (p/catch (p/let [_ (.getFile file-handle)] true)
  139. (fn [e]
  140. (js/console.dir e)
  141. (when (= "NotFoundError" (.-name e))
  142. (idb/remove-item! basename-handle-path)
  143. (remove-nfs-file-handle! basename-handle-path))
  144. false)))
  145. file-handle (if test-get-file file-handle nil)]
  146. (when file-handle
  147. (add-nfs-file-handle! basename-handle-path file-handle))
  148. (if file-handle
  149. (-> (p/let [local-file (.getFile file-handle)
  150. local-content (.text local-file)
  151. ext (string/lower-case (util/get-file-ext path))
  152. db-content (db/get-file repo path)
  153. contents-matched? (contents-matched? local-content (or db-content ""))]
  154. (when local-content
  155. (if (and
  156. (not (string/blank? db-content))
  157. (not (:skip-compare? opts))
  158. (not contents-matched?)
  159. (not (contains? #{"excalidraw" "edn" "css"} ext))
  160. (not (string/includes? path "/.recycle/")))
  161. (p/let [local-content (encrypt/decrypt local-content)]
  162. (state/pub-event! [:file/not-matched-from-disk path local-content content]))
  163. (p/let [_ (verify-permission repo file-handle true)
  164. _ (utils/writeFile file-handle content)
  165. file (.getFile file-handle)]
  166. (when file
  167. (p/let [content (if (encrypt/encrypted-db? (state/get-current-repo))
  168. (encrypt/decrypt content)
  169. content)]
  170. (db/set-file-content! repo path content))
  171. (nfs-saved-handler repo path file))))))
  172. (p/catch (fn [e]
  173. (js/console.error e))))
  174. ;; create file handle
  175. (->
  176. (p/let [handle (idb/get-item handle-path)]
  177. (if handle
  178. (p/let [_ (verify-permission repo handle true)
  179. file-handle (.getFileHandle ^js handle basename #js {:create true})
  180. ;; File exists if the file-handle has some content in it.
  181. file (.getFile file-handle)
  182. text (.text file)]
  183. (if (string/blank? text)
  184. (p/let [_ (idb/set-item! basename-handle-path file-handle)
  185. _ (utils/writeFile file-handle content)
  186. file (.getFile file-handle)]
  187. (when file
  188. (nfs-saved-handler repo path file)))
  189. (do
  190. (notification/show! (str "The file " path " already exists, please append the content if you need it.\n Unsaved content: \n" content)
  191. :warning
  192. false)
  193. (state/pub-event! [:file/alter repo path text]))))
  194. (println "Error: directory handle not exists: " handle-path)))
  195. (p/catch (fn [error]
  196. (println "Write local file failed: " {:path path})
  197. (js/console.error error)))))))))
  198. (rename! [this repo old-path new-path]
  199. (p/let [parts (->> (string/split new-path "/")
  200. (remove string/blank?))
  201. dir (str "/" (first parts))
  202. new-path (->> (rest parts)
  203. (string/join "/"))
  204. handle (idb/get-item (str "handle" old-path))
  205. file (.getFile handle)
  206. content (.text file)
  207. _ (protocol/write-file! this repo dir new-path content nil)]
  208. (protocol/unlink! this repo old-path nil)))
  209. (stat [_this dir path]
  210. (if-let [file (get-nfs-file-handle (str "handle/"
  211. (string/replace-first dir "/" "")
  212. path))]
  213. (p/let [file (.getFile file)]
  214. (let [get-attr #(gobj/get file %)]
  215. {:file/last-modified-at (get-attr "lastModified")
  216. :file/size (get-attr "size")
  217. :file/type (get-attr "type")}))
  218. (p/rejected "File not exists")))
  219. (open-dir [_this ok-handler]
  220. (utils/openDirectory #js {:recursive true}
  221. ok-handler))
  222. (get-files [_this path-or-handle ok-handler]
  223. (utils/getFiles path-or-handle true ok-handler))
  224. ;; TODO:
  225. (watch-dir! [_this _dir]
  226. nil))