chore: remove @logseq/capacitor-file-sync and capacitor fs

This commit is contained in:
Tienson Qin
2025-05-13 19:13:39 +08:00
parent 85b4243aac
commit 0d99eea39f
16 changed files with 65 additions and 4803 deletions

View File

@@ -111,8 +111,7 @@
"@js-joda/core": "3.2.0",
"@js-joda/locale_en-us": "3.1.1",
"@js-joda/timezone": "2.5.0",
"@logseq/capacitor-file-sync": "5.0.2",
"@logseq/diff-merge": "0.2.2",
"@logseq/diff-merge": "^0.2.2",
"@logseq/react-tweet-embed": "1.3.1-1",
"@logseq/sqlite-wasm": "=0.1.0",
"@radix-ui/colors": "^0.1.8",

View File

@@ -1,13 +1,13 @@
(ns frontend.db.persist
"Handles operations to persisting db to disk or indexedDB"
(:require [frontend.util :as util]
[frontend.idb :as idb]
(:require [cljs-bean.core :as bean]
[electron.ipc :as ipc]
[frontend.config :as config]
[frontend.db.conn :as db-conn]
[promesa.core :as p]
[frontend.idb :as idb]
[frontend.persist-db :as persist-db]
[cljs-bean.core :as bean]
[frontend.config :as config]))
[frontend.util :as util]
[promesa.core :as p]))
(defn get-all-graphs
[]
@@ -24,7 +24,7 @@
(distinct (concat
repos'
(map (fn [repo-name] {:name repo-name})
(concat idb-repos (some-> electron-disk-graphs bean/->clj)))))))
(concat idb-repos (some-> electron-disk-graphs bean/->clj)))))))
(defn delete-graph!
[graph]
@@ -34,13 +34,3 @@
(if (util/electron?)
(ipc/ipc "deleteGraph" graph key db-based?)
(idb/remove-item! key)))))
(defn rename-graph!
[old-repo new-repo]
(let [old-key (db-conn/get-repo-path old-repo)
new-key (db-conn/get-repo-path new-repo)]
(if (util/electron?)
(do
(js/console.error "rename-graph! is not supported in electron")
(idb/rename-item! old-key new-key))
(idb/rename-item! old-key new-key))))

View File

@@ -1,41 +1,20 @@
(ns frontend.encrypt
"Encryption related fns for use with encryption feature and file sync"
(:require [logseq.graph-parser.utf8 :as utf8]
(:require [electron.ipc :as ipc]
[frontend.util :as util]
[promesa.core :as p]
[electron.ipc :as ipc]
[frontend.mobile.util :as mobile-util]))
[logseq.graph-parser.utf8 :as utf8]
[promesa.core :as p]))
(defn encrypt-with-passphrase
[passphrase content]
(cond
(util/electron?)
(when (util/electron?)
(p/let [raw-content (utf8/encode content)
encrypted (ipc/ipc "encrypt-with-passphrase" passphrase raw-content)]
(utf8/decode encrypted))
(mobile-util/native-platform?)
(p/chain (.encryptWithPassphrase mobile-util/file-sync
(clj->js {:passphrase passphrase :content content}))
#(js->clj % :keywordize-keys true)
:data)
:else
nil))
(utf8/decode encrypted))))
(defn decrypt-with-passphrase
[passphrase content]
(cond
(util/electron?)
(when (util/electron?)
(p/let [raw-content (utf8/encode content)
decrypted (ipc/ipc "decrypt-with-passphrase" passphrase raw-content)]
(utf8/decode decrypted))
(mobile-util/native-platform?)
(p/chain (.decryptWithPassphrase mobile-util/file-sync
(clj->js {:passphrase passphrase :content content}))
#(js->clj % :keywordize-keys true)
:data)
:else
nil))
(utf8/decode decrypted))))

View File

@@ -5,11 +5,9 @@
[clojure.string :as string]
[electron.ipc :as ipc]
[frontend.config :as config]
[frontend.fs.capacitor-fs :as capacitor-fs]
[frontend.fs.memory-fs :as memory-fs]
[frontend.fs.node :as node]
[frontend.fs.protocol :as protocol]
[frontend.mobile.util :as mobile-util]
[frontend.state :as state]
[frontend.util :as util]
[lambdaisland.glogi :as log]
@@ -19,17 +17,12 @@
(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))
(when (util/electron?)
node-backend))
(defn get-fs
[dir & {:keys [repo rpath]}]
@@ -58,7 +51,7 @@
node-backend
:else
mobile-backend)))
nil)))
(defn mkdir!
[dir]
@@ -166,7 +159,7 @@
:else
(let [[old-path new-path]
(map #(if (or (util/electron?) (mobile-util/native-platform?))
(map #(if (util/electron?)
%
(str (config/get-repo-dir repo) "/" %))
[old-path new-path])
@@ -246,9 +239,6 @@
(util/electron?)
(path/url-to-path path)
(mobile-util/native-platform?)
path
:else
path))
@@ -258,12 +248,5 @@
(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
))
(when (util/electron?)
(ipc/ipc "backupDbFile" (config/get-local-dir repo) path db-content disk-content)))

View File

@@ -1,392 +0,0 @@
(ns frontend.fs.capacitor-fs
"Implementation of fs protocol for mobile"
(:require ["@capacitor/filesystem" :refer [Encoding Filesystem]]
[cljs-bean.core :as bean]
[clojure.string :as string]
[frontend.config :as config]
[frontend.db :as db]
[frontend.fs.protocol :as protocol]
[frontend.mobile.util :as mobile-util]
[frontend.state :as state]
[frontend.util :as util]
[goog.string :as gstring]
[lambdaisland.glogi :as log]
[logseq.common.path :as path]
[promesa.core :as p]
[rum.core :as rum]))
(when (mobile-util/native-ios?)
(defn ios-ensure-documents!
[]
(.ensureDocuments mobile-util/ios-file-container)))
(when (mobile-util/native-android?)
(defn- android-check-permission []
(p/let [permission (.checkPermissions Filesystem)
permission (-> permission
bean/->clj
:publicStorage)]
(when-not (= permission "granted")
(p/do!
(.requestPermissions Filesystem))))))
(defn- <dir-exists?
[fpath]
(p/catch (p/let [fpath (path/path-normalize fpath)
stat (.stat Filesystem (clj->js {:path fpath}))]
(-> stat
bean/->clj
:type
(= "directory")))
(fn [_error]
false)))
(defn- <write-file-with-utf8
[path content]
(when-not (string/blank? path)
(-> (p/chain (.writeFile Filesystem (clj->js {:path path
:data content
:encoding (.-UTF8 Encoding)
:recursive true}))
#(js->clj % :keywordize-keys true))
(p/catch (fn [error]
(js/console.error "writeFile Error: " path ": " error)
nil)))))
(defn- <read-file-with-utf8
[path]
(when-not (string/blank? path)
(-> (p/chain (.readFile Filesystem (clj->js {:path path
:encoding (.-UTF8 Encoding)}))
#(js->clj % :keywordize-keys true)
#(get % :data nil))
(p/catch (fn [error]
(js/console.error "readFile Error: " path ": " error)
nil)))))
(defn- <readdir [path]
(-> (p/chain (.readdir Filesystem (clj->js {:path path}))
#(js->clj % :keywordize-keys true)
:files)
(p/catch (fn [error]
(js/console.error "readdir Error: " path ": " error)
nil))))
(defn- get-file-paths
"get all file paths recursively"
[path]
(p/let [result (p/loop [result []
dirs [path]]
(if (empty? dirs)
result
(p/let [d (first dirs)
files (<readdir d)
files (->> files
(remove (fn [{:keys [name type]}]
(or (string/starts-with? name ".")
(and (= type "directory")
(or (= name "bak")
(= name "version-files")))))))
files-dir (->> files
(filterv #(= (:type %) "directory"))
(mapv :uri))
paths-result (->> files
(filterv #(= (:type %) "file"))
(mapv :uri))]
(p/recur (concat result paths-result)
(concat (rest dirs) files-dir)))))]
result))
(defn- get-files
"get all files and contents recursively"
[path]
(p/let [result (p/loop [result []
dirs [path]]
(if (empty? dirs)
result
(p/let [d (first dirs)
files (<readdir d)
files (->> files
(remove (fn [{:keys [name type]}]
(or (string/starts-with? name ".")
(and (= type "directory")
(or (= name "bak")
(= name "version-files")))))))
files-dir (->> files
(filterv #(= (:type %) "directory"))
(mapv :uri))
files-result
(p/all
(->> files
(filter #(= (:type %) "file"))
(filter
(fn [{:keys [uri]}]
(some #(string/ends-with? uri %)
[".md" ".markdown" ".org" ".edn" ".css"])))
(mapv
(fn [{:keys [uri] :as file-info}]
(p/chain (<read-file-with-utf8 uri)
#(assoc (dissoc file-info :uri)
:content %
:path uri))))))]
(p/recur (concat result files-result)
(concat (rest dirs) files-dir)))))]
(js->clj result :keywordize-keys true)))
(defn- <contents-matched?
[disk-content db-content]
(when (and (string? disk-content) (string? db-content))
(p/resolved (= (string/trim disk-content) (string/trim db-content)))))
(def backup-dir "logseq/bak")
(def version-file-dir "logseq/version-files/local")
(defn- get-backup-dir
[repo-dir path bak-dir ext]
(let [relative-path (-> path
(string/replace (re-pattern (str "^" (gstring/regExpEscape repo-dir)))
"")
(string/replace (re-pattern (str "(?i)" (gstring/regExpEscape (str "." ext)) "$"))
""))]
(path/path-join repo-dir bak-dir relative-path)))
(defn- <truncate-old-versioned-files!
"reserve the latest 6 version files"
[dir]
(-> (p/let [files (.readdir Filesystem (clj->js {:path dir}))
files (:files (js->clj files :keywordize-keys true))]
(drop 6 (reverse (sort-by :mtime files))))
(p/then (fn [old-version-files]
(p/all (mapv (fn [file]
(.deleteFile Filesystem (clj->js {:path (:uri file)})))
old-version-files))))
(p/catch (fn [_]))))
;; TODO: move this to FS protocol
(defn backup-file
"backup CONTENT under DIR :backup-dir or :version-file-dir
:backup-dir = `backup-dir`
:version-file-dir = `version-file-dir`"
[repo dir path content]
{:pre [(contains? #{:backup-dir :version-file-dir} dir)]}
(let [repo-dir (config/get-local-dir repo)
ext (util/get-file-ext path)
dir (case dir
:backup-dir (get-backup-dir repo-dir path backup-dir ext)
:version-file-dir (get-backup-dir repo-dir path version-file-dir ext))
new-path (path/path-join dir (str (string/replace (.toISOString (js/Date.)) ":" "_") "." (mobile-util/platform) "." ext))]
(<write-file-with-utf8 new-path content)
(<truncate-old-versioned-files! dir)))
(defn backup-file-handle-changed!
[repo-dir file-path content]
(let [divider-schema "://"
file-schema (string/split file-path divider-schema)
file-schema (if (> (count file-schema) 1) (first file-schema) "")
dir-schema? (and (string? repo-dir)
(string/includes? repo-dir divider-schema))
repo-dir (if-not dir-schema?
(str file-schema divider-schema repo-dir) repo-dir)
backup-root (path/path-join repo-dir backup-dir)
backup-dir-parent (util/node-path.dirname file-path)
backup-dir-parent (string/replace backup-dir-parent repo-dir "")
backup-dir-name (util/node-path.name file-path)
file-extname (.extname util/node-path file-path)
file-root (path/path-join backup-root backup-dir-parent backup-dir-name)
file-path (path/path-join file-root
(str (string/replace (.toISOString (js/Date.)) ":" "_") "." (mobile-util/platform) file-extname))]
(<write-file-with-utf8 file-path content)
(<truncate-old-versioned-files! file-root)))
(defn- write-file-impl!
[repo dir rpath content {:keys [ok-handler error-handler old-content skip-compare?]} stat]
(let [fpath (path/path-join dir rpath)]
(if (or (string/blank? repo) skip-compare?)
(p/catch
(p/let [result (<write-file-with-utf8 fpath content)]
(when ok-handler
(ok-handler repo fpath result)))
(fn [error]
(if error-handler
(error-handler error)
(log/error :write-file-failed error))))
;; Compare with disk content and backup if not equal
(p/let [disk-content (if (not= stat :not-found)
(<read-file-with-utf8 fpath)
"")
disk-content (or disk-content "")
repo-dir (config/get-local-dir repo)
db-content (or old-content (db/get-file repo rpath) "")
contents-matched? (<contents-matched? disk-content db-content)]
(->
(p/let [result (<write-file-with-utf8 fpath content)
mtime (-> (js->clj stat :keywordize-keys true)
:mtime)]
(when-not contents-matched?
(backup-file repo-dir :backup-dir fpath disk-content))
(db/set-file-last-modified-at! repo rpath mtime)
(db/set-file-content! repo rpath content)
(when ok-handler
(ok-handler repo fpath result))
result)
(p/catch (fn [error]
(if error-handler
(error-handler error)
(log/error :write-file-failed error)))))))))
(defn ios-force-include-private
"iOS sometimes return paths without the private part."
[path]
(if (mobile-util/native-ios?)
(cond
(or (string/includes? path "///private/")
;; virtual machine
(string/starts-with? path "file:///Users/"))
path
(string/includes? path "///")
(let [[prefix others] (string/split path "///")]
(str prefix "///private/" others))
:else
path)
path))
(defn- local-container-path?
"Check whether `path' is logseq's container `localDocumentsPath' on iOS"
[path localDocumentsPath]
(string/includes? path localDocumentsPath))
(rum/defc instruction
[]
[:div.instruction
[:h1.title "Please choose a valid directory!"]
[:p.leading-6 "Logseq app can only save or access your graphs stored in a specific directory with a "
[:strong "Logseq icon"]
" inside, located either in \"iCloud Drive\", \"On My iPhone\" or \"On My iPad\"."]
[:p.leading-6 "Please watch the following short instruction video. "
[:small.text-gray-500 "(may take few seconds to load...)"]]
[:iframe
{:src "https://www.loom.com/embed/dae612ae5fd94e508bd0acdf02efb888"
:frame-border "0"
:position "relative"
:allow-full-screen "allowfullscreen"
:webkit-allow-full-screen "webkitallowfullscreen"
:height "100%"}]])
(defn- open-dir
[dir]
(p/let [_ (when (mobile-util/native-android?) (android-check-permission))
{:keys [path localDocumentsPath]} (-> (.pickFolder mobile-util/folder-picker
(clj->js (when (and dir (mobile-util/native-ios?))
{:path dir})))
(p/then #(js->clj % :keywordize-keys true))
(p/catch (fn [e]
(js/alert (str e))
nil))) ;; NOTE: If pick folder fails, let it crash
_ (when (and (mobile-util/native-ios?)
(not (or (local-container-path? path localDocumentsPath)
(mobile-util/in-iCloud-container-path? path))))
(state/pub-event! [:modal/show-instruction]))
exists? (<dir-exists? path)
_ (when-not exists?
(p/rejected (str "Cannot access selected directory: " path)))
_ (when (mobile-util/is-iCloud-container-path? path)
(p/rejected (str "Please avoid accessing the top-level iCloud container path: " path)))
path (if (mobile-util/native-ios?)
(ios-force-include-private path)
path)
_ (js/console.log "Opening or Creating graph at directory: " path)
files (get-files path)]
{:path path
:files (into [] files)}))
(defrecord ^:large-vars/cleanup-todo Capacitorfs []
protocol/Fs
(mkdir! [_this dir]
(-> (<dir-exists? dir)
(p/then (fn [exists?]
(if exists?
(p/resolved true)
(.mkdir Filesystem
(clj->js
{:path dir})))))
(p/catch (fn [error]
(log/error :mkdir! {:path dir
:error error})))))
(mkdir-recur! [_this dir]
(-> (<dir-exists? dir)
(p/then (fn [exists?]
(if exists?
(p/resolved true)
(.mkdir Filesystem
(clj->js
{:path dir
:recursive true})))))
(p/catch (fn [error]
(log/error :mkdir-recur! {:path dir
:error error})))))
(readdir [_this dir] ; recursive
(let [dir (path/path-normalize dir)]
(get-file-paths dir)))
(unlink! [this repo fpath _opts]
(p/let [repo-dir (config/get-local-dir repo)
recycle-dir (path/path-join repo-dir config/app-name ".recycle") ;; logseq/.recycle
;; convert url to pure path
file-name (-> (path/trim-dir-prefix repo-dir fpath)
(string/replace "/" "_"))
new-path (path/path-join recycle-dir file-name)
_ (protocol/mkdir-recur! this recycle-dir)]
(protocol/rename! this repo fpath new-path)))
(rmdir! [_this _dir]
;; Too dangerous!!! We'll never implement this.
nil)
(read-file [_this dir path _options]
(let [fpath (path/path-join dir path)]
(->
(<read-file-with-utf8 fpath)
(p/catch (fn [error]
(log/error :read-file-failed error))))))
(write-file! [_this repo dir path content opts]
(let [fpath (path/path-join dir path)]
(p/let [stat (p/catch
(.stat Filesystem (clj->js {:path fpath}))
(fn [_e] :not-found))]
;; `path` is full-path
(write-file-impl! repo dir path content opts stat))))
(rename! [_this _repo old-fpath new-fpath]
(-> (.rename Filesystem
(clj->js
{:from old-fpath
:to new-fpath}))
(p/catch (fn [error]
(log/error :rename-file-failed error)))))
(copy! [_this _repo old-path new-path]
(-> (.copy Filesystem
(clj->js
{:from old-path
:to new-path}))
(p/catch (fn [error]
(log/error :copy-file-failed error)))))
(stat [_this fpath]
(-> (p/chain (.stat Filesystem (clj->js {:path fpath}))
#(js->clj % :keywordize-keys true))
(p/catch (fn [error]
(let [errstr (if error (.toString error) "")]
(when (string/includes? errstr "because you dont have permission to view it")
(state/pub-event! [:notification/show
{:content "No permission, please clear cache and re-open graph folder."
:status :error}]))
(p/rejected error))))))
(open-dir [_this dir]
(open-dir dir))
(get-files [_this dir]
(get-files dir))
(watch-dir! [_this dir _options]
(p/let [_ (.unwatch mobile-util/fs-watcher)]
(.watch mobile-util/fs-watcher (clj->js {:path dir}))))
(unwatch-dir! [_this _dir]
(.unwatch mobile-util/fs-watcher)))

View File

@@ -1,7 +1,6 @@
(ns frontend.fs.sync
"Main ns for providing file sync functionality"
(:require ["@capawesome/capacitor-background-task" :refer [BackgroundTask]]
["path" :as node-path]
(:require ["path" :as node-path]
[cljs-http.client :as http]
[cljs-time.coerce :as tc]
[cljs-time.core :as t]
@@ -23,12 +22,10 @@
[frontend.diff :as diff]
[frontend.encrypt :as encrypt]
[frontend.fs :as fs]
[frontend.fs.capacitor-fs :as capacitor-fs]
[frontend.fs.diff-merge :as diff-merge]
[frontend.handler.file-based.file :as file-handler]
[frontend.handler.notification :as notification]
[frontend.handler.user :as user]
[frontend.mobile.util :as mobile-util]
[frontend.pubsub :as pubsub]
[frontend.state :as state]
[frontend.util :as util]
@@ -911,149 +908,10 @@
(<add-new-version [_this repo path content]
(p->c (ipc/ipc "addVersionFile" (config/get-local-dir repo) path content))))
(deftype ^:large-vars/cleanup-todo CapacitorAPI [^:mutable graph-uuid' ^:mutable private-key ^:mutable public-key']
IToken
(<get-token [_this]
(user/<wrap-ensure-id&access-token
(state/get-auth-id-token)))
IRSAPI
(rsapi-ready? [_ graph-uuid] (and (= graph-uuid graph-uuid') private-key public-key'))
(<key-gen [_]
(go (let [r (<! (p->c (.keygen mobile-util/file-sync #js {})))]
(-> r
(js->clj :keywordize-keys true)))))
(<set-env [_ graph-uuid prod? secret-key public-key]
(set! graph-uuid' graph-uuid)
(set! private-key secret-key)
(set! public-key' public-key)
(p->c (.setEnv mobile-util/file-sync (clj->js {:graphUUID graph-uuid
:env (if prod? "prod" "dev")
:secretKey secret-key
:publicKey public-key}))))
(<get-local-all-files-meta [this graph-uuid base-path]
(go
(let [r (<! (p->c (.getLocalAllFilesMeta mobile-util/file-sync (clj->js {:graphUUID graph-uuid
:basePath base-path}))))]
(or (guard-ex r)
(<! (<build-local-file-metadatas this graph-uuid (.-result r)))))))
(<get-local-files-meta [this graph-uuid base-path filepaths]
(go
(let [r (<! (p->c (.getLocalFilesMeta mobile-util/file-sync
(clj->js {:graphUUID graph-uuid
:basePath base-path
:filePaths filepaths}))))]
(assert (not (instance? ExceptionInfo r)) "get-local-files-meta shouldn't return exception")
(<! (<build-local-file-metadatas this graph-uuid (.-result r))))))
(<rename-local-file [_ graph-uuid base-path from to]
(p->c (.renameLocalFile mobile-util/file-sync
(clj->js {:graphUUID graph-uuid
:basePath base-path
:from (path-normalize from)
:to (path-normalize to)}))))
(<update-local-files [this graph-uuid base-path filepaths]
(go
(let [token-or-exp (<! (<get-token this))
filepaths' (map path-normalize filepaths)]
(or (guard-ex token-or-exp)
(<! (p->c (.updateLocalFiles mobile-util/file-sync (clj->js {:graphUUID graph-uuid
:basePath base-path
:filePaths filepaths'
:token token-or-exp}))))))))
(<fetch-remote-files [this graph-uuid base-path filepaths]
(go
(let [token-or-exp (<! (<get-token this))]
(or (guard-ex token-or-exp)
(js->clj
(.-value
(<! (<retry-rsapi
#(p->c (.fetchRemoteFiles mobile-util/file-sync
(clj->js {:graphUUID graph-uuid
:basePath base-path
:filePaths filepaths
:token token-or-exp})))))))))))
(<download-version-files [this graph-uuid base-path filepaths]
(go
(let [token-or-exp (<! (<get-token this))]
(or (guard-ex token-or-exp)
(<! (<retry-rsapi
#(p->c (.updateLocalVersionFiles mobile-util/file-sync
(clj->js {:graphUUID graph-uuid
:basePath base-path
:filePaths filepaths
:token token-or-exp})))))))))
(<delete-local-files [_ graph-uuid base-path filepaths]
(let [normalized-filepaths (mapv path-normalize filepaths)]
(go
(let [r (<! (<retry-rsapi #(p->c (.deleteLocalFiles mobile-util/file-sync
(clj->js {:graphUUID graph-uuid
:basePath base-path
:filePaths normalized-filepaths})))))]
r))))
(<update-remote-files [this graph-uuid base-path filepaths local-txid]
(let [normalized-filepaths (mapv path-normalize filepaths)]
(go
(let [token-or-exp (<! (<get-token this))
r (or (guard-ex token-or-exp)
(<! (p->c (.updateRemoteFiles mobile-util/file-sync
(clj->js {:graphUUID graph-uuid
:basePath base-path
:filePaths normalized-filepaths
:txid local-txid
:token token-or-exp
:fnameEncryption true})))))]
(or (guard-ex r)
(get (js->clj r) "txid"))))))
(<delete-remote-files [this graph-uuid base-path filepaths local-txid]
(let [normalized-filepaths (mapv path-normalize filepaths)]
(go
(let [token-or-exp (<! (<get-token this))
r (or (guard-ex token-or-exp)
(<! (p->c (.deleteRemoteFiles mobile-util/file-sync
(clj->js {:graphUUID graph-uuid
:basePath base-path
:filePaths normalized-filepaths
:txid local-txid
:token token-or-exp})))))]
(or (guard-ex r)
(get (js->clj r) "txid"))))))
(<encrypt-fnames [_ graph-uuid fnames]
(go
(let [r (<! (p->c (.encryptFnames mobile-util/file-sync
(clj->js {:graphUUID graph-uuid
:filePaths fnames}))))]
(or (guard-ex r)
(get (js->clj r) "value")))))
(<decrypt-fnames [_ graph-uuid fnames]
(go (let [r (<! (p->c (.decryptFnames mobile-util/file-sync
(clj->js {:graphUUID graph-uuid
:filePaths fnames}))))]
(if (instance? ExceptionInfo r)
(ex-info "decrypt-failed" {:fnames fnames} (ex-cause r))
(get (js->clj r) "value")))))
(<cancel-all-requests [_]
(p->c (.cancelAllRequests mobile-util/file-sync)))
(<add-new-version [_this repo path content]
(p->c (capacitor-fs/backup-file repo :version-file-dir path content))))
(def rsapi (cond
(util/electron?)
(->RSAPI nil nil nil)
(mobile-util/native-ios?)
(->CapacitorAPI nil nil nil)
(mobile-util/native-android?)
(->CapacitorAPI nil nil nil)
:else
nil))
@@ -3227,9 +3085,7 @@
local->remote-syncer (->Local->RemoteSyncer user-uuid graph-uuid
base-path
repo *sync-state remoteapi-with-stop
(if (mobile-util/native-platform?)
2000
10000)
10000
*txid *txid-for-get-deletion-log nil (chan) *stopped? *paused?
(chan 1) (chan 1))
remote->local-syncer (->Remote->LocalSyncer user-uuid graph-uuid base-path
@@ -3260,11 +3116,6 @@
(reset! current-sm-graph-uuid nil)))
(defn <sync-local->remote-now []
(go
(when-let [_sm ^SyncManager (state/get-file-sync-manager (state/get-current-file-sync-graph-uuid))]
(offer! immediately-local->remote-chan true))))
(defn sync-need-password!
[]
(when-let [sm ^SyncManager (state/get-file-sync-manager (state/get-current-file-sync-graph-uuid))]
@@ -3410,52 +3261,8 @@
(finally
(reset! *sync-starting false)))))))
(defn- restart-if-stopped!
[is-active?]
(cond
(and is-active? (graph-sync-off? (second @graphs-txid)))
(<sync-start)
:else
(offer! pause-resume-chan is-active?)))
(def app-state-changed-cursor (rum/cursor state/state :mobile/app-state-change))
(def finished-local->remote-chan (chan 1))
(add-watch app-state-changed-cursor "sync"
(fn [_ _ _ {:keys [is-active?]}]
(cond
(mobile-util/native-android?)
(when-not is-active?
(<sync-local->remote-now))
(mobile-util/native-ios?)
(let [*task-id (atom nil)]
(if is-active?
(restart-if-stopped! is-active?)
(when (state/get-current-file-sync-graph-uuid)
(p/let [task-id (.beforeExit ^js BackgroundTask
(fn []
(go
;; Wait for file watcher events
(<! (timeout 2000))
(util/drain-chan finished-local->remote-chan)
(<! (<sync-local->remote-now))
;; wait at most 20s
(async/alts! [finished-local->remote-chan (timeout 20000)])
(p/let [active? (mobile-util/app-active?)]
(when-not active?
(offer! pause-resume-chan is-active?)))
(<! (timeout 5000))
(prn "finish task: " @*task-id)
(let [opt #js {:taskId @*task-id}]
(.finish ^js BackgroundTask opt)))))]
(reset! *task-id task-id)))))
:else
nil)))
;;; ### some add-watches
;; TODO: replace this logic by pause/resume state

View File

@@ -30,7 +30,6 @@
[frontend.handler.ui :as ui-handler]
[frontend.handler.user :as user-handler]
[frontend.idb :as idb]
[frontend.mobile.core :as mobile]
[frontend.mobile.util :as mobile-util]
[frontend.modules.instrumentation.core :as instrument]
[frontend.modules.shortcut.core :as shortcut]
@@ -167,8 +166,6 @@
(events/run!)
(p/do!
(when (mobile-util/native-platform?)
(mobile/mobile-preinit))
(-> (p/let [_ (db-browser/start-db-worker!)
repos (repo-handler/get-repos)
_ (state/set-repos! repos)

View File

@@ -3,8 +3,7 @@
core.async channel to handle them. Any part of the system can dispatch
one of these events using state/pub-event!"
(:refer-clojure :exclude [run!])
(:require ["@capacitor/filesystem" :refer [Directory Filesystem]]
["@sentry/react" :as Sentry]
(:require ["@sentry/react" :as Sentry]
[cljs-bean.core :as bean]
[clojure.core.async :as async]
[clojure.core.async.interop :refer [p->c]]
@@ -14,9 +13,7 @@
[frontend.date :as date]
[frontend.db :as db]
[frontend.db.async :as db-async]
[frontend.db.conn :as conn]
[frontend.db.model :as db-model]
[frontend.db.persist :as db-persist]
[frontend.db.transact :as db-transact]
[frontend.extensions.fsrs :as fsrs]
[frontend.fs :as fs]
@@ -30,7 +27,6 @@
[frontend.handler.db-based.rtc-flows :as rtc-flows]
[frontend.handler.editor :as editor-handler]
[frontend.handler.export :as export]
[frontend.handler.file-sync :as file-sync-handler]
[frontend.handler.graph :as graph-handler]
[frontend.handler.notification :as notification]
[frontend.handler.page :as page-handler]
@@ -49,7 +45,6 @@
[frontend.modules.shortcut.core :as st]
[frontend.persist-db :as persist-db]
[frontend.quick-capture :as quick-capture]
[frontend.search :as search]
[frontend.state :as state]
[frontend.util :as util]
[frontend.util.persist-var :as persist-var]
@@ -85,29 +80,22 @@
(when (= (:url repo) current-repo)
(file-sync-restart!)))
;; FIXME(andelf): awful multi-arty function.
;; Should use a `-impl` function instead of the awful `skip-ios-check?` param with nested callback.
(defn- graph-switch
([graph]
(graph-switch graph false))
([graph skip-ios-check?]
(let [db-based? (config/db-based-graph? graph)]
(if (and (mobile-util/native-ios?) (not skip-ios-check?))
(state/pub-event! [:validate-appId graph-switch graph])
(do
(state/set-current-repo! graph)
(page-handler/init-commands!)
[graph]
(let [db-based? (config/db-based-graph? graph)]
(state/set-current-repo! graph)
(page-handler/init-commands!)
;; load config
(repo-config-handler/restore-repo-config! graph)
(when-not (= :draw (state/get-current-route))
(route-handler/redirect-to-home!))
(when-not db-based?
(repo-config-handler/restore-repo-config! graph)
(when-not (= :draw (state/get-current-route))
(route-handler/redirect-to-home!))
(when-not db-based?
;; graph-switch will trigger a rtc-start automatically
;; (rtc-handler/<rtc-start! graph)
(file-sync-restart!))
(when-let [dir-name (and (not db-based?) (config/get-repo-dir graph))]
(fs/watch-dir! dir-name))
(graph-handler/settle-metadata-to-local! {:last-seen-at (js/Date.now)}))))))
(file-sync-restart!))
(when-let [dir-name (and (not db-based?) (config/get-repo-dir graph))]
(fs/watch-dir! dir-name))
(graph-handler/settle-metadata-to-local! {:last-seen-at (js/Date.now)})))
;; Parameters for the `persist-db` function, to show the notification messages
(defn- graph-switch-on-persisted
@@ -271,72 +259,12 @@
(when-let [toolbar (.querySelector main-node "#mobile-editor-toolbar")]
(set! (.. toolbar -style -bottom) 0)))))
(defn- get-ios-app-id
[repo-url]
(when repo-url
(let [app-id (-> (first (string/split repo-url "/Documents"))
(string/split "/")
last)]
app-id)))
(defmethod handle :validate-appId [[_ graph-switch-f graph]]
(when-let [deprecated-repo (or graph (state/get-current-repo))]
(if (mobile-util/in-iCloud-container-path? deprecated-repo)
;; Installation is not changed for iCloud
(when graph-switch-f
(graph-switch-f graph true)
(state/pub-event! [:graph/ready (state/get-current-repo)]))
;; Installation is changed for App Documents directory
(p/let [deprecated-app-id (get-ios-app-id deprecated-repo)
current-document-url (.getUri Filesystem #js {:path ""
:directory (.-Documents Directory)})
current-app-id (-> (js->clj current-document-url :keywordize-keys true)
get-ios-app-id)]
(if (= deprecated-app-id current-app-id)
(when graph-switch-f (graph-switch-f graph true))
(do
(notification/show! [:div "Migrating from previous App installation..."]
:warning
true)
(prn ::migrate-app-id :from deprecated-app-id :to current-app-id)
(file-sync-stop!)
(.unwatch mobile-util/fs-watcher)
(let [current-repo (string/replace deprecated-repo deprecated-app-id current-app-id)
current-repo-dir (config/get-repo-dir current-repo)]
(try
;; replace app-id part of repo url
(reset! conn/conns
(update-keys @conn/conns
(fn [key]
(if (string/includes? key deprecated-app-id)
(string/replace key deprecated-app-id current-app-id)
key))))
(db-persist/rename-graph! deprecated-repo current-repo)
(search/remove-db! deprecated-repo)
(state/add-repo! {:url current-repo :nfs? true})
(state/delete-repo! {:url deprecated-repo})
(catch :default e
(js/console.error e)))
(state/set-current-repo! current-repo)
(repo-config-handler/restore-repo-config! current-repo)
(when graph-switch-f (graph-switch-f current-repo true))
(.watch mobile-util/fs-watcher #js {:path current-repo-dir})
(file-sync-restart!))))
(state/pub-event! [:graph/ready (state/get-current-repo)])))))
(defmethod handle :plugin/hook-db-tx [[_ {:keys [blocks tx-data] :as payload}]]
(when-let [payload (and (seq blocks)
(merge payload {:tx-data (map #(into [] %) tx-data)}))]
(plugin-handler/hook-plugin-db :changed payload)
(plugin-handler/hook-plugin-block-changes payload)))
(defmethod handle :mobile-file-watcher/changed [[_ ^js event]]
(let [type (.-event event)
payload (js->clj event :keywordize-keys true)]
(fs-watcher/handle-changed! type payload)
(when (file-sync-handler/enable-sync?)
(sync/file-watch-handler type payload))))
(defmethod handle :rebuild-slash-commands-list [[_]]
(page-handler/rebuild-slash-commands-list!))

View File

@@ -19,7 +19,6 @@
[frontend.db :as db]
[frontend.extensions.fsrs :as fsrs]
[frontend.extensions.srs :as srs]
[frontend.fs.capacitor-fs :as capacitor-fs]
[frontend.fs.sync :as sync]
[frontend.handler.db-based.rtc :as rtc-handler]
[frontend.handler.editor :as editor-handler]
@@ -99,12 +98,6 @@
{:id :srs
:label "flashcards__cp"})))
(defmethod events/handle :modal/show-instruction [_]
(shui/dialog-open!
capacitor-fs/instruction
{:id :instruction
:label "instruction__cp"}))
(defmethod events/handle :modal/show-themes-modal [[_ classic?]]
(if classic?
(plugin/open-select-theme!)

View File

@@ -6,13 +6,11 @@
[frontend.db :as db]
[frontend.db.file-based.model :as file-model]
[frontend.fs :as fs]
[frontend.fs.capacitor-fs :as capacitor-fs]
[frontend.handler.common.config-edn :as config-edn-common-handler]
[frontend.handler.file-based.reset-file :as reset-file-handler]
[frontend.handler.global-config :as global-config-handler]
[frontend.handler.repo-config :as repo-config-handler]
[frontend.handler.ui :as ui-handler]
[frontend.mobile.util :as mobile-util]
[frontend.schema.handler.global-config :as global-config-schema]
[frontend.schema.handler.repo-config :as repo-config-schema]
[frontend.state :as state]
@@ -78,15 +76,8 @@
(defn backup-file!
"Backup db content to bak directory"
[repo-url path db-content content]
(cond
(util/electron?)
(ipc/ipc "backupDbFile" repo-url path db-content content)
(mobile-util/native-platform?)
(capacitor-fs/backup-file-handle-changed! repo-url path db-content)
:else
nil))
(when (util/electron?)
(ipc/ipc "backupDbFile" repo-url path db-content content)))
(defn- detect-deprecations
[path content]

View File

@@ -21,13 +21,14 @@
(when (and key @store)
(idb-keyval/set key value @store)))
(defn rename-item!
[old-key new-key]
(when (and old-key new-key @store)
(p/let [value (idb-keyval/get old-key @store)]
(when value
(idb-keyval/set new-key value @store)
(idb-keyval/del old-key @store)))))
(comment
(defn rename-item!
[old-key new-key]
(when (and old-key new-key @store)
(p/let [value (idb-keyval/get old-key @store)]
(when value
(idb-keyval/set new-key value @store)
(idb-keyval/del old-key @store))))))
(comment
(defn set-batch!

View File

@@ -3,16 +3,12 @@
(:require ["@capacitor/app" :refer [^js App]]
["@capacitor/keyboard" :refer [^js Keyboard]]
[clojure.string :as string]
[promesa.core :as p]
[frontend.fs.capacitor-fs :as capacitor-fs]
[frontend.handler.editor :as editor-handler]
[frontend.mobile.deeplink :as deeplink]
[frontend.mobile.intent :as intent]
[frontend.mobile.util :as mobile-util]
[frontend.state :as state]
[frontend.util :as util]
[cljs-bean.core :as bean]
[frontend.config :as config]))
[frontend.util :as util]))
(def *init-url (atom nil))
;; FIXME: `appUrlOpen` are fired twice when receiving a same intent.
@@ -21,14 +17,6 @@
(def *last-shared-url (atom nil))
(def *last-shared-seconds (atom 0))
(defn mobile-preinit
"preinit logic of mobile platforms: setup document folder permission"
[]
(when (mobile-util/native-ios?)
;; Caution: This must be called before any file accessing
(capacitor-fs/ios-ensure-documents!)))
(defn mobile-postinit
"postinit logic of mobile platforms: handle deeplink and intent"
[]
@@ -40,30 +28,7 @@
(defn- ios-init
"Initialize iOS-specified event listeners"
[]
(p/let [^js paths (capacitor-fs/ios-ensure-documents!)]
(when paths
(let [paths (-> paths
bean/->clj
(update-vals capacitor-fs/ios-force-include-private))]
(state/set-state! :mobile/container-urls paths)
(println "iOS container path: " paths))))
;; Fix iOS App directory change across installation
(when (not (config/demo-graph?))
(state/pub-event! [:validate-appId]))
(mobile-util/check-ios-zoomed-display)
;; keep this the same logic as src/main/electron/listener.cljs
(.addListener mobile-util/file-sync "debug"
(fn [event]
(let [event (js->clj event :keywordize-keys true)
payload (:data event)]
(when (or (= (:event event) "download:progress")
(= (:event event) "upload:progress"))
(state/set-state! [:file-sync/graph-state (:graphUUID payload) :file-sync/progress (:file payload)] payload))))))
(mobile-util/check-ios-zoomed-display))
(defn- android-init
"Initialize Android-specified event listeners"
@@ -98,13 +63,7 @@
(js/window.history.back)))))
(.addEventListener js/window "sendIntentReceived"
#(intent/handle-received))
(.addListener mobile-util/file-sync "progress"
(fn [event]
(js/console.log "🔄" event)
(let [event (js->clj event :keywordize-keys true)]
(state/set-state! [:file-sync/graph-state (:graphUUID event) :file-sync/progress (:file event)] event)))))
#(intent/handle-received)))
(defn- app-state-change-handler
[^js state]
@@ -112,8 +71,7 @@
(when (state/get-current-repo)
(let [is-active? (.-isActive state)]
(when-not is-active?
(editor-handler/save-current-block!))
(state/set-mobile-app-state-change is-active?))))
(editor-handler/save-current-block!)))))
(defn- general-init
"Initialize event listeners used by both iOS and Android"
@@ -129,10 +87,6 @@
(reset! *last-shared-seconds (.getSeconds (js/Date.)))
(deeplink/deeplink url))))))
(.addListener mobile-util/fs-watcher "watcher"
(fn [event]
(state/pub-event! [:mobile-file-watcher/changed event])))
(.addListener Keyboard "keyboardWillShow"
(fn [^js info]
(let [keyboard-height (.-keyboardHeight info)]
@@ -147,7 +101,6 @@
(.addListener App "appStateChange" app-state-change-handler))
(defn init! []
(when (mobile-util/native-android?)
(android-init))

View File

@@ -1,10 +1,8 @@
(ns frontend.mobile.util
(:require ["@capacitor/core" :refer [Capacitor registerPlugin ^js Plugins]]
(:require ["@capacitor/core" :refer [Capacitor registerPlugin]]
["@capacitor/splash-screen" :refer [SplashScreen]]
["@logseq/capacitor-file-sync" :refer [FileSync]]
[clojure.string :as string]
[promesa.core :as p]
[goog.object :as gobj]))
[promesa.core :as p]))
(defn platform []
(.getPlatform Capacitor))
@@ -25,13 +23,7 @@
(defonce folder-picker (registerPlugin "FolderPicker"))
(when (native-ios?)
(defonce ios-utils (registerPlugin "Utils"))
(defonce ios-file-container (registerPlugin "FileContainer")))
;; NOTE: both iOS and android share the same API
(when (native-platform?)
(defonce file-sync FileSync)
(defonce fs-watcher (registerPlugin "FsWatcher")))
(defonce ios-utils (registerPlugin "Utils")))
(defn hide-splash []
(.hide SplashScreen))
@@ -99,14 +91,10 @@
[path]
(string/includes? path "/iCloud~com~logseq~logseq/"))
(defn is-iCloud-container-path?
"Check whether `path' is iCloud container path on iOS"
[path]
(re-matches #"/iCloud~com~logseq~logseq/Documents/?$" path))
(defn app-active?
"Whether the app is active. This function returns a promise."
[]
(let [app ^js (gobj/get Plugins "App")]
(p/let [state (.getState app)]
(gobj/get state "isActive"))))
(comment
(defn app-active?
"Whether the app is active. This function returns a promise."
[]
(let [app ^js (gobj/get Plugins "App")]
(p/let [state (.getState app)]
(gobj/get state "isActive")))))

View File

@@ -218,10 +218,6 @@
:mobile/show-toolbar? false
:mobile/show-recording-bar? false
:mobile/show-tabbar? false
;;; Used to monitor mobile app status,
;;; value spec:
;;; {:is-active? bool, :timestamp int}
:mobile/app-state-change (atom nil)
;; plugin
:plugin/enabled (and util/plugin-platform?
@@ -2228,12 +2224,6 @@ Similar to re-frame subscriptions"
(every? not-empty (vals agent-opts)))
(str protocol "://" host ":" port))))
(defn set-mobile-app-state-change
[is-active?]
(set-state! :mobile/app-state-change
{:is-active? is-active?
:timestamp (inst-ms (js/Date.))}))
(defn get-sync-graph-by-id
[graph-uuid]
(when graph-uuid

View File

@@ -587,12 +587,7 @@
resolved "https://registry.yarnpkg.com/@js-joda/timezone/-/timezone-2.5.0.tgz#b422ff400c25ae311384239c62724eecee2e442b"
integrity sha512-HHFVhGUKIOtITiT+sbQRdYuO5Q+a8FDj/vQSGUSxe6+5w2in5JsavfRsAN2tU/NCdBeFx/6q8evHMtOrXfdn2g==
"@logseq/capacitor-file-sync@5.0.2":
version "5.0.2"
resolved "https://registry.yarnpkg.com/@logseq/capacitor-file-sync/-/capacitor-file-sync-5.0.2.tgz#10c56e35b41b1a0afd293c9b045fbcfe150c3477"
integrity sha512-FymsTeRtF66zG+oeO+ohZxWICMQMC8An4n9pdI0zz1WaGLer4oWC/lUghlC2DpztRLA32p0CH28tEzF5+2jARg==
"@logseq/diff-merge@0.2.2":
"@logseq/diff-merge@^0.2.2":
version "0.2.2"
resolved "https://registry.yarnpkg.com/@logseq/diff-merge/-/diff-merge-0.2.2.tgz#583bd8c8c66d5ff05ea70906475efaa078e839a3"
integrity sha512-0WeKNhq8PsjvunOqNEd9aSM4tgiClwhonXgXzrQ4KYj8VoyLaEAyEWWGOAoE7mwR+aqwM+bMB4MxuNFywnUb8A==
@@ -9002,6 +8997,11 @@ undertaker@^1.2.1:
object.reduce "^1.0.0"
undertaker-registry "^1.0.0"
undici-types@~6.20.0:
version "6.20.0"
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.20.0.tgz#8171bf22c1f588d1554d55bf204bc624af388433"
integrity sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==
undici-types@~6.21.0:
version "6.21.0"
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.21.0.tgz#691d00af3909be93a7faa13be61b3a5b50ef12cb"