From 00b6826303f9c57b89ea055259ecc8d40f8224c2 Mon Sep 17 00:00:00 2001 From: Ali <> Date: Fri, 24 Sep 2021 22:56:48 +0300 Subject: [PATCH] Refactoring [skip ci] --- .../Sources/InstantPageImageNode.swift | 2 +- .../Sources/ItemListVenueItem.swift | 8 +- .../ChatMessageLiveLocationPositionNode.swift | 2 +- submodules/LocationResources/BUILD | 2 +- .../Sources/MapResources.swift | 72 ++---- .../Sources/VenueIconResources.swift | 80 ++---- .../Sources/LocationActionListItem.swift | 8 +- .../Sources/LocationAnnotation.swift | 6 +- .../Sources/LocationInfoListItem.swift | 8 +- .../LocationPickerControllerNode.swift | 16 +- .../Sources/LocationSearchContainerNode.swift | 12 +- .../Sources/LocationViewControllerNode.swift | 4 +- .../Sources/DeviceContactInfoController.swift | 6 +- submodules/Postbox/Sources/MediaBox.swift | 242 ++++++++++++++---- .../Sources/Account/Account.swift | 3 +- .../Resources/TelegramEngineResources.swift | 137 +++++----- .../ChatMessageMapBubbleContentNode.swift | 2 +- .../Sources/CreateGroupController.swift | 4 +- .../Sources/FetchCachedRepresentations.swift | 2 - .../Sources/PeerInfo/PeerInfoScreen.swift | 4 +- .../Sources/SharedAccountContext.swift | 4 +- .../TelegramAccountAuxiliaryMethods.swift | 2 - 22 files changed, 346 insertions(+), 280 deletions(-) diff --git a/submodules/InstantPageUI/Sources/InstantPageImageNode.swift b/submodules/InstantPageUI/Sources/InstantPageImageNode.swift index 9d7eb5d2b0..559bf1e7f5 100644 --- a/submodules/InstantPageUI/Sources/InstantPageImageNode.swift +++ b/submodules/InstantPageUI/Sources/InstantPageImageNode.swift @@ -130,7 +130,7 @@ final class InstantPageImageNode: ASDisplayNode, InstantPageNode { } } let resource = MapSnapshotMediaResource(latitude: map.latitude, longitude: map.longitude, width: Int32(dimensions.width), height: Int32(dimensions.height)) - self.imageNode.setSignal(chatMapSnapshotImage(account: context.account, resource: resource)) + self.imageNode.setSignal(chatMapSnapshotImage(engine: context.engine, resource: resource)) } else if let webPage = media.media as? TelegramMediaWebpage, case let .Loaded(content) = webPage.content, let image = content.image { let imageReference = ImageMediaReference.webPage(webPage: WebpageReference(webPage), media: image) self.imageNode.setSignal(chatMessagePhoto(postbox: context.account.postbox, photoReference: imageReference)) diff --git a/submodules/ItemListVenueItem/Sources/ItemListVenueItem.swift b/submodules/ItemListVenueItem/Sources/ItemListVenueItem.swift index bd01e1d118..45cb7e2876 100644 --- a/submodules/ItemListVenueItem/Sources/ItemListVenueItem.swift +++ b/submodules/ItemListVenueItem/Sources/ItemListVenueItem.swift @@ -11,7 +11,7 @@ import ShimmerEffect public final class ItemListVenueItem: ListViewItem, ItemListItem { let presentationData: ItemListPresentationData - let account: Account + let engine: TelegramEngine let venue: TelegramMediaMap? let title: String? let subtitle: String? @@ -22,9 +22,9 @@ public final class ItemListVenueItem: ListViewItem, ItemListItem { public let sectionId: ItemListSectionId let header: ListViewItemHeader? - public init(presentationData: ItemListPresentationData, account: Account, venue: TelegramMediaMap?, title: String? = nil, subtitle: String? = nil, sectionId: ItemListSectionId = 0, style: ItemListStyle, action: (() -> Void)?, infoAction: (() -> Void)? = nil, header: ListViewItemHeader? = nil) { + public init(presentationData: ItemListPresentationData, engine: TelegramEngine, venue: TelegramMediaMap?, title: String? = nil, subtitle: String? = nil, sectionId: ItemListSectionId = 0, style: ItemListStyle, action: (() -> Void)?, infoAction: (() -> Void)? = nil, header: ListViewItemHeader? = nil) { self.presentationData = presentationData - self.account = account + self.engine = engine self.venue = venue self.title = title self.subtitle = subtitle @@ -281,7 +281,7 @@ public class ItemListVenueItemNode: ListViewItemNode, ItemListItemNode { let _ = addressApply() if let updatedVenueType = updatedVenueType { - strongSelf.iconNode.setSignal(venueIcon(postbox: item.account.postbox, type: updatedVenueType, background: true)) + strongSelf.iconNode.setSignal(venueIcon(engine: item.engine, type: updatedVenueType, background: true)) } let iconApply = iconLayout(TransformImageArguments(corners: ImageCorners(), imageSize: CGSize(width: iconSize, height: iconSize), boundingSize: CGSize(width: iconSize, height: iconSize), intrinsicInsets: UIEdgeInsets())) diff --git a/submodules/LiveLocationPositionNode/Sources/ChatMessageLiveLocationPositionNode.swift b/submodules/LiveLocationPositionNode/Sources/ChatMessageLiveLocationPositionNode.swift index cf0f6e80fd..7a65acaba8 100644 --- a/submodules/LiveLocationPositionNode/Sources/ChatMessageLiveLocationPositionNode.swift +++ b/submodules/LiveLocationPositionNode/Sources/ChatMessageLiveLocationPositionNode.swift @@ -217,7 +217,7 @@ public final class ChatMessageLiveLocationPositionNode: ASDisplayNode { if let updatedVenueType = updatedVenueType { strongSelf.venueType = updatedVenueType - strongSelf.iconNode.setSignal(venueIcon(postbox: context.account.postbox, type: updatedVenueType, background: false)) + strongSelf.iconNode.setSignal(venueIcon(engine: context.engine, type: updatedVenueType, background: false)) } let arguments = VenueIconArguments(defaultBackgroundColor: theme.chat.inputPanel.actionControlFillColor, defaultForegroundColor: theme.chat.inputPanel.actionControlForegroundColor) diff --git a/submodules/LocationResources/BUILD b/submodules/LocationResources/BUILD index beedfc749f..38cdd05e3a 100644 --- a/submodules/LocationResources/BUILD +++ b/submodules/LocationResources/BUILD @@ -11,10 +11,10 @@ swift_library( ], deps = [ "//submodules/TelegramCore:TelegramCore", - "//submodules/Postbox:Postbox", "//submodules/SSignalKit/SwiftSignalKit:SwiftSignalKit", "//submodules/Display:Display", "//submodules/AppBundle:AppBundle", + "//submodules/PersistentStringHash:PersistentStringHash", ], visibility = [ "//visibility:public", diff --git a/submodules/LocationResources/Sources/MapResources.swift b/submodules/LocationResources/Sources/MapResources.swift index 2299669217..259388cd80 100644 --- a/submodules/LocationResources/Sources/MapResources.swift +++ b/submodules/LocationResources/Sources/MapResources.swift @@ -1,7 +1,6 @@ import Foundation import UIKit import Display -import Postbox import TelegramCore import MapKit import SwiftSignalKit @@ -21,7 +20,7 @@ public struct MapSnapshotMediaResourceId { } } -public class MapSnapshotMediaResource: TelegramMediaResource { +public class MapSnapshotMediaResource { public let latitude: Double public let longitude: Double public let width: Int32 @@ -34,49 +33,8 @@ public class MapSnapshotMediaResource: TelegramMediaResource { self.height = height } - public required init(decoder: PostboxDecoder) { - self.latitude = decoder.decodeDoubleForKey("lt", orElse: 0.0) - self.longitude = decoder.decodeDoubleForKey("ln", orElse: 0.0) - self.width = decoder.decodeInt32ForKey("w", orElse: 0) - self.height = decoder.decodeInt32ForKey("h", orElse: 0) - } - - public func encode(_ encoder: PostboxEncoder) { - encoder.encodeDouble(self.latitude, forKey: "lt") - encoder.encodeDouble(self.longitude, forKey: "ln") - encoder.encodeInt32(self.width, forKey: "w") - encoder.encodeInt32(self.height, forKey: "h") - } - - public var id: MediaResourceId { - return MediaResourceId(MapSnapshotMediaResourceId(latitude: self.latitude, longitude: self.longitude, width: self.width, height: self.height).uniqueId) - } - - public func isEqual(to: MediaResource) -> Bool { - if let to = to as? MapSnapshotMediaResource { - return self.latitude == to.latitude && self.longitude == to.longitude && self.width == to.width && self.height == to.height - } else { - return false - } - } -} - -public final class MapSnapshotMediaResourceRepresentation: CachedMediaResourceRepresentation { - public let keepDuration: CachedMediaRepresentationKeepDuration = .shortLived - - public var uniqueId: String { - return "cached" - } - - public init() { - } - - public func isEqual(to: CachedMediaResourceRepresentation) -> Bool { - if to is MapSnapshotMediaResourceRepresentation { - return true - } else { - return false - } + public var id: EngineMediaResource.Id { + return EngineMediaResource.Id(MapSnapshotMediaResourceId(latitude: self.latitude, longitude: self.longitude, width: self.width, height: self.height).uniqueId) } } @@ -96,7 +54,7 @@ private func adjustGMapLatitude(_ latitude: Double, offset: Int, zoom: Int) -> D return yToLatitude(latitudeToY(latitude) + t) } -public func fetchMapSnapshotResource(resource: MapSnapshotMediaResource) -> Signal { +private func fetchMapSnapshotResource(resource: MapSnapshotMediaResource) -> Signal { return Signal { subscriber in let disposable = MetaDisposable() @@ -113,9 +71,9 @@ public func fetchMapSnapshotResource(resource: MapSnapshotMediaResource) -> Sign snapshotter.start(with: DispatchQueue.global(), completionHandler: { result, error in if let image = result?.image { if let data = image.jpegData(compressionQuality: 0.9) { - let tempFile = TempBox.shared.tempFile(fileName: "image.jpg") + let tempFile = EngineTempBox.shared.tempFile(fileName: "image.jpg") if let _ = try? data.write(to: URL(fileURLWithPath: tempFile.path), options: .atomic) { - subscriber.putNext(.tempFile(tempFile)) + subscriber.putNext(.moveTempFile(file: tempFile)) subscriber.putCompletion() } } @@ -130,11 +88,17 @@ public func fetchMapSnapshotResource(resource: MapSnapshotMediaResource) -> Sign } } -public func chatMapSnapshotData(account: Account, resource: MapSnapshotMediaResource) -> Signal { +public func chatMapSnapshotData(engine: TelegramEngine, resource: MapSnapshotMediaResource) -> Signal { return Signal { subscriber in - let dataDisposable = account.postbox.mediaBox.cachedResourceRepresentation(resource, representation: MapSnapshotMediaResourceRepresentation(), complete: true).start(next: { next in - if next.size != 0 { - subscriber.putNext(next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: [])) + let dataDisposable = engine.resources.custom( + id: resource.id.stringRepresentation, + fetch: EngineMediaResource.Fetch { + return fetchMapSnapshotResource(resource: resource) + }, + cacheTimeout: .shortLived + ).start(next: { next in + if next.availableSize != 0 { + subscriber.putNext(next.availableSize == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: [])) } }, error: subscriber.putError, completed: subscriber.putCompletion) @@ -144,8 +108,8 @@ public func chatMapSnapshotData(account: Account, resource: MapSnapshotMediaReso } } -public func chatMapSnapshotImage(account: Account, resource: MapSnapshotMediaResource) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { - let signal = chatMapSnapshotData(account: account, resource: resource) +public func chatMapSnapshotImage(engine: TelegramEngine, resource: MapSnapshotMediaResource) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { + let signal = chatMapSnapshotData(engine: engine, resource: resource) return signal |> map { fullSizeData in return { arguments in diff --git a/submodules/LocationResources/Sources/VenueIconResources.swift b/submodules/LocationResources/Sources/VenueIconResources.swift index 8dd18eeca5..08ce086052 100644 --- a/submodules/LocationResources/Sources/VenueIconResources.swift +++ b/submodules/LocationResources/Sources/VenueIconResources.swift @@ -1,10 +1,10 @@ import Foundation import UIKit import Display -import Postbox import TelegramCore import SwiftSignalKit import AppBundle +import PersistentStringHash public struct VenueIconResourceId { public let type: String @@ -22,79 +22,51 @@ public struct VenueIconResourceId { } } -public class VenueIconResource: TelegramMediaResource { +public class VenueIconResource { public let type: String public init(type: String) { self.type = type } - public required init(decoder: PostboxDecoder) { - self.type = decoder.decodeStringForKey("t", orElse: "") - } - - public func encode(_ encoder: PostboxEncoder) { - encoder.encodeString(self.type, forKey: "t") - } - - public var id: MediaResourceId { - return MediaResourceId(VenueIconResourceId(type: self.type).uniqueId) - } - - public func isEqual(to: MediaResource) -> Bool { - if let to = to as? VenueIconResource { - return self.type == to.type - } else { - return false - } + public var id: EngineMediaResource.Id { + return EngineMediaResource.Id(VenueIconResourceId(type: self.type).uniqueId) } } -public func fetchVenueIconResource(account: Account, resource: VenueIconResource) -> Signal { +private func fetchVenueIconResource(engine: TelegramEngine, resource: VenueIconResource) -> Signal { return Signal { subscriber in - subscriber.putNext(.reset) - let url = "https://ss3.4sqi.net/img/categories_v2/\(resource.type)_88.png" - let fetchDisposable = MetaDisposable() - fetchDisposable.set(fetchHttpResource(url: url).start(next: { next in - subscriber.putNext(next) + return engine.resources.httpData(url: url).start(next: { data in + let file = EngineTempBox.shared.tempFile(fileName: "file.png") + let _ = try? data.write(to: URL(fileURLWithPath: file.path)) + subscriber.putNext(.moveTempFile(file: file)) }, completed: { subscriber.putCompletion() - })) - - return ActionDisposable { - fetchDisposable.dispose() - } + }) } } -private func venueIconData(postbox: Postbox, resource: MediaResource) -> Signal { - let resourceData = postbox.mediaBox.resourceData(resource) +private func venueIconData(engine: TelegramEngine, resource: VenueIconResource) -> Signal { + let resourceData = engine.resources.custom( + id: resource.id.stringRepresentation, + fetch: EngineMediaResource.Fetch { + return fetchVenueIconResource(engine: engine, resource: resource) + }, + cacheTimeout: .shortLived + ) let signal = resourceData |> take(1) |> mapToSignal { maybeData -> Signal in - if maybeData.complete { - let loadedData: Data? = try? Data(contentsOf: URL(fileURLWithPath: maybeData.path), options: []) - return .single((loadedData)) + if maybeData.isComplete { + return .single(try? Data(contentsOf: URL(fileURLWithPath: maybeData.path))) } else { - let fetched = postbox.mediaBox.fetchedResource(resource, parameters: nil) - let data = Signal { subscriber in - let fetchedDisposable = fetched.start() - let resourceDisposable = resourceData.start(next: { next in - subscriber.putNext(next.size == 0 ? nil : try? Data(contentsOf: URL(fileURLWithPath: next.path), options: [])) - }, error: subscriber.putError, completed: subscriber.putCompletion) - - return ActionDisposable { - fetchedDisposable.dispose() - resourceDisposable.dispose() - } - } - - return data + return .single(nil) } - } |> distinctUntilChanged(isEqual: { lhs, rhs in + } + |> distinctUntilChanged(isEqual: { lhs, rhs in if lhs == nil && rhs == nil { return true } else { @@ -141,7 +113,7 @@ public func venueIconColor(type: String) -> UIColor { return color } - let index = Int(abs(persistentHash32(type)) % Int32(randomColors.count)) + let index = Int(abs(Int32(bitPattern: UInt32(clamping: type.persistentHashValue))) % Int32(randomColors.count)) return randomColors[index] } @@ -162,9 +134,9 @@ public struct VenueIconArguments: TransformImageCustomArguments { } } -public func venueIcon(postbox: Postbox, type: String, background: Bool) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { +public func venueIcon(engine: TelegramEngine, type: String, background: Bool) -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> { let isBuiltinIcon = ["", "home", "work"].contains(type) - let data: Signal = isBuiltinIcon ? .single(nil) : venueIconData(postbox: postbox, resource: VenueIconResource(type: type)) + let data: Signal = isBuiltinIcon ? .single(nil) : venueIconData(engine: engine, resource: VenueIconResource(type: type)) return data |> map { data in return { arguments in let context = DrawingContext(size: arguments.drawingSize, clear: true) diff --git a/submodules/LocationUI/Sources/LocationActionListItem.swift b/submodules/LocationUI/Sources/LocationActionListItem.swift index f2e7329d16..8e96a0e036 100644 --- a/submodules/LocationUI/Sources/LocationActionListItem.swift +++ b/submodules/LocationUI/Sources/LocationActionListItem.swift @@ -81,7 +81,7 @@ private func generateLiveLocationIcon(theme: PresentationTheme, stop: Bool) -> U final class LocationActionListItem: ListViewItem { let presentationData: ItemListPresentationData - let account: Account + let engine: TelegramEngine let title: String let subtitle: String let icon: LocationActionListItemIcon @@ -89,9 +89,9 @@ final class LocationActionListItem: ListViewItem { let action: () -> Void let highlighted: (Bool) -> Void - public init(presentationData: ItemListPresentationData, account: Account, title: String, subtitle: String, icon: LocationActionListItemIcon, beginTimeAndTimeout: (Double, Double)?, action: @escaping () -> Void, highlighted: @escaping (Bool) -> Void = { _ in }) { + public init(presentationData: ItemListPresentationData, engine: TelegramEngine, title: String, subtitle: String, icon: LocationActionListItemIcon, beginTimeAndTimeout: (Double, Double)?, action: @escaping () -> Void, highlighted: @escaping (Bool) -> Void = { _ in }) { self.presentationData = presentationData - self.account = account + self.engine = engine self.title = title self.subtitle = subtitle self.icon = icon @@ -279,7 +279,7 @@ final class LocationActionListItemNode: ListViewItemNode { case let .venue(venue): strongSelf.iconNode.isHidden = true strongSelf.venueIconNode.isHidden = false - strongSelf.venueIconNode.setSignal(venueIcon(postbox: item.account.postbox, type: venue.venue?.type ?? "", background: true)) + strongSelf.venueIconNode.setSignal(venueIcon(engine: item.engine, type: venue.venue?.type ?? "", background: true)) } if updatedIcon == .stopLiveLocation { diff --git a/submodules/LocationUI/Sources/LocationAnnotation.swift b/submodules/LocationUI/Sources/LocationAnnotation.swift index 58f91e2155..e248a3984e 100644 --- a/submodules/LocationUI/Sources/LocationAnnotation.swift +++ b/submodules/LocationUI/Sources/LocationAnnotation.swift @@ -307,8 +307,8 @@ class LocationPinAnnotationView: MKAnnotationView { let venueType = location.venue?.type ?? "" let color = venueType.isEmpty ? annotation.theme.list.itemAccentColor : venueIconColor(type: venueType) self.backgroundNode.image = generateTintedImage(image: UIImage(bundleImageName: "Location/PinBackground"), color: color) - self.iconNode.setSignal(venueIcon(postbox: annotation.context.account.postbox, type: venueType, background: false)) - self.smallIconNode.setSignal(venueIcon(postbox: annotation.context.account.postbox, type: venueType, background: false)) + self.iconNode.setSignal(venueIcon(engine: annotation.context.engine, type: venueType, background: false)) + self.smallIconNode.setSignal(venueIcon(engine: annotation.context.engine, type: venueType, background: false)) self.smallNode.image = generateSmallBackgroundImage(color: color) self.dotNode.image = generateFilledCircleImage(diameter: 6.0, color: color) @@ -619,7 +619,7 @@ class LocationPinAnnotationView: MKAnnotationView { func setCustom(_ custom: Bool, animated: Bool) { if let annotation = self.annotation as? LocationPinAnnotation { - self.iconNode.setSignal(venueIcon(postbox: annotation.context.account.postbox, type: "", background: false)) + self.iconNode.setSignal(venueIcon(engine: annotation.context.engine, type: "", background: false)) } if let avatarNode = self.avatarNode { diff --git a/submodules/LocationUI/Sources/LocationInfoListItem.swift b/submodules/LocationUI/Sources/LocationInfoListItem.swift index d5105c0ba7..77cdaadbca 100644 --- a/submodules/LocationUI/Sources/LocationInfoListItem.swift +++ b/submodules/LocationUI/Sources/LocationInfoListItem.swift @@ -13,7 +13,7 @@ import SolidRoundedButtonNode final class LocationInfoListItem: ListViewItem { let presentationData: ItemListPresentationData - let account: Account + let engine: TelegramEngine let location: TelegramMediaMap let address: String? let distance: String? @@ -21,9 +21,9 @@ final class LocationInfoListItem: ListViewItem { let action: () -> Void let getDirections: () -> Void - public init(presentationData: ItemListPresentationData, account: Account, location: TelegramMediaMap, address: String?, distance: String?, eta: String?, action: @escaping () -> Void, getDirections: @escaping () -> Void) { + public init(presentationData: ItemListPresentationData, engine: TelegramEngine, location: TelegramMediaMap, address: String?, distance: String?, eta: String?, action: @escaping () -> Void, getDirections: @escaping () -> Void) { self.presentationData = presentationData - self.account = account + self.engine = engine self.location = location self.address = address self.distance = distance @@ -192,7 +192,7 @@ final class LocationInfoListItemNode: ListViewItemNode { let arguments = VenueIconArguments(defaultBackgroundColor: item.presentationData.theme.chat.inputPanel.actionControlFillColor, defaultForegroundColor: item.presentationData.theme.chat.inputPanel.actionControlForegroundColor) if let updatedLocation = updatedLocation { - strongSelf.venueIconNode.setSignal(venueIcon(postbox: item.account.postbox, type: updatedLocation.venue?.type ?? "", background: true)) + strongSelf.venueIconNode.setSignal(venueIcon(engine: item.engine, type: updatedLocation.venue?.type ?? "", background: true)) } let iconApply = iconLayout(TransformImageArguments(corners: ImageCorners(), imageSize: CGSize(width: iconSize, height: iconSize), boundingSize: CGSize(width: iconSize, height: iconSize), intrinsicInsets: UIEdgeInsets(), custom: arguments)) diff --git a/submodules/LocationUI/Sources/LocationPickerControllerNode.swift b/submodules/LocationUI/Sources/LocationPickerControllerNode.swift index 2787eb5fdf..65c11c9f0d 100644 --- a/submodules/LocationUI/Sources/LocationPickerControllerNode.swift +++ b/submodules/LocationUI/Sources/LocationPickerControllerNode.swift @@ -129,7 +129,7 @@ private enum LocationPickerEntry: Comparable, Identifiable { } } - func item(account: Account, presentationData: PresentationData, interaction: LocationPickerInteraction?) -> ListViewItem { + func item(engine: TelegramEngine, presentationData: PresentationData, interaction: LocationPickerInteraction?) -> ListViewItem { switch self { case let .location(_, title, subtitle, venue, coordinate): let icon: LocationActionListItemIcon @@ -138,7 +138,7 @@ private enum LocationPickerEntry: Comparable, Identifiable { } else { icon = .location } - return LocationActionListItem(presentationData: ItemListPresentationData(presentationData), account: account, title: title, subtitle: subtitle, icon: icon, beginTimeAndTimeout: nil, action: { + return LocationActionListItem(presentationData: ItemListPresentationData(presentationData), engine: engine, title: title, subtitle: subtitle, icon: icon, beginTimeAndTimeout: nil, action: { if let venue = venue { interaction?.sendVenue(venue) } else if let coordinate = coordinate { @@ -148,7 +148,7 @@ private enum LocationPickerEntry: Comparable, Identifiable { interaction?.updateSendActionHighlight(highlighted) }) case let .liveLocation(_, title, subtitle, coordinate): - return LocationActionListItem(presentationData: ItemListPresentationData(presentationData), account: account, title: title, subtitle: subtitle, icon: .liveLocation, beginTimeAndTimeout: nil, action: { + return LocationActionListItem(presentationData: ItemListPresentationData(presentationData), engine: engine, title: title, subtitle: subtitle, icon: .liveLocation, beginTimeAndTimeout: nil, action: { if let coordinate = coordinate { interaction?.sendLiveLocation(coordinate) } @@ -157,7 +157,7 @@ private enum LocationPickerEntry: Comparable, Identifiable { return LocationSectionHeaderItem(presentationData: ItemListPresentationData(presentationData), title: title) case let .venue(_, venue, _): let venueType = venue?.venue?.type ?? "" - return ItemListVenueItem(presentationData: ItemListPresentationData(presentationData), account: account, venue: venue, style: .plain, action: venue.flatMap { venue in + return ItemListVenueItem(presentationData: ItemListPresentationData(presentationData), engine: engine, venue: venue, style: .plain, action: venue.flatMap { venue in return { interaction?.sendVenue(venue) } }, infoAction: ["home", "work"].contains(venueType) ? { interaction?.openHomeWorkInfo() @@ -168,12 +168,12 @@ private enum LocationPickerEntry: Comparable, Identifiable { } } -private func preparedTransition(from fromEntries: [LocationPickerEntry], to toEntries: [LocationPickerEntry], isLoading: Bool, isEmpty: Bool, crossFade: Bool, account: Account, presentationData: PresentationData, interaction: LocationPickerInteraction?) -> LocationPickerTransaction { +private func preparedTransition(from fromEntries: [LocationPickerEntry], to toEntries: [LocationPickerEntry], isLoading: Bool, isEmpty: Bool, crossFade: Bool, engine: TelegramEngine, presentationData: PresentationData, interaction: LocationPickerInteraction?) -> LocationPickerTransaction { let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries) let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) } - let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, presentationData: presentationData, interaction: interaction), directionHint: nil) } - let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, presentationData: presentationData, interaction: interaction), directionHint: nil) } + let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(engine: engine, presentationData: presentationData, interaction: interaction), directionHint: nil) } + let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(engine: engine, presentationData: presentationData, interaction: interaction), directionHint: nil) } return LocationPickerTransaction(deletions: deletions, insertions: insertions, updates: updates, isLoading: isLoading, isEmpty: isEmpty, crossFade: crossFade) } @@ -527,7 +527,7 @@ final class LocationPickerControllerNode: ViewControllerTracingNode, CLLocationM crossFade = true } - let transition = preparedTransition(from: previousEntries ?? [], to: entries, isLoading: displayedVenues == nil, isEmpty: displayedVenues?.isEmpty ?? false, crossFade: crossFade, account: context.account, presentationData: presentationData, interaction: strongSelf.interaction) + let transition = preparedTransition(from: previousEntries ?? [], to: entries, isLoading: displayedVenues == nil, isEmpty: displayedVenues?.isEmpty ?? false, crossFade: crossFade, engine: context.engine, presentationData: presentationData, interaction: strongSelf.interaction) strongSelf.enqueueTransition(transition) var displayingPlacesButton = false diff --git a/submodules/LocationUI/Sources/LocationSearchContainerNode.swift b/submodules/LocationUI/Sources/LocationSearchContainerNode.swift index e4f009ec12..7f976edcad 100644 --- a/submodules/LocationUI/Sources/LocationSearchContainerNode.swift +++ b/submodules/LocationUI/Sources/LocationSearchContainerNode.swift @@ -50,7 +50,7 @@ private struct LocationSearchEntry: Identifiable, Comparable { return lhs.index < rhs.index } - func item(account: Account, presentationData: PresentationData, sendVenue: @escaping (TelegramMediaMap) -> Void) -> ListViewItem { + func item(engine: TelegramEngine, presentationData: PresentationData, sendVenue: @escaping (TelegramMediaMap) -> Void) -> ListViewItem { let venue = self.location let header: ChatListSearchItemHeader let subtitle: String? @@ -61,7 +61,7 @@ private struct LocationSearchEntry: Identifiable, Comparable { header = ChatListSearchItemHeader(type: .mapAddress, theme: presentationData.theme, strings: presentationData.strings) subtitle = presentationData.strings.Map_DistanceAway(stringForDistance(strings: presentationData.strings, distance: self.distance)).string } - return ItemListVenueItem(presentationData: ItemListPresentationData(presentationData), account: account, venue: self.location, title: self.title, subtitle: subtitle, style: .plain, action: { + return ItemListVenueItem(presentationData: ItemListPresentationData(presentationData), engine: engine, venue: self.location, title: self.title, subtitle: subtitle, style: .plain, action: { sendVenue(venue) }, header: header) } @@ -76,12 +76,12 @@ struct LocationSearchContainerTransition { let isEmpty: Bool } -private func locationSearchContainerPreparedTransition(from fromEntries: [LocationSearchEntry], to toEntries: [LocationSearchEntry], query: String, isSearching: Bool, isEmpty: Bool, account: Account, presentationData: PresentationData, sendVenue: @escaping (TelegramMediaMap) -> Void) -> LocationSearchContainerTransition { +private func locationSearchContainerPreparedTransition(from fromEntries: [LocationSearchEntry], to toEntries: [LocationSearchEntry], query: String, isSearching: Bool, isEmpty: Bool, engine: TelegramEngine, presentationData: PresentationData, sendVenue: @escaping (TelegramMediaMap) -> Void) -> LocationSearchContainerTransition { let (deleteIndices, indicesAndItems, updateIndices) = mergeListsStableWithUpdates(leftList: fromEntries, rightList: toEntries) let deletions = deleteIndices.map { ListViewDeleteItem(index: $0, directionHint: nil) } - let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, presentationData: presentationData, sendVenue: sendVenue), directionHint: nil) } - let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(account: account, presentationData: presentationData, sendVenue: sendVenue), directionHint: nil) } + let insertions = indicesAndItems.map { ListViewInsertItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(engine: engine, presentationData: presentationData, sendVenue: sendVenue), directionHint: nil) } + let updates = updateIndices.map { ListViewUpdateItem(index: $0.0, previousIndex: $0.2, item: $0.1.item(engine: engine, presentationData: presentationData, sendVenue: sendVenue), directionHint: nil) } return LocationSearchContainerTransition(deletions: deletions, insertions: insertions, updates: updates, query: query, isSearching: isSearching, isEmpty: isEmpty) } @@ -211,7 +211,7 @@ final class LocationSearchContainerNode: ASDisplayNode { if let strongSelf = self { let (items, query) = itemsAndQuery ?? (nil, "") let previousItems = previousSearchItems.swap(items ?? []) - let transition = locationSearchContainerPreparedTransition(from: previousItems, to: items ?? [], query: query, isSearching: items != nil, isEmpty: items?.isEmpty ?? false, account: context.account, presentationData: strongSelf.presentationData, sendVenue: { venue in self?.listNode.clearHighlightAnimated(true) + let transition = locationSearchContainerPreparedTransition(from: previousItems, to: items ?? [], query: query, isSearching: items != nil, isEmpty: items?.isEmpty ?? false, engine: context.engine, presentationData: strongSelf.presentationData, sendVenue: { venue in self?.listNode.clearHighlightAnimated(true) if let _ = venue.venue { self?.interaction.sendVenue(venue) } else { diff --git a/submodules/LocationUI/Sources/LocationViewControllerNode.swift b/submodules/LocationUI/Sources/LocationViewControllerNode.swift index 8240717de2..14088385a9 100644 --- a/submodules/LocationUI/Sources/LocationViewControllerNode.swift +++ b/submodules/LocationUI/Sources/LocationViewControllerNode.swift @@ -126,7 +126,7 @@ private enum LocationViewEntry: Comparable, Identifiable { distanceString = nil } let eta = time.flatMap { stringForEstimatedDuration(strings: presentationData.strings, eta: $0) } - return LocationInfoListItem(presentationData: ItemListPresentationData(presentationData), account: context.account, location: location, address: addressString, distance: distanceString, eta: eta, action: { + return LocationInfoListItem(presentationData: ItemListPresentationData(presentationData), engine: context.engine, location: location, address: addressString, distance: distanceString, eta: eta, action: { interaction?.goToCoordinate(location.coordinate) }, getDirections: { interaction?.requestDirections() @@ -138,7 +138,7 @@ private enum LocationViewEntry: Comparable, Identifiable { } else { beginTimeAndTimeout = nil } - return LocationActionListItem(presentationData: ItemListPresentationData(presentationData), account: context.account, title: title, subtitle: subtitle, icon: beginTimeAndTimeout != nil ? .stopLiveLocation : .liveLocation, beginTimeAndTimeout: beginTimeAndTimeout, action: { + return LocationActionListItem(presentationData: ItemListPresentationData(presentationData), engine: context.engine, title: title, subtitle: subtitle, icon: beginTimeAndTimeout != nil ? .stopLiveLocation : .liveLocation, beginTimeAndTimeout: beginTimeAndTimeout, action: { if beginTimeAndTimeout != nil { interaction?.stopLiveLocation() } else { diff --git a/submodules/PeerInfoUI/Sources/DeviceContactInfoController.swift b/submodules/PeerInfoUI/Sources/DeviceContactInfoController.swift index d61735ae9c..d9e70787ba 100644 --- a/submodules/PeerInfoUI/Sources/DeviceContactInfoController.swift +++ b/submodules/PeerInfoUI/Sources/DeviceContactInfoController.swift @@ -614,7 +614,7 @@ private func filteredContactData(contactData: DeviceContactExtendedData, exclude return DeviceContactExtendedData(basicData: DeviceContactBasicData(firstName: contactData.basicData.firstName, lastName: contactData.basicData.lastName, phoneNumbers: phoneNumbers), middleName: contactData.middleName, prefix: contactData.prefix, suffix: contactData.suffix, organization: includeJob ? contactData.organization : "", jobTitle: includeJob ? contactData.jobTitle : "", department: includeJob ? contactData.department : "", emailAddresses: emailAddresses, urls: urls, addresses: addresses, birthdayDate: includeBirthday ? contactData.birthdayDate : nil, socialProfiles: socialProfiles, instantMessagingProfiles: instantMessagingProfiles, note: includeNote ? contactData.note : "") } -private func deviceContactInfoEntries(account: Account, presentationData: PresentationData, peer: Peer?, isShare: Bool, shareViaException: Bool, contactData: DeviceContactExtendedData, isContact: Bool, state: DeviceContactInfoState, selecting: Bool, editingPhoneNumbers: Bool) -> [DeviceContactInfoEntry] { +private func deviceContactInfoEntries(account: Account, engine: TelegramEngine, presentationData: PresentationData, peer: Peer?, isShare: Bool, shareViaException: Bool, contactData: DeviceContactExtendedData, isContact: Bool, state: DeviceContactInfoState, selecting: Bool, editingPhoneNumbers: Bool) -> [DeviceContactInfoEntry] { var entries: [DeviceContactInfoEntry] = [] var editingName: ItemListAvatarAndNameInfoItemName? @@ -731,7 +731,7 @@ private func deviceContactInfoEntries(account: Account, presentationData: Presen |> mapToSignal { coordinates -> Signal<(TransformImageArguments) -> DrawingContext?, NoError> in if let (latitude, longitude) = coordinates { let resource = MapSnapshotMediaResource(latitude: latitude, longitude: longitude, width: 90, height: 90) - return chatMapSnapshotImage(account: account, resource: resource) + return chatMapSnapshotImage(engine: engine, resource: resource) } else { return .single({ _ in return nil }) } @@ -1220,7 +1220,7 @@ public func deviceContactInfoController(context: AccountContext, updatedPresenta focusItemTag = DeviceContactInfoEntryTag.editingPhone(insertedPhoneId) } - let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: deviceContactInfoEntries(account: context.account, presentationData: presentationData, peer: peerAndContactData.0, isShare: isShare, shareViaException: shareViaException, contactData: peerAndContactData.2, isContact: peerAndContactData.1 != nil, state: state, selecting: selecting, editingPhoneNumbers: editingPhones), style: isShare ? .blocks : .plain, focusItemTag: focusItemTag) + let listState = ItemListNodeState(presentationData: ItemListPresentationData(presentationData), entries: deviceContactInfoEntries(account: context.account, engine: context.engine, presentationData: presentationData, peer: peerAndContactData.0, isShare: isShare, shareViaException: shareViaException, contactData: peerAndContactData.2, isContact: peerAndContactData.1 != nil, state: state, selecting: selecting, editingPhoneNumbers: editingPhones), style: isShare ? .blocks : .plain, focusItemTag: focusItemTag) return (controllerState, (listState, arguments)) } diff --git a/submodules/Postbox/Sources/MediaBox.swift b/submodules/Postbox/Sources/MediaBox.swift index e82cd05ec7..5efcdbdb88 100644 --- a/submodules/Postbox/Sources/MediaBox.swift +++ b/submodules/Postbox/Sources/MediaBox.swift @@ -98,17 +98,8 @@ public enum CachedMediaRepresentationKeepDuration { } private struct CachedMediaResourceRepresentationKey: Hashable { - let resourceId: MediaResourceId - let representation: CachedMediaResourceRepresentation - - static func ==(lhs: CachedMediaResourceRepresentationKey, rhs: CachedMediaResourceRepresentationKey) -> Bool { - return lhs.resourceId == rhs.resourceId && lhs.representation.isEqual(to: rhs.representation) - } - - func hash(into hasher: inout Hasher) { - hasher.combine(self.resourceId.hashValue) - hasher.combine(self.representation.uniqueId) - } + let resourceId: String? + let representation: String } private final class CachedMediaResourceRepresentationSubscriber { @@ -157,7 +148,7 @@ public final class MediaBox { private var keepResourceContexts: [MediaResourceId: MediaBoxKeepResourceContext] = [:] private var wrappedFetchResource = Promise<(MediaResource, Signal<[(Range, MediaBoxFetchPriority)], NoError>, MediaResourceFetchParameters?) -> Signal>() - public var preFetchedResourcePath: (MediaResource) -> String? = { _ in return nil } + public var fetchResource: ((MediaResource, Signal<[(Range, MediaBoxFetchPriority)], NoError>, MediaResourceFetchParameters?) -> Signal)? { didSet { if let fetchResource = self.fetchResource { @@ -222,15 +213,15 @@ public final class MediaBox { return ResourceStorePaths(partial: "\(fileNameForId(id))_partial", complete: "\(fileNameForId(id))") } - private func cachedRepresentationPathsForId(_ id: MediaResourceId, representation: CachedMediaResourceRepresentation) -> ResourceStorePaths { + private func cachedRepresentationPathsForId(_ id: MediaResourceId, representationId: String, keepDuration: CachedMediaRepresentationKeepDuration) -> ResourceStorePaths { let cacheString: String - switch representation.keepDuration { + switch keepDuration { case .general: cacheString = "cache" case .shortLived: cacheString = "short-cache" } - return ResourceStorePaths(partial: "\(self.basePath)/\(cacheString)/\(fileNameForId(id))_partial:\(representation.uniqueId)", complete: "\(self.basePath)/\(cacheString)/\(fileNameForId(id)):\(representation.uniqueId)") + return ResourceStorePaths(partial: "\(self.basePath)/\(cacheString)/\(fileNameForId(id))_partial:\(representationId)", complete: "\(self.basePath)/\(cacheString)/\(fileNameForId(id)):\(representationId)") } public func cachedRepresentationPathForId(_ id: String, representationId: String, keepDuration: CachedMediaRepresentationKeepDuration) -> String { @@ -317,12 +308,6 @@ public final class MediaBox { } } - private func maybeCopiedPreFetchedResource(completePath: String, resource: MediaResource) { - if let path = self.preFetchedResourcePath(resource) { - let _ = try? FileManager.default.copyItem(atPath: path, toPath: completePath) - } - } - public func resourceStatus(_ resource: MediaResource, approximateSynchronousValue: Bool = false) -> Signal { let signal = Signal { subscriber in let disposable = MetaDisposable() @@ -337,16 +322,6 @@ public final class MediaBox { subscriber.putNext(.Local) subscriber.putCompletion() } else { - self.maybeCopiedPreFetchedResource(completePath: paths.complete, resource: resource) - if let _ = fileSize(paths.complete) { - self.timeBasedCleanup.touch(paths: [ - paths.complete - ]) - subscriber.putNext(.Local) - subscriber.putCompletion() - return - } - self.statusQueue.async { let resourceId = resource.id let statusContext: ResourceStatusContext @@ -371,7 +346,7 @@ public final class MediaBox { if let statusUpdateDisposable = statusUpdateDisposable { let statusQueue = self.statusQueue self.dataQueue.async { - if let (fileContext, releaseContext) = self.fileContext(for: resource) { + if let (fileContext, releaseContext) = self.fileContext(for: resource.id) { let statusDisposable = fileContext.status(next: { [weak statusContext] value in statusQueue.async { if let current = self.statusContexts[resourceId], current === statusContext, current.status != value { @@ -457,19 +432,17 @@ public final class MediaBox { return nil } } - + public func resourceData(_ resource: MediaResource, pathExtension: String? = nil, option: ResourceDataRequestOption = .complete(waitUntilFetchStatus: false), attemptSynchronously: Bool = false) -> Signal { + return self.resourceData(id: resource.id, pathExtension: pathExtension, option: option, attemptSynchronously: attemptSynchronously) + } + + public func resourceData(id: MediaResourceId, pathExtension: String? = nil, option: ResourceDataRequestOption = .complete(waitUntilFetchStatus: false), attemptSynchronously: Bool = false) -> Signal { return Signal { subscriber in let disposable = MetaDisposable() let begin: () -> Void = { - let paths = self.storePathsForId(resource.id) - - var completeSize = fileSize(paths.complete) - if completeSize == nil { - self.maybeCopiedPreFetchedResource(completePath: paths.complete, resource: resource) - completeSize = fileSize(paths.complete) - } + let paths = self.storePathsForId(id) if let completeSize = fileSize(paths.complete) { self.timeBasedCleanup.touch(paths: [ @@ -491,7 +464,7 @@ public final class MediaBox { subscriber.putNext(MediaResourceData(path: paths.partial, offset: 0, size: fileSize(paths.partial) ?? 0, complete: false)) } self.dataQueue.async { - if let (fileContext, releaseContext) = self.fileContext(for: resource) { + if let (fileContext, releaseContext) = self.fileContext(for: id) { let waitUntilAfterInitialFetch: Bool switch option { case let .complete(waitUntilFetchStatus): @@ -535,16 +508,16 @@ public final class MediaBox { } } - private func fileContext(for resource: MediaResource) -> (MediaBoxFileContext, () -> Void)? { + private func fileContext(for id: MediaResourceId) -> (MediaBoxFileContext, () -> Void)? { assert(self.dataQueue.isCurrent()) - let resourceId = resource.id + let resourceId = id var context: MediaBoxFileContext? if let current = self.fileContexts[resourceId] { context = current } else { - let paths = self.storePathsForId(resource.id) + let paths = self.storePathsForId(id) self.timeBasedCleanup.touch(paths: [ paths.complete, paths.partial, @@ -581,7 +554,7 @@ public final class MediaBox { let disposable = MetaDisposable() self.dataQueue.async { - guard let (fileContext, releaseContext) = self.fileContext(for: resource) else { + guard let (fileContext, releaseContext) = self.fileContext(for: resource.id) else { subscriber.putCompletion() return } @@ -613,13 +586,17 @@ public final class MediaBox { return disposable } } - + public func resourceData(_ resource: MediaResource, size: Int, in range: Range, mode: ResourceDataRangeMode = .complete, notifyAboutIncomplete: Bool = false, attemptSynchronously: Bool = false) -> Signal<(Data, Bool), NoError> { + return self.resourceData(id: resource.id, size: size, in: range, mode: mode, notifyAboutIncomplete: notifyAboutIncomplete, attemptSynchronously: attemptSynchronously) + } + + public func resourceData(id: MediaResourceId, size: Int, in range: Range, mode: ResourceDataRangeMode = .complete, notifyAboutIncomplete: Bool = false, attemptSynchronously: Bool = false) -> Signal<(Data, Bool), NoError> { return Signal { subscriber in let disposable = MetaDisposable() if attemptSynchronously { - let paths = self.storePathsForId(resource.id) + let paths = self.storePathsForId(id) if let completeSize = fileSize(paths.complete) { self.timeBasedCleanup.touch(paths: [ @@ -649,7 +626,7 @@ public final class MediaBox { } self.dataQueue.async { - guard let (fileContext, releaseContext) = self.fileContext(for: resource) else { + guard let (fileContext, releaseContext) = self.fileContext(for: id) else { subscriber.putCompletion() return } @@ -700,7 +677,7 @@ public final class MediaBox { let disposable = MetaDisposable() self.dataQueue.async { - guard let (fileContext, releaseContext) = self.fileContext(for: resource) else { + guard let (fileContext, releaseContext) = self.fileContext(for: resource.id) else { subscriber.putCompletion() return } @@ -734,7 +711,7 @@ public final class MediaBox { } subscriber.putCompletion() } else { - if let (fileContext, releaseContext) = self.fileContext(for: resource) { + if let (fileContext, releaseContext) = self.fileContext(for: resource.id) { let fetchResource = self.wrappedFetchResource.get() let fetchedDisposable = fileContext.fetchedFullRange(fetch: { ranges in return fetchResource @@ -796,7 +773,7 @@ public final class MediaBox { public func cancelInteractiveResourceFetch(_ resource: MediaResource) { self.dataQueue.async { - if let (fileContext, releaseContext) = self.fileContext(for: resource) { + if let (fileContext, releaseContext) = self.fileContext(for: resource.id) { fileContext.cancelFullRangeFetches() releaseContext() } @@ -805,7 +782,7 @@ public final class MediaBox { public func storeCachedResourceRepresentation(_ resource: MediaResource, representation: CachedMediaResourceRepresentation, data: Data) { self.dataQueue.async { - let path = self.cachedRepresentationPathsForId(resource.id, representation: representation).complete + let path = self.cachedRepresentationPathsForId(resource.id, representationId: representation.uniqueId, keepDuration: representation.keepDuration).complete let _ = try? data.write(to: URL(fileURLWithPath: path)) } } @@ -815,7 +792,7 @@ public final class MediaBox { let disposable = MetaDisposable() let begin: () -> Void = { - let paths = self.cachedRepresentationPathsForId(resource.id, representation: representation) + let paths = self.cachedRepresentationPathsForId(resource.id, representationId: representation.uniqueId, keepDuration: representation.keepDuration) if let size = fileSize(paths.complete) { self.timeBasedCleanup.touch(paths: [ paths.complete @@ -837,7 +814,7 @@ public final class MediaBox { subscriber.putNext(MediaResourceData(path: paths.partial, offset: 0, size: 0, complete: false)) } self.dataQueue.async { - let key = CachedMediaResourceRepresentationKey(resourceId: resource.id, representation: representation) + let key = CachedMediaResourceRepresentationKey(resourceId: resource.id.stringRepresentation, representation: representation.uniqueId) let context: CachedMediaResourceRepresentationContext if let currentContext = self.cachedRepresentationContexts[key] { context = currentContext @@ -976,6 +953,161 @@ public final class MediaBox { } } } + + public func customResourceData(id: String, baseResourceId: String?, pathExtension: String?, complete: Bool, fetch: (() -> Signal)?, keepDuration: CachedMediaRepresentationKeepDuration, attemptSynchronously: Bool) -> Signal { + return Signal { subscriber in + let disposable = MetaDisposable() + + let begin: () -> Void = { + let paths: ResourceStorePaths + if let baseResourceId = baseResourceId { + paths = self.cachedRepresentationPathsForId(MediaResourceId(baseResourceId), representationId: id, keepDuration: keepDuration) + } else { + paths = self.storePathsForId(MediaResourceId(id)) + } + if let size = fileSize(paths.complete) { + self.timeBasedCleanup.touch(paths: [ + paths.complete + ]) + + if let pathExtension = pathExtension { + let symlinkPath = paths.complete + ".\(pathExtension)" + if fileSize(symlinkPath) == nil { + let _ = try? FileManager.default.linkItem(atPath: paths.complete, toPath: symlinkPath) + } + subscriber.putNext(MediaResourceData(path: symlinkPath, offset: 0, size: size, complete: true)) + subscriber.putCompletion() + } else { + subscriber.putNext(MediaResourceData(path: paths.complete, offset: 0, size: size, complete: true)) + subscriber.putCompletion() + } + } else if let fetch = fetch { + if attemptSynchronously && complete { + subscriber.putNext(MediaResourceData(path: paths.partial, offset: 0, size: 0, complete: false)) + } + self.dataQueue.async { + let key = CachedMediaResourceRepresentationKey(resourceId: baseResourceId, representation: id) + let context: CachedMediaResourceRepresentationContext + if let currentContext = self.cachedRepresentationContexts[key] { + context = currentContext + } else { + context = CachedMediaResourceRepresentationContext() + self.cachedRepresentationContexts[key] = context + } + + let index = context.dataSubscribers.add(CachedMediaResourceRepresentationSubscriber(update: { data in + if !complete || data.complete { + if let pathExtension = pathExtension, data.complete { + let symlinkPath = data.path + ".\(pathExtension)" + if fileSize(symlinkPath) == nil { + let _ = try? FileManager.default.linkItem(atPath: data.path, toPath: symlinkPath) + } + subscriber.putNext(MediaResourceData(path: symlinkPath, offset: data.offset, size: data.size, complete: data.complete)) + } else { + subscriber.putNext(data) + } + } + if data.complete { + subscriber.putCompletion() + } + }, onlyComplete: complete)) + if let currentData = context.currentData { + if !complete || currentData.complete { + subscriber.putNext(currentData) + } + if currentData.complete { + subscriber.putCompletion() + } + } else if !complete { + subscriber.putNext(MediaResourceData(path: paths.partial, offset: 0, size: 0, complete: false)) + } + + disposable.set(ActionDisposable { [weak context] in + self.dataQueue.async { + if let currentContext = self.cachedRepresentationContexts[key], currentContext === context { + currentContext.dataSubscribers.remove(index) + if currentContext.dataSubscribers.isEmpty { + currentContext.disposable.dispose() + self.cachedRepresentationContexts.removeValue(forKey: key) + } + } + } + }) + + if !context.initialized { + context.initialized = true + let signal = fetch() + |> deliverOn(self.dataQueue) + context.disposable.set(signal.start(next: { [weak self, weak context] next in + guard let strongSelf = self else { + return + } + var isDone = false + switch next { + case let .temporaryPath(temporaryPath): + rename(temporaryPath, paths.complete) + isDone = true + case let .tempFile(tempFile): + rename(tempFile.path, paths.complete) + TempBox.shared.dispose(tempFile) + isDone = true + case .reset: + let file = ManagedFile(queue: strongSelf.dataQueue, path: paths.partial, mode: .readwrite) + file?.truncate(count: 0) + unlink(paths.complete) + case let .data(dataPart): + let file = ManagedFile(queue: strongSelf.dataQueue, path: paths.partial, mode: .append) + let dataCount = dataPart.count + dataPart.withUnsafeBytes { rawBytes -> Void in + let bytes = rawBytes.baseAddress!.assumingMemoryBound(to: UInt8.self) + let _ = file?.write(bytes, count: dataCount) + } + case .done: + link(paths.partial, paths.complete) + isDone = true + } + + if let strongSelf = self, let currentContext = strongSelf.cachedRepresentationContexts[key], currentContext === context { + if isDone { + currentContext.disposable.dispose() + strongSelf.cachedRepresentationContexts.removeValue(forKey: key) + } + if let size = fileSize(paths.complete) { + let data = MediaResourceData(path: paths.complete, offset: 0, size: size, complete: isDone) + currentContext.currentData = data + for subscriber in currentContext.dataSubscribers.copyItems() { + if !subscriber.onlyComplete || isDone { + subscriber.update(data) + } + } + } else if let size = fileSize(paths.partial) { + let data = MediaResourceData(path: paths.partial, offset: 0, size: size, complete: isDone) + currentContext.currentData = data + for subscriber in currentContext.dataSubscribers.copyItems() { + if !subscriber.onlyComplete || isDone { + subscriber.update(data) + } + } + } + } + })) + } + } + } else { + subscriber.putNext(MediaResourceData(path: paths.partial, offset: 0, size: 0, complete: false)) + subscriber.putCompletion() + } + } + if attemptSynchronously { + begin() + } else { + self.concurrentQueue.async(begin) + } + return ActionDisposable { + disposable.dispose() + } + } + } public func collectResourceCacheUsage(_ ids: [MediaResourceId]) -> Signal<[MediaResourceId: Int64], NoError> { return Signal { subscriber in @@ -1160,7 +1292,7 @@ public final class MediaBox { return EmptyDisposable } } - + public func allFileContexts() -> Signal<[(partial: String, complete: String)], NoError> { return Signal { subscriber in self.dataQueue.async { diff --git a/submodules/TelegramCore/Sources/Account/Account.swift b/submodules/TelegramCore/Sources/Account/Account.swift index e35af6f9e3..5ccafd6349 100644 --- a/submodules/TelegramCore/Sources/Account/Account.swift +++ b/submodules/TelegramCore/Sources/Account/Account.swift @@ -1222,8 +1222,7 @@ public func updateAccountNetworkUsageStats(account: Account, category: MediaReso public typealias FetchCachedResourceRepresentation = (_ account: Account, _ resource: MediaResource, _ representation: CachedMediaResourceRepresentation) -> Signal public typealias TransformOutgoingMessageMedia = (_ postbox: Postbox, _ network: Network, _ media: AnyMediaReference, _ userInteractive: Bool) -> Signal -public func setupAccount(_ account: Account, fetchCachedResourceRepresentation: FetchCachedResourceRepresentation? = nil, transformOutgoingMessageMedia: TransformOutgoingMessageMedia? = nil, preFetchedResourcePath: @escaping (MediaResource) -> String? = { _ in return nil }) { - account.postbox.mediaBox.preFetchedResourcePath = preFetchedResourcePath +public func setupAccount(_ account: Account, fetchCachedResourceRepresentation: FetchCachedResourceRepresentation? = nil, transformOutgoingMessageMedia: TransformOutgoingMessageMedia? = nil) { account.postbox.mediaBox.fetchResource = { [weak account] resource, intervals, parameters -> Signal in if let strongAccount = account { if let result = strongAccount.auxiliaryMethods.fetchResource(strongAccount, resource, intervals, parameters) { diff --git a/submodules/TelegramCore/Sources/TelegramEngine/Resources/TelegramEngineResources.swift b/submodules/TelegramCore/Sources/TelegramEngine/Resources/TelegramEngineResources.swift index 5dcfd5b96e..6f4e4cd817 100644 --- a/submodules/TelegramCore/Sources/TelegramEngine/Resources/TelegramEngineResources.swift +++ b/submodules/TelegramCore/Sources/TelegramEngine/Resources/TelegramEngineResources.swift @@ -2,7 +2,15 @@ import Foundation import SwiftSignalKit import Postbox +public typealias EngineTempBox = TempBox +public typealias EngineTempBoxFile = TempBoxFile + public final class EngineMediaResource: Equatable { + public enum CacheTimeout { + case `default` + case shortLived + } + public struct ByteRange { public enum Priority { case `default` @@ -21,27 +29,16 @@ public final class EngineMediaResource: Equatable { public final class Fetch { public enum Result { - case dataPart(resourceOffset: Int, data: Data, range: Range, complete: Bool) - case resourceSizeUpdated(Int) - case progressUpdated(Float) - case replaceHeader(data: Data, range: Range) - case moveLocalFile(path: String) case moveTempFile(file: TempBoxFile) - case copyLocalItem(MediaResourceDataFetchCopyLocalItem) - case reset } public enum Error { case generic } - public let signal: ( - Signal<[EngineMediaResource.ByteRange], NoError> - ) -> Signal + public let signal: () -> Signal - public init(_ signal: @escaping ( - Signal<[EngineMediaResource.ByteRange], NoError> - ) -> Signal) { + public init(_ signal: @escaping () -> Signal) { self.signal = signal } } @@ -93,55 +90,9 @@ public final class EngineMediaResource: Equatable { } } -public extension MediaResource { - func fetch(engine: TelegramEngine, parameters: MediaResourceFetchParameters?) -> EngineMediaResource.Fetch { - return EngineMediaResource.Fetch { ranges in - return Signal { subscriber in - return engine.account.postbox.mediaBox.fetchResource!( - self, - ranges |> map { ranges -> [(Range, MediaBoxFetchPriority)] in - return ranges.map { range -> (Range, MediaBoxFetchPriority) in - let mappedPriority: MediaBoxFetchPriority - switch range.priority { - case .default: - mappedPriority = .default - case .elevated: - mappedPriority = .elevated - case .maximum: - mappedPriority = .maximum - } - return (range.range, mappedPriority) - } - }, - parameters - ).start(next: { result in - let mappedResult: EngineMediaResource.Fetch.Result - switch result { - case let .dataPart(resourceOffset, data, range, complete): - mappedResult = .dataPart(resourceOffset: resourceOffset, data: data, range: range, complete: complete) - case let .resourceSizeUpdated(size): - mappedResult = .resourceSizeUpdated(size) - case let .progressUpdated(progress): - mappedResult = .progressUpdated(progress) - case let .replaceHeader(data, range): - mappedResult = .replaceHeader(data: data, range: range) - case let .moveLocalFile(path): - mappedResult = .moveLocalFile(path: path) - case let .moveTempFile(file): - mappedResult = .moveTempFile(file: file) - case let .copyLocalItem(item): - mappedResult = .copyLocalItem(item) - case .reset: - mappedResult = .reset - } - subscriber.putNext(mappedResult) - }, error: { _ in - subscriber.putError(.generic) - }, completed: { - subscriber.putCompletion() - }) - } - } +public extension EngineMediaResource.ResourceData { + convenience init(_ data: MediaResourceData) { + self.init(path: data.path, availableSize: data.size, isComplete: data.complete) } } @@ -165,12 +116,66 @@ public extension TelegramEngine { return _internal_clearCachedMediaResources(account: self.account, mediaResourceIds: mediaResourceIds) } - public func data(id: String) -> Signal { - preconditionFailure() + public func data(id: EngineMediaResource.Id, attemptSynchronously: Bool = false) -> Signal { + return self.account.postbox.mediaBox.resourceData( + id: MediaResourceId(id.stringRepresentation), + pathExtension: nil, + option: .complete(waitUntilFetchStatus: false), + attemptSynchronously: attemptSynchronously + ) + |> map { data in + return EngineMediaResource.ResourceData(data) + } } - public func fetch(id: String, fetch: EngineMediaResource.Fetch) -> Signal { - preconditionFailure() + public func custom(id: String, fetch: EngineMediaResource.Fetch, cacheTimeout: EngineMediaResource.CacheTimeout = .default, attemptSynchronously: Bool = false) -> Signal { + let mappedKeepDuration: CachedMediaRepresentationKeepDuration + switch cacheTimeout { + case .default: + mappedKeepDuration = .general + case .shortLived: + mappedKeepDuration = .shortLived + } + return self.account.postbox.mediaBox.customResourceData( + id: id, + baseResourceId: nil, + pathExtension: nil, + complete: true, + fetch: { + return Signal { subscriber in + return fetch.signal().start(next: { result in + let mappedResult: CachedMediaResourceRepresentationResult + switch result { + case let .moveTempFile(file): + mappedResult = .tempFile(file) + } + subscriber.putNext(mappedResult) + }, completed: { + subscriber.putCompletion() + }) + } + }, + keepDuration: mappedKeepDuration, + attemptSynchronously: attemptSynchronously + ) + |> map { data in + return EngineMediaResource.ResourceData(data) + } + } + + public func httpData(url: String) -> Signal { + return fetchHttpResource(url: url) + |> mapError { _ -> EngineMediaResource.Fetch.Error in + return .generic + } + |> mapToSignal { value -> Signal in + switch value { + case let .dataPart(_, data, _, _): + return .single(data) + default: + return .complete() + } + } } public func cancelAllFetches(id: String) { diff --git a/submodules/TelegramUI/Sources/ChatMessageMapBubbleContentNode.swift b/submodules/TelegramUI/Sources/ChatMessageMapBubbleContentNode.swift index ef18419e76..93e1a0dd33 100644 --- a/submodules/TelegramUI/Sources/ChatMessageMapBubbleContentNode.swift +++ b/submodules/TelegramUI/Sources/ChatMessageMapBubbleContentNode.swift @@ -135,7 +135,7 @@ class ChatMessageMapBubbleContentNode: ChatMessageBubbleContentNode { } } if updated { - updateImageSignal = chatMapSnapshotImage(account: item.context.account, resource: MapSnapshotMediaResource(latitude: selectedMedia.latitude, longitude: selectedMedia.longitude, width: Int32(imageSize.width), height: Int32(imageSize.height))) + updateImageSignal = chatMapSnapshotImage(engine: item.context.engine, resource: MapSnapshotMediaResource(latitude: selectedMedia.latitude, longitude: selectedMedia.longitude, width: Int32(imageSize.width), height: Int32(imageSize.height))) } } diff --git a/submodules/TelegramUI/Sources/CreateGroupController.swift b/submodules/TelegramUI/Sources/CreateGroupController.swift index 312b5719cc..c783592849 100644 --- a/submodules/TelegramUI/Sources/CreateGroupController.swift +++ b/submodules/TelegramUI/Sources/CreateGroupController.swift @@ -247,7 +247,7 @@ private enum CreateGroupEntry: ItemListNodeEntry { case let .locationHeader(_, title): return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section) case let .location(theme, location): - let imageSignal = chatMapSnapshotImage(account: arguments.context.account, resource: MapSnapshotMediaResource(latitude: location.latitude, longitude: location.longitude, width: 90, height: 90)) + let imageSignal = chatMapSnapshotImage(engine: arguments.context.engine, resource: MapSnapshotMediaResource(latitude: location.latitude, longitude: location.longitude, width: 90, height: 90)) return ItemListAddressItem(theme: theme, label: "", text: location.address.replacingOccurrences(of: ", ", with: "\n"), imageSignal: imageSignal, selected: nil, sectionId: self.section, style: .blocks, action: nil) case let .changeLocation(_, text): return ItemListActionItem(presentationData: presentationData, title: text, kind: .generic, alignment: .natural, sectionId: ItemListSectionId(self.section), style: .blocks, action: { @@ -258,7 +258,7 @@ private enum CreateGroupEntry: ItemListNodeEntry { case let .venueHeader(_, title): return ItemListSectionHeaderItem(presentationData: presentationData, text: title, sectionId: self.section) case let .venue(_, _, venue): - return ItemListVenueItem(presentationData: presentationData, account: arguments.context.account, venue: venue, sectionId: self.section, style: .blocks, action: { + return ItemListVenueItem(presentationData: presentationData, engine: arguments.context.engine, venue: venue, sectionId: self.section, style: .blocks, action: { arguments.updateWithVenue(venue) }) } diff --git a/submodules/TelegramUI/Sources/FetchCachedRepresentations.swift b/submodules/TelegramUI/Sources/FetchCachedRepresentations.swift index 5284b1a36f..66f142a002 100644 --- a/submodules/TelegramUI/Sources/FetchCachedRepresentations.swift +++ b/submodules/TelegramUI/Sources/FetchCachedRepresentations.swift @@ -119,8 +119,6 @@ public func fetchCachedResourceRepresentation(account: Account, resource: MediaR } return fetchAnimatedStickerFirstFrameRepresentation(account: account, resource: resource, resourceData: data, representation: representation) } - } else if let resource = resource as? MapSnapshotMediaResource, let _ = representation as? MapSnapshotMediaResourceRepresentation { - return fetchMapSnapshotResource(resource: resource) } else if let resource = resource as? YoutubeEmbedStoryboardMediaResource, let _ = representation as? YoutubeEmbedStoryboardMediaResourceRepresentation { return fetchYoutubeEmbedStoryboardResource(resource: resource) } else if let representation = representation as? CachedPreparedPatternWallpaperRepresentation { diff --git a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift index f33d67ef08..f53dd49829 100644 --- a/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift +++ b/submodules/TelegramUI/Sources/PeerInfo/PeerInfoScreen.swift @@ -1041,7 +1041,7 @@ private func infoItems(data: PeerInfoScreenData?, context: AccountContext, prese if let location = (data.cachedData as? CachedChannelData)?.peerGeoLocation { items[.groupLocation]!.append(PeerInfoScreenHeaderItem(id: ItemLocationHeader, text: presentationData.strings.GroupInfo_Location.uppercased())) - let imageSignal = chatMapSnapshotImage(account: context.account, resource: MapSnapshotMediaResource(latitude: location.latitude, longitude: location.longitude, width: 90, height: 90)) + let imageSignal = chatMapSnapshotImage(engine: context.engine, resource: MapSnapshotMediaResource(latitude: location.latitude, longitude: location.longitude, width: 90, height: 90)) items[.groupLocation]!.append(PeerInfoScreenAddressItem( id: ItemLocation, label: "", @@ -1271,7 +1271,7 @@ private func editingItems(data: PeerInfoScreenData?, context: AccountContext, pr if isCreator, let location = cachedData.peerGeoLocation { items[.groupLocation]!.append(PeerInfoScreenHeaderItem(id: ItemLocationHeader, text: presentationData.strings.GroupInfo_Location.uppercased())) - let imageSignal = chatMapSnapshotImage(account: context.account, resource: MapSnapshotMediaResource(latitude: location.latitude, longitude: location.longitude, width: 90, height: 90)) + let imageSignal = chatMapSnapshotImage(engine: context.engine, resource: MapSnapshotMediaResource(latitude: location.latitude, longitude: location.longitude, width: 90, height: 90)) items[.groupLocation]!.append(PeerInfoScreenAddressItem( id: ItemLocation, label: "", diff --git a/submodules/TelegramUI/Sources/SharedAccountContext.swift b/submodules/TelegramUI/Sources/SharedAccountContext.swift index 25fe6ff699..aa6eee4753 100644 --- a/submodules/TelegramUI/Sources/SharedAccountContext.swift +++ b/submodules/TelegramUI/Sources/SharedAccountContext.swift @@ -408,9 +408,7 @@ public final class SharedAccountContextImpl: SharedAccountContext { |> mapToSignal { result -> Signal in switch result { case let .authorized(account): - setupAccount(account, fetchCachedResourceRepresentation: fetchCachedResourceRepresentation, transformOutgoingMessageMedia: transformOutgoingMessageMedia, preFetchedResourcePath: { resource in - return nil - }) + setupAccount(account, fetchCachedResourceRepresentation: fetchCachedResourceRepresentation, transformOutgoingMessageMedia: transformOutgoingMessageMedia) return account.postbox.transaction { transaction -> AddedAccountResult in let limitsConfiguration = transaction.getPreferencesEntry(key: PreferencesKeys.limitsConfiguration)?.get(LimitsConfiguration.self) ?? LimitsConfiguration.defaultValue let contentSettings = getContentSettings(transaction: transaction) diff --git a/submodules/TelegramUI/Sources/TelegramAccountAuxiliaryMethods.swift b/submodules/TelegramUI/Sources/TelegramAccountAuxiliaryMethods.swift index b3482cbf1e..f1d4979394 100644 --- a/submodules/TelegramUI/Sources/TelegramAccountAuxiliaryMethods.swift +++ b/submodules/TelegramUI/Sources/TelegramAccountAuxiliaryMethods.swift @@ -31,8 +31,6 @@ public let telegramAccountAuxiliaryMethods = AccountAuxiliaryMethods(fetchResour return fetchOpenInAppIconResource(resource: resource) } else if let resource = resource as? EmojiSpriteResource { return fetchEmojiSpriteResource(account: account, resource: resource) - } else if let resource = resource as? VenueIconResource { - return fetchVenueIconResource(account: account, resource: resource) } else if let resource = resource as? BundleResource { return Signal { subscriber in subscriber.putNext(.reset)