Merge commit '7621e2f8dec938cf48181c8b10afc9b01f444e68' into beta

This commit is contained in:
Ilya Laktyushin
2025-12-06 02:17:48 +04:00
commit 8344b97e03
28070 changed files with 7995182 additions and 0 deletions
@@ -0,0 +1,248 @@
import TelegramApi
import SwiftSignalKit
import Postbox
import Foundation
public enum InternalUpdaterError {
case generic
case xmlLoad
case archiveLoad
}
public func requestUpdatesXml(account: Account, source: String) -> Signal<Data, InternalUpdaterError> {
return TelegramEngine(account: account).peers.resolvePeerByName(name: source, referrer: nil)
|> castError(InternalUpdaterError.self)
|> mapToSignal { result -> Signal<Peer?, InternalUpdaterError> in
switch result {
case .progress:
return .never()
case let .result(peer):
return .single(peer?._asPeer())
}
}
|> mapToSignal { peer -> Signal<Data, InternalUpdaterError> in
if let peer = peer, let inputPeer = apiInputPeer(peer) {
return account.network.request(Api.functions.messages.getHistory(peer: inputPeer, offsetId: 0, offsetDate: 0, addOffset: 0, limit: 1, maxId: Int32.max, minId: 0, hash: 0))
|> retryRequest
|> castError(InternalUpdaterError.self)
|> mapToSignal { result in
switch result {
case let .channelMessages(_, _, _, _, apiMessages, _, apiChats, apiUsers):
if let apiMessage = apiMessages.first, let storeMessage = StoreMessage(apiMessage: apiMessage, accountPeerId: account.peerId, peerIsForum: peer.isForum) {
var peers: [PeerId: Peer] = [:]
for chat in apiChats {
if let groupOrChannel = parseTelegramGroupOrChannel(chat: chat) {
peers[groupOrChannel.id] = groupOrChannel
}
}
for user in apiUsers {
let telegramUser = TelegramUser(user: user)
peers[telegramUser.id] = telegramUser
}
if let message = locallyRenderedMessage(message: storeMessage, peers: peers), let media = message.media.first as? TelegramMediaFile {
return Signal { subscriber in
let fetchDispsable = fetchedMediaResource(mediaBox: account.postbox.mediaBox, userLocation: .other, userContentType: .other, reference: MediaResourceReference.media(media: AnyMediaReference.message(message: MessageReference(message), media: media), resource: media.resource)).start()
let dataDisposable = account.postbox.mediaBox.resourceData(media.resource, option: .complete(waitUntilFetchStatus: true)).start(next: { data in
if data.complete {
if let data = try? Data(contentsOf: URL.init(fileURLWithPath: data.path)) {
subscriber.putNext(data)
subscriber.putCompletion()
} else {
subscriber.putError(.xmlLoad)
}
}
})
return ActionDisposable {
fetchDispsable.dispose()
dataDisposable.dispose()
}
}
}
}
default:
break
}
return .fail(.xmlLoad)
}
} else {
return .fail(.xmlLoad)
}
}
}
public enum AppUpdateDownloadResult {
case started(Int)
case progress(Int, Int)
case finished(String)
}
public func downloadAppUpdate(account: Account, source: String, messageId: Int32) -> Signal<AppUpdateDownloadResult, InternalUpdaterError> {
return TelegramEngine(account: account).peers.resolvePeerByName(name: source, referrer: nil)
|> castError(InternalUpdaterError.self)
|> mapToSignal { result -> Signal<Peer?, InternalUpdaterError> in
switch result {
case .progress:
return .never()
case let .result(peer):
return .single(peer?._asPeer())
}
}
|> mapToSignal { peer in
if let peer = peer, let inputChannel = apiInputChannel(peer) {
return account.network.request(Api.functions.channels.getMessages(channel: inputChannel, id: [Api.InputMessage.inputMessageID(id: messageId)]))
|> retryRequest
|> castError(InternalUpdaterError.self)
|> mapToSignal { messages in
switch messages {
case let .channelMessages(_, _, _, _, apiMessages, _, apiChats, apiUsers):
var peers: [PeerId: Peer] = [:]
for chat in apiChats {
if let groupOrChannel = parseTelegramGroupOrChannel(chat: chat) {
peers[groupOrChannel.id] = groupOrChannel
}
}
for user in apiUsers {
let telegramUser = TelegramUser(user: user)
peers[telegramUser.id] = telegramUser
}
let messageAndFile:(Message, TelegramMediaFile)? = apiMessages.compactMap { value in
return StoreMessage(apiMessage: value, accountPeerId: account.peerId, peerIsForum: peer.isForum)
}.compactMap { value in
return locallyRenderedMessage(message: value, peers: peers)
}.sorted(by: {
$0.id > $1.id
}).first(where: { value -> Bool in
return value.media.first is TelegramMediaFile
}).map { ($0, $0.media.first as! TelegramMediaFile )}
if let (message, media) = messageAndFile {
return Signal { subscriber in
var dataDisposable: Disposable?
var fetchDisposable: Disposable?
var statusDisposable: Disposable?
let removeDisposable = account.postbox.mediaBox.removeCachedResources([media.resource.id]).start(completed: {
let reference = MediaResourceReference.media(media: .message(message: MessageReference(message), media: media), resource: media.resource)
fetchDisposable = fetchedMediaResource(mediaBox: account.postbox.mediaBox, userLocation: .other, userContentType: .other, reference: reference).start()
statusDisposable = account.postbox.mediaBox.resourceStatus(media.resource).start(next: { status in
switch status {
case let .Fetching(_, progress):
if let size = media.size {
if progress == 0 {
subscriber.putNext(.started(Int(size)))
} else {
subscriber.putNext(.progress(Int(progress * Float(size)), Int(size)))
}
}
default:
break
}
})
dataDisposable = account.postbox.mediaBox.resourceData(media.resource, option: .complete(waitUntilFetchStatus: true)).start(next: { data in
if data.complete {
subscriber.putNext(.finished(data.path))
subscriber.putCompletion()
}
})
})
return ActionDisposable {
fetchDisposable?.dispose()
dataDisposable?.dispose()
statusDisposable?.dispose()
removeDisposable.dispose()
}
}
} else {
return .fail(.archiveLoad)
}
default:
break
}
return .fail(.archiveLoad)
}
} else {
return .fail(.archiveLoad)
}
}
}
public func requestApplicationIcons(engine: TelegramEngine, source: String = "macos_app_icons") -> Signal<Void, NoError> {
return engine.peers.resolvePeerByName(name: source, referrer: nil)
|> mapToSignal { result -> Signal<Peer?, NoError> in
switch result {
case .progress:
return .never()
case let .result(peer):
return .single(peer?._asPeer())
}
} |> mapToSignal { peer -> Signal<Void, NoError> in
if let peer = peer, let inputPeer = apiInputPeer(peer) {
return engine.account.network.request(Api.functions.messages.getHistory(peer: inputPeer, offsetId: 0, offsetDate: 0, addOffset: 0, limit: 100, maxId: Int32.max, minId: 0, hash: 0))
|> retryRequest
|> mapToSignal { result in
switch result {
case let .channelMessages(_, _, _, _, apiMessages, _, apiChats, apiUsers):
var icons: [TelegramApplicationIcons.Icon] = []
for apiMessage in apiMessages.reversed() {
if let storeMessage = StoreMessage(apiMessage: apiMessage, accountPeerId: engine.account.peerId, peerIsForum: peer.isForum) {
var peers: [PeerId: Peer] = [:]
for chat in apiChats {
if let groupOrChannel = parseTelegramGroupOrChannel(chat: chat) {
peers[groupOrChannel.id] = groupOrChannel
}
}
for user in apiUsers {
let telegramUser = TelegramUser(user: user)
peers[telegramUser.id] = telegramUser
}
if let message = locallyRenderedMessage(message: storeMessage, peers: peers), let media = message.media.first as? TelegramMediaFile {
icons.append(.init(file: media, reference: MessageReference(message)))
}
}
}
return _internal_updateApplicationIcons(postbox: engine.account.postbox, icons: .init(icons: icons))
default:
break
}
return .complete()
}
} else {
return .complete()
}
}
}
/*
return Signal { subscriber in
let fetchDispsable = fetchedMediaResource(mediaBox: engine.account.postbox.mediaBox, userLocation: .other, userContentType: .other, reference: MediaResourceReference.media(media: AnyMediaReference.message(message: MessageReference(message), media: media), resource: media.resource)).start()
let dataDisposable = engine.account.postbox.mediaBox.resourceData(media.resource, option: .complete(waitUntilFetchStatus: true)).start(next: { data in
if data.complete {
if let data = try? Data(contentsOf: URL.init(fileURLWithPath: data.path)) {
subscriber.putNext(data)
subscriber.putCompletion()
} else {
subscriber.putError(.xmlLoad)
}
}
})
return ActionDisposable {
fetchDispsable.dispose()
dataDisposable.dispose()
}
}
*/
@@ -0,0 +1,80 @@
import Foundation
import TelegramApi
import Postbox
import SwiftSignalKit
import MtProtoKit
private typealias SignalKitTimer = SwiftSignalKit.Timer
private final class NotificationAutolockReportManagerImpl {
private let queue: Queue
private let network: Network
let isPerformingUpdate = ValuePromise<Bool>(false, ignoreRepeated: true)
private var deadlineDisposable: Disposable?
private let currentRequestDisposable = MetaDisposable()
private var onlineTimer: SignalKitTimer?
init(queue: Queue, deadline: Signal<Int32?, NoError>, network: Network) {
self.queue = queue
self.network = network
self.deadlineDisposable = (deadline
|> distinctUntilChanged
|> deliverOn(self.queue)).start(next: { [weak self] value in
self?.updateDeadline(value)
})
}
deinit {
assert(self.queue.isCurrent())
self.deadlineDisposable?.dispose()
self.currentRequestDisposable.dispose()
self.onlineTimer?.invalidate()
}
private func updateDeadline(_ deadline: Int32?) {
self.isPerformingUpdate.set(true)
let value: Int32
if let deadline = deadline {
value = max(0, deadline - Int32(CFAbsoluteTimeGetCurrent()))
} else {
value = -1
}
self.currentRequestDisposable.set((self.network.request(Api.functions.account.updateDeviceLocked(period: value))
|> `catch` { _ -> Signal<Api.Bool, NoError> in
return .single(.boolFalse)
}
|> deliverOn(self.queue)).start(completed: { [weak self] in
guard let strongSelf = self else {
return
}
strongSelf.isPerformingUpdate.set(false)
}))
}
}
final class NotificationAutolockReportManager {
private let queue = Queue()
private let impl: QueueLocalObject<NotificationAutolockReportManagerImpl>
init(deadline: Signal<Int32?, NoError>, network: Network) {
let queue = self.queue
self.impl = QueueLocalObject(queue: self.queue, generate: {
return NotificationAutolockReportManagerImpl(queue: queue, deadline: deadline, network: network)
})
}
func isPerformingUpdate() -> Signal<Bool, NoError> {
return Signal { subscriber in
let disposable = MetaDisposable()
self.impl.with { impl in
disposable.set(impl.isPerformingUpdate.get().start(next: { value in
subscriber.putNext(value)
}))
}
return disposable
}
}
}
@@ -0,0 +1,71 @@
//
// File.swift
//
//
// Created by Mikhail Filimonov on 25.01.2024.
//
import Foundation
import Postbox
import SwiftSignalKit
public struct TelegramApplicationIcons : PostboxCoding, Equatable {
public init(decoder: PostboxDecoder) {
self.icons = (try? decoder.decodeObjectArrayWithCustomDecoderForKey("i", decoder: { Icon(decoder: $0) })) ?? []
}
public func encode(_ encoder: PostboxEncoder) {
encoder.encodeObjectArray(self.icons, forKey: "i")
}
public struct Icon : PostboxCoding, Equatable {
public init(decoder: PostboxDecoder) {
self.file = decoder.decodeObjectForKey("f") as! TelegramMediaFile
self.reference = decoder.decodeObjectForKey("r", decoder: { MessageReference(decoder: $0) }) as! MessageReference
}
public func encode(_ encoder: PostboxEncoder) {
encoder.encodeObject(self.file, forKey: "f")
encoder.encodeObject(self.reference, forKey: "r")
}
public let file: TelegramMediaFile
public let reference: MessageReference
init(file: TelegramMediaFile, reference: MessageReference) {
self.file = file
self.reference = reference
}
}
public var icons: [Icon]
init(icons: [Icon]) {
self.icons = icons
}
static var entryId: ItemCacheEntryId {
let cacheKey = ValueBoxKey(length: 1)
cacheKey.setInt8(0, value: 0)
return ItemCacheEntryId(collectionId: Namespaces.CachedItemCollection.applicationIcons, key: cacheKey)
}
}
func _internal_applicationIcons(account: Account) -> Signal<TelegramApplicationIcons, NoError> {
let key = PostboxViewKey.cachedItem(TelegramApplicationIcons.entryId)
return account.postbox.combinedView(keys: [key])
|> mapToSignal { views -> Signal<TelegramApplicationIcons, NoError> in
guard let icons = (views.views[key] as? CachedItemView)?.value?.getLegacy(TelegramApplicationIcons.self) as? TelegramApplicationIcons else {
return .single(.init(icons: []))
}
return .single(icons)
}
}
func _internal_updateApplicationIcons(postbox: Postbox, icons: TelegramApplicationIcons) -> Signal<Void, NoError> {
return postbox.transaction { transaction -> Void in
let entry = CodableEntry(legacyValue: icons)
transaction.putItemCacheEntry(id: TelegramApplicationIcons.entryId, entry: entry)
}
}