mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
Merge branch 'master' of gitlab.com:peter-iakovlev/telegram-ios
This commit is contained in:
commit
1fe96da125
@ -105,7 +105,7 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag
|
|||||||
let appVersion = (Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) ?? "unknown"
|
let appVersion = (Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) ?? "unknown"
|
||||||
|
|
||||||
initializeAccountManagement()
|
initializeAccountManagement()
|
||||||
let accountManager = AccountManager(basePath: rootPath + "/accounts-metadata")
|
let accountManager = AccountManager(basePath: rootPath + "/accounts-metadata", isTemporary: true)
|
||||||
self.accountManager = accountManager
|
self.accountManager = accountManager
|
||||||
|
|
||||||
let deviceSpecificEncryptionParameters = BuildConfig.deviceSpecificEncryptionParameters(rootPath, baseAppBundleId: baseAppBundleId)
|
let deviceSpecificEncryptionParameters = BuildConfig.deviceSpecificEncryptionParameters(rootPath, baseAppBundleId: baseAppBundleId)
|
||||||
@ -820,6 +820,9 @@ class IntentHandler: INExtension, INSendMessageIntentHandling, INSearchForMessag
|
|||||||
|
|
||||||
return INObjectSection<Friend>(title: accountTitle, items: items)
|
return INObjectSection<Friend>(title: accountTitle, items: items)
|
||||||
})
|
})
|
||||||
|
|> `catch` { _ -> Signal<INObjectSection<Friend>, NoError> in
|
||||||
|
return .single(INObjectSection<Friend>(title: nil, items: []))
|
||||||
|
}
|
||||||
|> castError(Error.self))
|
|> castError(Error.self))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -895,7 +898,7 @@ class AvatarsIntentHandler: NSObject, SelectAvatarFriendsIntentHandling {
|
|||||||
let appVersion = (Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) ?? "unknown"
|
let appVersion = (Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String) ?? "unknown"
|
||||||
|
|
||||||
initializeAccountManagement()
|
initializeAccountManagement()
|
||||||
let accountManager = AccountManager(basePath: rootPath + "/accounts-metadata")
|
let accountManager = AccountManager(basePath: rootPath + "/accounts-metadata", isTemporary: true)
|
||||||
self.accountManager = accountManager
|
self.accountManager = accountManager
|
||||||
|
|
||||||
let deviceSpecificEncryptionParameters = BuildConfig.deviceSpecificEncryptionParameters(rootPath, baseAppBundleId: baseAppBundleId)
|
let deviceSpecificEncryptionParameters = BuildConfig.deviceSpecificEncryptionParameters(rootPath, baseAppBundleId: baseAppBundleId)
|
||||||
@ -1043,6 +1046,9 @@ class AvatarsIntentHandler: NSObject, SelectAvatarFriendsIntentHandling {
|
|||||||
|
|
||||||
return INObjectSection<Friend>(title: accountTitle, items: items)
|
return INObjectSection<Friend>(title: accountTitle, items: items)
|
||||||
})
|
})
|
||||||
|
|> `catch` { _ -> Signal<INObjectSection<Friend>, NoError> in
|
||||||
|
return .single(INObjectSection<Friend>(title: nil, items: []))
|
||||||
|
}
|
||||||
|> castError(Error.self))
|
|> castError(Error.self))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1103,6 +1109,9 @@ class AvatarsIntentHandler: NSObject, SelectAvatarFriendsIntentHandling {
|
|||||||
|
|
||||||
return items
|
return items
|
||||||
})
|
})
|
||||||
|
|> `catch` { _ -> Signal<[Friend], NoError> in
|
||||||
|
return .single([])
|
||||||
|
}
|
||||||
|> castError(Error.self))
|
|> castError(Error.self))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,13 +112,6 @@ struct Provider: IntentTimelineProvider {
|
|||||||
|
|
||||||
let rootPath = rootPathForBasePath(appGroupUrl.path)
|
let rootPath = rootPathForBasePath(appGroupUrl.path)
|
||||||
|
|
||||||
let dataPath = rootPath + "/widget-data"
|
|
||||||
|
|
||||||
guard let data = try? Data(contentsOf: URL(fileURLWithPath: dataPath)), let widgetData = try? JSONDecoder().decode(WidgetData.self, from: data), case let .peers(widgetPeers) = widgetData.content else {
|
|
||||||
completion(Timeline(entries: [SimpleEntry(date: entryDate, contents: .recent)], policy: .atEnd))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
TempBox.initializeShared(basePath: rootPath, processType: "widget", launchSpecificId: arc4random64())
|
TempBox.initializeShared(basePath: rootPath, processType: "widget", launchSpecificId: arc4random64())
|
||||||
|
|
||||||
let logsPath = rootPath + "/widget-logs"
|
let logsPath = rootPath + "/widget-logs"
|
||||||
@ -213,7 +206,10 @@ struct Provider: IntentTimelineProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}))
|
})
|
||||||
|
|> `catch` { _ -> Signal<[ParsedPeer], NoError> in
|
||||||
|
return .single([])
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = combineLatest(friendsByAccount).start(next: { allPeers in
|
let _ = combineLatest(friendsByAccount).start(next: { allPeers in
|
||||||
@ -269,13 +265,6 @@ struct AvatarsProvider: IntentTimelineProvider {
|
|||||||
|
|
||||||
let rootPath = rootPathForBasePath(appGroupUrl.path)
|
let rootPath = rootPathForBasePath(appGroupUrl.path)
|
||||||
|
|
||||||
let dataPath = rootPath + "/widget-data"
|
|
||||||
|
|
||||||
guard let data = try? Data(contentsOf: URL(fileURLWithPath: dataPath)), let widgetData = try? JSONDecoder().decode(WidgetData.self, from: data), case let .peers(widgetPeers) = widgetData.content else {
|
|
||||||
completion(Timeline(entries: [SimpleEntry(date: entryDate, contents: .recent)], policy: .atEnd))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
TempBox.initializeShared(basePath: rootPath, processType: "widget", launchSpecificId: arc4random64())
|
TempBox.initializeShared(basePath: rootPath, processType: "widget", launchSpecificId: arc4random64())
|
||||||
|
|
||||||
let logsPath = rootPath + "/widget-logs"
|
let logsPath = rootPath + "/widget-logs"
|
||||||
@ -370,7 +359,10 @@ struct AvatarsProvider: IntentTimelineProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return result
|
return result
|
||||||
}))
|
})
|
||||||
|
|> `catch` { _ -> Signal<[ParsedPeer], NoError> in
|
||||||
|
return .single([])
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = combineLatest(friendsByAccount).start(next: { allPeers in
|
let _ = combineLatest(friendsByAccount).start(next: { allPeers in
|
||||||
@ -1006,39 +998,7 @@ private let presentationData: WidgetPresentationData = {
|
|||||||
func getWidgetData(contents: SimpleEntry.Contents) -> PeersWidgetData {
|
func getWidgetData(contents: SimpleEntry.Contents) -> PeersWidgetData {
|
||||||
switch contents {
|
switch contents {
|
||||||
case .recent:
|
case .recent:
|
||||||
let appBundleIdentifier = Bundle.main.bundleIdentifier!
|
|
||||||
guard let lastDotRange = appBundleIdentifier.range(of: ".", options: [.backwards]) else {
|
|
||||||
return .empty
|
return .empty
|
||||||
}
|
|
||||||
let baseAppBundleId = String(appBundleIdentifier[..<lastDotRange.lowerBound])
|
|
||||||
|
|
||||||
let appGroupName = "group.\(baseAppBundleId)"
|
|
||||||
let maybeAppGroupUrl = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupName)
|
|
||||||
|
|
||||||
guard let appGroupUrl = maybeAppGroupUrl else {
|
|
||||||
return .empty
|
|
||||||
}
|
|
||||||
|
|
||||||
let rootPath = rootPathForBasePath(appGroupUrl.path)
|
|
||||||
|
|
||||||
/*if let data = try? Data(contentsOf: URL(fileURLWithPath: appLockStatePath(rootPath: rootPath))), let state = try? JSONDecoder().decode(LockState.self, from: data) {
|
|
||||||
if state.isManuallyLocked || state.autolockTimeout != nil {
|
|
||||||
return .empty
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
let dataPath = rootPath + "/widget-data"
|
|
||||||
|
|
||||||
if let data = try? Data(contentsOf: URL(fileURLWithPath: dataPath)), let widgetData = try? JSONDecoder().decode(WidgetData.self, from: data) {
|
|
||||||
switch widgetData.content {
|
|
||||||
case let .peers(peers):
|
|
||||||
return .peers(ParsedPeers(accountId: widgetData.accountId, peers: peers))
|
|
||||||
case .empty:
|
|
||||||
return .empty
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return .empty
|
|
||||||
}
|
|
||||||
case let .peers(peers):
|
case let .peers(peers):
|
||||||
return .peers(peers)
|
return .peers(peers)
|
||||||
}
|
}
|
||||||
|
@ -53,7 +53,7 @@ final class AccountManagerImpl {
|
|||||||
private var noticeEntryViews = Bag<(MutableNoticeEntryView, ValuePipe<NoticeEntryView>)>()
|
private var noticeEntryViews = Bag<(MutableNoticeEntryView, ValuePipe<NoticeEntryView>)>()
|
||||||
private var accessChallengeDataViews = Bag<(MutableAccessChallengeDataView, ValuePipe<AccessChallengeDataView>)>()
|
private var accessChallengeDataViews = Bag<(MutableAccessChallengeDataView, ValuePipe<AccessChallengeDataView>)>()
|
||||||
|
|
||||||
fileprivate init(queue: Queue, basePath: String, temporarySessionId: Int64) {
|
fileprivate init?(queue: Queue, basePath: String, isTemporary: Bool, temporarySessionId: Int64) {
|
||||||
let startTime = CFAbsoluteTimeGetCurrent()
|
let startTime = CFAbsoluteTimeGetCurrent()
|
||||||
|
|
||||||
self.queue = queue
|
self.queue = queue
|
||||||
@ -61,8 +61,14 @@ final class AccountManagerImpl {
|
|||||||
self.atomicStatePath = "\(basePath)/atomic-state"
|
self.atomicStatePath = "\(basePath)/atomic-state"
|
||||||
self.temporarySessionId = temporarySessionId
|
self.temporarySessionId = temporarySessionId
|
||||||
let _ = try? FileManager.default.createDirectory(atPath: basePath, withIntermediateDirectories: true, attributes: nil)
|
let _ = try? FileManager.default.createDirectory(atPath: basePath, withIntermediateDirectories: true, attributes: nil)
|
||||||
self.guardValueBox = SqliteValueBox(basePath: basePath + "/guard_db", queue: queue, encryptionParameters: nil, upgradeProgress: { _ in })
|
guard let guardValueBox = SqliteValueBox(basePath: basePath + "/guard_db", queue: queue, isTemporary: isTemporary, encryptionParameters: nil, upgradeProgress: { _ in }) else {
|
||||||
self.valueBox = SqliteValueBox(basePath: basePath + "/db", queue: queue, encryptionParameters: nil, upgradeProgress: { _ in })
|
return nil
|
||||||
|
}
|
||||||
|
self.guardValueBox = guardValueBox
|
||||||
|
guard let valueBox = SqliteValueBox(basePath: basePath + "/db", queue: queue, isTemporary: isTemporary, encryptionParameters: nil, upgradeProgress: { _ in }) else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
self.valueBox = valueBox
|
||||||
|
|
||||||
self.legacyMetadataTable = AccountManagerMetadataTable(valueBox: self.valueBox, table: AccountManagerMetadataTable.tableSpec(0))
|
self.legacyMetadataTable = AccountManagerMetadataTable(valueBox: self.valueBox, table: AccountManagerMetadataTable.tableSpec(0))
|
||||||
self.legacyRecordTable = AccountManagerRecordTable(valueBox: self.valueBox, table: AccountManagerRecordTable.tableSpec(1))
|
self.legacyRecordTable = AccountManagerRecordTable(valueBox: self.valueBox, table: AccountManagerRecordTable.tableSpec(1))
|
||||||
@ -451,7 +457,7 @@ public final class AccountManager {
|
|||||||
private let impl: QueueLocalObject<AccountManagerImpl>
|
private let impl: QueueLocalObject<AccountManagerImpl>
|
||||||
public let temporarySessionId: Int64
|
public let temporarySessionId: Int64
|
||||||
|
|
||||||
public init(basePath: String) {
|
public init(basePath: String, isTemporary: Bool) {
|
||||||
self.queue = sharedQueue
|
self.queue = sharedQueue
|
||||||
self.basePath = basePath
|
self.basePath = basePath
|
||||||
var temporarySessionId: Int64 = 0
|
var temporarySessionId: Int64 = 0
|
||||||
@ -459,7 +465,11 @@ public final class AccountManager {
|
|||||||
self.temporarySessionId = temporarySessionId
|
self.temporarySessionId = temporarySessionId
|
||||||
let queue = self.queue
|
let queue = self.queue
|
||||||
self.impl = QueueLocalObject(queue: queue, generate: {
|
self.impl = QueueLocalObject(queue: queue, generate: {
|
||||||
return AccountManagerImpl(queue: queue, basePath: basePath, temporarySessionId: temporarySessionId)
|
if let value = AccountManagerImpl(queue: queue, basePath: basePath, isTemporary: isTemporary, temporarySessionId: temporarySessionId) {
|
||||||
|
return value
|
||||||
|
} else {
|
||||||
|
preconditionFailure()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
self.mediaBox = MediaBox(basePath: basePath + "/media")
|
self.mediaBox = MediaBox(basePath: basePath + "/media")
|
||||||
}
|
}
|
||||||
|
@ -1096,6 +1096,7 @@ public final class Transaction {
|
|||||||
public enum PostboxResult {
|
public enum PostboxResult {
|
||||||
case upgrading(Float)
|
case upgrading(Float)
|
||||||
case postbox(Postbox)
|
case postbox(Postbox)
|
||||||
|
case error
|
||||||
}
|
}
|
||||||
|
|
||||||
func debugSaveState(basePath:String, name: String) {
|
func debugSaveState(basePath:String, name: String) {
|
||||||
@ -1137,9 +1138,12 @@ public func openPostbox(basePath: String, seedConfiguration: SeedConfiguration,
|
|||||||
|
|
||||||
let startTime = CFAbsoluteTimeGetCurrent()
|
let startTime = CFAbsoluteTimeGetCurrent()
|
||||||
|
|
||||||
var valueBox = SqliteValueBox(basePath: basePath + "/db", queue: queue, encryptionParameters: encryptionParameters, upgradeProgress: { progress in
|
guard var valueBox = SqliteValueBox(basePath: basePath + "/db", queue: queue, isTemporary: isTemporary, encryptionParameters: encryptionParameters, upgradeProgress: { progress in
|
||||||
subscriber.putNext(.upgrading(progress))
|
subscriber.putNext(.upgrading(progress))
|
||||||
})
|
}) else {
|
||||||
|
subscriber.putNext(.error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
loop: while true {
|
loop: while true {
|
||||||
let metadataTable = MetadataTable(valueBox: valueBox, table: MetadataTable.tableSpec(0))
|
let metadataTable = MetadataTable(valueBox: valueBox, table: MetadataTable.tableSpec(0))
|
||||||
@ -1150,16 +1154,20 @@ public func openPostbox(basePath: String, seedConfiguration: SeedConfiguration,
|
|||||||
if let userVersion = userVersion {
|
if let userVersion = userVersion {
|
||||||
if userVersion != currentUserVersion {
|
if userVersion != currentUserVersion {
|
||||||
if isTemporary {
|
if isTemporary {
|
||||||
subscriber.putNext(.upgrading(0.0))
|
subscriber.putNext(.error)
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
if userVersion > currentUserVersion {
|
if userVersion > currentUserVersion {
|
||||||
postboxLog("Version \(userVersion) is newer than supported")
|
postboxLog("Version \(userVersion) is newer than supported")
|
||||||
assertionFailure("Version \(userVersion) is newer than supported")
|
assertionFailure("Version \(userVersion) is newer than supported")
|
||||||
valueBox.drop()
|
valueBox.drop()
|
||||||
valueBox = SqliteValueBox(basePath: basePath + "/db", queue: queue, encryptionParameters: encryptionParameters, upgradeProgress: { progress in
|
guard let updatedValueBox = SqliteValueBox(basePath: basePath + "/db", queue: queue, isTemporary: isTemporary, encryptionParameters: encryptionParameters, upgradeProgress: { progress in
|
||||||
subscriber.putNext(.upgrading(progress))
|
subscriber.putNext(.upgrading(progress))
|
||||||
})
|
}) else {
|
||||||
|
subscriber.putNext(.error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
valueBox = updatedValueBox
|
||||||
} else {
|
} else {
|
||||||
if let operation = registeredUpgrades()[userVersion] {
|
if let operation = registeredUpgrades()[userVersion] {
|
||||||
switch operation {
|
switch operation {
|
||||||
@ -1177,9 +1185,13 @@ public func openPostbox(basePath: String, seedConfiguration: SeedConfiguration,
|
|||||||
valueBox.internalClose()
|
valueBox.internalClose()
|
||||||
let _ = try? FileManager.default.removeItem(atPath: basePath + "/db")
|
let _ = try? FileManager.default.removeItem(atPath: basePath + "/db")
|
||||||
let _ = try? FileManager.default.moveItem(atPath: updatedPath, toPath: basePath + "/db")
|
let _ = try? FileManager.default.moveItem(atPath: updatedPath, toPath: basePath + "/db")
|
||||||
valueBox = SqliteValueBox(basePath: basePath + "/db", queue: queue, encryptionParameters: encryptionParameters, upgradeProgress: { progress in
|
guard let updatedValueBox = SqliteValueBox(basePath: basePath + "/db", queue: queue, isTemporary: isTemporary, encryptionParameters: encryptionParameters, upgradeProgress: { progress in
|
||||||
subscriber.putNext(.upgrading(progress))
|
subscriber.putNext(.upgrading(progress))
|
||||||
})
|
}) else {
|
||||||
|
subscriber.putNext(.error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
valueBox = updatedValueBox
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
continue loop
|
continue loop
|
||||||
@ -1187,9 +1199,13 @@ public func openPostbox(basePath: String, seedConfiguration: SeedConfiguration,
|
|||||||
assertionFailure("Couldn't find any upgrade for \(userVersion)")
|
assertionFailure("Couldn't find any upgrade for \(userVersion)")
|
||||||
postboxLog("Couldn't find any upgrade for \(userVersion)")
|
postboxLog("Couldn't find any upgrade for \(userVersion)")
|
||||||
valueBox.drop()
|
valueBox.drop()
|
||||||
valueBox = SqliteValueBox(basePath: basePath + "/db", queue: queue, encryptionParameters: encryptionParameters, upgradeProgress: { progress in
|
guard let updatedValueBox = SqliteValueBox(basePath: basePath + "/db", queue: queue, isTemporary: isTemporary, encryptionParameters: encryptionParameters, upgradeProgress: { progress in
|
||||||
subscriber.putNext(.upgrading(progress))
|
subscriber.putNext(.upgrading(progress))
|
||||||
})
|
}) else {
|
||||||
|
subscriber.putNext(.error)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
valueBox = updatedValueBox
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -155,6 +155,7 @@ public final class SqliteValueBox: ValueBox {
|
|||||||
private let lock = NSRecursiveLock()
|
private let lock = NSRecursiveLock()
|
||||||
|
|
||||||
fileprivate let basePath: String
|
fileprivate let basePath: String
|
||||||
|
private let isTemporary: Bool
|
||||||
private let inMemory: Bool
|
private let inMemory: Bool
|
||||||
private let encryptionParameters: ValueBoxEncryptionParameters?
|
private let encryptionParameters: ValueBoxEncryptionParameters?
|
||||||
private let databasePath: String
|
private let databasePath: String
|
||||||
@ -193,13 +194,18 @@ public final class SqliteValueBox: ValueBox {
|
|||||||
|
|
||||||
private let queue: Queue
|
private let queue: Queue
|
||||||
|
|
||||||
public init(basePath: String, queue: Queue, encryptionParameters: ValueBoxEncryptionParameters?, upgradeProgress: (Float) -> Void, inMemory: Bool = false) {
|
public init?(basePath: String, queue: Queue, isTemporary: Bool, encryptionParameters: ValueBoxEncryptionParameters?, upgradeProgress: (Float) -> Void, inMemory: Bool = false) {
|
||||||
self.basePath = basePath
|
self.basePath = basePath
|
||||||
|
self.isTemporary = isTemporary
|
||||||
self.inMemory = inMemory
|
self.inMemory = inMemory
|
||||||
self.encryptionParameters = encryptionParameters
|
self.encryptionParameters = encryptionParameters
|
||||||
self.databasePath = basePath + "/db_sqlite"
|
self.databasePath = basePath + "/db_sqlite"
|
||||||
self.queue = queue
|
self.queue = queue
|
||||||
self.database = self.openDatabase(encryptionParameters: encryptionParameters, upgradeProgress: upgradeProgress)
|
if let database = self.openDatabase(encryptionParameters: encryptionParameters, isTemporary: isTemporary, upgradeProgress: upgradeProgress) {
|
||||||
|
self.database = database
|
||||||
|
} else {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
@ -212,7 +218,7 @@ public final class SqliteValueBox: ValueBox {
|
|||||||
self.database = nil
|
self.database = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
private func openDatabase(encryptionParameters: ValueBoxEncryptionParameters?, upgradeProgress: (Float) -> Void) -> Database {
|
private func openDatabase(encryptionParameters: ValueBoxEncryptionParameters?, isTemporary: Bool, upgradeProgress: (Float) -> Void) -> Database? {
|
||||||
precondition(self.queue.isCurrent())
|
precondition(self.queue.isCurrent())
|
||||||
|
|
||||||
checkpoints.set(nil)
|
checkpoints.set(nil)
|
||||||
@ -297,6 +303,9 @@ public final class SqliteValueBox: ValueBox {
|
|||||||
assert(resultCode)
|
assert(resultCode)
|
||||||
|
|
||||||
if self.isEncrypted(database) {
|
if self.isEncrypted(database) {
|
||||||
|
if isTemporary {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
postboxLog("Encryption key is invalid")
|
postboxLog("Encryption key is invalid")
|
||||||
|
|
||||||
for fileName in dabaseFileNames {
|
for fileName in dabaseFileNames {
|
||||||
@ -2060,7 +2069,7 @@ public final class SqliteValueBox: ValueBox {
|
|||||||
let _ = try? FileManager.default.removeItem(atPath: self.basePath + "/\(fileName)")
|
let _ = try? FileManager.default.removeItem(atPath: self.basePath + "/\(fileName)")
|
||||||
}
|
}
|
||||||
|
|
||||||
self.database = self.openDatabase(encryptionParameters: self.encryptionParameters, upgradeProgress: { _ in })
|
self.database = self.openDatabase(encryptionParameters: self.encryptionParameters, isTemporary: self.isTemporary, upgradeProgress: { _ in })
|
||||||
|
|
||||||
tables.removeAll()
|
tables.removeAll()
|
||||||
}
|
}
|
||||||
|
@ -69,19 +69,26 @@ public let telegramPostboxSeedConfiguration: SeedConfiguration = {
|
|||||||
}, additionalChatListIndexNamespace: Namespaces.Message.Cloud, messageNamespacesRequiringGroupStatsValidation: [Namespaces.Message.Cloud], defaultMessageNamespaceReadStates: [Namespaces.Message.Local: .idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 0, markedUnread: false)], chatMessagesNamespaces: Set([Namespaces.Message.Cloud, Namespaces.Message.Local, Namespaces.Message.SecretIncoming]), globalNotificationSettingsPreferencesKey: PreferencesKeys.globalNotifications, defaultGlobalNotificationSettings: GlobalNotificationSettings.defaultSettings)
|
}, additionalChatListIndexNamespace: Namespaces.Message.Cloud, messageNamespacesRequiringGroupStatsValidation: [Namespaces.Message.Cloud], defaultMessageNamespaceReadStates: [Namespaces.Message.Local: .idBased(maxIncomingReadId: 0, maxOutgoingReadId: 0, maxKnownId: 0, count: 0, markedUnread: false)], chatMessagesNamespaces: Set([Namespaces.Message.Cloud, Namespaces.Message.Local, Namespaces.Message.SecretIncoming]), globalNotificationSettingsPreferencesKey: PreferencesKeys.globalNotifications, defaultGlobalNotificationSettings: GlobalNotificationSettings.defaultSettings)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
public func accountTransaction<T>(rootPath: String, id: AccountRecordId, encryptionParameters: ValueBoxEncryptionParameters, transaction: @escaping (Postbox, Transaction) -> T) -> Signal<T, NoError> {
|
public enum AccountTransactionError {
|
||||||
|
case couldNotOpen
|
||||||
|
}
|
||||||
|
|
||||||
|
public func accountTransaction<T>(rootPath: String, id: AccountRecordId, encryptionParameters: ValueBoxEncryptionParameters, transaction: @escaping (Postbox, Transaction) -> T) -> Signal<T, AccountTransactionError> {
|
||||||
let path = "\(rootPath)/\(accountRecordIdPathName(id))"
|
let path = "\(rootPath)/\(accountRecordIdPathName(id))"
|
||||||
let postbox = openPostbox(basePath: path + "/postbox", seedConfiguration: telegramPostboxSeedConfiguration, encryptionParameters: encryptionParameters, timestampForAbsoluteTimeBasedOperations: Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970), isTemporary: true)
|
let postbox = openPostbox(basePath: path + "/postbox", seedConfiguration: telegramPostboxSeedConfiguration, encryptionParameters: encryptionParameters, timestampForAbsoluteTimeBasedOperations: Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970), isTemporary: true)
|
||||||
return postbox
|
return postbox
|
||||||
|> mapToSignal { value -> Signal<T, NoError> in
|
|> castError(AccountTransactionError.self)
|
||||||
|
|> mapToSignal { value -> Signal<T, AccountTransactionError> in
|
||||||
switch value {
|
switch value {
|
||||||
case let .postbox(postbox):
|
case let .postbox(postbox):
|
||||||
return postbox.transaction { t in
|
return postbox.transaction { t in
|
||||||
transaction(postbox, t)
|
transaction(postbox, t)
|
||||||
}
|
}
|
||||||
|
|> castError(AccountTransactionError.self)
|
||||||
|
case .error:
|
||||||
|
return .fail(.couldNotOpen)
|
||||||
default:
|
default:
|
||||||
return .complete()
|
return .complete()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -176,6 +176,8 @@ public func accountPreferenceEntries(rootPath: String, id: AccountRecordId, keys
|
|||||||
}
|
}
|
||||||
return .result(path, result)
|
return .result(path, result)
|
||||||
}
|
}
|
||||||
|
case .error:
|
||||||
|
return .single(.progress(0.0))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -197,6 +199,8 @@ public func accountNoticeEntries(rootPath: String, id: AccountRecordId, encrypti
|
|||||||
return postbox.transaction { transaction -> AccountNoticeEntriesResult in
|
return postbox.transaction { transaction -> AccountNoticeEntriesResult in
|
||||||
return .result(path, transaction.getAllNoticeEntries())
|
return .result(path, transaction.getAllNoticeEntries())
|
||||||
}
|
}
|
||||||
|
case .error:
|
||||||
|
return .single(.progress(0.0))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -218,6 +222,8 @@ public func accountLegacyAccessChallengeData(rootPath: String, id: AccountRecord
|
|||||||
return postbox.transaction { transaction -> LegacyAccessChallengeDataResult in
|
return postbox.transaction { transaction -> LegacyAccessChallengeDataResult in
|
||||||
return .result(transaction.legacyGetAccessChallengeData())
|
return .result(transaction.legacyGetAccessChallengeData())
|
||||||
}
|
}
|
||||||
|
case .error:
|
||||||
|
return .single(.progress(0.0))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -232,6 +238,8 @@ public func accountWithId(accountManager: AccountManager, networkArguments: Netw
|
|||||||
switch result {
|
switch result {
|
||||||
case let .upgrading(progress):
|
case let .upgrading(progress):
|
||||||
return .single(.upgrading(progress))
|
return .single(.upgrading(progress))
|
||||||
|
case .error:
|
||||||
|
return .single(.upgrading(0.0))
|
||||||
case let .postbox(postbox):
|
case let .postbox(postbox):
|
||||||
return accountManager.transaction { transaction -> (LocalizationSettings?, ProxySettings?) in
|
return accountManager.transaction { transaction -> (LocalizationSettings?, ProxySettings?) in
|
||||||
return (transaction.getSharedData(SharedDataKeys.localizationSettings) as? LocalizationSettings, transaction.getSharedData(SharedDataKeys.proxySettings) as? ProxySettings)
|
return (transaction.getSharedData(SharedDataKeys.localizationSettings) as? LocalizationSettings, transaction.getSharedData(SharedDataKeys.proxySettings) as? ProxySettings)
|
||||||
|
@ -219,6 +219,8 @@ public func temporaryAccount(manager: AccountManager, rootPath: String, encrypti
|
|||||||
switch result {
|
switch result {
|
||||||
case .upgrading:
|
case .upgrading:
|
||||||
return .complete()
|
return .complete()
|
||||||
|
case .error:
|
||||||
|
return .complete()
|
||||||
case let .postbox(postbox):
|
case let .postbox(postbox):
|
||||||
return .single(TemporaryAccount(id: id, basePath: path, postbox: postbox))
|
return .single(TemporaryAccount(id: id, basePath: path, postbox: postbox))
|
||||||
}
|
}
|
||||||
|
@ -665,7 +665,7 @@ final class SharedApplicationContext {
|
|||||||
})
|
})
|
||||||
|
|
||||||
let accountManagerSignal = Signal<AccountManager, NoError> { subscriber in
|
let accountManagerSignal = Signal<AccountManager, NoError> { subscriber in
|
||||||
let accountManager = AccountManager(basePath: rootPath + "/accounts-metadata")
|
let accountManager = AccountManager(basePath: rootPath + "/accounts-metadata", isTemporary: false)
|
||||||
return (upgradedAccounts(accountManager: accountManager, rootPath: rootPath, encryptionParameters: encryptionParameters)
|
return (upgradedAccounts(accountManager: accountManager, rootPath: rootPath, encryptionParameters: encryptionParameters)
|
||||||
|> deliverOnMainQueue).start(next: { progress in
|
|> deliverOnMainQueue).start(next: { progress in
|
||||||
if self.dataImportSplash == nil {
|
if self.dataImportSplash == nil {
|
||||||
|
@ -4998,6 +4998,10 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
return interfaceState.withUpdatedEffectiveInputState(updatedState)
|
return interfaceState.withUpdatedEffectiveInputState(updatedState)
|
||||||
}.updatedInputMode({ _ in updatedMode })
|
}.updatedInputMode({ _ in updatedMode })
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if !strongSelf.presentationInterfaceState.interfaceState.effectiveInputState.inputText.string.isEmpty {
|
||||||
|
strongSelf.silentPostTooltipController?.dismiss()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, updateInputModeAndDismissedButtonKeyboardMessageId: { [weak self] f in
|
}, updateInputModeAndDismissedButtonKeyboardMessageId: { [weak self] f in
|
||||||
if let strongSelf = self {
|
if let strongSelf = self {
|
||||||
@ -5626,6 +5630,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
let intervalText = timeIntervalString(strings: strongSelf.presentationData.strings, value: currentAutoremoveTimeout)
|
let intervalText = timeIntervalString(strings: strongSelf.presentationData.strings, value: currentAutoremoveTimeout)
|
||||||
let text: String = "Messages in this chat are automatically\ndeleted \(intervalText) after they have been sent."
|
let text: String = "Messages in this chat are automatically\ndeleted \(intervalText) after they have been sent."
|
||||||
|
|
||||||
|
strongSelf.mediaRecordingModeTooltipController?.dismiss()
|
||||||
|
|
||||||
if let tooltipController = strongSelf.silentPostTooltipController {
|
if let tooltipController = strongSelf.silentPostTooltipController {
|
||||||
tooltipController.updateContent(.text(text), animated: true, extendTimer: true)
|
tooltipController.updateContent(.text(text), animated: true, extendTimer: true)
|
||||||
} else {
|
} else {
|
||||||
@ -11476,6 +11482,8 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
text = self.presentationData.strings.Conversation_HoldForVideo
|
text = self.presentationData.strings.Conversation_HoldForVideo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.silentPostTooltipController?.dismiss()
|
||||||
|
|
||||||
if let tooltipController = self.mediaRecordingModeTooltipController {
|
if let tooltipController = self.mediaRecordingModeTooltipController {
|
||||||
tooltipController.updateContent(.text(text), animated: true, extendTimer: true)
|
tooltipController.updateContent(.text(text), animated: true, extendTimer: true)
|
||||||
} else if let rect = rect {
|
} else if let rect = rect {
|
||||||
@ -12033,7 +12041,6 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
strongSelf.updateChatPresentationInterfaceState(animated: false, interactive: false, { $0.updatedInterfaceState({ $0.withoutSelectionState() }) })
|
strongSelf.updateChatPresentationInterfaceState(animated: false, interactive: false, { $0.updatedInterfaceState({ $0.withoutSelectionState() }) })
|
||||||
|
|
||||||
var isOn: Bool = true
|
var isOn: Bool = true
|
||||||
var title: String?
|
|
||||||
var text: String?
|
var text: String?
|
||||||
if let myValue = value.value {
|
if let myValue = value.value {
|
||||||
text = strongSelf.presentationData.strings.Conversation_AutoremoveChanged("\(timeIntervalString(strings: strongSelf.presentationData.strings, value: myValue))").0
|
text = strongSelf.presentationData.strings.Conversation_AutoremoveChanged("\(timeIntervalString(strings: strongSelf.presentationData.strings, value: myValue))").0
|
||||||
@ -12042,7 +12049,7 @@ public final class ChatControllerImpl: TelegramBaseController, ChatController, G
|
|||||||
text = "Auto-Delete is now off."
|
text = "Auto-Delete is now off."
|
||||||
}
|
}
|
||||||
if let text = text {
|
if let text = text {
|
||||||
strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .autoDelete(isOn: isOn, title: title, text: text), elevatedLayout: false, action: { _ in return false }), in: .current)
|
strongSelf.present(UndoOverlayController(presentationData: strongSelf.presentationData, content: .autoDelete(isOn: isOn, title: nil, text: text), elevatedLayout: false, action: { _ in return false }), in: .current)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -12122,3 +12129,29 @@ private final class ContextControllerContentSourceImpl: ContextControllerContent
|
|||||||
func animatedIn() {
|
func animatedIn() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension Peer {
|
||||||
|
func canSetupAutoremoveTimeout(accountPeerId: PeerId) -> Bool {
|
||||||
|
if let _ = self as? TelegramSecretChat {
|
||||||
|
return false
|
||||||
|
} else if let group = self as? TelegramGroup {
|
||||||
|
if case .creator = group.role {
|
||||||
|
return true
|
||||||
|
} else if case let .admin(rights, _) = group.role {
|
||||||
|
if rights.flags.contains(.canDeleteMessages) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if let user = self as? TelegramUser {
|
||||||
|
if user.id != accountPeerId && user.botInfo == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} else if let channel = self as? TelegramChannel {
|
||||||
|
if channel.hasPermission(.deleteAllMessages) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -21,6 +21,7 @@ import JoinLinkPreviewUI
|
|||||||
import LanguageLinkPreviewUI
|
import LanguageLinkPreviewUI
|
||||||
import PeerInfoUI
|
import PeerInfoUI
|
||||||
import InviteLinksUI
|
import InviteLinksUI
|
||||||
|
import UndoUI
|
||||||
|
|
||||||
private final class ChatRecentActionsListOpaqueState {
|
private final class ChatRecentActionsListOpaqueState {
|
||||||
let entries: [ChatRecentActionsEntry]
|
let entries: [ChatRecentActionsEntry]
|
||||||
@ -160,6 +161,11 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
|||||||
strongSelf.pushController(controller)
|
strongSelf.pushController(controller)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
case .changeHistoryTTL:
|
||||||
|
if strongSelf.peer.canSetupAutoremoveTimeout(accountPeerId: strongSelf.context.account.peerId) {
|
||||||
|
strongSelf.presentAutoremoveSetup()
|
||||||
|
return true
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -876,4 +882,29 @@ final class ChatRecentActionsControllerNode: ViewControllerTracingNode {
|
|||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func presentAutoremoveSetup() {
|
||||||
|
let peer = self.peer
|
||||||
|
|
||||||
|
let controller = peerAutoremoveSetupScreen(context: self.context, peerId: peer.id, completion: { [weak self] updatedValue in
|
||||||
|
if case let .updated(value) = updatedValue {
|
||||||
|
guard let strongSelf = self else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var isOn: Bool = true
|
||||||
|
var text: String?
|
||||||
|
if let myValue = value.value {
|
||||||
|
text = strongSelf.presentationData.strings.Conversation_AutoremoveChanged("\(timeIntervalString(strings: strongSelf.presentationData.strings, value: myValue))").0
|
||||||
|
} else {
|
||||||
|
isOn = false
|
||||||
|
text = "Auto-Delete is now off."
|
||||||
|
}
|
||||||
|
if let text = text {
|
||||||
|
strongSelf.presentController(UndoOverlayController(presentationData: strongSelf.presentationData, content: .autoDelete(isOn: isOn, title: nil, text: text), elevatedLayout: false, action: { _ in return false }), nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
self.pushController(controller)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -113,7 +113,7 @@ public final class NotificationViewControllerImpl {
|
|||||||
|
|
||||||
if sharedAccountContext == nil {
|
if sharedAccountContext == nil {
|
||||||
initializeAccountManagement()
|
initializeAccountManagement()
|
||||||
let accountManager = AccountManager(basePath: rootPath + "/accounts-metadata")
|
let accountManager = AccountManager(basePath: rootPath + "/accounts-metadata", isTemporary: true)
|
||||||
|
|
||||||
var initialPresentationDataAndSettings: InitialPresentationDataAndSettings?
|
var initialPresentationDataAndSettings: InitialPresentationDataAndSettings?
|
||||||
let semaphore = DispatchSemaphore(value: 0)
|
let semaphore = DispatchSemaphore(value: 0)
|
||||||
|
@ -202,7 +202,7 @@ public class ShareRootControllerImpl {
|
|||||||
|
|
||||||
let internalContext: InternalContext
|
let internalContext: InternalContext
|
||||||
|
|
||||||
let accountManager = AccountManager(basePath: rootPath + "/accounts-metadata")
|
let accountManager = AccountManager(basePath: rootPath + "/accounts-metadata", isTemporary: true)
|
||||||
|
|
||||||
if let globalInternalContext = globalInternalContext {
|
if let globalInternalContext = globalInternalContext {
|
||||||
internalContext = globalInternalContext
|
internalContext = globalInternalContext
|
||||||
|
@ -740,9 +740,9 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
|||||||
self.updateNotificationTokensRegistration()
|
self.updateNotificationTokensRegistration()
|
||||||
|
|
||||||
if applicationBindings.isMainApp {
|
if applicationBindings.isMainApp {
|
||||||
self.widgetDataContext = WidgetDataContext(basePath: self.basePath, activeAccount: self.activeAccounts
|
self.widgetDataContext = WidgetDataContext(basePath: self.basePath, inForeground: self.applicationBindings.applicationInForeground, activeAccounts: self.activeAccounts
|
||||||
|> map { primary, _, _ in
|
|> map { _, accounts, _ in
|
||||||
return primary
|
return accounts.map { $0.1 }
|
||||||
}, presentationData: self.presentationData, appLockContext: self.appLockContext as! AppLockContextImpl)
|
}, presentationData: self.presentationData, appLockContext: self.appLockContext as! AppLockContextImpl)
|
||||||
|
|
||||||
let enableSpotlight = accountManager.sharedData(keys: Set([ApplicationSpecificSharedDataKeys.intentsSettings]))
|
let enableSpotlight = accountManager.sharedData(keys: Set([ApplicationSpecificSharedDataKeys.intentsSettings]))
|
||||||
|
@ -298,6 +298,9 @@ public func upgradedAccounts(accountManager: AccountManager, rootPath: String, e
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|> ignoreValues
|
|> ignoreValues
|
||||||
|
|> `catch` { _ -> Signal<Never, NoError> in
|
||||||
|
return .complete()
|
||||||
|
}
|
||||||
importSignal = importSignal |> then(importInfoAccounttSignal)
|
importSignal = importSignal |> then(importInfoAccounttSignal)
|
||||||
}
|
}
|
||||||
return importSignal
|
return importSignal
|
||||||
|
@ -29,251 +29,209 @@ private extension SelectFriendsIntent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final class WidgetDataContext {
|
private final class WidgetReloadManager {
|
||||||
private var currentAccount: Account?
|
private var inForeground = false
|
||||||
private var currentAccountDisposable: Disposable?
|
private var inForegroundDisposable: Disposable?
|
||||||
private var widgetPresentationDataDisposable: Disposable?
|
|
||||||
private var notificationPresentationDataDisposable: Disposable?
|
|
||||||
|
|
||||||
init(basePath: String, activeAccount: Signal<Account?, NoError>, presentationData: Signal<PresentationData, NoError>, appLockContext: AppLockContextImpl) {
|
private var isReloadRequested = false
|
||||||
self.currentAccountDisposable = (activeAccount
|
private var lastBackgroundReload: Double?
|
||||||
|> distinctUntilChanged(isEqual: { lhs, rhs in
|
|
||||||
return lhs === rhs
|
|
||||||
})
|
|
||||||
|> mapToSignal { account -> Signal<WidgetData, NoError> in
|
|
||||||
guard let account = account else {
|
|
||||||
return .single(WidgetData(accountId: 0, content: .empty, unlockedForLockId: nil))
|
|
||||||
}
|
|
||||||
|
|
||||||
enum CombinedRecentPeers {
|
init(inForeground: Signal<Bool, NoError>) {
|
||||||
struct Unread {
|
self.inForegroundDisposable = (inForeground
|
||||||
var count: Int32
|
|
||||||
var isMuted: Bool
|
|
||||||
}
|
|
||||||
|
|
||||||
case disabled
|
|
||||||
case peers(peers: [Peer], unread: [PeerId: Unread], messages: [PeerId: WidgetDataPeer.Message])
|
|
||||||
}
|
|
||||||
|
|
||||||
let updatedAdditionalPeerIds: Signal<(Set<PeerId>, Set<String>), NoError> = Signal { subscriber in
|
|
||||||
if #available(iOSApplicationExtension 14.0, iOS 14.0, *) {
|
|
||||||
#if arch(arm64) || arch(i386) || arch(x86_64)
|
|
||||||
WidgetCenter.shared.getCurrentConfigurations({ result in
|
|
||||||
var peerIds = Set<PeerId>()
|
|
||||||
var configurationHashes = Set<String>()
|
|
||||||
if case let .success(infos) = result {
|
|
||||||
for info in infos {
|
|
||||||
if let configuration = info.configuration as? SelectFriendsIntent {
|
|
||||||
if let items = configuration.friends {
|
|
||||||
for item in items {
|
|
||||||
guard let identifier = item.identifier, let peerIdValue = Int64(identifier) else {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
peerIds.insert(PeerId(peerIdValue))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
configurationHashes.insert(configuration.configurationHash)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
subscriber.putNext((peerIds, configurationHashes))
|
|
||||||
subscriber.putCompletion()
|
|
||||||
})
|
|
||||||
#else
|
|
||||||
subscriber.putNext((Set(), Set()))
|
|
||||||
subscriber.putCompletion()
|
|
||||||
#endif
|
|
||||||
} else {
|
|
||||||
subscriber.putNext((Set(), Set()))
|
|
||||||
subscriber.putCompletion()
|
|
||||||
}
|
|
||||||
|
|
||||||
return EmptyDisposable
|
|
||||||
}
|
|
||||||
|> runOn(.mainQueue())
|
|
||||||
|
|
||||||
let unlockedForLockId: Signal<String?, NoError> = .single(nil)
|
|
||||||
|
|
||||||
let sourcePeers: Signal<RecentPeers, NoError> = recentPeers(account: account)
|
|
||||||
|
|
||||||
let recent: Signal<CombinedRecentPeers, NoError> = sourcePeers
|
|
||||||
|> mapToSignal { recent -> Signal<CombinedRecentPeers, NoError> in
|
|
||||||
switch recent {
|
|
||||||
case .disabled:
|
|
||||||
return .single(.disabled)
|
|
||||||
case let .peers(peers):
|
|
||||||
return combineLatest(queue: .mainQueue(), peers.filter { !$0.isDeleted }.map { account.postbox.peerView(id: $0.id)})
|
|
||||||
|> mapToSignal { peerViews -> Signal<CombinedRecentPeers, NoError> in
|
|
||||||
let topMessagesKey: PostboxViewKey = .topChatMessage(peerIds: peerViews.map {
|
|
||||||
$0.peerId
|
|
||||||
})
|
|
||||||
return combineLatest(queue: .mainQueue(),
|
|
||||||
account.postbox.unreadMessageCountsView(items: peerViews.map {
|
|
||||||
.peer($0.peerId)
|
|
||||||
}),
|
|
||||||
account.postbox.combinedView(keys: [topMessagesKey])
|
|
||||||
)
|
|
||||||
|> map { values, combinedView -> CombinedRecentPeers in
|
|
||||||
var peers: [Peer] = []
|
|
||||||
var unread: [PeerId: CombinedRecentPeers.Unread] = [:]
|
|
||||||
var messages: [PeerId: WidgetDataPeer.Message] = [:]
|
|
||||||
|
|
||||||
let topMessages = combinedView.views[topMessagesKey] as! TopChatMessageView
|
|
||||||
|
|
||||||
for peerView in peerViews {
|
|
||||||
if let peer = peerViewMainPeer(peerView) {
|
|
||||||
var isMuted: Bool = false
|
|
||||||
if let notificationSettings = peerView.notificationSettings as? TelegramPeerNotificationSettings {
|
|
||||||
switch notificationSettings.muteState {
|
|
||||||
case .muted:
|
|
||||||
isMuted = true
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let unreadCount = values.count(for: .peer(peerView.peerId))
|
|
||||||
if let unreadCount = unreadCount, unreadCount > 0 {
|
|
||||||
unread[peerView.peerId] = CombinedRecentPeers.Unread(count: Int32(unreadCount), isMuted: isMuted)
|
|
||||||
}
|
|
||||||
|
|
||||||
if let message = topMessages.messages[peerView.peerId] {
|
|
||||||
messages[peerView.peerId] = WidgetDataPeer.Message(message: message)
|
|
||||||
}
|
|
||||||
|
|
||||||
peers.append(peer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return .peers(peers: peers, unread: unread, messages: messages)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let processedRecent = recent
|
|
||||||
|> map { _ -> WidgetData in
|
|
||||||
return WidgetData(accountId: account.id.int64, content: .peers(WidgetDataPeers(accountPeerId: account.peerId.toInt64(), peers: [], updateTimestamp: Int32(Date().timeIntervalSince1970))), unlockedForLockId: nil)
|
|
||||||
}
|
|
||||||
|> distinctUntilChanged
|
|> distinctUntilChanged
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] value in
|
||||||
let additionalPeerIds = Signal<(Set<PeerId>, Set<String>), NoError>.complete() |> then(updatedAdditionalPeerIds)
|
guard let strongSelf = self else {
|
||||||
let processedCustom: Signal<WidgetData, NoError> = additionalPeerIds
|
return
|
||||||
|> distinctUntilChanged(isEqual: { lhs, rhs in
|
}
|
||||||
if lhs.0 != rhs.0 {
|
if strongSelf.inForeground != value {
|
||||||
return false
|
strongSelf.inForeground = value
|
||||||
|
if value {
|
||||||
|
strongSelf.performReloadIfNeeded()
|
||||||
}
|
}
|
||||||
if lhs.1 != rhs.1 {
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
return true
|
|
||||||
})
|
})
|
||||||
|> mapToSignal { additionalPeerIds, _ -> Signal<CombinedRecentPeers, NoError> in
|
|
||||||
return combineLatest(queue: .mainQueue(), additionalPeerIds.map { account.postbox.peerView(id: $0) })
|
|
||||||
|> mapToSignal { peerViews -> Signal<CombinedRecentPeers, NoError> in
|
|
||||||
let topMessagesKey: PostboxViewKey = .topChatMessage(peerIds: peerViews.map {
|
|
||||||
$0.peerId
|
|
||||||
})
|
|
||||||
return combineLatest(queue: .mainQueue(),
|
|
||||||
account.postbox.unreadMessageCountsView(items: peerViews.map {
|
|
||||||
.peer($0.peerId)
|
|
||||||
}),
|
|
||||||
account.postbox.combinedView(keys: [topMessagesKey])
|
|
||||||
)
|
|
||||||
|> map { values, combinedView -> CombinedRecentPeers in
|
|
||||||
var peers: [Peer] = []
|
|
||||||
var unread: [PeerId: CombinedRecentPeers.Unread] = [:]
|
|
||||||
var messages: [PeerId: WidgetDataPeer.Message] = [:]
|
|
||||||
|
|
||||||
let topMessages = combinedView.views[topMessagesKey] as! TopChatMessageView
|
|
||||||
|
|
||||||
for peerView in peerViews {
|
|
||||||
if let peer = peerViewMainPeer(peerView) {
|
|
||||||
var isMuted: Bool = false
|
|
||||||
if let notificationSettings = peerView.notificationSettings as? TelegramPeerNotificationSettings {
|
|
||||||
switch notificationSettings.muteState {
|
|
||||||
case .muted:
|
|
||||||
isMuted = true
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let unreadCount = values.count(for: .peer(peerView.peerId))
|
deinit {
|
||||||
if let unreadCount = unreadCount, unreadCount > 0 {
|
self.inForegroundDisposable?.dispose()
|
||||||
unread[peerView.peerId] = CombinedRecentPeers.Unread(count: Int32(unreadCount), isMuted: isMuted)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let message = topMessages.messages[peerView.peerId] {
|
func requestReload() {
|
||||||
messages[peerView.peerId] = WidgetDataPeer.Message(message: message)
|
self.isReloadRequested = true
|
||||||
}
|
|
||||||
|
|
||||||
peers.append(peer)
|
if self.inForeground {
|
||||||
}
|
self.performReloadIfNeeded()
|
||||||
}
|
} else {
|
||||||
return .peers(peers: peers, unread: unread, messages: messages)
|
let timestamp = CFAbsoluteTimeGetCurrent()
|
||||||
}
|
if let lastBackgroundReloadValue = self.lastBackgroundReload {
|
||||||
}
|
if abs(lastBackgroundReloadValue - timestamp) > 25.0 * 60.0 {
|
||||||
}
|
self.lastBackgroundReload = timestamp
|
||||||
|> map { result -> WidgetData in
|
performReloadIfNeeded()
|
||||||
switch result {
|
|
||||||
case .disabled:
|
|
||||||
return WidgetData(accountId: account.id.int64, content: .empty, unlockedForLockId: nil)
|
|
||||||
case let .peers(peers, unread, messages):
|
|
||||||
return WidgetData(accountId: account.id.int64, content: .peers(WidgetDataPeers(accountPeerId: account.peerId.toInt64(), peers: peers.compactMap { peer -> WidgetDataPeer? in
|
|
||||||
var name: String = ""
|
|
||||||
var lastName: String?
|
|
||||||
|
|
||||||
if let user = peer as? TelegramUser {
|
|
||||||
if let firstName = user.firstName {
|
|
||||||
name = firstName
|
|
||||||
lastName = user.lastName
|
|
||||||
} else if let lastName = user.lastName {
|
|
||||||
name = lastName
|
|
||||||
} else if let phone = user.phone, !phone.isEmpty {
|
|
||||||
name = phone
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
name = peer.debugDisplayTitle
|
self.lastBackgroundReload = timestamp
|
||||||
|
performReloadIfNeeded()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var badge: WidgetDataPeer.Badge?
|
private func performReloadIfNeeded() {
|
||||||
if let unreadValue = unread[peer.id], unreadValue.count > 0 {
|
if !self.isReloadRequested {
|
||||||
badge = WidgetDataPeer.Badge(
|
return
|
||||||
count: Int(unreadValue.count),
|
|
||||||
isMuted: unreadValue.isMuted
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
let message = messages[peer.id]
|
|
||||||
|
|
||||||
return WidgetDataPeer(id: peer.id.toInt64(), name: name, lastName: lastName, letters: peer.displayLetters, avatarPath: smallestImageRepresentation(peer.profileImageRepresentations).flatMap { representation in
|
|
||||||
return account.postbox.mediaBox.resourcePath(representation.resource)
|
|
||||||
}, badge: badge, message: message)
|
|
||||||
}, updateTimestamp: Int32(Date().timeIntervalSince1970))), unlockedForLockId: nil)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|> distinctUntilChanged
|
|
||||||
|
|
||||||
return combineLatest(processedRecent, processedCustom, unlockedForLockId)
|
|
||||||
|> map { processedRecent, _, unlockedForLockId -> WidgetData in
|
|
||||||
var processedRecent = processedRecent
|
|
||||||
processedRecent.unlockedForLockId = unlockedForLockId
|
|
||||||
return processedRecent
|
|
||||||
}
|
|
||||||
}).start(next: { widgetData in
|
|
||||||
let path = basePath + "/widget-data"
|
|
||||||
if let data = try? JSONEncoder().encode(widgetData) {
|
|
||||||
let _ = try? data.write(to: URL(fileURLWithPath: path), options: [.atomic])
|
|
||||||
} else {
|
|
||||||
let _ = try? FileManager.default.removeItem(atPath: path)
|
|
||||||
}
|
}
|
||||||
|
self.isReloadRequested = false
|
||||||
|
|
||||||
|
DispatchQueue.global(qos: .background).async {
|
||||||
if #available(iOSApplicationExtension 14.0, iOS 14.0, *) {
|
if #available(iOSApplicationExtension 14.0, iOS 14.0, *) {
|
||||||
#if arch(arm64) || arch(i386) || arch(x86_64)
|
#if arch(arm64) || arch(i386) || arch(x86_64)
|
||||||
WidgetCenter.shared.reloadAllTimelines()
|
WidgetCenter.shared.reloadAllTimelines()
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final class WidgetDataContext {
|
||||||
|
private let reloadManager: WidgetReloadManager
|
||||||
|
|
||||||
|
private var disposable: Disposable?
|
||||||
|
private var widgetPresentationDataDisposable: Disposable?
|
||||||
|
private var notificationPresentationDataDisposable: Disposable?
|
||||||
|
|
||||||
|
init(basePath: String, inForeground: Signal<Bool, NoError>, activeAccounts: Signal<[Account], NoError>, presentationData: Signal<PresentationData, NoError>, appLockContext: AppLockContextImpl) {
|
||||||
|
self.reloadManager = WidgetReloadManager(inForeground: inForeground)
|
||||||
|
|
||||||
|
let queue = Queue()
|
||||||
|
let updatedAdditionalPeerIds: Signal<[AccountRecordId: Set<PeerId>], NoError> = Signal { subscriber in
|
||||||
|
if #available(iOSApplicationExtension 14.0, iOS 14.0, *) {
|
||||||
|
#if arch(arm64) || arch(i386) || arch(x86_64)
|
||||||
|
WidgetCenter.shared.getCurrentConfigurations { result in
|
||||||
|
var peerIds: [AccountRecordId: Set<PeerId>] = [:]
|
||||||
|
|
||||||
|
func processFriend(_ item: Friend) {
|
||||||
|
guard let identifier = item.identifier else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let index = identifier.firstIndex(of: ":") else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let accountIdValue = Int64(identifier[identifier.startIndex ..< index]) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
guard let peerIdValue = Int64(identifier[identifier.index(after: index)...]) else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let accountId = AccountRecordId(rawValue: accountIdValue)
|
||||||
|
let peerId = PeerId(peerIdValue)
|
||||||
|
if peerIds[accountId] == nil {
|
||||||
|
peerIds[accountId] = Set()
|
||||||
|
}
|
||||||
|
peerIds[accountId]?.insert(peerId)
|
||||||
|
}
|
||||||
|
|
||||||
|
if case let .success(infos) = result {
|
||||||
|
for info in infos {
|
||||||
|
if let configuration = info.configuration as? SelectFriendsIntent {
|
||||||
|
if let items = configuration.friends {
|
||||||
|
for item in items {
|
||||||
|
processFriend(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if let configuration = info.configuration as? SelectAvatarFriendsIntent {
|
||||||
|
if let items = configuration.friends {
|
||||||
|
for item in items {
|
||||||
|
processFriend(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
subscriber.putNext(peerIds)
|
||||||
|
subscriber.putCompletion()
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
subscriber.putNext([:])
|
||||||
|
subscriber.putCompletion()
|
||||||
|
#endif
|
||||||
|
} else {
|
||||||
|
subscriber.putNext([:])
|
||||||
|
subscriber.putCompletion()
|
||||||
|
}
|
||||||
|
|
||||||
|
return EmptyDisposable
|
||||||
|
}
|
||||||
|
|> runOn(queue)
|
||||||
|
|> then(
|
||||||
|
Signal<[AccountRecordId: Set<PeerId>], NoError>.complete()
|
||||||
|
|> delay(10.0, queue: queue)
|
||||||
|
)
|
||||||
|
|> restart
|
||||||
|
|
||||||
|
self.disposable = (combineLatest(queue: queue,
|
||||||
|
updatedAdditionalPeerIds |> distinctUntilChanged,
|
||||||
|
activeAccounts |> distinctUntilChanged(isEqual: { lhs, rhs in
|
||||||
|
if lhs.count != rhs.count {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for i in 0 ..< lhs.count {
|
||||||
|
if lhs[i] !== rhs[i] {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|> mapToSignal { peerIdsByAccount, accounts -> Signal<[WidgetDataPeer], NoError> in
|
||||||
|
var accountSignals: [Signal<[WidgetDataPeer], NoError>] = []
|
||||||
|
|
||||||
|
for (accountId, peerIds) in peerIdsByAccount {
|
||||||
|
var accountValue: Account?
|
||||||
|
for value in accounts {
|
||||||
|
if value.id == accountId {
|
||||||
|
accountValue = value
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
guard let account = accountValue else {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if peerIds.isEmpty {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
let topMessagesKey: PostboxViewKey = .topChatMessage(peerIds: Array(peerIds))
|
||||||
|
|
||||||
|
accountSignals.append(account.postbox.combinedView(keys: [topMessagesKey])
|
||||||
|
|> map { combinedView -> [WidgetDataPeer] in
|
||||||
|
guard let topMessages = combinedView.views[topMessagesKey] as? TopChatMessageView else {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
var result: [WidgetDataPeer] = []
|
||||||
|
for (peerId, message) in topMessages.messages {
|
||||||
|
result.append(WidgetDataPeer(id: peerId.toInt64(), name: "", lastName: "", letters: [], avatarPath: nil, badge: nil, message: WidgetDataPeer.Message(message: message)))
|
||||||
|
}
|
||||||
|
result.sort(by: { lhs, rhs in
|
||||||
|
return lhs.id < rhs.id
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return combineLatest(queue: queue, accountSignals)
|
||||||
|
|> map { lists -> [WidgetDataPeer] in
|
||||||
|
var result: [WidgetDataPeer] = []
|
||||||
|
for list in lists {
|
||||||
|
result.append(contentsOf: list)
|
||||||
|
}
|
||||||
|
result.sort(by: { lhs, rhs in
|
||||||
|
return lhs.id < rhs.id
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|> distinctUntilChanged
|
||||||
|
|> deliverOnMainQueue).start(next: { [weak self] _ in
|
||||||
|
self?.reloadManager.requestReload()
|
||||||
})
|
})
|
||||||
|
|
||||||
self.widgetPresentationDataDisposable = (presentationData
|
self.widgetPresentationDataDisposable = (presentationData
|
||||||
@ -310,6 +268,8 @@ final class WidgetDataContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
self.currentAccountDisposable?.dispose()
|
self.disposable?.dispose()
|
||||||
|
self.widgetPresentationDataDisposable?.dispose()
|
||||||
|
self.notificationPresentationDataDisposable?.dispose()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user