2022-03-29 16:00:59 +03:00
|
|
|
/*
|
|
|
|
* Copyright (C) 2022 by Claudio Cambra <claudio.cambra@nextcloud.com>
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful, but
|
|
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
|
|
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
|
|
* for more details.
|
|
|
|
*/
|
|
|
|
|
|
|
|
import FileProvider
|
2023-01-26 22:50:40 +03:00
|
|
|
import NextcloudKit
|
2022-03-29 16:00:59 +03:00
|
|
|
|
|
|
|
class FileProviderEnumerator: NSObject, NSFileProviderEnumerator {
|
|
|
|
|
|
|
|
private let enumeratedItemIdentifier: NSFileProviderItemIdentifier
|
2023-03-08 01:30:50 +03:00
|
|
|
private var enumeratedItemMetadata: NextcloudItemMetadataTable?
|
|
|
|
private var enumeratingSystemIdentifier: Bool {
|
|
|
|
return FileProviderEnumerator.isSystemIdentifier(enumeratedItemIdentifier)
|
|
|
|
}
|
2023-03-07 03:40:17 +03:00
|
|
|
private let anchor = NSFileProviderSyncAnchor(Date().description.data(using: .utf8)!) // TODO: actually use this in NCKit and server requests
|
2023-01-26 22:50:40 +03:00
|
|
|
private static let maxItemsPerFileProviderPage = 100
|
2023-02-02 22:13:35 +03:00
|
|
|
let ncAccount: NextcloudAccount
|
|
|
|
let ncKit: NextcloudKit
|
2023-01-27 04:26:27 +03:00
|
|
|
var serverUrl: String = ""
|
2023-03-08 01:30:50 +03:00
|
|
|
|
|
|
|
private static func isSystemIdentifier(_ identifier: NSFileProviderItemIdentifier) -> Bool {
|
|
|
|
return identifier == .rootContainer ||
|
|
|
|
identifier == .trashContainer ||
|
|
|
|
identifier == .workingSet
|
|
|
|
}
|
2022-03-29 16:00:59 +03:00
|
|
|
|
2023-02-02 22:13:35 +03:00
|
|
|
init(enumeratedItemIdentifier: NSFileProviderItemIdentifier, ncAccount: NextcloudAccount, ncKit: NextcloudKit) {
|
2022-03-29 16:00:59 +03:00
|
|
|
self.enumeratedItemIdentifier = enumeratedItemIdentifier
|
2023-01-26 22:50:40 +03:00
|
|
|
self.ncAccount = ncAccount
|
2023-02-02 22:13:35 +03:00
|
|
|
self.ncKit = ncKit
|
2023-01-10 22:25:26 +03:00
|
|
|
|
2023-03-08 01:30:50 +03:00
|
|
|
if FileProviderEnumerator.isSystemIdentifier(enumeratedItemIdentifier) {
|
2023-03-08 00:40:52 +03:00
|
|
|
NSLog("Providing enumerator for a system defined container: %@", enumeratedItemIdentifier.rawValue)
|
2023-01-27 04:26:27 +03:00
|
|
|
self.serverUrl = ncAccount.davFilesUrl
|
2023-01-10 22:25:26 +03:00
|
|
|
} else {
|
2023-01-27 03:54:32 +03:00
|
|
|
NSLog("Providing enumerator for item with identifier: %@", enumeratedItemIdentifier.rawValue)
|
2023-01-10 22:25:26 +03:00
|
|
|
let dbManager = NextcloudFilesDatabaseManager.shared
|
2023-02-01 20:24:23 +03:00
|
|
|
if let itemMetadata = dbManager.itemMetadataFromFileProviderItemIdentifier(enumeratedItemIdentifier) {
|
|
|
|
self.serverUrl = itemMetadata.serverUrl + "/" + itemMetadata.fileName
|
|
|
|
} else {
|
|
|
|
NSLog("Could not find itemMetadata for file with identifier: %@", enumeratedItemIdentifier.rawValue)
|
2023-01-10 22:25:26 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-01-27 04:44:34 +03:00
|
|
|
NSLog("Set up enumerator for user: %@ with serverUrl: %@", ncAccount.username, serverUrl)
|
2022-03-29 16:00:59 +03:00
|
|
|
super.init()
|
|
|
|
}
|
|
|
|
|
|
|
|
func invalidate() {
|
|
|
|
// TODO: perform invalidation of server connection if necessary
|
|
|
|
}
|
|
|
|
|
2023-01-12 23:49:28 +03:00
|
|
|
// MARK: - Protocol methods
|
|
|
|
|
2022-03-29 16:00:59 +03:00
|
|
|
func enumerateItems(for observer: NSFileProviderEnumerationObserver, startingAt page: NSFileProviderPage) {
|
2023-01-27 04:44:34 +03:00
|
|
|
NSLog("Received enumerate items request for enumerator with user: %@ with serverUrl: %@", ncAccount.username, serverUrl)
|
2022-03-29 16:00:59 +03:00
|
|
|
/* TODO:
|
|
|
|
- inspect the page to determine whether this is an initial or a follow-up request
|
|
|
|
|
|
|
|
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 active set:
|
|
|
|
- perform a server request to update your local database
|
|
|
|
- fetch the active 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
|
|
|
|
*/
|
2023-01-26 22:50:40 +03:00
|
|
|
|
|
|
|
if enumeratedItemIdentifier == .workingSet {
|
2023-01-27 04:44:34 +03:00
|
|
|
NSLog("Enumerating working set for user: %@ with serverUrl: %@", ncAccount.username, serverUrl)
|
2023-03-08 00:40:52 +03:00
|
|
|
// TODO: Enumerate favourites and other special items
|
|
|
|
|
|
|
|
let materialisedFilesMetadatas = NextcloudFilesDatabaseManager.shared.localFileItemMetadatas(account: ncAccount.ncKitAccount)
|
|
|
|
FileProviderEnumerator.completeObserver(observer, ncKit: self.ncKit, numPage: 1, itemMetadatas: materialisedFilesMetadatas, error: nil, createLocalFileOrDirectory: false)
|
|
|
|
return
|
|
|
|
} else if enumeratedItemIdentifier == .trashContainer {
|
|
|
|
NSLog("Enumerating trash set for user: %@ with serverUrl: %@", ncAccount.username, serverUrl)
|
|
|
|
// TODO!
|
|
|
|
|
2023-01-26 22:50:40 +03:00
|
|
|
observer.finishEnumerating(upTo: nil)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-03-07 03:40:17 +03:00
|
|
|
// TODO: Make better use of pagination
|
2023-01-26 22:50:40 +03:00
|
|
|
if page == NSFileProviderPage.initialPageSortedByDate as NSFileProviderPage ||
|
|
|
|
page == NSFileProviderPage.initialPageSortedByName as NSFileProviderPage {
|
|
|
|
|
2023-01-27 04:44:34 +03:00
|
|
|
NSLog("Enumerating initial page for user: %@ with serverUrl: %@", ncAccount.username, serverUrl)
|
2023-02-02 22:13:35 +03:00
|
|
|
FileProviderEnumerator.readServerUrl(serverUrl, ncAccount: ncAccount, ncKit: ncKit) { metadatas, readError in
|
|
|
|
FileProviderEnumerator.completeObserver(observer, ncKit: self.ncKit, numPage: 1, itemMetadatas: metadatas, error: readError)
|
2023-01-26 22:50:40 +03:00
|
|
|
}
|
2023-01-27 04:44:34 +03:00
|
|
|
|
|
|
|
return;
|
2023-01-26 22:50:40 +03:00
|
|
|
}
|
2023-01-27 04:44:34 +03:00
|
|
|
|
|
|
|
let numPage = Int(String(data: page.rawValue, encoding: .utf8)!)!
|
|
|
|
NSLog("Enumerating page %d for user: %@ with serverUrl: %@", numPage, ncAccount.username, serverUrl)
|
2023-02-02 22:13:35 +03:00
|
|
|
FileProviderEnumerator.completeObserver(observer, ncKit: ncKit, numPage: numPage, itemMetadatas: nil, error: nil)
|
2022-03-29 16:00:59 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
func enumerateChanges(for observer: NSFileProviderChangeObserver, from anchor: NSFileProviderSyncAnchor) {
|
2023-01-27 04:44:34 +03:00
|
|
|
NSLog("Received enumerate changes request for enumerator with user: %@ with serverUrl: %@", ncAccount.username, serverUrl)
|
2022-03-29 16:00:59 +03:00
|
|
|
/* TODO:
|
|
|
|
- query the server for updates since the passed-in sync anchor
|
|
|
|
|
|
|
|
If this is an enumerator for the active 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
|
|
|
|
*/
|
|
|
|
observer.finishEnumeratingChanges(upTo: anchor, moreComing: false)
|
|
|
|
}
|
|
|
|
|
|
|
|
func currentSyncAnchor(completionHandler: @escaping (NSFileProviderSyncAnchor?) -> Void) {
|
|
|
|
completionHandler(anchor)
|
|
|
|
}
|
2023-01-12 23:50:35 +03:00
|
|
|
|
|
|
|
// MARK: - Helper methods
|
|
|
|
|
2023-03-08 00:40:52 +03:00
|
|
|
private static func completeObserver(_ observer: NSFileProviderEnumerationObserver, ncKit: NextcloudKit, numPage: Int, itemMetadatas: [NextcloudItemMetadataTable]?, error: Error?, createLocalFileOrDirectory: Bool = true) {
|
2023-02-02 16:18:46 +03:00
|
|
|
guard error == nil else {
|
|
|
|
NSLog("Finishing enumeration with error")
|
|
|
|
observer.finishEnumeratingWithError(error!)
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-01-27 04:44:34 +03:00
|
|
|
guard itemMetadatas != nil else {
|
|
|
|
NSLog("Received nil metadatas, finish empty enumeration")
|
|
|
|
observer.finishEnumerating(upTo: nil)
|
|
|
|
return
|
|
|
|
}
|
2023-01-26 22:50:40 +03:00
|
|
|
|
2023-01-12 23:50:35 +03:00
|
|
|
var items: [NSFileProviderItem] = []
|
|
|
|
|
|
|
|
for itemMetadata in itemMetadatas! {
|
2023-01-27 04:44:34 +03:00
|
|
|
if itemMetadata.e2eEncrypted {
|
|
|
|
NSLog("Skipping encrypted metadata in enumeration")
|
|
|
|
continue
|
|
|
|
}
|
2023-01-12 23:50:35 +03:00
|
|
|
|
2023-03-08 00:40:52 +03:00
|
|
|
if createLocalFileOrDirectory {
|
|
|
|
createFileOrDirectoryLocally(metadata: itemMetadata)
|
|
|
|
}
|
2023-01-12 23:50:35 +03:00
|
|
|
|
|
|
|
if let parentItemIdentifier = parentItemIdentifierFromMetadata(itemMetadata) {
|
2023-02-02 22:13:35 +03:00
|
|
|
let item = FileProviderItem(metadata: itemMetadata, parentItemIdentifier: parentItemIdentifier, ncKit: ncKit)
|
2023-01-27 04:44:34 +03:00
|
|
|
NSLog("Will enumerate item with ocId: %@ and name: %@", itemMetadata.ocId, itemMetadata.fileName)
|
2023-01-12 23:50:35 +03:00
|
|
|
items.append(item)
|
2023-01-27 04:44:34 +03:00
|
|
|
} else {
|
|
|
|
NSLog("Could not get valid parentItemIdentifier for item with ocId: %@ and name: %@, skipping enumeration", itemMetadata.ocId, itemMetadata.fileName)
|
2023-01-12 23:50:35 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
observer.didEnumerate(items)
|
2023-01-27 04:44:34 +03:00
|
|
|
NSLog("Did enumerate %d items", items.count)
|
2023-01-12 23:50:35 +03:00
|
|
|
|
|
|
|
if items.count == maxItemsPerFileProviderPage {
|
|
|
|
let nextPage = numPage + 1
|
|
|
|
let providerPage = NSFileProviderPage("\(nextPage)".data(using: .utf8)!)
|
|
|
|
observer.finishEnumerating(upTo: providerPage)
|
|
|
|
} else {
|
|
|
|
observer.finishEnumerating(upTo: nil)
|
|
|
|
}
|
|
|
|
}
|
2023-01-26 22:50:40 +03:00
|
|
|
|
2023-02-02 16:18:46 +03:00
|
|
|
private static func finishReadServerUrl(_ serverUrlPath: String, ncKitAccount: String, readError: Error?, completionHandler: @escaping (_ metadatas: [NextcloudItemMetadataTable]?, _ readError: Error?) -> Void) {
|
2023-01-26 22:50:40 +03:00
|
|
|
let metadatas = NextcloudFilesDatabaseManager.shared.itemMetadatas(account: ncKitAccount, serverUrl: serverUrlPath)
|
2023-01-27 04:44:34 +03:00
|
|
|
|
|
|
|
NSLog("Finished reading serverUrl: %@ for user: %@. Processed %d metadatas", serverUrlPath, ncKitAccount, metadatas.count)
|
2023-02-02 16:18:46 +03:00
|
|
|
completionHandler(metadatas, readError)
|
2023-01-26 22:50:40 +03:00
|
|
|
}
|
|
|
|
|
2023-02-02 22:13:35 +03:00
|
|
|
private static func readServerUrl(_ serverUrl: String, ncAccount: NextcloudAccount, ncKit: NextcloudKit, completionHandler: @escaping (_ metadatas: [NextcloudItemMetadataTable]?, _ readError: Error?) -> Void) {
|
2023-01-26 22:50:40 +03:00
|
|
|
let dbManager = NextcloudFilesDatabaseManager.shared
|
2023-01-27 01:01:31 +03:00
|
|
|
let ncKitAccount = ncAccount.ncKitAccount
|
2023-01-26 22:50:40 +03:00
|
|
|
var directoryEtag: String?
|
|
|
|
|
2023-01-27 01:01:31 +03:00
|
|
|
if let directoryMetadata = dbManager.directoryMetadata(account: ncKitAccount, serverUrl: serverUrl) {
|
2023-01-26 22:50:40 +03:00
|
|
|
directoryEtag = directoryMetadata.etag
|
|
|
|
}
|
|
|
|
|
2023-02-02 22:13:35 +03:00
|
|
|
NSLog("Starting to read serverUrl: %@ for user: %@ at depth 0. NCKit info: user: %@, userId: %@, password: %@, urlBase: %@, ncVersion: %d", serverUrl, ncKitAccount, ncKit.nkCommonInstance.user, ncKit.nkCommonInstance.userId, ncKit.nkCommonInstance.password, ncKit.nkCommonInstance.urlBase, ncKit.nkCommonInstance.nextcloudVersion)
|
2023-01-27 04:44:34 +03:00
|
|
|
|
2023-02-02 22:13:35 +03:00
|
|
|
ncKit.readFileOrFolder(serverUrlFileName: serverUrl, depth: "0", showHiddenFiles: true) { account, files, _, error in
|
2023-01-27 04:44:34 +03:00
|
|
|
guard error == .success else {
|
|
|
|
NSLog("0 depth readFileOrFolder of url: %@ did not complete successfully, received error: %@", serverUrl, error.errorDescription)
|
2023-02-02 16:18:46 +03:00
|
|
|
finishReadServerUrl(serverUrl, ncKitAccount: ncKitAccount, readError: error.error, completionHandler: completionHandler)
|
2023-01-27 04:44:34 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-02-01 21:35:08 +03:00
|
|
|
// If we have already done a 0 depth scan of this folder then we might get matching etag
|
2023-01-26 22:50:40 +03:00
|
|
|
guard directoryEtag != files.first?.etag else {
|
2023-01-27 04:44:34 +03:00
|
|
|
NSLog("Fetched directory etag is same as that stored locally (serverUrl: %@ user: %@). Not fetching child items.", serverUrl, account)
|
2023-02-02 16:18:46 +03:00
|
|
|
finishReadServerUrl(serverUrl, ncKitAccount: ncKitAccount, readError: nil, completionHandler: completionHandler)
|
2023-01-26 22:50:40 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-01-27 04:44:34 +03:00
|
|
|
NSLog("Starting to read serverUrl: %@ for user: %@ at depth 1", serverUrl, ncKitAccount)
|
|
|
|
|
2023-02-02 22:13:35 +03:00
|
|
|
ncKit.readFileOrFolder(serverUrlFileName: serverUrl, depth: "1", showHiddenFiles: true) { account, files, _, error in
|
2023-01-26 22:50:40 +03:00
|
|
|
guard error == .success else {
|
2023-01-27 04:44:34 +03:00
|
|
|
NSLog("1 depth readFileOrFolder of url: %@ did not complete successfully, received error: %@", serverUrl, error.errorDescription)
|
2023-02-02 16:18:46 +03:00
|
|
|
finishReadServerUrl(serverUrl, ncKitAccount: ncKitAccount, readError: error.error, completionHandler: completionHandler)
|
2023-01-26 22:50:40 +03:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2023-01-27 04:44:34 +03:00
|
|
|
NSLog("Starting async conversion of NKFiles for serverUrl: %@ for user: %@", serverUrl, ncKitAccount)
|
2023-01-26 22:50:40 +03:00
|
|
|
DispatchQueue.global().async {
|
2023-02-01 21:35:08 +03:00
|
|
|
dbManager.convertNKFilesToItemMetadatas(files, account: ncKitAccount) { directoryMetadata, childDirectoriesMetadata, metadatas in
|
|
|
|
|
|
|
|
// We have now scanned this directory's contents, so update with etag in order to not check again if not needed
|
|
|
|
dbManager.updateDirectoryMetadatasFromItemMetadatas(account: ncKitAccount, parentDirectoryServerUrl: serverUrl, updatedDirectoryItemMetadatas: [directoryMetadata], recordEtag: true)
|
|
|
|
|
2023-01-27 01:01:31 +03:00
|
|
|
dbManager.updateItemMetadatas(account: ncKitAccount, serverUrl: serverUrl, updatedMetadatas: metadatas)
|
2023-02-01 21:35:08 +03:00
|
|
|
|
|
|
|
// Since we haven't scanned the contents of these, don't record their itemMetadata etags in the directory tables
|
2023-01-27 01:01:31 +03:00
|
|
|
dbManager.updateDirectoryMetadatasFromItemMetadatas(account: ncKitAccount, parentDirectoryServerUrl: serverUrl, updatedDirectoryItemMetadatas: childDirectoriesMetadata)
|
2023-02-01 21:35:08 +03:00
|
|
|
|
2023-02-02 16:18:46 +03:00
|
|
|
finishReadServerUrl(serverUrl, ncKitAccount: ncKitAccount, readError: nil, completionHandler: completionHandler)
|
2023-01-26 22:50:40 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-03-29 16:00:59 +03:00
|
|
|
}
|