155 lines
5.5 KiB
Mathematica
155 lines
5.5 KiB
Mathematica
|
//
|
||
|
// MLNotificationQueue.m
|
||
|
// monalxmpp
|
||
|
//
|
||
|
// Created by Thilo Molitor on 03.04.21.
|
||
|
// Copyright © 2021 Monal.im. All rights reserved.
|
||
|
//
|
||
|
|
||
|
#import "MLNotificationQueue.h"
|
||
|
|
||
|
@interface MLNotificationQueue()
|
||
|
{
|
||
|
NSString* _queueName;
|
||
|
NSMutableArray* _entries;
|
||
|
id _lowerQueue; //use id because this could be an MLNotificationQueue *or* [NSNotificationCenter defaultCenter]
|
||
|
}
|
||
|
+(NSMutableArray*) getThreadLocalNotificationQueueStack;
|
||
|
@end
|
||
|
|
||
|
@implementation MLNotificationQueue
|
||
|
|
||
|
//this is a contextmanager (like the ones found in python)
|
||
|
+(void) queueNotificationsInBlock:(monal_void_block_t) block onQueue:(NSString*) queueName
|
||
|
{
|
||
|
NSMutableArray* stack = [self getThreadLocalNotificationQueueStack];
|
||
|
for(MLNotificationQueue* queue in stack)
|
||
|
if([queue.name isEqualToString:queueName])
|
||
|
@throw [NSException exceptionWithName:@"NotificationQueueException" reason:[NSString stringWithFormat:@"Tried to instanciate queue twice: %@", queueName] userInfo:@{
|
||
|
@"stack": stack,
|
||
|
@"alreadyExistingQueue": queue,
|
||
|
}];
|
||
|
//create new notification queue and put it onto our stack of queues
|
||
|
MLNotificationQueue* queue = [[self alloc] initWithName:queueName];
|
||
|
[stack addObject:queue];
|
||
|
//call the context our contextmanager manages (a monal_void_block_t block)
|
||
|
block();
|
||
|
//remove own queue from stack again
|
||
|
[stack removeLastObject];
|
||
|
//flush the queue to the next queue in our stack (or send them to the notification center if no queue is left on the stack)
|
||
|
//don't use the flush deallocate because we want our flush to be "inline" thread-wise
|
||
|
[queue flush];
|
||
|
//this will deallocate our queue (flushing was already done before)
|
||
|
queue = nil;
|
||
|
}
|
||
|
|
||
|
+(id) currentQueue
|
||
|
{
|
||
|
NSMutableArray* stack = [self getThreadLocalNotificationQueueStack];
|
||
|
if(![stack count])
|
||
|
return [NSNotificationCenter defaultCenter];
|
||
|
return [stack lastObject];
|
||
|
}
|
||
|
|
||
|
//this is compatible to [NSNotificationCenter defaultCenter]
|
||
|
-(void) postNotificationName:(NSNotificationName) notificationName object:(id _Nullable) notificationObject userInfo:(id _Nullable) notificationUserInfo
|
||
|
{
|
||
|
DDLogDebug(@"Queueing notification: %@, object = %@, userInfo = %@", notificationName, notificationObject, notificationUserInfo);
|
||
|
//create queue entry (handle nil arguments)
|
||
|
NSMutableDictionary* entry = [NSMutableDictionary new];
|
||
|
entry[@"name"] = notificationName;
|
||
|
if(notificationObject != nil)
|
||
|
entry[@"obj"] = notificationObject;
|
||
|
if(notificationUserInfo != nil)
|
||
|
entry[@"userInfo"] = notificationUserInfo;
|
||
|
|
||
|
//add entry to our queue
|
||
|
@synchronized(_entries) {
|
||
|
[_entries addObject:entry];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//this is compatible to [NSNotificationCenter defaultCenter]
|
||
|
-(void) postNotificationName:(NSNotificationName) notificationName object:(id _Nullable) notificationObject
|
||
|
{
|
||
|
[self postNotificationName:notificationName object:notificationObject userInfo:nil];
|
||
|
}
|
||
|
|
||
|
//this is compatible to [NSNotificationCenter defaultCenter]
|
||
|
-(void) postNotification:(NSNotification*) notification
|
||
|
{
|
||
|
[self postNotificationName:notification.name object:notification.object userInfo:notification.userInfo];
|
||
|
}
|
||
|
|
||
|
-(NSUInteger) flush
|
||
|
{
|
||
|
DDLogDebug(@"Flushing queue '%@', current stack: %@", [self name], [[[[self class] getThreadLocalNotificationQueueStack] reverseObjectEnumerator] allObjects]);
|
||
|
NSArray* toFlush;
|
||
|
@synchronized(_entries) {
|
||
|
toFlush = _entries;
|
||
|
_entries = [NSMutableArray new];
|
||
|
}
|
||
|
DDLogVerbose(@"Notifications in queue '%@': %@", [self name], toFlush);
|
||
|
for(NSDictionary* entry in toFlush)
|
||
|
[_lowerQueue postNotificationName:entry[@"name"] object:entry[@"obj"] userInfo:entry[@"userInfo"]];
|
||
|
@synchronized(_entries) {
|
||
|
if([_entries count])
|
||
|
@throw [NSException exceptionWithName:@"NotificationQueueException" reason:[NSString stringWithFormat:@"Tried to add more entries to queue while flushing: %@", _queueName] userInfo:nil];
|
||
|
}
|
||
|
DDLogVerbose(@"Done flushing %@ notifications in queue '%@'", @([toFlush count]), [self name]);
|
||
|
return [toFlush count];
|
||
|
}
|
||
|
|
||
|
-(NSUInteger) clear
|
||
|
{
|
||
|
DDLogDebug(@"Clearing queue '%@', current stack: %@", [self name], [[[[self class] getThreadLocalNotificationQueueStack] reverseObjectEnumerator] allObjects]);
|
||
|
NSUInteger retval;
|
||
|
@synchronized(_entries) {
|
||
|
retval = [_entries count];
|
||
|
_entries = [NSMutableArray new];
|
||
|
}
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
-(NSString*) name
|
||
|
{
|
||
|
return _queueName;
|
||
|
}
|
||
|
|
||
|
-(NSString*) description
|
||
|
{
|
||
|
NSMutableArray* queuedNotificationNames = [NSMutableArray new];
|
||
|
@synchronized(_entries) {
|
||
|
for(NSDictionary* entry in _entries)
|
||
|
[queuedNotificationNames addObject:entry[@"name"]];
|
||
|
}
|
||
|
return [NSString stringWithFormat:@"%@: %@", self.name, queuedNotificationNames];
|
||
|
}
|
||
|
|
||
|
+(NSMutableArray*) getThreadLocalNotificationQueueStack
|
||
|
{
|
||
|
NSMutableDictionary* threadData = [[NSThread currentThread] threadDictionary];
|
||
|
//init dictionaries if neccessary
|
||
|
if(!threadData[@"_notificationQueueStack"])
|
||
|
threadData[@"_notificationQueueStack"] = [NSMutableArray new];
|
||
|
return threadData[@"_notificationQueueStack"];
|
||
|
}
|
||
|
|
||
|
-(instancetype) initWithName:(NSString*) queueName
|
||
|
{
|
||
|
self = [super init];
|
||
|
_queueName = queueName;
|
||
|
_entries = [NSMutableArray new];
|
||
|
_lowerQueue = [MLNotificationQueue currentQueue];
|
||
|
return self;
|
||
|
}
|
||
|
|
||
|
-(void) dealloc
|
||
|
{
|
||
|
//there should only be one thread calling dealloc ever (per objc runtime) --> no @synchronized needed
|
||
|
if([_entries count])
|
||
|
[self flush];
|
||
|
}
|
||
|
|
||
|
@end
|