diff --git a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudFilesDatabaseManager+Directories.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudFilesDatabaseManager+Directories.swift new file mode 100644 index 000000000..386791ecc --- /dev/null +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudFilesDatabaseManager+Directories.swift @@ -0,0 +1,145 @@ +/* + * 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? { + return 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 let error { + 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.swift b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudFilesDatabaseManager.swift index 7f18fcc74..0541ff819 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudFilesDatabaseManager.swift +++ b/shell_integration/MacOSX/NextcloudIntegration/FileProviderExt/Database/NextcloudFilesDatabaseManager.swift @@ -69,7 +69,7 @@ class NextcloudFilesDatabaseManager : NSObject { super.init() } - private func ncDatabase() -> Realm { + func ncDatabase() -> Realm { let realm = try! Realm() realm.refresh() return realm @@ -89,7 +89,7 @@ class NextcloudFilesDatabaseManager : NSObject { return nil } - private func sortedItemMetadatas(_ metadatas: Results) -> [NextcloudItemMetadataTable] { + func sortedItemMetadatas(_ metadatas: Results) -> [NextcloudItemMetadataTable] { let sortedMetadatas = metadatas.sorted(byKeyPath: "fileName", ascending: true) return Array(sortedMetadatas.map { NextcloudItemMetadataTable(value: $0) }) } @@ -324,132 +324,6 @@ class NextcloudFilesDatabaseManager : NSObject { return nil } - 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? { - return 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 let error { - 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) - } func localFileMetadataFromOcId(_ ocId: String) -> NextcloudLocalFileMetadataTable? { if let metadata = ncDatabase().objects(NextcloudLocalFileMetadataTable.self).filter("ocId == %@", ocId).first { diff --git a/shell_integration/MacOSX/NextcloudIntegration/NextcloudIntegration.xcodeproj/project.pbxproj b/shell_integration/MacOSX/NextcloudIntegration/NextcloudIntegration.xcodeproj/project.pbxproj index 590657cca..4c4d2f101 100644 --- a/shell_integration/MacOSX/NextcloudIntegration/NextcloudIntegration.xcodeproj/project.pbxproj +++ b/shell_integration/MacOSX/NextcloudIntegration/NextcloudIntegration.xcodeproj/project.pbxproj @@ -15,6 +15,7 @@ 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 */; }; + 5352B36629DC14970011CE03 /* NextcloudFilesDatabaseManager+Directories.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5352B36529DC14970011CE03 /* NextcloudFilesDatabaseManager+Directories.swift */; }; 5352E85B29B7BFE6002CE85C /* Progress+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5352E85A29B7BFE6002CE85C /* Progress+Extensions.swift */; }; 535AE30E29C0A2CC0042A9BA /* Logger+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 535AE30D29C0A2CC0042A9BA /* Logger+Extensions.swift */; }; 536EFBF7295CF58100F4CB13 /* FileProviderSocketLineProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 536EFBF6295CF58100F4CB13 /* FileProviderSocketLineProcessor.swift */; }; @@ -142,6 +143,7 @@ 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 = ""; }; + 5352B36529DC14970011CE03 /* NextcloudFilesDatabaseManager+Directories.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NextcloudFilesDatabaseManager+Directories.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 = ""; }; 536EFBF6295CF58100F4CB13 /* FileProviderSocketLineProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileProviderSocketLineProcessor.swift; sourceTree = ""; }; @@ -223,6 +225,7 @@ isa = PBXGroup; children = ( 5307A6F129675346001E0C6A /* NextcloudFilesDatabaseManager.swift */, + 5352B36529DC14970011CE03 /* NextcloudFilesDatabaseManager+Directories.swift */, 5318AD9029BF42FB00CBB71C /* NextcloudItemMetadataTable.swift */, 53ED472729C88E7000795DB1 /* NextcloudItemMetadataTable+NKFile.swift */, 5318AD9429BF438F00CBB71C /* NextcloudLocalFileMetadataTable.swift */, @@ -580,6 +583,7 @@ 53D056312970594F00988392 /* LocalFilesUtils.swift in Sources */, 538E396F27F4765000FA63D5 /* FileProviderItem.swift in Sources */, 5318AD9129BF42FB00CBB71C /* NextcloudItemMetadataTable.swift in Sources */, + 5352B36629DC14970011CE03 /* NextcloudFilesDatabaseManager+Directories.swift in Sources */, 5318AD9729BF493600CBB71C /* FileProviderMaterialisedEnumerationObserver.swift in Sources */, 538E397127F4765000FA63D5 /* FileProviderEnumerator.swift in Sources */, );