uchatMediaImageFromApiPhoto
1
2
3
4
5
6
7
8
9
func uchatMediaImageFromApiPhoto(_ photo: Api.Photo) -> UChatMediaImage? {
switch photo {
case let .photo(_, id, accessHash, fileReference, _, sizes, dcId):
let (immediateThumbnailData, representations) = uchatMediaImageRepresentationsFromApiSizes(datacenterId: dcId, photoId: id, accessHash: accessHash, fileReference: fileReference.makeData(), sizes: sizes)
return UChatMediaImage(imageId: MediaId(namespace: Namespaces.Media.CloudImage, id: id), representations: representations, immediateThumbnailData: immediateThumbnailData, reference: .cloud(imageId: id, accessHash: accessHash, fileReference: fileReference.makeData()), partialReference: nil)
case .photoEmpty:
return nil
}
}
uchatMediaImageRepresentationsFromApiSizes
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
func uchatMediaImageRepresentationsFromApiSizes(datacenterId: Int32, photoId: Int64, accessHash: Int64, fileReference: Data?, sizes: [Api.PhotoSize]) -> (immediateThumbnail: Data?, representations: [UChatMediaImageRepresentation]) {
var immediateThumbnailData: Data?
var representations: [UChatMediaImageRepresentation] = []
for size in sizes {
switch size {
case let .photoCachedSize(type, location, w, h, _):
switch location {
case let .fileLocationToBeDeprecated(volumeId, localId):
let resource = CloudPhotoSizeMediaResource(datacenterId: datacenterId, photoId: photoId, accessHash: accessHash, sizeSpec: type, volumeId: volumeId, localId: localId, fileReference: fileReference)
representations.append(UChatMediaImageRepresentation(dimensions: CGSize(width: CGFloat(w), height: CGFloat(h)), resource: resource))
}
case let .photoSize(type, location, w, h, _):
switch location {
case let .fileLocationToBeDeprecated(volumeId, localId):
let resource = CloudPhotoSizeMediaResource(datacenterId: datacenterId, photoId: photoId, accessHash: accessHash, sizeSpec: type, volumeId: volumeId, localId: localId, fileReference: fileReference)
representations.append(UChatMediaImageRepresentation(dimensions: CGSize(width: CGFloat(w), height: CGFloat(h)), resource: resource))
}
case let .photoStrippedSize(_, data):
immediateThumbnailData = data.makeData()
case .photoSizeEmpty:
break
}
}
return (immediateThumbnailData, representations)
}
Api.PhotoSize
1
2
3
4
5
6
public enum PhotoSize: TypeConstructorDescription {
case photoSizeEmpty(type: String)
case photoSize(type: String, location: Api.FileLocation, w: Int32, h: Int32, size: Int32)
case photoCachedSize(type: String, location: Api.FileLocation, w: Int32, h: Int32, bytes: Buffer)
case photoStrippedSize(type: String, bytes: Buffer)
}
parseMediaData
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public func parseMediaData(data: Data) -> Media? {
let buffer = BufferReader(Buffer(data: data))
var parseBuffer: Buffer?
guard let signature = buffer.readInt32() else {
return nil
}
if signature == 0x3072cfa1 {
parseBuffer = parseBytes(buffer).flatMap({ $0.makeData() }).flatMap(MTGzip.decompress).flatMap(Buffer.init(data:))
} else {
parseBuffer = Buffer(data: data)
}
if let parseBuffer = parseBuffer, let object = Api.parse(parseBuffer) {
if let photo = object as? Api.Photo {
return uchatMediaImageFromApiPhoto(photo)
} else if let document = object as? Api.Document {
return uchatMediaFileFromApiDocument(document)
}
}
return nil
}
collectPreCachedResources
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private func collectPreCachedResources(for photo: Api.Photo) -> [(MediaResource, Data)]? {
switch photo {
case let .photo(_, id, accessHash, fileReference, _, sizes, dcId):
for size in sizes {
switch size {
case let .photoCachedSize(type, location, _, _, bytes):
switch location {
case let .fileLocationToBeDeprecated(volumeId, localId):
let resource = CloudPhotoSizeMediaResource(datacenterId: dcId, photoId: id, accessHash: accessHash, sizeSpec: type, volumeId: volumeId, localId: localId, fileReference: fileReference.makeData())
let data = bytes.makeData()
return [(resource, data)]
}
default:
break
}
}
return nil
case .photoEmpty:
return nil
}
}
TelegramMediaImage
1
2
3
4
5
6
7
8
public let imageId: MediaId
public let representations: [TelegramMediaImageRepresentation]
public let videoRepresentations: [TelegramMediaImage.VideoRepresentation]
public let immediateThumbnailData: Data?
public let reference: TelegramMediaImageReference?
public let partialReference: PartialMediaReference?
public let peerIds: [PeerId] = []
public let flags: TelegramMediaImageFlags
UChatPeerPhoto
1
2
3
4
5
6
7
8
public struct UChatPeerPhoto {
public let image: UChatMediaImage
public let date: Int32
public let reference: UChatMediaImageReference?
public let index: Int
public let totalCount: Int
public let messageId: MessageId?
}
UChatMediaImageReference
1
case cloud(imageId: Int64, accessHash: Int64, fileReference: Data?)
requestPeerPhotos
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
public func requestPeerPhotos(account:Account, peerId: PeerId) -> Signal<[UChatPeerPhoto], NoError> {
return account.postbox.transaction{ transaction -> Peer? in
return transaction.getPeer(peerId)
}
|> mapToSignal { peer -> Signal<[UChatPeerPhoto], NoError> in
if let peer = peer as? UChatUser, let inputUser = apiInputUser(peer) {
return account.network.request(Api.functions.photos.getUserPhotos(userId: inputUser, offset: 0, maxId: 0, limit: 100))
|> map {Optional($0)}
|> mapError {_ in}
|> `catch` { _ -> Signal<Api.photos.Photos?, NoError> in
return .single(nil)
}
|> map { result -> [UChatPeerPhoto] in
if let result = result {
let totalCount:Int
let photos: [Api.Photo]
switch result {
case let .photos(data):
photos = data.photos
totalCount = photos.count
case let .photosSlice(data):
photos = data.photos
totalCount = Int(data.count)
}
var images: [UChatPeerPhoto] = []
for i in 0 ..< photos.count {
if let image = uchatMediaImageFromApiPhoto(photos[i]), let reference = image.reference {
var date: Int32 = 0
switch photos[i] {
case let .photo(_, _, _, _, apiDate, _, _):
date = apiDate
case .photoEmpty:
break
}
images.append(UChatPeerPhoto(image: image, date: date, reference: reference, index: i, totalCount: totalCount, messageId: nil))
}
}
return images
} else {
return []
}
}
} else if let peer = peer, let inputPeer = apiInputPeer(peer) {
return account.network.request(Api.functions.messages.search(flags: 0, peer: inputPeer, q: "", fromId: nil, filter: .inputMessagesFilterChatPhotos, minDate: 0, maxDate: 0, offsetId: 0, addOffset: 0, limit: 1000, maxId: 0, minId: 0, hash: 0))
|> map(Optional.init)
|> `catch` { _ -> Signal<Api.messages.Messages?, NoError> in
return .single(nil)
}
|> mapToSignal { result -> Signal<[UChatPeerPhoto], NoError> in
if let result = result {
let messages: [Api.Message]
let chats: [Api.Chat]
let users: [Api.User]
switch result {
case let .channelMessages(_, _, _, apiMessages, apiChats, apiUsers):
messages = apiMessages
chats = apiChats
users = apiUsers
case let .messages(apiMessages, apiChats, apiUsers):
messages = apiMessages
chats = apiChats
users = apiUsers
case let .messagesSlice(_, _, _, apiMessages, apiChats, apiUsers):
messages = apiMessages
chats = apiChats
users = apiUsers
case .messagesNotModified:
messages = []
chats = []
users = []
}
return account.postbox.transaction { transaction -> [Message] in
var peers: [PeerId: Peer] = [:]
for user in users {
if let user = UChatUser.merge(transaction.getPeer(user.peerId) as? UChatUser, rhs: user) {
peers[user.id] = user
}
}
for chat in chats {
if let groupOrChannel = parseUChatGroupOrChannel(chat: chat) {
peers[groupOrChannel.id] = groupOrChannel
}
}
var renderedMessages: [Message] = []
for message in messages {
if let message = StoreMessage(apiMessage: message), let renderedMessage = locallyRenderedMessage(message: message, peers: peers) {
renderedMessages.append(renderedMessage)
}
}
return renderedMessages
} |> map { messages -> [UChatPeerPhoto] in
var photos: [UChatPeerPhoto] = []
var index:Int = 0
for message in messages {
if let media = message.media.first as? UChatMediaAction {
switch media.action {
case let .photoUpdated(image):
if let image = image {
photos.append(UChatPeerPhoto(image: image, date: message.timestamp, reference: nil, index: index, totalCount: messages.count, messageId: message.id))
}
default:
break
}
}
index += 1
}
return photos
}
} else {
return .single([])
}
}
} else {
return .single([])
}
}
}
struct
Api.photos
1
2
3
4
5
6
7
8
public enum Photo: TypeConstructorDescription {
case photo(photo: Api.Photo, users: [Api.User])
}
public enum Photos: TypeConstructorDescription {
case photos(photos: [Api.Photo], users: [Api.User])
case photosSlice(count: Int32, photos: [Api.Photo], users: [Api.User])
}
peerAvatarImage
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
public func peerAvatarImage(account: Account, peer: Peer, authorOfMessage: MessageReference?, representation: UChatMediaImageRepresentation?, displayDimensions: CGSize = CGSize(width: 60.0, height: 60.0), inset: CGFloat = 0.0, clipStyle: AvatarNodeClipStyle = .circle, emptyColor: UIColor? = nil, synchronousLoad: Bool = false, grayishImage: Bool = false) -> Signal<UIImage?, NoError>? {
if let imageData = peerAvatarImageData(account: account, peer: peer, authorOfMessage: authorOfMessage, representation: representation, synchronousLoad: synchronousLoad) {
return imageData
|> mapToSignal { data -> Signal<UIImage?, NoError> in
let generate = deferred { () -> Signal<UIImage?, NoError> in
if emptyColor == nil && data == nil {
return .single(nil)
}
let image = generateImage(displayDimensions, contextGenerator: { size, context -> Void in
if let data = data {
if let imageSource = CGImageSourceCreateWithData(data as CFData, nil), let dataImage = CGImageSourceCreateImageAtIndex(imageSource, 0, nil) {
if grayishImage, let grayImage = generateGrayishImage(image: UIImage(cgImage: dataImage)) {
context.clear(CGRect(origin: CGPoint(), size: displayDimensions))
context.setBlendMode(.luminosity)
context.draw(grayImage.cgImage!, in: CGRect(origin: CGPoint(), size: displayDimensions).insetBy(dx: inset, dy: inset))
context.setBlendMode(.destinationOut)
} else {
context.clear(CGRect(origin: CGPoint(), size: displayDimensions))
context.setBlendMode(.copy)
context.draw(dataImage, in: CGRect(origin: CGPoint(), size: displayDimensions).insetBy(dx: inset, dy: inset))
context.setBlendMode(.destinationOut)
}
if case .circle = clipStyle {
context.draw(roundCorners.cgImage!, in: CGRect(origin: CGPoint(), size: displayDimensions).insetBy(dx: inset, dy: inset))
} else if case .round = clipStyle {
context.draw(rectCorners(7.5).cgImage!, in: CGRect(origin: CGPoint(), size: displayDimensions).insetBy(dx: inset, dy: inset))
} else if case .corner(let radius) = clipStyle {
context.draw(rectCorners(radius).cgImage!, in: CGRect(origin: CGPoint(), size: displayDimensions).insetBy(dx: inset, dy: inset))
}
} else {
if let emptyColor = emptyColor {
context.clear(CGRect(origin: CGPoint(), size: displayDimensions))
context.setFillColor(emptyColor.cgColor)
context.beginPath()
if case .circle = clipStyle {
context.addEllipse(in: CGRect(origin: CGPoint(), size: displayDimensions).insetBy(dx: inset, dy: inset))
} else if case .corner(let radius) = clipStyle {
context.addPath(UIBezierPath(roundedRect: CGRect(x: 0.0, y: 0.0, width: displayDimensions.width, height: displayDimensions.height).insetBy(dx: inset, dy: inset), cornerRadius: radius).cgPath)
} else if case .round = clipStyle {
context.addPath(UIBezierPath(roundedRect: CGRect(x: 0.0, y: 0.0, width: displayDimensions.width, height: displayDimensions.height).insetBy(dx: inset, dy: inset), cornerRadius: 7.5).cgPath)
}
context.fillPath()
}
}
} else if let emptyColor = emptyColor {
context.clear(CGRect(origin: CGPoint(), size: displayDimensions))
context.setFillColor(emptyColor.cgColor)
context.beginPath()
if case .circle = clipStyle {
context.addEllipse(in: CGRect(origin: CGPoint(), size: displayDimensions).insetBy(dx: inset, dy: inset))
} else if case .corner(let radius) = clipStyle {
context.addPath(UIBezierPath(roundedRect: CGRect(x: 0.0, y: 0.0, width: displayDimensions.width, height: displayDimensions.height).insetBy(dx: inset, dy: inset), cornerRadius: radius).cgPath)
} else if case .round = clipStyle {
context.addPath(UIBezierPath(roundedRect: CGRect(x: 0.0, y: 0.0, width: displayDimensions.width, height: displayDimensions.height).insetBy(dx: inset, dy: inset), cornerRadius: 7.5).cgPath)
}
context.fillPath()
}
})
return .single(image)
}
if synchronousLoad {
return generate
} else {
return generate |> runOn(Queue.concurrentDefaultQueue())
}
}
} else {
return nil
}
}
peerAvatarImageData
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
public func peerAvatarImageData(account: Account, peer: Peer, authorOfMessage: MessageReference?, representation: UChatMediaImageRepresentation?, synchronousLoad: Bool) -> Signal<Data?, NoError>? {
if let smallProfileImage = representation {
let resourceData = account.postbox.mediaBox.resourceData(smallProfileImage.resource, attemptSynchronously: synchronousLoad)
let imageData = resourceData
|> take(1)
|> mapToSignal { maybeData -> Signal<Data?, NoError> in
if maybeData.complete {
return .single(try? Data(contentsOf: URL(fileURLWithPath: maybeData.path)))
} else {
return Signal { subscriber in
let resourceDataDisposable = resourceData.start(next: { data in
if data.complete {
subscriber.putNext(try? Data(contentsOf: URL(fileURLWithPath: maybeData.path)))
subscriber.putCompletion()
} else {
subscriber.putNext(nil)
}
}, error: { error in
subscriber.putError(error)
}, completed: {
subscriber.putCompletion()
})
var fetchedDataDisposable: Disposable?
if let peerReference = PeerReference(peer) {
fetchedDataDisposable = fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: .avatar(peer: peerReference, resource: smallProfileImage.resource), statsCategory: .generic).start()
} else if let authorOfMessage = authorOfMessage {
fetchedDataDisposable = fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: .messageAuthorAvatar(message: authorOfMessage, resource: smallProfileImage.resource), statsCategory: .generic).start()
} else {
fetchedDataDisposable = fetchedMediaResource(mediaBox: account.postbox.mediaBox, reference: .standalone(resource: smallProfileImage.resource), statsCategory: .generic).start()
}
return ActionDisposable {
resourceDataDisposable.dispose()
fetchedDataDisposable?.dispose()
}
}
}
}
return imageData
} else {
return nil
}
}