302 lines
9.9 KiB
Objective-C
302 lines
9.9 KiB
Objective-C
/*
|
|
Copyright 2024 New Vector Ltd.
|
|
Copyright 2015 OpenMarket Ltd
|
|
|
|
SPDX-License-Identifier: AGPL-3.0-only
|
|
Please see LICENSE in the repository root for full details.
|
|
*/
|
|
|
|
#import "MXKRoomDataSourceManager.h"
|
|
|
|
@interface MXKRoomDataSourceManager()
|
|
{
|
|
MXSession *mxSession;
|
|
|
|
/**
|
|
The list of running roomDataSources.
|
|
Each key is a room ID. Each value, the MXKRoomDataSource instance.
|
|
*/
|
|
NSMutableDictionary *roomDataSources;
|
|
|
|
/**
|
|
The list of rooms with a "late decryption" event. Causing bubbles issues
|
|
Each element is a room ID.
|
|
*/
|
|
NSMutableSet *roomDataSourcesToDestroy;
|
|
|
|
/**
|
|
Observe UIApplicationDidReceiveMemoryWarningNotification to dispose of any resources that can be recreated.
|
|
*/
|
|
id UIApplicationDidReceiveMemoryWarningNotificationObserver;
|
|
|
|
/**
|
|
Observe kMXEventDidDecryptNotification to get late decrypted events.
|
|
*/
|
|
id mxEventDidDecryptNotificationObserver;
|
|
}
|
|
|
|
@end
|
|
|
|
static NSMutableDictionary *_roomDataSourceManagers = nil;
|
|
static Class _roomDataSourceClass;
|
|
|
|
@implementation MXKRoomDataSourceManager
|
|
|
|
+ (MXKRoomDataSourceManager *)sharedManagerForMatrixSession:(MXSession *)mxSession
|
|
{
|
|
// Manage a pool of managers: one per Matrix session
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
_roomDataSourceManagers = [NSMutableDictionary dictionary];
|
|
});
|
|
|
|
MXKRoomDataSourceManager *roomDataSourceManager;
|
|
|
|
// Compute an id for this mxSession object: its pointer address as a string
|
|
NSString *mxSessionId = [NSString stringWithFormat:@"%p", mxSession];
|
|
|
|
@synchronized(_roomDataSourceManagers)
|
|
{
|
|
if (_roomDataSourceClass == nil)
|
|
{
|
|
// Set default class
|
|
_roomDataSourceClass = MXKRoomDataSource.class;
|
|
}
|
|
// If not available yet, create the `MXKRoomDataSourceManager` for this Matrix session
|
|
roomDataSourceManager = _roomDataSourceManagers[mxSessionId];
|
|
if (!roomDataSourceManager)
|
|
{
|
|
roomDataSourceManager = [[MXKRoomDataSourceManager alloc]initWithMatrixSession:mxSession];
|
|
_roomDataSourceManagers[mxSessionId] = roomDataSourceManager;
|
|
}
|
|
}
|
|
|
|
return roomDataSourceManager;
|
|
}
|
|
|
|
+ (void)removeSharedManagerForMatrixSession:(MXSession*)mxSession
|
|
{
|
|
// Compute the id for this mxSession object: its pointer address as a string
|
|
NSString *mxSessionId = [NSString stringWithFormat:@"%p", mxSession];
|
|
|
|
@synchronized(_roomDataSourceManagers)
|
|
{
|
|
MXKRoomDataSourceManager *roomDataSourceManager = [_roomDataSourceManagers objectForKey:mxSessionId];
|
|
if (roomDataSourceManager)
|
|
{
|
|
[roomDataSourceManager destroy];
|
|
[_roomDataSourceManagers removeObjectForKey:mxSessionId];
|
|
}
|
|
}
|
|
}
|
|
|
|
+ (void)registerRoomDataSourceClass:(Class)roomDataSourceClass
|
|
{
|
|
// Sanity check: accept only MXKRoomDataSource classes or sub-classes
|
|
NSParameterAssert([roomDataSourceClass isSubclassOfClass:MXKRoomDataSource.class]);
|
|
|
|
@synchronized(_roomDataSourceManagers)
|
|
{
|
|
if (roomDataSourceClass !=_roomDataSourceClass)
|
|
{
|
|
_roomDataSourceClass = roomDataSourceClass;
|
|
|
|
NSArray *mxSessionIds = _roomDataSourceManagers.allKeys;
|
|
for (NSString *mxSessionId in mxSessionIds)
|
|
{
|
|
MXKRoomDataSourceManager *roomDataSourceManager = [_roomDataSourceManagers objectForKey:mxSessionId];
|
|
if (roomDataSourceManager)
|
|
{
|
|
[roomDataSourceManager destroy];
|
|
[_roomDataSourceManagers removeObjectForKey:mxSessionId];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
- (instancetype)initWithMatrixSession:(MXSession *)matrixSession
|
|
{
|
|
self = [super init];
|
|
if (self)
|
|
{
|
|
mxSession = matrixSession;
|
|
roomDataSources = [NSMutableDictionary dictionary];
|
|
roomDataSourcesToDestroy = [NSMutableSet set];
|
|
_releasePolicy = MXKRoomDataSourceManagerReleasePolicyNeverRelease;
|
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didMXSessionDidLeaveRoom:) name:kMXSessionDidLeaveRoomNotification object:nil];
|
|
|
|
// Observe UIApplicationDidReceiveMemoryWarningNotification
|
|
UIApplicationDidReceiveMemoryWarningNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:UIApplicationDidReceiveMemoryWarningNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {
|
|
|
|
MXLogDebug(@"[MXKRoomDataSourceManager] %@: Received memory warning.", self);
|
|
|
|
// Reload all data sources (except the current used ones) to reduce memory usage.
|
|
for (MXKRoomDataSource *roomDataSource in self->roomDataSources.allValues)
|
|
{
|
|
if (!roomDataSource.delegate)
|
|
{
|
|
[roomDataSource reload];
|
|
}
|
|
}
|
|
|
|
}];
|
|
|
|
// Observe late decrypted events, and store rooms ids in memory
|
|
mxEventDidDecryptNotificationObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXEventDidDecryptNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {
|
|
MXEvent *decryptedEvent = notif.object;
|
|
[self->roomDataSourcesToDestroy addObject:decryptedEvent.roomId];
|
|
}];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)dealloc
|
|
{
|
|
[[NSNotificationCenter defaultCenter] removeObserver:self name:kMXSessionDidLeaveRoomNotification object:nil];
|
|
}
|
|
|
|
- (void)destroy
|
|
{
|
|
[self reset];
|
|
|
|
if (UIApplicationDidReceiveMemoryWarningNotificationObserver)
|
|
{
|
|
[[NSNotificationCenter defaultCenter] removeObserver:UIApplicationDidReceiveMemoryWarningNotificationObserver];
|
|
UIApplicationDidReceiveMemoryWarningNotificationObserver = nil;
|
|
}
|
|
if (mxEventDidDecryptNotificationObserver)
|
|
{
|
|
[[NSNotificationCenter defaultCenter] removeObserver:mxEventDidDecryptNotificationObserver];
|
|
mxEventDidDecryptNotificationObserver = nil;
|
|
}
|
|
}
|
|
|
|
#pragma mark
|
|
|
|
- (BOOL)isServerSyncInProgress
|
|
{
|
|
// Check first the matrix session state
|
|
if (mxSession.state == MXSessionStateSyncInProgress)
|
|
{
|
|
return YES;
|
|
}
|
|
|
|
// Check all data sources (events process is asynchronous, server sync may not be complete in data source).
|
|
for (MXKRoomDataSource *roomDataSource in roomDataSources.allValues)
|
|
{
|
|
if (roomDataSource.serverSyncEventCount)
|
|
{
|
|
return YES;
|
|
}
|
|
}
|
|
|
|
return NO;
|
|
}
|
|
|
|
#pragma mark
|
|
|
|
- (void)reset
|
|
{
|
|
NSArray *roomIds = roomDataSources.allKeys;
|
|
for (NSString *roomId in roomIds)
|
|
{
|
|
[self closeRoomDataSourceWithRoomId:roomId forceClose:YES];
|
|
}
|
|
}
|
|
|
|
- (BOOL)hasRoomDataSourceForRoom:(NSString *)roomId
|
|
{
|
|
return roomDataSources[roomId] != nil;
|
|
}
|
|
|
|
- (void)roomDataSourceForRoom:(NSString *)roomId create:(BOOL)create onComplete:(void (^)(MXKRoomDataSource *roomDataSource))onComplete
|
|
{
|
|
NSParameterAssert(roomId);
|
|
|
|
// If not available yet, create the room data source
|
|
MXKRoomDataSource *roomDataSource = roomDataSources[roomId];
|
|
|
|
// check if the room's dataSource has events with late decryption issues and destroys it
|
|
BOOL roomDataSourceToBeDestroyed = [roomDataSourcesToDestroy containsObject:roomId];
|
|
|
|
if (roomDataSource && roomDataSourceToBeDestroyed && create) {
|
|
[roomDataSource destroy];
|
|
roomDataSources[roomId] = nil;
|
|
roomDataSource = nil;
|
|
}
|
|
|
|
if (!roomDataSource && create && roomId)
|
|
{
|
|
[roomDataSourcesToDestroy removeObject:roomId];
|
|
[_roomDataSourceClass loadRoomDataSourceWithRoomId:roomId threadId:nil andMatrixSession:mxSession onComplete:^(id roomDataSource) {
|
|
[self addRoomDataSource:roomDataSource];
|
|
onComplete(roomDataSource);
|
|
}];
|
|
}
|
|
else
|
|
{
|
|
onComplete(roomDataSource);
|
|
}
|
|
}
|
|
|
|
- (void)addRoomDataSource:(MXKRoomDataSource *)roomDataSource
|
|
{
|
|
roomDataSources[roomDataSource.roomId] = roomDataSource;
|
|
}
|
|
|
|
- (void)closeRoomDataSourceWithRoomId:(NSString*)roomId forceClose:(BOOL)forceRelease;
|
|
{
|
|
// Check first whether this roomDataSource is well handled by this manager
|
|
if (!roomId || !roomDataSources[roomId])
|
|
{
|
|
MXLogDebug(@"[MXKRoomDataSourceManager] Failed to close an unknown room id: %@", roomId);
|
|
return;
|
|
}
|
|
|
|
MXKRoomDataSource *roomDataSource = roomDataSources[roomId];
|
|
|
|
// According to the policy, it is interesting to keep the room data source in life: it can keep managing echo messages
|
|
// in background for instance
|
|
MXKRoomDataSourceManagerReleasePolicy releasePolicy = _releasePolicy;
|
|
if (forceRelease)
|
|
{
|
|
// Act as ReleaseOnClose policy
|
|
releasePolicy = MXKRoomDataSourceManagerReleasePolicyReleaseOnClose;
|
|
}
|
|
|
|
switch (releasePolicy)
|
|
{
|
|
case MXKRoomDataSourceManagerReleasePolicyReleaseOnClose:
|
|
|
|
// Destroy and forget the instance
|
|
[roomDataSource destroy];
|
|
[roomDataSources removeObjectForKey:roomDataSource.roomId];
|
|
break;
|
|
|
|
case MXKRoomDataSourceManagerReleasePolicyNeverRelease:
|
|
|
|
// The close here consists in no more sending actions to the current view controller, the room data source delegate
|
|
roomDataSource.delegate = nil;
|
|
|
|
// Keep the instance for life (reduce memory usage by flushing room data if the number of bubbles is over 30).
|
|
[roomDataSource limitMemoryUsage:roomDataSource.maxBackgroundCachedBubblesCount];
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
- (void)didMXSessionDidLeaveRoom:(NSNotification *)notif
|
|
{
|
|
if (mxSession == notif.object)
|
|
{
|
|
// The room is no more available, remove it from the manager
|
|
[self closeRoomDataSourceWithRoomId:notif.userInfo[kMXSessionNotificationRoomIdKey] forceClose:YES];
|
|
}
|
|
}
|
|
|
|
@end
|