diff --git a/Monal/Classes/ContactEntry.swift b/Monal/Classes/ContactEntry.swift index 55069e7cc3..51a1d1f78f 100644 --- a/Monal/Classes/ContactEntry.swift +++ b/Monal/Classes/ContactEntry.swift @@ -7,11 +7,12 @@ // struct ContactEntry: View { - let contact: ObservableKVOWrapper let selfnotesPrefix: Bool let fallback: String? @ViewBuilder let additionalContent: () -> AdditionalContent + @StateObject var contact: ObservableKVOWrapper + init(contact:ObservableKVOWrapper, selfnotesPrefix: Bool = true, fallback: String? = nil) where AdditionalContent == EmptyView { self.init(contact:contact, selfnotesPrefix:selfnotesPrefix, fallback:fallback, additionalContent:{ EmptyView() }) } @@ -33,7 +34,7 @@ struct ContactEntry: View { } init(contact:ObservableKVOWrapper, selfnotesPrefix: Bool, fallback: String?, @ViewBuilder additionalContent: @escaping () -> AdditionalContent) { - self.contact = contact + _contact = StateObject(wrappedValue: contact) self.selfnotesPrefix = selfnotesPrefix self.fallback = fallback self.additionalContent = additionalContent diff --git a/Monal/Classes/ContactRequestsMenu.swift b/Monal/Classes/ContactRequestsMenu.swift index cb03d545b4..5e4a1b566b 100644 --- a/Monal/Classes/ContactRequestsMenu.swift +++ b/Monal/Classes/ContactRequestsMenu.swift @@ -28,6 +28,17 @@ struct ContactRequestsMenuEntry: View { //see https://www.hackingwithswift.com/forums/swiftui/tap-button-in-hstack-activates-all-button-actions-ios-14-swiftui-2/2952 .buttonStyle(BorderlessButtonStyle()) + Button { + // deny request + MLXMPPManager.sharedInstance().remove(contact) + MLXMPPManager.sharedInstance().block(true, contact:contact) + } label: { + Image(systemName: "xmark.circle") + .accentColor(.red) + } + //see https://www.hackingwithswift.com/forums/swiftui/tap-button-in-hstack-activates-all-button-actions-ios-14-swiftui-2/2952 + .buttonStyle(BorderlessButtonStyle()) + Button { // deny request MLXMPPManager.sharedInstance().remove(contact) diff --git a/Monal/Classes/HelperTools.h b/Monal/Classes/HelperTools.h index c36e1e7890..050be3d85e 100644 --- a/Monal/Classes/HelperTools.h +++ b/Monal/Classes/HelperTools.h @@ -69,6 +69,10 @@ void swizzle(Class c, SEL orig, SEL new); -(id) initWithObj:(id) obj; @end +@interface DDLogMessage(TaggedMessage) +@property (nonatomic) BOOL ml_isDirect; +@end + @interface HelperTools : NSObject @property (class, nonatomic, strong, nullable) DDFileLogger* fileLogger; @@ -78,6 +82,7 @@ void swizzle(Class c, SEL orig, SEL new); +(void) installExceptionHandler; +(int) pendingCrashreportCount; +(void) flushLogsWithTimeout:(double) timeout; ++(BOOL) isAppSuspended; +(void) signalSuspension; +(void) signalResumption; +(void) __attribute__((noreturn)) MLAssertWithText:(NSString*) text andUserData:(id _Nullable) additionalData andFile:(const char* const) file andLine:(int) line andFunc:(const char* const) func; diff --git a/Monal/Classes/HelperTools.m b/Monal/Classes/HelperTools.m index 8d87f658e8..88af31c397 100644 --- a/Monal/Classes/HelperTools.m +++ b/Monal/Classes/HelperTools.m @@ -302,14 +302,45 @@ -(id) initWithObj:(id) obj } @end +@implementation DDLogMessage(TaggedMessage) +@dynamic ml_isDirect; +-(void) setMl_isDirect:(BOOL) value +{ + objc_setAssociatedObject(self, @selector(ml_isDirect), @(value), OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} +-(BOOL) ml_isDirect +{ + return ((NSNumber*)objc_getAssociatedObject(self, @selector(ml_isDirect))).boolValue; +} +@end + @implementation DDLog (AllowQueueFreeze) -(void) swizzled_queueLogMessage:(DDLogMessage*) logMessage asynchronously:(BOOL) asyncFlag { - //don't do sync logging for any message (usually ERROR), while the global logging queue is suspended - @synchronized(_suspensionHandling_lock) { - return [self swizzled_queueLogMessage:logMessage asynchronously:_suspensionHandling_isSuspended ? YES : asyncFlag]; + //make sure this method remains performant even when checking for udp logging presence + static BOOL udpLoggerEnabled = NO; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + udpLoggerEnabled = [[HelperTools defaultsDB] boolForKey:@"udpLoggerEnabled"]; + }); + + //use udp logger to log all messages, even if the loggging queue is in suspended state + //this hopefully enables us to catch strange bugs sometimes hanging and then watchdog-killing the app when resuming from resumption + if(udpLoggerEnabled && _suspensionHandling_isSuspended) + { + //this marks a message as already directly logged to allow the udp logger to later ignore the queued log request for the same message + logMessage.ml_isDirect = YES; + //make sure all udp log messages are still logged chronologically and prevent race conditions with our static counter var + dispatch_async([MLUDPLogger getCurrentInstance].loggerQueue, ^{ + [MLUDPLogger directlyWriteLogMessage:logMessage]; + }); } + + //don't do sync logging for any message (usually ERROR), while the global logging queue is suspended + //don't use _suspensionHandling_lock here because that can introduce deadlocks + //(for example if we have log statements in our MLLogFileManager code rotating the logfile and creating a new one) + return [self swizzled_queueLogMessage:logMessage asynchronously:_suspensionHandling_isSuspended ? YES : asyncFlag]; } //see https://stackoverflow.com/a/13326633 and https://fek.io/blog/method-swizzling-in-obj-c-and-swift/ @@ -1867,32 +1898,37 @@ +(NSData* _Nullable) convertLogmessageToJsonData:(DDLogMessage*) logMessage coun //construct json dictionary (*counter)++; - NSDictionary* representedObject = @{ - @"queueThreadLabel": [self getQueueThreadLabelFor:logMessage], + NSDictionary* tag = @{ + @"queueThreadLabel": nilWrapper([self getQueueThreadLabelFor:logMessage]), @"processType": [self isAppExtension] ? @"appex" : @"mainapp", - @"processName": [[[NSBundle mainBundle] executablePath] lastPathComponent], - @"counter": [NSNumber numberWithUnsignedLongLong:*counter], - @"processID": _processID, - @"qosName": qos2name(logMessage.qos), - @"representedObject": logMessage.representedObject ? logMessage.representedObject : [NSNull null], + @"processName": nilWrapper([[[NSBundle mainBundle] executablePath] lastPathComponent]), + @"counter": @(*counter), + @"processID": nilWrapper(_processID), + @"qosName": nilWrapper(qos2name(logMessage.qos)), + @"loggingQueueSuspended": @(_suspensionHandling_isSuspended), +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" + @"tag": nilWrapper(logMessage.tag), +#pragma clang diagnostic pop }; NSDictionary* msgDict = @{ - @"messageFormat": logMessage.messageFormat, - @"message": logMessage.message, - @"level": [NSNumber numberWithInteger:logMessage.level], - @"flag": [NSNumber numberWithInteger:logMessage.flag], - @"context": [NSNumber numberWithInteger:logMessage.context], - @"file": logMessage.file, - @"fileName": logMessage.fileName, - @"function": logMessage.function, - @"line": [NSNumber numberWithInteger:logMessage.line], - @"tag": representedObject, - @"options": [NSNumber numberWithInteger:logMessage.options], + @"messageFormat": nilWrapper(logMessage.messageFormat), + @"message": nilWrapper(logMessage.message), + @"level": @(logMessage.level), + @"flag": @(logMessage.flag), + @"context": @(logMessage.context), + @"file": nilWrapper(logMessage.file), + @"fileName": nilWrapper(logMessage.fileName), + @"function": nilWrapper(logMessage.function), + @"line": @(logMessage.line), + @"representedObject": nilWrapper(logMessage.representedObject), + @"tag": nilWrapper(tag), + @"options": @(logMessage.options), @"timestamp": [dateFormatter stringFromDate:logMessage.timestamp], - @"threadID": logMessage.threadID, - @"threadName": logMessage.threadName, - @"queueLabel": logMessage.queueLabel, - @"qos": [NSNumber numberWithInteger:logMessage.qos], + @"threadID": nilWrapper(logMessage.threadID), + @"threadName": nilWrapper(logMessage.threadName), + @"queueLabel": nilWrapper(logMessage.queueLabel), + @"qos": @(logMessage.qos), }; //encode json into NSData @@ -1915,6 +1951,13 @@ +(void) flushLogsWithTimeout:(double) timeout [MLUDPLogger flushWithTimeout:timeout]; } ++(BOOL) isAppSuspended +{ + @synchronized(_suspensionHandling_lock) { + return _suspensionHandling_isSuspended; + } +} + +(void) signalSuspension { @synchronized(_suspensionHandling_lock) { @@ -2074,7 +2117,7 @@ +(void) installCrashHandler handler.maxReportCount = 4; handler.deadlockWatchdogInterval = 0; // no main thread watchdog handler.userInfo = @{ - @"isAppex": @([self isAppExtension]), + @"isAppex": bool2str([self isAppExtension]), @"processName": [[[NSBundle mainBundle] executablePath] lastPathComponent], @"bundleName": nilWrapper([[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleName"]), @"appVersion": [self appBuildVersionInfoFor:MLVersionTypeLog], diff --git a/Monal/Classes/MLMessageProcessor.m b/Monal/Classes/MLMessageProcessor.m index b7059261ed..c2f3413374 100644 --- a/Monal/Classes/MLMessageProcessor.m +++ b/Monal/Classes/MLMessageProcessor.m @@ -579,18 +579,23 @@ +(MLMessage* _Nullable) processMessage:(XMPPMessage*) messageNode andOuterMessag { NSString* messageIdToReplace = [messageNode findFirst:@"{urn:xmpp:message-correct:0}replace@id"]; DDLogVerbose(@"Message id to LMC-replace: %@", messageIdToReplace); - //this checks if this message is from the same jid as the message it tries to do the LMC for (e.g. inbound can only correct inbound and outbound only outbound) - historyId = [[DataLayer sharedInstance] getLMCHistoryIDForMessageId:messageIdToReplace from:messageNode.fromUser occupantId:occupantId participantJid:participantJid andAccount:account.accountNo]; - DDLogVerbose(@"History id to LMC-replace: %@", historyId); - //now check if the LMC is allowed (we use historyIdToUse for MLhistory mam queries to only check LMC for the 3 messages coming before this ID in this converastion) - //historyIdToUse will be nil, for messages going forward in time which means (check for the newest 3 messages in this conversation) - if(historyId != nil && [[DataLayer sharedInstance] checkLMCEligible:historyId encrypted:encrypted historyBaseID:historyIdToUse]) + if(messageIdToReplace == nil) + DDLogWarn(@"Ignoring LMC message not carrying a replacement id, spec vialoation!"); + else { - [[DataLayer sharedInstance] updateMessageHistory:historyId withText:body]; - LMCReplaced = YES; + //this checks if this message is from the same jid as the message it tries to do the LMC for (e.g. inbound can only correct inbound and outbound only outbound) + historyId = [[DataLayer sharedInstance] getLMCHistoryIDForMessageId:messageIdToReplace from:messageNode.fromUser occupantId:occupantId participantJid:participantJid andAccount:account.accountNo]; + DDLogVerbose(@"History id to LMC-replace: %@", historyId); + //now check if the LMC is allowed (we use historyIdToUse for MLhistory mam queries to only check LMC for the 3 messages coming before this ID in this converastion) + //historyIdToUse will be nil, for messages going forward in time which means (check for the newest 3 messages in this conversation) + if(historyId != nil && [[DataLayer sharedInstance] checkLMCEligible:historyId encrypted:encrypted historyBaseID:historyIdToUse]) + { + [[DataLayer sharedInstance] updateMessageHistory:historyId withText:body]; + LMCReplaced = YES; + } + else + historyId = nil; } - else - historyId = nil; } //handle normal messages or LMC messages that can not be found diff --git a/Monal/Classes/MLMucProcessor.m b/Monal/Classes/MLMucProcessor.m index 9de041eeee..9bbe1df74d 100644 --- a/Monal/Classes/MLMucProcessor.m +++ b/Monal/Classes/MLMucProcessor.m @@ -971,7 +971,10 @@ -(NSString* _Nullable) generateMucJid NSString* mucServer = nil; for(NSString* jid in _account.connectionProperties.conferenceServers) { - if([_account.connectionProperties.conferenceServers[jid] check:@"identity"]) + // Do not use gateways + if(![_account.connectionProperties.conferenceServers[jid] check:@"identity"] + && [_account.connectionProperties.conferenceServers[jid] check:@"identity"] + && [_account.connectionProperties.conferenceServers[jid] check:@"identity"]) { mucServer = jid; break; diff --git a/Monal/Classes/MLStream.m b/Monal/Classes/MLStream.m index 2112a087f6..c9edf2ae8f 100644 --- a/Monal/Classes/MLStream.m +++ b/Monal/Classes/MLStream.m @@ -470,26 +470,35 @@ +(void) connectWithSNIDomain:(NSString*) SNIDomain connectHost:(NSString*) host return YES; }); - /* //some weird apple stuff creates the framer twice: once directly when starting the tcp handshake - //and again later after the tcp connection was established successfully --> ignore the first one + //and once a few milliseconds later, presumably after the tcp connection was established successfully + //--> ignore all but the first one if(framerId < 1) { - nw_framer_set_input_handler(framer, ^size_t(nw_framer_t framer) { - nw_framer_parse_input(framer, 1, BUFFER_SIZE, nil, ^size_t(uint8_t* buffer, size_t buffer_length, bool is_complete) { - MLAssert(NO, @"Unexpected incoming bytes in first framer!", (@{ - @"logtag": nilWrapper(logtag), - @"framer": framer, - @"buffer": [NSData dataWithBytes:buffer length:buffer_length], - @"buffer_length": @(buffer_length), - @"is_complete": bool2str(is_complete), - })); - return buffer_length; + DDLogVerbose(@"Framer is the first one, using it..."); + //we have to simulate nw_connection_state_ready because the connection state will not reflect that while our framer is active + //--> use framer start as "connection active" signal + //first framer start is allowed to directly send data which will be used as tcp early data + if(!wasOpenOnce) + { + wasOpenOnce = YES; + @synchronized(shared_state) { + shared_state.open = YES; + } + //make sure to not do this inside the framer thread to not cause any deadlocks + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + [input generateEvent:NSStreamEventOpenCompleted]; + [output generateEvent:NSStreamEventOpenCompleted]; }); - return 0; //why that? + } + + nw_framer_set_input_handler(framer, ^size_t(nw_framer_t framer) { + DDLogDebug(@"Got new input for framer: %@", framer); + [input schedule_read]; + return 0; //why that?? }); nw_framer_set_output_handler(framer, ^(nw_framer_t framer, nw_framer_message_t message, size_t message_length, bool is_complete) { - MLAssert(NO, @"Unexpected outgoing bytes in first framer!", (@{ + MLAssert(NO, @"Unexpected outgoing bytes in framer!", (@{ @"logtag": nilWrapper(logtag), @"framer": framer, @"message": message, @@ -497,32 +506,12 @@ +(void) connectWithSNIDomain:(NSString*) SNIDomain connectHost:(NSString*) host @"is_complete": bool2str(is_complete), })); }); - return nw_framer_start_result_will_mark_ready; - } - */ - - //we have to simulate nw_connection_state_ready because the connection state will not reflect that while our framer is active - //--> use framer start as "connection active" signal - //first framer start is allowed to directly send data which will be used as tcp early data - if(!wasOpenOnce) - { - wasOpenOnce = YES; - @synchronized(shared_state) { - shared_state.open = YES; - } - //make sure to not do this inside the framer thread to not cause any deadlocks - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ - [input generateEvent:NSStreamEventOpenCompleted]; - [output generateEvent:NSStreamEventOpenCompleted]; - }); + + shared_state.framer = framer; } + else + DDLogVerbose(@"Ignoring subsequent framer..."); - nw_framer_set_input_handler(framer, ^size_t(nw_framer_t framer) { - [input schedule_read]; - return 0; //why that?? - }); - - shared_state.framer = framer; return nw_framer_start_result_will_mark_ready; }); DDLogInfo(@"%@: Not doing direct TLS: appending framer to protocol stack...", logtag); diff --git a/Monal/Classes/MLUDPLogger.h b/Monal/Classes/MLUDPLogger.h index c889e14dba..7d1fb34ae2 100644 --- a/Monal/Classes/MLUDPLogger.h +++ b/Monal/Classes/MLUDPLogger.h @@ -11,9 +11,13 @@ NS_ASSUME_NONNULL_BEGIN +FOUNDATION_EXPORT DDLoggerName const DDLoggerNameUDP NS_SWIFT_NAME(DDLoggerName.udp); // MLUDPLogger + @interface MLUDPLogger : DDAbstractLogger +(void) flushWithTimeout:(double) timeout; ++(void) directlyWriteLogMessage:(DDLogMessage*) logMessage; ++(instancetype) getCurrentInstance; @end diff --git a/Monal/Classes/MLUDPLogger.m b/Monal/Classes/MLUDPLogger.m index 81d19776ab..2ec9d3d67f 100644 --- a/Monal/Classes/MLUDPLogger.m +++ b/Monal/Classes/MLUDPLogger.m @@ -19,6 +19,8 @@ #import "MLContact.h" #import "xmpp.h" +DDLoggerName const DDLoggerNameUDP = @"monal.loggger.udp.mainQueue"; + static NSData* _key; static volatile MLUDPLogger* _self; @@ -78,6 +80,22 @@ +(void) flushWithTimeout:(double) timeout } } ++(void) directlyWriteLogMessage:(DDLogMessage*) logMessage +{ + if(_self == nil) + { + [[self class] logError:@"Ignoring call to directlySyncWriteLogMessage: _self still nil!"]; + return; + } + return [_self realLogMessage:logMessage isDirect:YES]; +} + ++(instancetype) getCurrentInstance +{ + return (MLUDPLogger*)_self; + +} + -(void) dealloc { _self = nil; @@ -87,7 +105,7 @@ -(void) didAddLogger { _self = self; _send_condition = [NSCondition new]; - _send_queue = dispatch_queue_create("MLUDPLoggerSendQueue", dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INTERACTIVE, 0)); + _send_queue = dispatch_queue_create("MLUDPLoggerInternalSendQueue", dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INTERACTIVE, 0)); } -(void) willRemoveLogger @@ -95,6 +113,12 @@ -(void) willRemoveLogger _self = nil; } + +-(DDLoggerName) loggerName +{ + return DDLoggerNameUDP; +} + +(void) logError:(NSString*) format, ... NS_FORMAT_FUNCTION(1, 2) { #ifdef IS_ALPHA @@ -164,9 +188,11 @@ -(void) disconnect if(_connection != NULL) nw_connection_force_cancel(_connection); _connection = NULL; - [_send_condition lock]; - [_send_condition signal]; - [_send_condition unlock]; + @synchronized(_send_condition) { + [_send_condition lock]; + [_send_condition signal]; + [_send_condition unlock]; + } } -(void) createConnectionIfNeeded @@ -205,9 +231,11 @@ -(void) createConnectionIfNeeded [condition lock]; [condition signal]; [condition unlock]; - [self->_send_condition lock]; - [self->_send_condition signal]; - [self->_send_condition unlock]; + @synchronized(self->_send_condition) { + [self->_send_condition lock]; + [self->_send_condition signal]; + [self->_send_condition unlock]; + } } }); [condition lock]; @@ -225,6 +253,11 @@ -(void) createConnectionIfNeeded } -(void) logMessage:(DDLogMessage*) logMessage +{ + return [self realLogMessage:logMessage isDirect:NO]; +} + +-(void) realLogMessage:(DDLogMessage*) logMessage isDirect:(BOOL) direct { static uint64_t counter = 0; @@ -232,6 +265,10 @@ -(void) logMessage:(DDLogMessage*) logMessage if(![[HelperTools defaultsDB] boolForKey: @"udpLoggerEnabled"]) return; + //ignore log messages already udp-logged as direct messages when handling queued messages + if(!direct && logMessage.ml_isDirect) + return; + NSError* error = nil; NSData* rawData = [HelperTools convertLogmessageToJsonData:logMessage counter:&counter andError:&error]; if(error != nil || rawData == nil) @@ -272,9 +309,11 @@ -(void) sendData:(NSData*) data withOriginalMessage:(NSString*) msg } //[[self class] logError:@"unlocking send condition (%@)...", [NSNumber numberWithUnsignedLongLong:self->_counter]]; - [self->_send_condition lock]; - [self->_send_condition signal]; - [self->_send_condition unlock]; + @synchronized(self->_send_condition) { + [self->_send_condition lock]; + [self->_send_condition signal]; + [self->_send_condition unlock]; + } }); //block this queue until our udp message was sent or an error occured [_send_condition wait]; diff --git a/Monal/Classes/MonalAppDelegate.m b/Monal/Classes/MonalAppDelegate.m index 28d7896e34..2021c43b2a 100644 --- a/Monal/Classes/MonalAppDelegate.m +++ b/Monal/Classes/MonalAppDelegate.m @@ -60,7 +60,7 @@ @interface MonalAppDelegate() MLContact* _contactToOpen; monal_id_block_t _completionToCall; BOOL _shutdownPending; - BOOL _wasFreezed; + BOOL _wasFrozen; } @end @@ -319,7 +319,7 @@ -(id) init _wakeupCompletions = [NSMutableDictionary new]; DDLogVerbose(@"Setting _shutdownPending to NO..."); _shutdownPending = NO; - _wasFreezed = NO; + _wasFrozen = NO; //[self runParserTests]; //[self runSDPTests]; @@ -443,6 +443,11 @@ -(BOOL) application:(UIApplication*) application didFinishLaunchingWithOptions:( title:NSLocalizedString(@"Deny new contact", @"") options:UNNotificationActionOptionNone ]; + UNNotificationAction* blockSubscriptionAction = [UNNotificationAction + actionWithIdentifier:@"BLOCK_SUBSCRIPTION_ACTION" + title:NSLocalizedString(@"Block new contact", @"") + options:UNNotificationActionOptionNone + ]; if(@available(iOS 15.0, macCatalyst 15.0, *)) { replyAction = [UNTextInputNotificationAction @@ -469,6 +474,12 @@ -(BOOL) application:(UIApplication*) application didFinishLaunchingWithOptions:( actionWithIdentifier:@"DENY_SUBSCRIPTION_ACTION" title:NSLocalizedString(@"Deny new contact", @"") options:UNNotificationActionOptionNone + icon:[UNNotificationActionIcon iconWithSystemImageName:@"person.crop.circle.badge.minus"] + ]; + blockSubscriptionAction = [UNNotificationAction + actionWithIdentifier:@"BLOCK_SUBSCRIPTION_ACTION" + title:NSLocalizedString(@"Block new contact", @"") + options:UNNotificationActionOptionNone icon:[UNNotificationActionIcon iconWithSystemImageName:@"person.crop.circle.badge.xmark"] ]; } @@ -484,7 +495,7 @@ -(BOOL) application:(UIApplication*) application didFinishLaunchingWithOptions:( ]; UNNotificationCategory* subscriptionCategory = [UNNotificationCategory categoryWithIdentifier:@"subscription" - actions:@[approveSubscriptionAction, denySubscriptionAction] + actions:@[approveSubscriptionAction, denySubscriptionAction, blockSubscriptionAction] intentIdentifiers:@[] options:UNNotificationCategoryOptionCustomDismissAction ]; @@ -1012,6 +1023,12 @@ -(void) userNotificationCenter:(UNUserNotificationCenter*) center didReceiveNoti DDLogInfo(@"DENY_SUBSCRIPTION_ACTION triggered..."); [[MLXMPPManager sharedInstance] removeContact:fromContact]; } + else if([response.actionIdentifier isEqualToString:@"BLOCK_SUBSCRIPTION_ACTION"]) + { + DDLogInfo(@"BLOCK_SUBSCRIPTION_ACTION triggered..."); + [[MLXMPPManager sharedInstance] removeContact:fromContact]; + [[MLXMPPManager sharedInstance] block:YES contact:fromContact]; + } else if([response.actionIdentifier isEqualToString:@"com.apple.UNNotificationDefaultActionIdentifier"]) //open chat of this contact dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ while(self.activeChats == nil) @@ -1148,11 +1165,7 @@ -(void) prepareForFreeze:(NSNotification*) notification for(xmpp* account in [MLXMPPManager sharedInstance].connectedXMPP) [account freeze]; [MLProcessLock unlock]; - _wasFreezed = YES; - @synchronized(self) { - DDLogVerbose(@"Setting _shutdownPending to NO..."); - _shutdownPending = NO; - } + _wasFrozen = YES; } -(void) applicationWillEnterForeground:(UIApplication*) application @@ -1170,14 +1183,14 @@ -(void) applicationWillEnterForeground:(UIApplication*) application //only show loading HUD if we really got freezed before MBProgressHUD* loadingHUD; - if(_wasFreezed) + if(_wasFrozen) { loadingHUD = [MBProgressHUD showHUDAddedTo:[self getTopViewController].view animated:YES]; loadingHUD.label.text = NSLocalizedString(@"Refreshing...", @""); loadingHUD.mode = MBProgressHUDModeIndeterminate; loadingHUD.removeFromSuperViewOnHide = YES; - _wasFreezed = NO; + _wasFrozen = NO; } dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ @@ -1430,8 +1443,8 @@ -(void) checkIfBackgroundTaskIsStillNeeded [DDLog flushLog]; DDLogVerbose(@"Setting _shutdownPending to YES..."); _shutdownPending = YES; - [[MLXMPPManager sharedInstance] disconnectAll]; //disconnect all accounts to prevent TCP buffer leaking [HelperTools scheduleBackgroundTask:NO]; //request bg fetch execution in BGFETCH_DEFAULT_INTERVAL seconds + [[MLXMPPManager sharedInstance] disconnectAll]; //disconnect all accounts to prevent TCP buffer leaking [HelperTools dispatchAsync:NO reentrantOnQueue:dispatch_get_main_queue() withBlock:^{ BOOL stopped = NO; //make sure this will be done only once, even if we have an uikit bgtask and a bg fetch running simultaneously @@ -1508,13 +1521,13 @@ -(void) addBackgroundTask //this has to be before account disconnects, to detect which accounts are not idle (e.g. have a sync error) [HelperTools updateSyncErrorsWithDeleteOnly:NO andWaitForCompletion:YES]; - //disconnect all accounts to prevent TCP buffer leaking - [[MLXMPPManager sharedInstance] disconnectAll]; - //schedule a BGProcessingTaskRequest to process this further as soon as possible //(if we end up here, the graceful shuttdown did not work out because we are not idle --> we need more cpu time) [HelperTools scheduleBackgroundTask:YES]; //force as soon as possible + //disconnect all accounts to prevent TCP buffer leaking + [[MLXMPPManager sharedInstance] disconnectAll]; + //notify about pending app freeze (don't queue this notification because it should be handled IMMEDIATELY and INLINE) DDLogVerbose(@"Posting kMonalWillBeFreezed notification now..."); [[NSNotificationCenter defaultCenter] postNotificationName:kMonalWillBeFreezed object:nil]; @@ -1567,13 +1580,13 @@ -(void) handleBackgroundProcessingTask:(BGTask*) task //this has to be before account disconnects, to detect which accounts are not idle (e.g. have a sync error) [HelperTools updateSyncErrorsWithDeleteOnly:YES andWaitForCompletion:YES]; - //disconnect all accounts to prevent TCP buffer leaking - [[MLXMPPManager sharedInstance] disconnectAll]; - //schedule a new BGProcessingTaskRequest to process this further as soon as possible //(if we end up here, the graceful shuttdown did not work out because we are not idle --> we need more cpu time) [HelperTools scheduleBackgroundTask:YES]; //force as soon as possible + //disconnect all accounts to prevent TCP buffer leaking + [[MLXMPPManager sharedInstance] disconnectAll]; + //notify about pending app freeze (don't queue this notification because it should be handled IMMEDIATELY and INLINE) DDLogVerbose(@"Posting kMonalWillBeFreezed notification now..."); [[NSNotificationCenter defaultCenter] postNotificationName:kMonalWillBeFreezed object:nil]; @@ -1671,13 +1684,13 @@ -(void) handleBackgroundRefreshingTask:(BGTask*) task //this has to be before account disconnects, to detect which accounts are not idle (e.g. have a sync error) [HelperTools updateSyncErrorsWithDeleteOnly:YES andWaitForCompletion:YES]; - //disconnect all accounts to prevent TCP buffer leaking - [[MLXMPPManager sharedInstance] disconnectAll]; - //schedule a new BGProcessingTaskRequest to process this further as soon as possible //(if we end up here, the graceful shuttdown did not work out because we are not idle --> we need more cpu time) [HelperTools scheduleBackgroundTask:YES]; //force as soon as possible + //disconnect all accounts to prevent TCP buffer leaking + [[MLXMPPManager sharedInstance] disconnectAll]; + //notify about pending app freeze (don't queue this notification because it should be handled IMMEDIATELY and INLINE) DDLogVerbose(@"Posting kMonalWillBeFreezed notification now..."); [[NSNotificationCenter defaultCenter] postNotificationName:kMonalWillBeFreezed object:nil]; @@ -1898,13 +1911,13 @@ -(void) incomingWakeupWithCompletionHandler:(void (^)(UIBackgroundFetchResult re BOOL wasIdle = [[MLXMPPManager sharedInstance] allAccountsIdle] && [MLFiletransfer isIdle]; [HelperTools updateSyncErrorsWithDeleteOnly:NO andWaitForCompletion:YES]; - //disconnect all accounts to prevent TCP buffer leaking - [[MLXMPPManager sharedInstance] disconnectAll]; - //schedule a new BGProcessingTaskRequest to process this further as soon as possible, if we are not idle //(if we end up here, the graceful shuttdown did not work out because we are not idle --> we need more cpu time) [HelperTools scheduleBackgroundTask:!wasIdle]; + //disconnect all accounts to prevent TCP buffer leaking + [[MLXMPPManager sharedInstance] disconnectAll]; + //notify about pending app freeze (don't queue this notification because it should be handled IMMEDIATELY and INLINE) DDLogVerbose(@"Posting kMonalWillBeFreezed notification now..."); [[NSNotificationCenter defaultCenter] postNotificationName:kMonalWillBeFreezed object:nil]; diff --git a/Monal/Classes/chatViewController.m b/Monal/Classes/chatViewController.m index b9ce268932..7f6a1d33d9 100644 --- a/Monal/Classes/chatViewController.m +++ b/Monal/Classes/chatViewController.m @@ -235,6 +235,10 @@ -(void) viewDidLoad #endif [self updateCallButtonImage]; + + //ping this muc on open, to make sure we are still joined + if([[[DataLayer sharedInstance] listMucsForAccount:self.contact.accountID] containsObject:self.contact.contactJid]) + [self.xmppAccount.mucProcessor ping:self.contact.contactJid]; } -(void) updateCallButtonImage diff --git a/Monal/Classes/xmpp.m b/Monal/Classes/xmpp.m index 2869b25ca7..d0ffdbcdec 100644 --- a/Monal/Classes/xmpp.m +++ b/Monal/Classes/xmpp.m @@ -705,6 +705,8 @@ -(BOOL) connectionTask [row objectForKey:@"ttl"] ); } + else + DDLogWarn(@"No SRV records discovered, using legacy starttls to A/AAAA record of domain!"); [self createStreams]; return NO; @@ -871,6 +873,20 @@ -(void) reinitLoginTimer })); } +-(void) addTimerToCancelOnDisconnect:(monal_void_block_t) timer +{ + @synchronized(self->_timersToCancelOnDisconnect) { + [self->_timersToCancelOnDisconnect addObject:timer]; + } +} + +-(void) removeTimerToCancelOnDisconnect:(monal_void_block_t) timer +{ + @synchronized(self->_timersToCancelOnDisconnect) { + [self->_timersToCancelOnDisconnect removeObject:timer]; + } +} + -(void) connect { if([self parseQueueFrozen]) @@ -1476,6 +1492,8 @@ -(void) startXMPPStreamWithXMLOpening:(BOOL) withXMLOpening withStartTLS:(BOOL) -(void) sendPing:(double) timeout { + static monal_void_block_t delayTimer = nil; //it doesn't matter if this has a race condition + DDLogVerbose(@"sendPing called"); [self dispatchAsyncOnReceiveQueue: ^{ DDLogVerbose(@"sendPing called - now inside receiveQueue"); @@ -1506,12 +1524,18 @@ -(void) sendPing:(double) timeout } else if([self->_parseQueue operationCount] > 4) { - DDLogWarn(@"parseQueue overflow, delaying ping by 4 seconds."); - @synchronized(self->_timersToCancelOnDisconnect) { - [self->_timersToCancelOnDisconnect addObject:createTimer(4.0, (^{ + if(delayTimer != nil) + DDLogWarn(@"Ping already delayed, ignoring additional ping..."); + else + { + DDLogWarn(@"parseQueue overflow, delaying ping by 4 seconds."); + delayTimer = createTimer(4.0, (^{ + [self removeTimerToCancelOnDisconnect:delayTimer]; + delayTimer = nil; DDLogDebug(@"ping delay expired, retrying ping."); [self sendPing:timeout]; - }))]; + })); + [self addTimerToCancelOnDisconnect:delayTimer]; } } else @@ -4870,15 +4894,14 @@ -(void)stream:(NSStream*) stream handleEvent:(NSStreamEvent) eventCode DDLogInfo(@"%@ Stream %@ encountered eof, trying to reconnect via parse queue in 1 second", [stream class], stream); //use a timer to make sure the incoming data was pushed *through* the MLPipe and reached the parseQueue //already when pushing our reconnect block onto the parseQueue - @synchronized(self->_timersToCancelOnDisconnect) { - [self->_timersToCancelOnDisconnect addObject:createTimer(1.0, (^{ - //add this to parseQueue to make sure we completely handle everything that came in before the connection was closed, before handling the close event itself - [self->_parseQueue addOperations:@[[NSBlockOperation blockOperationWithBlock:^{ - DDLogInfo(@"Inside parseQueue: %@ Stream %@ encountered eof, trying to reconnect", [stream class], stream); - [self reconnect]; - }]] waitUntilFinished:NO]; - }))]; - } + //this timer will only be created once on every connect cycle (and removed from our timers list on reconnect/disconnect) + [self addTimerToCancelOnDisconnect:createTimer(1.0, (^{ + //add this to parseQueue to make sure we completely handle everything that came in before the connection was closed, before handling the close event itself + [self->_parseQueue addOperations:@[[NSBlockOperation blockOperationWithBlock:^{ + DDLogInfo(@"Inside parseQueue: %@ Stream %@ encountered eof, trying to reconnect", [stream class], stream); + [self reconnect]; + }]] waitUntilFinished:NO]; + }))]; break; } } diff --git a/Monal/Images.xcassets/AppIcon.appiconset/Monal-ios_1024.png b/Monal/Images.xcassets/AppIcon.appiconset/Monal-ios_1024.png index d66ae5773c..c695a38b4f 100644 Binary files a/Monal/Images.xcassets/AppIcon.appiconset/Monal-ios_1024.png and b/Monal/Images.xcassets/AppIcon.appiconset/Monal-ios_1024.png differ diff --git a/Monal/Images.xcassets/AppIcon.appiconset/Monal-macos-1024.png b/Monal/Images.xcassets/AppIcon.appiconset/Monal-macos-1024.png index d58490e29d..d2dd497e96 100644 Binary files a/Monal/Images.xcassets/AppIcon.appiconset/Monal-macos-1024.png and b/Monal/Images.xcassets/AppIcon.appiconset/Monal-macos-1024.png differ diff --git a/Monal/Images.xcassets/AppIcon.appiconset/Monal-macos-512.png b/Monal/Images.xcassets/AppIcon.appiconset/Monal-macos-512.png index b1799ac14e..9a8b63090d 100644 Binary files a/Monal/Images.xcassets/AppIcon.appiconset/Monal-macos-512.png and b/Monal/Images.xcassets/AppIcon.appiconset/Monal-macos-512.png differ diff --git a/Monal/Images.xcassets/AppLogo.imageset/Monal-ios_1024.png b/Monal/Images.xcassets/AppLogo.imageset/Monal-ios_1024.png index 5546f480b2..c695a38b4f 100644 Binary files a/Monal/Images.xcassets/AppLogo.imageset/Monal-ios_1024.png and b/Monal/Images.xcassets/AppLogo.imageset/Monal-ios_1024.png differ diff --git a/Monal/Monal-iOS/Launch Screen.storyboard b/Monal/Monal-iOS/Launch Screen.storyboard index 26ec0ecd94..9bf4ec4a89 100644 --- a/Monal/Monal-iOS/Launch Screen.storyboard +++ b/Monal/Monal-iOS/Launch Screen.storyboard @@ -19,7 +19,7 @@ -