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 OSLog
import NCDesktopClientSocketKit
2023-01-04 23:09:48 +03:00
import NextcloudKit
2022-03-29 16:00:59 +03:00
2023-01-28 19:07:13 +03:00
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 ( )
lazy var ncKitBackground : NKBackground = {
let nckb = NKBackground ( nkCommonInstance : ncKit . nkCommonInstance )
return nckb
} ( )
2023-01-04 23:09:48 +03:00
let appGroupIdentifier : String ? = Bundle . main . object ( forInfoDictionaryKey : " SocketApiPrefix " ) as ? String
2023-01-04 23:40:30 +03:00
var ncAccount : NextcloudAccount ?
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 {
2023-03-14 23:04:04 +03:00
Logger . fileProviderExtension . critical ( " Could not start file provider socket client properly as could not get container url " )
2023-01-04 23:18:21 +03:00
return nil ;
}
2023-01-12 18:38:57 +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
2023-01-04 23:09:48 +03:00
let urlSessionIdentifier : String = " com.nextcloud.session.upload.fileproviderext "
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
let session = URLSession ( configuration : configuration , delegate : ncKitBackground , delegateQueue : OperationQueue . main )
2023-01-04 23:09:48 +03:00
return session
} ( )
2022-03-29 16:00:59 +03:00
required init ( domain : NSFileProviderDomain ) {
2022-05-13 14:50:17 +03:00
self . domain = domain
2022-05-11 19:52:14 +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 ` 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-12-27 02:18:00 +03:00
2022-03-29 16:00:59 +03:00
super . init ( )
2023-01-04 23:18:21 +03:00
self . socketClient ? . start ( )
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
2023-03-14 23:34:08 +03:00
Logger . fileProviderExtension . debug ( " Extension for domain \( self . domain . displayName , privacy : OSLogPrivacy . auto ( mask : . hash ) ) 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
2022-03-29 16:00:59 +03:00
func item ( for identifier : NSFileProviderItemIdentifier , request : NSFileProviderRequest , completionHandler : @ escaping ( NSFileProviderItem ? , Error ? ) -> Void ) -> Progress {
// 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
2023-03-14 23:34:08 +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 {
2023-01-27 01:01:31 +03:00
guard let ncAccount = ncAccount else {
2023-03-14 23:34:08 +03:00
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
2023-02-02 22:13:35 +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
2023-03-10 20:37:00 +03:00
2023-01-26 22:51:23 +03:00
guard let metadata = dbManager . itemMetadataFromFileProviderItemIdentifier ( identifier ) ,
2023-03-13 14:34:05 +03:00
let parentItemIdentifier = dbManager . parentItemIdentifierFromMetadata ( metadata ) else {
2023-01-26 22:51:23 +03:00
completionHandler ( nil , NSFileProviderError ( . noSuchItem ) )
return Progress ( )
}
2023-02-02 22:13:35 +03:00
completionHandler ( FileProviderItem ( metadata : metadata , parentItemIdentifier : parentItemIdentifier , ncKit : ncKit ) , nil )
2022-03-29 16:00:59 +03:00
return Progress ( )
}
func fetchContents ( for itemIdentifier : NSFileProviderItemIdentifier , version requestedVersion : NSFileProviderItemVersion ? , request : NSFileProviderRequest , completionHandler : @ escaping ( URL ? , NSFileProviderItem ? , Error ? ) -> Void ) -> Progress {
2023-02-16 22:38:41 +03:00
2023-03-14 23:34:08 +03:00
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
2023-03-14 23:04:04 +03:00
Logger . fileProviderExtension . error ( " Can't return contents for specific version as this is not supported. " )
2023-03-07 15:47:01 +03:00
completionHandler ( nil , nil , NSError ( domain : NSCocoaErrorDomain , code : NSFeatureUnsupportedError , userInfo : [ : ] ) )
return Progress ( )
}
guard ncAccount != nil else {
2023-03-14 23:34:08 +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 {
2023-03-14 23:34:08 +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 {
2023-03-14 23:34:08 +03:00
Logger . fileProviderExtension . error ( " Could not get contents of item as is readonly: \( itemIdentifier . rawValue , privacy : . public ) \( metadata . fileName , privacy : OSLogPrivacy . auto ( mask : . hash ) ) " )
2023-02-16 22:38:41 +03:00
completionHandler ( nil , nil , NSFileProviderError ( . cannotSynchronize ) )
return Progress ( )
}
let serverUrlFileName = metadata . serverUrl + " / " + metadata . fileName
2023-03-14 23:34:08 +03:00
Logger . fileProviderExtension . debug ( " Fetching file with name \( metadata . fileName , privacy : OSLogPrivacy . auto ( mask : . hash ) ) at URL: \( serverUrlFileName , privacy : OSLogPrivacy . auto ( mask : . hash ) ) " )
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 {
2023-03-15 14:59:02 +03:00
let fileNameLocalPath = try localPathForNCFile ( ocId : metadata . ocId , fileNameView : metadata . fileNameView , domain : self . domain )
2023-02-16 22:38:41 +03:00
2023-03-15 01:15:02 +03:00
dbManager . setStatusForItemMetadata ( metadata , status : NextcloudItemMetadataTable . Status . downloading ) { updatedMetadata in
2023-02-16 22:38:41 +03:00
2023-03-15 01:15:02 +03:00
guard let updatedMetadata = updatedMetadata else {
Logger . fileProviderExtension . error ( " Could not acquire updated metadata of item with identifier: \( itemIdentifier . rawValue , privacy : . public ) , unable to update item status to downloading " )
completionHandler ( nil , nil , NSFileProviderError ( . noSuchItem ) )
return
}
self . ncKit . download ( serverUrlFileName : serverUrlFileName ,
fileNameLocalPath : fileNameLocalPath . path ,
2023-03-15 14:37:14 +03:00
requestHandler : { request in
progress . setHandlersFromAfRequest ( request )
2023-03-15 01:15:02 +03:00
} , taskHandler : { task in
NSFileProviderManager ( for : self . domain ) ? . register ( task , forItemWithIdentifier : itemIdentifier , completionHandler : { _ in } )
} , progressHandler : { downloadProgress in
downloadProgress . copyCurrentStateToProgress ( progress )
} ) { _ , etag , date , _ , _ , _ , error in
if error = = . success {
Logger . fileTransfer . debug ( " Acquired contents of item with identifier: \( itemIdentifier . rawValue , privacy : . public ) and filename: \( updatedMetadata . fileName , privacy : OSLogPrivacy . auto ( mask : . hash ) ) " )
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
2023-03-15 01:15:02 +03:00
guard let parentItemIdentifier = dbManager . parentItemIdentifierFromMetadata ( updatedMetadata ) else {
completionHandler ( nil , nil , NSFileProviderError ( . noSuchItem ) )
return
}
2023-03-15 13:19:30 +03:00
2023-03-15 01:15:02 +03:00
let fpItem = FileProviderItem ( metadata : updatedMetadata , parentItemIdentifier : parentItemIdentifier , ncKit : self . ncKit )
completionHandler ( fileNameLocalPath , fpItem , nil )
} else {
Logger . fileTransfer . error ( " Could not acquire contents of item with identifier: \( itemIdentifier . rawValue , privacy : . public ) and fileName: \( updatedMetadata . fileName , privacy : OSLogPrivacy . auto ( mask : . hash ) ) " )
updatedMetadata . status = NextcloudItemMetadataTable . Status . downloadError . rawValue
updatedMetadata . sessionError = error . errorDescription
dbManager . addItemMetadata ( updatedMetadata )
completionHandler ( nil , nil , error . toFileProviderError ( ) )
}
2023-02-16 22:38:41 +03:00
}
}
} catch let error {
2023-03-14 23:34:08 +03:00
Logger . fileProviderExtension . error ( " Could not find local path for file \( metadata . fileName , privacy : OSLogPrivacy . auto ( mask : . hash ) ) , received error: \( error , 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
}
func createItem ( basedOn itemTemplate : NSFileProviderItem , fields : NSFileProviderItemFields , contents url : URL ? , options : NSFileProviderCreateItemOptions = [ ] , request : NSFileProviderRequest , completionHandler : @ escaping ( NSFileProviderItem ? , NSFileProviderItemFields , Bool , Error ? ) -> Void ) -> Progress {
// 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
2023-03-14 23:34:08 +03:00
Logger . fileProviderExtension . debug ( " Received create item request for item with identifier: \( itemTemplate . itemIdentifier . rawValue , privacy : . public ) and filename: \( itemTemplate . filename , privacy : OSLogPrivacy . auto ( mask : . hash ) ) " )
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. " )
2023-03-07 15:59:18 +03:00
completionHandler ( itemTemplate , NSFileProviderItemFields ( ) , false , NSError ( domain : NSCocoaErrorDomain , code : NSFeatureUnsupportedError , userInfo : [ : ] ) )
return Progress ( )
}
2023-02-21 16:59:10 +03:00
guard let ncAccount = ncAccount else {
2023-03-14 23:34:08 +03:00
Logger . fileProviderExtension . error ( " Not creating item: \( itemTemplate . itemIdentifier . rawValue , privacy : . public ) as account not set up yet " )
2023-02-21 16:59:10 +03:00
completionHandler ( itemTemplate , NSFileProviderItemFields ( ) , false , NSFileProviderError ( . notAuthenticated ) )
return Progress ( )
}
let dbManager = NextcloudFilesDatabaseManager . shared
let parentItemIdentifier = itemTemplate . parentItemIdentifier
let itemTemplateIsFolder = itemTemplate . contentType = = . folder ||
itemTemplate . contentType = = . directory
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
2023-03-14 23:34:08 +03:00
Logger . fileProviderExtension . info ( " Not creating item: \( itemTemplate . itemIdentifier . rawValue , privacy : . public ) as it may already exist " )
2023-02-21 16:59:10 +03:00
completionHandler ( itemTemplate , NSFileProviderItemFields ( ) , false , NSFileProviderError ( . noSuchItem ) )
return Progress ( )
}
var parentItemMetadata : NextcloudDirectoryMetadataTable ?
if parentItemIdentifier = = . rootContainer {
let rootMetadata = NextcloudDirectoryMetadataTable ( )
rootMetadata . account = ncAccount . ncKitAccount
rootMetadata . ocId = NSFileProviderItemIdentifier . rootContainer . rawValue
rootMetadata . serverUrl = ncAccount . davFilesUrl
parentItemMetadata = rootMetadata
} else {
parentItemMetadata = dbManager . directoryMetadata ( ocId : parentItemIdentifier . rawValue )
}
guard let parentItemMetadata = parentItemMetadata else {
2023-03-14 23:34:08 +03:00
Logger . fileProviderExtension . error ( " Not creating item: \( itemTemplate . itemIdentifier . rawValue , privacy : . public ) , could not find metadata for parentItemIdentifier \( parentItemIdentifier . rawValue , privacy : . public ) " )
2023-02-21 16:59:10 +03:00
completionHandler ( itemTemplate , NSFileProviderItemFields ( ) , false , NSFileProviderError ( . noSuchItem ) )
return Progress ( )
}
let fileNameLocalPath = url ? . path ? ? " "
let newServerUrlFileName = parentItemMetadata . serverUrl + " / " + itemTemplate . filename
2023-03-14 23:34:08 +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 : OSLogPrivacy . auto ( mask : . hash ) ) with contents located at: \( fileNameLocalPath , privacy : OSLogPrivacy . auto ( mask : . hash ) ) " )
2023-02-21 16:59:10 +03:00
if itemTemplateIsFolder {
self . ncKit . createFolder ( serverUrlFileName : newServerUrlFileName ) { account , ocId , _ , error in
guard error = = . success else {
2023-03-14 23:34:08 +03:00
Logger . fileTransfer . error ( " Could not create new folder with name: \( itemTemplate . filename , privacy : . public ) , received error: \( error , privacy : . public ) " )
2023-03-13 17:17:26 +03:00
completionHandler ( itemTemplate , [ ] , false , error . toFileProviderError ( ) )
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
2023-02-21 16:59:10 +03:00
self . ncKit . readFileOrFolder ( serverUrlFileName : newServerUrlFileName , depth : " 0 " , showHiddenFiles : true ) { account , files , _ , error in
guard error = = . success else {
2023-03-14 23:34:08 +03:00
Logger . fileTransfer . error ( " Could not read new folder with name: \( itemTemplate . filename , privacy : OSLogPrivacy . auto ( mask : . hash ) ) , received error: \( error , privacy : . public ) " )
2023-02-21 16:59:10 +03:00
return
}
DispatchQueue . global ( ) . async {
2023-03-08 02:21:23 +03:00
dbManager . convertNKFilesFromDirectoryReadToItemMetadatas ( files , account : account ) { directoryMetadata , childDirectoriesMetadata , metadatas in
2023-02-21 16:59:10 +03:00
let newDirectoryMetadata = dbManager . directoryMetadataFromItemMetadata ( directoryItemMetadata : directoryMetadata )
dbManager . addDirectoryMetadata ( newDirectoryMetadata )
dbManager . addItemMetadata ( directoryMetadata )
let fpItem = FileProviderItem ( metadata : directoryMetadata , parentItemIdentifier : parentItemIdentifier , ncKit : self . ncKit )
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
2023-02-21 16:59:10 +03:00
self . ncKit . upload ( serverUrlFileName : newServerUrlFileName ,
fileNameLocalPath : fileNameLocalPath ,
2023-03-15 14:37:14 +03:00
requestHandler : { request in
progress . setHandlersFromAfRequest ( request )
2023-02-21 16:59:10 +03:00
} , taskHandler : { task in
NSFileProviderManager ( for : self . domain ) ? . register ( task , forItemWithIdentifier : itemTemplate . itemIdentifier , completionHandler : { _ in } )
2023-03-07 22:19:10 +03:00
} , progressHandler : { uploadProgress in
uploadProgress . copyCurrentStateToProgress ( progress )
2023-02-21 16:59:10 +03:00
} ) { account , ocId , etag , date , size , _ , _ , error in
2023-03-15 13:27:14 +03:00
guard error = = . success , let ocId = ocId else {
2023-03-14 23:34:08 +03:00
Logger . fileTransfer . error ( " Could not upload item with filename: \( itemTemplate . filename , privacy : OSLogPrivacy . auto ( mask : . hash ) ) , received error: \( error , privacy : . public ) " )
2023-03-13 17:17:26 +03:00
completionHandler ( itemTemplate , [ ] , false , error . toFileProviderError ( ) )
2023-02-21 16:59:10 +03:00
return
}
2023-03-14 23:34:08 +03:00
Logger . fileTransfer . info ( " Successfully uploaded item with identifier: \( ocId , privacy : . public ) and filename: \( itemTemplate . filename , privacy : OSLogPrivacy . auto ( mask : . hash ) ) " )
2023-02-21 16:59:10 +03:00
2023-03-15 13:27:14 +03:00
if size != itemTemplate . documentSize as ? Int64 {
Logger . fileTransfer . warning ( " Created item upload reported as successful, but there are differences between the received file size ( \( size , privacy : . public ) ) and the original file size ( \( itemTemplate . documentSize ? ? 0 ) ) " )
}
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
newMetadata . serverUrl = parentItemMetadata . serverUrl
newMetadata . session = " "
newMetadata . sessionError = " "
newMetadata . sessionTaskIdentifier = 0
newMetadata . status = NextcloudItemMetadataTable . Status . normal . rawValue
dbManager . addLocalFileMetadataFromItemMetadata ( newMetadata )
dbManager . addItemMetadata ( newMetadata )
let fpItem = FileProviderItem ( metadata : newMetadata , parentItemIdentifier : parentItemIdentifier , ncKit : self . ncKit )
completionHandler ( fpItem , [ ] , false , nil )
}
2023-03-07 22:19:10 +03:00
return progress
2022-03-29 16:00:59 +03:00
}
func modifyItem ( _ item : NSFileProviderItem , baseVersion version : 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
2023-03-14 23:34:08 +03:00
Logger . fileProviderExtension . debug ( " Received modify item request for item with identifier: \( item . itemIdentifier . rawValue , privacy : . public ) and filename: \( item . filename , privacy : OSLogPrivacy . auto ( mask : . hash ) ) " )
2023-02-27 19:31:35 +03:00
guard let ncAccount = ncAccount else {
2023-03-14 23:34:08 +03:00
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
let itemTemplateIsFolder = item . contentType = = . folder ||
item . contentType = = . directory
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
2023-03-14 23:34:08 +03:00
Logger . fileProviderExtension . warning ( " Modification for item: \( item . itemIdentifier . rawValue , privacy : . public ) may already exist " )
2023-02-27 19:31:35 +03:00
}
var parentItemMetadata : NextcloudDirectoryMetadataTable ?
if parentItemIdentifier = = . rootContainer {
let rootMetadata = NextcloudDirectoryMetadataTable ( )
rootMetadata . account = ncAccount . ncKitAccount
rootMetadata . ocId = NSFileProviderItemIdentifier . rootContainer . rawValue
rootMetadata . serverUrl = ncAccount . davFilesUrl
parentItemMetadata = rootMetadata
} else {
parentItemMetadata = dbManager . directoryMetadata ( ocId : parentItemIdentifier . rawValue )
}
guard let parentItemMetadata = parentItemMetadata else {
2023-03-14 23:34:08 +03:00
Logger . fileProviderExtension . error ( " Not modifying item: \( item . itemIdentifier . rawValue , privacy : . public ) , could not find metadata for parentItemIdentifier \( parentItemIdentifier . rawValue , privacy : . public ) " )
2023-03-06 20:18:46 +03:00
completionHandler ( item , [ ] , false , NSFileProviderError ( . noSuchItem ) )
2023-02-27 19:31:35 +03:00
return Progress ( )
}
let fileNameLocalPath = newContents ? . path ? ? " "
let newServerUrlFileName = parentItemMetadata . serverUrl + " / " + item . filename
2023-03-14 23:34:08 +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 : OSLogPrivacy . auto ( mask : . hash ) ) to server url: \( newServerUrlFileName , privacy : OSLogPrivacy . auto ( mask : . hash ) ) with contents located at: \( fileNameLocalPath , privacy : OSLogPrivacy . auto ( mask : . hash ) ) " )
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
Logger . fileProviderExtension . debug ( " Changed fields for item \( ocId , privacy : . public ) with filename \( item . filename , privacy : OSLogPrivacy . auto ( mask : . hash ) ) 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 {
Logger . fileProviderExtension . error ( " Could not acquire metadata of item with identifier: \( item . itemIdentifier . rawValue , privacy : . public ) " )
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
2023-03-15 19:21:10 +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
moveFileOrFolderDispatchGroup . enter ( )
2023-03-06 20:18:46 +03:00
2023-03-15 19:21:10 +03:00
self . ncKit . moveFileOrFolder ( serverUrlFileNameSource : oldServerUrlFileName ,
serverUrlFileNameDestination : newServerUrlFileName ,
overwrite : false ) { account , error in
guard error = = . success else {
Logger . fileTransfer . error ( " Could not move file or folder: \( oldServerUrlFileName , privacy : OSLogPrivacy . auto ( mask : . hash ) ) to \( newServerUrlFileName , privacy : OSLogPrivacy . auto ( mask : . hash ) ) , received error: \( error . error , privacy : . public ) " )
renameError = error . toFileProviderError ( )
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 {
dbManager . renameDirectoryAndPropagateToChildren ( ocId : ocId , newServerUrl : newServerUrlFileName , newFileName : item . filename )
} else {
dbManager . renameItemMetadata ( ocId : ocId , newServerUrl : parentItemMetadata . serverUrl , newFileName : item . filename )
}
2023-02-28 01:04:36 +03:00
2023-03-15 19:21:10 +03:00
guard let newMetadata = dbManager . itemMetadataFromOcId ( ocId ) else {
Logger . fileTransfer . error ( " Could not acquire metadata of item with identifier: \( ocId , privacy : . public ) , cannot correctly inform of modification " )
renameError = NSFileProviderError ( . noSuchItem )
moveFileOrFolderDispatchGroup . leave ( )
return
}
2023-02-28 01:04:36 +03:00
2023-03-15 19:21:10 +03:00
modifiedItem = FileProviderItem ( metadata : newMetadata , parentItemIdentifier : parentItemIdentifier , ncKit : self . ncKit )
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 {
Logger . fileTransfer . error ( " Stopping rename of item with ocId \( ocId , privacy : . public ) due to error: \( renameError , privacy : . public ) " )
completionHandler ( modifiedItem , [ ] , false , renameError )
return
}
2023-02-28 01:04:36 +03:00
2023-03-15 19:21:10 +03:00
guard ! itemTemplateIsFolder else {
Logger . fileTransfer . debug ( " Only handling renaming for folders. ocId: \( ocId , privacy : . public ) " )
completionHandler ( modifiedItem , [ ] , false , nil )
return
}
2023-03-06 18:58:36 +03:00
}
}
guard ! itemTemplateIsFolder else {
2023-03-14 23:34:08 +03:00
Logger . fileTransfer . debug ( " System requested modification for folder with ocID \( item . itemIdentifier . rawValue , privacy : . public ) ( \( newServerUrlFileName , privacy : OSLogPrivacy . auto ( mask : . hash ) ) ) 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 {
Logger . fileProviderExtension . debug ( " Item modification for \( item . itemIdentifier . rawValue , privacy : . public ) \( item . filename , privacy : OSLogPrivacy . auto ( mask : . hash ) ) includes contents " )
2023-03-15 01:21:08 +03:00
2023-03-15 19:21:10 +03:00
guard newContents != nil else {
Logger . fileProviderExtension . warning ( " WARNING. Could not upload modified contents as was provided nil contents url. ocId: \( item . itemIdentifier . rawValue , privacy : . public ) " )
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 {
Logger . fileProviderExtension . error ( " Could not acquire metadata of item with identifier: \( ocId , privacy : . public ) " )
completionHandler ( item , NSFileProviderItemFields ( ) , false , NSFileProviderError ( . noSuchItem ) )
return
}
2023-03-15 01:27:14 +03:00
2023-03-15 19:21:10 +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 {
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 01:27:14 +03:00
2023-03-15 19:21:10 +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 = ocId {
Logger . fileProviderExtension . info ( " Successfully uploaded item with identifier: \( ocId , privacy : . public ) and filename: \( item . filename , privacy : OSLogPrivacy . auto ( mask : . hash ) ) " )
if size != item . documentSize as ? Int64 {
Logger . fileTransfer . warning ( " Created item upload reported as successful, but there are differences between the received file size ( \( size , privacy : . public ) ) and the original file size ( \( item . documentSize ? ? 0 ) ) " )
}
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
newMetadata . serverUrl = parentItemMetadata . serverUrl
newMetadata . session = " "
newMetadata . sessionError = " "
newMetadata . sessionTaskIdentifier = 0
newMetadata . status = NextcloudItemMetadataTable . Status . normal . rawValue
dbManager . addLocalFileMetadataFromItemMetadata ( newMetadata )
dbManager . addItemMetadata ( newMetadata )
modifiedItem = FileProviderItem ( metadata : newMetadata , parentItemIdentifier : parentItemIdentifier , ncKit : self . ncKit )
completionHandler ( modifiedItem , [ ] , false , nil )
} else {
Logger . fileTransfer . error ( " Could not upload item \( item . itemIdentifier . rawValue , privacy : . public ) with filename: \( item . filename , privacy : OSLogPrivacy . auto ( mask : . hash ) ) , received error: \( error . error , privacy : . public ) " )
metadata . status = NextcloudItemMetadataTable . Status . uploadError . rawValue
metadata . sessionError = error . errorDescription
dbManager . addItemMetadata ( metadata )
completionHandler ( modifiedItem , [ ] , false , error . toFileProviderError ( ) )
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 {
2023-03-14 23:34:08 +03:00
Logger . fileProviderExtension . debug ( " Nothing more to do with \( item . itemIdentifier . rawValue , privacy : . public ) \( item . filename , privacy : OSLogPrivacy . auto ( mask : . hash ) ) , 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
}
func deleteItem ( identifier : NSFileProviderItemIdentifier , baseVersion version : NSFileProviderItemVersion , options : NSFileProviderDeleteItemOptions = [ ] , request : NSFileProviderRequest , completionHandler : @ escaping ( Error ? ) -> Void ) -> Progress {
2023-03-07 03:15:09 +03:00
2023-03-14 23:34:08 +03:00
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 {
2023-03-14 23:34:08 +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 ( )
}
self . ncKit . deleteFileOrFolder ( serverUrlFileName : serverFileNameUrl ) { account , error in
guard error = = . success else {
2023-03-14 23:34:08 +03:00
Logger . fileTransfer . error ( " Could not delete item with ocId \( identifier . rawValue , privacy : . public ) at \( serverFileNameUrl , privacy : OSLogPrivacy . auto ( mask : . hash ) ) , received error: \( error , privacy : . public ) " )
2023-03-13 17:17:26 +03:00
completionHandler ( error . toFileProviderError ( ) )
2023-03-07 03:15:09 +03:00
return
}
2023-03-14 23:34:08 +03:00
Logger . fileTransfer . info ( " Successfully deleted item with identifier: \( identifier . rawValue , privacy : . public ) at: \( serverFileNameUrl , privacy : OSLogPrivacy . auto ( mask : . hash ) ) " )
2023-03-07 03:15:09 +03:00
2023-03-09 18:47:27 +03:00
if itemMetadata . directory {
dbManager . deleteDirectoryAndSubdirectoriesMetadata ( ocId : ocId )
2023-03-07 03:15:09 +03:00
}
if dbManager . localFileMetadataFromOcId ( ocId ) != nil {
dbManager . deleteLocalFileMetadata ( ocId : ocId )
}
completionHandler ( nil )
}
2022-03-29 16:00:59 +03:00
return Progress ( )
}
func enumerator ( for containerItemIdentifier : NSFileProviderItemIdentifier , request : NSFileProviderRequest ) throws -> NSFileProviderEnumerator {
2023-01-27 04:26:27 +03:00
guard let ncAccount = ncAccount else {
2023-03-14 23:34:08 +03:00
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 )
}
2023-02-02 22:13:35 +03:00
return FileProviderEnumerator ( enumeratedItemIdentifier : containerItemIdentifier , ncAccount : ncAccount , ncKit : ncKit )
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 ) {
guard let ncAccount = self . ncAccount else {
2023-03-14 23:04:04 +03:00
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 {
2023-03-14 23:34:08 +03:00
Logger . fileProviderExtension . error ( " Could not get file provider manager for domain: \( self . domain . displayName , privacy : OSLogPrivacy . auto ( mask : . hash ) ) " )
2023-03-13 15:43:31 +03:00
completionHandler ( )
return
}
let dbManager = NextcloudFilesDatabaseManager . shared
let materialisedEnumerator = fpManager . enumeratorForMaterializedItems ( )
let materialisedObserver = FileProviderMaterialisedEnumerationObserver ( ncKitAccount : ncAccount . ncKitAccount ) { _ in
completionHandler ( )
}
let startingPage = NSFileProviderPage ( NSFileProviderPage . initialPageSortedByName as Data )
materialisedEnumerator . enumerateItems ( for : materialisedObserver , startingAt : startingPage )
}
2022-12-29 02:21:33 +03:00
// MARK: N e x t c l o u d d e s k t o p c l i e n t c o m m u n i c a t i o n
2022-12-30 21:52:51 +03:00
func sendFileProviderDomainIdentifier ( ) {
2022-12-29 02:21:33 +03:00
let command = " FILE_PROVIDER_DOMAIN_IDENTIFIER_REQUEST_REPLY "
let argument = domain . identifier . rawValue
let message = command + " : " + argument + " \n "
2023-01-04 23:18:21 +03:00
socketClient ? . sendMessage ( message )
2022-12-29 02:21:33 +03:00
}
2022-12-30 21:52:51 +03:00
2023-01-30 22:32:35 +03:00
private func signalEnumeratorAfterAccountSetup ( ) {
guard let fpManager = NSFileProviderManager ( for : domain ) else {
2023-03-14 23:34:08 +03:00
Logger . fileProviderExtension . error ( " Could not get file provider manager for domain \( self . domain . displayName , privacy : OSLogPrivacy . auto ( mask : . hash ) ) , cannot notify after account setup " )
2023-01-30 22:32:35 +03:00
return
}
assert ( ncAccount != nil )
2023-03-09 21:00:35 +03:00
fpManager . signalErrorResolved ( NSFileProviderError ( . notAuthenticated ) ) { error in
if error != nil {
2023-03-14 23:04:04 +03:00
Logger . fileProviderExtension . error ( " Error resolving not authenticated, received error: \( error ! ) " )
2023-03-09 21:00:35 +03:00
}
}
2023-03-14 23:34:08 +03:00
Logger . fileProviderExtension . debug ( " Signalling enumerators for user \( self . ncAccount ! . username ) at server \( self . ncAccount ! . serverUrl , privacy : OSLogPrivacy . auto ( mask : . hash ) ) " )
2023-03-10 20:37:00 +03:00
fpManager . signalEnumerator ( for : . workingSet ) { error in
if error != nil {
2023-03-14 23:34:08 +03:00
Logger . fileProviderExtension . error ( " Error signalling enumerator for working set, received error: \( error , privacy : . public ) " )
2023-01-30 22:32:35 +03:00
}
}
}
2023-01-27 01:01:31 +03:00
func setupDomainAccount ( user : String , serverUrl : String , password : String ) {
ncAccount = NextcloudAccount ( user : user , serverUrl : serverUrl , password : password )
2023-02-02 22:13:35 +03:00
ncKit . setup ( user : ncAccount ! . username ,
userId : ncAccount ! . username ,
password : ncAccount ! . password ,
urlBase : ncAccount ! . serverUrl ,
userAgent : " Nextcloud-macOS/FileProviderExt " ,
nextcloudVersion : 25 ,
delegate : nil ) // TODO: a d d d e l e g a t e m e t h o d s f o r s e l f
2023-01-28 19:07:13 +03:00
2023-03-14 23:34:08 +03:00
Logger . fileProviderExtension . info ( " Nextcloud account set up in File Provider extension for user: \( user , privacy : OSLogPrivacy . auto ( mask : . hash ) ) at server: \( serverUrl , privacy : OSLogPrivacy . auto ( mask : . hash ) ) " )
2023-01-27 04:26:27 +03:00
2023-03-09 21:00:35 +03:00
signalEnumeratorAfterAccountSetup ( )
2022-12-30 21:52:51 +03:00
}
2023-03-11 17:55:46 +03:00
func removeAccountConfig ( ) {
2023-03-14 23:34:08 +03:00
Logger . fileProviderExtension . info ( " Received instruction to remove account data for user \( self . ncAccount ! . username , privacy : OSLogPrivacy . auto ( mask : . hash ) ) at server \( self . ncAccount ! . serverUrl , privacy : OSLogPrivacy . auto ( mask : . hash ) ) " )
2023-03-11 17:55:46 +03:00
ncAccount = nil
}
2022-03-29 16:00:59 +03:00
}