Files
logseq/src/main/frontend/fs.cljs
2024-09-07 00:13:09 -04:00

280 lines
8.3 KiB
Clojure

(ns frontend.fs
"System-component-like ns that provides common file operations for all
platforms by delegating to implementations of the fs protocol"
(:require [cljs-bean.core :as bean]
[frontend.config :as config]
[frontend.fs.nfs :as nfs]
[frontend.fs.node :as node]
[frontend.fs.capacitor-fs :as capacitor-fs]
[frontend.fs.memory-fs :as memory-fs]
[frontend.mobile.util :as mobile-util]
[frontend.fs.protocol :as protocol]
[frontend.util :as util]
[lambdaisland.glogi :as log]
[promesa.core :as p]
[logseq.common.path :as path]
[clojure.string :as string]
[frontend.state :as state]
[logseq.common.util :as common-util]
[electron.ipc :as ipc]))
(defonce nfs-backend (nfs/->Nfs))
(defonce memory-backend (memory-fs/->MemoryFs))
(defonce node-backend (node/->Node))
(defonce mobile-backend (capacitor-fs/->Capacitorfs))
(defn- get-native-backend
"Native FS backend of current platform"
[]
(cond
(util/electron?)
node-backend
(mobile-util/native-platform?)
mobile-backend
:else
nfs-backend))
(defn get-fs
[dir & {:keys [repo rpath]}]
(let [repo (or repo (state/get-current-repo))
bfs-local? (and dir
(or (string/starts-with? dir (str "/" config/demo-repo))
(string/starts-with? dir config/demo-repo)))
db-assets? (and
(config/db-based-graph? repo)
rpath
(string/starts-with? rpath "assets/"))]
(cond
(and db-assets? (util/electron?))
node-backend
db-assets?
memory-backend
(nil? dir) ;; global file op, use native backend
(get-native-backend)
(string/starts-with? dir "memory://")
memory-backend
(and (util/electron?) (not bfs-local?))
node-backend
(mobile-util/native-platform?)
mobile-backend
:else
nfs-backend)))
(defn mkdir!
[dir]
(protocol/mkdir! (get-fs dir) dir))
(defn mkdir-recur!
[dir]
(protocol/mkdir-recur! (get-fs dir) dir))
(defn readdir
"list all absolute paths in dir, absolute"
[dir & {:keys [path-only?]}]
(when-not path-only?
(js/console.error "BUG: (deprecation) path-only? is always true"))
(p/let [result (protocol/readdir (get-fs dir) dir)
result (bean/->clj result)]
(map common-util/path-normalize result)))
(defn unlink!
"Should move the path to logseq/recycle instead of deleting it."
[repo fpath opts]
;; TODO(andelf): better handle backup here, instead of fs impl
(protocol/unlink! (get-fs fpath) repo fpath opts))
(defn rmdir!
"Remove the directory recursively.
Warning: only run it for browser cache."
[dir]
(when-let [fs (get-fs dir)]
(when (= fs memory-backend)
(protocol/rmdir! fs dir))))
;; TODO(andelf): distinguish from graph file writing and global file write
(defn write-file!
[repo dir rpath content opts]
(when content
(let [path (common-util/path-normalize rpath)
fs-record (get-fs dir {:repo repo
:rpath rpath})]
(->
(p/let [opts (assoc opts
:error-handler
(fn [error]
(state/pub-event! [:capture-error {:error error
:payload {:type :write-file/failed
:fs (type fs-record)
:user-agent (when js/navigator js/navigator.userAgent)
:content-length (count content)}}])))
_ (protocol/write-file! fs-record repo dir path content opts)])
(p/catch (fn [error]
(log/error :file/write-failed {:dir dir
:path path
:error error})
;; Disable this temporarily
;; (js/alert "Current file can't be saved! Please copy its content to your local file system and click the refresh button.")
))))))
(defn read-file
([dir path]
(let [fs (get-fs dir)
options (if (= fs memory-backend)
{:encoding "utf8"}
{})]
(read-file dir path options)))
([dir path options]
(protocol/read-file (get-fs dir) dir path options)))
(defn rename!
"Rename files, incoming relative path, converted to absolute path"
[repo old-path new-path]
(let [new-path (common-util/path-normalize new-path)]
(cond
; See https://github.com/isomorphic-git/lightning-fs/issues/41
(= old-path new-path)
(p/resolved nil)
:else
(let [repo-dir (config/get-repo-dir repo)
old-fpath (path/path-join repo-dir old-path)
new-fpath (path/path-join repo-dir new-path)]
(protocol/rename! (get-fs old-fpath) repo old-fpath new-fpath)))))
(defn stat
([fpath]
(protocol/stat (get-fs fpath) fpath))
([dir path]
(let [fpath (path/path-join dir path)]
(protocol/stat (get-fs dir) fpath))))
(defn mkdir-if-not-exists
[dir]
(when dir
(util/p-handle
(stat dir)
(fn [_stat])
(fn [_error]
(mkdir-recur! dir)))))
(defn copy!
"Only used by Logseq Sync"
[repo old-path new-path]
(cond
(= old-path new-path)
(p/resolved nil)
:else
(let [[old-path new-path]
(map #(if (or (util/electron?) (mobile-util/native-platform?))
%
(str (config/get-repo-dir repo) "/" %))
[old-path new-path])
new-dir (path/dirname new-path)]
(p/let [_ (mkdir-if-not-exists new-dir)]
(protocol/copy! (get-fs old-path) repo old-path new-path)))))
(defn open-dir
[dir]
(let [record (get-native-backend)]
(p/let [result (protocol/open-dir record dir)]
(when result
(let [{:keys [path files]} result
dir path
files (mapv (fn [entry]
(assoc entry :path (path/relative-path dir (:path entry))))
files)]
{:path path :files files})))))
(defn get-files
"List all files in the directory, recursively.
Wrap as {:path string :files []}, using relative path"
[dir]
(let [fs-record (get-native-backend)]
(p/let [files (protocol/get-files fs-record dir)]
(println ::get-files (count files) "files")
(let [files (mapv (fn [entry]
(assoc entry :path (path/relative-path dir (:path entry))))
files)]
{:path dir :files files}))))
(defn watch-dir!
([dir] (watch-dir! dir {}))
([dir options] (protocol/watch-dir! (get-fs dir) dir options)))
(defn unwatch-dir!
[dir]
(protocol/unwatch-dir! (get-fs dir) dir))
;; FIXME: counterintuitive return value
(defn create-if-not-exists
"Create a file if it doesn't exist. return false on written, true on already exists"
([repo dir path]
(create-if-not-exists repo dir path ""))
([repo dir path initial-content]
(-> (p/let [_stat (stat dir path)]
true)
(p/catch
(fn [_error]
(p/let [_ (write-file! repo dir path initial-content nil)]
false))))))
(defn file-exists?
([fpath]
(util/p-handle
(stat fpath)
(fn [stat'] (not (nil? stat')))
(fn [_e] false)))
([dir path]
(util/p-handle
(stat dir path)
(fn [stat'] (not (nil? stat')))
(fn [_e] false))))
(defn asset-href-exists?
"href is from `make-asset-url`, so it's most likely a full-path"
[href]
(p/let [repo-dir (config/get-repo-dir (state/get-current-repo))
rpath (path/relative-path repo-dir href)
exist? (file-exists? repo-dir rpath)]
exist?))
(defn asset-path-normalize
[path]
(cond
(util/electron?)
(path/url-to-path path)
(mobile-util/native-platform?)
path
:else
path))
(defn dir-exists?
[dir]
(file-exists? dir ""))
(defn backup-db-file!
[repo path db-content disk-content]
(cond
(util/electron?)
(ipc/ipc "backupDbFile" (config/get-local-dir repo) path db-content disk-content)
(mobile-util/native-platform?)
(capacitor-fs/backup-file repo :backup-dir path db-content)
;; TODO: nfs
))