Add HTTP Response StatusCode List and fix potential race condition

This commit is contained in:
Toni Förster 2024-05-17 15:29:42 +02:00
parent 6eba2a45c8
commit c3e4c074d6
No known key found for this signature in database
GPG Key ID: 292F3E5086C83FC7
4 changed files with 160 additions and 49 deletions

View File

@ -57,10 +57,21 @@ extension PlayerModel {
} }
func streamsWithInstance(instance _: Instance, streams: [Stream], completion: @escaping ([Stream]) -> Void) { func streamsWithInstance(instance _: Instance, streams: [Stream], completion: @escaping ([Stream]) -> Void) {
// Queue for stream processing
let streamProcessingQueue = DispatchQueue(label: "stream.yattee.streamProcessing.Queue", qos: .userInitiated)
// Queue for accessing the processedStreams array
let processedStreamsQueue = DispatchQueue(label: "stream.yattee.processedStreams.Queue")
// DispatchGroup for managing multiple tasks
let streamProcessingGroup = DispatchGroup()
var processedStreams = [Stream]()
for stream in streams {
streamProcessingQueue.async(group: streamProcessingGroup) {
let forbiddenAssetTestGroup = DispatchGroup() let forbiddenAssetTestGroup = DispatchGroup()
var hasForbiddenAsset = false var hasForbiddenAsset = false
let (nonHLSAssets, hlsURLs) = getAssets(from: streams) let (nonHLSAssets, hlsURLs) = self.getAssets(from: [stream])
if let randomStream = nonHLSAssets.randomElement() { if let randomStream = nonHLSAssets.randomElement() {
let instance = randomStream.0 let instance = randomStream.0
@ -68,15 +79,16 @@ extension PlayerModel {
let url = randomStream.2 let url = randomStream.2
let requestRange = randomStream.3 let requestRange = randomStream.3
// swiftlint:disable:next shorthand_optional_binding
if let asset = asset, let instance = instance, !instance.proxiesVideos { if let asset = asset, let instance = instance, !instance.proxiesVideos {
if instance.app == .invidious { if instance.app == .invidious {
testAsset(url: url, range: requestRange, isHLS: false, forbiddenAssetTestGroup: forbiddenAssetTestGroup) { isForbidden in self.testAsset(url: url, range: requestRange, isHLS: false, forbiddenAssetTestGroup: forbiddenAssetTestGroup) { isForbidden in
hasForbiddenAsset = isForbidden hasForbiddenAsset = isForbidden
} }
} else if instance.app == .piped { } else if instance.app == .piped {
testPipedAssets(asset: asset, requestRange: requestRange!, isHLS: false, forbiddenAssetTestGroup: forbiddenAssetTestGroup, completion: { isForbidden in self.testPipedAssets(asset: asset, requestRange: requestRange, isHLS: false, forbiddenAssetTestGroup: forbiddenAssetTestGroup) { isForbidden in
hasForbiddenAsset = isForbidden hasForbiddenAsset = isForbidden
}) }
} }
} }
} else if let randomHLS = hlsURLs.randomElement() { } else if let randomHLS = hlsURLs.randomElement() {
@ -84,14 +96,15 @@ extension PlayerModel {
let asset = AVURLAsset(url: randomHLS.1) let asset = AVURLAsset(url: randomHLS.1)
if instance?.app == .piped { if instance?.app == .piped {
testPipedAssets(asset: asset, requestRange: nil, isHLS: false, forbiddenAssetTestGroup: forbiddenAssetTestGroup, completion: { isForbidden in self.testPipedAssets(asset: asset, requestRange: nil, isHLS: true, forbiddenAssetTestGroup: forbiddenAssetTestGroup) { isForbidden in
hasForbiddenAsset = isForbidden hasForbiddenAsset = isForbidden
}) }
} }
} }
forbiddenAssetTestGroup.notify(queue: .main) { forbiddenAssetTestGroup.wait()
let processedStreams = streams.map { stream -> Stream in
// Post-processing code
if let instance = stream.instance { if let instance = stream.instance {
if instance.app == .invidious { if instance.app == .invidious {
if hasForbiddenAsset || instance.proxiesVideos { if hasForbiddenAsset || instance.proxiesVideos {
@ -104,35 +117,36 @@ extension PlayerModel {
} }
} else if instance.app == .piped, !instance.proxiesVideos, !hasForbiddenAsset { } else if instance.app == .piped, !instance.proxiesVideos, !hasForbiddenAsset {
if let hlsURL = stream.hlsURL { if let hlsURL = stream.hlsURL {
forbiddenAssetTestGroup.enter() PipedAPI.nonProxiedAsset(url: hlsURL) { possibleNonProxiedURL in
PipedAPI.nonProxiedAsset(url: hlsURL) { nonProxiedURL in if let nonProxiedURL = possibleNonProxiedURL {
if let nonProxiedURL = nonProxiedURL {
stream.hlsURL = nonProxiedURL.url stream.hlsURL = nonProxiedURL.url
} }
forbiddenAssetTestGroup.leave()
} }
} else { } else {
if let audio = stream.audioAsset { if let audio = stream.audioAsset {
forbiddenAssetTestGroup.enter()
PipedAPI.nonProxiedAsset(asset: audio) { nonProxiedAudioAsset in PipedAPI.nonProxiedAsset(asset: audio) { nonProxiedAudioAsset in
stream.audioAsset = nonProxiedAudioAsset stream.audioAsset = nonProxiedAudioAsset
forbiddenAssetTestGroup.leave()
} }
} }
if let video = stream.videoAsset { if let video = stream.videoAsset {
forbiddenAssetTestGroup.enter()
PipedAPI.nonProxiedAsset(asset: video) { nonProxiedVideoAsset in PipedAPI.nonProxiedAsset(asset: video) { nonProxiedVideoAsset in
stream.videoAsset = nonProxiedVideoAsset stream.videoAsset = nonProxiedVideoAsset
forbiddenAssetTestGroup.leave()
} }
} }
} }
} }
} }
return stream
}
forbiddenAssetTestGroup.notify(queue: .main) { // Append to processedStreams within the processedStreamsQueue
processedStreamsQueue.sync {
processedStreams.append(stream)
}
}
}
streamProcessingGroup.notify(queue: .main) {
// Access and pass processedStreams within the processedStreamsQueue block
processedStreamsQueue.sync {
completion(processedStreams) completion(processedStreams)
} }
} }
@ -161,21 +175,23 @@ extension PlayerModel {
} }
private func testAsset(url: URL, range: String?, isHLS: Bool, forbiddenAssetTestGroup: DispatchGroup, completion: @escaping (Bool) -> Void) { private func testAsset(url: URL, range: String?, isHLS: Bool, forbiddenAssetTestGroup: DispatchGroup, completion: @escaping (Bool) -> Void) {
// In case the range is nil, generate a random one.
let randomEnd = Int.random(in: 200 ... 800) let randomEnd = Int.random(in: 200 ... 800)
let requestRange = range ?? "0-\(randomEnd)" let requestRange = range ?? "0-\(randomEnd)"
let HTTPStatusForbidden = 403
forbiddenAssetTestGroup.enter() forbiddenAssetTestGroup.enter()
URLTester.testURLResponse(url: url, range: requestRange, isHLS: isHLS) { statusCode in URLTester.testURLResponse(url: url, range: requestRange, isHLS: isHLS) { statusCode in
completion(statusCode == HTTPStatusForbidden) completion(statusCode == HTTPStatus.Forbidden)
forbiddenAssetTestGroup.leave() forbiddenAssetTestGroup.leave()
} }
} }
private func testPipedAssets(asset: AVURLAsset, requestRange: String?, isHLS: Bool, forbiddenAssetTestGroup: DispatchGroup, completion: @escaping (Bool) -> Void) { private func testPipedAssets(asset: AVURLAsset, requestRange: String?, isHLS: Bool, forbiddenAssetTestGroup: DispatchGroup, completion: @escaping (Bool) -> Void) {
PipedAPI.nonProxiedAsset(asset: asset) { nonProxiedAsset in PipedAPI.nonProxiedAsset(asset: asset) { possibleNonProxiedAsset in
if let nonProxiedAsset = nonProxiedAsset { if let nonProxiedAsset = possibleNonProxiedAsset {
self.testAsset(url: nonProxiedAsset.url, range: requestRange, isHLS: isHLS, forbiddenAssetTestGroup: forbiddenAssetTestGroup, completion: completion) self.testAsset(url: nonProxiedAsset.url, range: requestRange, isHLS: isHLS, forbiddenAssetTestGroup: forbiddenAssetTestGroup, completion: completion)
} else {
completion(false)
} }
} }
} }

81
Shared/HTTPStatus.swift Normal file
View File

@ -0,0 +1,81 @@
// HTTP response status codes
enum HTTPStatus {
// Informational responses (100 - 199)
static let Continue = 100
static let SwitchingProtocols = 101
static let Processing = 102
static let EarlyHints = 103
// Successful responses (200 - 299)
static let OK = 200
static let Created = 201
static let Accepted = 202
static let NonAuthoritativeInformation = 203
static let NoContent = 204
static let ResetContent = 205
static let PartialContent = 206
static let MultiStatus = 207
static let AlreadyReported = 208
static let IMUsed = 226
// Redirection messages (300 - 399)
static let MultipleChoices = 300
static let MovedPermanently = 301
static let Found = 302
static let SeeOther = 303
static let NotModified = 304
static let UseProxy = 305
static let SwitchProxy = 306
static let TemporaryRedirect = 307
static let PermanentRedirect = 308
// Client error responses (400 - 499)
static let BadRequest = 400
static let Unauthorized = 401
static let PaymentRequired = 402
static let Forbidden = 403
static let NotFound = 404
static let MethodNotAllowed = 405
static let NotAcceptable = 406
static let ProxyAuthenticationRequired = 407
static let RequestTimeout = 408
static let Conflict = 409
static let Gone = 410
static let LengthRequired = 411
static let PreconditionFailed = 412
static let PayloadTooLarge = 413
static let URITooLong = 414
static let UnsupportedMediaType = 415
static let RangeNotSatisfiable = 416
static let ExpectationFailed = 417
static let IAmATeapot = 418
static let MisdirectedRequest = 421
static let UnprocessableEntity = 422
static let Locked = 423
static let FailedDependency = 424
static let TooEarly = 425
static let UpgradeRequired = 426
static let PreconditionRequired = 428
static let TooManyRequests = 429
static let RequestHeaderFieldsTooLarge = 431
static let UnavailableForLegalReasons = 451
// Server error responses (500 - 599)
static let InternalServerError = 500
static let NotImplemented = 501
static let BadGateway = 502
static let ServiceUnavailable = 503
static let GatewayTimeout = 504
static let HTTPVersionNotSupported = 505
static let VariantAlsoNegotiates = 506
static let InsufficientStorage = 507
static let LoopDetected = 508
static let NotExtended = 510
static let NetworkAuthenticationRequired = 511
}

View File

@ -5,7 +5,6 @@ enum URLTester {
private static let hlsMediaPrefix = "#EXT-X-MEDIA:" private static let hlsMediaPrefix = "#EXT-X-MEDIA:"
private static let hlsInfPrefix = "#EXTINF:" private static let hlsInfPrefix = "#EXTINF:"
private static let uriRegex = "(?<=URI=\")(.*?)(?=\")" private static let uriRegex = "(?<=URI=\")(.*?)(?=\")"
private static let HTTPStatusForbidden = 403
static func testURLResponse(url: URL, range: String, isHLS: Bool, completion: @escaping (Int) -> Void) { static func testURLResponse(url: URL, range: String, isHLS: Bool, completion: @escaping (Int) -> Void) {
if isHLS { if isHLS {
@ -24,7 +23,7 @@ enum URLTester {
var dataTask: URLSessionDataTask? var dataTask: URLSessionDataTask?
dataTask = URLSession.shared.dataTask(with: request) { _, response, _ in dataTask = URLSession.shared.dataTask(with: request) { _, response, _ in
let statusCode = (response as? HTTPURLResponse)?.statusCode ?? HTTPStatusForbidden let statusCode = (response as? HTTPURLResponse)?.statusCode ?? HTTPStatus.Forbidden
Logger(label: "stream.yattee.httpRequest").info("URL: \(url) | Status Code: \(statusCode)") Logger(label: "stream.yattee.httpRequest").info("URL: \(url) | Status Code: \(statusCode)")
completion(statusCode, dataTask) completion(statusCode, dataTask)
} }
@ -37,6 +36,8 @@ enum URLTester {
httpRequest(url: url, range: range) { statusCode, _ in httpRequest(url: url, range: range) { statusCode, _ in
completion(statusCode) completion(statusCode)
} }
} else {
completion(HTTPStatus.NotFound)
} }
} }
} }
@ -64,10 +65,15 @@ enum URLTester {
private static func parseHLSManifest(manifestUrl: URL, completion: @escaping ([URL]) -> Void) { private static func parseHLSManifest(manifestUrl: URL, completion: @escaping ([URL]) -> Void) {
URLSession.shared.dataTask(with: manifestUrl) { data, _, _ in URLSession.shared.dataTask(with: manifestUrl) { data, _, _ in
guard let data = data, // swiftlint:disable:next shorthand_optional_binding
let manifest = String(data: data, encoding: .utf8), guard let data = data else {
!manifest.isEmpty Logger(label: "stream.yattee.httpRequest").error("Data is nil")
else { completion([])
return
}
// swiftlint:disable:next non_optional_string_data_conversion
guard let manifest = String(data: data, encoding: .utf8), !manifest.isEmpty else {
Logger(label: "stream.yattee.httpRequest").error("Cannot read or empty HLS manifest") Logger(label: "stream.yattee.httpRequest").error("Cannot read or empty HLS manifest")
completion([]) completion([])
return return

View File

@ -1070,6 +1070,9 @@
37FFC440272734C3009FFD26 /* Throttle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FFC43F272734C3009FFD26 /* Throttle.swift */; }; 37FFC440272734C3009FFD26 /* Throttle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FFC43F272734C3009FFD26 /* Throttle.swift */; };
37FFC441272734C3009FFD26 /* Throttle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FFC43F272734C3009FFD26 /* Throttle.swift */; }; 37FFC441272734C3009FFD26 /* Throttle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FFC43F272734C3009FFD26 /* Throttle.swift */; };
37FFC442272734C3009FFD26 /* Throttle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FFC43F272734C3009FFD26 /* Throttle.swift */; }; 37FFC442272734C3009FFD26 /* Throttle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37FFC43F272734C3009FFD26 /* Throttle.swift */; };
E25028B02BF790F5002CB9FC /* HTTPStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25028AF2BF790F5002CB9FC /* HTTPStatus.swift */; };
E25028B12BF790F5002CB9FC /* HTTPStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25028AF2BF790F5002CB9FC /* HTTPStatus.swift */; };
E25028B22BF790F5002CB9FC /* HTTPStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = E25028AF2BF790F5002CB9FC /* HTTPStatus.swift */; };
E258F38A2BF61BD2005B8C28 /* URLTester.swift in Sources */ = {isa = PBXBuildFile; fileRef = E258F3892BF61BD2005B8C28 /* URLTester.swift */; }; E258F38A2BF61BD2005B8C28 /* URLTester.swift in Sources */ = {isa = PBXBuildFile; fileRef = E258F3892BF61BD2005B8C28 /* URLTester.swift */; };
E258F38B2BF61BD2005B8C28 /* URLTester.swift in Sources */ = {isa = PBXBuildFile; fileRef = E258F3892BF61BD2005B8C28 /* URLTester.swift */; }; E258F38B2BF61BD2005B8C28 /* URLTester.swift in Sources */ = {isa = PBXBuildFile; fileRef = E258F3892BF61BD2005B8C28 /* URLTester.swift */; };
E258F38C2BF61BD2005B8C28 /* URLTester.swift in Sources */ = {isa = PBXBuildFile; fileRef = E258F3892BF61BD2005B8C28 /* URLTester.swift */; }; E258F38C2BF61BD2005B8C28 /* URLTester.swift in Sources */ = {isa = PBXBuildFile; fileRef = E258F3892BF61BD2005B8C28 /* URLTester.swift */; };
@ -1542,6 +1545,7 @@
3DA101AD287C30F50027D920 /* DEVELOPMENT_TEAM.template.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = DEVELOPMENT_TEAM.template.xcconfig; sourceTree = "<group>"; }; 3DA101AD287C30F50027D920 /* DEVELOPMENT_TEAM.template.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = DEVELOPMENT_TEAM.template.xcconfig; sourceTree = "<group>"; };
3DA101AE287C30F50027D920 /* DEVELOPMENT_TEAM.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = DEVELOPMENT_TEAM.xcconfig; sourceTree = "<group>"; }; 3DA101AE287C30F50027D920 /* DEVELOPMENT_TEAM.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = DEVELOPMENT_TEAM.xcconfig; sourceTree = "<group>"; };
3DA101AF287C30F50027D920 /* Shared.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Shared.xcconfig; sourceTree = "<group>"; }; 3DA101AF287C30F50027D920 /* Shared.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Shared.xcconfig; sourceTree = "<group>"; };
E25028AF2BF790F5002CB9FC /* HTTPStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPStatus.swift; sourceTree = "<group>"; };
E258F3892BF61BD2005B8C28 /* URLTester.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLTester.swift; sourceTree = "<group>"; }; E258F3892BF61BD2005B8C28 /* URLTester.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = URLTester.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
@ -2290,6 +2294,7 @@
372915E52687E3B900F5A35B /* Defaults.swift */, 372915E52687E3B900F5A35B /* Defaults.swift */,
37D2E0D328B67EFC00F64D52 /* Delay.swift */, 37D2E0D328B67EFC00F64D52 /* Delay.swift */,
3761ABFC26F0F8DE00AA496F /* EnvironmentValues.swift */, 3761ABFC26F0F8DE00AA496F /* EnvironmentValues.swift */,
E25028AF2BF790F5002CB9FC /* HTTPStatus.swift */,
375B537828DF6CBB004C1D19 /* Localizable.strings */, 375B537828DF6CBB004C1D19 /* Localizable.strings */,
3729037D2739E47400EA99F6 /* MenuCommands.swift */, 3729037D2739E47400EA99F6 /* MenuCommands.swift */,
37B7958F2771DAE0001CF27B /* OpenURLHandler.swift */, 37B7958F2771DAE0001CF27B /* OpenURLHandler.swift */,
@ -3120,6 +3125,7 @@
37DD87C7271C9CFE0027CBF9 /* PlayerStreams.swift in Sources */, 37DD87C7271C9CFE0027CBF9 /* PlayerStreams.swift in Sources */,
37F7D82C289EB05F00E2B3D0 /* SettingsPickerModifier.swift in Sources */, 37F7D82C289EB05F00E2B3D0 /* SettingsPickerModifier.swift in Sources */,
375EC95D289EEEE000751258 /* QualityProfile.swift in Sources */, 375EC95D289EEEE000751258 /* QualityProfile.swift in Sources */,
E25028B02BF790F5002CB9FC /* HTTPStatus.swift in Sources */,
3773B8102ADC076800B5FEF3 /* ScrollViewMatcher.swift in Sources */, 3773B8102ADC076800B5FEF3 /* ScrollViewMatcher.swift in Sources */,
371B7E662759786B00D21217 /* Comment+Fixtures.swift in Sources */, 371B7E662759786B00D21217 /* Comment+Fixtures.swift in Sources */,
37BE0BD326A1D4780092E2DB /* AppleAVPlayerView.swift in Sources */, 37BE0BD326A1D4780092E2DB /* AppleAVPlayerView.swift in Sources */,
@ -3593,6 +3599,7 @@
37D4B19826717E1500C925CA /* Video.swift in Sources */, 37D4B19826717E1500C925CA /* Video.swift in Sources */,
371B7E5D27596B8400D21217 /* Comment.swift in Sources */, 371B7E5D27596B8400D21217 /* Comment.swift in Sources */,
37EF5C232739D37B00B03725 /* MenuModel.swift in Sources */, 37EF5C232739D37B00B03725 /* MenuModel.swift in Sources */,
E25028B12BF790F5002CB9FC /* HTTPStatus.swift in Sources */,
37A7D71C2B680E66009CB1ED /* LocationsSettingsGroupExporter.swift in Sources */, 37A7D71C2B680E66009CB1ED /* LocationsSettingsGroupExporter.swift in Sources */,
37BC50A92778A84700510953 /* HistorySettings.swift in Sources */, 37BC50A92778A84700510953 /* HistorySettings.swift in Sources */,
374DE88128BB896C0062BBF2 /* PlayerDragGesture.swift in Sources */, 374DE88128BB896C0062BBF2 /* PlayerDragGesture.swift in Sources */,
@ -3873,6 +3880,7 @@
37130A61277657300033018A /* PersistenceController.swift in Sources */, 37130A61277657300033018A /* PersistenceController.swift in Sources */,
37E70929271CDDAE00D34DDE /* OpenSettingsButton.swift in Sources */, 37E70929271CDDAE00D34DDE /* OpenSettingsButton.swift in Sources */,
3717407F2949D40800FDDBC7 /* ChannelLinkView.swift in Sources */, 3717407F2949D40800FDDBC7 /* ChannelLinkView.swift in Sources */,
E25028B22BF790F5002CB9FC /* HTTPStatus.swift in Sources */,
370F4FAA27CC163B001B35DC /* PlayerBackend.swift in Sources */, 370F4FAA27CC163B001B35DC /* PlayerBackend.swift in Sources */,
376A33E62720CB35000C1D6B /* Account.swift in Sources */, 376A33E62720CB35000C1D6B /* Account.swift in Sources */,
3763C98B290C7A50004D3B5F /* OpenVideosView.swift in Sources */, 3763C98B290C7A50004D3B5F /* OpenVideosView.swift in Sources */,