Add developer option for clearing the application cache (#852)

* Add developer option for clearing the application cache

* Tweaks following code review
This commit is contained in:
Stefan Ceriu 2023-05-05 19:41:21 +03:00 committed by GitHub
parent f679c0ca2a
commit 574424024c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 93 additions and 7 deletions

View File

@ -159,6 +159,8 @@ class AppCoordinator: AppCoordinatorProtocol {
self.logout(isSoft: isSoft)
case (.signingOut, .completedSigningOut(let isSoft), .signedOut):
self.presentSplashScreen(isSoftLogout: isSoft)
case (.signedIn, .clearCache, .initial):
clearCache()
default:
fatalError("Unknown transition: \(context)")
}
@ -238,6 +240,8 @@ class AppCoordinator: AppCoordinatorProtocol {
switch action {
case .signOut:
self?.stateMachine.processEvent(.signOut(isSoft: false))
case .clearCache:
self?.stateMachine.processEvent(.clearCache)
}
}
@ -428,6 +432,25 @@ class AppCoordinator: AppCoordinatorProtocol {
storedAppRoute = appRoute
}
}
private func clearCache() {
showLoadingIndicator()
defer {
hideLoadingIndicator()
}
navigationRootCoordinator.setRootCoordinator(SplashScreenCoordinator())
userSession.clientProxy.stopSync()
userSessionFlowCoordinator?.stop()
userSessionStore.clearCacheFor(userSession: userSession)
tearDownUserSession()
stateMachine.processEvent(.startWithExistingSession)
}
}
// MARK: - AuthenticationCoordinatorDelegate

View File

@ -52,6 +52,9 @@ class AppCoordinatorStateMachine {
case signOut(isSoft: Bool)
/// Signing out completed
case completedSigningOut(isSoft: Bool)
/// Request cache clearing
case clearCache
}
private let stateMachine: StateMachine<State, Event>
@ -71,6 +74,8 @@ class AppCoordinatorStateMachine {
stateMachine.addRoutes(event: .startWithExistingSession, transitions: [.initial => .restoringSession])
stateMachine.addRoutes(event: .createdUserSession, transitions: [.restoringSession => .signedIn])
stateMachine.addRoutes(event: .failedRestoringSession, transitions: [.restoringSession => .signedOut])
stateMachine.addRoutes(event: .clearCache, transitions: [.signedIn => .initial])
// Transitions with associated values need to be handled through `addRouteMapping`
stateMachine.addRouteMapping { event, fromState, _ in

View File

@ -16,11 +16,23 @@
import SwiftUI
enum DeveloperOptionsScreenCoordinatorAction {
case clearCache
}
final class DeveloperOptionsScreenCoordinator: CoordinatorProtocol {
private let viewModel: DeveloperOptionsScreenViewModelProtocol
private var viewModel: DeveloperOptionsScreenViewModelProtocol
var callback: ((DeveloperOptionsScreenCoordinatorAction) -> Void)?
init() {
viewModel = DeveloperOptionsScreenViewModel()
viewModel.callback = { [weak self] action in
switch action {
case .clearCache:
self?.callback?(.clearCache)
}
}
}
func toPresentable() -> AnyView {

View File

@ -16,7 +16,9 @@
import Foundation
enum DeveloperOptionsScreenViewModelAction { }
enum DeveloperOptionsScreenViewModelAction {
case clearCache
}
struct DeveloperOptionsScreenViewState: BindableState {
var bindings: DeveloperOptionsScreenViewStateBindings
@ -34,4 +36,5 @@ enum DeveloperOptionsScreenViewAction {
case changedStartChatFlowEnabled
case changedStartChatUserSuggestionsEnabled
case changedInvitesFlowEnabled
case clearCache
}

View File

@ -45,6 +45,8 @@ class DeveloperOptionsScreenViewModel: DeveloperOptionsScreenViewModelType, Deve
ServiceLocator.shared.settings.startChatUserSuggestionsEnabled = state.bindings.startChatUserSuggestionsEnabled
case .changedInvitesFlowEnabled:
ServiceLocator.shared.settings.invitesFlowEnabled = state.bindings.invitesFlowEnabled
case .clearCache:
callback?(.clearCache)
}
}
}

View File

@ -59,9 +59,16 @@ struct DeveloperOptionsScreen: View {
Text("🥳")
.frame(maxWidth: .infinity)
}
.buttonStyle(FormButtonStyle())
}
.formSectionStyle()
Section {
Button(role: .destructive) {
context.send(viewAction: .clearCache)
} label: {
Text("Clear cache")
.frame(maxWidth: .infinity)
}
}
}
.overlay(effectsView)
.scrollContentBackground(.hidden)

View File

@ -26,6 +26,7 @@ struct SettingsScreenCoordinatorParameters {
enum SettingsScreenCoordinatorAction {
case dismiss
case logout
case clearCache
}
final class SettingsScreenCoordinator: CoordinatorProtocol {
@ -114,6 +115,14 @@ final class SettingsScreenCoordinator: CoordinatorProtocol {
private func presentDeveloperOptions() {
let coordinator = DeveloperOptionsScreenCoordinator()
coordinator.callback = { [weak self] action in
switch action {
case .clearCache:
self?.callback?(.clearCache)
}
}
parameters.navigationStackCoordinator?.push(coordinator)
}

View File

@ -19,6 +19,7 @@ import SwiftUI
enum UserSessionFlowCoordinatorAction {
case signOut
case clearCache
}
class UserSessionFlowCoordinator: CoordinatorProtocol {
@ -304,6 +305,8 @@ class UserSessionFlowCoordinator: CoordinatorProtocol {
case .logout:
self.navigationSplitCoordinator.setSheetCoordinator(nil)
self.callback?(.signOut)
case .clearCache:
self.callback?(.clearCache)
}
}

View File

@ -21,6 +21,7 @@ import MatrixRustSDK
class UserSessionStore: UserSessionStoreProtocol {
private let keychainController: KeychainControllerProtocol
private let backgroundTaskService: BackgroundTaskServiceProtocol
private let cachesFolderName = "matrix-sdk-state"
/// Whether or not there are sessions in the store.
var hasSessions: Bool { !keychainController.restorationTokens().isEmpty }
@ -90,6 +91,11 @@ class UserSessionStore: UserSessionStoreProtocol {
deleteSessionDirectory(for: userID)
}
func clearCacheFor(userSession: UserSessionProtocol) {
let userID = userSession.clientProxy.userID
deleteCachesFolder(for: userID)
}
// MARK: - Private
private func buildUserSessionWithClient(_ clientProxy: ClientProxyProtocol) -> UserSessionProtocol {
@ -140,9 +146,15 @@ class UserSessionStore: UserSessionStoreProtocol {
}
private func deleteSessionDirectory(for userID: String) {
// Rust sanitises the user ID replacing invalid characters with an _
let sanitisedUserID = userID.replacingOccurrences(of: ":", with: "_")
let url = baseDirectory.appendingPathComponent(sanitisedUserID)
do {
try FileManager.default.removeItem(at: basePath(for: userID))
} catch {
MXLog.failure("Failed deleting the session data: \(error)")
}
}
private func deleteCachesFolder(for userID: String) {
let url = basePath(for: userID).appendingPathComponent(cachesFolderName)
do {
try FileManager.default.removeItem(at: url)
@ -150,4 +162,11 @@ class UserSessionStore: UserSessionStoreProtocol {
MXLog.failure("Failed deleting the session data: \(error)")
}
}
#warning("We should move this and the caches folder path to the rust side")
private func basePath(for userID: String) -> URL {
// Rust sanitises the user ID replacing invalid characters with an _
let sanitisedUserID = userID.replacingOccurrences(of: ":", with: "_")
return baseDirectory.appendingPathComponent(sanitisedUserID)
}
}

View File

@ -45,4 +45,7 @@ protocol UserSessionStoreProtocol {
/// Logs out of the specified session.
func logout(userSession: UserSessionProtocol)
/// Clears our all the matrix sdk state data for the specified session
func clearCacheFor(userSession: UserSessionProtocol)
}