diff --git a/TelegramCore/FetchedMediaResource.swift b/TelegramCore/FetchedMediaResource.swift index 9ec8c356a2..8f5121035e 100644 --- a/TelegramCore/FetchedMediaResource.swift +++ b/TelegramCore/FetchedMediaResource.swift @@ -902,7 +902,12 @@ final class MediaReferenceRevalidationContext { } } -func revalidateMediaResourceReference(postbox: Postbox, network: Network, revalidationContext: MediaReferenceRevalidationContext, info: TelegramCloudMediaResourceFetchInfo, resource: MediaResource) -> Signal { +struct RevalidatedMediaResource { + let updatedResource: TelegramMediaResource + let updatedReference: MediaResourceReference? +} + +func revalidateMediaResourceReference(postbox: Postbox, network: Network, revalidationContext: MediaReferenceRevalidationContext, info: TelegramCloudMediaResourceFetchInfo, resource: MediaResource) -> Signal { var updatedReference = info.reference if case let .media(media, resource) = updatedReference { if case let .message(_, mediaValue) = media { @@ -936,22 +941,22 @@ func revalidateMediaResourceReference(postbox: Postbox, network: Network, revali switch media { case let .message(message, previousMedia): return revalidationContext.message(postbox: postbox, network: network, background: info.preferBackgroundReferenceRevalidation, message: message) - |> mapToSignal { message -> Signal in + |> mapToSignal { message -> Signal in for media in message.media { if let updatedResource = findUpdatedMediaResource(media: media, previousMedia: previousMedia, resource: resource) { - return .single(updatedResource) + return .single(RevalidatedMediaResource(updatedResource: updatedResource, updatedReference: nil)) } } return .fail(.generic) } case let .stickerPack(stickerPack, media): return revalidationContext.stickerPack(postbox: postbox, network: network, background: info.preferBackgroundReferenceRevalidation, stickerPack: stickerPack) - |> mapToSignal { result -> Signal in + |> mapToSignal { result -> Signal in for item in result.1 { if let item = item as? StickerPackItem { if media.id != nil && item.file.id == media.id { if let updatedResource = findUpdatedMediaResource(media: item.file, previousMedia: media, resource: resource) { - return .single(updatedResource) + return .single(RevalidatedMediaResource(updatedResource: updatedResource, updatedReference: nil)) } } } @@ -960,19 +965,19 @@ func revalidateMediaResourceReference(postbox: Postbox, network: Network, revali } case let .webPage(webPage, previousMedia): return revalidationContext.webPage(postbox: postbox, network: network, background: info.preferBackgroundReferenceRevalidation, webPage: webPage) - |> mapToSignal { result -> Signal in + |> mapToSignal { result -> Signal in if let updatedResource = findUpdatedMediaResource(media: result, previousMedia: previousMedia, resource: resource) { - return .single(updatedResource) + return .single(RevalidatedMediaResource(updatedResource: updatedResource, updatedReference: nil)) } return .fail(.generic) } case let .savedGif(media): return revalidationContext.savedGifs(postbox: postbox, network: network, background: info.preferBackgroundReferenceRevalidation) - |> mapToSignal { result -> Signal in + |> mapToSignal { result -> Signal in for file in result { if media.id != nil && file.id == media.id { if let updatedResource = findUpdatedMediaResource(media: file, previousMedia: media, resource: resource) { - return .single(updatedResource) + return .single(RevalidatedMediaResource(updatedResource: updatedResource, updatedReference: nil)) } } } @@ -983,12 +988,12 @@ func revalidateMediaResourceReference(postbox: Postbox, network: Network, revali for attribute in file.attributes { if case let .Sticker(sticker) = attribute, let stickerPack = sticker.packReference { return revalidationContext.stickerPack(postbox: postbox, network: network, background: info.preferBackgroundReferenceRevalidation, stickerPack: stickerPack) - |> mapToSignal { result -> Signal in + |> mapToSignal { result -> Signal in for item in result.1 { if let item = item as? StickerPackItem { if media.id != nil && item.file.id == media.id { if let updatedResource = findUpdatedMediaResource(media: item.file, previousMedia: media, resource: resource) { - return .single(updatedResource) + return .single(RevalidatedMediaResource(updatedResource: updatedResource, updatedReference: nil)) } } } @@ -1002,17 +1007,17 @@ func revalidateMediaResourceReference(postbox: Postbox, network: Network, revali } case let .avatar(peer, _): return revalidationContext.peer(postbox: postbox, network: network, background: info.preferBackgroundReferenceRevalidation, peer: peer) - |> mapToSignal { updatedPeer -> Signal in + |> mapToSignal { updatedPeer -> Signal in for representation in updatedPeer.profileImageRepresentations { if representation.resource.id.isEqual(to: resource.id) { - return .single(representation.resource) + return .single(RevalidatedMediaResource(updatedResource: representation.resource, updatedReference: nil)) } } if let legacyResource = resource as? CloudFileMediaResource { for representation in updatedPeer.profileImageRepresentations { if let updatedResource = representation.resource as? CloudPeerPhotoSizeMediaResource { if updatedResource.localId == legacyResource.localId && updatedResource.volumeId == legacyResource.volumeId { - return .single(updatedResource) + return .single(RevalidatedMediaResource(updatedResource: updatedResource, updatedReference: nil)) } } } @@ -1021,19 +1026,20 @@ func revalidateMediaResourceReference(postbox: Postbox, network: Network, revali } case let .messageAuthorAvatar(message, _): return revalidationContext.message(postbox: postbox, network: network, background: info.preferBackgroundReferenceRevalidation, message: message) - |> mapToSignal { updatedMessage -> Signal in - if let author = updatedMessage.author { - for representation in author.profileImageRepresentations { - if representation.resource.id.isEqual(to: resource.id) { - return .single(representation.resource) - } + |> mapToSignal { updatedMessage -> Signal in + guard let author = updatedMessage.author, let authorReference = PeerReference(author) else { + return .fail(.generic) + } + for representation in author.profileImageRepresentations { + if representation.resource.id.isEqual(to: resource.id) { + return .single(RevalidatedMediaResource(updatedResource: representation.resource, updatedReference: .avatar(peer: authorReference, resource: representation.resource))) } - if let legacyResource = resource as? CloudFileMediaResource { - for representation in author.profileImageRepresentations { - if let updatedResource = representation.resource as? CloudPeerPhotoSizeMediaResource { - if updatedResource.localId == legacyResource.localId && updatedResource.volumeId == legacyResource.volumeId { - return .single(updatedResource) - } + } + if let legacyResource = resource as? CloudFileMediaResource { + for representation in author.profileImageRepresentations { + if let updatedResource = representation.resource as? CloudPeerPhotoSizeMediaResource { + if updatedResource.localId == legacyResource.localId && updatedResource.volumeId == legacyResource.volumeId { + return .single(RevalidatedMediaResource(updatedResource: updatedResource, updatedReference: .avatar(peer: authorReference, resource: updatedResource))) } } } @@ -1042,18 +1048,18 @@ func revalidateMediaResourceReference(postbox: Postbox, network: Network, revali } case .wallpaper: return revalidationContext.wallpapers(postbox: postbox, network: network, background: info.preferBackgroundReferenceRevalidation) - |> mapToSignal { wallpapers -> Signal in + |> mapToSignal { wallpapers -> Signal in for wallpaper in wallpapers { switch wallpaper { case let .image(representations, _): for representation in representations { if representation.resource.id.isEqual(to: resource.id) { - return .single(representation.resource) + return .single(RevalidatedMediaResource(updatedResource: representation.resource, updatedReference: nil)) } } case let .file(_, _, _, _, _, _, _, file, _): if let updatedResource = findUpdatedMediaResource(media: file, previousMedia: nil, resource: resource) { - return .single(updatedResource) + return .single(RevalidatedMediaResource(updatedResource: updatedResource, updatedReference: nil)) } default: break @@ -1063,15 +1069,15 @@ func revalidateMediaResourceReference(postbox: Postbox, network: Network, revali } case let .stickerPackThumbnail(packReference, resource): return revalidationContext.stickerPack(postbox: postbox, network: network, background: info.preferBackgroundReferenceRevalidation, stickerPack: packReference) - |> mapToSignal { result -> Signal in + |> mapToSignal { result -> Signal in if let thumbnail = result.0.thumbnail { if thumbnail.resource.id.isEqual(to: resource.id) { - return .single(thumbnail.resource) + return .single(RevalidatedMediaResource(updatedResource: thumbnail.resource, updatedReference: nil)) } if let legacyResource = resource as? CloudFileMediaResource { if let updatedResource = thumbnail.resource as? CloudStickerPackThumbnailMediaResource { if updatedResource.localId == legacyResource.localId && updatedResource.volumeId == legacyResource.volumeId { - return .single(updatedResource) + return .single(RevalidatedMediaResource(updatedResource: updatedResource, updatedReference: nil)) } } } diff --git a/TelegramCore/MultipartFetch.swift b/TelegramCore/MultipartFetch.swift index 995b8e3980..c099838d02 100644 --- a/TelegramCore/MultipartFetch.swift +++ b/TelegramCore/MultipartFetch.swift @@ -64,8 +64,14 @@ private enum MultipartFetchDownloadError { case hashesMissing } +private enum MultipartFetchGenericLocationResult { + case none + case location(Api.InputFileLocation) + case revalidate +} + private enum MultipartFetchMasterLocation { - case generic(Int32, (TelegramMediaResource, Data?) -> Api.InputFileLocation?) + case generic(Int32, (TelegramMediaResource, MediaResourceReference?, Data?) -> MultipartFetchGenericLocationResult) case web(Int32, Api.InputWebFileLocation) var datacenterId: Int32 { @@ -272,7 +278,7 @@ private enum MultipartFetchSource { case master(location: MultipartFetchMasterLocation, download: DownloadWrapper) case cdn(masterDatacenterId: Int32, fileToken: Data, key: Data, iv: Data, download: DownloadWrapper, masterDownload: DownloadWrapper, hashSource: MultipartCdnHashSource) - func request(offset: Int32, limit: Int32, tag: MediaResourceFetchTag?, resource: TelegramMediaResource, fileReference: Data?, continueInBackground: Bool) -> Signal { + func request(offset: Int32, limit: Int32, tag: MediaResourceFetchTag?, resource: TelegramMediaResource, resourceReference: MediaResourceReference?, fileReference: Data?, continueInBackground: Bool) -> Signal { switch self { case .none: return .never() @@ -284,36 +290,39 @@ private enum MultipartFetchSource { switch location { case let .generic(_, location): - if let parsedLocation = location(resource, fileReference) { - return download.request(Api.functions.upload.getFile(location: parsedLocation, offset: offset, limit: Int32(updatedLength)), tag: tag, continueInBackground: continueInBackground) - |> mapError { error -> MultipartFetchDownloadError in - if error.errorDescription.hasPrefix("FILEREF_INVALID") || error.errorDescription.hasPrefix("FILE_REFERENCE_") { - return .revalidateMediaReference + switch location(resource, resourceReference, fileReference) { + case .none: + return .fail(.revalidateMediaReference) + case .revalidate: + return .fail(.revalidateMediaReference) + case let .location(parsedLocation): + return download.request(Api.functions.upload.getFile(location: parsedLocation, offset: offset, limit: Int32(updatedLength)), tag: tag, continueInBackground: continueInBackground) + |> mapError { error -> MultipartFetchDownloadError in + if error.errorDescription.hasPrefix("FILEREF_INVALID") || error.errorDescription.hasPrefix("FILE_REFERENCE_") { + return .revalidateMediaReference + } + return .generic } - return .generic - } - |> mapToSignal { result -> Signal in - switch result { - case let .file(_, _, bytes): - var resultData = bytes.makeData() - if resultData.count > Int(limit) { - resultData.count = Int(limit) - } - return .single(resultData) - case let .fileCdnRedirect(dcId, fileToken, encryptionKey, encryptionIv, partHashes): - var parsedPartHashes: [Int32: Data] = [:] - for part in partHashes { - switch part { - case let .fileHash(offset, limit, bytes): - assert(limit == 128 * 1024) - parsedPartHashes[offset] = bytes.makeData() + |> mapToSignal { result -> Signal in + switch result { + case let .file(_, _, bytes): + var resultData = bytes.makeData() + if resultData.count > Int(limit) { + resultData.count = Int(limit) } - } - return .fail(.switchToCdn(id: dcId, token: fileToken.makeData(), key: encryptionKey.makeData(), iv: encryptionIv.makeData(), partHashes: parsedPartHashes)) + return .single(resultData) + case let .fileCdnRedirect(dcId, fileToken, encryptionKey, encryptionIv, partHashes): + var parsedPartHashes: [Int32: Data] = [:] + for part in partHashes { + switch part { + case let .fileHash(offset, limit, bytes): + assert(limit == 128 * 1024) + parsedPartHashes[offset] = bytes.makeData() + } + } + return .fail(.switchToCdn(id: dcId, token: fileToken.makeData(), key: encryptionKey.makeData(), iv: encryptionIv.makeData(), partHashes: parsedPartHashes)) + } } - } - } else { - return .fail(.revalidateMediaReference) } case let .web(_, location): return download.request(Api.functions.upload.getWebFile(location: location, offset: offset, limit: Int32(updatedLength)), tag: tag, continueInBackground: continueInBackground) @@ -388,6 +397,7 @@ private final class MultipartFetchManager { let partAlignment = 128 * 1024 var resource: TelegramMediaResource + var resourceReference: MediaResourceReference? var fileReference: Data? let parameters: MediaResourceFetchParameters? let consumerId: Int64 @@ -440,8 +450,10 @@ private final class MultipartFetchManager { if let info = parameters?.info as? TelegramCloudMediaResourceFetchInfo { self.fileReference = info.reference.apiFileReference self.continueInBackground = info.continueInBackground + self.resourceReference = info.reference } else { self.continueInBackground = false + self.resourceReference = nil } self.state = MultipartDownloadState(encryptionKey: encryptionKey, decryptedSize: decryptedSize) @@ -592,7 +604,7 @@ private final class MultipartFetchManager { requestLimit = (requestLimit / self.partAlignment + 1) * self.partAlignment } - let part = self.source.request(offset: Int32(downloadRange.lowerBound), limit: Int32(requestLimit), tag: self.parameters?.tag, resource: self.resource, fileReference: self.fileReference, continueInBackground: self.continueInBackground) + let part = self.source.request(offset: Int32(downloadRange.lowerBound), limit: Int32(requestLimit), tag: self.parameters?.tag, resource: self.resource, resourceReference: self.resourceReference, fileReference: self.fileReference, continueInBackground: self.continueInBackground) |> deliverOn(self.queue) let partDisposable = MetaDisposable() self.fetchingParts[downloadRange.lowerBound] = (downloadRange.count, partDisposable) @@ -621,19 +633,18 @@ private final class MultipartFetchManager { strongSelf.revalidatingMediaReference = true if let info = strongSelf.parameters?.info as? TelegramCloudMediaResourceFetchInfo { strongSelf.revalidateMediaReferenceDisposable.set((revalidateMediaResourceReference(postbox: strongSelf.postbox, network: strongSelf.network, revalidationContext: strongSelf.revalidationContext, info: info, resource: strongSelf.resource) - |> deliverOn(strongSelf.queue)).start(next: { validatedResource in + |> deliverOn(strongSelf.queue)).start(next: { validationResult in if let strongSelf = self { strongSelf.revalidatingMediaReference = false strongSelf.revalidatedMediaReference = true - if let validatedResource = validatedResource as? TelegramCloudMediaResourceWithFileReference, let reference = validatedResource.fileReference { + if let validatedResource = validationResult.updatedResource as? TelegramCloudMediaResourceWithFileReference, let reference = validatedResource.fileReference { strongSelf.fileReference = reference } - strongSelf.resource = validatedResource + strongSelf.resource = validationResult.updatedResource + strongSelf.resourceReference = validationResult.updatedReference strongSelf.checkState() } }, error: { _ in - if let strongSelf = self { - } })) } else { Logger.shared.log("MultipartFetch", "reference invalidation requested, but no valid reference given") @@ -680,34 +691,46 @@ func multipartFetch(postbox: Postbox, network: Network, mediaReferenceRevalidati if let resource = resource as? WebFileReferenceMediaResource { location = .web(Int32(datacenterId), resource.apiInputLocation) } else { - location = .generic(Int32(datacenterId), { resource, fileReference in + location = .generic(Int32(datacenterId), { resource, resourceReference, fileReference in if let resource = resource as? TelegramCloudMediaResource { - return resource.apiInputLocation(fileReference: fileReference) + if let location = resource.apiInputLocation(fileReference: fileReference){ + return .location(location) + } else { + return .none + } } else if let resource = resource as? CloudPeerPhotoSizeMediaResource { guard let info = parameters?.info as? TelegramCloudMediaResourceFetchInfo else { - return nil + return .none } - switch info.reference { + switch resourceReference ?? info.reference { case let .avatar(peer, _): - return resource.apiInputLocation(peerReference: peer) - case let .messageAuthorAvatar(message, resource): + if let location = resource.apiInputLocation(peerReference: peer) { + return .location(location) + } else { + return .none + } + case .messageAuthorAvatar: - return nil + return .revalidate default: - return nil + return .none } } else if let resource = resource as? CloudStickerPackThumbnailMediaResource { guard let info = parameters?.info as? TelegramCloudMediaResourceFetchInfo else { - return nil + return .none } switch info.reference { case let .stickerPackThumbnail(stickerPack, _): - return resource.apiInputLocation(packReference: stickerPack) + if let location = resource.apiInputLocation(packReference: stickerPack) { + return .location(location) + } else { + return .none + } default: - return nil + return .none } } else { - return nil + return .none } }) }