diff --git a/TelegramCore.xcodeproj/project.pbxproj b/TelegramCore.xcodeproj/project.pbxproj index b6f7bfdc76..a2f8d58cf1 100644 --- a/TelegramCore.xcodeproj/project.pbxproj +++ b/TelegramCore.xcodeproj/project.pbxproj @@ -8,6 +8,12 @@ /* Begin PBXBuildFile section */ 09028386218E5DBB0067EFBD /* ManagedVoipConfigurationUpdates.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09028385218E5DBB0067EFBD /* ManagedVoipConfigurationUpdates.swift */; }; + 0962E66721B59BAA00245FD9 /* ManagedAppConfigurationUpdates.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0962E66621B59BAA00245FD9 /* ManagedAppConfigurationUpdates.swift */; }; + 0962E66921B5A11100245FD9 /* SynchronizeAppLogEventsOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0962E66821B5A11100245FD9 /* SynchronizeAppLogEventsOperation.swift */; }; + 0962E66B21B5A41C00245FD9 /* ManagedSynchronizeAppLogEventsOperations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0962E66A21B5A41C00245FD9 /* ManagedSynchronizeAppLogEventsOperations.swift */; }; + 0962E66D21B5C56F00245FD9 /* JSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0962E66C21B5C56F00245FD9 /* JSON.swift */; }; + 0962E66F21B6147600245FD9 /* AppConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0962E66E21B6147600245FD9 /* AppConfiguration.swift */; }; + 0962E67521B6437600245FD9 /* SplitTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0962E67421B6437600245FD9 /* SplitTest.swift */; }; 9F06831021A40DEC001D8EDB /* NotificationExceptionsList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F06830F21A40DEC001D8EDB /* NotificationExceptionsList.swift */; }; 9F06831121A40DEC001D8EDB /* NotificationExceptionsList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F06830F21A40DEC001D8EDB /* NotificationExceptionsList.swift */; }; 9F10CE8B20613C78002DD61A /* SecretApiLayer73.swift in Sources */ = {isa = PBXBuildFile; fileRef = D018EDFF2044939F00CBB130 /* SecretApiLayer73.swift */; }; @@ -776,6 +782,12 @@ /* Begin PBXFileReference section */ 09028385218E5DBB0067EFBD /* ManagedVoipConfigurationUpdates.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManagedVoipConfigurationUpdates.swift; sourceTree = ""; }; + 0962E66621B59BAA00245FD9 /* ManagedAppConfigurationUpdates.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManagedAppConfigurationUpdates.swift; sourceTree = ""; }; + 0962E66821B5A11100245FD9 /* SynchronizeAppLogEventsOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SynchronizeAppLogEventsOperation.swift; sourceTree = ""; }; + 0962E66A21B5A41C00245FD9 /* ManagedSynchronizeAppLogEventsOperations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManagedSynchronizeAppLogEventsOperations.swift; sourceTree = ""; }; + 0962E66C21B5C56F00245FD9 /* JSON.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSON.swift; sourceTree = ""; }; + 0962E66E21B6147600245FD9 /* AppConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppConfiguration.swift; sourceTree = ""; }; + 0962E67421B6437600245FD9 /* SplitTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitTest.swift; sourceTree = ""; }; 9F06830F21A40DEC001D8EDB /* NotificationExceptionsList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationExceptionsList.swift; sourceTree = ""; }; 9FC8ADAA206BBFF10094F7B4 /* RecentWebSessions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RecentWebSessions.swift; sourceTree = ""; }; C205FEA71EB3B75900455808 /* ExportMessageLink.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExportMessageLink.swift; sourceTree = ""; }; @@ -1242,6 +1254,7 @@ D0FA08BA2046B37900DD23FC /* ContentPrivacySettings.swift */, D026099D20C695AF006C34AC /* Wallpapers.swift */, D051DB13215EC5A300F30F92 /* AppChangelogState.swift */, + 0962E66E21B6147600245FD9 /* AppConfiguration.swift */, ); name = Settings; sourceTree = ""; @@ -1340,6 +1353,8 @@ D0F8C39F2017AF2700236FC5 /* GlobalTelegramCoreConfiguration.swift */, D0E412E9206AD18E00BEE4A2 /* DecryptedResourceData.swift */, C28D3CEF20D3DA900027F4D6 /* DeepLinkInfo.swift */, + 0962E66C21B5C56F00245FD9 /* JSON.swift */, + 0962E67421B6437600245FD9 /* SplitTest.swift */, ); name = Utils; sourceTree = ""; @@ -1501,6 +1516,9 @@ 09028385218E5DBB0067EFBD /* ManagedVoipConfigurationUpdates.swift */, D0529D2321A4123400D7C3C4 /* SynchronizeRecentlyUsedMediaOperations.swift */, D0529D2621A4141800D7C3C4 /* ManagedSynchronizeRecentlyUsedMediaOperations.swift */, + 0962E66621B59BAA00245FD9 /* ManagedAppConfigurationUpdates.swift */, + 0962E66821B5A11100245FD9 /* SynchronizeAppLogEventsOperation.swift */, + 0962E66A21B5A41C00245FD9 /* ManagedSynchronizeAppLogEventsOperations.swift */, ); name = State; sourceTree = ""; @@ -2145,6 +2163,7 @@ D00C7CCF1E3628180080C3D5 /* UpdateCachedChannelParticipants.swift in Sources */, D03B0CB91D62233400955575 /* Either.swift in Sources */, D0D748021E7AE98B00F4B1F6 /* StickerPackInteractiveOperations.swift in Sources */, + 0962E66D21B5C56F00245FD9 /* JSON.swift in Sources */, D03B0CBD1D62234300955575 /* Regex.swift in Sources */, D00BDA191EE593D600C64C5E /* TelegramChannelAdminRights.swift in Sources */, D0B843B91DA7FF30005F29E1 /* NBMetadataCoreTest.m in Sources */, @@ -2254,12 +2273,14 @@ D00422D321677F4500719B67 /* ManagedAccountPresence.swift in Sources */, D03B0D0A1D62255C00955575 /* Holes.swift in Sources */, D05464972073872C002ECC1E /* SecureIdBankStatementValue.swift in Sources */, + 0962E67521B6437600245FD9 /* SplitTest.swift in Sources */, D0B843CB1DA7FF30005F29E1 /* NBPhoneNumberUtil.m in Sources */, D03B0D5E1D631A6900955575 /* Network.swift in Sources */, D0B8438E1DA7D296005F29E1 /* CachedGroupParticipants.swift in Sources */, D0B843BD1DA7FF30005F29E1 /* NBMetadataHelper.m in Sources */, D03B0CF51D62250800955575 /* TelegramMediaContact.swift in Sources */, D03B0CFB1D62250800955575 /* TelegramMediaWebpage.swift in Sources */, + 0962E66B21B5A41C00245FD9 /* ManagedSynchronizeAppLogEventsOperations.swift in Sources */, D09A2FEB1D7CDC320018FB72 /* PeerAccessRestrictionInfo.swift in Sources */, D0E35A101DE49E1C00BC6096 /* OutgoingMessageWithChatContextResult.swift in Sources */, D0E23DDA1E806F7700B9B6D2 /* ManagedSynchronizeMarkFeaturedStickerPacksAsSeenOperations.swift in Sources */, @@ -2267,6 +2288,7 @@ D0BE303A20619EE800FBE6D8 /* SecureIdForm.swift in Sources */, D0448C991E268F9A005A61A7 /* SecretApiLayer46.swift in Sources */, D03DC9131F82F89D001D584C /* RegularChatState.swift in Sources */, + 0962E66721B59BAA00245FD9 /* ManagedAppConfigurationUpdates.swift in Sources */, D0613FCF1E60520700202CDB /* ChannelMembers.swift in Sources */, D0B2F7742052DEF700D3BFB9 /* TelegramDeviceContactImportInfo.swift in Sources */, C2366C891E4F40480097CCFF /* SupportPeerId.swift in Sources */, @@ -2404,6 +2426,7 @@ D0CA3F84207391560042D2B6 /* SecureIdPadding.swift in Sources */, D0FA8BB61E223C16001E855B /* SecretApiLayer8.swift in Sources */, D0B843C71DA7FF30005F29E1 /* NBPhoneNumberDefines.m in Sources */, + 0962E66921B5A11100245FD9 /* SynchronizeAppLogEventsOperation.swift in Sources */, D049EAF51E44DF3300A2CD3A /* AccountState.swift in Sources */, C28725421EF967E700613564 /* NotificationInfoMessageAttribute.swift in Sources */, D03B0D5D1D631A6900955575 /* MultipartFetch.swift in Sources */, @@ -2472,6 +2495,7 @@ D03B0D081D62255C00955575 /* ChannelState.swift in Sources */, D08984F521187ECA00918162 /* NetworkType.swift in Sources */, C251D7431E65E50500283EDE /* StickerSetInstallation.swift in Sources */, + 0962E66F21B6147600245FD9 /* AppConfiguration.swift in Sources */, D07047BA1F3DF75500F6A8D4 /* ConsumePersonalMessageAction.swift in Sources */, D07E413F208A769D00FCA8F0 /* ProxyServersStatuses.swift in Sources */, D0C0B58D1ED9DC5A000F4D2C /* SynchronizeLocalizationUpdatesOperation.swift in Sources */, diff --git a/TelegramCore/Account.swift b/TelegramCore/Account.swift index 00e8184b29..f5fd59b781 100644 --- a/TelegramCore/Account.swift +++ b/TelegramCore/Account.swift @@ -1097,11 +1097,13 @@ public class Account { })) self.managedOperationsDisposable.add(managedConfigurationUpdates(postbox: self.postbox, network: self.network).start()) self.managedOperationsDisposable.add(managedVoipConfigurationUpdates(postbox: self.postbox, network: self.network).start()) + self.managedOperationsDisposable.add(managedAppConfigurationUpdates(postbox: self.postbox, network: self.network).start()) self.managedOperationsDisposable.add(managedTermsOfServiceUpdates(postbox: self.postbox, network: self.network, stateManager: self.stateManager).start()) self.managedOperationsDisposable.add(managedAppChangelog(postbox: self.postbox, network: self.network, stateManager: self.stateManager, appVersion: self.networkArguments.appVersion).start()) self.managedOperationsDisposable.add(managedProxyInfoUpdates(postbox: self.postbox, network: self.network, viewTracker: self.viewTracker).start()) self.managedOperationsDisposable.add(managedLocalizationUpdatesOperations(postbox: self.postbox, network: self.network).start()) self.managedOperationsDisposable.add(managedPendingPeerNotificationSettings(postbox: self.postbox, network: self.network).start()) + self.managedOperationsDisposable.add(managedSynchronizeAppLogEventsOperations(postbox: self.postbox, network: self.network).start()) let storagePreferencesKey: PostboxViewKey = .preferences(keys: Set([PreferencesKeys.cacheStorageSettings])) let mediaBox = postbox.mediaBox diff --git a/TelegramCore/AccountManager.swift b/TelegramCore/AccountManager.swift index 2dece4bb77..e75421650e 100644 --- a/TelegramCore/AccountManager.swift +++ b/TelegramCore/AccountManager.swift @@ -118,8 +118,11 @@ private var declaredEncodables: Void = { declareEncodable(CachedStickerQueryResult.self, f: { CachedStickerQueryResult(decoder: $0) }) declareEncodable(TelegramWallpaper.self, f: { TelegramWallpaper(decoder: $0) }) declareEncodable(SynchronizeMarkAllUnseenPersonalMessagesOperation.self, f: { SynchronizeMarkAllUnseenPersonalMessagesOperation(decoder: $0) }) + declareEncodable(SynchronizeAppLogEventsOperation.self, f: { SynchronizeAppLogEventsOperation(decoder: $0) }) declareEncodable(CachedRecentPeers.self, f: { CachedRecentPeers(decoder: $0) }) declareEncodable(AppChangelogState.self, f: { AppChangelogState(decoder: $0) }) + declareEncodable(AppConfiguration.self, f: { AppConfiguration(decoder: $0) }) + declareEncodable(JSON.self, f: { JSON(decoder: $0) }) return diff --git a/TelegramCore/Api0.swift b/TelegramCore/Api0.swift index 9ad24163bc..8a1f7c1c6f 100644 --- a/TelegramCore/Api0.swift +++ b/TelegramCore/Api0.swift @@ -51,6 +51,12 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[-614138572] = { return Api.account.TmpPassword.parse_tmpPassword($0) } dict[-2103600678] = { return Api.SecureRequiredType.parse_secureRequiredType($0) } dict[41187252] = { return Api.SecureRequiredType.parse_secureRequiredTypeOneOf($0) } + dict[1064139624] = { return Api.JSONValue.parse_jsonNull($0) } + dict[-952869270] = { return Api.JSONValue.parse_jsonBool($0) } + dict[736157604] = { return Api.JSONValue.parse_jsonNumber($0) } + dict[-1222740358] = { return Api.JSONValue.parse_jsonString($0) } + dict[-146520221] = { return Api.JSONValue.parse_jsonArray($0) } + dict[-1715350371] = { return Api.JSONValue.parse_jsonObject($0) } dict[590459437] = { return Api.Photo.parse_photoEmpty($0) } dict[-1673036328] = { return Api.Photo.parse_photo($0) } dict[-1683826688] = { return Api.Chat.parse_chatEmpty($0) } @@ -526,6 +532,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1471006352] = { return Api.PhoneCallDiscardReason.parse_phoneCallDiscardReasonHangup($0) } dict[-84416311] = { return Api.PhoneCallDiscardReason.parse_phoneCallDiscardReasonBusy($0) } dict[-1910892683] = { return Api.NearestDc.parse_nearestDc($0) } + dict[-1059185703] = { return Api.JSONObjectValue.parse_jsonObjectValue($0) } dict[-1916114267] = { return Api.photos.Photos.parse_photos($0) } dict[352657236] = { return Api.photos.Photos.parse_photosSlice($0) } dict[2010127419] = { return Api.contacts.ImportedContacts.parse_importedContacts($0) } @@ -662,7 +669,7 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[1788705589] = { return Api.updates.ChannelDifference.parse_channelDifferenceTooLong($0) } dict[543450958] = { return Api.updates.ChannelDifference.parse_channelDifference($0) } dict[-309659827] = { return Api.channels.AdminLogResults.parse_adminLogResults($0) } - dict[1996904104] = { return Api.InputAppEvent.parse_inputAppEvent($0) } + dict[488313413] = { return Api.InputAppEvent.parse_inputAppEvent($0) } dict[-1148011883] = { return Api.MessageEntity.parse_messageEntityUnknown($0) } dict[-100378723] = { return Api.MessageEntity.parse_messageEntityMention($0) } dict[1868782349] = { return Api.MessageEntity.parse_messageEntityHashtag($0) } @@ -778,6 +785,8 @@ struct Api { _1.serialize(buffer, boxed) case let _1 as Api.SecureRequiredType: _1.serialize(buffer, boxed) + case let _1 as Api.JSONValue: + _1.serialize(buffer, boxed) case let _1 as Api.Photo: _1.serialize(buffer, boxed) case let _1 as Api.Chat: @@ -1112,6 +1121,8 @@ struct Api { _1.serialize(buffer, boxed) case let _1 as Api.NearestDc: _1.serialize(buffer, boxed) + case let _1 as Api.JSONObjectValue: + _1.serialize(buffer, boxed) case let _1 as Api.photos.Photos: _1.serialize(buffer, boxed) case let _1 as Api.contacts.ImportedContacts: diff --git a/TelegramCore/Api1.swift b/TelegramCore/Api1.swift index 58d2849d19..b4245711f1 100644 --- a/TelegramCore/Api1.swift +++ b/TelegramCore/Api1.swift @@ -1242,6 +1242,146 @@ extension Api { } } + } + enum JSONValue: TypeConstructorDescription { + case jsonNull + case jsonBool(value: Api.Bool) + case jsonNumber(value: Double) + case jsonString(value: String) + case jsonArray(value: [Api.JSONValue]) + case jsonObject(value: [Api.JSONObjectValue]) + + func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .jsonNull: + if boxed { + buffer.appendInt32(1064139624) + } + + break + case .jsonBool(let value): + if boxed { + buffer.appendInt32(-952869270) + } + value.serialize(buffer, true) + break + case .jsonNumber(let value): + if boxed { + buffer.appendInt32(736157604) + } + serializeDouble(value, buffer: buffer, boxed: false) + break + case .jsonString(let value): + if boxed { + buffer.appendInt32(-1222740358) + } + serializeString(value, buffer: buffer, boxed: false) + break + case .jsonArray(let value): + if boxed { + buffer.appendInt32(-146520221) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(value.count)) + for item in value { + item.serialize(buffer, true) + } + break + case .jsonObject(let value): + if boxed { + buffer.appendInt32(-1715350371) + } + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(value.count)) + for item in value { + item.serialize(buffer, true) + } + break + } + } + + func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .jsonNull: + return ("jsonNull", []) + case .jsonBool(let value): + return ("jsonBool", [("value", value)]) + case .jsonNumber(let value): + return ("jsonNumber", [("value", value)]) + case .jsonString(let value): + return ("jsonString", [("value", value)]) + case .jsonArray(let value): + return ("jsonArray", [("value", value)]) + case .jsonObject(let value): + return ("jsonObject", [("value", value)]) + } + } + + static func parse_jsonNull(_ reader: BufferReader) -> JSONValue? { + return Api.JSONValue.jsonNull + } + static func parse_jsonBool(_ reader: BufferReader) -> JSONValue? { + var _1: Api.Bool? + if let signature = reader.readInt32() { + _1 = Api.parse(reader, signature: signature) as? Api.Bool + } + let _c1 = _1 != nil + if _c1 { + return Api.JSONValue.jsonBool(value: _1!) + } + else { + return nil + } + } + static func parse_jsonNumber(_ reader: BufferReader) -> JSONValue? { + var _1: Double? + _1 = reader.readDouble() + let _c1 = _1 != nil + if _c1 { + return Api.JSONValue.jsonNumber(value: _1!) + } + else { + return nil + } + } + static func parse_jsonString(_ reader: BufferReader) -> JSONValue? { + var _1: String? + _1 = parseString(reader) + let _c1 = _1 != nil + if _c1 { + return Api.JSONValue.jsonString(value: _1!) + } + else { + return nil + } + } + static func parse_jsonArray(_ reader: BufferReader) -> JSONValue? { + var _1: [Api.JSONValue]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.JSONValue.self) + } + let _c1 = _1 != nil + if _c1 { + return Api.JSONValue.jsonArray(value: _1!) + } + else { + return nil + } + } + static func parse_jsonObject(_ reader: BufferReader) -> JSONValue? { + var _1: [Api.JSONObjectValue]? + if let _ = reader.readInt32() { + _1 = Api.parseVector(reader, elementSignature: 0, elementType: Api.JSONObjectValue.self) + } + let _c1 = _1 != nil + if _c1 { + return Api.JSONValue.jsonObject(value: _1!) + } + else { + return nil + } + } + } enum Photo: TypeConstructorDescription { case photoEmpty(id: Int64) @@ -13428,6 +13568,46 @@ extension Api { } } + } + enum JSONObjectValue: TypeConstructorDescription { + case jsonObjectValue(key: String, value: Api.JSONValue) + + func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { + switch self { + case .jsonObjectValue(let key, let value): + if boxed { + buffer.appendInt32(-1059185703) + } + serializeString(key, buffer: buffer, boxed: false) + value.serialize(buffer, true) + break + } + } + + func descriptionFields() -> (String, [(String, Any)]) { + switch self { + case .jsonObjectValue(let key, let value): + return ("jsonObjectValue", [("key", key), ("value", value)]) + } + } + + static func parse_jsonObjectValue(_ reader: BufferReader) -> JSONObjectValue? { + var _1: String? + _1 = parseString(reader) + var _2: Api.JSONValue? + if let signature = reader.readInt32() { + _2 = Api.parse(reader, signature: signature) as? Api.JSONValue + } + let _c1 = _1 != nil + let _c2 = _2 != nil + if _c1 && _c2 { + return Api.JSONObjectValue.jsonObjectValue(key: _1!, value: _2!) + } + else { + return nil + } + } + } enum InputWebDocument: TypeConstructorDescription { case inputWebDocument(url: String, size: Int32, mimeType: String, attributes: [Api.DocumentAttribute]) @@ -16464,18 +16644,18 @@ extension Api { } enum InputAppEvent: TypeConstructorDescription { - case inputAppEvent(time: Double, type: String, peer: Int64, data: String) + case inputAppEvent(time: Double, type: String, peer: Int64, data: Api.JSONValue) func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) { switch self { case .inputAppEvent(let time, let type, let peer, let data): if boxed { - buffer.appendInt32(1996904104) + buffer.appendInt32(488313413) } serializeDouble(time, buffer: buffer, boxed: false) serializeString(type, buffer: buffer, boxed: false) serializeInt64(peer, buffer: buffer, boxed: false) - serializeString(data, buffer: buffer, boxed: false) + data.serialize(buffer, true) break } } @@ -16494,8 +16674,10 @@ extension Api { _2 = parseString(reader) var _3: Int64? _3 = reader.readInt64() - var _4: String? - _4 = parseString(reader) + var _4: Api.JSONValue? + if let signature = reader.readInt32() { + _4 = Api.parse(reader, signature: signature) as? Api.JSONValue + } let _c1 = _1 != nil let _c2 = _2 != nil let _c3 = _3 != nil diff --git a/TelegramCore/Api3.swift b/TelegramCore/Api3.swift index 07cd56a662..3664bdabfe 100644 --- a/TelegramCore/Api3.swift +++ b/TelegramCore/Api3.swift @@ -3956,24 +3956,6 @@ extension Api { }) } - static func saveAppLog(events: [Api.InputAppEvent]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { - let buffer = Buffer() - buffer.appendInt32(1862465352) - buffer.appendInt32(481674261) - buffer.appendInt32(Int32(events.count)) - for item in events { - item.serialize(buffer, true) - } - return (FunctionDescription(name: "help.saveAppLog", parameters: [("events", events)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in - let reader = BufferReader(buffer) - var result: Api.Bool? - if let signature = reader.readInt32() { - result = Api.parse(reader, signature: signature) as? Api.Bool - } - return result - }) - } - static func getInviteText() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { let buffer = Buffer() buffer.appendInt32(1295590211) @@ -4142,6 +4124,38 @@ extension Api { return result }) } + + static func getAppConfig() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(-1735311088) + + return (FunctionDescription(name: "help.getAppConfig", parameters: []), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.JSONValue? in + let reader = BufferReader(buffer) + var result: Api.JSONValue? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.JSONValue + } + return result + }) + } + + static func saveAppLog(events: [Api.InputAppEvent]) -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { + let buffer = Buffer() + buffer.appendInt32(1862465352) + buffer.appendInt32(481674261) + buffer.appendInt32(Int32(events.count)) + for item in events { + item.serialize(buffer, true) + } + return (FunctionDescription(name: "help.saveAppLog", parameters: [("events", events)]), buffer, DeserializeFunctionResponse { (buffer: Buffer) -> Api.Bool? in + let reader = BufferReader(buffer) + var result: Api.Bool? + if let signature = reader.readInt32() { + result = Api.parse(reader, signature: signature) as? Api.Bool + } + return result + }) + } } struct updates { static func getState() -> (FunctionDescription, Buffer, DeserializeFunctionResponse) { diff --git a/TelegramCore/AppConfiguration.swift b/TelegramCore/AppConfiguration.swift new file mode 100644 index 0000000000..2719561a11 --- /dev/null +++ b/TelegramCore/AppConfiguration.swift @@ -0,0 +1,53 @@ +import Foundation +#if os(macOS) + import PostboxMac +#else + import Postbox +#endif + +public struct AppConfiguration: PreferencesEntry, Equatable { + public var data: JSON? + + public static var defaultValue: AppConfiguration { + return AppConfiguration(data: nil) + } + + init(data: JSON?) { + self.data = data + } + + public init(decoder: PostboxDecoder) { + self.data = decoder.decodeObjectForKey("data", decoder: { JSON(decoder: $0) }) as? JSON + } + + public func encode(_ encoder: PostboxEncoder) { + if let data = self.data { + encoder.encodeObject(data, forKey: "data") + } else { + encoder.encodeNil(forKey: "data") + } + } + + public func isEqual(to: PreferencesEntry) -> Bool { + guard let to = to as? AppConfiguration else { + return false + } + return self == to + } +} + +public func currentAppConfiguration(transaction: Transaction) -> AppConfiguration { + if let entry = transaction.getPreferencesEntry(key: PreferencesKeys.appConfiguration) as? AppConfiguration { + return entry + } else { + return AppConfiguration.defaultValue + } +} + +func updateAppConfiguration(transaction: Transaction, _ f: (AppConfiguration) -> AppConfiguration) { + let current = currentAppConfiguration(transaction: transaction) + let updated = f(current) + if updated != current { + transaction.setPreferencesEntry(key: PreferencesKeys.appConfiguration, value: updated) + } +} diff --git a/TelegramCore/JSON.swift b/TelegramCore/JSON.swift new file mode 100644 index 0000000000..1e97fff39b --- /dev/null +++ b/TelegramCore/JSON.swift @@ -0,0 +1,480 @@ +import Foundation +import Postbox + +public indirect enum JSON: PostboxCoding, Equatable { + case null + case number(Double) + case string(String) + case bool(Bool) + case array([JSON]) + case dictionary([String: JSON]) + + private enum ValueType: Int32 { + case null = 0 + case number = 1 + case string = 2 + case bool = 3 + case array = 4 + case dictionary = 5 + } + + private init?(_ object: Any) { + if let object = object as? JSONValue { + self = object.jsonValue + } else { + return nil + } + } + + public init?(data: Data) { + if let object = try? JSONSerialization.jsonObject(with: data, options: []) { + self.init(object) + } else { + return nil + } + } + + public init?(string: String) { + if let data = string.data(using: .utf8) { + self.init(data: data) + } else { + return nil + } + } + + public init(decoder: PostboxDecoder) { + switch decoder.decodeInt32ForKey("r", orElse: 0) { + case ValueType.null.rawValue: + self = .null + case ValueType.number.rawValue: + self = .number(decoder.decodeDoubleForKey("v", orElse: 0.0)) + case ValueType.string.rawValue: + self = .string(decoder.decodeStringForKey("v", orElse: "")) + case ValueType.bool.rawValue: + self = .bool(decoder.decodeBoolForKey("v", orElse: false)) + case ValueType.array.rawValue: + self = .array(decoder.decodeObjectArrayForKey("v")) + case ValueType.dictionary.rawValue: + self = .dictionary(decoder.decodeObjectDictionaryForKey("v", keyDecoder: { $0.decodeStringForKey("k", orElse: "") + }, valueDecoder: { JSON(decoder: $0) })) + default: + self = .null + } + } + + public func encode(_ encoder: PostboxEncoder) { + switch self { + case .null: + encoder.encodeInt32(ValueType.null.rawValue, forKey: "r") + case let .number(value): + encoder.encodeInt32(ValueType.number.rawValue, forKey: "r") + encoder.encodeDouble(value, forKey: "v") + case let .string(value): + encoder.encodeInt32(ValueType.string.rawValue, forKey: "r") + encoder.encodeString(value, forKey: "v") + case let .bool(value): + encoder.encodeInt32(ValueType.bool.rawValue, forKey: "r") + encoder.encodeBool(value, forKey: "v") + case let .array(value): + encoder.encodeInt32(ValueType.array.rawValue, forKey: "r") + encoder.encodeObjectArray(value, forKey: "v") + case let .dictionary(value): + encoder.encodeInt32(ValueType.dictionary.rawValue, forKey: "r") + encoder.encodeObjectDictionary(value, forKey: "v") { key, encoder in + encoder.encodeString(key, forKey: "k") + } + } + } + + public static func ==(lhs: JSON, rhs: JSON) -> Bool { + switch lhs { + case .null: + if case .null = rhs { + return true + } else { + return false + } + case let .number(value): + if case .number(value) = rhs { + return true + } else { + return false + } + case let .string(value): + if case .string(value) = rhs { + return true + } else { + return false + } + case let .bool(value): + if case .bool(value) = rhs { + return true + } else { + return false + } + case let .array(value): + if case .array(value) = rhs { + return true + } else { + return false + } + case let .dictionary(value): + if case .dictionary(value) = rhs { + return true + } else { + return false + } + } + } + + public enum Index: Comparable { + case array(Int) + case dictionary(DictionaryIndex) + case null + + static public func ==(lhs: Index, rhs: Index) -> Bool { + switch (lhs, rhs) { + case let (.array(lhs), .array(rhs)): + return lhs == rhs + case let (.dictionary(lhs), .dictionary(rhs)): + return lhs == rhs + case (.null, .null): + return true + default: + return false + } + } + + static public func <(lhs: Index, rhs: Index) -> Bool { + switch (lhs, rhs) { + case let (.array(lhs), .array(rhs)): + return lhs < rhs + case let (.dictionary(lhs), .dictionary(rhs)): + return lhs < rhs + default: + return false + } + } + } +} + +extension JSON: Collection { + public var startIndex: Index { + switch self { + case let .array(value): + return .array(value.startIndex) + case let .dictionary(value): + return .dictionary(value.startIndex) + default: + return .null + } + } + + public var endIndex: Index { + switch self { + case let .array(value): + return .array(value.endIndex) + case let .dictionary(value): + return .dictionary(value.endIndex) + default: + return .null + } + } + + public func index(after i: Index) -> Index { + switch (i, self) { + case let (.array(index), .array(value)): + return .array(value.index(after: index)) + case let (.dictionary(index), .dictionary(value)): + return .dictionary(value.index(after: index)) + default: + return .null + } + } + + public subscript (position: Index) -> (String, JSON) { + switch (position, self) { + case let (.array(index), .array(value)): + return (String(index), value[index]) + case let (.dictionary(index), .dictionary(value)): + let (key, value) = value[index] + return (key, value) + default: + return ("", .null) + } + } +} + +public enum JSONKey { + case index(Int) + case key(String) +} + +public protocol JSONSubscriptType { + var jsonKey: JSONKey { get } +} + +extension Int: JSONSubscriptType { + public var jsonKey: JSONKey { + return .index(self) + } +} + +extension String: JSONSubscriptType { + public var jsonKey: JSONKey { + return .key(self) + } +} + +extension JSON { + fileprivate var value: JSONElement { + get { + switch self { + case .null: + return 0 + case let .number(value): + return value + case let .string(value): + return value + case let .bool(value): + return value + case let .array(values): + var array: [JSONElement] = [] + for value in values { + array.append(value.value) + } + return array + case let .dictionary(values): + var dictionary: [String: JSONElement] = [:] + for (key, value) in values { + dictionary[key] = value.value + } + return dictionary + } + } + } +} + +extension JSON { + public subscript(key: JSONSubscriptType) -> JSONElement? { + get { + switch (key.jsonKey, self) { + case let (.index(index), .array(value)): + if value.indices.contains(index) { + return value[index].value + } else { + return nil + } + case let (.key(key), .dictionary(value)): + if let value = value[key] { + return value.value + } else { + return nil + } + default: + return nil + } + } + } +} + +extension JSON: ExpressibleByDictionaryLiteral { + public init(dictionaryLiteral elements: (String, Any)...) { + self = .dictionary(elements.reduce([String: JSON]()) { (dictionary, element) in + var dictionary = dictionary + if let value = JSON(element.1) { + dictionary[element.0] = value + } + return dictionary + }) + } +} + +extension JSON: ExpressibleByArrayLiteral { + public init(arrayLiteral elements: Any...) { + self = .array(elements.compactMap { JSON($0) }) + } +} + +public protocol JSONElement {} +private protocol JSONValue { + var jsonValue: JSON { get } +} + +extension Int: JSONElement, JSONValue { + var jsonValue: JSON { + return .number(Double(self)) + } +} + +extension Int8: JSONElement, JSONValue { + var jsonValue: JSON { + return .number(Double(self)) + } +} + +extension Int16: JSONElement, JSONValue { + var jsonValue: JSON { + return .number(Double(self)) + } +} + +extension Int32: JSONElement, JSONValue { + var jsonValue: JSON { + return .number(Double(self)) + } +} + +extension Int64: JSONElement, JSONValue { + var jsonValue: JSON { + return .number(Double(self)) + } +} + +extension UInt: JSONElement, JSONValue { + var jsonValue: JSON { + return .number(Double(self)) + } +} + +extension UInt8: JSONElement, JSONValue { + var jsonValue: JSON { + return .number(Double(self)) + } +} + +extension UInt16: JSONElement, JSONValue { + var jsonValue: JSON { + return .number(Double(self)) + } +} + +extension UInt32: JSONElement, JSONValue { + var jsonValue: JSON { + return .number(Double(self)) + } +} + +extension UInt64: JSONElement, JSONValue { + var jsonValue: JSON { + return .number(Double(self)) + } +} + +extension Double: JSONElement, JSONValue { + var jsonValue: JSON { + return .number(self) + } +} + +extension String: JSONElement, JSONValue { + var jsonValue: JSON { + return .string(self) + } +} + +extension Bool: JSONElement, JSONValue { + var jsonValue: JSON { + return .bool(self) + } +} + +extension Array: JSONElement where Element == JSONElement { +} + +extension Array: JSONValue where Element == JSONValue { + var jsonValue: JSON { + return .array(self.map { $0.jsonValue }) + } +} + +extension Dictionary: JSONElement where Key == String, Value == JSONElement { +} + +extension Dictionary: JSONValue where Key == String, Value == JSONValue { + var jsonValue: JSON { + return .dictionary(self.mapValues { $0.jsonValue }) + } +} + +private extension Bool { + init(apiBool: Api.Bool) { + switch apiBool { + case .boolTrue: + self.init(true) + case .boolFalse: + self.init(false) + } + } + + var apiBool: Api.Bool { + if self { + return .boolTrue + } else { + return .boolFalse + } + } +} + +extension JSON { + private init?(apiJson: Api.JSONValue, root: Bool) { + switch (apiJson, root) { + case (.jsonNull, false): + self = .null + case let (.jsonNumber(value), false): + self = .number(value) + case let (.jsonString(value), false): + self = .string(value) + case let (.jsonBool(value), false): + self = .bool(Bool(apiBool: value)) + case let (.jsonArray(value), _): + self = .array(value.compactMap { JSON(apiJson: $0, root: false) }) + case let (.jsonObject(value), _): + self = .dictionary(value.reduce([String: JSON]()) { dictionary, value in + var dictionary = dictionary + switch value { + case let .jsonObjectValue(key, value): + if let value = JSON(apiJson: value, root: false) { + dictionary[key] = value + } + } + return dictionary + }) + default: + return nil + } + } + + init?(apiJson: Api.JSONValue) { + self.init(apiJson: apiJson, root: true) + } +} + +private func apiJson(_ json: JSON, root: Bool) -> Api.JSONValue? { + switch (json, root) { + case (.null, false): + return .jsonNull + case let (.number(value), false): + return .jsonNumber(value: value) + case let (.string(value), false): + return .jsonString(value: value) + case let (.bool(value), false): + return .jsonBool(value: value.apiBool) + case let (.array(value), _): + return .jsonArray(value: value.compactMap { apiJson($0, root: false) }) + case let (.dictionary(value), _): + return .jsonObject(value: value.reduce([Api.JSONObjectValue]()) { objectValues, keyAndValue in + var objectValues = objectValues + if let value = apiJson(keyAndValue.value, root: false) { + objectValues.append(.jsonObjectValue(key: keyAndValue.key, value: value)) + } + return objectValues + }) + default: + return nil + } +} + +func apiJson(_ json: JSON) -> Api.JSONValue? { + return apiJson(json, root: true) +} diff --git a/TelegramCore/ManagedAppConfigurationUpdates.swift b/TelegramCore/ManagedAppConfigurationUpdates.swift new file mode 100644 index 0000000000..1c6d975ffc --- /dev/null +++ b/TelegramCore/ManagedAppConfigurationUpdates.swift @@ -0,0 +1,29 @@ +import Foundation +#if os(macOS) + import PostboxMac + import SwiftSignalKitMac + import MtProtoKitMac +#else + import Postbox + import SwiftSignalKit + import MtProtoKitDynamic +#endif + +func managedAppConfigurationUpdates(postbox: Postbox, network: Network) -> Signal { + let poll = Signal { subscriber in + return (network.request(Api.functions.help.getAppConfig()) + |> retryRequest + |> mapToSignal { result -> Signal in + return postbox.transaction { transaction -> Void in + if let data = JSON(apiJson: result) { + updateAppConfiguration(transaction: transaction, { configuration -> AppConfiguration in + var configuration = configuration + configuration.data = data + return configuration + }) + } + } + }).start() + } + return (poll |> then(.complete() |> suspendAwareDelay(12.0 * 60.0 * 60.0, queue: Queue.concurrentDefaultQueue()))) |> restart +} diff --git a/TelegramCore/ManagedSynchronizeAppLogEventsOperations.swift b/TelegramCore/ManagedSynchronizeAppLogEventsOperations.swift new file mode 100644 index 0000000000..2ac3991e6a --- /dev/null +++ b/TelegramCore/ManagedSynchronizeAppLogEventsOperations.swift @@ -0,0 +1,143 @@ +import Foundation +#if os(macOS) + import PostboxMac + import SwiftSignalKitMac + import MtProtoKitMac +#else + import Postbox + import SwiftSignalKit + import MtProtoKitDynamic +#endif + +private final class ManagedSynchronizeAppLogEventsOperationsHelper { + var operationDisposables: [Int32: Disposable] = [:] + + func update(_ entries: [PeerMergedOperationLogEntry]) -> (disposeOperations: [Disposable], beginOperations: [(PeerMergedOperationLogEntry, MetaDisposable)]) { + var disposeOperations: [Disposable] = [] + var beginOperations: [(PeerMergedOperationLogEntry, MetaDisposable)] = [] + + var hasRunningOperationForPeerId = Set() + var validMergedIndices = Set() + 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) -> Signal { + return postbox.transaction { transaction -> Signal in + var result: PeerMergedOperationLogEntry? + transaction.operationLogUpdateEntry(peerId: peerId, tag: tag, tagLocalIndex: tagLocalIndex, { entry in + if let entry = entry, let _ = entry.mergedIndex, entry.contents is SynchronizeAppLogEventsOperation { + result = entry.mergedEntry! + return PeerOperationLogEntryUpdate(mergedIndex: .none, contents: .none) + } else { + return PeerOperationLogEntryUpdate(mergedIndex: .none, contents: .none) + } + }) + + return f(transaction, result) + } |> switchToLatest +} + +func managedSynchronizeAppLogEventsOperations(postbox: Postbox, network: Network) -> Signal { + return Signal { _ in + let tag: PeerOperationLogTag = OperationLogTags.SynchronizeAppLogEvents + + let helper = Atomic(value: ManagedSynchronizeAppLogEventsOperationsHelper()) + + let peerId = PeerId(namespace: 0, id: 0) + let _ = (postbox.transaction({ t in + t.operationLogRemoveAllEntries(peerId: peerId, tag: tag) + })).start() + + let disposable = postbox.mergedOperationLogView(tag: tag, limit: 50).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 in + if let entry = entry { + if let operation = entry.contents as? SynchronizeAppLogEventsOperation { + return synchronizeAppLogEvents(transaction: transaction, postbox: postbox, network: network, operations: [operation]) + } 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 func synchronizeAppLogEvents(transaction: Transaction, postbox: Postbox, network: Network, operations: [SynchronizeAppLogEventsOperation]) -> Signal { + var events: [Api.InputAppEvent] = [] + for operation in operations { + switch operation.content { + case let .add(time, type, peerId, data): + if let data = apiJson(data) { + events.append(.inputAppEvent(time: time, type: type, peer: peerId?.toInt64() ?? 0, data: data)) + } + default: + break + } + } + + return network.request(Api.functions.help.saveAppLog(events: events)) + |> `catch` { _ -> Signal in + return .complete() + } + |> mapToSignal { _ -> Signal in + return .complete() + } +} diff --git a/TelegramCore/ManagedSynchronizePinnedChatsOperations.swift b/TelegramCore/ManagedSynchronizePinnedChatsOperations.swift index 82a70fe2b3..c6be51def0 100644 --- a/TelegramCore/ManagedSynchronizePinnedChatsOperations.swift +++ b/TelegramCore/ManagedSynchronizePinnedChatsOperations.swift @@ -93,9 +93,9 @@ func managedSynchronizePinnedChatsOperations(postbox: Postbox, network: Network, } return .complete() }) - |> then(postbox.transaction { transaction -> Void in - let _ = transaction.operationLogRemoveEntry(peerId: entry.peerId, tag: OperationLogTags.SynchronizePinnedChats, tagLocalIndex: entry.tagLocalIndex) - }) + |> then(postbox.transaction { transaction -> Void in + let _ = transaction.operationLogRemoveEntry(peerId: entry.peerId, tag: OperationLogTags.SynchronizePinnedChats, tagLocalIndex: entry.tagLocalIndex) + }) disposable.set((signal |> delay(2.0, queue: Queue.concurrentDefaultQueue())).start()) } diff --git a/TelegramCore/Namespaces.swift b/TelegramCore/Namespaces.swift index de2d804107..fdc54f48e9 100644 --- a/TelegramCore/Namespaces.swift +++ b/TelegramCore/Namespaces.swift @@ -126,6 +126,7 @@ public struct OperationLogTags { static let SynchronizeGroupedPeers = PeerOperationLogTag(value: 15) static let SynchronizeMarkAllUnseenPersonalMessages = PeerOperationLogTag(value: 16) static let SynchronizeRecentlyUsedStickers = PeerOperationLogTag(value: 17) + static let SynchronizeAppLogEvents = PeerOperationLogTag(value: 18) } public extension PeerSummaryCounterTags { @@ -148,6 +149,7 @@ private enum PreferencesKeyValues: Int32 { case voipConfiguration = 11 case appChangelogState = 12 case localizationListState = 13 + case appConfiguration = 14 } public func applicationSpecificPreferencesKey(_ value: Int32) -> ValueBoxKey { @@ -234,6 +236,12 @@ public struct PreferencesKeys { key.setInt32(0, value: PreferencesKeyValues.localizationListState.rawValue) return key }() + + public static let appConfiguration: ValueBoxKey = { + let key = ValueBoxKey(length: 4) + key.setInt32(0, value: PreferencesKeyValues.appConfiguration.rawValue) + return key + }() } private enum SharedDataKeyValues: Int32 { diff --git a/TelegramCore/PeerPhotoUpdater.swift b/TelegramCore/PeerPhotoUpdater.swift index 6d8fe7bb4f..655d182175 100644 --- a/TelegramCore/PeerPhotoUpdater.swift +++ b/TelegramCore/PeerPhotoUpdater.swift @@ -68,26 +68,26 @@ public func updatePeerPhotoInternal(postbox: Postbox, network: Network, stateMan |> mapError {_ in return UploadPeerPhotoError.generic} |> mapToSignal { photo -> Signal<(UpdatePeerPhotoStatus, MediaResource?), UploadPeerPhotoError> in - let representations:[TelegramMediaImageRepresentation] + 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 { - postbox.mediaBox.storeResourceData(representation.resource.id, data: data) + 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 { + postbox.mediaBox.storeResourceData(representation.resource.id, data: data) + } } } - } - + } } return postbox.transaction { transaction -> (UpdatePeerPhotoStatus, MediaResource?) in diff --git a/TelegramCore/Serialization.swift b/TelegramCore/Serialization.swift index 9f0fa9fe29..8dfd1361ea 100644 --- a/TelegramCore/Serialization.swift +++ b/TelegramCore/Serialization.swift @@ -202,7 +202,7 @@ public class BoxedMessage: NSObject { public class Serialization: NSObject, MTSerialization { public func currentLayer() -> UInt { - return 90 + return 91 } public func parseMessage(_ data: Data!) -> Any! { diff --git a/TelegramCore/SplitTest.swift b/TelegramCore/SplitTest.swift new file mode 100644 index 0000000000..9ae33f4dbe --- /dev/null +++ b/TelegramCore/SplitTest.swift @@ -0,0 +1,29 @@ +import Foundation +import Postbox + +public protocol SplitTestEvent: RawRepresentable where RawValue == String { +} + +public protocol SplitTestConfiguration { + static var defaultValue: Self { get } +} + +public protocol SplitTest { + associatedtype Configuration: SplitTestConfiguration + associatedtype Event: SplitTestEvent + + var postbox: Postbox { get } + var bucket: String? { get } + var configuration: Configuration { get } + + init(postbox: Postbox, bucket: String?, configuration: Configuration) +} + +extension SplitTest { + public func addEvent(_ event: Self.Event, data: JSON = []) { + if let bucket = self.bucket { + //TODO: merge additional data + addAppLogEvent(postbox: self.postbox, time: Date().timeIntervalSince1970, type: event.rawValue, peerId: nil, data: ["bucket": bucket]) + } + } +} diff --git a/TelegramCore/SynchronizeAppLogEventsOperation.swift b/TelegramCore/SynchronizeAppLogEventsOperation.swift new file mode 100644 index 0000000000..d3fcac2a98 --- /dev/null +++ b/TelegramCore/SynchronizeAppLogEventsOperation.swift @@ -0,0 +1,98 @@ +import Foundation +#if os(macOS) + import PostboxMac + import SwiftSignalKitMac + import MtProtoKitMac +#else + import Postbox + import SwiftSignalKit + import MtProtoKitDynamic +#endif + +private enum SynchronizeAppLogEventsOperationContentType: Int32 { + case add + case sync +} + +enum SynchronizeAppLogEventsOperationContent: PostboxCoding { + case add(time: Double, type: String, peerId: PeerId?, data: JSON) + case sync + + init(decoder: PostboxDecoder) { + switch decoder.decodeInt32ForKey("r", orElse: 0) { + case SynchronizeAppLogEventsOperationContentType.add.rawValue: + var peerId: PeerId? + if let id = decoder.decodeOptionalInt64ForKey("p") { + peerId = PeerId(id) + } + self = .add(time: decoder.decodeDoubleForKey("tm", orElse: 0.0), type: decoder.decodeStringForKey("t", orElse: ""), peerId: peerId, data: decoder.decodeObjectForKey("d", decoder: { JSON(decoder: $0) }) as! JSON) + case SynchronizeAppLogEventsOperationContentType.sync.rawValue: + self = .sync + default: + assertionFailure() + self = .sync + } + } + + func encode(_ encoder: PostboxEncoder) { + switch self { + case let .add(time, type, peerId, data): + encoder.encodeInt32(SynchronizeAppLogEventsOperationContentType.add.rawValue, forKey: "r") + encoder.encodeDouble(time, forKey: "tm") + encoder.encodeString(type, forKey: "t") + if let peerId = peerId { + encoder.encodeInt64(peerId.toInt64(), forKey: "p") + } else { + encoder.encodeNil(forKey: "p") + } + encoder.encodeObject(data, forKey: "d") + case .sync: + encoder.encodeInt32(SynchronizeAppLogEventsOperationContentType.sync.rawValue, forKey: "r") + } + } +} + +final class SynchronizeAppLogEventsOperation: PostboxCoding { + let content: SynchronizeAppLogEventsOperationContent + + init(content: SynchronizeAppLogEventsOperationContent) { + self.content = content + } + + init(decoder: PostboxDecoder) { + self.content = decoder.decodeObjectForKey("c", decoder: { SynchronizeAppLogEventsOperationContent(decoder: $0) }) as! SynchronizeAppLogEventsOperationContent + } + + func encode(_ encoder: PostboxEncoder) { + encoder.encodeObject(self.content, forKey: "c") + } +} + +public func addAppLogEvent(postbox: Postbox, time: Double, type: String, peerId: PeerId?, data: JSON) { + let tag: PeerOperationLogTag = OperationLogTags.SynchronizeAppLogEvents + let peerId = PeerId(namespace: 0, id: 0) + let _ = (postbox.transaction { transaction in + transaction.operationLogAddEntry(peerId: peerId, tag: tag, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: SynchronizeAppLogEventsOperation(content: .add(time: time, type: type, peerId: peerId, data: data))) + }).start() +} + +public func invokeAppLogEventsSynchronization(postbox: Postbox) { + let tag: PeerOperationLogTag = OperationLogTags.SynchronizeAppLogEvents + let peerId = PeerId(namespace: 0, id: 0) + + let _ = (postbox.transaction { transaction in + var topOperation: (SynchronizeSavedStickersOperation, Int32)? + transaction.operationLogEnumerateEntries(peerId: peerId, tag: tag, { entry in + if let operation = entry.contents as? SynchronizeSavedStickersOperation, case .sync = operation.content { + topOperation = (operation, entry.tagLocalIndex) + } + return false + }) + + if let (_, topLocalIndex) = topOperation { + let _ = transaction.operationLogRemoveEntry(peerId: peerId, tag: tag, tagLocalIndex: topLocalIndex) + } + + transaction.operationLogAddEntry(peerId: peerId, tag: tag, tagLocalIndex: .automatic, tagMergedIndex: .automatic, contents: SynchronizeAppLogEventsOperation(content: .sync)) + }).start() +}