From cbe8943bc7270f45ca9a3c613b0e1ce84a42679b Mon Sep 17 00:00:00 2001 From: Peter Date: Fri, 20 Jul 2018 13:09:56 +0300 Subject: [PATCH] no message --- TelegramCore.xcodeproj/project.pbxproj | 6 + TelegramCore/Account.swift | 20 +- TelegramCore/AddressNames.swift | 47 +- TelegramCore/Api0.swift | 14 +- TelegramCore/Api1.swift | 230 +++--- TelegramCore/ApiUtils.swift | 95 +++ .../ApplyMaxReadIndexInteractively.swift | 2 +- TelegramCore/ApplyUpdateMessage.swift | 2 +- TelegramCore/CachedStickerPack.swift | 34 +- .../CheckPeerChatServiceActions.swift | 2 + TelegramCore/CloudFileMediaResource.swift | 58 +- TelegramCore/EnqueueMessage.swift | 15 +- TelegramCore/Fetch.swift | 12 +- TelegramCore/FetchSecretFileResource.swift | 4 +- TelegramCore/FetchedMediaResource.swift | 774 ++++++++++++++++++ TelegramCore/LoadedStickerPack.swift | 15 +- TelegramCore/ManagedRecentStickers.swift | 12 +- .../ManagedSecretChatOutgoingOperations.swift | 4 +- ...anagedSynchronizeSavedGifsOperations.swift | 72 +- ...edSynchronizeSavedStickersOperations.swift | 60 +- TelegramCore/MultipartFetch.swift | 131 ++- TelegramCore/MultipartUpload.swift | 2 +- ...OutgoingMessageWithChatContextResult.swift | 2 +- TelegramCore/PeerPhotoUpdater.swift | 12 +- TelegramCore/PendingMessageManager.swift | 197 +++-- .../PendingMessageUploadedContent.swift | 280 ++++--- ...ecretChatIncomingDecryptedOperations.swift | 53 +- TelegramCore/RequestChatContextResults.swift | 25 +- TelegramCore/RequestEditMessage.swift | 34 +- TelegramCore/RequestUserPhotos.swift | 4 +- TelegramCore/SearchMessages.swift | 315 +++++-- TelegramCore/SecureFileMediaResource.swift | 2 +- TelegramCore/Serialization.swift | 2 +- TelegramCore/StoreMessage_Telegram.swift | 4 + .../SynchronizeSavedGifsOperation.swift | 19 +- .../SynchronizeSavedStickersOperation.swift | 13 +- TelegramCore/TelegramMediaFile.swift | 54 +- TelegramCore/TelegramMediaImage.swift | 41 +- TelegramCore/UpdatePeers.swift | 3 - TelegramCore/UpdatesApiUtils.swift | 6 +- TelegramCore/UploadSecureIdFile.swift | 2 +- TelegramCore/Wallpapers.swift | 86 +- TelegramCore/WebpagePreview.swift | 23 + 43 files changed, 2064 insertions(+), 724 deletions(-) create mode 100644 TelegramCore/FetchedMediaResource.swift diff --git a/TelegramCore.xcodeproj/project.pbxproj b/TelegramCore.xcodeproj/project.pbxproj index 4f3ce10231..629963cb49 100644 --- a/TelegramCore.xcodeproj/project.pbxproj +++ b/TelegramCore.xcodeproj/project.pbxproj @@ -167,6 +167,8 @@ D03121021DA57E93006A2A60 /* TelegramPeerNotificationSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03121011DA57E93006A2A60 /* TelegramPeerNotificationSettings.swift */; }; D03229F41E6B39700000AF9C /* ImportAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03229F31E6B39700000AF9C /* ImportAccount.swift */; }; D03229F51E6B39700000AF9C /* ImportAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03229F31E6B39700000AF9C /* ImportAccount.swift */; }; + D032F5BC20EF84FD00037B6C /* FetchedMediaResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D032F5BB20EF84FD00037B6C /* FetchedMediaResource.swift */; }; + D032F5BD20EF84FD00037B6C /* FetchedMediaResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D032F5BB20EF84FD00037B6C /* FetchedMediaResource.swift */; }; D033FEB01E61EB0200644997 /* PeerReportStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = D033FEAF1E61EB0200644997 /* PeerReportStatus.swift */; }; D033FEB11E61EB0200644997 /* PeerReportStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = D033FEAF1E61EB0200644997 /* PeerReportStatus.swift */; }; D033FEB31E61F3C000644997 /* ReportPeer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D033FEB21E61F3C000644997 /* ReportPeer.swift */; }; @@ -811,6 +813,7 @@ D02D60AA206BA64100FEFE1E /* VerifySecureIdValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerifySecureIdValue.swift; sourceTree = ""; }; D03121011DA57E93006A2A60 /* TelegramPeerNotificationSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramPeerNotificationSettings.swift; sourceTree = ""; }; D03229F31E6B39700000AF9C /* ImportAccount.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImportAccount.swift; sourceTree = ""; }; + D032F5BB20EF84FD00037B6C /* FetchedMediaResource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchedMediaResource.swift; sourceTree = ""; }; D033FEAF1E61EB0200644997 /* PeerReportStatus.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeerReportStatus.swift; sourceTree = ""; }; D033FEB21E61F3C000644997 /* ReportPeer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReportPeer.swift; sourceTree = ""; }; D033FEB51E61F3F900644997 /* BlockedPeers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BlockedPeers.swift; sourceTree = ""; }; @@ -1448,6 +1451,7 @@ D03B0D121D62257600955575 /* Resources */ = { isa = PBXGroup; children = ( + D032F5BB20EF84FD00037B6C /* FetchedMediaResource.swift */, D0223A9A1EA5654D00211D94 /* TelegramMediaResource.swift */, D03B0D431D6319F900955575 /* CloudFileMediaResource.swift */, D0223A971EA564BD00211D94 /* MediaResourceNetworkStatsTag.swift */, @@ -2263,6 +2267,7 @@ D0E35A121DE4A25E00BC6096 /* OutgoingChatContextResultMessageAttribute.swift in Sources */, D093D806206539D000BC3599 /* SaveSecureIdValue.swift in Sources */, C239BE9C1E630CA700C2C453 /* UpdatePinnedMessage.swift in Sources */, + D032F5BC20EF84FD00037B6C /* FetchedMediaResource.swift in Sources */, D08CAA7D1ED77EE90000FDA8 /* LocalizationSettings.swift in Sources */, D0B844531DAC0773005F29E1 /* TelegramUserPresence.swift in Sources */, D08F4A661E79CC4A00A2AA15 /* SynchronizeInstalledStickerPacksOperations.swift in Sources */, @@ -2657,6 +2662,7 @@ D01A21AA1F38CDDC00DDA104 /* ManagedSynchronizeSavedStickersOperations.swift in Sources */, D0546495207386D7002ECC1E /* SecureIdUtilityBillValue.swift in Sources */, D03C53741DAD5CA9004C17B3 /* CachedChannelData.swift in Sources */, + D032F5BD20EF84FD00037B6C /* FetchedMediaResource.swift in Sources */, D0B418861D7E056D004562A4 /* Namespaces.swift in Sources */, D05A32E51E6F0B2E002760B4 /* RecentAccountSessions.swift in Sources */, D02395D71F8D09A50070F5C2 /* ChannelHistoryAvailabilitySettings.swift in Sources */, diff --git a/TelegramCore/Account.swift b/TelegramCore/Account.swift index 498cee13f3..4effbd8901 100644 --- a/TelegramCore/Account.swift +++ b/TelegramCore/Account.swift @@ -452,10 +452,10 @@ public enum AccountNetworkState: Equatable { public final class AccountAuxiliaryMethods { public let updatePeerChatInputState: (PeerChatInterfaceState?, SynchronizeableChatInputState?) -> PeerChatInterfaceState? - public let fetchResource: (Account, MediaResource, Signal, MediaResourceFetchTag?) -> Signal? + public let fetchResource: (Account, MediaResource, Signal, MediaResourceFetchParameters?) -> Signal? public let fetchResourceMediaReferenceHash: (MediaResource) -> Signal - public init(updatePeerChatInputState: @escaping (PeerChatInterfaceState?, SynchronizeableChatInputState?) -> PeerChatInterfaceState?, fetchResource: @escaping (Account, MediaResource, Signal, MediaResourceFetchTag?) -> Signal?, fetchResourceMediaReferenceHash: @escaping (MediaResource) -> Signal) { + public init(updatePeerChatInputState: @escaping (PeerChatInterfaceState?, SynchronizeableChatInputState?) -> PeerChatInterfaceState?, fetchResource: @escaping (Account, MediaResource, Signal, MediaResourceFetchParameters?) -> Signal?, fetchResourceMediaReferenceHash: @escaping (MediaResource) -> Signal) { self.updatePeerChatInputState = updatePeerChatInputState self.fetchResource = fetchResource self.fetchResourceMediaReferenceHash = fetchResourceMediaReferenceHash @@ -566,6 +566,7 @@ public class Account { public private(set) var viewTracker: AccountViewTracker! public private(set) var pendingMessageManager: PendingMessageManager! public private(set) var messageMediaPreuploadManager: MessageMediaPreuploadManager! + private(set) var mediaReferenceRevalidationContext: MediaReferenceRevalidationContext! private var peerInputActivityManager: PeerInputActivityManager! private var localInputActivityManager: PeerInputActivityManager! fileprivate let managedContactsDisposable = MetaDisposable() @@ -628,7 +629,8 @@ public class Account { self.localInputActivityManager = PeerInputActivityManager() self.viewTracker = AccountViewTracker(account: self) self.messageMediaPreuploadManager = MessageMediaPreuploadManager() - self.pendingMessageManager = PendingMessageManager(network: network, postbox: postbox, auxiliaryMethods: auxiliaryMethods, stateManager: self.stateManager, messageMediaPreuploadManager: self.messageMediaPreuploadManager) + self.mediaReferenceRevalidationContext = MediaReferenceRevalidationContext() + self.pendingMessageManager = PendingMessageManager(network: network, postbox: postbox, auxiliaryMethods: auxiliaryMethods, stateManager: self.stateManager, messageMediaPreuploadManager: self.messageMediaPreuploadManager, revalidationContext: self.mediaReferenceRevalidationContext) self.network.loggedOut = { [weak self] in if let strongSelf = self { @@ -775,8 +777,8 @@ public class Account { self.managedOperationsDisposable.add(managedSynchronizeInstalledStickerPacksOperations(postbox: self.postbox, network: self.network, stateManager: self.stateManager, namespace: .masks).start()) self.managedOperationsDisposable.add(managedSynchronizeMarkFeaturedStickerPacksAsSeenOperations(postbox: self.postbox, network: self.network).start()) self.managedOperationsDisposable.add(managedRecentStickers(postbox: self.postbox, network: self.network).start()) - self.managedOperationsDisposable.add(managedSynchronizeSavedGifsOperations(postbox: self.postbox, network: self.network).start()) - self.managedOperationsDisposable.add(managedSynchronizeSavedStickersOperations(postbox: self.postbox, network: self.network).start()) + self.managedOperationsDisposable.add(managedSynchronizeSavedGifsOperations(postbox: self.postbox, network: self.network, revalidationContext: self.mediaReferenceRevalidationContext).start()) + self.managedOperationsDisposable.add(managedSynchronizeSavedStickersOperations(postbox: self.postbox, network: self.network, revalidationContext: self.mediaReferenceRevalidationContext).start()) self.managedOperationsDisposable.add(managedRecentlyUsedInlineBots(postbox: self.postbox, network: self.network).start()) self.managedOperationsDisposable.add(managedLocalTypingActivities(activities: self.localInputActivityManager.allActivities(), postbox: self.postbox, network: self.network, accountPeerId: self.peerId).start()) self.managedOperationsDisposable.add(managedSynchronizeConsumeMessageContentOperations(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start()) @@ -874,14 +876,14 @@ public func accountNetworkUsageStats(account: Account, reset: ResetNetworkUsageS } public typealias FetchCachedResourceRepresentation = (_ account: Account, _ resource: MediaResource, _ resourceData: MediaResourceData, _ representation: CachedMediaResourceRepresentation) -> Signal -public typealias TransformOutgoingMessageMedia = (_ postbox: Postbox, _ network: Network, _ media: Media, _ userInteractive: Bool) -> Signal +public typealias TransformOutgoingMessageMedia = (_ postbox: Postbox, _ network: Network, _ media: AnyMediaReference, _ userInteractive: Bool) -> Signal public func setupAccount(_ account: Account, fetchCachedResourceRepresentation: FetchCachedResourceRepresentation? = nil, transformOutgoingMessageMedia: TransformOutgoingMessageMedia? = nil) { - account.postbox.mediaBox.fetchResource = { [weak account] resource, ranges, tag -> Signal in + account.postbox.mediaBox.fetchResource = { [weak account] resource, ranges, parameters -> Signal in if let strongAccount = account { - if let result = fetchResource(account: strongAccount, resource: resource, ranges: ranges, tag: tag) { + if let result = fetchResource(account: strongAccount, resource: resource, ranges: ranges, parameters: parameters) { return result - } else if let result = strongAccount.auxiliaryMethods.fetchResource(strongAccount, resource, ranges, tag) { + } else if let result = strongAccount.auxiliaryMethods.fetchResource(strongAccount, resource, ranges, parameters) { return result } else { return .never() diff --git a/TelegramCore/AddressNames.swift b/TelegramCore/AddressNames.swift index 347370819f..a3fb5c3ecc 100644 --- a/TelegramCore/AddressNames.swift +++ b/TelegramCore/AddressNames.swift @@ -137,25 +137,30 @@ public func updateAddressName(account: Account, domain: AddressNameDomain, name: public func adminedPublicChannels(account: Account) -> Signal<[Peer], NoError> { return account.network.request(Api.functions.channels.getAdminedPublicChannels()) - |> retryRequest - |> map { result -> [Peer] in - var peers: [Peer] = [] - switch result { - case let .chats(apiChats): - for chat in apiChats { - if let peer = parseTelegramGroupOrChannel(chat: chat) { - peers.append(peer) - } + |> retryRequest + |> mapToSignal { result -> Signal<[Peer], NoError> in + var peers: [Peer] = [] + switch result { + case let .chats(apiChats): + for chat in apiChats { + if let peer = parseTelegramGroupOrChannel(chat: chat) { + peers.append(peer) } - case let .chatsSlice(_, apiChats): - for chat in apiChats { - if let peer = parseTelegramGroupOrChannel(chat: chat) { - peers.append(peer) - } + } + case let .chatsSlice(_, apiChats): + for chat in apiChats { + if let peer = parseTelegramGroupOrChannel(chat: chat) { + peers.append(peer) } - } + } + } + return account.postbox.transaction { transaction -> [Peer] in + updatePeers(transaction: transaction, peers: peers, update: { _, updated in + return updated + }) return peers } + } } public enum ChannelAddressNameAssignmentAvailability { @@ -176,12 +181,12 @@ public func channelAddressNameAssignmentAvailability(account: Account, peerId: P } if let inputChannel = inputChannel { return account.network.request(Api.functions.channels.checkUsername(channel: inputChannel, username: "username")) - |> map { _ -> ChannelAddressNameAssignmentAvailability in - return .available - } - |> `catch` { error -> Signal in - return .single(.addressNameLimitReached) - } + |> map { _ -> ChannelAddressNameAssignmentAvailability in + return .available + } + |> `catch` { error -> Signal in + return .single(.addressNameLimitReached) + } } else { return .single(.unknown) } diff --git a/TelegramCore/Api0.swift b/TelegramCore/Api0.swift index 02571d974a..f5182188b6 100644 --- a/TelegramCore/Api0.swift +++ b/TelegramCore/Api0.swift @@ -44,7 +44,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[834148991] = { return Api.PageBlock.parse_pageBlockAudio($0) } dict[-614138572] = { return Api.account.TmpPassword.parse_tmpPassword($0) } dict[590459437] = { return Api.Photo.parse_photoEmpty($0) } - dict[-1836524247] = { return Api.Photo.parse_photo($0) } + dict[-1673036328] = { return Api.Photo.parse_photo($0) } dict[-1683826688] = { return Api.Chat.parse_chatEmpty($0) } dict[-652419756] = { return Api.Chat.parse_chat($0) } dict[120753115] = { return Api.Chat.parse_chatForbidden($0) } @@ -227,7 +227,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1359533640] = { return Api.messages.FoundStickerSets.parse_foundStickerSets($0) } dict[1158290442] = { return Api.messages.FoundGifs.parse_foundGifs($0) } dict[2086234950] = { return Api.FileLocation.parse_fileLocationUnavailable($0) } - dict[1406570614] = { return Api.FileLocation.parse_fileLocation($0) } + dict[152900075] = { return Api.FileLocation.parse_fileLocation($0) } dict[-1195615476] = { return Api.InputNotifyPeer.parse_inputNotifyPeer($0) } dict[423314455] = { return Api.InputNotifyPeer.parse_inputNotifyUsers($0) } dict[1251338318] = { return Api.InputNotifyPeer.parse_inputNotifyChats($0) } @@ -266,7 +266,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[326715557] = { return Api.auth.PasswordRecovery.parse_passwordRecovery($0) } dict[-1803769784] = { return Api.messages.BotResults.parse_botResults($0) } dict[1928391342] = { return Api.InputDocument.parse_inputDocumentEmpty($0) } - dict[410618194] = { return Api.InputDocument.parse_inputDocument($0) } + dict[448771445] = { return Api.InputDocument.parse_inputDocument($0) } dict[2131196633] = { return Api.contacts.ResolvedPeer.parse_resolvedPeer($0) } dict[-1964327229] = { return Api.SecureData.parse_secureData($0) } dict[-1771768449] = { return Api.InputMedia.parse_inputMediaEmpty($0) } @@ -462,10 +462,10 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1157215293] = { return Api.Message.parse_message($0) } dict[186120336] = { return Api.messages.RecentStickers.parse_recentStickersNotModified($0) } dict[586395571] = { return Api.messages.RecentStickers.parse_recentStickers($0) } - dict[342061462] = { return Api.InputFileLocation.parse_inputFileLocation($0) } dict[-182231723] = { return Api.InputFileLocation.parse_inputEncryptedFileLocation($0) } - dict[1125058340] = { return Api.InputFileLocation.parse_inputDocumentFileLocation($0) } dict[-876089816] = { return Api.InputFileLocation.parse_inputSecureFileLocation($0) } + dict[426148825] = { return Api.InputFileLocation.parse_inputDocumentFileLocation($0) } + dict[-539317279] = { return Api.InputFileLocation.parse_inputFileLocation($0) } dict[286776671] = { return Api.GeoPoint.parse_geoPointEmpty($0) } dict[43446532] = { return Api.GeoPoint.parse_geoPoint($0) } dict[506920429] = { return Api.InputPhoneCall.parse_inputPhoneCall($0) } @@ -640,7 +640,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-1687559349] = { return Api.MessageEntity.parse_messageEntityPhone($0) } dict[1280209983] = { return Api.MessageEntity.parse_messageEntityCashtag($0) } dict[483901197] = { return Api.InputPhoto.parse_inputPhotoEmpty($0) } - dict[-74070332] = { return Api.InputPhoto.parse_inputPhoto($0) } + dict[1001634122] = { return Api.InputPhoto.parse_inputPhoto($0) } dict[-567906571] = { return Api.contacts.TopPeers.parse_topPeersNotModified($0) } dict[1891070632] = { return Api.contacts.TopPeers.parse_topPeers($0) } dict[-1255369827] = { return Api.contacts.TopPeers.parse_topPeersDisabled($0) } @@ -654,7 +654,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-94974410] = { return Api.EncryptedChat.parse_encryptedChat($0) } dict[332848423] = { return Api.EncryptedChat.parse_encryptedChatDiscarded($0) } dict[922273905] = { return Api.Document.parse_documentEmpty($0) } - dict[-2027738169] = { return Api.Document.parse_document($0) } + dict[1498631756] = { return Api.Document.parse_document($0) } dict[-1707344487] = { return Api.messages.HighScores.parse_highScores($0) } dict[-892779534] = { return Api.WebAuthorization.parse_webAuthorization($0) } dict[-805141448] = { return Api.ImportedContact.parse_importedContact($0) } diff --git a/TelegramCore/Api1.swift b/TelegramCore/Api1.swift index cdbdfd8aec..fbfc06aeae 100644 --- a/TelegramCore/Api1.swift +++ b/TelegramCore/Api1.swift @@ -973,7 +973,7 @@ extension Api { } enum Photo: TypeConstructorDescription { case photoEmpty(id: Int64) - case photo(flags: Int32, id: Int64, accessHash: Int64, date: Int32, sizes: [Api.PhotoSize]) + case photo(flags: Int32, id: Int64, accessHash: Int64, fileReference: Buffer, date: Int32, sizes: [Api.PhotoSize]) func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { @@ -983,13 +983,14 @@ extension Api { } serializeInt64(id, buffer: buffer, boxed: false) break - case .photo(let flags, let id, let accessHash, let date, let sizes): + case .photo(let flags, let id, let accessHash, let fileReference, let date, let sizes): if boxed { - buffer.appendInt32(-1836524247) + buffer.appendInt32(-1673036328) } serializeInt32(flags, buffer: buffer, boxed: false) serializeInt64(id, buffer: buffer, boxed: false) serializeInt64(accessHash, buffer: buffer, boxed: false) + serializeBytes(fileReference, buffer: buffer, boxed: false) serializeInt32(date, buffer: buffer, boxed: false) buffer.appendInt32(481674261) buffer.appendInt32(Int32(sizes.count)) @@ -1004,8 +1005,8 @@ extension Api { switch self { case .photoEmpty(let id): return ("photoEmpty", [("id", id)]) - case .photo(let flags, let id, let accessHash, let date, let sizes): - return ("photo", [("flags", flags), ("id", id), ("accessHash", accessHash), ("date", date), ("sizes", sizes)]) + case .photo(let flags, let id, let accessHash, let fileReference, let date, let sizes): + return ("photo", [("flags", flags), ("id", id), ("accessHash", accessHash), ("fileReference", fileReference), ("date", date), ("sizes", sizes)]) } } @@ -1027,19 +1028,22 @@ extension Api { _2 = reader.readInt64() var _3: Int64? _3 = reader.readInt64() - var _4: Int32? - _4 = reader.readInt32() - var _5: [Api.PhotoSize]? + var _4: Buffer? + _4 = parseBytes(reader) + var _5: Int32? + _5 = reader.readInt32() + var _6: [Api.PhotoSize]? if let _ = reader.readInt32() { - _5 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PhotoSize.self) + _6 = Api.parseVector(reader, elementSignature: 0, elementType: Api.PhotoSize.self) } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil let _c5 = _5 != nil - if _c1 && _c2 && _c3 && _c4 && _c5 { - return Api.Photo.photo(flags: _1!, id: _2!, accessHash: _3!, date: _4!, sizes: _5!) + let _c6 = _6 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 { + return Api.Photo.photo(flags: _1!, id: _2!, accessHash: _3!, fileReference: _4!, date: _5!, sizes: _6!) } else { return nil @@ -5439,7 +5443,7 @@ extension Api { } enum FileLocation: TypeConstructorDescription { case fileLocationUnavailable(volumeId: Int64, localId: Int32, secret: Int64) - case fileLocation(dcId: Int32, volumeId: Int64, localId: Int32, secret: Int64) + case fileLocation(dcId: Int32, volumeId: Int64, localId: Int32, secret: Int64, fileReference: Buffer) func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { @@ -5451,14 +5455,15 @@ extension Api { serializeInt32(localId, buffer: buffer, boxed: false) serializeInt64(secret, buffer: buffer, boxed: false) break - case .fileLocation(let dcId, let volumeId, let localId, let secret): + case .fileLocation(let dcId, let volumeId, let localId, let secret, let fileReference): if boxed { - buffer.appendInt32(1406570614) + buffer.appendInt32(152900075) } serializeInt32(dcId, buffer: buffer, boxed: false) serializeInt64(volumeId, buffer: buffer, boxed: false) serializeInt32(localId, buffer: buffer, boxed: false) serializeInt64(secret, buffer: buffer, boxed: false) + serializeBytes(fileReference, buffer: buffer, boxed: false) break } } @@ -5467,8 +5472,8 @@ extension Api { switch self { case .fileLocationUnavailable(let volumeId, let localId, let secret): return ("fileLocationUnavailable", [("volumeId", volumeId), ("localId", localId), ("secret", secret)]) - case .fileLocation(let dcId, let volumeId, let localId, let secret): - return ("fileLocation", [("dcId", dcId), ("volumeId", volumeId), ("localId", localId), ("secret", secret)]) + case .fileLocation(let dcId, let volumeId, let localId, let secret, let fileReference): + return ("fileLocation", [("dcId", dcId), ("volumeId", volumeId), ("localId", localId), ("secret", secret), ("fileReference", fileReference)]) } } @@ -5498,12 +5503,15 @@ extension Api { _3 = reader.readInt32() var _4: Int64? _4 = reader.readInt64() + var _5: Buffer? + _5 = parseBytes(reader) let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil let _c4 = _4 != nil - if _c1 && _c2 && _c3 && _c4 { - return Api.FileLocation.fileLocation(dcId: _1!, volumeId: _2!, localId: _3!, secret: _4!) + let _c5 = _5 != nil + if _c1 && _c2 && _c3 && _c4 && _c5 { + return Api.FileLocation.fileLocation(dcId: _1!, volumeId: _2!, localId: _3!, secret: _4!, fileReference: _5!) } else { return nil @@ -6467,7 +6475,7 @@ extension Api { } enum InputDocument: TypeConstructorDescription { case inputDocumentEmpty - case inputDocument(id: Int64, accessHash: Int64) + case inputDocument(id: Int64, accessHash: Int64, fileReference: Buffer) func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { @@ -6477,12 +6485,13 @@ extension Api { } break - case .inputDocument(let id, let accessHash): + case .inputDocument(let id, let accessHash, let fileReference): if boxed { - buffer.appendInt32(410618194) + buffer.appendInt32(448771445) } serializeInt64(id, buffer: buffer, boxed: false) serializeInt64(accessHash, buffer: buffer, boxed: false) + serializeBytes(fileReference, buffer: buffer, boxed: false) break } } @@ -6491,8 +6500,8 @@ extension Api { switch self { case .inputDocumentEmpty: return ("inputDocumentEmpty", []) - case .inputDocument(let id, let accessHash): - return ("inputDocument", [("id", id), ("accessHash", accessHash)]) + case .inputDocument(let id, let accessHash, let fileReference): + return ("inputDocument", [("id", id), ("accessHash", accessHash), ("fileReference", fileReference)]) } } @@ -6504,10 +6513,13 @@ extension Api { _1 = reader.readInt64() var _2: Int64? _2 = reader.readInt64() + var _3: Buffer? + _3 = parseBytes(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.InputDocument.inputDocument(id: _1!, accessHash: _2!) + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.InputDocument.inputDocument(id: _1!, accessHash: _2!, fileReference: _3!) } else { return nil @@ -11616,21 +11628,13 @@ extension Api { } enum InputFileLocation: TypeConstructorDescription { - case inputFileLocation(volumeId: Int64, localId: Int32, secret: Int64) case inputEncryptedFileLocation(id: Int64, accessHash: Int64) - case inputDocumentFileLocation(id: Int64, accessHash: Int64, version: Int32) case inputSecureFileLocation(id: Int64, accessHash: Int64) + case inputDocumentFileLocation(id: Int64, accessHash: Int64, fileReference: Buffer) + case inputFileLocation(volumeId: Int64, localId: Int32, secret: Int64, fileReference: Buffer) func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { - case .inputFileLocation(let volumeId, let localId, let secret): - if boxed { - buffer.appendInt32(342061462) - } - serializeInt64(volumeId, buffer: buffer, boxed: false) - serializeInt32(localId, buffer: buffer, boxed: false) - serializeInt64(secret, buffer: buffer, boxed: false) - break case .inputEncryptedFileLocation(let id, let accessHash): if boxed { buffer.appendInt32(-182231723) @@ -11638,14 +11642,6 @@ extension Api { serializeInt64(id, buffer: buffer, boxed: false) serializeInt64(accessHash, buffer: buffer, boxed: false) break - case .inputDocumentFileLocation(let id, let accessHash, let version): - if boxed { - buffer.appendInt32(1125058340) - } - serializeInt64(id, buffer: buffer, boxed: false) - serializeInt64(accessHash, buffer: buffer, boxed: false) - serializeInt32(version, buffer: buffer, boxed: false) - break case .inputSecureFileLocation(let id, let accessHash): if boxed { buffer.appendInt32(-876089816) @@ -11653,39 +11649,39 @@ extension Api { serializeInt64(id, buffer: buffer, boxed: false) serializeInt64(accessHash, buffer: buffer, boxed: false) break + case .inputDocumentFileLocation(let id, let accessHash, let fileReference): + if boxed { + buffer.appendInt32(426148825) + } + serializeInt64(id, buffer: buffer, boxed: false) + serializeInt64(accessHash, buffer: buffer, boxed: false) + serializeBytes(fileReference, buffer: buffer, boxed: false) + break + case .inputFileLocation(let volumeId, let localId, let secret, let fileReference): + if boxed { + buffer.appendInt32(-539317279) + } + serializeInt64(volumeId, buffer: buffer, boxed: false) + serializeInt32(localId, buffer: buffer, boxed: false) + serializeInt64(secret, buffer: buffer, boxed: false) + serializeBytes(fileReference, buffer: buffer, boxed: false) + break } } func descriptionFields() -> (String, [(String, Any)]) { switch self { - case .inputFileLocation(let volumeId, let localId, let secret): - return ("inputFileLocation", [("volumeId", volumeId), ("localId", localId), ("secret", secret)]) case .inputEncryptedFileLocation(let id, let accessHash): return ("inputEncryptedFileLocation", [("id", id), ("accessHash", accessHash)]) - case .inputDocumentFileLocation(let id, let accessHash, let version): - return ("inputDocumentFileLocation", [("id", id), ("accessHash", accessHash), ("version", version)]) case .inputSecureFileLocation(let id, let accessHash): return ("inputSecureFileLocation", [("id", id), ("accessHash", accessHash)]) + case .inputDocumentFileLocation(let id, let accessHash, let fileReference): + return ("inputDocumentFileLocation", [("id", id), ("accessHash", accessHash), ("fileReference", fileReference)]) + case .inputFileLocation(let volumeId, let localId, let secret, let fileReference): + return ("inputFileLocation", [("volumeId", volumeId), ("localId", localId), ("secret", secret), ("fileReference", fileReference)]) } } - static func parse_inputFileLocation(_ reader: BufferReader) -> InputFileLocation? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int32? - _2 = reader.readInt32() - var _3: Int64? - _3 = reader.readInt64() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.InputFileLocation.inputFileLocation(volumeId: _1!, localId: _2!, secret: _3!) - } - else { - return nil - } - } static func parse_inputEncryptedFileLocation(_ reader: BufferReader) -> InputFileLocation? { var _1: Int64? _1 = reader.readInt64() @@ -11700,23 +11696,6 @@ extension Api { return nil } } - static func parse_inputDocumentFileLocation(_ reader: BufferReader) -> InputFileLocation? { - var _1: Int64? - _1 = reader.readInt64() - var _2: Int64? - _2 = reader.readInt64() - var _3: Int32? - _3 = reader.readInt32() - let _c1 = _1 != nil - let _c2 = _2 != nil - let _c3 = _3 != nil - if _c1 && _c2 && _c3 { - return Api.InputFileLocation.inputDocumentFileLocation(id: _1!, accessHash: _2!, version: _3!) - } - else { - return nil - } - } static func parse_inputSecureFileLocation(_ reader: BufferReader) -> InputFileLocation? { var _1: Int64? _1 = reader.readInt64() @@ -11731,6 +11710,43 @@ extension Api { return nil } } + static func parse_inputDocumentFileLocation(_ reader: BufferReader) -> InputFileLocation? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int64? + _2 = reader.readInt64() + var _3: Buffer? + _3 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.InputFileLocation.inputDocumentFileLocation(id: _1!, accessHash: _2!, fileReference: _3!) + } + else { + return nil + } + } + static func parse_inputFileLocation(_ reader: BufferReader) -> InputFileLocation? { + var _1: Int64? + _1 = reader.readInt64() + var _2: Int32? + _2 = reader.readInt32() + var _3: Int64? + _3 = reader.readInt64() + var _4: Buffer? + _4 = parseBytes(reader) + let _c1 = _1 != nil + let _c2 = _2 != nil + let _c3 = _3 != nil + let _c4 = _4 != nil + if _c1 && _c2 && _c3 && _c4 { + return Api.InputFileLocation.inputFileLocation(volumeId: _1!, localId: _2!, secret: _3!, fileReference: _4!) + } + else { + return nil + } + } } enum GeoPoint: TypeConstructorDescription { @@ -15755,7 +15771,7 @@ extension Api { } enum InputPhoto: TypeConstructorDescription { case inputPhotoEmpty - case inputPhoto(id: Int64, accessHash: Int64) + case inputPhoto(id: Int64, accessHash: Int64, fileReference: Buffer) func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { @@ -15765,12 +15781,13 @@ extension Api { } break - case .inputPhoto(let id, let accessHash): + case .inputPhoto(let id, let accessHash, let fileReference): if boxed { - buffer.appendInt32(-74070332) + buffer.appendInt32(1001634122) } serializeInt64(id, buffer: buffer, boxed: false) serializeInt64(accessHash, buffer: buffer, boxed: false) + serializeBytes(fileReference, buffer: buffer, boxed: false) break } } @@ -15779,8 +15796,8 @@ extension Api { switch self { case .inputPhotoEmpty: return ("inputPhotoEmpty", []) - case .inputPhoto(let id, let accessHash): - return ("inputPhoto", [("id", id), ("accessHash", accessHash)]) + case .inputPhoto(let id, let accessHash, let fileReference): + return ("inputPhoto", [("id", id), ("accessHash", accessHash), ("fileReference", fileReference)]) } } @@ -15792,10 +15809,13 @@ extension Api { _1 = reader.readInt64() var _2: Int64? _2 = reader.readInt64() + var _3: Buffer? + _3 = parseBytes(reader) let _c1 = _1 != nil let _c2 = _2 != nil - if _c1 && _c2 { - return Api.InputPhoto.inputPhoto(id: _1!, accessHash: _2!) + let _c3 = _3 != nil + if _c1 && _c2 && _c3 { + return Api.InputPhoto.inputPhoto(id: _1!, accessHash: _2!, fileReference: _3!) } else { return nil @@ -15979,7 +15999,7 @@ extension Api { } enum Document: TypeConstructorDescription { case documentEmpty(id: Int64) - case document(id: Int64, accessHash: Int64, date: Int32, mimeType: String, size: Int32, thumb: Api.PhotoSize, dcId: Int32, version: Int32, attributes: [Api.DocumentAttribute]) + case document(id: Int64, accessHash: Int64, fileReference: Buffer, date: Int32, mimeType: String, size: Int32, thumb: Api.PhotoSize, dcId: Int32, attributes: [Api.DocumentAttribute]) func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { @@ -15989,18 +16009,18 @@ extension Api { } serializeInt64(id, buffer: buffer, boxed: false) break - case .document(let id, let accessHash, let date, let mimeType, let size, let thumb, let dcId, let version, let attributes): + case .document(let id, let accessHash, let fileReference, let date, let mimeType, let size, let thumb, let dcId, let attributes): if boxed { - buffer.appendInt32(-2027738169) + buffer.appendInt32(1498631756) } serializeInt64(id, buffer: buffer, boxed: false) serializeInt64(accessHash, buffer: buffer, boxed: false) + serializeBytes(fileReference, buffer: buffer, boxed: false) serializeInt32(date, buffer: buffer, boxed: false) serializeString(mimeType, buffer: buffer, boxed: false) serializeInt32(size, buffer: buffer, boxed: false) thumb.serialize(buffer, true) serializeInt32(dcId, buffer: buffer, boxed: false) - serializeInt32(version, buffer: buffer, boxed: false) buffer.appendInt32(481674261) buffer.appendInt32(Int32(attributes.count)) for item in attributes { @@ -16014,8 +16034,8 @@ extension Api { switch self { case .documentEmpty(let id): return ("documentEmpty", [("id", id)]) - case .document(let id, let accessHash, let date, let mimeType, let size, let thumb, let dcId, let version, let attributes): - return ("document", [("id", id), ("accessHash", accessHash), ("date", date), ("mimeType", mimeType), ("size", size), ("thumb", thumb), ("dcId", dcId), ("version", version), ("attributes", attributes)]) + case .document(let id, let accessHash, let fileReference, let date, let mimeType, let size, let thumb, let dcId, let attributes): + return ("document", [("id", id), ("accessHash", accessHash), ("fileReference", fileReference), ("date", date), ("mimeType", mimeType), ("size", size), ("thumb", thumb), ("dcId", dcId), ("attributes", attributes)]) } } @@ -16035,18 +16055,18 @@ extension Api { _1 = reader.readInt64() var _2: Int64? _2 = reader.readInt64() - var _3: Int32? - _3 = reader.readInt32() - var _4: String? - _4 = parseString(reader) - var _5: Int32? - _5 = reader.readInt32() - var _6: Api.PhotoSize? + var _3: Buffer? + _3 = parseBytes(reader) + var _4: Int32? + _4 = reader.readInt32() + var _5: String? + _5 = parseString(reader) + var _6: Int32? + _6 = reader.readInt32() + var _7: Api.PhotoSize? if let signature = reader.readInt32() { - _6 = Api.parse(reader, signature: signature) as? Api.PhotoSize + _7 = Api.parse(reader, signature: signature) as? Api.PhotoSize } - var _7: Int32? - _7 = reader.readInt32() var _8: Int32? _8 = reader.readInt32() var _9: [Api.DocumentAttribute]? @@ -16063,7 +16083,7 @@ extension Api { let _c8 = _8 != nil let _c9 = _9 != nil if _c1 && _c2 && _c3 && _c4 && _c5 && _c6 && _c7 && _c8 && _c9 { - return Api.Document.document(id: _1!, accessHash: _2!, date: _3!, mimeType: _4!, size: _5!, thumb: _6!, dcId: _7!, version: _8!, attributes: _9!) + return Api.Document.document(id: _1!, accessHash: _2!, fileReference: _3!, date: _4!, mimeType: _5!, size: _6!, thumb: _7!, dcId: _8!, attributes: _9!) } else { return nil diff --git a/TelegramCore/ApiUtils.swift b/TelegramCore/ApiUtils.swift index 3b30690426..9a247a604f 100644 --- a/TelegramCore/ApiUtils.swift +++ b/TelegramCore/ApiUtils.swift @@ -5,6 +5,101 @@ import Foundation import Postbox #endif +public enum PeerReference: PostboxCoding, Hashable, Equatable { + case user(id: Int32, accessHash: Int64) + case group(id: Int32) + case channel(id: Int32, accessHash: Int64) + + public init(decoder: PostboxDecoder) { + switch decoder.decodeInt32ForKey("_r", orElse: 0) { + case 0: + self = .user(id: decoder.decodeInt32ForKey("i", orElse: 0), accessHash: decoder.decodeInt64ForKey("h", orElse: 0)) + case 1: + self = .group(id: decoder.decodeInt32ForKey("i", orElse: 0)) + case 2: + self = .channel(id: decoder.decodeInt32ForKey("i", orElse: 0), accessHash: decoder.decodeInt64ForKey("h", orElse: 0)) + default: + assertionFailure() + self = .user(id: 0, accessHash: 0) + } + } + + public func encode(_ encoder: PostboxEncoder) { + switch self { + case let .user(id, accessHash): + encoder.encodeInt32(0, forKey: "_r") + encoder.encodeInt32(id, forKey: "i") + encoder.encodeInt64(accessHash, forKey: "h") + case let .group(id): + encoder.encodeInt32(1, forKey: "_r") + encoder.encodeInt32(id, forKey: "i") + case let .channel(id, accessHash): + encoder.encodeInt32(2, forKey: "_r") + encoder.encodeInt32(id, forKey: "i") + encoder.encodeInt64(accessHash, forKey: "h") + } + } + + var id: PeerId { + switch self { + case let .user(id, _): + return PeerId(namespace: Namespaces.Peer.CloudUser, id: id) + case let .group(id): + return PeerId(namespace: Namespaces.Peer.CloudGroup, id: id) + case let .channel(id, _): + return PeerId(namespace: Namespaces.Peer.CloudChannel, id: id) + } + } + + public init?(_ peer: Peer) { + switch peer { + case let user as TelegramUser: + if let accessHash = user.accessHash { + self = .user(id: user.id.id, accessHash: accessHash) + } else { + return nil + } + case let group as TelegramGroup: + self = .group(id: group.id.id) + case let channel as TelegramChannel: + if let accessHash = channel.accessHash { + self = .channel(id: channel.id.id, accessHash: accessHash) + } else { + return nil + } + default: + return nil + } + } + + var inputPeer: Api.InputPeer { + switch self { + case let .user(id, accessHash): + return .inputPeerUser(userId: id, accessHash: accessHash) + case let .group(id): + return .inputPeerChat(chatId: id) + case let .channel(id, accessHash): + return .inputPeerChannel(channelId: id, accessHash: accessHash) + } + } + + var inputUser: Api.InputUser? { + if case let .user(id, accessHash) = self { + return .inputUser(userId: id, accessHash: accessHash) + } else { + return nil + } + } + + var inputChannel: Api.InputChannel? { + if case let .channel(id, accessHash) = self { + return .inputChannel(channelId: id, accessHash: accessHash) + } else { + return nil + } + } +} + func apiInputPeer(_ peer: Peer) -> Api.InputPeer? { switch peer { case let user as TelegramUser where user.accessHash != nil: diff --git a/TelegramCore/ApplyMaxReadIndexInteractively.swift b/TelegramCore/ApplyMaxReadIndexInteractively.swift index 998a1e4881..966552ebed 100644 --- a/TelegramCore/ApplyMaxReadIndexInteractively.swift +++ b/TelegramCore/ApplyMaxReadIndexInteractively.swift @@ -130,7 +130,7 @@ public func togglePeerUnreadMarkInteractively(postbox: Postbox, viewTracker: Acc let _ = transaction.applyInteractiveReadMaxIndex(index) } viewTracker.updateMarkAllMentionsSeen(peerId: peerId) - } else { + } else if namespace == Namespaces.Message.Cloud || namespace == Namespaces.Message.SecretIncoming { transaction.applyMarkUnread(peerId: peerId, namespace: namespace, value: true, interactive: true) } } diff --git a/TelegramCore/ApplyUpdateMessage.swift b/TelegramCore/ApplyUpdateMessage.swift index 072a2f5989..d86d864f51 100644 --- a/TelegramCore/ApplyUpdateMessage.swift +++ b/TelegramCore/ApplyUpdateMessage.swift @@ -20,7 +20,7 @@ func applyMediaResourceChanges(from: Media, to: Media, postbox: Postbox) { if let fromPreview = smallestImageRepresentation(fromFile.previewRepresentations), let toPreview = smallestImageRepresentation(toFile.previewRepresentations) { postbox.mediaBox.moveResourceData(from: fromPreview.resource.id, to: toPreview.resource.id) } - if fromFile.size == toFile.size && fromFile.mimeType == toFile.mimeType { + if (fromFile.size == toFile.size || fromFile.resource.size == toFile.resource.size) && fromFile.mimeType == toFile.mimeType { postbox.mediaBox.moveResourceData(from: fromFile.resource.id, to: toFile.resource.id) } } diff --git a/TelegramCore/CachedStickerPack.swift b/TelegramCore/CachedStickerPack.swift index 4c9ff64edd..de7307db2a 100644 --- a/TelegramCore/CachedStickerPack.swift +++ b/TelegramCore/CachedStickerPack.swift @@ -50,6 +50,10 @@ public enum CachedStickerPackResult { case result(StickerPackCollectionInfo, [ItemCollectionItem], Bool) } +func cacheStickerPack(transaction: Transaction, info: StickerPackCollectionInfo, items: [ItemCollectionItem]) { + transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedStickerPacks, key: CachedStickerPack.cacheKey(info.id)), entry: CachedStickerPack(info: info, items: items.map { $0 as! StickerPackItem }, hash: info.hash), collectionSpec: collectionSpec) +} + public func cachedStickerPack(postbox: Postbox, network: Network, reference: StickerPackReference) -> Signal { return postbox.transaction { transaction -> Signal in let namespace = Namespaces.ItemCollection.CloudStickerPacks @@ -72,25 +76,27 @@ public func cachedStickerPack(postbox: Postbox, network: Network, reference: Sti var signal = current if loadRemote { - let appliedRemote = remoteStickerPack(network: network, reference: reference) - |> mapToSignal { result -> Signal in - return postbox.transaction { transaction -> CachedStickerPackResult in - if let result = result { - transaction.putItemCacheEntry(id: ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.cachedStickerPacks, key: CachedStickerPack.cacheKey(result.0.id)), entry: CachedStickerPack(info: result.0, items: result.1.map { $0 as! StickerPackItem }, hash: result.0.hash), collectionSpec: collectionSpec) - - let currentInfo = transaction.getItemCollectionInfo(collectionId: result.0.id) as? StickerPackCollectionInfo - - return .result(result.0, result.1, currentInfo != nil) - } else { - return .none - } + let appliedRemote = updatedRemoteStickerPack(postbox: postbox, network: network, reference: reference) + |> mapToSignal { result -> Signal in + return postbox.transaction { transaction -> CachedStickerPackResult in + if let result = result { + cacheStickerPack(transaction: transaction, info: result.0, items: result.1) + + let currentInfo = transaction.getItemCollectionInfo(collectionId: result.0.id) as? StickerPackCollectionInfo + + return .result(result.0, result.1, currentInfo != nil) + } else { + return .none } } + } - signal = signal |> then(appliedRemote) + signal = signal + |> then(appliedRemote) } return signal } - } |> switchToLatest + } + |> switchToLatest } diff --git a/TelegramCore/CheckPeerChatServiceActions.swift b/TelegramCore/CheckPeerChatServiceActions.swift index bb486eff97..3cb76a539b 100644 --- a/TelegramCore/CheckPeerChatServiceActions.swift +++ b/TelegramCore/CheckPeerChatServiceActions.swift @@ -9,6 +9,8 @@ import Foundation public func checkPeerChatServiceActions(postbox: Postbox, peerId: PeerId) -> Signal { return postbox.transaction { transaction -> Void in + transaction.applyMarkUnread(peerId: peerId, namespace: Namespaces.Message.SecretIncoming, value: false, interactive: true) + if peerId.namespace == Namespaces.Peer.SecretChat { if let state = transaction.getPeerChatState(peerId) as? SecretChatState { let updatedState = secretChatCheckLayerNegotiationIfNeeded(transaction: transaction, peerId: peerId, state: state) diff --git a/TelegramCore/CloudFileMediaResource.swift b/TelegramCore/CloudFileMediaResource.swift index 0bd42d6840..3704f10ce5 100644 --- a/TelegramCore/CloudFileMediaResource.swift +++ b/TelegramCore/CloudFileMediaResource.swift @@ -6,7 +6,7 @@ import Foundation #endif protocol TelegramCloudMediaResource: TelegramMediaResource { - var apiInputLocation: Api.InputFileLocation { get } + func apiInputLocation(fileReference: Data?) -> Api.InputFileLocation? } protocol TelegramMultipartFetchableResource: TelegramMediaResource { @@ -49,21 +49,27 @@ public class CloudFileMediaResource: TelegramCloudMediaResource, TelegramMultipa public let localId: Int32 public let secret: Int64 public let size: Int? + public let fileReference: Data? public var id: MediaResourceId { return CloudFileMediaResourceId(datacenterId: self.datacenterId, volumeId: self.volumeId, localId: self.localId, secret: self.secret) } - var apiInputLocation: Api.InputFileLocation { - return Api.InputFileLocation.inputFileLocation(volumeId: self.volumeId, localId: self.localId, secret: self.secret) + func apiInputLocation(fileReference: Data?) -> Api.InputFileLocation? { + if let fileReference = fileReference { + return Api.InputFileLocation.inputFileLocation(volumeId: self.volumeId, localId: self.localId, secret: self.secret, fileReference: Buffer(data: fileReference)) + } else { + return nil + } } - public init(datacenterId: Int, volumeId: Int64, localId: Int32, secret: Int64, size: Int?) { + public init(datacenterId: Int, volumeId: Int64, localId: Int32, secret: Int64, size: Int?, fileReference: Data?) { self.datacenterId = datacenterId self.volumeId = volumeId self.localId = localId self.secret = secret self.size = size + self.fileReference = fileReference } public required init(decoder: PostboxDecoder) { @@ -76,6 +82,7 @@ public class CloudFileMediaResource: TelegramCloudMediaResource, TelegramMultipa } else { self.size = nil } + self.fileReference = decoder.decodeBytesForKey("fr")?.makeData() } public func encode(_ encoder: PostboxEncoder) { @@ -88,11 +95,16 @@ public class CloudFileMediaResource: TelegramCloudMediaResource, TelegramMultipa } else { encoder.encodeNil(forKey: "n") } + if let fileReference = self.fileReference { + encoder.encodeBytes(MemoryBuffer(data: fileReference), forKey: "fr") + } else { + encoder.encodeNil(forKey: "fr") + } } public func isEqual(to: TelegramMediaResource) -> Bool { if let to = to as? CloudFileMediaResource { - return self.datacenterId == to.datacenterId && self.volumeId == to.volumeId && self.localId == to.localId && self.secret == to.secret && self.size == to.size + return self.datacenterId == to.datacenterId && self.volumeId == to.volumeId && self.localId == to.localId && self.secret == to.secret && self.size == to.size && self.fileReference == to.fileReference } else { return false } @@ -102,16 +114,14 @@ public class CloudFileMediaResource: TelegramCloudMediaResource, TelegramMultipa public struct CloudDocumentMediaResourceId: MediaResourceId { let datacenterId: Int let fileId: Int64 - let accessHash: Int64 - init(datacenterId: Int, fileId: Int64, accessHash: Int64) { + init(datacenterId: Int, fileId: Int64) { self.datacenterId = datacenterId self.fileId = fileId - self.accessHash = accessHash } public var uniqueId: String { - return "telegram-cloud-document-\(self.datacenterId)-\(self.fileId)-\(self.accessHash)" + return "telegram-cloud-document-\(self.datacenterId)-\(self.fileId)" } public var hashValue: Int { @@ -120,7 +130,7 @@ public struct CloudDocumentMediaResourceId: MediaResourceId { public func isEqual(to: MediaResourceId) -> Bool { if let to = to as? CloudDocumentMediaResourceId { - return self.datacenterId == to.datacenterId && self.fileId == to.fileId && self.accessHash == to.accessHash + return self.datacenterId == to.datacenterId && self.fileId == to.fileId } else { return false } @@ -132,20 +142,26 @@ public class CloudDocumentMediaResource: TelegramCloudMediaResource, TelegramMul let fileId: Int64 public let accessHash: Int64 public let size: Int? + public let fileReference: Data? public var id: MediaResourceId { - return CloudDocumentMediaResourceId(datacenterId: self.datacenterId, fileId: self.fileId, accessHash: self.accessHash) + return CloudDocumentMediaResourceId(datacenterId: self.datacenterId, fileId: self.fileId) } - var apiInputLocation: Api.InputFileLocation { - return Api.InputFileLocation.inputDocumentFileLocation(id: self.fileId, accessHash: self.accessHash, version: 0) + func apiInputLocation(fileReference: Data?) -> Api.InputFileLocation? { + if let fileReference = fileReference { + return Api.InputFileLocation.inputDocumentFileLocation(id: self.fileId, accessHash: self.accessHash, fileReference: Buffer(data: fileReference)) + } else { + return nil + } } - public init(datacenterId: Int, fileId: Int64, accessHash: Int64, size: Int?) { + public init(datacenterId: Int, fileId: Int64, accessHash: Int64, size: Int?, fileReference: Data?) { self.datacenterId = datacenterId self.fileId = fileId self.accessHash = accessHash self.size = size + self.fileReference = fileReference } public required init(decoder: PostboxDecoder) { @@ -157,6 +173,7 @@ public class CloudDocumentMediaResource: TelegramCloudMediaResource, TelegramMul } else { self.size = nil } + self.fileReference = decoder.decodeBytesForKey("fr")?.makeData() } public func encode(_ encoder: PostboxEncoder) { @@ -168,11 +185,16 @@ public class CloudDocumentMediaResource: TelegramCloudMediaResource, TelegramMul } else { encoder.encodeNil(forKey: "n") } + if let fileReference = self.fileReference { + encoder.encodeBytes(MemoryBuffer(data: fileReference), forKey: "fr") + } else { + encoder.encodeNil(forKey: "fr") + } } public func isEqual(to: TelegramMediaResource) -> Bool { if let to = to as? CloudDocumentMediaResource { - return self.datacenterId == to.datacenterId && self.fileId == to.fileId && self.accessHash == to.accessHash && self.size == to.size + return self.datacenterId == to.datacenterId && self.fileId == to.fileId && self.accessHash == to.accessHash && self.size == to.size && self.fileReference == to.fileReference } else { return false } @@ -463,7 +485,7 @@ public struct SecretFileMediaResource: TelegramCloudMediaResource, TelegramMulti public let datacenterId: Int public let key: SecretFileEncryptionKey - var apiInputLocation: Api.InputFileLocation { + func apiInputLocation(fileReference: Data?) -> Api.InputFileLocation? { return .inputEncryptedFileLocation(id: self.fileId, accessHash: self.accessHash) } @@ -560,8 +582,8 @@ public final class EmptyMediaResource: TelegramMediaResource { func mediaResourceFromApiFileLocation(_ fileLocation: Api.FileLocation, size: Int?) -> TelegramMediaResource? { switch fileLocation { - case let .fileLocation(dcId, volumeId, localId, secret): - return CloudFileMediaResource(datacenterId: Int(dcId), volumeId: volumeId, localId: localId, secret: secret, size: size) + case let .fileLocation(dcId, volumeId, localId, secret, fileReference): + return CloudFileMediaResource(datacenterId: Int(dcId), volumeId: volumeId, localId: localId, secret: secret, size: size, fileReference: fileReference.makeData()) case .fileLocationUnavailable: return nil } diff --git a/TelegramCore/EnqueueMessage.swift b/TelegramCore/EnqueueMessage.swift index c11c7773aa..d56e422468 100644 --- a/TelegramCore/EnqueueMessage.swift +++ b/TelegramCore/EnqueueMessage.swift @@ -73,9 +73,9 @@ private func filterMessageAttributesForForwardedMessage(_ attributes: [MessageAt } } -func opportunisticallyTransformMessageWithMedia(network: Network, postbox: Postbox, transformOutgoingMessageMedia: TransformOutgoingMessageMedia, media: Media, userInteractive: Bool) -> Signal { - return transformOutgoingMessageMedia(postbox, network, media, userInteractive) - |> timeout(2.0, queue: Queue.concurrentDefaultQueue(), alternate: .single(nil)) +func opportunisticallyTransformMessageWithMedia(network: Network, postbox: Postbox, transformOutgoingMessageMedia: TransformOutgoingMessageMedia, mediaReference: AnyMediaReference, userInteractive: Bool) -> Signal { + return transformOutgoingMessageMedia(postbox, network, mediaReference, userInteractive) + |> timeout(2.0, queue: Queue.concurrentDefaultQueue(), alternate: .single(nil)) } private func opportunisticallyTransformOutgoingMedia(network: Network, postbox: Postbox, transformOutgoingMessageMedia: TransformOutgoingMessageMedia, messages: [EnqueueMessage], userInteractive: Bool) -> Signal<[(Bool, EnqueueMessage)], NoError> { @@ -101,9 +101,9 @@ private func opportunisticallyTransformOutgoingMedia(network: Network, postbox: switch message { case let .message(text, attributes, media, replyToMessageId, localGroupingKey): if let media = media { - signals.append(opportunisticallyTransformMessageWithMedia(network: network, postbox: postbox, transformOutgoingMessageMedia: transformOutgoingMessageMedia, media: media, userInteractive: userInteractive) |> map { result -> (Bool, EnqueueMessage) in + signals.append(opportunisticallyTransformMessageWithMedia(network: network, postbox: postbox, transformOutgoingMessageMedia: transformOutgoingMessageMedia, mediaReference: .standalone(media: media), userInteractive: userInteractive) |> map { result -> (Bool, EnqueueMessage) in if let result = result { - return (true, .message(text: text, attributes: attributes, media: result, replyToMessageId: replyToMessageId, localGroupingKey: localGroupingKey)) + return (true, .message(text: text, attributes: attributes, media: result.media, replyToMessageId: replyToMessageId, localGroupingKey: localGroupingKey)) } else { return (false, .message(text: text, attributes: attributes, media: media, replyToMessageId: replyToMessageId, localGroupingKey: localGroupingKey)) } @@ -187,6 +187,9 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId, if case let .message(desc) = message, let replyToMessageId = desc.replyToMessageId, replyToMessageId.peerId != peerId { if let replyMessage = transaction.getMessage(replyToMessageId) { var canBeForwarded = true + if replyMessage.id.namespace != Namespaces.Message.Cloud { + canBeForwarded = false + } inner: for media in replyMessage.media { if media is TelegramMediaAction { canBeForwarded = false @@ -337,7 +340,7 @@ func enqueueMessages(transaction: Transaction, account: Account, peerId: PeerId, var forwardInfo: StoreMessageForwardInfo? - if peerId.namespace != Namespaces.Peer.SecretChat { + if sourceMessage.id.namespace == Namespaces.Message.Cloud && peerId.namespace != Namespaces.Peer.SecretChat { attributes.append(ForwardSourceInfoAttribute(messageId: sourceMessage.id)) if peerId == account.peerId { diff --git a/TelegramCore/Fetch.swift b/TelegramCore/Fetch.swift index 2dd7e20b3a..666f5aaeb9 100644 --- a/TelegramCore/Fetch.swift +++ b/TelegramCore/Fetch.swift @@ -9,8 +9,8 @@ import SwiftSignalKit import Photos #endif -private func fetchCloudMediaLocation(account: Account, resource: TelegramMediaResource, datacenterId: Int, size: Int?, ranges: Signal, tag: MediaResourceFetchTag?) -> Signal { - return multipartFetch(account: account, resource: resource, datacenterId: datacenterId, size: size, ranges: ranges, tag: tag) +private func fetchCloudMediaLocation(account: Account, resource: TelegramMediaResource, datacenterId: Int, size: Int?, ranges: Signal, parameters: MediaResourceFetchParameters?) -> Signal { + return multipartFetch(account: account, resource: resource, datacenterId: datacenterId, size: size, ranges: ranges, parameters: parameters) } private func fetchLocalFileResource(path: String, move: Bool) -> Signal { @@ -30,17 +30,17 @@ private func fetchLocalFileResource(path: String, move: Bool) -> Signal, tag: MediaResourceFetchTag?) -> Signal? { +func fetchResource(account: Account, resource: MediaResource, ranges: Signal, parameters: MediaResourceFetchParameters?) -> Signal? { if let _ = resource as? EmptyMediaResource { return .single(.reset) |> then(.never()) } else if let secretFileResource = resource as? SecretFileMediaResource { - return .single(.dataPart(resourceOffset: 0, data: Data(), range: 0 ..< 0, complete: false)) |> then(fetchSecretFileResource(account: account, resource: secretFileResource, ranges: ranges, tag: tag)) + return .single(.dataPart(resourceOffset: 0, data: Data(), range: 0 ..< 0, complete: false)) |> then(fetchSecretFileResource(account: account, resource: secretFileResource, ranges: ranges, parameters: parameters)) } else if let cloudResource = resource as? TelegramMultipartFetchableResource { - return .single(.dataPart(resourceOffset: 0, data: Data(), range: 0 ..< 0, complete: false)) |> then(fetchCloudMediaLocation(account: account, resource: cloudResource, datacenterId: cloudResource.datacenterId, size: resource.size == 0 ? nil : resource.size, ranges: ranges, tag: tag)) + return .single(.dataPart(resourceOffset: 0, data: Data(), range: 0 ..< 0, complete: false)) |> then(fetchCloudMediaLocation(account: account, resource: cloudResource, datacenterId: cloudResource.datacenterId, size: resource.size == 0 ? nil : resource.size, ranges: ranges, parameters: parameters)) } else if let webFileResource = resource as? WebFileReferenceMediaResource { return currentWebDocumentsHostDatacenterId(postbox: account.postbox, isTestingEnvironment: account.testingEnvironment) |> mapToSignal { datacenterId -> Signal in - return .single(.dataPart(resourceOffset: 0, data: Data(), range: 0 ..< 0, complete: false)) |> then(fetchCloudMediaLocation(account: account, resource: webFileResource, datacenterId: Int(datacenterId), size: resource.size == 0 ? nil : resource.size, ranges: ranges, tag: tag)) + return .single(.dataPart(resourceOffset: 0, data: Data(), range: 0 ..< 0, complete: false)) |> then(fetchCloudMediaLocation(account: account, resource: webFileResource, datacenterId: Int(datacenterId), size: resource.size == 0 ? nil : resource.size, ranges: ranges, parameters: parameters)) } } else if let localFileResource = resource as? LocalFileReferenceMediaResource { return fetchLocalFileResource(path: localFileResource.localFilePath, move: localFileResource.isUniquelyReferencedTemporaryFile) diff --git a/TelegramCore/FetchSecretFileResource.swift b/TelegramCore/FetchSecretFileResource.swift index 1dd1880548..2e3350ad2f 100644 --- a/TelegramCore/FetchSecretFileResource.swift +++ b/TelegramCore/FetchSecretFileResource.swift @@ -9,6 +9,6 @@ import Foundation import MtProtoKitDynamic #endif -func fetchSecretFileResource(account: Account, resource: SecretFileMediaResource, ranges: Signal, tag: MediaResourceFetchTag?) -> Signal { - return multipartFetch(account: account, resource: resource, datacenterId: resource.datacenterId, size: resource.size, ranges: ranges, tag: tag, encryptionKey: resource.key, decryptedSize: resource.decryptedSize) +func fetchSecretFileResource(account: Account, resource: SecretFileMediaResource, ranges: Signal, parameters: MediaResourceFetchParameters?) -> Signal { + return multipartFetch(account: account, resource: resource, datacenterId: resource.datacenterId, size: resource.size, ranges: ranges, parameters: parameters, encryptionKey: resource.key, decryptedSize: resource.decryptedSize) } diff --git a/TelegramCore/FetchedMediaResource.swift b/TelegramCore/FetchedMediaResource.swift new file mode 100644 index 0000000000..40ec998794 --- /dev/null +++ b/TelegramCore/FetchedMediaResource.swift @@ -0,0 +1,774 @@ +import Foundation +#if os(macOS) +import PostboxMac +import SwiftSignalKitMac +#else +import Postbox +import SwiftSignalKit +#endif + +public struct MessageReference: PostboxCoding, Hashable, Equatable { + let content: MessageReferenceContent + + public init(_ message: Message) { + if let peer = message.peers[message.id.peerId], let inputPeer = PeerReference(peer) { + self.content = .message(peer: inputPeer, id: message.id) + } else { + self.content = .none + } + } + + public init(decoder: PostboxDecoder) { + self.content = decoder.decodeObjectForKey("c", decoder: { MessageReferenceContent(decoder: $0) }) as! MessageReferenceContent + } + + public func encode(_ encoder: PostboxEncoder) { + encoder.encodeObject(self.content, forKey: "c") + } +} + +enum MessageReferenceContent: PostboxCoding, Hashable, Equatable { + case none + case message(peer: PeerReference, id: MessageId) + + init(decoder: PostboxDecoder) { + switch decoder.decodeInt32ForKey("_r", orElse: 0) { + case 0: + self = .none + case 1: + self = .message(peer: decoder.decodeObjectForKey("p", decoder: { PeerReference(decoder: $0) }) as! PeerReference, id: MessageId(peerId: PeerId(decoder.decodeInt64ForKey("i.p", orElse: 0)), namespace: decoder.decodeInt32ForKey("i.n", orElse: 0), id: decoder.decodeInt32ForKey("i.i", orElse: 0))) + default: + assertionFailure() + self = .none + } + } + + func encode(_ encoder: PostboxEncoder) { + switch self { + case .none: + encoder.encodeInt32(0, forKey: "_r") + case let .message(peer, id): + encoder.encodeInt32(1, forKey: "_r") + encoder.encodeObject(peer, forKey: "p") + encoder.encodeInt64(id.peerId.toInt64(), forKey: "i.p") + encoder.encodeInt32(id.namespace, forKey: "i.n") + encoder.encodeInt32(id.id, forKey: "i.i") + } + } +} + +public struct WebpageReference: PostboxCoding, Hashable, Equatable { + let content: WebpageReferenceContent + + public init(_ webPage: TelegramMediaWebpage) { + if case let .Loaded(content) = webPage.content { + self.content = .webPage(id: webPage.webpageId.id, url: content.url) + } else { + self.content = .none + } + } + + public init(decoder: PostboxDecoder) { + self.content = decoder.decodeObjectForKey("c", decoder: { WebpageReferenceContent(decoder: $0) }) as! WebpageReferenceContent + } + + public func encode(_ encoder: PostboxEncoder) { + encoder.encodeObject(self.content, forKey: "c") + } +} + +enum WebpageReferenceContent: PostboxCoding, Hashable, Equatable { + case none + case webPage(id: Int64, url: String) + + init(decoder: PostboxDecoder) { + switch decoder.decodeInt32ForKey("_r", orElse: 0) { + case 0: + self = .none + case 1: + self = .webPage(id: decoder.decodeInt64ForKey("i", orElse: 0), url: decoder.decodeStringForKey("u", orElse: "")) + default: + assertionFailure() + self = .none + } + } + + func encode(_ encoder: PostboxEncoder) { + switch self { + case .none: + encoder.encodeInt32(0, forKey: "_r") + case let .webPage(id, url): + encoder.encodeInt32(1, forKey: "_r") + encoder.encodeInt64(id, forKey: "i") + encoder.encodeString(url, forKey: "u") + } + } +} + +public enum AnyMediaReference: Equatable { + case standalone(media: Media) + case message(message: MessageReference, media: Media) + case webPage(webPage: WebpageReference, media: Media) + case stickerPack(stickerPack: StickerPackReference, media: Media) + case savedGif(media: Media) + + public static func ==(lhs: AnyMediaReference, rhs: AnyMediaReference) -> Bool { + switch lhs { + case let .standalone(lhsMedia): + if case let .standalone(rhsMedia) = rhs, lhsMedia.isEqual(rhsMedia) { + return true + } else { + return false + } + case let .message(lhsMessage, lhsMedia): + if case let .message(rhsMessage, rhsMedia) = rhs, lhsMessage == rhsMessage, lhsMedia.isEqual(rhsMedia) { + return true + } else { + return false + } + case let .webPage(lhsWebPage, lhsMedia): + if case let .webPage(rhsWebPage, rhsMedia) = rhs, lhsWebPage == rhsWebPage, lhsMedia.isEqual(rhsMedia) { + return true + } else { + return false + } + case let .stickerPack(lhsStickerPack, lhsMedia): + if case let .stickerPack(rhsStickerPack, rhsMedia) = rhs, lhsStickerPack == rhsStickerPack, lhsMedia.isEqual(rhsMedia) { + return true + } else { + return false + } + case let .savedGif(lhsMedia): + if case let .savedGif(rhsMedia) = rhs, lhsMedia.isEqual(rhsMedia) { + return true + } else { + return false + } + } + } + + public func concrete(_ type: T.Type) -> MediaReference? { + switch self { + case let .standalone(media): + if let media = media as? T { + return .standalone(media: media) + } + case let .message(message, media): + if let media = media as? T { + return .message(message: message, media: media) + } + case let .webPage(webPage, media): + if let media = media as? T { + return .webPage(webPage: webPage, media: media) + } + case let .stickerPack(stickerPack, media): + if let media = media as? T { + return .stickerPack(stickerPack: stickerPack, media: media) + } + case let .savedGif(media): + if let media = media as? T { + return .savedGif(media: media) + } + } + return nil + } + + public var media: Media { + switch self { + case let .standalone(media): + return media + case let .message(_, media): + return media + case let .webPage(_, media): + return media + case let .stickerPack(_, media): + return media + case let .savedGif(media): + return media + } + } + + public func resourceReference(_ resource: MediaResource) -> MediaResourceReference { + return .media(media: self, resource: resource) + } +} + +public enum MediaReference { + private enum CodingCase: Int32 { + case standalone + case message + case webPage + case stickerPack + case savedGif + } + + case standalone(media: T) + case message(message: MessageReference, media: T) + case webPage(webPage: WebpageReference, media: T) + case stickerPack(stickerPack: StickerPackReference, media: T) + case savedGif(media: T) + + init?(decoder: PostboxDecoder) { + guard let caseIdValue = decoder.decodeOptionalInt32ForKey("_r"), let caseId = CodingCase(rawValue: caseIdValue) else { + return nil + } + switch caseId { + case .standalone: + guard let media = decoder.decodeObjectForKey("m") as? T else { + return nil + } + self = .standalone(media: media) + case .message: + let message = decoder.decodeObjectForKey("msg", decoder: { MessageReference(decoder: $0) }) as! MessageReference + guard let media = decoder.decodeObjectForKey("m") as? T else { + return nil + } + self = .message(message: message, media: media) + case .webPage: + let webPage = decoder.decodeObjectForKey("wpg", decoder: { WebpageReference(decoder: $0) }) as! WebpageReference + guard let media = decoder.decodeObjectForKey("m") as? T else { + return nil + } + self = .webPage(webPage: webPage, media: media) + case .stickerPack: + let stickerPack = decoder.decodeObjectForKey("spk", decoder: { StickerPackReference(decoder: $0) }) as! StickerPackReference + guard let media = decoder.decodeObjectForKey("m") as? T else { + return nil + } + self = .stickerPack(stickerPack: stickerPack, media: media) + case .savedGif: + guard let media = decoder.decodeObjectForKey("m") as? T else { + return nil + } + self = .savedGif(media: media) + } + } + + func encode(_ encoder: PostboxEncoder) { + switch self { + case let .standalone(media): + encoder.encodeInt32(CodingCase.standalone.rawValue, forKey: "_r") + encoder.encodeObject(media, forKey: "m") + case let .message(message, media): + encoder.encodeInt32(CodingCase.message.rawValue, forKey: "_r") + encoder.encodeObject(message, forKey: "msg") + encoder.encodeObject(media, forKey: "m") + case let .webPage(webPage, media): + encoder.encodeInt32(CodingCase.webPage.rawValue, forKey: "_r") + encoder.encodeObject(webPage, forKey: "wpg") + encoder.encodeObject(media, forKey: "m") + case let .stickerPack(stickerPack, media): + encoder.encodeInt32(CodingCase.stickerPack.rawValue, forKey: "_r") + encoder.encodeObject(stickerPack, forKey: "spk") + encoder.encodeObject(media, forKey: "m") + case let .savedGif(media): + encoder.encodeObject(media, forKey: "m") + } + } + + public var abstract: AnyMediaReference { + switch self { + case let .standalone(media): + return .standalone(media: media) + case let .message(message, media): + return .message(message: message, media: media) + case let .webPage(webPage, media): + return .webPage(webPage: webPage, media: media) + case let .stickerPack(stickerPack, media): + return .stickerPack(stickerPack: stickerPack, media: media) + case let .savedGif(media): + return .savedGif(media: media) + } + } + + public var media: T { + switch self { + case let .standalone(media): + return media + case let .message(_, media): + return media + case let .webPage(_, media): + return media + case let .stickerPack(_, media): + return media + case let .savedGif(media): + return media + } + } + + public func resourceReference(_ resource: MediaResource) -> MediaResourceReference { + return .media(media: self.abstract, resource: resource) + } +} + +public typealias FileMediaReference = MediaReference +public typealias ImageMediaReference = MediaReference + +public enum MediaResourceReference { + case media(media: AnyMediaReference, resource: MediaResource) + case standalone(resource: MediaResource) + case avatar(peer: PeerReference, resource: MediaResource) + case wallpaper(resource: MediaResource) + + public var resource: MediaResource { + switch self { + case let .media(_, resource): + return resource + case let .standalone(resource): + return resource + case let .avatar(_, resource): + return resource + case let .wallpaper(resource): + return resource + } + } +} + +extension MediaResourceReference { + var apiFileReference: Data? { + if let resource = self.resource as? CloudFileMediaResource { + return resource.fileReference + } else if let resource = self.resource as? CloudDocumentMediaResource { + return resource.fileReference + } else { + return nil + } + } +} + +final class TelegramCloudMediaResourceFetchInfo: MediaResourceFetchInfo { + let reference: MediaResourceReference + + init(reference: MediaResourceReference) { + self.reference = reference + } +} + +public func fetchedMediaResource(postbox: Postbox, reference: MediaResourceReference, range: Range? = nil, statsCategory: MediaResourceStatsCategory = .generic, reportResultStatus: Bool = false) -> Signal { + if let range = range { + return postbox.mediaBox.fetchedResourceData(reference.resource, in: range, parameters: MediaResourceFetchParameters(tag: TelegramMediaResourceFetchTag(statsCategory: statsCategory), info: TelegramCloudMediaResourceFetchInfo(reference: reference))) + |> map { _ in .local } + } else { + return postbox.mediaBox.fetchedResource(reference.resource, parameters: MediaResourceFetchParameters(tag: TelegramMediaResourceFetchTag(statsCategory: statsCategory), info: TelegramCloudMediaResourceFetchInfo(reference: reference)), implNext: reportResultStatus) + } +} + +enum RevalidateMediaReferenceError { + case generic +} + +public func stickerPackFileReference(_ file: TelegramMediaFile) -> FileMediaReference { + for attribute in file.attributes { + if case let .Sticker(sticker) = attribute, let stickerPack = sticker.packReference { + return .stickerPack(stickerPack: stickerPack, media: file) + } + } + return .standalone(media: file) +} + +private func findMediaResource(media: Media, resource: MediaResource) -> MediaResource? { + if let image = media as? TelegramMediaImage { + for representation in image.representations { + if representation.resource.id.isEqual(to: resource.id) { + return representation.resource + } + } + } else if let file = media as? TelegramMediaFile { + if file.resource.id.isEqual(to: resource.id) { + return file.resource + } else { + for representation in file.previewRepresentations { + if representation.resource.id.isEqual(to: resource.id) { + return representation.resource + } + } + } + } else if let webPage = media as? TelegramMediaWebpage, case let .Loaded(content) = webPage.content { + if let image = content.image, let result = findMediaResource(media: image, resource: resource) { + return result + } + if let file = content.file, let result = findMediaResource(media: file, resource: resource) { + return result + } + if let instantPage = content.instantPage { + for pageMedia in instantPage.media.values { + if let result = findMediaResource(media: pageMedia, resource: resource) { + return result + } + } + } + } + return nil +} + +private func findMediaResourceReference(media: Media, resource: MediaResource) -> Data? { + if let foundResource = findMediaResource(media: media, resource: resource) { + if let foundResource = foundResource as? CloudFileMediaResource { + return foundResource.fileReference + } else if let foundResource = foundResource as? CloudDocumentMediaResource { + return foundResource.fileReference + } else { + return nil + } + } else { + return nil + } +} + +private enum MediaReferenceRevalidationKey: Hashable { + case message(message: MessageReference) + case webPage(webPage: WebpageReference) + case stickerPack(stickerPack: StickerPackReference) + case savedGifs + case peer(peer: PeerReference) + case wallpapers +} + +private final class MediaReferenceRevalidationItemContext { + let subscribers = Bag<(Any) -> Void>() + let disposable: Disposable + + init(disposable: Disposable) { + self.disposable = disposable + } + + deinit { + self.disposable.dispose() + } + + var isEmpty: Bool { + return self.subscribers.isEmpty + } + + func addSubscriber(_ f: @escaping (Any) -> Void) -> Int { + return self.subscribers.add(f) + } + + func removeSubscriber(_ index: Int) { + self.subscribers.remove(index) + } +} + +private final class MediaReferenceRevalidationContextImpl { + let queue: Queue + + var itemContexts: [MediaReferenceRevalidationKey: MediaReferenceRevalidationItemContext] = [:] + + init(queue: Queue) { + self.queue = queue + } + + func genericItem(key: MediaReferenceRevalidationKey, request: @escaping (@escaping (Any) -> Void, @escaping (RevalidateMediaReferenceError) -> Void) -> Disposable, _ f: @escaping (Any) -> Void) -> Disposable { + let queue = self.queue + + let context: MediaReferenceRevalidationItemContext + if let current = self.itemContexts[key] { + context = current + } else { + let disposable = MetaDisposable() + context = MediaReferenceRevalidationItemContext(disposable: disposable) + self.itemContexts[key] = context + disposable.set(request({ [weak self] result in + queue.async { + guard let strongSelf = self else { + return + } + if let current = strongSelf.itemContexts[key], current === context { + strongSelf.itemContexts.removeValue(forKey: key) + for subscriber in current.subscribers.copyItems() { + subscriber(result) + } + } + } + }, { [weak self] _ in + queue.async { + guard let strongSelf = self else { + return + } + } + })) + } + + let index = context.addSubscriber(f) + + return ActionDisposable { [weak self, weak context] in + queue.async { + guard let strongSelf = self else { + return + } + if let current = strongSelf.itemContexts[key], current === context { + current.removeSubscriber(index) + if current.isEmpty { + current.disposable.dispose() + strongSelf.itemContexts.removeValue(forKey: key) + } + } + } + } + } +} + +final class MediaReferenceRevalidationContext { + private let queue: Queue + private let impl: QueueLocalObject + + init() { + self.queue = Queue() + let queue = self.queue + self.impl = QueueLocalObject(queue: self.queue, generate: { + return MediaReferenceRevalidationContextImpl(queue: queue) + }) + } + + private func genericItem(key: MediaReferenceRevalidationKey, request: @escaping (@escaping (Any) -> Void, @escaping (RevalidateMediaReferenceError) -> Void) -> Disposable) -> Signal { + return Signal { subscriber in + let disposable = MetaDisposable() + self.impl.with { impl in + disposable.set(impl.genericItem(key: key, request: request, { result in + subscriber.putNext(result) + subscriber.putCompletion() + })) + } + return disposable + } + } + + func message(postbox: Postbox, network: Network, message: MessageReference) -> Signal { + return self.genericItem(key: .message(message: message), request: { next, error in + return fetchRemoteMessage(postbox: postbox, network: network, message: message).start(next: { value in + if let value = value { + next(value) + } else { + error(.generic) + } + }, error: { _ in + error(.generic) + }) + }) |> mapToSignal { next -> Signal in + if let next = next as? Message { + return .single(next) + } else { + return .fail(.generic) + } + } + } + + func stickerPack(postbox: Postbox, network: Network, stickerPack: StickerPackReference) -> Signal<(StickerPackCollectionInfo, [ItemCollectionItem]), RevalidateMediaReferenceError> { + return self.genericItem(key: .stickerPack(stickerPack: stickerPack), request: { next, error in + return (updatedRemoteStickerPack(postbox: postbox, network: network, reference: stickerPack) + |> mapError { _ -> RevalidateMediaReferenceError in + return .generic + }).start(next: { value in + if let value = value { + next(value) + } else { + error(.generic) + } + }, error: { _ in + error(.generic) + }) + }) |> mapToSignal { next -> Signal<(StickerPackCollectionInfo, [ItemCollectionItem]), RevalidateMediaReferenceError> in + if let next = next as? (StickerPackCollectionInfo, [ItemCollectionItem]) { + return .single(next) + } else { + return .fail(.generic) + } + } + } + + func webPage(postbox: Postbox, network: Network, webPage: WebpageReference) -> Signal { + return self.genericItem(key: .webPage(webPage: webPage), request: { next, error in + return (updatedRemoteWebpage(postbox: postbox, network: network, webPage: webPage) + |> mapError { _ -> RevalidateMediaReferenceError in + return .generic + }).start(next: { value in + if let value = value { + next(value) + } else { + error(.generic) + } + }, error: { _ in + error(.generic) + }) + }) |> mapToSignal { next -> Signal in + if let next = next as? TelegramMediaWebpage { + return .single(next) + } else { + return .fail(.generic) + } + } + } + + func savedGifs(postbox: Postbox, network: Network) -> Signal<[TelegramMediaFile], RevalidateMediaReferenceError> { + return self.genericItem(key: .savedGifs, request: { next, error in + let loadRecentGifs: Signal<[TelegramMediaFile], Void> = postbox.transaction { transaction -> [TelegramMediaFile] in + return transaction.getOrderedListItems(collectionId: Namespaces.OrderedItemList.CloudRecentGifs).compactMap({ item -> TelegramMediaFile? in + if let contents = item.contents as? RecentMediaItem, let file = contents.media as? TelegramMediaFile { + return file + } + return nil + }) + } + return (managedRecentGifs(postbox: postbox, network: network, forceFetch: true) + |> mapToSignal { _ -> Signal<[TelegramMediaFile], Void> in + return .complete() + } + |> then(loadRecentGifs) + |> mapError { _ -> RevalidateMediaReferenceError in + return .generic + }).start(next: { value in + next(value) + }, error: { _ in + error(.generic) + }) + }) |> mapToSignal { next -> Signal<[TelegramMediaFile], RevalidateMediaReferenceError> in + if let next = next as? [TelegramMediaFile] { + return .single(next) + } else { + return .fail(.generic) + } + } + } + + func peer(postbox: Postbox, network: Network, peer: PeerReference) -> Signal { + return self.genericItem(key: .peer(peer: peer), request: { next, error in + return (updatedRemotePeer(postbox: postbox, network: network, peer: peer) + |> mapError { _ -> RevalidateMediaReferenceError in + return .generic + }).start(next: { value in + next(value) + }, error: { _ in + error(.generic) + }) + }) |> mapToSignal { next -> Signal in + if let next = next as? Peer { + return .single(next) + } else { + return .fail(.generic) + } + } + } + + func wallpapers(postbox: Postbox, network: Network) -> Signal<[TelegramWallpaper], RevalidateMediaReferenceError> { + return self.genericItem(key: .wallpapers, request: { next, error in + return (telegramWallpapers(postbox: postbox, network: network) + |> last + |> mapError { _ -> RevalidateMediaReferenceError in + return .generic + }).start(next: { value in + if let value = value { + next(value) + } else { + error(.generic) + } + }, error: { _ in + error(.generic) + }) + }) |> mapToSignal { next -> Signal<[TelegramWallpaper], RevalidateMediaReferenceError> in + if let next = next as? [TelegramWallpaper] { + return .single(next) + } else { + return .fail(.generic) + } + } + } +} + +func revalidateMediaResourceReference(postbox: Postbox, network: Network, revalidationContext: MediaReferenceRevalidationContext, info: TelegramCloudMediaResourceFetchInfo, resource: MediaResource) -> Signal { + switch info.reference { + case let .media(media, _): + switch media { + case let .message(message, _): + return revalidationContext.message(postbox: postbox, network: network, message: message) + |> mapToSignal { message -> Signal in + for media in message.media { + if let fileReference = findMediaResourceReference(media: media, resource: resource) { + return .single(fileReference) + } + } + return .fail(.generic) + } + case let .stickerPack(stickerPack, media): + return revalidationContext.stickerPack(postbox: postbox, network: network, stickerPack: stickerPack) + |> 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 fileReference = findMediaResourceReference(media: item.file, resource: resource) { + return .single(fileReference) + } + } + } + } + return .fail(.generic) + } + case let .webPage(webPage, _): + return revalidationContext.webPage(postbox: postbox, network: network, webPage: webPage) + |> mapToSignal { result -> Signal in + if let fileReference = findMediaResourceReference(media: result, resource: resource) { + return .single(fileReference) + } + return .fail(.generic) + } + case let .savedGif(media): + return revalidationContext.savedGifs(postbox: postbox, network: network) + |> mapToSignal { result -> Signal in + for file in result { + if media.id != nil && file.id == media.id { + if let fileReference = findMediaResourceReference(media: file, resource: resource) { + return .single(fileReference) + } + } + } + return .fail(.generic) + } + case let .standalone(media): + if let file = media as? TelegramMediaFile { + for attribute in file.attributes { + if case let .Sticker(sticker) = attribute, let stickerPack = sticker.packReference { + return revalidationContext.stickerPack(postbox: postbox, network: network, stickerPack: stickerPack) + |> 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 fileReference = findMediaResourceReference(media: item.file, resource: resource) { + return .single(fileReference) + } + } + } + } + return .fail(.generic) + } + } + } + } + return .fail(.generic) + } + case let .avatar(peer, _): + return revalidationContext.peer(postbox: postbox, network: network, peer: peer) + |> mapToSignal { updatedPeer -> Signal in + for representation in updatedPeer.profileImageRepresentations { + if representation.resource.id.isEqual(to: resource.id), let representationResource = representation.resource as? CloudFileMediaResource, let fileReference = representationResource.fileReference { + return .single(fileReference) + } + } + return .fail(.generic) + } + case .wallpaper: + return revalidationContext.wallpapers(postbox: postbox, network: network) + |> mapToSignal { wallpapers -> Signal in + for wallpaper in wallpapers { + if case let .image(representations) = wallpaper { + for representation in representations { + if representation.resource.id.isEqual(to: resource.id), let representationResource = representation.resource as? CloudFileMediaResource, let fileReference = representationResource.fileReference { + return .single(fileReference) + } + } + } + } + return .fail(.generic) + } + case .standalone: + return .fail(.generic) + } +} diff --git a/TelegramCore/LoadedStickerPack.swift b/TelegramCore/LoadedStickerPack.swift index 1dd7756b32..90563acfc2 100644 --- a/TelegramCore/LoadedStickerPack.swift +++ b/TelegramCore/LoadedStickerPack.swift @@ -28,15 +28,15 @@ public enum LoadedStickerPack { case result(info: StickerPackCollectionInfo, items: [ItemCollectionItem], installed: Bool) } -func remoteStickerPack(network: Network, reference: StickerPackReference) -> Signal<(StickerPackCollectionInfo, [ItemCollectionItem])?, NoError> { +func updatedRemoteStickerPack(postbox: Postbox, network: Network, reference: StickerPackReference) -> Signal<(StickerPackCollectionInfo, [ItemCollectionItem])?, NoError> { return network.request(Api.functions.messages.getStickerSet(stickerset: reference.apiInputStickerSet)) |> map { Optional($0) } |> `catch` { _ -> Signal in return .single(nil) } - |> map { result -> (StickerPackCollectionInfo, [ItemCollectionItem])? in + |> mapToSignal { result -> Signal<(StickerPackCollectionInfo, [ItemCollectionItem])?, NoError> in guard let result = result else { - return nil + return .single(nil) } let info: StickerPackCollectionInfo @@ -82,7 +82,14 @@ func remoteStickerPack(network: Network, reference: StickerPackReference) -> Sig } } - return (info, items) + return postbox.transaction { transaction -> (StickerPackCollectionInfo, [ItemCollectionItem])? in + if transaction.getItemCollectionInfo(collectionId: info.id) != nil { + transaction.replaceItemCollectionItems(collectionId: info.id, items: items) + } + cacheStickerPack(transaction: transaction, info: info, items: items) + + return (info, items) + } } } diff --git a/TelegramCore/ManagedRecentStickers.swift b/TelegramCore/ManagedRecentStickers.swift index e8efea9390..a060c3a24a 100644 --- a/TelegramCore/ManagedRecentStickers.swift +++ b/TelegramCore/ManagedRecentStickers.swift @@ -20,7 +20,7 @@ private func hashForIds(_ ids: [Int64]) -> Int32 { return Int32(bitPattern: acc & UInt32(0x7FFFFFFF)) } -private func managedRecentMedia(postbox: Postbox, network: Network, collectionId: Int32, reverseHashOrder: Bool, fetch: @escaping (Int32) -> Signal<[OrderedItemListEntry]?, NoError>) -> Signal { +private func managedRecentMedia(postbox: Postbox, network: Network, collectionId: Int32, reverseHashOrder: Bool, forceFetch: Bool, fetch: @escaping (Int32) -> Signal<[OrderedItemListEntry]?, NoError>) -> Signal { return postbox.transaction { transaction -> Signal in var itemIds = transaction.getOrderedListItemIds(collectionId: collectionId).map { RecentMediaItemId($0).mediaId.id @@ -28,7 +28,7 @@ private func managedRecentMedia(postbox: Postbox, network: Network, collectionId if reverseHashOrder { itemIds.reverse() } - return fetch(hashForIds(itemIds)) + return fetch(forceFetch ? 0 : hashForIds(itemIds)) |> mapToSignal { items in if let items = items { return postbox.transaction { transaction -> Void in @@ -42,7 +42,7 @@ private func managedRecentMedia(postbox: Postbox, network: Network, collectionId } func managedRecentStickers(postbox: Postbox, network: Network) -> Signal { - return managedRecentMedia(postbox: postbox, network: network, collectionId: Namespaces.OrderedItemList.CloudRecentStickers, reverseHashOrder: false, fetch: { hash in + return managedRecentMedia(postbox: postbox, network: network, collectionId: Namespaces.OrderedItemList.CloudRecentStickers, reverseHashOrder: false, forceFetch: false, fetch: { hash in return network.request(Api.functions.messages.getRecentStickers(flags: 0, hash: hash)) |> retryRequest |> mapToSignal { result -> Signal<[OrderedItemListEntry]?, NoError> in @@ -62,8 +62,8 @@ func managedRecentStickers(postbox: Postbox, network: Network) -> Signal Signal { - return managedRecentMedia(postbox: postbox, network: network, collectionId: Namespaces.OrderedItemList.CloudRecentGifs, reverseHashOrder: false, fetch: { hash in +func managedRecentGifs(postbox: Postbox, network: Network, forceFetch: Bool = false) -> Signal { + return managedRecentMedia(postbox: postbox, network: network, collectionId: Namespaces.OrderedItemList.CloudRecentGifs, reverseHashOrder: false, forceFetch: forceFetch, fetch: { hash in return network.request(Api.functions.messages.getSavedGifs(hash: hash)) |> retryRequest |> mapToSignal { result -> Signal<[OrderedItemListEntry]?, NoError> in @@ -84,7 +84,7 @@ func managedRecentGifs(postbox: Postbox, network: Network) -> Signal Signal { - return managedRecentMedia(postbox: postbox, network: network, collectionId: Namespaces.OrderedItemList.CloudSavedStickers, reverseHashOrder: true, fetch: { hash in + return managedRecentMedia(postbox: postbox, network: network, collectionId: Namespaces.OrderedItemList.CloudSavedStickers, reverseHashOrder: true, forceFetch: false, fetch: { hash in return network.request(Api.functions.messages.getFavedStickers(hash: hash)) |> retryRequest |> mapToSignal { result -> Signal<[OrderedItemListEntry]?, NoError> in diff --git a/TelegramCore/ManagedSecretChatOutgoingOperations.swift b/TelegramCore/ManagedSecretChatOutgoingOperations.swift index 63ddfdf033..cad20578c4 100644 --- a/TelegramCore/ManagedSecretChatOutgoingOperations.swift +++ b/TelegramCore/ManagedSecretChatOutgoingOperations.swift @@ -984,7 +984,7 @@ private func resourceThumbnailData(mediaBox: MediaBox, resource: MediaResource, return mediaBox.resourceData(resource, option: .complete(waitUntilFetchStatus: false)) |> take(1) |> map { data -> (MediaId, Data)? in - if data.complete, data.size < 1024 * 16, let content = try? Data(contentsOf: URL(fileURLWithPath: data.path)) { + if data.complete, data.size < 1024 * 1024, let content = try? Data(contentsOf: URL(fileURLWithPath: data.path)) { return (mediaId, content) } else { return nil @@ -1072,7 +1072,7 @@ private func sendMessage(postbox: Postbox, network: Network, messageId: MessageI if let fromMedia = currentMessage.media.first, let encryptedFile = encryptedFile, let file = file { var toMedia: Media? if let fromMedia = fromMedia as? TelegramMediaFile { - let updatedFile = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.CloudSecretFile, id: encryptedFile.id), resource: SecretFileMediaResource(fileId: encryptedFile.id, accessHash: encryptedFile.accessHash, containerSize: encryptedFile.size, decryptedSize: file.size, datacenterId: Int(encryptedFile.datacenterId), key: file.key), previewRepresentations: fromMedia.previewRepresentations, mimeType: fromMedia.mimeType, size: fromMedia.size, attributes: fromMedia.attributes) + let updatedFile = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.CloudSecretFile, id: encryptedFile.id), reference: nil, resource: SecretFileMediaResource(fileId: encryptedFile.id, accessHash: encryptedFile.accessHash, containerSize: encryptedFile.size, decryptedSize: file.size, datacenterId: Int(encryptedFile.datacenterId), key: file.key), previewRepresentations: fromMedia.previewRepresentations, mimeType: fromMedia.mimeType, size: fromMedia.size, attributes: fromMedia.attributes) toMedia = updatedFile updatedMedia = [updatedFile] } diff --git a/TelegramCore/ManagedSynchronizeSavedGifsOperations.swift b/TelegramCore/ManagedSynchronizeSavedGifsOperations.swift index 66f3ea5190..6c08117954 100644 --- a/TelegramCore/ManagedSynchronizeSavedGifsOperations.swift +++ b/TelegramCore/ManagedSynchronizeSavedGifsOperations.swift @@ -69,7 +69,7 @@ private func withTakenOperation(postbox: Postbox, peerId: PeerId, tag: PeerOpera } |> switchToLatest } -func managedSynchronizeSavedGifsOperations(postbox: Postbox, network: Network) -> Signal { +func managedSynchronizeSavedGifsOperations(postbox: Postbox, network: Network, revalidationContext: MediaReferenceRevalidationContext) -> Signal { return Signal { _ in let tag: PeerOperationLogTag = OperationLogTags.SynchronizeSavedGifs @@ -88,7 +88,7 @@ func managedSynchronizeSavedGifsOperations(postbox: Postbox, network: Network) - let signal = withTakenOperation(postbox: postbox, peerId: entry.peerId, tag: tag, tagLocalIndex: entry.tagLocalIndex, { transaction, entry -> Signal in if let entry = entry { if let operation = entry.contents as? SynchronizeSavedGifsOperation { - return synchronizeSavedGifs(transaction: transaction, postbox: postbox, network: network, operation: operation) + return synchronizeSavedGifs(transaction: transaction, postbox: postbox, network: network, revalidationContext: revalidationContext, operation: operation) } else { assertionFailure() } @@ -115,24 +115,64 @@ func managedSynchronizeSavedGifsOperations(postbox: Postbox, network: Network) - } } -private func synchronizeSavedGifs(transaction: Transaction, postbox: Postbox, network: Network, operation: SynchronizeSavedGifsOperation) -> Signal { +private enum SaveGifError { + case generic + case invalidReference +} + +private func synchronizeSavedGifs(transaction: Transaction, postbox: Postbox, network: Network, revalidationContext: MediaReferenceRevalidationContext, operation: SynchronizeSavedGifsOperation) -> Signal { switch operation.content { - case let .add(id, accessHash): - return network.request(Api.functions.messages.saveGif(id: .inputDocument(id: id, accessHash: accessHash), unsave: .boolFalse)) - |> `catch` { _ -> Signal in - return .single(.boolFalse) + case let .add(id, accessHash, fileReference): + guard let fileReference = fileReference else { + return .complete() + } + + let saveGif: (Data) -> Signal = { fileReference in + return network.request(Api.functions.messages.saveGif(id: .inputDocument(id: id, accessHash: accessHash, fileReference: Buffer(data: fileReference)), unsave: .boolFalse)) + |> mapError { error -> SaveGifError in + if error.errorDescription.hasPrefix("FILEREF_INVALID") || error.errorDescription.hasPrefix("FILE_REFERENCE_") { + return .invalidReference + } + return .generic } - |> mapToSignal { _ -> Signal in - return .complete() + } + + let initialSignal: Signal + if let reference = (fileReference.media.resource as? CloudDocumentMediaResource)?.fileReference { + initialSignal = saveGif(reference) + } else { + initialSignal = .fail(.invalidReference) + } + + return initialSignal + |> `catch` { error -> Signal in + switch error { + case .generic: + return .fail(.generic) + case .invalidReference: + return revalidateMediaResourceReference(postbox: postbox, network: network, revalidationContext: revalidationContext, info: TelegramCloudMediaResourceFetchInfo(reference: fileReference.resourceReference(fileReference.media.resource)), resource: fileReference.media.resource) + |> mapError { _ -> SaveGifError in + return .generic + } + |> mapToSignal { reference -> Signal in + return saveGif(reference) + } } + } + |> `catch` { _ -> Signal in + return .complete() + } + |> mapToSignal { _ -> Signal in + return .complete() + } case let .remove(id, accessHash): - return network.request(Api.functions.messages.saveGif(id: .inputDocument(id: id, accessHash: accessHash), unsave: .boolTrue)) - |> `catch` { _ -> Signal in - return .single(.boolFalse) - } - |> mapToSignal { _ -> Signal in - return .complete() - } + return network.request(Api.functions.messages.saveGif(id: .inputDocument(id: id, accessHash: accessHash, fileReference: Buffer()), unsave: .boolTrue)) + |> `catch` { _ -> Signal in + return .single(.boolFalse) + } + |> mapToSignal { _ -> Signal in + return .complete() + } case .sync: return managedRecentGifs(postbox: postbox, network: network) } diff --git a/TelegramCore/ManagedSynchronizeSavedStickersOperations.swift b/TelegramCore/ManagedSynchronizeSavedStickersOperations.swift index f51151b214..98137ba6e1 100644 --- a/TelegramCore/ManagedSynchronizeSavedStickersOperations.swift +++ b/TelegramCore/ManagedSynchronizeSavedStickersOperations.swift @@ -69,7 +69,7 @@ private func withTakenOperation(postbox: Postbox, peerId: PeerId, tag: PeerOpera } |> switchToLatest } -func managedSynchronizeSavedStickersOperations(postbox: Postbox, network: Network) -> Signal { +func managedSynchronizeSavedStickersOperations(postbox: Postbox, network: Network, revalidationContext: MediaReferenceRevalidationContext) -> Signal { return Signal { _ in let tag: PeerOperationLogTag = OperationLogTags.SynchronizeSavedStickers @@ -88,7 +88,7 @@ func managedSynchronizeSavedStickersOperations(postbox: Postbox, network: Networ let signal = withTakenOperation(postbox: postbox, peerId: entry.peerId, tag: tag, tagLocalIndex: entry.tagLocalIndex, { transaction, entry -> Signal in if let entry = entry { if let operation = entry.contents as? SynchronizeSavedStickersOperation { - return synchronizeSavedStickers(transaction: transaction, postbox: postbox, network: network, operation: operation) + return synchronizeSavedStickers(transaction: transaction, postbox: postbox, network: network, revalidationContext: revalidationContext, operation: operation) } else { assertionFailure() } @@ -115,18 +115,58 @@ func managedSynchronizeSavedStickersOperations(postbox: Postbox, network: Networ } } -private func synchronizeSavedStickers(transaction: Transaction, postbox: Postbox, network: Network, operation: SynchronizeSavedStickersOperation) -> Signal { +private enum SaveStickerError { + case generic + case invalidReference +} + +private func synchronizeSavedStickers(transaction: Transaction, postbox: Postbox, network: Network, revalidationContext: MediaReferenceRevalidationContext, operation: SynchronizeSavedStickersOperation) -> Signal { switch operation.content { - case let .add(id, accessHash): - return network.request(Api.functions.messages.faveSticker(id: .inputDocument(id: id, accessHash: accessHash), unfave: .boolFalse)) - |> `catch` { _ -> Signal in - return .single(.boolFalse) + case let .add(id, accessHash, fileReference): + guard let fileReference = fileReference else { + return .complete() + } + + let saveSticker: (Data) -> Signal = { fileReference in + return network.request(Api.functions.messages.faveSticker(id: .inputDocument(id: id, accessHash: accessHash, fileReference: Buffer(data: fileReference)), unfave: .boolFalse)) + |> mapError { error -> SaveStickerError in + if error.errorDescription.hasPrefix("FILEREF_INVALID") || error.errorDescription.hasPrefix("FILE_REFERENCE_") { + return .invalidReference + } + return .generic } - |> mapToSignal { _ -> Signal in - return .complete() + } + + let initialSignal: Signal + if let reference = (fileReference.media.resource as? CloudDocumentMediaResource)?.fileReference { + initialSignal = saveSticker(reference) + } else { + initialSignal = .fail(.invalidReference) + } + + return initialSignal + |> `catch` { error -> Signal in + switch error { + case .generic: + return .fail(.generic) + case .invalidReference: + return revalidateMediaResourceReference(postbox: postbox, network: network, revalidationContext: revalidationContext, info: TelegramCloudMediaResourceFetchInfo(reference: fileReference.resourceReference(fileReference.media.resource)), resource: fileReference.media.resource) + |> mapError { _ -> SaveStickerError in + return .generic + } + |> mapToSignal { reference -> Signal in + return saveSticker(reference) + } + } + } + |> `catch` { _ -> Signal in + return .complete() + } + |> mapToSignal { _ -> Signal in + return .complete() } case let .remove(id, accessHash): - return network.request(Api.functions.messages.faveSticker(id: .inputDocument(id: id, accessHash: accessHash), unfave: .boolTrue)) + return network.request(Api.functions.messages.faveSticker(id: .inputDocument(id: id, accessHash: accessHash, fileReference: Buffer()), unfave: .boolTrue)) |> `catch` { _ -> Signal in return .single(.boolFalse) } diff --git a/TelegramCore/MultipartFetch.swift b/TelegramCore/MultipartFetch.swift index 90748675f9..264b0da155 100644 --- a/TelegramCore/MultipartFetch.swift +++ b/TelegramCore/MultipartFetch.swift @@ -60,11 +60,12 @@ private enum MultipartFetchDownloadError { case generic case switchToCdn(id: Int32, token: Data, key: Data, iv: Data, partHashes: [Int32: Data]) case reuploadToCdn(masterDatacenterId: Int32, token: Data) + case revalidateMediaReference case hashesMissing } private enum MultipartFetchMasterLocation { - case generic(Int32, Api.InputFileLocation) + case generic(Int32, (Data?) -> Api.InputFileLocation?) case web(Int32, Api.InputWebFileLocation) var datacenterId: Int32 { @@ -269,7 +270,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?) -> Signal { + func request(offset: Int32, limit: Int32, tag: MediaResourceFetchTag?, fileReference: Data?) -> Signal { switch self { case .none: return .never() @@ -281,29 +282,37 @@ private enum MultipartFetchSource { switch location { case let .generic(_, location): - return download.request(Api.functions.upload.getFile(location: location, offset: offset, limit: Int32(updatedLength)), tag: tag) - |> mapError { _ -> MultipartFetchDownloadError in - 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() - } - } - return .fail(.switchToCdn(id: dcId, token: fileToken.makeData(), key: encryptionKey.makeData(), iv: encryptionIv.makeData(), partHashes: parsedPartHashes)) + if let parsedLocation = location(fileReference) { + + return download.request(Api.functions.upload.getFile(location: parsedLocation, offset: offset, limit: Int32(updatedLength)), tag: tag) + |> mapError { error -> MultipartFetchDownloadError in + if error.errorDescription.hasPrefix("FILEREF_INVALID") || error.errorDescription.hasPrefix("FILE_REFERENCE_") { + return .revalidateMediaReference + } + 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() + } + } + 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) @@ -377,9 +386,12 @@ private final class MultipartFetchManager { let defaultPartSize = 128 * 1024 let partAlignment = 128 * 1024 - let tag: MediaResourceFetchTag? + let resource: TelegramMediaResource + let parameters: MediaResourceFetchParameters? let consumerId: Int64 + var fileReference: Data? + let queue = Queue() var currentRanges: IndexSet? @@ -388,7 +400,9 @@ private final class MultipartFetchManager { var completeSize: Int? var completeSizeReported = false + let postbox: Postbox let network: Network + let revalidationContext: MediaReferenceRevalidationContext let partReady: (Int, Data) -> Void let reportCompleteSize: (Int) -> Void @@ -401,32 +415,34 @@ private final class MultipartFetchManager { var reuploadingToCdn = false let reuploadToCdnDisposable = MetaDisposable() + var revalidatedMediaReference = false + var revalidatingMediaReference = false + let revalidateMediaReferenceDisposable = MetaDisposable() + var state: MultipartDownloadState var rangesDisposable: Disposable? - init(tag: MediaResourceFetchTag?, size: Int?, ranges: Signal, encryptionKey: SecretFileEncryptionKey?, decryptedSize: Int32?, location: MultipartFetchMasterLocation, network: Network, partReady: @escaping (Int, Data) -> Void, reportCompleteSize: @escaping (Int) -> Void) { - self.tag = tag + init(resource: TelegramMediaResource, parameters: MediaResourceFetchParameters?, size: Int?, ranges: Signal, encryptionKey: SecretFileEncryptionKey?, decryptedSize: Int32?, location: MultipartFetchMasterLocation, postbox: Postbox, network: Network, revalidationContext: MediaReferenceRevalidationContext, partReady: @escaping (Int, Data) -> Void, reportCompleteSize: @escaping (Int) -> Void) { + self.resource = resource + self.parameters = parameters self.consumerId = arc4random64() self.completeSize = size - if let size = size { + if let _ = size { self.parallelParts = 4 - /*if size <= range.lowerBound { - self.range = range - self.parallelParts = 0 - } else { - self.range = range.lowerBound ..< min(range.upperBound, size) - - }*/ } else { - //self.range = range self.parallelParts = 1 } + if let info = parameters?.info as? TelegramCloudMediaResourceFetchInfo { + self.fileReference = info.reference.apiFileReference + } + self.state = MultipartDownloadState(encryptionKey: encryptionKey, decryptedSize: decryptedSize) - //self.committedOffset = range.lowerBound + self.postbox = postbox self.network = network + self.revalidationContext = revalidationContext self.source = .master(location: location, download: DownloadWrapper(consumerId: self.consumerId, datacenterId: location.datacenterId, isCdn: false, network: network)) self.partReady = partReady self.reportCompleteSize = reportCompleteSize @@ -465,6 +481,7 @@ private final class MultipartFetchManager { disposable.dispose() } self.reuploadToCdnDisposable.dispose() + self.revalidateMediaReferenceDisposable.dispose() } } @@ -515,7 +532,7 @@ private final class MultipartFetchManager { } } - while !rangesToFetch.isEmpty && self.fetchingParts.count < self.parallelParts && !self.reuploadingToCdn { + while !rangesToFetch.isEmpty && self.fetchingParts.count < self.parallelParts && !self.reuploadingToCdn && !self.revalidatingMediaReference { var selectedRange: (Range, Range)? for range in rangesToFetch.rangeView { var dataRange: Range = range.lowerBound ..< min(range.lowerBound + self.defaultPartSize, range.upperBound) @@ -537,9 +554,13 @@ private final class MultipartFetchManager { if requestLimit % self.partAlignment != 0 { requestLimit = (requestLimit / self.partAlignment + 1) * self.partAlignment } - let part = self.source.request(offset: Int32(downloadRange.lowerBound), limit: Int32(requestLimit), tag: self.tag) - |> deliverOn(self.queue) - self.fetchingParts[downloadRange.lowerBound] = (downloadRange.count, part.start(next: { [weak self] data in + + let part = self.source.request(offset: Int32(downloadRange.lowerBound), limit: Int32(requestLimit), tag: self.parameters?.tag, fileReference: self.fileReference) + |> deliverOn(self.queue) + let partDisposable = MetaDisposable() + self.fetchingParts[downloadRange.lowerBound] = (downloadRange.count, partDisposable) + + partDisposable.set(part.start(next: { [weak self] data in if let strongSelf = self { var data = data if data.count < downloadRange.count { @@ -555,6 +576,26 @@ private final class MultipartFetchManager { switch error { case .generic: break + case .revalidateMediaReference: + if !strongSelf.revalidatingMediaReference && !strongSelf.revalidatedMediaReference { + 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: { fileReference in + if let strongSelf = self { + strongSelf.revalidatingMediaReference = false + strongSelf.revalidatedMediaReference = true + strongSelf.fileReference = fileReference + strongSelf.checkState() + } + }, error: { _ in + if let strongSelf = self { + } + })) + } else { + Logger.shared.log("MultipartFetch", "reference invalidation requested, but no valid reference given") + } + } case let .switchToCdn(id, token, key, iv, partHashes): switch strongSelf.source { case let .master(location, download): @@ -594,11 +635,13 @@ private final class MultipartFetchManager { } } -func multipartFetch(account: Account, resource: TelegramMediaResource, datacenterId: Int, size: Int?, ranges: Signal, tag: MediaResourceFetchTag?, encryptionKey: SecretFileEncryptionKey? = nil, decryptedSize: Int32? = nil) -> Signal { +func multipartFetch(account: Account, resource: TelegramMediaResource, datacenterId: Int, size: Int?, ranges: Signal, parameters: MediaResourceFetchParameters?, encryptionKey: SecretFileEncryptionKey? = nil, decryptedSize: Int32? = nil) -> Signal { return Signal { subscriber in let location: MultipartFetchMasterLocation if let resource = resource as? TelegramCloudMediaResource { - location = .generic(Int32(datacenterId), resource.apiInputLocation) + location = .generic(Int32(datacenterId), { fileReference in + return resource.apiInputLocation(fileReference: fileReference) + }) } else if let resource = resource as? WebFileReferenceMediaResource { location = .web(Int32(datacenterId), resource.apiInputLocation) } else { @@ -610,7 +653,7 @@ func multipartFetch(account: Account, resource: TelegramMediaResource, datacente subscriber.putNext(.reset) } - let manager = MultipartFetchManager(tag: tag, size: size, ranges: ranges, encryptionKey: encryptionKey, decryptedSize: decryptedSize, location: location, network: account.network, partReady: { dataOffset, data in + let manager = MultipartFetchManager(resource: resource, parameters: parameters, size: size, ranges: ranges, encryptionKey: encryptionKey, decryptedSize: decryptedSize, location: location, postbox: account.postbox, network: account.network, revalidationContext: account.mediaReferenceRevalidationContext, partReady: { dataOffset, data in subscriber.putNext(.dataPart(resourceOffset: dataOffset, data: data, range: 0 ..< data.count, complete: false)) }, reportCompleteSize: { size in subscriber.putNext(.resourceSizeUpdated(size)) diff --git a/TelegramCore/MultipartUpload.swift b/TelegramCore/MultipartUpload.swift index 7fcb5fed54..04fda3fdb8 100644 --- a/TelegramCore/MultipartUpload.swift +++ b/TelegramCore/MultipartUpload.swift @@ -393,7 +393,7 @@ func multipartUpload(network: Network, postbox: Postbox, source: MultipartUpload case let .resource(resource): dataSignal = postbox.mediaBox.resourceData(resource, option: .incremental(waitUntilFetchStatus: true)) |> map { MultipartUploadData.resourceData($0) } headerSize = resource.headerSize - fetchedResource = postbox.mediaBox.fetchedResource(resource, tag: tag) |> map {_ in} + fetchedResource = postbox.mediaBox.fetchedResource(resource, parameters: nil) |> map {_ in} case let .data(data): dataSignal = .single(.data(data)) headerSize = 0 diff --git a/TelegramCore/OutgoingMessageWithChatContextResult.swift b/TelegramCore/OutgoingMessageWithChatContextResult.swift index bd86bf211a..3519eb9598 100644 --- a/TelegramCore/OutgoingMessageWithChatContextResult.swift +++ b/TelegramCore/OutgoingMessageWithChatContextResult.swift @@ -90,7 +90,7 @@ public func outgoingMessageWithChatContextResult(_ results: ChatContextResultCol var randomId: Int64 = 0 arc4random_buf(&randomId, 8) - let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: randomId), resource: EmptyMediaResource(), previewRepresentations: previewRepresentations, mimeType: content?.mimeType ?? "application/binary", size: nil, attributes: fileAttributes) + let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: randomId), reference: nil, resource: EmptyMediaResource(), previewRepresentations: previewRepresentations, mimeType: content?.mimeType ?? "application/binary", size: nil, attributes: fileAttributes) return .message(text: caption, attributes: attributes, media: file, replyToMessageId: nil, localGroupingKey: nil) } else { return .message(text: caption, attributes: attributes, media: nil, replyToMessageId: nil, localGroupingKey: nil) diff --git a/TelegramCore/PeerPhotoUpdater.swift b/TelegramCore/PeerPhotoUpdater.swift index bf43250782..869491e13f 100644 --- a/TelegramCore/PeerPhotoUpdater.swift +++ b/TelegramCore/PeerPhotoUpdater.swift @@ -70,8 +70,8 @@ public func updatePeerPhoto(account: Account, peerId: PeerId, photo: Signal Signal { if let reference = reference { switch reference { - case let .cloud(imageId, accessHash): - return network.request(Api.functions.photos.deletePhotos(id: [.inputPhoto(id: imageId, accessHash: accessHash)])) + case let .cloud(imageId, accessHash, fileReference): + if let fileReference = fileReference { + return network.request(Api.functions.photos.deletePhotos(id: [.inputPhoto(id: imageId, accessHash: accessHash, fileReference: Buffer(data: fileReference))])) |> `catch` { _ -> Signal<[Int64], NoError> in return .single([]) } |> mapToSignal { _ -> Signal in return .complete() + } + } else { + return .complete() } } } else { diff --git a/TelegramCore/PendingMessageManager.swift b/TelegramCore/PendingMessageManager.swift index 69ec1a85cb..b9baefe8bf 100644 --- a/TelegramCore/PendingMessageManager.swift +++ b/TelegramCore/PendingMessageManager.swift @@ -2,9 +2,11 @@ import Foundation #if os(macOS) import PostboxMac import SwiftSignalKitMac + import MtProtoKitMac #else import Postbox import SwiftSignalKit + import MtProtoKitDynamic #endif public struct PendingMessageStatus: Equatable { @@ -20,7 +22,7 @@ private enum PendingMessageState { case none case waitingForUploadToStart(groupId: Int64?, upload: Signal) case uploading(groupId: Int64?) - case waitingToBeSent(groupId: Int64?, content: PendingMessageUploadedContent) + case waitingToBeSent(groupId: Int64?, content: PendingMessageUploadedContentAndReuploadInfo) case sending(groupId: Int64?) var groupId: Int64? { @@ -41,9 +43,11 @@ private enum PendingMessageState { private final class PendingMessageContext { var state: PendingMessageState = .none - let disposable = MetaDisposable() + let uploadDisposable = MetaDisposable() + let sendDisposable = MetaDisposable() var status: PendingMessageStatus? var statusSubscribers = Bag<(PendingMessageStatus?) -> Void>() + var forcedReuploadOnce: Bool = false } private final class PeerPendingMessagesSummaryContext { @@ -91,6 +95,7 @@ public final class PendingMessageManager { private let auxiliaryMethods: AccountAuxiliaryMethods private let stateManager: AccountStateManager private let messageMediaPreuploadManager: MessageMediaPreuploadManager + private let revalidationContext: MediaReferenceRevalidationContext private let queue = Queue() @@ -107,12 +112,13 @@ public final class PendingMessageManager { var transformOutgoingMessageMedia: TransformOutgoingMessageMedia? - init(network: Network, postbox: Postbox, auxiliaryMethods: AccountAuxiliaryMethods, stateManager: AccountStateManager, messageMediaPreuploadManager: MessageMediaPreuploadManager) { + init(network: Network, postbox: Postbox, auxiliaryMethods: AccountAuxiliaryMethods, stateManager: AccountStateManager, messageMediaPreuploadManager: MessageMediaPreuploadManager, revalidationContext: MediaReferenceRevalidationContext) { self.network = network self.postbox = postbox self.auxiliaryMethods = auxiliaryMethods self.stateManager = stateManager self.messageMediaPreuploadManager = messageMediaPreuploadManager + self.revalidationContext = revalidationContext } deinit { @@ -133,7 +139,8 @@ public final class PendingMessageManager { } context.state = .none updateUploadingPeerIds.insert(id.peerId) - context.disposable.dispose() + context.sendDisposable.dispose() + context.uploadDisposable.dispose() if context.statusSubscribers.isEmpty { self.messageContexts.removeValue(forKey: id) } @@ -252,29 +259,12 @@ public final class PendingMessageManager { continue } - let contentToUpload = messageContentToUpload(network: strongSelf.network, postbox: strongSelf.postbox, auxiliaryMethods: strongSelf.auxiliaryMethods, transformOutgoingMessageMedia: strongSelf.transformOutgoingMessageMedia, messageMediaPreuploadManager: strongSelf.messageMediaPreuploadManager, message: message) + let contentUploadSignal = messageContentToUpload(network: strongSelf.network, postbox: strongSelf.postbox, auxiliaryMethods: strongSelf.auxiliaryMethods, transformOutgoingMessageMedia: strongSelf.transformOutgoingMessageMedia, messageMediaPreuploadManager: strongSelf.messageMediaPreuploadManager, revalidationContext: strongSelf.revalidationContext, forceReupload: messageContext.forcedReuploadOnce, message: message) - switch contentToUpload { - case let .ready(content): - if let groupingKey = message.groupingKey { - strongSelf.beginSendingMessage(messageContext: messageContext, messageId: message.id, groupId: message.groupingKey, content: content) - if let current = currentGroupId, current != groupingKey { - strongSelf.beginSendingGroupIfPossible(groupId: current) - } - } else { - if let currentGroupId = currentGroupId { - strongSelf.beginSendingGroupIfPossible(groupId: currentGroupId) - } - strongSelf.beginSendingMessage(messageContext: messageContext, messageId: message.id, groupId: message.groupingKey, content: content) - } - - currentGroupId = message.groupingKey - case let .upload(uploadSignal): - if strongSelf.canBeginUploadingMessage(id: message.id) { - strongSelf.beginUploadingMessage(messageContext: messageContext, id: message.id, groupId: message.groupingKey, uploadSignal: uploadSignal) - } else { - messageContext.state = .waitingForUploadToStart(groupId: message.groupingKey, upload: uploadSignal) - } + if strongSelf.canBeginUploadingMessage(id: message.id) { + strongSelf.beginUploadingMessage(messageContext: messageContext, id: message.id, groupId: message.groupingKey, uploadSignal: contentUploadSignal) + } else { + messageContext.state = .waitingForUploadToStart(groupId: message.groupingKey, upload: contentUploadSignal) } } @@ -285,7 +275,7 @@ public final class PendingMessageManager { })) } - private func beginSendingMessage(messageContext: PendingMessageContext, messageId: MessageId, groupId: Int64?, content: PendingMessageUploadedContent) { + private func beginSendingMessage(messageContext: PendingMessageContext, messageId: MessageId, groupId: Int64?, content: PendingMessageUploadedContentAndReuploadInfo) { if let groupId = groupId { messageContext.state = .waitingToBeSent(groupId: groupId, content: content) } else { @@ -299,8 +289,8 @@ public final class PendingMessageManager { } } - private func dataForPendingMessageGroup(_ groupId: Int64) -> [(messageContext: PendingMessageContext, messageId: MessageId, content: PendingMessageUploadedContent)]? { - var result: [(messageContext: PendingMessageContext, messageId: MessageId, content: PendingMessageUploadedContent)] = [] + private func dataForPendingMessageGroup(_ groupId: Int64) -> [(messageContext: PendingMessageContext, messageId: MessageId, content: PendingMessageUploadedContentAndReuploadInfo)]? { + var result: [(messageContext: PendingMessageContext, messageId: MessageId, content: PendingMessageUploadedContentAndReuploadInfo)] = [] loop: for (id, context) in self.messageContexts { switch context.state { @@ -332,16 +322,18 @@ public final class PendingMessageManager { } } - private func commitSendingMessageGroup(groupId: Int64, messages: [(messageContext: PendingMessageContext, messageId: MessageId, content: PendingMessageUploadedContent)]) { + private func commitSendingMessageGroup(groupId: Int64, messages: [(messageContext: PendingMessageContext, messageId: MessageId, content: PendingMessageUploadedContentAndReuploadInfo)]) { for (context, _, _) in messages { context.state = .sending(groupId: groupId) } let sendMessage: Signal = self.sendGroupMessagesContent(network: self.network, postbox: self.postbox, stateManager: self.stateManager, group: messages.map { ($0.1, $0.2) }) - |> map { next -> PendingMessageResult in - return .progress(1.0) - } - messages[0].0.disposable.set((sendMessage |> deliverOn(self.queue) |> afterDisposed { [weak self] in - if let strongSelf = self { + |> map { next -> PendingMessageResult in + return .progress(1.0) + } + messages[0].0.sendDisposable.set((sendMessage + |> deliverOn(self.queue) + |> afterDisposed { [weak self] in + /*if let strongSelf = self { assert(strongSelf.queue.isCurrent()) for (_, id, _) in messages { if let current = strongSelf.messageContexts[id] { @@ -354,18 +346,20 @@ public final class PendingMessageManager { } } } - } + }*/ }).start()) } - private func commitSendingSingleMessage(messageContext: PendingMessageContext, messageId: MessageId, content: PendingMessageUploadedContent) { + private func commitSendingSingleMessage(messageContext: PendingMessageContext, messageId: MessageId, content: PendingMessageUploadedContentAndReuploadInfo) { messageContext.state = .sending(groupId: nil) let sendMessage: Signal = self.sendMessageContent(network: self.network, postbox: self.postbox, stateManager: self.stateManager, messageId: messageId, content: content) - |> map { next -> PendingMessageResult in - return .progress(1.0) - } - messageContext.disposable.set((sendMessage |> deliverOn(self.queue) |> afterDisposed { [weak self] in - if let strongSelf = self { + |> map { next -> PendingMessageResult in + return .progress(1.0) + } + messageContext.sendDisposable.set((sendMessage + |> deliverOn(self.queue) + |> afterDisposed { [weak self] in + /*if let strongSelf = self { assert(strongSelf.queue.isCurrent()) if let current = strongSelf.messageContexts[messageId] { current.status = .none @@ -376,7 +370,7 @@ public final class PendingMessageManager { strongSelf.messageContexts.removeValue(forKey: messageId) } } - } + }*/ }).start(next: { [weak self] next in if let strongSelf = self { assert(strongSelf.queue.isCurrent()) @@ -404,7 +398,9 @@ public final class PendingMessageManager { subscriber(status) } - messageContext.disposable.set((uploadSignal |> deliverOn(self.queue) |> `catch` { [weak self] _ -> Signal in + messageContext.uploadDisposable.set((uploadSignal + |> deliverOn(self.queue) + |> `catch` { [weak self] _ -> Signal in if let strongSelf = self { let modify = strongSelf.postbox.transaction { transaction -> Void in transaction.updateMessage(id, update: { currentMessage in @@ -415,7 +411,8 @@ public final class PendingMessageManager { return .update(StoreMessage(id: id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: [.Failed], tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: currentMessage.media)) }) } - return modify |> mapToSignal { _ in return .complete() } + return modify + |> mapToSignal { _ in return .complete() } } return .fail(Void()) }).start(next: { [weak self] next in @@ -459,7 +456,7 @@ public final class PendingMessageManager { subscriber(status) } - context.disposable.set((uploadSignal |> deliverOn(self.queue)).start(next: { [weak self] next in + context.uploadDisposable.set((uploadSignal |> deliverOn(self.queue)).start(next: { [weak self] next in if let strongSelf = self { assert(strongSelf.queue.isCurrent()) @@ -489,7 +486,7 @@ public final class PendingMessageManager { } } - private func sendGroupMessagesContent(network: Network, postbox: Postbox, stateManager: AccountStateManager, group: [(messageId: MessageId, content: PendingMessageUploadedContent)]) -> Signal { + private func sendGroupMessagesContent(network: Network, postbox: Postbox, stateManager: AccountStateManager, group: [(messageId: MessageId, content: PendingMessageUploadedContentAndReuploadInfo)]) -> Signal { return postbox.transaction { [weak self] transaction -> Signal in if group.isEmpty { return .complete() @@ -497,7 +494,7 @@ public final class PendingMessageManager { let peerId = group[0].messageId.peerId - var messages: [(Message, PendingMessageUploadedContent)] = [] + var messages: [(Message, PendingMessageUploadedContentAndReuploadInfo)] = [] for (id, content) in group { if let message = transaction.getMessage(id) { messages.append((message, content)) @@ -532,7 +529,7 @@ public final class PendingMessageManager { } } - let sendMessageRequest: Signal + let sendMessageRequest: Signal if isForward { flags |= (1 << 9) @@ -547,7 +544,7 @@ public final class PendingMessageManager { } if let uniqueId = uniqueId { - switch content { + switch content.content { case let .forward(forwardAttribute): forwardIds.append((forwardAttribute.messageId, uniqueId)) default: @@ -561,27 +558,21 @@ public final class PendingMessageManager { let forwardPeerIds = Set(forwardIds.map { $0.0.peerId }) if forwardPeerIds.count != 1 { assertionFailure() - sendMessageRequest = .fail(NoError()) + sendMessageRequest = .fail(MTRpcError(errorCode: 400, errorDescription: "Invalid forward peer ids")) } else if let inputSourcePeerId = forwardPeerIds.first, let inputSourcePeer = transaction.getPeer(inputSourcePeerId).flatMap(apiInputPeer) { let dependencyTag = PendingMessageRequestDependencyTag(messageId: messages[0].0.id) sendMessageRequest = network.request(Api.functions.messages.forwardMessages(flags: flags, fromPeer: inputSourcePeer, id: forwardIds.map { $0.0.id }, randomId: forwardIds.map { $0.1 }, toPeer: inputPeer), tag: dependencyTag) - |> mapError { _ -> NoError in - return NoError() - } } else { assertionFailure() - sendMessageRequest = .fail(NoError()) + sendMessageRequest = .fail(MTRpcError(errorCode: 400, errorDescription: "Invalid forward source")) } - //messages.forwardMessages flags:# silent:flags.5?true background:flags.6?true with_my_score:flags.8?true from_peer:InputPeer id:Vector random_id:Vector to_peer:InputPeer grouped:flags.9?true = Updates; } else { flags |= (1 << 7) if let _ = replyMessageId { flags |= Int32(1 << 0) } - //messages.sendMultiMedia flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true peer:InputPeer reply_to_msg_id:flags.0?int multi_media:Vector = Updates; - var singleMedias: [Api.InputSingleMedia] = [] for (message, content) in messages { var uniqueId: Int64? @@ -592,7 +583,7 @@ public final class PendingMessageManager { } } if let uniqueId = uniqueId { - switch content { + switch content.content { case let .media(inputMedia, text): var messageEntities: [Api.MessageEntity]? for attribute in message.attributes { @@ -616,32 +607,36 @@ public final class PendingMessageManager { } sendMessageRequest = network.request(Api.functions.messages.sendMultiMedia(flags: flags, peer: inputPeer, replyToMsgId: replyMessageId, multiMedia: singleMedias)) - |> mapError { _ -> NoError in - return NoError() - } } return sendMessageRequest - |> mapToSignal { result -> Signal in - if let strongSelf = self { - return strongSelf.applySentGroupMessages(postbox: postbox, stateManager: stateManager, messages: messages.map { $0.0 }, result: result) - } else { - return .never() + |> mapToSignal { result -> Signal in + if let strongSelf = self { + return strongSelf.applySentGroupMessages(postbox: postbox, stateManager: stateManager, messages: messages.map { $0.0 }, result: result) + |> mapError { _ -> MTRpcError in + return MTRpcError(errorCode: 400, errorDescription: "empty") } + } else { + return .never() } - |> `catch` { _ -> Signal in - return failMessages(postbox: postbox, ids: group.map { $0.0 }) + } + |> `catch` { error -> Signal in + if error.errorDescription.hasPrefix("FILEREF_INVALID") || error.errorDescription.hasPrefix("FILE_REFERENCE_") { + } + return failMessages(postbox: postbox, ids: group.map { $0.0 }) + } } else { assertionFailure() return failMessages(postbox: postbox, ids: group.map { $0.0 }) } - } |> switchToLatest + } + |> switchToLatest } - private static func sendSecretMessageContent(transaction: Transaction, message: Message, content: PendingMessageUploadedContent) { + private static func sendSecretMessageContent(transaction: Transaction, message: Message, content: PendingMessageUploadedContentAndReuploadInfo) { var secretFile: SecretChatOutgoingFile? - switch content { + switch content.content { case let .secretMedia(file, size, key): if let fileReference = SecretChatOutgoingFileReference(file) { secretFile = SecretChatOutgoingFile(reference: fileReference, size: size, key: key) @@ -724,7 +719,8 @@ public final class PendingMessageManager { } } - private func sendMessageContent(network: Network, postbox: Postbox, stateManager: AccountStateManager, messageId: MessageId, content: PendingMessageUploadedContent) -> Signal { + private func sendMessageContent(network: Network, postbox: Postbox, stateManager: AccountStateManager, messageId: MessageId, content: PendingMessageUploadedContentAndReuploadInfo) -> Signal { + let queue = self.queue return postbox.transaction { [weak self] transaction -> Signal in guard let message = transaction.getMessage(messageId) else { return .complete() @@ -772,47 +768,49 @@ public final class PendingMessageManager { let dependencyTag = PendingMessageRequestDependencyTag(messageId: messageId) - let sendMessageRequest: Signal - switch content { + let sendMessageRequest: Signal + switch content.content { case .text: sendMessageRequest = network.request(Api.functions.messages.sendMessage(flags: flags, peer: inputPeer, replyToMsgId: replyMessageId, message: message.text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities), tag: dependencyTag) - |> mapError { _ -> NoError in - return NoError() - } case let .media(inputMedia, text): sendMessageRequest = network.request(Api.functions.messages.sendMedia(flags: flags, peer: inputPeer, replyToMsgId: replyMessageId, media: inputMedia, message: text, randomId: uniqueId, replyMarkup: nil, entities: messageEntities), tag: dependencyTag) - |> mapError { _ -> NoError in - return NoError() - } case let .forward(sourceInfo): if let forwardSourceInfoAttribute = forwardSourceInfoAttribute, let sourcePeer = transaction.getPeer(forwardSourceInfoAttribute.messageId.peerId), let sourceInputPeer = apiInputPeer(sourcePeer) { sendMessageRequest = network.request(Api.functions.messages.forwardMessages(flags: 0, fromPeer: sourceInputPeer, id: [sourceInfo.messageId.id], randomId: [uniqueId], toPeer: inputPeer), tag: dependencyTag) - |> mapError { _ -> NoError in - return NoError() - } } else { - sendMessageRequest = .fail(NoError()) + sendMessageRequest = .fail(MTRpcError(errorCode: 400, errorDescription: "internal")) } case let .chatContextResult(chatContextResult): sendMessageRequest = network.request(Api.functions.messages.sendInlineBotResult(flags: flags, peer: inputPeer, replyToMsgId: replyMessageId, randomId: uniqueId, queryId: chatContextResult.queryId, id: chatContextResult.id)) - |> mapError { _ -> NoError in - return NoError() - } case .secretMedia: assertionFailure() - sendMessageRequest = .fail(NoError()) + sendMessageRequest = .fail(MTRpcError(errorCode: 400, errorDescription: "internal")) } return sendMessageRequest - |> mapToSignal { result -> Signal in - if let strongSelf = self { - return strongSelf.applySentMessage(postbox: postbox, stateManager: stateManager, message: message, result: result) - } else { - return .never() + |> mapToSignal { result -> Signal in + if let strongSelf = self { + return strongSelf.applySentMessage(postbox: postbox, stateManager: stateManager, message: message, result: result) + |> mapError { _ -> MTRpcError in + return MTRpcError(errorCode: 400, errorDescription: "internal") } + } else { + return .never() } - |> `catch` { _ -> Signal in - let modify = postbox.transaction { transaction -> Void in + } + |> `catch` { error -> Signal in + queue.async { + guard let strongSelf = self, let context = strongSelf.messageContexts[messageId] else { + return + } + if error.errorDescription.hasPrefix("FILEREF_INVALID") || error.errorDescription.hasPrefix("FILE_REFERENCE_") { + if !context.forcedReuploadOnce { + context.forcedReuploadOnce = true + strongSelf.beginSendingMessages([messageId]) + return + } + } + let _ = (postbox.transaction { transaction -> Void in transaction.updateMessage(message.id, update: { currentMessage in var storeForwardInfo: StoreMessageForwardInfo? if let forwardInfo = currentMessage.forwardInfo { @@ -820,9 +818,10 @@ public final class PendingMessageManager { } return .update(StoreMessage(id: message.id, globallyUniqueId: currentMessage.globallyUniqueId, groupingKey: currentMessage.groupingKey, timestamp: currentMessage.timestamp, flags: [.Failed], tags: currentMessage.tags, globalTags: currentMessage.globalTags, localTags: currentMessage.localTags, forwardInfo: storeForwardInfo, authorId: currentMessage.author?.id, text: currentMessage.text, attributes: currentMessage.attributes, media: currentMessage.media)) }) - } - - return modify + }).start() + } + + return .complete() } } else { return postbox.transaction { transaction -> Void in diff --git a/TelegramCore/PendingMessageUploadedContent.swift b/TelegramCore/PendingMessageUploadedContent.swift index 7c5cf47184..ffeafe8c71 100644 --- a/TelegramCore/PendingMessageUploadedContent.swift +++ b/TelegramCore/PendingMessageUploadedContent.swift @@ -17,25 +17,29 @@ enum PendingMessageUploadedContent { case secretMedia(Api.InputEncryptedFile, Int32, SecretFileEncryptionKey) } +enum PendingMessageReuploadInfo { + case reuploadFile(FileMediaReference) +} + +struct PendingMessageUploadedContentAndReuploadInfo { + let content: PendingMessageUploadedContent + let reuploadInfo: PendingMessageReuploadInfo? +} + enum PendingMessageUploadedContentResult { case progress(Float) - case content(PendingMessageUploadedContent) + case content(PendingMessageUploadedContentAndReuploadInfo) } enum PendingMessageUploadError { case generic } -enum PendingMessageUploadContent { - case ready(PendingMessageUploadedContent) - case upload(Signal) +func messageContentToUpload(network: Network, postbox: Postbox, auxiliaryMethods: AccountAuxiliaryMethods, transformOutgoingMessageMedia: TransformOutgoingMessageMedia?, messageMediaPreuploadManager: MessageMediaPreuploadManager, revalidationContext: MediaReferenceRevalidationContext, forceReupload: Bool, message: Message) -> Signal { + return messageContentToUpload(network: network, postbox: postbox, auxiliaryMethods: auxiliaryMethods, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, revalidationContext: revalidationContext, forceReupload: forceReupload, peerId: message.id.peerId, messageId: message.id, attributes: message.attributes, text: message.text, media: message.media) } -func messageContentToUpload(network: Network, postbox: Postbox, auxiliaryMethods: AccountAuxiliaryMethods, transformOutgoingMessageMedia: TransformOutgoingMessageMedia?, messageMediaPreuploadManager: MessageMediaPreuploadManager, message: Message) -> PendingMessageUploadContent { - return messageContentToUpload(network: network, postbox: postbox, auxiliaryMethods: auxiliaryMethods, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, peerId: message.id.peerId, messageId: message.id, attributes: message.attributes, text: message.text, media: message.media) -} - -func messageContentToUpload(network: Network, postbox: Postbox, auxiliaryMethods: AccountAuxiliaryMethods, transformOutgoingMessageMedia: TransformOutgoingMessageMedia?, messageMediaPreuploadManager: MessageMediaPreuploadManager, peerId: PeerId, messageId: MessageId?, attributes: [MessageAttribute], text: String, media: [Media]) -> PendingMessageUploadContent { +func messageContentToUpload(network: Network, postbox: Postbox, auxiliaryMethods: AccountAuxiliaryMethods, transformOutgoingMessageMedia: TransformOutgoingMessageMedia?, messageMediaPreuploadManager: MessageMediaPreuploadManager, revalidationContext: MediaReferenceRevalidationContext, forceReupload: Bool, peerId: PeerId, messageId: MessageId?, attributes: [MessageAttribute], text: String, media: [Media]) -> Signal { var contextResult: OutgoingChatContextResultMessageAttribute? var autoremoveAttribute: AutoremoveTimeoutMessageAttribute? for attribute in attributes { @@ -58,40 +62,51 @@ func messageContentToUpload(network: Network, postbox: Postbox, auxiliaryMethods } if let forwardInfo = forwardInfo { - return .ready(.forward(forwardInfo)) - } - - if let forwardInfo = forwardInfo { - return .ready(.forward(forwardInfo)) + return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .forward(forwardInfo), reuploadInfo: nil))) } else if let contextResult = contextResult { - return .ready(.chatContextResult(contextResult)) - } else if let media = media.first, let mediaResult = mediaContentToUpload(network: network, postbox: postbox, auxiliaryMethods: auxiliaryMethods, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, peerId: peerId, media: media, text: text, autoremoveAttribute: autoremoveAttribute, messageId: messageId, attributes: attributes) { + return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .chatContextResult(contextResult), reuploadInfo: nil))) + } else if let media = media.first, let mediaResult = mediaContentToUpload(network: network, postbox: postbox, auxiliaryMethods: auxiliaryMethods, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, revalidationContext: revalidationContext, forceReupload: forceReupload, peerId: peerId, media: media, text: text, autoremoveAttribute: autoremoveAttribute, messageId: messageId, attributes: attributes) { return mediaResult } else { - return .ready(.text(text)) + return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .text(text), reuploadInfo: nil))) } } -func mediaContentToUpload(network: Network, postbox: Postbox, auxiliaryMethods: AccountAuxiliaryMethods, transformOutgoingMessageMedia: TransformOutgoingMessageMedia?, messageMediaPreuploadManager: MessageMediaPreuploadManager, peerId: PeerId, media: Media, text: String, autoremoveAttribute: AutoremoveTimeoutMessageAttribute?, messageId: MessageId?, attributes: [MessageAttribute]) -> PendingMessageUploadContent? { +func mediaContentToUpload(network: Network, postbox: Postbox, auxiliaryMethods: AccountAuxiliaryMethods, transformOutgoingMessageMedia: TransformOutgoingMessageMedia?, messageMediaPreuploadManager: MessageMediaPreuploadManager, revalidationContext: MediaReferenceRevalidationContext, forceReupload: Bool, peerId: PeerId, media: Media, text: String, autoremoveAttribute: AutoremoveTimeoutMessageAttribute?, messageId: MessageId?, attributes: [MessageAttribute]) -> Signal? { if let image = media as? TelegramMediaImage, let _ = largestImageRepresentation(image.representations) { - if let reference = image.reference, case let .cloud(id, accessHash) = reference { - return .ready(.media(Api.InputMedia.inputMediaPhoto(flags: 0, id: Api.InputPhoto.inputPhoto(id: id, accessHash: accessHash), ttlSeconds: nil), text)) + if let reference = image.reference, case let .cloud(id, accessHash, maybeFileReference) = reference, let fileReference = maybeFileReference { + return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(Api.InputMedia.inputMediaPhoto(flags: 0, id: Api.InputPhoto.inputPhoto(id: id, accessHash: accessHash, fileReference: Buffer(data: fileReference)), ttlSeconds: nil), text), reuploadInfo: nil))) } else { - return .upload(uploadedMediaImageContent(network: network, postbox: postbox, transformOutgoingMessageMedia: transformOutgoingMessageMedia, peerId: peerId, image: image, text: text, autoremoveAttribute: autoremoveAttribute)) + return uploadedMediaImageContent(network: network, postbox: postbox, transformOutgoingMessageMedia: transformOutgoingMessageMedia, peerId: peerId, image: image, text: text, autoremoveAttribute: autoremoveAttribute) } } else if let file = media as? TelegramMediaFile { if let resource = file.resource as? CloudDocumentMediaResource { if peerId.namespace == Namespaces.Peer.SecretChat { - return .upload(uploadedMediaFileContent(network: network, postbox: postbox, auxiliaryMethods: auxiliaryMethods, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, peerId: peerId, messageId: messageId, text: text, attributes: attributes, file: file)) + return uploadedMediaFileContent(network: network, postbox: postbox, auxiliaryMethods: auxiliaryMethods, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, peerId: peerId, messageId: messageId, text: text, attributes: attributes, file: file) } else { - return .ready(.media(Api.InputMedia.inputMediaDocument(flags: 0, id: Api.InputDocument.inputDocument(id: resource.fileId, accessHash: resource.accessHash), ttlSeconds: nil), text)) + if forceReupload { + let mediaReference: AnyMediaReference + if file.isSticker { + mediaReference = .standalone(media: file) + } else { + mediaReference = .savedGif(media: file) + } + return revalidateMediaResourceReference(postbox: postbox, network: network, revalidationContext: revalidationContext, info: TelegramCloudMediaResourceFetchInfo(reference: mediaReference.resourceReference(file.resource)), resource: resource) + |> mapError { _ -> PendingMessageUploadError in + return .generic + } + |> mapToSignal { fileReference -> Signal in + return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(Api.InputMedia.inputMediaDocument(flags: 0, id: Api.InputDocument.inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: fileReference)), ttlSeconds: nil), text), reuploadInfo: nil))) + } + } + return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(Api.InputMedia.inputMediaDocument(flags: 0, id: Api.InputDocument.inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: resource.fileReference ?? Data())), ttlSeconds: nil), text), reuploadInfo: nil))) } } else { - return .upload(uploadedMediaFileContent(network: network, postbox: postbox, auxiliaryMethods: auxiliaryMethods, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, peerId: peerId, messageId: messageId, text: text, attributes: attributes, file: file)) + return uploadedMediaFileContent(network: network, postbox: postbox, auxiliaryMethods: auxiliaryMethods, transformOutgoingMessageMedia: transformOutgoingMessageMedia, messageMediaPreuploadManager: messageMediaPreuploadManager, peerId: peerId, messageId: messageId, text: text, attributes: attributes, file: file) } } else if let contact = media as? TelegramMediaContact { let input = Api.InputMedia.inputMediaContact(phoneNumber: contact.phoneNumber, firstName: contact.firstName, lastName: contact.lastName, vcard: contact.vCardData ?? "") - return .ready(.media(input, text)) + return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(input, text), reuploadInfo: nil))) } else if let map = media as? TelegramMediaMap { let input: Api.InputMedia if let liveBroadcastingTimeout = map.liveBroadcastingTimeout { @@ -101,7 +116,7 @@ func mediaContentToUpload(network: Network, postbox: Postbox, auxiliaryMethods: } else { input = .inputMediaGeoPoint(geoPoint: Api.InputGeoPoint.inputGeoPoint(lat: map.latitude, long: map.longitude)) } - return .ready(.media(input, text)) + return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(input, text), reuploadInfo: nil))) } else { return nil } @@ -152,13 +167,14 @@ private func maybePredownloadedImageResource(postbox: Postbox, peerId: PeerId, r } } }) - let fetched = postbox.mediaBox.fetchedResource(resource, tag: nil).start() + let fetched = postbox.mediaBox.fetchedResource(resource, parameters: nil).start() return ActionDisposable { data.dispose() fetched.dispose() } - } |> switchToLatest + } + |> switchToLatest } private func maybePredownloadedFileResource(postbox: Postbox, auxiliaryMethods: AccountAuxiliaryMethods, peerId: PeerId, resource: MediaResource) -> Signal { @@ -167,20 +183,21 @@ private func maybePredownloadedFileResource(postbox: Postbox, auxiliaryMethods: } return auxiliaryMethods.fetchResourceMediaReferenceHash(resource) - |> mapToSignal { hash -> Signal in - if let hash = hash { - let reference: CachedSentMediaReferenceKey = .file(hash: hash) - return cachedSentMediaReference(postbox: postbox, key: reference) |> map { media -> PredownloadedResource in - if let media = media { - return .media(media) - } else { - return .localReference(reference) - } + |> mapToSignal { hash -> Signal in + if let hash = hash { + let reference: CachedSentMediaReferenceKey = .file(hash: hash) + return cachedSentMediaReference(postbox: postbox, key: reference) |> map { media -> PredownloadedResource in + if let media = media { + return .media(media) + } else { + return .localReference(reference) } - } else { - return .single(.localReference(nil)) } - } |> mapError { _ -> PendingMessageUploadError in return .generic } + } else { + return .single(.localReference(nil)) + } + } + |> mapError { _ -> PendingMessageUploadError in return .generic } } private func maybeCacheUploadedResource(postbox: Postbox, key: CachedSentMediaReferenceKey?, result: PendingMessageUploadedContentResult, media: Media) -> Signal { @@ -198,77 +215,78 @@ private func uploadedMediaImageContent(network: Network, postbox: Postbox, trans if let largestRepresentation = largestImageRepresentation(image.representations) { let predownloadedResource: Signal = maybePredownloadedImageResource(postbox: postbox, peerId: peerId, resource: largestRepresentation.resource) return predownloadedResource - |> mapToSignal { result -> Signal in - var referenceKey: CachedSentMediaReferenceKey? - switch result { - case let .media(media): - if let image = media as? TelegramMediaImage, let reference = image.reference, case let .cloud(id, accessHash) = reference { + |> mapToSignal { result -> Signal in + var referenceKey: CachedSentMediaReferenceKey? + switch result { + case let .media(media): + if let image = media as? TelegramMediaImage, let reference = image.reference, case let .cloud(id, accessHash, maybeFileReference) = reference, let fileReference = maybeFileReference { + var flags: Int32 = 0 + var ttlSeconds: Int32? + if let autoremoveAttribute = autoremoveAttribute { + flags |= 1 << 1 + ttlSeconds = autoremoveAttribute.timeout + } + return .single(.progress(1.0)) + |> then(.single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(.inputMediaPhoto(flags: flags, id: .inputPhoto(id: id, accessHash: accessHash, fileReference: Buffer(data: fileReference)), ttlSeconds: ttlSeconds), text), reuploadInfo: nil)))) + } + case let .localReference(key): + referenceKey = key + case .none: + referenceKey = nil + } + return multipartUpload(network: network, postbox: postbox, source: .resource(largestRepresentation.resource), encrypt: peerId.namespace == Namespaces.Peer.SecretChat, tag: TelegramMediaResourceFetchTag(statsCategory: .image), hintFileSize: nil, hintFileIsLarge: false) + |> mapError { _ -> PendingMessageUploadError in return .generic } + |> mapToSignal { next -> Signal in + switch next { + case let .progress(progress): + return .single(.progress(progress)) + case let .inputFile(file): var flags: Int32 = 0 var ttlSeconds: Int32? if let autoremoveAttribute = autoremoveAttribute { flags |= 1 << 1 ttlSeconds = autoremoveAttribute.timeout } - return .single(.progress(1.0)) |> then(.single(.content(.media(.inputMediaPhoto(flags: flags, id: .inputPhoto(id: id, accessHash: accessHash), ttlSeconds: ttlSeconds), text)))) - } - case let .localReference(key): - referenceKey = key - case .none: - referenceKey = nil - } - return multipartUpload(network: network, postbox: postbox, source: .resource(largestRepresentation.resource), encrypt: peerId.namespace == Namespaces.Peer.SecretChat, tag: TelegramMediaResourceFetchTag(statsCategory: .image), hintFileSize: nil, hintFileIsLarge: false) - |> mapError { _ -> PendingMessageUploadError in return .generic } - |> mapToSignal { next -> Signal in - switch next { - case let .progress(progress): - return .single(.progress(progress)) - case let .inputFile(file): - var flags: Int32 = 0 - var ttlSeconds: Int32? - if let autoremoveAttribute = autoremoveAttribute { - flags |= 1 << 1 - ttlSeconds = autoremoveAttribute.timeout - } - return postbox.transaction { transaction -> Api.InputPeer? in - return transaction.getPeer(peerId).flatMap(apiInputPeer) - } - |> mapError { _ -> PendingMessageUploadError in return .generic } - |> mapToSignal { inputPeer -> Signal in - if let inputPeer = inputPeer { - if autoremoveAttribute != nil { - return .single(.content(.media(.inputMediaUploadedPhoto(flags: flags, file: file, stickers: nil, ttlSeconds: ttlSeconds), text))) - } - - return network.request(Api.functions.messages.uploadMedia(peer: inputPeer, media: Api.InputMedia.inputMediaUploadedPhoto(flags: flags, file: file, stickers: nil, ttlSeconds: ttlSeconds))) - |> mapError { _ -> PendingMessageUploadError in return .generic } - |> mapToSignal { result -> Signal in - switch result { - case let .messageMediaPhoto(_, photo, _): - if let photo = photo, let mediaImage = telegramMediaImageFromApiPhoto(photo), let reference = mediaImage.reference, case let .cloud(id, accessHash) = reference { - var flags: Int32 = 0 - var ttlSeconds: Int32? - if let autoremoveAttribute = autoremoveAttribute { - flags |= 1 << 1 - ttlSeconds = autoremoveAttribute.timeout - } - return maybeCacheUploadedResource(postbox: postbox, key: referenceKey, result: .content(.media(.inputMediaPhoto(flags: flags, id: .inputPhoto(id: id, accessHash: accessHash), ttlSeconds: ttlSeconds), text)), media: mediaImage) - } - default: - break + return postbox.transaction { transaction -> Api.InputPeer? in + return transaction.getPeer(peerId).flatMap(apiInputPeer) + } + |> mapError { _ -> PendingMessageUploadError in return .generic } + |> mapToSignal { inputPeer -> Signal in + if let inputPeer = inputPeer { + if autoremoveAttribute != nil { + return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(.inputMediaUploadedPhoto(flags: flags, file: file, stickers: nil, ttlSeconds: ttlSeconds), text), reuploadInfo: nil))) + } + + return network.request(Api.functions.messages.uploadMedia(peer: inputPeer, media: Api.InputMedia.inputMediaUploadedPhoto(flags: flags, file: file, stickers: nil, ttlSeconds: ttlSeconds))) + |> mapError { _ -> PendingMessageUploadError in return .generic } + |> mapToSignal { result -> Signal in + switch result { + case let .messageMediaPhoto(_, photo, _): + if let photo = photo, let mediaImage = telegramMediaImageFromApiPhoto(photo), let reference = mediaImage.reference, case let .cloud(id, accessHash, maybeFileReference) = reference, let fileReference = maybeFileReference { + var flags: Int32 = 0 + var ttlSeconds: Int32? + if let autoremoveAttribute = autoremoveAttribute { + flags |= 1 << 1 + ttlSeconds = autoremoveAttribute.timeout + } + return maybeCacheUploadedResource(postbox: postbox, key: referenceKey, result: .content(PendingMessageUploadedContentAndReuploadInfo(content: .media(.inputMediaPhoto(flags: flags, id: .inputPhoto(id: id, accessHash: accessHash, fileReference: Buffer(data: fileReference)), ttlSeconds: ttlSeconds), text), reuploadInfo: nil)), media: mediaImage) } - return .fail(.generic) - } - } else { + default: + break + } return .fail(.generic) } + } else { + return .fail(.generic) } - case let .inputSecretFile(file, size, key): - return .single(.content(.secretMedia(file, size, key))) - } + } + case let .inputSecretFile(file, size, key): + return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .secretMedia(file, size, key), reuploadInfo: nil))) } - } + } + } } else { - return .single(.content(.text(text))) + return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .text(text), reuploadInfo: nil))) } } @@ -340,9 +358,9 @@ private enum UploadedMediaThumbnailResult { case none } -private enum UploadedMediaThumbnail { +private enum UploadedMediaFileAndThumbnail { case pending - case done(UploadedMediaThumbnailResult) + case done(TelegramMediaFile, UploadedMediaThumbnailResult) } private func uploadedThumbnail(network: Network, postbox: Postbox, image: TelegramMediaImageRepresentation) -> Signal { @@ -375,12 +393,14 @@ public func statsCategoryForFileWithAttributes(_ attributes: [TelegramMediaFileA } private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxiliaryMethods: AccountAuxiliaryMethods, transformOutgoingMessageMedia: TransformOutgoingMessageMedia?, messageMediaPreuploadManager: MessageMediaPreuploadManager, peerId: PeerId, messageId: MessageId?, text: String, attributes: [MessageAttribute], file: TelegramMediaFile) -> Signal { - return maybePredownloadedFileResource(postbox: postbox, auxiliaryMethods: auxiliaryMethods, peerId: peerId, resource: file.resource) |> mapToSignal { result -> Signal in + return maybePredownloadedFileResource(postbox: postbox, auxiliaryMethods: auxiliaryMethods, peerId: peerId, resource: file.resource) + |> mapToSignal { result -> Signal in var referenceKey: CachedSentMediaReferenceKey? switch result { case let .media(media): - if let file = media as? TelegramMediaFile, let resource = file.resource as? CloudDocumentMediaResource { - return .single(.progress(1.0)) |> then(.single(.content(.media(Api.InputMedia.inputMediaDocument(flags: 0, id: Api.InputDocument.inputDocument(id: resource.fileId, accessHash: resource.accessHash), ttlSeconds: nil), text)))) + if let file = media as? TelegramMediaFile, let resource = file.resource as? CloudDocumentMediaResource, let fileReference = resource.fileReference { + return .single(.progress(1.0)) + |> then(.single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(Api.InputMedia.inputMediaDocument(flags: 0, id: Api.InputDocument.inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: fileReference)), ttlSeconds: nil), text), reuploadInfo: nil)))) } case let .localReference(key): referenceKey = key @@ -412,10 +432,10 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxili let transform: Signal if let transformOutgoingMessageMedia = transformOutgoingMessageMedia, let messageId = messageId, !alreadyTransformed { transform = .single(.pending) - |> then(transformOutgoingMessageMedia(postbox, network, file, false) - |> mapToSignal { media -> Signal in + |> then(transformOutgoingMessageMedia(postbox, network, .standalone(media: file), false) + |> mapToSignal { mediaReference -> Signal in return postbox.transaction { transaction -> UploadedMediaTransform in - if let media = media { + if let media = mediaReference?.media { if let id = media.id { transaction.updateMedia(id, update: media) transaction.updateMessage(messageId, update: { currentMessage in @@ -443,40 +463,40 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxili transform = .single(.done(file)) } - let thumbnail: Signal = .single(.pending) + let transformedFileAndThumbnail: Signal = .single(.pending) |> then(transform - |> mapToSignalPromotingError { media -> Signal in + |> mapToSignalPromotingError { media -> Signal in switch media { case .pending: return .single(.pending) case let .done(media): if let media = media as? TelegramMediaFile, let smallestThumbnail = smallestImageRepresentation(media.previewRepresentations) { if peerId.namespace == Namespaces.Peer.SecretChat { - return .single(.done(.none)) + return .single(.done(media, .none)) } else { return uploadedThumbnail(network: network, postbox: postbox, image: smallestThumbnail) |> mapError { _ -> PendingMessageUploadError in return .generic } |> map { result in if let result = result { - return .done(.file(result)) + return .done(media, .file(result)) } else { - return .done(.none) + return .done(media, .none) } } } } else { - return .single(.done(.none)) + return .single(.done(file, .none)) } } }) - return combineLatest(upload, thumbnail) - |> mapToSignal { content, thumbnailResult -> Signal in + return combineLatest(upload, transformedFileAndThumbnail) + |> mapToSignal { content, fileAndThumbnailResult -> Signal in switch content { case let .progress(progress): return .single(.progress(progress)) case let .inputFile(inputFile): - if case let .done(thumbnail) = thumbnailResult { + if case let .done(file, thumbnail) = fileAndThumbnailResult { var flags: Int32 = 0 var thumbnailFile: Api.InputFile? @@ -497,7 +517,7 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxili } if ttlSeconds != nil { - return .single(.content(.media(.inputMediaUploadedDocument(flags: flags, file: inputFile, thumb: thumbnailFile, mimeType: file.mimeType, attributes: inputDocumentAttributesFromFileAttributes(file.attributes), stickers: nil, ttlSeconds: ttlSeconds), text))) + return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .media(.inputMediaUploadedDocument(flags: flags, file: inputFile, thumb: thumbnailFile, mimeType: file.mimeType, attributes: inputDocumentAttributesFromFileAttributes(file.attributes), stickers: nil, ttlSeconds: ttlSeconds), text), reuploadInfo: nil))) } return postbox.transaction { transaction -> Api.InputPeer? in @@ -507,18 +527,18 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxili |> mapToSignal { inputPeer -> Signal in if let inputPeer = inputPeer { return network.request(Api.functions.messages.uploadMedia(peer: inputPeer, media: .inputMediaUploadedDocument(flags: flags, file: inputFile, thumb: thumbnailFile, mimeType: file.mimeType, attributes: inputDocumentAttributesFromFileAttributes(file.attributes), stickers: nil, ttlSeconds: ttlSeconds))) - |> mapError { _ -> PendingMessageUploadError in return .generic } - |> mapToSignal { result -> Signal in - switch result { - case let .messageMediaDocument(_, document, _): - if let document = document, let mediaFile = telegramMediaFileFromApiDocument(document), let resource = mediaFile.resource as? CloudDocumentMediaResource { - return maybeCacheUploadedResource(postbox: postbox, key: referenceKey, result: .content(.media(.inputMediaDocument(flags: 0, id: .inputDocument(id: resource.fileId, accessHash: resource.accessHash), ttlSeconds: nil), text)), media: mediaFile) - } - default: - break - } - return .fail(.generic) + |> mapError { _ -> PendingMessageUploadError in return .generic } + |> mapToSignal { result -> Signal in + switch result { + case let .messageMediaDocument(_, document, _): + if let document = document, let mediaFile = telegramMediaFileFromApiDocument(document), let resource = mediaFile.resource as? CloudDocumentMediaResource, let fileReference = resource.fileReference { + return maybeCacheUploadedResource(postbox: postbox, key: referenceKey, result: .content(PendingMessageUploadedContentAndReuploadInfo(content: .media(.inputMediaDocument(flags: 0, id: .inputDocument(id: resource.fileId, accessHash: resource.accessHash, fileReference: Buffer(data: fileReference)), ttlSeconds: nil), text), reuploadInfo: nil)), media: mediaFile) + } + default: + break } + return .fail(.generic) + } } else { return .fail(.generic) } @@ -527,8 +547,8 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, auxili return .complete() } case let .inputSecretFile(file, size, key): - if case .done = thumbnailResult { - return .single(.content(.secretMedia(file, size, key))) + if case .done = fileAndThumbnailResult { + return .single(.content(PendingMessageUploadedContentAndReuploadInfo(content: .secretMedia(file, size, key), reuploadInfo: nil))) } else { return .complete() } diff --git a/TelegramCore/ProcessSecretChatIncomingDecryptedOperations.swift b/TelegramCore/ProcessSecretChatIncomingDecryptedOperations.swift index 3f8a506271..5235dc48ae 100644 --- a/TelegramCore/ProcessSecretChatIncomingDecryptedOperations.swift +++ b/TelegramCore/ProcessSecretChatIncomingDecryptedOperations.swift @@ -562,6 +562,18 @@ private func parseEntities(_ entities: [SecretApi46.MessageEntity]?) -> TextEnti return TextEntitiesMessageAttribute(entities: result) } +private func maximumMediaAutoremoveTimeout(_ media: [Media]) -> Int32 { + var maxDuration: Int32 = 0 + for media in media { + if let file = media as? TelegramMediaFile { + if let duration = file.duration { + maxDuration = max(maxDuration, duration) + } + } + } + return maxDuration +} + private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32, timestamp: Int32, apiMessage: SecretApi46.DecryptedMessage, file: SecretChatFileReference?, messageIdForGloballyUniqueMessageId: (Int64) -> MessageId?) -> (StoreMessage, [(MediaResource, Data)])? { switch apiMessage { case let .decryptedMessage(_, randomId, ttl, message, media, entities, viaBotName, replyToRandomId): @@ -570,10 +582,6 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 var attributes: [MessageAttribute] = [] var resources: [(MediaResource, Data)] = [] - if ttl > 0 { - attributes.append(AutoremoveTimeoutMessageAttribute(timeout: ttl, countdownBeginTime: nil)) - } - attributes.append(parseEntities(entities)) if let viaBotName = viaBotName, !viaBotName.isEmpty { @@ -599,7 +607,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 } case let .decryptedMessageMediaAudio(duration, mimeType, size, key, iv): if let file = file { - let fileMedia = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.CloudSecretFile, id: file.id), resource: file.resource(key: SecretFileEncryptionKey(aesKey: key.makeData(), aesIv: iv.makeData()), decryptedSize: size), previewRepresentations: [], mimeType: mimeType, size: Int(size), attributes: [TelegramMediaFileAttribute.Audio(isVoice: true, duration: Int(duration), title: nil, performer: nil, waveform: nil)]) + let fileMedia = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.CloudSecretFile, id: file.id), reference: nil, resource: file.resource(key: SecretFileEncryptionKey(aesKey: key.makeData(), aesIv: iv.makeData()), decryptedSize: size), previewRepresentations: [], mimeType: mimeType, size: Int(size), attributes: [TelegramMediaFileAttribute.Audio(isVoice: true, duration: Int(duration), title: nil, performer: nil, waveform: nil)]) parsedMedia.append(fileMedia) } case let .decryptedMessageMediaDocument(thumb, thumbW, thumbH, mimeType, size, key, iv, attributes, caption): @@ -619,7 +627,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: CGSize(width: CGFloat(thumbW), height: CGFloat(thumbH)), resource: resource)) resources.append((resource, thumb.makeData())) } - let fileMedia = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.CloudSecretFile, id: file.id), resource: file.resource(key: SecretFileEncryptionKey(aesKey: key.makeData(), aesIv: iv.makeData()), decryptedSize: size), previewRepresentations: previewRepresentations, mimeType: mimeType, size: Int(size), attributes: parsedAttributes) + let fileMedia = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.CloudSecretFile, id: file.id), reference: nil, resource: file.resource(key: SecretFileEncryptionKey(aesKey: key.makeData(), aesIv: iv.makeData()), decryptedSize: size), previewRepresentations: previewRepresentations, mimeType: mimeType, size: Int(size), attributes: parsedAttributes) parsedMedia.append(fileMedia) } case let .decryptedMessageMediaVideo(thumb, thumbW, thumbH, duration, mimeType, w, h, size, key, iv, caption): @@ -634,7 +642,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: CGSize(width: CGFloat(thumbW), height: CGFloat(thumbH)), resource: resource)) resources.append((resource, thumb.makeData())) } - let fileMedia = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.CloudSecretFile, id: file.id), resource: file.resource(key: SecretFileEncryptionKey(aesKey: key.makeData(), aesIv: iv.makeData()), decryptedSize: size), previewRepresentations: previewRepresentations, mimeType: mimeType, size: Int(size), attributes: parsedAttributes) + let fileMedia = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.CloudSecretFile, id: file.id), reference: nil, resource: file.resource(key: SecretFileEncryptionKey(aesKey: key.makeData(), aesIv: iv.makeData()), decryptedSize: size), previewRepresentations: previewRepresentations, mimeType: mimeType, size: Int(size), attributes: parsedAttributes) parsedMedia.append(fileMedia) } case let .decryptedMessageMediaExternalDocument(id, accessHash, _, mimeType, size, thumb, dcId, attributes): @@ -649,7 +657,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 case let .photoSize(_, location, w, h, size): switch location { case let .fileLocation(dcId, volumeId, localId, secret): - previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: CGSize(width: CGFloat(w), height: CGFloat(h)), resource: CloudFileMediaResource(datacenterId: Int(dcId), volumeId: volumeId, localId: localId, secret: secret, size: size == 0 ? nil : Int(size)))) + previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: CGSize(width: CGFloat(w), height: CGFloat(h)), resource: CloudFileMediaResource(datacenterId: Int(dcId), volumeId: volumeId, localId: localId, secret: secret, size: size == 0 ? nil : Int(size), fileReference: nil))) case .fileLocationUnavailable: break } @@ -657,7 +665,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 if bytes.size > 0 { switch location { case let .fileLocation(dcId, volumeId, localId, secret): - let resource = CloudFileMediaResource(datacenterId: Int(dcId), volumeId: volumeId, localId: localId, secret: secret, size: bytes.size) + let resource = CloudFileMediaResource(datacenterId: Int(dcId), volumeId: volumeId, localId: localId, secret: secret, size: bytes.size, fileReference: nil) resources.append((resource, bytes.makeData())) previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: CGSize(width: CGFloat(w), height: CGFloat(h)), resource: resource)) case .fileLocationUnavailable: @@ -667,7 +675,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 default: break } - let fileMedia = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.CloudFile, id: id), resource: CloudDocumentMediaResource(datacenterId: Int(dcId), fileId: id, accessHash: accessHash, size: Int(size)), previewRepresentations: previewRepresentations, mimeType: mimeType, size: Int(size), attributes: parsedAttributes) + let fileMedia = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.CloudFile, id: id), reference: nil, resource: CloudDocumentMediaResource(datacenterId: Int(dcId), fileId: id, accessHash: accessHash, size: Int(size), fileReference: nil), previewRepresentations: previewRepresentations, mimeType: mimeType, size: Int(size), attributes: parsedAttributes) parsedMedia.append(fileMedia) case let .decryptedMessageMediaWebPage(url): parsedMedia.append(TelegramMediaWebpage(webpageId: MediaId(namespace: Namespaces.Media.LocalWebpage, id: arc4random64()), content: .Pending(0, url))) @@ -681,6 +689,11 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 break } } + + if ttl > 0 { + attributes.append(AutoremoveTimeoutMessageAttribute(timeout: max(ttl, maximumMediaAutoremoveTimeout(parsedMedia)), countdownBeginTime: nil)) + } + if let replyToRandomId = replyToRandomId, let replyMessageId = messageIdForGloballyUniqueMessageId(replyToRandomId) { attributes.append(ReplyMessageAttribute(messageId: replyMessageId)) } @@ -765,10 +778,6 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 var attributes: [MessageAttribute] = [] var resources: [(MediaResource, Data)] = [] - if ttl > 0 { - attributes.append(AutoremoveTimeoutMessageAttribute(timeout: ttl, countdownBeginTime: nil)) - } - if let entitiesAttribute = entities.flatMap(parseEntities) { attributes.append(entitiesAttribute) } @@ -796,7 +805,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 } case let .decryptedMessageMediaAudio(duration, mimeType, size, key, iv): if let file = file { - let fileMedia = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.CloudSecretFile, id: file.id), resource: file.resource(key: SecretFileEncryptionKey(aesKey: key.makeData(), aesIv: iv.makeData()), decryptedSize: size), previewRepresentations: [], mimeType: mimeType, size: Int(size), attributes: [TelegramMediaFileAttribute.Audio(isVoice: true, duration: Int(duration), title: nil, performer: nil, waveform: nil)]) + let fileMedia = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.CloudSecretFile, id: file.id), reference: nil, resource: file.resource(key: SecretFileEncryptionKey(aesKey: key.makeData(), aesIv: iv.makeData()), decryptedSize: size), previewRepresentations: [], mimeType: mimeType, size: Int(size), attributes: [TelegramMediaFileAttribute.Audio(isVoice: true, duration: Int(duration), title: nil, performer: nil, waveform: nil)]) parsedMedia.append(fileMedia) } case let .decryptedMessageMediaDocument(thumb, thumbW, thumbH, mimeType, size, key, iv, attributes, caption): @@ -816,7 +825,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: CGSize(width: CGFloat(thumbW), height: CGFloat(thumbH)), resource: resource)) resources.append((resource, thumb.makeData())) } - let fileMedia = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.CloudSecretFile, id: file.id), resource: file.resource(key: SecretFileEncryptionKey(aesKey: key.makeData(), aesIv: iv.makeData()), decryptedSize: size), previewRepresentations: previewRepresentations, mimeType: mimeType, size: Int(size), attributes: parsedAttributes) + let fileMedia = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.CloudSecretFile, id: file.id), reference: nil, resource: file.resource(key: SecretFileEncryptionKey(aesKey: key.makeData(), aesIv: iv.makeData()), decryptedSize: size), previewRepresentations: previewRepresentations, mimeType: mimeType, size: Int(size), attributes: parsedAttributes) parsedMedia.append(fileMedia) } case let .decryptedMessageMediaVideo(thumb, thumbW, thumbH, duration, mimeType, w, h, size, key, iv, caption): @@ -831,7 +840,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: CGSize(width: CGFloat(thumbW), height: CGFloat(thumbH)), resource: resource)) resources.append((resource, thumb.makeData())) } - let fileMedia = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.CloudSecretFile, id: file.id), resource: file.resource(key: SecretFileEncryptionKey(aesKey: key.makeData(), aesIv: iv.makeData()), decryptedSize: size), previewRepresentations: previewRepresentations, mimeType: mimeType, size: Int(size), attributes: parsedAttributes) + let fileMedia = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.CloudSecretFile, id: file.id), reference: nil, resource: file.resource(key: SecretFileEncryptionKey(aesKey: key.makeData(), aesIv: iv.makeData()), decryptedSize: size), previewRepresentations: previewRepresentations, mimeType: mimeType, size: Int(size), attributes: parsedAttributes) parsedMedia.append(fileMedia) } case let .decryptedMessageMediaExternalDocument(id, accessHash, date, mimeType, size, thumb, dcId, attributes): @@ -846,7 +855,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 case let .photoSize(_, location, w, h, size): switch location { case let .fileLocation(dcId, volumeId, localId, secret): - previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: CGSize(width: CGFloat(w), height: CGFloat(h)), resource: CloudFileMediaResource(datacenterId: Int(dcId), volumeId: volumeId, localId: localId, secret: secret, size: size == 0 ? nil : Int(size)))) + previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: CGSize(width: CGFloat(w), height: CGFloat(h)), resource: CloudFileMediaResource(datacenterId: Int(dcId), volumeId: volumeId, localId: localId, secret: secret, size: size == 0 ? nil : Int(size), fileReference: nil))) case .fileLocationUnavailable: break } @@ -854,7 +863,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 if bytes.size > 0 { switch location { case let .fileLocation(dcId, volumeId, localId, secret): - let resource = CloudFileMediaResource(datacenterId: Int(dcId), volumeId: volumeId, localId: localId, secret: secret, size: bytes.size) + let resource = CloudFileMediaResource(datacenterId: Int(dcId), volumeId: volumeId, localId: localId, secret: secret, size: bytes.size, fileReference: nil) resources.append((resource, bytes.makeData())) previewRepresentations.append(TelegramMediaImageRepresentation(dimensions: CGSize(width: CGFloat(w), height: CGFloat(h)), resource: resource)) case .fileLocationUnavailable: @@ -864,7 +873,7 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 default: break } - let fileMedia = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.CloudFile, id: id), resource: CloudDocumentMediaResource(datacenterId: Int(dcId), fileId: id, accessHash: accessHash, size: Int(size)), previewRepresentations: [], mimeType: mimeType, size: Int(size), attributes: parsedAttributes) + let fileMedia = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.CloudFile, id: id), reference: nil, resource: CloudDocumentMediaResource(datacenterId: Int(dcId), fileId: id, accessHash: accessHash, size: Int(size), fileReference: nil), previewRepresentations: [], mimeType: mimeType, size: Int(size), attributes: parsedAttributes) parsedMedia.append(fileMedia) case let .decryptedMessageMediaWebPage(url): parsedMedia.append(TelegramMediaWebpage(webpageId: MediaId(namespace: Namespaces.Media.LocalWebpage, id: arc4random64()), content: .Pending(0, url))) @@ -879,6 +888,10 @@ private func parseMessage(peerId: PeerId, authorId: PeerId, tagLocalIndex: Int32 } } + if ttl > 0 { + attributes.append(AutoremoveTimeoutMessageAttribute(timeout: max(ttl, maximumMediaAutoremoveTimeout(parsedMedia)), countdownBeginTime: nil)) + } + var groupingKey: Int64? if let groupedId = groupedId { inner: for media in parsedMedia { diff --git a/TelegramCore/RequestChatContextResults.swift b/TelegramCore/RequestChatContextResults.swift index 5d4c0df0b6..0edcda8ff4 100644 --- a/TelegramCore/RequestChatContextResults.swift +++ b/TelegramCore/RequestChatContextResults.swift @@ -9,36 +9,27 @@ import Foundation import MtProtoKitDynamic #endif -public struct ChatContextGeoPoint { - let latitude: Double - let longtitude: Double - public init(latitude: Double, longtitude: Double) { - self.latitude = latitude - self.longtitude = longtitude - } -} - -public func requestChatContextResults(account: Account, botId: PeerId, peerId: PeerId, query: String, offset: String, geopoint: ChatContextGeoPoint? = nil) -> Signal { - return account.postbox.transaction { transaction -> (bot: Peer, peer: Peer)? in +public func requestChatContextResults(account: Account, botId: PeerId, peerId: PeerId, query: String, location: Signal<(Double, Double)?, NoError> = .single(nil), offset: String) -> Signal { + return combineLatest(account.postbox.transaction { transaction -> (bot: Peer, peer: Peer)? in if let bot = transaction.getPeer(botId), let peer = transaction.getPeer(peerId) { return (bot, peer) } else { return nil } - } - |> mapToSignal { botAndPeer -> Signal in + }, location) + |> mapToSignal { botAndPeer, location -> Signal in if let (bot, peer) = botAndPeer, let inputBot = apiInputUser(bot) { var flags: Int32 = 0 var inputPeer: Api.InputPeer = .inputPeerEmpty + var geoPoint: Api.InputGeoPoint? if let actualInputPeer = apiInputPeer(peer) { inputPeer = actualInputPeer } - var inputGeo: Api.InputGeoPoint? = nil - if let geopoint = geopoint { - inputGeo = Api.InputGeoPoint.inputGeoPoint(lat: geopoint.latitude, long: geopoint.longtitude) + if let (latitude, longitude) = location { flags |= (1 << 0) + geoPoint = Api.InputGeoPoint.inputGeoPoint(lat: latitude, long: longitude) } - return account.network.request(Api.functions.messages.getInlineBotResults(flags: flags, bot: inputBot, peer: inputPeer, geoPoint: inputGeo, query: query, offset: offset)) + return account.network.request(Api.functions.messages.getInlineBotResults(flags: flags, bot: inputBot, peer: inputPeer, geoPoint: geoPoint, query: query, offset: offset)) |> map { result -> ChatContextResultCollection? in return ChatContextResultCollection(apiResults: result, botId: bot.id) } diff --git a/TelegramCore/RequestEditMessage.swift b/TelegramCore/RequestEditMessage.swift index ea3b9848ac..32e6ac4740 100644 --- a/TelegramCore/RequestEditMessage.swift +++ b/TelegramCore/RequestEditMessage.swift @@ -27,25 +27,21 @@ public func requestEditMessage(account: Account, messageId: MessageId, text: Str let uploadedMedia: Signal switch media { case .keep: - uploadedMedia = .single(.progress(0.0)) |> then(.single(nil)) + uploadedMedia = .single(.progress(0.0)) + |> then(.single(nil)) case let .update(media): - if let uploadData = mediaContentToUpload(network: account.network, postbox: account.postbox, auxiliaryMethods: account.auxiliaryMethods, transformOutgoingMessageMedia: account.transformOutgoingMessageMedia, messageMediaPreuploadManager: account.messageMediaPreuploadManager, peerId: messageId.peerId, media: media, text: "", autoremoveAttribute: nil, messageId: nil, attributes: []) { - switch uploadData { - case let .ready(content): - uploadedMedia = .single(.content(content)) - case let .upload(upload): - uploadedMedia = .single(.progress(0.027)) |> then(upload) - |> map { result -> PendingMessageUploadedContentResult? in - switch result { - case let .progress(value): - return .progress(max(value, 0.027)) - case let .content(content): - return .content(content) - } - } - |> `catch` { _ -> Signal in - return .single(nil) - } + if let uploadSignal = mediaContentToUpload(network: account.network, postbox: account.postbox, auxiliaryMethods: account.auxiliaryMethods, transformOutgoingMessageMedia: account.transformOutgoingMessageMedia, messageMediaPreuploadManager: account.messageMediaPreuploadManager, revalidationContext: account.mediaReferenceRevalidationContext, forceReupload: false, peerId: messageId.peerId, media: media, text: "", autoremoveAttribute: nil, messageId: nil, attributes: []) { + uploadedMedia = .single(.progress(0.027)) |> then(uploadSignal) + |> map { result -> PendingMessageUploadedContentResult? in + switch result { + case let .progress(value): + return .progress(max(value, 0.027)) + case let .content(content): + return .content(content) + } + } + |> `catch` { _ -> Signal in + return .single(nil) } } else { uploadedMedia = .single(nil) @@ -60,7 +56,7 @@ public func requestEditMessage(account: Account, messageId: MessageId, text: Str case let .progress(value): return .single(.progress(value)) case let .content(content): - pendingMediaContent = content + pendingMediaContent = content.content } } return account.postbox.transaction { transaction -> (Peer?, SimpleDictionary) in diff --git a/TelegramCore/RequestUserPhotos.swift b/TelegramCore/RequestUserPhotos.swift index 74e1d53b24..d18dca664d 100644 --- a/TelegramCore/RequestUserPhotos.swift +++ b/TelegramCore/RequestUserPhotos.swift @@ -49,10 +49,10 @@ public func requestPeerPhotos(account:Account, peerId:PeerId) -> Signal<[Telegra let reference: TelegramMediaImageReference switch photo { case let .photo(data): - reference = .cloud(imageId: data.id, accessHash: data.accessHash) + reference = .cloud(imageId: data.id, accessHash: data.accessHash, fileReference: data.fileReference.makeData()) image = TelegramMediaImage(imageId: MediaId(namespace: Namespaces.Media.CloudImage, id: data.id), representations: telegramMediaImageRepresentationsFromApiSizes(data.sizes), reference: reference) case let .photoEmpty(id: id): - reference = .cloud(imageId: id, accessHash: 0) + reference = .cloud(imageId: id, accessHash: 0, fileReference: nil) image = TelegramMediaImage(imageId: MediaId(namespace: Namespaces.Media.CloudImage, id: id), representations: [], reference: reference) } images.append(TelegramPeerPhoto(image: image, reference: reference, index: i, totalCount: totalCount)) diff --git a/TelegramCore/SearchMessages.swift b/TelegramCore/SearchMessages.swift index 10032a275b..4d521bcd98 100644 --- a/TelegramCore/SearchMessages.swift +++ b/TelegramCore/SearchMessages.swift @@ -169,84 +169,173 @@ public func searchMessages(account: Account, location: SearchMessagesLocation, q return processedSearchResult } - -public func downloadMessage(account: Account, messageId: MessageId) -> Signal { - return account.postbox.transaction { transaction -> Message? in +public func downloadMessage(postbox: Postbox, network: Network, messageId: MessageId) -> Signal { + return postbox.transaction { transaction -> Message? in return transaction.getMessage(messageId) - } |> mapToSignal { message in - if let _ = message { - return .single(message) - } else { - return account.postbox.loadedPeerWithId(messageId.peerId) |> mapToSignal { peer -> Signal in - let signal: Signal - if messageId.peerId.namespace == Namespaces.Peer.CloudChannel { - if let channel = apiInputChannel(peer) { - signal = account.network.request(Api.functions.channels.getMessages(channel: channel, id: [Api.InputMessage.inputMessageID(id: messageId.id)])) - } else { - signal = .complete() - } + } |> mapToSignal { message in + if let _ = message { + return .single(message) + } else { + return postbox.loadedPeerWithId(messageId.peerId) |> mapToSignal { peer -> Signal in + let signal: Signal + if messageId.peerId.namespace == Namespaces.Peer.CloudChannel { + if let channel = apiInputChannel(peer) { + signal = network.request(Api.functions.channels.getMessages(channel: channel, id: [Api.InputMessage.inputMessageID(id: messageId.id)])) } else { - signal = account.network.request(Api.functions.messages.getMessages(id: [Api.InputMessage.inputMessageID(id: messageId.id)])) + signal = .complete() } - - return signal |> mapError {_ in} |> mapToSignal { result -> Signal in - let messages: [Api.Message] - let chats: [Api.Chat] - let users: [Api.User] - switch result { - case let .channelMessages(_, _, _, apiMessages, apiChats, apiUsers): - messages = apiMessages - chats = apiChats - users = apiUsers - case let .messages(apiMessages, apiChats, apiUsers): - messages = apiMessages - chats = apiChats - users = apiUsers - case let.messagesSlice(_, apiMessages, apiChats, apiUsers): - messages = apiMessages - chats = apiChats - users = apiUsers - case .messagesNotModified: - messages = [] - chats = [] - users = [] - } - - let postboxSignal = account.postbox.transaction { transaction -> Message? in - var peers: [PeerId: Peer] = [:] - - for user in users { - if let user = TelegramUser.merge(transaction.getPeer(user.peerId) as? TelegramUser, rhs: user) { - peers[user.id] = user - } - } - - for chat in chats { - if let groupOrChannel = mergeGroupOrChannel(lhs: transaction.getPeer(chat.peerId), rhs: chat) { - peers[groupOrChannel.id] = groupOrChannel - } - } - - var renderedMessages: [Message] = [] - for message in messages { - if let message = StoreMessage(apiMessage: message), let renderedMessage = locallyRenderedMessage(message: message, peers: peers) { - renderedMessages.append(renderedMessage) - } - } - - return renderedMessages.first - } - - return postboxSignal - } - - } - |> `catch` { _ -> Signal in - return .single(nil) + } else { + signal = network.request(Api.functions.messages.getMessages(id: [Api.InputMessage.inputMessageID(id: messageId.id)])) } + + return signal |> mapError {_ in} |> mapToSignal { result -> Signal in + let messages: [Api.Message] + let chats: [Api.Chat] + let users: [Api.User] + switch result { + case let .channelMessages(_, _, _, apiMessages, apiChats, apiUsers): + messages = apiMessages + chats = apiChats + users = apiUsers + case let .messages(apiMessages, apiChats, apiUsers): + messages = apiMessages + chats = apiChats + users = apiUsers + case let.messagesSlice(_, apiMessages, apiChats, apiUsers): + messages = apiMessages + chats = apiChats + users = apiUsers + case .messagesNotModified: + messages = [] + chats = [] + users = [] + } + + let postboxSignal = postbox.transaction { transaction -> Message? in + var peers: [PeerId: Peer] = [:] + + for user in users { + if let user = TelegramUser.merge(transaction.getPeer(user.peerId) as? TelegramUser, rhs: user) { + peers[user.id] = user + } + } + + for chat in chats { + if let groupOrChannel = mergeGroupOrChannel(lhs: transaction.getPeer(chat.peerId), rhs: chat) { + peers[groupOrChannel.id] = groupOrChannel + } + } + + var renderedMessages: [Message] = [] + for message in messages { + if let message = StoreMessage(apiMessage: message), let renderedMessage = locallyRenderedMessage(message: message, peers: peers) { + renderedMessages.append(renderedMessage) + } + } + + return renderedMessages.first + } + + return postboxSignal + } + + } + |> `catch` { _ -> Signal in + return .single(nil) } + } + } +} + +func fetchRemoteMessage(postbox: Postbox, network: Network, message: MessageReference) -> Signal { + guard case let .message(peer, id) = message.content else { + return .single(nil) + } + let signal: Signal + if id.peerId.namespace == Namespaces.Peer.CloudChannel { + if let channel = peer.inputChannel { + signal = network.request(Api.functions.channels.getMessages(channel: channel, id: [Api.InputMessage.inputMessageID(id: id.id)])) + } else { + signal = .fail(MTRpcError(errorCode: 400, errorDescription: "Peer Not Found")) + } + } else if id.peerId.namespace == Namespaces.Peer.CloudUser || id.peerId.namespace == Namespaces.Peer.CloudGroup { + signal = network.request(Api.functions.messages.getMessages(id: [Api.InputMessage.inputMessageID(id: id.id)])) + } else { + signal = .fail(MTRpcError(errorCode: 400, errorDescription: "Invalid Peer")) } + return signal + |> map(Optional.init) + |> `catch` { _ -> Signal in + return .single(nil) + } + |> mapToSignal { result -> Signal in + guard let result = result else { + return .single(nil) + } + let messages: [Api.Message] + let chats: [Api.Chat] + let users: [Api.User] + switch result { + case let .channelMessages(_, _, _, apiMessages, apiChats, apiUsers): + messages = apiMessages + chats = apiChats + users = apiUsers + case let .messages(apiMessages, apiChats, apiUsers): + messages = apiMessages + chats = apiChats + users = apiUsers + case let.messagesSlice(_, apiMessages, apiChats, apiUsers): + messages = apiMessages + chats = apiChats + users = apiUsers + case .messagesNotModified: + messages = [] + chats = [] + users = [] + } + + return postbox.transaction { transaction -> Message? in + var peers: [PeerId: Peer] = [:] + + for user in users { + if let user = TelegramUser.merge(transaction.getPeer(user.peerId) as? TelegramUser, rhs: user) { + peers[user.id] = user + } + } + + for chat in chats { + if let groupOrChannel = mergeGroupOrChannel(lhs: transaction.getPeer(chat.peerId), rhs: chat) { + peers[groupOrChannel.id] = groupOrChannel + } + } + + var renderedMessages: [Message] = [] + for message in messages { + if let message = StoreMessage(apiMessage: message), case let .Id(updatedId) = message.id { + var addedExisting = false + if transaction.getMessage(updatedId) != nil { + transaction.updateMessage(updatedId, update: { _ in + return .update(message) + }) + if let updatedMessage = transaction.getMessage(updatedId) { + renderedMessages.append(updatedMessage) + addedExisting = true + } + } + + if !addedExisting, let renderedMessage = locallyRenderedMessage(message: message, peers: peers) { + renderedMessages.append(renderedMessage) + } + } + } + + return renderedMessages.first + } + } + |> `catch` { _ -> Signal in + return .single(nil) + } } public func searchMessageIdByTimestamp(account: Account, peerId: PeerId, timestamp: Int32) -> Signal { @@ -282,3 +371,87 @@ public func searchMessageIdByTimestamp(account: Account, peerId: PeerId, timesta } } |> switchToLatest } + +enum UpdatedRemotePeerError { + case generic +} + +func updatedRemotePeer(postbox: Postbox, network: Network, peer: PeerReference) -> Signal { + if let inputUser = peer.inputUser { + return network.request(Api.functions.users.getUsers(id: [inputUser])) + |> mapError { _ -> UpdatedRemotePeerError in + return .generic + } + |> mapToSignal { result -> Signal in + if let updatedPeer = result.first.flatMap(TelegramUser.init(user:)), updatedPeer.id == peer.id { + return postbox.transaction { transaction -> Peer in + updatePeers(transaction: transaction, peers: [updatedPeer], update: { _, updated in + return updated + }) + return updatedPeer + } + |> mapError { _ -> UpdatedRemotePeerError in + return .generic + } + } else { + return .fail(.generic) + } + } + } else if case let .group(id) = peer { + return network.request(Api.functions.messages.getChats(id: [id])) + |> mapError { _ -> UpdatedRemotePeerError in + return .generic + } + |> mapToSignal { result -> Signal in + let chats: [Api.Chat] + switch result { + case let .chats(c): + chats = c + case let .chatsSlice(_, c): + chats = c + } + if let updatedPeer = chats.first.flatMap(parseTelegramGroupOrChannel), updatedPeer.id == peer.id { + return postbox.transaction { transaction -> Peer in + updatePeers(transaction: transaction, peers: [updatedPeer], update: { _, updated in + return updated + }) + return updatedPeer + } + |> mapError { _ -> UpdatedRemotePeerError in + return .generic + } + } else { + return .fail(.generic) + } + } + } else if let inputChannel = peer.inputChannel { + return network.request(Api.functions.channels.getChannels(id: [inputChannel])) + |> mapError { _ -> UpdatedRemotePeerError in + return .generic + } + |> mapToSignal { result -> Signal in + let chats: [Api.Chat] + switch result { + case let .chats(c): + chats = c + case let .chatsSlice(_, c): + chats = c + } + if let updatedPeer = chats.first.flatMap(parseTelegramGroupOrChannel), updatedPeer.id == peer.id { + return postbox.transaction { transaction -> Peer in + updatePeers(transaction: transaction, peers: [updatedPeer], update: { _, updated in + return updated + }) + return updatedPeer + } + |> mapError { _ -> UpdatedRemotePeerError in + return .generic + } + } else { + return .fail(.generic) + } + } + } else { + return .fail(.generic) + } +} diff --git a/TelegramCore/SecureFileMediaResource.swift b/TelegramCore/SecureFileMediaResource.swift index eca19cb806..e7fa291a61 100644 --- a/TelegramCore/SecureFileMediaResource.swift +++ b/TelegramCore/SecureFileMediaResource.swift @@ -44,7 +44,7 @@ public class SecureFileMediaResource: TelegramCloudMediaResource, TelegramMultip return Int(self.file.size) } - var apiInputLocation: Api.InputFileLocation { + func apiInputLocation(fileReference: Data?) -> Api.InputFileLocation? { return Api.InputFileLocation.inputSecureFileLocation(id: self.file.id, accessHash: self.file.accessHash) } diff --git a/TelegramCore/Serialization.swift b/TelegramCore/Serialization.swift index fe1f138e79..1d79636588 100644 --- a/TelegramCore/Serialization.swift +++ b/TelegramCore/Serialization.swift @@ -204,7 +204,7 @@ public class Serialization: NSObject, MTSerialization { public func currentLayer() -> UInt { - return 82 + return 83 } public func parseMessage(_ data: Data!) -> Any! { diff --git a/TelegramCore/StoreMessage_Telegram.swift b/TelegramCore/StoreMessage_Telegram.swift index 0686aba1a5..88f00bf233 100644 --- a/TelegramCore/StoreMessage_Telegram.swift +++ b/TelegramCore/StoreMessage_Telegram.swift @@ -445,6 +445,10 @@ extension StoreMessage { medias.append(mediaValue) if let expirationTimer = expirationTimer, expirationTimer > 0 { + var updatedExpirationTimer = expirationTimer + if let file = mediaValue as? TelegramMediaFile, let duration = file.duration { + updatedExpirationTimer = max(updatedExpirationTimer, duration) + } attributes.append(AutoremoveTimeoutMessageAttribute(timeout: expirationTimer, countdownBeginTime: nil)) consumableContent = (true, false) diff --git a/TelegramCore/SynchronizeSavedGifsOperation.swift b/TelegramCore/SynchronizeSavedGifsOperation.swift index 8623e5b539..7ddaba6b57 100644 --- a/TelegramCore/SynchronizeSavedGifsOperation.swift +++ b/TelegramCore/SynchronizeSavedGifsOperation.swift @@ -14,14 +14,14 @@ private enum SynchronizeSavedGifsOperationContentType: Int32 { } enum SynchronizeSavedGifsOperationContent: PostboxCoding { - case add(id: Int64, accessHash: Int64) + case add(id: Int64, accessHash: Int64, fileReference: FileMediaReference?) case remove(id: Int64, accessHash: Int64) case sync init(decoder: PostboxDecoder) { switch decoder.decodeInt32ForKey("r", orElse: 0) { case SynchronizeSavedGifsOperationContentType.add.rawValue: - self = .add(id: decoder.decodeInt64ForKey("i", orElse: 0), accessHash: decoder.decodeInt64ForKey("h", orElse: 0)) + self = .add(id: decoder.decodeInt64ForKey("i", orElse: 0), accessHash: decoder.decodeInt64ForKey("h", orElse: 0), fileReference: decoder.decodeAnyObjectForKey("fr", decoder: { FileMediaReference(decoder: $0) }) as? FileMediaReference) case SynchronizeSavedGifsOperationContentType.remove.rawValue: self = .remove(id: decoder.decodeInt64ForKey("i", orElse: 0), accessHash: decoder.decodeInt64ForKey("h", orElse: 0)) case SynchronizeSavedGifsOperationContentType.sync.rawValue: @@ -34,10 +34,15 @@ enum SynchronizeSavedGifsOperationContent: PostboxCoding { func encode(_ encoder: PostboxEncoder) { switch self { - case let .add(id, accessHash): + case let .add(id, accessHash, fileReference): encoder.encodeInt32(SynchronizeSavedGifsOperationContentType.add.rawValue, forKey: "r") encoder.encodeInt64(id, forKey: "i") encoder.encodeInt64(accessHash, forKey: "h") + if let fileReference = fileReference { + encoder.encodeObjectWithEncoder(fileReference, encoder: fileReference.encode, forKey: "fr") + } else { + encoder.encodeNil(forKey: "fr") + } case let .remove(id, accessHash): encoder.encodeInt32(SynchronizeSavedGifsOperationContentType.remove.rawValue, forKey: "r") encoder.encodeInt64(id, forKey: "i") @@ -84,11 +89,11 @@ func addSynchronizeSavedGifsOperation(transaction: Transaction, operation: Synch transaction.operationLogAddEntry(peerId: peerId, tag: tag, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: SynchronizeSavedGifsOperation(content: .sync)) } -public func addSavedGif(postbox: Postbox, file: TelegramMediaFile) -> Signal { +public func addSavedGif(postbox: Postbox, fileReference: FileMediaReference) -> Signal { return postbox.transaction { transaction -> Void in - if let resource = file.resource as? CloudDocumentMediaResource { - transaction.addOrMoveToFirstPositionOrderedItemListItem(collectionId: Namespaces.OrderedItemList.CloudRecentGifs, item: OrderedItemListEntry(id: RecentMediaItemId(file.fileId).rawValue, contents: RecentMediaItem(file)), removeTailIfCountExceeds: 200) - addSynchronizeSavedGifsOperation(transaction: transaction, operation: .add(id: resource.fileId, accessHash: resource.accessHash)) + if let resource = fileReference.media.resource as? CloudDocumentMediaResource { + transaction.addOrMoveToFirstPositionOrderedItemListItem(collectionId: Namespaces.OrderedItemList.CloudRecentGifs, item: OrderedItemListEntry(id: RecentMediaItemId(fileReference.media.fileId).rawValue, contents: RecentMediaItem(fileReference.media)), removeTailIfCountExceeds: 200) + addSynchronizeSavedGifsOperation(transaction: transaction, operation: .add(id: resource.fileId, accessHash: resource.accessHash, fileReference: fileReference)) } } } diff --git a/TelegramCore/SynchronizeSavedStickersOperation.swift b/TelegramCore/SynchronizeSavedStickersOperation.swift index 5941996c8d..e9ee9642e8 100644 --- a/TelegramCore/SynchronizeSavedStickersOperation.swift +++ b/TelegramCore/SynchronizeSavedStickersOperation.swift @@ -14,14 +14,14 @@ private enum SynchronizeSavedStickersOperationContentType: Int32 { } enum SynchronizeSavedStickersOperationContent: PostboxCoding { - case add(id: Int64, accessHash: Int64) + case add(id: Int64, accessHash: Int64, fileReference: FileMediaReference?) case remove(id: Int64, accessHash: Int64) case sync init(decoder: PostboxDecoder) { switch decoder.decodeInt32ForKey("r", orElse: 0) { case SynchronizeSavedStickersOperationContentType.add.rawValue: - self = .add(id: decoder.decodeInt64ForKey("i", orElse: 0), accessHash: decoder.decodeInt64ForKey("h", orElse: 0)) + self = .add(id: decoder.decodeInt64ForKey("i", orElse: 0), accessHash: decoder.decodeInt64ForKey("h", orElse: 0), fileReference: decoder.decodeAnyObjectForKey("fr", decoder: { FileMediaReference(decoder: $0) }) as? FileMediaReference) case SynchronizeSavedStickersOperationContentType.remove.rawValue: self = .remove(id: decoder.decodeInt64ForKey("i", orElse: 0), accessHash: decoder.decodeInt64ForKey("h", orElse: 0)) case SynchronizeSavedStickersOperationContentType.sync.rawValue: @@ -34,10 +34,15 @@ enum SynchronizeSavedStickersOperationContent: PostboxCoding { func encode(_ encoder: PostboxEncoder) { switch self { - case let .add(id, accessHash): + case let .add(id, accessHash, fileReference): encoder.encodeInt32(SynchronizeSavedStickersOperationContentType.add.rawValue, forKey: "r") encoder.encodeInt64(id, forKey: "i") encoder.encodeInt64(accessHash, forKey: "h") + if let fileReference = fileReference { + encoder.encodeObjectWithEncoder(fileReference, encoder: fileReference.encode, forKey: "fr") + } else { + encoder.encodeNil(forKey: "fr") + } case let .remove(id, accessHash): encoder.encodeInt32(SynchronizeSavedStickersOperationContentType.remove.rawValue, forKey: "r") encoder.encodeInt64(id, forKey: "i") @@ -166,7 +171,7 @@ public func addSavedSticker(postbox: Postbox, network: Network, file: TelegramMe public func addSavedSticker(transaction: Transaction, file: TelegramMediaFile, stringRepresentations: [String]) { if let resource = file.resource as? CloudDocumentMediaResource { transaction.addOrMoveToFirstPositionOrderedItemListItem(collectionId: Namespaces.OrderedItemList.CloudSavedStickers, item: OrderedItemListEntry(id: RecentMediaItemId(file.fileId).rawValue, contents: SavedStickerItem(file: file, stringRepresentations: stringRepresentations)), removeTailIfCountExceeds: 5) - addSynchronizeSavedStickersOperation(transaction: transaction, operation: .add(id: resource.fileId, accessHash: resource.accessHash)) + addSynchronizeSavedStickersOperation(transaction: transaction, operation: .add(id: resource.fileId, accessHash: resource.accessHash, fileReference: .standalone(media: file))) } } diff --git a/TelegramCore/TelegramMediaFile.swift b/TelegramCore/TelegramMediaFile.swift index fe0d60e891..77e7490ef3 100644 --- a/TelegramCore/TelegramMediaFile.swift +++ b/TelegramCore/TelegramMediaFile.swift @@ -13,7 +13,7 @@ private let typeVideo: Int32 = 4 private let typeAudio: Int32 = 5 private let typeHasLinkedStickers: Int32 = 6 -public enum StickerPackReference: PostboxCoding, Equatable { +public enum StickerPackReference: PostboxCoding, Hashable, Equatable { case id(id: Int64, accessHash: Int64) case name(String) @@ -214,8 +214,37 @@ func durationForFileAttributes(_ attributes: [TelegramMediaFileAttribute]) -> In return nil } +public enum TelegramMediaFileReference: PostboxCoding, Equatable { + case cloud(fileId: Int64, accessHash: Int64, fileReference: Data?) + + public init(decoder: PostboxDecoder) { + switch decoder.decodeInt32ForKey("_v", orElse: 0) { + case 0: + self = .cloud(fileId: decoder.decodeInt64ForKey("i", orElse: 0), accessHash: decoder.decodeInt64ForKey("h", orElse: 0), fileReference: decoder.decodeBytesForKey("fr")?.makeData()) + default: + self = .cloud(fileId: 0, accessHash: 0, fileReference: nil) + assertionFailure() + } + } + + public func encode(_ encoder: PostboxEncoder) { + switch self { + case let .cloud(imageId, accessHash, fileReference): + encoder.encodeInt32(0, forKey: "_v") + encoder.encodeInt64(imageId, forKey: "i") + encoder.encodeInt64(accessHash, forKey: "h") + if let fileReference = fileReference { + encoder.encodeBytes(MemoryBuffer(data: fileReference), forKey: "fr") + } else { + encoder.encodeNil(forKey: "fr") + } + } + } +} + public final class TelegramMediaFile: Media, Equatable { public let fileId: MediaId + public let reference: TelegramMediaFileReference? public let resource: TelegramMediaResource public let previewRepresentations: [TelegramMediaImageRepresentation] public let mimeType: String @@ -227,8 +256,9 @@ public final class TelegramMediaFile: Media, Equatable { return self.fileId } - public init(fileId: MediaId, resource: TelegramMediaResource, previewRepresentations: [TelegramMediaImageRepresentation], mimeType: String, size: Int?, attributes: [TelegramMediaFileAttribute]) { + public init(fileId: MediaId, reference: TelegramMediaFileReference?, resource: TelegramMediaResource, previewRepresentations: [TelegramMediaImageRepresentation], mimeType: String, size: Int?, attributes: [TelegramMediaFileAttribute]) { self.fileId = fileId + self.reference = reference self.resource = resource self.previewRepresentations = previewRepresentations self.mimeType = mimeType @@ -238,6 +268,7 @@ public final class TelegramMediaFile: Media, Equatable { public init(decoder: PostboxDecoder) { self.fileId = MediaId(decoder.decodeBytesForKeyNoCopy("i")!) + self.reference = decoder.decodeObjectForKey("rf", decoder: { TelegramMediaFileReference(decoder: $0) }) as? TelegramMediaFileReference self.resource = decoder.decodeObjectForKey("r") as! TelegramMediaResource self.previewRepresentations = decoder.decodeObjectArrayForKey("pr") self.mimeType = decoder.decodeStringForKey("mt", orElse: "") @@ -253,6 +284,11 @@ public final class TelegramMediaFile: Media, Equatable { let buffer = WriteBuffer() self.fileId.encodeToBuffer(buffer) encoder.encodeBytes(buffer, forKey: "i") + if let reference = self.reference { + encoder.encodeObject(reference, forKey: "rf") + } else { + encoder.encodeNil(forKey: "rf") + } encoder.encodeObject(self.resource, forKey: "r") encoder.encodeObjectArray(self.previewRepresentations, forKey: "pr") encoder.encodeString(self.mimeType, forKey: "mt") @@ -349,6 +385,10 @@ public final class TelegramMediaFile: Media, Equatable { return false } + if self.reference != other.reference { + return false + } + if !self.resource.isEqual(to: other.resource) { return false } @@ -373,15 +413,15 @@ public final class TelegramMediaFile: Media, Equatable { } public func withUpdatedSize(_ size: Int?) -> TelegramMediaFile { - return TelegramMediaFile(fileId: self.fileId, resource: self.resource, previewRepresentations: self.previewRepresentations, mimeType: self.mimeType, size: size, attributes: self.attributes) + return TelegramMediaFile(fileId: self.fileId, reference: self.reference, resource: self.resource, previewRepresentations: self.previewRepresentations, mimeType: self.mimeType, size: size, attributes: self.attributes) } public func withUpdatedPreviewRepresentations(_ previewRepresentations: [TelegramMediaImageRepresentation]) -> TelegramMediaFile { - return TelegramMediaFile(fileId: self.fileId, resource: self.resource, previewRepresentations: previewRepresentations, mimeType: self.mimeType, size: self.size, attributes: self.attributes) + return TelegramMediaFile(fileId: self.fileId, reference: self.reference, resource: self.resource, previewRepresentations: previewRepresentations, mimeType: self.mimeType, size: self.size, attributes: self.attributes) } public func withUpdatedAttributes(_ attributes: [TelegramMediaFileAttribute]) -> TelegramMediaFile { - return TelegramMediaFile(fileId: self.fileId, resource: self.resource, previewRepresentations: self.previewRepresentations, mimeType: self.mimeType, size: self.size, attributes: attributes) + return TelegramMediaFile(fileId: self.fileId, reference: self.reference, resource: self.resource, previewRepresentations: self.previewRepresentations, mimeType: self.mimeType, size: self.size, attributes: attributes) } } @@ -447,8 +487,8 @@ func telegramMediaFileAttributesFromApiAttributes(_ attributes: [Api.DocumentAtt func telegramMediaFileFromApiDocument(_ document: Api.Document) -> TelegramMediaFile? { switch document { - case let .document(id, accessHash, _, mimeType, size, thumb, dcId, _, attributes): - return TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.CloudFile, id: id), resource: CloudDocumentMediaResource(datacenterId: Int(dcId), fileId: id, accessHash: accessHash, size: Int(size)), previewRepresentations: telegramMediaImageRepresentationsFromApiSizes([thumb]), mimeType: mimeType, size: Int(size), attributes: telegramMediaFileAttributesFromApiAttributes(attributes)) + case let .document(id, accessHash, fileReference, _, mimeType, size, thumb, dcId, attributes): + return TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.CloudFile, id: id), reference: .cloud(fileId: id, accessHash: accessHash, fileReference: fileReference.makeData()), resource: CloudDocumentMediaResource(datacenterId: Int(dcId), fileId: id, accessHash: accessHash, size: Int(size), fileReference: fileReference.makeData()), previewRepresentations: telegramMediaImageRepresentationsFromApiSizes([thumb]), mimeType: mimeType, size: Int(size), attributes: telegramMediaFileAttributesFromApiAttributes(attributes)) case .documentEmpty: return nil } diff --git a/TelegramCore/TelegramMediaImage.swift b/TelegramCore/TelegramMediaImage.swift index 4083c1682d..d2e32cbd99 100644 --- a/TelegramCore/TelegramMediaImage.swift +++ b/TelegramCore/TelegramMediaImage.swift @@ -6,31 +6,36 @@ import Foundation #endif public enum TelegramMediaImageReference: PostboxCoding, Equatable { - case cloud(imageId: Int64, accessHash: Int64) + case cloud(imageId: Int64, accessHash: Int64, fileReference: Data?) public init(decoder: PostboxDecoder) { switch decoder.decodeInt32ForKey("_v", orElse: 0) { case 0: - self = .cloud(imageId: decoder.decodeInt64ForKey("i", orElse: 0), accessHash: decoder.decodeInt64ForKey("h", orElse: 0)) + self = .cloud(imageId: decoder.decodeInt64ForKey("i", orElse: 0), accessHash: decoder.decodeInt64ForKey("h", orElse: 0), fileReference: decoder.decodeBytesForKey("fr")?.makeData()) default: - self = .cloud(imageId: 0, accessHash: 0) + self = .cloud(imageId: 0, accessHash: 0, fileReference: nil) assertionFailure() } } public func encode(_ encoder: PostboxEncoder) { switch self { - case let .cloud(imageId, accessHash): + case let .cloud(imageId, accessHash, fileReference): encoder.encodeInt32(0, forKey: "_v") encoder.encodeInt64(imageId, forKey: "i") encoder.encodeInt64(accessHash, forKey: "h") + if let fileReference = fileReference { + encoder.encodeBytes(MemoryBuffer(data: fileReference), forKey: "fr") + } else { + encoder.encodeNil(forKey: "fr") + } } } public static func ==(lhs: TelegramMediaImageReference, rhs: TelegramMediaImageReference) -> Bool { switch lhs { - case let .cloud(imageId, accessHash): - if case .cloud(imageId, accessHash) = rhs { + case let .cloud(imageId, accessHash, fileReference): + if case .cloud(imageId, accessHash, fileReference) = rhs { return true } else { return false @@ -157,16 +162,16 @@ func telegramMediaImageRepresentationsFromApiSizes(_ sizes: [Api.PhotoSize]) -> var representations: [TelegramMediaImageRepresentation] = [] for size in sizes { switch size { - case let .photoCachedSize(_, location, w, h, bytes): - if let resource = mediaResourceFromApiFileLocation(location, size: bytes.size) { - representations.append(TelegramMediaImageRepresentation(dimensions: CGSize(width: CGFloat(w), height: CGFloat(h)), resource: resource)) - } - case let .photoSize(_, location, w, h, size): - if let resource = mediaResourceFromApiFileLocation(location, size: Int(size)) { - representations.append(TelegramMediaImageRepresentation(dimensions: CGSize(width: CGFloat(w), height: CGFloat(h)), resource: resource)) - } - case .photoSizeEmpty: - break + case let .photoCachedSize(_, location, w, h, bytes): + if let resource = mediaResourceFromApiFileLocation(location, size: bytes.size) { + representations.append(TelegramMediaImageRepresentation(dimensions: CGSize(width: CGFloat(w), height: CGFloat(h)), resource: resource)) + } + case let .photoSize(_, location, w, h, size): + if let resource = mediaResourceFromApiFileLocation(location, size: Int(size)) { + representations.append(TelegramMediaImageRepresentation(dimensions: CGSize(width: CGFloat(w), height: CGFloat(h)), resource: resource)) + } + case .photoSizeEmpty: + break } } return representations @@ -174,8 +179,8 @@ func telegramMediaImageRepresentationsFromApiSizes(_ sizes: [Api.PhotoSize]) -> func telegramMediaImageFromApiPhoto(_ photo: Api.Photo) -> TelegramMediaImage? { switch photo { - case let .photo(_, id, accessHash, _, sizes): - return TelegramMediaImage(imageId: MediaId(namespace: Namespaces.Media.CloudImage, id: id), representations: telegramMediaImageRepresentationsFromApiSizes(sizes), reference: .cloud(imageId: id, accessHash: accessHash)) + case let .photo(_, id, accessHash, fileReference, _, sizes): + return TelegramMediaImage(imageId: MediaId(namespace: Namespaces.Media.CloudImage, id: id), representations: telegramMediaImageRepresentationsFromApiSizes(sizes), reference: .cloud(imageId: id, accessHash: accessHash, fileReference: fileReference.makeData())) case .photoEmpty: return nil } diff --git a/TelegramCore/UpdatePeers.swift b/TelegramCore/UpdatePeers.swift index 416e2e705d..4f45641832 100644 --- a/TelegramCore/UpdatePeers.swift +++ b/TelegramCore/UpdatePeers.swift @@ -69,9 +69,6 @@ public func updatePeers(transaction: Transaction, peers: [Peer], update: (Peer?, } case Namespaces.Peer.CloudChannel: if let channel = updated as? TelegramChannel { - if channel.title.hasPrefix("Qwe") { - print("here") - } switch channel.participationStatus { case .member: if channel.creationDate != 0 { diff --git a/TelegramCore/UpdatesApiUtils.swift b/TelegramCore/UpdatesApiUtils.swift index 72a8f246de..807f2219a3 100644 --- a/TelegramCore/UpdatesApiUtils.swift +++ b/TelegramCore/UpdatesApiUtils.swift @@ -11,14 +11,14 @@ extension Api.MessageMedia { case let .messageMediaPhoto(_, photo, _): if let photo = photo { switch photo { - case let .photo(_, _, _, _, sizes): + case let .photo(_, _, _, _, _, sizes): for size in sizes { switch size { case let .photoCachedSize(_, location, _, _, bytes): switch location { - case let .fileLocation(dcId, volumeId, localId, secret): + case let .fileLocation(dcId, volumeId, localId, secret, fileReference): let data = bytes.makeData() - let resource = CloudFileMediaResource(datacenterId: Int(dcId), volumeId: volumeId, localId: localId, secret: secret, size: data.count) + let resource = CloudFileMediaResource(datacenterId: Int(dcId), volumeId: volumeId, localId: localId, secret: secret, size: data.count, fileReference: fileReference.makeData()) return [(resource, data)] default: break diff --git a/TelegramCore/UploadSecureIdFile.swift b/TelegramCore/UploadSecureIdFile.swift index fc6d2f0569..3f7b422ed6 100644 --- a/TelegramCore/UploadSecureIdFile.swift +++ b/TelegramCore/UploadSecureIdFile.swift @@ -111,7 +111,7 @@ public func uploadSecureIdFile(context: SecureIdAccessContext, postbox: Postbox, return .fail(.generic) } - return multipartUpload(network: network, postbox: postbox, source: .data(encryptedData.data), encrypt: false, tag: nil, hintFileSize: nil, hintFileIsLarge: false) + return multipartUpload(network: network, postbox: postbox, source: .data(encryptedData.data), encrypt: false, tag: TelegramMediaResourceFetchTag(statsCategory: .image), hintFileSize: nil, hintFileIsLarge: false) |> mapError { _ -> UploadSecureIdFileError in return .generic } diff --git a/TelegramCore/Wallpapers.swift b/TelegramCore/Wallpapers.swift index b3b65eb6e1..8426cd86a2 100644 --- a/TelegramCore/Wallpapers.swift +++ b/TelegramCore/Wallpapers.swift @@ -8,11 +8,11 @@ import SwiftSignalKit #endif public enum TelegramWallpaper: OrderedItemListEntryContents, Equatable { - case none + //case none case builtin case color(Int32) case image([TelegramMediaImageRepresentation]) - case custom(String) + //case custom(String) public init(decoder: PostboxDecoder) { switch decoder.decodeInt32ForKey("v", orElse: 0) { case 0: @@ -21,20 +21,20 @@ public enum TelegramWallpaper: OrderedItemListEntryContents, Equatable { self = .color(decoder.decodeInt32ForKey("c", orElse: 0)) case 2: self = .image(decoder.decodeObjectArrayWithDecoderForKey("i")) - case 3: + /*case 3: self = .none case 4: - self = .custom(decoder.decodeStringForKey("p", orElse: "")) + self = .custom(decoder.decodeStringForKey("p", orElse: ""))*/ default: assertionFailure() - self = .none + self = .color(0xffffff) } } public var hasWallpaper: Bool { switch self { - case .none: - return false + //case .none: + // return false case .color: return false default: @@ -52,56 +52,56 @@ public enum TelegramWallpaper: OrderedItemListEntryContents, Equatable { case let .image(representations): encoder.encodeInt32(2, forKey: "v") encoder.encodeObjectArray(representations, forKey: "i") - case .none: + /*case .none: encoder.encodeInt32(3, forKey: "v") case let .custom(path): encoder.encodeInt32(4, forKey: "v") - encoder.encodeString(path, forKey: "p") + encoder.encodeString(path, forKey: "p")*/ } } } -public func telegramWallpapers(account: Account) -> Signal<[TelegramWallpaper], NoError> { - return account.postbox.transaction { transaction -> [TelegramWallpaper] in +public func telegramWallpapers(postbox: Postbox, network: Network) -> Signal<[TelegramWallpaper], NoError> { + return postbox.transaction { transaction -> [TelegramWallpaper] in let items = transaction.getOrderedListItems(collectionId: Namespaces.OrderedItemList.CloudWallpapers) if items.count == 0 { - return [.color(0x000000), .builtin] + return [.builtin] } else { return items.map { $0.contents as! TelegramWallpaper } } } |> mapToSignal { list -> Signal<[TelegramWallpaper], NoError> in - let remote = account.network.request(Api.functions.account.getWallPapers()) - |> retryRequest - |> mapToSignal { result -> Signal<[TelegramWallpaper], NoError> in - var items: [TelegramWallpaper] = [] - for item in result { - switch item { - case let .wallPaper(_, _, sizes, color): - items.append(.image(telegramMediaImageRepresentationsFromApiSizes(sizes))) - case let .wallPaperSolid(_, _, bgColor, color): - items.append(.color(bgColor)) - } - } - items.removeFirst() - items.insert(.color(0x000000), at: 0) - items.insert(.builtin, at: 1) - - if items == list { - return .complete() - } else { - return account.postbox.transaction { transaction -> [TelegramWallpaper] in - var entries: [OrderedItemListEntry] = [] - for item in items { - var intValue = Int32(entries.count) - let id = MemoryBuffer(data: Data(bytes: &intValue, count: 4)) - entries.append(OrderedItemListEntry(id: id, contents: item)) - } - transaction.replaceOrderedItemListItems(collectionId: Namespaces.OrderedItemList.CloudWallpapers, items: entries) - - return items - } + let remote = network.request(Api.functions.account.getWallPapers()) + |> retryRequest + |> mapToSignal { result -> Signal<[TelegramWallpaper], NoError> in + var items: [TelegramWallpaper] = [] + for item in result { + switch item { + case let .wallPaper(_, _, sizes, _): + items.append(.image(telegramMediaImageRepresentationsFromApiSizes(sizes))) + case let .wallPaperSolid(_, _, bgColor, _): + items.append(.color(bgColor)) } } - return .single(list) |> then(remote) + items.removeFirst() + items.insert(.builtin, at: 0) + + if items == list { + return .complete() + } else { + return postbox.transaction { transaction -> [TelegramWallpaper] in + var entries: [OrderedItemListEntry] = [] + for item in items { + var intValue = Int32(entries.count) + let id = MemoryBuffer(data: Data(bytes: &intValue, count: 4)) + entries.append(OrderedItemListEntry(id: id, contents: item)) + } + transaction.replaceOrderedItemListItems(collectionId: Namespaces.OrderedItemList.CloudWallpapers, items: entries) + + return items + } + } + } + return .single(list) + |> then(remote) } } diff --git a/TelegramCore/WebpagePreview.swift b/TelegramCore/WebpagePreview.swift index 2255fe72ba..ca8152b201 100644 --- a/TelegramCore/WebpagePreview.swift +++ b/TelegramCore/WebpagePreview.swift @@ -58,3 +58,26 @@ public func actualizedWebpage(postbox: Postbox, network: Network, webpage: Teleg return .complete() } } + +func updatedRemoteWebpage(postbox: Postbox, network: Network, webPage: WebpageReference) -> Signal { + if case let .webPage(id, url) = webPage.content { + return network.request(Api.functions.messages.getWebPage(url: url, hash: 0)) + |> `catch` { _ -> Signal in + return .single(.webPageNotModified) + } + |> mapToSignal { result -> Signal in + if let updatedWebpage = telegramMediaWebpageFromApiWebpage(result, url: nil), case .Loaded = updatedWebpage.content, updatedWebpage.webpageId.id == id { + return postbox.transaction { transaction -> TelegramMediaWebpage? in + if transaction.getMedia(updatedWebpage.webpageId) != nil { + transaction.updateMedia(updatedWebpage.webpageId, update: updatedWebpage) + } + return updatedWebpage + } + } else { + return .single(nil) + } + } + } else { + return .single(nil) + } +}