mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-08-17 19:09:56 +00:00

Added support for "contact joined" service messages Updated password recovery API Updated Localization APIs Added limited contact presence polling after getDifference
304 lines
16 KiB
Swift
304 lines
16 KiB
Swift
import Foundation
|
|
#if os(macOS)
|
|
import PostboxMac
|
|
import SwiftSignalKitMac
|
|
import MtProtoKitMac
|
|
#else
|
|
import Postbox
|
|
import SwiftSignalKit
|
|
import MtProtoKitDynamic
|
|
#endif
|
|
|
|
private final class ManagedLocalizationUpdatesOperationsHelper {
|
|
var operationDisposables: [Int32: Disposable] = [:]
|
|
|
|
func update(_ entries: [PeerMergedOperationLogEntry]) -> (disposeOperations: [Disposable], beginOperations: [(PeerMergedOperationLogEntry, MetaDisposable)]) {
|
|
var disposeOperations: [Disposable] = []
|
|
var beginOperations: [(PeerMergedOperationLogEntry, MetaDisposable)] = []
|
|
|
|
var hasRunningOperationForPeerId = Set<PeerId>()
|
|
var validMergedIndices = Set<Int32>()
|
|
for entry in entries {
|
|
if !hasRunningOperationForPeerId.contains(entry.peerId) {
|
|
hasRunningOperationForPeerId.insert(entry.peerId)
|
|
validMergedIndices.insert(entry.mergedIndex)
|
|
|
|
if self.operationDisposables[entry.mergedIndex] == nil {
|
|
let disposable = MetaDisposable()
|
|
beginOperations.append((entry, disposable))
|
|
self.operationDisposables[entry.mergedIndex] = disposable
|
|
}
|
|
}
|
|
}
|
|
|
|
var removeMergedIndices: [Int32] = []
|
|
for (mergedIndex, disposable) in self.operationDisposables {
|
|
if !validMergedIndices.contains(mergedIndex) {
|
|
removeMergedIndices.append(mergedIndex)
|
|
disposeOperations.append(disposable)
|
|
}
|
|
}
|
|
|
|
for mergedIndex in removeMergedIndices {
|
|
self.operationDisposables.removeValue(forKey: mergedIndex)
|
|
}
|
|
|
|
return (disposeOperations, beginOperations)
|
|
}
|
|
|
|
func reset() -> [Disposable] {
|
|
let disposables = Array(self.operationDisposables.values)
|
|
self.operationDisposables.removeAll()
|
|
return disposables
|
|
}
|
|
}
|
|
|
|
private func withTakenOperation(postbox: Postbox, peerId: PeerId, tag: PeerOperationLogTag, tagLocalIndex: Int32, _ f: @escaping (Transaction, PeerMergedOperationLogEntry?) -> Signal<Void, NoError>) -> Signal<Void, NoError> {
|
|
return postbox.transaction { transaction -> Signal<Void, NoError> in
|
|
var result: PeerMergedOperationLogEntry?
|
|
transaction.operationLogUpdateEntry(peerId: peerId, tag: tag, tagLocalIndex: tagLocalIndex, { entry in
|
|
if let entry = entry, let _ = entry.mergedIndex, entry.contents is SynchronizeLocalizationUpdatesOperation {
|
|
result = entry.mergedEntry!
|
|
return PeerOperationLogEntryUpdate(mergedIndex: .none, contents: .none)
|
|
} else {
|
|
return PeerOperationLogEntryUpdate(mergedIndex: .none, contents: .none)
|
|
}
|
|
})
|
|
|
|
return f(transaction, result)
|
|
} |> switchToLatest
|
|
}
|
|
|
|
func managedLocalizationUpdatesOperations(postbox: Postbox, network: Network) -> Signal<Void, NoError> {
|
|
return Signal { _ in
|
|
let tag: PeerOperationLogTag = OperationLogTags.SynchronizeLocalizationUpdates
|
|
|
|
let helper = Atomic<ManagedLocalizationUpdatesOperationsHelper>(value: ManagedLocalizationUpdatesOperationsHelper())
|
|
|
|
let disposable = postbox.mergedOperationLogView(tag: tag, limit: 10).start(next: { view in
|
|
let (disposeOperations, beginOperations) = helper.with { helper -> (disposeOperations: [Disposable], beginOperations: [(PeerMergedOperationLogEntry, MetaDisposable)]) in
|
|
return helper.update(view.entries)
|
|
}
|
|
|
|
for disposable in disposeOperations {
|
|
disposable.dispose()
|
|
}
|
|
|
|
for (entry, disposable) in beginOperations {
|
|
let signal = withTakenOperation(postbox: postbox, peerId: entry.peerId, tag: tag, tagLocalIndex: entry.tagLocalIndex, { transaction, entry -> Signal<Void, NoError> in
|
|
if let entry = entry {
|
|
if let _ = entry.contents as? SynchronizeLocalizationUpdatesOperation {
|
|
return synchronizeLocalizationUpdates(transaction: transaction, postbox: postbox, network: network)
|
|
} else {
|
|
assertionFailure()
|
|
}
|
|
}
|
|
return .complete()
|
|
})
|
|
|> then(postbox.transaction { transaction -> Void in
|
|
let _ = transaction.operationLogRemoveEntry(peerId: entry.peerId, tag: tag, tagLocalIndex: entry.tagLocalIndex)
|
|
})
|
|
|
|
disposable.set(signal.start())
|
|
}
|
|
})
|
|
|
|
return ActionDisposable {
|
|
let disposables = helper.with { helper -> [Disposable] in
|
|
return helper.reset()
|
|
}
|
|
for disposable in disposables {
|
|
disposable.dispose()
|
|
}
|
|
disposable.dispose()
|
|
}
|
|
}
|
|
}
|
|
|
|
private enum SynchronizeLocalizationUpdatesError {
|
|
case done
|
|
case reset
|
|
}
|
|
|
|
func getLocalization(_ transaction: Transaction) -> (primary: (code: String, version: Int32, entries: [LocalizationEntry]), secondary: (code: String, version: Int32, entries: [LocalizationEntry])?) {
|
|
let localizationSettings: LocalizationSettings?
|
|
if let current = transaction.getPreferencesEntry(key: PreferencesKeys.localizationSettings) as? LocalizationSettings {
|
|
localizationSettings = current
|
|
} else {
|
|
localizationSettings = nil
|
|
}
|
|
if let localizationSettings = localizationSettings {
|
|
return (primary: (localizationSettings.primaryComponent.languageCode, localizationSettings.primaryComponent.localization.version, localizationSettings.primaryComponent.localization.entries), secondary: localizationSettings.secondaryComponent.flatMap({ ($0.languageCode, $0.localization.version, $0.localization.entries) }))
|
|
} else {
|
|
return (primary: ("en", 0, []), secondary: nil)
|
|
}
|
|
}
|
|
|
|
private func parseLangPackDifference(_ difference: Api.LangPackDifference) -> (code: String, fromVersion: Int32, version: Int32, entries: [LocalizationEntry]) {
|
|
switch difference {
|
|
case let .langPackDifference(code, fromVersion, version, strings):
|
|
var entries: [LocalizationEntry] = []
|
|
for string in strings {
|
|
switch string {
|
|
case let .langPackString(key, value):
|
|
entries.append(.string(key: key, value: value))
|
|
case let .langPackStringPluralized(_, key, zeroValue, oneValue, twoValue, fewValue, manyValue, otherValue):
|
|
entries.append(.pluralizedString(key: key, zero: zeroValue, one: oneValue, two: twoValue, few: fewValue, many: manyValue, other: otherValue))
|
|
case let .langPackStringDeleted(key):
|
|
entries.append(.string(key: key, value: ""))
|
|
}
|
|
}
|
|
return (code, fromVersion, version, entries)
|
|
}
|
|
}
|
|
|
|
private func synchronizeLocalizationUpdates(transaction: Transaction, postbox: Postbox, network: Network) -> Signal<Void, NoError> {
|
|
let currentLanguageAndVersion = postbox.transaction { transaction -> (primary: (code: String, version: Int32), secondary: (code: String, version: Int32)?) in
|
|
let (primary, secondary) = getLocalization(transaction)
|
|
return ((primary.code, primary.version), secondary.flatMap({ ($0.code, $0.version) }))
|
|
}
|
|
|
|
let poll = currentLanguageAndVersion
|
|
|> introduceError(SynchronizeLocalizationUpdatesError.self)
|
|
|> mapToSignal { (primary, secondary) -> Signal<Void, SynchronizeLocalizationUpdatesError> in
|
|
var differences: [Signal<Api.LangPackDifference, MTRpcError>] = []
|
|
differences.append(network.request(Api.functions.langpack.getDifference(langCode: primary.code, fromVersion: primary.version)))
|
|
if let secondary = secondary {
|
|
differences.append(network.request(Api.functions.langpack.getDifference(langCode: secondary.code, fromVersion: secondary.version)))
|
|
}
|
|
|
|
return combineLatest(differences)
|
|
|> mapError { _ -> SynchronizeLocalizationUpdatesError in return .reset }
|
|
|> mapToSignal { differences -> Signal<Void, SynchronizeLocalizationUpdatesError> in
|
|
let parsedDifferences = differences.map(parseLangPackDifference)
|
|
return postbox.transaction { transaction -> Signal<Void, SynchronizeLocalizationUpdatesError> in
|
|
let (primary, secondary) = getLocalization(transaction)
|
|
|
|
var currentSettings = transaction.getPreferencesEntry(key: PreferencesKeys.localizationSettings) as? LocalizationSettings ?? LocalizationSettings(primaryComponent: LocalizationComponent(languageCode: "en", localizedName: "English", localization: Localization(version: 0, entries: []), customPluralizationCode: nil), secondaryComponent: nil)
|
|
|
|
for difference in parsedDifferences {
|
|
let current: (isPrimary: Bool, entries: [LocalizationEntry])
|
|
if difference.code == primary.code {
|
|
if primary.version != difference.fromVersion {
|
|
return .complete()
|
|
}
|
|
current = (true, primary.entries)
|
|
} else if let secondary = secondary, difference.code == secondary.code {
|
|
if secondary.version != difference.fromVersion {
|
|
return .complete()
|
|
}
|
|
current = (false, secondary.entries)
|
|
} else {
|
|
return .fail(.reset)
|
|
}
|
|
|
|
var updatedEntryKeys = Set<String>()
|
|
for entry in difference.entries {
|
|
updatedEntryKeys.insert(entry.key)
|
|
}
|
|
|
|
var mergedEntries: [LocalizationEntry] = []
|
|
for entry in current.entries {
|
|
if !updatedEntryKeys.contains(entry.key) {
|
|
mergedEntries.append(entry)
|
|
}
|
|
}
|
|
mergedEntries.append(contentsOf: difference.entries)
|
|
if current.isPrimary {
|
|
currentSettings = LocalizationSettings(primaryComponent: LocalizationComponent(languageCode: currentSettings.primaryComponent.languageCode, localizedName: currentSettings.primaryComponent.localizedName, localization: Localization(version: difference.version, entries: mergedEntries), customPluralizationCode: currentSettings.primaryComponent.customPluralizationCode), secondaryComponent: currentSettings.secondaryComponent)
|
|
} else if let currentSecondary = currentSettings.secondaryComponent {
|
|
currentSettings = LocalizationSettings(primaryComponent: currentSettings.primaryComponent, secondaryComponent: LocalizationComponent(languageCode: currentSecondary.languageCode, localizedName: currentSecondary.localizedName, localization: Localization(version: difference.version, entries: mergedEntries), customPluralizationCode: currentSecondary.customPluralizationCode))
|
|
}
|
|
}
|
|
|
|
transaction.setPreferencesEntry(key: PreferencesKeys.localizationSettings, value: currentSettings)
|
|
return .fail(.done)
|
|
}
|
|
|> mapError { _ -> SynchronizeLocalizationUpdatesError in
|
|
return .reset
|
|
}
|
|
|> switchToLatest
|
|
}
|
|
}
|
|
|
|
return ((poll
|
|
|> `catch` { error -> Signal<Void, Void> in
|
|
switch error {
|
|
case .done:
|
|
return .fail(Void())
|
|
case .reset:
|
|
return postbox.transaction { transaction -> Signal<Void, Void> in
|
|
let (primary, _) = getLocalization(transaction)
|
|
return downloadAndApplyLocalization(postbox: postbox, network: network, languageCode: primary.code)
|
|
|> mapError { _ -> Void in
|
|
return Void()
|
|
}
|
|
}
|
|
|> introduceError(Void.self)
|
|
|> switchToLatest
|
|
}
|
|
}) |> restart) |> `catch` { _ -> Signal<Void, NoError> in
|
|
return .complete()
|
|
}
|
|
}
|
|
|
|
func tryApplyingLanguageDifference(transaction: Transaction, langCode: String, difference: Api.LangPackDifference) -> Bool {
|
|
let (primary, secondary) = getLocalization(transaction)
|
|
switch difference {
|
|
case let .langPackDifference(updatedCode, fromVersion, updatedVersion, strings):
|
|
var current: (isPrimary: Bool, version: Int32, entries: [LocalizationEntry])?
|
|
if updatedCode == primary.code {
|
|
current = (true, primary.version, primary.entries)
|
|
} else if let secondary = secondary, secondary.code == updatedCode {
|
|
current = (false, secondary.version, secondary.entries)
|
|
}
|
|
guard let (isPrimary, version, entries) = current else {
|
|
return false
|
|
}
|
|
guard fromVersion == version else {
|
|
return false
|
|
}
|
|
var updatedEntries: [LocalizationEntry] = []
|
|
|
|
for string in strings {
|
|
switch string {
|
|
case let .langPackString(key, value):
|
|
updatedEntries.append(.string(key: key, value: value))
|
|
case let .langPackStringPluralized(_, key, zeroValue, oneValue, twoValue, fewValue, manyValue, otherValue):
|
|
updatedEntries.append(.pluralizedString(key: key, zero: zeroValue, one: oneValue, two: twoValue, few: fewValue, many: manyValue, other: otherValue))
|
|
case let .langPackStringDeleted(key):
|
|
updatedEntries.append(.string(key: key, value: ""))
|
|
}
|
|
}
|
|
|
|
var updatedEntryKeys = Set<String>()
|
|
for entry in updatedEntries {
|
|
updatedEntryKeys.insert(entry.key)
|
|
}
|
|
|
|
var mergedEntries: [LocalizationEntry] = []
|
|
for entry in entries {
|
|
if !updatedEntryKeys.contains(entry.key) {
|
|
mergedEntries.append(entry)
|
|
}
|
|
}
|
|
mergedEntries.append(contentsOf: updatedEntries)
|
|
|
|
let currentSettings = transaction.getPreferencesEntry(key: PreferencesKeys.localizationSettings) as? LocalizationSettings ?? LocalizationSettings(primaryComponent: LocalizationComponent(languageCode: "en", localizedName: "English", localization: Localization(version: 0, entries: []), customPluralizationCode: nil), secondaryComponent: nil)
|
|
|
|
var updatedSettings: LocalizationSettings
|
|
if isPrimary {
|
|
updatedSettings = LocalizationSettings(primaryComponent: LocalizationComponent(languageCode: currentSettings.primaryComponent.languageCode, localizedName: currentSettings.primaryComponent.localizedName, localization: Localization(version: updatedVersion, entries: mergedEntries), customPluralizationCode: currentSettings.primaryComponent.customPluralizationCode), secondaryComponent: currentSettings.secondaryComponent)
|
|
} else if let currentSecondary = currentSettings.secondaryComponent {
|
|
updatedSettings = LocalizationSettings(primaryComponent: currentSettings.primaryComponent, secondaryComponent: LocalizationComponent(languageCode: currentSecondary.languageCode, localizedName: currentSecondary.localizedName, localization: Localization(version: updatedVersion, entries: mergedEntries), customPluralizationCode: currentSecondary.customPluralizationCode))
|
|
} else {
|
|
assertionFailure()
|
|
return false
|
|
}
|
|
|
|
transaction.setPreferencesEntry(key: PreferencesKeys.localizationSettings, value: updatedSettings)
|
|
|
|
return true
|
|
}
|
|
}
|