mirror of https://github.com/logseq/logseq
feat(ios): add polling based file watcher
parent
f1aff93807
commit
b260648b60
|
@ -22,6 +22,8 @@
|
|||
D32752BE275496C60039291C /* CloudKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D32752BD275496C60039291C /* CloudKit.framework */; };
|
||||
D3D62A0A275C92880003FBDC /* FileContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D3D62A09275C92880003FBDC /* FileContainer.swift */; };
|
||||
D3D62A0C275C928F0003FBDC /* FileContainer.m in Sources */ = {isa = PBXBuildFile; fileRef = D3D62A0B275C928F0003FBDC /* FileContainer.m */; };
|
||||
FE647FF427BDFEDE00F3206B /* FsWatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = FE647FF327BDFEDE00F3206B /* FsWatcher.swift */; };
|
||||
FE647FF627BDFEF500F3206B /* FsWatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = FE647FF527BDFEF500F3206B /* FsWatcher.m */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
|
@ -47,6 +49,8 @@
|
|||
D3D62A09275C92880003FBDC /* FileContainer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FileContainer.swift; sourceTree = "<group>"; };
|
||||
D3D62A0B275C928F0003FBDC /* FileContainer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FileContainer.m; sourceTree = "<group>"; };
|
||||
DE5650F4AD4E2242AB9C012D /* Pods-Logseq.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Logseq.debug.xcconfig"; path = "Target Support Files/Pods-Logseq/Pods-Logseq.debug.xcconfig"; sourceTree = "<group>"; };
|
||||
FE647FF327BDFEDE00F3206B /* FsWatcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FsWatcher.swift; sourceTree = "<group>"; };
|
||||
FE647FF527BDFEF500F3206B /* FsWatcher.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FsWatcher.m; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
|
@ -96,6 +100,8 @@
|
|||
2FAD9762203C412B000D30F8 /* config.xml */,
|
||||
50B271D01FEDC1A000F3C39B /* public */,
|
||||
7435D10B2704659F00AB88E0 /* FolderPicker.swift */,
|
||||
FE647FF327BDFEDE00F3206B /* FsWatcher.swift */,
|
||||
FE647FF527BDFEF500F3206B /* FsWatcher.m */,
|
||||
7435D10E2704660B00AB88E0 /* FolderPicker.m */,
|
||||
D3D62A09275C92880003FBDC /* FileContainer.swift */,
|
||||
D3D62A0B275C928F0003FBDC /* FileContainer.m */,
|
||||
|
@ -241,11 +247,13 @@
|
|||
files = (
|
||||
504EC3081FED79650016851F /* AppDelegate.swift in Sources */,
|
||||
5FD5BB71278579F5008E6875 /* DownloadiCloudFiles.swift in Sources */,
|
||||
FE647FF427BDFEDE00F3206B /* FsWatcher.swift in Sources */,
|
||||
5FD5BB73278579FF008E6875 /* DownloadiCloudFiles.m in Sources */,
|
||||
D3D62A0A275C92880003FBDC /* FileContainer.swift in Sources */,
|
||||
D3D62A0C275C928F0003FBDC /* FileContainer.m in Sources */,
|
||||
7435D10F2704660B00AB88E0 /* FolderPicker.m in Sources */,
|
||||
7435D10C2704659F00AB88E0 /* FolderPicker.swift in Sources */,
|
||||
FE647FF627BDFEF500F3206B /* FsWatcher.m in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// DowloadiCloudFiles.m
|
||||
// DownloadiCloudFiles.m
|
||||
// Logseq
|
||||
//
|
||||
// Created by leizhe on 2021/12/29.
|
||||
|
|
|
@ -36,7 +36,7 @@ public class FileContainer: CAPPlugin, UIDocumentPickerDelegate {
|
|||
guard let filename = self.containerUrl?.appendingPathComponent(".logseq") else {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
if !FileManager.default.fileExists(atPath: filename.path) {
|
||||
do {
|
||||
try str.write(to: filename, atomically: true, encoding: String.Encoding.utf8)
|
||||
|
@ -45,8 +45,6 @@ public class FileContainer: CAPPlugin, UIDocumentPickerDelegate {
|
|||
// failed to write file – bad permissions, bad filename, missing permissions, or more likely it can't be converted to the encoding
|
||||
}
|
||||
}
|
||||
self._call?.resolve([
|
||||
"path": self.containerUrl?.path
|
||||
])
|
||||
self._call?.resolve(["path": self.containerUrl?.path as Any])
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
//
|
||||
// FsWatcher.m
|
||||
// Logseq
|
||||
//
|
||||
// Created by Mono Wang on 2/17/R4.
|
||||
//
|
||||
|
||||
#import <Capacitor/Capacitor.h>
|
||||
|
||||
CAP_PLUGIN(FsWatcher, "FsWatcher",
|
||||
CAP_PLUGIN_METHOD(watch, CAPPluginReturnPromise);
|
||||
CAP_PLUGIN_METHOD(unwatch, CAPPluginReturnPromise);
|
||||
)
|
|
@ -0,0 +1,222 @@
|
|||
//
|
||||
// FsWatcher.swift
|
||||
// Logseq
|
||||
//
|
||||
// Created by Mono Wang on 2/17/R4.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Capacitor
|
||||
|
||||
// MARK: Watcher Plugin
|
||||
|
||||
@objc(FsWatcher)
|
||||
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 {
|
||||
call.reject("can not parse url")
|
||||
return
|
||||
}
|
||||
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 {
|
||||
case .Unlink:
|
||||
self.notifyListeners("watcher", data: ["event": "unlink",
|
||||
"dir": baseUrl?.description as Any,
|
||||
"path": url.description,
|
||||
])
|
||||
case .Add:
|
||||
let content = try? String(contentsOf: url, encoding: .utf8)
|
||||
self.notifyListeners("watcher", data: ["event": "add",
|
||||
"dir": baseUrl?.description as Any,
|
||||
"path": url.description,
|
||||
"content": content as Any,
|
||||
"stat": ["mtime": metadata?.contentModificationTimestamp,
|
||||
"ctime": metadata?.creationTimestamp]
|
||||
])
|
||||
case .Change:
|
||||
let content = try? String(contentsOf: url, encoding: .utf8)
|
||||
self.notifyListeners("watcher", data: ["event": "change",
|
||||
"dir": baseUrl?.description as Any,
|
||||
"path": url.description,
|
||||
"content": content as Any,
|
||||
"stat": ["mtime": metadata?.contentModificationTimestamp,
|
||||
"ctime": metadata?.creationTimestamp]])
|
||||
case .Error:
|
||||
// TODO: handle error?
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: URL extension
|
||||
|
||||
extension URL {
|
||||
func isSkipped() -> Bool {
|
||||
// skip hidden file
|
||||
if self.lastPathComponent.starts(with: ".") {
|
||||
return true
|
||||
}
|
||||
let allowedPathExtensions: Set = ["md", "markdown", "org", "css", "edn", "excalidraw"]
|
||||
if allowedPathExtensions.contains(self.pathExtension.lowercased()) {
|
||||
return false
|
||||
}
|
||||
// skip for other file types
|
||||
return true
|
||||
}
|
||||
|
||||
func isICloudPlaceholder() -> Bool {
|
||||
if self.lastPathComponent.starts(with: ".") && self.pathExtension.lowercased() == "icloud" {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: PollingWatcher
|
||||
|
||||
public protocol PollingWatcherDelegate {
|
||||
func recevedNotification(_ url: URL, _ event: PollingWatcherEvent, _ metadata: SimpleFileMetadata?)
|
||||
}
|
||||
|
||||
public enum PollingWatcherEvent: String {
|
||||
case Add
|
||||
case Change
|
||||
case Unlink
|
||||
case Error
|
||||
}
|
||||
|
||||
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])
|
||||
if fileAttributes.isRegularFile! {
|
||||
contentModificationTimestamp = fileAttributes.contentModificationDate?.timeIntervalSince1970 ?? 0.0
|
||||
creationTimestamp = fileAttributes.creationDate?.timeIntervalSince1970 ?? 0.0
|
||||
fileSize = fileAttributes.fileSize ?? 0
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
} catch {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public var description: String {
|
||||
return "Meta(size=\(self.fileSize), mtime=\(self.contentModificationTimestamp), ctime=\(self.creationTimestamp)"
|
||||
}
|
||||
}
|
||||
|
||||
public class PollingWatcher {
|
||||
private let url: URL
|
||||
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],
|
||||
// NOTE: icloud downloading requires non-skipsHiddenFiles
|
||||
options: [.skipsPackageDescendants]) {
|
||||
|
||||
var newMetaDb: [URL: SimpleFileMetadata] = [:]
|
||||
|
||||
for case let fileURL as URL in enumerator {
|
||||
if !fileURL.isSkipped() {
|
||||
if let meta = SimpleFileMetadata(of: fileURL) {
|
||||
newMetaDb[fileURL] = meta
|
||||
}
|
||||
} else if fileURL.isICloudPlaceholder() {
|
||||
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 {
|
||||
if let idx = self.metaDb.index(forKey: url) {
|
||||
let (_, oldMeta) = self.metaDb.remove(at: idx)
|
||||
if oldMeta != meta {
|
||||
self.delegate?.recevedNotification(url, .Change, meta)
|
||||
}
|
||||
} else {
|
||||
self.delegate?.recevedNotification(url, .Add, meta)
|
||||
}
|
||||
}
|
||||
for url in self.metaDb.keys {
|
||||
self.delegate?.recevedNotification(url, .Unlink, nil)
|
||||
}
|
||||
self.metaDb = newMetaDb
|
||||
}
|
||||
}
|
|
@ -114,11 +114,11 @@
|
|||
|
||||
:else
|
||||
(let [[old-path new-path]
|
||||
(map #(if (or (util/electron?) (mobile-util/is-native-platform?))
|
||||
%
|
||||
(str (config/get-repo-dir repo) "/" %))
|
||||
[old-path new-path])]
|
||||
(protocol/rename! (get-fs old-path) repo old-path new-path))))
|
||||
(map #(if (or (util/electron?) (mobile-util/is-native-platform?))
|
||||
%
|
||||
(str (config/get-repo-dir repo) "/" %))
|
||||
[old-path new-path])]
|
||||
(protocol/rename! (get-fs old-path) repo old-path new-path))))
|
||||
|
||||
(defn stat
|
||||
[dir path]
|
||||
|
@ -159,7 +159,7 @@
|
|||
|
||||
(defn watch-dir!
|
||||
[dir]
|
||||
(protocol/watch-dir! node-record dir))
|
||||
(protocol/watch-dir! (get-record) dir))
|
||||
|
||||
(defn mkdir-if-not-exists
|
||||
[dir]
|
||||
|
@ -184,9 +184,9 @@
|
|||
(p/let [_stat (stat dir path)]
|
||||
true)
|
||||
(p/catch
|
||||
(fn [_error]
|
||||
(p/let [_ (write-file! repo dir path initial-content nil)]
|
||||
false)))))))
|
||||
(fn [_error]
|
||||
(p/let [_ (write-file! repo dir path initial-content nil)]
|
||||
false)))))))
|
||||
|
||||
(defn file-exists?
|
||||
[dir path]
|
||||
|
|
|
@ -16,6 +16,20 @@
|
|||
[]
|
||||
(.ensureDocuments mobile-util/ios-file-container)))
|
||||
|
||||
(when (mobile-util/native-ios?)
|
||||
;; NOTE: avoid circular dependency
|
||||
#_:clj-kondo/ignore
|
||||
(def handle-changed! (delay frontend.fs.watcher-handler/handle-changed!))
|
||||
|
||||
(p/do!
|
||||
(.addListener mobile-util/fs-watcher "watcher"
|
||||
(fn [^js event]
|
||||
(@handle-changed!
|
||||
(.-event event)
|
||||
(update (js->clj event :keywordize-keys true)
|
||||
:path
|
||||
js/decodeURI))))))
|
||||
|
||||
(defn check-permission-android []
|
||||
(p/let [permission (.checkPermissions Filesystem)
|
||||
permission (-> permission
|
||||
|
@ -117,9 +131,9 @@
|
|||
(log/error :write-file-failed error))))
|
||||
|
||||
(p/let [disk-content (-> (p/chain (.readFile Filesystem (clj->js {:path path
|
||||
:encoding (.-UTF8 Encoding)}))
|
||||
#(js->clj % :keywordize-keys true)
|
||||
:data)
|
||||
:encoding (.-UTF8 Encoding)}))
|
||||
#(js->clj % :keywordize-keys true)
|
||||
:data)
|
||||
(p/catch (fn [error]
|
||||
(js/console.error error)
|
||||
nil)))
|
||||
|
@ -216,30 +230,30 @@
|
|||
(delete-file! [_this repo dir path {:keys [ok-handler error-handler]}]
|
||||
(let [path (get-file-path dir path)]
|
||||
(p/catch
|
||||
(p/let [result (.deleteFile Filesystem
|
||||
(clj->js
|
||||
{:path path}))]
|
||||
(when ok-handler
|
||||
(ok-handler repo path result)))
|
||||
(fn [error]
|
||||
(if error-handler
|
||||
(error-handler error)
|
||||
(log/error :delete-file-failed error))))))
|
||||
(p/let [result (.deleteFile Filesystem
|
||||
(clj->js
|
||||
{:path path}))]
|
||||
(when ok-handler
|
||||
(ok-handler repo path result)))
|
||||
(fn [error]
|
||||
(if error-handler
|
||||
(error-handler error)
|
||||
(log/error :delete-file-failed error))))))
|
||||
(write-file! [this repo dir path content opts]
|
||||
(let [path (get-file-path dir path)]
|
||||
(p/let [stat (p/catch
|
||||
(.stat Filesystem (clj->js {:path path}))
|
||||
(fn [_e] :not-found))]
|
||||
(.stat Filesystem (clj->js {:path path}))
|
||||
(fn [_e] :not-found))]
|
||||
(write-file-impl! this repo dir path content opts stat))))
|
||||
(rename! [_this _repo old-path new-path]
|
||||
(let [[old-path new-path] (map #(get-file-path "" %) [old-path new-path])]
|
||||
(p/catch
|
||||
(p/let [_ (.rename Filesystem
|
||||
(clj->js
|
||||
{:from old-path
|
||||
:to new-path}))])
|
||||
(fn [error]
|
||||
(log/error :rename-file-failed error)))))
|
||||
(p/let [_ (.rename Filesystem
|
||||
(clj->js
|
||||
{:from old-path
|
||||
:to new-path}))])
|
||||
(fn [error]
|
||||
(log/error :rename-file-failed error)))))
|
||||
(stat [_this dir path]
|
||||
(let [path (get-file-path dir path)]
|
||||
(p/let [result (.stat Filesystem (clj->js
|
||||
|
@ -259,45 +273,8 @@
|
|||
(into [] (concat [{:path path}] files))))
|
||||
(get-files [_this path-or-handle _ok-handler]
|
||||
(readdir path-or-handle))
|
||||
(watch-dir! [_this _dir]
|
||||
nil))
|
||||
|
||||
|
||||
(comment
|
||||
;;open-dir result
|
||||
#_
|
||||
["/storage/emulated/0/untitled folder 21"
|
||||
{:type "file",
|
||||
:size 2,
|
||||
:mtime 1630049904000,
|
||||
:uri "file:///storage/emulated/0/untitled%20folder%2021/pages/contents.md",
|
||||
:ctime 1630049904000,
|
||||
:content "-\n"}
|
||||
{:type "file",
|
||||
:size 0,
|
||||
:mtime 1630049904000,
|
||||
:uri "file:///storage/emulated/0/untitled%20folder%2021/logseq/custom.css",
|
||||
:ctime 1630049904000,
|
||||
:content ""}
|
||||
{:type "file",
|
||||
:size 2,
|
||||
:mtime 1630049904000,
|
||||
:uri "file:///storage/emulated/0/untitled%20folder%2021/logseq/metadata.edn",
|
||||
:ctime 1630049904000,
|
||||
:content "{}"}
|
||||
{:type "file",
|
||||
:size 181,
|
||||
:mtime 1630050535000,
|
||||
:uri
|
||||
"file:///storage/emulated/0/untitled%20folder%2021/journals/2021_08_27.md",
|
||||
:ctime 1630050535000,
|
||||
:content
|
||||
"- xx\n- xxx\n- xxx\n- xxxxxxxx\n- xxx\n- xzcxz\n- xzcxzc\n- asdsad\n- asdsadasda\n- asdsdaasdsad\n- asdasasdas\n- asdsad\n- sad\n- asd\n- asdsad\n- asdasd\n- sadsd\n-\n- asd\n- saddsa\n- asdsaasd\n- asd"}
|
||||
{:type "file",
|
||||
:size 132,
|
||||
:mtime 1630311293000,
|
||||
:uri
|
||||
"file:///storage/emulated/0/untitled%20folder%2021/journals/2021_08_30.md",
|
||||
:ctime 1630311293000,
|
||||
:content
|
||||
"- ccc\n- sadsa\n- sadasd\n- asdasd\n- asdasd\n\t- asdasd\n\t\t- asdasdsasd\n\t\t\t- sdsad\n\t\t-\n- sadasd\n- asdas\n- sadasd\n-\n-\n\t- sadasdasd\n\t- asdsd"}])
|
||||
(watch-dir! [_this dir]
|
||||
(when (mobile-util/native-ios?)
|
||||
(p/do!
|
||||
(.unwatch mobile-util/fs-watcher)
|
||||
(.watch mobile-util/fs-watcher #js {:path dir})))))
|
||||
|
|
|
@ -314,10 +314,9 @@
|
|||
|
||||
(defn watch-for-current-graph-dir!
|
||||
[]
|
||||
(when (util/electron?)
|
||||
(when-let [repo (state/get-current-repo)]
|
||||
(when-let [dir (config/get-repo-dir repo)]
|
||||
(fs/watch-dir! dir)))))
|
||||
(when-let [repo (state/get-current-repo)]
|
||||
(when-let [dir (config/get-repo-dir repo)]
|
||||
(fs/watch-dir! dir))))
|
||||
|
||||
(defn create-metadata-file
|
||||
[repo-url encrypted?]
|
||||
|
|
|
@ -179,8 +179,7 @@
|
|||
(state/add-repo! {:url repo :nfs? true})
|
||||
(state/set-loading-files! repo false)
|
||||
(when ok-handler (ok-handler))
|
||||
(when (util/electron?)
|
||||
(fs/watch-dir! dir-name))
|
||||
(fs/watch-dir! dir-name)
|
||||
(db/persist-if-idle! repo)))))
|
||||
(p/catch (fn [error]
|
||||
(log/error :nfs/load-files-error repo)
|
||||
|
|
|
@ -22,9 +22,10 @@
|
|||
|
||||
(defonce folder-picker (registerPlugin "FolderPicker"))
|
||||
(when (native-ios?)
|
||||
(defonce download-icloud-files (registerPlugin "DownloadiCloudFiles")))
|
||||
(when (native-ios?)
|
||||
(defonce download-icloud-files (registerPlugin "DownloadiCloudFiles"))
|
||||
(defonce ios-file-container (registerPlugin "FileContainer")))
|
||||
(when (native-ios?)
|
||||
(defonce fs-watcher (registerPlugin "FsWatcher")))
|
||||
|
||||
(defn sync-icloud-repo [repo-dir]
|
||||
(let [repo-name (-> (string/split repo-dir "Documents/")
|
||||
|
@ -32,7 +33,7 @@
|
|||
string/trim
|
||||
js/decodeURI)]
|
||||
(.syncGraph download-icloud-files
|
||||
(clj->js {:graph repo-name}))))
|
||||
(clj->js {:graph repo-name}))))
|
||||
|
||||
(defn hide-splash []
|
||||
(.hide SplashScreen))
|
||||
|
|
Loading…
Reference in New Issue