Skip to content

Commit

Permalink
Add write support for muc bookmarks (XEP-0048), fixes #19
Browse files Browse the repository at this point in the history
  • Loading branch information
tmolitor-stud-tu committed May 10, 2021
1 parent 9710d08 commit 00a53d6
Show file tree
Hide file tree
Showing 4 changed files with 156 additions and 28 deletions.
2 changes: 1 addition & 1 deletion Monal/Classes/MLMucProcessor.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ NS_ASSUME_NONNULL_BEGIN
+(void) sendDiscoQueryFor:(NSString*) roomJid onAccount:(xmpp*) account withJoin:(BOOL) join;
+(void) ping:(NSString*) roomJid onAccount:(xmpp*) account;
+(void) sendJoinPresenceFor:(NSString*) room onAccount:(xmpp*) account;
+(void) leave:(NSString*) room onAccount:(xmpp*) account;
+(void) leave:(NSString*) room onAccount:(xmpp*) account withBookmarksUpdate:(BOOL) updateBookmarks;

@end

Expand Down
50 changes: 43 additions & 7 deletions Monal/Classes/MLMucProcessor.m
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@
#import "XMPPMessage.h"
#import "XMPPPresence.h"
#import "MLNotificationQueue.h"
#import "MLPubSub.h"
#import "MLPubSubProcessor.h"

#define CURRENT_MUC_STATE_VERSION @1
#define CURRENT_MUC_STATE_VERSION @2

@interface MLMucProcessor()

Expand All @@ -34,6 +36,7 @@ @implementation MLMucProcessor
//persistent state
static NSMutableDictionary* _roomFeatures;
static NSMutableSet* _joining;
static NSMutableSet* _firstJoin;

//this won't be persisted because it is only for the ui
static NSMutableDictionary* _uiHandler;
Expand All @@ -43,6 +46,7 @@ +(void) initialize
_stateLockObject = [[NSObject alloc] init];
_roomFeatures = [[NSMutableDictionary alloc] init];
_joining = [[NSMutableSet alloc] init];
_firstJoin = [[NSMutableSet alloc] init];
_uiHandler = [[NSMutableDictionary alloc] init];
}

Expand All @@ -56,6 +60,7 @@ +(void) setState:(NSDictionary*) state
@synchronized(_stateLockObject) {
_roomFeatures = [state[@"roomFeatures"] mutableCopy];
_joining = [state[@"joining"] mutableCopy];
_firstJoin = [state[@"firstJoin"] mutableCopy];
}
}

Expand All @@ -65,7 +70,8 @@ +(NSDictionary*) state
return @{
@"version": CURRENT_MUC_STATE_VERSION,
@"roomFeatures": _roomFeatures,
@"joining": _joining
@"joining": _joining,
@"firstJoin": _firstJoin,
};
}
}
Expand Down Expand Up @@ -243,7 +249,13 @@ +(void) handleStatusCodes:(XMPPStanza*) node forAccount:(xmpp*) account
}

//we joined successfully --> add muc to our favorites (this will use the already up to date nick from buddylist db table)
//and update bookmarks if this was the first timewe joined this muc
[[DataLayer sharedInstance] addMucFavorite:node.fromUser forAccountId:account.accountNo andMucNick:nil];
@synchronized(_stateLockObject) {
if([_firstJoin containsObject:node.fromUser])
[self updateBookmarksForAccount:account];
[_firstJoin removeObject:node.fromUser];
}

monal_id_block_t uiHandler = [self getUIHandlerForMuc:node.fromUser];
if(uiHandler)
Expand Down Expand Up @@ -315,18 +327,25 @@ +(void) handleStatusCodes:(XMPPStanza*) node forAccount:(xmpp*) account
}
}

+(void) leave:(NSString*) room onAccount:(xmpp*) account
+(void) leave:(NSString*) room onAccount:(xmpp*) account withBookmarksUpdate:(BOOL) updateBookmarks
{
NSString* nick = [[DataLayer sharedInstance] ownNickNameforMuc:room forAccount:account.accountNo];
if(nick == nil)
{
{^
DDLogError(@"Cannot leave room '%@' on account %@ because nick is nil!", room, account.accountNo);
return;
}
DDLogInfo(@"Leaving room '%@' on account %@ using nick '%@'...", room, account.accountNo, nick);
XMPPPresence* presence = [[XMPPPresence alloc] init];
[presence leaveRoom:room withNick:nick];
[account send:presence];

//delete muc from favorites table
[[DataLayer sharedInstance] deleteMuc:room forAccountId:account.accountNo];

//update bookmarks if requested
if(updateBookmarks)
[self updateBookmarksForAccount:account];
}

+(void) sendDiscoQueryFor:(NSString*) roomJid onAccount:(xmpp*) account withJoin:(BOOL) join
Expand All @@ -343,7 +362,13 @@ +(void) sendDiscoQueryFor:(NSString*) roomJid onAccount:(xmpp*) account withJoin

+(void) ping:(NSString*) roomJid onAccount:(xmpp*) account
{
NSAssert([[DataLayer sharedInstance] isBuddyMuc:roomJid forAccount:account.accountNo], @"Tried to muc-ping non-muc jid!");
if(![[DataLayer sharedInstance] isBuddyMuc:roomJid forAccount:account.accountNo])
{
DDLogWarn(@"Tried to muc-ping non-muc jid '%@', trying to join regularily with disco...", roomJid);
//this will check if this jid really is not a muc and delete it fom favorites and bookmarks, if not (and join normally if it turns out is a muc after all)
[self sendDiscoQueryFor:roomJid onAccount:account withJoin:YES];
return;
}

XMPPIQ* ping = [[XMPPIQ alloc] initWithType:kiqGetType];
[ping setiqTo:roomJid];
Expand Down Expand Up @@ -403,9 +428,10 @@ +(void) ping:(NSString*) roomJid onAccount:(xmpp*) account
{
DDLogError(@"muc disco returned that this jid is not a muc!");

//delete muc from favorites table to be sure we don't try to rejoin it
//delete muc from favorites table to be sure we don't try to rejoin it and update bookmarks afterwards (to make sure this muc isn't accidentally left in our boomkmarks)
[[DataLayer sharedInstance] deleteMuc:iqNode.fromUser forAccountId:account.accountNo];

[self updateBookmarksForAccount:account];

[self handleError:[NSString stringWithFormat:NSLocalizedString(@"Failed to enter groupchat %@: This is not a groupchat!", @""), iqNode.fromUser] forMuc:iqNode.fromUser withNode:nil andAccount:account andIsSevere:YES];
return;
}
Expand All @@ -431,6 +457,10 @@ +(void) ping:(NSString*) roomJid onAccount:(xmpp*) account
//add new muc buddy (potentially deleting a non-muc buddy having the same jid)
DDLogInfo(@"Adding new muc %@ using nick '%@' to buddylist...", iqNode.fromUser, nick);
[[DataLayer sharedInstance] initMuc:iqNode.fromUser forAccountId:account.accountNo andMucNick:nick];
//add this room to firstJoin list
@synchronized(_stateLockObject) {
[_firstJoin addObject:iqNode.fromUser];
}
}
else
{
Expand Down Expand Up @@ -582,4 +612,10 @@ +(void) handleError:(NSString*) description forMuc:(NSString*) room withNode:(XM
[HelperTools postError:description withNode:node andAccount:account andIsSevere:isSevere];
}

+(void) updateBookmarksForAccount:(xmpp*) account
{
#ifdef IS_ALPHA
[account.pubsub fetchNode:@"storage:bookmarks" from:account.connectionProperties.identity.jid withItemsList:nil andHandler:$newHandler(MLPubSubProcessor, handleBookarksFetchResult)];
#endif
}
@end
115 changes: 111 additions & 4 deletions Monal/Classes/MLPubSubProcessor.m
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ @implementation MLPubSubProcessor
NSMutableSet* bookmarkedMucs = [[NSMutableSet alloc] init];
for(MLXMLNode* conference in [data[itemId] find:@"{storage:bookmarks}storage/conference"])
{
//we ignore the conference name (the name willbe taken from the muc itself)
//we ignore the conference name (the name will be taken from the muc itself)
//NSString* name = [conference findFirst:@"/@name"];
NSString* room = [conference findFirst:@"/@jid"];
//ignore non-xep-compliant entries
Expand Down Expand Up @@ -182,7 +182,7 @@ @implementation MLPubSubProcessor
DDLogInfo(@"Leaving muc '%@' on acount %@ because not listed as autojoin=true in bookmarks...", room, account.accountNo);
//delete local favorites entry and leave room afterwards
[[DataLayer sharedInstance] deleteMuc:room forAccountId:account.accountNo];
[MLMucProcessor leave:room onAccount:account];
[MLMucProcessor leave:room onAccount:account withBookmarksUpdate:NO];
}
//check for nickname changes
else if(ownFavorites[room] != nil && nick != nil)
Expand Down Expand Up @@ -212,7 +212,7 @@ @implementation MLPubSubProcessor
DDLogInfo(@"Leaving muc '%@' on acount %@ because not listed in bookmarks anymore...", room, account.accountNo);
//delete local favorites entry and leave room afterwards
[[DataLayer sharedInstance] deleteMuc:room forAccountId:account.accountNo];
[MLMucProcessor leave:room onAccount:account];
[MLMucProcessor leave:room onAccount:account withBookmarksUpdate:NO];
}

break; //we only need the first pep item (there should be only one item in the first place)
Expand All @@ -224,13 +224,120 @@ @implementation MLPubSubProcessor
//remove and leave all mucs
for(NSString* room in ownFavorites)
{
DDLogInfo(@"Leaving muc '%@' on acount %@ because all bookmarks got deleted...", room, account.accountNo);
//delete local favorites entry and leave room afterwards
[[DataLayer sharedInstance] deleteMuc:room forAccountId:account.accountNo];
[MLMucProcessor leave:room onAccount:account];
[MLMucProcessor leave:room onAccount:account withBookmarksUpdate:NO];
}
}
$$

$$handler(handleBookarksFetchResult, $_ID(xmpp*, account), $_BOOL(success), $_ID(XMPPIQ*, errorIq), $_ID(NSString*, errorReason), $_ID(NSDictionary*, data))
if(!success)
{
DDLogWarn(@"Could not fetch bookmarks from pep prior to publishing!");
[self handleErrorWithDescription:NSLocalizedString(@"Failed to save groupchat bookmarks", @"") andAccount:account andErrorIq:errorIq andErrorReason:errorReason andIsSevere:YES];
return;
}

NSMutableDictionary* ownFavorites = [[NSMutableDictionary alloc] init];
for(NSDictionary* entry in [[DataLayer sharedInstance] listMucsForAccount:account.accountNo])
ownFavorites[entry[@"room"]] = entry;

for(NSString* itemId in data)
{
//ignore non-xep-compliant data and continue as if no data was received at all
if(![data[itemId] check:@"{storage:bookmarks}storage"])
break;

NSMutableSet* bookmarkedMucs = [[NSMutableSet alloc] init];
for(MLXMLNode* conference in [data[itemId] find:@"{storage:bookmarks}storage/conference"])
{
//we ignore the conference name (the name will be taken from the muc itself)
//NSString* name = [conference findFirst:@"/@name"];
NSString* room = [conference findFirst:@"/@jid"];
//ignore non-xep-compliant entries
if(!room)
{
DDLogError(@"Received non-xep-compliant bookmarks entry, ignoring: %@", conference);
continue;
}
[bookmarkedMucs addObject:room];
NSNumber* autojoin = [conference findFirst:@"/@autojoin|bool"];
if(autojoin == nil)
autojoin = @NO; //default value specified in xep

//check if the bookmark exists with autojoin==false and only update the autojoin and nick values, if true
if(ownFavorites[room] && ![autojoin boolValue])
{
DDLogInfo(@"Updating autojoin of bookmarked muc '%@' on acount %@ to 'true'...", room, account.accountNo);

//add or update nickname
if(![conference check:@"nick"])
[conference addChild:[[MLXMLNode alloc] initWithElement:@"nick"]];
((MLXMLNode*)[conference findFirst:@"nick"]).data = [[DataLayer sharedInstance] ownNickNameforMuc:room forAccount:account.accountNo];

//update autojoin value to true
conference.attributes[@"autojoin"] = @"true";
}
}

//add all mucs not yet listed in bookmarks
NSMutableSet* toAdd = [NSMutableSet setWithArray:[ownFavorites allKeys]];
[toAdd minusSet:bookmarkedMucs];
for(NSString* room in toAdd)
{
DDLogInfo(@"Adding muc '%@' on acount %@ to bookmarks...", room, account.accountNo);
[((MLXMLNode*)[data[itemId] find:@"{storage:bookmarks}storage"]) addChild:[[MLXMLNode alloc] initWithElement:@"conference" withAttributes:@{
@"jid": room,
@"name": [[MLContact createContactFromJid:room andAccountNo:account.accountNo] contactDisplayName],
@"autojoin": @"true",
} andChildren:@[
[[MLXMLNode alloc] initWithElement:@"nick" withAttributes:@{} andChildren:@[] andData:[[DataLayer sharedInstance] ownNickNameforMuc:room forAccount:account.accountNo]]
] andData:nil]];
}

//publish new bookmarks
[account.pubsub publishItem:data[itemId] onNode:@"storage:bookmarks" withConfigOptions:@{
@"pubsub#persist_items": @"true",
@"pubsub#access_model": @"whitelist"
} andHandler:$newHandler(self, bookmarksPublished)];

//we only need the first pep item (there should be only one item in the first place)
return;
}

//no pep item was found: publish our bookmarks the first time
NSMutableArray* conferences = [[NSMutableArray alloc] init];
for(NSString* room in ownFavorites)
{
[conferences addObject:[[MLXMLNode alloc] initWithElement:@"conference" withAttributes:@{
@"jid": room,
@"name": [[MLContact createContactFromJid:room andAccountNo:account.accountNo] contactDisplayName],
@"autojoin": @"true",
} andChildren:@[
[[MLXMLNode alloc] initWithElement:@"nick" withAttributes:@{} andChildren:@[] andData:[[DataLayer sharedInstance] ownNickNameforMuc:room forAccount:account.accountNo]]
] andData:nil]];
}
[account.pubsub publishItem:
[[MLXMLNode alloc] initWithElement:@"item" withAttributes:@{@"id": @"current"} andChildren:@[
[[MLXMLNode alloc] initWithElement:@"storage" andNamespace:@"storage:bookmarks" withAttributes:@{} andChildren:conferences andData:nil]
] andData:nil]
onNode:@"storage:bookmarks" withConfigOptions:@{
@"pubsub#persist_items": @"true",
@"pubsub#access_model": @"whitelist"
} andHandler:$newHandler(self, bookmarksPublished)];
$$

$$handler(bookmarksPublished, $_ID(xmpp*, account), $_BOOL(success), $_ID(XMPPIQ*, errorIq), $_ID(NSString*, errorReason))
if(!success)
{
DDLogWarn(@"Could not publish bookmarks to pep!");
[self handleErrorWithDescription:NSLocalizedString(@"Failed to save groupchat bookmarks", @"") andAccount:account andErrorIq:errorIq andErrorReason:errorReason andIsSevere:YES];
return;
}
$$

$$handler(rosterNamePublished, $_ID(xmpp*, account), $_BOOL(success), $_ID(XMPPIQ*, errorIq), $_ID(NSString*, errorReason))
if(!success)
{
Expand Down
17 changes: 1 addition & 16 deletions Monal/Classes/xmpp.m
Original file line number Diff line number Diff line change
Expand Up @@ -2895,26 +2895,11 @@ -(void) setMAMQueryMostRecentForContact:(MLContact*) contact before:(NSString*)
-(void) joinMuc:(NSString* _Nonnull) room
{
[MLMucProcessor sendDiscoQueryFor:room onAccount:self withJoin:YES];
//TODO MUC: join muc interactively and add to favorites once we joined successful
}

-(void) leaveMuc:(NSString* _Nonnull) room
{
//TODO MUC: leave room and remove from favorites
}

-(void) queryMucType:(NSString*) room withCompletion:(xmppCompletion) completion
{
//TODO MUC: used only by ui (shouldn't this whole method be a proxy to MLMucProcessor?)
XMPPIQ* mucInfo = [[XMPPIQ alloc] initWithType:kiqGetType];
[mucInfo setiqTo:room];
[mucInfo setDiscoInfoNode];
[self sendIq:mucInfo withResponseHandler:^(XMPPIQ* response) {
//TODO MUC: implement this
} andErrorHandler:^(XMPPIQ* error) {
completion(NO, [HelperTools extractXMPPError:error withDescription:@"Failed to query MUC description"]);
}];
[self sendIq:mucInfo withHandler:$newHandler(MLIQProcessor, handleAccountDiscoInfo)];
[MLMucProcessor leave:room onAccount:self withBookmarksUpdate:YES];
}

#pragma mark- XMPP add and remove contact
Expand Down

0 comments on commit 00a53d6

Please sign in to comment.