Skip to content

Commit

Permalink
Refactor path handling on mobile platforms (logseq#6402)
Browse files Browse the repository at this point in the history
* refactor(mobile): use unescaped uri across app
* refactor(android): use uri for all path
  • Loading branch information
andelf committed Aug 19, 2022
1 parent 818a009 commit 1cf7108
Show file tree
Hide file tree
Showing 8 changed files with 120 additions and 122 deletions.
2 changes: 1 addition & 1 deletion android/app/src/main/java/com/logseq/app/FolderPicker.java
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ private void folderPickerResult(PluginCall call, ActivityResult result) {
if (path == null || path.isEmpty()) {
call.reject("Cannot support this directory type: " + docUri);
} else {
ret.put("path", path);
ret.put("path", "file://" + path);
call.resolve(ret);
}
}
Expand Down
2 changes: 1 addition & 1 deletion android/app/src/main/java/com/logseq/app/FsWatcher.java
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ public void onObserverEvent(int event, String path) {
// path.
File f = new File(path);
obj.put("path", Uri.fromFile(f));
obj.put("dir", mPath);
obj.put("dir", "file://" + mPath);

switch (event) {
case FileObserver.CLOSE_WRITE:
Expand Down
8 changes: 6 additions & 2 deletions src/main/frontend/config.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -305,11 +305,15 @@
path
(util/node-path.join (get-repo-dir repo-url) path)))

;; FIXME: There is another get-file-path at src/main/frontend/fs/capacitor_fs.cljs
(defn get-file-path
"Normalization happens here"
[repo-url relative-path]
(when (and repo-url relative-path)
(let [path (cond
(demo-graph?)
nil

(and (util/electron?) (local-db? repo-url))
(let [dir (get-repo-dir repo-url)]
(if (string/starts-with? relative-path dir)
Expand All @@ -319,7 +323,7 @@

(and (mobile-util/native-ios?) (local-db? repo-url))
(let [dir (get-repo-dir repo-url)]
(js/decodeURI (str dir relative-path)))
(str dir relative-path))

(and (mobile-util/native-android?) (local-db? repo-url))
(let [dir (get-repo-dir repo-url)
Expand All @@ -334,7 +338,7 @@

:else
relative-path)]
(gp-util/path-normalize path))))
(and (not-empty path) (gp-util/path-normalize path)))))

(defn get-config-path
([]
Expand Down
190 changes: 90 additions & 100 deletions src/main/frontend/fs/capacitor_fs.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -18,27 +18,58 @@
[]
(.ensureDocuments mobile-util/ios-file-container)))

(defn check-permission-android []
(p/let [permission (.checkPermissions Filesystem)
permission (-> permission
bean/->clj
:publicStorage)]
(when-not (= permission "granted")
(p/do!
(.requestPermissions Filesystem)))))
(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- clean-uri
[uri]
(when (string? uri)
(util/url-decode uri)))
(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-utf8
(defn- <read-file-with-utf8
[path]
(when-not (string/blank? path)
(.readFile Filesystem
(clj->js
{:path path
:encoding (.-UTF8 Encoding)}))))
(-> (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
#(get % "files" nil))
(p/catch (fn [error]
(js/console.error "readdir Error: " path ": " error)
nil))))

(defn- <stat [path]
(-> (p/chain (.stat Filesystem (clj->js {:path path}))
#(js->clj % :keywordize-keys true)
#(update % :type (fn [v]
(case v
"NSFileTypeDirectory" "directory"
"NSFileTypeRegular" "file"
v))))
(p/catch (fn [error]
(js/console.error "stat Error: " path ": " error)
nil))))

(defn readdir
"readdir recursively"
Expand All @@ -48,10 +79,7 @@
(if (empty? dirs)
result
(p/let [d (first dirs)
files (.readdir Filesystem (clj->js {:path d}))
files (-> files
js->clj
(get "files" []))
files (<readdir d)
files (->> files
(remove (fn [file]
(or (string/starts-with? file ".")
Expand All @@ -61,44 +89,31 @@
(= file "bak")))))
files (->> files
(map (fn [file]
;; TODO: use uri-join
(str (string/replace d #"/+$" "")
"/"
(if (mobile-util/native-ios?)
(util/url-encode file)
(js/encodeURI file)
file)))))
files-with-stats (p/all
(mapv
(fn [file]
(p/chain
(.stat Filesystem (clj->js {:path file}))
#(js->clj % :keywordize-keys true)))
files))
files-with-stats (p/all (mapv <stat files))
files-dir (->> files-with-stats
(filterv
(fn [{:keys [type]}]
(contains? #{"directory" "NSFileTypeDirectory"} type)))
(filterv #(= (:type %) "directory"))
(mapv :uri))
files-result
(p/all
(->> files-with-stats
(filter
(fn [{:keys [type]}]
(contains? #{"file" "NSFileTypeRegular"} type)))
(filter #(= (:type %) "file"))
(filter
(fn [{:keys [uri]}]
(some #(string/ends-with? uri %)
[".md" ".markdown" ".org" ".edn" ".css"])))
(mapv
(fn [{:keys [uri] :as file-result}]
(p/chain
(read-file-utf8 uri)
#(js->clj % :keywordize-keys true)
:data
#(assoc file-result :content %))))))]
(p/chain (<read-file-with-utf8 uri)
#(assoc file-result :content %))))))]
(p/recur (concat result files-result)
(concat (rest dirs) files-dir)))))
result (js->clj result :keywordize-keys true)]
(map (fn [result] (update result :uri clean-uri)) result)))
(concat (rest dirs) files-dir)))))]
(js->clj result :keywordize-keys true)))

(defn- contents-matched?
[disk-content db-content]
Expand All @@ -113,7 +128,7 @@
[repo-dir path ext]
(let [relative-path (-> (string/replace path repo-dir "")
(string/replace (str "." ext) ""))]
(str repo-dir backup-dir "/" relative-path)))
(util/safe-path-join repo-dir (str backup-dir "/" relative-path))))

(defn- truncate-old-versioned-files!
"reserve the latest 6 version files"
Expand All @@ -122,17 +137,14 @@
files (js->clj files :keywordize-keys true)
old-versioned-files (drop 6 (reverse (sort-by :mtime files)))]
(mapv (fn [file]
(.deleteFile Filesystem (clj->js {:path (js/encodeURI (:uri file))})))
(.deleteFile Filesystem (clj->js {:path (:uri file)})))
old-versioned-files)))

(defn backup-file
[repo-dir path content ext]
(let [backup-dir (get-backup-dir repo-dir path ext)
new-path (str backup-dir "/" (string/replace (.toISOString (js/Date.)) ":" "_") "." ext)]
(.writeFile Filesystem (clj->js {:data content
:path new-path
:encoding (.-UTF8 Encoding)
:recursive true}))
(<write-file-with-utf8 new-path content)
(truncate-old-versioned-files! backup-dir)))

(defn backup-file-handle-changed!
Expand All @@ -149,42 +161,32 @@
backup-root (util/safe-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 (util/safe-path-join backup-root backup-dir-parent backup-dir-name)
file-path (util/safe-path-join file-root
(str (string/replace (.toISOString (js/Date.)) ":" "_") "." (mobile-util/platform) file-extname))]
(.writeFile Filesystem (clj->js {:data content
:path (js/encodeURI file-path)
:encoding (.-UTF8 Encoding)
:recursive true}))
(truncate-old-versioned-files! (js/encodeURI file-root))))
backup-dir-name (util/node-path.name file-path)
file-extname (.extname util/node-path file-path)
file-root (util/safe-path-join backup-root backup-dir-parent backup-dir-name)
file-path (util/safe-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!
[_this repo _dir path content {:keys [ok-handler error-handler old-content skip-compare?]} stat]
(if skip-compare?
(p/catch
(p/let [result (.writeFile Filesystem (clj->js {:path path
:data content
:encoding (.-UTF8 Encoding)
:recursive true}))]
(p/let [result (<write-file-with-utf8 path content)]
(when ok-handler
(ok-handler repo path result)))
(fn [error]
(if error-handler
(error-handler error)
(log/error :write-file-failed error))))

(p/let [disk-content (-> (p/chain (read-file-utf8 path)
#(js->clj % :keywordize-keys true)
:data)
(p/catch (fn [error]
(js/console.error error)
nil)))
;; Compare with disk content and backup if not equal
(p/let [disk-content (<read-file-with-utf8 path)
disk-content (or disk-content "")
repo-dir (config/get-local-dir repo)
ext (string/lower-case (util/get-file-ext path))
db-content (or old-content (db/get-file repo (js/decodeURI path)) "")
ext (util/get-file-ext path)
db-content (or old-content (db/get-file repo path) "")
contents-matched? (contents-matched? disk-content db-content)
pending-writes (state/get-write-chan-length)]
(cond
Expand All @@ -199,10 +201,7 @@

:else
(->
(p/let [result (.writeFile Filesystem (clj->js {:path path
:data content
:encoding (.-UTF8 Encoding)
:recursive true}))
(p/let [result (<write-file-with-utf8 path content)
mtime (-> (js->clj stat :keywordize-keys true)
:mtime)]
(when-not contents-matched?
Expand All @@ -211,7 +210,7 @@
(p/let [content (if (encrypt/encrypted-db? (state/get-current-repo))
(encrypt/decrypt content)
content)]
(db/set-file-content! repo (js/decodeURI path) content))
(db/set-file-content! repo path content))
(when ok-handler
(ok-handler repo path result))
result)
Expand All @@ -221,25 +220,19 @@
(log/error :write-file-failed error)))))))))

(defn get-file-path [dir path]
(let [[dir path] (map #(some-> %
js/decodeURI)
[dir path])
dir (some-> dir (string/replace #"/+$" ""))
path (some-> path (string/replace #"^/+" ""))
path (cond (nil? path)
dir
(let [dir (some-> dir (string/replace #"/+$" ""))
path (some-> path (string/replace #"^/+" ""))]
(cond (nil? path)
dir

(nil? dir)
path
(nil? dir)
path

(string/starts-with? path dir)
path
(string/starts-with? path dir)
path

:else
(str dir "/" path))]
(if (mobile-util/native-ios?)
(js/encodeURI (js/decodeURI path))
path)))
:else
(str dir "/" path))))

(defn- local-container-path?
"Check whether `path' is logseq's container `localDocumentsPath' on iOS"
Expand Down Expand Up @@ -301,11 +294,7 @@
(read-file [_this dir path _options]
(let [path (get-file-path dir path)]
(->
(p/let [content (read-file-utf8 path)
content (-> (js->clj content :keywordize-keys true)
:data
clj->js)]
content)
(<read-file-with-utf8 path)
(p/catch (fn [error]
(log/error :read-file-failed error))))))
(write-file! [this repo dir path content opts]
Expand All @@ -332,16 +321,17 @@
}))]
result)))
(open-dir [_this _ok-handler]
(p/let [_ (when (= (mobile-util/platform) "android") (check-permission-android))
(p/let [_ (when (mobile-util/native-android?) (android-check-permission))
{:keys [path localDocumentsPath]} (-> (.pickFolder mobile-util/folder-picker)
(p/then #(js->clj % :keywordize-keys true))
(p/catch (fn [e]
(js/alert (str e))
nil))) ;; NOTE: Can not pick folder, let it crash
nil))) ;; NOTE: If pick folder fails, let it crash
_ (when (and (mobile-util/native-ios?)
(not (or (local-container-path? path localDocumentsPath)
(mobile-util/iCloud-container-path? path))))
(state/pub-event! [:modal/show-instruction]))
_ (js/console.log "Opening or Creating graph at directory: " path)
files (readdir path)
files (js->clj files :keywordize-keys true)]
(into [] (concat [{:path path}] files))))
Expand All @@ -350,6 +340,6 @@
(watch-dir! [_this dir]
(p/do!
(.unwatch mobile-util/fs-watcher)
(.watch mobile-util/fs-watcher #js {:path dir})))
(.watch mobile-util/fs-watcher (clj->js {:path dir}))))
(unwatch-dir! [_this _dir]
(.unwatch mobile-util/fs-watcher)))
7 changes: 4 additions & 3 deletions src/main/frontend/handler/events.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -453,10 +453,11 @@
(defmethod handle :file-watcher/changed [[_ ^js event]]
(let [type (.-event event)
payload (-> event
(js->clj :keywordize-keys true)
(update :path js/decodeURI))]
(js->clj :keywordize-keys true))
;; TODO: remove this
payload' (-> payload (update :path js/decodeURI))]
(fs-watcher/handle-changed! type payload)
(sync/file-watch-handler type payload)))
(sync/file-watch-handler type payload')))

(defmethod handle :rebuild-slash-commands-list [[_]]
(page-handler/rebuild-slash-commands-list!))
Expand Down
Loading

0 comments on commit 1cf7108

Please sign in to comment.