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
2023-03-14 23:03:49 +03:00
import OSLog
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: a c t u a l l y u s e t h i s i n N C K i t a n d s e r v e r r e q u e s t s
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-14 23:26:52 +03:00
Logger . enumeration . debug ( " Providing enumerator for a system defined container: \( enumeratedItemIdentifier . rawValue , privacy : . public ) " )
2023-01-27 04:26:27 +03:00
self . serverUrl = ncAccount . davFilesUrl
2023-01-10 22:25:26 +03:00
} else {
2023-03-14 23:26:52 +03:00
Logger . enumeration . debug ( " Providing enumerator for item with identifier: \( enumeratedItemIdentifier . rawValue , privacy : . public ) " )
2023-01-10 22:25:26 +03:00
let dbManager = NextcloudFilesDatabaseManager . shared
2023-03-09 04:09:39 +03:00
enumeratedItemMetadata = dbManager . itemMetadataFromFileProviderItemIdentifier ( enumeratedItemIdentifier )
if enumeratedItemMetadata != nil {
self . serverUrl = enumeratedItemMetadata ! . serverUrl + " / " + enumeratedItemMetadata ! . fileName
2023-02-01 20:24:23 +03:00
} else {
2023-03-14 23:26:52 +03:00
Logger . enumeration . error ( " Could not find itemMetadata for file with identifier: \( enumeratedItemIdentifier . rawValue , privacy : . public ) " )
2023-01-10 22:25:26 +03:00
}
}
2023-03-14 23:26:52 +03:00
Logger . enumeration . info ( " Set up enumerator for user: \( self . ncAccount . ncKitAccount , privacy : OSLogPrivacy . auto ( mask : . hash ) ) with serverUrl: \( self . serverUrl , privacy : OSLogPrivacy . auto ( mask : . hash ) ) " )
2022-03-29 16:00:59 +03:00
super . init ( )
}
func invalidate ( ) {
// TODO: p e r f o r m i n v a l i d a t i o n o f s e r v e r c o n n e c t i o n i f n e c e s s a r y
}
2023-01-12 23:49:28 +03:00
// MARK: - P r o t o c o l m e t h o d s
2022-03-29 16:00:59 +03:00
func enumerateItems ( for observer : NSFileProviderEnumerationObserver , startingAt page : NSFileProviderPage ) {
2023-03-14 23:26:52 +03:00
Logger . enumeration . debug ( " Received enumerate items request for enumerator with user: \( self . ncAccount . ncKitAccount , privacy : OSLogPrivacy . auto ( mask : . hash ) ) with serverUrl: \( self . serverUrl , privacy : OSLogPrivacy . auto ( mask : . hash ) ) " )
2023-03-14 23:03:49 +03:00
/*
- inspect the page to determine whether this is an initial or a follow - up request ( TODO )
2022-03-29 16:00:59 +03:00
If this is an enumerator for a directory , the root container or all directories :
- perform a server request to fetch directory contents
2023-03-14 23:03:49 +03:00
If this is an enumerator for the working set :
2022-03-29 16:00:59 +03:00
- perform a server request to update your local database
2023-03-14 23:03:49 +03:00
- fetch the working set from your local database
2022-03-29 16:00:59 +03:00
- 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
2023-03-10 03:16:23 +03:00
let dbManager = NextcloudFilesDatabaseManager . shared
2023-03-14 18:01:34 +03:00
// I f w e d o n ' t h a v e a n y i t e m s i n t h e d a t a b a s e , i g n o r e t h i s a n d g o f o r a n o r m a l s e r v e r U r l r e a d .
// B y d e f a u l t w e s e t t h e s e r v e r U r l t o b e t h e w e b d a v f i l e s r o o t w h e n w e a r e p r o v i d e d a s y s t e m c o n t a i n e r i d .
// H o w e v e r , i f w e d o h a v e i t e m s , w e w a n t t o d o a r e c u r s i v e s c a n o f a l l t h e f o l d e r s i n t h e s e r v e r t h a t
// * * w e h a v e a l r e a d y e x p l o r e d * * . T h i s i s t o n o t k i l l t h e s e r v e r .
2023-03-10 03:16:23 +03:00
if enumeratedItemIdentifier = = . workingSet && dbManager . anyItemMetadatasForAccount ( ncAccount . ncKitAccount ) {
2023-03-10 05:21:01 +03:00
if page = = NSFileProviderPage . initialPageSortedByDate as NSFileProviderPage ||
page = = NSFileProviderPage . initialPageSortedByName as NSFileProviderPage {
2023-03-08 00:40:52 +03:00
2023-03-10 04:09:24 +03:00
2023-03-10 05:21:01 +03:00
let directoryMetadatas = dbManager . directoryMetadatas ( account : ncAccount . ncKitAccount )
var allMetadatas : [ NextcloudItemMetadataTable ] = [ ]
2023-03-10 04:09:24 +03:00
2023-03-14 18:06:20 +03:00
var serverError : NKError ?
2023-03-10 05:42:30 +03:00
let dispatchGroup = DispatchGroup ( ) // TODO: M a y b e o w n t h r e a d ?
2023-03-10 04:09:24 +03:00
2023-03-10 05:21:01 +03:00
for directoryMetadata in directoryMetadatas {
2023-03-11 03:26:11 +03:00
guard directoryMetadata . etag != " " else {
2023-03-14 23:26:52 +03:00
Logger . enumeration . info ( " Skipping enumeration of unexplored directory for working set: \( directoryMetadata . serverUrl , privacy : OSLogPrivacy . auto ( mask : . hash ) ) " )
2023-03-11 03:26:11 +03:00
continue ;
}
2023-03-10 05:21:01 +03:00
dispatchGroup . enter ( )
2023-03-10 04:09:24 +03:00
2023-03-10 05:21:01 +03:00
FileProviderEnumerator . readServerUrl ( directoryMetadata . serverUrl , ncAccount : ncAccount , ncKit : ncKit ) { metadatas , _ , _ , _ , readError in
guard readError = = nil else {
2023-03-14 23:26:52 +03:00
Logger . enumeration . error ( " Finishing enumeration of working set directory \( directoryMetadata . serverUrl , privacy : OSLogPrivacy . auto ( mask : . hash ) ) with error \( readError ! , privacy : . public ) " )
2023-03-14 18:06:20 +03:00
let nkError = NKError ( error : readError ! )
if nkError . isUnauthenticatedError || nkError . isCouldntConnectError {
// I f i t i s a c r i t i c a l e r r o r t h e n s t o p , i f n o t t h e n c o n t i n u e
serverError = nkError
}
2023-03-10 05:21:01 +03:00
dispatchGroup . leave ( )
return
2023-03-10 04:09:24 +03:00
}
2023-03-10 05:21:01 +03:00
if let metadatas = metadatas {
allMetadatas += metadatas
} else {
allMetadatas += dbManager . itemMetadatas ( account : self . ncAccount . ncKitAccount , serverUrl : directoryMetadata . serverUrl )
}
2023-03-10 04:09:24 +03:00
2023-03-10 05:21:01 +03:00
dispatchGroup . leave ( )
2023-03-10 04:09:24 +03:00
}
2023-03-10 05:21:01 +03:00
dispatchGroup . wait ( )
2023-03-14 23:03:49 +03:00
2023-03-14 18:06:20 +03:00
guard serverError = = nil else {
observer . finishEnumeratingWithError ( serverError ! . error )
return
}
2023-03-10 04:09:24 +03:00
}
2023-03-10 05:21:01 +03:00
FileProviderEnumerator . completeEnumerationObserver ( observer , ncKit : self . ncKit , numPage : 1 , itemMetadatas : allMetadatas )
} else {
2023-03-14 23:26:52 +03:00
Logger . enumeration . debug ( " Enumerating page \( page . rawValue ) of working set for user: \( self . ncAccount . ncKitAccount , privacy : OSLogPrivacy . auto ( mask : . hash ) ) " )
2023-03-14 23:03:49 +03:00
// T O D O !
2023-03-10 05:21:01 +03:00
observer . finishEnumerating ( upTo : nil )
2023-03-10 04:09:24 +03:00
}
2023-03-08 00:40:52 +03:00
return
} else if enumeratedItemIdentifier = = . trashContainer {
2023-03-14 23:26:52 +03:00
Logger . enumeration . debug ( " Enumerating trash set for user: \( self . ncAccount . ncKitAccount , privacy : OSLogPrivacy . auto ( mask : . hash ) ) with serverUrl: \( self . serverUrl , privacy : OSLogPrivacy . auto ( mask : . hash ) ) " )
2023-03-08 00:40:52 +03:00
// T O D O !
2023-01-26 22:50:40 +03:00
observer . finishEnumerating ( upTo : nil )
return
}
2023-03-09 03:19:00 +03:00
guard serverUrl != " " else {
2023-03-14 23:26:52 +03:00
Logger . enumeration . error ( " Enumerator has empty serverUrl -- can't enumerate that! For identifier: \( self . enumeratedItemIdentifier . rawValue , privacy : . public ) " )
2023-03-09 03:19:00 +03:00
observer . finishEnumeratingWithError ( NSFileProviderError ( . noSuchItem ) )
return
}
2023-03-10 03:16:23 +03:00
// TODO: M a k e b e t t e r u s e o f p a g i n a t i o n a n d a n d l e p a g i n g p r o p e r l y
2023-01-26 22:50:40 +03:00
if page = = NSFileProviderPage . initialPageSortedByDate as NSFileProviderPage ||
page = = NSFileProviderPage . initialPageSortedByName as NSFileProviderPage {
2023-03-14 23:26:52 +03:00
Logger . enumeration . debug ( " Enumerating initial page for user: \( self . ncAccount . ncKitAccount , privacy : OSLogPrivacy . auto ( mask : . hash ) ) with serverUrl: \( self . serverUrl , privacy : OSLogPrivacy . auto ( mask : . hash ) ) " )
2023-03-09 03:19:00 +03:00
FileProviderEnumerator . readServerUrl ( serverUrl , ncAccount : ncAccount , ncKit : ncKit ) { _ , _ , _ , _ , readError in
guard readError = = nil else {
2023-03-14 23:26:52 +03:00
Logger . enumeration . error ( " Finishing enumeration for user: \( self . ncAccount . ncKitAccount , privacy : OSLogPrivacy . auto ( mask : . hash ) ) with serverUrl: \( self . serverUrl , privacy : OSLogPrivacy . auto ( mask : . hash ) ) with error \( readError ! , privacy : . public ) " )
2023-03-13 16:27:17 +03:00
2023-03-13 17:09:43 +03:00
let nkReadError = NKError ( error : readError ! )
observer . finishEnumeratingWithError ( nkReadError . toFileProviderError ( ) )
2023-03-09 04:09:39 +03:00
return
2023-03-09 03:19:00 +03:00
}
let ncKitAccount = self . ncAccount . ncKitAccount
// R e t u r n a l l n o w k n o w n m e t a d a t a s
2023-03-09 04:09:39 +03:00
var metadatas : [ NextcloudItemMetadataTable ]
if self . enumeratingSystemIdentifier || ( self . enumeratedItemMetadata != nil && self . enumeratedItemMetadata ! . directory ) {
metadatas = NextcloudFilesDatabaseManager . shared . itemMetadatas ( account : ncKitAccount , serverUrl : self . serverUrl )
} else if ( self . enumeratedItemMetadata != nil ) {
guard let updatedEnumeratedItemMetadata = NextcloudFilesDatabaseManager . shared . itemMetadataFromOcId ( self . enumeratedItemMetadata ! . ocId ) else {
2023-03-14 23:26:52 +03:00
Logger . enumeration . error ( " Could not finish enumeration for user: \( ncKitAccount , privacy : OSLogPrivacy . auto ( mask : . hash ) ) with serverUrl: \( self . serverUrl , privacy : OSLogPrivacy . auto ( mask : . hash ) ) as the enumerated item could not be fetched from database. \( self . enumeratedItemIdentifier . rawValue , privacy : . public ) " )
2023-03-09 04:09:39 +03:00
observer . finishEnumeratingWithError ( NSFileProviderError ( . noSuchItem ) )
return
}
2023-03-09 14:54:18 +03:00
2023-03-09 04:09:39 +03:00
metadatas = [ updatedEnumeratedItemMetadata ]
2023-03-14 23:03:49 +03:00
} else { // W e n e e d t o h a v e a n e n u m e r a t e d I t e m M e t a d a t a t o h a v e a n o n e m p t y s e r v e r U r l
2023-03-14 23:26:52 +03:00
Logger . enumeration . error ( " Cannot finish enumeration for user: \( ncKitAccount , privacy : OSLogPrivacy . auto ( mask : . hash ) ) as we do not have a valid server URL. NOTE: this error should not be possible and indicates something is going wrong before. " )
2023-03-09 04:09:39 +03:00
observer . finishEnumeratingWithError ( NSFileProviderError ( . noSuchItem ) )
return
}
2023-03-09 03:19:00 +03:00
2023-03-14 23:26:52 +03:00
Logger . enumeration . info ( " Finished reading serverUrl: \( self . serverUrl , privacy : OSLogPrivacy . auto ( mask : . hash ) ) for user: \( ncKitAccount , privacy : OSLogPrivacy . auto ( mask : . hash ) ) . Processed \( metadatas . count ) metadatas " )
2023-03-09 03:19:00 +03:00
2023-03-09 14:54:18 +03:00
FileProviderEnumerator . completeEnumerationObserver ( observer , ncKit : self . ncKit , numPage : 1 , itemMetadatas : metadatas )
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 ) ! ) !
2023-03-14 23:26:52 +03:00
Logger . enumeration . debug ( " Enumerating page \( numPage , privacy : . public ) for user: \( self . ncAccount . ncKitAccount , privacy : OSLogPrivacy . auto ( mask : . hash ) ) with serverUrl: \( self . serverUrl , privacy : OSLogPrivacy . auto ( mask : . hash ) ) " )
2023-03-09 03:19:00 +03:00
// TODO: H a n d l e p a g i n g p r o p e r l y
// F i l e P r o v i d e r E n u m e r a t o r . c o m p l e t e O b s e r v e r ( o b s e r v e r , n c K i t : n c K i t , n u m P a g e : n u m P a g e , i t e m M e t a d a t a s : n i l )
observer . finishEnumerating ( upTo : nil )
2022-03-29 16:00:59 +03:00
}
func enumerateChanges ( for observer : NSFileProviderChangeObserver , from anchor : NSFileProviderSyncAnchor ) {
2023-03-14 23:26:52 +03:00
Logger . enumeration . debug ( " Received enumerate changes request for enumerator for user: \( self . ncAccount . ncKitAccount , privacy : OSLogPrivacy . auto ( mask : . hash ) ) with serverUrl: \( self . serverUrl , privacy : OSLogPrivacy . auto ( mask : . hash ) ) " )
2023-03-09 15:14:03 +03:00
/*
2023-03-14 23:03:49 +03:00
- query the server for updates since the passed - in sync anchor ( TODO )
2022-03-29 16:00:59 +03:00
2023-03-14 23:03:49 +03:00
If this is an enumerator for the working set :
2022-03-29 16:00:59 +03:00
- 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
*/
2023-03-09 15:14:03 +03:00
if enumeratedItemIdentifier = = . workingSet {
2023-03-14 23:26:52 +03:00
Logger . enumeration . debug ( " Enumerating changes in working set for user: \( self . ncAccount . ncKitAccount , privacy : OSLogPrivacy . auto ( mask : . hash ) ) " )
2023-03-09 15:14:03 +03:00
2023-03-11 03:26:11 +03:00
let scanResults = FileProviderEnumerator . fullRecursiveScanForChanges ( ncAccount : self . ncAccount , ncKit : self . ncKit )
2023-03-11 00:11:05 +03:00
FileProviderEnumerator . completeChangesObserver ( observer ,
anchor : anchor ,
2023-03-11 03:26:11 +03:00
ncKit : self . ncKit ,
2023-03-11 00:11:05 +03:00
newMetadatas : scanResults . newMetadatas ,
updatedMetadatas : scanResults . updatedMetadatas ,
deletedMetadatas : scanResults . deletedMetadatas )
2023-03-09 15:14:03 +03:00
return
} else if enumeratedItemIdentifier = = . trashContainer {
2023-03-14 23:26:52 +03:00
Logger . enumeration . debug ( " Enumerating changes in trash set for user: \( self . ncAccount . ncKitAccount , privacy : OSLogPrivacy . auto ( mask : . hash ) ) " )
2023-03-09 15:14:03 +03:00
// T O D O !
observer . finishEnumeratingChanges ( upTo : anchor , moreComing : false )
return
}
2023-03-14 23:26:52 +03:00
Logger . enumeration . info ( " Enumerating changes for user: \( self . ncAccount . ncKitAccount , privacy : OSLogPrivacy . auto ( mask : . hash ) ) with serverUrl: \( self . serverUrl , privacy : OSLogPrivacy . auto ( mask : . hash ) ) " )
2023-03-09 15:14:03 +03:00
2023-03-09 15:27:57 +03:00
// N o m a t t e r w h a t h a p p e n s h e r e w e f i n i s h e n u m e r a t i o n i n s o m e w a y , e i t h e r f r o m t h e e r r o r
// h a n d l i n g b e l o w o r f r o m t h e c o m p l e t e C h a n g e s O b s e r v e r
2023-03-13 16:59:46 +03:00
FileProviderEnumerator . readServerUrl ( serverUrl , ncAccount : ncAccount , ncKit : ncKit , stopAtMatchingEtags : true ) { _ , newMetadatas , updatedMetadatas , deletedMetadatas , readError in
2023-03-09 15:14:03 +03:00
guard readError = = nil else {
2023-03-14 23:26:52 +03:00
Logger . enumeration . error ( " Finishing enumeration of changes for user: \( self . ncAccount . ncKitAccount , privacy : OSLogPrivacy . auto ( mask : . hash ) ) with serverUrl: \( self . serverUrl , privacy : OSLogPrivacy . auto ( mask : . hash ) ) with error: \( readError ! , privacy : . public ) " )
2023-03-09 18:51:31 +03:00
2023-03-13 17:09:43 +03:00
let nkReadError = NKError ( error : readError ! )
let fpError = nkReadError . toFileProviderError ( )
2023-03-13 16:15:32 +03:00
2023-03-13 17:09:43 +03:00
if nkReadError . isNotFoundError {
2023-03-14 23:26:52 +03:00
Logger . enumeration . info ( " 404 error means item no longer exists. Deleting metadata and reporting \( self . serverUrl , privacy : OSLogPrivacy . auto ( mask : . hash ) ) as deletion without error " )
2023-03-09 18:51:31 +03:00
2023-03-13 17:09:43 +03:00
guard let itemMetadata = self . enumeratedItemMetadata else {
2023-03-14 23:03:49 +03:00
Logger . enumeration . error ( " Invalid enumeratedItemMetadata, could not delete metadata nor report deletion " )
2023-03-13 17:09:43 +03:00
observer . finishEnumeratingWithError ( fpError )
2023-03-13 16:15:32 +03:00
return
2023-03-09 18:51:31 +03:00
}
2023-03-13 16:27:17 +03:00
2023-03-13 17:09:43 +03:00
let dbManager = NextcloudFilesDatabaseManager . shared
if itemMetadata . directory {
dbManager . deleteDirectoryAndSubdirectoriesMetadata ( ocId : itemMetadata . ocId )
} else {
dbManager . deleteItemMetadata ( ocId : itemMetadata . ocId )
}
FileProviderEnumerator . completeChangesObserver ( observer , anchor : anchor , ncKit : self . ncKit , newMetadatas : nil , updatedMetadatas : nil , deletedMetadatas : [ itemMetadata ] )
2023-03-13 16:27:17 +03:00
return
2023-03-13 17:09:43 +03:00
} else if nkReadError . isNoChangesError { // A l l i s w e l l , j u s t n o c h a n g e d e t a g s
2023-03-14 23:03:49 +03:00
Logger . enumeration . info ( " Error was to say no changed files -- not bad error. Finishing change enumeration. " )
2023-03-13 17:09:43 +03:00
observer . finishEnumeratingChanges ( upTo : anchor , moreComing : false )
return ;
2023-03-09 18:51:31 +03:00
}
2023-03-13 17:09:43 +03:00
observer . finishEnumeratingWithError ( fpError )
2023-03-09 15:14:03 +03:00
return
}
2023-03-14 23:26:52 +03:00
Logger . enumeration . info ( " Finished reading serverUrl: \( self . serverUrl , privacy : OSLogPrivacy . auto ( mask : . hash ) ) for user: \( self . ncAccount . ncKitAccount , privacy : OSLogPrivacy . auto ( mask : . hash ) ) " )
2023-03-09 15:14:03 +03:00
2023-03-10 03:16:23 +03:00
FileProviderEnumerator . completeChangesObserver ( observer , anchor : anchor , ncKit : self . ncKit , newMetadatas : newMetadatas , updatedMetadatas : updatedMetadatas , deletedMetadatas : deletedMetadatas )
2023-03-09 15:14:03 +03:00
}
2022-03-29 16:00:59 +03:00
}
func currentSyncAnchor ( completionHandler : @ escaping ( NSFileProviderSyncAnchor ? ) -> Void ) {
completionHandler ( anchor )
}
2023-01-12 23:50:35 +03:00
// MARK: - H e l p e r m e t h o d s
2023-03-09 14:54:18 +03:00
private static func completeEnumerationObserver ( _ observer : NSFileProviderEnumerationObserver , ncKit : NextcloudKit , numPage : Int , itemMetadatas : [ NextcloudItemMetadataTable ] , createLocalFileOrDirectory : Bool = true ) {
2023-01-26 22:50:40 +03:00
2023-01-12 23:50:35 +03:00
var items : [ NSFileProviderItem ] = [ ]
2023-03-09 03:19:00 +03:00
for itemMetadata in itemMetadatas {
2023-01-27 04:44:34 +03:00
if itemMetadata . e2eEncrypted {
2023-03-14 23:26:52 +03:00
Logger . enumeration . info ( " Skipping encrypted metadata in enumeration: \( itemMetadata . ocId ) \( itemMetadata . fileName , privacy : OSLogPrivacy . auto ( mask : . hash ) ) " )
2023-01-27 04:44:34 +03:00
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
2023-03-13 14:34:05 +03:00
if let parentItemIdentifier = NextcloudFilesDatabaseManager . shared . parentItemIdentifierFromMetadata ( itemMetadata ) {
2023-02-02 22:13:35 +03:00
let item = FileProviderItem ( metadata : itemMetadata , parentItemIdentifier : parentItemIdentifier , ncKit : ncKit )
2023-03-14 23:26:52 +03:00
Logger . enumeration . debug ( " Will enumerate item with ocId: \( itemMetadata . ocId ) and name: \( itemMetadata . fileName , privacy : OSLogPrivacy . auto ( mask : . hash ) ) " )
2023-01-12 23:50:35 +03:00
items . append ( item )
2023-01-27 04:44:34 +03:00
} else {
2023-03-14 23:26:52 +03:00
Logger . enumeration . error ( " Could not get valid parentItemIdentifier for item with ocId: \( itemMetadata . ocId , privacy : . public ) and name: \( itemMetadata . fileName , privacy : OSLogPrivacy . auto ( mask : . hash ) ) , skipping enumeration " )
2023-01-12 23:50:35 +03:00
}
}
observer . didEnumerate ( items )
2023-03-14 23:03:49 +03:00
Logger . enumeration . info ( " Did enumerate \( items . count ) items " )
2023-01-12 23:50:35 +03:00
2023-03-09 03:19:00 +03:00
// TODO: H a n d l e p a g i n g p r o p e r l y
/*
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-03-09 03:19:00 +03:00
*/
observer . finishEnumerating ( upTo : NSFileProviderPage ( " \( numPage ) " . data ( using : . utf8 ) ! ) )
2023-01-12 23:50:35 +03:00
}
2023-01-26 22:50:40 +03:00
2023-03-09 17:45:38 +03:00
private static func completeChangesObserver ( _ observer : NSFileProviderChangeObserver , anchor : NSFileProviderSyncAnchor , ncKit : NextcloudKit , newMetadatas : [ NextcloudItemMetadataTable ] ? , updatedMetadatas : [ NextcloudItemMetadataTable ] ? , deletedMetadatas : [ NextcloudItemMetadataTable ] ? ) {
guard newMetadatas != nil || updatedMetadatas != nil || deletedMetadatas != nil else {
2023-03-14 23:03:49 +03:00
Logger . enumeration . error ( " Received invalid newMetadatas, updatedMetadatas or deletedMetadatas. Finished enumeration of changes with error. " )
2023-03-09 15:27:57 +03:00
observer . finishEnumeratingWithError ( NSFileProviderError ( . noSuchItem ) )
return
}
2023-03-09 17:45:38 +03:00
// O b s e r v e r d o e s n o t c a r e a b o u t n e w v s u p d a t e d , s o j o i n
var allUpdatedMetadatas : [ NextcloudItemMetadataTable ] = [ ]
var allDeletedMetadatas : [ NextcloudItemMetadataTable ] = [ ]
if let newMetadatas = newMetadatas {
allUpdatedMetadatas += newMetadatas
}
if let updatedMetadatas = updatedMetadatas {
allUpdatedMetadatas += updatedMetadatas
}
if let deletedMetadatas = deletedMetadatas {
allDeletedMetadatas = deletedMetadatas
}
2023-03-09 15:27:57 +03:00
var allFpItemUpdates : [ FileProviderItem ] = [ ]
2023-03-09 17:45:38 +03:00
var allFpItemDeletionsIdentifiers = Array ( allDeletedMetadatas . map { NSFileProviderItemIdentifier ( $0 . ocId ) } )
2023-03-09 15:27:57 +03:00
2023-03-11 00:25:28 +03:00
for updMetadata in allUpdatedMetadatas {
2023-03-13 14:34:05 +03:00
guard let parentItemIdentifier = NextcloudFilesDatabaseManager . shared . parentItemIdentifierFromMetadata ( updMetadata ) else {
2023-03-14 23:26:52 +03:00
Logger . enumeration . warning ( " Not enumerating change for metadata: \( updMetadata . ocId ) \( updMetadata . fileName , privacy : OSLogPrivacy . auto ( mask : . hash ) ) as could not get parent item metadata. " )
2023-03-11 00:25:28 +03:00
continue
}
2023-03-09 15:27:57 +03:00
2023-03-11 00:25:28 +03:00
guard ! updMetadata . e2eEncrypted else {
// P r e c a u t i o n , i f a l l g o e s w e l l i n N K F i l e c o n v e r s i o n t h e n t h i s s h o u l d n o t h a p p e n
// TODO: R e m o v e w h e n E 2 E E s u p p o r t e d
2023-03-14 23:26:52 +03:00
Logger . enumeration . info ( " Encrypted metadata in changes enumeration \( updMetadata . ocId ) \( updMetadata . fileName , privacy : OSLogPrivacy . auto ( mask : . hash ) ) , adding to deletions " )
2023-03-11 00:25:28 +03:00
allFpItemDeletionsIdentifiers . append ( NSFileProviderItemIdentifier ( updMetadata . ocId ) )
continue
2023-03-10 20:36:25 +03:00
}
2023-03-11 00:25:28 +03:00
let fpItem = FileProviderItem ( metadata : updMetadata , parentItemIdentifier : parentItemIdentifier , ncKit : ncKit )
allFpItemUpdates . append ( fpItem )
2023-03-09 15:27:57 +03:00
}
2023-03-09 17:45:38 +03:00
if ! allFpItemUpdates . isEmpty {
observer . didUpdate ( allFpItemUpdates )
}
if ! allFpItemDeletionsIdentifiers . isEmpty {
observer . didDeleteItems ( withIdentifiers : allFpItemDeletionsIdentifiers )
}
2023-03-14 23:03:49 +03:00
Logger . enumeration . info ( " Processed \( allUpdatedMetadatas . count ) new or updated metadatas, \( allDeletedMetadatas . count ) deleted metadatas. " )
2023-03-09 17:45:38 +03:00
observer . finishEnumeratingChanges ( upTo : anchor , moreComing : false )
2023-03-09 15:27:57 +03:00
}
2023-03-11 00:11:05 +03:00
private static func fullRecursiveScanForChanges ( ncAccount : NextcloudAccount , ncKit : NextcloudKit ) -> ( newMetadatas : [ NextcloudItemMetadataTable ] , updatedMetadatas : [ NextcloudItemMetadataTable ] , deletedMetadatas : [ NextcloudItemMetadataTable ] ) {
let rootContainerDirectoryMetadata = NextcloudDirectoryMetadataTable ( )
rootContainerDirectoryMetadata . serverUrl = ncAccount . davFilesUrl
rootContainerDirectoryMetadata . account = ncAccount . ncKitAccount
rootContainerDirectoryMetadata . ocId = NSFileProviderItemIdentifier . rootContainer . rawValue
return scanRecursivelyForChanges ( rootContainerDirectoryMetadata , ncAccount : ncAccount , ncKit : ncKit )
}
private static func scanRecursivelyForChanges ( _ directoryMetadata : NextcloudDirectoryMetadataTable , ncAccount : NextcloudAccount , ncKit : NextcloudKit ) -> ( newMetadatas : [ NextcloudItemMetadataTable ] , updatedMetadatas : [ NextcloudItemMetadataTable ] , deletedMetadatas : [ NextcloudItemMetadataTable ] ) {
2023-03-11 03:26:11 +03:00
guard directoryMetadata . etag != " " || directoryMetadata . serverUrl = = ncAccount . davFilesUrl else {
2023-03-14 23:26:52 +03:00
Logger . enumeration . info ( " Skipping enumeration of changes in unexplored directory for working \( directoryMetadata . serverUrl , privacy : OSLogPrivacy . auto ( mask : . hash ) ) " )
2023-03-11 03:26:11 +03:00
return ( [ ] , [ ] , [ ] )
}
2023-03-11 00:11:05 +03:00
var allNewMetadatas : [ NextcloudItemMetadataTable ] = [ ]
var allUpdatedMetadatas : [ NextcloudItemMetadataTable ] = [ ]
var allDeletedMetadatas : [ NextcloudItemMetadataTable ] = [ ]
let dbManager = NextcloudFilesDatabaseManager . shared
let dispatchGroup = DispatchGroup ( ) // TODO: M a y b e o w n t h r e a d ?
dispatchGroup . enter ( )
2023-03-14 23:26:52 +03:00
Logger . enumeration . debug ( " About to read: \( directoryMetadata . serverUrl , privacy : OSLogPrivacy . auto ( mask : . hash ) ) " )
2023-03-13 16:59:46 +03:00
FileProviderEnumerator . readServerUrl ( directoryMetadata . serverUrl , ncAccount : ncAccount , ncKit : ncKit , stopAtMatchingEtags : true ) { _ , newMetadatas , updatedMetadatas , deletedMetadatas , readError in
2023-03-11 00:11:05 +03:00
guard readError = = nil else {
2023-03-14 23:26:52 +03:00
Logger . enumeration . error ( " Finishing enumeration of changes at \( directoryMetadata . serverUrl , privacy : OSLogPrivacy . auto ( mask : . hash ) ) with \( readError ! , privacy : . public ) " )
2023-03-11 00:11:05 +03:00
2023-03-13 17:09:43 +03:00
let nkReadError = NKError ( error : readError ! )
if nkReadError . isNotFoundError {
2023-03-14 23:03:49 +03:00
Logger . enumeration . info ( " 404 error means item no longer exists. Deleting metadata and reporting as deletion without error " )
2023-03-11 00:11:05 +03:00
2023-03-13 17:09:43 +03:00
guard let directoryItemMetadata = dbManager . itemMetadataFromOcId ( directoryMetadata . ocId ) else {
2023-03-14 23:03:49 +03:00
Logger . enumeration . error ( " Can't delete directory properly as item metadata not found... " )
2023-03-13 17:09:43 +03:00
dispatchGroup . leave ( )
return
2023-03-11 00:11:05 +03:00
}
2023-03-13 17:09:43 +03:00
dbManager . deleteDirectoryAndSubdirectoriesMetadata ( ocId : directoryMetadata . ocId )
allDeletedMetadatas . append ( directoryItemMetadata )
} else if nkReadError . isNoChangesError { // A l l i s w e l l , j u s t n o c h a n g e d e t a g s
2023-03-14 23:03:49 +03:00
Logger . enumeration . info ( " Error was to say no changed files -- not bad error. No need to check children. " )
2023-03-11 00:11:05 +03:00
}
dispatchGroup . leave ( )
return
}
2023-03-14 23:26:52 +03:00
Logger . enumeration . info ( " Finished reading serverUrl: \( directoryMetadata . serverUrl , privacy : OSLogPrivacy . auto ( mask : . hash ) ) for user: \( ncAccount . ncKitAccount , privacy : OSLogPrivacy . auto ( mask : . hash ) ) " )
2023-03-11 00:11:05 +03:00
if let newMetadatas = newMetadatas {
allNewMetadatas += newMetadatas
} else {
2023-03-14 23:26:52 +03:00
Logger . enumeration . warning ( " WARNING: Nil new metadatas received for reading of changes at \( directoryMetadata . serverUrl , privacy : OSLogPrivacy . auto ( mask : . hash ) ) for user: \( ncAccount . ncKitAccount , privacy : OSLogPrivacy . auto ( mask : . hash ) ) " )
2023-03-11 00:11:05 +03:00
}
if let updatedMetadatas = updatedMetadatas {
allUpdatedMetadatas += updatedMetadatas
} else {
2023-03-14 23:26:52 +03:00
Logger . enumeration . warning ( " WARNING: Nil updated metadatas received for reading of changes at \( directoryMetadata . serverUrl , privacy : OSLogPrivacy . auto ( mask : . hash ) ) for user: \( ncAccount . ncKitAccount , privacy : OSLogPrivacy . auto ( mask : . hash ) ) " )
2023-03-11 00:11:05 +03:00
}
if let deletedMetadatas = deletedMetadatas {
allDeletedMetadatas += deletedMetadatas
} else {
2023-03-14 23:26:52 +03:00
Logger . enumeration . warning ( " WARNING: Nil deleted metadatas received for reading of changes at \( directoryMetadata . serverUrl , privacy : OSLogPrivacy . auto ( mask : . hash ) ) for user: \( ncAccount . ncKitAccount , privacy : OSLogPrivacy . auto ( mask : . hash ) ) " )
2023-03-11 00:11:05 +03:00
}
dispatchGroup . leave ( )
}
dispatchGroup . wait ( )
var updatedDirectories : [ NextcloudDirectoryMetadataTable ] = [ ]
for updatedMetadata in allUpdatedMetadatas {
if updatedMetadata . directory {
guard let directoryMetadata = dbManager . directoryMetadata ( ocId : updatedMetadata . ocId ) else {
2023-03-14 23:03:49 +03:00
Logger . enumeration . error ( " Could not find matching directory metadata for updated item metadata, cannot scan for updates " )
2023-03-11 00:11:05 +03:00
continue
}
updatedDirectories . append ( directoryMetadata )
}
}
if updatedDirectories . isEmpty {
return ( newMetadatas : allNewMetadatas , updatedMetadatas : allUpdatedMetadatas , deletedMetadatas : allDeletedMetadatas )
}
for childDirectory in updatedDirectories {
let childScanResult = scanRecursivelyForChanges ( childDirectory , ncAccount : ncAccount , ncKit : ncKit )
allNewMetadatas += childScanResult . newMetadatas
allUpdatedMetadatas += childScanResult . updatedMetadatas
allDeletedMetadatas += childScanResult . deletedMetadatas
}
return ( newMetadatas : allNewMetadatas , updatedMetadatas : allUpdatedMetadatas , deletedMetadatas : allDeletedMetadatas )
}
2023-03-13 16:50:50 +03:00
private static func readServerUrl ( _ serverUrl : String , ncAccount : NextcloudAccount , ncKit : NextcloudKit , stopAtMatchingEtags : Bool = false , completionHandler : @ escaping ( _ metadatas : [ NextcloudItemMetadataTable ] ? , _ newMetadatas : [ NextcloudItemMetadataTable ] ? , _ updatedMetadatas : [ NextcloudItemMetadataTable ] ? , _ deletedMetadatas : [ 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
2023-03-14 23:26:52 +03:00
Logger . enumeration . debug ( " Starting to read serverUrl: \( serverUrl , privacy : OSLogPrivacy . auto ( mask : . hash ) ) for user: \( ncAccount . ncKitAccount , privacy : OSLogPrivacy . auto ( mask : . hash ) ) at depth 0. NCKit info: userId: \( ncKit . nkCommonInstance . user ) , password: \( ncKit . nkCommonInstance . password = = " " ? " EMPTY PASSWORD " : " NOT EMPTY PASSWORD " ) , urlBase: \( ncKit . nkCommonInstance . urlBase ) , ncVersion: \( 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 {
2023-03-14 23:26:52 +03:00
Logger . enumeration . error ( " 0 depth readFileOrFolder of url: \( serverUrl , privacy : OSLogPrivacy . auto ( mask : . hash ) ) did not complete successfully, received error: \( error , privacy : . public ) " )
2023-03-09 03:19:00 +03:00
completionHandler ( nil , nil , nil , nil , error . error )
2023-01-27 04:44:34 +03:00
return
}
2023-03-08 02:21:23 +03:00
guard let receivedItem = files . first else {
2023-03-14 23:26:52 +03:00
Logger . enumeration . error ( " Received no items from readFileOrFolder of \( serverUrl , privacy : OSLogPrivacy . auto ( mask : . hash ) ) , not much we can do... " )
2023-03-09 03:19:00 +03:00
completionHandler ( nil , nil , nil , nil , error . error )
2023-03-08 02:21:23 +03:00
return
}
guard receivedItem . directory else {
2023-03-14 23:26:52 +03:00
Logger . enumeration . debug ( " Read item is a file. Converting NKfile for serverUrl: \( serverUrl , privacy : OSLogPrivacy . auto ( mask : . hash ) ) for user: \( ncAccount . ncKitAccount , privacy : OSLogPrivacy . auto ( mask : . hash ) ) " )
2023-03-08 02:21:23 +03:00
let itemMetadata = dbManager . convertNKFileToItemMetadata ( receivedItem , account : ncKitAccount )
2023-03-09 16:51:13 +03:00
dbManager . addItemMetadata ( itemMetadata ) // TODO: R e t u r n s o m e v a l u e w h e n i t i s a n u p d a t e
2023-03-09 03:19:00 +03:00
completionHandler ( [ itemMetadata ] , nil , nil , nil , error . error )
2023-01-26 22:50:40 +03:00
return
}
2023-03-13 16:50:50 +03:00
if stopAtMatchingEtags ,
let directoryMetadata = dbManager . directoryMetadata ( account : ncKitAccount , serverUrl : serverUrl ) {
2023-03-08 02:21:23 +03:00
let directoryEtag = directoryMetadata . etag
guard directoryEtag = = " " || directoryEtag != receivedItem . etag else {
2023-03-14 23:03:49 +03:00
Logger . enumeration . debug ( " Read server url called with flag to stop enumerating at matching etags. Returning and providing soft error. " )
2023-03-14 23:26:52 +03:00
let description = " Fetched directory etag is same as that stored locally. Not fetching child items. "
2023-03-13 16:50:50 +03:00
let nkError = NKError ( errorCode : NKError . noChangesErrorCode , errorDescription : description )
completionHandler ( nil , nil , nil , nil , nkError . error )
2023-03-08 02:21:23 +03:00
return
}
}
2023-03-14 23:26:52 +03:00
Logger . enumeration . debug ( " Starting to read serverUrl: \( serverUrl , privacy : OSLogPrivacy . auto ( mask : . hash ) ) for user: \( ncAccount . ncKitAccount , privacy : OSLogPrivacy . auto ( mask : . hash ) ) at depth 1 " )
2023-01-27 04:44:34 +03:00
2023-03-13 16:50:50 +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-03-14 23:26:52 +03:00
Logger . enumeration . error ( " 1 depth readFileOrFolder of url: \( serverUrl , privacy : OSLogPrivacy . auto ( mask : . hash ) ) did not complete successfully, received error: \( error , privacy : . public ) " )
2023-03-09 03:19:00 +03:00
completionHandler ( nil , nil , nil , nil , error . error )
2023-01-26 22:50:40 +03:00
return
}
2023-03-14 23:26:52 +03:00
Logger . enumeration . debug ( " Starting async conversion of NKFiles for serverUrl: \( serverUrl , privacy : OSLogPrivacy . auto ( mask : . hash ) ) for user: \( ncAccount . ncKitAccount , privacy : OSLogPrivacy . auto ( mask : . hash ) ) " )
2023-03-11 00:25:28 +03:00
DispatchQueue . global ( ) . async {
2023-03-08 02:21:23 +03:00
dbManager . convertNKFilesFromDirectoryReadToItemMetadatas ( files , account : ncKitAccount ) { directoryMetadata , childDirectoriesMetadata , metadatas in
2023-02-01 21:35:08 +03:00
2023-03-09 03:19:00 +03:00
// S T O R E D A T A F O R C U R R E N T L Y S C A N N E D D I R E C T O R Y
2023-02-01 21:35:08 +03:00
// W e h a v e n o w s c a n n e d t h i s d i r e c t o r y ' s c o n t e n t s , s o u p d a t e w i t h e t a g i n o r d e r t o n o t c h e c k a g a i n i f n o t n e e d e d
2023-03-11 02:31:47 +03:00
// u n l e s s i t ' s t h e r o o t c o n t a i n e r
2023-03-09 03:29:38 +03:00
if serverUrl != ncAccount . davFilesUrl {
2023-03-11 02:31:47 +03:00
let directoryItemMetadata = dbManager . directoryMetadataFromItemMetadata ( directoryItemMetadata : directoryMetadata , recordEtag : true )
dbManager . addDirectoryMetadata ( directoryItemMetadata )
2023-03-09 03:29:38 +03:00
}
2023-02-01 21:35:08 +03:00
2023-03-09 03:19:00 +03:00
// S T O R E E T A G - L E S S D I R E C T O R Y M E T A D A T A F O R C H I L D D I R E C T O R I E S
// S i n c e w e h a v e n ' t s c a n n e d t h e c o n t e n t s o f t h e c h i l d d i r e c t o r i e s , d o n ' t r e c o r d t h e i r i t e m M e t a d a t a e t a g s i n t h e d i r e c t o r y t a b l e s
// T h i s w i l l d e l e t e d a t a b a s e r e c o r d s f o r d i r e c t o r i e s t h a t w e d i d n o t g e t f r o m t h e r e a d F i l e O r F o l d e r ( i n d i c a t i n g t h e y w e r e d e l e t e d )
// a s w e l l a s d e l e t i n g t h e r e c o r d s f o r a l l t h e c h i l d r e n c o n t a i n e d b y t h e d i r e c t o r i e s .
// TODO: F i n d a w a y t o d e t e c t i f f i l e s h a v e b e e n m o v e d r a t h e r t h a n d e l e t e d a n d c h a n g e t h e m e t a d a t a s e r v e r u r l s , m o v e t h e m a t e r i a l i s e d f i l e s
2023-01-27 01:01:31 +03:00
dbManager . updateDirectoryMetadatasFromItemMetadatas ( account : ncKitAccount , parentDirectoryServerUrl : serverUrl , updatedDirectoryItemMetadatas : childDirectoriesMetadata )
2023-03-10 03:16:23 +03:00
// TODO: N o t i f y w o r k i n g s e t c h a n g e d i f n e w f o l d e r s f o u n d
2023-02-01 21:35:08 +03:00
2023-03-10 20:36:25 +03:00
dbManager . updateItemMetadatas ( account : ncKitAccount , serverUrl : serverUrl , updatedMetadatas : metadatas ) { newMetadatas , updatedMetadatas , deletedMetadatas in
completionHandler ( metadatas , newMetadatas , updatedMetadatas , deletedMetadatas , nil )
2023-03-09 03:19:00 +03:00
}
2023-01-26 22:50:40 +03:00
}
}
}
}
}
2022-03-29 16:00:59 +03:00
}