import Foundation #if os(macOS) import PostboxMac import SwiftSignalKitMac import MtProtoKitMac #else import Postbox import SwiftSignalKit import MtProtoKitDynamic #endif public enum UpdatePeerPhotoStatus { case progress(Float) case complete([TelegramMediaImageRepresentation]) } public enum UploadPeerPhotoError { case generic } public func updateAccountPhoto(account:Account, resource: MediaResource?) -> Signal { return updatePeerPhoto(account: account, peerId: account.peerId, photo: resource.flatMap({ uploadedPeerPhoto(account: account, resource: $0) })) } public struct UploadedPeerPhotoData { fileprivate let resource: MediaResource fileprivate let content: UploadedPeerPhotoDataContent } private enum UploadedPeerPhotoDataContent { case result(MultipartUploadResult) case error } public func uploadedPeerPhoto(account: Account, resource: MediaResource) -> Signal { return multipartUpload(network: account.network, postbox: account.postbox, source: .resource(.standalone(resource: resource)), encrypt: false, tag: TelegramMediaResourceFetchTag(statsCategory: .image), hintFileSize: nil, hintFileIsLarge: false) |> map { result -> UploadedPeerPhotoData in return UploadedPeerPhotoData(resource: resource, content: .result(result)) } |> `catch` { _ -> Signal in return .single(UploadedPeerPhotoData(resource: resource, content: .error)) } } public func updatePeerPhoto(account: Account, peerId: PeerId, photo: Signal?) -> Signal { return account.postbox.loadedPeerWithId(peerId) |> mapError {_ in return .generic} |> mapToSignal { peer -> Signal in if let photo = photo { return photo |> mapError { _ -> UploadPeerPhotoError in return .generic } |> mapToSignal { result -> Signal<(UpdatePeerPhotoStatus, MediaResource?), UploadPeerPhotoError> in switch result.content { case .error: return .fail(.generic) case let .result(resultData): switch resultData { case let .progress(progress): return .single((.progress(progress), result.resource)) case let .inputFile(file): if peer is TelegramUser { return account.network.request(Api.functions.photos.uploadProfilePhoto(file: file)) |> mapError {_ in return UploadPeerPhotoError.generic} |> mapToSignal { photo -> Signal<(UpdatePeerPhotoStatus, MediaResource?), UploadPeerPhotoError> in let representations:[TelegramMediaImageRepresentation] switch photo { case let .photo(photo: apiPhoto, users: _): switch apiPhoto { case .photoEmpty: representations = [] case let .photo(photo): var sizes = photo.sizes if sizes.count == 3 { sizes.remove(at: 1) } representations = telegramMediaImageRepresentationsFromApiSizes(sizes) if let resource = result.resource as? LocalFileReferenceMediaResource { if let data = try? Data(contentsOf: URL(fileURLWithPath: resource.localFilePath)) { for representation in representations { account.postbox.mediaBox.storeResourceData(representation.resource.id, data: data) } } } } } return account.postbox.transaction { transaction -> (UpdatePeerPhotoStatus, MediaResource?) in if let peer = transaction.getPeer(peer.id) { updatePeers(transaction: transaction, peers: [peer], update: { (_, peer) -> Peer? in if let peer = peer as? TelegramUser { return peer.withUpdatedPhoto(representations) } else { return peer } }) } return (.complete(representations), result.resource) } |> mapError {_ in return UploadPeerPhotoError.generic} } } else { let request: Signal if let peer = peer as? TelegramGroup { request = account.network.request(Api.functions.messages.editChatPhoto(chatId: peer.id.id, photo: .inputChatUploadedPhoto(file: file))) } else if let peer = peer as? TelegramChannel, let inputChannel = apiInputChannel(peer) { request = account.network.request(Api.functions.channels.editPhoto(channel: inputChannel, photo: .inputChatUploadedPhoto(file: file))) } else { assertionFailure() request = .complete() } return request |> mapError {_ in return UploadPeerPhotoError.generic} |> mapToSignal { updates -> Signal<(UpdatePeerPhotoStatus, MediaResource?), UploadPeerPhotoError> in account.stateManager.addUpdates(updates) for chat in updates.chats { if chat.peerId == peerId { if let groupOrChannel = parseTelegramGroupOrChannel(chat: chat) { return account.postbox.transaction { transaction -> (UpdatePeerPhotoStatus, MediaResource?) in updatePeers(transaction: transaction, peers: [groupOrChannel], update: { _, updated in return updated }) return (.complete(groupOrChannel.profileImageRepresentations), result.resource) } |> mapError { _ in return .generic } } } } return .fail(.generic) } } default: return .fail(.generic) } } } |> map { result, resource -> UpdatePeerPhotoStatus in switch result { case let .complete(representations): if let resource = resource as? LocalFileReferenceMediaResource { if let data = try? Data(contentsOf: URL(fileURLWithPath: resource.localFilePath)) { for representation in representations { account.postbox.mediaBox.storeResourceData(representation.resource.id, data: data) } } } default: break } return result } } else { if let _ = peer as? TelegramUser { return account.network.request(Api.functions.photos.updateProfilePhoto(id: Api.InputPhoto.inputPhotoEmpty)) |> `catch` { _ -> Signal in return .fail(.generic) } |> mapToSignal { _ -> Signal in return .single(.complete([])) } } else { let request: Signal if let peer = peer as? TelegramGroup { request = account.network.request(Api.functions.messages.editChatPhoto(chatId: peer.id.id, photo: .inputChatPhotoEmpty)) } else if let peer = peer as? TelegramChannel, let inputChannel = apiInputChannel(peer) { request = account.network.request(Api.functions.channels.editPhoto(channel: inputChannel, photo: .inputChatPhotoEmpty)) } else { assertionFailure() request = .complete() } return request |> mapError {_ in return UploadPeerPhotoError.generic} |> mapToSignal { updates -> Signal in account.stateManager.addUpdates(updates) for chat in updates.chats { if chat.peerId == peerId { if let groupOrChannel = parseTelegramGroupOrChannel(chat: chat) { return account.postbox.transaction { transaction -> UpdatePeerPhotoStatus in updatePeers(transaction: transaction, peers: [groupOrChannel], update: { _, updated in return updated }) return .complete(groupOrChannel.profileImageRepresentations) } |> mapError { _ in return .generic } } } } return .fail(.generic) } } } } } public func removeAccountPhoto(network: Network, reference: TelegramMediaImageReference?) -> Signal { if let reference = reference { switch reference { case let .cloud(imageId, accessHash, fileReference): if let fileReference = fileReference { return network.request(Api.functions.photos.deletePhotos(id: [.inputPhoto(id: imageId, accessHash: accessHash, fileReference: Buffer(data: fileReference))])) |> `catch` { _ -> Signal<[Int64], NoError> in return .single([]) } |> mapToSignal { _ -> Signal in return .complete() } } else { return .complete() } } } else { let api = Api.functions.photos.updateProfilePhoto(id: Api.InputPhoto.inputPhotoEmpty) return network.request(api) |> map { _ in } |> retryRequest } }