Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

export to f5a/f5m; import from f5m #163

Merged
merged 1 commit into from
Jun 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified assets/zh-Hans.lproj/Localizable.strings
Binary file not shown.
2 changes: 1 addition & 1 deletion src/config/about.swift
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ struct AboutView: View {
.resizable()
.frame(width: 80, height: 80)
}
Text("Fcitx5 macOS")
Text(String("Fcitx5 macOS")) // no i18n by design
.font(.title)

Spacer().frame(height: gapSize)
Expand Down
119 changes: 119 additions & 0 deletions src/config/datamanager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,34 @@ import UniformTypeIdentifiers

let extractDir = cacheDir.appendingPathComponent("import")
let extractPath = extractDir.localPath()
let composeDir = cacheDir.appendingPathComponent("export")

private func extractZip(_ file: URL) -> Bool {
// unzip is unfriendly with Chinese file names
return exec("/usr/bin/ditto", ["-xk", file.localPath(), extractPath])
}

private func getTimeString() -> String {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH_mm_ss'Z'"
dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
return dateFormatter.string(from: Date())
}

struct DataView: View {
@State private var openPanel = NSOpenPanel()
@AppStorage("ImportDataSelectedDirectory") var importDataSelectedDirectory: String?
@AppStorage("ExportDataSelectedDirectory") var exportDataSelectedDirectory: String?
@State private var showImportF5a = false
@State private var showImportF5m = false
@State private var showImportSquirrel = false
@State private var showImportHamster = false
@State private var showSquirrelError = false
@State private var showInvalidZip = false
@State private var showRunning = false
@State private var showExportSuccess = false
@State private var showExportFailure = false

private func importZip(_ binding: Binding<Bool>, _ validator: @escaping () -> Bool) {
// Keep a single openPanel to avoid confusion.
Expand Down Expand Up @@ -45,6 +59,44 @@ struct DataView: View {
}
}

private func exportZip(_ name: String) -> Bool {
_ = removeFile(composeDir)
// Fake f5a structure.
for name in ["databases", "external", "recently_used", "shared_prefs"] {
mkdirP(composeDir.appendingPathComponent(name).localPath())
}
let externalDir = composeDir.appendingPathComponent("external")
for operation in [
{ copyFile(configDir, externalDir.appendingPathComponent("config")) },
{ copyFile(localDir, externalDir.appendingPathComponent("data")) },
{
writeUTF8(
composeDir.appendingPathComponent("metadata.json"),
"""
{
"packageName": "org.fcitx.fcitx5.android",
"versionCode": 0,
"versionName": "",
"exportTime": \(Int(Date().timeIntervalSince1970 * 1000))
}\n
""")
},
{
exec(
"/bin/zsh",
[
"-c",
"cd \(quote(composeDir.localPath())) && /usr/bin/zip -r \(name) * -x \"*.DS_Store\"",
])
},
] {
if !operation() {
return false
}
}
return true
}

var body: some View {
VStack {
Text("Import data from …")
Expand Down Expand Up @@ -75,6 +127,18 @@ struct DataView: View {
ImportDataView().load(f5aItems)
}

Button {
importZip(
$showImportF5m,
{
extractDir.appendingPathComponent("metadata.json").exists()
})
} label: {
Text("Fcitx5 macOS").tooltip("fcitx5-macos_YYYY-MM-DD*.zip")
}.sheet(isPresented: $showImportF5m) {
ImportDataView().load(f5mItems)
}

Button {
importZip(
$showImportHamster,
Expand All @@ -86,6 +150,46 @@ struct DataView: View {
}.sheet(isPresented: $showImportHamster) {
ImportDataView().load(hamsterItems)
}

Spacer().frame(height: gapSize)

Text("Export data to …")

Button {
showRunning = true
let name = "fcitx5-macos_\(getTimeString()).zip"
DispatchQueue.global().async {
let res = exportZip(name)
DispatchQueue.main.async {
showRunning = false
if res {
if openPanel.isVisible {
openPanel.cancel(nil)
openPanel = NSOpenPanel()
}
openPanel.allowsMultipleSelection = false
openPanel.canChooseDirectories = true
openPanel.canChooseFiles = false
if openPanel.runModal() == .OK {
if let url = openPanel.url {
if moveFile(
composeDir.appendingPathComponent(name), url.appendingPathComponent(name))
{
showExportSuccess = true
} else {
showExportFailure = true
}
exportDataSelectedDirectory = url.localPath()
}
}
} else {
showExportFailure = true
}
}
}
} label: {
Text("Fcitx5 Android/macOS")
}
}.padding()
.toast(isPresenting: $showSquirrelError) {
AlertToast(
Expand All @@ -97,5 +201,20 @@ struct DataView: View {
displayMode: .hud, type: .error(Color.red),
title: NSLocalizedString("Invalid zip", comment: ""))
}
.toast(isPresenting: $showExportSuccess) {
AlertToast(
displayMode: .hud,
type: .complete(Color.green), title: NSLocalizedString("Export succeeded", comment: ""))
}
.toast(isPresenting: $showExportFailure) {
AlertToast(
displayMode: .hud, type: .error(Color.red),
title: NSLocalizedString("Export failed", comment: ""))
}
.toast(
isPresenting: $showRunning
) {
AlertToast(type: .loading)
}
}
}
22 changes: 22 additions & 0 deletions src/config/importdata.swift
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,28 @@ let f5aItems = [
importableRimeUser(f5aRimeDir),
]

let f5mItems = [
ImportableItem(
name: NSLocalizedString("Config", comment: ""), enabled: true,
exists: {
dataDir.appendingPathComponent("config").exists()
},
doImport: {
mkdirP(configDir.localPath())
return moveAndMerge(dataDir.appendingPathComponent("config"), configDir)
}),
ImportableItem(
name: NSLocalizedString("Data", comment: ""), enabled: true,
exists: {
dataDir.appendingPathComponent("data").exists()
},
doImport: {
mkdirP(localDir.localPath())
return moveAndMerge(
dataDir.appendingPathComponent("data"), localDir)
}),
]

class ImportDataVM: ObservableObject {
@Published var importableItems = [ImportableItem]()
@Published var items = [ImportableItem]()
Expand Down