diff --git a/TelegramCore.xcodeproj/project.pbxproj b/TelegramCore.xcodeproj/project.pbxproj index b537c02847..6fdccda6d2 100644 --- a/TelegramCore.xcodeproj/project.pbxproj +++ b/TelegramCore.xcodeproj/project.pbxproj @@ -91,6 +91,10 @@ D01D6BFA1E42A718006151C6 /* SearchStickers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01D6BF81E42A713006151C6 /* SearchStickers.swift */; }; D021E0DF1DB539FC00C6B04F /* StickerPack.swift in Sources */ = {isa = PBXBuildFile; fileRef = D021E0DE1DB539FC00C6B04F /* StickerPack.swift */; }; D021E0E21DB5401A00C6B04F /* StickerManagement.swift in Sources */ = {isa = PBXBuildFile; fileRef = D021E0E11DB5401A00C6B04F /* StickerManagement.swift */; }; + D0223A981EA564BD00211D94 /* MediaResourceNetworkStatsTag.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0223A971EA564BD00211D94 /* MediaResourceNetworkStatsTag.swift */; }; + D0223A991EA564BD00211D94 /* MediaResourceNetworkStatsTag.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0223A971EA564BD00211D94 /* MediaResourceNetworkStatsTag.swift */; }; + D0223A9B1EA5654D00211D94 /* TelegramMediaResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0223A9A1EA5654D00211D94 /* TelegramMediaResource.swift */; }; + D0223A9C1EA5654D00211D94 /* TelegramMediaResource.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0223A9A1EA5654D00211D94 /* TelegramMediaResource.swift */; }; D02ABC7B1E30058F00CAE539 /* DeleteMessagesInteractively.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02ABC7A1E30058F00CAE539 /* DeleteMessagesInteractively.swift */; }; D02ABC7C1E30058F00CAE539 /* DeleteMessagesInteractively.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02ABC7A1E30058F00CAE539 /* DeleteMessagesInteractively.swift */; }; D02ABC7E1E3109F000CAE539 /* CloudChatRemoveMessagesOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02ABC7D1E3109F000CAE539 /* CloudChatRemoveMessagesOperation.swift */; }; @@ -472,6 +476,10 @@ D0F7B1EB1E045C87007EB8A5 /* ResolvePeerByName.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F3CC7C1DDE289E008148FA /* ResolvePeerByName.swift */; }; D0F7B1EC1E045C87007EB8A5 /* SearchPeers.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07827BA1E00451F00071108 /* SearchPeers.swift */; }; D0FA0ABD1E76C908005BB9B7 /* TwoStepVerification.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FA0ABC1E76C908005BB9B7 /* TwoStepVerification.swift */; }; + D0FA35051EA6135D00E56FFA /* CacheStorageSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FA35041EA6135D00E56FFA /* CacheStorageSettings.swift */; }; + D0FA35061EA6135D00E56FFA /* CacheStorageSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FA35041EA6135D00E56FFA /* CacheStorageSettings.swift */; }; + D0FA35081EA632E400E56FFA /* CollectCacheUsageStats.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FA35071EA632E400E56FFA /* CollectCacheUsageStats.swift */; }; + D0FA35091EA632E400E56FFA /* CollectCacheUsageStats.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FA35071EA632E400E56FFA /* CollectCacheUsageStats.swift */; }; D0FA8B981E1E955C001E855B /* SecretChatOutgoingOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FA8B971E1E955C001E855B /* SecretChatOutgoingOperation.swift */; }; D0FA8B991E1E955C001E855B /* SecretChatOutgoingOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FA8B971E1E955C001E855B /* SecretChatOutgoingOperation.swift */; }; D0FA8B9E1E1F973B001E855B /* SecretChatIncomingEncryptedOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FA8B9D1E1F973B001E855B /* SecretChatIncomingEncryptedOperation.swift */; }; @@ -546,6 +554,8 @@ D01D6BF81E42A713006151C6 /* SearchStickers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchStickers.swift; sourceTree = ""; }; D021E0DE1DB539FC00C6B04F /* StickerPack.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StickerPack.swift; sourceTree = ""; }; D021E0E11DB5401A00C6B04F /* StickerManagement.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StickerManagement.swift; sourceTree = ""; }; + D0223A971EA564BD00211D94 /* MediaResourceNetworkStatsTag.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaResourceNetworkStatsTag.swift; sourceTree = ""; }; + D0223A9A1EA5654D00211D94 /* TelegramMediaResource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TelegramMediaResource.swift; sourceTree = ""; }; D02ABC7A1E30058F00CAE539 /* DeleteMessagesInteractively.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeleteMessagesInteractively.swift; sourceTree = ""; }; D02ABC7D1E3109F000CAE539 /* CloudChatRemoveMessagesOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CloudChatRemoveMessagesOperation.swift; sourceTree = ""; }; D02ABC801E310E5D00CAE539 /* ManagedCloudChatRemoveMessagesOperations.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedCloudChatRemoveMessagesOperations.swift; sourceTree = ""; }; @@ -764,6 +774,8 @@ D0F7AB2B1DCE889D009AD9A1 /* EditedMessageAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditedMessageAttribute.swift; sourceTree = ""; }; D0F7AB2E1DCF507E009AD9A1 /* ReplyMarkupMessageAttribute.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReplyMarkupMessageAttribute.swift; sourceTree = ""; }; D0FA0ABC1E76C908005BB9B7 /* TwoStepVerification.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TwoStepVerification.swift; sourceTree = ""; }; + D0FA35041EA6135D00E56FFA /* CacheStorageSettings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CacheStorageSettings.swift; sourceTree = ""; }; + D0FA35071EA632E400E56FFA /* CollectCacheUsageStats.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CollectCacheUsageStats.swift; sourceTree = ""; }; D0FA8B971E1E955C001E855B /* SecretChatOutgoingOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecretChatOutgoingOperation.swift; sourceTree = ""; }; D0FA8B9D1E1F973B001E855B /* SecretChatIncomingEncryptedOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecretChatIncomingEncryptedOperation.swift; sourceTree = ""; }; D0FA8BA01E1F99E1001E855B /* SecretChatFileReference.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SecretChatFileReference.swift; sourceTree = ""; }; @@ -823,6 +835,7 @@ D01B27A11E394D8B0022A4C0 /* PrivacySettings.swift */, D08774FD1E3E3A3500A97350 /* GlobalNotificationSettings.swift */, D05A32E61E6F0B5C002760B4 /* RecentAccountSession.swift */, + D0FA35041EA6135D00E56FFA /* CacheStorageSettings.swift */, ); name = Settings; sourceTree = ""; @@ -1054,7 +1067,9 @@ D03B0D121D62257600955575 /* Resources */ = { isa = PBXGroup; children = ( + D0223A9A1EA5654D00211D94 /* TelegramMediaResource.swift */, D03B0D431D6319F900955575 /* CloudFileMediaResource.swift */, + D0223A971EA564BD00211D94 /* MediaResourceNetworkStatsTag.swift */, D03B0D391D6319E200955575 /* Fetch.swift */, D0E35A0D1DE4953E00BC6096 /* FetchHttpResource.swift */, D0448CA11E291B14005A61A7 /* FetchSecretFileResource.swift */, @@ -1127,6 +1142,7 @@ C239BE961E62EE1E00C2C453 /* LoadMessagesIfNecessary.swift */, D0528E5F1E65B94E00E2FEF5 /* SingleMessageView.swift */, D0528E691E65DD2100E2FEF5 /* WebpagePreview.swift */, + D0FA35071EA632E400E56FFA /* CollectCacheUsageStats.swift */, ); name = Messages; sourceTree = ""; @@ -1623,6 +1639,7 @@ D019B1CC1E2E3B6A00F80DB3 /* SecretChatRekeySession.swift in Sources */, D03B0CD91D62245B00955575 /* PeerUtils.swift in Sources */, D03B0CE41D62249F00955575 /* TextEntitiesMessageAttribute.swift in Sources */, + D0FA35081EA632E400E56FFA /* CollectCacheUsageStats.swift in Sources */, D049EAEB1E44B71B00A2CD3A /* RecentlySearchedPeerIds.swift in Sources */, D03B0CD31D62244300955575 /* Namespaces.swift in Sources */, D01D6BF91E42A713006151C6 /* SearchStickers.swift in Sources */, @@ -1644,6 +1661,7 @@ D0448C8E1E22993C005A61A7 /* ProcessSecretChatIncomingDecryptedOperations.swift in Sources */, D0448C911E251F96005A61A7 /* SecretChatEncryption.swift in Sources */, D0FA8BA11E1F99E1001E855B /* SecretChatFileReference.swift in Sources */, + D0223A981EA564BD00211D94 /* MediaResourceNetworkStatsTag.swift in Sources */, D07827BB1E00451F00071108 /* SearchPeers.swift in Sources */, D0DC354E1DE368F7000195EB /* RequestChatContextResults.swift in Sources */, D0BC38771E40BAAA0044D6FE /* ManagedSynchronizePinnedChatsOperations.swift in Sources */, @@ -1662,6 +1680,7 @@ D03B0D0F1D62255C00955575 /* UpdateMessageService.swift in Sources */, D03B0CF61D62250800955575 /* TelegramMediaFile.swift in Sources */, D03B0CE81D6224AD00955575 /* ViewCountMessageAttribute.swift in Sources */, + D0FA35051EA6135D00E56FFA /* CacheStorageSettings.swift in Sources */, D03B0D0C1D62255C00955575 /* AccountStateManagementUtils.swift in Sources */, D073CE5D1DCB97F6007511FD /* ForwardSourceInfoAttribute.swift in Sources */, D0FA8B9E1E1F973B001E855B /* SecretChatIncomingEncryptedOperation.swift in Sources */, @@ -1677,6 +1696,7 @@ D03B0D5C1D631A6900955575 /* Download.swift in Sources */, D01749591E1092BC0057C89A /* RequestStartBot.swift in Sources */, D01B27A21E394D8B0022A4C0 /* PrivacySettings.swift in Sources */, + D0223A9B1EA5654D00211D94 /* TelegramMediaResource.swift in Sources */, D017495E1E118F790057C89A /* AccountStateManager.swift in Sources */, D0FA8BB61E223C16001E855B /* SecretApiLayer8.swift in Sources */, D0B843C71DA7FF30005F29E1 /* NBPhoneNumberDefines.m in Sources */, @@ -1789,6 +1809,7 @@ D03C53711DAD5CA9004C17B3 /* CachedGroupParticipants.swift in Sources */, C2366C841E4F3EAA0097CCFF /* GroupReturnAndLeft.swift in Sources */, D03C53671DAD5CA9004C17B3 /* ApiUtils.swift in Sources */, + D0223A9C1EA5654D00211D94 /* TelegramMediaResource.swift in Sources */, D0AAD1B91E326FE200D5B9DE /* ManagedAutoremoveMessageOperations.swift in Sources */, D001F3F21E128A1C007A8C60 /* UpdateGroup.swift in Sources */, D0F7B1EB1E045C87007EB8A5 /* ResolvePeerByName.swift in Sources */, @@ -1828,6 +1849,7 @@ D0F3A8A61E82C94C00B4C64C /* SynchronizeableChatInputState.swift in Sources */, D00D34431E6EDD2E0057B307 /* ManagedSynchronizeConsumeMessageContentsOperations.swift in Sources */, D049EAE91E44B67100A2CD3A /* RecentPeerItem.swift in Sources */, + D0FA35091EA632E400E56FFA /* CollectCacheUsageStats.swift in Sources */, D001F3F31E128A1C007A8C60 /* UpdateMessageService.swift in Sources */, D0C50E351E93A86600F62E39 /* CallSessionManager.swift in Sources */, D0B8442D1DAB91E0005F29E1 /* NBMetadataCoreTest.m in Sources */, @@ -1885,6 +1907,7 @@ D0DC35511DE36908000195EB /* RequestChatContextResults.swift in Sources */, D0F7B1EC1E045C87007EB8A5 /* SearchPeers.swift in Sources */, D001F3EF1E128A1C007A8C60 /* AccountIntermediateState.swift in Sources */, + D0223A991EA564BD00211D94 /* MediaResourceNetworkStatsTag.swift in Sources */, D00C7CCD1E3620C30080C3D5 /* CachedChannelParticipants.swift in Sources */, D03C536E1DAD5CA9004C17B3 /* PhoneNumber.swift in Sources */, D0BC387C1E40D2880044D6FE /* TogglePeerChatPinned.swift in Sources */, @@ -1894,6 +1917,7 @@ D0BEAF5E1E54941B00BD963D /* Authorization.swift in Sources */, D073CEA41DCBF3EA007511FD /* MultipartUpload.swift in Sources */, D03C53701DAD5CA9004C17B3 /* ExportedInvitation.swift in Sources */, + D0FA35061EA6135D00E56FFA /* CacheStorageSettings.swift in Sources */, D08F4A671E79CC4A00A2AA15 /* SynchronizeInstalledStickerPacksOperations.swift in Sources */, D05A32E81E6F0B5C002760B4 /* RecentAccountSession.swift in Sources */, D0F7B1E31E045C7B007EB8A5 /* RichText.swift in Sources */, diff --git a/TelegramCore/Account.swift b/TelegramCore/Account.swift index 379eb05dab..597ca6bc8c 100644 --- a/TelegramCore/Account.swift +++ b/TelegramCore/Account.swift @@ -145,7 +145,7 @@ public class UnauthorizedAccount { postbox.removeKeychainEntryForKey(key) }) - return initializedNetwork(apiId: self.apiId, supplementary: false, datacenterId: Int(masterDatacenterId), keychain: keychain, networkUsageInfoPath: accountNetworkUsageInfoPath(basePath: self.basePath), testingEnvironment: self.testingEnvironment) + return initializedNetwork(apiId: self.apiId, supplementary: false, datacenterId: Int(masterDatacenterId), keychain: keychain, basePath: self.basePath, testingEnvironment: self.testingEnvironment) |> map { network in let updated = UnauthorizedAccount(apiId: self.apiId, id: self.id, appGroupPath: self.appGroupPath, basePath: self.basePath, testingEnvironment: self.testingEnvironment, postbox: self.postbox, network: network) updated.shouldBeServiceTaskMaster.set(self.shouldBeServiceTaskMaster.get()) @@ -219,6 +219,7 @@ private var declaredEncodables: Void = { declareEncodable(ArchivedStickerPacksInfo.self, f: { ArchivedStickerPacksInfo(decoder: $0) }) declareEncodable(SynchronizeChatInputStateOperation.self, f: { SynchronizeChatInputStateOperation(decoder: $0) }) declareEncodable(SynchronizeSavedGifsOperation.self, f: { SynchronizeSavedGifsOperation(decoder: $0) }) + declareEncodable(CacheStorageSettings.self, f: { CacheStorageSettings(decoder: $0) }) return }() @@ -272,12 +273,12 @@ public func accountWithId(apiId: Int32, id: AccountRecordId, supplementary: Bool if let accountState = accountState { switch accountState { case let unauthorizedState as UnauthorizedAccountState: - return initializedNetwork(apiId: apiId, supplementary: supplementary, datacenterId: Int(unauthorizedState.masterDatacenterId), keychain: keychain, networkUsageInfoPath: accountNetworkUsageInfoPath(basePath: path), testingEnvironment: testingEnvironment) + return initializedNetwork(apiId: apiId, supplementary: supplementary, datacenterId: Int(unauthorizedState.masterDatacenterId), keychain: keychain, basePath: path, testingEnvironment: testingEnvironment) |> map { network -> AccountResult in return .unauthorized(UnauthorizedAccount(apiId: apiId, id: id, appGroupPath: appGroupPath, basePath: path, testingEnvironment: testingEnvironment, postbox: postbox, network: network, shouldKeepAutoConnection: shouldKeepAutoConnection)) } case let authorizedState as AuthorizedAccountState: - return initializedNetwork(apiId: apiId, supplementary: supplementary, datacenterId: Int(authorizedState.masterDatacenterId), keychain: keychain, networkUsageInfoPath: accountNetworkUsageInfoPath(basePath: path), testingEnvironment: testingEnvironment) + return initializedNetwork(apiId: apiId, supplementary: supplementary, datacenterId: Int(authorizedState.masterDatacenterId), keychain: keychain, basePath: path, testingEnvironment: testingEnvironment) |> map { network -> AccountResult in return .authorized(Account(id: id, basePath: path, testingEnvironment: testingEnvironment, postbox: postbox, network: network, peerId: authorizedState.peerId, auxiliaryMethods: auxiliaryMethods)) } @@ -286,7 +287,7 @@ public func accountWithId(apiId: Int32, id: AccountRecordId, supplementary: Bool } } - return initializedNetwork(apiId: apiId, supplementary: supplementary, datacenterId: 2, keychain: keychain, networkUsageInfoPath: accountNetworkUsageInfoPath(basePath: path), testingEnvironment: testingEnvironment) + return initializedNetwork(apiId: apiId, supplementary: supplementary, datacenterId: 2, keychain: keychain, basePath: path, testingEnvironment: testingEnvironment) |> map { network -> AccountResult in return .unauthorized(UnauthorizedAccount(apiId: apiId, id: id, appGroupPath: appGroupPath, basePath: path, testingEnvironment: testingEnvironment, postbox: postbox, network: network, shouldKeepAutoConnection: shouldKeepAutoConnection)) } @@ -365,9 +366,9 @@ public enum AccountNetworkState { public final class AccountAuxiliaryMethods { public let updatePeerChatInputState: (PeerChatInterfaceState?, SynchronizeableChatInputState?) -> PeerChatInterfaceState? - public let fetchResource: (Account, MediaResource, Range) -> Signal? + public let fetchResource: (Account, MediaResource, Range, MediaResourceFetchTag?) -> Signal? - public init(updatePeerChatInputState: @escaping (PeerChatInterfaceState?, SynchronizeableChatInputState?) -> PeerChatInterfaceState?, fetchResource: @escaping (Account, MediaResource, Range) -> Signal?) { + public init(updatePeerChatInputState: @escaping (PeerChatInterfaceState?, SynchronizeableChatInputState?) -> PeerChatInterfaceState?, fetchResource: @escaping (Account, MediaResource, Range, MediaResourceFetchTag?) -> Signal?) { self.updatePeerChatInputState = updatePeerChatInputState self.fetchResource = fetchResource } @@ -624,20 +625,6 @@ public class Account { self.managedOperationsDisposable.dispose() } - /*public func currentNetworkStats() -> Signal { - return Signal { subscriber in - let manager = MTNetworkUsageManager(info: MTNetworkUsageCalculationInfo(filePath: accountNetworkUsageInfoPath(basePath: self.basePath)))! - manager.currentStats().start(next: { next in - if let stats = next as? MTNetworkUsageManagerStats { - subscriber.putNext(stats) - } - subscriber.putCompletion() - }, error: nil, completed: nil) - - return EmptyDisposable - } - }*/ - public func peerInputActivities(peerId: PeerId) -> Signal<[(PeerId, PeerInputActivity)], NoError> { return self.peerInputActivityManager.activities(peerId: peerId) } @@ -653,15 +640,19 @@ public class Account { } } +public func accountNetworkUsageStats(account: Account, reset: ResetNetworkUsageStats) -> Signal { + return networkUsageStats(basePath: account.basePath, reset: reset) +} + public typealias FetchCachedResourceRepresentation = (_ account: Account, _ resource: MediaResource, _ resourceData: MediaResourceData, _ representation: CachedMediaResourceRepresentation) -> Signal public typealias TransformOutgoingMessageMedia = (_ postbox: Postbox, _ network: Network, _ media: Media, _ userInteractive: Bool) -> Signal public func setupAccount(_ account: Account, fetchCachedResourceRepresentation: FetchCachedResourceRepresentation? = nil, transformOutgoingMessageMedia: TransformOutgoingMessageMedia? = nil) { - account.postbox.mediaBox.fetchResource = { [weak account] resource, range -> Signal in + account.postbox.mediaBox.fetchResource = { [weak account] resource, range, tag -> Signal in if let strongAccount = account { - if let result = fetchResource(account: strongAccount, resource: resource, range: range) { + if let result = fetchResource(account: strongAccount, resource: resource, range: range, tag: tag) { return result - } else if let result = strongAccount.auxiliaryMethods.fetchResource(strongAccount, resource, range) { + } else if let result = strongAccount.auxiliaryMethods.fetchResource(strongAccount, resource, range, tag) { return result } else { return .never() diff --git a/TelegramCore/Api.swift b/TelegramCore/Api.swift index 5f601aa955..7855ddf5f0 100644 --- a/TelegramCore/Api.swift +++ b/TelegramCore/Api.swift @@ -130,6 +130,8 @@ fileprivate let parsers: [Int32 : (BufferReader) -> Any?] = { dict[393186209] = { return Api.SendMessageAction.parse_sendMessageGeoLocationAction($0) } dict[1653390447] = { return Api.SendMessageAction.parse_sendMessageChooseContactAction($0) } dict[-580219064] = { return Api.SendMessageAction.parse_sendMessageGamePlayAction($0) } + dict[-1997373508] = { return Api.SendMessageAction.parse_sendMessageRecordRoundAction($0) } + dict[-1150187996] = { return Api.SendMessageAction.parse_sendMessageUploadRoundAction($0) } dict[-1137792208] = { return Api.PrivacyKey.parse_privacyKeyStatusTimestamp($0) } dict[1343122938] = { return Api.PrivacyKey.parse_privacyKeyChatInvite($0) } dict[1030105979] = { return Api.PrivacyKey.parse_privacyKeyPhoneCall($0) } @@ -4875,6 +4877,8 @@ public struct Api { case sendMessageGeoLocationAction case sendMessageChooseContactAction case sendMessageGamePlayAction + case sendMessageRecordRoundAction + case sendMessageUploadRoundAction public func serialize(_ buffer: Buffer, _ boxed: Swift.Bool) -> Swift.Bool { switch self { @@ -4943,6 +4947,18 @@ public struct Api { buffer.appendInt32(-580219064) } + break + case .sendMessageRecordRoundAction: + if boxed { + buffer.appendInt32(-1997373508) + } + + break + case .sendMessageUploadRoundAction: + if boxed { + buffer.appendInt32(-1150187996) + } + break } return true @@ -5013,6 +5029,12 @@ public struct Api { fileprivate static func parse_sendMessageGamePlayAction(_ reader: BufferReader) -> SendMessageAction? { return Api.SendMessageAction.sendMessageGamePlayAction } + fileprivate static func parse_sendMessageRecordRoundAction(_ reader: BufferReader) -> SendMessageAction? { + return Api.SendMessageAction.sendMessageRecordRoundAction + } + fileprivate static func parse_sendMessageUploadRoundAction(_ reader: BufferReader) -> SendMessageAction? { + return Api.SendMessageAction.sendMessageUploadRoundAction + } public var description: String { get { @@ -5039,6 +5061,10 @@ public struct Api { return "(sendMessageChooseContactAction)" case .sendMessageGamePlayAction: return "(sendMessageGamePlayAction)" + case .sendMessageRecordRoundAction: + return "(sendMessageRecordRoundAction)" + case .sendMessageUploadRoundAction: + return "(sendMessageUploadRoundAction)" } } } diff --git a/TelegramCore/CacheStorageSettings.swift b/TelegramCore/CacheStorageSettings.swift new file mode 100644 index 0000000000..4ebb824513 --- /dev/null +++ b/TelegramCore/CacheStorageSettings.swift @@ -0,0 +1,58 @@ +import Foundation +#if os(macOS) + import PostboxMac + import SwiftSignalKitMac +#else + import Postbox + import SwiftSignalKit +#endif + +public struct CacheStorageSettings: PreferencesEntry, Equatable { + public let defaultCacheStorageTimeout: Int32 + + public static var defaultSettings: CacheStorageSettings { + return CacheStorageSettings(defaultCacheStorageTimeout: Int32.max) + } + + init(defaultCacheStorageTimeout: Int32) { + self.defaultCacheStorageTimeout = defaultCacheStorageTimeout + } + + public init(decoder: Decoder) { + self.defaultCacheStorageTimeout = decoder.decodeInt32ForKey("dt") as Int32 + } + + public func encode(_ encoder: Encoder) { + encoder.encodeInt32(self.defaultCacheStorageTimeout, forKey: "dt") + } + + public func isEqual(to: PreferencesEntry) -> Bool { + if let to = to as? CacheStorageSettings { + return self == to + } else { + return false + } + } + + public static func ==(lhs: CacheStorageSettings, rhs: CacheStorageSettings) -> Bool { + return lhs.defaultCacheStorageTimeout == rhs.defaultCacheStorageTimeout + } + + public func withUpdatedDefaultCacheStorageTimeout(_ defaultCacheStorageTimeout: Int32) -> CacheStorageSettings { + return CacheStorageSettings(defaultCacheStorageTimeout: defaultCacheStorageTimeout) + } +} + +public func updateCacheStorageSettingsInteractively(postbox: Postbox, _ f: @escaping (CacheStorageSettings) -> CacheStorageSettings) -> Signal { + return postbox.modify { modifier -> Void in + modifier.updatePreferencesEntry(key: PreferencesKeys.cacheStorageSettings, { entry in + let currentSettings: CacheStorageSettings + if let entry = entry as? CacheStorageSettings { + currentSettings = entry + } else { + currentSettings = CacheStorageSettings.defaultSettings + } + return f(currentSettings) + }) + } +} diff --git a/TelegramCore/CloudFileMediaResource.swift b/TelegramCore/CloudFileMediaResource.swift index 20c72d3d23..9cad717aa4 100644 --- a/TelegramCore/CloudFileMediaResource.swift +++ b/TelegramCore/CloudFileMediaResource.swift @@ -5,10 +5,6 @@ import Foundation import Postbox #endif -public protocol TelegramMediaResource: MediaResource, Coding { - func isEqual(to: TelegramMediaResource) -> Bool -} - protocol TelegramCloudMediaResource: TelegramMediaResource { var datacenterId: Int { get } var apiInputLocation: Api.InputFileLocation { get } diff --git a/TelegramCore/CollectCacheUsageStats.swift b/TelegramCore/CollectCacheUsageStats.swift new file mode 100644 index 0000000000..eb4c003bba --- /dev/null +++ b/TelegramCore/CollectCacheUsageStats.swift @@ -0,0 +1,159 @@ +import Foundation +#if os(macOS) + import PostboxMac + import SwiftSignalKitMac +#else + import Postbox + import SwiftSignalKit +#endif + +public enum PeerCacheUsageCategory: Int32 { + case image = 0 + case video + case audio + case file +} + +public struct CacheUsageStats { + public let media: [PeerId: [PeerCacheUsageCategory: [MediaId: Int64]]] + public let mediaResourceIds: [MediaId: [MediaResourceId]] + public let peers: [PeerId: Peer] + + public init(media: [PeerId: [PeerCacheUsageCategory: [MediaId: Int64]]], mediaResourceIds: [MediaId: [MediaResourceId]], peers: [PeerId: Peer]) { + self.media = media + self.mediaResourceIds = mediaResourceIds + self.peers = peers + } +} + +public enum CacheUsageStatsResult { + case progress(Float) + case result(CacheUsageStats) +} + +private enum CollectCacheUsageStatsError { + case done(CacheUsageStats) +} + +private final class CacheUsageStatsState { + var media: [PeerId: [PeerCacheUsageCategory: [MediaId: Int64]]] = [:] + var mediaResourceIds: [MediaId: [MediaResourceId]] = [:] + var lowerBound: MessageIndex? +} + +public func collectCacheUsageStats(account: Account) -> Signal { + let state = Atomic(value: CacheUsageStatsState()) + + let fetch = account.postbox.modify { modifier -> ([PeerId : Set], [MediaId : Media], MessageIndex?) in + return modifier.enumerateMedia(lowerBound: state.with { $0.lowerBound }, limit: 1000) + } |> mapError { _ -> CollectCacheUsageStatsError in preconditionFailure() } + + let process: ([PeerId : Set], [MediaId : Media], MessageIndex?) -> Signal = { mediaByPeer, mediaRefs, updatedLowerBound in + var mediaIdToPeerId: [MediaId: PeerId] = [:] + for (peerId, mediaIds) in mediaByPeer { + for id in mediaIds { + mediaIdToPeerId[id] = peerId + } + } + + var resourceIdToMediaId: [WrappedMediaResourceId: (MediaId, PeerCacheUsageCategory)] = [:] + var mediaResourceIds: [MediaId: [MediaResourceId]] = [:] + var resourceIds: [MediaResourceId] = [] + for (id, media) in mediaRefs { + mediaResourceIds[id] = [] + switch media { + case let image as TelegramMediaImage: + for representation in image.representations { + resourceIds.append(representation.resource.id) + resourceIdToMediaId[WrappedMediaResourceId(representation.resource.id)] = (id, .image) + mediaResourceIds[id]!.append(representation.resource.id) + } + case let file as TelegramMediaFile: + var category: PeerCacheUsageCategory = .file + loop: for attribute in file.attributes { + switch attribute { + case .Video: + category = .video + break loop + case .Audio: + category = .audio + break loop + default: + break + } + } + for representation in file.previewRepresentations { + resourceIds.append(representation.resource.id) + resourceIdToMediaId[WrappedMediaResourceId(representation.resource.id)] = (id, category) + mediaResourceIds[id]!.append(representation.resource.id) + } + resourceIds.append(file.resource.id) + resourceIdToMediaId[WrappedMediaResourceId(file.resource.id)] = (id, category) + mediaResourceIds[id]!.append(file.resource.id) + default: + break + } + } + return account.postbox.mediaBox.collectResourceCacheUsage(resourceIds) + |> mapError { _ -> CollectCacheUsageStatsError in preconditionFailure() } + |> mapToSignal { result -> Signal in + state.with { state -> Void in + state.lowerBound = updatedLowerBound + for (wrappedId, size) in result { + if let (id, category) = resourceIdToMediaId[wrappedId] { + if let peerId = mediaIdToPeerId[id] { + if state.media[peerId] == nil { + state.media[peerId] = [:] + } + if state.media[peerId]![category] == nil { + state.media[peerId]![category] = [:] + } + var currentSize: Int64 = 0 + if let current = state.media[peerId]![category]![id] { + currentSize = current + } + state.media[peerId]![category]![id] = currentSize + size + } + } + } + for (id, ids) in mediaResourceIds { + state.mediaResourceIds[id] = ids + } + } + if updatedLowerBound == nil { + let (finalMedia, finalMediaResourceIds) = state.with { state -> ([PeerId: [PeerCacheUsageCategory: [MediaId: Int64]]], [MediaId: [MediaResourceId]]) in + return (state.media, state.mediaResourceIds) + } + return account.postbox.modify { modifier -> CacheUsageStats in + var peers: [PeerId: Peer] = [:] + for peerId in finalMedia.keys { + if let peer = modifier.getPeer(peerId) { + peers[peer.id] = peer + } + } + return CacheUsageStats(media: finalMedia, mediaResourceIds: finalMediaResourceIds, peers: peers) + } |> mapError { _ -> CollectCacheUsageStatsError in preconditionFailure() } + |> mapToSignal { stats -> Signal in + return .fail(.done(stats)) + } + } else { + return .complete() + } + } + } + + let signal = (fetch |> mapToSignal { mediaByPeer, mediaRefs, updatedLowerBound -> Signal in + return process(mediaByPeer, mediaRefs, updatedLowerBound) + }) |> restart + + return signal |> `catch` { error in + switch error { + case let .done(result): + return .single(.result(result)) + } + } +} + +public func clearCachedMediaResources(account: Account, mediaResourceIds: Set) -> Signal { + return account.postbox.mediaBox.removeCachedResources(mediaResourceIds) +} diff --git a/TelegramCore/Download.swift b/TelegramCore/Download.swift index 6d984210ed..68bc86ecb4 100644 --- a/TelegramCore/Download.swift +++ b/TelegramCore/Download.swift @@ -28,11 +28,11 @@ class Download: NSObject, MTRequestMessageServiceDelegate { let mtProto: MTProto let requestService: MTRequestMessageService - init(datacenterId: Int, context: MTContext, masterDatacenterId: Int) { + init(datacenterId: Int, context: MTContext, masterDatacenterId: Int, usageInfo: MTNetworkUsageCalculationInfo?) { self.datacenterId = datacenterId self.context = context - self.mtProto = MTProto(context: self.context, datacenterId: datacenterId, usageCalculationInfo: nil) + self.mtProto = MTProto(context: self.context, datacenterId: datacenterId, usageCalculationInfo: usageInfo) if datacenterId != masterDatacenterId { self.mtProto.authTokenMasterDatacenterId = masterDatacenterId self.mtProto.requiredAuthToken = Int(datacenterId) as NSNumber diff --git a/TelegramCore/Fetch.swift b/TelegramCore/Fetch.swift index 318242301c..4afe8053b9 100644 --- a/TelegramCore/Fetch.swift +++ b/TelegramCore/Fetch.swift @@ -9,8 +9,8 @@ import SwiftSignalKit import Photos #endif -private func fetchCloudMediaLocation(account: Account, resource: TelegramCloudMediaResource, size: Int?, range: Range) -> Signal { - return multipartFetch(account: account, resource: resource, size: size, range: range) +private func fetchCloudMediaLocation(account: Account, resource: TelegramCloudMediaResource, size: Int?, range: Range, tag: MediaResourceFetchTag?) -> Signal { + return multipartFetch(account: account, resource: resource, size: size, range: range, tag: tag) } private func fetchLocalFileResource(path: String, move: Bool) -> Signal { @@ -30,13 +30,13 @@ private func fetchLocalFileResource(path: String, move: Bool) -> Signal) -> Signal? { +func fetchResource(account: Account, resource: MediaResource, range: Range, tag: MediaResourceFetchTag?) -> Signal? { if let _ = resource as? EmptyMediaResource { return .never() } else if let secretFileResource = resource as? SecretFileMediaResource { - return .single(.dataPart(data: Data(), range: 0 ..< 0, complete: false)) |> then(fetchSecretFileResource(account: account, resource: secretFileResource, range: range)) + return .single(.dataPart(data: Data(), range: 0 ..< 0, complete: false)) |> then(fetchSecretFileResource(account: account, resource: secretFileResource, range: range, tag: tag)) } else if let cloudResource = resource as? TelegramCloudMediaResource { - return .single(.dataPart(data: Data(), range: 0 ..< 0, complete: false)) |> then(fetchCloudMediaLocation(account: account, resource: cloudResource, size: resource.size, range: range)) + return .single(.dataPart(data: Data(), range: 0 ..< 0, complete: false)) |> then(fetchCloudMediaLocation(account: account, resource: cloudResource, size: resource.size, range: range, tag: tag)) } else if let localFileResource = resource as? LocalFileReferenceMediaResource { if false { //return .single(.dataPart(data: Data(), range: 0 ..< 0, complete: false)) |> then(fetchLocalFileResource(path: localFileResource.localFilePath) |> delay(10.0, queue: Queue.concurrentDefaultQueue())) diff --git a/TelegramCore/FetchSecretFileResource.swift b/TelegramCore/FetchSecretFileResource.swift index ae7b5602f7..1dfebf7e7f 100644 --- a/TelegramCore/FetchSecretFileResource.swift +++ b/TelegramCore/FetchSecretFileResource.swift @@ -9,6 +9,6 @@ import Foundation import MtProtoKitDynamic #endif -func fetchSecretFileResource(account: Account, resource: SecretFileMediaResource, range: Range) -> Signal { - return multipartFetch(account: account, resource: resource, size: resource.size, range: range, encryptionKey: resource.key, decryptedSize: resource.decryptedSize) +func fetchSecretFileResource(account: Account, resource: SecretFileMediaResource, range: Range, tag: MediaResourceFetchTag?) -> Signal { + return multipartFetch(account: account, resource: resource, size: resource.size, range: range, tag: tag, encryptionKey: resource.key, decryptedSize: resource.decryptedSize) } diff --git a/TelegramCore/ManagedLocalInputActivities.swift b/TelegramCore/ManagedLocalInputActivities.swift index 87b6cac93a..f35ae7ed04 100644 --- a/TelegramCore/ManagedLocalInputActivities.swift +++ b/TelegramCore/ManagedLocalInputActivities.swift @@ -95,6 +95,10 @@ private func actionFromActivity(_ activity: PeerInputActivity?) -> Api.SendMessa return .sendMessageGamePlayAction case let .uploadingFile(progress): return .sendMessageUploadDocumentAction(progress: progress) + case .recordingInstantVideo: + return .sendMessageRecordRoundAction + case .uploadingInstantVideo: + return .sendMessageUploadRoundAction } } else { return .sendMessageCancelAction diff --git a/TelegramCore/MediaResourceNetworkStatsTag.swift b/TelegramCore/MediaResourceNetworkStatsTag.swift new file mode 100644 index 0000000000..fb117081b4 --- /dev/null +++ b/TelegramCore/MediaResourceNetworkStatsTag.swift @@ -0,0 +1,21 @@ +#if os(macOS) + import PostboxMac +#else + import Postbox +#endif + +public enum MediaResourceStatsCategory { + case generic + case image + case video + case audio + case file +} + +public final class TelegramMediaResourceFetchTag: MediaResourceFetchTag { + public let statsCategory: MediaResourceStatsCategory + + public init(statsCategory: MediaResourceStatsCategory) { + self.statsCategory = statsCategory + } +} diff --git a/TelegramCore/MultipartFetch.swift b/TelegramCore/MultipartFetch.swift index f1aff31a65..f9865b890b 100644 --- a/TelegramCore/MultipartFetch.swift +++ b/TelegramCore/MultipartFetch.swift @@ -192,8 +192,8 @@ private final class MultipartFetchManager { } } -func multipartFetch(account: Account, resource: TelegramCloudMediaResource, size: Int?, range: Range, encryptionKey: SecretFileEncryptionKey? = nil, decryptedSize: Int32? = nil) -> Signal { - return account.network.download(datacenterId: resource.datacenterId) +func multipartFetch(account: Account, resource: TelegramCloudMediaResource, size: Int?, range: Range, tag: MediaResourceFetchTag?, encryptionKey: SecretFileEncryptionKey? = nil, decryptedSize: Int32? = nil) -> Signal { + return account.network.download(datacenterId: resource.datacenterId, tag: tag) |> mapToSignal { download -> Signal in return Signal { subscriber in let inputLocation = resource.apiInputLocation diff --git a/TelegramCore/MultipartUpload.swift b/TelegramCore/MultipartUpload.swift index 35cfdc1546..fe44f200dc 100644 --- a/TelegramCore/MultipartUpload.swift +++ b/TelegramCore/MultipartUpload.swift @@ -322,8 +322,8 @@ enum MultipartUploadSource { case data(Data) } -func multipartUpload(network: Network, postbox: Postbox, source: MultipartUploadSource, encrypt: Bool, hintFileSize: Int? = nil) -> Signal { - return network.download(datacenterId: network.datacenterId) +func multipartUpload(network: Network, postbox: Postbox, source: MultipartUploadSource, encrypt: Bool, tag: MediaResourceFetchTag?, hintFileSize: Int? = nil) -> Signal { + return network.download(datacenterId: network.datacenterId, tag: tag) |> mapToSignal { download -> Signal in return Signal { subscriber in var encryptionKey: SecretFileEncryptionKey? @@ -348,7 +348,7 @@ func multipartUpload(network: Network, postbox: Postbox, source: MultipartUpload case let .resource(resource): dataSignal = postbox.mediaBox.resourceData(resource, option: .incremental(waitUntilFetchStatus: true)) |> map { MultipartUploadData.resourceData($0) } headerSize = resource.headerSize - fetchedResource = postbox.mediaBox.fetchedResource(resource) |> map {_ in} + fetchedResource = postbox.mediaBox.fetchedResource(resource, tag: tag) |> map {_ in} case let .data(data): dataSignal = .single(.data(data)) headerSize = 0 diff --git a/TelegramCore/Namespaces.swift b/TelegramCore/Namespaces.swift index c51c6f0999..9048606bb8 100644 --- a/TelegramCore/Namespaces.swift +++ b/TelegramCore/Namespaces.swift @@ -84,6 +84,7 @@ struct OperationLogTags { private enum PreferencesKeyValues: Int32 { case globalNotifications = 0 + case cacheStorageSettings = 1 } public func applicationSpecificPreferencesKey(_ value: Int32) -> ValueBoxKey { @@ -98,4 +99,10 @@ public struct PreferencesKeys { key.setInt32(0, value: PreferencesKeyValues.globalNotifications.rawValue) return key }() + + public static let cacheStorageSettings: ValueBoxKey = { + let key = ValueBoxKey(length: 4) + key.setInt32(0, value: PreferencesKeyValues.cacheStorageSettings.rawValue) + return key + }() } diff --git a/TelegramCore/Network.swift b/TelegramCore/Network.swift index 9192ffff72..fdf218bcf1 100644 --- a/TelegramCore/Network.swift +++ b/TelegramCore/Network.swift @@ -94,7 +94,206 @@ private var registeredLoggingFunctions: Void = { registerLoggingFunctions() }() -func initializedNetwork(apiId: Int32, supplementary: Bool, datacenterId: Int, keychain: Keychain, networkUsageInfoPath: String?, testingEnvironment: Bool) -> Signal { +private enum UsageCalculationConnection: Int32 { + case cellular = 0 + case wifi = 1 +} + +private enum UsageCalculationDirection: Int32 { + case incoming = 0 + case outgoing = 1 +} + +private struct UsageCalculationTag { + let connection: UsageCalculationConnection + let direction: UsageCalculationDirection + let category: MediaResourceStatsCategory + + var key: Int32 { + switch category { + case .generic: + return 0 * 4 + self.connection.rawValue * 2 + self.direction.rawValue * 1 + case .image: + return 1 * 4 + self.connection.rawValue * 2 + self.direction.rawValue * 1 + case .video: + return 2 * 4 + self.connection.rawValue * 2 + self.direction.rawValue * 1 + case .audio: + return 3 * 4 + self.connection.rawValue * 2 + self.direction.rawValue * 1 + case .file: + return 4 * 4 + self.connection.rawValue * 2 + self.direction.rawValue * 1 + } + } +} + +private enum UsageCalculationResetKey: Int32 { + case wifi = 80 //20 * 4 + 0 + case cellular = 81 //20 * 4 + 2 +} + +private func usageCalculationInfo(basePath: String, category: MediaResourceStatsCategory?) -> MTNetworkUsageCalculationInfo { + let categoryValue: MediaResourceStatsCategory + if let category = category { + categoryValue = category + } else { + categoryValue = .generic + } + return MTNetworkUsageCalculationInfo(filePath: basePath + "/network-stats", incomingWWANKey: UsageCalculationTag(connection: .cellular, direction: .incoming, category: categoryValue).key, outgoingWWANKey: UsageCalculationTag(connection: .cellular, direction: .outgoing, category: categoryValue).key, incomingOtherKey: UsageCalculationTag(connection: .wifi, direction: .incoming, category: categoryValue).key, outgoingOtherKey: UsageCalculationTag(connection: .wifi, direction: .outgoing, category: categoryValue).key) +} + +public struct NetworkUsageStatsDirectionsEntry: Equatable { + public let incoming: Int64 + public let outgoing: Int64 + + public static func ==(lhs: NetworkUsageStatsDirectionsEntry, rhs: NetworkUsageStatsDirectionsEntry) -> Bool { + return lhs.incoming == rhs.incoming && lhs.outgoing == rhs.outgoing + } +} + +public struct NetworkUsageStatsConnectionsEntry: Equatable { + public let cellular: NetworkUsageStatsDirectionsEntry + public let wifi: NetworkUsageStatsDirectionsEntry + + public static func ==(lhs: NetworkUsageStatsConnectionsEntry, rhs: NetworkUsageStatsConnectionsEntry) -> Bool { + return lhs.cellular == rhs.cellular && lhs.wifi == rhs.wifi + } +} + +public struct NetworkUsageStats: Equatable { + public let generic: NetworkUsageStatsConnectionsEntry + public let image: NetworkUsageStatsConnectionsEntry + public let video: NetworkUsageStatsConnectionsEntry + public let audio: NetworkUsageStatsConnectionsEntry + public let file: NetworkUsageStatsConnectionsEntry + + public let resetWifiTimestamp: Int32 + public let resetCellularTimestamp: Int32 + + public static func ==(lhs: NetworkUsageStats, rhs: NetworkUsageStats) -> Bool { + return lhs.generic == rhs.generic && lhs.image == rhs.image && lhs.video == rhs.video && lhs.audio == rhs.audio && lhs.file == rhs.file && lhs.resetWifiTimestamp == rhs.resetWifiTimestamp && lhs.resetCellularTimestamp == rhs.resetCellularTimestamp + } +} + +public struct ResetNetworkUsageStats: OptionSet { + public var rawValue: Int32 + + public init(rawValue: Int32) { + self.rawValue = rawValue + } + + public init() { + self.rawValue = 0 + } + + public static let wifi = ResetNetworkUsageStats(rawValue: 1 << 0) + public static let cellular = ResetNetworkUsageStats(rawValue: 1 << 1) +} + +func networkUsageStats(basePath: String, reset: ResetNetworkUsageStats) -> Signal { + return ((Signal { subscriber in + let info = usageCalculationInfo(basePath: basePath, category: nil) + let manager = MTNetworkUsageManager(info: info)! + + let rawKeys: [UsageCalculationTag] = [ + UsageCalculationTag(connection: .cellular, direction: .incoming, category: .generic), + UsageCalculationTag(connection: .cellular, direction: .outgoing, category: .generic), + UsageCalculationTag(connection: .wifi, direction: .incoming, category: .generic), + UsageCalculationTag(connection: .wifi, direction: .outgoing, category: .generic), + + UsageCalculationTag(connection: .cellular, direction: .incoming, category: .image), + UsageCalculationTag(connection: .cellular, direction: .outgoing, category: .image), + UsageCalculationTag(connection: .wifi, direction: .incoming, category: .image), + UsageCalculationTag(connection: .wifi, direction: .outgoing, category: .image), + + UsageCalculationTag(connection: .cellular, direction: .incoming, category: .video), + UsageCalculationTag(connection: .cellular, direction: .outgoing, category: .video), + UsageCalculationTag(connection: .wifi, direction: .incoming, category: .video), + UsageCalculationTag(connection: .wifi, direction: .outgoing, category: .video), + + UsageCalculationTag(connection: .cellular, direction: .incoming, category: .audio), + UsageCalculationTag(connection: .cellular, direction: .outgoing, category: .audio), + UsageCalculationTag(connection: .wifi, direction: .incoming, category: .audio), + UsageCalculationTag(connection: .wifi, direction: .outgoing, category: .audio), + + UsageCalculationTag(connection: .cellular, direction: .incoming, category: .file), + UsageCalculationTag(connection: .cellular, direction: .outgoing, category: .file), + UsageCalculationTag(connection: .wifi, direction: .incoming, category: .file), + UsageCalculationTag(connection: .wifi, direction: .outgoing, category: .file) + ] + + var keys: [NSNumber] = rawKeys.map { $0.key as NSNumber } + + var resetKeys: [NSNumber] = [] + var resetAddKeys: [NSNumber: NSNumber] = [:] + let timestamp = Int32(CFAbsoluteTimeGetCurrent() + NSTimeIntervalSince1970) + if reset.contains(.wifi) { + resetKeys = rawKeys.filter({ $0.connection == .wifi }).map({ $0.key as NSNumber }) + resetAddKeys[UsageCalculationResetKey.wifi.rawValue as NSNumber] = Int64(timestamp) as NSNumber + } + if reset.contains(.cellular) { + resetKeys = rawKeys.filter({ $0.connection == .cellular }).map({ $0.key as NSNumber }) + resetAddKeys[UsageCalculationResetKey.cellular.rawValue as NSNumber] = Int64(timestamp) as NSNumber + } + if !resetKeys.isEmpty { + manager.resetKeys(resetKeys, setKeys: resetAddKeys, completion: {}) + } + keys.append(UsageCalculationResetKey.cellular.rawValue as NSNumber) + keys.append(UsageCalculationResetKey.wifi.rawValue as NSNumber) + + let disposable = manager.currentStats(forKeys: keys).start(next: { next in + var dict: [Int32: Int64] = [:] + for key in keys { + dict[key.int32Value] = 0 + } + (next as! NSDictionary).enumerateKeysAndObjects({ key, value, _ in + dict[(key as AnyObject).int32Value] = (value as AnyObject).int64Value! + }) + subscriber.putNext(NetworkUsageStats( + generic: NetworkUsageStatsConnectionsEntry( + cellular: NetworkUsageStatsDirectionsEntry( + incoming: dict[UsageCalculationTag(connection: .cellular, direction: .incoming, category: .generic).key]!, + outgoing: dict[UsageCalculationTag(connection: .cellular, direction: .outgoing, category: .generic).key]!), + wifi: NetworkUsageStatsDirectionsEntry( + incoming: dict[UsageCalculationTag(connection: .wifi, direction: .incoming, category: .generic).key]!, + outgoing: dict[UsageCalculationTag(connection: .wifi, direction: .outgoing, category: .generic).key]!)), + image: NetworkUsageStatsConnectionsEntry( + cellular: NetworkUsageStatsDirectionsEntry( + incoming: dict[UsageCalculationTag(connection: .cellular, direction: .incoming, category: .image).key]!, + outgoing: dict[UsageCalculationTag(connection: .cellular, direction: .outgoing, category: .image).key]!), + wifi: NetworkUsageStatsDirectionsEntry( + incoming: dict[UsageCalculationTag(connection: .wifi, direction: .incoming, category: .image).key]!, + outgoing: dict[UsageCalculationTag(connection: .wifi, direction: .outgoing, category: .image).key]!)), + video: NetworkUsageStatsConnectionsEntry( + cellular: NetworkUsageStatsDirectionsEntry( + incoming: dict[UsageCalculationTag(connection: .cellular, direction: .incoming, category: .video).key]!, + outgoing: dict[UsageCalculationTag(connection: .cellular, direction: .outgoing, category: .video).key]!), + wifi: NetworkUsageStatsDirectionsEntry( + incoming: dict[UsageCalculationTag(connection: .wifi, direction: .incoming, category: .video).key]!, + outgoing: dict[UsageCalculationTag(connection: .wifi, direction: .outgoing, category: .video).key]!)), + audio: NetworkUsageStatsConnectionsEntry( + cellular: NetworkUsageStatsDirectionsEntry( + incoming: dict[UsageCalculationTag(connection: .cellular, direction: .incoming, category: .audio).key]!, + outgoing: dict[UsageCalculationTag(connection: .cellular, direction: .outgoing, category: .audio).key]!), + wifi: NetworkUsageStatsDirectionsEntry( + incoming: dict[UsageCalculationTag(connection: .wifi, direction: .incoming, category: .audio).key]!, + outgoing: dict[UsageCalculationTag(connection: .wifi, direction: .outgoing, category: .audio).key]!)), + file: NetworkUsageStatsConnectionsEntry( + cellular: NetworkUsageStatsDirectionsEntry( + incoming: dict[UsageCalculationTag(connection: .cellular, direction: .incoming, category: .file).key]!, + outgoing: dict[UsageCalculationTag(connection: .cellular, direction: .outgoing, category: .file).key]!), + wifi: NetworkUsageStatsDirectionsEntry( + incoming: dict[UsageCalculationTag(connection: .wifi, direction: .incoming, category: .file).key]!, + outgoing: dict[UsageCalculationTag(connection: .wifi, direction: .outgoing, category: .file).key]!)), + resetWifiTimestamp: Int32(dict[UsageCalculationResetKey.wifi.rawValue]!), + resetCellularTimestamp: Int32(dict[UsageCalculationResetKey.cellular.rawValue]!) + )) + })! + return ActionDisposable { + disposable.dispose() + } + }) |> then(Signal.complete() |> delay(5.0, queue: Queue.concurrentDefaultQueue()))) |> restart +} + +func initializedNetwork(apiId: Int32, supplementary: Bool, datacenterId: Int, keychain: Keychain, basePath: String, testingEnvironment: Bool) -> Signal { return Signal { subscriber in Queue.concurrentDefaultQueue().async { let _ = registeredLoggingFunctions @@ -131,7 +330,7 @@ func initializedNetwork(apiId: Int32, supplementary: Bool, datacenterId: Int, ke } context.keychain = keychain - let mtProto = MTProto(context: context, datacenterId: datacenterId, usageCalculationInfo: nil)! + let mtProto = MTProto(context: context, datacenterId: datacenterId, usageCalculationInfo: usageCalculationInfo(basePath: basePath, category: nil))! let connectionStatus = Promise(.WaitingForNetwork) @@ -159,7 +358,7 @@ func initializedNetwork(apiId: Int32, supplementary: Bool, datacenterId: Int, ke mtProto.delegate = connectionStatusDelegate mtProto.add(requestService) - subscriber.putNext(Network(datacenterId: datacenterId, context: context, mtProto: mtProto, requestService: requestService, connectionStatusDelegate: connectionStatusDelegate, _connectionStatus: connectionStatus)) + subscriber.putNext(Network(datacenterId: datacenterId, context: context, mtProto: mtProto, requestService: requestService, connectionStatusDelegate: connectionStatusDelegate, _connectionStatus: connectionStatus, basePath: basePath)) subscriber.putCompletion() } @@ -172,6 +371,7 @@ public class Network: NSObject, MTRequestMessageServiceDelegate { let context: MTContext let mtProto: MTProto let requestService: MTRequestMessageService + let basePath: String private let connectionStatusDelegate: MTProtoConnectionStatusDelegate private let _connectionStatus: Promise @@ -184,13 +384,14 @@ public class Network: NSObject, MTRequestMessageServiceDelegate { var loggedOut: (() -> Void)? - fileprivate init(datacenterId: Int, context: MTContext, mtProto: MTProto, requestService: MTRequestMessageService, connectionStatusDelegate: MTProtoConnectionStatusDelegate, _connectionStatus: Promise) { + fileprivate init(datacenterId: Int, context: MTContext, mtProto: MTProto, requestService: MTRequestMessageService, connectionStatusDelegate: MTProtoConnectionStatusDelegate, _connectionStatus: Promise, basePath: String) { self.datacenterId = datacenterId self.context = context self.mtProto = mtProto self.requestService = requestService self.connectionStatusDelegate = connectionStatusDelegate self._connectionStatus = _connectionStatus + self.basePath = basePath super.init() @@ -219,10 +420,10 @@ public class Network: NSObject, MTRequestMessageServiceDelegate { self.loggedOut?() } - func download(datacenterId: Int) -> Signal { + func download(datacenterId: Int, tag: MediaResourceFetchTag?) -> Signal { return Signal { [weak self] subscriber in if let strongSelf = self { - subscriber.putNext(Download(datacenterId: datacenterId, context: strongSelf.context, masterDatacenterId: strongSelf.datacenterId)) + subscriber.putNext(Download(datacenterId: datacenterId, context: strongSelf.context, masterDatacenterId: strongSelf.datacenterId, usageInfo: usageCalculationInfo(basePath: strongSelf.basePath, category: (tag as? TelegramMediaResourceFetchTag)?.statsCategory))) } subscriber.putCompletion() diff --git a/TelegramCore/PeerInputActivity.swift b/TelegramCore/PeerInputActivity.swift index 9c205dd958..520ad23767 100644 --- a/TelegramCore/PeerInputActivity.swift +++ b/TelegramCore/PeerInputActivity.swift @@ -5,6 +5,8 @@ public enum PeerInputActivity: Comparable { case uploadingFile(progress: Int32) case recordingVoice case playingGame + case recordingInstantVideo + case uploadingInstantVideo public static func ==(lhs: PeerInputActivity, rhs: PeerInputActivity) -> Bool { switch lhs { @@ -32,6 +34,18 @@ public enum PeerInputActivity: Comparable { } else { return false } + case .recordingInstantVideo: + if case .recordingInstantVideo = rhs { + return true + } else { + return false + } + case .uploadingInstantVideo: + if case .uploadingInstantVideo = rhs { + return true + } else { + return false + } } } @@ -43,8 +57,12 @@ public enum PeerInputActivity: Comparable { return 1 case .recordingVoice: return 2 - case .playingGame: + case .recordingInstantVideo: return 3 + case .uploadingInstantVideo: + return 4 + case .playingGame: + return 5 } } @@ -70,6 +88,10 @@ extension PeerInputActivity { self = .uploadingFile(progress: progress) case let .sendMessageUploadVideoAction(progress): self = .uploadingFile(progress: progress) + case .sendMessageRecordRoundAction: + self = .recordingInstantVideo + case .sendMessageUploadRoundAction: + self = .uploadingInstantVideo } } } diff --git a/TelegramCore/PeerPhotoUpdater.swift b/TelegramCore/PeerPhotoUpdater.swift index d5998f3f1b..3afe24b8b8 100644 --- a/TelegramCore/PeerPhotoUpdater.swift +++ b/TelegramCore/PeerPhotoUpdater.swift @@ -25,7 +25,7 @@ public func updateAccountPhoto(account:Account, resource:MediaResource) -> Signa public func updatePeerPhoto(account:Account, peerId:PeerId, resource:MediaResource) -> Signal { return account.postbox.loadedPeerWithId(peerId) |> mapError {_ in return .generic} |> mapToSignal { peer in - return multipartUpload(network: account.network, postbox: account.postbox, source: .resource(resource), encrypt: false) + return multipartUpload(network: account.network, postbox: account.postbox, source: .resource(resource), encrypt: false, tag: TelegramMediaResourceFetchTag(statsCategory: .image)) |> mapError {_ in return .generic} |> mapToSignal { result -> Signal in switch result { @@ -89,7 +89,12 @@ public func updatePeerPhoto(account:Account, peerId:PeerId, resource:MediaResour for chat in updates.chats { if chat.peerId == peerId { if let groupOrChannel = parseTelegramGroupOrChannel(chat: chat) { - return .single(.complete(groupOrChannel.profileImageRepresentations)) + return account.postbox.modify { modifier -> UpdatePeerPhotoStatus in + updatePeers(modifier: modifier, peers: [groupOrChannel], update: { _, updated in + return updated + }) + return .complete(groupOrChannel.profileImageRepresentations) + } |> mapError { _ in return .generic } } } } diff --git a/TelegramCore/PendingMessageUploadedContent.swift b/TelegramCore/PendingMessageUploadedContent.swift index 9b7aa2c1a2..aa1af5ebcd 100644 --- a/TelegramCore/PendingMessageUploadedContent.swift +++ b/TelegramCore/PendingMessageUploadedContent.swift @@ -74,7 +74,7 @@ func messageContentToUpload(network: Network, postbox: Postbox, transformOutgoin private func uploadedMediaImageContent(network: Network, postbox: Postbox, peerId: PeerId, image: TelegramMediaImage, text: String) -> Signal { if let largestRepresentation = largestImageRepresentation(image.representations) { - return multipartUpload(network: network, postbox: postbox, source: .resource(largestRepresentation.resource), encrypt: peerId.namespace == Namespaces.Peer.SecretChat) + return multipartUpload(network: network, postbox: postbox, source: .resource(largestRepresentation.resource), encrypt: peerId.namespace == Namespaces.Peer.SecretChat, tag: TelegramMediaResourceFetchTag(statsCategory: .image)) |> map { next -> PendingMessageUploadedContentResult in switch next { case let .progress(progress): @@ -153,7 +153,7 @@ private enum UploadedMediaThumbnail { } private func uploadedThumbnail(network: Network, postbox: Postbox, image: TelegramMediaImageRepresentation) -> Signal { - return multipartUpload(network: network, postbox: postbox, source: .resource(image.resource), encrypt: false) + return multipartUpload(network: network, postbox: postbox, source: .resource(image.resource), encrypt: false, tag: TelegramMediaResourceFetchTag(statsCategory: .image)) |> mapToSignal { result -> Signal in switch result { case .progress: @@ -166,6 +166,20 @@ private func uploadedThumbnail(network: Network, postbox: Postbox, image: Telegr } } +public func statsCategoryForFileWithAttributes(_ attributes: [TelegramMediaFileAttribute]) -> MediaResourceStatsCategory { + for attribute in attributes { + switch attribute { + case .Audio: + return .audio + case .Video: + return .video + default: + break + } + } + return .file +} + private func uploadedMediaFileContent(network: Network, postbox: Postbox, transformOutgoingMessageMedia: TransformOutgoingMessageMedia?, peerId: PeerId, messageId: MessageId?, text: String, attributes: [MessageAttribute], file: TelegramMediaFile) -> Signal { var hintSize: Int? if let size = file.size { @@ -173,7 +187,7 @@ private func uploadedMediaFileContent(network: Network, postbox: Postbox, transf } else if let resource = file.resource as? LocalFileReferenceMediaResource, let size = resource.size { hintSize = Int(size) } - let upload = multipartUpload(network: network, postbox: postbox, source: .resource(file.resource), encrypt: peerId.namespace == Namespaces.Peer.SecretChat, hintFileSize: hintSize) + let upload = multipartUpload(network: network, postbox: postbox, source: .resource(file.resource), encrypt: peerId.namespace == Namespaces.Peer.SecretChat, tag: TelegramMediaResourceFetchTag(statsCategory: statsCategoryForFileWithAttributes(file.attributes)), hintFileSize: hintSize) /*|> map { next -> UploadedMediaFileContent in switch next { case let .progress(progress): diff --git a/TelegramCore/StandaloneSendMessage.swift b/TelegramCore/StandaloneSendMessage.swift index 4f78aa2bff..ce575a9d04 100644 --- a/TelegramCore/StandaloneSendMessage.swift +++ b/TelegramCore/StandaloneSendMessage.swift @@ -140,7 +140,7 @@ private enum UploadMediaEvent { } private func uploadedImage(account: Account, text: String, data: Data) -> Signal { - return multipartUpload(network: account.network, postbox: account.postbox, source: .data(data), encrypt: false) + return multipartUpload(network: account.network, postbox: account.postbox, source: .data(data), encrypt: false, tag: TelegramMediaResourceFetchTag(statsCategory: .image)) |> map { next -> UploadMediaEvent in switch next { case let .inputFile(inputFile): @@ -154,7 +154,7 @@ private func uploadedImage(account: Account, text: String, data: Data) -> Signal } private func uploadedFile(account: Account, text: String, data: Data, mimeType: String, attributes: [TelegramMediaFileAttribute]) -> Signal { - return multipartUpload(network: account.network, postbox: account.postbox, source: .data(data), encrypt: false) + return multipartUpload(network: account.network, postbox: account.postbox, source: .data(data), encrypt: false, tag: TelegramMediaResourceFetchTag(statsCategory: statsCategoryForFileWithAttributes(attributes))) |> map { next -> UploadMediaEvent in switch next { case let .inputFile(inputFile): diff --git a/TelegramCore/StringFormat.swift b/TelegramCore/StringFormat.swift index 1af9c1d7e2..d6055dab94 100644 --- a/TelegramCore/StringFormat.swift +++ b/TelegramCore/StringFormat.swift @@ -1,9 +1,26 @@ public func dataSizeString(_ size: Int) -> String { - if size >= 1024 * 1024 { - return "\(size / (1024 * 1024)) MB" + if size >= 1024 * 1024 * 1024 { + let remainder = (size % (1024 * 1024 * 1024)) / (1024 * 1024 * 102) + if remainder != 0 { + return "\(size / (1024 * 1024 * 1024)),\(remainder) GB" + } else { + return "\(size / (1024 * 1024 * 1024)) GB" + } + } else if size >= 1024 * 1024 { + let remainder = (size % (1024 * 1024)) / (1024 * 102) + if remainder != 0 { + return "\(size / (1024 * 1024)),\(remainder) MB" + } else { + return "\(size / (1024 * 1024)) MB" + } } else if size >= 1024 { - return "\(size / 1024) KB" + let remainder = (size % (1024)) / (102) + if remainder != 0 { + return "\(size / 1024),\(remainder) KB" + } else { + return "\(size / 1024) KB" + } } else { return "\(size) B" } diff --git a/TelegramCore/TelegramMediaResource.swift b/TelegramCore/TelegramMediaResource.swift new file mode 100644 index 0000000000..b47645fd40 --- /dev/null +++ b/TelegramCore/TelegramMediaResource.swift @@ -0,0 +1,11 @@ +import Foundation +#if os(macOS) + import PostboxMac +#else + import Postbox +#endif + +public protocol TelegramMediaResource: MediaResource, Coding { + func isEqual(to: TelegramMediaResource) -> Bool +} +