Fix main-thread blocking I/O
This commit is contained in:
@@ -48,27 +48,32 @@ public final class EditProfileContentViewModel: BaseViewModel {
|
||||
private func checkConfiguration() async {
|
||||
let content = profileContent
|
||||
if content.isEmpty { return }
|
||||
var error: NSError?
|
||||
LibboxCheckConfig(content, &error)
|
||||
if let error {
|
||||
configurationError = error.localizedDescription
|
||||
} else {
|
||||
configurationError = nil
|
||||
let errorDescription: String? = await BlockingIO.run {
|
||||
var error: NSError?
|
||||
LibboxCheckConfig(content, &error)
|
||||
return error?.localizedDescription
|
||||
}
|
||||
configurationError = errorDescription
|
||||
}
|
||||
|
||||
public func formatConfiguration() async {
|
||||
let content = profileContent
|
||||
if content.isEmpty { return }
|
||||
var error: NSError?
|
||||
let result = LibboxFormatConfig(content, &error)
|
||||
if let error {
|
||||
do {
|
||||
let formatted: String? = try await BlockingIO.run {
|
||||
var error: NSError?
|
||||
let result = LibboxFormatConfig(content, &error)
|
||||
if let error {
|
||||
throw error
|
||||
}
|
||||
return result?.value
|
||||
}
|
||||
if let formatted, formatted != content {
|
||||
profileContent = formatted
|
||||
isChanged = true
|
||||
}
|
||||
} catch {
|
||||
configurationError = error.localizedDescription
|
||||
return
|
||||
}
|
||||
if let formatted = result?.value, formatted != content {
|
||||
profileContent = formatted
|
||||
isChanged = true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,7 +97,7 @@ public final class EditProfileContentViewModel: BaseViewModel {
|
||||
guard let profile = try await ProfileManager.get(profileID) else {
|
||||
throw NSError(domain: "EditProfileContentViewModel", code: 0, userInfo: [NSLocalizedDescriptionKey: String(localized: "Profile missing")])
|
||||
}
|
||||
let profileContent = try profile.read()
|
||||
let profileContent = try await profile.readAsync()
|
||||
await MainActor.run {
|
||||
self.profile = profile
|
||||
self.profileContent = profileContent
|
||||
@@ -114,6 +119,6 @@ public final class EditProfileContentViewModel: BaseViewModel {
|
||||
|
||||
private nonisolated func saveContentBackground(_ profile: Profile) async throws {
|
||||
let profileContent = await profileContent
|
||||
try profile.write(profileContent)
|
||||
try await profile.writeAsync(profileContent)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -139,17 +139,32 @@
|
||||
default:
|
||||
break
|
||||
}
|
||||
let profileName = content.name
|
||||
let profileConfigContent = content.config
|
||||
let remotePath = content.remotePath
|
||||
let autoUpdate = content.autoUpdate
|
||||
let autoUpdateInterval = content.autoUpdateInterval
|
||||
let nextProfileID = try await ProfileManager.nextID()
|
||||
let profileConfigDirectory = FilePath.sharedDirectory.appendingPathComponent("configs", isDirectory: true)
|
||||
try FileManager.default.createDirectory(at: profileConfigDirectory, withIntermediateDirectories: true)
|
||||
let profileConfig = profileConfigDirectory.appendingPathComponent("config_\(nextProfileID).json")
|
||||
try content.config.write(to: profileConfig, atomically: true, encoding: .utf8)
|
||||
try await BlockingIO.run {
|
||||
try FileManager.default.createDirectory(at: profileConfigDirectory, withIntermediateDirectories: true)
|
||||
try profileConfigContent.write(to: profileConfig, atomically: true, encoding: .utf8)
|
||||
}
|
||||
var lastUpdated: Date?
|
||||
if content.lastUpdated > 0 {
|
||||
lastUpdated = dateFromTimestamp(content.lastUpdated)
|
||||
}
|
||||
let uniqueProfileName = try await ProfileManager.uniqueName(content.name)
|
||||
let profile = Profile(name: uniqueProfileName, type: type, path: profileConfig.relativePath, remoteURL: content.remotePath, autoUpdate: content.autoUpdate, lastUpdated: lastUpdated)
|
||||
let uniqueProfileName = try await ProfileManager.uniqueName(profileName)
|
||||
let profile = Profile(
|
||||
name: uniqueProfileName,
|
||||
type: type,
|
||||
path: profileConfig.relativePath,
|
||||
remoteURL: remotePath,
|
||||
autoUpdate: autoUpdate,
|
||||
autoUpdateInterval: autoUpdateInterval,
|
||||
lastUpdated: lastUpdated
|
||||
)
|
||||
try await ProfileManager.create(profile)
|
||||
await SharedPreferences.selectedProfileID.set(profile.mustID)
|
||||
await reset()
|
||||
|
||||
@@ -193,20 +193,26 @@ public struct NewProfileMenuView: View {
|
||||
|
||||
#if !os(tvOS)
|
||||
private func handleFileImport(_ result: Result<[URL], Error>) {
|
||||
do {
|
||||
let urls = try result.get()
|
||||
guard let url = urls.first else { return }
|
||||
Task { @MainActor in
|
||||
do {
|
||||
let urls = try result.get()
|
||||
guard let url = urls.first else { return }
|
||||
|
||||
if url.pathExtension.lowercased() == "json" {
|
||||
let fileName = url.deletingPathExtension().lastPathComponent
|
||||
localImportRequest = NewProfileView.LocalImportRequest(name: fileName, fileURL: url)
|
||||
} else {
|
||||
let content = try url.withRequiredSecurityScopedAccess(
|
||||
or: NSError(domain: "NewProfileMenuView", code: 0, userInfo: [NSLocalizedDescriptionKey: String(localized: "Missing access to selected file")])
|
||||
) {
|
||||
try LibboxProfileContent.from(Data(contentsOf: url))
|
||||
if url.pathExtension.lowercased() == "json" {
|
||||
let fileName = url.deletingPathExtension().lastPathComponent
|
||||
localImportRequest = NewProfileView.LocalImportRequest(name: fileName, fileURL: url)
|
||||
return
|
||||
}
|
||||
|
||||
let data = try await BlockingIO.run {
|
||||
try url.withRequiredSecurityScopedAccess(
|
||||
or: NSError(domain: "NewProfileMenuView", code: 0, userInfo: [NSLocalizedDescriptionKey: String(localized: "Missing access to selected file")])
|
||||
) {
|
||||
try Data(contentsOf: url)
|
||||
}
|
||||
}
|
||||
let content = try LibboxProfileContent.from(data)
|
||||
|
||||
alert = AlertState(
|
||||
title: String(localized: "Import Profile"),
|
||||
message: String(localized: "Are you sure to import profile \(content.name)?"),
|
||||
@@ -223,9 +229,9 @@ public struct NewProfileMenuView: View {
|
||||
},
|
||||
secondaryButton: .cancel()
|
||||
)
|
||||
} catch {
|
||||
alert = AlertState(error: error)
|
||||
}
|
||||
} catch {
|
||||
alert = AlertState(error: error)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -107,43 +107,52 @@ public final class NewProfileViewModel: BaseViewModel {
|
||||
|
||||
if profileType == .local {
|
||||
let profileConfigDirectory = FilePath.sharedDirectory.appendingPathComponent("configs", isDirectory: true)
|
||||
try FileManager.default.createDirectory(at: profileConfigDirectory, withIntermediateDirectories: true)
|
||||
let profileConfig = profileConfigDirectory.appendingPathComponent("config_\(nextProfileID).json")
|
||||
if fileImport {
|
||||
guard let fileURL else {
|
||||
throw NSError(domain: "NewProfileViewModel", code: 0, userInfo: [NSLocalizedDescriptionKey: String(localized: "Missing file")])
|
||||
try await BlockingIO.run {
|
||||
try FileManager.default.createDirectory(at: profileConfigDirectory, withIntermediateDirectories: true)
|
||||
if fileImport {
|
||||
guard let fileURL else {
|
||||
throw NSError(domain: "NewProfileViewModel", code: 0, userInfo: [NSLocalizedDescriptionKey: String(localized: "Missing file")])
|
||||
}
|
||||
try fileURL.withRequiredSecurityScopedAccess(
|
||||
or: NSError(domain: "NewProfileViewModel", code: 0, userInfo: [NSLocalizedDescriptionKey: String(localized: "Missing access to selected file")])
|
||||
) {
|
||||
try String(contentsOf: fileURL).write(to: profileConfig, atomically: true, encoding: .utf8)
|
||||
}
|
||||
} else {
|
||||
try "{}".write(to: profileConfig, atomically: true, encoding: .utf8)
|
||||
}
|
||||
try fileURL.withRequiredSecurityScopedAccess(
|
||||
or: NSError(domain: "NewProfileViewModel", code: 0, userInfo: [NSLocalizedDescriptionKey: String(localized: "Missing access to selected file")])
|
||||
) {
|
||||
try String(contentsOf: fileURL).write(to: profileConfig, atomically: true, encoding: .utf8)
|
||||
}
|
||||
} else {
|
||||
try "{}".write(to: profileConfig, atomically: true, encoding: .utf8)
|
||||
}
|
||||
savePath = profileConfig.relativePath
|
||||
} else if profileType == .icloud {
|
||||
if !FileManager.default.fileExists(atPath: FilePath.iCloudDirectory.path) {
|
||||
try FileManager.default.createDirectory(at: FilePath.iCloudDirectory, withIntermediateDirectories: true)
|
||||
}
|
||||
let saveURL = FilePath.iCloudDirectory.appendingPathComponent(remotePath, isDirectory: false)
|
||||
do {
|
||||
_ = try String(contentsOf: saveURL)
|
||||
} catch {
|
||||
try "{}".write(to: saveURL, atomically: true, encoding: .utf8)
|
||||
let iCloudDirectory = FilePath.iCloudDirectory
|
||||
try await BlockingIO.run {
|
||||
if !FileManager.default.fileExists(atPath: iCloudDirectory.path) {
|
||||
try FileManager.default.createDirectory(at: iCloudDirectory, withIntermediateDirectories: true)
|
||||
}
|
||||
let saveURL = iCloudDirectory.appendingPathComponent(remotePath, isDirectory: false)
|
||||
do {
|
||||
_ = try String(contentsOf: saveURL)
|
||||
} catch {
|
||||
try "{}".write(to: saveURL, atomically: true, encoding: .utf8)
|
||||
}
|
||||
}
|
||||
savePath = remotePath
|
||||
} else if profileType == .remote {
|
||||
let remoteContent = try HTTPClient().getString(remotePath)
|
||||
var error: NSError?
|
||||
LibboxCheckConfig(remoteContent, &error)
|
||||
if let error {
|
||||
throw error
|
||||
let remoteContent = try await HTTPClient.getStringAsync(remotePath)
|
||||
try await BlockingIO.run {
|
||||
var error: NSError?
|
||||
LibboxCheckConfig(remoteContent, &error)
|
||||
if let error {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
let profileConfigDirectory = FilePath.sharedDirectory.appendingPathComponent("configs", isDirectory: true)
|
||||
try FileManager.default.createDirectory(at: profileConfigDirectory, withIntermediateDirectories: true)
|
||||
let profileConfig = profileConfigDirectory.appendingPathComponent("config_\(nextProfileID).json")
|
||||
try remoteContent.write(to: profileConfig, atomically: true, encoding: .utf8)
|
||||
try await BlockingIO.run {
|
||||
try FileManager.default.createDirectory(at: profileConfigDirectory, withIntermediateDirectories: true)
|
||||
try remoteContent.write(to: profileConfig, atomically: true, encoding: .utf8)
|
||||
}
|
||||
savePath = profileConfig.relativePath
|
||||
remoteURL = remotePath
|
||||
lastUpdated = .now
|
||||
|
||||
Reference in New Issue
Block a user