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
2022-12-27 02:18:00 +03:00
import NCDesktopClientSocketKit
2023-01-04 23:09:48 +03:00
import NextcloudKit
2024-01-22 09:40:47 +03:00
import OSLog
2022-03-29 16:00:59 +03:00
2023-11-21 05:40:54 +03:00
@objc class FileProviderExtension : NSObject , NSFileProviderReplicatedExtension , NKCommonDelegate {
2022-05-13 14:50:17 +03:00
let domain : NSFileProviderDomain
2023-02-02 22:13:35 +03:00
let ncKit = NextcloudKit ( )
2024-02-05 12:21:32 +03:00
let appGroupIdentifier = Bundle . main . object ( forInfoDictionaryKey : " SocketApiPrefix " ) as ? String
2023-01-04 23:40:30 +03:00
var ncAccount : NextcloudAccount ?
2024-02-05 12:21:32 +03:00
lazy var ncKitBackground = NKBackground ( nkCommonInstance : ncKit . nkCommonInstance )
2023-01-04 23:18:21 +03:00
lazy var socketClient : LocalSocketClient ? = {
2023-01-12 18:38:57 +03:00
guard let containerUrl = pathForAppGroupContainer ( ) else {
2024-02-05 11:56:12 +03:00
Logger . fileProviderExtension . critical ( " Won't start socket client, no container url " )
return nil ;
2023-01-04 23:18:21 +03:00
}
2024-02-05 11:56:12 +03:00
2024-01-22 09:40:47 +03:00
let socketPath = containerUrl . appendingPathComponent (
" .fileprovidersocket " , conformingTo : . archive )
2023-01-04 23:18:21 +03:00
let lineProcessor = FileProviderSocketLineProcessor ( delegate : self )
2023-01-12 18:38:57 +03:00
return LocalSocketClient ( socketPath : socketPath . path , lineProcessor : lineProcessor )
2023-01-04 23:18:21 +03:00
} ( )
2022-12-29 02:21:33 +03:00
2024-02-05 11:56:12 +03:00
let urlSessionIdentifier = " com.nextcloud.session.upload.fileproviderext "
2023-01-04 23:09:48 +03:00
let urlSessionMaximumConnectionsPerHost = 5
lazy var urlSession : URLSession = {
let configuration = URLSessionConfiguration . background ( withIdentifier : urlSessionIdentifier )
configuration . allowsCellularAccess = true
configuration . sessionSendsLaunchEvents = true
configuration . isDiscretionary = false
configuration . httpMaximumConnectionsPerHost = urlSessionMaximumConnectionsPerHost
configuration . requestCachePolicy = NSURLRequest . CachePolicy . reloadIgnoringLocalCacheData
configuration . sharedContainerIdentifier = appGroupIdentifier
2023-02-02 22:13:35 +03:00
2024-01-22 09:40:47 +03:00
let session = URLSession (
2024-02-05 11:56:12 +03:00
configuration : configuration ,
delegate : ncKitBackground ,
delegateQueue : OperationQueue . main
)
2023-01-04 23:09:48 +03:00
return session
} ( )
2024-01-31 11:35:40 +03:00
// W h e t h e r o r n o t w e a r e g o i n g t o r e c u r s i v e l y s c a n n e w f o l d e r s w h e n t h e y a r e d i s c o v e r e d .
// A p p l e ' s r e c o m m e n d a t i o n i s t h a t w e s h o u l d a l w a y s s c a n t h e f i l e h i e r a r c h y f u l l y .
// T h i s d o e s l e a d t o l o n g l o a d t i m e s w h e n a f i l e p r o v i d e r d o m a i n i s i n i t i a l l y c o n f i g u r e d .
// W e c a n i n s t e a d d o a f a s t e n u m e r a t i o n w h e r e w e o n l y s c a n f o l d e r s a s t h e u s e r n a v i g a t e s t h r o u g h
// t h e m , t h e r e b y a v o i d i n g t h i s i s s u e ; t h e t r a d e - o f f i s t h a t w e w i l l b e u n a b l e t o d e t e c t
// m a t e r i a l i s e d f i l e m o v e s t o u n e x p l o r e d f o l d e r s , t h e r e f o r e d e l e t i n g t h e i t e m w h e n w e c o u l d h a v e
// j u s t m o v e d i t i n s t e a d .
//
// S i n c e i t ' s n o t d e s i r a b l e t o c a n c e l a l o n g r e c u r s i v e e n u m e r a t i o n h a l f - w a y t h r o u g h , w e d o t h e
// f a s t e n u m e r a t i o n b y d e f a u l t . W e p r o m p t t h e u s e r o n t h e c l i e n t s i d e t o r u n a p r o p e r , f u l l
// e n u m e r a t i o n i f t h e y w a n t f o r s a f e t y .
2024-02-05 11:57:52 +03:00
lazy var config = FileProviderConfig ( domainIdentifier : domain . identifier )
2024-01-31 11:35:40 +03:00
2022-03-29 16:00:59 +03:00
required init ( domain : NSFileProviderDomain ) {
2024-02-05 11:56:12 +03:00
// T h e c o n t a i n i n g a p p l i c a t i o n m u s t c r e a t e a d o m a i n u s i n g
2024-02-05 12:21:32 +03:00
// ` N S F i l e P r o v i d e r M a n a g e r . a d d ( _ : , c o m p l e t i o n H a n d l e r : ) ` . T h e s y s t e m w i l l t h e n l a u n c h t h e
// a p p l i c a t i o n e x t e n s i o n p r o c e s s , c a l l ` F i l e P r o v i d e r E x t e n s i o n . i n i t ( d o m a i n : ) ` t o i n s t a n t i a t e
// t h e e x t e n s i o n f o r t h a t d o m a i n , a n d c a l l m e t h o d s o n t h e i n s t a n c e .
2022-05-13 14:50:17 +03:00
self . domain = domain
2022-03-29 16:00:59 +03:00
super . init ( )
2024-01-22 09:40:47 +03:00
socketClient ? . start ( )
2022-03-29 16:00:59 +03:00
}
2024-01-22 09:40:47 +03:00
2022-03-29 16:00:59 +03:00
func invalidate ( ) {
// TODO: c l e a n u p a n y r e s o u r c e s
2024-01-22 09:40:47 +03:00
Logger . fileProviderExtension . debug (
2024-02-05 11:56:12 +03:00
" Extension for domain \( self . domain . displayName , privacy : . public ) is being torn down "
)
2022-03-29 16:00:59 +03:00
}
2022-12-29 02:21:33 +03:00
// MARK: N S F i l e P r o v i d e r R e p l i c a t e d E x t e n s i o n p r o t o c o l m e t h o d s
2024-01-22 09:40:47 +03:00
func item (
for identifier : NSFileProviderItemIdentifier , request _ : NSFileProviderRequest ,
completionHandler : @ escaping ( NSFileProviderItem ? , Error ? ) -> Void
) -> Progress {
2022-03-29 16:00:59 +03:00
// r e s o l v e t h e g i v e n i d e n t i f i e r t o a r e c o r d i n t h e m o d e l
2023-01-27 03:54:32 +03:00
2024-01-22 09:40:47 +03:00
Logger . fileProviderExtension . debug (
" Received item request for item with identifier: \( identifier . rawValue , privacy : . public ) "
)
2023-01-26 22:51:23 +03:00
if identifier = = . rootContainer {
2024-01-22 09:40:47 +03:00
guard let ncAccount else {
Logger . fileProviderExtension . error (
" Not providing item: \( identifier . rawValue , privacy : . public ) as account not set up yet "
)
2023-01-26 22:51:23 +03:00
completionHandler ( nil , NSFileProviderError ( . notAuthenticated ) )
return Progress ( )
}
2022-03-29 16:00:59 +03:00
2023-01-26 22:51:23 +03:00
let metadata = NextcloudItemMetadataTable ( )
2023-01-27 01:01:31 +03:00
metadata . account = ncAccount . ncKitAccount
2023-01-26 22:51:23 +03:00
metadata . directory = true
metadata . ocId = NSFileProviderItemIdentifier . rootContainer . rawValue
metadata . fileName = " root "
metadata . fileNameView = " root "
2023-01-27 01:01:31 +03:00
metadata . serverUrl = ncAccount . serverUrl
2023-03-11 03:44:25 +03:00
metadata . classFile = NKCommon . TypeClassFile . directory . rawValue
2023-01-26 22:51:23 +03:00
2024-01-22 09:40:47 +03:00
completionHandler (
FileProviderItem (
metadata : metadata ,
parentItemIdentifier : NSFileProviderItemIdentifier . rootContainer ,
ncKit : ncKit ) , nil )
2023-01-26 22:51:23 +03:00
return Progress ( )
}
let dbManager = NextcloudFilesDatabaseManager . shared
2024-01-22 09:40:47 +03:00
2023-01-26 22:51:23 +03:00
guard let metadata = dbManager . itemMetadataFromFileProviderItemIdentifier ( identifier ) ,
2024-01-22 09:40:47 +03:00
let parentItemIdentifier = dbManager . parentItemIdentifierFromMetadata ( metadata )
else {
2023-01-26 22:51:23 +03:00
completionHandler ( nil , NSFileProviderError ( . noSuchItem ) )
return Progress ( )
}
2024-01-22 09:40:47 +03:00
completionHandler (
FileProviderItem (
metadata : metadata , parentItemIdentifier : parentItemIdentifier , ncKit : ncKit ) , nil )
2022-03-29 16:00:59 +03:00
return Progress ( )
}
2023-02-16 22:38:41 +03:00
2024-01-22 09:40:47 +03:00
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 ) "
)
2023-02-16 22:38:41 +03:00
2023-03-07 15:47:01 +03:00
guard requestedVersion = = nil else {
// TODO: A d d p r o p e r s u p p o r t f o r f i l e v e r s i o n i n g
2024-01-22 09:40:47 +03:00
Logger . fileProviderExtension . error (
" Can't return contents for specific version as this is not supported. " )
completionHandler (
nil , nil ,
NSError ( domain : NSCocoaErrorDomain , code : NSFeatureUnsupportedError , userInfo : [ : ] ) )
2023-03-07 15:47:01 +03:00
return Progress ( )
}
guard ncAccount != nil else {
2024-01-22 09:40:47 +03:00
Logger . fileProviderExtension . error (
" Not fetching contents item: \( itemIdentifier . rawValue , privacy : . public ) as account not set up yet "
)
2023-03-07 15:47:01 +03:00
completionHandler ( nil , nil , NSFileProviderError ( . notAuthenticated ) )
return Progress ( )
}
2023-02-16 22:38:41 +03:00
let dbManager = NextcloudFilesDatabaseManager . shared
let ocId = itemIdentifier . rawValue
guard let metadata = dbManager . itemMetadataFromOcId ( ocId ) else {
2024-01-22 09:40:47 +03:00
Logger . fileProviderExtension . error (
" Could not acquire metadata of item with identifier: \( itemIdentifier . rawValue , privacy : . public ) "
)
2023-02-16 22:38:41 +03:00
completionHandler ( nil , nil , NSFileProviderError ( . noSuchItem ) )
return Progress ( )
}
guard ! metadata . isDocumentViewableOnly else {
2024-01-22 09:40:47 +03:00
Logger . fileProviderExtension . error (
" Could not get contents of item as is readonly: \( itemIdentifier . rawValue , privacy : . public ) \( metadata . fileName , privacy : . public ) "
)
2023-02-16 22:38:41 +03:00
completionHandler ( nil , nil , NSFileProviderError ( . cannotSynchronize ) )
return Progress ( )
}
let serverUrlFileName = metadata . serverUrl + " / " + metadata . fileName
2024-01-22 09:40:47 +03:00
Logger . fileProviderExtension . debug (
" Fetching file with name \( metadata . fileName , privacy : . public ) at URL: \( serverUrlFileName , privacy : . public ) "
)
2023-02-16 22:38:41 +03:00
2023-03-11 14:35:12 +03:00
let progress = Progress ( )
2023-03-07 22:19:10 +03:00
2023-03-08 02:21:23 +03:00
// TODO: H a n d l e f o l d e r s n i c e l y
2023-02-16 22:38:41 +03:00
do {
2024-01-22 09:40:47 +03:00
let fileNameLocalPath = try localPathForNCFile (
ocId : metadata . ocId , fileNameView : metadata . fileNameView , domain : domain )
2023-02-16 22:38:41 +03:00
2024-01-22 09:40:47 +03:00
dbManager . setStatusForItemMetadata (
metadata , status : NextcloudItemMetadataTable . Status . downloading
) { updatedMetadata in
2023-02-16 22:38:41 +03:00
2024-01-22 09:40:47 +03:00
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 "
)
2023-03-15 01:15:02 +03:00
completionHandler ( nil , nil , NSFileProviderError ( . noSuchItem ) )
return
}
2024-01-22 09:40:47 +03:00
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
2023-03-15 01:15:02 +03:00
if error = = . success {
2024-01-22 09:40:47 +03:00
Logger . fileTransfer . debug (
" Acquired contents of item with identifier: \( itemIdentifier . rawValue , privacy : . public ) and filename: \( updatedMetadata . fileName , privacy : . public ) "
)
2023-03-15 13:19:30 +03:00
2023-03-15 01:15:02 +03:00
updatedMetadata . status = NextcloudItemMetadataTable . Status . normal . rawValue
2023-03-15 13:19:30 +03:00
updatedMetadata . sessionError = " "
2023-03-15 01:15:02 +03:00
updatedMetadata . date = ( date ? ? NSDate ( ) ) as Date
updatedMetadata . etag = etag ? ? " "
2023-02-16 22:38:41 +03:00
2023-03-15 01:15:02 +03:00
dbManager . addLocalFileMetadataFromItemMetadata ( updatedMetadata )
dbManager . addItemMetadata ( updatedMetadata )
2023-02-16 22:38:41 +03:00
2024-01-22 09:40:47 +03:00
guard
let parentItemIdentifier = dbManager . parentItemIdentifierFromMetadata (
updatedMetadata )
else {
2023-03-15 01:15:02 +03:00
completionHandler ( nil , nil , NSFileProviderError ( . noSuchItem ) )
return
}
2023-03-15 13:19:30 +03:00
2024-01-22 09:40:47 +03:00
let fpItem = FileProviderItem (
metadata : updatedMetadata , parentItemIdentifier : parentItemIdentifier ,
ncKit : self . ncKit )
2023-03-15 01:15:02 +03:00
completionHandler ( fileNameLocalPath , fpItem , nil )
} else {
2024-01-22 09:40:47 +03:00
Logger . fileTransfer . error (
" Could not acquire contents of item with identifier: \( itemIdentifier . rawValue , privacy : . public ) and fileName: \( updatedMetadata . fileName , privacy : . public ) "
)
2023-03-15 01:15:02 +03:00
2024-01-22 09:40:47 +03:00
updatedMetadata . status =
NextcloudItemMetadataTable . Status . downloadError . rawValue
2023-03-15 01:15:02 +03:00
updatedMetadata . sessionError = error . errorDescription
dbManager . addItemMetadata ( updatedMetadata )
2023-03-21 15:21:51 +03:00
completionHandler ( nil , nil , error . fileProviderError )
2023-03-15 01:15:02 +03:00
}
2023-02-16 22:38:41 +03:00
}
}
2024-01-22 09:40:47 +03:00
} catch {
Logger . fileProviderExtension . error (
" Could not find local path for file \( metadata . fileName , privacy : . public ) , received error: \( error . localizedDescription , privacy : . public ) "
)
2023-03-14 21:01:34 +03:00
completionHandler ( nil , nil , NSFileProviderError ( . cannotSynchronize ) )
2023-02-16 22:38:41 +03:00
}
2023-03-07 22:19:10 +03:00
return progress
2022-03-29 16:00:59 +03:00
}
2024-01-22 09:40:47 +03:00
func createItem (
basedOn itemTemplate : NSFileProviderItem , fields _ : NSFileProviderItemFields ,
contents url : URL ? , options : NSFileProviderCreateItemOptions = [ ] ,
request : NSFileProviderRequest ,
completionHandler : @ escaping ( NSFileProviderItem ? , NSFileProviderItemFields , Bool , Error ? )
->
Void
) -> Progress {
2022-03-29 16:00:59 +03:00
// TODO: a n e w i t e m w a s c r e a t e d o n d i s k , p r o c e s s t h e i t e m ' s c r e a t i o n
2023-02-20 15:42:28 +03:00
2024-01-22 09:40:47 +03:00
Logger . fileProviderExtension . debug (
" Received create item request for item with identifier: \( itemTemplate . itemIdentifier . rawValue , privacy : . public ) and filename: \( itemTemplate . filename , privacy : . public ) "
)
2023-02-21 16:59:10 +03:00
2023-03-07 15:59:18 +03:00
guard itemTemplate . contentType != . symbolicLink else {
2023-03-14 23:04:04 +03:00
Logger . fileProviderExtension . error ( " Cannot create item, symbolic links not supported. " )
2024-01-22 09:40:47 +03:00
completionHandler (
itemTemplate , NSFileProviderItemFields ( ) , false ,
NSError ( domain : NSCocoaErrorDomain , code : NSFeatureUnsupportedError , userInfo : [ : ] ) )
2023-03-07 15:59:18 +03:00
return Progress ( )
}
2024-01-22 09:40:47 +03:00
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 ) )
2023-02-21 16:59:10 +03:00
return Progress ( )
}
let dbManager = NextcloudFilesDatabaseManager . shared
let parentItemIdentifier = itemTemplate . parentItemIdentifier
2024-01-22 09:40:47 +03:00
let itemTemplateIsFolder =
itemTemplate . contentType = = . folder || itemTemplate . contentType = = . directory
2023-02-21 16:59:10 +03:00
if options . contains ( . mayAlreadyExist ) {
// TODO: T h i s n e e d s t o b e p r o p e r l y h a n d l e d w i t h a c h e c k i n t h e d b
2024-01-22 09:40:47 +03:00
Logger . fileProviderExtension . info (
" Not creating item: \( itemTemplate . itemIdentifier . rawValue , privacy : . public ) as it may already exist "
)
completionHandler (
itemTemplate , NSFileProviderItemFields ( ) , false , NSFileProviderError ( . noSuchItem ) )
2023-02-21 16:59:10 +03:00
return Progress ( )
}
2023-03-18 03:43:05 +03:00
var parentItemServerUrl : String
2023-02-21 16:59:10 +03:00
if parentItemIdentifier = = . rootContainer {
2023-03-18 03:43:05 +03:00
parentItemServerUrl = ncAccount . davFilesUrl
2023-02-21 16:59:10 +03:00
} else {
2024-01-22 09:40:47 +03:00
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 ) )
2023-03-18 03:43:05 +03:00
return Progress ( )
}
2023-02-21 16:59:10 +03:00
2023-03-18 03:43:05 +03:00
parentItemServerUrl = parentItemMetadata . serverUrl + " / " + parentItemMetadata . fileName
2023-02-21 16:59:10 +03:00
}
let fileNameLocalPath = url ? . path ? ? " "
2023-03-18 03:43:05 +03:00
let newServerUrlFileName = parentItemServerUrl + " / " + itemTemplate . filename
2023-02-21 16:59:10 +03:00
2024-01-22 09:40:47 +03:00
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 ) "
)
2023-02-21 16:59:10 +03:00
if itemTemplateIsFolder {
2024-01-22 09:40:47 +03:00
ncKit . createFolder ( serverUrlFileName : newServerUrlFileName ) { account , _ , _ , error in
2023-02-21 16:59:10 +03:00
guard error = = . success else {
2024-01-22 09:40:47 +03:00
Logger . fileTransfer . error (
" Could not create new folder with name: \( itemTemplate . filename , privacy : . public ) , received error: \( error . errorDescription , privacy : . public ) "
)
2023-03-21 15:21:51 +03:00
completionHandler ( itemTemplate , [ ] , false , error . fileProviderError )
2023-02-21 16:59:10 +03:00
return
}
2023-03-14 23:04:04 +03:00
// R e a d c o n t e n t s a f t e r c r e a t i o n
2024-01-22 09:40:47 +03:00
self . ncKit . readFileOrFolder (
serverUrlFileName : newServerUrlFileName , depth : " 0 " , showHiddenFiles : true
) { account , files , _ , error in
2023-02-21 16:59:10 +03:00
guard error = = . success else {
2024-01-22 09:40:47 +03:00
Logger . fileTransfer . error (
" Could not read new folder with name: \( itemTemplate . filename , privacy : . public ) , received error: \( error . errorDescription , privacy : . public ) "
)
2023-02-21 16:59:10 +03:00
return
}
DispatchQueue . global ( ) . async {
2024-01-22 09:40:47 +03:00
NextcloudItemMetadataTable . metadatasFromDirectoryReadNKFiles (
files , account : account
) {
directoryMetadata , _ , _ in
2023-02-21 16:59:10 +03:00
dbManager . addItemMetadata ( directoryMetadata )
2024-01-22 09:40:47 +03:00
let fpItem = FileProviderItem (
metadata : directoryMetadata ,
parentItemIdentifier : parentItemIdentifier ,
ncKit : self . ncKit )
2023-02-21 16:59:10 +03:00
completionHandler ( fpItem , [ ] , true , nil )
}
}
}
}
return Progress ( )
}
2023-03-11 14:35:12 +03:00
let progress = Progress ( )
2023-03-07 22:19:10 +03:00
2024-01-22 09:40:47 +03:00
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 ) "
)
2023-03-21 15:21:51 +03:00
completionHandler ( itemTemplate , [ ] , false , error . fileProviderError )
2023-02-21 16:59:10 +03:00
return
}
2024-01-22 09:40:47 +03:00
Logger . fileTransfer . info (
" Successfully uploaded item with identifier: \( ocId , privacy : . public ) and filename: \( itemTemplate . filename , privacy : . public ) "
)
2023-02-21 16:59:10 +03:00
2023-03-15 13:27:14 +03:00
if size != itemTemplate . documentSize as ? Int64 {
2024-01-22 09:40:47 +03:00
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 ) ) "
)
2023-03-15 13:27:14 +03:00
}
2023-02-21 16:59:10 +03:00
let newMetadata = NextcloudItemMetadataTable ( )
newMetadata . date = ( date ? ? NSDate ( ) ) as Date
newMetadata . etag = etag ? ? " "
newMetadata . account = account
newMetadata . fileName = itemTemplate . filename
newMetadata . fileNameView = itemTemplate . filename
newMetadata . ocId = ocId
newMetadata . size = size
newMetadata . contentType = itemTemplate . contentType ? . preferredMIMEType ? ? " "
newMetadata . directory = itemTemplateIsFolder
2023-03-18 03:43:05 +03:00
newMetadata . serverUrl = parentItemServerUrl
2023-02-21 16:59:10 +03:00
newMetadata . session = " "
newMetadata . sessionError = " "
newMetadata . sessionTaskIdentifier = 0
newMetadata . status = NextcloudItemMetadataTable . Status . normal . rawValue
dbManager . addLocalFileMetadataFromItemMetadata ( newMetadata )
dbManager . addItemMetadata ( newMetadata )
2024-01-22 09:40:47 +03:00
let fpItem = FileProviderItem (
metadata : newMetadata , parentItemIdentifier : parentItemIdentifier , ncKit : self . ncKit
)
2023-02-21 16:59:10 +03:00
completionHandler ( fpItem , [ ] , false , nil )
}
2023-03-07 22:19:10 +03:00
return progress
2022-03-29 16:00:59 +03:00
}
2024-01-22 09:40:47 +03:00
func modifyItem (
_ item : NSFileProviderItem , baseVersion _ : NSFileProviderItemVersion ,
changedFields : NSFileProviderItemFields , contents newContents : URL ? ,
options : NSFileProviderModifyItemOptions = [ ] , request : NSFileProviderRequest ,
completionHandler : @ escaping ( NSFileProviderItem ? , NSFileProviderItemFields , Bool , Error ? )
->
Void
) -> Progress {
2023-03-06 21:11:34 +03:00
// A n i t e m w a s m o d i f i e d o n d i s k , p r o c e s s t h e i t e m ' s m o d i f i c a t i o n
// TODO: H a n d l e f i n d e r t h i n g s l i k e t a g s , o t h e r p o s s i b l e i t e m c h a n g e d f i e l d s
2023-02-27 19:31:35 +03:00
2024-01-22 09:40:47 +03:00
Logger . fileProviderExtension . debug (
" Received modify item request for item with identifier: \( item . itemIdentifier . rawValue , privacy : . public ) and filename: \( item . filename , privacy : . public ) "
)
2023-02-27 19:31:35 +03:00
2024-01-22 09:40:47 +03:00
guard let ncAccount else {
Logger . fileProviderExtension . error (
" Not modifying item: \( item . itemIdentifier . rawValue , privacy : . public ) as account not set up yet "
)
2023-03-06 20:18:46 +03:00
completionHandler ( item , [ ] , false , NSFileProviderError ( . notAuthenticated ) )
2023-02-27 19:31:35 +03:00
return Progress ( )
}
let dbManager = NextcloudFilesDatabaseManager . shared
let parentItemIdentifier = item . parentItemIdentifier
2024-01-22 09:40:47 +03:00
let itemTemplateIsFolder = item . contentType = = . folder || item . contentType = = . directory
2023-02-27 19:31:35 +03:00
if options . contains ( . mayAlreadyExist ) {
// TODO: T h i s n e e d s t o b e p r o p e r l y h a n d l e d w i t h a c h e c k i n t h e d b
2024-01-22 09:40:47 +03:00
Logger . fileProviderExtension . warning (
" Modification for item: \( item . itemIdentifier . rawValue , privacy : . public ) may already exist "
)
2023-02-27 19:31:35 +03:00
}
2023-03-18 03:43:05 +03:00
var parentItemServerUrl : String
2023-02-27 19:31:35 +03:00
if parentItemIdentifier = = . rootContainer {
2023-03-18 03:43:05 +03:00
parentItemServerUrl = ncAccount . davFilesUrl
2023-02-27 19:31:35 +03:00
} else {
2024-01-22 09:40:47 +03:00
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 ) "
)
2023-03-18 03:43:05 +03:00
completionHandler ( item , [ ] , false , NSFileProviderError ( . noSuchItem ) )
return Progress ( )
}
2023-02-27 19:31:35 +03:00
2023-03-18 03:43:05 +03:00
parentItemServerUrl = parentItemMetadata . serverUrl + " / " + parentItemMetadata . fileName
2023-02-27 19:31:35 +03:00
}
let fileNameLocalPath = newContents ? . path ? ? " "
2023-03-18 03:43:05 +03:00
let newServerUrlFileName = parentItemServerUrl + " / " + item . filename
2023-02-27 19:31:35 +03:00
2024-01-22 09:40:47 +03:00
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 ) "
)
2023-02-27 19:31:35 +03:00
2023-03-06 20:18:46 +03:00
var modifiedItem = item
2023-03-15 19:21:10 +03:00
// 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
// W e w a n t t o w a i t f o r n e t w o r k o p e r a t i o n s t o f i n i s h b e f o r e w e f i r e o f f s u b s e q u e n t n e t w o r k
// o p e r a t i o n s , o r w e m i g h t c a u s e e x p l o s i o n s ( e . g . t r y i n g t o m o d i f y i t e m s t h a t h a v e j u s t b e e n
// m o v e d e l s e w h e r e )
let dispatchQueue = DispatchQueue ( label : " modifyItemQueue " , qos : . userInitiated )
2023-03-06 21:11:34 +03:00
if changedFields . contains ( . filename ) || changedFields . contains ( . parentItemIdentifier ) {
2023-03-15 19:21:10 +03:00
dispatchQueue . async {
let ocId = item . itemIdentifier . rawValue
2024-01-22 09:40:47 +03:00
Logger . fileProviderExtension . debug (
" Changed fields for item \( ocId , privacy : . public ) with filename \( item . filename , privacy : . public ) includes filename or parentitemidentifier... "
)
2023-02-28 01:04:36 +03:00
2023-03-15 19:21:10 +03:00
guard let metadata = dbManager . itemMetadataFromOcId ( ocId ) else {
2024-01-22 09:40:47 +03:00
Logger . fileProviderExtension . error (
" Could not acquire metadata of item with identifier: \( item . itemIdentifier . rawValue , privacy : . public ) "
)
2023-03-15 19:21:10 +03:00
completionHandler ( item , [ ] , false , NSFileProviderError ( . noSuchItem ) )
return
}
2023-02-28 01:04:36 +03:00
2023-03-15 19:21:10 +03:00
var renameError : NSFileProviderError ?
let oldServerUrlFileName = metadata . serverUrl + " / " + metadata . fileName
2023-02-28 01:04:36 +03:00
2024-01-22 09:40:47 +03:00
let moveFileOrFolderDispatchGroup = DispatchGroup ( ) // M a k e t h i s b l o c k w a i t u n t i l d o n e
2023-03-15 19:21:10 +03:00
moveFileOrFolderDispatchGroup . enter ( )
2023-03-06 20:18:46 +03:00
2024-01-22 09:40:47 +03:00
self . ncKit . moveFileOrFolder (
serverUrlFileNameSource : oldServerUrlFileName ,
serverUrlFileNameDestination : newServerUrlFileName ,
overwrite : false
) { _ , error in
2023-03-15 19:21:10 +03:00
guard error = = . success else {
2024-01-22 09:40:47 +03:00
Logger . fileTransfer . error (
" Could not move file or folder: \( oldServerUrlFileName , privacy : . public ) to \( newServerUrlFileName , privacy : . public ) , received error: \( error . errorDescription , privacy : . public ) "
)
2023-03-21 15:21:51 +03:00
renameError = error . fileProviderError
2023-03-15 19:21:10 +03:00
moveFileOrFolderDispatchGroup . leave ( )
return
}
2023-02-28 01:04:36 +03:00
2023-03-15 19:21:10 +03:00
// R e m e m b e r t h a t a f o l d e r m e t a d a t a ' s s e r v e r U r l i s i t s d i r e c t s e r v e r U R L , w h i l e f o r
// a n i t e m m e t a d a t a t h e s e r v e r U R L i s t h e p a r e n t f o l d e r ' s U R L
if itemTemplateIsFolder {
2024-01-22 09:40:47 +03:00
_ = dbManager . renameDirectoryAndPropagateToChildren (
ocId : ocId , newServerUrl : newServerUrlFileName ,
newFileName : item . filename )
2023-03-20 18:15:39 +03:00
self . signalEnumerator { error in
if error != nil {
2024-01-22 09:40:47 +03:00
Logger . fileTransfer . error (
" Error notifying change in moved directory: \( error ) " )
2023-03-18 22:07:35 +03:00
}
}
2023-03-15 19:21:10 +03:00
} else {
2024-01-22 09:40:47 +03:00
dbManager . renameItemMetadata (
ocId : ocId , newServerUrl : parentItemServerUrl ,
newFileName : item . filename )
2023-03-15 19:21:10 +03:00
}
2023-02-28 01:04:36 +03:00
2023-03-15 19:21:10 +03:00
guard let newMetadata = dbManager . itemMetadataFromOcId ( ocId ) else {
2024-01-22 09:40:47 +03:00
Logger . fileTransfer . error (
" Could not acquire metadata of item with identifier: \( ocId , privacy : . public ) , cannot correctly inform of modification "
)
2023-03-15 19:21:10 +03:00
renameError = NSFileProviderError ( . noSuchItem )
moveFileOrFolderDispatchGroup . leave ( )
return
}
2023-02-28 01:04:36 +03:00
2024-01-22 09:40:47 +03:00
modifiedItem = FileProviderItem (
metadata : newMetadata , parentItemIdentifier : parentItemIdentifier ,
ncKit : self . ncKit )
2023-03-15 19:21:10 +03:00
moveFileOrFolderDispatchGroup . leave ( )
}
2023-03-06 20:18:46 +03:00
2023-03-15 19:21:10 +03:00
moveFileOrFolderDispatchGroup . wait ( )
2023-02-28 01:04:36 +03:00
2023-03-15 19:21:10 +03:00
guard renameError = = nil else {
2024-01-22 09:40:47 +03:00
Logger . fileTransfer . error (
" Stopping rename of item with ocId \( ocId , privacy : . public ) due to error: \( renameError ! . localizedDescription , privacy : . public ) "
)
2023-03-15 19:21:10 +03:00
completionHandler ( modifiedItem , [ ] , false , renameError )
return
}
2023-02-28 01:04:36 +03:00
2023-03-15 19:21:10 +03:00
guard ! itemTemplateIsFolder else {
2024-01-22 09:40:47 +03:00
Logger . fileTransfer . debug (
" Only handling renaming for folders. ocId: \( ocId , privacy : . public ) " )
2023-03-15 19:21:10 +03:00
completionHandler ( modifiedItem , [ ] , false , nil )
return
}
2023-03-06 18:58:36 +03:00
}
2023-03-18 01:38:21 +03:00
// R e t u r n t h e p r o g r e s s i f i t e m i s f o l d e r h e r e w h i l e t h e a s y n c b l o c k r u n s
guard ! itemTemplateIsFolder else {
return Progress ( )
}
2023-03-06 18:58:36 +03:00
}
guard ! itemTemplateIsFolder else {
2024-01-22 09:40:47 +03:00
Logger . fileTransfer . debug (
" System requested modification for folder with ocID \( item . itemIdentifier . rawValue , privacy : . public ) ( \( newServerUrlFileName , privacy : . public ) ) of something other than folder name. "
)
2023-03-06 20:18:46 +03:00
completionHandler ( modifiedItem , [ ] , false , nil )
2023-02-28 01:04:36 +03:00
return Progress ( )
}
2023-03-11 14:35:12 +03:00
let progress = Progress ( )
2023-03-07 22:19:10 +03:00
2023-03-06 18:58:36 +03:00
if changedFields . contains ( . contents ) {
2023-03-15 19:21:10 +03:00
dispatchQueue . async {
2024-01-22 09:40:47 +03:00
Logger . fileProviderExtension . debug (
" Item modification for \( item . itemIdentifier . rawValue , privacy : . public ) \( item . filename , privacy : . public ) includes contents "
)
2023-03-15 01:21:08 +03:00
2023-03-15 19:21:10 +03:00
guard newContents != nil else {
2024-01-22 09:40:47 +03:00
Logger . fileProviderExtension . warning (
" WARNING. Could not upload modified contents as was provided nil contents url. ocId: \( item . itemIdentifier . rawValue , privacy : . public ) "
)
2023-03-15 19:21:10 +03:00
completionHandler ( modifiedItem , [ ] , false , NSFileProviderError ( . noSuchItem ) )
return
2023-03-06 18:58:36 +03:00
}
2023-02-27 19:31:35 +03:00
2023-03-15 19:21:10 +03:00
let ocId = item . itemIdentifier . rawValue
guard let metadata = dbManager . itemMetadataFromOcId ( ocId ) else {
2024-01-22 09:40:47 +03:00
Logger . fileProviderExtension . error (
" Could not acquire metadata of item with identifier: \( ocId , privacy : . public ) "
)
completionHandler (
item , NSFileProviderItemFields ( ) , false , NSFileProviderError ( . noSuchItem ) )
2023-03-15 19:21:10 +03:00
return
}
2023-03-15 01:27:14 +03:00
2024-01-22 09:40:47 +03:00
dbManager . setStatusForItemMetadata (
metadata , status : NextcloudItemMetadataTable . Status . uploading
) { updatedMetadata in
2023-03-15 01:27:14 +03:00
2023-03-15 19:21:10 +03:00
if updatedMetadata = = nil {
2024-01-22 09:40:47 +03:00
Logger . fileProviderExtension . warning (
" Could not acquire updated metadata of item with identifier: \( ocId , privacy : . public ) , unable to update item status to uploading "
)
2023-03-15 19:21:10 +03:00
}
2023-03-15 01:27:14 +03:00
2024-01-22 09:40:47 +03:00
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 ) "
)
2023-03-15 19:21:10 +03:00
if size != item . documentSize as ? Int64 {
2024-01-22 09:40:47 +03:00
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 ) ) "
)
2023-03-15 19:21:10 +03:00
}
let newMetadata = NextcloudItemMetadataTable ( )
newMetadata . date = ( date ? ? NSDate ( ) ) as Date
newMetadata . etag = etag ? ? " "
newMetadata . account = account
newMetadata . fileName = item . filename
newMetadata . fileNameView = item . filename
newMetadata . ocId = ocId
newMetadata . size = size
newMetadata . contentType = item . contentType ? . preferredMIMEType ? ? " "
newMetadata . directory = itemTemplateIsFolder
2023-03-18 03:43:05 +03:00
newMetadata . serverUrl = parentItemServerUrl
2023-03-15 19:21:10 +03:00
newMetadata . session = " "
newMetadata . sessionError = " "
newMetadata . sessionTaskIdentifier = 0
newMetadata . status = NextcloudItemMetadataTable . Status . normal . rawValue
dbManager . addLocalFileMetadataFromItemMetadata ( newMetadata )
dbManager . addItemMetadata ( newMetadata )
2024-01-22 09:40:47 +03:00
modifiedItem = FileProviderItem (
metadata : newMetadata , parentItemIdentifier : parentItemIdentifier ,
ncKit : self . ncKit
)
2023-03-15 19:21:10 +03:00
completionHandler ( modifiedItem , [ ] , false , nil )
} else {
2024-01-22 09:40:47 +03:00
Logger . fileTransfer . error (
" Could not upload item \( item . itemIdentifier . rawValue , privacy : . public ) with filename: \( item . filename , privacy : . public ) , received error: \( error . errorDescription , privacy : . public ) "
)
2023-03-15 19:21:10 +03:00
metadata . status = NextcloudItemMetadataTable . Status . uploadError . rawValue
metadata . sessionError = error . errorDescription
dbManager . addItemMetadata ( metadata )
2023-03-21 15:21:51 +03:00
completionHandler ( modifiedItem , [ ] , false , error . fileProviderError )
2023-03-15 19:21:10 +03:00
return
}
2023-03-15 01:21:08 +03:00
}
}
2023-03-06 18:58:36 +03:00
}
2023-03-11 05:41:20 +03:00
} else {
2024-01-22 09:40:47 +03:00
Logger . fileProviderExtension . debug (
" Nothing more to do with \( item . itemIdentifier . rawValue , privacy : . public ) \( item . filename , privacy : . public ) , modifications complete "
)
2023-03-11 05:41:20 +03:00
completionHandler ( modifiedItem , [ ] , false , nil )
2023-02-27 19:31:35 +03:00
}
2023-03-07 22:19:10 +03:00
return progress
2022-03-29 16:00:59 +03:00
}
2023-03-07 03:15:09 +03:00
2024-01-22 09:40:47 +03:00
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 ) "
)
2023-03-07 03:15:09 +03:00
guard ncAccount != nil else {
2024-01-22 09:40:47 +03:00
Logger . fileProviderExtension . error (
" Not deleting item: \( identifier . rawValue , privacy : . public ) as account not set up yet "
)
2023-03-07 03:15:09 +03:00
completionHandler ( NSFileProviderError ( . notAuthenticated ) )
return Progress ( )
}
let dbManager = NextcloudFilesDatabaseManager . shared
let ocId = identifier . rawValue
guard let itemMetadata = dbManager . itemMetadataFromOcId ( ocId ) else {
completionHandler ( NSFileProviderError ( . noSuchItem ) )
return Progress ( )
}
let serverFileNameUrl = itemMetadata . serverUrl + " / " + itemMetadata . fileName
guard serverFileNameUrl != " " else {
completionHandler ( NSFileProviderError ( . noSuchItem ) )
return Progress ( )
}
2024-01-22 09:40:47 +03:00
ncKit . deleteFileOrFolder ( serverUrlFileName : serverFileNameUrl ) { _ , error in
2023-03-07 03:15:09 +03:00
guard error = = . success else {
2024-01-22 09:40:47 +03:00
Logger . fileTransfer . error (
" Could not delete item with ocId \( identifier . rawValue , privacy : . public ) at \( serverFileNameUrl , privacy : . public ) , received error: \( error . errorDescription , privacy : . public ) "
)
2023-03-21 15:21:51 +03:00
completionHandler ( error . fileProviderError )
2023-03-07 03:15:09 +03:00
return
}
2024-01-22 09:40:47 +03:00
Logger . fileTransfer . info (
" Successfully deleted item with identifier: \( identifier . rawValue , privacy : . public ) at: \( serverFileNameUrl , privacy : . public ) "
)
2023-03-07 03:15:09 +03:00
2023-03-09 18:47:27 +03:00
if itemMetadata . directory {
2023-03-20 16:56:41 +03:00
_ = dbManager . deleteDirectoryAndSubdirectoriesMetadata ( ocId : ocId )
} else {
dbManager . deleteItemMetadata ( ocId : ocId )
if dbManager . localFileMetadataFromOcId ( ocId ) != nil {
dbManager . deleteLocalFileMetadata ( ocId : ocId )
}
2023-03-07 03:15:09 +03:00
}
completionHandler ( nil )
}
2022-03-29 16:00:59 +03:00
return Progress ( )
}
2023-01-27 04:26:27 +03:00
2024-01-22 09:40:47 +03:00
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 "
)
2023-01-27 04:26:27 +03:00
throw NSFileProviderError ( . notAuthenticated )
}
2024-01-22 09:40:47 +03:00
return FileProviderEnumerator (
2024-01-31 11:35:40 +03:00
enumeratedItemIdentifier : containerItemIdentifier ,
ncAccount : ncAccount ,
ncKit : ncKit ,
2024-02-05 13:30:41 +03:00
fastEnumeration : config . fastEnumerationEnabled
2024-01-31 11:35:40 +03:00
)
2022-03-29 16:00:59 +03:00
}
2022-12-29 02:21:33 +03:00
2023-03-13 15:43:31 +03:00
func materializedItemsDidChange ( completionHandler : @ escaping ( ) -> Void ) {
2024-01-22 09:40:47 +03:00
guard let ncAccount else {
Logger . fileProviderExtension . error (
" Not purging stale local file metadatas, account not set up " )
2023-03-13 15:43:31 +03:00
completionHandler ( )
return
}
guard let fpManager = NSFileProviderManager ( for : domain ) else {
2024-01-22 09:40:47 +03:00
Logger . fileProviderExtension . error (
" Could not get file provider manager for domain: \( self . domain . displayName , privacy : . public ) "
)
2023-03-13 15:43:31 +03:00
completionHandler ( )
return
}
let materialisedEnumerator = fpManager . enumeratorForMaterializedItems ( )
2024-01-22 09:40:47 +03:00
let materialisedObserver = FileProviderMaterialisedEnumerationObserver (
ncKitAccount : ncAccount . ncKitAccount
) { _ in
2023-03-13 15:43:31 +03:00
completionHandler ( )
}
let startingPage = NSFileProviderPage ( NSFileProviderPage . initialPageSortedByName as Data )
materialisedEnumerator . enumerateItems ( for : materialisedObserver , startingAt : startingPage )
}
2024-01-22 09:40:47 +03:00
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. "
)
2023-03-18 03:43:05 +03:00
return
}
fpManager . signalEnumerator ( for : . workingSet , completionHandler : completionHandler )
}
2022-03-29 16:00:59 +03:00
}