diff --git a/Application/Resources/Apps/Play RSI/ApplicationConfiguration.json b/Application/Resources/Apps/Play RSI/ApplicationConfiguration.json index 0fdfc645b..de137b026 100755 --- a/Application/Resources/Apps/Play RSI/ApplicationConfiguration.json +++ b/Application/Resources/Apps/Play RSI/ApplicationConfiguration.json @@ -5,7 +5,6 @@ "tvSiteName": "rsi-player-tvos-apple", "voiceOverLanguageCode": "it", "appStoreProductIdentifier": 920753497, - "serviceURL": "https://il.srf.ch", "playURLs": "{\"rsi\":\"https://www.rsi.ch/play/\",\"rtr\":\"https://www.rtr.ch/play/\",\"rts\":\"https://www.rts.ch/play/\",\"srf\":\"https://www.srf.ch/play/\",\"swi\":\"https://play.swissinfo.ch/play/\"}", "playServiceURL": "https://www.rsi.ch/play/", "middlewareURL": "https://playfff.herokuapp.com", diff --git a/Application/Resources/Apps/Play RTR/ApplicationConfiguration.json b/Application/Resources/Apps/Play RTR/ApplicationConfiguration.json index 84863b498..5637b118e 100755 --- a/Application/Resources/Apps/Play RTR/ApplicationConfiguration.json +++ b/Application/Resources/Apps/Play RTR/ApplicationConfiguration.json @@ -4,7 +4,6 @@ "siteName": "rtr-player-ios-v", "tvSiteName": "rtr-player-tvos-apple", "appStoreProductIdentifier": 920754925, - "serviceURL": "https://il.srf.ch", "playURLs": "{\"rsi\":\"https://www.rsi.ch/play/\",\"rtr\":\"https://www.rtr.ch/play/\",\"rts\":\"https://www.rts.ch/play/\",\"srf\":\"https://www.srf.ch/play/\",\"swi\":\"https://play.swissinfo.ch/play/\"}", "playServiceURL": "https://www.rtr.ch/play/", "middlewareURL": "https://playfff.herokuapp.com", diff --git a/Application/Resources/Apps/Play RTS/ApplicationConfiguration.json b/Application/Resources/Apps/Play RTS/ApplicationConfiguration.json index 7c3964337..e9f68bb0f 100755 --- a/Application/Resources/Apps/Play RTS/ApplicationConfiguration.json +++ b/Application/Resources/Apps/Play RTS/ApplicationConfiguration.json @@ -5,7 +5,6 @@ "tvSiteName": "rts-player-tvos-apple", "voiceOverLanguageCode": "fr", "appStoreProductIdentifier": 920754415, - "serviceURL": "https://il.srf.ch", "playURLs": "{\"rsi\":\"https://www.rsi.ch/play/\",\"rtr\":\"https://www.rtr.ch/play/\",\"rts\":\"https://www.rts.ch/play/\",\"srf\":\"https://www.srf.ch/play/\",\"swi\":\"https://play.swissinfo.ch/play/\"}", "playServiceURL": "https://www.rts.ch/play/", "middlewareURL": "https://playfff.herokuapp.com", diff --git a/Application/Resources/Apps/Play SRF/ApplicationConfiguration.json b/Application/Resources/Apps/Play SRF/ApplicationConfiguration.json index 59be87314..099b90644 100755 --- a/Application/Resources/Apps/Play SRF/ApplicationConfiguration.json +++ b/Application/Resources/Apps/Play SRF/ApplicationConfiguration.json @@ -5,7 +5,6 @@ "tvSiteName": "srf-player-tvos-apple", "voiceOverLanguageCode": "de", "appStoreProductIdentifier": 638194352, - "serviceURL": "https://il.srf.ch", "playURLs": "{\"rsi\":\"https://www.rsi.ch/play/\",\"rtr\":\"https://www.rtr.ch/play/\",\"rts\":\"https://www.rts.ch/play/\",\"srf\":\"https://www.srf.ch/play/\",\"swi\":\"https://play.swissinfo.ch/play/\"}", "playServiceURL": "https://www.srf.ch/play/", "middlewareURL": "https://playfff.herokuapp.com", diff --git a/Application/Resources/Apps/Play SWI/ApplicationConfiguration.json b/Application/Resources/Apps/Play SWI/ApplicationConfiguration.json index 4338c212a..5612d9e11 100755 --- a/Application/Resources/Apps/Play SWI/ApplicationConfiguration.json +++ b/Application/Resources/Apps/Play SWI/ApplicationConfiguration.json @@ -5,7 +5,6 @@ "tvSiteName": "swi-player-tvos-apple", "voiceOverLanguageCode": "en", "appStoreProductIdentifier": 920785201, - "serviceURL": "https://il.srf.ch", "playURLs": "{\"rsi\":\"https://www.rsi.ch/play/\",\"rtr\":\"https://www.rtr.ch/play/\",\"rts\":\"https://www.rts.ch/play/\",\"srf\":\"https://www.srf.ch/play/\",\"swi\":\"https://play.swissinfo.ch/play/\"}", "playServiceURL": "https://play.swissinfo.ch/play/", "middlewareURL": "https://playfff.herokuapp.com", diff --git a/Application/Sources/Application/SceneDelegate.m b/Application/Sources/Application/SceneDelegate.m index e0590a6c5..45db2115e 100644 --- a/Application/Sources/Application/SceneDelegate.m +++ b/Application/Sources/Application/SceneDelegate.m @@ -110,7 +110,7 @@ - (void)handleDeepLinkAction:(DeepLinkAction *)action if (! [serviceIdentifier isEqual:ApplicationSettingServiceIdentifier()]) { ApplicationSettingSetServiceIdentifier(serviceIdentifier); - NSString *serviceName = [ServiceObjC nameForServiceId:serviceIdentifier]; + NSString *serviceName = [ServiceObjC nameFor:serviceIdentifier]; [Banner showWith:BannerStyleInfo message:[NSString stringWithFormat:NSLocalizedString(@"Server changed to '%@'", @"Notification message when the server URL changed due to a custom URL."), serviceName] image:[UIImage imageNamed:@"settings"] diff --git a/Application/Sources/Configuration/ApplicationConfiguration.h b/Application/Sources/Configuration/ApplicationConfiguration.h index 41460af0d..7f84997c4 100755 --- a/Application/Sources/Configuration/ApplicationConfiguration.h +++ b/Application/Sources/Configuration/ApplicationConfiguration.h @@ -38,7 +38,6 @@ OBJC_EXPORT NSString * const ApplicationConfigurationDidChangeNotification; @property (nonatomic, readonly, copy) NSNumber *appStoreProductIdentifier; -@property (nonatomic, readonly, nullable) NSURL *serviceURL; @property (nonatomic, readonly) NSURL *playServiceURL; @property (nonatomic, readonly) NSURL *middlewareURL; @property (nonatomic, readonly, nullable) NSURL *identityWebserviceURL; @@ -114,6 +113,7 @@ OBJC_EXPORT NSString * const ApplicationConfigurationDidChangeNotification; - (nullable NSURL *)playURLForVendor:(SRGVendor)vendor; +- (nullable NSURL *)serviceURLForId:(NSString *)serviceId; /** * URLs to be used for sharing diff --git a/Application/Sources/Configuration/ApplicationConfiguration.m b/Application/Sources/Configuration/ApplicationConfiguration.m index 376923e79..7230c7b58 100755 --- a/Application/Sources/Configuration/ApplicationConfiguration.m +++ b/Application/Sources/Configuration/ApplicationConfiguration.m @@ -122,7 +122,7 @@ @interface ApplicationConfiguration () @property (nonatomic, copy) NSNumber *appStoreProductIdentifier; -@property (nonatomic) NSURL *serviceURL; +@property (nonatomic) NSDictionary *serviceURLs; @property (nonatomic) NSDictionary *playURLs; @property (nonatomic) NSURL *playServiceURL; @property (nonatomic) NSURL *middlewareURL; @@ -450,8 +450,7 @@ - (BOOL)synchronizeWithFirebaseConfiguration:(PlayFirebaseConfiguration *)fireba self.voiceOverLanguageCode = [firebaseConfiguration stringForKey:@"voiceOverLanguageCode"]; - NSString *serviceURLString = [firebaseConfiguration stringForKey:@"serviceURL"]; - self.serviceURL = serviceURLString ? [NSURL URLWithString:serviceURLString] : nil; + self.serviceURLs = [firebaseConfiguration serviceURLsForKey:@"serviceURLs"]; NSString *identityWebserviceURLString = [firebaseConfiguration stringForKey:@"identityWebserviceURL"]; self.identityWebserviceURL = identityWebserviceURLString ? [NSURL URLWithString:identityWebserviceURLString] : nil; @@ -613,6 +612,11 @@ - (NSURL *)playURLForVendor:(SRGVendor)vendor return playURLs[@(vendor)]; } +- (NSURL *)serviceURLForId:(NSString *)serviceId +{ + return self.serviceURLs[serviceId]; +} + - (NSURL *)sharingURLForMedia:(SRGMedia *)media atTime:(CMTime)time { if (! media || ! [self playURLForVendor:media.vendor]) { diff --git a/Application/Sources/Configuration/PlayFirebaseConfiguration.h b/Application/Sources/Configuration/PlayFirebaseConfiguration.h index 407c000d8..8a5d0f95b 100644 --- a/Application/Sources/Configuration/PlayFirebaseConfiguration.h +++ b/Application/Sources/Configuration/PlayFirebaseConfiguration.h @@ -72,6 +72,12 @@ OBJC_EXPORT NSArray * _Nullable FirebaseConfigurat */ - (NSDictionary *)playURLsForKey:(NSString *)key; +/** + * Service URLs accessor. Return an empty dictionnary if no valid data is found under the specified key. + */ +- (NSDictionary *)serviceURLsForKey:(NSString *)key; + + @end NS_ASSUME_NONNULL_END diff --git a/Application/Sources/Configuration/PlayFirebaseConfiguration.m b/Application/Sources/Configuration/PlayFirebaseConfiguration.m index 4e3b956ea..5889f632b 100644 --- a/Application/Sources/Configuration/PlayFirebaseConfiguration.m +++ b/Application/Sources/Configuration/PlayFirebaseConfiguration.m @@ -7,6 +7,7 @@ #import "PlayFirebaseConfiguration.h" #import "PlayLogger.h" +#import "PlaySRG-Swift.h" @import Firebase; @import SRGAppearance; @@ -367,6 +368,29 @@ - (NSDictionary *)JSONDictionaryForKey:(NSString *)key return playURLs.copy; } +- (NSDictionary *)serviceURLsForKey:(NSString *)key +{ + NSMutableDictionary *serviceURLs = [NSMutableDictionary dictionary]; + + NSDictionary *serviceURLsDictionary = [self JSONDictionaryForKey:key]; + for (NSString *key in serviceURLsDictionary) { + if ([ServiceObjC.environments containsObject:key]) { + NSURL *URL = [NSURL URLWithString:serviceURLsDictionary[key]]; + if (URL) { + serviceURLs[key] = URL; + } + else { + PlayLogWarning(@"configuration", @"Service URL configuration is not valid. The URL of %@ is not valid.", key); + } + } + else { + PlayLogWarning(@"configuration", @"Service URL configuration identifier is not valid. %@ is not valid.", key); + } + } + + return serviceURLs.copy; +} + #pragma mark Update - (void)update diff --git a/Application/Sources/Settings/ApplicationSettings+Common.m b/Application/Sources/Settings/ApplicationSettings+Common.m index a626b478d..50a0f3e3a 100755 --- a/Application/Sources/Settings/ApplicationSettings+Common.m +++ b/Application/Sources/Settings/ApplicationSettings+Common.m @@ -209,7 +209,7 @@ void ApplicationSettingSetServiceIdentifier(NSString *identifier) NSURL *ApplicationSettingServiceURL(void) { - return [ServiceObjC urlForServiceId:ApplicationSettingServiceIdentifier()]; + return [ServiceObjC urlFor:ApplicationSettingServiceIdentifier()]; } BOOL ApplicationSettingAutoplayEnabled(void) diff --git a/Application/Sources/Settings/Service.swift b/Application/Sources/Settings/Service.swift index 7947a1d5f..4aba2fd47 100644 --- a/Application/Sources/Settings/Service.swift +++ b/Application/Sources/Settings/Service.swift @@ -7,70 +7,76 @@ import Foundation import SRGDataProvider -struct Service: Identifiable, Equatable { - let id: String - let name: String - let url: URL +enum Service: String, Identifiable, CaseIterable { + case production + case stage + case test + case mmf = "play mmf" + case samProduction = "sam production" + case samStage = "sam stage" + case samTest = "sam test" - static var production = Self( - id: "production", - name: NSLocalizedString("Production", comment: "Server setting name"), - url: SRGIntegrationLayerProductionServiceURL() - ) - - static var stage = Self( - id: "stage", - name: NSLocalizedString("Stage", comment: "Server setting name"), - url: SRGIntegrationLayerStagingServiceURL() - ) - - static var test = Self( - id: "test", - name: NSLocalizedString("Test", comment: "Server setting name"), - url: SRGIntegrationLayerTestServiceURL() - ) - - static var mmf = Self( - id: "play mmf", - name: "Play MMF", - url: mmfUrl - ) - - private static var mmfUrl: URL = { + private static var mmfUrl: URL { guard let mmfUrlString = Bundle.main.object(forInfoDictionaryKey: "PlayMMFServiceURL") as? String, !mmfUrlString.isEmpty else { return URL(string: "https://play-mmf.herokuapp.com")! } return URL(string: mmfUrlString)! - }() + } - static var samProduction = Self( - id: "sam production", - name: "SAM \(NSLocalizedString("Production", comment: "Server setting name"))", - url: SRGIntegrationLayerProductionServiceURL().appendingPathComponent("sam") - ) + var id: Self { + self + } - static var samStage = Self( - id: "sam stage", - name: "SAM \(NSLocalizedString("Stage", comment: "Server setting name"))", - url: SRGIntegrationLayerStagingServiceURL().appendingPathComponent("sam") - ) + var environment: String { + rawValue + } - static var samTest = Self( - id: "sam test", - name: "SAM \(NSLocalizedString("Test", comment: "Server setting name"))", - url: SRGIntegrationLayerTestServiceURL().appendingPathComponent("sam") - ) + var name: String { + switch self { + case .production: + NSLocalizedString("Production", comment: "Server setting name") + case .stage: + NSLocalizedString("Stage", comment: "Server setting name") + case .test: + NSLocalizedString("Test", comment: "Server setting name") + case .mmf: + "Play MMF" + case .samProduction: + "SAM \(NSLocalizedString("Production", comment: "Server setting name"))" + case .samStage: + "SAM \(NSLocalizedString("Stage", comment: "Server setting name"))" + case .samTest: + "SAM \(NSLocalizedString("Test", comment: "Server setting name"))" + } + } - static var services: [Self] = [production, stage, test, mmf, samProduction, samStage, samTest] + var url: URL { + switch self { + case .production: + SRGIntegrationLayerProductionServiceURL() + case .stage: + SRGIntegrationLayerStagingServiceURL() + case .test: + SRGIntegrationLayerTestServiceURL() + case .mmf: + Self.mmfUrl + case .samProduction: + SRGIntegrationLayerProductionServiceURL().appendingPathComponent("sam") + case .samStage: + SRGIntegrationLayerStagingServiceURL().appendingPathComponent("sam") + case .samTest: + SRGIntegrationLayerTestServiceURL().appendingPathComponent("sam") + } + } - static func service(forId id: String?) -> Self { + static func service(for environment: String?) -> Self { #if DEBUG || NIGHTLY || BETA - guard let id, let server = services.first(where: { $0.id == id }) else { + guard let environment, let service = Self(rawValue: environment) else { return .production } - return server + return service #else return .production #endif @@ -78,11 +84,13 @@ struct Service: Identifiable, Equatable { } @objc class ServiceObjC: NSObject { - @objc static func name(forServiceId serviceId: String) -> String { - Service.service(forId: serviceId).name + @objc static var environments = Service.allCases.map(\.environment) + + @objc static func name(for environment: String) -> String { + Service.service(for: environment).name } - @objc static func url(forServiceId serviceId: String) -> URL { - ApplicationConfiguration().serviceURL ?? Service.service(forId: serviceId).url + @objc static func url(for environment: String) -> URL { + ApplicationConfiguration().serviceURL(forId: environment) ?? Service.service(for: environment).url } } diff --git a/Application/Sources/Settings/SettingsView.swift b/Application/Sources/Settings/SettingsView.swift index 1a5f2e61a..0e0dbba34 100644 --- a/Application/Sources/Settings/SettingsView.swift +++ b/Application/Sources/Settings/SettingsView.swift @@ -565,7 +565,7 @@ struct SettingsView: View { @AppStorage(PlaySRGSettingServiceIdentifier) private var selectedServiceId: String? private var selectedService: Service { - Service.service(forId: selectedServiceId) + Service.service(for: selectedServiceId) } var body: some View { @@ -643,7 +643,7 @@ struct SettingsView: View { private struct ServiceSelectionView: View { var body: some View { List { - ForEach(Service.services) { service in + ForEach(Service.allCases) { service in ServiceCell(service: service) } } @@ -660,7 +660,7 @@ struct SettingsView: View { private struct ServiceCell: View { let service: Service - @AppStorage(PlaySRGSettingServiceIdentifier) var selectedServiceId: String? + @AppStorage(PlaySRGSettingServiceIdentifier) var selectedServiceIdentifier: String? var body: some View { Button(action: select) { @@ -676,15 +676,11 @@ struct SettingsView: View { } private func isSelected() -> Bool { - if let selectedServiceId { - service.id == selectedServiceId - } else { - service == .production - } + service == Service.service(for: selectedServiceIdentifier) } private func select() { - selectedServiceId = service.id + selectedServiceIdentifier = service.rawValue } } diff --git a/Scripts/check-quality.sh b/Scripts/check-quality.sh index db6d49012..b7e50f3fb 100755 --- a/Scripts/check-quality.sh +++ b/Scripts/check-quality.sh @@ -2,12 +2,13 @@ set -e -echo "... checking Swift code..." +echo "... checking Swift code with SwiftLint..." if [ $# -eq 0 ]; then swiftlint --quiet --strict elif [[ "$1" == "only-changes" ]]; then git diff --staged --name-only | grep ".swift$" | xargs -I FILE swiftlint lint --quiet --strict "FILE" fi +echo "... checking Swift code with SwiftFormat..." if [ $# -eq 0 ]; then swiftformat --lint --quiet . elif [[ "$1" == "only-changes" ]]; then diff --git a/docs/REMOTE_CONFIGURATION.md b/docs/REMOTE_CONFIGURATION.md index 3d7f7ca91..aaf756095 100755 --- a/docs/REMOTE_CONFIGURATION.md +++ b/docs/REMOTE_CONFIGURATION.md @@ -26,7 +26,7 @@ If a remote configuration is found to be invalid (usually a mandatory parameter * `faqURL` (optional, string): The URL of the FAQs. * `dataProtectionURL` (optional, string): The URL of the data protection information page. * `impressumURL` (optional, string): The URL of the impressum page. If none is provided, the corresponding menu entry will not be displayed. -* `serviceURL` (optional, string): The URL of the Content service (DataProvider). If set, it overrides the local server option available in beta builds only. +* `serviceURLs` (optional, JSON): A JSON dictionary describing all URLs of the DataProvider services. Key (string) is the environment identifier of the service, value (string) is the base URL of the DataProvider service. If the key matches a service environment identifier, it overrides the default URL for this service. See `Service.swift` enum raw values for available environment identifiers. * `identityWebserviceURL` (optional, string): The URL of the identity webservices. * `identityWebsiteURL` (optional, string): The URL of the identity web portal. * `userDataServiceURL` (optional, string): The URL of the service with which user data can be synchronized (history, preferences, playlists).