diff --git a/submodules/AccountContext/Sources/FetchMediaUtils.swift b/submodules/AccountContext/Sources/FetchMediaUtils.swift index 96d91b784d..2a11ec0d06 100644 --- a/submodules/AccountContext/Sources/FetchMediaUtils.swift +++ b/submodules/AccountContext/Sources/FetchMediaUtils.swift @@ -93,3 +93,10 @@ public func messageMediaFileStatus(context: AccountContext, messageId: MessageId } |> distinctUntilChanged } + +public func messageMediaImageStatus(context: AccountContext, messageId: MessageId, image: TelegramMediaImage) -> Signal { + guard let representation = image.representations.last else { + return .single(.Remote) + } + return context.fetchManager.fetchStatus(category: .image, location: .chat(messageId.peerId), locationKey: .messageId(messageId), resource: representation.resource) +} diff --git a/submodules/DebugSettingsUI/BUILD b/submodules/DebugSettingsUI/BUILD index 9c920b19db..2b0c03d0e3 100644 --- a/submodules/DebugSettingsUI/BUILD +++ b/submodules/DebugSettingsUI/BUILD @@ -22,7 +22,8 @@ swift_library( "//submodules/OverlayStatusController:OverlayStatusController", "//submodules/AccountContext:AccountContext", "//submodules/AppBundle:AppBundle", - "//submodules/GZip:GZip" + "//submodules/GZip:GZip", + "//third-party/ZipArchive:ZipArchive", ], visibility = [ "//visibility:public", diff --git a/submodules/DebugSettingsUI/Sources/DebugController.swift b/submodules/DebugSettingsUI/Sources/DebugController.swift index dc121da0b3..5715148260 100644 --- a/submodules/DebugSettingsUI/Sources/DebugController.swift +++ b/submodules/DebugSettingsUI/Sources/DebugController.swift @@ -13,7 +13,7 @@ import PresentationDataUtils import OverlayStatusController import AccountContext import AppBundle -import GZip +import ZipArchive @objc private final class DebugControllerMailComposeDelegate: NSObject, MFMailComposeViewControllerDelegate { public func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) { @@ -252,7 +252,19 @@ private enum DebugControllerEntry: ItemListNodeEntry { } } - let gzippedData = TGGZipData(rawLogData, 1.0) + let tempSource = TempBox.shared.tempFile(fileName: "Log.txt") + let tempZip = TempBox.shared.tempFile(fileName: "destination.zip") + + let _ = try? rawLogData.write(to: URL(fileURLWithPath: tempSource.path)) + + SSZipArchive.createZipFile(atPath: tempZip.path, withFilesAtPaths: [tempSource.path]) + + guard let gzippedData = try? Data(contentsOf: URL(fileURLWithPath: tempZip.path)) else { + return + } + + TempBox.shared.dispose(tempSource) + TempBox.shared.dispose(tempZip) let id = Int64.random(in: Int64.min ... Int64.max) let fileResource = LocalFileMediaResource(fileId: id, size: gzippedData.count, isSecretRelated: false) @@ -406,7 +418,19 @@ private enum DebugControllerEntry: ItemListNodeEntry { } } - let gzippedData = TGGZipData(rawLogData, 1.0) + let tempSource = TempBox.shared.tempFile(fileName: "Log.txt") + let tempZip = TempBox.shared.tempFile(fileName: "destination.zip") + + let _ = try? rawLogData.write(to: URL(fileURLWithPath: tempSource.path)) + + SSZipArchive.createZipFile(atPath: tempZip.path, withFilesAtPaths: [tempSource.path]) + + guard let gzippedData = try? Data(contentsOf: URL(fileURLWithPath: tempZip.path)) else { + return + } + + TempBox.shared.dispose(tempSource) + TempBox.shared.dispose(tempZip) let id = Int64.random(in: Int64.min ... Int64.max) let fileResource = LocalFileMediaResource(fileId: id, size: gzippedData.count, isSecretRelated: false) @@ -478,7 +502,19 @@ private enum DebugControllerEntry: ItemListNodeEntry { } } - let gzippedData = TGGZipData(rawLogData, 1.0) + let tempSource = TempBox.shared.tempFile(fileName: "Log.txt") + let tempZip = TempBox.shared.tempFile(fileName: "destination.zip") + + let _ = try? rawLogData.write(to: URL(fileURLWithPath: tempSource.path)) + + SSZipArchive.createZipFile(atPath: tempZip.path, withFilesAtPaths: [tempSource.path]) + + guard let gzippedData = try? Data(contentsOf: URL(fileURLWithPath: tempZip.path)) else { + return + } + + TempBox.shared.dispose(tempSource) + TempBox.shared.dispose(tempZip) let id = Int64.random(in: Int64.min ... Int64.max) let fileResource = LocalFileMediaResource(fileId: id, size: gzippedData.count, isSecretRelated: false) @@ -1055,3 +1091,61 @@ public func debugController(sharedContext: SharedAccountContext, context: Accoun } return controller } + +public func triggerDebugSendLogsUI(context: AccountContext, additionalInfo: String = "", pushController: @escaping (ViewController) -> Void) { + let _ = (Logger.shared.collectLogs() + |> deliverOnMainQueue).start(next: { logs in + let controller = context.sharedContext.makePeerSelectionController(PeerSelectionControllerParams(context: context, filter: [.onlyWriteable, .excludeDisabled])) + controller.peerSelected = { [weak controller] peer in + let peerId = peer.id + + if let strongController = controller { + strongController.dismiss() + + let lineFeed = "\n".data(using: .utf8)! + var rawLogData: Data = Data() + for (name, path) in logs { + if !rawLogData.isEmpty { + rawLogData.append(lineFeed) + rawLogData.append(lineFeed) + } + + rawLogData.append("------ File: \(name) ------\n".data(using: .utf8)!) + + if let data = try? Data(contentsOf: URL(fileURLWithPath: path)) { + rawLogData.append(data) + } + } + + if !additionalInfo.isEmpty { + rawLogData.append("------ Additional Info ------\n".data(using: .utf8)!) + rawLogData.append("\(additionalInfo)".data(using: .utf8)!) + } + + let tempSource = TempBox.shared.tempFile(fileName: "Log.txt") + let tempZip = TempBox.shared.tempFile(fileName: "destination.zip") + + let _ = try? rawLogData.write(to: URL(fileURLWithPath: tempSource.path)) + + SSZipArchive.createZipFile(atPath: tempZip.path, withFilesAtPaths: [tempSource.path]) + + guard let gzippedData = try? Data(contentsOf: URL(fileURLWithPath: tempZip.path)) else { + return + } + + TempBox.shared.dispose(tempSource) + TempBox.shared.dispose(tempZip) + + let id = Int64.random(in: Int64.min ... Int64.max) + let fileResource = LocalFileMediaResource(fileId: id, size: gzippedData.count, isSecretRelated: false) + context.account.postbox.mediaBox.storeResourceData(fileResource.id, data: gzippedData) + + let file = TelegramMediaFile(fileId: MediaId(namespace: Namespaces.Media.LocalFile, id: id), partialReference: nil, resource: fileResource, previewRepresentations: [], videoThumbnails: [], immediateThumbnailData: nil, mimeType: "application/text", size: gzippedData.count, attributes: [.FileName(fileName: "Log-iOS-Full.txt.zip")]) + let message: EnqueueMessage = .message(text: "", attributes: [], mediaReference: .standalone(media: file), replyToMessageId: nil, localGroupingKey: nil, correlationId: nil) + + let _ = enqueueMessages(account: context.account, peerId: peerId, messages: [message]).start() + } + } + pushController(controller) + }) +} diff --git a/submodules/FetchManagerImpl/BUILD b/submodules/FetchManagerImpl/BUILD new file mode 100644 index 0000000000..93a7be74c7 --- /dev/null +++ b/submodules/FetchManagerImpl/BUILD @@ -0,0 +1,23 @@ +load("@build_bazel_rules_swift//swift:swift.bzl", "swift_library") + +swift_library( + name = "FetchManagerImpl", + module_name = "FetchManagerImpl", + srcs = glob([ + "Sources/**/*.swift", + ]), + copts = [ + "-warnings-as-errors", + ], + deps = [ + "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", + "//submodules/Postbox:Postbox", + "//submodules/TelegramCore:TelegramCore", + "//submodules/TelegramUIPreferences:TelegramUIPreferences", + "//submodules/AccountContext:AccountContext", + "//submodules/MediaPlayer:UniversalMediaPlayer", + ], + visibility = [ + "//visibility:public", + ], +) diff --git a/submodules/TelegramUI/Sources/FetchManager.swift b/submodules/FetchManagerImpl/Sources/FetchManagerImpl.swift similarity index 93% rename from submodules/TelegramUI/Sources/FetchManager.swift rename to submodules/FetchManagerImpl/Sources/FetchManagerImpl.swift index 92636870fc..15cd0c210c 100644 --- a/submodules/TelegramUI/Sources/FetchManager.swift +++ b/submodules/FetchManagerImpl/Sources/FetchManagerImpl.swift @@ -7,12 +7,12 @@ import TelegramUIPreferences import AccountContext import UniversalMediaPlayer -private struct FetchManagerLocationEntryId: Hashable { - let location: FetchManagerLocation - let resourceId: MediaResourceId - let locationKey: FetchManagerLocationKey +public struct FetchManagerLocationEntryId: Hashable { + public let location: FetchManagerLocation + public let resourceId: MediaResourceId + public let locationKey: FetchManagerLocationKey - static func ==(lhs: FetchManagerLocationEntryId, rhs: FetchManagerLocationEntryId) -> Bool { + public static func ==(lhs: FetchManagerLocationEntryId, rhs: FetchManagerLocationEntryId) -> Bool { if lhs.location != rhs.location { return false } @@ -25,7 +25,7 @@ private struct FetchManagerLocationEntryId: Hashable { return true } - func hash(into hasher: inout Hasher) { + public func hash(into hasher: inout Hasher) { hasher.combine(self.resourceId.hashValue) hasher.combine(self.locationKey) } @@ -116,7 +116,7 @@ private final class FetchManagerCategoryContext { private let postbox: Postbox private let storeManager: DownloadedMediaStoreManager? private let entryCompleted: (FetchManagerLocationEntryId) -> Void - private let activeEntriesUpdated: () -> Void + private let activeEntriesUpdated: ([FetchManagerEntrySummary]) -> Void private var topEntryIdAndPriority: (FetchManagerLocationEntryId, FetchManagerPriorityKey)? private var entries: [FetchManagerLocationEntryId: FetchManagerLocationEntry] = [:] @@ -133,7 +133,7 @@ private final class FetchManagerCategoryContext { return false } - init(postbox: Postbox, storeManager: DownloadedMediaStoreManager?, entryCompleted: @escaping (FetchManagerLocationEntryId) -> Void, activeEntriesUpdated: @escaping () -> Void) { + init(postbox: Postbox, storeManager: DownloadedMediaStoreManager?, entryCompleted: @escaping (FetchManagerLocationEntryId) -> Void, activeEntriesUpdated: @escaping ([FetchManagerEntrySummary]) -> Void) { self.postbox = postbox self.storeManager = storeManager self.entryCompleted = entryCompleted @@ -182,6 +182,7 @@ private final class FetchManagerCategoryContext { } var activeContextsUpdated = false + let _ = activeContextsUpdated if self.maybeFindAndActivateNewTopEntry() { activeContextsUpdated = true @@ -285,9 +286,7 @@ private final class FetchManagerCategoryContext { } } - if activeContextsUpdated { - self.activeEntriesUpdated() - } + self.activeEntriesUpdated(self.entries.values.compactMap(FetchManagerEntrySummary.init).sorted(by: { $0.priority < $1.priority })) } func maybeFindAndActivateNewTopEntry() -> Bool { @@ -406,8 +405,12 @@ private final class FetchManagerCategoryContext { } } + var entriesRemoved = false + let _ = entriesRemoved + if let _ = self.entries[id] { self.entries.removeValue(forKey: id) + entriesRemoved = true if let statusContext = self.statusContexts[id] { if statusContext.hasEntry { @@ -426,6 +429,7 @@ private final class FetchManagerCategoryContext { } var activeContextsUpdated = false + let _ = activeContextsUpdated if let activeContext = self.activeContexts[id] { activeContext.disposable?.dispose() @@ -442,9 +446,7 @@ private final class FetchManagerCategoryContext { activeContextsUpdated = true } - if activeContextsUpdated { - self.activeEntriesUpdated() - } + self.activeEntriesUpdated(self.entries.values.compactMap(FetchManagerEntrySummary.init).sorted(by: { $0.priority < $1.priority })) } func withFetchStatusContext(_ id: FetchManagerLocationEntryId, _ f: (FetchManagerStatusContext) -> Void) { @@ -472,6 +474,25 @@ private final class FetchManagerCategoryContext { } } +public struct FetchManagerEntrySummary: Equatable { + public let id: FetchManagerLocationEntryId + public let mediaReference: AnyMediaReference? + public let resourceReference: MediaResourceReference + public let priority: FetchManagerPriorityKey +} + +private extension FetchManagerEntrySummary { + init?(entry: FetchManagerLocationEntry) { + guard let priority = entry.priorityKey else { + return nil + } + self.id = entry.id + self.mediaReference = entry.mediaReference + self.resourceReference = entry.resourceReference + self.priority = priority + } +} + public final class FetchManagerImpl: FetchManager { public let queue = Queue.mainQueue() private let postbox: Postbox @@ -486,7 +507,12 @@ public final class FetchManagerImpl: FetchManager { return self.hasUserInitiatedEntriesValue.get() } - init(postbox: Postbox, storeManager: DownloadedMediaStoreManager?) { + private let entriesSummaryValue = ValuePromise<[FetchManagerEntrySummary]>([], ignoreRepeated: true) + public var entriesSummary: Signal<[FetchManagerEntrySummary], NoError> { + return self.entriesSummaryValue.get() + } + + public init(postbox: Postbox, storeManager: DownloadedMediaStoreManager?) { self.postbox = postbox self.storeManager = storeManager } @@ -519,7 +545,7 @@ public final class FetchManagerImpl: FetchManager { context.cancelEntry(id, isCompleted: true) }) } - }, activeEntriesUpdated: { [weak self] in + }, activeEntriesUpdated: { [weak self] entries in queue.async { guard let strongSelf = self else { return @@ -532,6 +558,8 @@ public final class FetchManagerImpl: FetchManager { } } strongSelf.hasUserInitiatedEntriesValue.set(hasActiveUserInitiatedEntries) + + strongSelf.entriesSummaryValue.set(entries) } }) self.categoryContexts[key] = context diff --git a/submodules/FileMediaResourceStatus/Sources/FileMediaResourceStatus.swift b/submodules/FileMediaResourceStatus/Sources/FileMediaResourceStatus.swift index db4f075a66..e514d49fca 100644 --- a/submodules/FileMediaResourceStatus/Sources/FileMediaResourceStatus.swift +++ b/submodules/FileMediaResourceStatus/Sources/FileMediaResourceStatus.swift @@ -97,3 +97,26 @@ public func messageFileMediaResourceStatus(context: AccountContext, file: Telegr } } } + +public func messageImageMediaResourceStatus(context: AccountContext, image: TelegramMediaImage, message: Message, isRecentActions: Bool, isSharedMedia: Bool = false, isGlobalSearch: Bool = false) -> Signal { + if message.flags.isSending { + return combineLatest(messageMediaImageStatus(context: context, messageId: message.id, image: image), context.account.pendingMessageManager.pendingMessageStatus(message.id) |> map { $0.0 }) + |> map { resourceStatus, pendingStatus -> FileMediaResourceStatus in + let mediaStatus: FileMediaResourceMediaStatus + if let pendingStatus = pendingStatus { + mediaStatus = .fetchStatus(.Fetching(isActive: pendingStatus.isRunning, progress: pendingStatus.progress)) + } else { + mediaStatus = .fetchStatus(resourceStatus) + } + return FileMediaResourceStatus(mediaStatus: mediaStatus, fetchStatus: resourceStatus) + } + } else { + return messageMediaImageStatus(context: context, messageId: message.id, image: image) + |> map { resourceStatus -> FileMediaResourceStatus in + let mediaStatus: FileMediaResourceMediaStatus + mediaStatus = .fetchStatus(resourceStatus) + return FileMediaResourceStatus(mediaStatus: mediaStatus, fetchStatus: resourceStatus) + } + } +} + diff --git a/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift b/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift index a3d654770e..b9da1334b7 100644 --- a/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift +++ b/submodules/GalleryUI/Sources/Items/ChatImageGalleryItem.swift @@ -92,7 +92,7 @@ final class ChatMediaGalleryThumbnailItem: GalleryThumbnailItem { } case let .file(fileReference): if let representation = smallestImageRepresentation(fileReference.media.previewRepresentations) { - return (chatWebpageSnippetFile(account: self.account, fileReference: fileReference, representation: representation), representation.dimensions.cgSize) + return (chatWebpageSnippetFile(account: self.account, mediaReference: fileReference.abstract, representation: representation), representation.dimensions.cgSize) } else { return (.single({ _ in return nil }), CGSize(width: 128.0, height: 128.0)) } diff --git a/submodules/ListMessageItem/Sources/ListMessageFileItemNode.swift b/submodules/ListMessageItem/Sources/ListMessageFileItemNode.swift index e5b33f081a..85db99f903 100644 --- a/submodules/ListMessageItem/Sources/ListMessageFileItemNode.swift +++ b/submodules/ListMessageItem/Sources/ListMessageFileItemNode.swift @@ -95,30 +95,30 @@ private struct FetchControls { } private enum FileIconImage: Equatable { - case imageRepresentation(TelegramMediaFile, TelegramMediaImageRepresentation) + case imageRepresentation(Media, TelegramMediaImageRepresentation) case albumArt(TelegramMediaFile, SharedMediaPlaybackAlbumArt) case roundVideo(TelegramMediaFile) static func ==(lhs: FileIconImage, rhs: FileIconImage) -> Bool { switch lhs { - case let .imageRepresentation(file, value): - if case .imageRepresentation(file, value) = rhs { - return true - } else { - return false - } - case let .albumArt(file, value): - if case .albumArt(file, value) = rhs { - return true - } else { - return false - } - case let .roundVideo(file): - if case .roundVideo(file) = rhs { - return true - } else { - return false - } + case let .imageRepresentation(lhsMedia, lhsValue): + if case let .imageRepresentation(rhsMedia, rhsValue) = rhs, lhsMedia.isEqual(to: rhsMedia), lhsValue == rhsValue { + return true + } else { + return false + } + case let .albumArt(file, value): + if case .albumArt(file, value) = rhs { + return true + } else { + return false + } + case let .roundVideo(file): + if case .roundVideo(file) = rhs { + return true + } else { + return false + } } } } @@ -155,6 +155,7 @@ public final class ListMessageFileItemNode: ListMessageNode { private let offsetContainerNode: ASDisplayNode + private var backgroundNode: ASDisplayNode? private let highlightedBackgroundNode: ASDisplayNode public let separatorNode: ASDisplayNode @@ -402,7 +403,7 @@ public final class ListMessageFileItemNode: ListMessageNode { let message = item.message - var selectedMedia: TelegramMediaFile? + var selectedMedia: Media? for media in message.media { if let file = media as? TelegramMediaFile { selectedMedia = file @@ -539,6 +540,34 @@ public final class ListMessageFileItemNode: ListMessageNode { } break + } else if let image = media as? TelegramMediaImage { + selectedMedia = image + + //TODO:localize + let fileName: String = "Photo" + titleText = NSAttributedString(string: fileName, font: titleFont, textColor: item.presentationData.theme.theme.list.itemPrimaryTextColor) + + if let representation = smallestImageRepresentation(image.representations) { + iconImage = .imageRepresentation(image, representation) + } + + let dateString = stringForFullDate(timestamp: item.message.timestamp, strings: item.presentationData.strings, dateTimeFormat: item.presentationData.dateTimeFormat) + + var descriptionString: String = "" + if !item.isGlobalSearchResult { + descriptionString = "\(dateString)" + } + + if item.isGlobalSearchResult { + let authorString = stringForFullAuthorName(message: EngineMessage(item.message), strings: item.presentationData.strings, nameDisplayOrder: item.presentationData.nameDisplayOrder, accountPeerId: item.context.account.peerId) + if descriptionString.isEmpty { + descriptionString = authorString + } else { + descriptionString = "\(descriptionString) • \(authorString)" + } + } + + descriptionText = NSAttributedString(string: descriptionString, font: descriptionFont, textColor: item.presentationData.theme.theme.list.itemSecondaryTextColor) } } @@ -570,38 +599,57 @@ public final class ListMessageFileItemNode: ListMessageNode { let context = item.context updatedFetchControls = FetchControls(fetch: { [weak self] in if let strongSelf = self { - strongSelf.fetchDisposable.set(messageMediaFileInteractiveFetched(context: context, message: message, file: selectedMedia, userInitiated: true).start()) + if let file = selectedMedia as? TelegramMediaFile { + strongSelf.fetchDisposable.set(messageMediaFileInteractiveFetched(context: context, message: message, file: file, userInitiated: true).start()) + } else if let image = selectedMedia as? TelegramMediaImage, let representation = image.representations.last { + strongSelf.fetchDisposable.set(messageMediaImageInteractiveFetched(context: context, message: message, image: image, resource: representation.resource, userInitiated: true, storeToDownloadsPeerType: nil).start()) + } } }, cancel: { - messageMediaFileCancelInteractiveFetch(context: context, messageId: message.id, file: selectedMedia) + if let file = selectedMedia as? TelegramMediaFile { + messageMediaFileCancelInteractiveFetch(context: context, messageId: message.id, file: file) + } else if let image = selectedMedia as? TelegramMediaImage, let representation = image.representations.last { + messageMediaImageCancelInteractiveFetch(context: context, messageId: message.id, image: image, resource: representation.resource) + } }) } if statusUpdated { - updatedStatusSignal = messageFileMediaResourceStatus(context: item.context, file: selectedMedia, message: message, isRecentActions: false, isSharedMedia: true, isGlobalSearch: item.isGlobalSearchResult) - |> mapToSignal { value -> Signal in - if case .Fetching = value.fetchStatus { - return .single(value) |> delay(0.1, queue: Queue.concurrentDefaultQueue()) - } else { - return .single(value) + if let file = selectedMedia as? TelegramMediaFile { + updatedStatusSignal = messageFileMediaResourceStatus(context: item.context, file: file, message: message, isRecentActions: false, isSharedMedia: true, isGlobalSearch: item.isGlobalSearchResult) + |> mapToSignal { value -> Signal in + if case .Fetching = value.fetchStatus { + return .single(value) |> delay(0.1, queue: Queue.concurrentDefaultQueue()) + } else { + return .single(value) + } } - } - - if isAudio || isInstantVideo { - if let currentUpdatedStatusSignal = updatedStatusSignal { - updatedStatusSignal = currentUpdatedStatusSignal - |> map { status in - switch status.mediaStatus { - case .fetchStatus: - return FileMediaResourceStatus(mediaStatus: .fetchStatus(.Local), fetchStatus: status.fetchStatus) - case .playbackStatus: - return status + + if isAudio || isInstantVideo { + if let currentUpdatedStatusSignal = updatedStatusSignal { + updatedStatusSignal = currentUpdatedStatusSignal + |> map { status in + switch status.mediaStatus { + case .fetchStatus: + return FileMediaResourceStatus(mediaStatus: .fetchStatus(.Local), fetchStatus: status.fetchStatus) + case .playbackStatus: + return status + } } } } - } - if isVoice { - updatedPlaybackStatusSignal = messageFileMediaPlaybackStatus(context: item.context, file: selectedMedia, message: message, isRecentActions: false, isGlobalSearch: item.isGlobalSearchResult) + if isVoice { + updatedPlaybackStatusSignal = messageFileMediaPlaybackStatus(context: item.context, file: file, message: message, isRecentActions: false, isGlobalSearch: item.isGlobalSearchResult) + } + } else if let image = selectedMedia as? TelegramMediaImage { + updatedStatusSignal = messageImageMediaResourceStatus(context: item.context, image: image, message: message, isRecentActions: false, isSharedMedia: true, isGlobalSearch: item.isGlobalSearchResult) + |> mapToSignal { value -> Signal in + if case .Fetching = value.fetchStatus { + return .single(value) |> delay(0.1, queue: Queue.concurrentDefaultQueue()) + } else { + return .single(value) + } + } } } } @@ -684,8 +732,14 @@ public final class ListMessageFileItemNode: ListMessageNode { if currentIconImage != iconImage { if let iconImage = iconImage { switch iconImage { - case let .imageRepresentation(file, representation): - updateIconImageSignal = chatWebpageSnippetFile(account: item.context.account, fileReference: .message(message: MessageReference(message), media: file), representation: representation) + case let .imageRepresentation(media, representation): + if let file = media as? TelegramMediaFile { + updateIconImageSignal = chatWebpageSnippetFile(account: item.context.account, mediaReference: FileMediaReference.message(message: MessageReference(message), media: file).abstract, representation: representation) + } else if let image = media as? TelegramMediaImage { + updateIconImageSignal = mediaGridMessagePhoto(account: item.context.account, photoReference: ImageMediaReference.message(message: MessageReference(message), media: image)) + } else { + updateIconImageSignal = .complete() + } case let .albumArt(file, albumArt): updateIconImageSignal = playerAlbumArt(postbox: item.context.account.postbox, engine: item.context.engine, fileReference: .message(message: MessageReference(message), media: file), albumArt: albumArt, thumbnail: true, overlayColor: UIColor(white: 0.0, alpha: 0.3), emptyColor: item.presentationData.theme.theme.list.itemAccentColor) case let .roundVideo(file): @@ -746,6 +800,18 @@ public final class ListMessageFileItemNode: ListMessageNode { strongSelf.currentLeftOffset = leftOffset if let _ = updatedTheme { + if item.displayBackground { + let backgroundNode: ASDisplayNode + if let current = strongSelf.backgroundNode { + backgroundNode = current + } else { + backgroundNode = ASDisplayNode() + strongSelf.backgroundNode = backgroundNode + strongSelf.insertSubnode(backgroundNode, at: 0) + } + backgroundNode.backgroundColor = item.presentationData.theme.theme.list.itemBlocksBackgroundColor + } + strongSelf.separatorNode.backgroundColor = item.presentationData.theme.theme.list.itemPlainSeparatorColor strongSelf.highlightedBackgroundNode.backgroundColor = item.presentationData.theme.theme.list.itemHighlightedBackgroundColor strongSelf.linearProgressNode?.updateTheme(theme: item.presentationData.theme.theme) @@ -778,6 +844,10 @@ public final class ListMessageFileItemNode: ListMessageNode { transition.updateFrame(node: strongSelf.separatorNode, frame: CGRect(origin: CGPoint(x: leftInset + leftOffset, y: nodeLayout.contentSize.height - UIScreenPixel), size: CGSize(width: params.width - leftInset - leftOffset, height: UIScreenPixel))) strongSelf.highlightedBackgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -UIScreenPixel - nodeLayout.insets.top), size: CGSize(width: params.width, height: nodeLayout.size.height + UIScreenPixel)) + if let backgroundNode = strongSelf.backgroundNode { + backgroundNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -nodeLayout.insets.top), size: CGSize(width: params.width, height: nodeLayout.size.height)) + } + transition.updateFrame(node: strongSelf.titleNode, frame: CGRect(origin: CGPoint(x: leftOffset + leftInset, y: 9.0), size: titleNodeLayout.size)) let _ = titleNodeApply() diff --git a/submodules/ListMessageItem/Sources/ListMessageItem.swift b/submodules/ListMessageItem/Sources/ListMessageItem.swift index e78181bb05..e62933525c 100644 --- a/submodules/ListMessageItem/Sources/ListMessageItem.swift +++ b/submodules/ListMessageItem/Sources/ListMessageItem.swift @@ -51,12 +51,13 @@ public final class ListMessageItem: ListViewItem { public let selection: ChatHistoryMessageSelection let hintIsLink: Bool let isGlobalSearchResult: Bool + let displayBackground: Bool let header: ListViewItemHeader? public let selectable: Bool = true - public init(presentationData: ChatPresentationData, context: AccountContext, chatLocation: ChatLocation, interaction: ListMessageItemInteraction, message: Message, selection: ChatHistoryMessageSelection, displayHeader: Bool, customHeader: ListViewItemHeader? = nil, hintIsLink: Bool = false, isGlobalSearchResult: Bool = false) { + public init(presentationData: ChatPresentationData, context: AccountContext, chatLocation: ChatLocation, interaction: ListMessageItemInteraction, message: Message, selection: ChatHistoryMessageSelection, displayHeader: Bool, customHeader: ListViewItemHeader? = nil, hintIsLink: Bool = false, isGlobalSearchResult: Bool = false, displayBackground: Bool = false) { self.presentationData = presentationData self.context = context self.chatLocation = chatLocation @@ -72,6 +73,7 @@ public final class ListMessageItem: ListViewItem { self.selection = selection self.hintIsLink = hintIsLink self.isGlobalSearchResult = isGlobalSearchResult + self.displayBackground = displayBackground } public func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, (ListViewItemApply) -> Void)) -> Void) { @@ -82,6 +84,9 @@ public final class ListMessageItem: ListViewItem { if let _ = media as? TelegramMediaFile { viewClassName = ListMessageFileItemNode.self break + } else if let _ = media as? TelegramMediaImage { + viewClassName = ListMessageFileItemNode.self + break } } } diff --git a/submodules/ListMessageItem/Sources/ListMessageSnippetItemNode.swift b/submodules/ListMessageItem/Sources/ListMessageSnippetItemNode.swift index 9d70f1decf..90bda009a6 100644 --- a/submodules/ListMessageItem/Sources/ListMessageSnippetItemNode.swift +++ b/submodules/ListMessageItem/Sources/ListMessageSnippetItemNode.swift @@ -528,7 +528,7 @@ public final class ListMessageSnippetItemNode: ListMessageNode { if let imageReference = iconImageReferenceAndRepresentation.0.concrete(TelegramMediaImage.self) { updateIconImageSignal = chatWebpageSnippetPhoto(account: item.context.account, photoReference: imageReference) } else if let fileReference = iconImageReferenceAndRepresentation.0.concrete(TelegramMediaFile.self) { - updateIconImageSignal = chatWebpageSnippetFile(account: item.context.account, fileReference: fileReference, representation: iconImageReferenceAndRepresentation.1) + updateIconImageSignal = chatWebpageSnippetFile(account: item.context.account, mediaReference: fileReference.abstract, representation: iconImageReferenceAndRepresentation.1) } } else { updateIconImageSignal = .complete() diff --git a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTLogging.h b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTLogging.h index bf31305149..d6eea88e33 100644 --- a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTLogging.h +++ b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTLogging.h @@ -11,9 +11,10 @@ extern "C" { bool MTLogEnabled(); void MTLog(NSString *format, ...); +void MTLogWithPrefix(NSString *(^getLogPrefix)(), NSString *format, ...); void MTShortLog(NSString *format, ...); -void MTLogSetLoggingFunction(void (*function)(NSString *, va_list args)); -void MTLogSetShortLoggingFunction(void (*function)(NSString *, va_list args)); +void MTLogSetLoggingFunction(void (*function)(NSString *)); +void MTLogSetShortLoggingFunction(void (*function)(NSString *)); void MTLogSetEnabled(bool); #ifdef __cplusplus diff --git a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTProto.h b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTProto.h index 25d1268c0a..990b94fba6 100644 --- a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTProto.h +++ b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTProto.h @@ -53,6 +53,8 @@ @property (nonatomic) id requiredAuthToken; @property (nonatomic) NSInteger authTokenMasterDatacenterId; +@property (nonatomic, strong) NSString *(^getLogPrefix)(); + - (instancetype)initWithContext:(MTContext *)context datacenterId:(NSInteger)datacenterId usageCalculationInfo:(MTNetworkUsageCalculationInfo *)usageCalculationInfo requiredAuthToken:(id)requiredAuthToken authTokenMasterDatacenterId:(NSInteger)authTokenMasterDatacenterId; - (void)setUsageCalculationInfo:(MTNetworkUsageCalculationInfo *)usageCalculationInfo; diff --git a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTTransport.h b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTTransport.h index c1e8923493..24143e7ba0 100644 --- a/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTTransport.h +++ b/submodules/MtProtoKit/PublicHeaders/MtProtoKit/MTTransport.h @@ -43,8 +43,9 @@ @property (nonatomic, strong, readonly) MTSocksProxySettings * _Nullable proxySettings; @property (nonatomic) bool simultaneousTransactionsEnabled; @property (nonatomic) bool reportTransportConnectionContextUpdateStates; +@property (nonatomic, strong) NSString * _Nullable (^ _Nullable getLogPrefix)(); -- (instancetype _Nonnull)initWithDelegate:(id _Nullable)delegate context:(MTContext * _Nonnull)context datacenterId:(NSInteger)datacenterId schemes:(NSArray * _Nonnull)schemes proxySettings:(MTSocksProxySettings * _Null_unspecified)proxySettings usageCalculationInfo:(MTNetworkUsageCalculationInfo * _Nullable)usageCalculationInfo; +- (instancetype _Nonnull)initWithDelegate:(id _Nullable)delegate context:(MTContext * _Nonnull)context datacenterId:(NSInteger)datacenterId schemes:(NSArray * _Nonnull)schemes proxySettings:(MTSocksProxySettings * _Null_unspecified)proxySettings usageCalculationInfo:(MTNetworkUsageCalculationInfo * _Nullable)usageCalculationInfo getLogPrefix:(NSString * _Nullable (^ _Nullable)())getLogPrefix; - (void)setUsageCalculationInfo:(MTNetworkUsageCalculationInfo * _Null_unspecified)usageCalculationInfo; diff --git a/submodules/MtProtoKit/Sources/GCDAsyncSocket.h b/submodules/MtProtoKit/Sources/GCDAsyncSocket.h index aa3a3f59b6..1f7f6e6378 100644 --- a/submodules/MtProtoKit/Sources/GCDAsyncSocket.h +++ b/submodules/MtProtoKit/Sources/GCDAsyncSocket.h @@ -151,6 +151,8 @@ typedef enum GCDAsyncSocketError GCDAsyncSocketError; @property (nonatomic) bool useTcpNodelay; @property (nonatomic, strong) MTNetworkUsageCalculationInfo *usageCalculationInfo; +@property (nonatomic, strong) NSString *(^getLogPrefix)(); + /** * GCDAsyncSocket uses the standard delegate paradigm, * but executes all delegate callbacks on a given delegate dispatch queue. diff --git a/submodules/MtProtoKit/Sources/GCDAsyncSocket.m b/submodules/MtProtoKit/Sources/GCDAsyncSocket.m index 0670de7879..011885b457 100755 --- a/submodules/MtProtoKit/Sources/GCDAsyncSocket.m +++ b/submodules/MtProtoKit/Sources/GCDAsyncSocket.m @@ -2468,7 +2468,7 @@ enum GCDAsyncSocketConfig freeifaddrs(ifaddr); if (MTLogEnabled()) { - MTLog(@"Connection time: %f ms, interface: %@", (CFAbsoluteTimeGetCurrent() - startTime) * 1000.0f, isWifi ? @"Wifi" : @"WAN"); + MTLogWithPrefix(_getLogPrefix, @"Connection time: %f ms, interface: %@", (CFAbsoluteTimeGetCurrent() - startTime) * 1000.0f, isWifi ? @"Wifi" : @"WAN"); } dispatch_async(socketQueue, ^{ @autoreleasepool { diff --git a/submodules/MtProtoKit/Sources/MTDiscoverConnectionSignals.m b/submodules/MtProtoKit/Sources/MTDiscoverConnectionSignals.m index 5f34d8ac2f..13eb82c945 100644 --- a/submodules/MtProtoKit/Sources/MTDiscoverConnectionSignals.m +++ b/submodules/MtProtoKit/Sources/MTDiscoverConnectionSignals.m @@ -95,7 +95,7 @@ MTPayloadData payloadData; NSData *data = [self payloadData:&payloadData context:context address:address]; - MTTcpConnection *connection = [[MTTcpConnection alloc] initWithContext:context datacenterId:datacenterId scheme:[[MTTransportScheme alloc] initWithTransportClass:[MTTcpTransport class] address:address media:false] interface:nil usageCalculationInfo:nil]; + MTTcpConnection *connection = [[MTTcpConnection alloc] initWithContext:context datacenterId:datacenterId scheme:[[MTTransportScheme alloc] initWithTransportClass:[MTTcpTransport class] address:address media:false] interface:nil usageCalculationInfo:nil getLogPrefix:nil]; __weak MTTcpConnection *weakConnection = connection; connection.connectionOpened = ^ { diff --git a/submodules/MtProtoKit/Sources/MTLogging.m b/submodules/MtProtoKit/Sources/MTLogging.m index ba0948f776..e6c5606f4e 100644 --- a/submodules/MtProtoKit/Sources/MTLogging.m +++ b/submodules/MtProtoKit/Sources/MTLogging.m @@ -1,7 +1,7 @@ #import -static void (*loggingFunction)(NSString *, va_list args) = NULL; -static void (*shortLoggingFunction)(NSString *, va_list args) = NULL; +static void (*loggingFunction)(NSString *) = NULL; +static void (*shortLoggingFunction)(NSString *) = NULL; static bool MTLogEnabledValue = true; bool MTLogEnabled() { @@ -12,7 +12,24 @@ void MTLog(NSString *format, ...) { va_list L; va_start(L, format); if (loggingFunction != NULL) { - loggingFunction(format, L); + NSString *string = [[NSString alloc] initWithFormat:format arguments:L]; + loggingFunction(string); + } + va_end(L); +} + +void MTLogWithPrefix(NSString *(^getLogPrefix)(), NSString *format, ...) { + va_list L; + va_start(L, format); + if (loggingFunction != NULL) { + NSString *string = [[NSString alloc] initWithFormat:format arguments:L]; + if (getLogPrefix) { + NSString *prefix = getLogPrefix(); + if (prefix) { + string = [prefix stringByAppendingString:string]; + } + } + loggingFunction(string); } va_end(L); } @@ -21,16 +38,17 @@ void MTShortLog(NSString *format, ...) { va_list L; va_start(L, format); if (shortLoggingFunction != NULL) { - shortLoggingFunction(format, L); + NSString *string = [[NSString alloc] initWithFormat:format arguments:L]; + shortLoggingFunction(string); } va_end(L); } -void MTLogSetLoggingFunction(void (*function)(NSString *, va_list args)) { +void MTLogSetLoggingFunction(void (*function)(NSString *)) { loggingFunction = function; } -void MTLogSetShortLoggingFunction(void (*function)(NSString *, va_list args)) { +void MTLogSetShortLoggingFunction(void (*function)(NSString *)) { shortLoggingFunction = function; } diff --git a/submodules/MtProtoKit/Sources/MTProto.m b/submodules/MtProtoKit/Sources/MTProto.m index 7281f7b10c..98a731112d 100644 --- a/submodules/MtProtoKit/Sources/MTProto.m +++ b/submodules/MtProtoKit/Sources/MTProto.m @@ -201,7 +201,7 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; if ((_mtState & MTProtoStatePaused) == 0) { if (MTLogEnabled()) { - MTLog(@"[MTProto#%p@%p pause]", self, _context); + MTLogWithPrefix(_getLogPrefix, @"[MTProto#%p@%p pause]", self, _context); } MTShortLog(@"[MTProto#%p@%p pause]", self, _context); @@ -220,7 +220,7 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; if (_mtState & MTProtoStatePaused) { if (MTLogEnabled()) { - MTLog(@"[MTProto#%p@%p resume]", self, _context); + MTLogWithPrefix(_getLogPrefix, @"[MTProto#%p@%p resume]", self, _context); } MTShortLog(@"[MTProto#%p@%p resume]", self, _context); @@ -274,7 +274,7 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; [[MTProto managerQueue] dispatchOnQueue:^ { if (MTLogEnabled()) { - MTLog(@"[MTProto#%p@%p changing transport %@#%p to %@#%p]", self, _context, [_transport class] == nil ? @"" : NSStringFromClass([_transport class]), _transport, [transport class] == nil ? @"" : NSStringFromClass([transport class]), transport); + MTLogWithPrefix(_getLogPrefix, @"[MTProto#%p@%p changing transport %@#%p to %@#%p]", self, _context, [_transport class] == nil ? @"" : NSStringFromClass([_transport class]), _transport, [transport class] == nil ? @"" : NSStringFromClass([transport class]), transport); } [self allTransactionsMayHaveFailed]; @@ -331,7 +331,7 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; } } else { assert(transportSchemes.count != 0); - MTTransport *transport = [[MTTcpTransport alloc] initWithDelegate:self context:_context datacenterId:_datacenterId schemes:transportSchemes proxySettings:_context.apiEnvironment.socksProxySettings usageCalculationInfo:_usageCalculationInfo]; + MTTransport *transport = [[MTTcpTransport alloc] initWithDelegate:self context:_context datacenterId:_datacenterId schemes:transportSchemes proxySettings:_context.apiEnvironment.socksProxySettings usageCalculationInfo:_usageCalculationInfo getLogPrefix:_getLogPrefix]; [self setTransport:transport]; } @@ -343,7 +343,7 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; [[MTProto managerQueue] dispatchOnQueue:^ { if (MTLogEnabled()) { - MTLog(@"[MTProto#%p@%p resetting session]", self, _context); + MTLogWithPrefix(_getLogPrefix, @"[MTProto#%p@%p resetting session]", self, _context); } MTShortLog(@"[MTProto#%p@%p resetting session]", self, _context); @@ -383,9 +383,9 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; if (!alreadySyncing) { if (MTLogEnabled()) { - MTLog(@"[MTProto#%p@%p begin time sync]", self, _context); + MTLogWithPrefix(_getLogPrefix, @"[MTProto#%p@%p begin time sync]", self, _context); } - MTShortLog(@"[MTProto#%p@%p begin time sync]", self, _context); + MTLogWithPrefix(_getLogPrefix, @"[MTProto#%p@%p begin time sync]", self, _context); MTTimeSyncMessageService *timeSyncService = [[MTTimeSyncMessageService alloc] init]; timeSyncService.delegate = self; @@ -416,7 +416,7 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; } if (MTLogEnabled()) { - MTLog(@"[MTProto#%p@%p service tasks state: %d, resend: %s]", self, _context, _mtState, haveResendMessagesPending ? "yes" : "no"); + MTLogWithPrefix(_getLogPrefix, @"[MTProto#%p@%p service tasks state: %d, resend: %s]", self, _context, _mtState, haveResendMessagesPending ? "yes" : "no"); } MTShortLog(@"[MTProto#%p@%p service tasks state: %d, resend: %s]", self, _context, _mtState, haveResendMessagesPending ? "yes" : "no"); @@ -467,7 +467,7 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; if (notifyAboutServiceTask) { if (MTLogEnabled()) { - MTLog(@"[MTProto#%p@%p service tasks state: %d, resend: %s]", self, _context, _mtState, "yes"); + MTLogWithPrefix(_getLogPrefix, @"[MTProto#%p@%p service tasks state: %d, resend: %s]", self, _context, _mtState, "yes"); } MTShortLog(@"[MTProto#%p@%p service tasks state: %d, resend: %s]", self, _context, _mtState, "yes"); @@ -520,7 +520,7 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; if (!performingServiceTasks) { if (MTLogEnabled()) { - MTLog(@"[MTProto#%p@%p service tasks state: %d, resend: %s]", self, _context, _mtState, "no"); + MTLogWithPrefix(_getLogPrefix, @"[MTProto#%p@%p service tasks state: %d, resend: %s]", self, _context, _mtState, "no"); } MTShortLog(@"[MTProto#%p@%p service tasks state: %d, resend: %s]", self, _context, _mtState, "no"); @@ -682,7 +682,7 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; return; if (MTLogEnabled()) { - MTLog(@"[MTProto#%p@%p network state: %s]", self, _context, isNetworkAvailable ? "available" : "waiting"); + MTLogWithPrefix(_getLogPrefix, @"[MTProto#%p@%p network state: %s]", self, _context, isNetworkAvailable ? "available" : "waiting"); } for (id messageService in _messageServices) @@ -713,7 +713,7 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; return; if (MTLogEnabled()) { - MTLog(@"[MTProto#%p@%p transport #%p connection state: %s]", self, _context, transport, isConnected ? "connected" : "connecting"); + MTLogWithPrefix(_getLogPrefix, @"[MTProto#%p@%p transport #%p connection state: %s]", self, _context, transport, isConnected ? "connected" : "connecting"); } for (id messageService in _messageServices) @@ -740,7 +740,7 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; return; if (MTLogEnabled()) { - MTLog(@"[MTProto#%p@%p connection context update state: %s]", self, _context, isUpdatingConnectionContext ? "updating" : "up to date"); + MTLogWithPrefix(_getLogPrefix, @"[MTProto#%p@%p connection context update state: %s]", self, _context, isUpdatingConnectionContext ? "updating" : "up to date"); } for (id messageService in _messageServices) @@ -1048,7 +1048,7 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; if (MTLogEnabled()) { NSString *messageDescription = [self outgoingMessageDescription:outgoingMessage messageId:messageId messageSeqNo:messageSeqNo]; - MTLog(@"[MTProto#%p@%p preparing %@]", self, _context, messageDescription); + MTLogWithPrefix(_getLogPrefix, @"[MTProto#%p@%p preparing %@]", self, _context, messageDescription); } NSString *shortMessageDescription = [self outgoingShortMessageDescription:outgoingMessage messageId:messageId messageSeqNo:messageSeqNo]; MTShortLog(@"[MTProto#%p@%p preparing %@]", self, _context, shortMessageDescription); @@ -1094,7 +1094,7 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; if (monotonityViolated) { if (MTLogEnabled()) { - MTLog(@"[MTProto#%p@%p client message id monotonity violated]", self, _context); + MTLogWithPrefix(_getLogPrefix, @"[MTProto#%p@%p client message id monotonity violated]", self, _context); } MTShortLog(@"[MTProto#%p@%p client message id monotonity violated]", self, _context); @@ -1283,7 +1283,7 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; } if (MTLogEnabled()) { - MTLog(@"[MTProto#%p@%p transport did not accept transactions with messages (%@)]", self, _context, idsString); + MTLogWithPrefix(_getLogPrefix, @"[MTProto#%p@%p transport did not accept transactions with messages (%@)]", self, _context, idsString); } } }]; @@ -1339,7 +1339,7 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; MTOutputStream *decryptedOs = [[MTOutputStream alloc] init]; if (MTLogEnabled()) { - MTLog(@"[MTProto#%p@%p sending time fix ping (%" PRId64 "/%" PRId32 ", %" PRId64 ")]", self, _context, timeFixMessageId, timeFixSeqNo, _sessionInfo.sessionId); + MTLogWithPrefix(_getLogPrefix, @"[MTProto#%p@%p sending time fix ping (%" PRId64 "/%" PRId32 ", %" PRId64 ")]", self, _context, timeFixMessageId, timeFixSeqNo, _sessionInfo.sessionId); } MTShortLog(@"[MTProto#%p@%p sending time fix ping (%" PRId64 "/%" PRId32 ", %" PRId64 ")]", self, _context, timeFixMessageId, timeFixSeqNo, _sessionInfo.sessionId); @@ -1455,7 +1455,7 @@ static const NSUInteger MTMaxUnacknowledgedMessageCount = 64; [idsString appendFormat:@"%lld", [nMessageId longLongValue]]; } if (MTLogEnabled()) { - MTLog(@" container (%" PRId64 ") of (%@), in %" PRId64 "", containerMessageId, idsString, sessionInfo.sessionId); + MTLogWithPrefix(_getLogPrefix, @" container (%" PRId64 ") of (%@), in %" PRId64 "", containerMessageId, idsString, sessionInfo.sessionId); } MTShortLog(@" container (%" PRId64 ") of (%@), in %" PRId64 "", containerMessageId, idsString, sessionInfo.sessionId); } @@ -1931,7 +1931,7 @@ static NSString *dumpHexString(NSData *data, int maxLength) { [data getBytes:&protocolErrorCode range:NSMakeRange(0, 4)]; if (MTLogEnabled()) { - MTLog(@"[MTProto#%p@%p protocol error %" PRId32 "", self, _context, protocolErrorCode); + MTLogWithPrefix(_getLogPrefix, @"[MTProto#%p@%p protocol error %" PRId32 "", self, _context, protocolErrorCode); } MTShortLog(@"[MTProto#%p@%p protocol error %" PRId32 "", self, _context, protocolErrorCode); @@ -1991,7 +1991,7 @@ static NSString *dumpHexString(NSData *data, int maxLength) { MTRpcError *rpcError = maybeInternalMessage; if (rpcError.errorCode == 401 && [rpcError.errorDescription isEqualToString:@"AUTH_KEY_PERM_EMPTY"]) { if (MTLogEnabled()) { - MTLog(@"[MTProto#%p@%p received AUTH_KEY_PERM_EMPTY]", self, _context); + MTLogWithPrefix(_getLogPrefix, @"[MTProto#%p@%p received AUTH_KEY_PERM_EMPTY]", self, _context); } MTShortLog(@"[MTProto#%p@%p received AUTH_KEY_PERM_EMPTY]", self, _context); [self handleMissingKey:scheme]; @@ -2004,7 +2004,7 @@ static NSString *dumpHexString(NSData *data, int maxLength) { } if (parseError) { if (MTLogEnabled()) { - MTLog(@"[MTProto#%p@%p incoming data parse error, header: %d:%@]", self, _context, (int)decryptedData.length, dumpHexString(decryptedData, 128)); + MTLogWithPrefix(_getLogPrefix, @"[MTProto#%p@%p incoming data parse error, header: %d:%@]", self, _context, (int)decryptedData.length, dumpHexString(decryptedData, 128)); } MTShortLog(@"[MTProto#%p@%p incoming data parse error, header: %d:%@]", self, _context, (int)decryptedData.length, dumpHexString(decryptedData, 128)); @@ -2026,7 +2026,7 @@ static NSString *dumpHexString(NSData *data, int maxLength) { } } else { if (MTLogEnabled()) { - MTLog(@"[MTProto#%p@%p couldn't decrypt incoming data]", self, _context); + MTLogWithPrefix(_getLogPrefix, @"[MTProto#%p@%p couldn't decrypt incoming data]", self, _context); } MTShortLog(@"[MTProto#%p@%p couldn't decrypt incoming data]", self, _context); @@ -2046,7 +2046,7 @@ static NSString *dumpHexString(NSData *data, int maxLength) { if (_useUnauthorizedMode) { if (MTLogEnabled()) { - MTLog(@"[MTProto#%p@%p don't handleMissingKey when useUnauthorizedMode]", self, _context); + MTLogWithPrefix(_getLogPrefix, @"[MTProto#%p@%p don't handleMissingKey when useUnauthorizedMode]", self, _context); } return; } @@ -2055,7 +2055,7 @@ static NSString *dumpHexString(NSData *data, int maxLength) { [self getAuthKeyForCurrentScheme:scheme createIfNeeded:false authInfoSelector:&authInfoSelector]; if (MTLogEnabled()) { - MTLog(@"[MTProto#%p@%p missing key %lld selector %d useExplicitAuthKey: %lld, canResetAuthData: %s]", self, _context, _validAuthInfo.authInfo.authKeyId, authInfoSelector, _useExplicitAuthKey.authKeyId, _canResetAuthData ? "true" : "false"); + MTLogWithPrefix(_getLogPrefix, @"[MTProto#%p@%p missing key %lld selector %d useExplicitAuthKey: %lld, canResetAuthData: %s]", self, _context, _validAuthInfo.authInfo.authKeyId, authInfoSelector, _useExplicitAuthKey.authKeyId, _canResetAuthData ? "true" : "false"); } if (_useExplicitAuthKey != nil) { @@ -2346,7 +2346,7 @@ static bool isDataEqualToDataConstTime(NSData *data1, NSData *data2) { if ([_sessionInfo messageProcessed:incomingMessage.messageId]) { if (MTLogEnabled()) { - MTLog(@"[MTProto#%p@%p received duplicate message %" PRId64 "]", self, _context, incomingMessage.messageId); + MTLogWithPrefix(_getLogPrefix, @"[MTProto#%p@%p received duplicate message %" PRId64 "]", self, _context, incomingMessage.messageId); } MTShortLog(@"[MTProto#%p@%p received duplicate message %" PRId64 "]", self, _context, incomingMessage.messageId); [_sessionInfo scheduleMessageConfirmation:incomingMessage.messageId size:incomingMessage.size]; @@ -2358,7 +2358,7 @@ static bool isDataEqualToDataConstTime(NSData *data1, NSData *data2) { } if (MTLogEnabled()) { - MTLog(@"[MTProto#%p@%p [%d] received %@]", self, _context, totalSize, [self incomingMessageDescription:incomingMessage]); + MTLogWithPrefix(_getLogPrefix, @"[MTProto#%p@%p [%d] received %@]", self, _context, totalSize, [self incomingMessageDescription:incomingMessage]); } [_sessionInfo setMessageProcessed:incomingMessage.messageId]; @@ -2473,7 +2473,7 @@ static bool isDataEqualToDataConstTime(NSData *data1, NSData *data2) { int64_t requestMessageId = ((MTMsgDetailedResponseInfoMessage *)detailedInfoMessage).requestMessageId; if (MTLogEnabled()) { - MTLog(@"[MTProto#%p@%p detailed info %" PRId64 " is for %" PRId64 "", self, _context, incomingMessage.messageId, requestMessageId); + MTLogWithPrefix(_getLogPrefix, @"[MTProto#%p@%p detailed info %" PRId64 " is for %" PRId64 "", self, _context, incomingMessage.messageId, requestMessageId); } MTShortLog(@"[MTProto#%p@%p detailed info %" PRId64 " is for %" PRId64 "", self, _context, incomingMessage.messageId, requestMessageId); @@ -2496,7 +2496,7 @@ static bool isDataEqualToDataConstTime(NSData *data1, NSData *data2) { { [self requestMessageWithId:detailedInfoMessage.responseMessageId]; if (MTLogEnabled()) { - MTLog(@"[MTProto#%p@%p will request message %" PRId64 "", self, _context, detailedInfoMessage.responseMessageId); + MTLogWithPrefix(_getLogPrefix, @"[MTProto#%p@%p will request message %" PRId64 "", self, _context, detailedInfoMessage.responseMessageId); } MTShortLog(@"[MTProto#%p@%p will request message %" PRId64 "", self, _context, detailedInfoMessage.responseMessageId); } diff --git a/submodules/MtProtoKit/Sources/MTProxyConnectivity.m b/submodules/MtProtoKit/Sources/MTProxyConnectivity.m index a12f89d080..9a780104fa 100644 --- a/submodules/MtProtoKit/Sources/MTProxyConnectivity.m +++ b/submodules/MtProtoKit/Sources/MTProxyConnectivity.m @@ -66,7 +66,7 @@ MTContext *proxyContext = [[MTContext alloc] initWithSerialization:context.serialization encryptionProvider:context.encryptionProvider apiEnvironment:[[context apiEnvironment] withUpdatedSocksProxySettings:settings] isTestingEnvironment:context.isTestingEnvironment useTempAuthKeys:false]; - MTTcpConnection *connection = [[MTTcpConnection alloc] initWithContext:proxyContext datacenterId:datacenterId scheme:[[MTTransportScheme alloc] initWithTransportClass:[MTTcpConnection class] address:address media:false] interface:nil usageCalculationInfo:nil]; + MTTcpConnection *connection = [[MTTcpConnection alloc] initWithContext:proxyContext datacenterId:datacenterId scheme:[[MTTransportScheme alloc] initWithTransportClass:[MTTcpConnection class] address:address media:false] interface:nil usageCalculationInfo:nil getLogPrefix:nil]; __weak MTTcpConnection *weakConnection = connection; __block NSTimeInterval startTime = CFAbsoluteTimeGetCurrent(); connection.connectionOpened = ^ { diff --git a/submodules/MtProtoKit/Sources/MTTcpConnection.h b/submodules/MtProtoKit/Sources/MTTcpConnection.h index 891a42a8ae..cbc8c5886e 100644 --- a/submodules/MtProtoKit/Sources/MTTcpConnection.h +++ b/submodules/MtProtoKit/Sources/MTTcpConnection.h @@ -32,9 +32,11 @@ @property (nonatomic, strong, readonly) MTTransportScheme *scheme; @property (nonatomic, strong, readonly) NSString *interface; +@property (nonatomic, strong) NSString *(^getLogPrefix)(); + + (MTQueue *)tcpQueue; -- (instancetype)initWithContext:(MTContext *)context datacenterId:(NSInteger)datacenterId scheme:(MTTransportScheme *)scheme interface:(NSString *)interface usageCalculationInfo:(MTNetworkUsageCalculationInfo *)usageCalculationInfo; +- (instancetype)initWithContext:(MTContext *)context datacenterId:(NSInteger)datacenterId scheme:(MTTransportScheme *)scheme interface:(NSString *)interface usageCalculationInfo:(MTNetworkUsageCalculationInfo *)usageCalculationInfo getLogPrefix:(NSString *(^)())getLogPrefix; - (void)setUsageCalculationInfo:(MTNetworkUsageCalculationInfo *)usageCalculationInfo; diff --git a/submodules/MtProtoKit/Sources/MTTcpConnection.m b/submodules/MtProtoKit/Sources/MTTcpConnection.m index 48d3917d51..d1aac2807b 100644 --- a/submodules/MtProtoKit/Sources/MTTcpConnection.m +++ b/submodules/MtProtoKit/Sources/MTTcpConnection.m @@ -689,7 +689,7 @@ struct ctr_state { return queue; } -- (instancetype)initWithContext:(MTContext *)context datacenterId:(NSInteger)datacenterId scheme:(MTTransportScheme *)scheme interface:(NSString *)interface usageCalculationInfo:(MTNetworkUsageCalculationInfo *)usageCalculationInfo +- (instancetype)initWithContext:(MTContext *)context datacenterId:(NSInteger)datacenterId scheme:(MTTransportScheme *)scheme interface:(NSString *)interface usageCalculationInfo:(MTNetworkUsageCalculationInfo *)usageCalculationInfo getLogPrefix:(NSString *(^)())getLogPrefix { #ifdef DEBUG NSAssert(scheme != nil, @"scheme should not be nil"); @@ -700,6 +700,8 @@ struct ctr_state { { _internalId = [[MTInternalId(MTTcpConnection) alloc] init]; + _getLogPrefix = [getLogPrefix copy]; + _encryptionProvider = context.encryptionProvider; _scheme = scheme; @@ -799,6 +801,7 @@ struct ctr_state { if (_socket == nil) { _socket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:[[MTTcpConnection tcpQueue] nativeQueue]]; + _socket.getLogPrefix = _getLogPrefix; _socket.usageCalculationInfo = _usageCalculationInfo; NSString *addressIp = _scheme.address.ip; diff --git a/submodules/MtProtoKit/Sources/MTTcpTransport.m b/submodules/MtProtoKit/Sources/MTTcpTransport.m index 4d490d0657..fe01d9e39d 100644 --- a/submodules/MtProtoKit/Sources/MTTcpTransport.m +++ b/submodules/MtProtoKit/Sources/MTTcpTransport.m @@ -82,7 +82,7 @@ static const NSTimeInterval MTTcpTransportSleepWatchdogTimeout = 60.0; return queue; } -- (instancetype)initWithDelegate:(id)delegate context:(MTContext *)context datacenterId:(NSInteger)datacenterId schemes:(NSArray * _Nonnull)schemes proxySettings:(MTSocksProxySettings *)proxySettings usageCalculationInfo:(MTNetworkUsageCalculationInfo *)usageCalculationInfo +- (instancetype)initWithDelegate:(id)delegate context:(MTContext *)context datacenterId:(NSInteger)datacenterId schemes:(NSArray * _Nonnull)schemes proxySettings:(MTSocksProxySettings *)proxySettings usageCalculationInfo:(MTNetworkUsageCalculationInfo *)usageCalculationInfo getLogPrefix:(NSString *(^)())getLogPrefix { #ifdef DEBUG NSAssert(context != nil, @"context should not be nil"); @@ -90,7 +90,7 @@ static const NSTimeInterval MTTcpTransportSleepWatchdogTimeout = 60.0; NSAssert(schemes.count != 0, @"schemes should not be empty"); #endif - self = [super initWithDelegate:delegate context:context datacenterId:datacenterId schemes:schemes proxySettings:proxySettings usageCalculationInfo:usageCalculationInfo]; + self = [super initWithDelegate:delegate context:context datacenterId:datacenterId schemes:schemes proxySettings:proxySettings usageCalculationInfo:usageCalculationInfo getLogPrefix:getLogPrefix]; if (self != nil) { _context = context; @@ -196,7 +196,7 @@ static const NSTimeInterval MTTcpTransportSleepWatchdogTimeout = 60.0; [self startConnectionWatchdogTimer:scheme]; [self startSleepWatchdogTimer]; - transportContext.connection = [[MTTcpConnection alloc] initWithContext:context datacenterId:_datacenterId scheme:scheme interface:nil usageCalculationInfo:_usageCalculationInfo]; + transportContext.connection = [[MTTcpConnection alloc] initWithContext:context datacenterId:_datacenterId scheme:scheme interface:nil usageCalculationInfo:_usageCalculationInfo getLogPrefix:self.getLogPrefix]; transportContext.connection.delegate = self; [transportContext.connection start]; } diff --git a/submodules/MtProtoKit/Sources/MTTransport.m b/submodules/MtProtoKit/Sources/MTTransport.m index 9752eb6537..d7e42ec43e 100644 --- a/submodules/MtProtoKit/Sources/MTTransport.m +++ b/submodules/MtProtoKit/Sources/MTTransport.m @@ -12,7 +12,7 @@ @implementation MTTransport -- (instancetype)initWithDelegate:(id)delegate context:(MTContext *)context datacenterId:(NSInteger)datacenterId schemes:(NSArray * _Nonnull)schemes proxySettings:(MTSocksProxySettings *)proxySettings usageCalculationInfo:(MTNetworkUsageCalculationInfo *)__unused usageCalculationInfo +- (instancetype)initWithDelegate:(id)delegate context:(MTContext *)context datacenterId:(NSInteger)datacenterId schemes:(NSArray * _Nonnull)schemes proxySettings:(MTSocksProxySettings *)proxySettings usageCalculationInfo:(MTNetworkUsageCalculationInfo *)__unused usageCalculationInfo getLogPrefix:(NSString * _Nullable (^ _Nullable)())getLogPrefix { #ifdef DEBUG NSAssert(context != nil, @"context should not be nil"); @@ -25,6 +25,7 @@ _context = context; _datacenterId = datacenterId; _proxySettings = proxySettings; + _getLogPrefix = [getLogPrefix copy]; _networkAvailability = [[MTNetworkAvailability alloc] initWithDelegate:self]; diff --git a/submodules/NetworkLogging/Sources/NetworkLogging.m b/submodules/NetworkLogging/Sources/NetworkLogging.m index 90bc12ba29..406ad011ec 100644 --- a/submodules/NetworkLogging/Sources/NetworkLogging.m +++ b/submodules/NetworkLogging/Sources/NetworkLogging.m @@ -13,15 +13,15 @@ void setBridgingShortTraceFunction(void (*f)(NSString *, NSString *)) { bridgingShortTrace = f; } -static void TGTelegramLoggingFunction(NSString *format, va_list args) { +static void TGTelegramLoggingFunction(NSString *format) { if (bridgingTrace) { - bridgingTrace(@"MT", [[NSString alloc] initWithFormat:format arguments:args]); + bridgingTrace(@"MT", format); } } -static void TGTelegramShortLoggingFunction(NSString *format, va_list args) { +static void TGTelegramShortLoggingFunction(NSString *format) { if (bridgingShortTrace) { - bridgingShortTrace(@"MT", [[NSString alloc] initWithFormat:format arguments:args]); + bridgingShortTrace(@"MT", format); } } diff --git a/submodules/PhotoResources/Sources/PhotoResources.swift b/submodules/PhotoResources/Sources/PhotoResources.swift index 7864cdc241..0d7e667934 100644 --- a/submodules/PhotoResources/Sources/PhotoResources.swift +++ b/submodules/PhotoResources/Sources/PhotoResources.swift @@ -1768,7 +1768,7 @@ public func chatMessageWebFileCancelInteractiveFetch(account: Account, image: Te return account.postbox.mediaBox.cancelInteractiveResourceFetch(image.resource) } -public func chatWebpageSnippetFileData(account: Account, fileReference: FileMediaReference, resource: MediaResource) -> Signal { +public func chatWebpageSnippetFileData(account: Account, mediaReference: AnyMediaReference, resource: MediaResource) -> Signal { let resourceData = account.postbox.mediaBox.resourceData(resource) |> map { next in return next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: .mappedIfSafe) @@ -1782,7 +1782,7 @@ public func chatWebpageSnippetFileData(account: Account, fileReference: FileMedi }, completed: { subscriber.putCompletion() })) - disposable.add(fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: fileReference.resourceReference(resource)).start()) + disposable.add(fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: mediaReference.resourceReference(resource)).start()) return disposable } } @@ -1810,8 +1810,8 @@ public func chatWebpageSnippetPhotoData(account: Account, photoReference: ImageM } } -public func chatWebpageSnippetFile(account: Account, fileReference: FileMediaReference, representation: TelegramMediaImageRepresentation) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { - let signal = chatWebpageSnippetFileData(account: account, fileReference: fileReference, resource: representation.resource) +public func chatWebpageSnippetFile(account: Account, mediaReference: AnyMediaReference, representation: TelegramMediaImageRepresentation) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { + let signal = chatWebpageSnippetFileData(account: account, mediaReference: mediaReference, resource: representation.resource) return signal |> map { fullSizeData in return { arguments in diff --git a/submodules/SettingsUI/BUILD b/submodules/SettingsUI/BUILD index 8508419ca6..8328190984 100644 --- a/submodules/SettingsUI/BUILD +++ b/submodules/SettingsUI/BUILD @@ -97,6 +97,8 @@ swift_library( "//submodules/QrCodeUI:QrCodeUI", "//submodules/AnimatedStickerNode:AnimatedStickerNode", "//submodules/TelegramAnimatedStickerNode:TelegramAnimatedStickerNode", + "//submodules/FetchManagerImpl:FetchManagerImpl", + "//submodules/ListMessageItem:ListMessageItem", ], visibility = [ "//visibility:public", diff --git a/submodules/SettingsUI/Sources/Downloads/DownloadsController.swift b/submodules/SettingsUI/Sources/Downloads/DownloadsController.swift new file mode 100644 index 0000000000..9b9188487d --- /dev/null +++ b/submodules/SettingsUI/Sources/Downloads/DownloadsController.swift @@ -0,0 +1,337 @@ +import Foundation +import UIKit +import Display +import AsyncDisplayKit +import SwiftSignalKit +import Postbox +import TelegramCore +import TelegramPresentationData +import TelegramUIPreferences +import ItemListUI +import PresentationDataUtils +import AccountContext +import ReactionImageComponent +import WebPBinding +import FetchManagerImpl +import ListMessageItem +import ListSectionHeaderNode + +private struct DownloadItem: Equatable { + let resourceId: MediaResourceId + let message: Message + let priority: FetchManagerPriorityKey + + static func ==(lhs: DownloadItem, rhs: DownloadItem) -> Bool { + if lhs.resourceId != rhs.resourceId { + return false + } + if lhs.message.id != rhs.message.id { + return false + } + if lhs.priority != rhs.priority { + return false + } + return true + } +} + +private final class DownloadsControllerArguments { + let context: AccountContext + + init( + context: AccountContext + ) { + self.context = context + } +} + +private enum DownloadsControllerSection: Int32 { + case items +} + +public final class DownloadsItemHeader: ListViewItemHeader { + public let id: ListViewItemNode.HeaderId + public let title: String + public let stickDirection: ListViewItemHeaderStickDirection = .top + public let stickOverInsets: Bool = true + public let theme: PresentationTheme + + public let height: CGFloat = 28.0 + + public init(id: ListViewItemNode.HeaderId, title: String, theme: PresentationTheme) { + self.id = id + self.title = title + self.theme = theme + } + + public func combinesWith(other: ListViewItemHeader) -> Bool { + if let other = other as? DownloadsItemHeader, other.id == self.id { + return true + } else { + return false + } + } + + public func node(synchronousLoad: Bool) -> ListViewItemHeaderNode { + return DownloadsItemHeaderNode(title: self.title, theme: self.theme) + } + + public func updateNode(_ node: ListViewItemHeaderNode, previous: ListViewItemHeader?, next: ListViewItemHeader?) { + (node as? DownloadsItemHeaderNode)?.update(title: self.title) + } +} + +public final class DownloadsItemHeaderNode: ListViewItemHeaderNode { + private var title: String + private var theme: PresentationTheme + + private var validLayout: (size: CGSize, leftInset: CGFloat, rightInset: CGFloat)? + + private let sectionHeaderNode: ListSectionHeaderNode + + public init(title: String, theme: PresentationTheme) { + self.title = title + self.theme = theme + + self.sectionHeaderNode = ListSectionHeaderNode(theme: theme) + + super.init() + + self.sectionHeaderNode.title = title + self.sectionHeaderNode.action = nil + + self.addSubnode(self.sectionHeaderNode) + } + + public func updateTheme(theme: PresentationTheme) { + self.theme = theme + self.sectionHeaderNode.updateTheme(theme: theme) + } + + public func update(title: String) { + self.sectionHeaderNode.title = title + self.sectionHeaderNode.action = nil + + if let (size, leftInset, rightInset) = self.validLayout { + self.sectionHeaderNode.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset) + } + } + + override public func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat) { + self.validLayout = (size, leftInset, rightInset) + self.sectionHeaderNode.frame = CGRect(origin: CGPoint(), size: size) + self.sectionHeaderNode.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset) + } + + override public func animateRemoved(duration: Double) { + self.alpha = 0.0 + self.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration, removeOnCompletion: true) + } +} + +private enum DownloadsControllerEntry: ItemListNodeEntry { + enum StableId: Hashable { + case item(MediaResourceId) + } + + case item(item: DownloadItem) + + var section: ItemListSectionId { + switch self { + case .item: + return DownloadsControllerSection.items.rawValue + } + } + + var stableId: StableId { + switch self { + case let .item(item): + return .item(item.resourceId) + } + } + + var sortId: FetchManagerPriorityKey { + switch self { + case let .item(item): + return item.priority + } + } + + static func ==(lhs: DownloadsControllerEntry, rhs: DownloadsControllerEntry) -> Bool { + switch lhs { + case let .item(item): + if case .item(item) = rhs { + return true + } else { + return false + } + } + } + + static func <(lhs: DownloadsControllerEntry, rhs: DownloadsControllerEntry) -> Bool { + return lhs.sortId < rhs.sortId + } + + func item(presentationData: ItemListPresentationData, arguments: Any) -> ListViewItem { + let arguments = arguments as! DownloadsControllerArguments + let _ = arguments + switch self { + case let .item(item): + let listInteraction = ListMessageItemInteraction(openMessage: { message, mode -> Bool in + return false + }, openMessageContextMenu: { message, _, node, rect, gesture in + }, toggleMessagesSelection: { messageId, selected in + }, openUrl: { url, _, _, message in + }, openInstantPage: { message, data in + }, longTap: { action, message in + }, getHiddenMedia: { + return [:] + }) + + let presentationData = arguments.context.sharedContext.currentPresentationData.with({ $0 }) + + return ListMessageItem(presentationData: ChatPresentationData(presentationData: presentationData), context: arguments.context, chatLocation: .peer(item.message.id.peerId), interaction: listInteraction, message: item.message, selection: .none, displayHeader: false, customHeader: nil/*DownloadsItemHeader(id: ListViewItemNode.HeaderId(space: 0, id: item.message.id.peerId), title: item.message.peers[item.message.id.peerId].flatMap(EnginePeer.init)?.displayTitle(strings: presentationData.strings, displayOrder: presentationData.nameDisplayOrder) ?? "", theme: presentationData.theme)*/, hintIsLink: false, isGlobalSearchResult: false) + } + } +} + +private struct DownloadsControllerState: Equatable { + var hasReaction: Bool = false +} + +private func downloadsControllerEntries( + presentationData: PresentationData, + items: [DownloadItem], + state: DownloadsControllerState +) -> [DownloadsControllerEntry] { + var entries: [DownloadsControllerEntry] = [] + + var index = 0 + for item in items { + entries.append(.item( + item: item + )) + index += 1 + } + + return entries +} + +public func downloadsController( + context: AccountContext, + updatedPresentationData: (initial: PresentationData, signal: Signal)? = nil +) -> ViewController { + let statePromise = ValuePromise(DownloadsControllerState(), ignoreRepeated: true) + let stateValue = Atomic(value: DownloadsControllerState()) + let updateState: ((DownloadsControllerState) -> DownloadsControllerState) -> Void = { f in + statePromise.set(stateValue.modify { f($0) }) + } + let _ = updateState + + var dismissImpl: (() -> Void)? + let _ = dismissImpl + + let actionsDisposable = DisposableSet() + + let arguments = DownloadsControllerArguments( + context: context + ) + + let settings = context.account.postbox.preferencesView(keys: [PreferencesKeys.reactionSettings]) + |> map { preferencesView -> ReactionSettings in + let reactionSettings: ReactionSettings + if let entry = preferencesView.values[PreferencesKeys.reactionSettings], let value = entry.get(ReactionSettings.self) { + reactionSettings = value + } else { + reactionSettings = .default + } + return reactionSettings + } + + let downloadItems: Signal<[DownloadItem], NoError> = (context.fetchManager as! FetchManagerImpl).entriesSummary + |> mapToSignal { entries -> Signal<[DownloadItem], NoError> in + var itemSignals: [Signal] = [] + + for entry in entries { + switch entry.id.locationKey { + case let .messageId(id): + itemSignals.append(context.account.postbox.transaction { transaction -> DownloadItem? in + if let message = transaction.getMessage(id) { + return DownloadItem(resourceId: entry.resourceReference.resource.id, message: message, priority: entry.priority) + } + return nil + }) + default: + break + } + } + + return combineLatest(queue: .mainQueue(), itemSignals) + |> map { items -> [DownloadItem] in + return items.compactMap { $0 } + } + } + + let presentationData = updatedPresentationData?.signal ?? context.sharedContext.presentationData + let signal = combineLatest(queue: .mainQueue(), + presentationData, + statePromise.get(), + context.engine.stickers.availableReactions(), + settings, + downloadItems + ) + |> deliverOnMainQueue + |> map { presentationData, state, availableReactions, settings, downloadItems -> (ItemListControllerState, (ItemListNodeState, Any)) in + //TODO:localize + let title: String = "Downloads" + + let entries = downloadsControllerEntries( + presentationData: presentationData, + items: downloadItems, + state: state + ) + + let controllerState = ItemListControllerState( + presentationData: ItemListPresentationData(presentationData), + title: .text(title), + leftNavigationButton: nil, + rightNavigationButton: nil, + backNavigationButton: ItemListBackButton(title: presentationData.strings.Common_Back), + animateChanges: false + ) + let listState = ItemListNodeState( + presentationData: ItemListPresentationData(presentationData), + entries: entries, + style: .plain, + animateChanges: true + ) + + return (controllerState, (listState, arguments)) + } + |> afterDisposed { + actionsDisposable.dispose() + } + + let controller = ItemListController(context: context, state: signal) + + controller.didScrollWithOffset = { [weak controller] offset, transition, _ in + guard let controller = controller else { + return + } + controller.forEachItemNode { itemNode in + if let itemNode = itemNode as? ReactionChatPreviewItemNode { + itemNode.standaloneReactionAnimation?.addRelativeContentOffset(CGPoint(x: 0.0, y: offset), transition: transition) + } + } + } + + dismissImpl = { [weak controller] in + guard let controller = controller else { + return + } + controller.dismiss() + } + + return controller +} + diff --git a/submodules/TelegramCore/Sources/ApiUtils/CloudFileMediaResource.swift b/submodules/TelegramCore/Sources/ApiUtils/CloudFileMediaResource.swift index 6552324071..4bc6d79da2 100644 --- a/submodules/TelegramCore/Sources/ApiUtils/CloudFileMediaResource.swift +++ b/submodules/TelegramCore/Sources/ApiUtils/CloudFileMediaResource.swift @@ -7,6 +7,17 @@ protocol TelegramCloudMediaResource: TelegramMediaResource { func apiInputLocation(fileReference: Data?) -> Api.InputFileLocation? } +public func extractMediaResourceDebugInfo(resource: MediaResource) -> String? { + if let resource = resource as? TelegramCloudMediaResource { + guard let inputLocation = resource.apiInputLocation(fileReference: nil) else { + return nil + } + return String(describing: inputLocation) + } else { + return nil + } +} + public protocol TelegramMultipartFetchableResource: TelegramMediaResource { var datacenterId: Int { get } } diff --git a/submodules/TelegramCore/Sources/Network/Download.swift b/submodules/TelegramCore/Sources/Network/Download.swift index 6cf8e80924..aa8f4d4d4e 100644 --- a/submodules/TelegramCore/Sources/Network/Download.swift +++ b/submodules/TelegramCore/Sources/Network/Download.swift @@ -43,6 +43,7 @@ class Download: NSObject, MTRequestMessageServiceDelegate { let context: MTContext let mtProto: MTProto let requestService: MTRequestMessageService + private let logPrefix = Atomic(value: nil) private var shouldKeepConnectionDisposable: Disposable? @@ -59,6 +60,10 @@ class Download: NSObject, MTRequestMessageServiceDelegate { } self.mtProto = MTProto(context: self.context, datacenterId: datacenterId, usageCalculationInfo: usageInfo, requiredAuthToken: requiredAuthToken, authTokenMasterDatacenterId: authTokenMasterDatacenterId) + let logPrefix = self.logPrefix + self.mtProto.getLogPrefix = { + return logPrefix.with { $0 } + } self.mtProto.cdn = isCdn self.mtProto.useTempAuthKeys = self.context.useTempAuthKeys && !isCdn self.mtProto.media = isMedia @@ -375,7 +380,7 @@ class Download: NSObject, MTRequestMessageServiceDelegate { } } - func rawRequest(_ data: (FunctionDescription, Buffer, (Buffer) -> Any?), automaticFloodWait: Bool = true, failOnServerErrors: Bool = false) -> Signal<(Any, Double), (MTRpcError, Double)> { + func rawRequest(_ data: (FunctionDescription, Buffer, (Buffer) -> Any?), automaticFloodWait: Bool = true, failOnServerErrors: Bool = false, logPrefix: String = "") -> Signal<(Any, Double), (MTRpcError, Double)> { let requestService = self.requestService return Signal { subscriber in let request = MTRequest() diff --git a/submodules/TelegramUI/BUILD b/submodules/TelegramUI/BUILD index b18b86672a..e70273fb5b 100644 --- a/submodules/TelegramUI/BUILD +++ b/submodules/TelegramUI/BUILD @@ -256,6 +256,7 @@ swift_library( "//submodules/TabBarUI:TabBarUI", "//submodules/SoftwareVideo:SoftwareVideo", "//submodules/ManagedFile:ManagedFile", + "//submodules/FetchManagerImpl:FetchManagerImpl", ] + select({ "@build_bazel_rules_apple//apple:ios_armv7": [], "@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets, diff --git a/submodules/TelegramUI/Sources/AccountContext.swift b/submodules/TelegramUI/Sources/AccountContext.swift index 9249524e72..70d221e193 100644 --- a/submodules/TelegramUI/Sources/AccountContext.swift +++ b/submodules/TelegramUI/Sources/AccountContext.swift @@ -17,6 +17,7 @@ import TelegramBaseController import AsyncDisplayKit import PresentationDataUtils import MeshAnimationCache +import FetchManagerImpl private final class DeviceSpecificContactImportContext { let disposable = MetaDisposable() diff --git a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift index 8be4753e49..2e733509e1 100644 --- a/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift +++ b/submodules/TelegramUI/Sources/ChatInterfaceStateContextMenus.swift @@ -26,6 +26,7 @@ import TelegramNotices import ReactionListContextMenuContent import TelegramUIPreferences import Translate +import DebugSettingsUI private struct MessageContextMenuData { let starStatus: Bool? @@ -565,7 +566,7 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState return transaction.getCombinedPeerReadState(messages[0].id.peerId) } - let dataSignal: Signal<(MessageContextMenuData, [MessageId: ChatUpdatingMessageMedia], CachedPeerData?, AppConfiguration, Bool, Int32, AvailableReactions?, TranslationSettings), NoError> = combineLatest( + let dataSignal: Signal<(MessageContextMenuData, [MessageId: ChatUpdatingMessageMedia], CachedPeerData?, AppConfiguration, Bool, Int32, AvailableReactions?, TranslationSettings, LoggingSettings), NoError> = combineLatest( loadLimits, loadStickerSaveStatusSignal, loadResourceStatusSignal, @@ -576,9 +577,9 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState readState, ApplicationSpecificNotice.getMessageViewsPrivacyTips(accountManager: context.sharedContext.accountManager), context.engine.stickers.availableReactions(), - context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.translationSettings]) + context.sharedContext.accountManager.sharedData(keys: [ApplicationSpecificSharedDataKeys.translationSettings, SharedDataKeys.loggingSettings]) ) - |> map { limitsAndAppConfig, stickerSaveStatus, resourceStatus, messageActions, updatingMessageMedia, cachedData, readState, messageViewsPrivacyTips, availableReactions, sharedData -> (MessageContextMenuData, [MessageId: ChatUpdatingMessageMedia], CachedPeerData?, AppConfiguration, Bool, Int32, AvailableReactions?, TranslationSettings) in + |> map { limitsAndAppConfig, stickerSaveStatus, resourceStatus, messageActions, updatingMessageMedia, cachedData, readState, messageViewsPrivacyTips, availableReactions, sharedData -> (MessageContextMenuData, [MessageId: ChatUpdatingMessageMedia], CachedPeerData?, AppConfiguration, Bool, Int32, AvailableReactions?, TranslationSettings, LoggingSettings) in let (limitsConfiguration, appConfig) = limitsAndAppConfig var canEdit = false if !isAction { @@ -598,12 +599,19 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState translationSettings = TranslationSettings.defaultSettings } - return (MessageContextMenuData(starStatus: stickerSaveStatus, canReply: canReply, canPin: canPin, canEdit: canEdit, canSelect: canSelect, resourceStatus: resourceStatus, messageActions: messageActions), updatingMessageMedia, cachedData, appConfig, isMessageRead, messageViewsPrivacyTips, availableReactions, translationSettings) + let loggingSettings: LoggingSettings + if let current = sharedData.entries[SharedDataKeys.loggingSettings]?.get(LoggingSettings.self) { + loggingSettings = current + } else { + loggingSettings = LoggingSettings.defaultSettings + } + + return (MessageContextMenuData(starStatus: stickerSaveStatus, canReply: canReply, canPin: canPin, canEdit: canEdit, canSelect: canSelect, resourceStatus: resourceStatus, messageActions: messageActions), updatingMessageMedia, cachedData, appConfig, isMessageRead, messageViewsPrivacyTips, availableReactions, translationSettings, loggingSettings) } return dataSignal |> deliverOnMainQueue - |> map { data, updatingMessageMedia, cachedData, appConfig, isMessageRead, messageViewsPrivacyTips, availableReactions, translationSettings -> ContextController.Items in + |> map { data, updatingMessageMedia, cachedData, appConfig, isMessageRead, messageViewsPrivacyTips, availableReactions, translationSettings, loggingSettings -> ContextController.Items in var actions: [ContextMenuItem] = [] var isPinnedMessages = false @@ -867,6 +875,32 @@ func contextMenuForChatPresentationInterfaceState(chatPresentationInterfaceState } } + var downloadableMediaResourceInfos: [String] = [] + for media in message.media { + if let file = media as? TelegramMediaFile { + if let info = extractMediaResourceDebugInfo(resource: file.resource) { + downloadableMediaResourceInfos.append(info) + } + } else if let image = media as? TelegramMediaImage { + for representation in image.representations { + if let info = extractMediaResourceDebugInfo(resource: representation.resource) { + downloadableMediaResourceInfos.append(info) + } + } + } + } + + if (loggingSettings.logToFile || loggingSettings.logToConsole) && !downloadableMediaResourceInfos.isEmpty { + actions.append(.action(ContextMenuActionItem(text: "Send Logs", icon: { theme in + return generateTintedImage(image: UIImage(bundleImageName: "Chat/Context Menu/Message"), color: theme.actionSheet.primaryTextColor) + }, action: { _, f in + triggerDebugSendLogsUI(context: context, additionalInfo: "User has requested download logs for \(downloadableMediaResourceInfos)", pushController: { c in + controllerInteraction.navigationController()?.pushViewController(c) + }) + f(.default) + }))) + } + var threadId: Int64? var threadMessageCount: Int = 0 if case .peer = chatPresentationInterfaceState.chatLocation, let channel = chatPresentationInterfaceState.renderedPeer?.peer as? TelegramChannel, case .group = channel.info { diff --git a/submodules/TelegramUI/Sources/ChatMessageReplyInfoNode.swift b/submodules/TelegramUI/Sources/ChatMessageReplyInfoNode.swift index 3f75ac9223..54af0e788f 100644 --- a/submodules/TelegramUI/Sources/ChatMessageReplyInfoNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageReplyInfoNode.swift @@ -189,7 +189,7 @@ class ChatMessageReplyInfoNode: ASDisplayNode { if fileReference.media.isVideo { updateImageSignal = chatMessageVideoThumbnail(account: context.account, fileReference: fileReference) } else if let iconImageRepresentation = smallestImageRepresentation(fileReference.media.previewRepresentations) { - updateImageSignal = chatWebpageSnippetFile(account: context.account, fileReference: fileReference, representation: iconImageRepresentation) + updateImageSignal = chatWebpageSnippetFile(account: context.account, mediaReference: fileReference.abstract, representation: iconImageRepresentation) } } } diff --git a/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift b/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift index 7677776378..1ec99a8373 100644 --- a/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift +++ b/submodules/TelegramUI/Sources/ChatPinnedMessageTitlePanelNode.swift @@ -457,7 +457,7 @@ final class ChatPinnedMessageTitlePanelNode: ChatTitleAccessoryPanelNode { } else if fileReference.media.isVideo { updateImageSignal = chatMessageVideoThumbnail(account: context.account, fileReference: fileReference) } else if let iconImageRepresentation = smallestImageRepresentation(fileReference.media.previewRepresentations) { - updateImageSignal = chatWebpageSnippetFile(account: context.account, fileReference: fileReference, representation: iconImageRepresentation) + updateImageSignal = chatWebpageSnippetFile(account: context.account, mediaReference: fileReference.abstract, representation: iconImageRepresentation) } } } else { diff --git a/submodules/TelegramUI/Sources/EditAccessoryPanelNode.swift b/submodules/TelegramUI/Sources/EditAccessoryPanelNode.swift index 6100fa6f54..1164f46a3a 100644 --- a/submodules/TelegramUI/Sources/EditAccessoryPanelNode.swift +++ b/submodules/TelegramUI/Sources/EditAccessoryPanelNode.swift @@ -226,7 +226,7 @@ final class EditAccessoryPanelNode: AccessoryPanelNode { if fileReference.media.isVideo { updateImageSignal = chatMessageVideoThumbnail(account: self.context.account, fileReference: fileReference) } else if let iconImageRepresentation = smallestImageRepresentation(fileReference.media.previewRepresentations) { - updateImageSignal = chatWebpageSnippetFile(account: self.context.account, fileReference: fileReference, representation: iconImageRepresentation) + updateImageSignal = chatWebpageSnippetFile(account: self.context.account, mediaReference: fileReference.abstract, representation: iconImageRepresentation) } } } else { diff --git a/submodules/TelegramUI/Sources/FetchManagerLocation.swift b/submodules/TelegramUI/Sources/FetchManagerLocation.swift deleted file mode 100644 index 7c6c2b2ca0..0000000000 --- a/submodules/TelegramUI/Sources/FetchManagerLocation.swift +++ /dev/null @@ -1,2 +0,0 @@ -import Foundation -import Postbox diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index 3b2ec15c7e..8947d8c076 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -403,6 +403,7 @@ private enum PeerInfoSettingsSection { case avatar case edit case proxy + case downloads case savedMessages case recentCalls case devices @@ -662,10 +663,14 @@ private func settingsItems(data: PeerInfoScreenData?, context: AccountContext, p } } - items[.shortcuts]!.append(PeerInfoScreenDisclosureItem(id: 0, text: presentationData.strings.Settings_SavedMessages, icon: PresentationResourcesSettings.savedMessages, action: { + //TODO:localize + items[.shortcuts]!.append(PeerInfoScreenDisclosureItem(id: 0, text: "Downloads", icon: PresentationResourcesSettings.savedMessages, action: { + interaction.openSettings(.downloads) + })) + items[.shortcuts]!.append(PeerInfoScreenDisclosureItem(id: 1, text: presentationData.strings.Settings_SavedMessages, icon: PresentationResourcesSettings.savedMessages, action: { interaction.openSettings(.savedMessages) })) - items[.shortcuts]!.append(PeerInfoScreenDisclosureItem(id: 1, text: presentationData.strings.CallSettings_RecentCalls, icon: PresentationResourcesSettings.recentCalls, action: { + items[.shortcuts]!.append(PeerInfoScreenDisclosureItem(id: 2, text: presentationData.strings.CallSettings_RecentCalls, icon: PresentationResourcesSettings.recentCalls, action: { interaction.openSettings(.recentCalls) })) @@ -680,10 +685,10 @@ private func settingsItems(data: PeerInfoScreenData?, context: AccountContext, p devicesLabel = "" } - items[.shortcuts]!.append(PeerInfoScreenDisclosureItem(id: 2, label: .text(devicesLabel), text: presentationData.strings.Settings_Devices, icon: PresentationResourcesSettings.devices, action: { + items[.shortcuts]!.append(PeerInfoScreenDisclosureItem(id: 3, label: .text(devicesLabel), text: presentationData.strings.Settings_Devices, icon: PresentationResourcesSettings.devices, action: { interaction.openSettings(.devices) })) - items[.shortcuts]!.append(PeerInfoScreenDisclosureItem(id: 3, text: presentationData.strings.Settings_ChatFolders, icon: PresentationResourcesSettings.chatFolders, action: { + items[.shortcuts]!.append(PeerInfoScreenDisclosureItem(id: 4, text: presentationData.strings.Settings_ChatFolders, icon: PresentationResourcesSettings.chatFolders, action: { interaction.openSettings(.chatFolders) })) @@ -5527,6 +5532,8 @@ final class PeerInfoScreenNode: ViewControllerTracingNode, UIScrollViewDelegate self.headerNode.navigationButtonContainer.performAction?(.edit, nil, nil) case .proxy: self.controller?.push(proxySettingsController(context: self.context)) + case .downloads: + self.controller?.push(downloadsController(context: self.context, updatedPresentationData: self.controller?.updatedPresentationData)) case .savedMessages: if let controller = self.controller, let navigationController = controller.navigationController as? NavigationController { self.context.sharedContext.navigateToChatController(NavigateToChatControllerParams(navigationController: navigationController, context: self.context, chatLocation: .peer(self.context.account.peerId))) diff --git a/submodules/TelegramUI/Sources/ReplyAccessoryPanelNode.swift b/submodules/TelegramUI/Sources/ReplyAccessoryPanelNode.swift index 3a5ca4785a..1f5db3883a 100644 --- a/submodules/TelegramUI/Sources/ReplyAccessoryPanelNode.swift +++ b/submodules/TelegramUI/Sources/ReplyAccessoryPanelNode.swift @@ -196,7 +196,7 @@ final class ReplyAccessoryPanelNode: AccessoryPanelNode { if fileReference.media.isVideo { updateImageSignal = chatMessageVideoThumbnail(account: context.account, fileReference: fileReference) } else if let iconImageRepresentation = smallestImageRepresentation(fileReference.media.previewRepresentations) { - updateImageSignal = chatWebpageSnippetFile(account: context.account, fileReference: fileReference, representation: iconImageRepresentation) + updateImageSignal = chatWebpageSnippetFile(account: context.account, mediaReference: fileReference.abstract, representation: iconImageRepresentation) } } } else { diff --git a/submodules/WatchBridge/Sources/WatchRequestHandlers.swift b/submodules/WatchBridge/Sources/WatchRequestHandlers.swift index 923ac1176d..f283fa82d1 100644 --- a/submodules/WatchBridge/Sources/WatchRequestHandlers.swift +++ b/submodules/WatchBridge/Sources/WatchRequestHandlers.swift @@ -556,7 +556,7 @@ final class WatchMediaHandler: WatchRequestHandler { imageSignal = chatMessageVideoThumbnail(account: context.account, fileReference: fileReference) roundVideo = fileReference.media.isInstantVideo } else if let iconImageRepresentation = smallestImageRepresentation(fileReference.media.previewRepresentations) { - imageSignal = chatWebpageSnippetFile(account: context.account, fileReference: fileReference, representation: iconImageRepresentation) + imageSignal = chatWebpageSnippetFile(account: context.account, mediaReference: fileReference.abstract, representation: iconImageRepresentation) } } }