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 .
*/
import Foundation
import RealmSwift
2023-01-10 20:36:10 +03:00
import FileProvider
2023-01-13 16:53:40 +03:00
import NextcloudKit
2023-03-14 23:03:30 +03:00
import OSLog
2023-01-05 22:59:50 +03:00
class NextcloudFilesDatabaseManager : NSObject {
static let shared = {
return NextcloudFilesDatabaseManager ( ) ;
} ( )
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 ( ) {
self . relativeDatabaseFilePath = self . relativeDatabaseFolderPath + self . databaseFilename
2023-01-12 18:51:44 +03:00
guard let fileProviderDataDirUrl = pathForFileProviderExtData ( ) else {
2023-01-05 22:59:50 +03:00
super . init ( )
return
}
2023-01-27 01:10:43 +03:00
self . databasePath = fileProviderDataDirUrl . appendingPathComponent ( self . 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
2023-01-27 01:10:43 +03:00
let dbFolder = fileProviderDataDirUrl . appendingPathComponent ( self . relativeDatabaseFolderPath )
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 )
2023-01-30 22:29:15 +03:00
try FileManager . default . setAttributes ( [ FileAttributeKey . protectionKey : FileProtectionType . completeUntilFirstUserAuthentication ] , ofItemAtPath : dbFolderPath )
} catch let error {
NSLog ( " Could not set permission level for File Provider database folder, received error: %@ " , error . localizedDescription )
}
2023-01-05 22:59:50 +03:00
let config = Realm . Configuration (
fileURL : self . databasePath ,
schemaVersion : self . schemaVersion ,
2023-01-27 01:08:16 +03:00
objectTypes : [ NextcloudItemMetadataTable . self , NextcloudDirectoryMetadataTable . 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 {
2023-03-14 23:14:09 +03:00
Logger . ncFilesDatabase . error ( " Error opening Realm db: \( error , privacy : . public ) " )
2023-01-05 22:59:50 +03:00
}
super . init ( )
}
2023-01-10 19:58:00 +03:00
2023-01-10 21:23:33 +03:00
private 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 {
return ! ncDatabase ( ) . objects ( NextcloudItemMetadataTable . self ) . filter ( " account == %@ " , account ) . isEmpty
}
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
if let itemMetadata = ncDatabase ( ) . objects ( NextcloudItemMetadataTable . self ) . filter ( " ocId == %@ " , ocId ) . first {
return NextcloudItemMetadataTable ( value : itemMetadata )
}
return nil
2023-01-10 21:23:33 +03:00
}
2023-01-23 19:25:00 +03:00
private 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 ] {
let metadatas = ncDatabase ( ) . objects ( NextcloudItemMetadataTable . self ) . filter ( " account == %@ " , account )
return sortedItemMetadatas ( metadatas )
}
2023-01-23 19:25:00 +03:00
func itemMetadatas ( account : String , serverUrl : String ) -> [ NextcloudItemMetadataTable ] {
let metadatas = ncDatabase ( ) . objects ( NextcloudItemMetadataTable . self ) . filter ( " account == %@ AND serverUrl == %@ " , account , serverUrl )
return sortedItemMetadatas ( metadatas )
}
func itemMetadatas ( account : String , serverUrl : String , status : NextcloudItemMetadataTable . Status ) -> [ NextcloudItemMetadataTable ] {
2023-01-30 22:27:37 +03:00
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 )
}
2023-01-12 23:16:49 +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
2023-01-25 22:18:51 +03:00
private func processItemMetadatasToDelete ( databaseToWriteTo : Realm ,
2023-02-02 22:12:03 +03:00
existingMetadatas : Results < NextcloudItemMetadataTable > ,
2023-03-08 19:43:55 +03:00
updatedMetadatas : [ NextcloudItemMetadataTable ] ) -> [ NextcloudItemMetadataTable ] {
2023-01-25 22:18:51 +03:00
assert ( databaseToWriteTo . isInWriteTransaction )
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 } ) ,
let metadataToDelete = itemMetadataFromOcId ( existingMetadata . ocId ) else { continue }
2023-03-08 19:43:55 +03:00
deletedMetadatas . append ( metadataToDelete )
2023-03-15 00:05:50 +03:00
Logger . ncFilesDatabase . debug ( " Deleting item metadata during update. ocID: \( existingMetadata . ocId , privacy : . public ) , etag: \( existingMetadata . etag , privacy : . public ) , fileName: \( existingMetadata . fileName , privacy : OSLogPrivacy . auto ( mask : . hash ) ) " )
2023-02-02 22:12:03 +03:00
// 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
databaseToWriteTo . delete ( ncDatabase ( ) . objects ( NextcloudItemMetadataTable . self ) . filter ( " ocId == %@ " , metadataToDelete . ocId ) )
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
}
2023-01-25 22:20:46 +03:00
private func processItemMetadatasToUpdate ( databaseToWriteTo : Realm ,
2023-02-02 22:12:03 +03:00
existingMetadatas : Results < NextcloudItemMetadataTable > ,
2023-03-08 19:51:59 +03:00
updatedMetadatas : [ NextcloudItemMetadataTable ] ) -> ( newMetadatas : [ NextcloudItemMetadataTable ] , updatedMetadatas : [ NextcloudItemMetadataTable ] ) {
2023-01-25 22:20:46 +03:00
assert ( databaseToWriteTo . isInWriteTransaction )
2023-03-08 19:51:59 +03:00
var returningNewMetadatas : [ NextcloudItemMetadataTable ] = [ ]
var returningUpdatedMetadatas : [ NextcloudItemMetadataTable ] = [ ]
2023-01-25 22:20:46 +03:00
for updatedMetadata in updatedMetadatas {
if let existingMetadata = existingMetadatas . first ( where : { $0 . ocId = = updatedMetadata . ocId } ) {
if existingMetadata . status = = NextcloudItemMetadataTable . Status . normal . rawValue &&
2023-03-11 04:30:54 +03:00
! existingMetadata . isInSameDatabaseStoreableRemoteState ( updatedMetadata ) {
2023-01-25 22:20:46 +03:00
2023-03-11 00:09:19 +03:00
returningUpdatedMetadatas . append ( NextcloudItemMetadataTable ( value : updatedMetadata ) )
2023-03-08 19:51:59 +03:00
databaseToWriteTo . add ( updatedMetadata , update : . all )
2023-03-14 23:03:30 +03:00
2023-03-15 00:05:50 +03:00
Logger . ncFilesDatabase . debug ( " Updated existing item metadata. ocID: \( updatedMetadata . ocId , privacy : . public ) , etag: \( updatedMetadata . etag , privacy : . public ) , fileName: \( updatedMetadata . fileName , privacy : OSLogPrivacy . auto ( mask : . hash ) ) " )
2023-03-08 19:51:59 +03:00
} else {
2023-03-15 00:05:50 +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 : OSLogPrivacy . auto ( mask : . hash ) ) " )
2023-01-25 22:20:46 +03:00
}
} else { // T h i s i s a n e w m e t a d a t a
2023-03-11 00:09:19 +03:00
returningNewMetadatas . append ( NextcloudItemMetadataTable ( value : updatedMetadata ) )
2023-03-08 19:51:59 +03:00
databaseToWriteTo . add ( updatedMetadata , update : . all )
2023-03-15 00:05:50 +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 : OSLogPrivacy . auto ( mask : . hash ) ) " )
2023-01-25 22:20:46 +03:00
}
}
2023-03-08 19:51:59 +03:00
return ( returningNewMetadatas , returningUpdatedMetadatas )
2023-01-25 22:20:46 +03:00
}
2023-03-10 20:36:01 +03:00
func updateItemMetadatas ( account : String , serverUrl : String , updatedMetadatas : [ NextcloudItemMetadataTable ] , completionHandler : @ escaping ( _ newMetadatas : [ NextcloudItemMetadataTable ] ? , _ updatedMetadatas : [ NextcloudItemMetadataTable ] ? , _ deletedMetadatas : [ NextcloudItemMetadataTable ] ? ) -> Void ) {
2023-01-25 22:15:13 +03:00
let database = ncDatabase ( )
do {
try database . write {
2023-03-10 03:06:18 +03:00
let existingMetadatas = database . objects ( NextcloudItemMetadataTable . self ) . filter ( " account == %@ AND serverUrl == %@ AND status == %@ " , account , serverUrl , NextcloudItemMetadataTable . Status . normal . rawValue )
2023-02-02 22:12:03 +03:00
2023-03-08 19:57:43 +03:00
let deletedMetadatas = processItemMetadatasToDelete ( databaseToWriteTo : database ,
existingMetadatas : existingMetadatas ,
updatedMetadatas : updatedMetadatas )
2023-01-25 22:18:51 +03:00
2023-03-08 19:57:43 +03:00
let metadatasFromUpdate = processItemMetadatasToUpdate ( databaseToWriteTo : database ,
existingMetadatas : existingMetadatas ,
updatedMetadatas : updatedMetadatas )
2023-03-10 20:36:01 +03:00
completionHandler ( metadatasFromUpdate . newMetadatas , metadatasFromUpdate . updatedMetadatas , deletedMetadatas )
2023-01-25 22:15:13 +03:00
}
} catch let error {
2023-03-14 23:14:09 +03:00
Logger . ncFilesDatabase . error ( " Could not update any item metadatas, received error: \( error , privacy : . public ) " )
2023-03-10 20:36:01 +03:00
completionHandler ( nil , nil , nil )
2023-01-25 22:15:13 +03:00
}
}
2023-02-14 15:48:08 +03:00
func setStatusForItemMetadata ( _ metadata : NextcloudItemMetadataTable , status : NextcloudItemMetadataTable . Status ) -> NextcloudItemMetadataTable ? {
let database = ncDatabase ( )
var result : NextcloudItemMetadataTable ?
do {
try database . write {
2023-03-14 16:22:28 +03:00
guard let result = database . objects ( NextcloudItemMetadataTable . self ) . filter ( " ocId == %@ " , metadata . ocId ) . first else {
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 )
2023-03-15 00:05:50 +03:00
Logger . ncFilesDatabase . debug ( " Updated status for item metadata. ocID: \( metadata . ocId , privacy : . public ) , etag: \( metadata . etag , privacy : . public ) , fileName: \( metadata . fileName , privacy : OSLogPrivacy . auto ( mask : . hash ) ) " )
2023-02-14 15:48:08 +03:00
}
} catch let error {
2023-03-15 00:05:50 +03:00
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 : OSLogPrivacy . auto ( mask : . hash ) ) , received error: \( error , privacy : . public ) " )
2023-02-14 15:48:08 +03:00
}
if result != nil {
return NextcloudItemMetadataTable ( value : result ! )
}
return nil
}
2023-02-16 16:44:16 +03:00
func addItemMetadata ( _ metadata : NextcloudItemMetadataTable ) {
let database = ncDatabase ( )
do {
try database . write {
database . add ( metadata , update : . all )
2023-03-15 00:05:50 +03:00
Logger . ncFilesDatabase . debug ( " Added item metadata. ocID: \( metadata . ocId , privacy : . public ) , etag: \( metadata . etag , privacy : . public ) , fileName: \( metadata . fileName , privacy : OSLogPrivacy . auto ( mask : . hash ) ) " )
2023-02-16 16:44:16 +03:00
}
} catch let error {
2023-03-15 00:05:50 +03:00
Logger . ncFilesDatabase . error ( " Could not add item metadata. ocID: \( metadata . ocId , privacy : . public ) , etag: \( metadata . etag , privacy : . public ) , fileName: \( metadata . fileName , privacy : OSLogPrivacy . auto ( mask : . hash ) ) , received error: \( error , privacy : . public ) " )
2023-02-16 16:44:16 +03:00
}
}
2023-03-09 18:45:28 +03:00
func deleteItemMetadata ( ocId : String ) {
2023-02-27 20:24:12 +03:00
let database = ncDatabase ( )
do {
try database . write {
2023-03-09 18:45:28 +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 )
}
} catch let error {
2023-03-15 00:05:50 +03:00
Logger . ncFilesDatabase . error ( " Could not delete item metadata with ocId: \( ocId , privacy : . public ) , received error: \( error , privacy : . public ) " )
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 {
guard let itemMetadata = database . objects ( NextcloudItemMetadataTable . self ) . filter ( " ocId == %@ " , ocId ) . first else {
2023-03-15 00:05:50 +03:00
Logger . ncFilesDatabase . debug ( " Could not find an item with ocID \( ocId , privacy : . public ) to rename to \( newFileName , privacy : OSLogPrivacy . auto ( mask : . hash ) ) " )
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
2023-03-15 00:05:50 +03:00
Logger . ncFilesDatabase . debug ( " Renamed item \( oldFileName , privacy : OSLogPrivacy . auto ( mask : . hash ) ) to \( newFileName , privacy : OSLogPrivacy . auto ( mask : . hash ) ) , moved from serverUrl: \( oldServerUrl , privacy : OSLogPrivacy . auto ( mask : . hash ) ) to serverUrl: \( newServerUrl , privacy : OSLogPrivacy . auto ( mask : . hash ) ) " )
2023-03-06 17:52:58 +03:00
}
} catch let error {
2023-03-15 00:05:50 +03:00
Logger . ncFilesDatabase . error ( " Could not rename filename of item metadata with ocID: \( ocId , privacy : . public ) to proposed name \( newFileName , privacy : OSLogPrivacy . auto ( mask : . hash ) ) at proposed serverUrl \( newServerUrl , privacy : OSLogPrivacy . auto ( mask : . hash ) ) , received error: \( error , privacy : . public ) " )
2023-03-06 17:52:58 +03:00
}
}
2023-03-13 14:34:05 +03:00
func parentItemIdentifierFromMetadata ( _ metadata : NextcloudItemMetadataTable ) -> NSFileProviderItemIdentifier ? {
let homeServerFilesUrl = metadata . urlBase + " /remote.php/dav/files/ " + metadata . userId
if metadata . serverUrl = = homeServerFilesUrl {
return . rootContainer
}
guard let itemParentDirectory = parentDirectoryMetadataForItem ( metadata ) else {
2023-03-15 00:05:50 +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 : OSLogPrivacy . auto ( mask : . hash ) ) " )
2023-03-13 14:34:05 +03:00
return nil
}
if let parentDirectoryMetadata = itemMetadataFromOcId ( itemParentDirectory . ocId ) {
return NSFileProviderItemIdentifier ( parentDirectoryMetadata . ocId )
}
2023-03-15 00:05:50 +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 : OSLogPrivacy . auto ( mask : . hash ) ) " )
2023-03-13 14:34:05 +03:00
return nil
}
2023-01-13 03:59:50 +03:00
func directoryMetadata ( account : String , serverUrl : String ) -> NextcloudDirectoryMetadataTable ? {
2023-01-26 20:47:34 +03:00
if let metadata = ncDatabase ( ) . objects ( NextcloudDirectoryMetadataTable . self ) . filter ( " account == %@ AND serverUrl == %@ " , account , serverUrl ) . first {
return NextcloudDirectoryMetadataTable ( value : metadata )
}
return nil
2023-01-13 03:59:50 +03:00
}
2023-01-26 18:07:46 +03:00
func directoryMetadata ( ocId : String ) -> NextcloudDirectoryMetadataTable ? {
2023-01-26 20:47:34 +03:00
if let metadata = ncDatabase ( ) . objects ( NextcloudDirectoryMetadataTable . self ) . filter ( " ocId == %@ " , ocId ) . first {
return NextcloudDirectoryMetadataTable ( value : metadata )
}
return nil
2023-01-26 18:07:46 +03:00
}
2023-03-10 15:34:44 +03:00
private func sortedDirectoryMetadatas ( _ metadatas : Results < NextcloudDirectoryMetadataTable > ) -> [ NextcloudDirectoryMetadataTable ] {
2023-03-10 15:32:23 +03:00
let sortedMetadatas = metadatas . sorted ( byKeyPath : " serverUrl " , ascending : true )
return Array ( sortedMetadatas . map { NextcloudDirectoryMetadataTable ( value : $0 ) } )
}
2023-03-10 15:34:44 +03:00
func childDirectoriesForDirectory ( _ directoryMetadata : NextcloudDirectoryMetadataTable ) -> [ NextcloudDirectoryMetadataTable ] {
let metadatas = ncDatabase ( ) . objects ( NextcloudDirectoryMetadataTable . self ) . filter ( " serverUrl BEGINSWITH %@ AND ocId != %@ " , directoryMetadata . serverUrl , directoryMetadata . account )
return sortedDirectoryMetadatas ( metadatas )
}
2023-01-12 23:16:49 +03:00
func parentDirectoryMetadataForItem ( _ itemMetadata : NextcloudItemMetadataTable ) -> NextcloudDirectoryMetadataTable ? {
2023-01-13 03:59:50 +03:00
return directoryMetadata ( account : itemMetadata . account , serverUrl : itemMetadata . serverUrl )
2023-01-10 22:24:34 +03:00
}
2023-01-12 23:36:51 +03:00
2023-03-10 03:06:18 +03:00
func directoryMetadatas ( account : String ) -> [ NextcloudDirectoryMetadataTable ] {
let metadatas = ncDatabase ( ) . objects ( NextcloudDirectoryMetadataTable . self ) . filter ( " account == %@ " , account )
2023-03-10 15:34:44 +03:00
return sortedDirectoryMetadatas ( metadatas )
2023-03-10 03:06:18 +03:00
}
2023-01-26 20:54:52 +03:00
func directoryMetadatas ( account : String , parentDirectoryServerUrl : String ) -> [ NextcloudDirectoryMetadataTable ] {
let metadatas = ncDatabase ( ) . objects ( NextcloudDirectoryMetadataTable . self ) . filter ( " account == %@ AND parentDirectoryServerUrl == %@ " , account , parentDirectoryServerUrl )
2023-03-10 15:34:44 +03:00
return sortedDirectoryMetadatas ( metadatas )
2023-01-26 20:54:52 +03:00
}
2023-01-26 20:09:19 +03:00
private func processDirectoryMetadatasToDelete ( databaseToWriteTo : Realm ,
2023-02-02 22:12:03 +03:00
existingDirectoryMetadatas : Results < NextcloudDirectoryMetadataTable > ,
2023-01-26 20:09:19 +03:00
updatedDirectoryMetadatas : [ NextcloudDirectoryMetadataTable ] ) {
for existingMetadata in existingDirectoryMetadatas {
guard ! updatedDirectoryMetadatas . contains ( where : { $0 . ocId = = existingMetadata . ocId } ) ,
let metadataToDelete = directoryMetadata ( ocId : existingMetadata . ocId ) else { continue }
2023-03-15 00:05:50 +03:00
Logger . ncFilesDatabase . debug ( " Deleting directory metadata during update. ocID: \( existingMetadata . ocId , privacy : . public ) , etag: \( existingMetadata . etag , privacy : . public ) , serverUrl: \( existingMetadata . serverUrl ) " )
2023-02-02 22:12:03 +03:00
2023-03-09 18:47:27 +03:00
self . deleteDirectoryAndSubdirectoriesMetadata ( ocId : metadataToDelete . ocId )
2023-01-26 20:09:19 +03:00
}
}
private func processDirectoryMetadatasToUpdate ( databaseToWriteTo : Realm ,
2023-02-02 22:12:03 +03:00
existingDirectoryMetadatas : Results < NextcloudDirectoryMetadataTable > ,
2023-01-26 20:09:19 +03:00
updatedDirectoryMetadatas : [ NextcloudDirectoryMetadataTable ] ) {
assert ( databaseToWriteTo . isInWriteTransaction )
for updatedMetadata in updatedDirectoryMetadatas {
if let existingMetadata = existingDirectoryMetadatas . first ( where : { $0 . ocId = = updatedMetadata . ocId } ) {
if ! existingMetadata . isInSameRemoteState ( updatedMetadata ) {
2023-01-26 20:47:34 +03:00
databaseToWriteTo . add ( NextcloudDirectoryMetadataTable ( value : updatedMetadata ) , update : . all )
2023-03-15 00:05:50 +03:00
Logger . ncFilesDatabase . debug ( " Updated existing directory metadata. ocID: \( updatedMetadata . ocId , privacy : . public ) , etag: \( updatedMetadata . etag , privacy : . public ) , serverUrl: \( updatedMetadata . serverUrl ) " )
2023-01-26 20:09:19 +03:00
}
// D o n ' t u p d a t e u n d e r o t h e r c i r c u m s t a n c e s i n w h i c h t h e m e t a d a t a a l r e a d y e x i s t s
} else { // T h i s i s a n e w m e t a d a t a
2023-01-26 20:47:34 +03:00
databaseToWriteTo . add ( NextcloudDirectoryMetadataTable ( value : updatedMetadata ) , update : . all )
2023-03-15 00:05:50 +03:00
Logger . ncFilesDatabase . debug ( " Created new directory metadata during update. ocID: \( updatedMetadata . ocId , privacy : . public ) , etag: \( updatedMetadata . etag , privacy : . public ) , serverUrl: \( updatedMetadata . serverUrl ) " )
2023-01-26 20:09:19 +03:00
}
}
}
2023-01-26 21:09:24 +03:00
func updateDirectoryMetadatas ( account : String , parentDirectoryServerUrl : String , updatedDirectoryMetadatas : [ NextcloudDirectoryMetadataTable ] ) {
2023-01-26 18:07:46 +03:00
let database = ncDatabase ( )
2023-01-26 20:09:19 +03:00
2023-03-09 01:27:45 +03:00
let existingDirectoryMetadatas = ncDatabase ( ) . objects ( NextcloudDirectoryMetadataTable . self ) . filter ( " account == %@ AND parentDirectoryServerUrl == %@ " , account , parentDirectoryServerUrl )
// A c t u a l d b w r i t i n g h a n d l e d i n t e r n a l l y
processDirectoryMetadatasToDelete ( databaseToWriteTo : database ,
existingDirectoryMetadatas : existingDirectoryMetadatas ,
updatedDirectoryMetadatas : updatedDirectoryMetadatas )
2023-01-26 18:07:46 +03:00
do {
try database . write {
2023-01-26 20:09:19 +03:00
processDirectoryMetadatasToUpdate ( databaseToWriteTo : database ,
existingDirectoryMetadatas : existingDirectoryMetadatas ,
updatedDirectoryMetadatas : updatedDirectoryMetadatas )
2023-01-26 18:07:46 +03:00
}
} catch let error {
2023-03-14 23:03:30 +03:00
Logger . ncFilesDatabase . error ( " Could not update directory metadatas, received error: \( error . localizedDescription ) " )
2023-01-26 18:07:46 +03:00
}
}
2023-02-20 15:37:10 +03:00
func directoryMetadataFromItemMetadata ( directoryItemMetadata : NextcloudItemMetadataTable , recordEtag : Bool = false ) -> NextcloudDirectoryMetadataTable {
2023-02-01 21:35:08 +03:00
var newDirectoryMetadata = NextcloudDirectoryMetadataTable ( )
let directoryOcId = directoryItemMetadata . ocId
2023-01-26 20:09:19 +03:00
2023-02-01 21:35:08 +03:00
if let existingDirectoryMetadata = directoryMetadata ( ocId : directoryOcId ) {
newDirectoryMetadata = existingDirectoryMetadata
}
2023-01-26 20:09:19 +03:00
2023-02-01 21:35:08 +03:00
if recordEtag {
newDirectoryMetadata . etag = directoryItemMetadata . etag
}
2023-01-26 20:09:19 +03:00
2023-02-01 21:35:08 +03:00
newDirectoryMetadata . ocId = directoryOcId
newDirectoryMetadata . fileId = directoryItemMetadata . fileId
newDirectoryMetadata . parentDirectoryServerUrl = directoryItemMetadata . serverUrl
newDirectoryMetadata . serverUrl = directoryItemMetadata . serverUrl + " / " + directoryItemMetadata . fileNameView
newDirectoryMetadata . account = directoryItemMetadata . account
newDirectoryMetadata . e2eEncrypted = directoryItemMetadata . e2eEncrypted
newDirectoryMetadata . favorite = directoryItemMetadata . favorite
newDirectoryMetadata . permissions = directoryItemMetadata . permissions
2023-01-26 20:09:19 +03:00
2023-02-01 21:35:08 +03:00
return newDirectoryMetadata
}
func updateDirectoryMetadatasFromItemMetadatas ( account : String , parentDirectoryServerUrl : String , updatedDirectoryItemMetadatas : [ NextcloudItemMetadataTable ] , recordEtag : Bool = false ) {
2023-01-26 20:09:19 +03:00
2023-02-01 21:35:08 +03:00
var updatedDirMetadatas : [ NextcloudDirectoryMetadataTable ] = [ ]
for directoryItemMetadata in updatedDirectoryItemMetadatas {
let newDirectoryMetadata = directoryMetadataFromItemMetadata ( directoryItemMetadata : directoryItemMetadata , recordEtag : recordEtag )
2023-01-26 20:09:19 +03:00
updatedDirMetadatas . append ( newDirectoryMetadata )
}
2023-01-26 21:09:24 +03:00
updateDirectoryMetadatas ( account : account , parentDirectoryServerUrl : parentDirectoryServerUrl , updatedDirectoryMetadatas : updatedDirMetadatas )
2023-01-26 20:09:19 +03:00
}
2023-02-20 15:36:27 +03:00
func addDirectoryMetadata ( _ metadata : NextcloudDirectoryMetadataTable ) {
let database = ncDatabase ( )
do {
try database . write {
database . add ( metadata , update : . all )
2023-03-15 00:05:50 +03:00
Logger . ncFilesDatabase . debug ( " Added new directory metadata. ocId: \( metadata . ocId , privacy : . public ) , etag: \( metadata . etag , privacy : . public ) , serverUrl: \( metadata . serverUrl ) " )
2023-02-20 15:36:27 +03:00
}
} catch let error {
2023-03-15 00:05:50 +03:00
Logger . ncFilesDatabase . error ( " Could not add new directory metadata. ocId: \( metadata . ocId , privacy : . public ) , etag: \( metadata . etag , privacy : . public ) , serverUrl: \( metadata . serverUrl ) , received error: \( error , privacy : . public ) " )
2023-02-20 15:36:27 +03:00
}
}
2023-03-09 18:45:28 +03:00
// D e l e t e s a l l m e t a d a t a s r e l a t e d t o t h e i n f o o f t h e d i r e c t o r y p r o v i d e d
2023-03-09 18:47:27 +03:00
func deleteDirectoryAndSubdirectoriesMetadata ( ocId : String ) {
2023-02-27 20:24:12 +03:00
let database = ncDatabase ( )
2023-03-09 18:47:27 +03:00
guard let directoryMetadata = database . objects ( NextcloudDirectoryMetadataTable . self ) . filter ( " ocId == %@ " , ocId ) . first else {
2023-03-15 00:05:50 +03:00
Logger . ncFilesDatabase . error ( " Could not find directory metadata for ocId \( ocId , privacy : . public ) . Not proceeding with deletion " )
2023-03-09 18:47:27 +03:00
return
}
let results = database . objects ( NextcloudDirectoryMetadataTable . self ) . filter ( " account == %@ AND serverUrl BEGINSWITH %@ " , directoryMetadata . account , directoryMetadata . serverUrl )
2023-02-27 20:24:12 +03:00
for result in results {
2023-03-09 18:45:28 +03:00
deleteItemMetadata ( ocId : result . ocId )
2023-02-27 20:24:12 +03:00
deleteLocalFileMetadata ( ocId : result . ocId )
}
do {
try database . write {
2023-03-15 00:05:50 +03:00
Logger . ncFilesDatabase . debug ( " Deleting root directory metadata in recursive delete. ocID: \( directoryMetadata . ocId , privacy : . public ) , etag: \( directoryMetadata . etag , privacy : . public ) , serverUrl: \( directoryMetadata . serverUrl ) " )
2023-02-27 20:24:12 +03:00
database . delete ( results )
}
} catch let error {
2023-03-15 00:05:50 +03:00
Logger . ncFilesDatabase . error ( " Could not delete root directory metadata in recursive delete. ocID: \( directoryMetadata . ocId , privacy : . public ) , etag: \( directoryMetadata . etag , privacy : . public ) , serverUrl: \( directoryMetadata . serverUrl ) , received error: \( error , privacy : . public ) " )
2023-02-27 20:24:12 +03:00
}
}
2023-02-28 01:03:50 +03:00
func renameDirectoryAndPropagateToChildren ( ocId : String , newServerUrl : String , newFileName : String ) {
let database = ncDatabase ( )
do {
try database . write {
guard let directoryTableResult = database . objects ( NextcloudDirectoryMetadataTable . self ) . filter ( " ocId == %@ " , ocId ) . first ,
let directoryItemResult = database . objects ( NextcloudItemMetadataTable . self ) . filter ( " ocId == %@ " , ocId ) . first else {
2023-03-15 00:05:50 +03:00
Logger . ncFilesDatabase . error ( " Could not find a directory with ocID \( ocId , privacy : . public ) , cannot proceed with recursive renaming " )
2023-02-28 01:03:50 +03:00
return
}
let oldServerUrl = directoryTableResult . serverUrl
let childItemResults = database . objects ( NextcloudItemMetadataTable . self ) . filter ( " account == %@ AND serverUrl BEGINSWITH %@ " , directoryTableResult . account , oldServerUrl )
let childDirectoryResults = database . objects ( NextcloudDirectoryMetadataTable . self ) . filter ( " account == %@ AND serverUrl BEGINSWITH %@ " , directoryTableResult . account , oldServerUrl )
directoryTableResult . serverUrl = newServerUrl
database . add ( directoryTableResult , update : . all )
directoryItemResult . fileName = newFileName
directoryItemResult . fileNameView = newFileName
database . add ( directoryItemResult , update : . all )
2023-03-14 23:03:30 +03:00
Logger . ncFilesDatabase . debug ( " Renamed root renaming directory at \( oldServerUrl ) to \( newServerUrl ) " )
2023-02-28 01:03:50 +03:00
for childItem in childItemResults {
let oldServerUrl = childItem . serverUrl
let movedServerUrl = oldServerUrl . replacingOccurrences ( of : oldServerUrl , with : newServerUrl )
childItem . serverUrl = movedServerUrl
database . add ( childItem , update : . all )
2023-03-14 23:03:30 +03:00
Logger . ncFilesDatabase . debug ( " Moved childItem at \( oldServerUrl ) to \( movedServerUrl ) " )
2023-02-28 01:03:50 +03:00
}
for childDirectory in childDirectoryResults {
let oldServerUrl = childDirectory . serverUrl
let oldParentServerUrl = childDirectory . parentDirectoryServerUrl
let movedServerUrl = oldServerUrl . replacingOccurrences ( of : oldServerUrl , with : newServerUrl )
let movedParentServerUrl = oldServerUrl . replacingOccurrences ( of : oldParentServerUrl , with : newServerUrl )
childDirectory . serverUrl = movedServerUrl
childDirectory . parentDirectoryServerUrl = movedParentServerUrl
database . add ( childDirectory , update : . all )
2023-03-14 23:03:30 +03:00
Logger . ncFilesDatabase . debug ( " Moved childDirectory at \( oldServerUrl ) to \( movedServerUrl ) " )
2023-02-28 01:03:50 +03:00
}
}
} catch let error {
2023-03-15 00:05:50 +03:00
Logger . ncFilesDatabase . error ( " Could not rename directory metadata with ocId: \( ocId , privacy : . public ) to new serverUrl: \( newServerUrl ) , received error: \( error , privacy : . public ) " )
2023-02-28 01:03:50 +03:00
}
}
2023-01-12 23:36:51 +03:00
func localFileMetadataFromOcId ( _ ocId : String ) -> NextcloudLocalFileMetadataTable ? {
2023-01-26 20:47:34 +03:00
if let metadata = ncDatabase ( ) . objects ( NextcloudLocalFileMetadataTable . self ) . filter ( " ocId == %@ " , ocId ) . first {
return NextcloudLocalFileMetadataTable ( value : metadata )
}
return nil
2023-01-12 23:36:51 +03:00
}
2023-01-13 16:53:40 +03:00
2023-02-16 17:43:44 +03:00
func addLocalFileMetadataFromItemMetadata ( _ itemMetadata : NextcloudItemMetadataTable ) {
let database = ncDatabase ( )
do {
try database . write {
let newLocalFileMetadata = NextcloudLocalFileMetadataTable ( )
newLocalFileMetadata . ocId = itemMetadata . ocId
newLocalFileMetadata . fileName = itemMetadata . fileName
newLocalFileMetadata . account = itemMetadata . account
newLocalFileMetadata . etag = itemMetadata . etag
newLocalFileMetadata . exifDate = Date ( )
newLocalFileMetadata . exifLatitude = " -1 "
newLocalFileMetadata . exifLongitude = " -1 "
database . add ( newLocalFileMetadata , update : . all )
2023-03-15 00:05:50 +03:00
Logger . ncFilesDatabase . debug ( " Added local file metadata from item metadata. ocID: \( itemMetadata . ocId , privacy : . public ) , etag: \( itemMetadata . etag , privacy : . public ) , fileName: \( itemMetadata . fileName , privacy : OSLogPrivacy . auto ( mask : . hash ) ) " )
2023-02-16 17:43:44 +03:00
}
} catch let error {
2023-03-15 00:05:50 +03:00
Logger . ncFilesDatabase . error ( " Could not add local file metadata from item metadata. ocID: \( itemMetadata . ocId , privacy : . public ) , etag: \( itemMetadata . etag , privacy : . public ) , fileName: \( itemMetadata . fileName , privacy : OSLogPrivacy . auto ( mask : . hash ) ) , received error: \( error , privacy : . public ) " )
2023-02-16 17:43:44 +03:00
}
}
2023-02-27 20:24:12 +03:00
func deleteLocalFileMetadata ( ocId : String ) {
let database = ncDatabase ( )
do {
try database . write {
let results = database . objects ( NextcloudLocalFileMetadataTable . self ) . filter ( " ocId == %@ " , ocId )
database . delete ( results )
}
} catch let error {
2023-03-15 00:05:50 +03:00
Logger . ncFilesDatabase . error ( " Could not delete local file metadata with ocId: \( ocId , privacy : . public ) , received error: \( error , privacy : . public ) " )
2023-02-27 20:24:12 +03:00
}
}
2023-03-07 23:48:08 +03:00
private func sortedLocalFileMetadatas ( _ metadatas : Results < NextcloudLocalFileMetadataTable > ) -> [ NextcloudLocalFileMetadataTable ] {
let sortedMetadatas = metadatas . sorted ( byKeyPath : " fileName " , ascending : true )
return Array ( sortedMetadatas . map { NextcloudLocalFileMetadataTable ( value : $0 ) } )
}
func localFileMetadatas ( account : String ) -> [ NextcloudLocalFileMetadataTable ] {
let results = ncDatabase ( ) . objects ( NextcloudLocalFileMetadataTable . self ) . filter ( " account == %@ " , account )
return sortedLocalFileMetadatas ( results )
}
2023-03-08 00:06:46 +03:00
func localFileItemMetadatas ( account : String ) -> [ NextcloudItemMetadataTable ] {
let localFileMetadatas = localFileMetadatas ( account : account )
let localFileMetadatasOcIds = Array ( localFileMetadatas . map { $0 . ocId } )
var itemMetadatas : [ NextcloudItemMetadataTable ] = [ ]
for ocId in localFileMetadatasOcIds {
guard let itemMetadata = itemMetadataFromOcId ( ocId ) else {
2023-03-15 00:05:50 +03:00
Logger . ncFilesDatabase . error ( " Could not find matching item metadata for local file metadata with ocId: \( ocId , privacy : . public ) with request from account: \( account ) " )
2023-03-08 00:06:46 +03:00
continue ;
}
itemMetadatas . append ( NextcloudItemMetadataTable ( value : itemMetadata ) )
}
return itemMetadatas
}
2023-03-07 23:48:08 +03:00
func convertNKFileToItemMetadata ( _ file : NKFile , account : String ) -> NextcloudItemMetadataTable {
2023-01-13 16:53:40 +03:00
let metadata = NextcloudItemMetadataTable ( )
metadata . account = account
metadata . checksums = file . checksums
metadata . commentsUnread = file . commentsUnread
metadata . contentType = file . contentType
if let date = file . creationDate {
2023-01-26 22:48:58 +03:00
metadata . creationDate = date as Date
2023-01-13 16:53:40 +03:00
} else {
2023-01-26 22:48:58 +03:00
metadata . creationDate = file . date as Date
2023-01-13 16:53:40 +03:00
}
metadata . dataFingerprint = file . dataFingerprint
2023-01-26 22:48:58 +03:00
metadata . date = file . date as Date
2023-01-13 16:53:40 +03:00
metadata . directory = file . directory
metadata . downloadURL = file . downloadURL
metadata . e2eEncrypted = file . e2eEncrypted
metadata . etag = file . etag
metadata . favorite = file . favorite
metadata . fileId = file . fileId
metadata . fileName = file . fileName
metadata . fileNameView = file . fileName
metadata . hasPreview = file . hasPreview
metadata . iconName = file . iconName
metadata . mountType = file . mountType
metadata . name = file . name
metadata . note = file . note
metadata . ocId = file . ocId
metadata . ownerId = file . ownerId
metadata . ownerDisplayName = file . ownerDisplayName
metadata . lock = file . lock
metadata . lockOwner = file . lockOwner
metadata . lockOwnerEditor = file . lockOwnerEditor
metadata . lockOwnerType = file . lockOwnerType
metadata . lockOwnerDisplayName = file . lockOwnerDisplayName
metadata . lockTime = file . lockTime
metadata . lockTimeOut = file . lockTimeOut
metadata . path = file . path
metadata . permissions = file . permissions
metadata . quotaUsedBytes = file . quotaUsedBytes
metadata . quotaAvailableBytes = file . quotaAvailableBytes
metadata . richWorkspace = file . richWorkspace
metadata . resourceType = file . resourceType
metadata . serverUrl = file . serverUrl
metadata . sharePermissionsCollaborationServices = file . sharePermissionsCollaborationServices
for element in file . sharePermissionsCloudMesh {
metadata . sharePermissionsCloudMesh . append ( element )
}
for element in file . shareType {
metadata . shareType . append ( element )
}
metadata . size = file . size
metadata . classFile = file . classFile
// FIXME: i O S 1 2 . 0 , * d o n ' t d e t e c t U T I t e x t / m a r k d o w n , t e x t / x - m a r k d o w n
2023-03-11 03:44:25 +03:00
if ( metadata . contentType = = " text/markdown " || metadata . contentType = = " text/x-markdown " ) && metadata . classFile = = NKCommon . TypeClassFile . unknow . rawValue {
metadata . classFile = NKCommon . TypeClassFile . document . rawValue
2023-01-13 16:53:40 +03:00
}
if let date = file . uploadDate {
2023-01-26 22:48:58 +03:00
metadata . uploadDate = date as Date
2023-01-13 16:53:40 +03:00
} else {
2023-01-26 22:48:58 +03:00
metadata . uploadDate = file . date as Date
2023-01-13 16:53:40 +03:00
}
metadata . urlBase = file . urlBase
metadata . user = file . user
metadata . userId = file . userId
// S u p p o r t f o r f i n d i n g t h e c o r r e c t f i l e n a m e f o r e 2 e e f i l e s s h o u l d g o h e r e
return metadata
}
2023-03-08 02:21:23 +03:00
func convertNKFilesFromDirectoryReadToItemMetadatas ( _ files : [ NKFile ] , account : String , completionHandler : @ escaping ( _ directoryMetadata : NextcloudItemMetadataTable , _ childDirectoriesMetadatas : [ NextcloudItemMetadataTable ] , _ metadatas : [ NextcloudItemMetadataTable ] ) -> Void ) {
2023-01-13 16:53:40 +03:00
var directoryMetadataSet = false
var directoryMetadata = NextcloudItemMetadataTable ( )
var childDirectoriesMetadatas : [ NextcloudItemMetadataTable ] = [ ]
var metadatas : [ NextcloudItemMetadataTable ] = [ ]
for file in files {
let metadata = convertNKFileToItemMetadata ( file , account : account )
if metadatas . isEmpty && ! directoryMetadataSet {
directoryMetadata = metadata ;
directoryMetadataSet = true ;
} else {
metadatas . append ( metadata )
if metadata . directory {
childDirectoriesMetadatas . append ( metadata )
}
}
}
completionHandler ( directoryMetadata , childDirectoriesMetadatas , metadatas )
}
2023-01-05 22:59:50 +03:00
}