184 lines
5.4 KiB
Swift
184 lines
5.4 KiB
Swift
//
|
|
// Swiftfin is subject to the terms of the Mozilla Public
|
|
// License, v2.0. If a copy of the MPL was not distributed with this
|
|
// file, you can obtain one at https://mozilla.org/MPL/2.0/.
|
|
//
|
|
// Copyright (c) 2025 Jellyfin & Jellyfin Contributors
|
|
//
|
|
|
|
import Combine
|
|
import Defaults
|
|
import Foundation
|
|
|
|
final class ConnectToXtreamViewModel: ViewModel, Eventful, Stateful {
|
|
|
|
// MARK: Event
|
|
|
|
enum Event {
|
|
case connected(XtreamServer)
|
|
case error(XtreamAPIError)
|
|
}
|
|
|
|
// MARK: Action
|
|
|
|
enum Action: Equatable {
|
|
case cancel
|
|
case connect(name: String, url: String, username: String, password: String)
|
|
case testConnection(name: String, url: String, username: String, password: String)
|
|
}
|
|
|
|
// MARK: State
|
|
|
|
enum State: Hashable {
|
|
case connecting
|
|
case initial
|
|
case testing
|
|
}
|
|
|
|
@Published
|
|
var state: State = .initial
|
|
|
|
var events: AnyPublisher<Event, Never> {
|
|
eventSubject
|
|
.receive(on: RunLoop.main)
|
|
.eraseToAnyPublisher()
|
|
}
|
|
|
|
private var connectTask: AnyCancellable?
|
|
private var eventSubject: PassthroughSubject<Event, Never> = .init()
|
|
|
|
func respond(to action: Action) -> State {
|
|
switch action {
|
|
case .cancel:
|
|
connectTask?.cancel()
|
|
return .initial
|
|
|
|
case let .connect(name, urlString, username, password):
|
|
connectTask?.cancel()
|
|
|
|
connectTask = Task {
|
|
do {
|
|
let server = try await connectToXtream(
|
|
name: name,
|
|
url: urlString,
|
|
username: username,
|
|
password: password
|
|
)
|
|
|
|
await MainActor.run {
|
|
self.eventSubject.send(.connected(server))
|
|
self.state = .initial
|
|
}
|
|
} catch is CancellationError {
|
|
// cancel doesn't matter
|
|
} catch let error as XtreamAPIError {
|
|
await MainActor.run {
|
|
self.eventSubject.send(.error(error))
|
|
self.state = .initial
|
|
}
|
|
} catch {
|
|
await MainActor.run {
|
|
self.eventSubject.send(.error(.invalidResponse))
|
|
self.state = .initial
|
|
}
|
|
}
|
|
}
|
|
.asAnyCancellable()
|
|
|
|
return .connecting
|
|
|
|
case let .testConnection(name, urlString, username, password):
|
|
connectTask?.cancel()
|
|
|
|
connectTask = Task {
|
|
do {
|
|
_ = try await connectToXtream(
|
|
name: name,
|
|
url: urlString,
|
|
username: username,
|
|
password: password
|
|
)
|
|
|
|
await MainActor.run {
|
|
self.state = .initial
|
|
}
|
|
} catch is CancellationError {
|
|
// cancel doesn't matter
|
|
} catch let error as XtreamAPIError {
|
|
await MainActor.run {
|
|
self.eventSubject.send(.error(error))
|
|
self.state = .initial
|
|
}
|
|
} catch {
|
|
await MainActor.run {
|
|
self.eventSubject.send(.error(.invalidResponse))
|
|
self.state = .initial
|
|
}
|
|
}
|
|
}
|
|
.asAnyCancellable()
|
|
|
|
return .testing
|
|
}
|
|
}
|
|
|
|
private func connectToXtream(
|
|
name: String,
|
|
url urlString: String,
|
|
username: String,
|
|
password: String
|
|
) async throws -> XtreamServer {
|
|
|
|
let formattedURL = urlString.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
.trimmingCharacters(in: .objectReplacement)
|
|
.trimmingCharacters(in: ["/"])
|
|
.prepending("http://", if: !urlString.contains("://"))
|
|
|
|
guard let url = URL(string: formattedURL) else {
|
|
throw XtreamAPIError.invalidURL
|
|
}
|
|
|
|
let server = XtreamServer(
|
|
name: name.isEmpty ? "Xtream Server" : name,
|
|
url: url,
|
|
username: username,
|
|
password: password
|
|
)
|
|
|
|
// Test connection
|
|
let client = XtreamAPIClient(server: server)
|
|
_ = try await client.testConnection()
|
|
|
|
return server
|
|
}
|
|
|
|
func saveServer(_ server: XtreamServer) {
|
|
var servers = Defaults[.xtreamServers]
|
|
|
|
// Check if server with same ID exists and update, otherwise append
|
|
if let index = servers.firstIndex(where: { $0.id == server.id }) {
|
|
servers[index] = server
|
|
} else {
|
|
servers.append(server)
|
|
}
|
|
|
|
Defaults[.xtreamServers] = servers
|
|
|
|
// Set as current server if it's the first one
|
|
if Defaults[.currentXtreamServerID] == nil {
|
|
Defaults[.currentXtreamServerID] = server.id
|
|
}
|
|
}
|
|
|
|
func deleteServer(_ server: XtreamServer) {
|
|
var servers = Defaults[.xtreamServers]
|
|
servers.removeAll { $0.id == server.id }
|
|
Defaults[.xtreamServers] = servers
|
|
|
|
// Clear current server if deleted
|
|
if Defaults[.currentXtreamServerID] == server.id {
|
|
Defaults[.currentXtreamServerID] = servers.first?.id
|
|
}
|
|
}
|
|
}
|