Fix main-thread blocking I/O
This commit is contained in:
@@ -2,6 +2,9 @@ import Foundation
|
||||
|
||||
public extension Profile {
|
||||
func read() throws -> String {
|
||||
#if DEBUG
|
||||
precondition(!Thread.isMainThread, "Profile.read() must not be called on the main thread")
|
||||
#endif
|
||||
switch type {
|
||||
case .local, .remote:
|
||||
return try String(contentsOfFile: path)
|
||||
@@ -12,6 +15,9 @@ public extension Profile {
|
||||
}
|
||||
|
||||
func write(_ content: String) throws {
|
||||
#if DEBUG
|
||||
precondition(!Thread.isMainThread, "Profile.write(...) must not be called on the main thread")
|
||||
#endif
|
||||
switch type {
|
||||
case .local, .remote:
|
||||
try content.write(toFile: path, atomically: true, encoding: .utf8)
|
||||
@@ -20,4 +26,33 @@ public extension Profile {
|
||||
try content.write(to: saveURL, atomically: true, encoding: .utf8)
|
||||
}
|
||||
}
|
||||
|
||||
func readAsync() async throws -> String {
|
||||
let type = type
|
||||
let path = path
|
||||
return try await BlockingIO.run {
|
||||
switch type {
|
||||
case .local, .remote:
|
||||
return try String(contentsOfFile: path)
|
||||
case .icloud:
|
||||
let saveURL = FilePath.iCloudDirectory.appendingPathComponent(path)
|
||||
return try String(contentsOf: saveURL)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func writeAsync(_ content: String) async throws {
|
||||
let type = type
|
||||
let path = path
|
||||
let content = content
|
||||
try await BlockingIO.run {
|
||||
switch type {
|
||||
case .local, .remote:
|
||||
try content.write(toFile: path, atomically: true, encoding: .utf8)
|
||||
case .icloud:
|
||||
let saveURL = FilePath.iCloudDirectory.appendingPathComponent(path)
|
||||
try content.write(to: saveURL, atomically: true, encoding: .utf8)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,79 @@ public extension Profile {
|
||||
}
|
||||
return content
|
||||
}
|
||||
|
||||
func encodedContentDataAsync() async throws -> Data {
|
||||
let name = name
|
||||
let type = type
|
||||
let remoteURL = remoteURL
|
||||
let autoUpdate = autoUpdate
|
||||
let autoUpdateInterval = autoUpdateInterval
|
||||
let lastUpdated = lastUpdated
|
||||
|
||||
let config = try await readAsync()
|
||||
return try await BlockingIO.run {
|
||||
let content = LibboxProfileContent()
|
||||
content.name = name
|
||||
switch type {
|
||||
case .local, .icloud:
|
||||
content.type = LibboxProfileTypeLocal
|
||||
case .remote:
|
||||
content.type = LibboxProfileTypeRemote
|
||||
}
|
||||
content.config = config
|
||||
if type == .remote {
|
||||
content.remotePath = remoteURL!
|
||||
content.autoUpdate = autoUpdate
|
||||
content.autoUpdateInterval = autoUpdateInterval
|
||||
if let lastUpdated {
|
||||
content.lastUpdated = Int64(lastUpdated.timeIntervalSince1970 * 1000)
|
||||
}
|
||||
}
|
||||
guard let encoded = content.encode() else {
|
||||
throw NSError(domain: "Profile", code: -1, userInfo: [NSLocalizedDescriptionKey: "Failed to encode profile"])
|
||||
}
|
||||
return encoded
|
||||
}
|
||||
}
|
||||
|
||||
func generateShareFileAsync() async throws -> URL {
|
||||
let name = name
|
||||
let type = type
|
||||
let remoteURL = remoteURL
|
||||
let autoUpdate = autoUpdate
|
||||
let autoUpdateInterval = autoUpdateInterval
|
||||
let lastUpdated = lastUpdated
|
||||
|
||||
let config = try await readAsync()
|
||||
return try await BlockingIO.run {
|
||||
let content = LibboxProfileContent()
|
||||
content.name = name
|
||||
switch type {
|
||||
case .local, .icloud:
|
||||
content.type = LibboxProfileTypeLocal
|
||||
case .remote:
|
||||
content.type = LibboxProfileTypeRemote
|
||||
}
|
||||
content.config = config
|
||||
if type == .remote {
|
||||
content.remotePath = remoteURL!
|
||||
content.autoUpdate = autoUpdate
|
||||
content.autoUpdateInterval = autoUpdateInterval
|
||||
if let lastUpdated {
|
||||
content.lastUpdated = Int64(lastUpdated.timeIntervalSince1970 * 1000)
|
||||
}
|
||||
}
|
||||
return try content.generateShareFile()
|
||||
}
|
||||
}
|
||||
|
||||
func generateJSONShareFileAsync(name: String) async throws -> URL {
|
||||
let filename = name
|
||||
let config = try await readAsync()
|
||||
return try await BlockingIO.run {
|
||||
try config.generateShareFile(name: filename)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func dateFromTimestamp(_ timestamp: Int64) -> Date {
|
||||
@@ -57,17 +130,34 @@ public extension LibboxProfileContent {
|
||||
|
||||
@discardableResult
|
||||
func importProfile() async throws -> Profile {
|
||||
let name = name
|
||||
let type = type
|
||||
let config = config
|
||||
let remotePath = remotePath
|
||||
let autoUpdate = autoUpdate
|
||||
let autoUpdateInterval = autoUpdateInterval
|
||||
let lastUpdated = lastUpdated
|
||||
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 config.write(to: profileConfig, atomically: true, encoding: .utf8)
|
||||
var lastUpdatedAt: Date?
|
||||
if lastUpdated > 0 {
|
||||
lastUpdatedAt = dateFromTimestamp(lastUpdated)
|
||||
}
|
||||
try await BlockingIO.run {
|
||||
try FileManager.default.createDirectory(at: profileConfigDirectory, withIntermediateDirectories: true)
|
||||
try config.write(to: profileConfig, atomically: true, encoding: .utf8)
|
||||
}
|
||||
let uniqueProfileName = try await ProfileManager.uniqueName(name)
|
||||
let profile = Profile(name: uniqueProfileName, type: ProfileType(rawValue: Int(type))!, path: profileConfig.relativePath, remoteURL: remotePath, autoUpdate: autoUpdate, autoUpdateInterval: autoUpdateInterval, lastUpdated: lastUpdatedAt)
|
||||
let profile = Profile(
|
||||
name: uniqueProfileName,
|
||||
type: ProfileType(rawValue: Int(type))!,
|
||||
path: profileConfig.relativePath,
|
||||
remoteURL: remotePath,
|
||||
autoUpdate: autoUpdate,
|
||||
autoUpdateInterval: autoUpdateInterval,
|
||||
lastUpdated: lastUpdatedAt
|
||||
)
|
||||
try await ProfileManager.create(profile)
|
||||
await SharedPreferences.selectedProfileID.set(profile.mustID)
|
||||
return profile
|
||||
@@ -190,6 +280,12 @@ public extension UTType {
|
||||
public let filename: String
|
||||
public let contentType: UTType
|
||||
|
||||
public init(data: Data, filename: String, contentType: UTType) {
|
||||
self.data = data
|
||||
self.filename = filename
|
||||
self.contentType = contentType
|
||||
}
|
||||
|
||||
public init(profile: ProfileExportDocument) {
|
||||
data = profile.data
|
||||
filename = profile.filename
|
||||
|
||||
@@ -7,21 +7,23 @@ public extension Profile {
|
||||
if type != .remote {
|
||||
return
|
||||
}
|
||||
let remoteContent = try HTTPClient().getString(remoteURL)
|
||||
var error: NSError?
|
||||
LibboxCheckConfig(remoteContent, &error)
|
||||
if let error {
|
||||
throw error
|
||||
let remoteContent = try await HTTPClient.getStringAsync(remoteURL)
|
||||
try await BlockingIO.run {
|
||||
var error: NSError?
|
||||
LibboxCheckConfig(remoteContent, &error)
|
||||
if let error {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
lastUpdated = Date()
|
||||
try await ProfileManager.update(self)
|
||||
do {
|
||||
let oldContent = try read()
|
||||
let oldContent = try await readAsync()
|
||||
if oldContent == remoteContent {
|
||||
return
|
||||
}
|
||||
} catch {}
|
||||
try write(remoteContent)
|
||||
try await writeAsync(remoteContent)
|
||||
try await onProfileUpdated()
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user