diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudFilesDatabaseManager+Directories.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudFilesDatabaseManager+Directories.swift deleted file mode 100644 index 62ad0445cf13d..0000000000000 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudFilesDatabaseManager+Directories.swift +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Copyright (C) 2023 by Claudio Cambra - * - * 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 2 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. - */ - -import Foundation -import OSLog - -extension NextcloudFilesDatabaseManager { - func directoryMetadata(account: String, serverUrl: String) -> NextcloudItemMetadataTable? { - // We want to split by "/" (e.g. cloud.nc.com/files/a/b) but we need to be mindful of "https://c.nc.com" - let problematicSeparator = "://" - let placeholderSeparator = "__TEMP_REPLACE__" - let serverUrlWithoutPrefix = serverUrl.replacingOccurrences( - of: problematicSeparator, with: placeholderSeparator) - var splitServerUrl = serverUrlWithoutPrefix.split(separator: "/") - let directoryItemFileName = String(splitServerUrl.removeLast()) - let directoryItemServerUrl = splitServerUrl.joined(separator: "/").replacingOccurrences( - of: placeholderSeparator, with: problematicSeparator) - - if let metadata = ncDatabase().objects(NextcloudItemMetadataTable.self).filter( - "account == %@ AND serverUrl == %@ AND fileName == %@ AND directory == true", - account, - directoryItemServerUrl, - directoryItemFileName - ).first { - return NextcloudItemMetadataTable(value: metadata) - } - - return nil - } - - func childItemsForDirectory(_ directoryMetadata: NextcloudItemMetadataTable) - -> [NextcloudItemMetadataTable] - { - let directoryServerUrl = directoryMetadata.serverUrl + "/" + directoryMetadata.fileName - let metadatas = ncDatabase().objects(NextcloudItemMetadataTable.self).filter( - "serverUrl BEGINSWITH %@", directoryServerUrl) - return sortedItemMetadatas(metadatas) - } - - func childDirectoriesForDirectory(_ directoryMetadata: NextcloudItemMetadataTable) - -> [NextcloudItemMetadataTable] - { - let directoryServerUrl = directoryMetadata.serverUrl + "/" + directoryMetadata.fileName - let metadatas = ncDatabase().objects(NextcloudItemMetadataTable.self).filter( - "serverUrl BEGINSWITH %@ AND directory == true", directoryServerUrl) - return sortedItemMetadatas(metadatas) - } - - func parentDirectoryMetadataForItem(_ itemMetadata: NextcloudItemMetadataTable) - -> NextcloudItemMetadataTable? - { - directoryMetadata(account: itemMetadata.account, serverUrl: itemMetadata.serverUrl) - } - - func directoryMetadata(ocId: String) -> NextcloudItemMetadataTable? { - if let metadata = ncDatabase().objects(NextcloudItemMetadataTable.self).filter( - "ocId == %@ AND directory == true", ocId - ).first { - return NextcloudItemMetadataTable(value: metadata) - } - - return nil - } - - func directoryMetadatas(account: String) -> [NextcloudItemMetadataTable] { - let metadatas = ncDatabase().objects(NextcloudItemMetadataTable.self).filter( - "account == %@ AND directory == true", account) - return sortedItemMetadatas(metadatas) - } - - func directoryMetadatas(account: String, parentDirectoryServerUrl: String) - -> [NextcloudItemMetadataTable] - { - let metadatas = ncDatabase().objects(NextcloudItemMetadataTable.self).filter( - "account == %@ AND parentDirectoryServerUrl == %@ AND directory == true", account, - parentDirectoryServerUrl) - return sortedItemMetadatas(metadatas) - } - - // Deletes all metadatas related to the info of the directory provided - func deleteDirectoryAndSubdirectoriesMetadata(ocId: String) -> [NextcloudItemMetadataTable]? { - let database = ncDatabase() - guard - let directoryMetadata = database.objects(NextcloudItemMetadataTable.self).filter( - "ocId == %@ AND directory == true", ocId - ).first - else { - Logger.ncFilesDatabase.error( - "Could not find directory metadata for ocId \(ocId, privacy: .public). Not proceeding with deletion" - ) - return nil - } - - let directoryMetadataCopy = NextcloudItemMetadataTable(value: directoryMetadata) - let directoryUrlPath = directoryMetadata.serverUrl + "/" + directoryMetadata.fileName - let directoryAccount = directoryMetadata.account - let directoryEtag = directoryMetadata.etag - - Logger.ncFilesDatabase.debug( - "Deleting root directory metadata in recursive delete. ocID: \(directoryMetadata.ocId, privacy: .public), etag: \(directoryEtag, privacy: .public), serverUrl: \(directoryUrlPath, privacy: .public)" - ) - - guard deleteItemMetadata(ocId: directoryMetadata.ocId) else { - Logger.ncFilesDatabase.debug( - "Failure to delete root directory metadata in recursive delete. ocID: \(directoryMetadata.ocId, privacy: .public), etag: \(directoryEtag, privacy: .public), serverUrl: \(directoryUrlPath, privacy: .public)" - ) - return nil - } - - var deletedMetadatas: [NextcloudItemMetadataTable] = [directoryMetadataCopy] - - let results = database.objects(NextcloudItemMetadataTable.self).filter( - "account == %@ AND serverUrl BEGINSWITH %@", directoryAccount, directoryUrlPath) - - for result in results { - let successfulItemMetadataDelete = deleteItemMetadata(ocId: result.ocId) - if successfulItemMetadataDelete { - deletedMetadatas.append(NextcloudItemMetadataTable(value: result)) - } - - if localFileMetadataFromOcId(result.ocId) != nil { - deleteLocalFileMetadata(ocId: result.ocId) - } - } - - Logger.ncFilesDatabase.debug( - "Completed deletions in directory recursive delete. ocID: \(directoryMetadata.ocId, privacy: .public), etag: \(directoryEtag, privacy: .public), serverUrl: \(directoryUrlPath, privacy: .public)" - ) - - return deletedMetadatas - } - - func renameDirectoryAndPropagateToChildren( - ocId: String, newServerUrl: String, newFileName: String - ) -> [NextcloudItemMetadataTable]? { - let database = ncDatabase() - - guard - let directoryMetadata = database.objects(NextcloudItemMetadataTable.self).filter( - "ocId == %@ AND directory == true", ocId - ).first - else { - Logger.ncFilesDatabase.error( - "Could not find a directory with ocID \(ocId, privacy: .public), cannot proceed with recursive renaming" - ) - return nil - } - - let oldItemServerUrl = directoryMetadata.serverUrl - let oldDirectoryServerUrl = oldItemServerUrl + "/" + directoryMetadata.fileName - let newDirectoryServerUrl = newServerUrl + "/" + newFileName - let childItemResults = database.objects(NextcloudItemMetadataTable.self).filter( - "account == %@ AND serverUrl BEGINSWITH %@", directoryMetadata.account, - oldDirectoryServerUrl) - - renameItemMetadata(ocId: ocId, newServerUrl: newServerUrl, newFileName: newFileName) - Logger.ncFilesDatabase.debug("Renamed root renaming directory") - - do { - try database.write { - for childItem in childItemResults { - let oldServerUrl = childItem.serverUrl - let movedServerUrl = oldServerUrl.replacingOccurrences( - of: oldDirectoryServerUrl, with: newDirectoryServerUrl) - childItem.serverUrl = movedServerUrl - database.add(childItem, update: .all) - Logger.ncFilesDatabase.debug( - "Moved childItem at \(oldServerUrl) to \(movedServerUrl)") - } - } - } catch { - Logger.ncFilesDatabase.error( - "Could not rename directory metadata with ocId: \(ocId, privacy: .public) to new serverUrl: \(newServerUrl), received error: \(error.localizedDescription, privacy: .public)" - ) - - return nil - } - - let updatedChildItemResults = database.objects(NextcloudItemMetadataTable.self).filter( - "account == %@ AND serverUrl BEGINSWITH %@", directoryMetadata.account, - newDirectoryServerUrl) - return sortedItemMetadatas(updatedChildItemResults) - } -} diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudFilesDatabaseManager+LocalFiles.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudFilesDatabaseManager+LocalFiles.swift deleted file mode 100644 index 3174bc491b9e8..0000000000000 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudFilesDatabaseManager+LocalFiles.swift +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright (C) 2023 by Claudio Cambra - * - * 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 2 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. - */ - -import Foundation -import OSLog -import RealmSwift - -extension NextcloudFilesDatabaseManager { - func localFileMetadataFromOcId(_ ocId: String) -> NextcloudLocalFileMetadataTable? { - if let metadata = ncDatabase().objects(NextcloudLocalFileMetadataTable.self).filter( - "ocId == %@", ocId - ).first { - return NextcloudLocalFileMetadataTable(value: metadata) - } - - return nil - } - - func addLocalFileMetadataFromItemMetadata(_ itemMetadata: NextcloudItemMetadataTable) { - let database = ncDatabase() - - do { - try database.write { - let newLocalFileMetadata = NextcloudLocalFileMetadataTable() - - newLocalFileMetadata.ocId = itemMetadata.ocId - newLocalFileMetadata.fileName = itemMetadata.fileName - newLocalFileMetadata.account = itemMetadata.account - newLocalFileMetadata.etag = itemMetadata.etag - newLocalFileMetadata.exifDate = Date() - newLocalFileMetadata.exifLatitude = "-1" - newLocalFileMetadata.exifLongitude = "-1" - - database.add(newLocalFileMetadata, update: .all) - Logger.ncFilesDatabase.debug( - "Added local file metadata from item metadata. ocID: \(itemMetadata.ocId, privacy: .public), etag: \(itemMetadata.etag, privacy: .public), fileName: \(itemMetadata.fileName, privacy: .public)" - ) - } - } catch { - Logger.ncFilesDatabase.error( - "Could not add local file metadata from item metadata. ocID: \(itemMetadata.ocId, privacy: .public), etag: \(itemMetadata.etag, privacy: .public), fileName: \(itemMetadata.fileName, privacy: .public), received error: \(error.localizedDescription, privacy: .public)" - ) - } - } - - func deleteLocalFileMetadata(ocId: String) { - let database = ncDatabase() - - do { - try database.write { - let results = database.objects(NextcloudLocalFileMetadataTable.self).filter( - "ocId == %@", ocId) - database.delete(results) - } - } catch { - Logger.ncFilesDatabase.error( - "Could not delete local file metadata with ocId: \(ocId, privacy: .public), received error: \(error.localizedDescription, privacy: .public)" - ) - } - } - - private func sortedLocalFileMetadatas(_ metadatas: Results) - -> [NextcloudLocalFileMetadataTable] - { - let sortedMetadatas = metadatas.sorted(byKeyPath: "fileName", ascending: true) - return Array(sortedMetadatas.map { NextcloudLocalFileMetadataTable(value: $0) }) - } - - func localFileMetadatas(account: String) -> [NextcloudLocalFileMetadataTable] { - let results = ncDatabase().objects(NextcloudLocalFileMetadataTable.self).filter( - "account == %@", account) - return sortedLocalFileMetadatas(results) - } - - func localFileItemMetadatas(account: String) -> [NextcloudItemMetadataTable] { - let localFileMetadatas = localFileMetadatas(account: account) - let localFileMetadatasOcIds = Array(localFileMetadatas.map(\.ocId)) - - var itemMetadatas: [NextcloudItemMetadataTable] = [] - - for ocId in localFileMetadatasOcIds { - guard let itemMetadata = itemMetadataFromOcId(ocId) else { - Logger.ncFilesDatabase.error( - "Could not find matching item metadata for local file metadata with ocId: \(ocId, privacy: .public) with request from account: \(account)" - ) - continue - } - - itemMetadatas.append(NextcloudItemMetadataTable(value: itemMetadata)) - } - - return itemMetadatas - } -} diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudFilesDatabaseManager.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudFilesDatabaseManager.swift deleted file mode 100644 index 77d980a78832a..0000000000000 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudFilesDatabaseManager.swift +++ /dev/null @@ -1,427 +0,0 @@ -/* - * Copyright (C) 2022 by Claudio Cambra - * - * 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 2 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. - */ - -import FileProvider -import Foundation -import NextcloudKit -import OSLog -import RealmSwift - -class NextcloudFilesDatabaseManager: NSObject { - static let shared = NextcloudFilesDatabaseManager() - - let relativeDatabaseFolderPath = "Database/" - let databaseFilename = "fileproviderextdatabase.realm" - let relativeDatabaseFilePath: String - var databasePath: URL? - - let schemaVersion: UInt64 = 100 - - override init() { - relativeDatabaseFilePath = relativeDatabaseFolderPath + databaseFilename - - guard let fileProviderDataDirUrl = pathForFileProviderExtData() else { - super.init() - return - } - - databasePath = fileProviderDataDirUrl.appendingPathComponent(relativeDatabaseFilePath) - - // Disable file protection for directory DB - // https://docs.mongodb.com/realm/sdk/ios/examples/configure-and-open-a-realm/#std-label-ios-open-a-local-realm - let dbFolder = fileProviderDataDirUrl.appendingPathComponent(relativeDatabaseFolderPath) - let dbFolderPath = dbFolder.path - do { - try FileManager.default.createDirectory(at: dbFolder, withIntermediateDirectories: true) - try FileManager.default.setAttributes( - [ - FileAttributeKey.protectionKey: FileProtectionType - .completeUntilFirstUserAuthentication - ], - ofItemAtPath: dbFolderPath) - } catch { - Logger.ncFilesDatabase.error( - "Could not set permission level for File Provider database folder, received error: \(error.localizedDescription, privacy: .public)" - ) - } - - let config = Realm.Configuration( - fileURL: databasePath, - schemaVersion: schemaVersion, - objectTypes: [NextcloudItemMetadataTable.self, NextcloudLocalFileMetadataTable.self] - ) - - Realm.Configuration.defaultConfiguration = config - - do { - _ = try Realm() - Logger.ncFilesDatabase.info("Successfully started Realm db for FileProviderExt") - } catch let error as NSError { - Logger.ncFilesDatabase.error( - "Error opening Realm db: \(error.localizedDescription, privacy: .public)") - } - - super.init() - } - - func ncDatabase() -> Realm { - let realm = try! Realm() - realm.refresh() - return realm - } - - func anyItemMetadatasForAccount(_ account: String) -> Bool { - !ncDatabase().objects(NextcloudItemMetadataTable.self).filter("account == %@", account) - .isEmpty - } - - func itemMetadataFromOcId(_ ocId: String) -> NextcloudItemMetadataTable? { - // Realm objects are live-fire, i.e. they will be changed and invalidated according to changes in the db - // Let's therefore create a copy - if let itemMetadata = ncDatabase().objects(NextcloudItemMetadataTable.self).filter( - "ocId == %@", ocId - ).first { - return NextcloudItemMetadataTable(value: itemMetadata) - } - - return nil - } - - func sortedItemMetadatas(_ metadatas: Results) - -> [NextcloudItemMetadataTable] - { - let sortedMetadatas = metadatas.sorted(byKeyPath: "fileName", ascending: true) - return Array(sortedMetadatas.map { NextcloudItemMetadataTable(value: $0) }) - } - - func itemMetadatas(account: String) -> [NextcloudItemMetadataTable] { - let metadatas = ncDatabase().objects(NextcloudItemMetadataTable.self).filter( - "account == %@", account) - return sortedItemMetadatas(metadatas) - } - - func itemMetadatas(account: String, serverUrl: String) -> [NextcloudItemMetadataTable] { - let metadatas = ncDatabase().objects(NextcloudItemMetadataTable.self).filter( - "account == %@ AND serverUrl == %@", account, serverUrl) - return sortedItemMetadatas(metadatas) - } - - func itemMetadatas( - account: String, serverUrl: String, status: NextcloudItemMetadataTable.Status - ) - -> [NextcloudItemMetadataTable] - { - let metadatas = ncDatabase().objects(NextcloudItemMetadataTable.self).filter( - "account == %@ AND serverUrl == %@ AND status == %@", - account, - serverUrl, - status.rawValue) - return sortedItemMetadatas(metadatas) - } - - func itemMetadataFromFileProviderItemIdentifier(_ identifier: NSFileProviderItemIdentifier) - -> NextcloudItemMetadataTable? - { - let ocId = identifier.rawValue - return itemMetadataFromOcId(ocId) - } - - private func processItemMetadatasToDelete( - existingMetadatas: Results, - updatedMetadatas: [NextcloudItemMetadataTable] - ) -> [NextcloudItemMetadataTable] { - var deletedMetadatas: [NextcloudItemMetadataTable] = [] - - for existingMetadata in existingMetadatas { - guard !updatedMetadatas.contains(where: { $0.ocId == existingMetadata.ocId }), - let metadataToDelete = itemMetadataFromOcId(existingMetadata.ocId) - else { continue } - - deletedMetadatas.append(metadataToDelete) - - Logger.ncFilesDatabase.debug( - "Deleting item metadata during update. ocID: \(existingMetadata.ocId, privacy: .public), etag: \(existingMetadata.etag, privacy: .public), fileName: \(existingMetadata.fileName, privacy: .public)" - ) - } - - return deletedMetadatas - } - - private func processItemMetadatasToUpdate( - existingMetadatas: Results, - updatedMetadatas: [NextcloudItemMetadataTable], - updateDirectoryEtags: Bool - ) -> ( - newMetadatas: [NextcloudItemMetadataTable], updatedMetadatas: [NextcloudItemMetadataTable], - directoriesNeedingRename: [NextcloudItemMetadataTable] - ) { - var returningNewMetadatas: [NextcloudItemMetadataTable] = [] - var returningUpdatedMetadatas: [NextcloudItemMetadataTable] = [] - var directoriesNeedingRename: [NextcloudItemMetadataTable] = [] - - for updatedMetadata in updatedMetadatas { - if let existingMetadata = existingMetadatas.first(where: { - $0.ocId == updatedMetadata.ocId - }) { - if existingMetadata.status == NextcloudItemMetadataTable.Status.normal.rawValue, - !existingMetadata.isInSameDatabaseStoreableRemoteState(updatedMetadata) - { - if updatedMetadata.directory { - if updatedMetadata.serverUrl != existingMetadata.serverUrl - || updatedMetadata.fileName != existingMetadata.fileName - { - directoriesNeedingRename.append( - NextcloudItemMetadataTable(value: updatedMetadata)) - updatedMetadata.etag = "" // Renaming doesn't change the etag so reset manually - - } else if !updateDirectoryEtags { - updatedMetadata.etag = existingMetadata.etag - } - } - - returningUpdatedMetadatas.append(updatedMetadata) - - Logger.ncFilesDatabase.debug( - "Updated existing item metadata. ocID: \(updatedMetadata.ocId, privacy: .public), etag: \(updatedMetadata.etag, privacy: .public), fileName: \(updatedMetadata.fileName, privacy: .public)" - ) - } else { - Logger.ncFilesDatabase.debug( - "Skipping item metadata update; same as existing, or still downloading/uploading. ocID: \(updatedMetadata.ocId, privacy: .public), etag: \(updatedMetadata.etag, privacy: .public), fileName: \(updatedMetadata.fileName, privacy: .public)" - ) - } - - } else { // This is a new metadata - if !updateDirectoryEtags, updatedMetadata.directory { - updatedMetadata.etag = "" - } - - returningNewMetadatas.append(updatedMetadata) - - Logger.ncFilesDatabase.debug( - "Created new item metadata during update. ocID: \(updatedMetadata.ocId, privacy: .public), etag: \(updatedMetadata.etag, privacy: .public), fileName: \(updatedMetadata.fileName, privacy: .public)" - ) - } - } - - return (returningNewMetadatas, returningUpdatedMetadatas, directoriesNeedingRename) - } - - func updateItemMetadatas( - account: String, - serverUrl: String, - updatedMetadatas: [NextcloudItemMetadataTable], - updateDirectoryEtags: Bool - ) -> ( - newMetadatas: [NextcloudItemMetadataTable]?, - updatedMetadatas: [NextcloudItemMetadataTable]?, - deletedMetadatas: [NextcloudItemMetadataTable]? - ) { - let database = ncDatabase() - - do { - let existingMetadatas = database.objects(NextcloudItemMetadataTable.self).filter( - "account == %@ AND serverUrl == %@ AND status == %@", - account, - serverUrl, - NextcloudItemMetadataTable.Status.normal.rawValue) - - let metadatasToDelete = processItemMetadatasToDelete( - existingMetadatas: existingMetadatas, - updatedMetadatas: updatedMetadatas) - - let metadatasToChange = processItemMetadatasToUpdate( - existingMetadatas: existingMetadatas, - updatedMetadatas: updatedMetadatas, - updateDirectoryEtags: updateDirectoryEtags) - - var metadatasToUpdate = metadatasToChange.updatedMetadatas - let metadatasToCreate = metadatasToChange.newMetadatas - let directoriesNeedingRename = metadatasToChange.directoriesNeedingRename - - let metadatasToAdd = - Array(metadatasToUpdate.map { NextcloudItemMetadataTable(value: $0) }) - + Array(metadatasToCreate.map { NextcloudItemMetadataTable(value: $0) }) - - for metadata in directoriesNeedingRename { - if let updatedDirectoryChildren = renameDirectoryAndPropagateToChildren( - ocId: metadata.ocId, - newServerUrl: metadata.serverUrl, - newFileName: metadata.fileName) - { - metadatasToUpdate += updatedDirectoryChildren - } - } - - try database.write { - for metadata in metadatasToDelete { - // Can't pass copies, we need the originals from the database - database.delete( - ncDatabase().objects(NextcloudItemMetadataTable.self).filter( - "ocId == %@", metadata.ocId)) - } - - for metadata in metadatasToAdd { - database.add(metadata, update: .all) - } - } - - return ( - newMetadatas: metadatasToCreate, - updatedMetadatas: metadatasToUpdate, - deletedMetadatas: metadatasToDelete - ) - } catch { - Logger.ncFilesDatabase.error( - "Could not update any item metadatas, received error: \(error.localizedDescription, privacy: .public)" - ) - return (nil, nil, nil) - } - } - - func setStatusForItemMetadata( - _ metadata: NextcloudItemMetadataTable, - status: NextcloudItemMetadataTable.Status, - completionHandler: @escaping (_ updatedMetadata: NextcloudItemMetadataTable?) -> Void - ) { - let database = ncDatabase() - - do { - try database.write { - guard - let result = database.objects(NextcloudItemMetadataTable.self).filter( - "ocId == %@", metadata.ocId - ).first - else { - Logger.ncFilesDatabase.debug( - "Did not update status for item metadata as it was not found. ocID: \(metadata.ocId, privacy: .public)" - ) - return - } - - result.status = status.rawValue - database.add(result, update: .all) - Logger.ncFilesDatabase.debug( - "Updated status for item metadata. ocID: \(metadata.ocId, privacy: .public), etag: \(metadata.etag, privacy: .public), fileName: \(metadata.fileName, privacy: .public)" - ) - - completionHandler(NextcloudItemMetadataTable(value: result)) - } - } catch { - Logger.ncFilesDatabase.error( - "Could not update status for item metadata with ocID: \(metadata.ocId, privacy: .public), etag: \(metadata.etag, privacy: .public), fileName: \(metadata.fileName, privacy: .public), received error: \(error.localizedDescription, privacy: .public)" - ) - completionHandler(nil) - } - } - - func addItemMetadata(_ metadata: NextcloudItemMetadataTable) { - let database = ncDatabase() - - do { - try database.write { - database.add(metadata, update: .all) - Logger.ncFilesDatabase.debug( - "Added item metadata. ocID: \(metadata.ocId, privacy: .public), etag: \(metadata.etag, privacy: .public), fileName: \(metadata.fileName, privacy: .public)" - ) - } - } catch { - Logger.ncFilesDatabase.error( - "Could not add item metadata. ocID: \(metadata.ocId, privacy: .public), etag: \(metadata.etag, privacy: .public), fileName: \(metadata.fileName, privacy: .public), received error: \(error.localizedDescription, privacy: .public)" - ) - } - } - - @discardableResult func deleteItemMetadata(ocId: String) -> Bool { - let database = ncDatabase() - - do { - try database.write { - let results = database.objects(NextcloudItemMetadataTable.self).filter( - "ocId == %@", ocId) - - Logger.ncFilesDatabase.debug("Deleting item metadata. \(ocId, privacy: .public)") - database.delete(results) - } - - return true - } catch { - Logger.ncFilesDatabase.error( - "Could not delete item metadata with ocId: \(ocId, privacy: .public), received error: \(error.localizedDescription, privacy: .public)" - ) - return false - } - } - - func renameItemMetadata(ocId: String, newServerUrl: String, newFileName: String) { - let database = ncDatabase() - - do { - try database.write { - guard - let itemMetadata = database.objects(NextcloudItemMetadataTable.self).filter( - "ocId == %@", ocId - ).first - else { - Logger.ncFilesDatabase.debug( - "Could not find an item with ocID \(ocId, privacy: .public) to rename to \(newFileName, privacy: .public)" - ) - return - } - - let oldFileName = itemMetadata.fileName - let oldServerUrl = itemMetadata.serverUrl - - itemMetadata.fileName = newFileName - itemMetadata.fileNameView = newFileName - itemMetadata.serverUrl = newServerUrl - - database.add(itemMetadata, update: .all) - - Logger.ncFilesDatabase.debug( - "Renamed item \(oldFileName, privacy: .public) to \(newFileName, privacy: .public), moved from serverUrl: \(oldServerUrl, privacy: .public) to serverUrl: \(newServerUrl, privacy: .public)" - ) - } - } catch { - Logger.ncFilesDatabase.error( - "Could not rename filename of item metadata with ocID: \(ocId, privacy: .public) to proposed name \(newFileName, privacy: .public) at proposed serverUrl \(newServerUrl, privacy: .public), received error: \(error.localizedDescription, privacy: .public)" - ) - } - } - - func parentItemIdentifierFromMetadata(_ metadata: NextcloudItemMetadataTable) - -> NSFileProviderItemIdentifier? - { - let homeServerFilesUrl = metadata.urlBase + "/remote.php/dav/files/" + metadata.userId - - if metadata.serverUrl == homeServerFilesUrl { - return .rootContainer - } - - guard let itemParentDirectory = parentDirectoryMetadataForItem(metadata) else { - Logger.ncFilesDatabase.error( - "Could not get item parent directory metadata for metadata. ocID: \(metadata.ocId, privacy: .public), etag: \(metadata.etag, privacy: .public), fileName: \(metadata.fileName, privacy: .public)" - ) - return nil - } - - if let parentDirectoryMetadata = itemMetadataFromOcId(itemParentDirectory.ocId) { - return NSFileProviderItemIdentifier(parentDirectoryMetadata.ocId) - } - - Logger.ncFilesDatabase.error( - "Could not get item parent directory item metadata for metadata. ocID: \(metadata.ocId, privacy: .public), etag: \(metadata.etag, privacy: .public), fileName: \(metadata.fileName, privacy: .public)" - ) - return nil - } -} diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudItemMetadataTable+NKFile.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudItemMetadataTable+NKFile.swift deleted file mode 100644 index c54744948af41..0000000000000 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudItemMetadataTable+NKFile.swift +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright (C) 2023 by Claudio Cambra - * - * 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 2 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. - */ - -import Foundation -import NextcloudKit - -extension NextcloudItemMetadataTable { - static func fromNKFile(_ file: NKFile, account: String) -> NextcloudItemMetadataTable { - let metadata = NextcloudItemMetadataTable() - - metadata.account = account - metadata.checksums = file.checksums - metadata.commentsUnread = file.commentsUnread - metadata.contentType = file.contentType - if let date = file.creationDate { - metadata.creationDate = date as Date - } else { - metadata.creationDate = file.date as Date - } - metadata.dataFingerprint = file.dataFingerprint - metadata.date = file.date as Date - metadata.directory = file.directory - metadata.downloadURL = file.downloadURL - metadata.e2eEncrypted = file.e2eEncrypted - metadata.etag = file.etag - metadata.favorite = file.favorite - metadata.fileId = file.fileId - metadata.fileName = file.fileName - metadata.fileNameView = file.fileName - metadata.hasPreview = file.hasPreview - metadata.iconName = file.iconName - metadata.mountType = file.mountType - metadata.name = file.name - metadata.note = file.note - metadata.ocId = file.ocId - metadata.ownerId = file.ownerId - metadata.ownerDisplayName = file.ownerDisplayName - metadata.lock = file.lock - metadata.lockOwner = file.lockOwner - metadata.lockOwnerEditor = file.lockOwnerEditor - metadata.lockOwnerType = file.lockOwnerType - metadata.lockOwnerDisplayName = file.lockOwnerDisplayName - metadata.lockTime = file.lockTime - metadata.lockTimeOut = file.lockTimeOut - metadata.path = file.path - metadata.permissions = file.permissions - metadata.quotaUsedBytes = file.quotaUsedBytes - metadata.quotaAvailableBytes = file.quotaAvailableBytes - metadata.richWorkspace = file.richWorkspace - metadata.resourceType = file.resourceType - metadata.serverUrl = file.serverUrl - metadata.sharePermissionsCollaborationServices = file.sharePermissionsCollaborationServices - for element in file.sharePermissionsCloudMesh { - metadata.sharePermissionsCloudMesh.append(element) - } - for element in file.shareType { - metadata.shareType.append(element) - } - metadata.size = file.size - metadata.classFile = file.classFile - // FIXME: iOS 12.0,* don't detect UTI text/markdown, text/x-markdown - if metadata.contentType == "text/markdown" || metadata.contentType == "text/x-markdown", - metadata.classFile == NKCommon.TypeClassFile.unknow.rawValue - { - metadata.classFile = NKCommon.TypeClassFile.document.rawValue - } - if let date = file.uploadDate { - metadata.uploadDate = date as Date - } else { - metadata.uploadDate = file.date as Date - } - metadata.urlBase = file.urlBase - metadata.user = file.user - metadata.userId = file.userId - - // Support for finding the correct filename for e2ee files should go here - - return metadata - } - - static func metadatasFromDirectoryReadNKFiles( - _ files: [NKFile], - account: String, - completionHandler: @escaping ( - _ directoryMetadata: NextcloudItemMetadataTable, - _ childDirectoriesMetadatas: [NextcloudItemMetadataTable], - _ metadatas: [NextcloudItemMetadataTable] - ) -> Void - ) { - var directoryMetadataSet = false - var directoryMetadata = NextcloudItemMetadataTable() - var childDirectoriesMetadatas: [NextcloudItemMetadataTable] = [] - var metadatas: [NextcloudItemMetadataTable] = [] - - let conversionQueue = DispatchQueue( - label: "nkFileToMetadataConversionQueue", - qos: .userInitiated, - attributes: .concurrent) - // appendQueue is a serial queue, not concurrent - let appendQueue = DispatchQueue(label: "metadataAppendQueue", qos: .userInitiated) - let dispatchGroup = DispatchGroup() - - for file in files { - if metadatas.isEmpty, !directoryMetadataSet { - let metadata = NextcloudItemMetadataTable.fromNKFile(file, account: account) - directoryMetadata = metadata - directoryMetadataSet = true - } else { - conversionQueue.async(group: dispatchGroup) { - let metadata = NextcloudItemMetadataTable.fromNKFile(file, account: account) - - appendQueue.async(group: dispatchGroup) { - metadatas.append(metadata) - if metadata.directory { - childDirectoriesMetadatas.append(metadata) - } - } - } - } - } - - dispatchGroup.notify(queue: DispatchQueue.main) { - completionHandler(directoryMetadata, childDirectoriesMetadatas, metadatas) - } - } -} diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudItemMetadataTable.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudItemMetadataTable.swift deleted file mode 100644 index 1ba072128e676..0000000000000 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudItemMetadataTable.swift +++ /dev/null @@ -1,219 +0,0 @@ -/* - * Copyright (C) 2023 by Claudio Cambra - * - * 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 2 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. - */ - -import FileProvider -import Foundation -import NextcloudKit -import RealmSwift - -class NextcloudItemMetadataTable: Object { - enum Status: Int { - case downloadError = -4 - case downloading = -3 - case inDownload = -2 - case waitDownload = -1 - - case normal = 0 - - case waitUpload = 1 - case inUpload = 2 - case uploading = 3 - case uploadError = 4 - } - - enum SharePermissions: Int { - case readShare = 1 - case updateShare = 2 - case createShare = 4 - case deleteShare = 8 - case shareShare = 16 - - case maxFileShare = 19 - case maxFolderShare = 31 - } - - @Persisted(primaryKey: true) var ocId: String - @Persisted var account = "" - @Persisted var assetLocalIdentifier = "" - @Persisted var checksums = "" - @Persisted var chunk: Bool = false - @Persisted var classFile = "" - @Persisted var commentsUnread: Bool = false - @Persisted var contentType = "" - @Persisted var creationDate = Date() - @Persisted var dataFingerprint = "" - @Persisted var date = Date() - @Persisted var directory: Bool = false - @Persisted var deleteAssetLocalIdentifier: Bool = false - @Persisted var downloadURL = "" - @Persisted var e2eEncrypted: Bool = false - @Persisted var edited: Bool = false - @Persisted var etag = "" - @Persisted var etagResource = "" - @Persisted var favorite: Bool = false - @Persisted var fileId = "" - @Persisted var fileName = "" - @Persisted var fileNameView = "" - @Persisted var hasPreview: Bool = false - @Persisted var iconName = "" - @Persisted var iconUrl = "" - @Persisted var isExtractFile: Bool = false - @Persisted var livePhoto: Bool = false - @Persisted var mountType = "" - @Persisted var name = "" // for unifiedSearch is the provider.id - @Persisted var note = "" - @Persisted var ownerId = "" - @Persisted var ownerDisplayName = "" - @Persisted var lock = false - @Persisted var lockOwner = "" - @Persisted var lockOwnerEditor = "" - @Persisted var lockOwnerType = 0 - @Persisted var lockOwnerDisplayName = "" - @Persisted var lockTime: Date? - @Persisted var lockTimeOut: Date? - @Persisted var path = "" - @Persisted var permissions = "" - @Persisted var quotaUsedBytes: Int64 = 0 - @Persisted var quotaAvailableBytes: Int64 = 0 - @Persisted var resourceType = "" - @Persisted var richWorkspace: String? - @Persisted var serverUrl = "" // For parent directory!! - @Persisted var session = "" - @Persisted var sessionError = "" - @Persisted var sessionSelector = "" - @Persisted var sessionTaskIdentifier: Int = 0 - @Persisted var sharePermissionsCollaborationServices: Int = 0 - // TODO: Find a way to compare these two below in remote state check - let sharePermissionsCloudMesh = List() - let shareType = List() - @Persisted var size: Int64 = 0 - @Persisted var status: Int = 0 - @Persisted var subline: String? - @Persisted var trashbinFileName = "" - @Persisted var trashbinOriginalLocation = "" - @Persisted var trashbinDeletionTime = Date() - @Persisted var uploadDate = Date() - @Persisted var url = "" - @Persisted var urlBase = "" - @Persisted var user = "" - @Persisted var userId = "" - - var fileExtension: String { - (fileNameView as NSString).pathExtension - } - - var fileNoExtension: String { - (fileNameView as NSString).deletingPathExtension - } - - var isRenameable: Bool { - lock - } - - var isPrintable: Bool { - if isDocumentViewableOnly { - return false - } - if ["application/pdf", "com.adobe.pdf"].contains(contentType) - || contentType.hasPrefix("text/") - || classFile == NKCommon.TypeClassFile.image.rawValue - { - return true - } - return false - } - - var isDocumentViewableOnly: Bool { - sharePermissionsCollaborationServices == SharePermissions.readShare.rawValue - && classFile == NKCommon.TypeClassFile.document.rawValue - } - - var isCopyableInPasteboard: Bool { - !isDocumentViewableOnly && !directory - } - - var isModifiableWithQuickLook: Bool { - if directory || isDocumentViewableOnly { - return false - } - return contentType == "com.adobe.pdf" || contentType == "application/pdf" - || classFile == NKCommon.TypeClassFile.image.rawValue - } - - var isSettableOnOffline: Bool { - session.isEmpty && !isDocumentViewableOnly - } - - var canOpenIn: Bool { - session.isEmpty && !isDocumentViewableOnly && !directory - } - - var isDownloadUpload: Bool { - status == Status.inDownload.rawValue || status == Status.downloading.rawValue - || status == Status.inUpload.rawValue || status == Status.uploading.rawValue - } - - var isDownload: Bool { - status == Status.inDownload.rawValue || status == Status.downloading.rawValue - } - - var isUpload: Bool { - status == Status.inUpload.rawValue || status == Status.uploading.rawValue - } - - override func isEqual(_ object: Any?) -> Bool { - if let object = object as? NextcloudItemMetadataTable { - return fileId == object.fileId && account == object.account && path == object.path - && fileName == object.fileName - } - - return false - } - - func isInSameDatabaseStoreableRemoteState(_ comparingMetadata: NextcloudItemMetadataTable) - -> Bool - { - comparingMetadata.etag == etag - && comparingMetadata.fileNameView == fileNameView - && comparingMetadata.date == date - && comparingMetadata.permissions == permissions - && comparingMetadata.hasPreview == hasPreview - && comparingMetadata.note == note - && comparingMetadata.lock == lock - && comparingMetadata.sharePermissionsCollaborationServices - == sharePermissionsCollaborationServices - && comparingMetadata.favorite == favorite - } - - /// Returns false if the user is lokced out of the file. I.e. The file is locked but by someone else - func canUnlock(as user: String) -> Bool { - !lock || (lockOwner == user && lockOwnerType == 0) - } - - func thumbnailUrl(size: CGSize) -> URL? { - guard hasPreview else { - return nil - } - - let urlBase = urlBase.urlEncoded! - // Leave the leading slash in webdavUrl - let webdavUrl = urlBase + NextcloudAccount.webDavFilesUrlSuffix + user - let serverFileRelativeUrl = - serverUrl.replacingOccurrences(of: webdavUrl, with: "") + "/" + fileName - - let urlString = - "\(urlBase)/index.php/core/preview.png?file=\(serverFileRelativeUrl)&x=\(size.width)&y=\(size.height)&a=1&mode=cover" - return URL(string: urlString) - } -} diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudLocalFileMetadataTable.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudLocalFileMetadataTable.swift deleted file mode 100644 index 7eda4817c0253..0000000000000 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudLocalFileMetadataTable.swift +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (C) 2023 by Claudio Cambra - * - * 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 2 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. - */ - -import Foundation -import RealmSwift - -class NextcloudLocalFileMetadataTable: Object { - @Persisted(primaryKey: true) var ocId: String - @Persisted var account = "" - @Persisted var etag = "" - @Persisted var exifDate: Date? - @Persisted var exifLatitude = "" - @Persisted var exifLongitude = "" - @Persisted var exifLensModel: String? - @Persisted var favorite: Bool = false - @Persisted var fileName = "" - @Persisted var offline: Bool = false -} diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Extensions/Logger+Extensions.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Extensions/Logger+Extensions.swift index e9e5d78fd0d2a..5bcd98c323594 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Extensions/Logger+Extensions.swift +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Extensions/Logger+Extensions.swift @@ -20,17 +20,9 @@ extension Logger { static let desktopClientConnection = Logger( subsystem: subsystem, category: "desktopclientconnection") static let fpUiExtensionService = Logger(subsystem: subsystem, category: "fpUiExtensionService") - static let enumeration = Logger(subsystem: subsystem, category: "enumeration") static let fileProviderExtension = Logger( subsystem: subsystem, category: "fileproviderextension") - static let fileTransfer = Logger(subsystem: subsystem, category: "filetransfer") - static let localFileOps = Logger(subsystem: subsystem, category: "localfileoperations") - static let ncFilesDatabase = Logger(subsystem: subsystem, category: "nextcloudfilesdatabase") static let shares = Logger(subsystem: subsystem, category: "shares") - static let ncAccount = Logger(subsystem: subsystem, category: "ncAccount") - static let materialisedFileHandling = Logger( - subsystem: subsystem, category: "materialisedfilehandling" - ) static let logger = Logger(subsystem: subsystem, category: "logger") @available(macOSApplicationExtension 12.0, *) diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Extensions/NKError+Extensions.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Extensions/NKError+Extensions.swift deleted file mode 100644 index b3c1be80384b8..0000000000000 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Extensions/NKError+Extensions.swift +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (C) 2023 by Claudio Cambra - * - * 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 2 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. - */ - -import FileProvider -import Foundation -import NextcloudKit - -extension NKError { - static var noChangesErrorCode: Int { - -200 - } - - var isCouldntConnectError: Bool { - errorCode == -9999 || errorCode == -1001 || errorCode == -1004 || errorCode == -1005 - || errorCode == -1009 || errorCode == -1012 || errorCode == -1200 || errorCode == -1202 - || errorCode == 500 || errorCode == 503 || errorCode == 200 - } - - var isUnauthenticatedError: Bool { - errorCode == -1013 - } - - var isGoingOverQuotaError: Bool { - errorCode == 507 - } - - var isNotFoundError: Bool { - errorCode == 404 - } - - var isNoChangesError: Bool { - errorCode == NKError.noChangesErrorCode - } - - var fileProviderError: NSFileProviderError { - if isNotFoundError { - NSFileProviderError(.noSuchItem) - } else if isCouldntConnectError { - // Provide something the file provider can do something with - NSFileProviderError(.serverUnreachable) - } else if isUnauthenticatedError { - NSFileProviderError(.notAuthenticated) - } else if isGoingOverQuotaError { - NSFileProviderError(.insufficientQuota) - } else { - NSFileProviderError(.cannotSynchronize) - } - } -} diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Extensions/Progress+Extensions.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Extensions/Progress+Extensions.swift deleted file mode 100644 index 630a370dd63c9..0000000000000 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Extensions/Progress+Extensions.swift +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) 2023 by Claudio Cambra - * - * 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 2 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. - */ - -import Alamofire -import Foundation - -extension Progress { - func setHandlersFromAfRequest(_ request: Request) { - cancellationHandler = { request.cancel() } - pausingHandler = { request.suspend() } - resumingHandler = { request.resume() } - } - - func copyCurrentStateToProgress(_ otherProgress: Progress, includeHandlers: Bool = false) { - if includeHandlers { - otherProgress.cancellationHandler = cancellationHandler - otherProgress.pausingHandler = pausingHandler - otherProgress.resumingHandler = resumingHandler - } - - otherProgress.totalUnitCount = totalUnitCount - otherProgress.completedUnitCount = completedUnitCount - otherProgress.estimatedTimeRemaining = estimatedTimeRemaining - otherProgress.localizedDescription = localizedAdditionalDescription - otherProgress.localizedAdditionalDescription = localizedAdditionalDescription - otherProgress.isCancellable = isCancellable - otherProgress.isPausable = isPausable - otherProgress.fileCompletedCount = fileCompletedCount - otherProgress.fileURL = fileURL - otherProgress.fileTotalCount = fileTotalCount - otherProgress.fileCompletedCount = fileCompletedCount - otherProgress.fileOperationKind = fileOperationKind - otherProgress.kind = kind - otherProgress.throughput = throughput - - for (key, object) in userInfo { - otherProgress.setUserInfoObject(object, forKey: key) - } - } - - func copyOfCurrentState(includeHandlers: Bool = false) -> Progress { - let newProgress = Progress() - copyCurrentStateToProgress(newProgress, includeHandlers: includeHandlers) - return newProgress - } -} diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderEnumerator+SyncEngine.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderEnumerator+SyncEngine.swift deleted file mode 100644 index f9e0ab0c7c678..0000000000000 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderEnumerator+SyncEngine.swift +++ /dev/null @@ -1,405 +0,0 @@ -/* - * Copyright (C) 2023 by Claudio Cambra - * - * 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 2 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. - */ - -import FileProvider -import NextcloudKit -import OSLog - -extension FileProviderEnumerator { - func fullRecursiveScan( - ncAccount: NextcloudAccount, - ncKit: NextcloudKit, - scanChangesOnly: Bool, - completionHandler: @escaping ( - _ metadatas: [NextcloudItemMetadataTable], - _ newMetadatas: [NextcloudItemMetadataTable], - _ updatedMetadatas: [NextcloudItemMetadataTable], - _ deletedMetadatas: [NextcloudItemMetadataTable], - _ error: NKError? - ) -> Void - ) { - let rootContainerDirectoryMetadata = NextcloudItemMetadataTable() - rootContainerDirectoryMetadata.directory = true - rootContainerDirectoryMetadata.ocId = NSFileProviderItemIdentifier.rootContainer.rawValue - - // Create a serial dispatch queue - let dispatchQueue = DispatchQueue( - label: "recursiveChangeEnumerationQueue", qos: .userInitiated) - - dispatchQueue.async { - let results = self.scanRecursively( - rootContainerDirectoryMetadata, - ncAccount: ncAccount, - ncKit: ncKit, - scanChangesOnly: scanChangesOnly) - - // Run a check to ensure files deleted in one location are not updated in another (e.g. when moved) - // The recursive scan provides us with updated/deleted metadatas only on a folder by folder basis; - // so we need to check we are not simultaneously marking a moved file as deleted and updated - var checkedDeletedMetadatas = results.deletedMetadatas - - for updatedMetadata in results.updatedMetadatas { - guard - let matchingDeletedMetadataIdx = checkedDeletedMetadatas.firstIndex(where: { - $0.ocId == updatedMetadata.ocId - }) - else { - continue - } - - checkedDeletedMetadatas.remove(at: matchingDeletedMetadataIdx) - } - - DispatchQueue.main.async { - completionHandler( - results.metadatas, results.newMetadatas, results.updatedMetadatas, - checkedDeletedMetadatas, results.error) - } - } - } - - private func scanRecursively( - _ directoryMetadata: NextcloudItemMetadataTable, - ncAccount: NextcloudAccount, - ncKit: NextcloudKit, - scanChangesOnly: Bool - ) -> ( - metadatas: [NextcloudItemMetadataTable], - newMetadatas: [NextcloudItemMetadataTable], - updatedMetadatas: [NextcloudItemMetadataTable], - deletedMetadatas: [NextcloudItemMetadataTable], - error: NKError? - ) { - if isInvalidated { - return ([], [], [], [], nil) - } - - assert(directoryMetadata.directory, "Can only recursively scan a directory.") - - // Will include results of recursive calls - var allMetadatas: [NextcloudItemMetadataTable] = [] - var allNewMetadatas: [NextcloudItemMetadataTable] = [] - var allUpdatedMetadatas: [NextcloudItemMetadataTable] = [] - var allDeletedMetadatas: [NextcloudItemMetadataTable] = [] - - let dbManager = NextcloudFilesDatabaseManager.shared - let dispatchGroup = DispatchGroup() // TODO: Maybe own thread? - - dispatchGroup.enter() - - var criticalError: NKError? - let itemServerUrl = - directoryMetadata.ocId == NSFileProviderItemIdentifier.rootContainer.rawValue - ? ncAccount.davFilesUrl : directoryMetadata.serverUrl + "/" + directoryMetadata.fileName - - Logger.enumeration.debug("About to read: \(itemServerUrl, privacy: .public)") - - FileProviderEnumerator.readServerUrl( - itemServerUrl, ncAccount: ncAccount, ncKit: ncKit, stopAtMatchingEtags: scanChangesOnly - ) { metadatas, newMetadatas, updatedMetadatas, deletedMetadatas, readError in - - if readError != nil { - let nkReadError = NKError(error: readError!) - - // Is the error is that we have found matching etags on this item, then ignore it - // if we are doing a full rescan - guard nkReadError.isNoChangesError, scanChangesOnly else { - Logger.enumeration.error( - "Finishing enumeration of changes at \(itemServerUrl, privacy: .public) with \(readError!.localizedDescription, privacy: .public)" - ) - - if nkReadError.isNotFoundError { - Logger.enumeration.info( - "404 error means item no longer exists. Deleting metadata and reporting as deletion without error" - ) - - if let deletedMetadatas = - dbManager.deleteDirectoryAndSubdirectoriesMetadata( - ocId: directoryMetadata.ocId) - { - allDeletedMetadatas += deletedMetadatas - } else { - Logger.enumeration.error( - "An error occurred while trying to delete directory and children not found in recursive scan" - ) - } - - } else if nkReadError.isNoChangesError { // All is well, just no changed etags - Logger.enumeration.info( - "Error was to say no changed files -- not bad error. No need to check children." - ) - - } else if nkReadError.isUnauthenticatedError - || nkReadError.isCouldntConnectError - { - // If it is a critical error then stop, if not then continue - Logger.enumeration.error( - "Error will affect next enumerated items, so stopping enumeration.") - criticalError = nkReadError - } - - dispatchGroup.leave() - return - } - } - - Logger.enumeration.info( - "Finished reading serverUrl: \(itemServerUrl, privacy: .public) for user: \(ncAccount.ncKitAccount, privacy: .public)" - ) - - if let metadatas { - allMetadatas += metadatas - } else { - Logger.enumeration.warning( - "WARNING: Nil metadatas received for reading of changes at \(itemServerUrl, privacy: .public) for user: \(ncAccount.ncKitAccount, privacy: .public)" - ) - } - - if let newMetadatas { - allNewMetadatas += newMetadatas - } else { - Logger.enumeration.warning( - "WARNING: Nil new metadatas received for reading of changes at \(itemServerUrl, privacy: .public) for user: \(ncAccount.ncKitAccount, privacy: .public)" - ) - } - - if let updatedMetadatas { - allUpdatedMetadatas += updatedMetadatas - } else { - Logger.enumeration.warning( - "WARNING: Nil updated metadatas received for reading of changes at \(itemServerUrl, privacy: .public) for user: \(ncAccount.ncKitAccount, privacy: .public)" - ) - } - - if let deletedMetadatas { - allDeletedMetadatas += deletedMetadatas - } else { - Logger.enumeration.warning( - "WARNING: Nil deleted metadatas received for reading of changes at \(itemServerUrl, privacy: .public) for user: \(ncAccount.ncKitAccount, privacy: .public)" - ) - } - - dispatchGroup.leave() - } - - dispatchGroup.wait() - - guard criticalError == nil else { - Logger.enumeration.error( - "Received critical error stopping further scanning: \(criticalError!.errorDescription, privacy: .public)" - ) - return ([], [], [], [], error: criticalError) - } - - var childDirectoriesToScan: [NextcloudItemMetadataTable] = [] - var candidateMetadatas: [NextcloudItemMetadataTable] - - if scanChangesOnly, fastEnumeration { - candidateMetadatas = allUpdatedMetadatas - } else if scanChangesOnly { - candidateMetadatas = allUpdatedMetadatas + allNewMetadatas - } else { - candidateMetadatas = allMetadatas - } - - for candidateMetadata in candidateMetadatas { - if candidateMetadata.directory { - childDirectoriesToScan.append(candidateMetadata) - } - } - - Logger.enumeration.debug("Candidate metadatas for further scan: \(candidateMetadatas, privacy: .public)") - - if childDirectoriesToScan.isEmpty { - return ( - metadatas: allMetadatas, newMetadatas: allNewMetadatas, - updatedMetadatas: allUpdatedMetadatas, deletedMetadatas: allDeletedMetadatas, nil - ) - } - - for childDirectory in childDirectoriesToScan { - Logger.enumeration.debug( - "About to recursively scan: \(childDirectory.urlBase, privacy: .public) with etag: \(childDirectory.etag, privacy: .public)" - ) - let childScanResult = scanRecursively( - childDirectory, ncAccount: ncAccount, ncKit: ncKit, scanChangesOnly: scanChangesOnly - ) - - allMetadatas += childScanResult.metadatas - allNewMetadatas += childScanResult.newMetadatas - allUpdatedMetadatas += childScanResult.updatedMetadatas - allDeletedMetadatas += childScanResult.deletedMetadatas - } - - return ( - metadatas: allMetadatas, newMetadatas: allNewMetadatas, - updatedMetadatas: allUpdatedMetadatas, - deletedMetadatas: allDeletedMetadatas, nil - ) - } - - static func handleDepth1ReadFileOrFolder( - serverUrl: String, - ncAccount: NextcloudAccount, - files: [NKFile], - error: NKError, - completionHandler: @escaping ( - _ metadatas: [NextcloudItemMetadataTable]?, - _ newMetadatas: [NextcloudItemMetadataTable]?, - _ updatedMetadatas: [NextcloudItemMetadataTable]?, - _ deletedMetadatas: [NextcloudItemMetadataTable]?, - _ readError: Error? - ) -> Void - ) { - guard error == .success else { - Logger.enumeration.error( - "1 depth readFileOrFolder of url: \(serverUrl, privacy: .public) did not complete successfully, received error: \(error.errorDescription, privacy: .public)" - ) - completionHandler(nil, nil, nil, nil, error.error) - return - } - - Logger.enumeration.debug( - "Starting async conversion of NKFiles for serverUrl: \(serverUrl, privacy: .public) for user: \(ncAccount.ncKitAccount, privacy: .public)" - ) - - let dbManager = NextcloudFilesDatabaseManager.shared - - DispatchQueue.global(qos: .userInitiated).async { - NextcloudItemMetadataTable.metadatasFromDirectoryReadNKFiles( - files, account: ncAccount.ncKitAccount - ) { directoryMetadata, _, metadatas in - - // STORE DATA FOR CURRENTLY SCANNED DIRECTORY - // We have now scanned this directory's contents, so update with etag in order to not check again if not needed - // unless it's the root container - if serverUrl != ncAccount.davFilesUrl { - dbManager.addItemMetadata(directoryMetadata) - } - - // Don't update the etags for folders as we haven't checked their contents. - // When we do a recursive check, if we update the etags now, we will think - // that our local copies are up to date -- instead, leave them as the old. - // They will get updated when they are the subject of a readServerUrl call. - // (See above) - let changedMetadatas = dbManager.updateItemMetadatas( - account: ncAccount.ncKitAccount, serverUrl: serverUrl, - updatedMetadatas: metadatas, - updateDirectoryEtags: false) - - DispatchQueue.main.async { - completionHandler( - metadatas, changedMetadatas.newMetadatas, changedMetadatas.updatedMetadatas, - changedMetadatas.deletedMetadatas, nil) - } - } - } - } - - static func readServerUrl( - _ serverUrl: String, - ncAccount: NextcloudAccount, - ncKit: NextcloudKit, - stopAtMatchingEtags: Bool = false, - depth: String = "1", - completionHandler: @escaping ( - _ metadatas: [NextcloudItemMetadataTable]?, - _ newMetadatas: [NextcloudItemMetadataTable]?, - _ updatedMetadatas: [NextcloudItemMetadataTable]?, - _ deletedMetadatas: [NextcloudItemMetadataTable]?, - _ readError: Error? - ) -> Void - ) { - let dbManager = NextcloudFilesDatabaseManager.shared - let ncKitAccount = ncAccount.ncKitAccount - - Logger.enumeration.debug( - "Starting to read serverUrl: \(serverUrl, privacy: .public) for user: \(ncAccount.ncKitAccount, privacy: .public) at depth \(depth, privacy: .public). NCKit info: userId: \(ncKit.nkCommonInstance.user, privacy: .public), password is empty: \(ncKit.nkCommonInstance.password == "" ? "EMPTY PASSWORD" : "NOT EMPTY PASSWORD"), urlBase: \(ncKit.nkCommonInstance.urlBase, privacy: .public), ncVersion: \(ncKit.nkCommonInstance.nextcloudVersion, privacy: .public)" - ) - - ncKit.readFileOrFolder(serverUrlFileName: serverUrl, depth: depth, showHiddenFiles: true) { - _, files, _, error in - guard error == .success else { - Logger.enumeration.error( - "\(depth, privacy: .public) depth readFileOrFolder of url: \(serverUrl, privacy: .public) did not complete successfully, received error: \(error.errorDescription, privacy: .public)" - ) - completionHandler(nil, nil, nil, nil, error.error) - return - } - - guard let receivedFile = files.first else { - Logger.enumeration.error( - "Received no items from readFileOrFolder of \(serverUrl, privacy: .public), not much we can do..." - ) - completionHandler(nil, nil, nil, nil, error.error) - return - } - - guard receivedFile.directory else { - Logger.enumeration.debug( - "Read item is a file. Converting NKfile for serverUrl: \(serverUrl, privacy: .public) for user: \(ncAccount.ncKitAccount, privacy: .public)" - ) - let itemMetadata = NextcloudItemMetadataTable.fromNKFile( - receivedFile, account: ncKitAccount) - dbManager.addItemMetadata(itemMetadata) // TODO: Return some value when it is an update - completionHandler([itemMetadata], nil, nil, nil, error.error) - return - } - - if stopAtMatchingEtags, - let directoryMetadata = dbManager.directoryMetadata( - account: ncKitAccount, serverUrl: serverUrl) - { - let directoryEtag = directoryMetadata.etag - - guard directoryEtag == "" || directoryEtag != receivedFile.etag else { - Logger.enumeration.debug( - "Read server url called with flag to stop enumerating at matching etags. Returning and providing soft error." - ) - - let description = - "Fetched directory etag is same as that stored locally. Not fetching child items." - let nkError = NKError( - errorCode: NKError.noChangesErrorCode, errorDescription: description) - - let metadatas = dbManager.itemMetadatas( - account: ncKitAccount, serverUrl: serverUrl) - - completionHandler(metadatas, nil, nil, nil, nkError.error) - return - } - } - - if depth == "0" { - if serverUrl != ncAccount.davFilesUrl { - let metadata = NextcloudItemMetadataTable.fromNKFile( - receivedFile, account: ncKitAccount) - let isNew = dbManager.itemMetadataFromOcId(metadata.ocId) == nil - let updatedMetadatas = isNew ? [] : [metadata] - let newMetadatas = isNew ? [metadata] : [] - - dbManager.addItemMetadata(metadata) - - DispatchQueue.main.async { - completionHandler([metadata], newMetadatas, updatedMetadatas, nil, nil) - } - } - } else { - handleDepth1ReadFileOrFolder( - serverUrl: serverUrl, ncAccount: ncAccount, files: files, error: error, - completionHandler: completionHandler) - } - } - } -} diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderEnumerator.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderEnumerator.swift deleted file mode 100644 index e74011e8fb368..0000000000000 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderEnumerator.swift +++ /dev/null @@ -1,461 +0,0 @@ -/* - * Copyright (C) 2022 by Claudio Cambra - * - * 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 2 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. - */ - -import FileProvider -import NextcloudKit -import OSLog - -class FileProviderEnumerator: NSObject, NSFileProviderEnumerator { - private let enumeratedItemIdentifier: NSFileProviderItemIdentifier - private var enumeratedItemMetadata: NextcloudItemMetadataTable? - private var enumeratingSystemIdentifier: Bool { - FileProviderEnumerator.isSystemIdentifier(enumeratedItemIdentifier) - } - - // TODO: actually use this in NCKit and server requests - private let anchor = NSFileProviderSyncAnchor(Date().description.data(using: .utf8)!) - private static let maxItemsPerFileProviderPage = 100 - let ncAccount: NextcloudAccount - let ncKit: NextcloudKit - let fastEnumeration: Bool - var serverUrl: String = "" - var isInvalidated = false - - private static func isSystemIdentifier(_ identifier: NSFileProviderItemIdentifier) -> Bool { - identifier == .rootContainer || identifier == .trashContainer || identifier == .workingSet - } - - init( - enumeratedItemIdentifier: NSFileProviderItemIdentifier, - ncAccount: NextcloudAccount, - ncKit: NextcloudKit, - fastEnumeration: Bool = true - ) { - self.enumeratedItemIdentifier = enumeratedItemIdentifier - self.ncAccount = ncAccount - self.ncKit = ncKit - self.fastEnumeration = fastEnumeration - - if FileProviderEnumerator.isSystemIdentifier(enumeratedItemIdentifier) { - Logger.enumeration.debug( - "Providing enumerator for a system defined container: \(enumeratedItemIdentifier.rawValue, privacy: .public)" - ) - serverUrl = ncAccount.davFilesUrl - } else { - Logger.enumeration.debug( - "Providing enumerator for item with identifier: \(enumeratedItemIdentifier.rawValue, privacy: .public)" - ) - let dbManager = NextcloudFilesDatabaseManager.shared - - enumeratedItemMetadata = dbManager.itemMetadataFromFileProviderItemIdentifier( - enumeratedItemIdentifier) - if enumeratedItemMetadata != nil { - serverUrl = - enumeratedItemMetadata!.serverUrl + "/" + enumeratedItemMetadata!.fileName - } else { - Logger.enumeration.error( - "Could not find itemMetadata for file with identifier: \(enumeratedItemIdentifier.rawValue, privacy: .public)" - ) - } - } - - Logger.enumeration.info( - "Set up enumerator for user: \(self.ncAccount.ncKitAccount, privacy: .public) with serverUrl: \(self.serverUrl, privacy: .public)" - ) - super.init() - } - - func invalidate() { - Logger.enumeration.debug( - "Enumerator is being invalidated for item with identifier: \(self.enumeratedItemIdentifier.rawValue, privacy: .public)" - ) - isInvalidated = true - } - - // MARK: - Protocol methods - - func enumerateItems( - for observer: NSFileProviderEnumerationObserver, startingAt page: NSFileProviderPage - ) { - Logger.enumeration.debug( - "Received enumerate items request for enumerator with user: \(self.ncAccount.ncKitAccount, privacy: .public) with serverUrl: \(self.serverUrl, privacy: .public)" - ) - /* - - inspect the page to determine whether this is an initial or a follow-up request (TODO) - - If this is an enumerator for a directory, the root container or all directories: - - perform a server request to fetch directory contents - If this is an enumerator for the working set: - - perform a server request to update your local database - - fetch the working set from your local database - - - inform the observer about the items returned by the server (possibly multiple times) - - inform the observer that you are finished with this page - */ - - if enumeratedItemIdentifier == .trashContainer { - Logger.enumeration.debug( - "Enumerating trash set for user: \(self.ncAccount.ncKitAccount, privacy: .public) with serverUrl: \(self.serverUrl, privacy: .public)" - ) - // TODO! - - observer.finishEnumerating(upTo: nil) - return - } - - // Handle the working set as if it were the root container - // If we do a full server scan per the recommendations of the File Provider documentation, - // we will be stuck for a huge period of time without being able to access files as the - // entire server gets scanned. Instead, treat the working set as the root container here. - // Then, when we enumerate changes, we'll go through everything -- while we can still - // navigate a little bit in Finder, file picker, etc - - guard serverUrl != "" else { - Logger.enumeration.error( - "Enumerator has empty serverUrl -- can't enumerate that! For identifier: \(self.enumeratedItemIdentifier.rawValue, privacy: .public)" - ) - observer.finishEnumeratingWithError(NSFileProviderError(.noSuchItem)) - return - } - - // TODO: Make better use of pagination and handle paging properly - if page == NSFileProviderPage.initialPageSortedByDate as NSFileProviderPage - || page == NSFileProviderPage.initialPageSortedByName as NSFileProviderPage - { - Logger.enumeration.debug( - "Enumerating initial page for user: \(self.ncAccount.ncKitAccount, privacy: .public) with serverUrl: \(self.serverUrl, privacy: .public)" - ) - - FileProviderEnumerator.readServerUrl(serverUrl, ncAccount: ncAccount, ncKit: ncKit) { - metadatas, _, _, _, readError in - - guard readError == nil else { - Logger.enumeration.error( - "Finishing enumeration for user: \(self.ncAccount.ncKitAccount, privacy: .public) with serverUrl: \(self.serverUrl, privacy: .public) with error \(readError!.localizedDescription, privacy: .public)" - ) - - let nkReadError = NKError(error: readError!) - observer.finishEnumeratingWithError(nkReadError.fileProviderError) - return - } - - guard let metadatas else { - Logger.enumeration.error( - "Finishing enumeration for user: \(self.ncAccount.ncKitAccount, privacy: .public) with serverUrl: \(self.serverUrl, privacy: .public) with invalid metadatas." - ) - observer.finishEnumeratingWithError(NSFileProviderError(.cannotSynchronize)) - return - } - - Logger.enumeration.info( - "Finished reading serverUrl: \(self.serverUrl, privacy: .public) for user: \(self.ncAccount.ncKitAccount, privacy: .public). Processed \(metadatas.count) metadatas" - ) - - FileProviderEnumerator.completeEnumerationObserver( - observer, ncKit: self.ncKit, numPage: 1, itemMetadatas: metadatas) - } - - return - } - - let numPage = Int(String(data: page.rawValue, encoding: .utf8)!)! - Logger.enumeration.debug( - "Enumerating page \(numPage, privacy: .public) for user: \(self.ncAccount.ncKitAccount, privacy: .public) with serverUrl: \(self.serverUrl, privacy: .public)" - ) - // TODO: Handle paging properly - // FileProviderEnumerator.completeObserver(observer, ncKit: ncKit, numPage: numPage, itemMetadatas: nil) - observer.finishEnumerating(upTo: nil) - } - - func enumerateChanges( - for observer: NSFileProviderChangeObserver, from anchor: NSFileProviderSyncAnchor - ) { - Logger.enumeration.debug( - "Received enumerate changes request for enumerator for user: \(self.ncAccount.ncKitAccount, privacy: .public) with serverUrl: \(self.serverUrl, privacy: .public)" - ) - /* - - query the server for updates since the passed-in sync anchor (TODO) - - If this is an enumerator for the working set: - - note the changes in your local database - - - inform the observer about item deletions and updates (modifications + insertions) - - inform the observer when you have finished enumerating up to a subsequent sync anchor - */ - - if enumeratedItemIdentifier == .workingSet { - Logger.enumeration.debug( - "Enumerating changes in working set for user: \(self.ncAccount.ncKitAccount, privacy: .public)" - ) - - // Unlike when enumerating items we can't progressively enumerate items as we need to wait to resolve which items are truly deleted and which - // have just been moved elsewhere. - fullRecursiveScan( - ncAccount: ncAccount, - ncKit: ncKit, - scanChangesOnly: true - ) { _, newMetadatas, updatedMetadatas, deletedMetadatas, error in - - if self.isInvalidated { - Logger.enumeration.info( - "Enumerator invalidated during working set change scan. For user: \(self.ncAccount.ncKitAccount, privacy: .public)" - ) - observer.finishEnumeratingWithError(NSFileProviderError(.cannotSynchronize)) - return - } - - guard error == nil else { - Logger.enumeration.info( - "Finished recursive change enumeration of working set for user: \(self.ncAccount.ncKitAccount, privacy: .public) with error: \(error!.errorDescription, privacy: .public)" - ) - observer.finishEnumeratingWithError(error!.fileProviderError) - return - } - - Logger.enumeration.info( - "Finished recursive change enumeration of working set for user: \(self.ncAccount.ncKitAccount, privacy: .public). Enumerating items." - ) - - FileProviderEnumerator.completeChangesObserver( - observer, - anchor: anchor, - ncKit: self.ncKit, - newMetadatas: newMetadatas, - updatedMetadatas: updatedMetadatas, - deletedMetadatas: deletedMetadatas) - } - return - } else if enumeratedItemIdentifier == .trashContainer { - Logger.enumeration.debug( - "Enumerating changes in trash set for user: \(self.ncAccount.ncKitAccount, privacy: .public)" - ) - // TODO! - - observer.finishEnumeratingChanges(upTo: anchor, moreComing: false) - return - } - - Logger.enumeration.info( - "Enumerating changes for user: \(self.ncAccount.ncKitAccount, privacy: .public) with serverUrl: \(self.serverUrl, privacy: .public)" - ) - - // No matter what happens here we finish enumeration in some way, either from the error - // handling below or from the completeChangesObserver - // TODO: Move to the sync engine extension - FileProviderEnumerator.readServerUrl( - serverUrl, ncAccount: ncAccount, ncKit: ncKit, stopAtMatchingEtags: true - ) { _, newMetadatas, updatedMetadatas, deletedMetadatas, readError in - - // If we get a 404 we might add more deleted metadatas - var currentDeletedMetadatas: [NextcloudItemMetadataTable] = [] - if let notNilDeletedMetadatas = deletedMetadatas { - currentDeletedMetadatas = notNilDeletedMetadatas - } - - guard readError == nil else { - Logger.enumeration.error( - "Finishing enumeration of changes for user: \(self.ncAccount.ncKitAccount, privacy: .public) with serverUrl: \(self.serverUrl, privacy: .public) with error: \(readError!.localizedDescription, privacy: .public)" - ) - - let nkReadError = NKError(error: readError!) - let fpError = nkReadError.fileProviderError - - if nkReadError.isNotFoundError { - Logger.enumeration.info( - "404 error means item no longer exists. Deleting metadata and reporting \(self.serverUrl, privacy: .public) as deletion without error" - ) - - guard let itemMetadata = self.enumeratedItemMetadata else { - Logger.enumeration.error( - "Invalid enumeratedItemMetadata, could not delete metadata nor report deletion" - ) - observer.finishEnumeratingWithError(fpError) - return - } - - let dbManager = NextcloudFilesDatabaseManager.shared - if itemMetadata.directory { - if let deletedDirectoryMetadatas = - dbManager.deleteDirectoryAndSubdirectoriesMetadata( - ocId: itemMetadata.ocId) - { - currentDeletedMetadatas += deletedDirectoryMetadatas - } else { - Logger.enumeration.error( - "Something went wrong when recursively deleting directory not found." - ) - } - } else { - dbManager.deleteItemMetadata(ocId: itemMetadata.ocId) - } - - FileProviderEnumerator.completeChangesObserver( - observer, anchor: anchor, ncKit: self.ncKit, newMetadatas: nil, - updatedMetadatas: nil, - deletedMetadatas: [itemMetadata]) - return - } else if nkReadError.isNoChangesError { // All is well, just no changed etags - Logger.enumeration.info( - "Error was to say no changed files -- not bad error. Finishing change enumeration." - ) - observer.finishEnumeratingChanges(upTo: anchor, moreComing: false) - return - } - - observer.finishEnumeratingWithError(fpError) - return - } - - Logger.enumeration.info( - "Finished reading serverUrl: \(self.serverUrl, privacy: .public) for user: \(self.ncAccount.ncKitAccount, privacy: .public)" - ) - - FileProviderEnumerator.completeChangesObserver( - observer, - anchor: anchor, - ncKit: self.ncKit, - newMetadatas: newMetadatas, - updatedMetadatas: updatedMetadatas, - deletedMetadatas: deletedMetadatas) - } - } - - func currentSyncAnchor(completionHandler: @escaping (NSFileProviderSyncAnchor?) -> Void) { - completionHandler(anchor) - } - - // MARK: - Helper methods - - private static func metadatasToFileProviderItems( - _ itemMetadatas: [NextcloudItemMetadataTable], ncKit: NextcloudKit, - completionHandler: @escaping (_ items: [NSFileProviderItem]) -> Void - ) { - var items: [NSFileProviderItem] = [] - - let conversionQueue = DispatchQueue( - label: "metadataToItemConversionQueue", qos: .userInitiated, attributes: .concurrent) - let appendQueue = DispatchQueue(label: "enumeratorItemAppendQueue", qos: .userInitiated) // Serial queue - let dispatchGroup = DispatchGroup() - - for itemMetadata in itemMetadatas { - conversionQueue.async(group: dispatchGroup) { - if itemMetadata.e2eEncrypted { - Logger.enumeration.info( - "Skipping encrypted metadata in enumeration: \(itemMetadata.ocId, privacy: .public) \(itemMetadata.fileName, privacy: .public)" - ) - return - } - - if let parentItemIdentifier = NextcloudFilesDatabaseManager.shared - .parentItemIdentifierFromMetadata(itemMetadata) - { - let item = FileProviderItem( - metadata: itemMetadata, parentItemIdentifier: parentItemIdentifier, - ncKit: ncKit) - Logger.enumeration.debug( - "Will enumerate item with ocId: \(itemMetadata.ocId, privacy: .public) and name: \(itemMetadata.fileName, privacy: .public)" - ) - - appendQueue.async(group: dispatchGroup) { - items.append(item) - } - } else { - Logger.enumeration.error( - "Could not get valid parentItemIdentifier for item with ocId: \(itemMetadata.ocId, privacy: .public) and name: \(itemMetadata.fileName, privacy: .public), skipping enumeration" - ) - } - } - } - - dispatchGroup.notify(queue: DispatchQueue.main) { - completionHandler(items) - } - } - - private static func fileProviderPageforNumPage(_ numPage: Int) -> NSFileProviderPage { - NSFileProviderPage("\(numPage)".data(using: .utf8)!) - } - - private static func completeEnumerationObserver( - _ observer: NSFileProviderEnumerationObserver, ncKit: NextcloudKit, numPage: Int, - itemMetadatas: [NextcloudItemMetadataTable] - ) { - metadatasToFileProviderItems(itemMetadatas, ncKit: ncKit) { items in - observer.didEnumerate(items) - Logger.enumeration.info("Did enumerate \(items.count) items") - - // TODO: Handle paging properly - /* - if items.count == maxItemsPerFileProviderPage { - let nextPage = numPage + 1 - let providerPage = NSFileProviderPage("\(nextPage)".data(using: .utf8)!) - observer.finishEnumerating(upTo: providerPage) - } else { - observer.finishEnumerating(upTo: nil) - } - */ - observer.finishEnumerating(upTo: fileProviderPageforNumPage(numPage)) - } - } - - private static func completeChangesObserver( - _ observer: NSFileProviderChangeObserver, anchor: NSFileProviderSyncAnchor, - ncKit: NextcloudKit, - newMetadatas: [NextcloudItemMetadataTable]?, - updatedMetadatas: [NextcloudItemMetadataTable]?, - deletedMetadatas: [NextcloudItemMetadataTable]? - ) { - guard newMetadatas != nil || updatedMetadatas != nil || deletedMetadatas != nil else { - Logger.enumeration.error( - "Received invalid newMetadatas, updatedMetadatas or deletedMetadatas. Finished enumeration of changes with error." - ) - observer.finishEnumeratingWithError(NSFileProviderError(.noSuchItem)) - return - } - - // Observer does not care about new vs updated, so join - var allUpdatedMetadatas: [NextcloudItemMetadataTable] = [] - var allDeletedMetadatas: [NextcloudItemMetadataTable] = [] - - if let newMetadatas { - allUpdatedMetadatas += newMetadatas - } - - if let updatedMetadatas { - allUpdatedMetadatas += updatedMetadatas - } - - if let deletedMetadatas { - allDeletedMetadatas = deletedMetadatas - } - - let allFpItemDeletionsIdentifiers = Array( - allDeletedMetadatas.map { NSFileProviderItemIdentifier($0.ocId) }) - if !allFpItemDeletionsIdentifiers.isEmpty { - observer.didDeleteItems(withIdentifiers: allFpItemDeletionsIdentifiers) - } - - metadatasToFileProviderItems(allUpdatedMetadatas, ncKit: ncKit) { updatedItems in - - if !updatedItems.isEmpty { - observer.didUpdate(updatedItems) - } - - Logger.enumeration.info( - "Processed \(updatedItems.count) new or updated metadatas, \(allDeletedMetadatas.count) deleted metadatas." - ) - observer.finishEnumeratingChanges(upTo: anchor, moreComing: false) - } - } -} diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension+ClientInterface.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension+ClientInterface.swift index 5a5fcecfce60e..bc66d57f4bc1d 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension+ClientInterface.swift +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension+ClientInterface.swift @@ -16,6 +16,7 @@ import FileProvider import Foundation import NCDesktopClientSocketKit import NextcloudKit +import NextcloudFileProviderKit import OSLog extension FileProviderExtension: NSFileProviderServicing { @@ -89,18 +90,22 @@ extension FileProviderExtension: NSFileProviderServicing { } @objc func setupDomainAccount(user: String, serverUrl: String, password: String) { - let newNcAccount = NextcloudAccount(user: user, serverUrl: serverUrl, password: password) + let newNcAccount = Account(user: user, serverUrl: serverUrl, password: password) guard newNcAccount != ncAccount else { return } ncAccount = newNcAccount ncKit.setup( - user: ncAccount!.username, - userId: ncAccount!.username, - password: ncAccount!.password, - urlBase: ncAccount!.serverUrl, + account: newNcAccount.ncKitAccount, + user: newNcAccount.username, + userId: newNcAccount.username, + password: newNcAccount.password, + urlBase: newNcAccount.serverUrl, userAgent: "Nextcloud-macOS/FileProviderExt", nextcloudVersion: 25, delegate: nil) // TODO: add delegate methods for self + changeObserver = RemoteChangeObserver(ncKit: ncKit, domain: domain) + ncKit.setup(delegate: changeObserver) + Logger.fileProviderExtension.info( "Nextcloud account set up in File Provider extension for user: \(user, privacy: .public) at server: \(serverUrl, privacy: .public)" ) diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension+Thumbnailing.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension+Thumbnailing.swift index daa4a24da40c4..f0d0bbe0e7899 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension+Thumbnailing.swift +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension+Thumbnailing.swift @@ -15,6 +15,7 @@ import FileProvider import Foundation import NextcloudKit +import NextcloudFileProviderKit import OSLog extension FileProviderExtension: NSFileProviderThumbnailing { @@ -28,46 +29,12 @@ extension FileProviderExtension: NSFileProviderThumbnailing { ) -> Void, completionHandler: @escaping (Error?) -> Void ) -> Progress { - let progress = Progress(totalUnitCount: Int64(itemIdentifiers.count)) - var progressCounter: Int64 = 0 - - func finishCurrent() { - progressCounter += 1 - - if progressCounter == progress.totalUnitCount { - completionHandler(nil) - } - } - - for itemIdentifier in itemIdentifiers { - Logger.fileProviderExtension.debug( - "Fetching thumbnail for item with identifier:\(itemIdentifier.rawValue, privacy: .public)" - ) - guard - let metadata = NextcloudFilesDatabaseManager.shared - .itemMetadataFromFileProviderItemIdentifier(itemIdentifier), - let thumbnailUrl = metadata.thumbnailUrl(size: size) - else { - Logger.fileProviderExtension.debug("Did not fetch thumbnail URL") - finishCurrent() - continue - } - - Logger.fileProviderExtension.debug( - "Fetching thumbnail for file:\(metadata.fileName) at:\(thumbnailUrl.absoluteString, privacy: .public)" - ) - - ncKit.getPreview(url: thumbnailUrl) { _, data, error in - if error == .success, data != nil { - perThumbnailCompletionHandler(itemIdentifier, data, nil) - } else { - perThumbnailCompletionHandler( - itemIdentifier, nil, NSFileProviderError(.serverUnreachable)) - } - finishCurrent() - } - } - - return progress + return NextcloudFileProviderKit.fetchThumbnails( + for: itemIdentifiers, + requestedSize: size, + usingKit: self.ncKit, + perThumbnailCompletionHandler: perThumbnailCompletionHandler, + completionHandler: completionHandler + ) } } diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension.swift index 2f93e8c4756d8..1223982037416 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension.swift +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderExtension.swift @@ -15,13 +15,15 @@ import FileProvider import NCDesktopClientSocketKit import NextcloudKit +import NextcloudFileProviderKit import OSLog @objc class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension, NKCommonDelegate { let domain: NSFileProviderDomain let ncKit = NextcloudKit() let appGroupIdentifier = Bundle.main.object(forInfoDictionaryKey: "SocketApiPrefix") as? String - var ncAccount: NextcloudAccount? + var ncAccount: Account? + var changeObserver: RemoteChangeObserver? lazy var ncKitBackground = NKBackground(nkCommonInstance: ncKit.nkCommonInstance) lazy var socketClient: LocalSocketClient? = { guard let containerUrl = pathForAppGroupContainer() else { @@ -84,62 +86,25 @@ import OSLog ) } - // MARK: NSFileProviderReplicatedExtension protocol methods + // MARK: - NSFileProviderReplicatedExtension protocol methods func item( - for identifier: NSFileProviderItemIdentifier, request _: NSFileProviderRequest, + for identifier: NSFileProviderItemIdentifier, + request _: NSFileProviderRequest, completionHandler: @escaping (NSFileProviderItem?, Error?) -> Void ) -> Progress { - // resolve the given identifier to a record in the model - - Logger.fileProviderExtension.debug( - "Received item request for item with identifier: \(identifier.rawValue, privacy: .public)" - ) - if identifier == .rootContainer { - guard let ncAccount else { - Logger.fileProviderExtension.error( - "Not providing item: \(identifier.rawValue, privacy: .public) as account not set up yet" - ) - completionHandler(nil, NSFileProviderError(.notAuthenticated)) - return Progress() - } - - let metadata = NextcloudItemMetadataTable() - - metadata.account = ncAccount.ncKitAccount - metadata.directory = true - metadata.ocId = NSFileProviderItemIdentifier.rootContainer.rawValue - metadata.fileName = "root" - metadata.fileNameView = "root" - metadata.serverUrl = ncAccount.serverUrl - metadata.classFile = NKCommon.TypeClassFile.directory.rawValue - - completionHandler( - FileProviderItem( - metadata: metadata, - parentItemIdentifier: NSFileProviderItemIdentifier.rootContainer, - ncKit: ncKit), nil) - return Progress() - } - - let dbManager = NextcloudFilesDatabaseManager.shared - - guard let metadata = dbManager.itemMetadataFromFileProviderItemIdentifier(identifier), - let parentItemIdentifier = dbManager.parentItemIdentifierFromMetadata(metadata) - else { + if let item = Item.storedItem(identifier: identifier, usingKit: ncKit) { + completionHandler(item, nil) + } else { completionHandler(nil, NSFileProviderError(.noSuchItem)) - return Progress() } - - completionHandler( - FileProviderItem( - metadata: metadata, parentItemIdentifier: parentItemIdentifier, ncKit: ncKit), nil) return Progress() } func fetchContents( for itemIdentifier: NSFileProviderItemIdentifier, - version requestedVersion: NSFileProviderItemVersion?, request: NSFileProviderRequest, + version requestedVersion: NSFileProviderItemVersion?, + request: NSFileProviderRequest, completionHandler: @escaping (URL?, NSFileProviderItem?, Error?) -> Void ) -> Progress { Logger.fileProviderExtension.debug( @@ -149,648 +114,206 @@ import OSLog guard requestedVersion == nil else { // TODO: Add proper support for file versioning Logger.fileProviderExtension.error( - "Can't return contents for specific version as this is not supported.") + "Can't return contents for a specific version as this is not supported." + ) completionHandler( - nil, nil, - NSError(domain: NSCocoaErrorDomain, code: NSFeatureUnsupportedError, userInfo: [:])) + nil, + nil, + NSError(domain: NSCocoaErrorDomain, code: NSFeatureUnsupportedError) + ) return Progress() } guard ncAccount != nil else { Logger.fileProviderExtension.error( - "Not fetching contents item: \(itemIdentifier.rawValue, privacy: .public) as account not set up yet" + """ + Not fetching contents for item: \(itemIdentifier.rawValue, privacy: .public) + as account not set up yet. + """ ) completionHandler(nil, nil, NSFileProviderError(.notAuthenticated)) return Progress() } - let dbManager = NextcloudFilesDatabaseManager.shared - let ocId = itemIdentifier.rawValue - guard let metadata = dbManager.itemMetadataFromOcId(ocId) else { + guard let item = Item.storedItem(identifier: itemIdentifier, usingKit: ncKit) else { Logger.fileProviderExtension.error( - "Could not acquire metadata of item with identifier: \(itemIdentifier.rawValue, privacy: .public)" + """ + Not fetching contents for item: \(itemIdentifier.rawValue, privacy: .public) + as item not found. + """ ) completionHandler(nil, nil, NSFileProviderError(.noSuchItem)) return Progress() } - guard !metadata.isDocumentViewableOnly else { - Logger.fileProviderExtension.error( - "Could not get contents of item as is readonly: \(itemIdentifier.rawValue, privacy: .public) \(metadata.fileName, privacy: .public)" - ) - completionHandler(nil, nil, NSFileProviderError(.cannotSynchronize)) - return Progress() - } - - let serverUrlFileName = metadata.serverUrl + "/" + metadata.fileName - - Logger.fileProviderExtension.debug( - "Fetching file with name \(metadata.fileName, privacy: .public) at URL: \(serverUrlFileName, privacy: .public)" - ) - let progress = Progress() - - // TODO: Handle folders nicely - do { - let fileNameLocalPath = try localPathForNCFile( - ocId: metadata.ocId, fileNameView: metadata.fileNameView, domain: domain) - - dbManager.setStatusForItemMetadata( - metadata, status: NextcloudItemMetadataTable.Status.downloading - ) { updatedMetadata in - - guard let updatedMetadata else { - Logger.fileProviderExtension.error( - "Could not acquire updated metadata of item with identifier: \(itemIdentifier.rawValue, privacy: .public), unable to update item status to downloading" - ) - completionHandler(nil, nil, NSFileProviderError(.noSuchItem)) - return - } - - self.ncKit.download( - serverUrlFileName: serverUrlFileName, - fileNameLocalPath: fileNameLocalPath.path, - requestHandler: { request in - progress.setHandlersFromAfRequest(request) - }, - taskHandler: { task in - NSFileProviderManager(for: self.domain)?.register( - task, forItemWithIdentifier: itemIdentifier, completionHandler: { _ in } - ) - }, - progressHandler: { downloadProgress in - downloadProgress.copyCurrentStateToProgress(progress) - } - ) { _, etag, date, _, _, _, error in - if error == .success { - Logger.fileTransfer.debug( - "Acquired contents of item with identifier: \(itemIdentifier.rawValue, privacy: .public) and filename: \(updatedMetadata.fileName, privacy: .public)" - ) - - updatedMetadata.status = NextcloudItemMetadataTable.Status.normal.rawValue - updatedMetadata.sessionError = "" - updatedMetadata.date = (date ?? NSDate()) as Date - updatedMetadata.etag = etag ?? "" - - dbManager.addLocalFileMetadataFromItemMetadata(updatedMetadata) - dbManager.addItemMetadata(updatedMetadata) - - guard - let parentItemIdentifier = dbManager.parentItemIdentifierFromMetadata( - updatedMetadata) - else { - completionHandler(nil, nil, NSFileProviderError(.noSuchItem)) - return - } - - let fpItem = FileProviderItem( - metadata: updatedMetadata, parentItemIdentifier: parentItemIdentifier, - ncKit: self.ncKit) - - completionHandler(fileNameLocalPath, fpItem, nil) - } else { - Logger.fileTransfer.error( - "Could not acquire contents of item with identifier: \(itemIdentifier.rawValue, privacy: .public) and fileName: \(updatedMetadata.fileName, privacy: .public)" - ) - - updatedMetadata.status = - NextcloudItemMetadataTable.Status.downloadError.rawValue - updatedMetadata.sessionError = error.errorDescription - - dbManager.addItemMetadata(updatedMetadata) - - completionHandler(nil, nil, error.fileProviderError) - } - } - } - } catch { - Logger.fileProviderExtension.error( - "Could not find local path for file \(metadata.fileName, privacy: .public), received error: \(error.localizedDescription, privacy: .public)" + Task { + let (localUrl, updatedItem, error) = await item.fetchContents( + domain: self.domain, progress: progress ) - completionHandler(nil, nil, NSFileProviderError(.cannotSynchronize)) + completionHandler(localUrl, updatedItem, error) } - return progress } func createItem( - basedOn itemTemplate: NSFileProviderItem, fields _: NSFileProviderItemFields, - contents url: URL?, options: NSFileProviderCreateItemOptions = [], + basedOn itemTemplate: NSFileProviderItem, + fields: NSFileProviderItemFields, + contents url: URL?, + options: NSFileProviderCreateItemOptions = [], request: NSFileProviderRequest, - completionHandler: @escaping (NSFileProviderItem?, NSFileProviderItemFields, Bool, Error?) - -> - Void + completionHandler: @escaping ( + NSFileProviderItem?, NSFileProviderItemFields, Bool, Error? + ) -> Void ) -> Progress { - // TODO: a new item was created on disk, process the item's creation + let tempId = itemTemplate.itemIdentifier.rawValue Logger.fileProviderExtension.debug( - "Received create item request for item with identifier: \(itemTemplate.itemIdentifier.rawValue, privacy: .public) and filename: \(itemTemplate.filename, privacy: .public)" + """ + Received create item request for item with identifier: \(tempId, privacy: .public) + and filename: \(itemTemplate.filename, privacy: .public) + """ ) - guard itemTemplate.contentType != .symbolicLink else { - Logger.fileProviderExtension.error("Cannot create item, symbolic links not supported.") - completionHandler( - itemTemplate, NSFileProviderItemFields(), false, - NSError(domain: NSCocoaErrorDomain, code: NSFeatureUnsupportedError, userInfo: [:])) - return Progress() - } - guard let ncAccount else { Logger.fileProviderExtension.error( - "Not creating item: \(itemTemplate.itemIdentifier.rawValue, privacy: .public) as account not set up yet" + """ + Not creating item: \(itemTemplate.itemIdentifier.rawValue, privacy: .public) + as account not set up yet + """ ) completionHandler( - itemTemplate, NSFileProviderItemFields(), false, - NSFileProviderError(.notAuthenticated)) - return Progress() - } - - let dbManager = NextcloudFilesDatabaseManager.shared - let parentItemIdentifier = itemTemplate.parentItemIdentifier - let itemTemplateIsFolder = - itemTemplate.contentType == .folder || itemTemplate.contentType == .directory - - if options.contains(.mayAlreadyExist) { - // TODO: This needs to be properly handled with a check in the db - Logger.fileProviderExtension.info( - "Not creating item: \(itemTemplate.itemIdentifier.rawValue, privacy: .public) as it may already exist" + itemTemplate, + NSFileProviderItemFields(), + false, + NSFileProviderError(.notAuthenticated) ) - completionHandler( - itemTemplate, NSFileProviderItemFields(), false, NSFileProviderError(.noSuchItem)) - return Progress() - } - - var parentItemServerUrl: String - - if parentItemIdentifier == .rootContainer { - parentItemServerUrl = ncAccount.davFilesUrl - } else { - guard - let parentItemMetadata = dbManager.directoryMetadata( - ocId: parentItemIdentifier.rawValue) - else { - Logger.fileProviderExtension.error( - "Not creating item: \(itemTemplate.itemIdentifier.rawValue, privacy: .public), could not find metadata for parentItemIdentifier \(parentItemIdentifier.rawValue, privacy: .public)" - ) - completionHandler( - itemTemplate, NSFileProviderItemFields(), false, - NSFileProviderError(.noSuchItem)) - return Progress() - } - - parentItemServerUrl = parentItemMetadata.serverUrl + "/" + parentItemMetadata.fileName - } - - let fileNameLocalPath = url?.path ?? "" - let newServerUrlFileName = parentItemServerUrl + "/" + itemTemplate.filename - - Logger.fileProviderExtension.debug( - "About to upload item with identifier: \(itemTemplate.itemIdentifier.rawValue, privacy: .public) of type: \(itemTemplate.contentType?.identifier ?? "UNKNOWN") (is folder: \(itemTemplateIsFolder ? "yes" : "no") and filename: \(itemTemplate.filename) to server url: \(newServerUrlFileName, privacy: .public) with contents located at: \(fileNameLocalPath, privacy: .public)" - ) - - if itemTemplateIsFolder { - ncKit.createFolder(serverUrlFileName: newServerUrlFileName) { account, _, _, error in - guard error == .success else { - Logger.fileTransfer.error( - "Could not create new folder with name: \(itemTemplate.filename, privacy: .public), received error: \(error.errorDescription, privacy: .public)" - ) - completionHandler(itemTemplate, [], false, error.fileProviderError) - return - } - - // Read contents after creation - self.ncKit.readFileOrFolder( - serverUrlFileName: newServerUrlFileName, depth: "0", showHiddenFiles: true - ) { account, files, _, error in - guard error == .success else { - Logger.fileTransfer.error( - "Could not read new folder with name: \(itemTemplate.filename, privacy: .public), received error: \(error.errorDescription, privacy: .public)" - ) - return - } - - DispatchQueue.global().async { - NextcloudItemMetadataTable.metadatasFromDirectoryReadNKFiles( - files, account: account - ) { - directoryMetadata, _, _ in - - dbManager.addItemMetadata(directoryMetadata) - - let fpItem = FileProviderItem( - metadata: directoryMetadata, - parentItemIdentifier: parentItemIdentifier, - ncKit: self.ncKit) - - completionHandler(fpItem, [], true, nil) - } - } - } - } - return Progress() } let progress = Progress() - - ncKit.upload( - serverUrlFileName: newServerUrlFileName, - fileNameLocalPath: fileNameLocalPath, - requestHandler: { request in - progress.setHandlersFromAfRequest(request) - }, - taskHandler: { task in - NSFileProviderManager(for: self.domain)?.register( - task, forItemWithIdentifier: itemTemplate.itemIdentifier, - completionHandler: { _ in }) - }, - progressHandler: { uploadProgress in - uploadProgress.copyCurrentStateToProgress(progress) - } - ) { account, ocId, etag, date, size, _, _, error in - guard error == .success, let ocId else { - Logger.fileTransfer.error( - "Could not upload item with filename: \(itemTemplate.filename, privacy: .public), received error: \(error.errorDescription, privacy: .public)" - ) - completionHandler(itemTemplate, [], false, error.fileProviderError) - return - } - - Logger.fileTransfer.info( - "Successfully uploaded item with identifier: \(ocId, privacy: .public) and filename: \(itemTemplate.filename, privacy: .public)" + Task { + let (item, error) = await Item.create( + basedOn: itemTemplate, + fields: fields, + contents: url, + request: request, + domain: self.domain, + ncKit: ncKit, + ncAccount: ncAccount, + progress: progress ) - - if size != itemTemplate.documentSize as? Int64 { - Logger.fileTransfer.warning( - "Created item upload reported as successful, but there are differences between the received file size (\(size, privacy: .public)) and the original file size (\(itemTemplate.documentSize??.int64Value ?? 0))" - ) + if error != nil { + signalEnumerator(completionHandler: { _ in }) } - - let newMetadata = NextcloudItemMetadataTable() - newMetadata.date = (date ?? NSDate()) as Date - newMetadata.etag = etag ?? "" - newMetadata.account = account - newMetadata.fileName = itemTemplate.filename - newMetadata.fileNameView = itemTemplate.filename - newMetadata.ocId = ocId - newMetadata.size = size - newMetadata.contentType = itemTemplate.contentType?.preferredMIMEType ?? "" - newMetadata.directory = itemTemplateIsFolder - newMetadata.serverUrl = parentItemServerUrl - newMetadata.session = "" - newMetadata.sessionError = "" - newMetadata.sessionTaskIdentifier = 0 - newMetadata.status = NextcloudItemMetadataTable.Status.normal.rawValue - - dbManager.addLocalFileMetadataFromItemMetadata(newMetadata) - dbManager.addItemMetadata(newMetadata) - - let fpItem = FileProviderItem( - metadata: newMetadata, parentItemIdentifier: parentItemIdentifier, ncKit: self.ncKit + completionHandler( + item ?? itemTemplate, + NSFileProviderItemFields(), + false, + error ) - - completionHandler(fpItem, [], false, nil) } - return progress } func modifyItem( - _ item: NSFileProviderItem, baseVersion _: NSFileProviderItemVersion, - changedFields: NSFileProviderItemFields, contents newContents: URL?, - options: NSFileProviderModifyItemOptions = [], request: NSFileProviderRequest, - completionHandler: @escaping (NSFileProviderItem?, NSFileProviderItemFields, Bool, Error?) - -> - Void + _ item: NSFileProviderItem, + baseVersion: NSFileProviderItemVersion, + changedFields: NSFileProviderItemFields, + contents newContents: URL?, + options: NSFileProviderModifyItemOptions = [], + request: NSFileProviderRequest, + completionHandler: @escaping ( + NSFileProviderItem?, NSFileProviderItemFields, Bool, Error? + ) -> Void ) -> Progress { // An item was modified on disk, process the item's modification // TODO: Handle finder things like tags, other possible item changed fields + let identifier = item.itemIdentifier + let ocId = identifier.rawValue Logger.fileProviderExtension.debug( - "Received modify item request for item with identifier: \(item.itemIdentifier.rawValue, privacy: .public) and filename: \(item.filename, privacy: .public)" + """ + Received modify item request for item with identifier: \(ocId, privacy: .public) + and filename: \(item.filename, privacy: .public) + """ ) guard let ncAccount else { Logger.fileProviderExtension.error( - "Not modifying item: \(item.itemIdentifier.rawValue, privacy: .public) as account not set up yet" + "Not modifying item: \(ocId, privacy: .public) as account not set up yet." ) completionHandler(item, [], false, NSFileProviderError(.notAuthenticated)) return Progress() } - let dbManager = NextcloudFilesDatabaseManager.shared - let parentItemIdentifier = item.parentItemIdentifier - let itemTemplateIsFolder = item.contentType == .folder || item.contentType == .directory - - if options.contains(.mayAlreadyExist) { - // TODO: This needs to be properly handled with a check in the db - Logger.fileProviderExtension.warning( - "Modification for item: \(item.itemIdentifier.rawValue, privacy: .public) may already exist" - ) - } - - var parentItemServerUrl: String - - if parentItemIdentifier == .rootContainer { - parentItemServerUrl = ncAccount.davFilesUrl - } else { - guard - let parentItemMetadata = dbManager.directoryMetadata( - ocId: parentItemIdentifier.rawValue) - else { - Logger.fileProviderExtension.error( - "Not modifying item: \(item.itemIdentifier.rawValue, privacy: .public), could not find metadata for parentItemIdentifier \(parentItemIdentifier.rawValue, privacy: .public)" - ) - completionHandler(item, [], false, NSFileProviderError(.noSuchItem)) - return Progress() - } - - parentItemServerUrl = parentItemMetadata.serverUrl + "/" + parentItemMetadata.fileName - } - - let fileNameLocalPath = newContents?.path ?? "" - let newServerUrlFileName = parentItemServerUrl + "/" + item.filename - - Logger.fileProviderExtension.debug( - "About to upload modified item with identifier: \(item.itemIdentifier.rawValue, privacy: .public) of type: \(item.contentType?.identifier ?? "UNKNOWN") (is folder: \(itemTemplateIsFolder ? "yes" : "no") and filename: \(item.filename, privacy: .public) to server url: \(newServerUrlFileName, privacy: .public) with contents located at: \(fileNameLocalPath, privacy: .public)" - ) - - var modifiedItem = item - - // Create a serial dispatch queue - // We want to wait for network operations to finish before we fire off subsequent network - // operations, or we might cause explosions (e.g. trying to modify items that have just been - // moved elsewhere) - let dispatchQueue = DispatchQueue(label: "modifyItemQueue", qos: .userInitiated) - - if changedFields.contains(.filename) || changedFields.contains(.parentItemIdentifier) { - dispatchQueue.async { - let ocId = item.itemIdentifier.rawValue - Logger.fileProviderExtension.debug( - "Changed fields for item \(ocId, privacy: .public) with filename \(item.filename, privacy: .public) includes filename or parentitemidentifier..." - ) - - guard let metadata = dbManager.itemMetadataFromOcId(ocId) else { - Logger.fileProviderExtension.error( - "Could not acquire metadata of item with identifier: \(item.itemIdentifier.rawValue, privacy: .public)" - ) - completionHandler(item, [], false, NSFileProviderError(.noSuchItem)) - return - } - - var renameError: NSFileProviderError? - let oldServerUrlFileName = metadata.serverUrl + "/" + metadata.fileName - - let moveFileOrFolderDispatchGroup = DispatchGroup() // Make this block wait until done - moveFileOrFolderDispatchGroup.enter() - - self.ncKit.moveFileOrFolder( - serverUrlFileNameSource: oldServerUrlFileName, - serverUrlFileNameDestination: newServerUrlFileName, - overwrite: false - ) { _, error in - guard error == .success else { - Logger.fileTransfer.error( - "Could not move file or folder: \(oldServerUrlFileName, privacy: .public) to \(newServerUrlFileName, privacy: .public), received error: \(error.errorDescription, privacy: .public)" - ) - renameError = error.fileProviderError - moveFileOrFolderDispatchGroup.leave() - return - } - - // Remember that a folder metadata's serverUrl is its direct server URL, while for - // an item metadata the server URL is the parent folder's URL - if itemTemplateIsFolder { - _ = dbManager.renameDirectoryAndPropagateToChildren( - ocId: ocId, newServerUrl: newServerUrlFileName, - newFileName: item.filename) - self.signalEnumerator { error in - if error != nil { - Logger.fileTransfer.error( - "Error notifying change in moved directory: \(error)") - } - } - } else { - dbManager.renameItemMetadata( - ocId: ocId, newServerUrl: parentItemServerUrl, - newFileName: item.filename) - } - - guard let newMetadata = dbManager.itemMetadataFromOcId(ocId) else { - Logger.fileTransfer.error( - "Could not acquire metadata of item with identifier: \(ocId, privacy: .public), cannot correctly inform of modification" - ) - renameError = NSFileProviderError(.noSuchItem) - moveFileOrFolderDispatchGroup.leave() - return - } - - modifiedItem = FileProviderItem( - metadata: newMetadata, parentItemIdentifier: parentItemIdentifier, - ncKit: self.ncKit) - moveFileOrFolderDispatchGroup.leave() - } - - moveFileOrFolderDispatchGroup.wait() - - guard renameError == nil else { - Logger.fileTransfer.error( - "Stopping rename of item with ocId \(ocId, privacy: .public) due to error: \(renameError!.localizedDescription, privacy: .public)" - ) - completionHandler(modifiedItem, [], false, renameError) - return - } - - guard !itemTemplateIsFolder else { - Logger.fileTransfer.debug( - "Only handling renaming for folders. ocId: \(ocId, privacy: .public)") - completionHandler(modifiedItem, [], false, nil) - return - } - } - - // Return the progress if item is folder here while the async block runs - guard !itemTemplateIsFolder else { - return Progress() - } - } - - guard !itemTemplateIsFolder else { - Logger.fileTransfer.debug( - "System requested modification for folder with ocID \(item.itemIdentifier.rawValue, privacy: .public) (\(newServerUrlFileName, privacy: .public)) of something other than folder name." + guard let existingItem = Item.storedItem(identifier: identifier, usingKit: ncKit) else { + Logger.fileProviderExtension.error( + "Not modifying item: \(ocId, privacy: .public) as item not found." ) - completionHandler(modifiedItem, [], false, nil) + completionHandler(item, [], false, NSFileProviderError(.noSuchItem)) return Progress() } let progress = Progress() - - if changedFields.contains(.contents) { - dispatchQueue.async { - Logger.fileProviderExtension.debug( - "Item modification for \(item.itemIdentifier.rawValue, privacy: .public) \(item.filename, privacy: .public) includes contents" - ) - - guard newContents != nil else { - Logger.fileProviderExtension.warning( - "WARNING. Could not upload modified contents as was provided nil contents url. ocId: \(item.itemIdentifier.rawValue, privacy: .public)" - ) - completionHandler(modifiedItem, [], false, NSFileProviderError(.noSuchItem)) - return - } - - let ocId = item.itemIdentifier.rawValue - guard let metadata = dbManager.itemMetadataFromOcId(ocId) else { - Logger.fileProviderExtension.error( - "Could not acquire metadata of item with identifier: \(ocId, privacy: .public)" - ) - completionHandler( - item, NSFileProviderItemFields(), false, NSFileProviderError(.noSuchItem)) - return - } - - dbManager.setStatusForItemMetadata( - metadata, status: NextcloudItemMetadataTable.Status.uploading - ) { updatedMetadata in - - if updatedMetadata == nil { - Logger.fileProviderExtension.warning( - "Could not acquire updated metadata of item with identifier: \(ocId, privacy: .public), unable to update item status to uploading" - ) - } - - self.ncKit.upload( - serverUrlFileName: newServerUrlFileName, - fileNameLocalPath: fileNameLocalPath, - requestHandler: { request in - progress.setHandlersFromAfRequest(request) - }, - taskHandler: { task in - NSFileProviderManager(for: self.domain)?.register( - task, forItemWithIdentifier: item.itemIdentifier, - completionHandler: { _ in }) - }, - progressHandler: { uploadProgress in - uploadProgress.copyCurrentStateToProgress(progress) - } - ) { account, ocId, etag, date, size, _, _, error in - if error == .success, let ocId { - Logger.fileProviderExtension.info( - "Successfully uploaded item with identifier: \(ocId, privacy: .public) and filename: \(item.filename, privacy: .public)" - ) - - if size != item.documentSize as? Int64 { - Logger.fileTransfer.warning( - "Created item upload reported as successful, but there are differences between the received file size (\(size, privacy: .public)) and the original file size (\(item.documentSize??.int64Value ?? 0))" - ) - } - - let newMetadata = NextcloudItemMetadataTable() - newMetadata.date = (date ?? NSDate()) as Date - newMetadata.etag = etag ?? "" - newMetadata.account = account - newMetadata.fileName = item.filename - newMetadata.fileNameView = item.filename - newMetadata.ocId = ocId - newMetadata.size = size - newMetadata.contentType = item.contentType?.preferredMIMEType ?? "" - newMetadata.directory = itemTemplateIsFolder - newMetadata.serverUrl = parentItemServerUrl - newMetadata.session = "" - newMetadata.sessionError = "" - newMetadata.sessionTaskIdentifier = 0 - newMetadata.status = NextcloudItemMetadataTable.Status.normal.rawValue - - dbManager.addLocalFileMetadataFromItemMetadata(newMetadata) - dbManager.addItemMetadata(newMetadata) - - modifiedItem = FileProviderItem( - metadata: newMetadata, parentItemIdentifier: parentItemIdentifier, - ncKit: self.ncKit - ) - completionHandler(modifiedItem, [], false, nil) - } else { - Logger.fileTransfer.error( - "Could not upload item \(item.itemIdentifier.rawValue, privacy: .public) with filename: \(item.filename, privacy: .public), received error: \(error.errorDescription, privacy: .public)" - ) - - metadata.status = NextcloudItemMetadataTable.Status.uploadError.rawValue - metadata.sessionError = error.errorDescription - - dbManager.addItemMetadata(metadata) - - completionHandler(modifiedItem, [], false, error.fileProviderError) - return - } - } - } - } - } else { - Logger.fileProviderExtension.debug( - "Nothing more to do with \(item.itemIdentifier.rawValue, privacy: .public) \(item.filename, privacy: .public), modifications complete" + Task { + let (modifiedItem, error) = await existingItem.modify( + itemTarget: item, + baseVersion: baseVersion, + changedFields: changedFields, + contents: newContents, + options: options, + request: request, + ncAccount: ncAccount, + domain: domain, + progress: progress ) - completionHandler(modifiedItem, [], false, nil) + if error != nil { + signalEnumerator(completionHandler: { _ in }) + } + completionHandler(modifiedItem ?? item, [], false, error) } - return progress } func deleteItem( - identifier: NSFileProviderItemIdentifier, baseVersion _: NSFileProviderItemVersion, - options _: NSFileProviderDeleteItemOptions = [], request _: NSFileProviderRequest, + identifier: NSFileProviderItemIdentifier, + baseVersion _: NSFileProviderItemVersion, + options _: NSFileProviderDeleteItemOptions = [], + request _: NSFileProviderRequest, completionHandler: @escaping (Error?) -> Void ) -> Progress { Logger.fileProviderExtension.debug( - "Received delete item request for item with identifier: \(identifier.rawValue, privacy: .public)" + "Received delete request for item: \(identifier.rawValue, privacy: .public)" ) guard ncAccount != nil else { Logger.fileProviderExtension.error( - "Not deleting item: \(identifier.rawValue, privacy: .public) as account not set up yet" + "Not deleting item \(identifier.rawValue, privacy: .public), account not set up yet" ) completionHandler(NSFileProviderError(.notAuthenticated)) return Progress() } - let dbManager = NextcloudFilesDatabaseManager.shared - let ocId = identifier.rawValue - guard let itemMetadata = dbManager.itemMetadataFromOcId(ocId) else { - completionHandler(NSFileProviderError(.noSuchItem)) - return Progress() - } - let serverFileNameUrl = itemMetadata.serverUrl + "/" + itemMetadata.fileName - guard serverFileNameUrl != "" else { + guard let item = Item.storedItem(identifier: identifier, usingKit: ncKit) else { + Logger.fileProviderExtension.error( + "Not deleting item \(identifier.rawValue, privacy: .public), item not found" + ) completionHandler(NSFileProviderError(.noSuchItem)) return Progress() } - ncKit.deleteFileOrFolder(serverUrlFileName: serverFileNameUrl) { _, error in - guard error == .success else { - Logger.fileTransfer.error( - "Could not delete item with ocId \(identifier.rawValue, privacy: .public) at \(serverFileNameUrl, privacy: .public), received error: \(error.errorDescription, privacy: .public)" - ) - completionHandler(error.fileProviderError) - return + let progress = Progress(totalUnitCount: 1) + Task { + let error = await item.delete() + if error != nil { + signalEnumerator(completionHandler: { _ in }) } - - Logger.fileTransfer.info( - "Successfully deleted item with identifier: \(identifier.rawValue, privacy: .public) at: \(serverFileNameUrl, privacy: .public)" - ) - - if itemMetadata.directory { - _ = dbManager.deleteDirectoryAndSubdirectoriesMetadata(ocId: ocId) - } else { - dbManager.deleteItemMetadata(ocId: ocId) - if dbManager.localFileMetadataFromOcId(ocId) != nil { - dbManager.deleteLocalFileMetadata(ocId: ocId) - } - } - - completionHandler(nil) + progress.completedUnitCount = 1 + completionHandler(await item.delete()) } - - return Progress() + return progress } func enumerator( @@ -803,10 +326,11 @@ import OSLog throw NSFileProviderError(.notAuthenticated) } - return FileProviderEnumerator( + return Enumerator( enumeratedItemIdentifier: containerItemIdentifier, ncAccount: ncAccount, ncKit: ncKit, + domain: domain, fastEnumeration: config.fastEnumerationEnabled ) } @@ -828,7 +352,7 @@ import OSLog } let materialisedEnumerator = fpManager.enumeratorForMaterializedItems() - let materialisedObserver = FileProviderMaterialisedEnumerationObserver( + let materialisedObserver = MaterialisedEnumerationObserver( ncKitAccount: ncAccount.ncKitAccount ) { _ in completionHandler() diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderItem.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderItem.swift deleted file mode 100644 index eea2b950c572b..0000000000000 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderItem.swift +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright (C) 2022 by Claudio Cambra - * - * 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 2 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. - */ - -import FileProvider -import NextcloudKit -import UniformTypeIdentifiers - -class FileProviderItem: NSObject, NSFileProviderItem { - enum FileProviderItemTransferError: Error { - case downloadError - case uploadError - } - - let metadata: NextcloudItemMetadataTable - let parentItemIdentifier: NSFileProviderItemIdentifier - let ncKit: NextcloudKit - - var itemIdentifier: NSFileProviderItemIdentifier { - NSFileProviderItemIdentifier(metadata.ocId) - } - - var capabilities: NSFileProviderItemCapabilities { - guard !metadata.directory else { - if #available(macOS 13.0, *) { - // .allowsEvicting deprecated on macOS 13.0+, use contentPolicy instead - return [ - .allowsAddingSubItems, - .allowsContentEnumerating, - .allowsReading, - .allowsDeleting, - .allowsRenaming - ] - } else { - return [ - .allowsAddingSubItems, - .allowsContentEnumerating, - .allowsReading, - .allowsDeleting, - .allowsRenaming, - .allowsEvicting - ] - } - } - guard !metadata.lock else { - return [.allowsReading] - } - return [ - .allowsWriting, - .allowsReading, - .allowsDeleting, - .allowsRenaming, - .allowsReparenting, - .allowsEvicting - ] - } - - var itemVersion: NSFileProviderItemVersion { - NSFileProviderItemVersion( - contentVersion: metadata.etag.data(using: .utf8)!, - metadataVersion: metadata.etag.data(using: .utf8)!) - } - - var filename: String { - metadata.fileNameView - } - - var contentType: UTType { - if itemIdentifier == .rootContainer || metadata.directory { - return .folder - } - - let internalType = ncKit.nkCommonInstance.getInternalType( - fileName: metadata.fileNameView, - mimeType: "", - directory: metadata.directory) - return UTType(filenameExtension: internalType.ext) ?? .content - } - - var documentSize: NSNumber? { - NSNumber(value: metadata.size) - } - - var creationDate: Date? { - metadata.creationDate as Date - } - - var lastUsedDate: Date? { - metadata.date as Date - } - - var contentModificationDate: Date? { - metadata.date as Date - } - - var isDownloaded: Bool { - metadata.directory - || NextcloudFilesDatabaseManager.shared.localFileMetadataFromOcId(metadata.ocId) != nil - } - - var isDownloading: Bool { - metadata.status == NextcloudItemMetadataTable.Status.downloading.rawValue - } - - var downloadingError: Error? { - if metadata.status == NextcloudItemMetadataTable.Status.downloadError.rawValue { - return FileProviderItemTransferError.downloadError - } - return nil - } - - var isUploaded: Bool { - NextcloudFilesDatabaseManager.shared.localFileMetadataFromOcId(metadata.ocId) != nil - } - - var isUploading: Bool { - metadata.status == NextcloudItemMetadataTable.Status.uploading.rawValue - } - - var uploadingError: Error? { - if metadata.status == NextcloudItemMetadataTable.Status.uploadError.rawValue { - FileProviderItemTransferError.uploadError - } else { - nil - } - } - - var childItemCount: NSNumber? { - if metadata.directory { - NSNumber( - integerLiteral: NextcloudFilesDatabaseManager.shared.childItemsForDirectory( - metadata - ).count) - } else { - nil - } - } - - @available(macOSApplicationExtension 13.0, *) - var contentPolicy: NSFileProviderContentPolicy { - .downloadLazily - } - - required init( - metadata: NextcloudItemMetadataTable, - parentItemIdentifier: NSFileProviderItemIdentifier, - ncKit: NextcloudKit - ) { - self.metadata = metadata - self.parentItemIdentifier = parentItemIdentifier - self.ncKit = ncKit - super.init() - } -} diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderMaterialisedEnumerationObserver.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderMaterialisedEnumerationObserver.swift index e68c3fd8becd9..42582832ed6ac 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderMaterialisedEnumerationObserver.swift +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/FileProviderMaterialisedEnumerationObserver.swift @@ -14,6 +14,7 @@ import FileProvider import Foundation +import NextcloudFileProviderKit import OSLog class FileProviderMaterialisedEnumerationObserver: NSObject, NSFileProviderEnumerationObserver { @@ -59,7 +60,7 @@ class FileProviderMaterialisedEnumerationObserver: NSObject, NSFileProviderEnume _ itemIds: Set, account: String, completionHandler: @escaping (_ deletedOcIds: Set) -> Void ) { - let dbManager = NextcloudFilesDatabaseManager.shared + let dbManager = FilesDatabaseManager.shared let databaseLocalFileMetadatas = dbManager.localFileMetadatas(account: account) var noLongerMaterialisedIds = Set() diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/LocalFilesUtils.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/LocalFilesUtils.swift deleted file mode 100644 index d1465276a6d07..0000000000000 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/LocalFilesUtils.swift +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (C) 2023 by Claudio Cambra - * - * 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 2 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. - */ - -import FileProvider -import Foundation -import OSLog - -func pathForAppGroupContainer() -> URL? { - guard - let appGroupIdentifier = Bundle.main.object(forInfoDictionaryKey: "SocketApiPrefix") - as? String - else { - Logger.localFileOps.critical( - "Could not get container url as missing SocketApiPrefix info in app Info.plist") - return nil - } - - return FileManager.default.containerURL( - forSecurityApplicationGroupIdentifier: appGroupIdentifier) -} - -func pathForFileProviderExtData() -> URL? { - let containerUrl = pathForAppGroupContainer() - return containerUrl?.appendingPathComponent("FileProviderExt/") -} - -func pathForFileProviderTempFilesForDomain(_ domain: NSFileProviderDomain) throws -> URL? { - guard let fpManager = NSFileProviderManager(for: domain) else { - Logger.localFileOps.error( - "Unable to get file provider manager for domain: \(domain.displayName, privacy: .public)" - ) - throw NSFileProviderError(.providerNotFound) - } - - let fileProviderDataUrl = try fpManager.temporaryDirectoryURL() - return fileProviderDataUrl.appendingPathComponent("TemporaryNextcloudFiles/") -} - -func localPathForNCFile(ocId _: String, fileNameView: String, domain: NSFileProviderDomain) throws - -> URL -{ - guard let fileProviderFilesPathUrl = try pathForFileProviderTempFilesForDomain(domain) else { - Logger.localFileOps.error( - "Unable to get path for file provider temp files for domain: \(domain.displayName, privacy: .public)" - ) - throw URLError(.badURL) - } - - let filePathUrl = fileProviderFilesPathUrl.appendingPathComponent(fileNameView) - let filePath = filePathUrl.path - - if !FileManager.default.fileExists(atPath: filePath) { - FileManager.default.createFile(atPath: filePath, contents: nil) - } - - return filePathUrl -} diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/NextcloudAccount.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/NextcloudAccount.swift deleted file mode 100644 index 4af76fa4fdf91..0000000000000 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/NextcloudAccount.swift +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2022 by Claudio Cambra - * - * 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 2 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. - */ - -import FileProvider -import Foundation - -let ncAccountDictUsernameKey = "usernameKey" -let ncAccountDictPasswordKey = "passwordKey" -let ncAccountDictNcKitAccountKey = "ncKitAccountKey" -let ncAccountDictServerUrlKey = "serverUrlKey" -let ncAccountDictDavFilesUrlKey = "davFilesUrlKey" - -struct NextcloudAccount: Equatable { - static let webDavFilesUrlSuffix: String = "/remote.php/dav/files/" - let username, password, ncKitAccount, serverUrl, davFilesUrl: String - - init(user: String, serverUrl: String, password: String) { - username = user - self.password = password - ncKitAccount = user + " " + serverUrl - self.serverUrl = serverUrl - davFilesUrl = serverUrl + NextcloudAccount.webDavFilesUrlSuffix + user - } - - init?(dictionary: Dictionary) { - guard let username = dictionary[ncAccountDictUsernameKey], - let password = dictionary[ncAccountDictPasswordKey], - let ncKitAccount = dictionary[ncAccountDictNcKitAccountKey], - let serverUrl = dictionary[ncAccountDictServerUrlKey], - let davFilesUrl = dictionary[ncAccountDictDavFilesUrlKey] - else { - return nil - } - - self.username = username - self.password = password - self.ncKitAccount = ncKitAccount - self.serverUrl = serverUrl - self.davFilesUrl = davFilesUrl - } - - func dictionary() -> Dictionary { - return [ - ncAccountDictUsernameKey: username, - ncAccountDictPasswordKey: password, - ncAccountDictNcKitAccountKey: ncKitAccount, - ncAccountDictServerUrlKey: serverUrl, - ncAccountDictDavFilesUrlKey: davFilesUrl - ] - } -} diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Services/FPUIExtensionServiceSource.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Services/FPUIExtensionServiceSource.swift index 76f7f10f4e48d..5b8fd45751ade 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Services/FPUIExtensionServiceSource.swift +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Services/FPUIExtensionServiceSource.swift @@ -8,6 +8,7 @@ import FileProvider import Foundation import NextcloudKit +import NextcloudFileProviderKit import OSLog class FPUIExtensionServiceSource: NSObject, NSFileProviderServiceSource, NSXPCListenerDelegate, FPUIExtensionService { @@ -52,7 +53,7 @@ class FPUIExtensionServiceSource: NSObject, NSFileProviderServiceSource, NSXPCLi return nil } - let dbManager = NextcloudFilesDatabaseManager.shared + let dbManager = FilesDatabaseManager.shared guard let item = dbManager.itemMetadataFromFileProviderItemIdentifier(identifier) else { Logger.shares.error("No item \(rawIdentifier, privacy: .public) in db, no shares.") return nil diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderUIExt/ShareTableViewDataSource.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderUIExt/ShareTableViewDataSource.swift index 1200499ab5ec0..70aeb8c50a7c0 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderUIExt/ShareTableViewDataSource.swift +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderUIExt/ShareTableViewDataSource.swift @@ -8,6 +8,7 @@ import AppKit import FileProvider import NextcloudKit +import NextcloudFileProviderKit import NextcloudCapabilitiesKit import OSLog @@ -35,7 +36,7 @@ class ShareTableViewDataSource: NSObject, NSTableViewDataSource, NSTableViewDele private(set) var shares: [NKShare] = [] { didSet { Task { @MainActor in sharesTableView?.reloadData() } } } - private var account: NextcloudAccount? { + private var account: Account? { didSet { guard let account = account else { return } kit = NextcloudKit() @@ -86,7 +87,7 @@ class ShareTableViewDataSource: NSObject, NSTableViewDataSource, NSTableViewDele let connection = try await serviceConnection(url: itemURL) guard let serverPath = await connection.itemServerPath(identifier: itemIdentifier), let credentials = await connection.credentials() as? Dictionary, - let convertedAccount = NextcloudAccount(dictionary: credentials), + let convertedAccount = Account(dictionary: credentials), !convertedAccount.password.isEmpty else { presentError("Failed to get details from File Provider Extension. Retrying.") diff --git a/shell_integration/MacOSX/NextcloudIntegration/NextcloudIntegration.xcodeproj/project.pbxproj b/shell_integration/MacOSX/NextcloudIntegration/NextcloudIntegration.xcodeproj/project.pbxproj index e830d9ac877dc..cd05dd8ef676e 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/NextcloudIntegration.xcodeproj/project.pbxproj +++ b/shell_integration/MacOSX/NextcloudIntegration/NextcloudIntegration.xcodeproj/project.pbxproj @@ -9,24 +9,14 @@ /* Begin PBXBuildFile section */ 5307A6E62965C6FA001E0C6A /* NextcloudKit in Frameworks */ = {isa = PBXBuildFile; productRef = 5307A6E52965C6FA001E0C6A /* NextcloudKit */; }; 5307A6E82965DAD8001E0C6A /* NextcloudKit in Frameworks */ = {isa = PBXBuildFile; productRef = 5307A6E72965DAD8001E0C6A /* NextcloudKit */; }; - 5307A6EB2965DB8D001E0C6A /* RealmSwift in Frameworks */ = {isa = PBXBuildFile; productRef = 5307A6EA2965DB8D001E0C6A /* RealmSwift */; }; - 5307A6F229675346001E0C6A /* NextcloudFilesDatabaseManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5307A6F129675346001E0C6A /* NextcloudFilesDatabaseManager.swift */; }; 531522822B8E01C6002E31BE /* ShareTableItemView.xib in Resources */ = {isa = PBXBuildFile; fileRef = 531522812B8E01C6002E31BE /* ShareTableItemView.xib */; }; - 5318AD9129BF42FB00CBB71C /* NextcloudItemMetadataTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5318AD9029BF42FB00CBB71C /* NextcloudItemMetadataTable.swift */; }; - 5318AD9529BF438F00CBB71C /* NextcloudLocalFileMetadataTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5318AD9429BF438F00CBB71C /* NextcloudLocalFileMetadataTable.swift */; }; - 5318AD9729BF493600CBB71C /* FileProviderMaterialisedEnumerationObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5318AD9629BF493600CBB71C /* FileProviderMaterialisedEnumerationObserver.swift */; }; - 5318AD9929BF58D000CBB71C /* NKError+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5318AD9829BF58D000CBB71C /* NKError+Extensions.swift */; }; 5350E4E92B0C534A00F276CB /* ClientCommunicationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5350E4E82B0C534A00F276CB /* ClientCommunicationService.swift */; }; - 5352B36629DC14970011CE03 /* NextcloudFilesDatabaseManager+Directories.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5352B36529DC14970011CE03 /* NextcloudFilesDatabaseManager+Directories.swift */; }; - 5352B36829DC17D60011CE03 /* NextcloudFilesDatabaseManager+LocalFiles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5352B36729DC17D60011CE03 /* NextcloudFilesDatabaseManager+LocalFiles.swift */; }; 5352B36C29DC44B50011CE03 /* FileProviderExtension+Thumbnailing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5352B36B29DC44B50011CE03 /* FileProviderExtension+Thumbnailing.swift */; }; - 5352E85B29B7BFE6002CE85C /* Progress+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5352E85A29B7BFE6002CE85C /* Progress+Extensions.swift */; }; 5358F2B92BAA0F5300E3C729 /* NextcloudCapabilitiesKit in Frameworks */ = {isa = PBXBuildFile; productRef = 5358F2B82BAA0F5300E3C729 /* NextcloudCapabilitiesKit */; }; 535AE30E29C0A2CC0042A9BA /* Logger+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 535AE30D29C0A2CC0042A9BA /* Logger+Extensions.swift */; }; 53651E442BBC0CA300ECAC29 /* SuggestionsTextFieldKit in Frameworks */ = {isa = PBXBuildFile; productRef = 53651E432BBC0CA300ECAC29 /* SuggestionsTextFieldKit */; }; 53651E462BBC0D9500ECAC29 /* ShareeSuggestionsDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53651E452BBC0D9500ECAC29 /* ShareeSuggestionsDataSource.swift */; }; 536EFBF7295CF58100F4CB13 /* FileProviderSocketLineProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 536EFBF6295CF58100F4CB13 /* FileProviderSocketLineProcessor.swift */; }; - 536EFC36295E3C1100F4CB13 /* NextcloudAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = 536EFC35295E3C1100F4CB13 /* NextcloudAccount.swift */; }; 5374FD442B95EE1400C78D54 /* ShareController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5374FD432B95EE1400C78D54 /* ShareController.swift */; }; 5376307D2B85E2ED0026BFAB /* Logger+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5376307C2B85E2ED0026BFAB /* Logger+Extensions.swift */; }; 537630912B85F4980026BFAB /* ShareViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 537630902B85F4980026BFAB /* ShareViewController.xib */; }; @@ -36,8 +26,6 @@ 537630982B8612F00026BFAB /* FPUIExtensionService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 537630962B860D920026BFAB /* FPUIExtensionService.swift */; }; 538E396A27F4765000FA63D5 /* UniformTypeIdentifiers.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 538E396927F4765000FA63D5 /* UniformTypeIdentifiers.framework */; }; 538E396D27F4765000FA63D5 /* FileProviderExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 538E396C27F4765000FA63D5 /* FileProviderExtension.swift */; }; - 538E396F27F4765000FA63D5 /* FileProviderItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 538E396E27F4765000FA63D5 /* FileProviderItem.swift */; }; - 538E397127F4765000FA63D5 /* FileProviderEnumerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 538E397027F4765000FA63D5 /* FileProviderEnumerator.swift */; }; 538E397627F4765000FA63D5 /* FileProviderExt.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 538E396727F4765000FA63D5 /* FileProviderExt.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 53903D1E2956164F00D0B308 /* NCDesktopClientSocketKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 53903D0E2956164F00D0B308 /* NCDesktopClientSocketKit.h */; settings = {ATTRIBUTES = (Public, ); }; }; 53903D212956164F00D0B308 /* NCDesktopClientSocketKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 53903D0C2956164F00D0B308 /* NCDesktopClientSocketKit.framework */; }; @@ -51,14 +39,12 @@ 53903D37295618A400D0B308 /* LineProcessor.h in Headers */ = {isa = PBXBuildFile; fileRef = 53903D36295618A400D0B308 /* LineProcessor.h */; settings = {ATTRIBUTES = (Public, ); }; }; 539158AC27BE71A900816F56 /* FinderSyncSocketLineProcessor.m in Sources */ = {isa = PBXBuildFile; fileRef = 539158AB27BE71A900816F56 /* FinderSyncSocketLineProcessor.m */; }; 53B979812B84C81F002DA742 /* DocumentActionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53B979802B84C81F002DA742 /* DocumentActionViewController.swift */; }; - 53D056312970594F00988392 /* LocalFilesUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53D056302970594F00988392 /* LocalFilesUtils.swift */; }; + 53C331B22BCD28C30093D38B /* NextcloudFileProviderKit in Frameworks */ = {isa = PBXBuildFile; productRef = 53C331B12BCD28C30093D38B /* NextcloudFileProviderKit */; }; + 53C331B62BCD3AFF0093D38B /* NextcloudFileProviderKit in Frameworks */ = {isa = PBXBuildFile; productRef = 53C331B52BCD3AFF0093D38B /* NextcloudFileProviderKit */; }; 53D666612B70C9A70042C03D /* FileProviderConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53D666602B70C9A70042C03D /* FileProviderConfig.swift */; }; - 53ED472029C5E64200795DB1 /* FileProviderEnumerator+SyncEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53ED471F29C5E64200795DB1 /* FileProviderEnumerator+SyncEngine.swift */; }; - 53ED472829C88E7000795DB1 /* NextcloudItemMetadataTable+NKFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53ED472729C88E7000795DB1 /* NextcloudItemMetadataTable+NKFile.swift */; }; 53ED473029C9CE0B00795DB1 /* FileProviderExtension+ClientInterface.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53ED472F29C9CE0B00795DB1 /* FileProviderExtension+ClientInterface.swift */; }; 53FE14502B8E0658006C4193 /* ShareTableViewDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53FE144F2B8E0658006C4193 /* ShareTableViewDataSource.swift */; }; 53FE14542B8E1219006C4193 /* NextcloudKit in Frameworks */ = {isa = PBXBuildFile; productRef = 53FE14532B8E1219006C4193 /* NextcloudKit */; }; - 53FE14552B8E28E9006C4193 /* NextcloudAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = 536EFC35295E3C1100F4CB13 /* NextcloudAccount.swift */; }; 53FE14592B8E3F6C006C4193 /* ShareTableItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53FE14582B8E3F6C006C4193 /* ShareTableItemView.swift */; }; 53FE145B2B8F1305006C4193 /* NKShare+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53FE145A2B8F1305006C4193 /* NKShare+Extensions.swift */; }; 53FE14652B8F6700006C4193 /* ShareViewDataSourceUIDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 53FE14642B8F6700006C4193 /* ShareViewDataSourceUIDelegate.swift */; }; @@ -161,23 +147,14 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 5307A6F129675346001E0C6A /* NextcloudFilesDatabaseManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NextcloudFilesDatabaseManager.swift; sourceTree = ""; }; 531522812B8E01C6002E31BE /* ShareTableItemView.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = ShareTableItemView.xib; sourceTree = ""; }; - 5318AD9029BF42FB00CBB71C /* NextcloudItemMetadataTable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NextcloudItemMetadataTable.swift; sourceTree = ""; }; - 5318AD9429BF438F00CBB71C /* NextcloudLocalFileMetadataTable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NextcloudLocalFileMetadataTable.swift; sourceTree = ""; }; - 5318AD9629BF493600CBB71C /* FileProviderMaterialisedEnumerationObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileProviderMaterialisedEnumerationObserver.swift; sourceTree = ""; }; - 5318AD9829BF58D000CBB71C /* NKError+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NKError+Extensions.swift"; sourceTree = ""; }; 5350E4E72B0C514400F276CB /* ClientCommunicationProtocol.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ClientCommunicationProtocol.h; sourceTree = ""; }; 5350E4E82B0C534A00F276CB /* ClientCommunicationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientCommunicationService.swift; sourceTree = ""; }; 5350E4EA2B0C9CE100F276CB /* FileProviderExt-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "FileProviderExt-Bridging-Header.h"; sourceTree = ""; }; - 5352B36529DC14970011CE03 /* NextcloudFilesDatabaseManager+Directories.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NextcloudFilesDatabaseManager+Directories.swift"; sourceTree = ""; }; - 5352B36729DC17D60011CE03 /* NextcloudFilesDatabaseManager+LocalFiles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NextcloudFilesDatabaseManager+LocalFiles.swift"; sourceTree = ""; }; 5352B36B29DC44B50011CE03 /* FileProviderExtension+Thumbnailing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FileProviderExtension+Thumbnailing.swift"; sourceTree = ""; }; - 5352E85A29B7BFE6002CE85C /* Progress+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Progress+Extensions.swift"; sourceTree = ""; }; 535AE30D29C0A2CC0042A9BA /* Logger+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Logger+Extensions.swift"; sourceTree = ""; }; 53651E452BBC0D9500ECAC29 /* ShareeSuggestionsDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareeSuggestionsDataSource.swift; sourceTree = ""; }; 536EFBF6295CF58100F4CB13 /* FileProviderSocketLineProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileProviderSocketLineProcessor.swift; sourceTree = ""; }; - 536EFC35295E3C1100F4CB13 /* NextcloudAccount.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NextcloudAccount.swift; sourceTree = ""; }; 5374FD432B95EE1400C78D54 /* ShareController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareController.swift; sourceTree = ""; }; 5376307C2B85E2ED0026BFAB /* Logger+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Logger+Extensions.swift"; sourceTree = ""; }; 5376307E2B85E5650026BFAB /* FileProviderUIExt.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = FileProviderUIExt.entitlements; sourceTree = ""; }; @@ -188,8 +165,6 @@ 538E396727F4765000FA63D5 /* FileProviderExt.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = FileProviderExt.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 538E396927F4765000FA63D5 /* UniformTypeIdentifiers.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UniformTypeIdentifiers.framework; path = System/Library/Frameworks/UniformTypeIdentifiers.framework; sourceTree = SDKROOT; }; 538E396C27F4765000FA63D5 /* FileProviderExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileProviderExtension.swift; sourceTree = ""; }; - 538E396E27F4765000FA63D5 /* FileProviderItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileProviderItem.swift; sourceTree = ""; }; - 538E397027F4765000FA63D5 /* FileProviderEnumerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileProviderEnumerator.swift; sourceTree = ""; }; 538E397227F4765000FA63D5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 538E397327F4765000FA63D5 /* FileProviderExt.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = FileProviderExt.entitlements; sourceTree = ""; }; 53903D0C2956164F00D0B308 /* NCDesktopClientSocketKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = NCDesktopClientSocketKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -203,10 +178,7 @@ 53B9797E2B84C81F002DA742 /* FileProviderUIExt.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = FileProviderUIExt.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 53B979802B84C81F002DA742 /* DocumentActionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentActionViewController.swift; sourceTree = ""; }; 53B979852B84C81F002DA742 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 53D056302970594F00988392 /* LocalFilesUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalFilesUtils.swift; sourceTree = ""; }; 53D666602B70C9A70042C03D /* FileProviderConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileProviderConfig.swift; sourceTree = ""; }; - 53ED471F29C5E64200795DB1 /* FileProviderEnumerator+SyncEngine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FileProviderEnumerator+SyncEngine.swift"; sourceTree = ""; }; - 53ED472729C88E7000795DB1 /* NextcloudItemMetadataTable+NKFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NextcloudItemMetadataTable+NKFile.swift"; sourceTree = ""; }; 53ED472F29C9CE0B00795DB1 /* FileProviderExtension+ClientInterface.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FileProviderExtension+ClientInterface.swift"; sourceTree = ""; }; 53FE144F2B8E0658006C4193 /* ShareTableViewDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareTableViewDataSource.swift; sourceTree = ""; }; 53FE14572B8E3A7C006C4193 /* FileProviderUIExtRelease.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = FileProviderUIExtRelease.entitlements; sourceTree = ""; }; @@ -234,10 +206,10 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 5307A6EB2965DB8D001E0C6A /* RealmSwift in Frameworks */, 5307A6E82965DAD8001E0C6A /* NextcloudKit in Frameworks */, 538E396A27F4765000FA63D5 /* UniformTypeIdentifiers.framework in Frameworks */, 53903D302956173F00D0B308 /* NCDesktopClientSocketKit.framework in Frameworks */, + 53C331B22BCD28C30093D38B /* NextcloudFileProviderKit in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -253,6 +225,7 @@ buildActionMask = 2147483647; files = ( 5358F2B92BAA0F5300E3C729 /* NextcloudCapabilitiesKit in Frameworks */, + 53C331B62BCD3AFF0093D38B /* NextcloudFileProviderKit in Frameworks */, 53651E442BBC0CA300ECAC29 /* SuggestionsTextFieldKit in Frameworks */, 53FE14542B8E1219006C4193 /* NextcloudKit in Frameworks */, ); @@ -278,19 +251,6 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 5318AD8F29BF406500CBB71C /* Database */ = { - isa = PBXGroup; - children = ( - 5307A6F129675346001E0C6A /* NextcloudFilesDatabaseManager.swift */, - 5352B36529DC14970011CE03 /* NextcloudFilesDatabaseManager+Directories.swift */, - 5352B36729DC17D60011CE03 /* NextcloudFilesDatabaseManager+LocalFiles.swift */, - 5318AD9029BF42FB00CBB71C /* NextcloudItemMetadataTable.swift */, - 53ED472729C88E7000795DB1 /* NextcloudItemMetadataTable+NKFile.swift */, - 5318AD9429BF438F00CBB71C /* NextcloudLocalFileMetadataTable.swift */, - ); - path = Database; - sourceTree = ""; - }; 5350E4C72B0C368B00F276CB /* Services */ = { isa = PBXGroup; children = ( @@ -306,8 +266,6 @@ isa = PBXGroup; children = ( 535AE30D29C0A2CC0042A9BA /* Logger+Extensions.swift */, - 5318AD9829BF58D000CBB71C /* NKError+Extensions.swift */, - 5352E85A29B7BFE6002CE85C /* Progress+Extensions.swift */, ); path = Extensions; sourceTree = ""; @@ -332,20 +290,13 @@ 538E396B27F4765000FA63D5 /* FileProviderExt */ = { isa = PBXGroup; children = ( - 5318AD8F29BF406500CBB71C /* Database */, 5352E85929B7BFB4002CE85C /* Extensions */, 5350E4C72B0C368B00F276CB /* Services */, 53D666602B70C9A70042C03D /* FileProviderConfig.swift */, - 538E397027F4765000FA63D5 /* FileProviderEnumerator.swift */, - 53ED471F29C5E64200795DB1 /* FileProviderEnumerator+SyncEngine.swift */, 538E396C27F4765000FA63D5 /* FileProviderExtension.swift */, 53ED472F29C9CE0B00795DB1 /* FileProviderExtension+ClientInterface.swift */, 5352B36B29DC44B50011CE03 /* FileProviderExtension+Thumbnailing.swift */, - 538E396E27F4765000FA63D5 /* FileProviderItem.swift */, - 5318AD9629BF493600CBB71C /* FileProviderMaterialisedEnumerationObserver.swift */, 536EFBF6295CF58100F4CB13 /* FileProviderSocketLineProcessor.swift */, - 53D056302970594F00988392 /* LocalFilesUtils.swift */, - 536EFC35295E3C1100F4CB13 /* NextcloudAccount.swift */, 538E397327F4765000FA63D5 /* FileProviderExt.entitlements */, 538E397227F4765000FA63D5 /* Info.plist */, 5350E4EA2B0C9CE100F276CB /* FileProviderExt-Bridging-Header.h */, @@ -487,7 +438,7 @@ name = FileProviderExt; packageProductDependencies = ( 5307A6E72965DAD8001E0C6A /* NextcloudKit */, - 5307A6EA2965DB8D001E0C6A /* RealmSwift */, + 53C331B12BCD28C30093D38B /* NextcloudFileProviderKit */, ); productName = FileProviderExt; productReference = 538E396727F4765000FA63D5 /* FileProviderExt.appex */; @@ -529,6 +480,7 @@ 53FE14532B8E1219006C4193 /* NextcloudKit */, 5358F2B82BAA0F5300E3C729 /* NextcloudCapabilitiesKit */, 53651E432BBC0CA300ECAC29 /* SuggestionsTextFieldKit */, + 53C331B52BCD3AFF0093D38B /* NextcloudFileProviderKit */, ); productName = FileProviderUIExt; productReference = 53B9797E2B84C81F002DA742 /* FileProviderUIExt.appex */; @@ -586,7 +538,7 @@ isa = PBXProject; attributes = { BuildIndependentTargetsInParallel = YES; - LastSwiftUpdateCheck = 1520; + LastSwiftUpdateCheck = 1530; LastUpgradeCheck = 1240; TargetAttributes = { 538E396627F4765000FA63D5 = { @@ -628,9 +580,9 @@ mainGroup = C2B573941B1CD88000303B36; packageReferences = ( 5307A6E42965C6FA001E0C6A /* XCRemoteSwiftPackageReference "NextcloudKit" */, - 5307A6E92965DB57001E0C6A /* XCRemoteSwiftPackageReference "realm-swift" */, 5358F2B72BAA045E00E3C729 /* XCRemoteSwiftPackageReference "NextcloudCapabilitiesKit" */, 53651E422BBC0C7F00ECAC29 /* XCRemoteSwiftPackageReference "SuggestionsTextFieldKit" */, + 53C331B02BCD28C30093D38B /* XCRemoteSwiftPackageReference "NextcloudFileProviderKit" */, ); productRefGroup = C2B573B21B1CD91E00303B36 /* Products */; projectDirPath = ""; @@ -713,29 +665,15 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 5352E85B29B7BFE6002CE85C /* Progress+Extensions.swift in Sources */, 53D666612B70C9A70042C03D /* FileProviderConfig.swift in Sources */, - 536EFC36295E3C1100F4CB13 /* NextcloudAccount.swift in Sources */, 53ED473029C9CE0B00795DB1 /* FileProviderExtension+ClientInterface.swift in Sources */, 538E396D27F4765000FA63D5 /* FileProviderExtension.swift in Sources */, 536EFBF7295CF58100F4CB13 /* FileProviderSocketLineProcessor.swift in Sources */, - 53ED472029C5E64200795DB1 /* FileProviderEnumerator+SyncEngine.swift in Sources */, - 5318AD9929BF58D000CBB71C /* NKError+Extensions.swift in Sources */, - 53ED472829C88E7000795DB1 /* NextcloudItemMetadataTable+NKFile.swift in Sources */, 537630972B860D920026BFAB /* FPUIExtensionService.swift in Sources */, - 5318AD9529BF438F00CBB71C /* NextcloudLocalFileMetadataTable.swift in Sources */, 535AE30E29C0A2CC0042A9BA /* Logger+Extensions.swift in Sources */, - 5307A6F229675346001E0C6A /* NextcloudFilesDatabaseManager.swift in Sources */, 537630952B860D560026BFAB /* FPUIExtensionServiceSource.swift in Sources */, - 53D056312970594F00988392 /* LocalFilesUtils.swift in Sources */, - 538E396F27F4765000FA63D5 /* FileProviderItem.swift in Sources */, - 5352B36829DC17D60011CE03 /* NextcloudFilesDatabaseManager+LocalFiles.swift in Sources */, - 5318AD9129BF42FB00CBB71C /* NextcloudItemMetadataTable.swift in Sources */, 5350E4E92B0C534A00F276CB /* ClientCommunicationService.swift in Sources */, - 5352B36629DC14970011CE03 /* NextcloudFilesDatabaseManager+Directories.swift in Sources */, - 5318AD9729BF493600CBB71C /* FileProviderMaterialisedEnumerationObserver.swift in Sources */, 5352B36C29DC44B50011CE03 /* FileProviderExtension+Thumbnailing.swift in Sources */, - 538E397127F4765000FA63D5 /* FileProviderEnumerator.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -759,7 +697,6 @@ 5374FD442B95EE1400C78D54 /* ShareController.swift in Sources */, 53FE145B2B8F1305006C4193 /* NKShare+Extensions.swift in Sources */, 53FE14592B8E3F6C006C4193 /* ShareTableItemView.swift in Sources */, - 53FE14552B8E28E9006C4193 /* NextcloudAccount.swift in Sources */, 5376307D2B85E2ED0026BFAB /* Logger+Extensions.swift in Sources */, 53FE14502B8E0658006C4193 /* ShareTableViewDataSource.swift in Sources */, 537630982B8612F00026BFAB /* FPUIExtensionService.swift in Sources */, @@ -1531,14 +1468,6 @@ minimumVersion = 2.5.9; }; }; - 5307A6E92965DB57001E0C6A /* XCRemoteSwiftPackageReference "realm-swift" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/realm/realm-swift.git"; - requirement = { - kind = exactVersion; - version = 10.33.0; - }; - }; 5358F2B72BAA045E00E3C729 /* XCRemoteSwiftPackageReference "NextcloudCapabilitiesKit" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/claucambra/NextcloudCapabilitiesKit.git"; @@ -1555,6 +1484,14 @@ minimumVersion = 1.0.0; }; }; + 53C331B02BCD28C30093D38B /* XCRemoteSwiftPackageReference "NextcloudFileProviderKit" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/claucambra/NextcloudFileProviderKit.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 0.9.0; + }; + }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ @@ -1568,11 +1505,6 @@ package = 5307A6E42965C6FA001E0C6A /* XCRemoteSwiftPackageReference "NextcloudKit" */; productName = NextcloudKit; }; - 5307A6EA2965DB8D001E0C6A /* RealmSwift */ = { - isa = XCSwiftPackageProductDependency; - package = 5307A6E92965DB57001E0C6A /* XCRemoteSwiftPackageReference "realm-swift" */; - productName = RealmSwift; - }; 5358F2B82BAA0F5300E3C729 /* NextcloudCapabilitiesKit */ = { isa = XCSwiftPackageProductDependency; package = 5358F2B72BAA045E00E3C729 /* XCRemoteSwiftPackageReference "NextcloudCapabilitiesKit" */; @@ -1583,6 +1515,16 @@ package = 53651E422BBC0C7F00ECAC29 /* XCRemoteSwiftPackageReference "SuggestionsTextFieldKit" */; productName = SuggestionsTextFieldKit; }; + 53C331B12BCD28C30093D38B /* NextcloudFileProviderKit */ = { + isa = XCSwiftPackageProductDependency; + package = 53C331B02BCD28C30093D38B /* XCRemoteSwiftPackageReference "NextcloudFileProviderKit" */; + productName = NextcloudFileProviderKit; + }; + 53C331B52BCD3AFF0093D38B /* NextcloudFileProviderKit */ = { + isa = XCSwiftPackageProductDependency; + package = 53C331B02BCD28C30093D38B /* XCRemoteSwiftPackageReference "NextcloudFileProviderKit" */; + productName = NextcloudFileProviderKit; + }; 53FE14512B8E1213006C4193 /* NextcloudKit */ = { isa = XCSwiftPackageProductDependency; package = 5307A6E42965C6FA001E0C6A /* XCRemoteSwiftPackageReference "NextcloudKit" */; diff --git a/src/gui/macOS/fileproviderdomainmanager.h b/src/gui/macOS/fileproviderdomainmanager.h index 8de3fc1f24939..dcb1f9f6071c3 100644 --- a/src/gui/macOS/fileproviderdomainmanager.h +++ b/src/gui/macOS/fileproviderdomainmanager.h @@ -49,17 +49,10 @@ private slots: void disconnectFileProviderDomainForAccount(const OCC::AccountState * const accountState, const QString &reason); void reconnectFileProviderDomainForAccount(const OCC::AccountState * const accountState); - void trySetupPushNotificationsForAccount(const OCC::Account * const account); - void setupPushNotificationsForAccount(const OCC::Account * const account); void signalEnumeratorChanged(const OCC::Account * const account); - void slotAccountStateChanged(const OCC::AccountState * const accountState); - void slotEnumeratorSignallingTimerTimeout(); private: - // Starts regular enumerator signalling if no push notifications available - QTimer _enumeratorSignallingTimer; - class MacImplementation; std::unique_ptr d; }; diff --git a/src/gui/macOS/fileproviderdomainmanager_mac.mm b/src/gui/macOS/fileproviderdomainmanager_mac.mm index 331c44df54dbf..6e59f621ac8f5 100644 --- a/src/gui/macOS/fileproviderdomainmanager_mac.mm +++ b/src/gui/macOS/fileproviderdomainmanager_mac.mm @@ -20,7 +20,6 @@ #include "config.h" #include "fileproviderdomainmanager.h" #include "fileprovidersettingscontroller.h" -#include "pushnotifications.h" #include "gui/accountmanager.h" #include "libsync/account.h" @@ -67,14 +66,6 @@ QString accountIdFromDomain(NSFileProviderDomain * const domain) return accountIdFromDomainId(domain.identifier); } -bool accountFilesPushNotificationsReady(const OCC::AccountPtr &account) -{ - const auto pushNotifications = account->pushNotifications(); - const auto pushNotificationsCapability = account->capabilities().availablePushNotifications() & OCC::PushNotificationType::Files; - - return pushNotificationsCapability && pushNotifications && pushNotifications->isReady(); -} - } namespace OCC { @@ -420,11 +411,6 @@ QStringList configuredDomainIds() const void FileProviderDomainManager::start() { ConfigFile cfg; - std::chrono::milliseconds polltime = cfg.remotePollInterval(); - _enumeratorSignallingTimer.setInterval(polltime.count()); - connect(&_enumeratorSignallingTimer, &QTimer::timeout, - this, &FileProviderDomainManager::slotEnumeratorSignallingTimerTimeout); - _enumeratorSignallingTimer.start(); setupFileProviderDomains(); @@ -498,54 +484,6 @@ QStringList configuredDomainIds() const connect(accountState, &AccountState::stateChanged, this, [this, accountState] { slotAccountStateChanged(accountState); }); - - // Setup push notifications - const auto accountCapabilities = account->capabilities().isValid(); - if (!accountCapabilities) { - connect(account.get(), &Account::capabilitiesChanged, this, [this, account] { - trySetupPushNotificationsForAccount(account.get()); - }); - return; - } - - trySetupPushNotificationsForAccount(account.get()); -} - -void FileProviderDomainManager::trySetupPushNotificationsForAccount(const Account * const account) -{ - if (!d) { - return; - } - - Q_ASSERT(account); - - const auto pushNotifications = account->pushNotifications(); - const auto pushNotificationsCapability = account->capabilities().availablePushNotifications() & PushNotificationType::Files; - - if (pushNotificationsCapability && pushNotifications && pushNotifications->isReady()) { - qCDebug(lcMacFileProviderDomainManager) << "Push notifications already ready, connecting them to enumerator signalling." - << account->displayName(); - setupPushNotificationsForAccount(account); - } else if (pushNotificationsCapability) { - qCDebug(lcMacFileProviderDomainManager) << "Push notifications not yet ready, will connect to signalling when ready." - << account->displayName(); - connect(account, &Account::pushNotificationsReady, this, &FileProviderDomainManager::setupPushNotificationsForAccount); - } -} - -void FileProviderDomainManager::setupPushNotificationsForAccount(const Account * const account) -{ - if (!d) { - return; - } - - Q_ASSERT(account); - - qCDebug(lcMacFileProviderDomainManager) << "Setting up push notifications for file provider domain for account:" - << account->displayName(); - - connect(account->pushNotifications(), &PushNotifications::filesChanged, this, &FileProviderDomainManager::signalEnumeratorChanged); - disconnect(account, &Account::pushNotificationsReady, this, &FileProviderDomainManager::setupPushNotificationsForAccount); } void FileProviderDomainManager::signalEnumeratorChanged(const Account * const account) @@ -569,13 +507,6 @@ QStringList configuredDomainIds() const Q_ASSERT(account); d->removeFileProviderDomain(accountState); - - if (accountFilesPushNotificationsReady(account)) { - const auto pushNotifications = account->pushNotifications(); - disconnect(pushNotifications, &PushNotifications::filesChanged, this, &FileProviderDomainManager::signalEnumeratorChanged); - } else if (const auto hasFilesPushNotificationsCapability = account->capabilities().availablePushNotifications() & PushNotificationType::Files) { - disconnect(account.get(), &Account::pushNotificationsReady, this, &FileProviderDomainManager::setupPushNotificationsForAccount); - } } void FileProviderDomainManager::disconnectFileProviderDomainForAccount(const AccountState * const accountState, const QString &reason) @@ -639,27 +570,6 @@ QStringList configuredDomainIds() const } } -void FileProviderDomainManager::slotEnumeratorSignallingTimerTimeout() -{ - if (!d) { - return; - } - - qCDebug(lcMacFileProviderDomainManager) << "Enumerator signalling timer timed out, notifying domains for accounts without push notifications"; - - const auto registeredDomainIds = d->configuredDomainIds(); - for (const auto &domainId : registeredDomainIds) { - const auto accountUserId = accountIdFromDomainId(domainId); - const auto accountState = AccountManager::instance()->accountFromUserId(accountUserId); - const auto account = accountState->account(); - - if (!accountFilesPushNotificationsReady(account)) { - qCDebug(lcMacFileProviderDomainManager) << "Notifying domain for account:" << account->userIdAtHostWithPort(); - d->signalEnumeratorChanged(account.get()); - } - } -} - AccountStatePtr FileProviderDomainManager::accountStateFromFileProviderDomainIdentifier(const QString &domainIdentifier) { if (domainIdentifier.isEmpty()) {