Merge pull request #6368 from nextcloud/bugfix/swift-format

Swift-format FileProviderExt
This commit is contained in:
Matthieu Gallien 2024-02-06 11:45:52 +01:00 committed by GitHub
commit ead399895d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 1365 additions and 676 deletions

69
.swift-format.json Normal file
View file

@ -0,0 +1,69 @@
{
"fileScopedDeclarationPrivacy" : {
"accessLevel" : "private"
},
"indentation" : {
"spaces" : 4
},
"indentConditionalCompilationBlocks" : true,
"indentSwitchCaseLabels" : false,
"lineBreakAroundMultilineExpressionChainComponents" : false,
"lineBreakBeforeControlFlowKeywords" : false,
"lineBreakBeforeEachArgument" : false,
"lineBreakBeforeEachGenericRequirement" : false,
"lineLength" : 100,
"maximumBlankLines" : 1,
"multiElementCollectionTrailingCommas" : true,
"noAssignmentInExpressions" : {
"allowedFunctions" : [
"XCTAssertNoThrow"
]
},
"prioritizeKeepingFunctionOutputTogether" : false,
"respectsExistingLineBreaks" : true,
"rules" : {
"AllPublicDeclarationsHaveDocumentation" : false,
"AlwaysUseLiteralForEmptyCollectionInit" : false,
"AlwaysUseLowerCamelCase" : true,
"AmbiguousTrailingClosureOverload" : true,
"BeginDocumentationCommentWithOneLineSummary" : false,
"DoNotUseSemicolons" : true,
"DontRepeatTypeInStaticProperties" : true,
"FileScopedDeclarationPrivacy" : true,
"FullyIndirectEnum" : true,
"GroupNumericLiterals" : true,
"IdentifiersMustBeASCII" : true,
"NeverForceUnwrap" : false,
"NeverUseForceTry" : false,
"NeverUseImplicitlyUnwrappedOptionals" : false,
"NoAccessLevelOnExtensionDeclaration" : true,
"NoAssignmentInExpressions" : true,
"NoBlockComments" : true,
"NoCasesWithOnlyFallthrough" : true,
"NoEmptyTrailingClosureParentheses" : true,
"NoLabelsInCasePatterns" : true,
"NoLeadingUnderscores" : false,
"NoParensAroundConditions" : true,
"NoPlaygroundLiterals" : true,
"NoVoidReturnOnFunctionSignature" : true,
"OmitExplicitReturns" : false,
"OneCasePerLine" : true,
"OneVariableDeclarationPerLine" : true,
"OnlyOneTrailingClosureArgument" : true,
"OrderedImports" : true,
"ReplaceForEachWithForLoop" : true,
"ReturnVoidInsteadOfEmptyTuple" : true,
"TypeNamesShouldBeCapitalized" : true,
"UseEarlyExits" : false,
"UseLetInEveryBoundCaseVariable" : true,
"UseShorthandTypeNames" : true,
"UseSingleLinePropertyGetter" : true,
"UseSynthesizedInitializer" : true,
"UseTripleSlashForDocumentationComments" : true,
"UseWhereClausesInForLoops" : false,
"ValidateDocumentationComments" : false
},
"spacesAroundRangeFormationOperators" : false,
"tabWidth" : 8,
"version" : 1
}

View file

@ -20,36 +20,53 @@ extension NextcloudFilesDatabaseManager {
// 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)
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)
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 {
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] {
func childItemsForDirectory(_ directoryMetadata: NextcloudItemMetadataTable)
-> [NextcloudItemMetadataTable]
{
let directoryServerUrl = directoryMetadata.serverUrl + "/" + directoryMetadata.fileName
let metadatas = ncDatabase().objects(NextcloudItemMetadataTable.self).filter("serverUrl BEGINSWITH %@", directoryServerUrl)
let metadatas = ncDatabase().objects(NextcloudItemMetadataTable.self).filter(
"serverUrl BEGINSWITH %@", directoryServerUrl)
return sortedItemMetadatas(metadatas)
}
func childDirectoriesForDirectory(_ directoryMetadata: NextcloudItemMetadataTable) -> [NextcloudItemMetadataTable] {
func childDirectoriesForDirectory(_ directoryMetadata: NextcloudItemMetadataTable)
-> [NextcloudItemMetadataTable]
{
let directoryServerUrl = directoryMetadata.serverUrl + "/" + directoryMetadata.fileName
let metadatas = ncDatabase().objects(NextcloudItemMetadataTable.self).filter("serverUrl BEGINSWITH %@ AND directory == true", directoryServerUrl)
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 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 {
if let metadata = ncDatabase().objects(NextcloudItemMetadataTable.self).filter(
"ocId == %@ AND directory == true", ocId
).first {
return NextcloudItemMetadataTable(value: metadata)
}
@ -57,20 +74,31 @@ extension NextcloudFilesDatabaseManager {
}
func directoryMetadatas(account: String) -> [NextcloudItemMetadataTable] {
let metadatas = ncDatabase().objects(NextcloudItemMetadataTable.self).filter("account == %@ AND directory == true", account)
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)
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")
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
}
@ -79,20 +107,25 @@ extension NextcloudFilesDatabaseManager {
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)")
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)")
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)
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) {
if successfulItemMetadataDelete {
deletedMetadatas.append(NextcloudItemMetadataTable(value: result))
}
@ -101,24 +134,35 @@ extension NextcloudFilesDatabaseManager {
}
}
Logger.ncFilesDatabase.debug("Completed deletions in directory recursive delete. ocID: \(directoryMetadata.ocId, privacy: .public), etag: \(directoryEtag, privacy: .public), serverUrl: \(directoryUrlPath, privacy: .public)")
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]? {
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")
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)
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")
@ -127,19 +171,25 @@ extension NextcloudFilesDatabaseManager {
try database.write {
for childItem in childItemResults {
let oldServerUrl = childItem.serverUrl
let movedServerUrl = oldServerUrl.replacingOccurrences(of: oldDirectoryServerUrl, with: newDirectoryServerUrl)
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)")
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)")
} 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)
let updatedChildItemResults = database.objects(NextcloudItemMetadataTable.self).filter(
"account == %@ AND serverUrl BEGINSWITH %@", directoryMetadata.account,
newDirectoryServerUrl)
return sortedItemMetadatas(updatedChildItemResults)
}
}

View file

@ -13,12 +13,14 @@
*/
import Foundation
import RealmSwift
import OSLog
import RealmSwift
extension NextcloudFilesDatabaseManager {
func localFileMetadataFromOcId(_ ocId: String) -> NextcloudLocalFileMetadataTable? {
if let metadata = ncDatabase().objects(NextcloudLocalFileMetadataTable.self).filter("ocId == %@", ocId).first {
if let metadata = ncDatabase().objects(NextcloudLocalFileMetadataTable.self).filter(
"ocId == %@", ocId
).first {
return NextcloudLocalFileMetadataTable(value: metadata)
}
@ -41,10 +43,14 @@ extension NextcloudFilesDatabaseManager {
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)")
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 let error {
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)")
} 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)"
)
}
}
@ -53,34 +59,42 @@ extension NextcloudFilesDatabaseManager {
do {
try database.write {
let results = database.objects(NextcloudLocalFileMetadataTable.self).filter("ocId == %@", ocId)
let results = database.objects(NextcloudLocalFileMetadataTable.self).filter(
"ocId == %@", ocId)
database.delete(results)
}
} catch let error {
Logger.ncFilesDatabase.error("Could not delete local file metadata with ocId: \(ocId, privacy: .public), received error: \(error.localizedDescription, privacy: .public)")
} 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>) -> [NextcloudLocalFileMetadataTable] {
private func sortedLocalFileMetadatas(_ metadatas: Results<NextcloudLocalFileMetadataTable>)
-> [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)
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 { $0.ocId })
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;
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))

View file

@ -12,16 +12,14 @@
* for more details.
*/
import Foundation
import RealmSwift
import FileProvider
import Foundation
import NextcloudKit
import OSLog
import RealmSwift
class NextcloudFilesDatabaseManager : NSObject {
static let shared = {
return NextcloudFilesDatabaseManager();
}()
class NextcloudFilesDatabaseManager: NSObject {
static let shared = NextcloudFilesDatabaseManager()
let relativeDatabaseFolderPath = "Database/"
let databaseFilename = "fileproviderextdatabase.realm"
@ -31,29 +29,36 @@ class NextcloudFilesDatabaseManager : NSObject {
let schemaVersion: UInt64 = 100
override init() {
self.relativeDatabaseFilePath = self.relativeDatabaseFolderPath + self.databaseFilename
relativeDatabaseFilePath = relativeDatabaseFolderPath + databaseFilename
guard let fileProviderDataDirUrl = pathForFileProviderExtData() else {
super.init()
return
}
self.databasePath = fileProviderDataDirUrl.appendingPathComponent(self.relativeDatabaseFilePath)
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(self.relativeDatabaseFolderPath)
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 let error {
Logger.ncFilesDatabase.error("Could not set permission level for File Provider database folder, received error: \(error.localizedDescription, privacy: .public)")
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: self.databasePath,
schemaVersion: self.schemaVersion,
fileURL: databasePath,
schemaVersion: schemaVersion,
objectTypes: [NextcloudItemMetadataTable.self, NextcloudLocalFileMetadataTable.self]
)
@ -63,7 +68,8 @@ class NextcloudFilesDatabaseManager : NSObject {
_ = 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)")
Logger.ncFilesDatabase.error(
"Error opening Realm db: \(error.localizedDescription, privacy: .public)")
}
super.init()
@ -76,81 +82,108 @@ class NextcloudFilesDatabaseManager : NSObject {
}
func anyItemMetadatasForAccount(_ account: String) -> Bool {
return !ncDatabase().objects(NextcloudItemMetadataTable.self).filter("account == %@", account).isEmpty
!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 {
if let itemMetadata = ncDatabase().objects(NextcloudItemMetadataTable.self).filter(
"ocId == %@", ocId
).first {
return NextcloudItemMetadataTable(value: itemMetadata)
}
return nil
}
func sortedItemMetadatas(_ metadatas: Results<NextcloudItemMetadataTable>) -> [NextcloudItemMetadataTable] {
func sortedItemMetadatas(_ metadatas: Results<NextcloudItemMetadataTable>)
-> [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)
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)
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)
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? {
func itemMetadataFromFileProviderItemIdentifier(_ identifier: NSFileProviderItemIdentifier)
-> NextcloudItemMetadataTable?
{
let ocId = identifier.rawValue
return itemMetadataFromOcId(ocId)
}
private func processItemMetadatasToDelete(existingMetadatas: Results<NextcloudItemMetadataTable>,
updatedMetadatas: [NextcloudItemMetadataTable]) -> [NextcloudItemMetadataTable] {
private func processItemMetadatasToDelete(
existingMetadatas: Results<NextcloudItemMetadataTable>,
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 }
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)")
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<NextcloudItemMetadataTable>,
updatedMetadatas: [NextcloudItemMetadataTable],
updateDirectoryEtags: Bool) -> (newMetadatas: [NextcloudItemMetadataTable], updatedMetadatas: [NextcloudItemMetadataTable], directoriesNeedingRename: [NextcloudItemMetadataTable]) {
private func processItemMetadatasToUpdate(
existingMetadatas: Results<NextcloudItemMetadataTable>,
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 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
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
@ -159,49 +192,73 @@ class NextcloudFilesDatabaseManager : NSObject {
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)")
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)")
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 {
} 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)")
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]?) {
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 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 metadatasToDelete = processItemMetadatasToDelete(
existingMetadatas: existingMetadatas,
updatedMetadatas: updatedMetadatas)
let metadatasToChange = processItemMetadatasToUpdate(existingMetadatas: existingMetadatas,
updatedMetadatas: updatedMetadatas,
updateDirectoryEtags: updateDirectoryEtags)
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) })
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) {
if let updatedDirectoryChildren = renameDirectoryAndPropagateToChildren(
ocId: metadata.ocId,
newServerUrl: metadata.serverUrl,
newFileName: metadata.fileName)
{
metadatasToUpdate += updatedDirectoryChildren
}
}
@ -209,40 +266,61 @@ class NextcloudFilesDatabaseManager : NSObject {
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))
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 let error {
Logger.ncFilesDatabase.error("Could not update any item metadatas, received error: \(error.localizedDescription, privacy: .public)")
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) {
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)")
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)")
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 let error {
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)")
} 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)
}
}
@ -253,10 +331,14 @@ class NextcloudFilesDatabaseManager : NSObject {
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)")
Logger.ncFilesDatabase.debug(
"Added item metadata. ocID: \(metadata.ocId, privacy: .public), etag: \(metadata.etag, privacy: .public), fileName: \(metadata.fileName, privacy: .public)"
)
}
} catch let error {
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)")
} 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)"
)
}
}
@ -265,15 +347,18 @@ class NextcloudFilesDatabaseManager : NSObject {
do {
try database.write {
let results = database.objects(NextcloudItemMetadataTable.self).filter("ocId == %@", ocId)
let results = database.objects(NextcloudItemMetadataTable.self).filter(
"ocId == %@", ocId)
Logger.ncFilesDatabase.debug("Deleting item metadata. \(ocId, privacy: .public)")
database.delete(results)
}
return true
} catch let error {
Logger.ncFilesDatabase.error("Could not delete item metadata with ocId: \(ocId, privacy: .public), received error: \(error.localizedDescription, privacy: .public)")
} catch {
Logger.ncFilesDatabase.error(
"Could not delete item metadata with ocId: \(ocId, privacy: .public), received error: \(error.localizedDescription, privacy: .public)"
)
return false
}
}
@ -283,8 +368,14 @@ class NextcloudFilesDatabaseManager : NSObject {
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)")
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
}
@ -297,14 +388,20 @@ class NextcloudFilesDatabaseManager : NSObject {
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)")
Logger.ncFilesDatabase.debug(
"Renamed item \(oldFileName, privacy: .public) to \(newFileName, privacy: .public), moved from serverUrl: \(oldServerUrl, privacy: .public) to serverUrl: \(newServerUrl, privacy: .public)"
)
}
} catch let error {
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)")
} 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? {
func parentItemIdentifierFromMetadata(_ metadata: NextcloudItemMetadataTable)
-> NSFileProviderItemIdentifier?
{
let homeServerFilesUrl = metadata.urlBase + "/remote.php/dav/files/" + metadata.userId
if metadata.serverUrl == homeServerFilesUrl {
@ -312,7 +409,9 @@ class NextcloudFilesDatabaseManager : NSObject {
}
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)")
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
}
@ -320,7 +419,9 @@ class NextcloudFilesDatabaseManager : NSObject {
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)")
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
}
}

View file

@ -69,8 +69,10 @@ extension NextcloudItemMetadataTable {
}
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 {
// 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 {
@ -87,26 +89,33 @@ extension NextcloudItemMetadataTable {
return metadata
}
static func metadatasFromDirectoryReadNKFiles(_ files: [NKFile],
account: String,
completionHandler: @escaping (_ directoryMetadata: NextcloudItemMetadataTable,
_ childDirectoriesMetadatas: [NextcloudItemMetadataTable],
_ metadatas: [NextcloudItemMetadataTable]) -> Void) {
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)
let appendQueue = DispatchQueue(label: "metadataAppendQueue", qos: .userInitiated) // Serial queue
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 {
if metadatas.isEmpty, !directoryMetadataSet {
let metadata = NextcloudItemMetadataTable.fromNKFile(file, account: account)
directoryMetadata = metadata;
directoryMetadataSet = true;
directoryMetadata = metadata
directoryMetadataSet = true
} else {
conversionQueue.async(group: dispatchGroup) {
let metadata = NextcloudItemMetadataTable.fromNKFile(file, account: account)

View file

@ -12,10 +12,10 @@
* for more details.
*/
import Foundation
import RealmSwift
import FileProvider
import Foundation
import NextcloudKit
import RealmSwift
class NextcloudItemMetadataTable: Object {
enum Status: Int {
@ -71,7 +71,7 @@ class NextcloudItemMetadataTable: Object {
@Persisted var isExtractFile: Bool = false
@Persisted var livePhoto: Bool = false
@Persisted var mountType = ""
@Persisted var name = "" // for unifiedSearch is the provider.id
@Persisted var name = "" // for unifiedSearch is the provider.id
@Persisted var note = ""
@Persisted var ownerId = ""
@Persisted var ownerDisplayName = ""
@ -88,13 +88,14 @@ class NextcloudItemMetadataTable: Object {
@Persisted var quotaAvailableBytes: Int64 = 0
@Persisted var resourceType = ""
@Persisted var richWorkspace: String?
@Persisted var serverUrl = "" // For parent directory!!
@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
let sharePermissionsCloudMesh = List<String>() // TODO: Find a way to compare these in remote state check
// TODO: Find a way to compare these two below in remote state check
let sharePermissionsCloudMesh = List<String>()
let shareType = List<Int>()
@Persisted var size: Int64 = 0
@Persisted var status: Int = 0
@ -117,22 +118,25 @@ class NextcloudItemMetadataTable: Object {
}
var isRenameable: Bool {
return lock
lock
}
var isPrintable: Bool {
if isDocumentViewableOnly {
return false
}
if ["application/pdf", "com.adobe.pdf"].contains(contentType) || contentType.hasPrefix("text/") || classFile == NKCommon.TypeClassFile.image.rawValue {
if ["application/pdf", "com.adobe.pdf"].contains(contentType)
|| contentType.hasPrefix("text/")
|| classFile == NKCommon.TypeClassFile.image.rawValue
{
return true
}
return false
}
var isDocumentViewableOnly: Bool {
return sharePermissionsCollaborationServices == SharePermissions.readShare.rawValue &&
classFile == NKCommon.TypeClassFile.document.rawValue
sharePermissionsCollaborationServices == SharePermissions.readShare.rawValue
&& classFile == NKCommon.TypeClassFile.document.rawValue
}
var isCopyableInPasteboard: Bool {
@ -143,22 +147,21 @@ class NextcloudItemMetadataTable: Object {
if directory || isDocumentViewableOnly {
return false
}
return contentType == "com.adobe.pdf" || contentType == "application/pdf" || classFile == NKCommon.TypeClassFile.image.rawValue
return contentType == "com.adobe.pdf" || contentType == "application/pdf"
|| classFile == NKCommon.TypeClassFile.image.rawValue
}
var isSettableOnOffline: Bool {
return session.isEmpty && !isDocumentViewableOnly
session.isEmpty && !isDocumentViewableOnly
}
var canOpenIn: Bool {
return session.isEmpty && !isDocumentViewableOnly && !directory
session.isEmpty && !isDocumentViewableOnly && !directory
}
var isDownloadUpload: Bool {
return status == Status.inDownload.rawValue ||
status == Status.downloading.rawValue ||
status == Status.inUpload.rawValue ||
status == Status.uploading.rawValue
status == Status.inDownload.rawValue || status == Status.downloading.rawValue
|| status == Status.inUpload.rawValue || status == Status.uploading.rawValue
}
var isDownload: Bool {
@ -171,30 +174,31 @@ class NextcloudItemMetadataTable: Object {
override func isEqual(_ object: Any?) -> Bool {
if let object = object as? NextcloudItemMetadataTable {
return self.fileId == object.fileId &&
self.account == object.account &&
self.path == object.path &&
self.fileName == object.fileName
return fileId == object.fileId && account == object.account && path == object.path
&& fileName == object.fileName
}
return false
}
func isInSameDatabaseStoreableRemoteState(_ comparingMetadata: NextcloudItemMetadataTable) -> Bool {
return comparingMetadata.etag == self.etag &&
comparingMetadata.fileNameView == self.fileNameView &&
comparingMetadata.date == self.date &&
comparingMetadata.permissions == self.permissions &&
comparingMetadata.hasPreview == self.hasPreview &&
comparingMetadata.note == self.note &&
comparingMetadata.lock == self.lock &&
comparingMetadata.sharePermissionsCollaborationServices == self.sharePermissionsCollaborationServices &&
comparingMetadata.favorite == self.favorite
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 {
return !lock || (lockOwner == user && lockOwnerType == 0)
!lock || (lockOwner == user && lockOwnerType == 0)
}
func thumbnailUrl(size: CGSize) -> URL? {
@ -203,11 +207,13 @@ class NextcloudItemMetadataTable: Object {
}
let urlBase = urlBase.urlEncoded!
let webdavUrl = urlBase + NextcloudAccount.webDavFilesUrlSuffix + user // Leave the leading slash
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"
// 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)
}
}

View file

@ -17,11 +17,14 @@ import OSLog
extension Logger {
private static var subsystem = Bundle.main.bundleIdentifier!
static let desktopClientConnection = Logger(subsystem: subsystem, category: "desktopclientconnection")
static let desktopClientConnection = Logger(
subsystem: subsystem, category: "desktopclientconnection")
static let enumeration = Logger(subsystem: subsystem, category: "enumeration")
static let fileProviderExtension = Logger(subsystem: subsystem, category: "fileproviderextension")
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 materialisedFileHandling = Logger(subsystem: subsystem, category: "materialisedfilehandling")
static let materialisedFileHandling = Logger(
subsystem: subsystem, category: "materialisedfilehandling")
}

View file

@ -12,57 +12,49 @@
* for more details.
*/
import Foundation
import FileProvider
import Foundation
import NextcloudKit
extension NKError {
static var noChangesErrorCode: Int {
return -200
-200
}
var isCouldntConnectError: Bool {
return errorCode == -9999 ||
errorCode == -1001 ||
errorCode == -1004 ||
errorCode == -1005 ||
errorCode == -1009 ||
errorCode == -1012 ||
errorCode == -1200 ||
errorCode == -1202 ||
errorCode == 500 ||
errorCode == 503 ||
errorCode == 200
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 {
return errorCode == -1013
errorCode == -1013
}
var isGoingOverQuotaError: Bool {
return errorCode == 507
errorCode == 507
}
var isNotFoundError: Bool {
return errorCode == 404
errorCode == 404
}
var isNoChangesError: Bool {
return errorCode == NKError.noChangesErrorCode
errorCode == NKError.noChangesErrorCode
}
var fileProviderError: NSFileProviderError {
if isNotFoundError {
return NSFileProviderError(.noSuchItem)
NSFileProviderError(.noSuchItem)
} else if isCouldntConnectError {
// Provide something the file provider can do something with
return NSFileProviderError(.serverUnreachable)
NSFileProviderError(.serverUnreachable)
} else if isUnauthenticatedError {
return NSFileProviderError(.notAuthenticated)
NSFileProviderError(.notAuthenticated)
} else if isGoingOverQuotaError {
return NSFileProviderError(.insufficientQuota)
NSFileProviderError(.insufficientQuota)
} else {
return NSFileProviderError(.cannotSynchronize)
NSFileProviderError(.cannotSynchronize)
}
}
}

View file

@ -12,39 +12,39 @@
* for more details.
*/
import Foundation
import Alamofire
import Foundation
extension Progress {
func setHandlersFromAfRequest(_ request: Request) {
self.cancellationHandler = { request.cancel() }
self.pausingHandler = { request.suspend() }
self.resumingHandler = { request.resume() }
cancellationHandler = { request.cancel() }
pausingHandler = { request.suspend() }
resumingHandler = { request.resume() }
}
func copyCurrentStateToProgress(_ otherProgress: Progress, includeHandlers: Bool = false) {
if includeHandlers {
otherProgress.cancellationHandler = self.cancellationHandler
otherProgress.pausingHandler = self.pausingHandler
otherProgress.resumingHandler = self.resumingHandler
otherProgress.cancellationHandler = cancellationHandler
otherProgress.pausingHandler = pausingHandler
otherProgress.resumingHandler = resumingHandler
}
otherProgress.totalUnitCount = self.totalUnitCount
otherProgress.completedUnitCount = self.completedUnitCount
otherProgress.estimatedTimeRemaining = self.estimatedTimeRemaining
otherProgress.localizedDescription = self.localizedAdditionalDescription
otherProgress.localizedAdditionalDescription = self.localizedAdditionalDescription
otherProgress.isCancellable = self.isCancellable
otherProgress.isPausable = self.isPausable
otherProgress.fileCompletedCount = self.fileCompletedCount
otherProgress.fileURL = self.fileURL
otherProgress.fileTotalCount = self.fileTotalCount
otherProgress.fileCompletedCount = self.fileCompletedCount
otherProgress.fileOperationKind = self.fileOperationKind
otherProgress.kind = self.kind
otherProgress.throughput = self.throughput
for (key, object) in self.userInfo {
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)
}
}

View file

@ -17,27 +17,32 @@ 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) {
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)
let dispatchQueue = DispatchQueue(
label: "recursiveChangeEnumerationQueue", qos: .userInitiated)
dispatchQueue.async {
let results = self.scanRecursively(rootContainerDirectoryMetadata,
ncAccount: ncAccount,
ncKit: ncKit,
scanChangesOnly: scanChangesOnly)
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;
@ -45,29 +50,38 @@ extension FileProviderEnumerator {
var checkedDeletedMetadatas = results.deletedMetadatas
for updatedMetadata in results.updatedMetadatas {
guard let matchingDeletedMetadataIdx = checkedDeletedMetadatas.firstIndex(where: { $0.ocId == updatedMetadata.ocId } ) else {
continue;
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)
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 self.isInvalidated {
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)
}
@ -80,43 +94,59 @@ extension FileProviderEnumerator {
var allDeletedMetadatas: [NextcloudItemMetadataTable] = []
let dbManager = NextcloudFilesDatabaseManager.shared
let dispatchGroup = DispatchGroup() // TODO: Maybe own thread?
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
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
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)")
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")
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) {
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")
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.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 {
} 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.")
Logger.enumeration.error(
"Error will affect next enumerated items, so stopping enumeration.")
criticalError = nkReadError
}
dispatchGroup.leave()
@ -124,30 +154,40 @@ extension FileProviderEnumerator {
}
}
Logger.enumeration.info("Finished reading serverUrl: \(itemServerUrl, privacy: .public) for user: \(ncAccount.ncKitAccount, privacy: .public)")
Logger.enumeration.info(
"Finished reading serverUrl: \(itemServerUrl, privacy: .public) for user: \(ncAccount.ncKitAccount, privacy: .public)"
)
if let metadatas = metadatas {
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)")
Logger.enumeration.warning(
"WARNING: Nil metadatas received for reading of changes at \(itemServerUrl, privacy: .public) for user: \(ncAccount.ncKitAccount, privacy: .public)"
)
}
if let newMetadatas = newMetadatas {
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)")
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 = updatedMetadatas {
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)")
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 = deletedMetadatas {
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)")
Logger.enumeration.warning(
"WARNING: Nil deleted metadatas received for reading of changes at \(itemServerUrl, privacy: .public) for user: \(ncAccount.ncKitAccount, privacy: .public)"
)
}
dispatchGroup.leave()
@ -160,13 +200,12 @@ extension FileProviderEnumerator {
}
var childDirectoriesToScan: [NextcloudItemMetadataTable] = []
var candidateMetadatas: [NextcloudItemMetadataTable]
if scanChangesOnly {
candidateMetadatas = allUpdatedMetadatas + allNewMetadatas
} else {
candidateMetadatas = allMetadatas
}
var candidateMetadatas: [NextcloudItemMetadataTable] =
if scanChangesOnly {
allUpdatedMetadatas + allNewMetadatas
} else {
allMetadatas
}
for candidateMetadata in candidateMetadatas {
if candidateMetadata.directory {
@ -175,14 +214,18 @@ extension FileProviderEnumerator {
}
if childDirectoriesToScan.isEmpty {
return (metadatas: allMetadatas, newMetadatas: allNewMetadatas, updatedMetadatas: allUpdatedMetadatas, deletedMetadatas: allDeletedMetadatas, nil)
return (
metadatas: allMetadatas, newMetadatas: allNewMetadatas,
updatedMetadatas: allUpdatedMetadatas, deletedMetadatas: allDeletedMetadatas, nil
)
}
for childDirectory in childDirectoriesToScan {
let childScanResult = scanRecursively(childDirectory,
ncAccount: ncAccount,
ncKit: ncKit,
scanChangesOnly: scanChangesOnly)
let childScanResult = scanRecursively(
childDirectory,
ncAccount: ncAccount,
ncKit: ncKit,
scanChangesOnly: scanChangesOnly)
allMetadatas += childScanResult.metadatas
allNewMetadatas += childScanResult.newMetadatas
@ -190,31 +233,44 @@ extension FileProviderEnumerator {
allDeletedMetadatas += childScanResult.deletedMetadatas
}
return (metadatas: allMetadatas, newMetadatas: allNewMetadatas, updatedMetadatas: allUpdatedMetadatas, deletedMetadatas: allDeletedMetadatas, nil)
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) {
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)")
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)")
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, childDirectoriesMetadata, metadatas in
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
@ -228,64 +284,88 @@ extension FileProviderEnumerator {
// 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)
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)
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) {
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)")
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
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)")
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...")
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
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 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.")
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 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)
let metadatas = dbManager.itemMetadatas(
account: ncKitAccount, serverUrl: serverUrl)
completionHandler(metadatas, nil, nil, nil, nkError.error)
return
@ -294,7 +374,8 @@ extension FileProviderEnumerator {
if depth == "0" {
if serverUrl != ncAccount.davFilesUrl {
let metadata = NextcloudItemMetadataTable.fromNKFile(receivedFile, account: ncKitAccount)
let metadata = NextcloudItemMetadataTable.fromNKFile(
receivedFile, account: ncKitAccount)
let isNew = dbManager.itemMetadataFromOcId(metadata.ocId) == nil
let updatedMetadatas = isNew ? [] : [metadata]
let newMetadatas = isNew ? [metadata] : []
@ -306,7 +387,9 @@ extension FileProviderEnumerator {
}
}
} else {
handleDepth1ReadFileOrFolder(serverUrl: serverUrl, ncAccount: ncAccount, files: files, error: error, completionHandler: completionHandler)
handleDepth1ReadFileOrFolder(
serverUrl: serverUrl, ncAccount: ncAccount, files: files, error: error,
completionHandler: completionHandler)
}
}
}

View file

@ -17,13 +17,14 @@ import NextcloudKit
import OSLog
class FileProviderEnumerator: NSObject, NSFileProviderEnumerator {
private let enumeratedItemIdentifier: NSFileProviderItemIdentifier
private var enumeratedItemMetadata: NextcloudItemMetadataTable?
private var enumeratingSystemIdentifier: Bool {
return FileProviderEnumerator.isSystemIdentifier(enumeratedItemIdentifier)
FileProviderEnumerator.isSystemIdentifier(enumeratedItemIdentifier)
}
private let anchor = NSFileProviderSyncAnchor(Date().description.data(using: .utf8)!) // TODO: actually use this in NCKit and server requests
// 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
@ -31,59 +32,79 @@ class FileProviderEnumerator: NSObject, NSFileProviderEnumerator {
var isInvalidated = false
private static func isSystemIdentifier(_ identifier: NSFileProviderItemIdentifier) -> Bool {
return identifier == .rootContainer ||
identifier == .trashContainer ||
identifier == .workingSet
identifier == .rootContainer || identifier == .trashContainer || identifier == .workingSet
}
init(enumeratedItemIdentifier: NSFileProviderItemIdentifier, ncAccount: NextcloudAccount, ncKit: NextcloudKit) {
init(
enumeratedItemIdentifier: NSFileProviderItemIdentifier,
ncAccount: NextcloudAccount,
ncKit: NextcloudKit
) {
self.enumeratedItemIdentifier = enumeratedItemIdentifier
self.ncAccount = ncAccount
self.ncKit = ncKit
if FileProviderEnumerator.isSystemIdentifier(enumeratedItemIdentifier) {
Logger.enumeration.debug("Providing enumerator for a system defined container: \(enumeratedItemIdentifier.rawValue, privacy: .public)")
self.serverUrl = ncAccount.davFilesUrl
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)")
Logger.enumeration.debug(
"Providing enumerator for item with identifier: \(enumeratedItemIdentifier.rawValue, privacy: .public)"
)
let dbManager = NextcloudFilesDatabaseManager.shared
enumeratedItemMetadata = dbManager.itemMetadataFromFileProviderItemIdentifier(enumeratedItemIdentifier)
enumeratedItemMetadata = dbManager.itemMetadataFromFileProviderItemIdentifier(
enumeratedItemIdentifier)
if enumeratedItemMetadata != nil {
self.serverUrl = enumeratedItemMetadata!.serverUrl + "/" + enumeratedItemMetadata!.fileName
serverUrl =
enumeratedItemMetadata!.serverUrl + "/" + enumeratedItemMetadata!.fileName
} else {
Logger.enumeration.error("Could not find itemMetadata for file with identifier: \(enumeratedItemIdentifier.rawValue, privacy: .public)")
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)")
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)")
self.isInvalidated = true
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)")
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)")
Logger.enumeration.debug(
"Enumerating trash set for user: \(self.ncAccount.ncKitAccount, privacy: .public) with serverUrl: \(self.serverUrl, privacy: .public)"
)
// TODO!
observer.finishEnumerating(upTo: nil)
@ -98,105 +119,140 @@ class FileProviderEnumerator: NSObject, NSFileProviderEnumerator {
// 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)")
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 {
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)"
)
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
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)")
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 = metadatas else {
Logger.enumeration.error("Finishing enumeration for user: \(self.ncAccount.ncKitAccount, privacy: .public) with serverUrl: \(self.serverUrl, privacy: .public) with invalid metadatas.")
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")
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)
FileProviderEnumerator.completeEnumerationObserver(
observer, ncKit: self.ncKit, numPage: 1, itemMetadatas: metadatas)
}
return;
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)")
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)")
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)")
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: self.ncAccount,
ncKit: self.ncKit,
scanChangesOnly: true) { _, newMetadatas, updatedMetadatas, deletedMetadatas, error in
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)")
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)")
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.")
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)
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)")
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)")
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
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] = []
@ -205,46 +261,70 @@ class FileProviderEnumerator: NSObject, NSFileProviderEnumerator {
}
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)")
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")
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")
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) {
if let deletedDirectoryMetadatas =
dbManager.deleteDirectoryAndSubdirectoriesMetadata(
ocId: itemMetadata.ocId)
{
currentDeletedMetadatas += deletedDirectoryMetadatas
} else {
Logger.enumeration.error("Something went wrong when recursively deleting directory not found.")
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])
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.")
} 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;
return
}
observer.finishEnumeratingWithError(fpError)
return
}
Logger.enumeration.info("Finished reading serverUrl: \(self.serverUrl, privacy: .public) for user: \(self.ncAccount.ncKitAccount, privacy: .public)")
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)
FileProviderEnumerator.completeChangesObserver(
observer,
anchor: anchor,
ncKit: self.ncKit,
newMetadatas: newMetadatas,
updatedMetadatas: updatedMetadatas,
deletedMetadatas: deletedMetadatas)
}
}
@ -254,29 +334,43 @@ class FileProviderEnumerator: NSObject, NSFileProviderEnumerator {
// MARK: - Helper methods
private static func metadatasToFileProviderItems(_ itemMetadatas: [NextcloudItemMetadataTable], ncKit: NextcloudKit, completionHandler: @escaping(_ items: [NSFileProviderItem]) -> Void) {
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 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)")
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)")
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")
Logger.enumeration.error(
"Could not get valid parentItemIdentifier for item with ocId: \(itemMetadata.ocId, privacy: .public) and name: \(itemMetadata.fileName, privacy: .public), skipping enumeration"
)
}
}
}
@ -287,11 +381,13 @@ class FileProviderEnumerator: NSObject, NSFileProviderEnumerator {
}
private static func fileProviderPageforNumPage(_ numPage: Int) -> NSFileProviderPage {
return NSFileProviderPage("\(numPage)".data(using: .utf8)!)
NSFileProviderPage("\(numPage)".data(using: .utf8)!)
}
private static func completeEnumerationObserver(_ observer: NSFileProviderEnumerationObserver, ncKit: NextcloudKit, numPage: Int, itemMetadatas: [NextcloudItemMetadataTable]) {
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")
@ -310,10 +406,17 @@ class FileProviderEnumerator: NSObject, NSFileProviderEnumerator {
}
}
private static func completeChangesObserver(_ observer: NSFileProviderChangeObserver, anchor: NSFileProviderSyncAnchor, ncKit: NextcloudKit, newMetadatas: [NextcloudItemMetadataTable]?, updatedMetadatas: [NextcloudItemMetadataTable]?, deletedMetadatas: [NextcloudItemMetadataTable]?) {
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.")
Logger.enumeration.error(
"Received invalid newMetadatas, updatedMetadatas or deletedMetadatas. Finished enumeration of changes with error."
)
observer.finishEnumeratingWithError(NSFileProviderError(.noSuchItem))
return
}
@ -322,19 +425,20 @@ class FileProviderEnumerator: NSObject, NSFileProviderEnumerator {
var allUpdatedMetadatas: [NextcloudItemMetadataTable] = []
var allDeletedMetadatas: [NextcloudItemMetadataTable] = []
if let newMetadatas = newMetadatas {
if let newMetadatas {
allUpdatedMetadatas += newMetadatas
}
if let updatedMetadatas = updatedMetadatas {
if let updatedMetadatas {
allUpdatedMetadatas += updatedMetadatas
}
if let deletedMetadatas = deletedMetadatas {
if let deletedMetadatas {
allDeletedMetadatas = deletedMetadatas
}
let allFpItemDeletionsIdentifiers = Array(allDeletedMetadatas.map { NSFileProviderItemIdentifier($0.ocId) })
let allFpItemDeletionsIdentifiers = Array(
allDeletedMetadatas.map { NSFileProviderItemIdentifier($0.ocId) })
if !allFpItemDeletionsIdentifiers.isEmpty {
observer.didDeleteItems(withIdentifiers: allFpItemDeletionsIdentifiers)
}
@ -345,7 +449,9 @@ class FileProviderEnumerator: NSObject, NSFileProviderEnumerator {
observer.didUpdate(updatedItems)
}
Logger.enumeration.info("Processed \(updatedItems.count) new or updated metadatas, \(allDeletedMetadatas.count) deleted metadatas.")
Logger.enumeration.info(
"Processed \(updatedItems.count) new or updated metadatas, \(allDeletedMetadatas.count) deleted metadatas."
)
observer.finishEnumeratingChanges(upTo: anchor, moreComing: false)
}
}

View file

@ -12,11 +12,11 @@
* for more details.
*/
import Foundation
import FileProvider
import OSLog
import Foundation
import NCDesktopClientSocketKit
import NextcloudKit
import OSLog
extension FileProviderExtension {
func sendFileProviderDomainIdentifier() {
@ -28,7 +28,9 @@ extension FileProviderExtension {
private func signalEnumeratorAfterAccountSetup() {
guard let fpManager = NSFileProviderManager(for: domain) else {
Logger.fileProviderExtension.error("Could not get file provider manager for domain \(self.domain.displayName, privacy: .public), cannot notify after account setup")
Logger.fileProviderExtension.error(
"Could not get file provider manager for domain \(self.domain.displayName, privacy: .public), cannot notify after account setup"
)
return
}
@ -36,36 +38,47 @@ extension FileProviderExtension {
fpManager.signalErrorResolved(NSFileProviderError(.notAuthenticated)) { error in
if error != nil {
Logger.fileProviderExtension.error("Error resolving not authenticated, received error: \(error!.localizedDescription)")
Logger.fileProviderExtension.error(
"Error resolving not authenticated, received error: \(error!.localizedDescription)"
)
}
}
Logger.fileProviderExtension.debug("Signalling enumerators for user \(self.ncAccount!.username) at server \(self.ncAccount!.serverUrl, privacy: .public)")
Logger.fileProviderExtension.debug(
"Signalling enumerators for user \(self.ncAccount!.username) at server \(self.ncAccount!.serverUrl, privacy: .public)"
)
fpManager.signalEnumerator(for: .workingSet) { error in
if error != nil {
Logger.fileProviderExtension.error("Error signalling enumerator for working set, received error: \(error!.localizedDescription, privacy: .public)")
Logger.fileProviderExtension.error(
"Error signalling enumerator for working set, received error: \(error!.localizedDescription, privacy: .public)"
)
}
}
}
func setupDomainAccount(user: String, serverUrl: String, password: String) {
ncAccount = NextcloudAccount(user: user, serverUrl: serverUrl, password: password)
ncKit.setup(user: ncAccount!.username,
userId: ncAccount!.username,
password: ncAccount!.password,
urlBase: ncAccount!.serverUrl,
userAgent: "Nextcloud-macOS/FileProviderExt",
nextcloudVersion: 25,
delegate: nil) // TODO: add delegate methods for self
ncKit.setup(
user: ncAccount!.username,
userId: ncAccount!.username,
password: ncAccount!.password,
urlBase: ncAccount!.serverUrl,
userAgent: "Nextcloud-macOS/FileProviderExt",
nextcloudVersion: 25,
delegate: nil) // TODO: add delegate methods for self
Logger.fileProviderExtension.info("Nextcloud account set up in File Provider extension for user: \(user, privacy: .public) at server: \(serverUrl, privacy: .public)")
Logger.fileProviderExtension.info(
"Nextcloud account set up in File Provider extension for user: \(user, privacy: .public) at server: \(serverUrl, privacy: .public)"
)
signalEnumeratorAfterAccountSetup()
}
func removeAccountConfig() {
Logger.fileProviderExtension.info("Received instruction to remove account data for user \(self.ncAccount!.username, privacy: .public) at server \(self.ncAccount!.serverUrl, privacy: .public)")
Logger.fileProviderExtension.info(
"Received instruction to remove account data for user \(self.ncAccount!.username, privacy: .public) at server \(self.ncAccount!.serverUrl, privacy: .public)"
)
ncAccount = nil
}
}

View file

@ -12,19 +12,22 @@
* for more details.
*/
import Foundation
import FileProvider
import Foundation
import NextcloudKit
import OSLog
extension FileProviderExtension: NSFileProviderThumbnailing {
func fetchThumbnails(for itemIdentifiers: [NSFileProviderItemIdentifier],
requestedSize size: CGSize,
perThumbnailCompletionHandler: @escaping (NSFileProviderItemIdentifier,
Data?,
Error?) -> Void,
completionHandler: @escaping (Error?) -> Void) -> Progress {
func fetchThumbnails(
for itemIdentifiers: [NSFileProviderItemIdentifier],
requestedSize size: CGSize,
perThumbnailCompletionHandler: @escaping (
NSFileProviderItemIdentifier,
Data?,
Error?
) -> Void,
completionHandler: @escaping (Error?) -> Void
) -> Progress {
let progress = Progress(totalUnitCount: Int64(itemIdentifiers.count))
var progressCounter: Int64 = 0
@ -37,21 +40,29 @@ extension FileProviderExtension: NSFileProviderThumbnailing {
}
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(
"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)")
Logger.fileProviderExtension.debug(
"Fetching thumbnail for file:\(metadata.fileName) at:\(thumbnailUrl.absoluteString, privacy: .public)"
)
self.ncKit.getPreview(url: thumbnailUrl) { _, data, error in
if error == .success && data != nil {
ncKit.getPreview(url: thumbnailUrl) { _, data, error in
if error == .success, data != nil {
perThumbnailCompletionHandler(itemIdentifier, data, nil)
} else {
perThumbnailCompletionHandler(itemIdentifier, nil, NSFileProviderError(.serverUnreachable))
perThumbnailCompletionHandler(
itemIdentifier, nil, NSFileProviderError(.serverUnreachable))
}
finishCurrent()
}

View file

@ -13,29 +13,24 @@
*/
import FileProvider
import OSLog
import NCDesktopClientSocketKit
import NextcloudKit
import OSLog
class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension, NKCommonDelegate {
let domain: NSFileProviderDomain
let ncKit = NextcloudKit()
lazy var ncKitBackground: NKBackground = {
let nckb = NKBackground(nkCommonInstance: ncKit.nkCommonInstance)
return nckb
}()
let appGroupIdentifier: String? = Bundle.main.object(forInfoDictionaryKey: "SocketApiPrefix") as? String
let appGroupIdentifier = Bundle.main.object(forInfoDictionaryKey: "SocketApiPrefix") as? String
var ncAccount: NextcloudAccount?
lazy var ncKitBackground = NKBackground(nkCommonInstance: ncKit.nkCommonInstance)
lazy var socketClient: LocalSocketClient? = {
guard let containerUrl = pathForAppGroupContainer() else {
Logger.fileProviderExtension.critical("Could not start file provider socket client properly as could not get container url")
return nil;
Logger.fileProviderExtension.critical("Won't start client, no container url")
return nil
}
let socketPath = containerUrl.appendingPathComponent(".fileprovidersocket", conformingTo: .archive)
let socketPath = containerUrl.appendingPathComponent(
".fileprovidersocket", conformingTo: .archive)
let lineProcessor = FileProviderSocketLineProcessor(delegate: self)
return LocalSocketClient(socketPath: socketPath.path, lineProcessor: lineProcessor)
}()
@ -50,32 +45,44 @@ class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension, NKComm
configuration.requestCachePolicy = NSURLRequest.CachePolicy.reloadIgnoringLocalCacheData
configuration.sharedContainerIdentifier = appGroupIdentifier
let session = URLSession(configuration: configuration, delegate: ncKitBackground, delegateQueue: OperationQueue.main)
let session = URLSession(
configuration: configuration, delegate: ncKitBackground,
delegateQueue: OperationQueue.main)
return session
}()
required init(domain: NSFileProviderDomain) {
// The containing application must create a domain using
// `NSFileProviderManager.add(_:, completionHandler:)`. The system will then launch the
// application extension process, call `FileProviderExtension.init(domain:)` to instantiate
// the extension for that domain, and call methods on the instance.
self.domain = domain
// The containing application must create a domain using `NSFileProviderManager.add(_:, completionHandler:)`. The system will then launch the application extension process, call `FileProviderExtension.init(domain:)` to instantiate the extension for that domain, and call methods on the instance.
super.init()
self.socketClient?.start()
socketClient?.start()
}
func invalidate() {
// TODO: cleanup any resources
Logger.fileProviderExtension.debug("Extension for domain \(self.domain.displayName, privacy: .public) is being torn down")
Logger.fileProviderExtension.debug(
"Extension for domain \(self.domain.displayName, privacy: .public) is being torn down")
}
// MARK: NSFileProviderReplicatedExtension protocol methods
func item(for identifier: NSFileProviderItemIdentifier, request: NSFileProviderRequest, completionHandler: @escaping (NSFileProviderItem?, Error?) -> Void) -> Progress {
func item(
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)")
Logger.fileProviderExtension.debug(
"Received item request for item with identifier: \(identifier.rawValue, privacy: .public)"
)
if identifier == .rootContainer {
guard let ncAccount = ncAccount else {
Logger.fileProviderExtension.error("Not providing item: \(identifier.rawValue, privacy: .public) as account not set up yet")
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()
}
@ -90,35 +97,52 @@ class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension, NKComm
metadata.serverUrl = ncAccount.serverUrl
metadata.classFile = NKCommon.TypeClassFile.directory.rawValue
completionHandler(FileProviderItem(metadata: metadata, parentItemIdentifier: NSFileProviderItemIdentifier.rootContainer, ncKit: ncKit), nil)
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 {
let parentItemIdentifier = dbManager.parentItemIdentifierFromMetadata(metadata)
else {
completionHandler(nil, NSFileProviderError(.noSuchItem))
return Progress()
}
completionHandler(FileProviderItem(metadata: metadata, parentItemIdentifier: parentItemIdentifier, ncKit: ncKit), nil)
completionHandler(
FileProviderItem(
metadata: metadata, parentItemIdentifier: parentItemIdentifier, ncKit: ncKit), nil)
return Progress()
}
func fetchContents(for itemIdentifier: NSFileProviderItemIdentifier, version requestedVersion: NSFileProviderItemVersion?, request: NSFileProviderRequest, completionHandler: @escaping (URL?, NSFileProviderItem?, Error?) -> Void) -> Progress {
Logger.fileProviderExtension.debug("Received request to fetch contents of item with identifier: \(itemIdentifier.rawValue, privacy: .public)")
func fetchContents(
for itemIdentifier: NSFileProviderItemIdentifier,
version requestedVersion: NSFileProviderItemVersion?, request: NSFileProviderRequest,
completionHandler: @escaping (URL?, NSFileProviderItem?, Error?) -> Void
) -> Progress {
Logger.fileProviderExtension.debug(
"Received request to fetch contents of item with identifier: \(itemIdentifier.rawValue, privacy: .public)"
)
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.")
completionHandler(nil, nil, NSError(domain: NSCocoaErrorDomain, code: NSFeatureUnsupportedError, userInfo:[:]))
Logger.fileProviderExtension.error(
"Can't return contents for specific version as this is not supported.")
completionHandler(
nil, nil,
NSError(domain: NSCocoaErrorDomain, code: NSFeatureUnsupportedError, userInfo: [:]))
return Progress()
}
guard ncAccount != nil else {
Logger.fileProviderExtension.error("Not fetching contents item: \(itemIdentifier.rawValue, privacy: .public) as account not set up yet")
Logger.fileProviderExtension.error(
"Not fetching contents item: \(itemIdentifier.rawValue, privacy: .public) as account not set up yet"
)
completionHandler(nil, nil, NSFileProviderError(.notAuthenticated))
return Progress()
}
@ -126,46 +150,65 @@ class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension, NKComm
let dbManager = NextcloudFilesDatabaseManager.shared
let ocId = itemIdentifier.rawValue
guard let metadata = dbManager.itemMetadataFromOcId(ocId) else {
Logger.fileProviderExtension.error("Could not acquire metadata of item with identifier: \(itemIdentifier.rawValue, privacy: .public)")
Logger.fileProviderExtension.error(
"Could not acquire metadata of item with identifier: \(itemIdentifier.rawValue, privacy: .public)"
)
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)")
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)")
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: self.domain)
let fileNameLocalPath = try localPathForNCFile(
ocId: metadata.ocId, fileNameView: metadata.fileNameView, domain: domain)
dbManager.setStatusForItemMetadata(metadata, status: NextcloudItemMetadataTable.Status.downloading) { updatedMetadata in
dbManager.setStatusForItemMetadata(
metadata, status: NextcloudItemMetadataTable.Status.downloading
) { updatedMetadata in
guard let updatedMetadata = 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")
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
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)")
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 = ""
@ -175,18 +218,26 @@ class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension, NKComm
dbManager.addLocalFileMetadataFromItemMetadata(updatedMetadata)
dbManager.addItemMetadata(updatedMetadata)
guard let parentItemIdentifier = dbManager.parentItemIdentifierFromMetadata(updatedMetadata) else {
guard
let parentItemIdentifier = dbManager.parentItemIdentifierFromMetadata(
updatedMetadata)
else {
completionHandler(nil, nil, NSFileProviderError(.noSuchItem))
return
}
let fpItem = FileProviderItem(metadata: updatedMetadata, parentItemIdentifier: parentItemIdentifier, ncKit: self.ncKit)
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)")
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.status =
NextcloudItemMetadataTable.Status.downloadError.rawValue
updatedMetadata.sessionError = error.errorDescription
dbManager.addItemMetadata(updatedMetadata)
@ -195,40 +246,60 @@ class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension, NKComm
}
}
}
} catch let error {
Logger.fileProviderExtension.error("Could not find local path for file \(metadata.fileName, privacy: .public), received error: \(error.localizedDescription, privacy: .public)")
} catch {
Logger.fileProviderExtension.error(
"Could not find local path for file \(metadata.fileName, privacy: .public), received error: \(error.localizedDescription, privacy: .public)"
)
completionHandler(nil, nil, NSFileProviderError(.cannotSynchronize))
}
return progress
}
func createItem(basedOn itemTemplate: NSFileProviderItem, fields: NSFileProviderItemFields, contents url: URL?, options: NSFileProviderCreateItemOptions = [], request: NSFileProviderRequest, completionHandler: @escaping (NSFileProviderItem?, NSFileProviderItemFields, Bool, Error?) -> Void) -> Progress {
func createItem(
basedOn itemTemplate: NSFileProviderItem, fields _: NSFileProviderItemFields,
contents url: URL?, options: NSFileProviderCreateItemOptions = [],
request: NSFileProviderRequest,
completionHandler: @escaping (NSFileProviderItem?, NSFileProviderItemFields, Bool, Error?)
->
Void
) -> Progress {
// TODO: a new item was created on disk, process the item's creation
Logger.fileProviderExtension.debug("Received create item request for item with identifier: \(itemTemplate.itemIdentifier.rawValue, privacy: .public) and filename: \(itemTemplate.filename, privacy: .public)")
Logger.fileProviderExtension.debug(
"Received create item request for item with identifier: \(itemTemplate.itemIdentifier.rawValue, 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:[:]))
completionHandler(
itemTemplate, NSFileProviderItemFields(), false,
NSError(domain: NSCocoaErrorDomain, code: NSFeatureUnsupportedError, userInfo: [:]))
return Progress()
}
guard let ncAccount = ncAccount else {
Logger.fileProviderExtension.error("Not creating item: \(itemTemplate.itemIdentifier.rawValue, privacy: .public) as account not set up yet")
completionHandler(itemTemplate, NSFileProviderItemFields(), false, NSFileProviderError(.notAuthenticated))
guard let ncAccount else {
Logger.fileProviderExtension.error(
"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
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")
completionHandler(itemTemplate, NSFileProviderItemFields(), false, NSFileProviderError(.noSuchItem))
Logger.fileProviderExtension.info(
"Not creating item: \(itemTemplate.itemIdentifier.rawValue, privacy: .public) as it may already exist"
)
completionHandler(
itemTemplate, NSFileProviderItemFields(), false, NSFileProviderError(.noSuchItem))
return Progress()
}
@ -237,9 +308,16 @@ class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension, NKComm
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))
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()
}
@ -249,29 +327,43 @@ class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension, NKComm
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)")
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 {
self.ncKit.createFolder(serverUrlFileName: newServerUrlFileName) { account, ocId, _, error in
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)")
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
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)")
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, childDirectoriesMetadata, metadatas in
NextcloudItemMetadataTable.metadatasFromDirectoryReadNKFiles(
files, account: account
) {
directoryMetadata, _, _ in
dbManager.addItemMetadata(directoryMetadata)
let fpItem = FileProviderItem(metadata: directoryMetadata, parentItemIdentifier: parentItemIdentifier, ncKit: self.ncKit)
let fpItem = FileProviderItem(
metadata: directoryMetadata,
parentItemIdentifier: parentItemIdentifier,
ncKit: self.ncKit)
completionHandler(fpItem, [], true, nil)
}
@ -284,25 +376,37 @@ class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension, NKComm
let progress = Progress()
self.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 = ocId else {
Logger.fileTransfer.error("Could not upload item with filename: \(itemTemplate.filename, privacy: .public), received error: \(error.errorDescription, privacy: .public)")
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)")
Logger.fileTransfer.info(
"Successfully uploaded item with identifier: \(ocId, privacy: .public) and filename: \(itemTemplate.filename, privacy: .public)"
)
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))")
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))"
)
}
let newMetadata = NextcloudItemMetadataTable()
@ -324,34 +428,48 @@ class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension, NKComm
dbManager.addLocalFileMetadataFromItemMetadata(newMetadata)
dbManager.addItemMetadata(newMetadata)
let fpItem = FileProviderItem(metadata: newMetadata, parentItemIdentifier: parentItemIdentifier, ncKit: self.ncKit)
let fpItem = FileProviderItem(
metadata: newMetadata, parentItemIdentifier: parentItemIdentifier, ncKit: self.ncKit
)
completionHandler(fpItem, [], false, nil)
}
return progress
}
func modifyItem(_ item: NSFileProviderItem, baseVersion version: NSFileProviderItemVersion, changedFields: NSFileProviderItemFields, contents newContents: URL?, options: NSFileProviderModifyItemOptions = [], request: NSFileProviderRequest, completionHandler: @escaping (NSFileProviderItem?, NSFileProviderItemFields, Bool, Error?) -> Void) -> Progress {
func modifyItem(
_ 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
Logger.fileProviderExtension.debug("Received modify item request for item with identifier: \(item.itemIdentifier.rawValue, privacy: .public) and filename: \(item.filename, privacy: .public)")
Logger.fileProviderExtension.debug(
"Received modify item request for item with identifier: \(item.itemIdentifier.rawValue, privacy: .public) and filename: \(item.filename, privacy: .public)"
)
guard let ncAccount = ncAccount else {
Logger.fileProviderExtension.error("Not modifying item: \(item.itemIdentifier.rawValue, privacy: .public) as account not set up yet")
guard let ncAccount else {
Logger.fileProviderExtension.error(
"Not modifying item: \(item.itemIdentifier.rawValue, 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
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")
Logger.fileProviderExtension.warning(
"Modification for item: \(item.itemIdentifier.rawValue, privacy: .public) may already exist"
)
}
var parentItemServerUrl: String
@ -359,8 +477,13 @@ class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension, NKComm
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)")
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()
}
@ -371,7 +494,9 @@ class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension, NKComm
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)")
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
@ -384,10 +509,14 @@ class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension, NKComm
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...")
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)")
Logger.fileProviderExtension.error(
"Could not acquire metadata of item with identifier: \(item.itemIdentifier.rawValue, privacy: .public)"
)
completionHandler(item, [], false, NSFileProviderError(.noSuchItem))
return
}
@ -395,14 +524,18 @@ class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension, NKComm
var renameError: NSFileProviderError?
let oldServerUrlFileName = metadata.serverUrl + "/" + metadata.fileName
let moveFileOrFolderDispatchGroup = DispatchGroup() // Make this block wait until done
let moveFileOrFolderDispatchGroup = DispatchGroup() // Make this block wait until done
moveFileOrFolderDispatchGroup.enter()
self.ncKit.moveFileOrFolder(serverUrlFileNameSource: oldServerUrlFileName,
serverUrlFileNameDestination: newServerUrlFileName,
overwrite: false) { account, error in
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)")
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
@ -411,37 +544,49 @@ class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension, NKComm
// 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)
_ = 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)")
Logger.fileTransfer.error(
"Error notifying change in moved directory: \(error)")
}
}
} else {
dbManager.renameItemMetadata(ocId: ocId, newServerUrl: parentItemServerUrl, newFileName: item.filename)
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")
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)
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)")
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)")
Logger.fileTransfer.debug(
"Only handling renaming for folders. ocId: \(ocId, privacy: .public)")
completionHandler(modifiedItem, [], false, nil)
return
}
@ -454,7 +599,9 @@ class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension, NKComm
}
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.")
Logger.fileTransfer.debug(
"System requested modification for folder with ocID \(item.itemIdentifier.rawValue, privacy: .public) (\(newServerUrlFileName, privacy: .public)) of something other than folder name."
)
completionHandler(modifiedItem, [], false, nil)
return Progress()
}
@ -463,41 +610,62 @@ class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension, NKComm
if changedFields.contains(.contents) {
dispatchQueue.async {
Logger.fileProviderExtension.debug("Item modification for \(item.itemIdentifier.rawValue, privacy: .public) \(item.filename, privacy: .public) includes contents")
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)")
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))
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
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")
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 = ocId {
Logger.fileProviderExtension.info("Successfully uploaded item with identifier: \(ocId, privacy: .public) and filename: \(item.filename, privacy: .public)")
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))")
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()
@ -519,10 +687,15 @@ class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension, NKComm
dbManager.addLocalFileMetadataFromItemMetadata(newMetadata)
dbManager.addItemMetadata(newMetadata)
modifiedItem = FileProviderItem(metadata: newMetadata, parentItemIdentifier: parentItemIdentifier, ncKit: self.ncKit)
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)")
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
@ -536,19 +709,28 @@ class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension, NKComm
}
}
} else {
Logger.fileProviderExtension.debug("Nothing more to do with \(item.itemIdentifier.rawValue, privacy: .public) \(item.filename, privacy: .public), modifications complete")
Logger.fileProviderExtension.debug(
"Nothing more to do with \(item.itemIdentifier.rawValue, privacy: .public) \(item.filename, privacy: .public), modifications complete"
)
completionHandler(modifiedItem, [], false, nil)
}
return progress
}
func deleteItem(identifier: NSFileProviderItemIdentifier, baseVersion version: 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)")
func deleteItem(
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)"
)
guard ncAccount != nil else {
Logger.fileProviderExtension.error("Not deleting item: \(identifier.rawValue, privacy: .public) as account not set up yet")
Logger.fileProviderExtension.error(
"Not deleting item: \(identifier.rawValue, privacy: .public) as account not set up yet"
)
completionHandler(NSFileProviderError(.notAuthenticated))
return Progress()
}
@ -566,14 +748,18 @@ class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension, NKComm
return Progress()
}
self.ncKit.deleteFileOrFolder(serverUrlFileName: serverFileNameUrl) { account, error in
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)")
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
}
Logger.fileTransfer.info("Successfully deleted item with identifier: \(identifier.rawValue, privacy: .public) at: \(serverFileNameUrl, privacy: .public)")
Logger.fileTransfer.info(
"Successfully deleted item with identifier: \(identifier.rawValue, privacy: .public) at: \(serverFileNameUrl, privacy: .public)"
)
if itemMetadata.directory {
_ = dbManager.deleteDirectoryAndSubdirectoriesMetadata(ocId: ocId)
@ -589,32 +775,41 @@ class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension, NKComm
return Progress()
}
func enumerator(for containerItemIdentifier: NSFileProviderItemIdentifier, request: NSFileProviderRequest) throws -> NSFileProviderEnumerator {
guard let ncAccount = ncAccount else {
Logger.fileProviderExtension.error("Not providing enumerator for container with identifier \(containerItemIdentifier.rawValue, privacy: .public) yet as account not set up")
func enumerator(
for containerItemIdentifier: NSFileProviderItemIdentifier, request _: NSFileProviderRequest
) throws -> NSFileProviderEnumerator {
guard let ncAccount else {
Logger.fileProviderExtension.error(
"Not providing enumerator for container with identifier \(containerItemIdentifier.rawValue, privacy: .public) yet as account not set up"
)
throw NSFileProviderError(.notAuthenticated)
}
return FileProviderEnumerator(enumeratedItemIdentifier: containerItemIdentifier, ncAccount: ncAccount, ncKit: ncKit)
return FileProviderEnumerator(
enumeratedItemIdentifier: containerItemIdentifier, ncAccount: ncAccount, ncKit: ncKit)
}
func materializedItemsDidChange(completionHandler: @escaping () -> Void) {
guard let ncAccount = self.ncAccount else {
Logger.fileProviderExtension.error("Not purging stale local file metadatas, account not set up")
guard let ncAccount else {
Logger.fileProviderExtension.error(
"Not purging stale local file metadatas, account not set up")
completionHandler()
return
}
guard let fpManager = NSFileProviderManager(for: domain) else {
Logger.fileProviderExtension.error("Could not get file provider manager for domain: \(self.domain.displayName, privacy: .public)")
Logger.fileProviderExtension.error(
"Could not get file provider manager for domain: \(self.domain.displayName, privacy: .public)"
)
completionHandler()
return
}
let materialisedEnumerator = fpManager.enumeratorForMaterializedItems()
let materialisedObserver = FileProviderMaterialisedEnumerationObserver(ncKitAccount: ncAccount.ncKitAccount) { _ in
let materialisedObserver = FileProviderMaterialisedEnumerationObserver(
ncKitAccount: ncAccount.ncKitAccount
) { _ in
completionHandler()
}
let startingPage = NSFileProviderPage(NSFileProviderPage.initialPageSortedByName as Data)
@ -622,9 +817,11 @@ class FileProviderExtension: NSObject, NSFileProviderReplicatedExtension, NKComm
materialisedEnumerator.enumerateItems(for: materialisedObserver, startingAt: startingPage)
}
func signalEnumerator(completionHandler: @escaping(_ error: Error?) -> Void) {
guard let fpManager = NSFileProviderManager(for: self.domain) else {
Logger.fileProviderExtension.error("Could not get file provider manager for domain, could not signal enumerator. This might lead to future conflicts.")
func signalEnumerator(completionHandler: @escaping (_ error: Error?) -> Void) {
guard let fpManager = NSFileProviderManager(for: domain) else {
Logger.fileProviderExtension.error(
"Could not get file provider manager for domain, could not signal enumerator. This might lead to future conflicts."
)
return
}

View file

@ -13,11 +13,10 @@
*/
import FileProvider
import UniformTypeIdentifiers
import NextcloudKit
import UniformTypeIdentifiers
class FileProviderItem: NSObject, NSFileProviderItem {
enum FileProviderItemTransferError: Error {
case downloadError
case uploadError
@ -26,71 +25,78 @@ class FileProviderItem: NSObject, NSFileProviderItem {
let metadata: NextcloudItemMetadataTable
let parentItemIdentifier: NSFileProviderItemIdentifier
let ncKit: NextcloudKit
var itemIdentifier: NSFileProviderItemIdentifier {
return NSFileProviderItemIdentifier(metadata.ocId)
NSFileProviderItemIdentifier(metadata.ocId)
}
var capabilities: NSFileProviderItemCapabilities {
guard !metadata.directory else {
return [ .allowsAddingSubItems,
.allowsContentEnumerating,
.allowsReading,
.allowsDeleting,
.allowsRenaming ]
return [
.allowsAddingSubItems,
.allowsContentEnumerating,
.allowsReading,
.allowsDeleting,
.allowsRenaming,
]
}
guard !metadata.lock else {
return [ .allowsReading ]
return [.allowsReading]
}
return [ .allowsWriting,
.allowsReading,
.allowsDeleting,
.allowsRenaming,
.allowsReparenting ]
return [
.allowsWriting,
.allowsReading,
.allowsDeleting,
.allowsRenaming,
.allowsReparenting,
]
}
var itemVersion: NSFileProviderItemVersion {
NSFileProviderItemVersion(contentVersion: metadata.etag.data(using: .utf8)!,
metadataVersion: metadata.etag.data(using: .utf8)!)
NSFileProviderItemVersion(
contentVersion: metadata.etag.data(using: .utf8)!,
metadataVersion: metadata.etag.data(using: .utf8)!)
}
var filename: String {
return metadata.fileNameView
metadata.fileNameView
}
var contentType: UTType {
if self.itemIdentifier == .rootContainer || metadata.directory {
if itemIdentifier == .rootContainer || metadata.directory {
return .folder
}
let internalType = ncKit.nkCommonInstance.getInternalType(fileName: metadata.fileNameView,
mimeType: "",
directory: metadata.directory)
let internalType = ncKit.nkCommonInstance.getInternalType(
fileName: metadata.fileNameView,
mimeType: "",
directory: metadata.directory)
return UTType(filenameExtension: internalType.ext) ?? .content
}
var documentSize: NSNumber? {
return NSNumber(value: metadata.size)
NSNumber(value: metadata.size)
}
var creationDate: Date? {
return metadata.creationDate as Date
metadata.creationDate as Date
}
var lastUsedDate: Date? {
return metadata.date as Date
metadata.date as Date
}
var contentModificationDate: Date? {
return metadata.date as Date
metadata.date as Date
}
var isDownloaded: Bool {
return metadata.directory || NextcloudFilesDatabaseManager.shared.localFileMetadataFromOcId(metadata.ocId) != nil
metadata.directory
|| NextcloudFilesDatabaseManager.shared.localFileMetadataFromOcId(metadata.ocId) != nil
}
var isDownloading: Bool {
return metadata.status == NextcloudItemMetadataTable.Status.downloading.rawValue
metadata.status == NextcloudItemMetadataTable.Status.downloading.rawValue
}
var downloadingError: Error? {
@ -101,30 +107,37 @@ class FileProviderItem: NSObject, NSFileProviderItem {
}
var isUploaded: Bool {
return NextcloudFilesDatabaseManager.shared.localFileMetadataFromOcId(metadata.ocId) != nil
NextcloudFilesDatabaseManager.shared.localFileMetadataFromOcId(metadata.ocId) != nil
}
var isUploading: Bool {
return metadata.status == NextcloudItemMetadataTable.Status.uploading.rawValue
metadata.status == NextcloudItemMetadataTable.Status.uploading.rawValue
}
var uploadingError: Error? {
if metadata.status == NextcloudItemMetadataTable.Status.uploadError.rawValue {
return FileProviderItemTransferError.uploadError
FileProviderItemTransferError.uploadError
} else {
return nil
nil
}
}
var childItemCount: NSNumber? {
if metadata.directory {
return NSNumber(integerLiteral: NextcloudFilesDatabaseManager.shared.childItemsForDirectory(metadata).count)
NSNumber(
integerLiteral: NextcloudFilesDatabaseManager.shared.childItemsForDirectory(
metadata
).count)
} else {
return nil
nil
}
}
required init(metadata: NextcloudItemMetadataTable, parentItemIdentifier: NSFileProviderItemIdentifier, ncKit: NextcloudKit) {
required init(
metadata: NextcloudItemMetadataTable,
parentItemIdentifier: NSFileProviderItemIdentifier,
ncKit: NextcloudKit
) {
self.metadata = metadata
self.parentItemIdentifier = parentItemIdentifier
self.ncKit = ncKit

View file

@ -12,44 +12,53 @@
* for more details.
*/
import Foundation
import FileProvider
import Foundation
import OSLog
class FileProviderMaterialisedEnumerationObserver : NSObject, NSFileProviderEnumerationObserver {
class FileProviderMaterialisedEnumerationObserver: NSObject, NSFileProviderEnumerationObserver {
let ncKitAccount: String
let completionHandler: (_ deletedOcIds: Set<String>) -> Void
var allEnumeratedItemIds: Set<String> = Set<String>()
var allEnumeratedItemIds: Set<String> = .init()
required init(ncKitAccount: String, completionHandler: @escaping(_ deletedOcIds: Set<String>) -> Void) {
required init(
ncKitAccount: String, completionHandler: @escaping (_ deletedOcIds: Set<String>) -> Void
) {
self.ncKitAccount = ncKitAccount
self.completionHandler = completionHandler
super.init()
}
func didEnumerate(_ updatedItems: [NSFileProviderItemProtocol]) {
let updatedItemsIds = Array(updatedItems.map { $0.itemIdentifier.rawValue })
let updatedItemsIds = Array(updatedItems.map(\.itemIdentifier.rawValue))
for updatedItemsId in updatedItemsIds {
allEnumeratedItemIds.insert(updatedItemsId)
}
}
func finishEnumerating(upTo nextPage: NSFileProviderPage?) {
func finishEnumerating(upTo _: NSFileProviderPage?) {
Logger.materialisedFileHandling.debug("Handling enumerated materialised items.")
FileProviderMaterialisedEnumerationObserver.handleEnumeratedItems(self.allEnumeratedItemIds,
account: self.ncKitAccount,
completionHandler: self.completionHandler)
FileProviderMaterialisedEnumerationObserver.handleEnumeratedItems(
allEnumeratedItemIds,
account: ncKitAccount,
completionHandler: completionHandler)
}
func finishEnumeratingWithError(_ error: Error) {
Logger.materialisedFileHandling.error("Ran into error when enumerating materialised items: \(error.localizedDescription, privacy: .public). Handling items enumerated so far")
FileProviderMaterialisedEnumerationObserver.handleEnumeratedItems(self.allEnumeratedItemIds,
account: self.ncKitAccount,
completionHandler: self.completionHandler)
Logger.materialisedFileHandling.error(
"Ran into error when enumerating materialised items: \(error.localizedDescription, privacy: .public). Handling items enumerated so far"
)
FileProviderMaterialisedEnumerationObserver.handleEnumeratedItems(
allEnumeratedItemIds,
account: ncKitAccount,
completionHandler: completionHandler)
}
static func handleEnumeratedItems(_ itemIds: Set<String>, account: String, completionHandler: @escaping(_ deletedOcIds: Set<String>) -> Void) {
static func handleEnumeratedItems(
_ itemIds: Set<String>, account: String,
completionHandler: @escaping (_ deletedOcIds: Set<String>) -> Void
) {
let dbManager = NextcloudFilesDatabaseManager.shared
let databaseLocalFileMetadatas = dbManager.localFileMetadatas(account: account)
var noLongerMaterialisedIds = Set<String>()
@ -60,12 +69,13 @@ class FileProviderMaterialisedEnumerationObserver : NSObject, NSFileProviderEnum
guard itemIds.contains(localFileOcId) else {
noLongerMaterialisedIds.insert(localFileOcId)
continue;
continue
}
}
DispatchQueue.main.async {
Logger.materialisedFileHandling.info("Cleaning up local file metadatas for unmaterialised items")
Logger.materialisedFileHandling.info(
"Cleaning up local file metadatas for unmaterialised items")
for itemId in noLongerMaterialisedIds {
dbManager.deleteLocalFileMetadata(ocId: itemId)
}

View file

@ -24,25 +24,27 @@ class FileProviderSocketLineProcessor: NSObject, LineProcessor {
}
func process(_ line: String) {
if (line.contains("~")) { // We use this as the separator specifically in ACCOUNT_DETAILS
Logger.desktopClientConnection.debug("Processing file provider line with potentially sensitive user data")
if line.contains("~") { // We use this as the separator specifically in ACCOUNT_DETAILS
Logger.desktopClientConnection.debug(
"Processing file provider line with potentially sensitive user data")
} else {
Logger.desktopClientConnection.debug("Processing file provider line: \(line, privacy: .public)")
Logger.desktopClientConnection.debug(
"Processing file provider line: \(line, privacy: .public)")
}
let splitLine = line.split(separator: ":", maxSplits: 1)
guard let commandSubsequence = splitLine.first else {
Logger.desktopClientConnection.error("Input line did not have a first element")
return;
return
}
let command = String(commandSubsequence);
let command = String(commandSubsequence)
Logger.desktopClientConnection.debug("Received command: \(command, privacy: .public)")
if (command == "SEND_FILE_PROVIDER_DOMAIN_IDENTIFIER") {
if command == "SEND_FILE_PROVIDER_DOMAIN_IDENTIFIER" {
delegate.sendFileProviderDomainIdentifier()
} else if (command == "ACCOUNT_NOT_AUTHENTICATED") {
} else if command == "ACCOUNT_NOT_AUTHENTICATED" {
delegate.removeAccountConfig()
} else if (command == "ACCOUNT_DETAILS") {
} else if command == "ACCOUNT_DETAILS" {
guard let accountDetailsSubsequence = splitLine.last else { return }
let splitAccountDetails = accountDetailsSubsequence.split(separator: "~", maxSplits: 2)

View file

@ -12,17 +12,22 @@
* for more details.
*/
import Foundation
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")
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)
return FileManager.default.containerURL(
forSecurityApplicationGroupIdentifier: appGroupIdentifier)
}
func pathForFileProviderExtData() -> URL? {
@ -32,7 +37,9 @@ func pathForFileProviderExtData() -> URL? {
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)")
Logger.localFileOps.error(
"Unable to get file provider manager for domain: \(domain.displayName, privacy: .public)"
)
throw NSFileProviderError(.providerNotFound)
}
@ -40,9 +47,13 @@ func pathForFileProviderTempFilesForDomain(_ domain: NSFileProviderDomain) throw
return fileProviderDataUrl.appendingPathComponent("TemporaryNextcloudFiles/")
}
func localPathForNCFile(ocId: String, fileNameView: String, domain: NSFileProviderDomain) throws -> URL {
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)")
Logger.localFileOps.error(
"Unable to get path for file provider temp files for domain: \(domain.displayName, privacy: .public)"
)
throw URLError(.badURL)
}

View file

@ -12,21 +12,20 @@
* for more details.
*/
import Foundation
import FileProvider
import Foundation
class NextcloudAccount: NSObject {
static let webDavFilesUrlSuffix: String = "/remote.php/dav/files/"
let username, password, ncKitAccount, serverUrl, davFilesUrl: String
init(user: String, serverUrl: String, password: String) {
self.username = user
username = user
self.password = password
self.ncKitAccount = user + " " + serverUrl
ncKitAccount = user + " " + serverUrl
self.serverUrl = serverUrl
self.davFilesUrl = serverUrl + NextcloudAccount.webDavFilesUrlSuffix + user
davFilesUrl = serverUrl + NextcloudAccount.webDavFilesUrlSuffix + user
super.init()
}
}