2023-01-05 22:59:50 +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 .
*/
2023-01-10 20:36:10 +03:00
import FileProvider
2024-01-22 09:40:47 +03:00
import Foundation
2023-01-13 16:53:40 +03:00
import NextcloudKit
2023-03-14 23:03:30 +03:00
import OSLog
2024-01-22 09:40:47 +03:00
import RealmSwift
2023-01-05 22:59:50 +03:00
2024-01-22 09:40:47 +03:00
class NextcloudFilesDatabaseManager : NSObject {
static let shared = NextcloudFilesDatabaseManager ( )
2023-01-05 22:59:50 +03:00
2023-01-27 01:10:43 +03:00
let relativeDatabaseFolderPath = " Database/ "
let databaseFilename = " fileproviderextdatabase.realm "
2023-01-05 22:59:50 +03:00
let relativeDatabaseFilePath : String
var databasePath : URL ?
let schemaVersion : UInt64 = 100
override init ( ) {
2024-01-22 09:40:47 +03:00
relativeDatabaseFilePath = relativeDatabaseFolderPath + databaseFilename
2023-01-05 22:59:50 +03:00
2023-01-12 18:51:44 +03:00
guard let fileProviderDataDirUrl = pathForFileProviderExtData ( ) else {
2023-01-05 22:59:50 +03:00
super . init ( )
return
}
2024-01-22 09:40:47 +03:00
databasePath = fileProviderDataDirUrl . appendingPathComponent ( relativeDatabaseFilePath )
2023-01-05 22:59:50 +03:00
// D i s a b l e f i l e p r o t e c t i o n f o r d i r e c t o r y D B
// h t t p s : / / d o c s . m o n g o d b . c o m / r e a l m / s d k / i o s / e x a m p l e s / c o n f i g u r e - a n d - o p e n - a - r e a l m / # s t d - l a b e l - i o s - o p e n - a - l o c a l - r e a l m
2024-01-22 09:40:47 +03:00
let dbFolder = fileProviderDataDirUrl . appendingPathComponent ( relativeDatabaseFolderPath )
2023-01-27 01:10:43 +03:00
let dbFolderPath = dbFolder . path
2023-01-12 18:51:44 +03:00
do {
2023-01-27 01:10:43 +03:00
try FileManager . default . createDirectory ( at : dbFolder , withIntermediateDirectories : true )
2024-01-22 09:40:47 +03:00
try FileManager . default . setAttributes (
[
FileAttributeKey . protectionKey : FileProtectionType
. completeUntilFirstUserAuthentication
] ,
ofItemAtPath : dbFolderPath )
} catch {
Logger . ncFilesDatabase . error (
" Could not set permission level for File Provider database folder, received error: \( error . localizedDescription , privacy : . public ) "
)
2023-01-30 22:29:15 +03:00
}
2023-01-05 22:59:50 +03:00
let config = Realm . Configuration (
2024-01-22 09:40:47 +03:00
fileURL : databasePath ,
schemaVersion : schemaVersion ,
2023-03-18 03:43:05 +03:00
objectTypes : [ NextcloudItemMetadataTable . self , NextcloudLocalFileMetadataTable . self ]
2023-01-05 22:59:50 +03:00
)
Realm . Configuration . defaultConfiguration = config
do {
2023-03-11 14:35:12 +03:00
_ = try Realm ( )
2023-03-14 23:03:30 +03:00
Logger . ncFilesDatabase . info ( " Successfully started Realm db for FileProviderExt " )
2023-01-05 22:59:50 +03:00
} catch let error as NSError {
2024-01-22 09:40:47 +03:00
Logger . ncFilesDatabase . error (
" Error opening Realm db: \( error . localizedDescription , privacy : . public ) " )
2023-01-05 22:59:50 +03:00
}
super . init ( )
}
2023-01-10 19:58:00 +03:00
2023-04-04 11:24:17 +03:00
func ncDatabase ( ) -> Realm {
2023-01-10 19:58:00 +03:00
let realm = try ! Realm ( )
realm . refresh ( )
2023-01-10 21:23:33 +03:00
return realm
2023-01-10 19:58:00 +03:00
}
2023-01-10 20:36:10 +03:00
2023-02-01 20:16:46 +03:00
func anyItemMetadatasForAccount ( _ account : String ) -> Bool {
2024-01-22 09:40:47 +03:00
! ncDatabase ( ) . objects ( NextcloudItemMetadataTable . self ) . filter ( " account == %@ " , account )
. isEmpty
2023-02-01 20:16:46 +03:00
}
2023-01-12 23:16:49 +03:00
func itemMetadataFromOcId ( _ ocId : String ) -> NextcloudItemMetadataTable ? {
2023-01-26 20:47:34 +03:00
// R e a l m o b j e c t s a r e l i v e - f i r e , i . e . t h e y w i l l b e c h a n g e d a n d i n v a l i d a t e d a c c o r d i n g t o c h a n g e s i n t h e d b
// L e t ' s t h e r e f o r e c r e a t e a c o p y
2024-01-22 09:40:47 +03:00
if let itemMetadata = ncDatabase ( ) . objects ( NextcloudItemMetadataTable . self ) . filter (
" ocId == %@ " , ocId
) . first {
2023-01-26 20:47:34 +03:00
return NextcloudItemMetadataTable ( value : itemMetadata )
}
return nil
2023-01-10 21:23:33 +03:00
}
2024-01-22 09:40:47 +03:00
func sortedItemMetadatas ( _ metadatas : Results < NextcloudItemMetadataTable > )
-> [ NextcloudItemMetadataTable ]
{
2023-01-13 16:17:04 +03:00
let sortedMetadatas = metadatas . sorted ( byKeyPath : " fileName " , ascending : true )
2023-01-26 20:47:34 +03:00
return Array ( sortedMetadatas . map { NextcloudItemMetadataTable ( value : $0 ) } )
2023-01-13 16:17:04 +03:00
}
2023-03-10 03:15:53 +03:00
func itemMetadatas ( account : String ) -> [ NextcloudItemMetadataTable ] {
2024-01-22 09:40:47 +03:00
let metadatas = ncDatabase ( ) . objects ( NextcloudItemMetadataTable . self ) . filter (
" account == %@ " , account )
2023-03-10 03:15:53 +03:00
return sortedItemMetadatas ( metadatas )
}
2023-01-23 19:25:00 +03:00
func itemMetadatas ( account : String , serverUrl : String ) -> [ NextcloudItemMetadataTable ] {
2024-01-22 09:40:47 +03:00
let metadatas = ncDatabase ( ) . objects ( NextcloudItemMetadataTable . self ) . filter (
" account == %@ AND serverUrl == %@ " , account , serverUrl )
2023-01-23 19:25:00 +03:00
return sortedItemMetadatas ( metadatas )
}
2024-01-22 09:40:47 +03:00
func itemMetadatas (
account : String , serverUrl : String , status : NextcloudItemMetadataTable . Status
)
-> [ NextcloudItemMetadataTable ]
{
let metadatas = ncDatabase ( ) . objects ( NextcloudItemMetadataTable . self ) . filter (
" account == %@ AND serverUrl == %@ AND status == %@ " , account , serverUrl ,
status . rawValue )
2023-01-23 19:25:00 +03:00
return sortedItemMetadatas ( metadatas )
}
2024-01-22 09:40:47 +03:00
func itemMetadataFromFileProviderItemIdentifier ( _ identifier : NSFileProviderItemIdentifier )
-> NextcloudItemMetadataTable ?
{
2023-01-10 20:36:10 +03:00
let ocId = identifier . rawValue
2023-01-12 23:16:49 +03:00
return itemMetadataFromOcId ( ocId )
2023-01-10 20:36:10 +03:00
}
2023-01-10 22:24:34 +03:00
2024-01-22 09:40:47 +03:00
private func processItemMetadatasToDelete (
existingMetadatas : Results < NextcloudItemMetadataTable > ,
updatedMetadatas : [ NextcloudItemMetadataTable ]
) -> [ NextcloudItemMetadataTable ] {
2023-03-08 19:43:55 +03:00
var deletedMetadatas : [ NextcloudItemMetadataTable ] = [ ]
2023-01-25 22:18:51 +03:00
for existingMetadata in existingMetadatas {
guard ! updatedMetadatas . contains ( where : { $0 . ocId = = existingMetadata . ocId } ) ,
2024-01-22 09:40:47 +03:00
let metadataToDelete = itemMetadataFromOcId ( existingMetadata . ocId )
else { continue }
2023-01-25 22:18:51 +03:00
2023-03-08 19:43:55 +03:00
deletedMetadatas . append ( metadataToDelete )
2024-01-22 09:40:47 +03:00
Logger . ncFilesDatabase . debug (
" Deleting item metadata during update. ocID: \( existingMetadata . ocId , privacy : . public ) , etag: \( existingMetadata . etag , privacy : . public ) , fileName: \( existingMetadata . fileName , privacy : . public ) "
)
2023-01-25 22:18:51 +03:00
}
2023-03-08 19:43:55 +03:00
return deletedMetadatas
2023-01-25 22:18:51 +03:00
}
2024-01-22 09:40:47 +03:00
private func processItemMetadatasToUpdate (
existingMetadatas : Results < NextcloudItemMetadataTable > ,
updatedMetadatas : [ NextcloudItemMetadataTable ] ,
updateDirectoryEtags : Bool
) -> (
newMetadatas : [ NextcloudItemMetadataTable ] , updatedMetadatas : [ NextcloudItemMetadataTable ] ,
directoriesNeedingRename : [ NextcloudItemMetadataTable ]
) {
2023-03-08 19:51:59 +03:00
var returningNewMetadatas : [ NextcloudItemMetadataTable ] = [ ]
var returningUpdatedMetadatas : [ NextcloudItemMetadataTable ] = [ ]
2023-03-18 06:41:47 +03:00
var directoriesNeedingRename : [ NextcloudItemMetadataTable ] = [ ]
2023-03-08 19:51:59 +03:00
2023-01-25 22:20:46 +03:00
for updatedMetadata in updatedMetadatas {
2024-01-22 09:40:47 +03:00
if let existingMetadata = existingMetadatas . first ( where : {
$0 . ocId = = updatedMetadata . ocId
} ) {
if existingMetadata . status = = NextcloudItemMetadataTable . Status . normal . rawValue ,
! existingMetadata . isInSameDatabaseStoreableRemoteState ( updatedMetadata )
{
2023-03-18 06:41:47 +03:00
if updatedMetadata . directory {
2024-01-22 09:40:47 +03:00
if updatedMetadata . serverUrl != existingMetadata . serverUrl
|| updatedMetadata . fileName != existingMetadata . fileName
{
directoriesNeedingRename . append (
NextcloudItemMetadataTable ( value : updatedMetadata ) )
updatedMetadata . etag = " " // R e n a m i n g d o e s n ' t c h a n g e t h e e t a g s o r e s e t m a n u a l l y
2023-03-18 06:41:47 +03:00
} else if ! updateDirectoryEtags {
updatedMetadata . etag = existingMetadata . etag
}
2023-03-18 04:35:02 +03:00
}
2023-03-18 06:41:47 +03:00
returningUpdatedMetadatas . append ( updatedMetadata )
2024-01-22 09:40:47 +03:00
Logger . ncFilesDatabase . debug (
" Updated existing item metadata. ocID: \( updatedMetadata . ocId , privacy : . public ) , etag: \( updatedMetadata . etag , privacy : . public ) , fileName: \( updatedMetadata . fileName , privacy : . public ) "
)
2023-03-08 19:51:59 +03:00
} else {
2024-01-22 09:40:47 +03:00
Logger . ncFilesDatabase . debug (
" Skipping item metadata update; same as existing, or still downloading/uploading. ocID: \( updatedMetadata . ocId , privacy : . public ) , etag: \( updatedMetadata . etag , privacy : . public ) , fileName: \( updatedMetadata . fileName , privacy : . public ) "
)
2023-01-25 22:20:46 +03:00
}
2024-01-22 09:40:47 +03:00
} else { // T h i s i s a n e w m e t a d a t a
if ! updateDirectoryEtags , updatedMetadata . directory {
2023-03-18 21:00:25 +03:00
updatedMetadata . etag = " "
}
2024-01-22 09:40:47 +03:00
2023-03-18 06:41:47 +03:00
returningNewMetadatas . append ( updatedMetadata )
2023-03-08 19:51:59 +03:00
2024-01-22 09:40:47 +03:00
Logger . ncFilesDatabase . debug (
" Created new item metadata during update. ocID: \( updatedMetadata . ocId , privacy : . public ) , etag: \( updatedMetadata . etag , privacy : . public ) , fileName: \( updatedMetadata . fileName , privacy : . public ) "
)
2023-01-25 22:20:46 +03:00
}
}
2023-03-08 19:51:59 +03:00
2023-03-18 06:41:47 +03:00
return ( returningNewMetadatas , returningUpdatedMetadatas , directoriesNeedingRename )
2023-01-25 22:20:46 +03:00
}
2024-01-22 09:40:47 +03:00
func updateItemMetadatas (
account : String , serverUrl : String , updatedMetadatas : [ NextcloudItemMetadataTable ] ,
updateDirectoryEtags : Bool
) -> (
newMetadatas : [ NextcloudItemMetadataTable ] ? ,
updatedMetadatas : [ NextcloudItemMetadataTable ] ? ,
deletedMetadatas : [ NextcloudItemMetadataTable ] ?
) {
2023-01-25 22:15:13 +03:00
let database = ncDatabase ( )
do {
2024-01-22 09:40:47 +03:00
let existingMetadatas = database . objects ( NextcloudItemMetadataTable . self ) . filter (
" account == %@ AND serverUrl == %@ AND status == %@ " , account , serverUrl ,
NextcloudItemMetadataTable . Status . normal . rawValue )
2023-03-18 06:41:47 +03:00
2024-01-22 09:40:47 +03:00
let metadatasToDelete = processItemMetadatasToDelete (
existingMetadatas : existingMetadatas ,
updatedMetadatas : updatedMetadatas )
2023-03-18 06:41:47 +03:00
2024-01-22 09:40:47 +03:00
let metadatasToChange = processItemMetadatasToUpdate (
existingMetadatas : existingMetadatas ,
updatedMetadatas : updatedMetadatas ,
updateDirectoryEtags : updateDirectoryEtags )
2023-03-18 06:41:47 +03:00
var metadatasToUpdate = metadatasToChange . updatedMetadatas
let metadatasToCreate = metadatasToChange . newMetadatas
let directoriesNeedingRename = metadatasToChange . directoriesNeedingRename
2024-01-22 09:40:47 +03:00
let metadatasToAdd =
Array ( metadatasToUpdate . map { NextcloudItemMetadataTable ( value : $0 ) } )
+ Array ( metadatasToCreate . map { NextcloudItemMetadataTable ( value : $0 ) } )
2023-03-18 06:41:47 +03:00
2023-03-18 22:01:29 +03:00
for metadata in directoriesNeedingRename {
2024-01-22 09:40:47 +03:00
if let updatedDirectoryChildren = renameDirectoryAndPropagateToChildren (
ocId : metadata . ocId , newServerUrl : metadata . serverUrl ,
newFileName : metadata . fileName )
{
2023-03-18 22:01:29 +03:00
metadatasToUpdate += updatedDirectoryChildren
}
}
2023-01-25 22:15:13 +03:00
try database . write {
2023-03-18 06:41:47 +03:00
for metadata in metadatasToDelete {
// C a n ' t p a s s c o p i e s , w e n e e d t h e o r i g i n a l s f r o m t h e d a t a b a s e
2024-01-22 09:40:47 +03:00
database . delete (
ncDatabase ( ) . objects ( NextcloudItemMetadataTable . self ) . filter (
" ocId == %@ " , metadata . ocId ) )
2023-03-18 06:41:47 +03:00
}
2023-02-02 22:12:03 +03:00
2023-03-18 06:41:47 +03:00
for metadata in metadatasToAdd {
database . add ( metadata , update : . all )
}
}
2023-03-08 19:57:43 +03:00
2024-01-22 09:40:47 +03:00
return (
newMetadatas : metadatasToCreate , updatedMetadatas : metadatasToUpdate ,
deletedMetadatas : metadatasToDelete
)
} catch {
Logger . ncFilesDatabase . error (
" Could not update any item metadatas, received error: \( error . localizedDescription , privacy : . public ) "
)
2023-03-18 15:23:53 +03:00
return ( nil , nil , nil )
2023-01-25 22:15:13 +03:00
}
}
2024-01-22 09:40:47 +03:00
func setStatusForItemMetadata (
_ metadata : NextcloudItemMetadataTable , status : NextcloudItemMetadataTable . Status ,
completionHandler : @ escaping ( _ updatedMetadata : NextcloudItemMetadataTable ? ) -> Void
) {
2023-02-14 15:48:08 +03:00
let database = ncDatabase ( )
do {
try database . write {
2024-01-22 09:40:47 +03:00
guard
let result = database . objects ( NextcloudItemMetadataTable . self ) . filter (
" ocId == %@ " , metadata . ocId
) . first
else {
Logger . ncFilesDatabase . debug (
" Did not update status for item metadata as it was not found. ocID: \( metadata . ocId , privacy : . public ) "
)
2023-03-14 16:22:28 +03:00
return
}
2023-03-14 23:03:30 +03:00
2023-03-14 16:22:28 +03:00
result . status = status . rawValue
database . add ( result , update : . all )
2024-01-22 09:40:47 +03:00
Logger . ncFilesDatabase . debug (
" Updated status for item metadata. ocID: \( metadata . ocId , privacy : . public ) , etag: \( metadata . etag , privacy : . public ) , fileName: \( metadata . fileName , privacy : . public ) "
)
2023-03-15 01:15:02 +03:00
completionHandler ( NextcloudItemMetadataTable ( value : result ) )
2023-02-14 15:48:08 +03:00
}
2024-01-22 09:40:47 +03:00
} catch {
Logger . ncFilesDatabase . error (
" Could not update status for item metadata with ocID: \( metadata . ocId , privacy : . public ) , etag: \( metadata . etag , privacy : . public ) , fileName: \( metadata . fileName , privacy : . public ) , received error: \( error . localizedDescription , privacy : . public ) "
)
2023-03-15 01:15:02 +03:00
completionHandler ( nil )
2023-02-14 15:48:08 +03:00
}
}
2023-02-16 16:44:16 +03:00
func addItemMetadata ( _ metadata : NextcloudItemMetadataTable ) {
let database = ncDatabase ( )
do {
try database . write {
database . add ( metadata , update : . all )
2024-01-22 09:40:47 +03:00
Logger . ncFilesDatabase . debug (
" Added item metadata. ocID: \( metadata . ocId , privacy : . public ) , etag: \( metadata . etag , privacy : . public ) , fileName: \( metadata . fileName , privacy : . public ) "
)
2023-02-16 16:44:16 +03:00
}
2024-01-22 09:40:47 +03:00
} catch {
Logger . ncFilesDatabase . error (
" Could not add item metadata. ocID: \( metadata . ocId , privacy : . public ) , etag: \( metadata . etag , privacy : . public ) , fileName: \( metadata . fileName , privacy : . public ) , received error: \( error . localizedDescription , privacy : . public ) "
)
2023-02-16 16:44:16 +03:00
}
}
2023-03-20 16:51:54 +03:00
@ discardableResult func deleteItemMetadata ( ocId : String ) -> Bool {
2023-02-27 20:24:12 +03:00
let database = ncDatabase ( )
do {
try database . write {
2024-01-22 09:40:47 +03:00
let results = database . objects ( NextcloudItemMetadataTable . self ) . filter (
" ocId == %@ " , ocId )
2023-03-14 23:03:30 +03:00
2023-03-15 00:05:50 +03:00
Logger . ncFilesDatabase . debug ( " Deleting item metadata. \( ocId , privacy : . public ) " )
2023-02-27 20:24:12 +03:00
database . delete ( results )
}
2023-03-20 16:51:54 +03:00
return true
2024-01-22 09:40:47 +03:00
} catch {
Logger . ncFilesDatabase . error (
" Could not delete item metadata with ocId: \( ocId , privacy : . public ) , received error: \( error . localizedDescription , privacy : . public ) "
)
2023-03-20 16:51:54 +03:00
return false
2023-02-27 20:24:12 +03:00
}
}
2023-03-06 21:11:34 +03:00
func renameItemMetadata ( ocId : String , newServerUrl : String , newFileName : String ) {
2023-03-06 17:52:58 +03:00
let database = ncDatabase ( )
do {
try database . write {
2024-01-22 09:40:47 +03:00
guard
let itemMetadata = database . objects ( NextcloudItemMetadataTable . self ) . filter (
" ocId == %@ " , ocId
) . first
else {
Logger . ncFilesDatabase . debug (
" Could not find an item with ocID \( ocId , privacy : . public ) to rename to \( newFileName , privacy : . public ) "
)
2023-03-06 17:52:58 +03:00
return
}
let oldFileName = itemMetadata . fileName
2023-03-06 21:11:34 +03:00
let oldServerUrl = itemMetadata . serverUrl
2023-03-06 17:52:58 +03:00
itemMetadata . fileName = newFileName
itemMetadata . fileNameView = newFileName
2023-03-06 21:11:34 +03:00
itemMetadata . serverUrl = newServerUrl
2023-03-06 17:52:58 +03:00
database . add ( itemMetadata , update : . all )
2023-03-06 21:11:34 +03:00
2024-01-22 09:40:47 +03:00
Logger . ncFilesDatabase . debug (
" Renamed item \( oldFileName , privacy : . public ) to \( newFileName , privacy : . public ) , moved from serverUrl: \( oldServerUrl , privacy : . public ) to serverUrl: \( newServerUrl , privacy : . public ) "
)
2023-03-06 17:52:58 +03:00
}
2024-01-22 09:40:47 +03:00
} catch {
Logger . ncFilesDatabase . error (
" Could not rename filename of item metadata with ocID: \( ocId , privacy : . public ) to proposed name \( newFileName , privacy : . public ) at proposed serverUrl \( newServerUrl , privacy : . public ) , received error: \( error . localizedDescription , privacy : . public ) "
)
2023-03-06 17:52:58 +03:00
}
}
2024-01-22 09:40:47 +03:00
func parentItemIdentifierFromMetadata ( _ metadata : NextcloudItemMetadataTable )
-> NSFileProviderItemIdentifier ?
{
2023-03-13 14:34:05 +03:00
let homeServerFilesUrl = metadata . urlBase + " /remote.php/dav/files/ " + metadata . userId
if metadata . serverUrl = = homeServerFilesUrl {
return . rootContainer
}
guard let itemParentDirectory = parentDirectoryMetadataForItem ( metadata ) else {
2024-01-22 09:40:47 +03:00
Logger . ncFilesDatabase . error (
" Could not get item parent directory metadata for metadata. ocID: \( metadata . ocId , privacy : . public ) , etag: \( metadata . etag , privacy : . public ) , fileName: \( metadata . fileName , privacy : . public ) "
)
2023-03-13 14:34:05 +03:00
return nil
}
if let parentDirectoryMetadata = itemMetadataFromOcId ( itemParentDirectory . ocId ) {
return NSFileProviderItemIdentifier ( parentDirectoryMetadata . ocId )
}
2024-01-22 09:40:47 +03:00
Logger . ncFilesDatabase . error (
" Could not get item parent directory item metadata for metadata. ocID: \( metadata . ocId , privacy : . public ) , etag: \( metadata . etag , privacy : . public ) , fileName: \( metadata . fileName , privacy : . public ) "
)
2023-03-13 14:34:05 +03:00
return nil
}
2023-01-05 22:59:50 +03:00
}