Prevent connecting/signing in if already exists

This commit is contained in:
Ethan Pippin 2021-10-14 15:28:13 -06:00
parent a5a842e815
commit 3599df56e9
10 changed files with 188 additions and 19 deletions

View File

@ -242,6 +242,9 @@
C4E5081B2703F82A0045C9AB /* LibraryListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E508172703E8190045C9AB /* LibraryListView.swift */; };
C4E5081D2703F8370045C9AB /* LibrarySearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4E5081C2703F8370045C9AB /* LibrarySearchView.swift */; };
E100720726BDABC100CE3E31 /* MediaPlayButtonRowView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E100720626BDABC100CE3E31 /* MediaPlayButtonRowView.swift */; };
E11B1B6C2718CD68006DA3E8 /* JellyfinAPIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11B1B6B2718CD68006DA3E8 /* JellyfinAPIError.swift */; };
E11B1B6D2718CD68006DA3E8 /* JellyfinAPIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11B1B6B2718CD68006DA3E8 /* JellyfinAPIError.swift */; };
E11B1B6E2718CDBA006DA3E8 /* JellyfinAPIError.swift in Sources */ = {isa = PBXBuildFile; fileRef = E11B1B6B2718CD68006DA3E8 /* JellyfinAPIError.swift */; };
E131691726C583BC0074BFEE /* LogConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E131691626C583BC0074BFEE /* LogConstructor.swift */; };
E131691826C583BC0074BFEE /* LogConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E131691626C583BC0074BFEE /* LogConstructor.swift */; };
E131691926C583BC0074BFEE /* LogConstructor.swift in Sources */ = {isa = PBXBuildFile; fileRef = E131691626C583BC0074BFEE /* LogConstructor.swift */; };
@ -512,6 +515,7 @@
D79953919FED0C4DF72BA578 /* Pods-JellyfinPlayer tvOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JellyfinPlayer tvOS.release.xcconfig"; path = "Target Support Files/Pods-JellyfinPlayer tvOS/Pods-JellyfinPlayer tvOS.release.xcconfig"; sourceTree = "<group>"; };
DE5004F745B19E28744A7DE7 /* Pods-JellyfinPlayer tvOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-JellyfinPlayer tvOS.debug.xcconfig"; path = "Target Support Files/Pods-JellyfinPlayer tvOS/Pods-JellyfinPlayer tvOS.debug.xcconfig"; sourceTree = "<group>"; };
E100720626BDABC100CE3E31 /* MediaPlayButtonRowView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaPlayButtonRowView.swift; sourceTree = "<group>"; };
E11B1B6B2718CD68006DA3E8 /* JellyfinAPIError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JellyfinAPIError.swift; sourceTree = "<group>"; };
E131691626C583BC0074BFEE /* LogConstructor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LogConstructor.swift; sourceTree = "<group>"; };
E13DD3BC27163C63009D4DAF /* EmailHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailHelper.swift; sourceTree = "<group>"; };
E13DD3BE27163DD7009D4DAF /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
@ -1164,9 +1168,10 @@
E1AD105226D96D5F003E4A08 /* JellyfinAPIExtensions */ = {
isa = PBXGroup;
children = (
5364F454266CA0DC0026ECBA /* BaseItemPersonExtensions.swift */,
E1AD104C26D96CE3003E4A08 /* BaseItemDtoExtensions.swift */,
E18845F426DD631E00B0C5B7 /* BaseItemDto+Stackable.swift */,
E1AD104C26D96CE3003E4A08 /* BaseItemDtoExtensions.swift */,
5364F454266CA0DC0026ECBA /* BaseItemPersonExtensions.swift */,
E11B1B6B2718CD68006DA3E8 /* JellyfinAPIError.swift */,
E1AD105E26D9ADDD003E4A08 /* NameGUIDPairExtensions.swift */,
);
path = JellyfinAPIExtensions;
@ -1595,6 +1600,7 @@
535870A52669D8AE00D05A09 /* ParallaxHeader.swift in Sources */,
53272532268BF09D0035FBF1 /* MediaViewActionButton.swift in Sources */,
531690F0267ABF72005D8AB9 /* NextUpView.swift in Sources */,
E11B1B6D2718CD68006DA3E8 /* JellyfinAPIError.swift in Sources */,
535870A72669D8AE00D05A09 /* MultiSelectorView.swift in Sources */,
E1AD104E26D96CE3003E4A08 /* BaseItemDtoExtensions.swift in Sources */,
E13DD4032717EE79009D4DAF /* UserListCoordinator.swift in Sources */,
@ -1716,6 +1722,7 @@
E1AD106226D9B7CD003E4A08 /* ItemPortraitHeaderOverlayView.swift in Sources */,
53E4E649263F725B00F67C6B /* MultiSelectorView.swift in Sources */,
6220D0C626D62D8700B8E046 /* VideoPlayerCoordinator.swift in Sources */,
E11B1B6C2718CD68006DA3E8 /* JellyfinAPIError.swift in Sources */,
621338B32660A07800A81A2A /* LazyView.swift in Sources */,
6220D0B126D5EC9900B8E046 /* SettingsCoordinator.swift in Sources */,
62C29EA626D1036A00C1D2E7 /* HomeCoordinator.swift in Sources */,
@ -1766,6 +1773,7 @@
628B953C2670D2430091AF3B /* StringExtensions.swift in Sources */,
6267B3DB2671139400A7371D /* ImageExtensions.swift in Sources */,
E1AD105926D9A543003E4A08 /* LazyView.swift in Sources */,
E11B1B6E2718CDBA006DA3E8 /* JellyfinAPIError.swift in Sources */,
628B95372670CB800091AF3B /* JellyfinWidget.swift in Sources */,
E1AD105426D97161003E4A08 /* BaseItemDtoExtensions.swift in Sources */,
E1FCD09A26C4F35A007C8DCF /* ErrorMessage.swift in Sources */,

View File

@ -63,8 +63,8 @@ struct ConnectToServerView: View {
.headerProminence(.increased)
}
.alert(item: $viewModel.errorMessage) { _ in
Alert(title: Text("\(viewModel.errorMessage?.code ?? -1)\n\(viewModel.errorMessage?.title ?? "Error")"),
message: Text(viewModel.errorMessage?.displayMessage ?? "Error"),
Alert(title: Text(viewModel.alertTitle),
message: Text(viewModel.errorMessage?.displayMessage ?? "Unknown Error"),
dismissButton: .cancel())
}
.navigationTitle("Connect")

View File

@ -45,6 +45,11 @@ struct UserSignInView: View {
Text("Sign In to \(viewModel.server.name)")
}
}
.alert(item: $viewModel.errorMessage) { _ in
Alert(title: Text(viewModel.alertTitle),
message: Text(viewModel.errorMessage?.displayMessage ?? "Unknown Error"),
dismissButton: .cancel())
}
.navigationTitle("Sign In")
}
}

View File

@ -16,6 +16,10 @@ struct ErrorMessage: Identifiable {
let title: String
let displayMessage: String
let logConstructor: LogConstructor
// Chosen value such that if an error has this code, don't show the code to the UI
// This was chosen because of its unlikelyhood to ever be used
static let noShowErrorCode = -69420
var id: String {
return "\(code)\(title)\(logConstructor.message)"

View File

@ -0,0 +1,23 @@
//
/*
* 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 2021 Aiden Vigue & Jellyfin Contributors
*/
import Foundation
struct JellyfinAPIError: Error {
private let message: String
init(_ message: String) {
self.message = message
}
var localizedDescription: String {
return message
}
}

View File

@ -75,16 +75,30 @@ final class SessionManager {
JellyfinAPI.basePath = uri
return SystemAPI.getPublicSystemInfo()
.map({ response -> (SwiftfinStore.Models.StoredServer, UnsafeDataTransaction) in
.tryMap({ response -> (SwiftfinStore.Models.StoredServer, UnsafeDataTransaction) in
let transaction = SwiftfinStore.dataStack.beginUnsafe()
let newServer = transaction.create(Into<SwiftfinStore.Models.StoredServer>())
newServer.uri = response.localAddress ?? "SfUri"
newServer.name = response.serverName ?? "SfServerName"
newServer.id = response.id ?? ""
newServer.os = response.operatingSystem ?? "SfOS"
newServer.version = response.version ?? "SfVersion"
guard let uri = response.localAddress,
let name = response.serverName,
let id = response.id,
let os = response.operatingSystem,
let version = response.version else { throw JellyfinAPIError("Missing server data from network call") }
newServer.uri = uri
newServer.name = name
newServer.id = id
newServer.os = os
newServer.version = version
newServer.users = []
// Check for existing server on device
if let existingServer = try? SwiftfinStore.dataStack.fetchOne(From<SwiftfinStore.Models.StoredServer>(),
[Where<SwiftfinStore.Models.StoredServer>("id == %@", newServer.id)]) {
throw SwiftfinStore.Errors.existingServer(existingServer.state)
}
return (newServer, transaction)
})
.handleEvents(receiveOutput: { (_, transaction) in
@ -100,17 +114,29 @@ final class SessionManager {
func loginUser(server: SwiftfinStore.State.Server, username: String, password: String) -> AnyPublisher<SwiftfinStore.State.User, Error> {
setAuthHeader(with: "")
JellyfinAPI.basePath = server.uri
return UserAPI.authenticateUserByName(authenticateUserByName: AuthenticateUserByName(username: username, pw: password))
.map({ response -> (SwiftfinStore.Models.StoredServer, SwiftfinStore.Models.StoredUser, UnsafeDataTransaction) in
.tryMap({ response -> (SwiftfinStore.Models.StoredServer, SwiftfinStore.Models.StoredUser, UnsafeDataTransaction) in
guard let accessToken = response.accessToken else { fatalError("Received successful user with no access token") }
guard let accessToken = response.accessToken else { throw JellyfinAPIError("Access token missing from network call") }
let transaction = SwiftfinStore.dataStack.beginUnsafe()
let newUser = transaction.create(Into<SwiftfinStore.Models.StoredUser>())
newUser.username = response.user?.name ?? "SfUsername"
newUser.id = response.user?.id ?? "SfID"
guard let username = response.user?.name,
let id = response.user?.id else { throw JellyfinAPIError("Missing user data from network call") }
newUser.username = username
newUser.id = id
newUser.appleTVID = ""
// Check for existing user on device
if let existingUser = try? SwiftfinStore.dataStack.fetchOne(From<SwiftfinStore.Models.StoredUser>(),
[Where<SwiftfinStore.Models.StoredUser>("id == %@", newUser.id)]) {
throw SwiftfinStore.Errors.existingUser(existingUser.state)
}
let newAccessToken = transaction.create(Into<SwiftfinStore.Models.StoredAccessToken>())
newAccessToken.value = accessToken
newUser.accessToken = newAccessToken
@ -156,6 +182,14 @@ final class SessionManager {
SwiftfinNotificationCenter.main.post(name: SwiftfinNotificationCenter.Keys.didSignOut, object: nil)
}
func delete(user: SwiftfinStore.State.User) {
}
func delete(server: SwiftfinStore.State.Server) {
}
private func setAuthHeader(with accessToken: String) {
let appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
var deviceName = UIDevice.current.name

View File

@ -119,6 +119,13 @@ enum SwiftfinStore {
}
}
// MARK: Errors
enum Errors {
case existingServer(State.Server)
case existingUser(State.User)
}
// MARK: dataStack
static let dataStack: DataStack = {
let schema = CoreStoreSchema(modelVersion: "V1",
entities: [
@ -138,3 +145,24 @@ enum SwiftfinStore {
return _dataStack
}()
}
extension SwiftfinStore.Errors: LocalizedError {
var title: String {
switch self {
case .existingServer(_):
return "Existing Server"
case .existingUser(_):
return "Existing User"
}
}
var errorDescription: String? {
switch self {
case .existingServer(let server):
return "Server \(server.name) already exists with same server ID"
case .existingUser(let user):
return "User \(user.username) already exists with same user ID"
}
}
}

View File

@ -18,6 +18,15 @@ final class ConnectToServerViewModel: ViewModel {
@Published var discoveredServers: Set<ServerDiscovery.ServerLookupResponse> = []
@Published var searching = false
private let discovery = ServerDiscovery()
var alertTitle: String {
var message: String = ""
if errorMessage?.code != ErrorMessage.noShowErrorCode {
message.append(contentsOf: "\(errorMessage?.code ?? ErrorMessage.noShowErrorCode)\n")
}
message.append(contentsOf: "\(errorMessage?.title ?? "Unkown Error")")
return message
}
func connectToServer(uri: String) {
#if targetEnvironment(simulator)

View File

@ -20,9 +20,18 @@ final class UserSignInViewModel: ViewModel {
self.server = server
}
var alertTitle: String {
var message: String = ""
if errorMessage?.code != ErrorMessage.noShowErrorCode {
message.append(contentsOf: "\(errorMessage?.code ?? ErrorMessage.noShowErrorCode)\n")
}
message.append(contentsOf: "\(errorMessage?.title ?? "Unkown Error")")
return message
}
func login(username: String, password: String) {
LogManager.shared.log.debug("Attempting to login to server at \"\(server.uri)\"", tag: "login")
LogManager.shared.log.debug("username == \"\": \(username), password == \"\": \(password)", tag: "login")
LogManager.shared.log.debug("username: \(username), password: \(password)", tag: "login")
SessionManager.main.loginUser(server: server, username: username, password: password)
.trackActivity(loading)

View File

@ -29,11 +29,12 @@ class ViewModel: ObservableObject {
case .finished:
break
case .failure(let error):
if let errorResponse = error as? ErrorResponse {
let logConstructor = LogConstructor(message: "__NOTHING__", tag: tag, level: logLevel, function: function, file: file, line: line)
switch error {
case is ErrorResponse:
let networkError: NetworkError
let logConstructor = LogConstructor(message: "__NOTHING__", tag: tag, level: logLevel, function: function, file: file, line: line)
let errorResponse = error as! ErrorResponse
switch errorResponse {
case .error(-1, _, _, _):
networkError = .URLError(response: errorResponse, displayMessage: displayMessage, logConstructor: logConstructor)
@ -51,7 +52,55 @@ class ViewModel: ObservableObject {
self.errorMessage = networkError.errorMessage
networkError.logMessage()
case is SwiftfinStore.Errors:
let swiftfinError = error as! SwiftfinStore.Errors
let errorMessage = ErrorMessage(code: ErrorMessage.noShowErrorCode,
title: swiftfinError.title,
displayMessage: swiftfinError.errorDescription ?? "",
logConstructor: logConstructor)
self.errorMessage = errorMessage
LogManager.shared.log.error("Request failed: \(swiftfinError.errorDescription ?? "")")
default:
let genericErrorMessage = ErrorMessage(code: ErrorMessage.noShowErrorCode,
title: "Generic Error",
displayMessage: error.localizedDescription,
logConstructor: logConstructor)
self.errorMessage = genericErrorMessage
LogManager.shared.log.error("Request failed: Generic error - \(error.localizedDescription)")
}
// if let errorResponse = error as? ErrorResponse {
//
// let networkError: NetworkError
//
// switch errorResponse {
// case .error(-1, _, _, _):
// networkError = .URLError(response: errorResponse, displayMessage: displayMessage, logConstructor: logConstructor)
// // Use the errorResponse description for debugging, rather than the user-facing friendly description which may not be implemented
// LogManager.shared.log.error("Request failed: URL request failed with error \(networkError.errorMessage.code): \(errorResponse.localizedDescription)")
// case .error(-2, _, _, _):
// networkError = .HTTPURLError(response: errorResponse, displayMessage: displayMessage, logConstructor: logConstructor)
// LogManager.shared.log.error("Request failed: HTTP URL request failed with description: \(errorResponse.localizedDescription)")
// default:
// networkError = .JellyfinError(response: errorResponse, displayMessage: displayMessage, logConstructor: logConstructor)
// // Able to use user-facing friendly description here since just HTTP status codes
// LogManager.shared.log.error("Request failed: \(networkError.errorMessage.code) - \(networkError.errorMessage.title): \(networkError.errorMessage.logConstructor.message)\n\(error.localizedDescription)")
// }
//
// self.errorMessage = networkError.errorMessage
//
// networkError.logMessage()
// } else {
// let generalErrorMessage = ErrorMessage(code: 0,
// title: "Error",
// displayMessage: error.localizedDescription,
// logConstructor: logConstructor)
//
// self.errorMessage = generalErrorMessage
// LogManager.shared.log.error("Request failed: General error - \(error.localizedDescription)")
// }
}
}
}