diff --git a/Sources/DBConnect/ICEDataController.swift b/Sources/DBConnect/ICEDataController.swift index c9fb94d..43f5fbe 100644 --- a/Sources/DBConnect/ICEDataController.swift +++ b/Sources/DBConnect/ICEDataController.swift @@ -22,7 +22,8 @@ public final class ICEDataController: NSObject, TrainDataController { if demoMode { return MoyaProvider(stubClosure: MoyaProvider.immediatelyStub) } else { - return MoyaProvider(stubClosure: MoyaProvider.neverStub) + return MoyaProvider(stubClosure: MoyaProvider.neverStub, + session: alamofireSessionWithFasterTimeout) } } @@ -33,43 +34,10 @@ public final class ICEDataController: NSObject, TrainDataController { } public func loadTripData(demoMode: Bool = false, completionHandler: @escaping (TripResponse?, Error?) -> ()){ + let decoder = JSONDecoder() + decoder.dateDecodingStrategy = .formatted(DateFormatter.yyyyMMdd) let provider = getProvider(demoMode: demoMode) - provider.request(.trip) { result in - switch result { - case .success(let response): - do { - let response = try response.filterSuccessfulStatusCodes() - let decoder = JSONDecoder() - decoder.dateDecodingStrategy = .formatted(DateFormatter.yyyyMMdd) - let trip = try decoder.decode(TripResponse.self, from: response.data) - completionHandler(trip, nil) - } catch DecodingError.dataCorrupted(let context) { - if response.data.count == 0 { - // iceportal.de outside WiFi returns 200 with an empty body. - completionHandler(nil, TrainConnectionError.notConnected) - break - } - print(context) - } catch DecodingError.keyNotFound(let key, let context) { - print("Key '\(key)' not found:", context.debugDescription) - print("codingPath:", context.codingPath) - } catch DecodingError.valueNotFound(let value, let context) { - print("Value '\(value)' not found:", context.debugDescription) - print("codingPath:", context.codingPath) - } catch DecodingError.typeMismatch(let type, let context) { - print("Type '\(type)' mismatch:", context.debugDescription) - print("codingPath:", context.codingPath) - } catch { - print(error.localizedDescription) - completionHandler(nil, error) - } - break - case .failure(let error): - print(error.localizedDescription) - completionHandler(nil, error) - break - } - } + provider.loadJson(decoder: decoder, target: .trip, completionHandler: completionHandler) } @@ -80,42 +48,7 @@ public final class ICEDataController: NSObject, TrainDataController { } public func loadStatus(demoMode: Bool = false, completionHandler: @escaping (Status?, Error?) -> ()) { - let provider = getProvider(demoMode: demoMode) - provider.request(.status) { result in - switch result { - case .success(let response): - do { - let response = try response.filterSuccessfulStatusCodes() - let decoder = JSONDecoder() - let status = try decoder.decode(Status.self, from: response.data) - completionHandler(status, nil) - } catch DecodingError.dataCorrupted(let context) { - if response.data.count == 0 { - // iceportal.de outside WiFi returns 200 with an empty body. - completionHandler(nil, TrainConnectionError.notConnected) - break - } - print(context) - } catch DecodingError.keyNotFound(let key, let context) { - print("Key '\(key)' not found:", context.debugDescription) - print("codingPath:", context.codingPath) - } catch DecodingError.valueNotFound(let value, let context) { - print("Value '\(value)' not found:", context.debugDescription) - print("codingPath:", context.codingPath) - } catch DecodingError.typeMismatch(let type, let context) { - print("Type '\(type)' mismatch:", context.debugDescription) - print("codingPath:", context.codingPath) - } catch { - print(error.localizedDescription) - completionHandler(nil, error) - } - break - case .failure(let error): - print(error.localizedDescription) - completionHandler(nil, error) - break - } - } + getProvider(demoMode: demoMode).loadJson(target: .status, completionHandler: completionHandler) } } diff --git a/Sources/SNCFConnect/TGVDataController.swift b/Sources/SNCFConnect/TGVDataController.swift index 3723fc8..fa6aab9 100644 --- a/Sources/SNCFConnect/TGVDataController.swift +++ b/Sources/SNCFConnect/TGVDataController.swift @@ -33,7 +33,8 @@ public class TGVDataController: NSObject, TrainDataController { if demoMode { return MoyaProvider(stubClosure: MoyaProvider.immediatelyStub) } else { - return MoyaProvider(stubClosure: MoyaProvider.neverStub) + return MoyaProvider(stubClosure: MoyaProvider.neverStub, + session: alamofireSessionWithFasterTimeout) } } @@ -43,40 +44,10 @@ public class TGVDataController: NSObject, TrainDataController { }) } private func loadDetails(demoMode: Bool, completionHandler: @escaping (DetailsResponse?, Error?) -> ()){ - + let decoder = JSONDecoder() + decoder.dateDecodingStrategy = .formatted(DateFormatter.tgvFormatter) let provider = getProvider(demoMode: demoMode) - provider.request(.details) { result in - switch result { - case .success(let response): - do { - let response = try response.filterSuccessfulStatusCodes() - let decoder = JSONDecoder() - print(DateFormatter.tgvFormatter.string(from: .init())) - decoder.dateDecodingStrategy = .formatted(DateFormatter.tgvFormatter) - let trip = try decoder.decode(DetailsResponse.self, from: response.data) - completionHandler(trip, nil) - } catch DecodingError.dataCorrupted(let context) { - print(context) - } catch DecodingError.keyNotFound(let key, let context) { - print("Key '\(key)' not found:", context.debugDescription) - print("codingPath:", context.codingPath) - } catch DecodingError.valueNotFound(let value, let context) { - print("Value '\(value)' not found:", context.debugDescription) - print("codingPath:", context.codingPath) - } catch DecodingError.typeMismatch(let type, let context) { - print("Type '\(type)' mismatch:", context.debugDescription) - print("codingPath:", context.codingPath) - } catch { - print(error.localizedDescription) - completionHandler(nil, error) - } - break - case .failure(let error): - print(error.localizedDescription) - completionHandler(nil, error) - break - } - } + provider.loadJson(decoder: decoder, target: .details, completionHandler: completionHandler) } public func loadTrainStatus(demoMode: Bool = false, completionHandler: @escaping (TrainStatus?, Error?) -> ()) { @@ -102,71 +73,11 @@ public class TGVDataController: NSObject, TrainDataController { } private func loadGPS(demoMode: Bool = false, completionHandler: @escaping (GPSResponse?, Error?) -> ()) { - let provider = getProvider(demoMode: demoMode) - provider.request(.gps) { result in - switch result { - case .success(let response): - do { - let response = try response.filterSuccessfulStatusCodes() - let decoder = JSONDecoder() - let status = try decoder.decode(GPSResponse.self, from: response.data) - completionHandler(status, nil) - } catch DecodingError.dataCorrupted(let context) { - print(context) - } catch DecodingError.keyNotFound(let key, let context) { - print("Key '\(key)' not found:", context.debugDescription) - print("codingPath:", context.codingPath) - } catch DecodingError.valueNotFound(let value, let context) { - print("Value '\(value)' not found:", context.debugDescription) - print("codingPath:", context.codingPath) - } catch DecodingError.typeMismatch(let type, let context) { - print("Type '\(type)' mismatch:", context.debugDescription) - print("codingPath:", context.codingPath) - } catch { - print(error.localizedDescription) - completionHandler(nil, error) - } - break - case .failure(let error): - print(error.localizedDescription) - completionHandler(nil, error) - break - } - } + getProvider(demoMode: demoMode).loadJson(target: .gps, completionHandler: completionHandler) } private func loadStatistics(demoMode: Bool = false, completionHandler: @escaping (StatisticsResponse?, Error?) -> ()) { - let provider = getProvider(demoMode: demoMode) - provider.request(.statistics) { result in - switch result { - case .success(let response): - do { - let response = try response.filterSuccessfulStatusCodes() - let decoder = JSONDecoder() - let status = try decoder.decode(StatisticsResponse.self, from: response.data) - completionHandler(status, nil) - } catch DecodingError.dataCorrupted(let context) { - print(context) - } catch DecodingError.keyNotFound(let key, let context) { - print("Key '\(key)' not found:", context.debugDescription) - print("codingPath:", context.codingPath) - } catch DecodingError.valueNotFound(let value, let context) { - print("Value '\(value)' not found:", context.debugDescription) - print("codingPath:", context.codingPath) - } catch DecodingError.typeMismatch(let type, let context) { - print("Type '\(type)' mismatch:", context.debugDescription) - print("codingPath:", context.codingPath) - } catch { - print(error.localizedDescription) - completionHandler(nil, error) - } - break - case .failure(let error): - print(error.localizedDescription) - completionHandler(nil, error) - break - } - } + getProvider(demoMode: demoMode).loadJson(target: .statistics, completionHandler: completionHandler) } } @@ -184,4 +95,3 @@ public class TGVDataController: NSObject, TrainDataController { // return stop // } //} - diff --git a/Sources/TrainConnect/TrainDataCommon.swift b/Sources/TrainConnect/TrainDataCommon.swift new file mode 100644 index 0000000..b79ae0c --- /dev/null +++ b/Sources/TrainConnect/TrainDataCommon.swift @@ -0,0 +1,60 @@ +import Foundation +import Moya +import Alamofire + +public extension MoyaProvider { + func loadJson(decoder: JSONDecoder = .init(), + target: Target, + completionHandler: @escaping (D?, Error?) -> ()) { + request(target) { result in + switch result { + case .success(let response): + do { + let response = try response.filterSuccessfulStatusCodes() + if response.data.isEmpty { + // iceportal.de outside WiFi returns 200 with an empty body. + completionHandler(nil, TrainConnectionError.notConnected) + } + else { + let result = try decoder.decode(D.self, from: response.data) + completionHandler(result, nil) + } + } catch let error { + logDecodingError(error: error) + completionHandler(nil, error) + } + break + case .failure(let error): + print(error.localizedDescription) + completionHandler(nil, error) + break + } + } + } +} + +private func logDecodingError(error: Error) { + switch error { + case DecodingError.dataCorrupted(let context): + print(context) + case DecodingError.keyNotFound(let key, let context): + print("Key '\(key)' not found:", context.debugDescription) + print("codingPath:", context.codingPath) + case DecodingError.valueNotFound(let value, let context): + print("Value '\(value)' not found:", context.debugDescription) + print("codingPath:", context.codingPath) + case DecodingError.typeMismatch(let type, let context): + print("Type '\(type)' mismatch:", context.debugDescription) + print("codingPath:", context.codingPath) + default: + print(error.localizedDescription) + } +} + +public let alamofireSessionWithFasterTimeout: Alamofire.Session = { + let configuration = URLSessionConfiguration.default + // Use a very short timeout so we don't wait a minute before showing the "not connected" UI + configuration.timeoutIntervalForRequest = 2 + configuration.timeoutIntervalForResource = 2 + return Alamofire.Session(configuration: configuration) +}() diff --git a/Sources/TrainConnect/TrainDataController.swift b/Sources/TrainConnect/TrainDataController.swift index db47e82..75d44b6 100644 --- a/Sources/TrainConnect/TrainDataController.swift +++ b/Sources/TrainConnect/TrainDataController.swift @@ -22,36 +22,31 @@ public class CombinedDataController: TrainDataController { } public func loadTrip(demoMode: Bool, completionHandler: @escaping (TrainTrip?, Error?) -> ()) { - var completed: Bool = false - var failed: Int = 0 - for controller in controllers { - controller.loadTrip(demoMode: demoMode) { - if let error = $1 { - failed += 1 - if failed >= self.controllers.count { - completionHandler(nil, error) - } - } else if !completed, let trip = $0 { - completed = true - completionHandler(trip, nil) - } - } + delegate(completionHandler: completionHandler) { + $0.loadTrip(demoMode: demoMode, completionHandler: $1) } } public func loadTrainStatus(demoMode: Bool, completionHandler: @escaping (TrainStatus?, Error?) -> ()) { + delegate(completionHandler: completionHandler) { + $0.loadTrainStatus(demoMode: demoMode, completionHandler: $1) + } + } + + private func delegate(completionHandler: @escaping (D?, Error?) -> (), + action: (TrainDataController, @escaping (D?, Error?) -> ()) -> ()) { var completed: Bool = false var failed: Int = 0 for controller in controllers { - controller.loadTrainStatus(demoMode: demoMode) { - if let error = $1 { + action(controller) { result, error in + if let error = error { failed += 1 if failed >= self.controllers.count { completionHandler(nil, error) } - } else if !completed, let status = $0 { + } else if !completed, let result = result { completed = true - completionHandler(status, nil) + completionHandler(result, nil) } } }