session-ios/SignalServiceKit/src/Storage/OWSPrimaryStorage.m

463 lines
16 KiB
Mathematica
Raw Normal View History

//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
//
2015-12-07 03:31:43 +01:00
#import "OWSPrimaryStorage.h"
#import "AppContext.h"
#import "OWSAnalytics.h"
2017-11-16 16:12:47 +01:00
#import "OWSBatchMessageProcessor.h"
#import "OWSDisappearingMessagesFinder.h"
#import "OWSFailedAttachmentDownloadsJob.h"
#import "OWSFailedMessagesJob.h"
2017-11-16 16:12:47 +01:00
#import "OWSFileSystem.h"
2017-02-16 00:32:27 +01:00
#import "OWSIncomingMessageFinder.h"
#import "OWSIncompleteCallsJob.h"
#import "OWSMediaGalleryFinder.h"
2017-11-16 16:12:47 +01:00
#import "OWSMessageReceiver.h"
2017-12-19 05:00:11 +01:00
#import "OWSStorage+Subclass.h"
2018-09-17 15:27:58 +02:00
#import "SSKEnvironment.h"
2015-12-07 03:31:43 +01:00
#import "TSDatabaseSecondaryIndexes.h"
#import "TSDatabaseView.h"
2020-06-05 05:43:06 +02:00
#import <SessionServiceKit/SessionServiceKit-Swift.h>
2015-12-07 03:31:43 +01:00
NS_ASSUME_NONNULL_BEGIN
NSString *const OWSUIDatabaseConnectionWillUpdateNotification = @"OWSUIDatabaseConnectionWillUpdateNotification";
NSString *const OWSUIDatabaseConnectionDidUpdateNotification = @"OWSUIDatabaseConnectionDidUpdateNotification";
NSString *const OWSUIDatabaseConnectionWillUpdateExternallyNotification = @"OWSUIDatabaseConnectionWillUpdateExternallyNotification";
NSString *const OWSUIDatabaseConnectionDidUpdateExternallyNotification = @"OWSUIDatabaseConnectionDidUpdateExternallyNotification";
NSString *const OWSUIDatabaseConnectionNotificationsKey = @"OWSUIDatabaseConnectionNotificationsKey";
void VerifyRegistrationsForPrimaryStorage(OWSStorage *storage)
{
OWSCAssertDebug(storage);
[[storage newDatabaseConnection] asyncReadWithBlock:^(YapDatabaseReadTransaction *transaction) {
2018-04-25 15:30:07 +02:00
for (NSString *extensionName in storage.registeredExtensionNames) {
OWSLogVerbose(@"Verifying database extension: %@", extensionName);
YapDatabaseViewTransaction *_Nullable viewTransaction = [transaction ext:extensionName];
if (!viewTransaction) {
2018-08-27 16:29:51 +02:00
OWSCFailDebug(@"VerifyRegistrationsForPrimaryStorage missing database extension: %@", extensionName);
[OWSStorage incrementVersionOfDatabaseExtension:extensionName];
}
}
}];
}
#pragma mark -
2018-03-05 16:39:08 +01:00
@interface OWSPrimaryStorage ()
2017-12-19 03:34:22 +01:00
2017-12-19 04:56:02 +01:00
@property (atomic) BOOL areAsyncRegistrationsComplete;
@property (atomic) BOOL areSyncRegistrationsComplete;
2019-02-20 18:58:21 +01:00
@property (nonatomic, readonly) YapDatabaseConnectionPool *dbReadPool;
2017-12-19 04:56:02 +01:00
2017-12-19 03:34:22 +01:00
@end
#pragma mark -
@implementation OWSPrimaryStorage
2015-12-07 03:31:43 +01:00
@synthesize uiDatabaseConnection = _uiDatabaseConnection;
+ (instancetype)sharedManager
{
2018-09-17 15:27:58 +02:00
OWSAssertDebug(SSKEnvironment.shared.primaryStorage);
return SSKEnvironment.shared.primaryStorage;
2015-12-07 03:31:43 +01:00
}
2017-12-19 03:34:22 +01:00
- (instancetype)initStorage
{
self = [super initStorage];
if (self) {
2018-03-06 16:10:22 +01:00
[self loadDatabase];
2019-02-20 18:58:21 +01:00
_dbReadPool = [[YapDatabaseConnectionPool alloc] initWithDatabase:self.database];
_dbReadWriteConnection = [self newDatabaseConnection];
_uiDatabaseConnection = [self newDatabaseConnection];
2019-02-20 18:58:21 +01:00
// Increase object cache limit. Default is 250.
_uiDatabaseConnection.objectCacheLimit = 500;
[_uiDatabaseConnection beginLongLivedReadTransaction];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(yapDatabaseModified:)
name:YapDatabaseModifiedNotification
object:self.dbNotificationObject];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(yapDatabaseModifiedExternally:)
name:YapDatabaseModifiedExternallyNotification
object:nil];
2017-12-19 03:34:22 +01:00
OWSSingletonAssert();
}
return self;
}
2018-09-17 15:27:58 +02:00
- (void)dealloc
{
// Surface memory leaks by logging the deallocation of this class.
OWSLogVerbose(@"Dealloc: %@", self.class);
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)yapDatabaseModifiedExternally:(NSNotification *)notification
{
// Notify observers we're about to update the database connection
[[NSNotificationCenter defaultCenter] postNotificationName:OWSUIDatabaseConnectionWillUpdateExternallyNotification object:self.dbNotificationObject];
// Move uiDatabaseConnection to the latest commit.
// Do so atomically, and fetch all the notifications for each commit we jump.
NSArray *notifications = [self.uiDatabaseConnection beginLongLivedReadTransaction];
// Notify observers that the uiDatabaseConnection was updated
NSDictionary *userInfo = @{ OWSUIDatabaseConnectionNotificationsKey: notifications };
[[NSNotificationCenter defaultCenter] postNotificationName:OWSUIDatabaseConnectionDidUpdateExternallyNotification
object:self.dbNotificationObject
userInfo:userInfo];
}
- (void)yapDatabaseModified:(NSNotification *)notification
{
OWSAssertIsOnMainThread();
OWSLogVerbose(@"");
[self updateUIDatabaseConnectionToLatest];
}
- (void)updateUIDatabaseConnectionToLatest
{
OWSAssertIsOnMainThread();
// Notify observers we're about to update the database connection
[[NSNotificationCenter defaultCenter] postNotificationName:OWSUIDatabaseConnectionWillUpdateNotification object:self.dbNotificationObject];
// Move uiDatabaseConnection to the latest commit.
// Do so atomically, and fetch all the notifications for each commit we jump.
NSArray *notifications = [self.uiDatabaseConnection beginLongLivedReadTransaction];
// Notify observers that the uiDatabaseConnection was updated
NSDictionary *userInfo = @{ OWSUIDatabaseConnectionNotificationsKey: notifications };
[[NSNotificationCenter defaultCenter] postNotificationName:OWSUIDatabaseConnectionDidUpdateNotification
object:self.dbNotificationObject
userInfo:userInfo];
}
- (YapDatabaseConnection *)uiDatabaseConnection
{
OWSAssertIsOnMainThread();
return _uiDatabaseConnection;
}
2017-12-19 03:34:22 +01:00
- (void)resetStorage
{
2019-02-20 18:58:21 +01:00
_dbReadPool = nil;
_uiDatabaseConnection = nil;
2017-12-19 03:34:22 +01:00
_dbReadWriteConnection = nil;
[super resetStorage];
}
2017-12-19 04:56:02 +01:00
- (void)runSyncRegistrations
{
// Synchronously register extensions which are essential for views.
[TSDatabaseView registerCrossProcessNotifier:self];
2017-08-23 15:55:53 +02:00
// See comments on OWSDatabaseConnection.
//
// In the absence of finding documentation that can shed light on the issue we've been
// seeing, this issue only seems to affect sync and not async registrations. We've always
// been opening write transactions before the async registrations complete without negative
// consequences.
OWSAssertDebug(!self.areSyncRegistrationsComplete);
2017-12-19 04:56:02 +01:00
self.areSyncRegistrationsComplete = YES;
}
2017-12-19 04:56:02 +01:00
- (void)runAsyncRegistrationsWithCompletion:(void (^_Nonnull)(void))completion
{
OWSAssertDebug(completion);
OWSAssertDebug(self.database);
OWSLogVerbose(@"async registrations enqueuing.");
// Asynchronously register other extensions.
//
// All sync registrations must be done before all async registrations,
// or the sync registrations will block on the async registrations.
[TSDatabaseView asyncRegisterLegacyThreadInteractionsDatabaseView:self];
[TSDatabaseView asyncRegisterThreadInteractionsDatabaseView:self];
[TSDatabaseView asyncRegisterThreadDatabaseView:self];
[TSDatabaseView asyncRegisterUnreadDatabaseView:self];
[self asyncRegisterExtension:[TSDatabaseSecondaryIndexes registerTimeStampIndex]
withName:[TSDatabaseSecondaryIndexes registerTimeStampIndexExtensionName]];
[OWSMessageReceiver asyncRegisterDatabaseExtension:self];
[OWSBatchMessageProcessor asyncRegisterDatabaseExtension:self];
[TSDatabaseView asyncRegisterUnseenDatabaseView:self];
[TSDatabaseView asyncRegisterThreadOutgoingMessagesDatabaseView:self];
[TSDatabaseView asyncRegisterThreadSpecialMessagesDatabaseView:self];
[FullTextSearchFinder asyncRegisterDatabaseExtensionWithStorage:self];
[OWSIncomingMessageFinder asyncRegisterExtensionWithPrimaryStorage:self];
[TSDatabaseView asyncRegisterSecondaryDevicesDatabaseView:self];
[OWSDisappearingMessagesFinder asyncRegisterDatabaseExtensions:self];
[OWSFailedMessagesJob asyncRegisterDatabaseExtensionsWithPrimaryStorage:self];
[OWSIncompleteCallsJob asyncRegisterDatabaseExtensionsWithPrimaryStorage:self];
[OWSFailedAttachmentDownloadsJob asyncRegisterDatabaseExtensionsWithPrimaryStorage:self];
[OWSMediaGalleryFinder asyncRegisterDatabaseExtensionsWithPrimaryStorage:self];
[TSDatabaseView asyncRegisterLazyRestoreAttachmentsDatabaseView:self];
[SSKJobRecordFinder asyncRegisterDatabaseExtensionObjCWithStorage:self];
2019-09-26 06:43:37 +02:00
// Loki
2020-04-20 08:53:40 +02:00
[LKExpiringFriendRequestFinder asyncRegisterDatabaseExtensions:self];
2019-09-26 06:43:37 +02:00
[LKDeviceLinkIndex asyncRegisterDatabaseExtensions:self];
[self.database
flushExtensionRequestsWithCompletionQueue:dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
completionBlock:^{
OWSAssertDebug(!self.areAsyncRegistrationsComplete);
OWSLogVerbose(@"async registrations complete.");
self.areAsyncRegistrationsComplete = YES;
completion();
[self verifyDatabaseViews];
}];
}
- (void)verifyDatabaseViews
{
VerifyRegistrationsForPrimaryStorage(self);
}
+ (void)protectFiles
2017-11-28 19:46:26 +01:00
{
OWSLogInfo(@"Database file size: %@", [OWSFileSystem fileSizeOfPath:self.sharedDataDatabaseFilePath]);
OWSLogInfo(@"\t SHM file size: %@", [OWSFileSystem fileSizeOfPath:self.sharedDataDatabaseFilePath_SHM]);
OWSLogInfo(@"\t WAL file size: %@", [OWSFileSystem fileSizeOfPath:self.sharedDataDatabaseFilePath_WAL]);
2018-01-29 16:11:19 +01:00
// Protect the entire new database directory.
[OWSFileSystem protectFileOrFolderAtPath:self.sharedDataDatabaseDirPath];
2015-12-07 03:31:43 +01:00
}
2017-11-30 16:02:04 +01:00
+ (NSString *)legacyDatabaseDirPath
{
return [OWSFileSystem appDocumentDirectoryPath];
2015-12-07 03:31:43 +01:00
}
2017-11-30 16:02:04 +01:00
+ (NSString *)sharedDataDatabaseDirPath
{
NSString *databaseDirPath = [[OWSFileSystem appSharedDataDirectoryPath] stringByAppendingPathComponent:@"database"];
2015-12-07 03:31:43 +01:00
if (![OWSFileSystem ensureDirectoryExists:databaseDirPath]) {
OWSFail(@"Could not create new database directory");
}
return databaseDirPath;
}
2017-11-28 19:46:26 +01:00
+ (NSString *)databaseFilename
{
return @"Signal.sqlite";
}
2017-11-28 19:46:26 +01:00
+ (NSString *)databaseFilename_SHM
{
return [self.databaseFilename stringByAppendingString:@"-shm"];
}
2017-11-28 19:46:26 +01:00
+ (NSString *)databaseFilename_WAL
{
return [self.databaseFilename stringByAppendingString:@"-wal"];
}
2017-11-30 16:02:04 +01:00
+ (NSString *)legacyDatabaseFilePath
{
2017-11-30 16:02:04 +01:00
return [self.legacyDatabaseDirPath stringByAppendingPathComponent:self.databaseFilename];
}
2015-12-07 03:31:43 +01:00
2017-11-30 16:02:04 +01:00
+ (NSString *)legacyDatabaseFilePath_SHM
{
2017-11-30 16:02:04 +01:00
return [self.legacyDatabaseDirPath stringByAppendingPathComponent:self.databaseFilename_SHM];
}
2017-11-30 16:02:04 +01:00
+ (NSString *)legacyDatabaseFilePath_WAL
{
2017-11-30 16:02:04 +01:00
return [self.legacyDatabaseDirPath stringByAppendingPathComponent:self.databaseFilename_WAL];
}
2017-11-30 16:02:04 +01:00
+ (NSString *)sharedDataDatabaseFilePath
{
2017-11-30 16:02:04 +01:00
return [self.sharedDataDatabaseDirPath stringByAppendingPathComponent:self.databaseFilename];
}
2017-11-30 16:02:04 +01:00
+ (NSString *)sharedDataDatabaseFilePath_SHM
{
2017-11-30 16:02:04 +01:00
return [self.sharedDataDatabaseDirPath stringByAppendingPathComponent:self.databaseFilename_SHM];
}
2015-12-07 03:31:43 +01:00
2017-11-30 16:02:04 +01:00
+ (NSString *)sharedDataDatabaseFilePath_WAL
{
2017-11-30 16:02:04 +01:00
return [self.sharedDataDatabaseDirPath stringByAppendingPathComponent:self.databaseFilename_WAL];
}
2015-12-07 03:31:43 +01:00
+ (nullable NSError *)migrateToSharedData
{
OWSLogInfo(@"");
// Given how sensitive this migration is, we verbosely
// log the contents of all involved paths before and after.
NSArray<NSString *> *paths = @[
self.legacyDatabaseFilePath,
self.legacyDatabaseFilePath_SHM,
self.legacyDatabaseFilePath_WAL,
self.sharedDataDatabaseFilePath,
self.sharedDataDatabaseFilePath_SHM,
self.sharedDataDatabaseFilePath_WAL,
];
NSFileManager *fileManager = [NSFileManager defaultManager];
for (NSString *path in paths) {
if ([fileManager fileExistsAtPath:path]) {
OWSLogInfo(@"before migrateToSharedData: %@, %@", path, [OWSFileSystem fileSizeOfPath:path]);
}
}
// We protect the db files here, which is somewhat redundant with what will happen in
// `moveAppFilePath:` which also ensures file protection.
// However that method dispatches async, since it can take a while with large attachment directories.
//
// Since we only have three files here it'll be quick to do it sync, and we want to make
// sure it happens as part of the migration.
//
// FileProtection attributes move with the file, so we do it on the legacy files before moving
// them.
[OWSFileSystem protectFileOrFolderAtPath:self.legacyDatabaseFilePath];
[OWSFileSystem protectFileOrFolderAtPath:self.legacyDatabaseFilePath_SHM];
[OWSFileSystem protectFileOrFolderAtPath:self.legacyDatabaseFilePath_WAL];
NSError *_Nullable error = nil;
if ([fileManager fileExistsAtPath:self.legacyDatabaseFilePath] &&
[fileManager fileExistsAtPath:self.sharedDataDatabaseFilePath]) {
// In the case that we have a "database conflict" (i.e. database files
// in the src and dst locations), ensure database integrity by renaming
// all of the dst database files.
for (NSString *filePath in @[
self.sharedDataDatabaseFilePath,
self.sharedDataDatabaseFilePath_SHM,
self.sharedDataDatabaseFilePath_WAL,
]) {
error = [OWSFileSystem renameFilePathUsingRandomExtension:filePath];
if (error) {
return error;
}
}
}
error =
[OWSFileSystem moveAppFilePath:self.legacyDatabaseFilePath sharedDataFilePath:self.sharedDataDatabaseFilePath];
if (error) {
return error;
}
error = [OWSFileSystem moveAppFilePath:self.legacyDatabaseFilePath_SHM
sharedDataFilePath:self.sharedDataDatabaseFilePath_SHM];
if (error) {
return error;
}
error = [OWSFileSystem moveAppFilePath:self.legacyDatabaseFilePath_WAL
sharedDataFilePath:self.sharedDataDatabaseFilePath_WAL];
if (error) {
return error;
}
for (NSString *path in paths) {
if ([fileManager fileExistsAtPath:path]) {
OWSLogInfo(@"after migrateToSharedData: %@, %@", path, [OWSFileSystem fileSizeOfPath:path]);
}
}
return nil;
2017-11-28 19:46:26 +01:00
}
2017-12-19 18:02:58 +01:00
+ (NSString *)databaseFilePath
2017-11-28 19:46:26 +01:00
{
OWSLogVerbose(@"databasePath: %@", OWSPrimaryStorage.sharedDataDatabaseFilePath);
2015-12-07 03:31:43 +01:00
2017-12-19 18:02:58 +01:00
return self.sharedDataDatabaseFilePath;
}
2018-03-12 20:10:37 +01:00
+ (NSString *)databaseFilePath_SHM
{
return self.sharedDataDatabaseFilePath_SHM;
}
+ (NSString *)databaseFilePath_WAL
{
return self.sharedDataDatabaseFilePath_WAL;
}
2017-12-19 18:02:58 +01:00
- (NSString *)databaseFilePath
{
return OWSPrimaryStorage.databaseFilePath;
2015-12-07 03:31:43 +01:00
}
2018-03-16 12:48:21 +01:00
- (NSString *)databaseFilePath_SHM
{
return OWSPrimaryStorage.databaseFilePath_SHM;
}
- (NSString *)databaseFilePath_WAL
{
return OWSPrimaryStorage.databaseFilePath_WAL;
}
2018-03-12 20:10:37 +01:00
- (NSString *)databaseFilename_SHM
{
return OWSPrimaryStorage.databaseFilename_SHM;
}
- (NSString *)databaseFilename_WAL
{
return OWSPrimaryStorage.databaseFilename_WAL;
}
+ (YapDatabaseConnection *)dbReadConnection
{
return OWSPrimaryStorage.sharedManager.dbReadConnection;
2015-12-07 03:31:43 +01:00
}
2019-02-20 18:58:21 +01:00
- (YapDatabaseConnection *)dbReadConnection
{
return self.dbReadPool.connection;
}
+ (YapDatabaseConnection *)dbReadWriteConnection
{
return OWSPrimaryStorage.sharedManager.dbReadWriteConnection;
2017-02-10 19:20:11 +01:00
}
#pragma mark - Misc.
- (void)touchDbAsync
{
OWSLogInfo(@"");
// There appears to be a bug in YapDatabase that sometimes delays modifications
// made in another process (e.g. the SAE) from showing up in other processes.
// There's a simple workaround: a trivial write to the database flushes changes
// made from other processes.
[LKStorage writeWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
[transaction setObject:[NSUUID UUID].UUIDString forKey:@"conversation_view_noop_mod" inCollection:@"temp"];
}];
}
2015-12-07 03:31:43 +01:00
@end
NS_ASSUME_NONNULL_END