Simplify ClassicAppAccount loading (only load what we need).

This commit is contained in:
Doug
2026-03-09 16:45:17 +00:00
committed by Doug
parent 95b3a8fbb1
commit 06c6ed3b2d
3 changed files with 108 additions and 374 deletions
@@ -8,38 +8,23 @@
import Foundation
class ClassicAppAccountManager {
static let matrixKitFolder = "MatrixKit"
static let kMXKAccountsKey = "accountsV2"
static let kMXFileStoreFolder = "MXFileStore"
static let kMXFileStoreUsersFolder = "users"
static let cryptoStoreFolder = "MXCryptoStore"
private let cacheFolder: URL
private let aesKey: Data
private let iv: Data
private let cryptoStorePassphrase: Data
let cacheFolder: URL
let iv: Data
let aesKey: Data
private(set) var accounts: [ClassicAppAccount] = []
private(set) var accounts: [ClassicAppMXAccount] = []
private var users: [String: ClassicAppMXUser] = [:]
var activeAccounts: [ClassicAppAccount] {
accounts
.filter { !$0.isDisabled && !$0.isSoftLogout }
.compactMap(activeAccount)
}
init(cacheFolder: URL, iv: Data, aesKey: Data) {
init(cacheFolder: URL, aesKey: Data, iv: Data, cryptoStorePassphrase: Data) {
self.cacheFolder = cacheFolder
self.iv = iv
self.aesKey = aesKey
}
/// Return the path of the file containing stored MXAccounts array
func accountFile() -> URL {
cacheFolder.appending(component: Self.matrixKitFolder).appending(component: Self.kMXKAccountsKey)
self.iv = iv
self.cryptoStorePassphrase = cryptoStorePassphrase
}
func loadAccounts() {
MXLog.info("Loading accounts from Classic app.")
let accountFile = accountFile()
if FileManager.default.fileExists(atPath: accountFile.path(percentEncoded: false)) {
let startDate = Date()
@@ -51,103 +36,58 @@ class ClassicAppAccountManager {
let unciphered = try ClassicAppAES.decrypt(fileContent, aesKey: aesKey, iv: iv)
let decoder = NSKeyedUnarchiver(forReadingWith: unciphered)
decoder.setClass(ClassicAppMXAccount.self, forClassName: "MXKAccount")
decoder.setClass(ClassicAppMXThirdPartyIdentifier.self, forClassName: "MXThirdPartyIdentifier")
decoder.setClass(ClassicAppMXDevice.self, forClassName: "MXDevice")
guard let accounts = decoder.decodeObject(forKey: "mxAccounts") as? [ClassicAppMXAccount] else {
guard let mxAccounts = decoder.decodeObject(forKey: "mxAccounts") as? [ClassicAppMXAccount] else {
MXLog.error("Failed to decode accounts.")
return
}
self.accounts = accounts
MXLog.info("\(mxAccounts.count) accounts loaded in \(Date().timeIntervalSince(startDate) * 1000)ms.")
MXLog.info("[MXKAccountManager] loadAccounts. \(accounts.count) accounts loaded in \(Date().timeIntervalSince(startDate) * 1000)ms")
accounts = mxAccounts
.filter(\.isActive)
.compactMap(makeAccount)
MXLog.info("\(mxAccounts.count) active accounts available.")
} catch {
MXLog.error("Failed to load account file: \(error)")
}
for account in activeAccounts {
if let user = loadUsers([account.userID], forAccount: account.userID).first {
users[user.userID] = user
}
}
}
}
// MARK: - Private
private func makeAccount(for mxAccount: ClassicAppMXAccount) -> ClassicAppAccount? {
let userID = mxAccount.userID
let user = loadUser(for: mxAccount)
if accounts.isEmpty {
MXLog.info("[MXKAccountManager] loadAccounts. No accounts")
}
}
/// From `MXCryptoMachineStore`
func cryptoStoreURL(for userID: String) -> URL {
cacheFolder.appending(component: Self.cryptoStoreFolder).appending(component: userID)
}
var fileStorePath: URL {
cacheFolder.appending(component: Self.kMXFileStoreFolder)
}
func storePath(for userID: String) -> URL {
fileStorePath.appending(component: userID)
}
/// This store contains all of the users known to the specific user ID.
func storeUsersPath(for userID: String) -> URL {
storePath(for: userID).appending(component: Self.kMXFileStoreUsersFolder)
}
func loadUsers(_ userIDs: [String], forAccount accountUserID: String) -> [ClassicAppMXUser] {
// Determine which groups to load based on userIds
var groups: [String: [String]] = [:]
for userID in userIDs {
let groupID = String(userID.hash % 100)
if groups[groupID] != nil {
groups[groupID]?.append(userID)
} else {
groups[groupID] = [userID]
}
}
let usersFolder = storeUsersPath(for: accountUserID)
var loadedUsers: [ClassicAppMXUser] = []
for group in groups.keys {
autoreleasepool {
let groupFile = usersFolder.appendingPathComponent(group)
// Load stored users in this group
do {
let fileContent = try Data(contentsOf: groupFile)
let decoder = NSKeyedUnarchiver(forReadingWith: fileContent)
decoder.setClass(ClassicAppMXUser.self, forClassName: "MXUser")
decoder.setClass(ClassicAppMXUser.self, forClassName: "MXMyUser")
if let groupUsers = decoder.decodeObject(forKey: NSKeyedArchiveRootObjectKey) as? [String: ClassicAppMXUser] {
let usersToLoad = Set(groups[group] ?? [])
for user in groupUsers.values where usersToLoad.contains(user.userID) {
loadedUsers.append(user)
}
}
} catch {
MXLog.warning("[MXFileStore] Warning: MXFileRoomStore file for users group \(group) has been corrupted")
}
}
}
return loadedUsers
}
private func activeAccount(mxAccount: ClassicAppMXAccount) -> ClassicAppAccount? {
guard let userID = mxAccount.credentials.userID, let serverName = serverName(for: userID) else {
return nil
}
guard let serverName = serverName(for: userID) else { return nil }
return ClassicAppAccount(userID: userID,
displayName: users[userID]?.displayName,
displayName: user?.displayName,
avatarURL: user?.avatarURL.flatMap(URL.init(string:)),
serverName: serverName,
cryptoStoreURL: cryptoStoreURL(for: userID))
cryptoStoreURL: cryptoStoreURL(for: userID),
cryptoStorePassphrase: cryptoStorePassphrase)
}
private func loadUser(for mxAccount: ClassicAppMXAccount) -> ClassicAppMXUser? {
let userID = mxAccount.userID
let groupID = String(userID.hash % 100)
let groupFile = storeUsersPath(for: userID).appendingPathComponent(groupID)
do {
let fileContent = try Data(contentsOf: groupFile)
let decoder = NSKeyedUnarchiver(forReadingWith: fileContent)
decoder.setClass(ClassicAppMXUser.self, forClassName: "MXUser")
decoder.setClass(ClassicAppMXUser.self, forClassName: "MXMyUser")
let groupUsers = decoder.decodeObject(forKey: NSKeyedArchiveRootObjectKey) as? [String: ClassicAppMXUser]
return groupUsers?[userID]
} catch {
MXLog.warning("Users group \(groupID) file for \(mxAccount.userID) has been corrupted.")
return nil
}
}
/// The server name extracted from the user's ID.
@@ -157,43 +97,35 @@ class ClassicAppAccountManager {
guard components.count > 1 else { return nil }
return components[1] // Directly use [1] as .last may be the port number.
}
}
// MARK: - Probably not needed
private extension ClassicAppAccountManager {
func loadUsers(forAccount accountUserID: String) {
let startDate = Date()
var users: [String: ClassicAppMXUser] = [:]
// Load all users which are distributed in several files
let storeUsersPath = storeUsersPath(for: accountUserID)
let groups = try? FileManager.default.contentsOfDirectory(atPath: storeUsersPath.path(percentEncoded: false))
if let groups {
for group in groups {
let groupFile = storeUsersPath.appending(path: group)
// Load stored users in this group
do {
let fileContent = try Data(contentsOf: groupFile)
let decoder = NSKeyedUnarchiver(forReadingWith: fileContent)
decoder.setClass(ClassicAppMXUser.self, forClassName: "MXUser")
decoder.setClass(ClassicAppMXUser.self, forClassName: "MXMyUser")
if let groupUsers = decoder.decodeObject(forKey: NSKeyedArchiveRootObjectKey) as? [String: ClassicAppMXUser] {
// Append them
users.merge(groupUsers) { _, new in new }
}
} catch {
MXLog.error("Failed to load users from group \(group): \(error)")
}
}
}
MXLog.debug("[MXFileStore] Loaded \(users.count) MXUsers in \(Date().timeIntervalSince(startDate) * 1000)ms")
self.users = users
// MARK: - File URLs
private static let matrixKitFolder = "MatrixKit"
private static let cryptoStoreFolder = "MXCryptoStore"
private static let kMXKAccountsKey = "accountsV2"
private static let kMXFileStoreFolder = "MXFileStore"
private static let kMXFileStoreUsersFolder = "users"
/// The file URL that contains the app's `MXAccounts` array.
private func accountFile() -> URL {
cacheFolder.appending(component: Self.matrixKitFolder).appending(component: Self.kMXKAccountsKey)
}
/// The database file URL as defined in `MXCryptoMachineStore`.
private func cryptoStoreURL(for userID: String) -> URL {
cacheFolder.appending(component: Self.cryptoStoreFolder).appending(component: userID)
}
/// This store contains all of the users known to the specific user ID.
private func storeUsersPath(for userID: String) -> URL {
storePath(for: userID).appending(component: Self.kMXFileStoreUsersFolder)
}
private func storePath(for userID: String) -> URL {
fileStorePath.appending(component: userID)
}
private var fileStorePath: URL {
cacheFolder.appending(component: Self.kMXFileStoreFolder)
}
}