Skip to content

Commit

Permalink
fix(ios): ios path fix for file-sync
Browse files Browse the repository at this point in the history
  • Loading branch information
andelf committed Aug 26, 2022
1 parent 09f90f4 commit 7a1aae0
Show file tree
Hide file tree
Showing 7 changed files with 54 additions and 54 deletions.
4 changes: 2 additions & 2 deletions ios/App/App/FileContainer.swift
Expand Up @@ -29,8 +29,8 @@ public class FileContainer: CAPPlugin, UIDocumentPickerDelegate {
validateDocuments(at: self.localContainerUrl!)
}

call.resolve(["path": [self.iCloudContainerUrl?.path as Any,
self.localContainerUrl?.path as Any]])
call.resolve(["path": [self.iCloudContainerUrl?.absoluteString as Any,
self.localContainerUrl?.absoluteString as Any]])
}

func validateDocuments(at url: URL) {
Expand Down
29 changes: 16 additions & 13 deletions ios/App/App/FileSync/FileSync.swift
Expand Up @@ -192,7 +192,7 @@ public class FileSync: CAPPlugin, SyncDebugDelegate {
}

let nFiles = fnames.count
fnames = fnames.compactMap { $0.fnameEncrypt(rawKey: FNAME_ENCRYPTION_KEY!) }
fnames = fnames.compactMap { $0.removingPercentEncoding!.fnameEncrypt(rawKey: FNAME_ENCRYPTION_KEY!) }
if fnames.count != nFiles {
call.reject("cannot encrypt \(nFiles - fnames.count) file names")
}
Expand All @@ -209,14 +209,15 @@ public class FileSync: CAPPlugin, SyncDebugDelegate {
return
}
let nFiles = fnames.count
fnames = fnames.compactMap { $0.fnameDecrypt(rawKey: FNAME_ENCRYPTION_KEY!) }
fnames = fnames.compactMap { $0.fnameDecrypt(rawKey: FNAME_ENCRYPTION_KEY!)?.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) }
if fnames.count != nFiles {
call.reject("cannot decrypt \(nFiles - fnames.count) file names")
}
call.resolve(["value": fnames])
}

@objc func getLocalFilesMeta(_ call: CAPPluginCall) {
// filePaths are url encoded
guard let basePath = call.getString("basePath"),
let filePaths = call.getArray("filePaths") as? [String] else {
call.reject("required paremeters: basePath, filePaths")
Expand All @@ -228,7 +229,8 @@ public class FileSync: CAPPlugin, SyncDebugDelegate {
}

var fileMetadataDict: [String: [String: Any]] = [:]
for filePath in filePaths {
for percentFilePath in filePaths {
let filePath = percentFilePath.removingPercentEncoding!
let url = baseURL.appendingPathComponent(filePath)
if let meta = SyncMetadata(of: url) {
var metaObj: [String: Any] = ["md5": meta.md5,
Expand All @@ -238,7 +240,7 @@ public class FileSync: CAPPlugin, SyncDebugDelegate {
metaObj["encryptedFname"] = filePath.fnameEncrypt(rawKey: FNAME_ENCRYPTION_KEY!)
}

fileMetadataDict[filePath] = metaObj
fileMetadataDict[percentFilePath] = metaObj
}
}

Expand All @@ -265,7 +267,7 @@ public class FileSync: CAPPlugin, SyncDebugDelegate {
if fnameEncryptionEnabled() {
metaObj["encryptedFname"] = filePath.fnameEncrypt(rawKey: FNAME_ENCRYPTION_KEY!)
}
fileMetadataDict[filePath] = metaObj
fileMetadataDict[filePath.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed)!] = metaObj
}
} else if fileURL.isICloudPlaceholder() {
try? FileManager.default.startDownloadingUbiquitousItem(at: fileURL)
Expand All @@ -291,8 +293,8 @@ public class FileSync: CAPPlugin, SyncDebugDelegate {
return
}

let fromUrl = baseURL.appendingPathComponent(from)
let toUrl = baseURL.appendingPathComponent(to)
let fromUrl = baseURL.appendingPathComponent(from.removingPercentEncoding!)
let toUrl = baseURL.appendingPathComponent(to.removingPercentEncoding!)

do {
try FileManager.default.moveItem(at: fromUrl, to: toUrl)
Expand All @@ -312,7 +314,7 @@ public class FileSync: CAPPlugin, SyncDebugDelegate {
}

for filePath in filePaths {
let fileUrl = baseURL.appendingPathComponent(filePath)
let fileUrl = baseURL.appendingPathComponent(filePath.removingPercentEncoding!)
try? FileManager.default.removeItem(at: fileUrl) // ignore any delete errors
}
call.resolve(["ok": true])
Expand All @@ -332,7 +334,7 @@ public class FileSync: CAPPlugin, SyncDebugDelegate {
var encryptedFilePathDict: [String: String] = [:]
if fnameEncryptionEnabled() {
for filePath in filePaths {
if let encryptedPath = filePath.fnameEncrypt(rawKey: FNAME_ENCRYPTION_KEY!) {
if let encryptedPath = filePath.removingPercentEncoding!.fnameEncrypt(rawKey: FNAME_ENCRYPTION_KEY!) {
encryptedFilePathDict[encryptedPath] = filePath
} else {
call.reject("cannot decrypt all file names")
Expand Down Expand Up @@ -363,7 +365,7 @@ public class FileSync: CAPPlugin, SyncDebugDelegate {

let filePath = encryptedFilePathDict[encryptedFilePath]!
// NOTE: fileURLs from getFiles API is percent-encoded
let localFileURL = baseURL.appendingPathComponent(filePath)
let localFileURL = baseURL.appendingPathComponent(filePath.removingPercentEncoding!)
remoteFileURL.download(toFile: localFileURL) {error in
if let error = error {
self.debugNotification(["event": "download:error", "data": ["message": "error while downloading \(filePath): \(error)"]])
Expand Down Expand Up @@ -413,7 +415,7 @@ public class FileSync: CAPPlugin, SyncDebugDelegate {
group.enter()

// NOTE: fileURLs from getFiles API is percent-encoded
let localFileURL = baseURL.appendingPathComponent("logseq/version-files/").appendingPathComponent(filePath)
let localFileURL = baseURL.appendingPathComponent("logseq/version-files/").appendingPathComponent(filePath.removingPercentEncoding!)
remoteFileURL.download(toFile: localFileURL) {error in
if let error = error {
self.debugNotification(["event": "version-download:error", "data": ["message": "error while downloading \(filePath): \(error)"]])
Expand All @@ -434,6 +436,7 @@ public class FileSync: CAPPlugin, SyncDebugDelegate {
}
}

// filePaths: Encrypted file paths
@objc func deleteRemoteFiles(_ call: CAPPluginCall) {
guard var filePaths = call.getArray("filePaths") as? [String],
let graphUUID = call.getString("graphUUID"),
Expand Down Expand Up @@ -499,7 +502,7 @@ public class FileSync: CAPPlugin, SyncDebugDelegate {
var files: [String: URL] = [:]
for filePath in filePaths {
// NOTE: filePath from js may contain spaces
let fileURL = baseURL.appendingPathComponent(filePath)
let fileURL = baseURL.appendingPathComponent(filePath.removingPercentEncoding!)
files[filePath] = fileURL
}

Expand All @@ -522,7 +525,7 @@ public class FileSync: CAPPlugin, SyncDebugDelegate {

if fnameEncryptionEnabled() && fnameEncryption {
for (filePath, fileKey) in uploadedFileKeyDict {
guard let encryptedFilePath = filePath.fnameEncrypt(rawKey: FNAME_ENCRYPTION_KEY!) else {
guard let encryptedFilePath = filePath.removingPercentEncoding!.fnameEncrypt(rawKey: FNAME_ENCRYPTION_KEY!) else {
call.reject("cannot encrypt file name")
return
}
Expand Down
59 changes: 31 additions & 28 deletions ios/App/App/FsWatcher.swift
Expand Up @@ -14,11 +14,11 @@ import Capacitor
public class FsWatcher: CAPPlugin, PollingWatcherDelegate {
private var watcher: PollingWatcher? = nil
private var baseUrl: URL? = nil

override public func load() {
print("debug FsWatcher iOS plugin loaded!")
}

@objc func watch(_ call: CAPPluginCall) {
if let path = call.getString("path") {
guard let url = URL(string: path) else {
Expand All @@ -28,22 +28,22 @@ public class FsWatcher: CAPPlugin, PollingWatcherDelegate {
self.baseUrl = url
self.watcher = PollingWatcher(at: url)
self.watcher?.delegate = self

call.resolve(["ok": true])

} else {
call.reject("missing path string parameter")
}
}

@objc func unwatch(_ call: CAPPluginCall) {
watcher?.stop()
watcher = nil
baseUrl = nil

call.resolve()
}

public func recevedNotification(_ url: URL, _ event: PollingWatcherEvent, _ metadata: SimpleFileMetadata?) {
// NOTE: Event in js {dir path content stat{mtime}}
switch event {
Expand All @@ -67,7 +67,7 @@ public class FsWatcher: CAPPlugin, PollingWatcherDelegate {
"ctime": metadata?.creationTimestamp ?? 0,
"size": metadata?.fileSize as Any]
])

case .Error:
// TODO: handle error?
break
Expand All @@ -83,20 +83,23 @@ extension URL {
if self.lastPathComponent.starts(with: ".") {
return true
}
if self.absoluteString.contains("logseq/bak/") {
return true
}
if self.lastPathComponent == "graphs-txid.edn" || self.lastPathComponent == "broken-config.edn" {
return true
}
return false
}

func shouldNotifyWithContent() -> Bool {
let allowedPathExtensions: Set = ["md", "markdown", "org", "js", "edn", "css", "excalidraw"]
if allowedPathExtensions.contains(self.pathExtension.lowercased()) {
return true
}
return false
}

func isICloudPlaceholder() -> Bool {
if self.lastPathComponent.starts(with: ".") && self.pathExtension.lowercased() == "icloud" {
return true
Expand All @@ -116,7 +119,7 @@ public enum PollingWatcherEvent {
case Change
case Unlink
case Error

var description: String {
switch self {
case .Add:
Expand All @@ -136,7 +139,7 @@ public struct SimpleFileMetadata: CustomStringConvertible, Equatable {
var contentModificationTimestamp: Double
var creationTimestamp: Double
var fileSize: Int

public init?(of fileURL: URL) {
do {
let fileAttributes = try fileURL.resourceValues(forKeys:[.isRegularFileKey, .fileSizeKey, .contentModificationDateKey, .creationDateKey])
Expand All @@ -151,7 +154,7 @@ public struct SimpleFileMetadata: CustomStringConvertible, Equatable {
return nil
}
}

public var description: String {
return "Meta(size=\(self.fileSize), mtime=\(self.contentModificationTimestamp), ctime=\(self.creationTimestamp)"
}
Expand All @@ -162,40 +165,40 @@ public class PollingWatcher {
private var timer: DispatchSourceTimer?
public var delegate: PollingWatcherDelegate? = nil
private var metaDb: [URL: SimpleFileMetadata] = [:]

public init?(at: URL) {
url = at

let queue = DispatchQueue(label: Bundle.main.bundleIdentifier! + ".timer")
timer = DispatchSource.makeTimerSource(queue: queue)
timer!.setEventHandler(qos: .background, flags: []) { [weak self] in
self?.tick()
}
timer!.schedule(deadline: .now())
timer!.resume()

}

deinit {
self.stop()
}

public func stop() {
timer?.cancel()
timer = nil
}

private func tick() {
// let startTime = DispatchTime.now()

if let enumerator = FileManager.default.enumerator(
at: url,
includingPropertiesForKeys: [.isRegularFileKey, .nameKey, .isDirectoryKey],
// NOTE: icloud downloading requires non-skipsHiddenFiles
options: [.skipsPackageDescendants]) {

var newMetaDb: [URL: SimpleFileMetadata] = [:]

for case let fileURL as URL in enumerator {
guard let resourceValues = try? fileURL.resourceValues(forKeys: [.isRegularFileKey, .nameKey, .isDirectoryKey]),
let isDirectory = resourceValues.isDirectory,
Expand All @@ -204,14 +207,14 @@ public class PollingWatcher {
else {
continue
}

if isDirectory {
// NOTE: URL.path won't end with a `/`
if fileURL.path.hasSuffix("/logseq/bak") || fileURL.path.hasSuffix("/logseq/version-files") || name == ".recycle" || name.hasPrefix(".") || name == "node_modules" {
enumerator.skipDescendants()
}
}

if isRegularFile && !fileURL.isSkipped() {
if let meta = SimpleFileMetadata(of: fileURL) {
newMetaDb[fileURL] = meta
Expand All @@ -220,22 +223,22 @@ public class PollingWatcher {
try? FileManager.default.startDownloadingUbiquitousItem(at: fileURL)
}
}

self.updateMetaDb(with: newMetaDb)
}

// let elapsedNanoseconds = DispatchTime.now().uptimeNanoseconds - startTime.uptimeNanoseconds
// let elapsedInMs = Double(elapsedNanoseconds) / 1_000_000
// print("debug ticker elapsed=\(elapsedInMs)ms")

if #available(iOS 13.0, *) {
timer?.schedule(deadline: .now().advanced(by: .seconds(2)), leeway: .milliseconds(100))
} else {
// Fallback on earlier versions
timer?.schedule(deadline: .now() + 2.0, leeway: .milliseconds(100))
}
}

// TODO: batch?
private func updateMetaDb(with newMetaDb: [URL: SimpleFileMetadata]) {
for (url, meta) in newMetaDb {
Expand Down
2 changes: 1 addition & 1 deletion src/main/frontend/fs/capacitor_fs.cljs
Expand Up @@ -14,7 +14,7 @@
[rum.core :as rum]))

(when (mobile-util/native-ios?)
(defn iOS-ensure-documents!
(defn ios-ensure-documents!
[]
(.ensureDocuments mobile-util/ios-file-container)))

Expand Down
6 changes: 1 addition & 5 deletions src/main/frontend/fs/sync.cljs
Expand Up @@ -288,12 +288,8 @@
(swap! *on-flying-request disj name)
r))))

;; FIXME: For Android, dir is plain path
;; For iOS, dir is URL
(defn- remove-dir-prefix [dir path]
(let [is-mobile-url? (string/starts-with? dir "file://")
dir (if is-mobile-url? (gstring/urlDecode dir) dir)
r (string/replace path (js/RegExp. (str "^" "(file://)?" (gstring/regExpEscape dir))) "")]
(let [r (string/replace path (js/RegExp. (str "^" (gstring/regExpEscape dir))) "")]
(if (string/starts-with? r "/")
(string/replace-first r "/" "")
r)))
Expand Down
6 changes: 2 additions & 4 deletions src/main/frontend/handler/events.cljs
Expand Up @@ -507,11 +507,9 @@
(defmethod handle :file-watcher/changed [[_ ^js event]]
(let [type (.-event event)
payload (-> event
(js->clj :keywordize-keys true))
;; TODO: remove this
payload' (-> payload (update :path js/decodeURI))]
(js->clj :keywordize-keys true))]
(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
2 changes: 1 addition & 1 deletion src/main/frontend/mobile/core.cljs
Expand Up @@ -22,7 +22,7 @@
(defn- ios-init
"Initialize iOS-specified event listeners"
[]
(p/let [path (mobile-fs/iOS-ensure-documents!)]
(p/let [path (mobile-fs/ios-ensure-documents!)]
(println "iOS container path: " (js->clj path)))

(state/pub-event! [:validate-appId])
Expand Down

0 comments on commit 7a1aae0

Please sign in to comment.