- Update
This commit is contained in:
1 parent
d281d765ea
commit
5b074a5176
1261 files changed
+199158
-7303
No files matched your search
@@ -0,0 +1,51 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "XMPPModule.h"
|
||||
|
||||
extern NSString *const XMPPGoogleSharedStatusShow;
|
||||
extern NSString *const XMPPGoogleSharedStatusInvisible;
|
||||
extern NSString *const XMPPGoogleSharedStatusStatus;
|
||||
|
||||
extern NSString *const XMPPGoogleSharedStatusShowAvailable;
|
||||
extern NSString *const XMPPGoogleSharedStatusShowBusy;
|
||||
extern NSString *const XMPPGoogleSharedStatusShowIdle;
|
||||
|
||||
@protocol XMPPGoogleSharedStatusDelegate;
|
||||
|
||||
|
||||
#define _XMPP_GOOGLE_SHARED_STATUS_H
|
||||
|
||||
@interface XMPPGoogleSharedStatus : XMPPModule
|
||||
|
||||
@property (nonatomic, assign) BOOL sharedStatusSupported;
|
||||
@property (nonatomic, strong) NSDictionary *sharedStatus;
|
||||
|
||||
// Determines if the user is idle based on last received HID
|
||||
// event, and sends the presence as away, if the user is idle.
|
||||
@property (nonatomic, assign) BOOL assumeIdleUpdateResponsibility;
|
||||
|
||||
@property (nonatomic, strong) NSString *status;
|
||||
@property (nonatomic, strong) NSString *show;
|
||||
@property (nonatomic, assign) BOOL invisible;
|
||||
|
||||
// Convenience auto-updater for presence status.
|
||||
// Does not use XMPPGoogleSharedStatus.
|
||||
@property (nonatomic, copy) NSString *statusMessage;
|
||||
@property (nonatomic, copy) NSString *statusAvailability;
|
||||
|
||||
- (void)updateSharedStatus:(NSString *)status
|
||||
show:(NSString *)show
|
||||
invisible:(BOOL)invisible;
|
||||
|
||||
- (void)refreshSharedStatus;
|
||||
|
||||
@end
|
||||
|
||||
@protocol XMPPGoogleSharedStatusDelegate
|
||||
|
||||
@optional
|
||||
|
||||
// This delegate method is called when the server updates the shared
|
||||
// status module with new status information, or upon manual refresh.
|
||||
- (void)xmppGoogleSharedStatus:(XMPPGoogleSharedStatus *)sender didReceiveUpdatedStatus:(NSDictionary *)sharedStatus;
|
||||
|
||||
@end
|
||||
@@ -0,0 +1,369 @@
|
||||
#import "XMPPGoogleSharedStatus.h"
|
||||
#import "XMPP.h"
|
||||
#import "XMPPFramework.h"
|
||||
|
||||
#if ! __has_feature(objc_arc)
|
||||
#warning This file must be compiled with ARC. Use -fobjc-arc flag (or convert project to ARC).
|
||||
#endif
|
||||
|
||||
#define GOOGLE_SHARED_STATUS @"google:shared-status"
|
||||
#define GOOGLE_DISCO_INFO @"http://jabber.org/protocol/disco#info"
|
||||
#define GOOGLE_PRESENCE_PRIORITY @"24"
|
||||
|
||||
// Dictionary keys to access shared status information.
|
||||
NSString *const XMPPGoogleSharedStatusShow = @"show";
|
||||
NSString *const XMPPGoogleSharedStatusInvisible = @"invisible";
|
||||
NSString *const XMPPGoogleSharedStatusStatus = @"status";
|
||||
|
||||
// Shared status display values. Note that you cannot set
|
||||
// a shared status for an idle show.
|
||||
NSString *const XMPPGoogleSharedStatusShowAvailable = @"default";
|
||||
NSString *const XMPPGoogleSharedStatusShowBusy = @"dnd";
|
||||
NSString *const XMPPGoogleSharedStatusShowIdle = @"away";
|
||||
|
||||
@interface XMPPGoogleSharedStatus () {
|
||||
|
||||
// Server specified maximum values.
|
||||
NSInteger _statusListMaxCount;
|
||||
NSInteger _statusMessageMaxLength;
|
||||
}
|
||||
|
||||
@property (nonatomic, copy) NSString *previousShow;
|
||||
|
||||
@end
|
||||
|
||||
@implementation XMPPGoogleSharedStatus
|
||||
|
||||
#pragma mark - Object Lifecycle
|
||||
|
||||
- (id)initWithDispatchQueue:(dispatch_queue_t)queue {
|
||||
if((self = [super initWithDispatchQueue:queue])) {
|
||||
self.sharedStatus = [NSDictionary dictionary];
|
||||
_status = @"";
|
||||
_show = XMPPGoogleSharedStatusShowAvailable;
|
||||
_invisible = NO;
|
||||
|
||||
#ifdef _XMPP_SYSTEM_INPUT_ACTIVITY_MONITOR_H
|
||||
self.assumeIdleUpdateResponsibility = YES;
|
||||
#endif
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
|
||||
- (BOOL)activate:(XMPPStream *)aXmppStream
|
||||
{
|
||||
if ([super activate:aXmppStream])
|
||||
{
|
||||
|
||||
#ifdef _XMPP_SYSTEM_INPUT_ACTIVITY_MONITOR_H
|
||||
[xmppStream autoAddDelegate:self
|
||||
delegateQueue:moduleQueue
|
||||
toModulesOfClass:[XMPPSystemInputActivityMonitor class]];
|
||||
#endif
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)deactivate
|
||||
{
|
||||
|
||||
#ifdef _XMPP_SYSTEM_INPUT_ACTIVITY_MONITOR_H
|
||||
[xmppStream removeAutoDelegate:self
|
||||
delegateQueue:moduleQueue
|
||||
fromModulesOfClass:[XMPPSystemInputActivityMonitor class]];
|
||||
#endif
|
||||
|
||||
[super deactivate];
|
||||
}
|
||||
|
||||
|
||||
|
||||
#pragma mark - Convenience Properties
|
||||
|
||||
// Convinience single-item setters which forward status setting
|
||||
// to the more complex update method below. Use these sparingly,
|
||||
// because they have heavy load when used in succession.
|
||||
- (void)setStatus:(NSString *)status {
|
||||
[self updateSharedStatus:status show:self.show invisible:self.invisible];
|
||||
}
|
||||
|
||||
- (void)setShow:(NSString *)show {
|
||||
[self updateSharedStatus:self.status show:show invisible:self.invisible];
|
||||
}
|
||||
|
||||
- (void)setInvisible:(BOOL)invisible {
|
||||
[self updateSharedStatus:self.status show:self.show invisible:invisible];
|
||||
}
|
||||
|
||||
#pragma mark - Status Updates
|
||||
|
||||
// This is a heavyweight method used to update all three attributes,
|
||||
// status, show, and invisible, at once. Use this method over the
|
||||
// convinience accessors above. Wherever possible, lessen the status
|
||||
// update load. This method refuses service if shared status is not
|
||||
// supported, removes previous statuses if the list reaches max
|
||||
// count, and cuts status strings to the max string length.
|
||||
- (void)updateSharedStatus:(NSString *)status show:(NSString *)show invisible:(BOOL)invisible {
|
||||
if(!self.sharedStatusSupported) {
|
||||
NSLog(@"Google Shared Status Not Supported!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Create a mutable copy of shared status information and chop the status.
|
||||
NSMutableDictionary *sharedStatusUpdate = [self.sharedStatus mutableCopy];
|
||||
if(status.length > _statusMessageMaxLength)
|
||||
status = [status substringToIndex:_statusMessageMaxLength];
|
||||
|
||||
// If the show has changed update it.
|
||||
if(![show isEqualToString:sharedStatusUpdate[XMPPGoogleSharedStatusShow]]) {
|
||||
[sharedStatusUpdate removeObjectForKey:XMPPGoogleSharedStatusShow];
|
||||
sharedStatusUpdate[XMPPGoogleSharedStatusShow] = show;
|
||||
}
|
||||
|
||||
// Get the current show value, and whether the status should update for it.
|
||||
BOOL displayShow = ([show isEqualToString:XMPPGoogleSharedStatusShowAvailable] ||
|
||||
[show isEqualToString:XMPPGoogleSharedStatusShowBusy]);
|
||||
NSString *currentShow = self.sharedStatus[XMPPGoogleSharedStatusShow];
|
||||
|
||||
// Since we can't show an away status, only change the status it has
|
||||
// changed and is not showing idle.
|
||||
if(![status isEqualToString:sharedStatusUpdate[XMPPGoogleSharedStatusStatus]] && displayShow) {
|
||||
[sharedStatusUpdate removeObjectForKey:XMPPGoogleSharedStatusStatus];
|
||||
sharedStatusUpdate[XMPPGoogleSharedStatusStatus] = status;
|
||||
|
||||
// Update the appropriate status list by adding the new status.
|
||||
// If the list max count was reached, truncate its earliest status.
|
||||
NSMutableArray *statusList = [sharedStatusUpdate[currentShow] mutableCopy];
|
||||
[statusList insertObject:status atIndex:0];
|
||||
if(statusList.count > _statusListMaxCount)
|
||||
[statusList removeObjectAtIndex:statusList.count-1];
|
||||
|
||||
// Remove any blank statuses.
|
||||
for(int i = 0; i < statusList.count; i++) {
|
||||
NSString *status = statusList[i];
|
||||
if([status isEqualToString:@""])
|
||||
[statusList removeObjectAtIndex:i];
|
||||
}
|
||||
|
||||
// Update the status list for this status.
|
||||
[sharedStatusUpdate removeObjectForKey:currentShow];
|
||||
sharedStatusUpdate[currentShow] = statusList;
|
||||
}
|
||||
|
||||
// Update the invisibility for this shared status.
|
||||
[sharedStatusUpdate removeObjectForKey:XMPPGoogleSharedStatusInvisible];
|
||||
sharedStatusUpdate[XMPPGoogleSharedStatusInvisible] = @(invisible);
|
||||
|
||||
// Wrap it in an XMPPIQ "set" and send it to the server.
|
||||
XMPPIQ *statusIQ = [XMPPIQ iqWithType:@"set" to:self.xmppStream.myJID.bareJID];
|
||||
[statusIQ addAttributeWithName:@"id" stringValue:self.xmppStream.myJID.resource];
|
||||
[statusIQ addChild:[self packSharedStatus:sharedStatusUpdate]];
|
||||
[self.xmppStream sendElement:statusIQ];
|
||||
|
||||
// Now update our local values, toll-free.
|
||||
_show = show;
|
||||
_status = status;
|
||||
_invisible = invisible;
|
||||
}
|
||||
|
||||
#pragma mark - Capability Discovery
|
||||
|
||||
// Discover and refresh shared status as soon as authentication.
|
||||
- (void)xmppStreamDidAuthenticate:(XMPPStream *)sender {
|
||||
[self discoverCapabilities];
|
||||
[self refreshSharedStatus];
|
||||
}
|
||||
|
||||
// Disable support when the stream disconnects.
|
||||
- (void)xmppStreamDidDisconnect:(XMPPStream *)sender withError:(NSError *)error {
|
||||
self.sharedStatus = [NSDictionary dictionary];
|
||||
self.sharedStatusSupported = NO;
|
||||
}
|
||||
|
||||
// First discover capabilities to find google:shared-status. If it exists,
|
||||
// we can continue with the shared status support.
|
||||
- (void)discoverCapabilities {
|
||||
XMPPIQ *discoIQ = [XMPPIQ iqWithType:@"get" to:[XMPPJID jidWithString:@"gmail.com"]];
|
||||
[discoIQ addChild:[XMPPElement elementWithName:@"query" xmlns:GOOGLE_DISCO_INFO]];
|
||||
[self.xmppStream sendElement:discoIQ];
|
||||
}
|
||||
|
||||
// Manually trigger a refresh cycle for getting shared statuses. This will
|
||||
// return an XML stanza containing status-lists and current status info.
|
||||
- (void)refreshSharedStatus {
|
||||
XMPPIQ *iq = [XMPPIQ iqWithType:@"get" to:self.xmppStream.myJID.bareJID];
|
||||
[iq addAttributeWithName:@"id" stringValue:self.xmppStream.myJID.resource];
|
||||
|
||||
XMPPElement *query = (XMPPElement *)[XMPPElement elementWithName:@"query" xmlns:GOOGLE_SHARED_STATUS];
|
||||
[query addAttributeWithName:@"version" stringValue:@"2"];
|
||||
|
||||
[iq addChild:query];
|
||||
[self.xmppStream sendElement:iq];
|
||||
}
|
||||
|
||||
// If we received a shared-status IQ and we support it, unpack it into a
|
||||
// dictionary and notify the delegates. If it is the first shared-status
|
||||
// subscription IQ, it contains the maximum string length and list count
|
||||
// information attributes as well, so save those.
|
||||
// If we received a discovery IQ, determine shared status support.
|
||||
- (BOOL)xmppStream:(XMPPStream *)sender didReceiveIQ:(XMPPIQ *)iq {
|
||||
XMPPElement *query = (XMPPElement *) [iq children][0];
|
||||
if([query.xmlns isEqualToString:GOOGLE_SHARED_STATUS] && self.sharedStatusSupported) {
|
||||
self.sharedStatus = [self unpackSharedStatus:query];
|
||||
_show = self.sharedStatus[XMPPGoogleSharedStatusShow];
|
||||
_status = self.sharedStatus[XMPPGoogleSharedStatusStatus];
|
||||
_invisible = [self.sharedStatus[XMPPGoogleSharedStatusInvisible] boolValue];
|
||||
[multicastDelegate xmppGoogleSharedStatus:self didReceiveUpdatedStatus:self.sharedStatus];
|
||||
|
||||
if([query attributeForName:@"status-max"])
|
||||
_statusMessageMaxLength = [[query attributeForName:@"status-max"].stringValue integerValue];
|
||||
if([query attributeForName:@"status-list-contents-max"])
|
||||
_statusListMaxCount = [[query attributeForName:@"status-list-contents-max"].stringValue integerValue];
|
||||
|
||||
} else if([query.xmlns isEqualToString:GOOGLE_DISCO_INFO]) {
|
||||
for(XMPPElement *feature in query.children)
|
||||
if([[[feature attributeForName:@"var"] stringValue] isEqualToString:GOOGLE_SHARED_STATUS])
|
||||
self.sharedStatusSupported = YES;
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
#pragma mark - Packing & Unpacking
|
||||
|
||||
// Unpacks the XMPPElement "query" for a shared status and returns
|
||||
// a formatted NSDictionary with corresponding arrays and strings.
|
||||
// The XMPPElement "query" is a child of a shared status XMPPIQ.
|
||||
- (NSDictionary *)unpackSharedStatus:(XMPPElement *)sharedStatus {
|
||||
if([sharedStatus.xmlns isEqualToString:GOOGLE_SHARED_STATUS]) {
|
||||
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
|
||||
|
||||
for(XMPPElement *element in sharedStatus.children) {
|
||||
if([element.name isEqualToString:@"status-list"]) {
|
||||
NSMutableArray *array = [NSMutableArray array];
|
||||
for(XMPPElement *status in element.children)
|
||||
[array addObject:[status stringValue]];
|
||||
dict[[[element attributeForName:XMPPGoogleSharedStatusShow] stringValue]] = array;
|
||||
} else if([element.name isEqualToString:XMPPGoogleSharedStatusStatus]) {
|
||||
dict[element.name] = [element stringValue];
|
||||
} else if([element.name isEqualToString:XMPPGoogleSharedStatusShow]) {
|
||||
dict[element.name] = [element stringValue];
|
||||
} else if([element.name isEqualToString:XMPPGoogleSharedStatusInvisible]) {
|
||||
dict[element.name] = @([[[element attributeForName:@"value"] stringValue] boolValue]);
|
||||
} else {
|
||||
NSLog(@"Missed element: %@", element);
|
||||
}
|
||||
}
|
||||
|
||||
return dict;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
// Packs a formatted NSDictionary with corresponding arrays and strings
|
||||
// back into an XMPPElement "query" for shared status. This must be
|
||||
// further wrapped in an XMPPIQ to send a status update.
|
||||
- (XMPPElement *)packSharedStatus:(NSDictionary *)sharedStatus {
|
||||
XMPPElement *element = (XMPPElement *)[XMPPElement elementWithName:@"query" xmlns:GOOGLE_SHARED_STATUS];
|
||||
[element addAttributeWithName:@"version" stringValue:@"2"];
|
||||
|
||||
[element addChild:[XMPPElement elementWithName:XMPPGoogleSharedStatusStatus
|
||||
stringValue:sharedStatus[XMPPGoogleSharedStatusStatus]]];
|
||||
[element addChild:[XMPPElement elementWithName:XMPPGoogleSharedStatusShow
|
||||
stringValue:sharedStatus[XMPPGoogleSharedStatusShow]]];
|
||||
|
||||
for(NSString *key in sharedStatus.allKeys.reverseObjectEnumerator) {
|
||||
if([key isEqualToString:XMPPGoogleSharedStatusShowAvailable] ||
|
||||
[key isEqualToString:XMPPGoogleSharedStatusShowBusy]) {
|
||||
|
||||
NSArray *statusList = sharedStatus[key];
|
||||
XMPPElement *statusElement = [XMPPElement elementWithName:@"status-list"];
|
||||
[statusElement addAttributeWithName:@"show" stringValue:key];
|
||||
|
||||
for(NSString *status in statusList)
|
||||
[statusElement addChild:[XMPPElement elementWithName:@"status" stringValue:status]];
|
||||
[element addChild:statusElement];
|
||||
} else if(!([key isEqualToString:XMPPGoogleSharedStatusInvisible] ||
|
||||
[key isEqualToString:XMPPGoogleSharedStatusShow] ||
|
||||
[key isEqualToString:XMPPGoogleSharedStatusStatus])) {
|
||||
NSLog(@"Invalid element: %@", key);
|
||||
}
|
||||
}
|
||||
|
||||
XMPPElement *invisible = [XMPPElement elementWithName:XMPPGoogleSharedStatusInvisible];
|
||||
[invisible addAttributeWithName:@"value"
|
||||
stringValue:[sharedStatus[XMPPGoogleSharedStatusInvisible] boolValue] ? @"true" : @"false"];
|
||||
[element addChild:invisible];
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
#pragma mark - Other Properties
|
||||
|
||||
- (void)setAssumeIdleUpdateResponsibility:(BOOL)flag {
|
||||
_assumeIdleUpdateResponsibility = flag;
|
||||
}
|
||||
|
||||
- (void)setStatusAvailability:(NSString *)statusAvailability {
|
||||
_statusAvailability = statusAvailability;
|
||||
|
||||
XMPPPresence *presence = [XMPPPresence presence];
|
||||
[presence addChild:[XMPPElement elementWithName:@"priority" stringValue:GOOGLE_PRESENCE_PRIORITY]];
|
||||
XMPPElement *show = [XMPPElement elementWithName:@"show"];
|
||||
XMPPElement *status = [XMPPElement elementWithName:@"status"];
|
||||
|
||||
if(self.statusAvailability) {
|
||||
[show setStringValue:self.statusAvailability];
|
||||
[presence addChild:show];
|
||||
}
|
||||
|
||||
if(self.statusMessage) {
|
||||
[status setStringValue:self.statusMessage];
|
||||
[presence addChild:status];
|
||||
}
|
||||
|
||||
[self.xmppStream sendElement:presence];
|
||||
}
|
||||
|
||||
- (void)setStatusMessage:(NSString *)statusMessage {
|
||||
_statusMessage = statusMessage;
|
||||
|
||||
XMPPPresence *presence = [XMPPPresence presence];
|
||||
XMPPElement *show = [XMPPElement elementWithName:@"show"];
|
||||
XMPPElement *status = [XMPPElement elementWithName:@"status"];
|
||||
|
||||
if(self.statusAvailability) {
|
||||
[show setStringValue:self.statusAvailability];
|
||||
[presence addChild:show];
|
||||
}
|
||||
|
||||
if(self.statusMessage) {
|
||||
[status setStringValue:self.statusMessage];
|
||||
[presence addChild:status];
|
||||
}
|
||||
|
||||
[self.xmppStream sendElement:presence];
|
||||
}
|
||||
|
||||
#ifdef _XMPP_SYSTEM_INPUT_ACTIVITY_MONITOR_H
|
||||
|
||||
#pragma mark - XMPP System Input Activity Monitor Delegate
|
||||
|
||||
- (void)xmppSystemInputActivityMonitorDidBecomeActive:(XMPPSystemInputActivityMonitor *)xmppSystemInputActivityMonitor
|
||||
{
|
||||
self.show = self.previousShow;
|
||||
}
|
||||
|
||||
- (void)xmppSystemInputActivityMonitorDidBecomeInactive:(XMPPSystemInputActivityMonitor *)xmppSystemInputActivityMonitor
|
||||
{
|
||||
self.previousShow = self.show;
|
||||
self.show = XMPPGoogleSharedStatusShowIdle;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@end
|
||||
Reference in new issue
Block a user