From 469cc3d1e50ebe150f9dba38014bae08e2537352 Mon Sep 17 00:00:00 2001 From: Philippe Weidmann Date: Fri, 19 Jan 2024 14:48:58 +0100 Subject: [PATCH 1/2] fix: Use uid instead of id to differentiate files Signed-off-by: Philippe Weidmann --- .../File List/InMemoryFileListViewModel.swift | 2 +- .../Files/RootMenuViewController.swift | 4 +- .../Menu/Trash/TrashListViewModel.swift | 2 +- kDriveCore/Data/Api/Endpoint.swift | 6 ++- .../Data/Cache/DriveFileManager+Listing.swift | 10 ++-- kDriveCore/Data/Cache/DriveFileManager.swift | 47 ++++++++++--------- kDriveCore/Data/Models/File.swift | 14 ++++-- 7 files changed, 50 insertions(+), 35 deletions(-) diff --git a/kDrive/UI/Controller/Files/File List/InMemoryFileListViewModel.swift b/kDrive/UI/Controller/Files/File List/InMemoryFileListViewModel.swift index 7dfb815e8..6080d06df 100644 --- a/kDrive/UI/Controller/Files/File List/InMemoryFileListViewModel.swift +++ b/kDrive/UI/Controller/Files/File List/InMemoryFileListViewModel.swift @@ -79,7 +79,7 @@ class InMemoryFileListViewModel: FileListViewModel { func removeFiles(_ files: [ProxyFile]) { try? realm.write { for file in files { - if let file = realm.object(ofType: File.self, forPrimaryKey: file.id), !file.isInvalidated { + if let file = realm.object(ofType: File.self, forPrimaryKey: file.uid), !file.isInvalidated { realm.delete(file) } } diff --git a/kDrive/UI/Controller/Files/RootMenuViewController.swift b/kDrive/UI/Controller/Files/RootMenuViewController.swift index 126a3ef64..81ea3e4b0 100644 --- a/kDrive/UI/Controller/Files/RootMenuViewController.swift +++ b/kDrive/UI/Controller/Files/RootMenuViewController.swift @@ -117,8 +117,8 @@ class RootMenuViewController: CustomLargeTitleCollectionViewController, SelectSw configureDataSource() - let rootChildren = driveFileManager.getRealm() - .object(ofType: File.self, forPrimaryKey: DriveFileManager.constants.rootID)?.children + let rootFileUid = File.uid(driveId: driveFileManager.drive.id, fileId: DriveFileManager.constants.rootID) + let rootChildren = driveFileManager.getRealm().object(ofType: File.self, forPrimaryKey: rootFileUid)?.children rootChildrenObservationToken = rootChildren?.observe { [weak self] changes in guard let self else { return } switch changes { diff --git a/kDrive/UI/Controller/Menu/Trash/TrashListViewModel.swift b/kDrive/UI/Controller/Menu/Trash/TrashListViewModel.swift index 6fa571c0c..8431170af 100644 --- a/kDrive/UI/Controller/Menu/Trash/TrashListViewModel.swift +++ b/kDrive/UI/Controller/Menu/Trash/TrashListViewModel.swift @@ -321,7 +321,7 @@ class MultipleSelectionTrashViewModel: MultipleSelectionFileListViewModel { guard let realm = try? Realm(configuration: realmConfiguration) else { return } try? realm.write { for file in deletedFiles { - if let file = realm.object(ofType: File.self, forPrimaryKey: file.id), !file.isInvalidated { + if let file = realm.object(ofType: File.self, forPrimaryKey: file.uid), !file.isInvalidated { realm.delete(file) } } diff --git a/kDriveCore/Data/Api/Endpoint.swift b/kDriveCore/Data/Api/Endpoint.swift index 18eab02f2..3e0737637 100644 --- a/kDriveCore/Data/Api/Endpoint.swift +++ b/kDriveCore/Data/Api/Endpoint.swift @@ -118,6 +118,10 @@ public protocol AbstractFile { } public struct ProxyFile: AbstractFile, Sendable { + public var uid: String { + File.uid(driveId: driveId, fileId: id) + } + public var driveId: Int public var id: Int public var isRoot: Bool { @@ -130,7 +134,7 @@ public struct ProxyFile: AbstractFile, Sendable { } func resolve(using realm: Realm) throws -> File { - guard let file = realm.object(ofType: File.self, forPrimaryKey: id), !file.isInvalidated else { + guard let file = realm.object(ofType: File.self, forPrimaryKey: uid), !file.isInvalidated else { throw DriveError.errorWithUserInfo(.fileNotFound, info: [.fileId: ErrorUserInfo(intValue: id)]) } return file diff --git a/kDriveCore/Data/Cache/DriveFileManager+Listing.swift b/kDriveCore/Data/Cache/DriveFileManager+Listing.swift index 5b18c1b9e..117bb9683 100644 --- a/kDriveCore/Data/Cache/DriveFileManager+Listing.swift +++ b/kDriveCore/Data/Cache/DriveFileManager+Listing.swift @@ -74,12 +74,14 @@ public extension DriveFileManager { !alreadyHandledActionIds.contains(fileAction.fileId) else { continue } alreadyHandledActionIds.insert(fileAction.fileId) + let fileUid = File.uid(driveId: directory.driveId, fileId: fileAction.fileId) + switch fileAction.action { case .fileDelete, .fileTrash: - removeFileInDatabase(fileId: fileAction.fileId, cascade: true, withTransaction: false, using: realm) + removeFileInDatabase(fileUid: fileUid, cascade: true, withTransaction: false, using: realm) case .fileMoveOut: - guard let movedOutFile: File = realm.getObject(id: fileAction.fileId), + guard let movedOutFile: File = realm.getObject(id: fileUid), let oldParent = movedOutFile.parent else { continue } oldParent.children.remove(movedOutFile) @@ -87,7 +89,7 @@ public extension DriveFileManager { keepCacheAttributesForFile(newFile: actionFile, keepProperties: [.standard, .extras], using: realm) realm.add(actionFile, update: .modified) - if let existingFile: File = realm.getObject(id: fileAction.fileId), + if let existingFile: File = realm.getObject(id: fileUid), let oldParent = existingFile.parent { oldParent.children.remove(existingFile) } @@ -100,7 +102,7 @@ public extension DriveFileManager { .fileColorUpdate, .fileColorDelete, .fileCategorize, .fileUncategorize: - if let oldFile: File = realm.getObject(id: fileAction.fileId), + if let oldFile: File = realm.getObject(id: fileUid), oldFile.name != actionFile.name { try? renameCachedFile(updatedFile: actionFile, oldFile: oldFile) } diff --git a/kDriveCore/Data/Cache/DriveFileManager.swift b/kDriveCore/Data/Cache/DriveFileManager.swift index 2de6c9d09..f2d1460ae 100644 --- a/kDriveCore/Data/Cache/DriveFileManager.swift +++ b/kDriveCore/Data/Cache/DriveFileManager.swift @@ -468,7 +468,8 @@ public final class DriveFileManager { let realm = realm ?? getRealm() realm.refresh() - guard let file = realm.object(ofType: File.self, forPrimaryKey: id), !file.isInvalidated else { + let uid = File.uid(driveId: drive.id, fileId: id) + guard let file = realm.object(ofType: File.self, forPrimaryKey: uid), !file.isInvalidated else { return nil } return freeze ? file.freeze() : file @@ -736,7 +737,7 @@ public final class DriveFileManager { token?.cancel() if error != nil && error != .taskRescheduled { // Mark it as not available offline - self.updateFileProperty(fileId: safeFile.id) { file in + self.updateFileProperty(fileUid: safeFile.uid) { file in file.isAvailableOffline = false } } @@ -762,14 +763,14 @@ public final class DriveFileManager { } public func setFileShareLink(file: ProxyFile, shareLink: ShareLink?) { - updateFileProperty(fileId: file.id) { file in + updateFileProperty(fileUid: file.uid) { file in file.sharelink = shareLink file.capabilities.canBecomeSharelink = shareLink == nil } } public func setFileDropBox(file: ProxyFile, dropBox: DropBox?) { - updateFileProperty(fileId: file.id) { file in + updateFileProperty(fileUid: file.uid) { file in file.dropbox = dropBox file.capabilities.canBecomeDropbox = dropBox == nil } @@ -882,7 +883,7 @@ public final class DriveFileManager { let timestamp = try TimeInterval(timestamp ?? file.resolve(using: realm).responseAt) var page = 1 var moreComing = true - var pagedActions = [Int: FileActivityType]() + var pagedActions = [String: FileActivityType]() var pagedActivities = ActivitiesResult() var responseAt = 0 while moreComing { @@ -916,7 +917,7 @@ public final class DriveFileManager { // swiftlint:disable:next cyclomatic_complexity private func apply(activities: [FileActivity], to file: File, - pagedActions: inout [Int: FileActivityType], + pagedActions: inout [String: FileActivityType], timestamp: Int, using realm: Realm? = nil) -> ActivitiesResult { var insertedFiles = [File]() @@ -926,14 +927,14 @@ public final class DriveFileManager { realm.refresh() realm.beginWrite() for activity in activities { - let fileId = activity.fileId + let fileId = File.uid(driveId: file.driveId, fileId: activity.fileId) if pagedActions[fileId] == nil { switch activity.action { case .fileDelete, .fileTrash: if let file = realm.object(ofType: File.self, forPrimaryKey: fileId), !file.isInvalidated { deletedFiles.append(file.freeze()) } - removeFileInDatabase(fileId: fileId, cascade: true, withTransaction: false, using: realm) + removeFileInDatabase(fileUid: fileId, cascade: true, withTransaction: false, using: realm) if let file = activity.file { deletedFiles.append(file) } @@ -980,7 +981,7 @@ public final class DriveFileManager { .fileColorDelete: if let newFile = activity.file { if newFile.isTrashed { - removeFileInDatabase(fileId: fileId, cascade: true, withTransaction: false, using: realm) + removeFileInDatabase(fileUid: fileId, cascade: true, withTransaction: false, using: realm) deletedFiles.append(newFile) pagedActions[fileId] = .fileDelete } else { @@ -1080,7 +1081,7 @@ public final class DriveFileManager { let categoryId = category.id let response = try await apiFetcher.add(category: category, to: file) if response.result { - updateFileProperty(fileId: file.id) { file in + updateFileProperty(fileUid: file.uid) { file in let newCategory = FileCategory(categoryId: categoryId, userId: self.drive.userId) file.categories.append(newCategory) } @@ -1091,7 +1092,7 @@ public final class DriveFileManager { let categoryId = category.id let response = try await apiFetcher.add(drive: drive, category: category, to: files) for fileResponse in response where fileResponse.result { - updateFileProperty(fileId: fileResponse.id) { file in + updateFileProperty(fileUid: File.uid(driveId: drive.id, fileId: fileResponse.id)) { file in let newCategory = FileCategory(categoryId: categoryId, userId: self.drive.userId) file.categories.append(newCategory) } @@ -1102,7 +1103,7 @@ public final class DriveFileManager { let categoryId = category.id let response = try await apiFetcher.remove(category: category, from: file) if response { - updateFileProperty(fileId: file.id) { file in + updateFileProperty(fileUid: file.uid) { file in if let index = file.categories.firstIndex(where: { $0.categoryId == categoryId }) { file.categories.remove(at: index) } @@ -1114,7 +1115,7 @@ public final class DriveFileManager { let categoryId = category.id let response = try await apiFetcher.remove(drive: drive, category: category, from: files) for fileResponse in response where fileResponse.result { - updateFileProperty(fileId: fileResponse.id) { file in + updateFileProperty(fileUid: File.uid(driveId: drive.id, fileId: fileResponse.id)) { file in if let index = file.categories.firstIndex(where: { $0.categoryId == categoryId }) { file.categories.remove(at: index) } @@ -1185,7 +1186,7 @@ public final class DriveFileManager { response = try await apiFetcher.unfavorite(file: file) } if response { - updateFileProperty(fileId: file.id) { file in + updateFileProperty(fileUid: file.uid) { file in file.isFavorite = favorite } } @@ -1196,7 +1197,7 @@ public final class DriveFileManager { backgroundQueue.async { [self] in let localRealm = getRealm() let savedFile = try? file.resolve(using: localRealm).freeze() - removeFileInDatabase(fileId: file.id, cascade: true, withTransaction: true, using: localRealm) + removeFileInDatabase(fileUid: file.uid, cascade: true, withTransaction: true, using: localRealm) if let file = savedFile { savedFile?.signalChanges(userId: drive.userId) notifyObserversWith(file: file) @@ -1414,18 +1415,18 @@ public final class DriveFileManager { return Array(children.freeze()) } - func removeFileInDatabase(fileId: Int, cascade: Bool, withTransaction: Bool, using realm: Realm? = nil) { + func removeFileInDatabase(fileUid: String, cascade: Bool, withTransaction: Bool, using realm: Realm? = nil) { let realm = realm ?? getRealm() realm.refresh() - if let file = realm.object(ofType: File.self, forPrimaryKey: fileId), !file.isInvalidated { + if let file = realm.object(ofType: File.self, forPrimaryKey: fileUid), !file.isInvalidated { if fileManager.fileExists(atPath: file.localContainerUrl.path) { try? fileManager.removeItem(at: file.localContainerUrl) // Check that it was correctly removed? } if cascade { for child in file.children.freeze() where !child.isInvalidated { - removeFileInDatabase(fileId: child.id, cascade: cascade, withTransaction: withTransaction, using: realm) + removeFileInDatabase(fileUid: child.uid, cascade: cascade, withTransaction: withTransaction, using: realm) } } if withTransaction { @@ -1460,11 +1461,11 @@ public final class DriveFileManager { } } - private func updateFileProperty(fileId: Int, using realm: Realm? = nil, _ block: (File) -> Void) { + private func updateFileProperty(fileUid: String, using realm: Realm? = nil, _ block: (File) -> Void) { let realm = realm ?? getRealm() realm.refresh() - if let file = realm.object(ofType: File.self, forPrimaryKey: fileId), !file.isInvalidated { + if let file = realm.object(ofType: File.self, forPrimaryKey: fileUid), !file.isInvalidated { try? realm.write { block(file) } @@ -1523,7 +1524,7 @@ public final class DriveFileManager { let realm = realm ?? getRealm() realm.refresh() - guard let savedChild = realm.object(ofType: File.self, forPrimaryKey: newFile.id), + guard let savedChild = realm.object(ofType: File.self, forPrimaryKey: newFile.uid), !savedChild.isInvalidated else { return } newFile.isAvailableOffline = savedChild.isAvailableOffline newFile.versionCode = savedChild.versionCode @@ -1577,10 +1578,10 @@ public final class DriveFileManager { } public func updateColor(directory: File, color: String) async throws -> Bool { - let fileId = directory.id + let fileUid = directory.uid let result = try await apiFetcher.updateColor(directory: directory.proxify(), color: color) if result { - updateFileProperty(fileId: fileId) { file in + updateFileProperty(fileUid: fileUid) { file in file.color = color } } diff --git a/kDriveCore/Data/Models/File.swift b/kDriveCore/Data/Models/File.swift index 72005615a..bf0de8c43 100644 --- a/kDriveCore/Data/Models/File.swift +++ b/kDriveCore/Data/Models/File.swift @@ -352,7 +352,8 @@ public final class File: Object, Codable { @LazyInjectService var accountManager: AccountManageable - @Persisted(primaryKey: true) public var id = UUID().uuidString.hashValue + @Persisted(primaryKey: true) public var uid = UUID().uuidString + @Persisted public var id: Int @Persisted public var parentId: Int /// Drive identifier @Persisted public var driveId: Int @@ -730,13 +731,20 @@ public final class File: Object, Codable { return ProxyFile(driveId: driveId, id: id) } + public static func uid(driveId: Int, fileId: Int) -> String { + "\(fileId)\(driveId)" + } + public convenience init(from decoder: Decoder) throws { self.init() let container = try decoder.container(keyedBy: CodingKeys.self) - id = try container.decode(Int.self, forKey: .id) + let id = try container.decode(Int.self, forKey: .id) + self.id = id parentId = try container.decode(Int.self, forKey: .parentId) - driveId = try container.decode(Int.self, forKey: .driveId) + let driveId = try container.decode(Int.self, forKey: .driveId) + self.driveId = driveId + uid = File.uid(driveId: driveId, fileId: id) let decodedName = try container.decode(String.self, forKey: .name) name = decodedName sortedName = try container.decodeIfPresent(String.self, forKey: .sortedName) ?? decodedName From a0f767667fb3e2aaf7ae7bde618b8c4acee8a61b Mon Sep 17 00:00:00 2001 From: Philippe Weidmann Date: Tue, 23 Jan 2024 10:28:28 +0100 Subject: [PATCH 2/2] fix: Ensure fake roots have correct uid Signed-off-by: Philippe Weidmann --- kDriveCore/Data/Cache/DriveFileManager.swift | 6 +++++- kDriveCore/Data/Models/File.swift | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/kDriveCore/Data/Cache/DriveFileManager.swift b/kDriveCore/Data/Cache/DriveFileManager.swift index f2d1460ae..f8873fd96 100644 --- a/kDriveCore/Data/Cache/DriveFileManager.swift +++ b/kDriveCore/Data/Cache/DriveFileManager.swift @@ -250,7 +250,7 @@ public final class DriveFileManager { } return freeze ? root.freeze() : root } else { - return File(id: DriveFileManager.constants.rootID, name: drive.name) + return File(id: DriveFileManager.constants.rootID, name: drive.name, driveId: drive.id) } } @@ -1565,6 +1565,10 @@ public final class DriveFileManager { if let cachedFile = getCachedFile(id: file.id, freeze: false, using: realm) { return cachedFile } else { + if file.isRoot { + file.driveId = drive.id + file.uid = File.uid(driveId: file.driveId, fileId: file.id) + } keepCacheAttributesForFile(newFile: file, keepProperties: [.all], using: realm) try? realm.write { realm.add(file, update: .all) diff --git a/kDriveCore/Data/Models/File.swift b/kDriveCore/Data/Models/File.swift index bf0de8c43..19a5eb313 100644 --- a/kDriveCore/Data/Models/File.swift +++ b/kDriveCore/Data/Models/File.swift @@ -781,10 +781,14 @@ public final class File: Object, Codable { // primary key is set as default value } - convenience init(id: Int, name: String) { + convenience init(id: Int, name: String, driveId: Int? = nil) { self.init() self.id = id self.name = name + if let driveId { + self.driveId = driveId + uid = File.uid(driveId: driveId, fileId: id) + } rawType = "dir" children = MutableSet() }