990 lines
43 KiB
Mathematica
990 lines
43 KiB
Mathematica
|
//
|
||
|
// MLPubSub.m
|
||
|
// monalxmpp
|
||
|
//
|
||
|
// Created by Thilo Molitor on 20.09.20.
|
||
|
// Copyright © 2020 Monal.im. All rights reserved.
|
||
|
//
|
||
|
|
||
|
#import "MLPubSub.h"
|
||
|
#import "MLHandler.h"
|
||
|
#import "xmpp.h"
|
||
|
#import "MLXMLNode.h"
|
||
|
#import "XMPPDataForm.h"
|
||
|
#import "XMPPStanza.h"
|
||
|
#import "XMPPIQ.h"
|
||
|
#import "XMPPMessage.h"
|
||
|
#import "HelperTools.h"
|
||
|
|
||
|
#define CURRENT_PUBSUB_DATA_VERSION @6
|
||
|
|
||
|
@interface MLPubSub ()
|
||
|
{
|
||
|
__weak xmpp* _account;
|
||
|
NSMutableDictionary* _registeredHandlers;
|
||
|
NSMutableArray* _queue;
|
||
|
}
|
||
|
@end
|
||
|
|
||
|
@implementation MLPubSub
|
||
|
|
||
|
static NSDictionary* _defaultOptions;
|
||
|
|
||
|
+(void) initialize
|
||
|
{
|
||
|
//TODO: wait for servers to support pubsub#publish_node_full and set it at least for bookmarks2
|
||
|
_defaultOptions = @{
|
||
|
@"pubsub#notify_retract": @"true",
|
||
|
@"pubsub#notify_delete": @"true"
|
||
|
};
|
||
|
}
|
||
|
|
||
|
-(id) initWithAccount:(xmpp*) account
|
||
|
{
|
||
|
self = [super init];
|
||
|
_account = account;
|
||
|
_registeredHandlers = [NSMutableDictionary new];
|
||
|
_queue = [NSMutableArray new];
|
||
|
//retry our pubsub operation as soon as possible
|
||
|
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleAccountDiscoReady:) name:kMonalAccountDiscoDone object:nil];
|
||
|
return self;
|
||
|
}
|
||
|
|
||
|
-(void) registerForNode:(NSString*) node withHandler:(MLHandler*) handler
|
||
|
{
|
||
|
DDLogInfo(@"Adding PEP handler %@ for node %@", handler, node);
|
||
|
@synchronized(_registeredHandlers) {
|
||
|
if(!_registeredHandlers[node])
|
||
|
_registeredHandlers[node] = [NSMutableDictionary new];
|
||
|
_registeredHandlers[node][handler.id] = handler;
|
||
|
[_account setPubSubNotificationsForNodes:[_registeredHandlers allKeys] persistState:NO];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
-(void) unregisterHandler:(MLHandler*) handler forNode:(NSString*) node
|
||
|
{
|
||
|
DDLogInfo(@"Removing PEP handler %@ for node %@", handler, node);
|
||
|
@synchronized(_registeredHandlers) {
|
||
|
if(!_registeredHandlers[node])
|
||
|
return;
|
||
|
[_registeredHandlers[node] removeObjectForKey:handler.id];
|
||
|
[_account setPubSubNotificationsForNodes:[_registeredHandlers allKeys] persistState:NO];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
-(void) handleAccountDiscoReady:(NSNotification*) notification
|
||
|
{
|
||
|
if(_account.accountID.intValue != ((xmpp*)notification.object).accountID.intValue)
|
||
|
return;
|
||
|
//we clear the queue so that the invalidation handlers can't get called twice:
|
||
|
//once as invalidation of the queued operation handler and once as the invalidation of an iq handler of this operation
|
||
|
//note: these are two different handler object, hence the double invalidation would *not* be catched by the handler framework!
|
||
|
NSArray* queue;
|
||
|
@synchronized(_queue) {
|
||
|
queue = [_queue copy];
|
||
|
_queue = [NSMutableArray new];
|
||
|
}
|
||
|
for(MLHandler* handler in queue)
|
||
|
$call(handler, $ID(account, _account));
|
||
|
}
|
||
|
|
||
|
$$instance_handler(queuedFetchNodeHandler, account.pubsub, $$ID(xmpp*, account), $$ID(NSString*, node), $$ID(NSString*, jid), $_ID(NSArray*, itemsList), $$HANDLER(handler))
|
||
|
[self fetchNode:node from:jid withItemsList:itemsList andHandler:handler];
|
||
|
$$
|
||
|
-(void) fetchNode:(NSString*) node from:(NSString*) jid withItemsList:(NSArray* _Nullable) itemsList andHandler:(MLHandler*) handler
|
||
|
{
|
||
|
DDLogInfo(@"Fetching node '%@' at jid '%@' using callback %@...", node, jid, handler);
|
||
|
xmpp* account = _account;
|
||
|
|
||
|
if(account.accountState < kStateBound || !account.connectionProperties.accountDiscoDone)
|
||
|
{
|
||
|
DDLogWarn(@"Queueing pubsub call until account disco is resolved...");
|
||
|
[_queue addObject:$newHandlerWithInvalidation(self, queuedFetchNodeHandler, handleFetchInvalidation, $ID(node), $ID(jid), $ID(itemsList), $HANDLER(handler))];
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if(!account.connectionProperties.supportsPubSub)
|
||
|
{
|
||
|
DDLogWarn(@"Pubsub not supported, ignoring this call for node '%@' and jid '%@'!", node, jid);
|
||
|
return;
|
||
|
}
|
||
|
if(jid != nil)
|
||
|
{
|
||
|
NSDictionary* splitJid = [HelperTools splitJid:jid];
|
||
|
MLAssert(splitJid[@"resource"] == nil, @"Jid MUST be a bare jid, not full jid!");
|
||
|
}
|
||
|
|
||
|
//build list of items to query (empty list means all items)
|
||
|
if(!itemsList)
|
||
|
itemsList = @[];
|
||
|
NSMutableArray* queryItems = [NSMutableArray new];
|
||
|
for(NSString* itemId in itemsList)
|
||
|
[queryItems addObject:[[MLXMLNode alloc] initWithElement:@"item" withAttributes:@{@"id": itemId} andChildren:@[] andData:nil]];
|
||
|
|
||
|
//build query
|
||
|
XMPPIQ* query = [[XMPPIQ alloc] initWithType:kiqGetType to:jid];
|
||
|
[query addChildNode:[[MLXMLNode alloc] initWithElement:@"pubsub" andNamespace:@"http://jabber.org/protocol/pubsub" withAttributes:@{} andChildren:@[
|
||
|
[[MLXMLNode alloc] initWithElement:@"items" withAttributes:@{@"node": node} andChildren:queryItems andData:nil]
|
||
|
] andData:nil]];
|
||
|
|
||
|
[account sendIq:query withHandler:$newHandlerWithInvalidation(self, handleFetch, handleFetchInvalidation,
|
||
|
$ID(node),
|
||
|
$ID(jid),
|
||
|
$ID(queryItems),
|
||
|
$ID(data, [NSMutableDictionary new]),
|
||
|
$HANDLER(handler),
|
||
|
)];
|
||
|
}
|
||
|
|
||
|
$$instance_handler(queuedSubscribeToNodeHandler, account.pubsub, $$ID(xmpp*, account), $$ID(NSString*, node), $$ID(NSString*, jid), $$HANDLER(handler))
|
||
|
[self subscribeToNode:node onJid:jid withHandler:handler];
|
||
|
$$
|
||
|
-(void) subscribeToNode:(NSString*) node onJid:(NSString*) jid withHandler:(MLHandler*) handler
|
||
|
{
|
||
|
DDLogInfo(@"Subscribing to node '%@' at jid '%@' using callback %@...", node, jid, handler);
|
||
|
xmpp* account = _account;
|
||
|
|
||
|
if(account.accountState < kStateBound || !account.connectionProperties.accountDiscoDone)
|
||
|
{
|
||
|
DDLogWarn(@"Queueing pubsub call until account disco is resolved...");
|
||
|
[_queue addObject:$newHandlerWithInvalidation(self, queuedSubscribeToNodeHandler, handleSubscribeInvalidation, $ID(node), $ID(jid), $HANDLER(handler))];
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if(!account.connectionProperties.supportsPubSub)
|
||
|
{
|
||
|
DDLogWarn(@"Pubsub not supported, ignoring this call for node '%@' and jid '%@'!", node, jid);
|
||
|
return;
|
||
|
}
|
||
|
if(jid != nil)
|
||
|
{
|
||
|
NSDictionary* splitJid = [HelperTools splitJid:jid];
|
||
|
MLAssert(splitJid[@"resource"] == nil, @"Jid MUST be a bare jid, not full jid!");
|
||
|
}
|
||
|
|
||
|
//build subscription request
|
||
|
XMPPIQ* query = [[XMPPIQ alloc] initWithType:kiqSetType to:jid];
|
||
|
[query addChildNode:[[MLXMLNode alloc] initWithElement:@"pubsub" andNamespace:@"http://jabber.org/protocol/pubsub" withAttributes:@{} andChildren:@[
|
||
|
[[MLXMLNode alloc] initWithElement:@"subscribe" withAttributes:@{
|
||
|
@"node": node,
|
||
|
@"jid": account.connectionProperties.identity.jid,
|
||
|
} andChildren:@[] andData:nil]
|
||
|
] andData:nil]];
|
||
|
|
||
|
[account sendIq:query withHandler:$newHandlerWithInvalidation(self, handleSubscribe, handleSubscribeInvalidation,
|
||
|
$ID(node),
|
||
|
$ID(jid),
|
||
|
$HANDLER(handler),
|
||
|
)];
|
||
|
}
|
||
|
|
||
|
$$instance_handler(queuedUnsubscribeFromNodeHandler, account.pubsub, $$ID(xmpp*, account), $$ID(NSString*, node), $$ID(NSString*, jid), $_HANDLER(handler))
|
||
|
[self unsubscribeFromNode:node forJid:jid withHandler:handler];
|
||
|
$$
|
||
|
-(void) unsubscribeFromNode:(NSString*) node forJid:(NSString*) jid withHandler:(MLHandler* _Nullable) handler
|
||
|
{
|
||
|
DDLogInfo(@"Unsubscribing from node '%@' at jid '%@' using callback %@...", node, jid, handler);
|
||
|
|
||
|
if(!_account.connectionProperties.accountDiscoDone)
|
||
|
{
|
||
|
DDLogWarn(@"Queueing pubsub call until account disco is resolved...");
|
||
|
[_queue addObject:$newHandlerWithInvalidation(self, queuedUnsubscribeFromNodeHandler, handleUnsubscribeInvalidation, $ID(node), $ID(jid), $HANDLER(handler))];
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if(!_account.connectionProperties.supportsPubSub)
|
||
|
{
|
||
|
DDLogWarn(@"Pubsub not supported, ignoring this call for node '%@' and jid '%@'!", node, jid);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if(jid != nil)
|
||
|
{
|
||
|
NSDictionary* splitJid = [HelperTools splitJid:jid];
|
||
|
MLAssert(splitJid[@"resource"] == nil, @"Jid MUST be a bare jid, not full jid!");
|
||
|
}
|
||
|
|
||
|
//build subscription request
|
||
|
XMPPIQ* query = [[XMPPIQ alloc] initWithType:kiqSetType to:jid];
|
||
|
[query addChildNode:[[MLXMLNode alloc] initWithElement:@"pubsub" andNamespace:@"http://jabber.org/protocol/pubsub" withAttributes:@{} andChildren:@[
|
||
|
[[MLXMLNode alloc] initWithElement:@"unsubscribe" withAttributes:@{
|
||
|
@"node": node,
|
||
|
@"jid": _account.connectionProperties.identity.jid,
|
||
|
} andChildren:@[] andData:nil]
|
||
|
] andData:nil]];
|
||
|
|
||
|
[_account sendIq:query withHandler:$newHandlerWithInvalidation(self, handleUnsubscribe, handleUnsubscribeInvalidation,
|
||
|
$ID(node),
|
||
|
$ID(jid),
|
||
|
$HANDLER(handler),
|
||
|
)];
|
||
|
}
|
||
|
|
||
|
$$instance_handler(queuedConfigureNodeHandler, account.pubsub, $$ID(xmpp*, account), $$ID(NSString*, node), $$ID(NSDictionary*, configOptions), $_HANDLER(handler))
|
||
|
[self configureNode:node withConfigOptions:configOptions andHandler:handler];
|
||
|
$$
|
||
|
-(void) configureNode:(NSString*) node withConfigOptions:(NSDictionary*) configOptions andHandler:(MLHandler* _Nullable) handler
|
||
|
{
|
||
|
xmpp* account = _account;
|
||
|
|
||
|
if(account.accountState < kStateBound || !account.connectionProperties.accountDiscoDone)
|
||
|
{
|
||
|
DDLogWarn(@"Queueing pubsub call until account disco is resolved...");
|
||
|
[_queue addObject:$newHandlerWithInvalidation(self, queuedConfigureNodeHandler, handleConfigFormResultInvalidation, $ID(node), $ID(configOptions), $HANDLER(handler))];
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if(!account.connectionProperties.supportsPubSub)
|
||
|
{
|
||
|
DDLogWarn(@"Pubsub not supported, ignoring this call for node '%@'!", node);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
XMPPIQ* query = [[XMPPIQ alloc] initWithType:kiqGetType];
|
||
|
[query addChildNode:[[MLXMLNode alloc] initWithElement:@"pubsub" andNamespace:@"http://jabber.org/protocol/pubsub#owner" withAttributes:@{} andChildren:@[
|
||
|
[[MLXMLNode alloc] initWithElement:@"configure" withAttributes:@{@"node": node} andChildren:@[] andData:nil]
|
||
|
] andData:nil]];
|
||
|
[account sendIq:query withHandler:$newHandlerWithInvalidation(self, handleConfigFormResult, handleConfigFormResultInvalidation,
|
||
|
$ID(node),
|
||
|
$ID(configOptions),
|
||
|
$HANDLER(handler)
|
||
|
)];
|
||
|
}
|
||
|
|
||
|
-(void) publishItem:(MLXMLNode*) item onNode:(NSString*) node
|
||
|
{
|
||
|
[self publishItem:item onNode:node withConfigOptions:nil andHandler:nil];
|
||
|
}
|
||
|
|
||
|
-(void) publishItem:(MLXMLNode*) item onNode:(NSString*) node withHandler:(MLHandler* _Nullable) handler
|
||
|
{
|
||
|
[self publishItem:item onNode:node withConfigOptions:nil andHandler:handler];
|
||
|
}
|
||
|
|
||
|
-(void) publishItem:(MLXMLNode*) item onNode:(NSString*) node withConfigOptions:(NSDictionary* _Nullable) configOptions
|
||
|
{
|
||
|
[self publishItem:item onNode:node withConfigOptions:configOptions andHandler:nil];
|
||
|
}
|
||
|
|
||
|
$$instance_handler(queuedPublishItemHandler, account.pubsub, $$ID(xmpp*, account), $$ID(MLXMLNode*, item), $$ID(NSString*, node), $_ID(NSDictionary*, configOptions), $_HANDLER(handler))
|
||
|
[self publishItem:item onNode:node withConfigOptions:configOptions andHandler:handler];
|
||
|
$$
|
||
|
-(void) publishItem:(MLXMLNode*) item onNode:(NSString*) node withConfigOptions:(NSDictionary* _Nullable) configOptions andHandler:(MLHandler* _Nullable) handler
|
||
|
{
|
||
|
if(!_account.connectionProperties.accountDiscoDone)
|
||
|
{
|
||
|
DDLogWarn(@"Queueing pubsub call until account disco is resolved...");
|
||
|
[_queue addObject:$newHandlerWithInvalidation(self, queuedPublishItemHandler, handlePublishResultInvalidation, $ID(item), $ID(node), $ID(configOptions), $HANDLER(handler))];
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if(!_account.connectionProperties.supportsPubSub)
|
||
|
{
|
||
|
DDLogWarn(@"Pubsub not supported, ignoring this call for node '%@'!", node);
|
||
|
return;
|
||
|
}
|
||
|
if(!configOptions)
|
||
|
configOptions = @{};
|
||
|
|
||
|
//update config options with our own defaults if not already present
|
||
|
configOptions = [self copyDefaultNodeOptions:_defaultOptions forConfigForm:nil into:configOptions];
|
||
|
|
||
|
[self internalPublishItem:item onNode:node withConfigOptions:configOptions andHandler:handler andIsRetry:NO];
|
||
|
}
|
||
|
|
||
|
-(void) retractItemWithId:(NSString*) itemId onNode:(NSString*) node
|
||
|
{
|
||
|
[self retractItemWithId:itemId onNode:node andHandler:nil];
|
||
|
}
|
||
|
|
||
|
$$instance_handler(queuedRetractItemWithIdHandler, account.pubsub, $$ID(xmpp*, account), $$ID(NSString*, itemId), $$ID(NSString*, node), $_HANDLER(handler))
|
||
|
[self retractItemWithId:itemId onNode:node andHandler:handler];
|
||
|
$$
|
||
|
-(void) retractItemWithId:(NSString*) itemId onNode:(NSString*) node andHandler:(MLHandler* _Nullable) handler
|
||
|
{
|
||
|
xmpp* account = _account;
|
||
|
|
||
|
if(account.accountState < kStateBound || !account.connectionProperties.accountDiscoDone)
|
||
|
{
|
||
|
DDLogWarn(@"Queueing pubsub call until account disco is resolved...");
|
||
|
[_queue addObject:$newHandlerWithInvalidation(self, queuedRetractItemWithIdHandler, handleRetractResultInvalidation, $ID(itemId), $ID(node), $HANDLER(handler))];
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if(!account.connectionProperties.supportsPubSub)
|
||
|
{
|
||
|
DDLogWarn(@"Pubsub not supported, ignoring this call for node '%@'!", node);
|
||
|
return;
|
||
|
}
|
||
|
DDLogDebug(@"Retracting item '%@' on node '%@'", itemId, node);
|
||
|
MLXMLNode* item = [[MLXMLNode alloc] initWithElement:@"item" withAttributes:@{@"id": itemId} andChildren:@[] andData:nil];
|
||
|
XMPPIQ* query = [[XMPPIQ alloc] initWithType:kiqSetType];
|
||
|
[query addChildNode:[[MLXMLNode alloc] initWithElement:@"pubsub" andNamespace:@"http://jabber.org/protocol/pubsub" withAttributes:@{} andChildren:@[
|
||
|
[[MLXMLNode alloc] initWithElement:@"retract" withAttributes:@{@"node": node, @"notify": @"true"} andChildren:@[item] andData:nil]
|
||
|
] andData:nil]];
|
||
|
[account sendIq:query withHandler:$newHandlerWithInvalidation(self, handleRetractResult, handleRetractResultInvalidation,
|
||
|
$ID(node),
|
||
|
$ID(itemId),
|
||
|
$HANDLER(handler)
|
||
|
)];
|
||
|
}
|
||
|
|
||
|
-(void) purgeNode:(NSString*) node
|
||
|
{
|
||
|
[self purgeNode:node andHandler:nil];
|
||
|
}
|
||
|
|
||
|
$$instance_handler(queuedPurgeNodeNodeHandler, account.pubsub, $$ID(xmpp*, account), $$ID(NSString*, node), $_HANDLER(handler))
|
||
|
[self purgeNode:node andHandler:handler];
|
||
|
$$
|
||
|
-(void) purgeNode:(NSString*) node andHandler:(MLHandler* _Nullable) handler
|
||
|
{
|
||
|
xmpp* account = _account;
|
||
|
|
||
|
if(account.accountState < kStateBound || !account.connectionProperties.accountDiscoDone)
|
||
|
{
|
||
|
DDLogWarn(@"Queueing pubsub call until account disco is resolved...");
|
||
|
[_queue addObject:$newHandlerWithInvalidation(self, queuedPurgeNodeNodeHandler, handlePurgeOrDeleteResultInvalidation, $ID(node), $HANDLER(handler))];
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if(!account.connectionProperties.supportsPubSub)
|
||
|
{
|
||
|
DDLogWarn(@"Pubsub not supported, ignoring this call for node '%@'!", node);
|
||
|
return;
|
||
|
}
|
||
|
XMPPIQ* query = [[XMPPIQ alloc] initWithType:kiqSetType];
|
||
|
[query addChildNode:[[MLXMLNode alloc] initWithElement:@"pubsub" andNamespace:@"http://jabber.org/protocol/pubsub#owner" withAttributes:@{} andChildren:@[
|
||
|
[[MLXMLNode alloc] initWithElement:@"purge" withAttributes:@{@"node": node} andChildren:@[] andData:nil]
|
||
|
] andData:nil]];
|
||
|
[account sendIq:query withHandler:$newHandlerWithInvalidation(self, handlePurgeOrDeleteResult, handlePurgeOrDeleteResultInvalidation,
|
||
|
$ID(node),
|
||
|
$HANDLER(handler)
|
||
|
)];
|
||
|
}
|
||
|
|
||
|
-(void) deleteNode:(NSString*) node
|
||
|
{
|
||
|
[self deleteNode:node andHandler:nil];
|
||
|
}
|
||
|
|
||
|
$$instance_handler(queuedDeleteNodeHandler, account.pubsub, $$ID(xmpp*, account), $$ID(NSString*, node), $_HANDLER(handler))
|
||
|
[self deleteNode:node andHandler:handler];
|
||
|
$$
|
||
|
-(void) deleteNode:(NSString*) node andHandler:(MLHandler* _Nullable) handler
|
||
|
{
|
||
|
xmpp* account = _account;
|
||
|
|
||
|
if(account.accountState < kStateBound || !account.connectionProperties.accountDiscoDone)
|
||
|
{
|
||
|
DDLogWarn(@"Queueing pubsub call until account disco is resolved...");
|
||
|
[_queue addObject:$newHandlerWithInvalidation(self, queuedDeleteNodeHandler, handlePurgeOrDeleteResultInvalidation, $ID(node), $HANDLER(handler))];
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if(!account.connectionProperties.supportsPubSub)
|
||
|
{
|
||
|
DDLogWarn(@"Pubsub not supported, ignoring this call for node '%@'!", node);
|
||
|
return;
|
||
|
}
|
||
|
XMPPIQ* query = [[XMPPIQ alloc] initWithType:kiqSetType];
|
||
|
[query addChildNode:[[MLXMLNode alloc] initWithElement:@"pubsub" andNamespace:@"http://jabber.org/protocol/pubsub#owner" withAttributes:@{} andChildren:@[
|
||
|
[[MLXMLNode alloc] initWithElement:@"delete" withAttributes:@{@"node": node} andChildren:@[] andData:nil]
|
||
|
] andData:nil]];
|
||
|
[account sendIq:query withHandler:$newHandlerWithInvalidation(self, handlePurgeOrDeleteResult, handlePurgeOrDeleteResultInvalidation,
|
||
|
$ID(node),
|
||
|
$HANDLER(handler)
|
||
|
)];
|
||
|
}
|
||
|
|
||
|
//*** framework methods below
|
||
|
|
||
|
-(NSDictionary*) getInternalData
|
||
|
{
|
||
|
@synchronized(_queue) {
|
||
|
return @{
|
||
|
@"version": CURRENT_PUBSUB_DATA_VERSION,
|
||
|
@"queue": [_queue copy],
|
||
|
};
|
||
|
}
|
||
|
}
|
||
|
|
||
|
-(void) setInternalData:(NSDictionary*) data
|
||
|
{
|
||
|
DDLogDebug(@"Loading internal pubsub data");
|
||
|
@synchronized(_queue) {
|
||
|
if(!data[@"version"] || ![data[@"version"] isEqualToNumber:CURRENT_PUBSUB_DATA_VERSION])
|
||
|
return; //ignore old data
|
||
|
_queue = [data[@"queue"] mutableCopy];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
-(void) invalidateQueue
|
||
|
{
|
||
|
//we clear the queue so that the invalidation handlers can't get called twice:
|
||
|
//once as invalidation of the queued operation handler and once as the invalidation of an iq handler of this operation
|
||
|
//note: these are two different handler object, hence the double invalidation would *not* be catched by the handler framework!
|
||
|
NSArray* queue;
|
||
|
@synchronized(_queue) {
|
||
|
queue = [_queue copy];
|
||
|
_queue = [NSMutableArray new];
|
||
|
}
|
||
|
for(MLHandler* handler in queue)
|
||
|
$invalidate(handler, $ID(account, _account));
|
||
|
}
|
||
|
|
||
|
-(void) handleHeadlineMessage:(XMPPMessage*) messageNode
|
||
|
{
|
||
|
NSString* node = [messageNode findFirst:@"/<type=headline>/{http://jabber.org/protocol/pubsub#event}event/{*}*@node"];
|
||
|
if(!node)
|
||
|
{
|
||
|
DDLogWarn(@"Got pubsub data without node attribute!");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if(!_account.connectionProperties.supportsPubSub)
|
||
|
{
|
||
|
DDLogError(@"Pubsub not supported, ignoring this call for headline message (THIS SHOULD NEVER HAPPEN): %@", messageNode);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
DDLogDebug(@"Handling pubsub data for node '%@'", node);
|
||
|
|
||
|
//handle node purge
|
||
|
if([messageNode check:@"/<type=headline>/{http://jabber.org/protocol/pubsub#event}event/purge"])
|
||
|
{
|
||
|
DDLogDebug(@"Handling purge");
|
||
|
[self callHandlersForNode:node andJid:messageNode.fromUser withType:@"purge" andData:nil];
|
||
|
return; //we are done here (no items element for purge events)
|
||
|
}
|
||
|
|
||
|
//handle node delete
|
||
|
if([messageNode check:@"/<type=headline>/{http://jabber.org/protocol/pubsub#event}event/delete"])
|
||
|
{
|
||
|
DDLogDebug(@"Handling delete");
|
||
|
[self callHandlersForNode:node andJid:messageNode.fromUser withType:@"delete" andData:nil];
|
||
|
return; //we are done here (no items element for delete events)
|
||
|
}
|
||
|
|
||
|
//handle published items
|
||
|
MLXMLNode* items = [messageNode findFirst:@"/<type=headline>/{http://jabber.org/protocol/pubsub#event}event/items"];
|
||
|
if(!items)
|
||
|
{
|
||
|
DDLogWarn(@"Got pubsub event data without items node, ignoring!");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
//handle item delete
|
||
|
if([items check:@"retract"])
|
||
|
{
|
||
|
DDLogDebug(@"Handling retract");
|
||
|
NSMutableDictionary* data = [self handleRetraction:items fromJid:messageNode.fromUser withData:[NSMutableDictionary new]];
|
||
|
if(data) //ignore unexpected/wrong data
|
||
|
[self callHandlersForNode:node andJid:messageNode.fromUser withType:@"retract" andData:data];
|
||
|
}
|
||
|
|
||
|
//handle xep-0060 6.5.6 (check if payload is included or if it has to be fetched separately)
|
||
|
if([items check:@"item/{*}*"])
|
||
|
{
|
||
|
DDLogDebug(@"Handling publish");
|
||
|
NSMutableDictionary* data = [self handleItems:items fromJid:messageNode.fromUser withData:[NSMutableDictionary new]];
|
||
|
if(data) //ignore unexpected/wrong data
|
||
|
[self callHandlersForNode:node andJid:messageNode.fromUser withType:@"publish" andData:data];
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
DDLogDebug(@"Handling truncated publish");
|
||
|
[self fetchNode:node from:messageNode.fromUser withItemsList:[items find:@"item@id"] andHandler:$newHandler(self, handleInternalFetch, $ID(node))];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//*** internal methods below
|
||
|
|
||
|
-(void) internalPublishItem:(MLXMLNode*) item onNode:(NSString*) node withConfigOptions:(NSDictionary*) configOptions andHandler:(MLHandler* _Nullable) handler andIsRetry:(BOOL) is_retry
|
||
|
{
|
||
|
DDLogDebug(@"Publishing item on node '%@': %@", node, item);
|
||
|
XMPPIQ* query = [[XMPPIQ alloc] initWithType:kiqSetType];
|
||
|
[query addChildNode:[[MLXMLNode alloc] initWithElement:@"pubsub" andNamespace:@"http://jabber.org/protocol/pubsub" withAttributes:@{} andChildren:@[
|
||
|
[[MLXMLNode alloc] initWithElement:@"publish" withAttributes:@{@"node": node} andChildren:@[item] andData:nil]
|
||
|
] andData:nil]];
|
||
|
//only add publish-options if present
|
||
|
if([configOptions count] > 0)
|
||
|
[(MLXMLNode*)[query findFirst:@"{http://jabber.org/protocol/pubsub}pubsub"] addChildNode:[[MLXMLNode alloc] initWithElement:@"publish-options" withAttributes:@{} andChildren:@[
|
||
|
[[XMPPDataForm alloc] initWithType:@"submit" formType:@"http://jabber.org/protocol/pubsub#publish-options" andDictionary:configOptions]
|
||
|
] andData:nil]];
|
||
|
[_account sendIq:query withHandler:$newHandlerWithInvalidation(self, handlePublishResult, handlePublishResultInvalidation,
|
||
|
$ID(item),
|
||
|
$ID(node),
|
||
|
$ID(configOptions),
|
||
|
$HANDLER(handler),
|
||
|
$BOOL(is_retry)
|
||
|
)];
|
||
|
}
|
||
|
|
||
|
//NOTE: this will be called for iq *or* message stanzas carrying pubsub data.
|
||
|
-(NSMutableDictionary*) handleItems:(MLXMLNode* _Nullable) items fromJid:(NSString* _Nullable) jid withData:(NSMutableDictionary*) data
|
||
|
{
|
||
|
if(!items)
|
||
|
{
|
||
|
DDLogWarn(@"Got pubsub data without items node!");
|
||
|
return nil;
|
||
|
}
|
||
|
|
||
|
NSString* node = [items findFirst:@"/@node"];
|
||
|
if(!node)
|
||
|
{
|
||
|
DDLogWarn(@"Got pubsub data without node attribute!");
|
||
|
return nil;
|
||
|
}
|
||
|
DDLogDebug(@"Processing pubsub data from jid '%@' for node '%@'", jid, node);
|
||
|
for(MLXMLNode* item in [items find:@"item"])
|
||
|
{
|
||
|
NSString* itemId = [item findFirst:@"/@id"];
|
||
|
if(!itemId)
|
||
|
itemId = @"";
|
||
|
data[itemId] = [item copy]; //make a copy to make sure the original iq stanza won't be changed by a handler modifying the items
|
||
|
}
|
||
|
return data;
|
||
|
}
|
||
|
|
||
|
//NOTE: this will be called for message stanzas carrying pubsub data.
|
||
|
-(NSMutableDictionary*) handleRetraction:(MLXMLNode* _Nullable) items fromJid:(NSString* _Nullable) jid withData:(NSMutableDictionary*) data
|
||
|
{
|
||
|
if(!items)
|
||
|
{
|
||
|
DDLogWarn(@"Got pubsub retraction without items node!");
|
||
|
return nil;
|
||
|
}
|
||
|
|
||
|
NSString* node = [items findFirst:@"/@node"];
|
||
|
if(!node)
|
||
|
{
|
||
|
DDLogWarn(@"Got pubsub data without node attribute!");
|
||
|
return nil;
|
||
|
}
|
||
|
DDLogDebug(@"Removing some pubsub items from jid '%@' for node '%@'", jid, node);
|
||
|
for(MLXMLNode* item in [items find:@"retract"])
|
||
|
{
|
||
|
NSString* itemId = [item findFirst:@"/@id"];
|
||
|
if(!itemId)
|
||
|
itemId = @"";
|
||
|
DDLogDebug(@"Deleting pubsub item with id '%@' from jid '%@' for node '%@'", itemId, jid, node);
|
||
|
data[itemId] = @YES;
|
||
|
}
|
||
|
return data;
|
||
|
}
|
||
|
|
||
|
-(void) callHandlersForNode:(NSString*) node andJid:(NSString*) jid withType:(NSString*) type andData:(NSDictionary*) data
|
||
|
{
|
||
|
xmpp* account = _account;
|
||
|
DDLogInfo(@"Calling pubsub handlers for node '%@' (and jid '%@')", node, jid);
|
||
|
NSDictionary* handlers;
|
||
|
@synchronized(_registeredHandlers) {
|
||
|
handlers = [[NSDictionary alloc] initWithDictionary:_registeredHandlers[node] copyItems:YES];
|
||
|
}
|
||
|
for(NSString* handlerId in handlers)
|
||
|
$call(handlers[handlerId],
|
||
|
$ID(account),
|
||
|
$ID(node),
|
||
|
$ID(jid),
|
||
|
$ID(type),
|
||
|
$ID(data)
|
||
|
);
|
||
|
DDLogDebug(@"All pubsub handlers called");
|
||
|
}
|
||
|
|
||
|
-(NSDictionary*) copyDefaultNodeOptions:(NSDictionary*) defaultOptions forConfigForm:(XMPPDataForm* _Nullable) configForm into:(NSDictionary*) configOptions
|
||
|
{
|
||
|
NSMutableDictionary* retval = [configOptions mutableCopy];
|
||
|
for(NSString* option in defaultOptions)
|
||
|
if((configForm == nil || configForm[option] != nil) && retval[option] == nil)
|
||
|
retval[option] = defaultOptions[option];
|
||
|
return retval;
|
||
|
}
|
||
|
|
||
|
$$instance_handler(handleSubscribeInvalidation, account.pubsub, $$ID(xmpp*, account), $_ID(NSString*, reason), $$ID(NSString*, node), $$ID(NSString*, jid), $$HANDLER(handler))
|
||
|
//invalidate our user handler
|
||
|
$invalidate(handler,
|
||
|
$ID(account),
|
||
|
$BOOL(success, NO),
|
||
|
$ID(node),
|
||
|
$ID(jid),
|
||
|
$ID(reason),
|
||
|
);
|
||
|
$$
|
||
|
|
||
|
$$instance_handler(handleSubscribe, account.pubsub, $$ID(xmpp*, account), $$ID(XMPPIQ*, iqNode), $$ID(NSString*, node), $$ID(NSString*, jid), $$HANDLER(handler))
|
||
|
if([iqNode check:@"/<type=error>"])
|
||
|
{
|
||
|
DDLogError(@"Got error iq for pubsub subscribe request: %@", iqNode);
|
||
|
//call subscribe callback (if given) with error iq node
|
||
|
$call(handler,
|
||
|
$ID(account),
|
||
|
$BOOL(success, NO),
|
||
|
$ID(node),
|
||
|
$ID(jid, iqNode.fromUser),
|
||
|
$ID(errorIq, iqNode)
|
||
|
);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if([iqNode check:@"{http://jabber.org/protocol/pubsub}pubsub/subscription<node=%@><jid=%@><subscription=subscribed>", node, account.connectionProperties.identity.jid])
|
||
|
{
|
||
|
DDLogDebug(@"Successfully subscribed to node '%@' on jid '%@' for '%@'...", node, iqNode.fromUser, account.connectionProperties.identity.jid);
|
||
|
|
||
|
//call subscribe callback (if given)
|
||
|
$call(handler,
|
||
|
$ID(account),
|
||
|
$BOOL(success, YES),
|
||
|
$ID(node),
|
||
|
$ID(jid, iqNode.fromUser)
|
||
|
);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
DDLogError(@"Could not subscribe to node '%@' on jid '%@' for '%@': %@", node, iqNode.fromUser, account.connectionProperties.identity.jid, iqNode);
|
||
|
|
||
|
//call subscribe callback (if given) with error iq node
|
||
|
$call(handler,
|
||
|
$ID(account),
|
||
|
$BOOL(success, NO),
|
||
|
$ID(node),
|
||
|
$ID(jid, iqNode.fromUser),
|
||
|
$ID(errorReason, @"Unexpected iq result (wrong node or jid)!")
|
||
|
);
|
||
|
}
|
||
|
$$
|
||
|
|
||
|
$$instance_handler(handleUnsubscribeInvalidation, account.pubsub, $$ID(xmpp*, account), $_ID(NSString*, reason), $$ID(NSString*, node), $$ID(NSString*, jid), $_HANDLER(handler))
|
||
|
//invalidate our user handler
|
||
|
$invalidate(handler,
|
||
|
$ID(account),
|
||
|
$BOOL(success, NO),
|
||
|
$ID(node),
|
||
|
$ID(jid),
|
||
|
$ID(reason),
|
||
|
);
|
||
|
$$
|
||
|
|
||
|
$$instance_handler(handleUnsubscribe, account.pubsub, $$ID(xmpp*, account), $$ID(XMPPIQ*, iqNode), $$ID(NSString*, node), $$ID(NSString*, jid), $_HANDLER(handler))
|
||
|
if([iqNode check:@"/<type=error>"])
|
||
|
{
|
||
|
DDLogError(@"Got error iq from pubsub unsubscribe request: %@", iqNode);
|
||
|
//call unsubscribe callback (if given) with error iq node
|
||
|
$call(handler,
|
||
|
$ID(account),
|
||
|
$BOOL(success, NO),
|
||
|
$ID(node),
|
||
|
$ID(jid, iqNode.fromUser),
|
||
|
$ID(errorIq, iqNode)
|
||
|
);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if([iqNode check:@"{http://jabber.org/protocol/pubsub}pubsub/subscription<node=%@><jid=%@><subscription=none>", node, jid])
|
||
|
{
|
||
|
DDLogDebug(@"Successfully unsubscribed from node '%@' on jid '%@'...", node, iqNode.fromUser);
|
||
|
|
||
|
//call unsubscribe callback (if given)
|
||
|
$call(handler,
|
||
|
$ID(account),
|
||
|
$BOOL(success, YES),
|
||
|
$ID(node),
|
||
|
$ID(jid, iqNode.fromUser)
|
||
|
);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
DDLogError(@"Could not unsubscribe from node '%@' on jid '%@': %@", node, iqNode.fromUser, iqNode);
|
||
|
|
||
|
//call unsubscribe callback (if given) with error iq node
|
||
|
$call(handler,
|
||
|
$ID(account),
|
||
|
$BOOL(success, NO),
|
||
|
$ID(node),
|
||
|
$ID(jid, iqNode.fromUser),
|
||
|
$ID(errorReason, @"Unexpected iq result (wrong node or jid)!")
|
||
|
);
|
||
|
}
|
||
|
$$
|
||
|
|
||
|
$$instance_handler(handleFetchInvalidation, account.pubsub, $$ID(xmpp*, account), $_ID(NSString*, reason), $$ID(NSString*, node), $$ID(NSString*, jid), $$HANDLER(handler))
|
||
|
//invalidate user handler
|
||
|
$invalidate(handler,
|
||
|
$ID(account),
|
||
|
$BOOL(success, NO),
|
||
|
$ID(node),
|
||
|
$ID(jid),
|
||
|
$ID(reason),
|
||
|
);
|
||
|
$$
|
||
|
|
||
|
$$instance_handler(handleFetch, account.pubsub, $$ID(xmpp*, account), $$ID(XMPPIQ*, iqNode), $$ID(NSString*, node), $$ID(NSString*, jid), $$ID(NSMutableArray*, queryItems), $$ID(NSMutableDictionary*, data), $$HANDLER(handler))
|
||
|
if([iqNode check:@"/<type=error>"])
|
||
|
{
|
||
|
DDLogError(@"Got error iq for pubsub fetch request: %@", iqNode);
|
||
|
//call fetch callback (if given) with error iq node
|
||
|
$call(handler,
|
||
|
$ID(account),
|
||
|
$BOOL(success, NO),
|
||
|
$ID(node),
|
||
|
$ID(jid, iqNode.fromUser),
|
||
|
$ID(errorIq, iqNode)
|
||
|
);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
NSString* first = [iqNode findFirst:@"{http://jabber.org/protocol/pubsub}pubsub/{http://jabber.org/protocol/rsm}set/first#"];
|
||
|
NSString* last = [iqNode findFirst:@"{http://jabber.org/protocol/pubsub}pubsub/{http://jabber.org/protocol/rsm}set/last#"];
|
||
|
NSUInteger index = [[iqNode findFirst:@"{http://jabber.org/protocol/pubsub}pubsub/{http://jabber.org/protocol/rsm}set/first@index|int"] unsignedIntegerValue];
|
||
|
NSUInteger total_count = [[iqNode findFirst:@"{http://jabber.org/protocol/pubsub}pubsub/{http://jabber.org/protocol/rsm}set/count#|int"] unsignedIntegerValue];
|
||
|
NSUInteger items_count = [[iqNode find:@"{http://jabber.org/protocol/pubsub}pubsub/items/item"] count];
|
||
|
//check for rsm paging
|
||
|
if(
|
||
|
!last || //no rsm at all
|
||
|
[last isEqualToString:first] || //reached end of rsm (only one element, e.g. last==first)
|
||
|
index + items_count == total_count //reached end of rsm per rsm xep (this is a SHOULD)
|
||
|
) {
|
||
|
//--> process data *and* inform handlers of new data
|
||
|
[self handleItems:[iqNode findFirst:@"{http://jabber.org/protocol/pubsub}pubsub/items"] fromJid:iqNode.fromUser withData:data];
|
||
|
//call fetch callback (if given)
|
||
|
$call(handler,
|
||
|
$ID(account),
|
||
|
$BOOL(success, YES),
|
||
|
$ID(node),
|
||
|
$ID(jid, iqNode.fromUser),
|
||
|
$ID(data)
|
||
|
);
|
||
|
}
|
||
|
else if(first && last)
|
||
|
{
|
||
|
//only process data but *don't* call fetch callback because the data is still partial
|
||
|
[self handleItems:[iqNode findFirst:@"{http://jabber.org/protocol/pubsub}pubsub/items"] fromJid:iqNode.fromUser withData:data];
|
||
|
XMPPIQ* query = [[XMPPIQ alloc] initWithType:kiqGetType to:iqNode.fromUser];
|
||
|
[query addChildNode:[[MLXMLNode alloc] initWithElement:@"pubsub" andNamespace:@"http://jabber.org/protocol/pubsub" withAttributes:@{} andChildren:@[
|
||
|
[[MLXMLNode alloc] initWithElement:@"items" withAttributes:@{@"node": node} andChildren:queryItems andData:nil],
|
||
|
[[MLXMLNode alloc] initWithElement:@"set" andNamespace:@"http://jabber.org/protocol/rsm" withAttributes:@{} andChildren:@[
|
||
|
[[MLXMLNode alloc] initWithElement:@"after" withAttributes:@{} andChildren:@[] andData:last]
|
||
|
] andData:nil]
|
||
|
] andData:nil]];
|
||
|
[account sendIq:query withHandler:$newHandlerWithInvalidation(self, handleFetch, handleFetchInvalidation,
|
||
|
$ID(node),
|
||
|
$ID(jid, iqNode.fromUser),
|
||
|
$ID(queryItems),
|
||
|
$ID(data),
|
||
|
$HANDLER(handler)
|
||
|
)];
|
||
|
}
|
||
|
$$
|
||
|
|
||
|
$$instance_handler(handleInternalFetch, account.pubsub, $$ID(xmpp*, account), $$ID(NSString*, node), $$BOOL(success), $$ID(NSString*, jid), $_ID(NSDictionary*, data))
|
||
|
if(success != NO && data != nil) //ignore errors (--> ignore invalidations, too)
|
||
|
[self callHandlersForNode:node andJid:jid withType:@"publish" andData:data];
|
||
|
$$
|
||
|
|
||
|
$$instance_handler(handleConfigFormResultInvalidation, account.pubsub, $$ID(xmpp*, account), $_ID(NSString*, reason), $$ID(NSString*, node), $_HANDLER(handler))
|
||
|
//invalidate user handler
|
||
|
$invalidate(handler, $ID(account), $BOOL(success, NO), $ID(node), $ID(reason));
|
||
|
$$
|
||
|
|
||
|
$$instance_handler(handleConfigFormResult, account.pubsub, $$ID(xmpp*, account), $$ID(XMPPIQ*, iqNode), $$ID(NSString*, node), $$ID(NSDictionary*, configOptions), $_HANDLER(handler))
|
||
|
if([iqNode check:@"/<type=error>"])
|
||
|
{
|
||
|
DDLogError(@"Got error iq for pubsub configure request 1: %@", iqNode);
|
||
|
//signal error if a handler was given
|
||
|
$call(handler, $ID(account), $BOOL(success, NO), $ID(node), $ID(errorIq, iqNode));
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
XMPPDataForm* dataForm = [[iqNode findFirst:@"{http://jabber.org/protocol/pubsub#owner}pubsub/configure/\\{http://jabber.org/protocol/pubsub#node_config}form\\"] copy];
|
||
|
if(!dataForm)
|
||
|
{
|
||
|
DDLogError(@"Server returned invalid config form, aborting!");
|
||
|
//abort config operation
|
||
|
XMPPIQ* query = [[XMPPIQ alloc] initWithType:kiqSetType];
|
||
|
[query addChildNode:[[MLXMLNode alloc] initWithElement:@"pubsub" andNamespace:@"http://jabber.org/protocol/pubsub#owner" withAttributes:@{} andChildren:@[
|
||
|
[[MLXMLNode alloc] initWithElement:@"configure" withAttributes:@{@"node": node} andChildren:@[
|
||
|
[[XMPPDataForm alloc] initWithType:@"cancel" andFormType:@"http://jabber.org/protocol/pubsub#node_config"]
|
||
|
] andData:nil]
|
||
|
] andData:nil]];
|
||
|
[account send:query];
|
||
|
//signal error if a handler was given
|
||
|
$call(handler, $ID(account), $BOOL(success, NO), $ID(node), $ID(errorReason, NSLocalizedString(@"Unexpected server response: invalid PEP config form", @"")));
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
//update config options with our own defaults if not already present
|
||
|
configOptions = [self copyDefaultNodeOptions:_defaultOptions forConfigForm:dataForm into:configOptions];
|
||
|
|
||
|
for(NSString* option in configOptions)
|
||
|
{
|
||
|
if(!dataForm[option])
|
||
|
{
|
||
|
DDLogError(@"Server returned config form not containing the required fields or options, aborting! Required field: %@", option);
|
||
|
//abort config operation
|
||
|
XMPPIQ* query = [[XMPPIQ alloc] initWithType:kiqSetType];
|
||
|
[query addChildNode:[[MLXMLNode alloc] initWithElement:@"pubsub" andNamespace:@"http://jabber.org/protocol/pubsub#owner" withAttributes:@{} andChildren:@[
|
||
|
[[MLXMLNode alloc] initWithElement:@"configure" withAttributes:@{@"node": node} andChildren:@[
|
||
|
[[XMPPDataForm alloc] initWithType:@"cancel" andFormType:@"http://jabber.org/protocol/pubsub#node_config"]
|
||
|
] andData:nil]
|
||
|
] andData:nil]];
|
||
|
[account send:query];
|
||
|
//signal error if a handler was given
|
||
|
$call(handler, $ID(account), $BOOL(success, NO), $ID(node), $ID(errorReason, NSLocalizedString(@"Unexpected server response: missing required fields in PEP config form", @"")));
|
||
|
return;
|
||
|
}
|
||
|
else
|
||
|
dataForm[option] = configOptions[option]; //change requested value
|
||
|
}
|
||
|
|
||
|
//reconfigure the node
|
||
|
dataForm.type = @"submit";
|
||
|
XMPPIQ* query = [[XMPPIQ alloc] initWithType:kiqSetType];
|
||
|
[query addChildNode:[[MLXMLNode alloc] initWithElement:@"pubsub" andNamespace:@"http://jabber.org/protocol/pubsub#owner" withAttributes:@{} andChildren:@[
|
||
|
[[MLXMLNode alloc] initWithElement:@"configure" withAttributes:@{@"node": node} andChildren:@[dataForm] andData:nil]
|
||
|
] andData:nil]];
|
||
|
[account sendIq:query withHandler:$newHandlerWithInvalidation(self, handleConfigureResult, handleConfigureResultInvalidation,
|
||
|
$ID(node),
|
||
|
$HANDLER(handler)
|
||
|
)];
|
||
|
$$
|
||
|
|
||
|
$$instance_handler(handleConfigureResultInvalidation, account.pubsub, $$ID(xmpp*, account), $_ID(NSString*, reason), $$ID(NSString*, node), $_HANDLER(handler))
|
||
|
//invalidate user handler
|
||
|
$invalidate(handler, $ID(account), $BOOL(success, NO), $ID(node), $ID(reason));
|
||
|
$$
|
||
|
|
||
|
$$instance_handler(handleConfigureResult, account.pubsub, $$ID(xmpp*, account), $$ID(NSString*, node), $$ID(XMPPIQ*, iqNode), $_HANDLER(handler))
|
||
|
if([iqNode check:@"/<type=error>"])
|
||
|
{
|
||
|
DDLogError(@"Got error iq for pubsub configure request 2: %@", iqNode);
|
||
|
//signal error if a handler was given
|
||
|
$call(handler, $ID(account), $BOOL(success, NO), $ID(node), $ID(errorIq, iqNode));
|
||
|
return;
|
||
|
}
|
||
|
//inform handler of successful completion of config request
|
||
|
$call(handler, $ID(account), $BOOL(success, YES), $ID(node));
|
||
|
$$
|
||
|
|
||
|
//this is a user handler for configureNode: called from handlePublishResult
|
||
|
$$instance_handler(handlePublishAgainInvalidation, account.pubsub, $$ID(xmpp*, account), $_ID(NSString*, reason), $$BOOL(success), $$ID(NSString*, node), $_HANDLER(handler))
|
||
|
//invalidate user handler
|
||
|
$invalidate(handler, $ID(account), $BOOL(success), $ID(node), $ID(reason));
|
||
|
$$
|
||
|
|
||
|
//this is a user handler for configureNode: called from handlePublishResult
|
||
|
$$instance_handler(handlePublishAgain, account.pubsub, $$ID(xmpp*, account), $$BOOL(success), $_ID(XMPPIQ*, errorIq), $_ID(NSString*, errorReason), $$ID(MLXMLNode*, item), $$ID(NSString*, node), $$ID(NSDictionary*, configOptions), $_HANDLER(handler))
|
||
|
if(!success)
|
||
|
{
|
||
|
DDLogError(@"Publish failed for node '%@' even after configuring it!", node);
|
||
|
$call(handler, $ID(account), $BOOL(success, NO), $ID(node), $ID(errorIq), $ID(errorReason));
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
//try again
|
||
|
[self internalPublishItem:item onNode:node withConfigOptions:configOptions andHandler:handler andIsRetry:YES];
|
||
|
$$
|
||
|
|
||
|
//this is a user handler for internalPublishItem: called from handlePublishResult
|
||
|
$$instance_handler(handleConfigureAfterPublishInvalidation, account.pubsub, $$ID(xmpp*, account), $_ID(NSString*, reason), $$BOOL(success), $$ID(NSString*, node), $_HANDLER(handler))
|
||
|
//invalidate user handler
|
||
|
$invalidate(handler, $ID(account), $BOOL(success), $ID(node), $ID(reason));
|
||
|
$$
|
||
|
|
||
|
//this is a user handler for internalPublishItem: called from handlePublishResult
|
||
|
$$instance_handler(handleConfigureAfterPublish, account.pubsub, $$ID(xmpp*, account), $$BOOL(success), $_ID(XMPPIQ*, errorIq), $_ID(NSString*, errorReason), $$ID(NSString*, node), $$ID(NSDictionary*, configOptions), $_HANDLER(handler))
|
||
|
if(!success)
|
||
|
{
|
||
|
DDLogError(@"Second publish attempt failed again for node '%@', not configuring it!", node);
|
||
|
$call(handler, $ID(account), $BOOL(success, NO), $ID(node), $ID(errorIq), $ID(errorReason));
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
//configure node after publishing it
|
||
|
[self configureNode:node withConfigOptions:configOptions andHandler:handler];
|
||
|
$$
|
||
|
|
||
|
$$instance_handler(handlePublishResultInvalidation, account.pubsub, $$ID(xmpp*, account), $_ID(NSString*, reason), $$ID(MLXMLNode*, item), $$ID(NSString*, node), $$ID(NSDictionary*, configOptions), $_HANDLER(handler))
|
||
|
//invalidate user handler
|
||
|
$invalidate(handler, $ID(account), $BOOL(success, NO), $ID(node), $ID(reason));
|
||
|
$$
|
||
|
|
||
|
$$instance_handler(handlePublishResult, account.pubsub, $$ID(xmpp*, account), $$ID(XMPPIQ*, iqNode), $$ID(MLXMLNode*, item), $$ID(NSString*, node), $$ID(NSDictionary*, configOptions), $_HANDLER(handler), $$BOOL(is_retry))
|
||
|
if([iqNode check:@"/<type=error>"])
|
||
|
{
|
||
|
//NOTE: workaround for old ejabberd versions < 23.10 only supporting two special settings as preconditions
|
||
|
if([@"http://www.process-one.net/en/ejabberd/" isEqualToString:account.connectionProperties.serverIdentity] && [configOptions count] > 0 && [iqNode check:@"error<type=wait>/{urn:ietf:params:xml:ns:xmpp-stanzas}resource-constraint"])
|
||
|
{
|
||
|
DDLogError(@"ejabberd (< 23.10) workaround for old preconditions handling active for node: %@", node);
|
||
|
|
||
|
//make sure we don't try all preconditions from configOptions again: only these two listed preconditions are safe to use with ejabberd
|
||
|
NSMutableDictionary* publishPreconditions = [NSMutableDictionary new];
|
||
|
if(configOptions[@"pubsub#persist_items"])
|
||
|
publishPreconditions[@"pubsub#persist_items"] = configOptions[@"pubsub#persist_items"];
|
||
|
if(configOptions[@"pubsub#access_model"])
|
||
|
publishPreconditions[@"pubsub#access_model"] = configOptions[@"pubsub#access_model"];
|
||
|
|
||
|
[self internalPublishItem:item onNode:node withConfigOptions:publishPreconditions andHandler:$newHandlerWithInvalidation(self, handleConfigureAfterPublish, handleConfigureAfterPublishInvalidation,
|
||
|
$ID(node),
|
||
|
$ID(configOptions),
|
||
|
$HANDLER(handler)
|
||
|
) andIsRetry:NO];
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
//check if this node is already present and configured --> reconfigure it according to our access-model
|
||
|
if(!is_retry && [iqNode check:@"error<type=cancel>/{http://jabber.org/protocol/pubsub#errors}precondition-not-met"])
|
||
|
{
|
||
|
DDLogWarn(@"Node precondition not met, reconfiguring node: %@", node);
|
||
|
[self configureNode:node withConfigOptions:configOptions andHandler:$newHandlerWithInvalidation(self, handlePublishAgain, handlePublishAgainInvalidation,
|
||
|
$ID(item),
|
||
|
$ID(node),
|
||
|
$ID(configOptions), //modern servers support XEP-0060 Version 1.15.0 (2017-12-12) --> all node config options are allowed as preconditions
|
||
|
$HANDLER(handler)
|
||
|
)];
|
||
|
return;
|
||
|
}
|
||
|
if(is_retry && [iqNode check:@"error<type=cancel>/{http://jabber.org/protocol/pubsub#errors}precondition-not-met"])
|
||
|
DDLogError(@"Node precondition not met even after reconfiguring node, aborting: %@", node);
|
||
|
|
||
|
//all other errors are real errors --> inform user handler
|
||
|
$call(handler, $ID(account), $BOOL(success, NO), $ID(node), $ID(errorIq, iqNode));
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
//no errors means everything worked out as expected
|
||
|
$call(handler, $ID(account), $BOOL(success, YES), $ID(node));
|
||
|
$$
|
||
|
|
||
|
$$instance_handler(handleRetractResultInvalidation, account.pubsub, $$ID(xmpp*, account), $_ID(NSString*, reason), $$ID(NSString*, node), $$ID(NSString*, itemId), $_HANDLER(handler))
|
||
|
//invalidate user handler
|
||
|
$invalidate(handler, $ID(account), $BOOL(success, NO), $ID(node), $ID(itemId), $ID(reason));
|
||
|
$$
|
||
|
|
||
|
$$instance_handler(handleRetractResult, account.pubsub, $$ID(xmpp*, account), $$ID(XMPPIQ*, iqNode), $$ID(NSString*, node), $$ID(NSString*, itemId), $_HANDLER(handler))
|
||
|
if([iqNode check:@"/<type=error>"])
|
||
|
{
|
||
|
DDLogError(@"Retract for item '%@' of node '%@' failed: %@", itemId, node, iqNode);
|
||
|
$call(handler, $ID(account), $BOOL(success, NO), $ID(node), $ID(itemId), $ID(errorIq, iqNode));
|
||
|
return;
|
||
|
}
|
||
|
$call(handler, $ID(account), $BOOL(success, YES), $ID(node), $ID(itemId));
|
||
|
$$
|
||
|
|
||
|
$$instance_handler(handlePurgeOrDeleteResultInvalidation, account.pubsub, $$ID(xmpp*, account), $_ID(NSString*, reason), $$ID(NSString*, node), $_HANDLER(handler))
|
||
|
//invalidate user handler
|
||
|
$invalidate(handler, $ID(account), $BOOL(success, NO), $ID(node), $ID(reason));
|
||
|
$$
|
||
|
|
||
|
$$instance_handler(handlePurgeOrDeleteResult, account.pubsub, $$ID(xmpp*, account), $$ID(XMPPIQ*, iqNode), $$ID(NSString*, node), $_HANDLER(handler))
|
||
|
if([iqNode check:@"/<type=error>"])
|
||
|
{
|
||
|
DDLogError(@"Purge/Delete of node '%@' failed: %@", node, iqNode);
|
||
|
$call(handler, $ID(account), $BOOL(success, NO), $ID(node), $ID(errorIq, iqNode));
|
||
|
return;
|
||
|
}
|
||
|
$call(handler, $ID(account), $BOOL(success, YES), $ID(node));
|
||
|
$$
|
||
|
|
||
|
@end
|