From 53a20f2a896a490267d08fe748aa69fdbca3e316 Mon Sep 17 00:00:00 2001 From: Stefan Ceriu Date: Thu, 18 Dec 2025 17:49:39 +0200 Subject: [PATCH] Display the store sizes under the developer options menu --- .../SettingsFlowCoordinator.swift | 4 +- ElementX/Sources/Mocks/ClientProxyMock.swift | 2 + .../Mocks/Generated/GeneratedMocks.swift | 64 +++++++++++++++++++ .../DeveloperOptionsScreenCoordinator.swift | 5 +- .../DeveloperOptionsScreenModels.swift | 10 +++ .../DeveloperOptionsScreenViewModel.swift | 29 +++++++-- .../View/DeveloperOptionsScreen.swift | 11 +++- .../Sources/Services/Client/ClientProxy.swift | 9 +++ .../Services/Client/ClientProxyProtocol.swift | 2 + 9 files changed, 128 insertions(+), 8 deletions(-) diff --git a/ElementX/Sources/FlowCoordinators/SettingsFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/SettingsFlowCoordinator.swift index 47bfb30c3..85f6bab33 100644 --- a/ElementX/Sources/FlowCoordinators/SettingsFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/SettingsFlowCoordinator.swift @@ -252,7 +252,9 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol { } private func presentDeveloperOptions() { - let coordinator = DeveloperOptionsScreenCoordinator(appSettings: flowParameters.appSettings, appHooks: flowParameters.appHooks) + let coordinator = DeveloperOptionsScreenCoordinator(appSettings: flowParameters.appSettings, + appHooks: flowParameters.appHooks, + clientProxy: flowParameters.userSession.clientProxy) coordinator.actions .sink { [weak self] action in diff --git a/ElementX/Sources/Mocks/ClientProxyMock.swift b/ElementX/Sources/Mocks/ClientProxyMock.swift index 635df96d3..93311c049 100644 --- a/ElementX/Sources/Mocks/ClientProxyMock.swift +++ b/ElementX/Sources/Mocks/ClientProxyMock.swift @@ -150,5 +150,7 @@ extension ClientProxyMock { underlyingHideInviteAvatarsPublisher = CurrentValueSubject(configuration.hideInviteAvatars).asCurrentValuePublisher() underlyingMaxMediaUploadSize = .success(configuration.maxMediaUploadSize) + + storeSizesReturnValue = .success(.init(cryptoStore: 1, stateStore: 9, eventCacheStore: 8, mediaStore: 6)) } } diff --git a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift index 1012511a7..e790d0765 100644 --- a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift +++ b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift @@ -4707,6 +4707,70 @@ class ClientProxyMock: ClientProxyProtocol, @unchecked Sendable { return optimizeStoresReturnValue } } + //MARK: - storeSizes + + var storeSizesUnderlyingCallsCount = 0 + var storeSizesCallsCount: Int { + get { + if Thread.isMainThread { + return storeSizesUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = storeSizesUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + storeSizesUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + storeSizesUnderlyingCallsCount = newValue + } + } + } + } + var storeSizesCalled: Bool { + return storeSizesCallsCount > 0 + } + + var storeSizesUnderlyingReturnValue: Result! + var storeSizesReturnValue: Result! { + get { + if Thread.isMainThread { + return storeSizesUnderlyingReturnValue + } else { + var returnValue: Result? = nil + DispatchQueue.main.sync { + returnValue = storeSizesUnderlyingReturnValue + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + storeSizesUnderlyingReturnValue = newValue + } else { + DispatchQueue.main.sync { + storeSizesUnderlyingReturnValue = newValue + } + } + } + } + var storeSizesClosure: (() async -> Result)? + + func storeSizes() async -> Result { + storeSizesCallsCount += 1 + if let storeSizesClosure = storeSizesClosure { + return await storeSizesClosure() + } else { + return storeSizesReturnValue + } + } //MARK: - fetchMediaPreviewConfiguration var fetchMediaPreviewConfigurationUnderlyingCallsCount = 0 diff --git a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenCoordinator.swift b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenCoordinator.swift index 981daadbc..fd98f45c2 100644 --- a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenCoordinator.swift +++ b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenCoordinator.swift @@ -23,10 +23,11 @@ final class DeveloperOptionsScreenCoordinator: CoordinatorProtocol { actionsSubject.eraseToAnyPublisher() } - init(appSettings: AppSettings, appHooks: AppHooks) { + init(appSettings: AppSettings, appHooks: AppHooks, clientProxy: ClientProxyProtocol) { viewModel = DeveloperOptionsScreenViewModel(developerOptions: appSettings, elementCallBaseURL: appSettings.elementCallBaseURL, - appHooks: appHooks) + appHooks: appHooks, + clientProxy: clientProxy) viewModel.actions .sink { [weak self] action in diff --git a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift index b4eba7b24..d0a50f860 100644 --- a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift +++ b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenModels.swift @@ -15,7 +15,17 @@ enum DeveloperOptionsScreenViewModelAction { struct DeveloperOptionsScreenViewState: BindableState { let elementCallBaseURL: URL let appHooks: AppHooks + var storeSizes: [StoreSize]? var bindings: DeveloperOptionsScreenViewStateBindings + + struct StoreSize: Identifiable { + let name: String + let size: String + + var id: String { + name + size + } + } } // periphery: ignore - subscripts are seen as false positive diff --git a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenViewModel.swift b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenViewModel.swift index ee70b54a1..861f082cf 100644 --- a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenViewModel.swift +++ b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/DeveloperOptionsScreenViewModel.swift @@ -18,11 +18,32 @@ class DeveloperOptionsScreenViewModel: DeveloperOptionsScreenViewModelType, Deve actionsSubject.eraseToAnyPublisher() } - init(developerOptions: DeveloperOptionsProtocol, elementCallBaseURL: URL, appHooks: AppHooks) { - let bindings = DeveloperOptionsScreenViewStateBindings(developerOptions: developerOptions) - let state = DeveloperOptionsScreenViewState(elementCallBaseURL: elementCallBaseURL, appHooks: appHooks, bindings: bindings) + init(developerOptions: DeveloperOptionsProtocol, elementCallBaseURL: URL, appHooks: AppHooks, clientProxy: ClientProxyProtocol) { + super.init(initialViewState: .init(elementCallBaseURL: elementCallBaseURL, + appHooks: appHooks, + bindings: .init(developerOptions: developerOptions))) - super.init(initialViewState: state) + Task { + if case let .success(sizes) = await clientProxy.storeSizes() { + let formatter = ByteCountFormatStyle(style: .file) + + var components = [DeveloperOptionsScreenViewState.StoreSize]() + if let cryptoStore = sizes.cryptoStore { + components.append(.init(name: "CryptoStore", size: formatter.format(Int64(cryptoStore)))) + } + if let stateStore = sizes.stateStore { + components.append(.init(name: "StateStore", size: formatter.format(Int64(stateStore)))) + } + if let eventCacheStore = sizes.eventCacheStore { + components.append(.init(name: "EventCacheStore", size: formatter.format(Int64(eventCacheStore)))) + } + if let mediaStore = sizes.mediaStore { + components.append(.init(name: "MediaStore", size: formatter.format(Int64(mediaStore)))) + } + + state.storeSizes = components + } + } } override func process(viewAction: DeveloperOptionsScreenViewAction) { diff --git a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift index e3c939f4e..9ea07a664 100644 --- a/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift +++ b/ElementX/Sources/Screens/Settings/DeveloperOptionsScreen/View/DeveloperOptionsScreen.swift @@ -21,6 +21,14 @@ struct DeveloperOptionsScreen: View { var body: some View { Form { + if let storeSizes = context.viewState.storeSizes { + Section("Usage") { + ForEach(storeSizes) { storeSize in + LabeledContent(storeSize.name, value: storeSize.size) + } + } + } + Section("Logging") { LogLevelConfigurationView(logLevel: $context.logLevel) @@ -207,7 +215,8 @@ private extension Set { struct DeveloperOptionsScreen_Previews: PreviewProvider { static let viewModel = DeveloperOptionsScreenViewModel(developerOptions: ServiceLocator.shared.settings, elementCallBaseURL: ServiceLocator.shared.settings.elementCallBaseURL, - appHooks: AppHooks()) + appHooks: AppHooks(), + clientProxy: ClientProxyMock(.init())) static var previews: some View { NavigationStack { DeveloperOptionsScreen(context: viewModel.context) diff --git a/ElementX/Sources/Services/Client/ClientProxy.swift b/ElementX/Sources/Services/Client/ClientProxy.swift index bcd92eaeb..6069294d1 100644 --- a/ElementX/Sources/Services/Client/ClientProxy.swift +++ b/ElementX/Sources/Services/Client/ClientProxy.swift @@ -816,6 +816,15 @@ class ClientProxy: ClientProxyProtocol { } } + func storeSizes() async -> Result { + do { + return try await .success(client.getStoreSizes()) + } catch { + MXLog.error("Failed optimizing client stores with error: \(error)") + return .failure(.sdkError(error)) + } + } + func fetchMediaPreviewConfiguration() async -> Result { do { let config = try await client.fetchMediaPreviewConfig() diff --git a/ElementX/Sources/Services/Client/ClientProxyProtocol.swift b/ElementX/Sources/Services/Client/ClientProxyProtocol.swift index bf1e1c511..2916679c9 100644 --- a/ElementX/Sources/Services/Client/ClientProxyProtocol.swift +++ b/ElementX/Sources/Services/Client/ClientProxyProtocol.swift @@ -217,6 +217,8 @@ protocol ClientProxyProtocol: AnyObject { @discardableResult func optimizeStores() async -> Result + func storeSizes() async -> Result + func fetchMediaPreviewConfiguration() async -> Result // MARK: - Ignored users