2023-03-18 15:41:31 +03:00
/*
* Copyright ( C ) 2023 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
import NextcloudKit
import OSLog
2023-03-19 01:36:25 +03:00
extension FileProviderEnumerator {
2024-01-22 09:40:47 +03:00
func fullRecursiveScan (
ncAccount : NextcloudAccount ,
ncKit : NextcloudKit ,
scanChangesOnly : Bool ,
completionHandler : @ escaping (
_ metadatas : [ NextcloudItemMetadataTable ] ,
_ newMetadatas : [ NextcloudItemMetadataTable ] ,
_ updatedMetadatas : [ NextcloudItemMetadataTable ] ,
_ deletedMetadatas : [ NextcloudItemMetadataTable ] ,
_ error : NKError ?
) -> Void
) {
2023-03-18 15:41:31 +03:00
let rootContainerDirectoryMetadata = NextcloudItemMetadataTable ( )
rootContainerDirectoryMetadata . directory = true
rootContainerDirectoryMetadata . ocId = NSFileProviderItemIdentifier . rootContainer . rawValue
// C r e a t e a s e r i a l d i s p a t c h q u e u e
2024-01-22 09:40:47 +03:00
let dispatchQueue = DispatchQueue (
label : " recursiveChangeEnumerationQueue " , qos : . userInitiated )
2023-03-18 15:41:31 +03:00
dispatchQueue . async {
2024-01-22 09:40:47 +03:00
let results = self . scanRecursively (
rootContainerDirectoryMetadata ,
ncAccount : ncAccount ,
ncKit : ncKit ,
scanChangesOnly : scanChangesOnly )
2023-03-18 15:41:31 +03:00
2023-03-18 16:10:24 +03:00
// R u n a c h e c k t o e n s u r e f i l e s d e l e t e d i n o n e l o c a t i o n a r e n o t u p d a t e d i n a n o t h e r ( e . g . w h e n m o v e d )
// T h e r e c u r s i v e s c a n p r o v i d e s u s w i t h u p d a t e d / d e l e t e d m e t a d a t a s o n l y o n a f o l d e r b y f o l d e r b a s i s ;
// s o w e n e e d t o c h e c k w e a r e n o t s i m u l t a n e o u s l y m a r k i n g a m o v e d f i l e a s d e l e t e d a n d u p d a t e d
var checkedDeletedMetadatas = results . deletedMetadatas
for updatedMetadata in results . updatedMetadatas {
2024-01-22 09:40:47 +03:00
guard
let matchingDeletedMetadataIdx = checkedDeletedMetadatas . firstIndex ( where : {
$0 . ocId = = updatedMetadata . ocId
} )
else {
continue
2023-03-18 16:10:24 +03:00
}
checkedDeletedMetadatas . remove ( at : matchingDeletedMetadataIdx )
}
2023-03-18 15:41:31 +03:00
DispatchQueue . main . async {
2024-01-22 09:40:47 +03:00
completionHandler (
results . metadatas , results . newMetadatas , results . updatedMetadatas ,
checkedDeletedMetadatas , results . error )
2023-03-18 15:41:31 +03:00
}
}
}
2024-01-22 09:40:47 +03:00
private func scanRecursively (
_ directoryMetadata : NextcloudItemMetadataTable ,
ncAccount : NextcloudAccount ,
ncKit : NextcloudKit ,
scanChangesOnly : Bool
) -> (
metadatas : [ NextcloudItemMetadataTable ] ,
newMetadatas : [ NextcloudItemMetadataTable ] ,
updatedMetadatas : [ NextcloudItemMetadataTable ] ,
deletedMetadatas : [ NextcloudItemMetadataTable ] ,
error : NKError ?
) {
if isInvalidated {
2023-03-18 18:05:34 +03:00
return ( [ ] , [ ] , [ ] , [ ] , nil )
}
2023-03-18 15:41:31 +03:00
assert ( directoryMetadata . directory , " Can only recursively scan a directory. " )
2023-03-18 18:44:30 +03:00
// W i l l i n c l u d e r e s u l t s o f r e c u r s i v e c a l l s
2023-03-18 15:41:31 +03:00
var allMetadatas : [ NextcloudItemMetadataTable ] = [ ]
var allNewMetadatas : [ NextcloudItemMetadataTable ] = [ ]
var allUpdatedMetadatas : [ NextcloudItemMetadataTable ] = [ ]
var allDeletedMetadatas : [ NextcloudItemMetadataTable ] = [ ]
let dbManager = NextcloudFilesDatabaseManager . shared
2024-01-22 09:40:47 +03:00
let dispatchGroup = DispatchGroup ( ) // TODO: M a y b e o w n t h r e a d ?
2023-03-18 15:41:31 +03:00
dispatchGroup . enter ( )
2023-03-18 15:57:02 +03:00
var criticalError : NKError ?
2024-01-22 09:40:47 +03:00
let itemServerUrl =
directoryMetadata . ocId = = NSFileProviderItemIdentifier . rootContainer . rawValue
? ncAccount . davFilesUrl : directoryMetadata . serverUrl + " / " + directoryMetadata . fileName
2023-03-18 15:41:31 +03:00
2023-03-21 18:29:21 +03:00
Logger . enumeration . debug ( " About to read: \( itemServerUrl , privacy : . public ) " )
2023-03-18 15:41:31 +03:00
2024-01-22 09:40:47 +03:00
FileProviderEnumerator . readServerUrl (
itemServerUrl , ncAccount : ncAccount , ncKit : ncKit , stopAtMatchingEtags : scanChangesOnly
) { metadatas , newMetadatas , updatedMetadatas , deletedMetadatas , readError in
2023-03-18 15:41:31 +03:00
if readError != nil {
let nkReadError = NKError ( error : readError ! )
// I s t h e e r r o r i s t h a t w e h a v e f o u n d m a t c h i n g e t a g s o n t h i s i t e m , t h e n i g n o r e i t
// i f w e a r e d o i n g a f u l l r e s c a n
2024-01-22 09:40:47 +03:00
guard nkReadError . isNoChangesError , scanChangesOnly else {
Logger . enumeration . error (
" Finishing enumeration of changes at \( itemServerUrl , privacy : . public ) with \( readError ! . localizedDescription , privacy : . public ) "
)
2023-03-18 15:41:31 +03:00
if nkReadError . isNotFoundError {
2024-01-22 09:40:47 +03:00
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 )
{
2023-03-18 15:41:31 +03:00
allDeletedMetadatas += deletedMetadatas
} else {
2024-01-22 09:40:47 +03:00
Logger . enumeration . error (
" An error occurred while trying to delete directory and children not found in recursive scan "
)
2023-03-18 15:41:31 +03:00
}
2023-03-18 15:57:02 +03:00
2024-01-22 09:40:47 +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
Logger . enumeration . info (
" Error was to say no changed files -- not bad error. No need to check children. "
)
2023-03-18 15:57:02 +03:00
2024-01-22 09:40:47 +03:00
} else if nkReadError . isUnauthenticatedError
|| nkReadError . isCouldntConnectError
{
2023-03-18 15:57:02 +03:00
// 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
2024-01-22 09:40:47 +03:00
Logger . enumeration . error (
" Error will affect next enumerated items, so stopping enumeration. " )
2023-03-18 15:57:02 +03:00
criticalError = nkReadError
2023-03-18 15:41:31 +03:00
}
dispatchGroup . leave ( )
return
}
}
2024-01-22 09:40:47 +03:00
Logger . enumeration . info (
" Finished reading serverUrl: \( itemServerUrl , privacy : . public ) for user: \( ncAccount . ncKitAccount , privacy : . public ) "
)
2023-03-18 15:41:31 +03:00
2024-01-22 09:40:47 +03:00
if let metadatas {
2023-03-18 15:41:31 +03:00
allMetadatas += metadatas
} else {
2024-01-22 09:40:47 +03:00
Logger . enumeration . warning (
" WARNING: Nil metadatas received for reading of changes at \( itemServerUrl , privacy : . public ) for user: \( ncAccount . ncKitAccount , privacy : . public ) "
)
2023-03-18 15:41:31 +03:00
}
2024-01-22 09:40:47 +03:00
if let newMetadatas {
2023-03-18 15:41:31 +03:00
allNewMetadatas += newMetadatas
} else {
2024-01-22 09:40:47 +03:00
Logger . enumeration . warning (
" WARNING: Nil new metadatas received for reading of changes at \( itemServerUrl , privacy : . public ) for user: \( ncAccount . ncKitAccount , privacy : . public ) "
)
2023-03-18 15:41:31 +03:00
}
2024-01-22 09:40:47 +03:00
if let updatedMetadatas {
2023-03-18 15:41:31 +03:00
allUpdatedMetadatas += updatedMetadatas
} else {
2024-01-22 09:40:47 +03:00
Logger . enumeration . warning (
" WARNING: Nil updated metadatas received for reading of changes at \( itemServerUrl , privacy : . public ) for user: \( ncAccount . ncKitAccount , privacy : . public ) "
)
2023-03-18 15:41:31 +03:00
}
2024-01-22 09:40:47 +03:00
if let deletedMetadatas {
2023-03-18 15:41:31 +03:00
allDeletedMetadatas += deletedMetadatas
} else {
2024-01-22 09:40:47 +03:00
Logger . enumeration . warning (
" WARNING: Nil deleted metadatas received for reading of changes at \( itemServerUrl , privacy : . public ) for user: \( ncAccount . ncKitAccount , privacy : . public ) "
)
2023-03-18 15:41:31 +03:00
}
dispatchGroup . leave ( )
}
dispatchGroup . wait ( )
2023-03-18 18:44:30 +03:00
guard criticalError = = nil else {
2023-03-18 15:57:02 +03:00
return ( [ ] , [ ] , [ ] , [ ] , error : criticalError )
}
2023-03-18 17:49:20 +03:00
var childDirectoriesToScan : [ NextcloudItemMetadataTable ] = [ ]
2024-01-22 09:40:47 +03:00
var candidateMetadatas : [ NextcloudItemMetadataTable ] =
if scanChangesOnly {
allUpdatedMetadatas + allNewMetadatas
} else {
allMetadatas
}
2023-03-18 17:49:20 +03:00
for candidateMetadata in candidateMetadatas {
if candidateMetadata . directory {
childDirectoriesToScan . append ( candidateMetadata )
2023-03-18 15:41:31 +03:00
}
}
2023-03-18 17:49:20 +03:00
if childDirectoriesToScan . isEmpty {
2024-01-22 09:40:47 +03:00
return (
metadatas : allMetadatas , newMetadatas : allNewMetadatas ,
updatedMetadatas : allUpdatedMetadatas , deletedMetadatas : allDeletedMetadatas , nil
)
2023-03-18 15:41:31 +03:00
}
2023-03-18 17:49:20 +03:00
for childDirectory in childDirectoriesToScan {
2024-01-22 09:40:47 +03:00
let childScanResult = scanRecursively (
childDirectory ,
ncAccount : ncAccount ,
ncKit : ncKit ,
scanChangesOnly : scanChangesOnly )
2023-03-18 15:41:31 +03:00
allMetadatas += childScanResult . metadatas
allNewMetadatas += childScanResult . newMetadatas
allUpdatedMetadatas += childScanResult . updatedMetadatas
allDeletedMetadatas += childScanResult . deletedMetadatas
}
2024-01-22 09:40:47 +03:00
return (
metadatas : allMetadatas , newMetadatas : allNewMetadatas ,
updatedMetadatas : allUpdatedMetadatas ,
deletedMetadatas : allDeletedMetadatas , nil
)
2023-03-18 15:41:31 +03:00
}
2024-01-22 09:40:47 +03:00
static func handleDepth1ReadFileOrFolder (
serverUrl : String ,
ncAccount : NextcloudAccount ,
files : [ NKFile ] ,
error : NKError ,
completionHandler : @ escaping (
_ metadatas : [ NextcloudItemMetadataTable ] ? ,
_ newMetadatas : [ NextcloudItemMetadataTable ] ? ,
_ updatedMetadatas : [ NextcloudItemMetadataTable ] ? ,
_ deletedMetadatas : [ NextcloudItemMetadataTable ] ? ,
_ readError : Error ?
) -> Void
) {
2023-03-18 23:32:26 +03:00
guard error = = . success else {
2024-01-22 09:40:47 +03:00
Logger . enumeration . error (
" 1 depth readFileOrFolder of url: \( serverUrl , privacy : . public ) did not complete successfully, received error: \( error . errorDescription , privacy : . public ) "
)
2023-03-18 23:32:26 +03:00
completionHandler ( nil , nil , nil , nil , error . error )
return
}
2024-01-22 09:40:47 +03:00
Logger . enumeration . debug (
" Starting async conversion of NKFiles for serverUrl: \( serverUrl , privacy : . public ) for user: \( ncAccount . ncKitAccount , privacy : . public ) "
)
2023-03-18 23:32:26 +03:00
let dbManager = NextcloudFilesDatabaseManager . shared
DispatchQueue . global ( qos : . userInitiated ) . async {
2024-01-22 09:40:47 +03:00
NextcloudItemMetadataTable . metadatasFromDirectoryReadNKFiles (
files , account : ncAccount . ncKitAccount
) { directoryMetadata , _ , metadatas in
2023-03-18 23:32:26 +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
// 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
// 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
if serverUrl != ncAccount . davFilesUrl {
dbManager . addItemMetadata ( directoryMetadata )
}
// D o n ' t u p d a t e t h e e t a g s f o r f o l d e r s a s w e h a v e n ' t c h e c k e d t h e i r c o n t e n t s .
// W h e n w e d o a r e c u r s i v e c h e c k , i f w e u p d a t e t h e e t a g s n o w , w e w i l l t h i n k
// t h a t o u r l o c a l c o p i e s a r e u p t o d a t e - - i n s t e a d , l e a v e t h e m a s t h e o l d .
// T h e y w i l l g e t u p d a t e d w h e n t h e y a r e t h e s u b j e c t o f a r e a d S e r v e r U r l c a l l .
// ( S e e a b o v e )
2024-01-22 09:40:47 +03:00
let changedMetadatas = dbManager . updateItemMetadatas (
account : ncAccount . ncKitAccount , serverUrl : serverUrl ,
updatedMetadatas : metadatas ,
updateDirectoryEtags : false )
2023-03-18 23:32:26 +03:00
DispatchQueue . main . async {
2024-01-22 09:40:47 +03:00
completionHandler (
metadatas , changedMetadatas . newMetadatas , changedMetadatas . updatedMetadatas ,
changedMetadatas . deletedMetadatas , nil )
2023-03-18 23:32:26 +03:00
}
}
}
}
2024-01-22 09:40:47 +03:00
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
) {
2023-03-18 15:41:31 +03:00
let dbManager = NextcloudFilesDatabaseManager . shared
let ncKitAccount = ncAccount . ncKitAccount
2024-01-22 09:40:47 +03:00
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 ) "
)
2023-03-18 15:41:31 +03:00
2024-01-22 09:40:47 +03:00
ncKit . readFileOrFolder ( serverUrlFileName : serverUrl , depth : depth , showHiddenFiles : true ) {
_ , files , _ , error in
2023-03-18 15:41:31 +03:00
guard error = = . success else {
2024-01-22 09:40:47 +03:00
Logger . enumeration . error (
" \( depth , privacy : . public ) depth readFileOrFolder of url: \( serverUrl , privacy : . public ) did not complete successfully, received error: \( error . errorDescription , privacy : . public ) "
)
2023-03-18 15:41:31 +03:00
completionHandler ( nil , nil , nil , nil , error . error )
return
}
2023-03-20 16:29:14 +03:00
guard let receivedFile = files . first else {
2024-01-22 09:40:47 +03:00
Logger . enumeration . error (
" Received no items from readFileOrFolder of \( serverUrl , privacy : . public ) , not much we can do... "
)
2023-03-18 15:41:31 +03:00
completionHandler ( nil , nil , nil , nil , error . error )
return
}
2023-03-20 16:29:14 +03:00
guard receivedFile . directory else {
2024-01-22 09:40:47 +03:00
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: 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-18 15:41:31 +03:00
completionHandler ( [ itemMetadata ] , nil , nil , nil , error . error )
return
}
if stopAtMatchingEtags ,
2024-01-22 09:40:47 +03:00
let directoryMetadata = dbManager . directoryMetadata (
account : ncKitAccount , serverUrl : serverUrl )
{
2023-03-18 15:41:31 +03:00
let directoryEtag = directoryMetadata . etag
2023-03-20 16:29:14 +03:00
guard directoryEtag = = " " || directoryEtag != receivedFile . etag else {
2024-01-22 09:40:47 +03:00
Logger . enumeration . debug (
" Read server url called with flag to stop enumerating at matching etags. Returning and providing soft error. "
)
2023-03-18 15:41:31 +03:00
2024-01-22 09:40:47 +03:00
let description =
" Fetched directory etag is same as that stored locally. Not fetching child items. "
let nkError = NKError (
errorCode : NKError . noChangesErrorCode , errorDescription : description )
2023-03-18 15:41:31 +03:00
2024-01-22 09:40:47 +03:00
let metadatas = dbManager . itemMetadatas (
account : ncKitAccount , serverUrl : serverUrl )
2023-03-18 15:41:31 +03:00
completionHandler ( metadatas , nil , nil , nil , nkError . error )
return
}
}
2023-03-18 23:32:26 +03:00
if depth = = " 0 " {
if serverUrl != ncAccount . davFilesUrl {
2024-01-22 09:40:47 +03:00
let metadata = NextcloudItemMetadataTable . fromNKFile (
receivedFile , account : ncKitAccount )
2023-03-18 23:32:26 +03:00
let isNew = dbManager . itemMetadataFromOcId ( metadata . ocId ) = = nil
let updatedMetadatas = isNew ? [ ] : [ metadata ]
let newMetadatas = isNew ? [ metadata ] : [ ]
2023-03-18 15:41:31 +03:00
2023-03-18 23:32:26 +03:00
dbManager . addItemMetadata ( metadata )
2023-03-19 01:36:25 +03:00
DispatchQueue . main . async {
completionHandler ( [ metadata ] , newMetadatas , updatedMetadatas , nil , nil )
}
2023-03-18 15:41:31 +03:00
}
2023-03-18 23:32:26 +03:00
} else {
2024-01-22 09:40:47 +03:00
handleDepth1ReadFileOrFolder (
serverUrl : serverUrl , ncAccount : ncAccount , files : files , error : error ,
completionHandler : completionHandler )
2023-03-18 15:41:31 +03:00
}
}
}
}