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
10 changed files with 93 additions and 7 deletions
@@ -159,6 +159,8 @@ class AppCoordinator: AppCoordinatorProtocol {
self.logout(isSoft: isSoft) self.logout(isSoft: isSoft)
case (.signingOut, .completedSigningOut(let isSoft), .signedOut): case (.signingOut, .completedSigningOut(let isSoft), .signedOut):
self.presentSplashScreen(isSoftLogout: isSoft) self.presentSplashScreen(isSoftLogout: isSoft)
case (.signedIn, .clearCache, .initial):
clearCache()
default: default:
fatalError("Unknown transition: \(context)") fatalError("Unknown transition: \(context)")
} }
@@ -238,6 +240,8 @@ class AppCoordinator: AppCoordinatorProtocol {
switch action { switch action {
case .signOut: case .signOut:
self?.stateMachine.processEvent(.signOut(isSoft: false)) self?.stateMachine.processEvent(.signOut(isSoft: false))
case .clearCache:
self?.stateMachine.processEvent(.clearCache)
} }
} }
@@ -428,6 +432,25 @@ class AppCoordinator: AppCoordinatorProtocol {
storedAppRoute = appRoute 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 // MARK: - AuthenticationCoordinatorDelegate
@@ -52,6 +52,9 @@ class AppCoordinatorStateMachine {
case signOut(isSoft: Bool) case signOut(isSoft: Bool)
/// Signing out completed /// Signing out completed
case completedSigningOut(isSoft: Bool) case completedSigningOut(isSoft: Bool)
/// Request cache clearing
case clearCache
} }
private let stateMachine: StateMachine<State, Event> private let stateMachine: StateMachine<State, Event>
@@ -71,6 +74,8 @@ class AppCoordinatorStateMachine {
stateMachine.addRoutes(event: .startWithExistingSession, transitions: [.initial => .restoringSession]) stateMachine.addRoutes(event: .startWithExistingSession, transitions: [.initial => .restoringSession])
stateMachine.addRoutes(event: .createdUserSession, transitions: [.restoringSession => .signedIn]) stateMachine.addRoutes(event: .createdUserSession, transitions: [.restoringSession => .signedIn])
stateMachine.addRoutes(event: .failedRestoringSession, transitions: [.restoringSession => .signedOut]) stateMachine.addRoutes(event: .failedRestoringSession, transitions: [.restoringSession => .signedOut])
stateMachine.addRoutes(event: .clearCache, transitions: [.signedIn => .initial])
// Transitions with associated values need to be handled through `addRouteMapping` // Transitions with associated values need to be handled through `addRouteMapping`
stateMachine.addRouteMapping { event, fromState, _ in stateMachine.addRouteMapping { event, fromState, _ in
@@ -16,11 +16,23 @@
import SwiftUI import SwiftUI
enum DeveloperOptionsScreenCoordinatorAction {
case clearCache
}
final class DeveloperOptionsScreenCoordinator: CoordinatorProtocol { final class DeveloperOptionsScreenCoordinator: CoordinatorProtocol {
private let viewModel: DeveloperOptionsScreenViewModelProtocol private var viewModel: DeveloperOptionsScreenViewModelProtocol
var callback: ((DeveloperOptionsScreenCoordinatorAction) -> Void)?
init() { init() {
viewModel = DeveloperOptionsScreenViewModel() viewModel = DeveloperOptionsScreenViewModel()
viewModel.callback = { [weak self] action in
switch action {
case .clearCache:
self?.callback?(.clearCache)
}
}
} }
func toPresentable() -> AnyView { func toPresentable() -> AnyView {
@@ -16,7 +16,9 @@
import Foundation import Foundation
enum DeveloperOptionsScreenViewModelAction { } enum DeveloperOptionsScreenViewModelAction {
case clearCache
}
struct DeveloperOptionsScreenViewState: BindableState { struct DeveloperOptionsScreenViewState: BindableState {
var bindings: DeveloperOptionsScreenViewStateBindings var bindings: DeveloperOptionsScreenViewStateBindings
@@ -34,4 +36,5 @@ enum DeveloperOptionsScreenViewAction {
case changedStartChatFlowEnabled case changedStartChatFlowEnabled
case changedStartChatUserSuggestionsEnabled case changedStartChatUserSuggestionsEnabled
case changedInvitesFlowEnabled case changedInvitesFlowEnabled
case clearCache
} }
@@ -45,6 +45,8 @@ class DeveloperOptionsScreenViewModel: DeveloperOptionsScreenViewModelType, Deve
ServiceLocator.shared.settings.startChatUserSuggestionsEnabled = state.bindings.startChatUserSuggestionsEnabled ServiceLocator.shared.settings.startChatUserSuggestionsEnabled = state.bindings.startChatUserSuggestionsEnabled
case .changedInvitesFlowEnabled: case .changedInvitesFlowEnabled:
ServiceLocator.shared.settings.invitesFlowEnabled = state.bindings.invitesFlowEnabled ServiceLocator.shared.settings.invitesFlowEnabled = state.bindings.invitesFlowEnabled
case .clearCache:
callback?(.clearCache)
} }
} }
} }
@@ -59,9 +59,16 @@ struct DeveloperOptionsScreen: View {
Text("🥳") Text("🥳")
.frame(maxWidth: .infinity) .frame(maxWidth: .infinity)
} }
.buttonStyle(FormButtonStyle())
} }
.formSectionStyle()
Section {
Button(role: .destructive) {
context.send(viewAction: .clearCache)
} label: {
Text("Clear cache")
.frame(maxWidth: .infinity)
}
}
} }
.overlay(effectsView) .overlay(effectsView)
.scrollContentBackground(.hidden) .scrollContentBackground(.hidden)
@@ -26,6 +26,7 @@ struct SettingsScreenCoordinatorParameters {
enum SettingsScreenCoordinatorAction { enum SettingsScreenCoordinatorAction {
case dismiss case dismiss
case logout case logout
case clearCache
} }
final class SettingsScreenCoordinator: CoordinatorProtocol { final class SettingsScreenCoordinator: CoordinatorProtocol {
@@ -114,6 +115,14 @@ final class SettingsScreenCoordinator: CoordinatorProtocol {
private func presentDeveloperOptions() { private func presentDeveloperOptions() {
let coordinator = DeveloperOptionsScreenCoordinator() let coordinator = DeveloperOptionsScreenCoordinator()
coordinator.callback = { [weak self] action in
switch action {
case .clearCache:
self?.callback?(.clearCache)
}
}
parameters.navigationStackCoordinator?.push(coordinator) parameters.navigationStackCoordinator?.push(coordinator)
} }
@@ -19,6 +19,7 @@ import SwiftUI
enum UserSessionFlowCoordinatorAction { enum UserSessionFlowCoordinatorAction {
case signOut case signOut
case clearCache
} }
class UserSessionFlowCoordinator: CoordinatorProtocol { class UserSessionFlowCoordinator: CoordinatorProtocol {
@@ -304,6 +305,8 @@ class UserSessionFlowCoordinator: CoordinatorProtocol {
case .logout: case .logout:
self.navigationSplitCoordinator.setSheetCoordinator(nil) self.navigationSplitCoordinator.setSheetCoordinator(nil)
self.callback?(.signOut) self.callback?(.signOut)
case .clearCache:
self.callback?(.clearCache)
} }
} }
@@ -21,6 +21,7 @@ import MatrixRustSDK
class UserSessionStore: UserSessionStoreProtocol { class UserSessionStore: UserSessionStoreProtocol {
private let keychainController: KeychainControllerProtocol private let keychainController: KeychainControllerProtocol
private let backgroundTaskService: BackgroundTaskServiceProtocol private let backgroundTaskService: BackgroundTaskServiceProtocol
private let cachesFolderName = "matrix-sdk-state"
/// Whether or not there are sessions in the store. /// Whether or not there are sessions in the store.
var hasSessions: Bool { !keychainController.restorationTokens().isEmpty } var hasSessions: Bool { !keychainController.restorationTokens().isEmpty }
@@ -90,6 +91,11 @@ class UserSessionStore: UserSessionStoreProtocol {
deleteSessionDirectory(for: userID) deleteSessionDirectory(for: userID)
} }
func clearCacheFor(userSession: UserSessionProtocol) {
let userID = userSession.clientProxy.userID
deleteCachesFolder(for: userID)
}
// MARK: - Private // MARK: - Private
private func buildUserSessionWithClient(_ clientProxy: ClientProxyProtocol) -> UserSessionProtocol { private func buildUserSessionWithClient(_ clientProxy: ClientProxyProtocol) -> UserSessionProtocol {
@@ -140,9 +146,15 @@ class UserSessionStore: UserSessionStoreProtocol {
} }
private func deleteSessionDirectory(for userID: String) { private func deleteSessionDirectory(for userID: String) {
// Rust sanitises the user ID replacing invalid characters with an _ do {
let sanitisedUserID = userID.replacingOccurrences(of: ":", with: "_") try FileManager.default.removeItem(at: basePath(for: userID))
let url = baseDirectory.appendingPathComponent(sanitisedUserID) } catch {
MXLog.failure("Failed deleting the session data: \(error)")
}
}
private func deleteCachesFolder(for userID: String) {
let url = basePath(for: userID).appendingPathComponent(cachesFolderName)
do { do {
try FileManager.default.removeItem(at: url) try FileManager.default.removeItem(at: url)
@@ -150,4 +162,11 @@ class UserSessionStore: UserSessionStoreProtocol {
MXLog.failure("Failed deleting the session data: \(error)") 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)
}
} }
@@ -45,4 +45,7 @@ protocol UserSessionStoreProtocol {
/// Logs out of the specified session. /// Logs out of the specified session.
func logout(userSession: UserSessionProtocol) func logout(userSession: UserSessionProtocol)
/// Clears our all the matrix sdk state data for the specified session
func clearCacheFor(userSession: UserSessionProtocol)
} }