From 73a50acc7f4d831a686bd322feb8559373349abe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Coye=20de=20Brune=CC=81lis?= Date: Wed, 28 Feb 2024 10:33:55 +0100 Subject: [PATCH] feat: Signal Files.app on FileProviderDomain change refactor: Split DriveInfoManager + FileProvider --- .../DriveInfosManager+FileProvider.swift | 174 ++++++++++++++++++ kDriveCore/Data/Cache/DriveInfosManager.swift | 137 +------------- .../FileProviderDomain+Identifier.swift | 45 +++++ 3 files changed, 224 insertions(+), 132 deletions(-) create mode 100644 kDriveCore/Data/Cache/DriveInfosManager+FileProvider.swift create mode 100644 kDriveCore/Utils/FileProvider/FileProviderDomain+Identifier.swift diff --git a/kDriveCore/Data/Cache/DriveInfosManager+FileProvider.swift b/kDriveCore/Data/Cache/DriveInfosManager+FileProvider.swift new file mode 100644 index 000000000..64024631d --- /dev/null +++ b/kDriveCore/Data/Cache/DriveInfosManager+FileProvider.swift @@ -0,0 +1,174 @@ +/* + Infomaniak kDrive - iOS App + Copyright (C) 2024 Infomaniak Network SA + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +import CocoaLumberjackSwift +import FileProvider +import Foundation +import InfomaniakConcurrency +import InfomaniakCore + +public extension DriveInfosManager { + private typealias FilteredDomain = (new: NSFileProviderDomain, existing: NSFileProviderDomain?) + + internal func initFileProviderDomains(drives: [Drive], user: InfomaniakCore.UserProfile) { + // Clean file provider storage if needed + if UserDefaults.shared.fpStorageVersion < currentFpStorageVersion { + do { + let fileURLs = try FileManager.default.contentsOfDirectory( + at: NSFileProviderManager.default.documentStorageURL, + includingPropertiesForKeys: nil + ) + for url in fileURLs { + try FileManager.default.removeItem(at: url) + } + UserDefaults.shared.fpStorageVersion = currentFpStorageVersion + } catch { + // TODO: Sentry + } + } + + // TODO: Start Activity + Task { + let updatedDomains = drives.map { + NSFileProviderDomain( + identifier: NSFileProviderDomainIdentifier($0.objectId), + displayName: "\($0.name) (\(user.email))", + pathRelativeToDocumentStorage: "\($0.objectId)" + ) + } + + do { + let allDomains = try await NSFileProviderManager.domains() + let existingDomainsForCurrentUser = allDomains.filter { $0.identifier.rawValue.hasSuffix("_\(user.id)") } + + let updatedDomainsForCurrentUser: [FilteredDomain] = updatedDomains.map { newDomain in + let existingDomain = existingDomainsForCurrentUser.first { $0.identifier == newDomain.identifier } + return (newDomain, existingDomain) + } + + try await updatedDomainsForCurrentUser.concurrentForEach(customConcurrency: 1) { domain in + // Simply add domain if new + let newDomain = domain.new + guard let existingDomain = domain.existing else { + try await NSFileProviderManager.add(newDomain) + return + } + + // Update existing accounts if necessary + if existingDomain.displayName != newDomain.displayName { + try await NSFileProviderManager.remove(existingDomain) + try await NSFileProviderManager.add(newDomain) + self.signalChanges(for: newDomain) + } + } + + // Remove domains no longer present for current user + let removedDomainsForCurrentUser = updatedDomains.filter { updatedDomain in + guard existingDomainsForCurrentUser.contains(where: { $0.identifier == updatedDomain.identifier }) else { + return true + } + + return false + } + + try await removedDomainsForCurrentUser.concurrentForEach(customConcurrency: 1) { oldDomain in + try await NSFileProviderManager.remove(oldDomain) + } + } catch { + DDLogError("Error while updating file provider domains: \(error)") + // TODO: add Sentry + } + + // TODO: notify for consistency + } + } + + internal func deleteFileProviderDomains(for userId: Int) { + NSFileProviderManager.getDomainsWithCompletionHandler { allDomains, error in + if let error { + DDLogError("Error while getting domains: \(error)") + } + + let domainsForCurrentUser = allDomains.filter { $0.identifier.rawValue.hasSuffix("_\(userId)") } + for domain in domainsForCurrentUser { + NSFileProviderManager.remove(domain) { error in + if let error { + DDLogError("Error while removing domain \(domain.displayName): \(error)") + } + } + } + } + } + + func deleteAllFileProviderDomains() { + NSFileProviderManager.removeAllDomains { error in + if let error { + DDLogError("Error while removing domains: \(error)") + } + } + } + + internal func getFileProviderDomain(for driveId: String, completion: @escaping (NSFileProviderDomain?) -> Void) { + NSFileProviderManager.getDomainsWithCompletionHandler { domains, error in + if let error { + DDLogError("Error while getting domains: \(error)") + completion(nil) + } else { + completion(domains.first { $0.identifier.rawValue == driveId }) + } + } + } + + func getFileProviderManager(for drive: Drive, completion: @escaping (NSFileProviderManager) -> Void) { + getFileProviderManager(for: drive.objectId, completion: completion) + } + + func getFileProviderManager(driveId: Int, userId: Int, completion: @escaping (NSFileProviderManager) -> Void) { + let objectId = DriveInfosManager.getObjectId(driveId: driveId, userId: userId) + getFileProviderManager(for: objectId, completion: completion) + } + + func getFileProviderManager(for driveId: String, completion: @escaping (NSFileProviderManager) -> Void) { + getFileProviderDomain(for: driveId) { domain in + if let domain { + completion(NSFileProviderManager(for: domain) ?? .default) + } else { + completion(.default) + } + } + } + + // MARK: Signal + + /// Signal changes on this Drive to the File Provider Extension + private func signalChanges(for domain: NSFileProviderDomain) { + guard let driveId = domain.driveId, let userId = domain.userId else { + // Sentry + return + } + + DriveInfosManager.instance.getFileProviderManager(driveId: driveId, userId: userId) { manager in + manager.signalEnumerator(for: .workingSet) { _ in + // META: keep SonarCloud happy + } + manager.signalEnumerator(for: .rootContainer) { _ in + // META: keep SonarCloud happy + } + } + } +} diff --git a/kDriveCore/Data/Cache/DriveInfosManager.swift b/kDriveCore/Data/Cache/DriveInfosManager.swift index 2d77ed660..d60819b3f 100644 --- a/kDriveCore/Data/Cache/DriveInfosManager.swift +++ b/kDriveCore/Data/Cache/DriveInfosManager.swift @@ -19,16 +19,18 @@ import CocoaLumberjackSwift import FileProvider import Foundation -import InfomaniakConcurrency import InfomaniakCore import Realm import RealmSwift import Sentry -public class DriveInfosManager { +public final class DriveInfosManager { + // TODO: use DI public static let instance = DriveInfosManager() private static let currentDbVersion: UInt64 = 9 - private let currentFpStorageVersion = 1 + + let currentFpStorageVersion = 1 + public let realmConfiguration: Realm.Configuration private let dbName = "DrivesInfos.realm" private var fileProviderManagers: [String: NSFileProviderManager] = [:] @@ -117,135 +119,6 @@ public class DriveInfosManager { drive.sharedWithMe = sharedWithMe } - private typealias FilteredDomain = (new: NSFileProviderDomain, existing: NSFileProviderDomain?) - - private func initFileProviderDomains(drives: [Drive], user: InfomaniakCore.UserProfile) { - // Clean file provider storage if needed - if UserDefaults.shared.fpStorageVersion < currentFpStorageVersion { - do { - let fileURLs = try FileManager.default.contentsOfDirectory( - at: NSFileProviderManager.default.documentStorageURL, - includingPropertiesForKeys: nil - ) - for url in fileURLs { - try FileManager.default.removeItem(at: url) - } - UserDefaults.shared.fpStorageVersion = currentFpStorageVersion - } catch { - // TODO: Sentry - } - } - - // TODO: Start Activity - Task { - let updatedDomains = drives.map { - NSFileProviderDomain( - identifier: NSFileProviderDomainIdentifier($0.objectId), - displayName: "\($0.name) (\(user.email))", - pathRelativeToDocumentStorage: "\($0.objectId)" - ) - } - - do { - let allDomains = try await NSFileProviderManager.domains() - let existingDomainsForCurrentUser = allDomains.filter { $0.identifier.rawValue.hasSuffix("_\(user.id)") } - - let updatedDomainsForCurrentUser: [FilteredDomain] = updatedDomains.map { newDomain in - let existingDomain = existingDomainsForCurrentUser.first { $0.identifier == newDomain.identifier } - return (newDomain, existingDomain) - } - - try await updatedDomainsForCurrentUser.concurrentForEach(customConcurrency: 1) { domain in - // Simply add domain if new - let newDomain = domain.new - guard let existingDomain = domain.existing else { - try await NSFileProviderManager.add(newDomain) - return - } - - // Update existing accounts if necessary - if existingDomain.displayName != newDomain.displayName { - try await NSFileProviderManager.remove(existingDomain) - try await NSFileProviderManager.add(newDomain) - } - } - - // Remove domains no longer present for current user - let removedDomainsForCurrentUser = updatedDomains.filter { updatedDomain in - guard existingDomainsForCurrentUser.contains(where: { $0.identifier == updatedDomain.identifier }) else { - return true - } - - return false - } - - try await removedDomainsForCurrentUser.concurrentForEach(customConcurrency: 1) { oldDomain in - try await NSFileProviderManager.remove(oldDomain) - } - } catch { - DDLogError("Error while updating file provider domains: \(error)") - // TODO: add Sentry - } - - // TODO: notify for consistency - } - } - - func deleteFileProviderDomains(for userId: Int) { - NSFileProviderManager.getDomainsWithCompletionHandler { allDomains, error in - if let error { - DDLogError("Error while getting domains: \(error)") - } - - let domainsForCurrentUser = allDomains.filter { $0.identifier.rawValue.hasSuffix("_\(userId)") } - for domain in domainsForCurrentUser { - NSFileProviderManager.remove(domain) { error in - if let error { - DDLogError("Error while removing domain \(domain.displayName): \(error)") - } - } - } - } - } - - public func deleteAllFileProviderDomains() { - NSFileProviderManager.removeAllDomains { error in - if let error { - DDLogError("Error while removing domains: \(error)") - } - } - } - - func getFileProviderDomain(for driveId: String, completion: @escaping (NSFileProviderDomain?) -> Void) { - NSFileProviderManager.getDomainsWithCompletionHandler { domains, error in - if let error { - DDLogError("Error while getting domains: \(error)") - completion(nil) - } else { - completion(domains.first { $0.identifier.rawValue == driveId }) - } - } - } - - public func getFileProviderManager(for drive: Drive, completion: @escaping (NSFileProviderManager) -> Void) { - getFileProviderManager(for: drive.objectId, completion: completion) - } - - public func getFileProviderManager(driveId: Int, userId: Int, completion: @escaping (NSFileProviderManager) -> Void) { - let objectId = DriveInfosManager.getObjectId(driveId: driveId, userId: userId) - getFileProviderManager(for: objectId, completion: completion) - } - - public func getFileProviderManager(for driveId: String, completion: @escaping (NSFileProviderManager) -> Void) { - getFileProviderDomain(for: driveId) { domain in - if let domain { - completion(NSFileProviderManager(for: domain) ?? .default) - } else { - completion(.default) - } - } - } - @discardableResult func storeDriveResponse(user: InfomaniakCore.UserProfile, driveResponse: DriveResponse) -> [Drive] { var driveList = [Drive]() diff --git a/kDriveCore/Utils/FileProvider/FileProviderDomain+Identifier.swift b/kDriveCore/Utils/FileProvider/FileProviderDomain+Identifier.swift new file mode 100644 index 000000000..0c76b70f5 --- /dev/null +++ b/kDriveCore/Utils/FileProvider/FileProviderDomain+Identifier.swift @@ -0,0 +1,45 @@ +/* + Infomaniak kDrive - iOS App + Copyright (C) 2023 Infomaniak Network SA + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + */ + +import FileProvider +import Foundation + +public extension NSFileProviderDomain { + private typealias DriveIdAndUserId = (driveId: Int, userId: Int) + + private var userAndDrive: DriveIdAndUserId? { + let identifiers = identifier.rawValue.components(separatedBy: "_") + + guard let driveIdString = identifiers[safe: 0], + let usedIdString = identifiers[safe: 1], + let driveId = Int(driveIdString), + let usedId = Int(usedIdString) else { + return nil + } + + return (driveId, usedId) + } + + var userId: Int? { + userAndDrive?.userId + } + + var driveId: Int? { + userAndDrive?.driveId + } +}