Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make the SessionDirectories type responsible for cleaning up data. #3261

Merged
merged 1 commit into from
Sep 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions ElementX/Sources/Other/Extensions/FileManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -57,4 +57,8 @@ extension FileManager {

return size
}

func numberOfItems(at url: URL) throws -> Int {
try contentsOfDirectory(at: url, includingPropertiesForKeys: nil).count
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -140,13 +140,7 @@ class AuthenticationService: AuthenticationServiceProtocol {
}

private func rotateSessionDirectory() {
if FileManager.default.directoryExists(at: sessionDirectories.dataDirectory) {
try? FileManager.default.removeItem(at: sessionDirectories.dataDirectory)
}
if FileManager.default.directoryExists(at: sessionDirectories.cacheDirectory) {
try? FileManager.default.removeItem(at: sessionDirectories.cacheDirectory)
}

sessionDirectories.delete()
sessionDirectories = .init()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,8 +110,7 @@ class KeychainController: KeychainControllerProtocol {
fatalError("Something has gone mega wrong, all bets are off.")
}
let restorationToken = RestorationToken(session: session,
sessionDirectory: oldToken.sessionDirectory,
cacheDirectory: oldToken.cacheDirectory,
sessionDirectories: oldToken.sessionDirectories,
passphrase: oldToken.passphrase,
pusherNotificationClientIdentifier: oldToken.pusherNotificationClientIdentifier)
setRestorationToken(restorationToken, forUsername: session.userId)
Expand Down
8 changes: 1 addition & 7 deletions ElementX/Sources/Services/QRCode/QRCodeLoginService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,7 @@ final class QRCodeLoginService: QRCodeLoginServiceProtocol {
}

private func rotateSessionDirectory() {
if FileManager.default.directoryExists(at: sessionDirectories.dataDirectory) {
try? FileManager.default.removeItem(at: sessionDirectories.dataDirectory)
}
if FileManager.default.directoryExists(at: sessionDirectories.cacheDirectory) {
try? FileManager.default.removeItem(at: sessionDirectories.cacheDirectory)
}

sessionDirectories.delete()
sessionDirectories = .init()
}

Expand Down
23 changes: 19 additions & 4 deletions ElementX/Sources/Services/UserSession/RestorationToken.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,17 @@ import MatrixRustSDK

struct RestorationToken: Equatable {
let session: MatrixRustSDK.Session
let sessionDirectory: URL
let cacheDirectory: URL
let sessionDirectories: SessionDirectories
let passphrase: String?
let pusherNotificationClientIdentifier: String?

enum CodingKeys: CodingKey {
case session
case sessionDirectory
case cacheDirectory
case passphrase
case pusherNotificationClientIdentifier
}
}

extension RestorationToken: Codable {
Expand All @@ -35,11 +42,19 @@ extension RestorationToken: Codable {
}

self = try .init(session: session,
sessionDirectory: sessionDirectories.dataDirectory,
cacheDirectory: sessionDirectories.cacheDirectory,
sessionDirectories: sessionDirectories,
passphrase: container.decodeIfPresent(String.self, forKey: .passphrase),
pusherNotificationClientIdentifier: container.decodeIfPresent(String.self, forKey: .pusherNotificationClientIdentifier))
}

func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(session, forKey: .session)
try container.encode(sessionDirectories.dataDirectory, forKey: .sessionDirectory)
try container.encode(sessionDirectories.cacheDirectory, forKey: .cacheDirectory)
try container.encode(passphrase, forKey: .passphrase)
try container.encode(pusherNotificationClientIdentifier, forKey: .pusherNotificationClientIdentifier)
}
}

extension MatrixRustSDK.Session: Codable {
Expand Down
44 changes: 44 additions & 0 deletions ElementX/Sources/Services/UserSession/SessionDirectories.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,50 @@ struct SessionDirectories: Hashable, Codable {

var dataPath: String { dataDirectory.path(percentEncoded: false) }
var cachePath: String { cacheDirectory.path(percentEncoded: false) }

// MARK: Data Management

/// Removes the directories from disk if they have been created.
func delete() {
do {
if FileManager.default.directoryExists(at: dataDirectory) {
try FileManager.default.removeItem(at: dataDirectory)
}
} catch {
MXLog.failure("Failed deleting the session data: \(error)")
}
do {
if FileManager.default.directoryExists(at: cacheDirectory) {
try FileManager.default.removeItem(at: cacheDirectory)
}
} catch {
MXLog.failure("Failed deleting the session caches: \(error)")
}
}

/// Deletes the Rust state store and event cache data, leaving the crypto store and both
/// session directories in place along with any other data that may have been written in them.
func deleteTransientUserData() {
do {
let prefix = "matrix-sdk-state"
try deleteFiles(at: dataDirectory, with: prefix)
} catch {
MXLog.failure("Failed clearing state store: \(error)")
}
do {
let prefix = "matrix-sdk-event-cache"
try deleteFiles(at: cacheDirectory, with: prefix)
} catch {
MXLog.failure("Failed clearing event cache store: \(error)")
}
}

private func deleteFiles(at url: URL, with prefix: String) throws {
let sessionDirectoryContents = try FileManager.default.contentsOfDirectory(at: url, includingPropertiesForKeys: nil)
for url in sessionDirectoryContents where url.lastPathComponent.hasPrefix(prefix) {
try FileManager.default.removeItem(at: url)
}
}
}

extension SessionDirectories {
Expand Down
47 changes: 6 additions & 41 deletions ElementX/Sources/Services/UserSession/UserSessionStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ class UserSessionStore: UserSessionStoreProtocol {
private let appSettings: AppSettings
private let networkMonitor: NetworkMonitorProtocol
private let appHooks: AppHooks
private let matrixSDKStateKey = "matrix-sdk-state"

/// Whether or not there are sessions in the store.
var hasSessions: Bool { !keychainController.restorationTokens().isEmpty }
Expand Down Expand Up @@ -55,7 +54,7 @@ class UserSessionStore: UserSessionStoreProtocol {

// On any restoration failure reset the token and restart
keychainController.removeRestorationTokenForUsername(credentials.userID)
deleteSessionDirectories(for: credentials)
credentials.restorationToken.sessionDirectories.delete()

return .failure(error)
}
Expand All @@ -68,8 +67,7 @@ class UserSessionStore: UserSessionStoreProtocol {
let clientProxy = await setupProxyForClient(client)

keychainController.setRestorationToken(RestorationToken(session: session,
sessionDirectory: sessionDirectories.dataDirectory,
cacheDirectory: sessionDirectories.cacheDirectory,
sessionDirectories: sessionDirectories,
passphrase: passphrase,
pusherNotificationClientIdentifier: clientProxy.pusherNotificationClientIdentifier),
forUsername: userID)
Expand All @@ -87,7 +85,7 @@ class UserSessionStore: UserSessionStoreProtocol {
keychainController.removeRestorationTokenForUsername(userID)

if let credentials {
deleteSessionDirectories(for: credentials)
credentials.restorationToken.sessionDirectories.delete()
}
}

Expand All @@ -96,7 +94,7 @@ class UserSessionStore: UserSessionStoreProtocol {
MXLog.error("Failed to clearing caches: Credentials missing")
return
}
deleteCaches(for: credentials)
credentials.restorationToken.sessionDirectories.deleteTransientUserData()
}

// MARK: - Private
Expand Down Expand Up @@ -125,8 +123,8 @@ class UserSessionStore: UserSessionStoreProtocol {
slidingSync: .restored,
sessionDelegate: keychainController,
appHooks: appHooks)
.sessionPaths(dataPath: credentials.restorationToken.sessionDirectory.path(percentEncoded: false),
cachePath: credentials.restorationToken.cacheDirectory.path(percentEncoded: false))
.sessionPaths(dataPath: credentials.restorationToken.sessionDirectories.dataPath,
cachePath: credentials.restorationToken.sessionDirectories.cachePath)
.username(username: credentials.userID)
.homeserverUrl(url: homeserverURL)
.passphrase(passphrase: credentials.restorationToken.passphrase)
Expand All @@ -148,37 +146,4 @@ class UserSessionStore: UserSessionStoreProtocol {
networkMonitor: networkMonitor,
appSettings: appSettings)
}

private func deleteSessionDirectories(for credentials: KeychainCredentials) {
do {
try FileManager.default.removeItem(at: credentials.restorationToken.sessionDirectory)
} catch {
MXLog.failure("Failed deleting the session data: \(error)")
}
do {
try FileManager.default.removeItem(at: credentials.restorationToken.cacheDirectory)
} catch {
MXLog.failure("Failed deleting the session caches: \(error)")
}
}

private func deleteCaches(for credentials: KeychainCredentials) {
do {
try deleteContentsOfDirectory(at: credentials.restorationToken.sessionDirectory)
} catch {
MXLog.failure("Failed clearing state store: \(error)")
}
do {
try deleteContentsOfDirectory(at: credentials.restorationToken.cacheDirectory)
} catch {
MXLog.failure("Failed clearing event cache store: \(error)")
}
}

private func deleteContentsOfDirectory(at url: URL) throws {
let sessionDirectoryContents = try FileManager.default.contentsOfDirectory(at: url, includingPropertiesForKeys: nil)
for url in sessionDirectoryContents where url.path.contains(matrixSDKStateKey) {
try FileManager.default.removeItem(at: url)
}
}
}
4 changes: 2 additions & 2 deletions NSE/Sources/Other/NSEUserSession.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,8 @@ final class NSEUserSession {
slidingSync: .restored,
sessionDelegate: clientSessionDelegate,
appHooks: appHooks)
.sessionPaths(dataPath: credentials.restorationToken.sessionDirectory.path(percentEncoded: false),
cachePath: credentials.restorationToken.cacheDirectory.path(percentEncoded: false))
.sessionPaths(dataPath: credentials.restorationToken.sessionDirectories.dataPath,
cachePath: credentials.restorationToken.sessionDirectories.cachePath)
.username(username: credentials.userID)
.homeserverUrl(url: homeserverURL)
.passphrase(passphrase: credentials.restorationToken.passphrase)
Expand Down
15 changes: 5 additions & 10 deletions UnitTests/Sources/KeychainControllerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,7 @@ class KeychainControllerTests: XCTestCase {
homeserverUrl: "homeserverUrl",
oidcData: "oidcData",
slidingSyncVersion: .proxy(url: "https://my.sync.proxy")),
sessionDirectory: .homeDirectory.appending(component: UUID().uuidString),
cacheDirectory: .homeDirectory.appending(component: UUID().uuidString),
sessionDirectories: .init(),
passphrase: "passphrase",
pusherNotificationClientIdentifier: "pusherClientID")
keychain.setRestorationToken(restorationToken, forUsername: username)
Expand All @@ -51,8 +50,7 @@ class KeychainControllerTests: XCTestCase {
homeserverUrl: "homeserverUrl",
oidcData: "oidcData",
slidingSyncVersion: .proxy(url: "https://my.sync.proxy")),
sessionDirectory: .homeDirectory.appending(component: UUID().uuidString),
cacheDirectory: .homeDirectory.appending(component: UUID().uuidString),
sessionDirectories: .init(),
passphrase: "passphrase",
pusherNotificationClientIdentifier: "pusherClientID")
keychain.setRestorationToken(restorationToken, forUsername: username)
Expand All @@ -77,8 +75,7 @@ class KeychainControllerTests: XCTestCase {
homeserverUrl: "homeserverUrl",
oidcData: "oidcData",
slidingSyncVersion: .proxy(url: "https://my.sync.proxy")),
sessionDirectory: .homeDirectory.appending(component: UUID().uuidString),
cacheDirectory: .homeDirectory.appending(component: UUID().uuidString),
sessionDirectories: .init(),
passphrase: "passphrase",
pusherNotificationClientIdentifier: "pusherClientID")
keychain.setRestorationToken(restorationToken, forUsername: "@test\(index):example.com")
Expand All @@ -102,8 +99,7 @@ class KeychainControllerTests: XCTestCase {
homeserverUrl: "homeserverUrl",
oidcData: "oidcData",
slidingSyncVersion: .proxy(url: "https://my.sync.proxy")),
sessionDirectory: .homeDirectory.appending(component: UUID().uuidString),
cacheDirectory: .homeDirectory.appending(component: UUID().uuidString),
sessionDirectories: .init(),
passphrase: "passphrase",
pusherNotificationClientIdentifier: "pusherClientID")
keychain.setRestorationToken(restorationToken, forUsername: "@test\(index):example.com")
Expand Down Expand Up @@ -135,8 +131,7 @@ class KeychainControllerTests: XCTestCase {
homeserverUrl: "homeserverUrl",
oidcData: "oidcData",
slidingSyncVersion: .native),
sessionDirectory: .homeDirectory.appending(component: UUID().uuidString),
cacheDirectory: .homeDirectory.appending(component: UUID().uuidString),
sessionDirectories: .init(),
passphrase: "passphrase",
pusherNotificationClientIdentifier: "pusherClientID")
keychain.setRestorationToken(restorationToken, forUsername: username)
Expand Down
Loading
Loading