169 lines
4.7 KiB
Swift
169 lines
4.7 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) 2024 Jellyfin & Jellyfin Contributors
|
|
//
|
|
|
|
import CoreStore
|
|
import Factory
|
|
import Foundation
|
|
import JellyfinAPI
|
|
import KeychainSwift
|
|
import Pulse
|
|
import UIKit
|
|
|
|
// Note: it is kind of backwards to have a "state" object with a mix of
|
|
// non-mutable and "mutable" values, but it just works.
|
|
|
|
extension SwiftfinStore.State {
|
|
|
|
struct User: Hashable, Identifiable {
|
|
|
|
let id: String
|
|
let serverID: String
|
|
let username: String
|
|
|
|
init(
|
|
id: String,
|
|
serverID: String,
|
|
username: String
|
|
) {
|
|
self.id = id
|
|
self.serverID = serverID
|
|
self.username = username
|
|
}
|
|
}
|
|
}
|
|
|
|
extension UserState {
|
|
|
|
typealias Key = StoredValues.Key
|
|
|
|
var accessToken: String {
|
|
get {
|
|
guard let accessToken = Container.shared.keychainService().get("\(id)-accessToken") else {
|
|
assertionFailure("access token missing in keychain")
|
|
return ""
|
|
}
|
|
|
|
return accessToken
|
|
}
|
|
nonmutating set {
|
|
Container.shared.keychainService().set(newValue, forKey: "\(id)-accessToken")
|
|
}
|
|
}
|
|
|
|
var data: UserDto {
|
|
get {
|
|
StoredValues[.User.data(id: id)]
|
|
}
|
|
nonmutating set {
|
|
StoredValues[.User.data(id: id)] = newValue
|
|
}
|
|
}
|
|
|
|
var isAdministrator: Bool {
|
|
data.policy?.isAdministrator ?? false
|
|
}
|
|
|
|
var pinHint: String {
|
|
get {
|
|
StoredValues[.User.pinHint(id: id)]
|
|
}
|
|
nonmutating set {
|
|
StoredValues[.User.pinHint(id: id)] = newValue
|
|
}
|
|
}
|
|
|
|
var accessPolicy: UserAccessPolicy {
|
|
get {
|
|
StoredValues[.User.accessPolicy(id: id)]
|
|
}
|
|
nonmutating set {
|
|
StoredValues[.User.accessPolicy(id: id)] = newValue
|
|
}
|
|
}
|
|
}
|
|
|
|
extension UserState {
|
|
|
|
/// Deletes the model that this state represents and
|
|
/// all settings from `Defaults` `Keychain`, and `StoredValues`
|
|
func delete() throws {
|
|
try SwiftfinStore.dataStack.perform { transaction in
|
|
guard let storedUser = try transaction.fetchOne(From<UserModel>().where(\.$id == id)) else {
|
|
throw JellyfinAPIError("Unable to find user to delete")
|
|
}
|
|
|
|
let storedDataClause = AnyStoredData.fetchClause(ownerID: id)
|
|
let storedData = try transaction.fetchAll(storedDataClause)
|
|
|
|
transaction.delete(storedUser)
|
|
transaction.delete(storedData)
|
|
}
|
|
|
|
UserDefaults.userSuite(id: id).removeAll()
|
|
|
|
let keychain = Container.shared.keychainService()
|
|
keychain.delete("\(id)-pin")
|
|
}
|
|
|
|
/// Deletes user settings from `UserDefaults` and `StoredValues`
|
|
///
|
|
/// Note: if performing deletion with another transaction, use
|
|
/// `AnyStoredData.fetchClause` instead within that transaction
|
|
/// and delete `Defaults` manually
|
|
func deleteSettings() throws {
|
|
try SwiftfinStore.dataStack.perform { transaction in
|
|
let userData = try transaction.fetchAll(
|
|
From<AnyStoredData>()
|
|
.where(combineByAnd: Where(\.$ownerID == id), Where("%K BEGINSWITH %@", "domain", "setting"))
|
|
)
|
|
|
|
transaction.delete(userData)
|
|
}
|
|
|
|
UserDefaults.userSuite(id: id).removeAll()
|
|
}
|
|
|
|
/// Must pass the server to create a JellyfinClient
|
|
/// with an access token
|
|
func getUserData(server: ServerState) async throws -> UserDto {
|
|
let client = JellyfinClient(
|
|
configuration: .swiftfinConfiguration(url: server.currentURL),
|
|
sessionConfiguration: .swiftfin,
|
|
sessionDelegate: URLSessionProxyDelegate(logger: Container.shared.pulseNetworkLogger()),
|
|
accessToken: accessToken
|
|
)
|
|
|
|
let request = Paths.getCurrentUser
|
|
let response = try await client.send(request)
|
|
|
|
return response.value
|
|
}
|
|
|
|
// we will always crop to a square, so just use width
|
|
func profileImageSource(
|
|
client: JellyfinClient,
|
|
maxWidth: CGFloat? = nil
|
|
) -> ImageSource {
|
|
let scaleWidth = maxWidth == nil ? nil : UIScreen.main.scale(maxWidth!)
|
|
|
|
let parameters = Paths.GetUserImageParameters(
|
|
tag: data.primaryImageTag,
|
|
maxWidth: scaleWidth
|
|
)
|
|
let request = Paths.getUserImage(
|
|
userID: id,
|
|
imageType: "Primary",
|
|
parameters: parameters
|
|
)
|
|
|
|
let profileImageURL = client.fullURL(with: request)
|
|
|
|
return ImageSource(url: profileImageURL)
|
|
}
|
|
}
|