123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726 |
- // _____ _
- // |_ _| |_ _ _ ___ ___ _ __ __ _
- // | | | ' \| '_/ -_) -_) ' \/ _` |_
- // |_| |_||_|_| \___\___|_|_|_\__,_(_)
- //
- // Threema iOS Client
- // Copyright (c) 2015-2020 Threema GmbH
- //
- // This program is free software: you can redistribute it and/or modify
- // it under the terms of the GNU Affero General Public License, version 3,
- // as published by the Free Software Foundation.
- //
- // 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 Affero General Public License for more details.
- //
- // You should have received a copy of the GNU Affero General Public License
- // along with this program. If not, see <https://www.gnu.org/licenses/>.
- #import "DatabaseManager.h"
- #import "ErrorHandler.h"
- #import "DocumentManager.h"
- #import "AppGroup.h"
- #import "BundleUtil.h"
- #import "UserSettings.h"
- #import "ValidationLogger.h"
- #import "MDMSetup.h"
- #import "DatabaseContext.h"
- #import "ThreemaFramework/ThreemaFramework-Swift.h"
- #import "Utils.h"
- #define THREEMA_DB_MODEL @"ThreemaData"
- #define THREEMA_DB_FILE @"ThreemaData.sqlite"
- #define THREEMA_DB_IMPORT_FILE @"RepairedThreemaData.sqlite"
- #define THREEMA_DB_EXTERNALS @".ThreemaData_SUPPORT"
- #define THREEMA_DB_DIRTY_OBJECT_KEY @"DBDirtyObjectsKey"
- #define THREEMA_DB_DID_UPDATE_EXTERNAL_DATA_PROTECTION_KEY @"DBDidUpdateExternalDataProtectionNewKey"
- #ifdef DEBUG
- static const DDLogLevel ddLogLevel = DDLogLevelVerbose;
- #else
- static const DDLogLevel ddLogLevel = DDLogLevelWarning;
- #endif
- @implementation DatabaseManager
- #pragma mark - Core Data
- static DatabaseManager *dbManager;
- + (instancetype)dbManager {
- if (dbManager == nil) {
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- dbManager = [[DatabaseManager alloc] init];
- });
- }
-
- return dbManager;
- }
- - (DatabaseContext *)getDatabaseContext:(BOOL)forBackgroundProcess
- {
- DatabaseContext *context;
-
- NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
- if (coordinator != nil) {
- context = [[DatabaseContext alloc] initWithPersistentCoordinator:coordinator forBackgroundProcess:forBackgroundProcess];
- }
- else {
- [NSException raise:@"Invalid persistent store coordinator" format:@"Could not create persistent store coordinator"];
- }
-
- return context;
- }
- - (BOOL)shouldUpdateProtection {
- BOOL didUpdateProtectionForExternalData = [[AppGroup userDefaults] boolForKey:THREEMA_DB_DID_UPDATE_EXTERNAL_DATA_PROTECTION_KEY];
-
- NSError *error;
- NSURL *storeURL = [DatabaseManager storeUrl];
- NSDictionary *dict = [[NSFileManager defaultManager] attributesOfItemAtPath:storeURL.path error:&error];
- if (dict[@"NSFileProtectionKey"] == NSFileProtectionCompleteUntilFirstUserAuthentication && didUpdateProtectionForExternalData) {
- // Update shared directories every time to avoid crash (IOS-1406)
- [self updateDirectoryProtectionAtURL:[DocumentManager databaseDirectory]];
- return NO;
- }
- return YES;
- }
- - (void)updateDirectoryProtectionAtURL:(NSURL *)baseURL {
- NSFileManager *fileManager = [[NSFileManager alloc] init];
- NSDirectoryEnumerator *directoryEnumerator = [fileManager enumeratorAtURL:baseURL includingPropertiesForKeys:@[NSURLNameKey, NSURLFileProtectionKey] options:0 errorHandler:nil];
- for (NSURL *fileURL in [directoryEnumerator.allObjects filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"RWIsDirectory == YES"]]) {
- NSString *fileProtection = nil;
- [fileURL getResourceValue:&fileProtection forKey:NSURLFileProtectionKey error:nil];
-
- if (fileProtection != NSURLFileProtectionComplete)
- continue;
-
- [fileManager setAttributes:@{NSFileProtectionKey: NSFileProtectionCompleteUntilFirstUserAuthentication} ofItemAtPath:fileURL.path error:nil];
- }
- }
- - (void)updateProtection {
- // In earlier versions, when we updated the database from NSFileProtectionComplete to NSFileProtectionCompleteUntilFirstUserAuthentication,
- // we did not also update the contents of the external data directory. This needs to be done now, because the Web client must be
- // able to access images etc. when the device is locked, and Core Data needs to save external data files for received media.
- // To be sure, we check our entire app and group containers and change any files or directories that are still set to
- // NSFileProtectionComplete to the more apppropriate NSFileProtectionCompleteUntilFirstUserAuthentication.
- // Note that directories may have a file protection class (which then applies to all new files created within them), but they do not have to.
-
- [self updateProtectionAtURL:[DocumentManager databaseDirectory]];
- [self updateProtectionAtURL:[NSURL fileURLWithPath:NSHomeDirectory()]];
-
- NSUserDefaults *defaults = [AppGroup userDefaults];
- [defaults setBool:YES forKey:THREEMA_DB_DID_UPDATE_EXTERNAL_DATA_PROTECTION_KEY];
- [defaults synchronize];
- }
- - (void)updateProtectionAtURL:(NSURL*)baseURL {
- NSFileManager *fileManager = [[NSFileManager alloc] init];
- NSDirectoryEnumerator *directoryEnumerator = [fileManager enumeratorAtURL:baseURL includingPropertiesForKeys:@[NSURLNameKey, NSURLFileProtectionKey] options:0 errorHandler:nil];
-
- for (NSURL *fileURL in directoryEnumerator) {
- NSString *fileProtection = nil;
- [fileURL getResourceValue:&fileProtection forKey:NSURLFileProtectionKey error:nil];
-
- if (fileProtection != NSURLFileProtectionComplete)
- continue;
-
- [fileManager setAttributes:@{NSFileProtectionKey: NSFileProtectionCompleteUntilFirstUserAuthentication} ofItemAtPath:fileURL.path error:nil];
- }
- }
- // Returns the managed object model for the application.
- // If the model doesn't already exist, it is created from the application's model.
- - (NSManagedObjectModel *)managedObjectModel
- {
- if (_managedObjectModel != nil) {
- return _managedObjectModel;
- }
-
- NSURL *modelURL = [BundleUtil URLForResource:THREEMA_DB_MODEL withExtension:@"momd"];
- _managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
- return _managedObjectModel;
- }
- + (BOOL)storeExists {
- NSURL *storeURL = [DatabaseManager storeUrl];
- return [[NSFileManager defaultManager] fileExistsAtPath:storeURL.path];
- }
- - (BOOL)storeRequiresMigration {
- NSURL *storeURL = [DatabaseManager storeUrl];
-
- if (![[NSFileManager defaultManager] fileExistsAtPath:storeURL.path]) {
- return NO; /* no store = no migration */
- }
-
- NSError *error;
- NSFileProtectionType protectionType = NSFileProtectionCompleteUntilFirstUserAuthentication;
- NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
- [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
- [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,
- protectionType, NSPersistentStoreFileProtectionKey, nil];
- NSDictionary *sourceMetadata = [NSPersistentStoreCoordinator metadataForPersistentStoreOfType:NSSQLiteStoreType URL:storeURL options:options error:&error];
- return ![self.managedObjectModel isConfiguration:nil compatibleWithStoreMetadata:sourceMetadata];
- }
- - (BOOL)storeRequiresImport {
- NSURL *repairedThreemaDataUrl = [[DocumentManager applicationDocumentsDirectory] URLByAppendingPathComponent:THREEMA_DB_IMPORT_FILE];
- return [[NSFileManager defaultManager] fileExistsAtPath:repairedThreemaDataUrl.path];
- }
- /**
- Returns the persistent store coordinator for the application.
- If the coordinator doesn't already exist, it is created and the application's store added to it.
- */
- - (NSPersistentStoreCoordinator *)persistentStoreCoordinator
- {
- // Execute possible DB migration just once
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
-
- if (_persistentStoreCoordinator == nil) {
-
- #ifdef DEBUG
- if ([AppGroup getCurrentType] == AppGroupTypeApp) {
- [FileUtility logDirectoriesAndFilesWithPath:[DocumentManager databaseDirectory] logFileName:@"db-migration-before.log"];
- }
- #endif
-
- double startTime = CACurrentMediaTime();
-
- NSURL *storeURL = [DatabaseManager storeUrl];
-
- [self migrateDatabaseLocation];
-
- NSFileManager *fileManager = [NSFileManager defaultManager];
- NSURL *documentsUrl = [DocumentManager databaseDirectory];
- NSURL *tmpUrlToExternalStorage = [documentsUrl URLByAppendingPathComponent:@"tmpPathToReplacementData"];
- NSURL *urlToExternalStorage = [documentsUrl URLByAppendingPathComponent:@".ThreemaData_SUPPORT/_EXTERNAL_DATA"];
-
- NSString *coreDataModelVersion = [BundleUtil objectForInfoDictionaryKey:@"ThreemaCoreDataVersion"];
-
- NSURL *urlToBackupStorage = [NSURL URLWithString:[NSString stringWithFormat:@"%@.bak.%@", storeURL.absoluteString, coreDataModelVersion]];
- NSURL *tmpUrlToBackupStorage = [NSURL URLWithString:[NSString stringWithFormat:@"%@.bak.%@.%.0f", storeURL.absoluteString, coreDataModelVersion, [[NSDate date] timeIntervalSince1970]]];
-
- //Check if the new model is compatible with any previously stored model
- BOOL requiresMigration = [self storeRequiresMigration];
-
- if (requiresMigration) {
- // Migration is required - check if a store backup file (.bak) exists. If so, the last migration attempt has
- // failed, and before trying again, we copy the backup back to the store URL so Core Data can make another try.
- // Also, during migration, we move away the external data storage folder to keep Core Data from copying every
- // single external data item (media etc.), which is useless, and takes a long time and a lot of disk space.
- if ([fileManager fileExistsAtPath:[urlToBackupStorage path]]) {
- // Delete the broken, half-migrated store and copy the backup
- NSError *copyBackupError = nil;
- [fileManager removeItemAtURL:storeURL error:nil];
- [fileManager copyItemAtURL:urlToBackupStorage toURL:storeURL error:©BackupError];
- if (copyBackupError != nil) {
- _storeError = copyBackupError;
- }
- else {
- // Remove wal and shm temporary files to prevent problems with the SQLite store
- NSURL *walFile = [NSURL URLWithString:[NSString stringWithFormat:@"%@-wal", storeURL.absoluteString]];
- [fileManager removeItemAtURL:walFile error:nil];
- NSURL *shmFile = [NSURL URLWithString:[NSString stringWithFormat:@"%@-shm", storeURL.absoluteString]];
- [fileManager removeItemAtURL:shmFile error:nil];
- // Remove external storage folder; the original will be at tmpUrlToExternalStorage at this point
- [fileManager removeItemAtURL:urlToExternalStorage error:nil];
- }
- } else {
- // Before migration begins, copy the store to a backup file (.bak). We do this in two steps:
- // first we copy the store to a .bak2 file, and then we rename the .bak2 to .bak. This is
- // so that if the copy operation is interrupted (which is possible as it can take some time for
- // large stores), we don't end up using a broken .bak when we start again.
- NSError *copyBackupError = nil;
- [fileManager removeItemAtURL:tmpUrlToBackupStorage error:nil];
- [fileManager copyItemAtURL:storeURL toURL:tmpUrlToBackupStorage error:©BackupError];
- if (copyBackupError != nil) {
- _storeError = copyBackupError;
- }
- else {
- // Rename .bak2 to .bak
- [fileManager removeItemAtURL:urlToBackupStorage error:nil];
- [fileManager moveItemAtURL:tmpUrlToBackupStorage toURL:urlToBackupStorage error:nil];
-
- // Move away external storage directory during migration
- [fileManager removeItemAtURL:tmpUrlToExternalStorage error:nil];
- if ([fileManager fileExistsAtPath:[urlToExternalStorage path]]) {
- [fileManager moveItemAtURL:urlToExternalStorage toURL:tmpUrlToExternalStorage error:nil];
- }
- }
- }
- } else {
- // Migration is currently not required, but if a previous migration completed without
- // us having a chance to put the external data storage folder back in place, we will
- // end up with the media in tmpUrlToExternalStorage where it is inaccessible to Core Data.
- // Attempt to move the media back in such a case, if necessary
- if ([fileManager fileExistsAtPath:[tmpUrlToExternalStorage path]]) {
- if ([fileManager fileExistsAtPath:[urlToExternalStorage path]]) {
- // Ooops, the external storage directory already exists, so we should not delete it or
- // we will risk losing some (new) media. Instead, merge the contents of the two directories
- NSError *mergeError = nil;
- [self mergeContentsOfPath:[tmpUrlToExternalStorage path] intoPath:[urlToExternalStorage path] error:&mergeError];
- if (!mergeError) {
- [fileManager removeItemAtURL:tmpUrlToExternalStorage error:nil];
- }
- } else {
- [fileManager moveItemAtURL:tmpUrlToExternalStorage toURL:urlToExternalStorage error:nil];
- }
- [self removeMigrationLeftover];
- }
- }
-
- if (_storeError == nil) {
- NSError *error = nil;
-
- NSPersistentStoreCoordinator *persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
-
- NSFileProtectionType protectionType = NSFileProtectionCompleteUntilFirstUserAuthentication;
- NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
- [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
- [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,
- protectionType, NSPersistentStoreFileProtectionKey, nil];
- if (![persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error]) {
- if ([fileManager fileExistsAtPath:[urlToBackupStorage path]]) {
- [fileManager removeItemAtURL:storeURL error:nil];
- [fileManager copyItemAtURL:urlToBackupStorage toURL:storeURL error:nil];
- }
-
- DDLogError(@"Unresolved error %@, %@", error, [error userInfo]);
- _storeError = error;
- }
-
- if (requiresMigration && error == nil) {
- // Core Data migration is now complete. Replace the default external storage folder with the version pre upgrade,
- // and delete the store backup files.
- [fileManager removeItemAtURL:urlToExternalStorage error:nil];
- [fileManager moveItemAtURL:tmpUrlToExternalStorage toURL:urlToExternalStorage error:nil];
- [fileManager removeItemAtURL:urlToBackupStorage error:nil];
- [fileManager removeItemAtURL:tmpUrlToBackupStorage error:nil];
-
- [self removeMigrationLeftover];
- }
-
- double endTime = CACurrentMediaTime();
- DDLogInfo(@"DB setup time %f s", (endTime - startTime));
-
- #ifdef DEBUG
- [FileUtility logDirectoriesAndFilesWithPath:[DocumentManager databaseDirectory] logFileName:@"db-migration-after.log"];
- #endif
- _persistentStoreCoordinator = persistentStoreCoordinator;
- }
- }
- });
-
- return _persistentStoreCoordinator;
- }
- - (void)removeMigrationLeftover {
- // remove any leftover from previous failed migrations
- NSURL *documentsUrl = [DocumentManager databaseDirectory];
- NSFileManager *fileManager = [NSFileManager defaultManager];
- NSArray *files = [fileManager contentsOfDirectoryAtPath:documentsUrl.path error:nil];
- for (NSString *fileName in files) {
- if ([fileName hasPrefix:[NSString stringWithFormat:@"%@.v2.bak", THREEMA_DB_FILE]] || [fileName hasPrefix:[NSString stringWithFormat:@"%@.bak", THREEMA_DB_FILE]]) {
- NSURL *fileUrl = [documentsUrl URLByAppendingPathComponent:fileName];
- [fileManager removeItemAtURL:fileUrl error:nil];
- }
- }
- }
- - (unsigned long long)storeSize {
- unsigned long long storeSize = 0;
-
- NSString *documentsPath = [DocumentManager databaseDirectory].path;
-
- storeSize += [DocumentManager sizeOfObjectAtPath:[documentsPath stringByAppendingPathComponent:THREEMA_DB_FILE]];
-
- // NSString *pathToSupportDir = [documentsPath stringByAppendingPathComponent:THREEMA_DB_EXTERNALS];
- // storeSize += [Utils sizeOfObjectAtPath:pathToSupportDir];
-
- return storeSize;
- }
- - (BOOL)canMigrateDB {
- unsigned long long storeSize = [self storeSize];
- unsigned long long freeDiskSpace = [self freeDiskSpace];
- unsigned long long minFreeRequired = MAX(storeSize*3, 512*1024*1024);
-
- /* must have at least 3 * storeSize free, and in any case at least 512 MB */
- if (freeDiskSpace < minFreeRequired) {
- DDLogError(@"Not enough space for migration (store size %llu, %llu free)", storeSize, freeDiskSpace);
- dispatch_async(dispatch_get_main_queue(), ^{
- NSString *message = [NSString stringWithFormat:NSLocalizedString(@"database_migration_storage_warning_message", nil), minFreeRequired/1073741824.0f, freeDiskSpace/1073741824.0f];
- [ErrorHandler abortWithTitle:NSLocalizedString(@"database_migration_storage_warning_title", nil) message:message];
- });
-
- return NO;
- }
-
- return YES;
- }
- - (void)doMigrateDB {
- [self persistentStoreCoordinator];
- }
- - (void)copyImportedDatabase {
- double startTime = CACurrentMediaTime();
-
- NSURL *storeURL = [DatabaseManager storeUrl];
-
- [self migrateDatabaseLocation];
-
- NSFileManager *fileManager = [NSFileManager defaultManager];
- NSURL *documentsUrl = [DocumentManager applicationDocumentsDirectory];
- NSURL *urlToImportStorage = [NSURL URLWithString:[NSString stringWithFormat:@"%@%@", documentsUrl.absoluteString, THREEMA_DB_IMPORT_FILE]];
-
- if ([fileManager fileExistsAtPath:[urlToImportStorage path]]) {
- NSError *copyImportError = nil;
- [fileManager removeItemAtURL:storeURL error:nil];
- [fileManager copyItemAtURL:urlToImportStorage toURL:storeURL error:©ImportError];
-
- if (copyImportError == nil) {
- // Remove wal and shm temporary files to prevent problems with the SQLite store
- NSURL *walFile = [NSURL URLWithString:[NSString stringWithFormat:@"%@-wal", storeURL.absoluteString]];
- [fileManager removeItemAtURL:walFile error:nil];
- NSURL *shmFile = [NSURL URLWithString:[NSString stringWithFormat:@"%@-shm", storeURL.absoluteString]];
- [fileManager removeItemAtURL:shmFile error:nil];
-
- [fileManager removeItemAtURL:urlToImportStorage error:nil];
- }
-
- double endTime = CACurrentMediaTime();
- DDLogInfo(@"DB setup time %f s", (endTime - startTime));
- }
- }
- - (void)eraseDB {
- NSArray *stores = [_persistentStoreCoordinator persistentStores];
- for (NSPersistentStore *store in stores) {
- [_persistentStoreCoordinator removePersistentStore:store error:nil];
- [[NSFileManager defaultManager] removeItemAtPath:store.URL.path error:nil];
- }
-
- _persistentStoreCoordinator = nil;
- }
- - (void)migrateDatabaseLocation {
- NSFileManager *fileManager = [NSFileManager defaultManager];
- // check if data is at application location, if yes move it to group directory
- NSURL *appUrl = [DocumentManager applicationDocumentsDirectory];
- NSURL *appFile = [appUrl URLByAppendingPathComponent:THREEMA_DB_FILE];
-
- if ([fileManager fileExistsAtPath:appFile.path]) {
- NSURL *targetURL = [DocumentManager databaseDirectory];
- [self moveDBFilesFrom:appUrl to:targetURL];
- }
- }
- - (void)moveDBFilesFrom:(NSURL *)sourceUrl to:(NSURL *)targetUrl {
- NSError *error;
- NSFileManager *fileManager = [NSFileManager defaultManager];
-
- [fileManager createDirectoryAtURL:targetUrl withIntermediateDirectories:YES attributes:nil error:&error];
-
- NSURL *sourceDBFile = [sourceUrl URLByAppendingPathComponent:THREEMA_DB_FILE];
- NSURL *targetDBFile = [targetUrl URLByAppendingPathComponent:THREEMA_DB_FILE];
- if ([fileManager fileExistsAtPath:sourceDBFile.path]) {
- [fileManager removeItemAtURL:targetDBFile error:&error];
- [fileManager moveItemAtURL:sourceDBFile toURL:targetDBFile error:&error];
- }
-
- NSString *walFile = [THREEMA_DB_FILE stringByAppendingString:@"-wal"];
- NSURL *sourceDBWalFile = [sourceUrl URLByAppendingPathComponent:walFile];
- NSURL *targetDBWalFile = [targetUrl URLByAppendingPathComponent:walFile];
- if ([fileManager fileExistsAtPath:sourceDBWalFile.path]) {
- [fileManager removeItemAtURL:targetDBWalFile error:&error];
- [fileManager moveItemAtURL:sourceDBWalFile toURL:targetDBWalFile error:&error];
- }
-
- // no need to move shm file, it is recreated by sqllite (https://www.sqlite.org/tempfiles.html)
- // to keep clean -> delete it
- NSString *shmFile = [THREEMA_DB_FILE stringByAppendingString:@"-shm"];
- NSURL *shmUrl = [sourceUrl URLByAppendingPathComponent:shmFile];
- if ([fileManager fileExistsAtPath:shmUrl.path]) {
- [fileManager removeItemAtURL:shmUrl error:nil];
- }
- NSURL *sourceExternals = [sourceUrl URLByAppendingPathComponent:THREEMA_DB_EXTERNALS];
- NSURL *targetExternals = [targetUrl URLByAppendingPathComponent:THREEMA_DB_EXTERNALS];
- if ([fileManager fileExistsAtPath:sourceExternals.path]) {
-
- if ([fileManager fileExistsAtPath:targetExternals.path]) {
- // there already are some external files at target -> keep it and move source files one by one
-
- NSURL *sourceExternalsSubDir = [sourceExternals URLByAppendingPathComponent:@"_EXTERNAL_DATA"];
- NSURL *targetExternalsSubDir = [targetExternals URLByAppendingPathComponent:@"_EXTERNAL_DATA"];
- if ([fileManager fileExistsAtPath:targetExternalsSubDir.path] == NO) {
- [fileManager createDirectoryAtURL:targetExternalsSubDir withIntermediateDirectories:YES attributes:0 error:nil];
- }
-
- NSDirectoryEnumerator *dirEnum = [fileManager enumeratorAtPath:sourceExternalsSubDir.path];
-
- NSString *file;
- while ((file = [dirEnum nextObject])) {
- NSURL *externalDataSource = [sourceExternalsSubDir URLByAppendingPathComponent:file];
- NSURL *externalDataTarget = [targetExternalsSubDir URLByAppendingPathComponent:file];
-
- [fileManager moveItemAtPath:externalDataSource.path toPath:externalDataTarget.path error:&error];
- }
-
-
- // remove old directory
- [fileManager removeItemAtURL:sourceExternalsSubDir error:nil];
-
- } else {
- // move whole directory
- [fileManager moveItemAtURL:sourceExternals toURL:targetExternals error:&error];
- }
- }
- }
- + (NSURL *)storeUrl {
- return [[DocumentManager databaseDirectory] URLByAppendingPathComponent:THREEMA_DB_FILE];
- }
- - (unsigned long long)freeDiskSpace {
- if (@available(iOS 11.0, *)) {
- NSURL *fileUrl = [NSURL fileURLWithPath:NSHomeDirectory()];
- NSError *error = nil;
- NSDictionary *dict = [fileUrl resourceValuesForKeys:@[NSURLVolumeAvailableCapacityForImportantUsageKey] error:&error];
- if (dict) {
- NSNumber *availableCapacity = dict[NSURLVolumeAvailableCapacityForImportantUsageKey];
- return availableCapacity.unsignedLongLongValue;
- } else {
- DDLogError(@"Cannot retrieve free disk space: %@", error);
- return 0;
- }
- } else {
- //unsigned long long totalSpace = 0;
- unsigned long long totalFreeSpace = 0;
-
- NSError *error = nil;
- NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
- NSDictionary *dictionary = [[NSFileManager defaultManager] attributesOfFileSystemForPath:[paths lastObject] error: &error];
-
- if (dictionary) {
- //NSNumber *fileSystemSizeInBytes = [dictionary objectForKey: NSFileSystemSize];
- NSNumber *freeFileSystemSizeInBytes = [dictionary objectForKey:NSFileSystemFreeSize];
- //totalSpace = [fileSystemSizeInBytes unsignedLongLongValue];
- totalFreeSpace = [freeFileSystemSizeInBytes unsignedLongLongValue];
- }
-
- return totalFreeSpace;
- }
- }
- - (void)disableBackupForDatabaseDirectory:(BOOL)disable
- {
- NSString *documentsPath = [DocumentManager databaseDirectory].path;
- NSString *applicationDocumentsPath = [DocumentManager applicationDocumentsDirectory].path;
- NSString *cachePath = [[[NSFileManager defaultManager] URLsForDirectory:NSCachesDirectory inDomains:NSUserDomainMask] lastObject].path;
-
- NSURL *urlToExternalStorage = [NSURL fileURLWithPath:[documentsPath stringByAppendingPathComponent:@".ThreemaData_SUPPORT"]];
- [self setResourceValue:disable forUrl:urlToExternalStorage];
-
- urlToExternalStorage = [NSURL fileURLWithPath:[documentsPath stringByAppendingPathComponent:THREEMA_DB_FILE]];
- [self setResourceValue:disable forUrl:urlToExternalStorage];
-
- urlToExternalStorage = [NSURL fileURLWithPath:[documentsPath stringByAppendingPathComponent:@"ThreemaData.sqlite-shm"]];
- [self setResourceValue:disable forUrl:urlToExternalStorage];
-
- urlToExternalStorage = [NSURL fileURLWithPath:[documentsPath stringByAppendingPathComponent:@"ThreemaData.sqlite-wal"]];
- [self setResourceValue:disable forUrl:urlToExternalStorage];
-
- urlToExternalStorage = [NSURL fileURLWithPath:[documentsPath stringByAppendingPathComponent:@"DoneMessages"]];
- [self setResourceValue:disable forUrl:urlToExternalStorage];
-
- urlToExternalStorage = [NSURL fileURLWithPath:[documentsPath stringByAppendingPathComponent:@"WebSessions"]];
- [self setResourceValue:disable forUrl:urlToExternalStorage];
-
- urlToExternalStorage = [NSURL fileURLWithPath:[documentsPath stringByAppendingPathComponent:@"PreviousContext"]];
- [self setResourceValue:disable forUrl:urlToExternalStorage];
-
- urlToExternalStorage = [NSURL fileURLWithPath:[applicationDocumentsPath stringByAppendingPathComponent:@"idbackup.txt"]];
- [self setResourceValue:disable forUrl:urlToExternalStorage];
-
- urlToExternalStorage = [NSURL fileURLWithPath:[cachePath stringByAppendingPathComponent:@"ch.threema.work.iapp/Cache.db"]];
- [self setResourceValue:disable forUrl:urlToExternalStorage];
-
- urlToExternalStorage = [NSURL fileURLWithPath:[cachePath stringByAppendingPathComponent:@"ch.threema.work.iapp/Cache.db-shm"]];
- [self setResourceValue:disable forUrl:urlToExternalStorage];
-
- urlToExternalStorage = [NSURL fileURLWithPath:[cachePath stringByAppendingPathComponent:@"ch.threema.work.iapp/Cache.db-wal"]];
- [self setResourceValue:disable forUrl:urlToExternalStorage];
- }
- - (void)setResourceValue:(BOOL)disable forUrl:(NSURL *)url {
- NSError *error = nil;
- if ([[NSFileManager defaultManager] fileExistsAtPath:[url path]]) {
- // assert([[NSFileManager defaultManager] fileExistsAtPath:[url path]]);
- BOOL success = [url setResourceValue:[NSNumber numberWithBool:disable] forKey: NSURLIsExcludedFromBackupKey error: &error];
- if(!success){
- DDLogError(@"Error excluding %@ from backup %@", [url lastPathComponent], error);
- }
- }
- }
- #pragma mark - dirty object handling (e.g. when switching from share extension back to app)
- - (void)refreshDirtyObjectIDs:(NSDictionary *)changes intoContext:(NSManagedObjectContext *)context {
- [NSManagedObjectContext mergeChangesFromRemoteContextSave:changes intoContexts:@[context]];
- }
- - (void)refreshDirtyObjects {
-
- DatabaseContext *dbContext = [self getDatabaseContext:NO];
- NSUserDefaults *defaults = [AppGroup userDefaults];
- NSArray *objects = [defaults arrayForKey:THREEMA_DB_DIRTY_OBJECT_KEY];
- if (objects == nil) {
- return;
- }
-
- [defaults removeObjectForKey:THREEMA_DB_DIRTY_OBJECT_KEY];
- [defaults synchronize];
-
- NSTimeInterval stalenessInterval = dbContext.main.stalenessInterval;
- [dbContext main].stalenessInterval = 0.0;
-
- NSMutableSet *notifyObjectIds = [NSMutableSet setWithCapacity:[objects count]];
- // first refresh objects in context
- for (NSString *urlString in objects) {
- NSURL *url = [NSURL URLWithString:urlString];
- NSManagedObjectID *objectID = [_persistentStoreCoordinator managedObjectIDForURIRepresentation:url];
- if (objectID) {
- NSManagedObject *object = [[dbContext main] objectWithID:objectID];
- [[dbContext main] refreshObject:object mergeChanges:YES];
-
- [notifyObjectIds addObject:objectID];
- }
- }
-
- [dbContext main].stalenessInterval = stalenessInterval;
- // notfiy object changes
- for (NSManagedObjectID *objectID in notifyObjectIds) {
- DDLogInfo(@"Notify refresh of dirty object: %@", objectID);
- [self notifyObjectRefresh:objectID];
- }
- }
- - (void)notifyObjectRefresh:(NSManagedObjectID *)objectID {
- NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys:
- objectID, kKeyObjectID,
- nil];
-
- [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationDBRefreshedDirtyObject object:self userInfo:info];
- }
- - (void)addDirtyObject:(NSManagedObject *)object {
- if (object == nil || object.objectID == nil) {
- return;
- }
- NSMutableSet *newObjects;
-
- NSUserDefaults *defaults = [AppGroup userDefaults];
- NSArray *objects = [defaults arrayForKey:THREEMA_DB_DIRTY_OBJECT_KEY];
- if (objects) {
- newObjects = [NSMutableSet setWithArray:objects];
- } else {
- newObjects = [NSMutableSet set];
- }
-
- NSURL *url = object.objectID.URIRepresentation;
- [newObjects addObject:url.absoluteString];
-
- NSArray *newObjectsArray = [newObjects allObjects];
- [defaults setObject:newObjectsArray forKey:THREEMA_DB_DIRTY_OBJECT_KEY];
- [defaults synchronize];
-
- [AppGroup notifyAppGroupSyncNeeded];
- }
- - (void)mergeContentsOfPath:(NSString *)srcDir intoPath:(NSString *)dstDir error:(NSError**)err {
- NSFileManager *fm = [NSFileManager defaultManager];
- NSDirectoryEnumerator *srcDirEnum = [fm enumeratorAtPath:srcDir];
- NSString *subPath;
- while ((subPath = [srcDirEnum nextObject])) {
- NSString *srcPath = [srcDir stringByAppendingPathComponent:subPath];
- NSString *dstPath = [dstDir stringByAppendingPathComponent:subPath];
-
- [fm moveItemAtPath:srcPath toPath:dstPath error:err];
- if (err && *err) {
- NSLog(@"ERROR: %@", *err);
- return;
- }
- }
- }
- - (BOOL)copyOldVersionOfDatabase {
- NSURL *oldVersionUrl = [[DocumentManager applicationDocumentsDirectory] URLByAppendingPathComponent:@"ThreemaDataOldVersion"];
- NSFileManager *fm = [NSFileManager defaultManager];
- if ([fm fileExistsAtPath:oldVersionUrl.path]) {
-
- // delete current DB
- NSURL *databaseUrl = [DatabaseManager storeUrl];
- [DocumentManager removeItemIfExists:databaseUrl];
-
- NSString *shmFile = [THREEMA_DB_FILE stringByAppendingString:@"-shm"];
- [DocumentManager removeItemIfExists:[[DocumentManager databaseDirectory] URLByAppendingPathComponent:shmFile]];
-
- NSString *walFile = [THREEMA_DB_FILE stringByAppendingString:@"-wal"];
- [DocumentManager removeItemIfExists:[[DocumentManager databaseDirectory] URLByAppendingPathComponent:walFile]];
-
- NSURL *externalsUrl = [[DocumentManager databaseDirectory] URLByAppendingPathComponent:THREEMA_DB_EXTERNALS];
- [DocumentManager removeItemIfExists:externalsUrl];
-
- // move older version of DB
- NSURL *sourceDatabaseUrl = [[[DocumentManager applicationDocumentsDirectory] URLByAppendingPathComponent:@"ThreemaDataOldVersion"] URLByAppendingPathComponent:THREEMA_DB_FILE];
- [DocumentManager moveItemIfExists:sourceDatabaseUrl destination:databaseUrl];
-
- NSURL *sourceExternalsUrl = [[[DocumentManager applicationDocumentsDirectory] URLByAppendingPathComponent:@"ThreemaDataOldVersion"] URLByAppendingPathComponent:THREEMA_DB_EXTERNALS];
- [DocumentManager moveItemIfExists:sourceExternalsUrl destination:externalsUrl];
-
- // delete older version files
- [fm removeItemAtURL:oldVersionUrl error:nil];
-
- NSURL *pendingMessages = [[DocumentManager documentsDirectory] URLByAppendingPathComponent:@"PendingMessages"];
- [DocumentManager removeItemIfExists:pendingMessages];
-
- return YES;
- }
-
- return NO;
- }
- @end
- @implementation NSURL (RWIsDirectory)
- - (BOOL)RWIsDirectory
- {
- NSNumber * isDir;
- [self getResourceValue:&isDir forKey:NSURLIsDirectoryKey error:NULL];
- return [isDir boolValue];
- }
- @end
|