Refactor to MVVM architecture
This commit is contained in:
@@ -0,0 +1,157 @@
|
||||
#if os(tvOS)
|
||||
|
||||
import DeviceDiscoveryUI
|
||||
import Libbox
|
||||
import Library
|
||||
import Network
|
||||
import SwiftUI
|
||||
|
||||
@MainActor
|
||||
public final class ImportProfileViewModel: ObservableObject {
|
||||
@Published public var isLoading = false
|
||||
@Published public var selected = false
|
||||
@Published public var alert: Alert?
|
||||
@Published public var connection: NWConnection?
|
||||
@Published public var socket: NWSocket?
|
||||
@Published public var profiles: [LibboxProfilePreview]?
|
||||
@Published public var isImporting = false
|
||||
|
||||
public init() {}
|
||||
|
||||
public func reset() {
|
||||
if let connection {
|
||||
connection.stateUpdateHandler = nil
|
||||
connection.cancel()
|
||||
self.connection = nil
|
||||
}
|
||||
if let socket {
|
||||
socket.cancel()
|
||||
self.socket = nil
|
||||
}
|
||||
selected = false
|
||||
profiles = nil
|
||||
}
|
||||
|
||||
public func handleEndpoint(_ endpoint: NWEndpoint, environments: ExtensionEnvironments, dismiss: DismissAction) async {
|
||||
let connection = NWConnection(to: endpoint, using: NWParameters.applicationService)
|
||||
self.connection = connection
|
||||
socket = NWSocket(connection)
|
||||
connection.stateUpdateHandler = { state in
|
||||
switch state {
|
||||
case let .failed(error):
|
||||
DispatchQueue.main.async { [self] in
|
||||
reset()
|
||||
alert = Alert(error)
|
||||
}
|
||||
default: break
|
||||
}
|
||||
}
|
||||
connection.start(queue: .global())
|
||||
do {
|
||||
try await loopMessages(environments: environments, dismiss: dismiss)
|
||||
} catch {
|
||||
alert = Alert(error)
|
||||
reset()
|
||||
}
|
||||
}
|
||||
|
||||
private nonisolated func loopMessages(environments: ExtensionEnvironments, dismiss: DismissAction) async throws {
|
||||
guard let socket = await socket else {
|
||||
return
|
||||
}
|
||||
var message: Data
|
||||
while true {
|
||||
do {
|
||||
message = try socket.read()
|
||||
} catch {
|
||||
throw NSError(domain: "read from connection: \(error.localizedDescription)", code: 0)
|
||||
}
|
||||
var error: NSError?
|
||||
switch Int64(message[0]) {
|
||||
case LibboxMessageTypeError:
|
||||
let message = LibboxDecodeErrorMessage(message, &error)
|
||||
if let error {
|
||||
throw error
|
||||
}
|
||||
if let message {
|
||||
throw NSError(domain: "remote error: \(message.message)", code: 0)
|
||||
}
|
||||
case LibboxMessageTypeProfileList:
|
||||
let decoder = LibboxProfileDecoder()
|
||||
try decoder.decode(message)
|
||||
let iterator = decoder.iterator()!
|
||||
var profiles = [LibboxProfilePreview]()
|
||||
while iterator.hasNext() {
|
||||
let profile = iterator.next()!
|
||||
if profile.type == LibboxProfileTypeiCloud {
|
||||
continue
|
||||
}
|
||||
profiles.append(profile)
|
||||
}
|
||||
await MainActor.run { [self, profiles] in
|
||||
self.profiles = profiles
|
||||
isImporting = false
|
||||
}
|
||||
case LibboxMessageTypeProfileContent:
|
||||
let content = LibboxDecodeProfileContent(message, &error)
|
||||
if let error {
|
||||
throw error
|
||||
}
|
||||
try await importProfile(content!, environments: environments, dismiss: dismiss)
|
||||
return
|
||||
default:
|
||||
throw NSError(domain: "unknown message type \(message[0])", code: 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public func selectProfile(profileID: Int64) {
|
||||
guard let connection else {
|
||||
return
|
||||
}
|
||||
guard let socket else {
|
||||
return
|
||||
}
|
||||
connection.stateUpdateHandler = nil
|
||||
let request = LibboxProfileContentRequest()
|
||||
request.profileID = profileID
|
||||
do {
|
||||
try socket.write(request.encode())
|
||||
isImporting = true
|
||||
} catch {
|
||||
alert = Alert(error)
|
||||
reset()
|
||||
}
|
||||
}
|
||||
|
||||
private nonisolated func importProfile(_ content: LibboxProfileContent, environments: ExtensionEnvironments, dismiss: DismissAction) async throws {
|
||||
var type: ProfileType = .local
|
||||
switch content.type {
|
||||
case LibboxProfileTypeLocal:
|
||||
type = .local
|
||||
case LibboxProfileTypeiCloud:
|
||||
type = .icloud
|
||||
case LibboxProfileTypeRemote:
|
||||
type = .remote
|
||||
default:
|
||||
break
|
||||
}
|
||||
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)
|
||||
var lastUpdated: Date?
|
||||
if content.lastUpdated > 0 {
|
||||
lastUpdated = Date(timeIntervalSince1970: Double(content.lastUpdated))
|
||||
}
|
||||
try await ProfileManager.create(Profile(name: content.name, type: type, path: profileConfig.relativePath, remoteURL: content.remotePath, autoUpdate: content.autoUpdate, lastUpdated: lastUpdated))
|
||||
await reset()
|
||||
await MainActor.run {
|
||||
environments.profileUpdate.send()
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user