Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 20 additions & 4 deletions Monal/Classes/MLPipe.m
Original file line number Diff line number Diff line change
Expand Up @@ -86,10 +86,26 @@ -(NSInputStream*) getNewOutputStream
if(_output)
{
DDLogInfo(@"Pipe making output stream orphan");
[_output setDelegate:nil];
[_output removeFromRunLoop:[HelperTools getExtraRunloopWithIdentifier:MLRunLoopIdentifierNetwork] forMode:NSDefaultRunLoopMode];
[_output close];
_output = nil;
NSCondition* condition = [NSCondition new];
[condition lock];
NSRunLoop* runLoop = [HelperTools getExtraRunloopWithIdentifier:MLRunLoopIdentifierNetwork];
DDLogDebug(@"Scheduling _output teardown block on our network runloop...");
CFRunLoopPerformBlock([runLoop getCFRunLoop], (__bridge CFStringRef)NSDefaultRunLoopMode, ^{
DDLogDebug(@"Tearing down _output...");
[condition lock];
[self->_output setDelegate:nil];
[self->_output removeFromRunLoop:runLoop forMode:NSDefaultRunLoopMode];
[self->_output close];
self->_output = nil;
[condition signal];
[condition unlock];
});
CFRunLoopWakeUp([runLoop getCFRunLoop]); //trigger wakeup of runloop to execute the block as soon as possible
//wait for our block to finish executing
DDLogDebug(@"Waiting for scheduled _output teardown block to finish...");
[condition wait];
[condition unlock];
DDLogDebug(@"_output teardown complete...");
}
[self cleanupOutputBuffer];

Expand Down
4 changes: 2 additions & 2 deletions Monal/Classes/MLXMPPManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -242,7 +242,7 @@ -(id) init
[self setPushToken:nil]; //load push settings from defaultsDB (can be overwritten later on in mainapp, but *not* in appex)

//set up regular ping
dispatch_queue_t q_background = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
dispatch_queue_t q_background = dispatch_queue_create_with_target("im.monal.activityLog", DISPATCH_QUEUE_SERIAL, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0));
_pinger = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, q_background);

dispatch_source_set_timer(_pinger,
Expand All @@ -255,7 +255,7 @@ -(id) init
{
if(xmppAccount.accountState >= kStateInitStarted)
{
DDLogInfo(@"began a idle ping");
DDLogInfo(@"sending idle ping");
[xmppAccount sendPing:LONG_PING]; //long ping timeout because this is a background/interval ping
}
}
Expand Down
163 changes: 86 additions & 77 deletions Monal/Classes/xmpp.m
Original file line number Diff line number Diff line change
Expand Up @@ -494,7 +494,6 @@ -(BOOL) idle
) || (
//test if we are connected and idle (e.g. we're done with catchup and neither process any incoming stanzas nor trying to send anything)
_catchupDone &&
_pingTimer == nil &&
!unackedCount &&
![_parseQueue operationCount] && //if something blocks the parse queue it is either an incoming stanza currently processed or waiting to be processed
//[_receiveQueue operationCount] <= ([NSOperationQueue currentQueue]==_receiveQueue ? 1 : 0) &&
Expand All @@ -508,7 +507,6 @@ -(BOOL) idle
"\t_accountState < kStateReconnecting = %@\n"
"\t_reconnectInProgress = %@\n"
"\t_catchupDone = %@\n"
"\t_pingTimer = %@\n"
"\t[self.unAckedStanzas count] = %lu\n"
"\t[_parseQueue operationCount] = %lu\n"
//"\t[_receiveQueue operationCount] = %lu\n"
Expand All @@ -519,7 +517,6 @@ -(BOOL) idle
bool2str(_accountState < kStateReconnecting),
bool2str(_reconnectInProgress),
bool2str(_catchupDone),
_pingTimer == nil ? @"none" : @"running timer",
unackedCount,
(unsigned long)[_parseQueue operationCount],
//(unsigned long)[_receiveQueue operationCount],
Expand Down Expand Up @@ -764,94 +761,103 @@ -(void) unfreezeParseQueue

-(void) freezeSendQueue
{
if(_sendQueue.suspended)
{
DDLogWarn(@"Send queue of account %@ already frozen, doing nothing...", self);
return;
@synchronized(_sendQueue) {
if(_sendQueue.suspended)
{
DDLogWarn(@"Send queue of account %@ already frozen, doing nothing...", self);
return;
}

//wait for all queued operations to finish (this will NOT block if the tcp stream is not writable)
[self->_sendQueue addOperations: @[[NSBlockOperation blockOperationWithBlock:^{
self->_sendQueue.suspended = YES;
}]] waitUntilFinished:YES]; //block until finished because we are closing the socket directly afterwards
[HelperTools busyWaitForOperationQueue:_sendQueue];
}

//wait for all queued operations to finish (this will NOT block if the tcp stream is not writable)
[self->_sendQueue addOperations: @[[NSBlockOperation blockOperationWithBlock:^{
self->_sendQueue.suspended = YES;
}]] waitUntilFinished:YES]; //block until finished because we are closing the socket directly afterwards
[HelperTools busyWaitForOperationQueue:_sendQueue];
}

-(void) unfreezeSendQueue
{
//no need to dispatch anything here, just start processing jobs
self->_sendQueue.suspended = NO;
@synchronized(_sendQueue) {
//no need to dispatch anything here, just start processing jobs
self->_sendQueue.suspended = NO;
}
}

-(void) freeze
{
//this can only be done if this method is the only one that freezes the receive queue,
//because this shortcut assumes that parse and send queues are always frozen, too, if the receive queue is frozen
if(_receiveQueue.suspended)
{
DDLogWarn(@"Account %@ already frozen, doing nothing...", self);
return;
@synchronized(self) {
//this can only be done if this method is the only one that freezes the receive queue,
//because this shortcut assumes that parse and send queues are always frozen, too, if the receive queue is frozen
if(_receiveQueue.suspended)
{
DDLogWarn(@"Account %@ already frozen, doing nothing...", self);
return;
}

DDLogInfo(@"Freezing account: %@", self);

//this does not have to be synchronized with the freezing of the parse queue and receive queue
[self freezeSendQueue];

//don't merge the sync dispatch to freeze the receive queue with the sync dispatch done by freezeParseQueue
//merging those might leave some tasks in the receive queue that got added to it after the parse queue freeze
//was signalled but before it actually completed the freeze
//statement 1:
//this is not okay because leaked stanzas while frozen could be processed twice if the complete app gets frozen afterwards,
//then these stanzas get processed by the appex and afterwards the complete app and subsequently the receive queue gets unfrozen again
//statement 2:
//stanzas still in the parse queue when unfreezing the account will be dropped because self.accountState < kStateConnected
//will instruct the block inside prepareXMPPParser to drop any stanzas still queued in the parse queue
//and having even self.accountState < kStateReconnecting will make a call to [self connect] mandatory,
//which will cancel all operations still queued on the parse queue
//statement 3:
//normally a complete app freeze will only occur after calling [MLXMPPManager disconnectAll] and subsequently [xmpp freeze],
//so self.accountState < kStateReconnecting should always be true on unfreeze (which will make statement 2 above always hold true)
//statement 4:
//if an app freeze takes too long, for example because disconnecting does not finish in time, or if the app still holds the MLProcessLock,
//the app will be killed by iOS, which will immediately invalidate every block in every queue
[self freezeParseQueue];
[self dispatchOnReceiveQueue:^{
//this is the last block running in the receive queue (it will be frozen once this block finishes execution)
self->_receiveQueue.suspended = YES;
}];
[HelperTools busyWaitForOperationQueue:_receiveQueue];
}

DDLogInfo(@"Freezing account: %@", self);

//this does not have to be synchronized with the freezing of the parse queue and receive queue
[self freezeSendQueue];

//don't merge the sync dispatch to freeze the receive queue with the sync dispatch done by freezeParseQueue
//merging those might leave some tasks in the receive queue that got added to it after the parse queue freeze
//was signalled but before it actually completed the freeze
//statement 1:
//this is not okay because leaked stanzas while frozen could be processed twice if the complete app gets frozen afterwards,
//then these stanzas get processed by the appex and afterwards the complete app and subsequently the receive queue gets unfrozen again
//statement 2:
//stanzas still in the parse queue when unfreezing the account will be dropped because self.accountState < kStateConnected
//will instruct the block inside prepareXMPPParser to drop any stanzas still queued in the parse queue
//and having even self.accountState < kStateReconnecting will make a call to [self connect] mandatory,
//which will cancel all operations still queued on the parse queue
//statement 3:
//normally a complete app freeze will only occur after calling [MLXMPPManager disconnectAll] and subsequently [xmpp freeze],
//so self.accountState < kStateReconnecting should always be true on unfreeze (which will make statement 2 above always hold true)
//statement 4:
//if an app freeze takes too long, for example because disconnecting does not finish in time, or if the app still holds the MLProcessLock,
//the app will be killed by iOS, which will immediately invalidate every block in every queue
[self freezeParseQueue];
[self dispatchOnReceiveQueue:^{
//this is the last block running in the receive queue (it will be frozen once this block finishes execution)
self->_receiveQueue.suspended = YES;
}];
[HelperTools busyWaitForOperationQueue:_receiveQueue];
}

-(void) unfreeze
{
DDLogInfo(@"Unfreezing account: %@", self);

//make sure we don't have any race conditions by dispatching this to our receive queue
//this operation has highest priority to make sure it will be executed first once unfrozen
NSBlockOperation* unfreezeOperation = [NSBlockOperation blockOperationWithBlock:^{
//this has to be the very first thing even before unfreezing the parse or send queues
@synchronized(self->_stateLockObject) {
if(self.accountState < kStateReconnecting)
{
DDLogInfo(@"Reloading UNfrozen account %@", self.accountNo);
//(re)read persisted state (could be changed by appex)
[self readState];
@synchronized(self) {
DDLogInfo(@"Unfreezing account: %@", self);

//make sure we don't have any race conditions by dispatching this to our receive queue
//this operation has highest priority to make sure it will be executed first once unfrozen
NSBlockOperation* unfreezeOperation = [NSBlockOperation blockOperationWithBlock:^{
//this has to be the very first thing even before unfreezing the parse or send queues
//make sure to lock this against our explicitLogout, even if not reloading state
@synchronized(self->_stateLockObject) {
if(self.accountState < kStateReconnecting)
{
DDLogInfo(@"Reloading UNfrozen account %@", self.accountNo);
//(re)read persisted state (could be changed by appex)
[self readState];
}
else
DDLogInfo(@"Not reloading UNfrozen account %@, already connected", self.accountNo);

//this must be inside the dispatch async, because it will dispatch *SYNC* to the receive queue and potentially block or even deadlock the system
[self unfreezeParseQueue];

[self unfreezeSendQueue];
}
else
DDLogInfo(@"Not reloading UNfrozen account %@, already connected", self.accountNo);

//this must be inside the dispatch async, because it will dispatch *SYNC* to the receive queue and potentially block or even deadlock the system
[self unfreezeParseQueue];

[self unfreezeSendQueue];
}
}];
unfreezeOperation.queuePriority = NSOperationQueuePriorityVeryHigh; //make sure this will become the first operation executed once unfrozen
[self->_receiveQueue addOperations: @[unfreezeOperation] waitUntilFinished:NO];

//unfreeze receive queue and execute block added above
self->_receiveQueue.suspended = NO;
}];
unfreezeOperation.queuePriority = NSOperationQueuePriorityVeryHigh; //make sure this will become the first operation executed once unfrozen
[self->_receiveQueue addOperations: @[unfreezeOperation] waitUntilFinished:NO];

//unfreeze receive queue and execute block added above
self->_receiveQueue.suspended = NO;
}
}

-(void) reinitLoginTimer
Expand Down Expand Up @@ -1528,10 +1534,13 @@ -(void) sendPing:(double) timeout
return;
}

//this sometimes races with a disconnect triggered by app freeze, but we can't remove this because in some (rare?)
//cases triggering a reconnect if sendPing was called by the connectivity monitor is the only way to get connected again
//--> use big reconnect time here to not race (freezing the app and triggering the ping after waking up again is okay)
if(self.accountState<kStateReconnecting)
{
DDLogInfo(@"ping calling reconnect");
[self reconnect:0];
[self reconnect:CONNECT_TIMEOUT];
return;
}

Expand Down
20 changes: 20 additions & 0 deletions appstore_metadata/zh-Hans/description.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
现在正是加入 XMPP 的最佳时机——这个自由开放的公共聊天网络不受任何人控制或拥有。Monal 提供快速便捷的 XMPP 使用体验:只需下载应用、登录或注册,几分钟内即可畅聊。其界面操作与主流应用无异,无需“学习 XMPP”,甚至不必了解其原理。

核心特性:
- 开源架构
- 无广告!严守隐私原则。绝不向服务器回传数据,无软件“度量”机制
- 不读取任何个人信息
- 通过直连服务器,密码及其他信息永不泄露给第三方
- OMEMO 加密聊天
- 支持需 VPN 连接的企业 XMPP 服务器
- 群组聊天支持多用户聊天(MUC)
- 音视频通话功能

实现多项提升移动通信体验的 XMPP 扩展:
- XEP-0357:推送通知
- XEP-0280:消息副本同步技术
- XEP-0198:流管理技术实现快速重连
- XEP-0199:XMPP Ping 连接维护协议
- XEP-0313:消息归档管理,支持下载聊天记录
- XEP-0352:客户端状态指示,显著降低功耗
- XEP-0363:HTTP 文件上传,支持对话中发送图片
1 change: 1 addition & 0 deletions appstore_metadata/zh-Hans/keywords.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
xmpp, jabber, 聊天, 即时通讯, 通讯, ejabberd, prosody, OMEMO
1 change: 1 addition & 0 deletions appstore_metadata/zh-Hans/marketing_url.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
https://monal-im.org/
1 change: 1 addition & 0 deletions appstore_metadata/zh-Hans/privacy_url.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
https://monal-im.org/privacy/
1 change: 1 addition & 0 deletions appstore_metadata/zh-Hans/support_url.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
https://monal-im.org/support/
5 changes: 5 additions & 0 deletions appstore_quicksy_metadata/zh-Hans/description.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Quicksy 是热门 XMPP 客户端 Monal 的衍生版本,具备自动联系人发现功能。

您只需使用手机号码注册,Quicksy 便会根据通讯录中的号码自动为您推荐潜在联系人。其底层架构是功能完整的 XMPP 客户端,支持您与任何公开联合服务器上的用户进行通信。同样地,外部用户只需在通讯录添加 [email protected] 即可联系 Quicksy 用户。

除联系人同步功能外,用户界面刻意保持与 Monal 高度一致。此设计使用户未来迁移至 Monal 时无需重新学习操作逻辑。
1 change: 1 addition & 0 deletions appstore_quicksy_metadata/zh-Hans/keywords.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
xmpp, jabber, 聊天, 即时通讯, 通讯, ejabberd, prosody, OMEMO
1 change: 1 addition & 0 deletions appstore_quicksy_metadata/zh-Hans/marketing_url.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
https://quicksy.im/
1 change: 1 addition & 0 deletions appstore_quicksy_metadata/zh-Hans/privacy_url.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
https://quicksy.im/privacy.htm
1 change: 1 addition & 0 deletions appstore_quicksy_metadata/zh-Hans/support_url.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
https://quicksy.im/