mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-14 09:13:25 +01:00
[Anisette-Servers]: cleanup and enhanced error handling for anisette-servers-list
This commit is contained in:
@@ -34,9 +34,13 @@ final class AuthenticationViewController: UIViewController
|
|||||||
// fetch anisette servers asap when loading Auth Screen (if list is empty
|
// fetch anisette servers asap when loading Auth Screen (if list is empty
|
||||||
if(UserDefaults.standard.menuAnisetteServersList.isEmpty){
|
if(UserDefaults.standard.menuAnisetteServersList.isEmpty){
|
||||||
Task{
|
Task{
|
||||||
await AnisetteViewModel.getListOfServers(
|
let sourceURL = UserDefaults.standard.menuAnisetteList
|
||||||
serverSource: UserDefaults.standard.menuAnisetteList
|
do{
|
||||||
)
|
_ = try await AnisetteViewModel.getListOfServers(serverSource: sourceURL)
|
||||||
|
print("AuthenticationViewController: Server list refresh request completed for sourceURL: \(sourceURL)")
|
||||||
|
}catch{
|
||||||
|
print("AuthenticationViewController: Server list refresh request Failed for sourceURL: \(sourceURL) Error: \(error)")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,65 +36,79 @@ class AnisetteViewModel: ObservableObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDefaultListOfServers() {
|
@MainActor
|
||||||
// initiate fetch but do not wait in blocking manner
|
func getCurrentListOfServers(_ completionHandler: @escaping (Result<Void, Error>) -> Void = {_ in }) {
|
||||||
Task{
|
// dispatch fetch operation but don't do a blocking wait for results
|
||||||
let anisetteServers = await AnisetteViewModel.getListOfServers(serverSource: self.source)
|
Task {
|
||||||
|
do {
|
||||||
// always update on main thread for Observables
|
let anisetteServers = try await AnisetteViewModel.getListOfServers(serverSource: self.source)
|
||||||
DispatchQueue.main.async {
|
// Update UI-related state on the main thread
|
||||||
self.servers = anisetteServers
|
self.servers = anisetteServers
|
||||||
|
print("AnisetteViewModel: Server list refresh request completed for sourceURL: \(self.source)")
|
||||||
|
completionHandler(.success(()))
|
||||||
|
} catch {
|
||||||
|
print("AnisetteViewModel: Server list refresh request Failed for sourceURL: \(self.source) Error: \(error)")
|
||||||
|
completionHandler(.failure(error))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static func getListOfServers(serverSource: String) async -> [Server] {
|
static func getListOfServers(serverSource: String) async throws -> [Server] {
|
||||||
var aniServers: [Server] = []
|
var aniServers: [Server] = []
|
||||||
|
|
||||||
guard let url = URL(string: serverSource) else {
|
guard let url = URL(string: serverSource) else {
|
||||||
return aniServers
|
return aniServers
|
||||||
}
|
}
|
||||||
|
|
||||||
// DO NOT use local cache when fetching anisette servers
|
// DO NOT use local cache when fetching anisette servers
|
||||||
var request = URLRequest(url: url)
|
var request = URLRequest(url: url)
|
||||||
request.cachePolicy = .reloadIgnoringLocalCacheData
|
request.cachePolicy = .reloadIgnoringLocalCacheData
|
||||||
|
|
||||||
return await withCheckedContinuation{(continuation: CheckedContinuation<[Server], Never>) in
|
|
||||||
// perform async operation with callback
|
|
||||||
URLSession.shared.dataTask(with: request) { data, response, error in
|
|
||||||
if error == nil, let data = data {
|
|
||||||
do {
|
|
||||||
let decoder = Foundation.JSONDecoder()
|
|
||||||
let servers = try decoder.decode(AnisetteServerData.self, from: data)
|
|
||||||
aniServers.append(contentsOf: servers.servers)
|
|
||||||
// store server addresses as list
|
|
||||||
UserDefaults.standard.menuAnisetteServersList = aniServers.map(\.self.address)
|
|
||||||
} catch {
|
|
||||||
// Handle decoding error
|
|
||||||
print("Failed to decode JSON: \(error)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//unblock the continuation
|
do {
|
||||||
continuation.resume(returning: aniServers)
|
// Use async/await pattern here, avoiding CheckedContinuation directly
|
||||||
}.resume()
|
let (data, response) = try await URLSession.shared.data(for: request)
|
||||||
|
|
||||||
|
// Check if the response is valid and has a 2xx HTTP status code
|
||||||
|
guard let httpResponse = response as? HTTPURLResponse, (200...299).contains(httpResponse.statusCode) else {
|
||||||
|
// Handle non-2xx status codes
|
||||||
|
let statusCode = (response as? HTTPURLResponse)?.statusCode ?? -1
|
||||||
|
throw NSError(domain: "AnisetteViewModel: ServerError", code: statusCode, userInfo: [NSLocalizedDescriptionKey: "Request failed with status code: \(statusCode)"])
|
||||||
|
}
|
||||||
|
|
||||||
|
let decoder = Foundation.JSONDecoder()
|
||||||
|
let servers = try decoder.decode(AnisetteServerData.self, from: data)
|
||||||
|
print("AnisetteViewModel: JSON Decode successful for sourceURL: \(serverSource) servers: \(servers)")
|
||||||
|
aniServers.append(contentsOf: servers.servers)
|
||||||
|
// Store server addresses as list
|
||||||
|
UserDefaults.standard.menuAnisetteServersList = aniServers.map(\.address)
|
||||||
|
return aniServers
|
||||||
|
} catch {
|
||||||
|
if let urlError = error as? URLError {
|
||||||
|
print("AnisetteViewModel: URL Error: \(urlError.localizedDescription)")
|
||||||
|
} else if let decodingError = error as? DecodingError {
|
||||||
|
print("AnisetteViewModel: Failed to decode JSON: \(decodingError.localizedDescription)")
|
||||||
|
} else {
|
||||||
|
print("AnisetteViewModel: An unexpected error occurred: \(error.localizedDescription)")
|
||||||
|
}
|
||||||
|
throw error // Propagate the error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct AnisetteServers: View {
|
struct AnisetteServersView: View {
|
||||||
@Environment(\.presentationMode) var presentationMode
|
@Environment(\.presentationMode) var presentationMode
|
||||||
@StateObject var viewModel: AnisetteViewModel = AnisetteViewModel()
|
@StateObject var viewModel: AnisetteViewModel = AnisetteViewModel()
|
||||||
@State var selected: String? = nil
|
@State var selected: String? = nil
|
||||||
@State private var showingConfirmation = false
|
@State private var showingConfirmation = false
|
||||||
var errorCallback: () -> ()
|
var errorCallback: () -> ()
|
||||||
|
var refreshCallback: (Result<Void, any Error>) -> Void
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
Color(UIColor.systemBackground)
|
Color(UIColor.systemBackground)
|
||||||
.ignoresSafeArea()
|
.ignoresSafeArea()
|
||||||
.onAppear {
|
.onAppear {
|
||||||
viewModel.getDefaultListOfServers()
|
viewModel.getCurrentListOfServers(refreshCallback)
|
||||||
}
|
}
|
||||||
VStack {
|
VStack {
|
||||||
if #available(iOS 16.0, *) {
|
if #available(iOS 16.0, *) {
|
||||||
@@ -161,7 +175,8 @@ struct AnisetteServers: View {
|
|||||||
.shadow(color: Color.black.opacity(0.2), radius: 5, x: 0, y: 5)
|
.shadow(color: Color.black.opacity(0.2), radius: 5, x: 0, y: 5)
|
||||||
.onChange(of: viewModel.source) { newValue in
|
.onChange(of: viewModel.source) { newValue in
|
||||||
UserDefaults.standard.menuAnisetteList = newValue
|
UserDefaults.standard.menuAnisetteList = newValue
|
||||||
viewModel.getDefaultListOfServers()
|
// viewModel.getCurrentListOfServers(refreshCallback) // don't spam
|
||||||
|
viewModel.getCurrentListOfServers()
|
||||||
}
|
}
|
||||||
|
|
||||||
HStack(spacing: 16) {
|
HStack(spacing: 16) {
|
||||||
@@ -183,7 +198,7 @@ struct AnisetteServers: View {
|
|||||||
.shadow(color: Color.accentColor.opacity(0.4), radius: 10, x: 0, y: 5)
|
.shadow(color: Color.accentColor.opacity(0.4), radius: 10, x: 0, y: 5)
|
||||||
|
|
||||||
SUIButton(action: {
|
SUIButton(action: {
|
||||||
viewModel.getDefaultListOfServers()
|
viewModel.getCurrentListOfServers(refreshCallback)
|
||||||
}) {
|
}) {
|
||||||
HStack{
|
HStack{
|
||||||
Spacer()
|
Spacer()
|
||||||
|
|||||||
@@ -209,14 +209,19 @@ final class SettingsViewController: UITableViewController
|
|||||||
{
|
{
|
||||||
super.viewWillAppear(animated)
|
super.viewWillAppear(animated)
|
||||||
|
|
||||||
|
// show nav bar if not shown already
|
||||||
|
self.navigationController?.setNavigationBarHidden(false, animated: animated)
|
||||||
|
|
||||||
self.update()
|
self.update()
|
||||||
}
|
}
|
||||||
|
|
||||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
||||||
if segue.identifier == "anisetteServers" {
|
if segue.identifier == "anisetteServers" {
|
||||||
let controller = UIHostingController(rootView: AnisetteServers(selected: UserDefaults.standard.menuAnisetteURL, errorCallback: {
|
let controller = segue.destination
|
||||||
ToastView(text: "Cleared adi.pb!", detailText: "You will need to log back into Apple ID in SideStore.").show(in: self)
|
|
||||||
}))
|
// disable bottom tab bar since 'back' button is already available
|
||||||
|
// controller.hidesBottomBarWhenPushed = true
|
||||||
|
|
||||||
self.show(controller, sender: nil)
|
self.show(controller, sender: nil)
|
||||||
} else {
|
} else {
|
||||||
super.prepare(for: segue, sender: sender)
|
super.prepare(for: segue, sender: sender)
|
||||||
@@ -944,10 +949,39 @@ extension SettingsViewController
|
|||||||
self.tableView.deselectRow(at: indexPath, animated: true)
|
self.tableView.deselectRow(at: indexPath, animated: true)
|
||||||
|
|
||||||
case .anisetteServers:
|
case .anisetteServers:
|
||||||
self.prepare(for: UIStoryboardSegue(identifier: "anisetteServers", source: self, destination: UIHostingController(rootView: AnisetteServers(selected: "", errorCallback: {
|
|
||||||
ToastView(text: "Reset adi.pb", detailText: "Buh").show(in: self)
|
func handleRefreshResult(_ result: Result<Void, any Error>) {
|
||||||
}))), sender: nil)
|
var message = "Servers list refreshed"
|
||||||
// self.performSegue(withIdentifier: "anisetteServers", sender: nil)
|
var details: String? = nil
|
||||||
|
var duration: TimeInterval = 1.0
|
||||||
|
|
||||||
|
switch result {
|
||||||
|
case .success:
|
||||||
|
// No additional action needed, default message is sufficient
|
||||||
|
break
|
||||||
|
case .failure(let error):
|
||||||
|
message = "Failed to refresh servers list"
|
||||||
|
details = String(describing: error)
|
||||||
|
duration = 4.0
|
||||||
|
}
|
||||||
|
|
||||||
|
let toast = ToastView(text: message, detailText: details)
|
||||||
|
toast.preferredDuration = duration
|
||||||
|
toast.show(in: self)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Instantiate SwiftUI View inside UIHostingController
|
||||||
|
let anisetteServersView = AnisetteServersView(selected: UserDefaults.standard.menuAnisetteURL, errorCallback: {
|
||||||
|
ToastView(text: "Cleared adi.pb!", detailText: "You will need to log back into Apple ID in SideStore.")
|
||||||
|
.show(in: self)
|
||||||
|
}, refreshCallback: {result in
|
||||||
|
handleRefreshResult(result)
|
||||||
|
})
|
||||||
|
|
||||||
|
let anisetteServersController = UIHostingController(rootView: anisetteServersView)
|
||||||
|
|
||||||
|
self.prepare(for: UIStoryboardSegue(identifier: "anisetteServers", source: self, destination: anisetteServersController), sender: nil)
|
||||||
|
|
||||||
case .advancedSettings:
|
case .advancedSettings:
|
||||||
// Create the URL that deep links to your app's custom settings.
|
// Create the URL that deep links to your app's custom settings.
|
||||||
if let url = URL(string: UIApplication.openSettingsURLString) {
|
if let url = URL(string: UIApplication.openSettingsURLString) {
|
||||||
|
|||||||
Reference in New Issue
Block a user