Skip to content

Commit 3a970da

Browse files
committed
auto sync rime dir to app group
1 parent 085a54a commit 3a970da

File tree

3 files changed

+80
-2
lines changed

3 files changed

+80
-2
lines changed

src/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ add_executable(
33
MACOSX_BUNDLE
44
ContentView.swift
55
App.swift
6+
util.swift
67
)
78

89
add_custom_command(

src/ContentView.swift

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import SwiftUI
22

33
struct ContentView: View {
4+
@Environment(\.scenePhase) private var scenePhase
5+
46
var body: some View {
57
VStack {
68
Image(systemName: "globe")
@@ -12,8 +14,12 @@ struct ContentView: View {
1214
.onAppear {
1315
// The stupid iOS doesn't show empty directory in Files.app.
1416
try? "".write(
15-
to: FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
16-
.appendingPathComponent("placeholder"), atomically: true, encoding: .utf8)
17+
to: documents.appendingPathComponent("placeholder"), atomically: true, encoding: .utf8)
18+
}
19+
.onChange(of: scenePhase) { newPhase in
20+
if newPhase == .active {
21+
sync(documents.appendingPathComponent("rime"), appGroupData.appendingPathComponent("rime"))
22+
}
1723
}
1824
}
1925
}

src/util.swift

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import CryptoKit
2+
import Foundation
3+
4+
let documents = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
5+
let appGroupData = FileManager.default.containerURL(
6+
forSecurityApplicationGroupIdentifier: "org.fcitx.Fcitx5")!.appendingPathComponent("data")
7+
8+
extension URL {
9+
var isDirectory: Bool {
10+
(try? resourceValues(forKeys: [.isDirectoryKey]))?.isDirectory == true
11+
}
12+
13+
// Local file name is %-encoded with path()
14+
func localPath() -> String {
15+
let path = self.path
16+
guard let decoded = path.removingPercentEncoding else {
17+
return path
18+
}
19+
return decoded
20+
}
21+
22+
func exists() -> Bool {
23+
return FileManager.default.fileExists(atPath: self.path)
24+
}
25+
}
26+
27+
func md5Hash(_ url: URL) -> String {
28+
guard let fileData = try? Data(contentsOf: url) else {
29+
return ""
30+
}
31+
32+
let digest = Insecure.MD5.hash(data: fileData)
33+
return digest.map { String(format: "%02hhx", $0) }.joined()
34+
}
35+
36+
// Remove if different type, then copy if different content.
37+
func sync(_ src: URL, _ dst: URL) -> Bool {
38+
if !src.exists() {
39+
return false
40+
}
41+
if dst.exists() && dst.isDirectory != src.isDirectory {
42+
try? FileManager.default.removeItem(at: dst)
43+
}
44+
if src.isDirectory {
45+
try? FileManager.default.createDirectory(at: dst, withIntermediateDirectories: true)
46+
var success = true
47+
for fileName in (try? FileManager.default.contentsOfDirectory(atPath: src.localPath())) ?? [] {
48+
if !sync(src.appendingPathComponent(fileName), dst.appendingPathComponent(fileName)) {
49+
success = false
50+
}
51+
}
52+
return success
53+
}
54+
if dst.exists() {
55+
let srcHash = md5Hash(src)
56+
let dstHash = md5Hash(dst)
57+
if srcHash == dstHash {
58+
return true
59+
}
60+
try? FileManager.default.removeItem(at: dst)
61+
} else if !dst.deletingLastPathComponent().exists() {
62+
try? FileManager.default.createDirectory(
63+
at: dst.deletingLastPathComponent(), withIntermediateDirectories: true)
64+
}
65+
do {
66+
try FileManager.default.copyItem(at: src, to: dst)
67+
return true
68+
} catch {
69+
return false
70+
}
71+
}

0 commit comments

Comments
 (0)