Plugins/Purple Service/ESPurpleJabberAccount.m
author Zachary West <zacw@adium.im>
Fri Oct 16 10:54:20 2009 -0400 (2009-10-16)
changeset 2615 bcbb03bada1a
parent 2386 b76c84a95828
child 3042 c6ef8efaf14f
permissions -rw-r--r--
Add a "BOSH Server" option for Jabber accounts.
David@0
     1
/* 
David@0
     2
 * Adium is the legal property of its developers, whose names are listed in the copyright file included
David@0
     3
 * with this source distribution.
David@0
     4
 * 
David@0
     5
 * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
David@0
     6
 * General Public License as published by the Free Software Foundation; either version 2 of the License,
David@0
     7
 * or (at your option) any later version.
David@0
     8
 * 
David@0
     9
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
David@0
    10
 * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
David@0
    11
 * Public License for more details.
David@0
    12
 * 
David@0
    13
 * You should have received a copy of the GNU General Public License along with this program; if not,
David@0
    14
 * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
David@0
    15
 */
David@0
    16
David@0
    17
#import "ESPurpleJabberAccount.h"
David@0
    18
#import <AdiumLibpurple/SLPurpleCocoaAdapter.h>
David@0
    19
#import <Adium/AIAccountControllerProtocol.h>
David@0
    20
#import <Adium/AIInterfaceControllerProtocol.h>
David@0
    21
#import <Adium/AIStatusControllerProtocol.h>
David@0
    22
#import <Adium/AIContactControllerProtocol.h>
David@0
    23
#import <Adium/AIChat.h>
David@0
    24
#import <Adium/AIHTMLDecoder.h>
David@0
    25
#import <Adium/AIListContact.h>
David@0
    26
#import <Adium/AIStatus.h>
David@0
    27
#import <Adium/AIStatusIcons.h>
David@0
    28
#import <Adium/ESFileTransfer.h>
David@0
    29
#import <Adium/AIService.h>
David@0
    30
#import <AIUtilities/AIApplicationAdditions.h>
David@0
    31
#import <AIUtilities/AIAttributedStringAdditions.h>
David@0
    32
#import <AIUtilities/AIDictionaryAdditions.h>
David@0
    33
#import <AIUtilities/AIStringAdditions.h>
catfish@2093
    34
#import <libpurple/presence.h>
catfish@2093
    35
#import <libpurple/si.h>
catfish@2093
    36
#import <SystemConfiguration/SystemConfiguration.h>
David@0
    37
#import "AMXMLConsoleController.h"
David@0
    38
#import "AMPurpleJabberServiceDiscoveryBrowsing.h"
David@0
    39
#import "ESPurpleJabberAccountViewController.h"
David@0
    40
#import "AMPurpleJabberAdHocServer.h"
David@0
    41
#import "AMPurpleJabberAdHocPing.h"
David@0
    42
David@0
    43
#define DEFAULT_JABBER_HOST @"@jabber.org"
David@0
    44
David@84
    45
@interface ESPurpleJabberAccount ()
David@0
    46
- (BOOL)enableXMLConsole;
David@0
    47
@end
David@0
    48
David@0
    49
@implementation ESPurpleJabberAccount
David@0
    50
David@0
    51
/*!
David@0
    52
 * @brief The UID will be changed. The account has a chance to perform modifications
David@0
    53
 *
David@0
    54
 * Upgrade old Jabber accounts stored with the host in a separate key to have the right UID, in the form
David@0
    55
 * name@server.org
David@0
    56
 *
David@0
    57
 * Append @jabber.org to a proposed UID which has no domain name and does not need to be updated.
David@0
    58
 *
David@0
    59
 * @param proposedUID The proposed, pre-filtered UID (filtered means it has no characters invalid for this servce)
David@0
    60
 * @result The UID to use; the default implementation just returns proposedUID.
David@0
    61
 */
David@0
    62
- (NSString *)accountWillSetUID:(NSString *)proposedUID
David@0
    63
{
David@0
    64
	proposedUID = [proposedUID lowercaseString];
David@0
    65
	NSString	*correctUID;
David@0
    66
	
David@0
    67
	if ((proposedUID && ([proposedUID length] > 0)) && 
David@0
    68
	   ([proposedUID rangeOfString:@"@"].location == NSNotFound)) {
David@0
    69
		
David@0
    70
		NSString	*host;
David@0
    71
		//Upgrade code: grab a previously specified Jabber host
David@740
    72
		if ((host = [self preferenceForKey:@"Jabber:Host" group:GROUP_ACCOUNT_STATUS])) {
David@0
    73
			//Determine our new, full UID
David@0
    74
			correctUID = [NSString stringWithFormat:@"%@@%@",proposedUID, host];
David@0
    75
David@0
    76
			//Clear the preference and then set the UID so we don't perform this upgrade again
David@0
    77
			[self setPreference:nil forKey:@"Jabber:Host" group:GROUP_ACCOUNT_STATUS];
David@0
    78
			[self setPreference:correctUID forKey:@"FormattedUID" group:GROUP_ACCOUNT_STATUS];
David@0
    79
David@0
    80
		} else {
David@0
    81
			//Append [self serverSuffix] (e.g. @jabber.org) to a Jabber account with no server
David@0
    82
			correctUID = [proposedUID stringByAppendingString:[self serverSuffix]];
David@0
    83
		}
David@0
    84
	} else {
David@0
    85
		correctUID = proposedUID;
David@0
    86
	}
David@0
    87
David@0
    88
	return correctUID;
David@0
    89
}
David@0
    90
David@0
    91
- (const char*)protocolPlugin
David@0
    92
{
David@0
    93
   return "prpl-jabber";
David@0
    94
}
David@0
    95
David@0
    96
- (void)dealloc
David@0
    97
{
David@0
    98
	[xmlConsoleController close];
David@0
    99
	[xmlConsoleController release];
David@0
   100
David@0
   101
	[super dealloc];
David@0
   102
}
David@0
   103
David@0
   104
- (NSSet *)supportedPropertyKeys
David@0
   105
{
David@0
   106
	static NSMutableSet *supportedPropertyKeys = nil;
David@0
   107
	
David@0
   108
	if (!supportedPropertyKeys) {
David@0
   109
		supportedPropertyKeys = [[NSMutableSet alloc] initWithObjects:
David@0
   110
			@"AvailableMessage",
David@0
   111
			@"Invisible",
David@0
   112
			nil];
David@0
   113
		[supportedPropertyKeys unionSet:[super supportedPropertyKeys]];
David@0
   114
	}
David@0
   115
	
David@0
   116
	return supportedPropertyKeys;
David@0
   117
}
David@0
   118
David@0
   119
- (void)configurePurpleAccount
David@0
   120
{
David@0
   121
	[super configurePurpleAccount];
David@0
   122
	
David@0
   123
	NSString	*connectServer;
David@0
   124
	BOOL		forceOldSSL, allowPlaintext, requireTLS;
David@0
   125
David@427
   126
	purple_account_set_username(account, self.purpleAccountName);
David@0
   127
David@0
   128
	//'Connect via' server (nil by default)
David@0
   129
	connectServer = [self preferenceForKey:KEY_JABBER_CONNECT_SERVER group:GROUP_ACCOUNT_STATUS];
David@0
   130
	//XXX - As of libpurple 2.0.0, 'localhost' doesn't work properly by 127.0.0.1 does. Hack!
David@0
   131
	if (connectServer && [connectServer isEqualToString:@"localhost"])
David@0
   132
		connectServer = @"127.0.0.1";
David@0
   133
	
David@0
   134
	purple_account_set_string(account, "connect_server", (connectServer ?
David@0
   135
														[connectServer UTF8String] :
David@0
   136
														""));
David@0
   137
	
zacw@2615
   138
	NSString *boshServer = [self preferenceForKey:KEY_JABBER_BOSH_SERVER group:GROUP_ACCOUNT_STATUS];
zacw@2615
   139
	
zacw@2615
   140
	purple_account_set_string(account, "bosh_url", (boshServer ? [boshServer UTF8String] : ""));
zacw@2615
   141
	
zacw@2202
   142
	// FT proxies
zacw@2202
   143
	NSString *ftProxies = [self preferenceForKey:KEY_JABBER_FT_PROXIES group:GROUP_ACCOUNT_STATUS];
zacw@2202
   144
	if (ftProxies.length) {
zacw@2202
   145
		purple_account_set_string(account, "ft_proxies", [ftProxies UTF8String]);
zacw@2202
   146
	}
zacw@2202
   147
	
David@0
   148
	//Force old SSL usage? (off by default)
David@0
   149
	forceOldSSL = [[self preferenceForKey:KEY_JABBER_FORCE_OLD_SSL group:GROUP_ACCOUNT_STATUS] boolValue];
David@0
   150
	purple_account_set_bool(account, "old_ssl", forceOldSSL);
David@0
   151
David@0
   152
	//Require SSL or TLS? (off by default)
David@0
   153
	requireTLS = [[self preferenceForKey:KEY_JABBER_REQUIRE_TLS group:GROUP_ACCOUNT_STATUS] boolValue];
David@0
   154
	purple_account_set_bool(account, "require_tls", requireTLS);
David@0
   155
David@0
   156
	//Allow plaintext authorization over an unencrypted connection? Purple will prompt if this is NO and is needed.
David@0
   157
	allowPlaintext = [[self preferenceForKey:KEY_JABBER_ALLOW_PLAINTEXT group:GROUP_ACCOUNT_STATUS] boolValue];
David@0
   158
	purple_account_set_bool(account, "auth_plain_in_clear", allowPlaintext);
David@0
   159
	
David@0
   160
	/* Mac OS X 10.4's cyrus-sasl's PLAIN mech gives us problems.  Is it a bug in the installed library, a bug in its compilation, or a bug
David@0
   161
	 * in our linkage against it? I don't know. The result is that the username gets included twice before the base64 encoding is performed.
David@0
   162
	 *
David@0
   163
	 * Furthermore, on any version, using the cyrus-sasl PLAIN mech prevents us from following Google Talk best practices for handling of domain names.
David@0
   164
	 * This is because we can't add to the <auth> response's attributes:
David@0
   165
	 *		xmlns:ga='http://www.google.com/talk/protocol/auth' ga:client-uses-full-bind-result='true'
David@0
   166
	 * as per http://code.google.com/apis/talk/jep_extensions/jid_domain_change.html and therefore we won't automatically resolve changing an
David@0
   167
	 * "@gmail.com" to "@googlemail.com" or some other domain name.
David@0
   168
	 *
David@0
   169
	 * We therefore use the PLAIN implementation in libpurple itself. Libpurple's own DIGEST-MD5 is always used for compatibility with old OpenFire
David@0
   170
	 * servers.
David@0
   171
	 *
David@0
   172
	 * This preference and the changes for it are added via the "libpurple_jabber_avoid_sasl_option_hack.diff" patch we apply during the build process.
David@0
   173
	 */
David@0
   174
	purple_prefs_set_bool("/plugins/prpl/jabber/avoid_sasl_for_plain_auth", YES);
David@0
   175
}
David@0
   176
David@0
   177
- (NSString *)serverSuffix
David@0
   178
{
David@0
   179
	NSString *host = [self preferenceForKey:KEY_JABBER_CONNECT_SERVER group:GROUP_ACCOUNT_STATUS];
David@0
   180
	
David@0
   181
	return (host ? host : DEFAULT_JABBER_HOST);
David@0
   182
}
David@0
   183
David@0
   184
/*!	@brief	Obtain the resource name for this Jabber account.
David@0
   185
 *
David@0
   186
 *	This could be extended in the future to perform keyword substitution (e.g. s/%computerName%/CSCopyMachineName()/).
David@0
   187
 *
David@0
   188
 *	@return	The resource name for the account.
David@0
   189
 */
David@0
   190
- (NSString *)resourceName
David@0
   191
{
David@0
   192
    NSString *resource = [self preferenceForKey:KEY_JABBER_RESOURCE group:GROUP_ACCOUNT_STATUS];
David@0
   193
    
David@0
   194
    if(resource == nil || [resource length] == 0)
David@0
   195
        resource = [(NSString*)SCDynamicStoreCopyLocalHostName(NULL) autorelease];
David@0
   196
    
David@0
   197
	return resource;
David@0
   198
}
David@0
   199
David@0
   200
- (const char *)purpleAccountName
David@0
   201
{
David@0
   202
	NSString	*userNameWithHost = nil, *completeUserName = nil;
David@0
   203
	BOOL		serverAppendedToUID;
David@0
   204
	
David@0
   205
	/*
David@0
   206
	 * Purple stores the username in the format username@server/resource.  We need to pass it a username in this format
David@0
   207
	 *
David@0
   208
	 * The user should put the username in username@server format, which is common for Jabber. If the user does
David@0
   209
	 * not specify the server, use jabber.org.
David@0
   210
	 */
David@0
   211
	
David@0
   212
	serverAppendedToUID = ([UID rangeOfString:@"@"].location != NSNotFound);
David@0
   213
	
David@0
   214
	if (serverAppendedToUID) {
David@0
   215
		userNameWithHost = UID;
David@0
   216
	} else {
David@0
   217
		userNameWithHost = [UID stringByAppendingString:[self serverSuffix]];
David@0
   218
	}
David@0
   219
David@0
   220
	completeUserName = [NSString stringWithFormat:@"%@/%@" ,userNameWithHost, [self resourceName]];
David@0
   221
David@0
   222
	return [completeUserName UTF8String];
David@0
   223
}
David@0
   224
David@0
   225
/*!
David@0
   226
 * @brief Connect Host
David@0
   227
 *
David@0
   228
 * Convenience method for retrieving the connect host for this account
David@0
   229
 *
David@0
   230
 * Rather than having a separate server field, Jabber uses the servername after the user name.
David@0
   231
 * username@server.org
David@0
   232
 *
David@0
   233
 * The connect server, stored in KEY_JABBER_CONNECT_SERVER, overrides this to provide the connect host. It will
David@0
   234
 * not be set in most cases.
David@0
   235
 */
David@0
   236
- (NSString *)host
David@0
   237
{
David@0
   238
	NSString	*host;
David@0
   239
	
David@0
   240
	if (!(host = [self preferenceForKey:KEY_JABBER_CONNECT_SERVER group:GROUP_ACCOUNT_STATUS])) {
David@3
   241
		NSUInteger location = [UID rangeOfString:@"@"].location;
David@0
   242
David@0
   243
		if ((location != NSNotFound) && (location + 1 < [UID length])) {
David@0
   244
			host = [UID substringFromIndex:(location + 1)];
David@0
   245
David@0
   246
		} else {
David@0
   247
			host = [self serverSuffix];
David@0
   248
		}
David@0
   249
	}
David@0
   250
	
David@0
   251
	return host;
David@0
   252
}
David@0
   253
David@0
   254
/*!
David@0
   255
 * @brief Should set aliases serverside?
David@0
   256
 *
David@0
   257
 * Jabber supports serverside aliases.
David@0
   258
 */
David@0
   259
- (BOOL)shouldSetAliasesServerside
David@0
   260
{
David@0
   261
	return YES;
David@0
   262
}
David@0
   263
David@0
   264
- (AIListContact *)contactWithUID:(NSString *)sourceUID
David@0
   265
{
David@0
   266
	AIListContact	*contact;
David@0
   267
	
David@89
   268
	contact = [adium.contactController existingContactWithService:service
David@0
   269
															account:self
David@0
   270
																UID:sourceUID];
David@0
   271
	if (!contact) {		
David@89
   272
		contact = [adium.contactController contactWithService:[self _serviceForUID:sourceUID]
David@0
   273
														account:self
David@0
   274
															UID:sourceUID];
David@0
   275
	}
David@0
   276
	
David@0
   277
	return contact;
David@0
   278
}
David@0
   279
David@0
   280
- (AIService *)_serviceForUID:(NSString *)contactUID
David@0
   281
{
David@0
   282
	AIService	*contactService;
David@0
   283
	NSString	*contactServiceID = nil;
David@0
   284
David@0
   285
	if ([contactUID hasSuffix:@"@gmail.com"] ||
David@0
   286
		[contactUID hasSuffix:@"@googlemail.com"]) {
David@0
   287
		contactServiceID = @"libpurple-jabber-gtalk";
David@0
   288
David@0
   289
	} else if([contactUID hasSuffix:@"@livejournal.com"]){
David@0
   290
		contactServiceID = @"libpurple-jabber-livejournal";
David@0
   291
		
David@0
   292
	} else {
David@0
   293
		contactServiceID = @"libpurple-Jabber";
David@0
   294
	}
David@0
   295
David@95
   296
	contactService = [adium.accountController serviceWithUniqueID:contactServiceID];
David@0
   297
	
David@0
   298
	return contactService;
David@0
   299
}
David@0
   300
David@0
   301
- (id)authorizationRequestWithDict:(NSDictionary*)dict {
David@0
   302
	switch ([[self preferenceForKey:KEY_JABBER_SUBSCRIPTION_BEHAVIOR group:GROUP_ACCOUNT_STATUS] intValue]) {
David@0
   303
		case 2: // always accept + add
David@0
   304
			// add
David@0
   305
			{
David@0
   306
				NSString *groupname = [self preferenceForKey:KEY_JABBER_SUBSCRIPTION_GROUP group:GROUP_ACCOUNT_STATUS];
David@0
   307
				if ([groupname length] > 0) {
David@427
   308
					AIListContact *contact = [adium.contactController contactWithService:self.service account:self UID:[dict objectForKey:@"Remote Name"]];
David@89
   309
					AIListGroup *group = [adium.contactController groupWithUID:groupname];
David@199
   310
					[contact.account addContact:contact toGroup:group];
David@0
   311
				}
David@0
   312
			}
David@0
   313
			// fallthrough
David@0
   314
		case 1: // always accept
David@0
   315
			[[self purpleAdapter] doAuthRequestCbValue:[[[dict objectForKey:@"authorizeCB"] retain] autorelease] withUserDataValue:[[[dict objectForKey:@"userData"] retain] autorelease]];
David@0
   316
			break;
David@0
   317
		case 3: // always deny
David@0
   318
			[[self purpleAdapter] doAuthRequestCbValue:[[[dict objectForKey:@"denyCB"] retain] autorelease] withUserDataValue:[[[dict objectForKey:@"userData"] retain] autorelease]];
David@0
   319
			break;
David@0
   320
		default: // ask (should be 0)
David@0
   321
			return [super authorizationRequestWithDict:dict];
David@0
   322
	}
David@0
   323
David@0
   324
	return NULL;
David@0
   325
}
David@0
   326
David@0
   327
- (void)purpleAccountRegistered:(BOOL)success
David@0
   328
{
David@427
   329
	if(success && [self.service accountViewController]) {
David@0
   330
		const char *usernamestr = purple_account_get_username(account);
David@0
   331
		NSString *username;
David@0
   332
		if (usernamestr) {
David@0
   333
			NSString *userWithResource = [NSString stringWithUTF8String:usernamestr];
David@0
   334
			NSRange slashrange = [userWithResource rangeOfString:@"/"];
David@0
   335
			if(slashrange.location != NSNotFound)
David@0
   336
				username = [userWithResource substringToIndex:slashrange.location];
David@0
   337
			else
David@0
   338
				username = userWithResource;
David@0
   339
		} else
David@0
   340
			username = (id)[NSNull null];
David@0
   341
David@0
   342
		NSString *pw = (purple_account_get_password(account) ? [NSString stringWithUTF8String:purple_account_get_password(account)] : [NSNull null]);
David@0
   343
		
David@1109
   344
		[[NSNotificationCenter defaultCenter] postNotificationName:AIAccountUsernameAndPasswordRegisteredNotification
David@0
   345
												  object:self
David@0
   346
												userInfo:[NSDictionary dictionaryWithObjectsAndKeys:
David@0
   347
													username, @"username",
David@0
   348
													pw, @"password",
David@0
   349
													nil]];
David@0
   350
	}
David@0
   351
}
David@0
   352
David@0
   353
#pragma mark Status
David@0
   354
David@0
   355
- (NSString *)encodedAttributedString:(NSAttributedString *)inAttributedString forListObject:(AIListObject *)inListObject
David@0
   356
{
David@0
   357
	static AIHTMLDecoder *jabberHtmlEncoder = nil;
David@0
   358
	if (!jabberHtmlEncoder) {
David@0
   359
		jabberHtmlEncoder = [[AIHTMLDecoder alloc] init];
David@0
   360
		[jabberHtmlEncoder setIncludesHeaders:NO];
David@0
   361
		[jabberHtmlEncoder setIncludesFontTags:YES];
David@0
   362
		[jabberHtmlEncoder setClosesFontTags:YES];
David@0
   363
		[jabberHtmlEncoder setIncludesStyleTags:YES];
David@0
   364
		[jabberHtmlEncoder setIncludesColorTags:YES];
David@0
   365
		[jabberHtmlEncoder setEncodesNonASCII:NO];
David@0
   366
		[jabberHtmlEncoder setPreservesAllSpaces:NO];
David@0
   367
		[jabberHtmlEncoder setUsesAttachmentTextEquivalents:YES];
David@0
   368
	}
David@0
   369
	
David@0
   370
	return [jabberHtmlEncoder encodeHTML:inAttributedString imagesPath:nil];
David@0
   371
}
David@0
   372
David@0
   373
- (NSString *)_UIDForAddingObject:(AIListContact *)object
David@0
   374
{
David@837
   375
	NSString	*objectUID = object.UID;
David@0
   376
	NSString	*properUID;
David@0
   377
	
David@0
   378
	if ([objectUID rangeOfString:@"@"].location != NSNotFound) {
David@0
   379
		properUID = objectUID;
David@0
   380
	} else {
David@427
   381
		properUID = [NSString stringWithFormat:@"%@@%@",objectUID,self.host];
David@0
   382
	}
David@0
   383
	
David@0
   384
	return [properUID lowercaseString];
David@0
   385
}
David@0
   386
David@0
   387
- (NSString *)unknownGroupName {
David@0
   388
    return (AILocalizedString(@"Roster","Roster - the Jabber default group"));
David@0
   389
}
David@0
   390
David@0
   391
- (NSString *)connectionStringForStep:(int)step
David@0
   392
{
David@0
   393
	switch (step) {
David@0
   394
		case 0:
David@0
   395
			return AILocalizedString(@"Connecting",nil);
David@0
   396
			break;
David@0
   397
		case 1:
David@0
   398
			return AILocalizedString(@"Initializing Stream",nil);
David@0
   399
			break;
David@0
   400
		case 2:
David@0
   401
			return AILocalizedString(@"Reading data",nil);
David@0
   402
			break;			
David@0
   403
		case 3:
David@0
   404
			return AILocalizedString(@"Authenticating",nil);
David@0
   405
			break;
David@0
   406
		case 5:
David@0
   407
			return AILocalizedString(@"Initializing Stream",nil);
David@0
   408
			break;
David@0
   409
		case 6:
David@0
   410
			return AILocalizedString(@"Authenticating",nil);
David@0
   411
			break;
David@0
   412
	}
David@0
   413
	return nil;
David@0
   414
}
David@0
   415
David@0
   416
- (AIReconnectDelayType)shouldAttemptReconnectAfterDisconnectionError:(NSString **)disconnectionError
David@0
   417
{
David@0
   418
	AIReconnectDelayType shouldAttemptReconnect = [super shouldAttemptReconnectAfterDisconnectionError:disconnectionError];
David@0
   419
David@0
   420
	if (([self lastDisconnectionReason] == PURPLE_CONNECTION_ERROR_CERT_OTHER_ERROR) &&
David@0
   421
		([self shouldVerifyCertificates])) {
David@0
   422
		shouldAttemptReconnect = AIReconnectNever;
David@0
   423
	} else if (!finishedConnectProcess && ![password length] &&
David@0
   424
			   (disconnectionError &&
David@0
   425
			   ([*disconnectionError isEqualToString:[NSString stringWithUTF8String:_("Read Error")]] ||
David@0
   426
				[*disconnectionError isEqualToString:[NSString stringWithUTF8String:_("Service Unavailable")]] ||
David@0
   427
				[*disconnectionError isEqualToString:[NSString stringWithUTF8String:_("Forbidden")]]))) {
David@0
   428
		//No password specified + above error while we're connecting = behavior of various broken servers. Prompt for a password.
David@0
   429
		[self serverReportedInvalidPassword];
David@0
   430
		shouldAttemptReconnect = AIReconnectImmediately;
David@0
   431
	}
David@0
   432
 
David@0
   433
	return shouldAttemptReconnect;
David@0
   434
}
David@0
   435
David@0
   436
- (void)disconnectFromDroppedNetworkConnection
David@0
   437
{
David@0
   438
	/* Before we disconnect from a dropped network connection, set gc->disconnect_timeout to a non-0 value.
David@0
   439
	 * This will let the prpl know that we are disconnecting with no backing ssl connection and that therefore
David@0
   440
	 * the ssl connection is has should not be messaged in the process of disconnecting.
David@0
   441
	 */
David@0
   442
	PurpleConnection *gc = purple_account_get_connection(account);
David@0
   443
	if (PURPLE_CONNECTION_IS_VALID(gc) &&
David@0
   444
		!gc->disconnect_timeout) {
David@0
   445
		gc->disconnect_timeout = -1;
David@0
   446
		AILog(@"%@: Disconnecting from a dropped network connection", self);
David@0
   447
	}
David@0
   448
David@0
   449
	[super disconnectFromDroppedNetworkConnection];
David@0
   450
}
David@0
   451
David@0
   452
#pragma mark File transfer
David@0
   453
- (BOOL)canSendFolders
David@0
   454
{
David@0
   455
	return NO;
David@0
   456
}
David@0
   457
David@0
   458
- (void)beginSendOfFileTransfer:(ESFileTransfer *)fileTransfer
David@0
   459
{
David@0
   460
	[super _beginSendOfFileTransfer:fileTransfer];
David@0
   461
}
David@0
   462
David@0
   463
- (void)acceptFileTransferRequest:(ESFileTransfer *)fileTransfer
David@0
   464
{
David@0
   465
    [super acceptFileTransferRequest:fileTransfer];    
David@0
   466
}
David@0
   467
David@0
   468
- (void)rejectFileReceiveRequest:(ESFileTransfer *)fileTransfer
David@0
   469
{
David@0
   470
    [super rejectFileReceiveRequest:fileTransfer];    
David@0
   471
}
David@0
   472
David@0
   473
- (void)cancelFileTransfer:(ESFileTransfer *)fileTransfer
David@0
   474
{
David@0
   475
	[super cancelFileTransfer:fileTransfer];
David@0
   476
}
David@0
   477
David@0
   478
#pragma mark Status Messages
David@0
   479
- (NSString *)statusNameForPurpleBuddy:(PurpleBuddy *)buddy
David@0
   480
{
David@0
   481
	NSString		*statusName = nil;
David@0
   482
	PurplePresence	*presence = purple_buddy_get_presence(buddy);
David@0
   483
	PurpleStatus		*status = purple_presence_get_active_status(presence);
David@0
   484
	const char		*purpleStatusID = purple_status_get_id(status);
David@0
   485
	
David@0
   486
	if (!purpleStatusID) return nil;
David@0
   487
David@0
   488
	if (!strcmp(purpleStatusID, jabber_buddy_state_get_status_id(JABBER_BUDDY_STATE_CHAT))) {
David@0
   489
		statusName = STATUS_NAME_FREE_FOR_CHAT;
David@0
   490
		
David@0
   491
	} else if (!strcmp(purpleStatusID, jabber_buddy_state_get_status_id(JABBER_BUDDY_STATE_XA))) {
David@0
   492
		statusName = STATUS_NAME_EXTENDED_AWAY;
David@0
   493
		
David@0
   494
	} else if (!strcmp(purpleStatusID, jabber_buddy_state_get_status_id(JABBER_BUDDY_STATE_DND))) {
David@0
   495
		statusName = STATUS_NAME_DND;
David@0
   496
		
David@0
   497
	}
David@0
   498
	
David@0
   499
	return statusName;
David@0
   500
}
David@0
   501
David@0
   502
#pragma mark Menu items
David@0
   503
- (NSString *)titleForContactMenuLabel:(const char *)label forContact:(AIListContact *)inContact
David@0
   504
{
David@0
   505
	if (strcmp(label, "Un-hide From") == 0) {
David@837
   506
		return [NSString stringWithFormat:AILocalizedString(@"Un-hide From %@",nil),inContact.formattedUID];
David@0
   507
David@0
   508
	} else if (strcmp(label, "Temporarily Hide From") == 0) {
David@837
   509
		return [NSString stringWithFormat:AILocalizedString(@"Temporarily Hide From %@",nil),inContact.formattedUID];
David@0
   510
David@0
   511
	} else if (strcmp(label, "Unsubscribe") == 0) {
David@837
   512
		return [NSString stringWithFormat:AILocalizedString(@"Unsubscribe %@",nil),inContact.formattedUID];
David@0
   513
David@0
   514
	} else if (strcmp(label, "(Re-)Request authorization") == 0) {
David@837
   515
		return [NSString stringWithFormat:AILocalizedString(@"Re-request Authorization from %@",nil),inContact.formattedUID];
David@0
   516
David@0
   517
	} else if (strcmp(label,  "Cancel Presence Notification") == 0) {
David@837
   518
		return [NSString stringWithFormat:AILocalizedString(@"Cancel Presence Notification to %@",nil),inContact.formattedUID];	
zacw@2386
   519
		
zacw@2386
   520
	} else if (strcmp(label,  _("Ping")) == 0) {
zacw@2386
   521
		return [NSString stringWithFormat:AILocalizedString(@"Ping %@",nil),inContact.formattedUID];	
zacw@2386
   522
		
David@0
   523
	}
David@0
   524
	
David@0
   525
	return [super titleForContactMenuLabel:label forContact:inContact];
David@0
   526
}
David@0
   527
David@0
   528
- (NSString *)titleForAccountActionMenuLabel:(const char *)label
David@0
   529
{	
David@0
   530
	if (strcmp(label, "Set User Info...") == 0) {
David@0
   531
		return [AILocalizedString(@"Set User Info", nil) stringByAppendingEllipsis];
David@0
   532
		
David@0
   533
	} else 	if (strcmp(label, "Search for Users...") == 0) {
David@0
   534
		return [AILocalizedString(@"Search for Users", nil) stringByAppendingEllipsis];
David@0
   535
		
David@0
   536
	} else 	if (strcmp(label, "Set Mood...") == 0) {
David@0
   537
		return [AILocalizedString(@"Set Mood", nil) stringByAppendingEllipsis];
David@0
   538
		
David@0
   539
	} else 	if (strcmp(label, "Set Nickname...") == 0) {
David@0
   540
		return [AILocalizedString(@"Set Nickname", nil) stringByAppendingEllipsis];
David@0
   541
	} 
David@0
   542
	
David@0
   543
	return [super titleForAccountActionMenuLabel:label];
David@0
   544
}
David@0
   545
David@0
   546
#pragma mark Multiuser chat
David@0
   547
/*!
David@0
   548
 * @brief A chat will be joined
David@0
   549
 *
David@0
   550
 * This gives the account a chance to update any information in the chat's creation dictionary if desired.
David@0
   551
 *
David@0
   552
 * @result The final chat creation dictionary to use.
David@0
   553
 */
David@0
   554
- (NSDictionary *)willJoinChatUsingDictionary:(NSDictionary *)chatCreationDictionary
David@0
   555
{
David@0
   556
	if (![[chatCreationDictionary objectForKey:@"handle"] length]) {
David@0
   557
		NSMutableDictionary *dict = [[chatCreationDictionary mutableCopy] autorelease];
David@0
   558
		
David@837
   559
		[dict setObject:self.displayName
David@0
   560
				 forKey:@"handle"];
David@0
   561
David@0
   562
		chatCreationDictionary = dict;
David@0
   563
	}
David@0
   564
	
David@0
   565
	return chatCreationDictionary;
David@0
   566
}
David@0
   567
David@0
   568
- (BOOL)chatCreationDictionary:(NSDictionary *)chatCreationDict isEqualToDictionary:(NSDictionary *)baseDict
David@0
   569
{
David@0
   570
	/* If the chat isn't keeping track of a handle, it's because we added it in
David@0
   571
	 * willJoinChatUsingDictionary: above. Remove it from baseDict so the comparison is accurate.
David@0
   572
	 */
David@0
   573
	if (![chatCreationDict objectForKey:@"handle"])
David@0
   574
		baseDict = [baseDict dictionaryWithDifferenceWithSetOfKeys:[NSSet setWithObject:@"handle"]];
David@0
   575
David@0
   576
	return [chatCreationDict isEqualToDictionary:baseDict];
David@0
   577
}
David@0
   578
zacw@1347
   579
/*!
zacw@1347
   580
 * @brief Do group chats support topics?
zacw@1347
   581
 */
zacw@1347
   582
- (BOOL)groupChatsSupportTopic
zacw@1347
   583
{
zacw@1347
   584
	return YES;
zacw@1347
   585
}
zacw@1347
   586
zacw@2127
   587
/*!
zacw@2127
   588
 * @brief Return the "nickname" part of a MUC JID
zacw@2127
   589
 *
zacw@2127
   590
 * @param contact The AIListContact
zacw@2127
   591
 * @param chat the AIChat
zacw@2127
   592
 * @return The nickname for a chat participant
zacw@2127
   593
 */
zacw@2127
   594
- (NSString *)fallbackAliasForContact:(AIListContact *)contact inChat:(AIChat *)chat
zacw@2127
   595
{
zacw@2127
   596
	if (contact.isStranger && [contact.UID.lowercaseString rangeOfString:chat.name.lowercaseString].location != NSNotFound) {
zacw@2127
   597
		return [contact.UID substringFromIndex:[contact.UID rangeOfString:@"/"].location + 1];		
zacw@2127
   598
	} else {
zacw@2127
   599
		return [super fallbackAliasForContact:contact inChat:chat];
zacw@2127
   600
	}
zacw@2127
   601
}
zacw@2127
   602
David@0
   603
#pragma mark Status
David@0
   604
/*!
David@0
   605
 * @brief Return the purple status type to be used for a status
David@0
   606
 *
David@0
   607
 * Most subclasses should override this method; these generic values may be appropriate for others.
David@0
   608
 *
David@0
   609
 * Active services provided nonlocalized status names.  An AIStatus is passed to this method along with a pointer
David@0
   610
 * to the status message.  This method should handle any status whose statusNname this service set as well as any statusName
David@0
   611
 * defined in  AIStatusController.h (which will correspond to the services handled by Adium by default).
David@0
   612
 * It should also handle a status name not specified in either of these places with a sane default, most likely by loooking at
David@837
   613
 * statusState.statusType for a general idea of the status's type.
David@0
   614
 *
David@0
   615
 * @param statusState The status for which to find the purple status ID
David@0
   616
 * @param arguments Prpl-specific arguments which will be passed with the state. Message is handled automatically.
David@0
   617
 *
David@0
   618
 * @result The purple status ID
David@0
   619
 */
David@0
   620
- (const char *)purpleStatusIDForStatus:(AIStatus *)statusState
David@0
   621
							arguments:(NSMutableDictionary *)arguments
David@0
   622
{
David@0
   623
	const char		*statusID = NULL;
David@837
   624
	NSString		*statusName = statusState.statusName;
David@0
   625
	NSString		*statusMessageString = [statusState statusMessageString];
David@0
   626
	NSNumber		*priority = nil;
David@0
   627
	
David@0
   628
	if (!statusMessageString) statusMessageString = @"";
David@0
   629
David@837
   630
	switch (statusState.statusType) {
David@0
   631
		case AIAvailableStatusType:
David@0
   632
		{
David@0
   633
			if (([statusName isEqualToString:STATUS_NAME_FREE_FOR_CHAT]) ||
David@100
   634
			   ([statusMessageString caseInsensitiveCompare:[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_FREE_FOR_CHAT]] == NSOrderedSame))
David@0
   635
				statusID = jabber_buddy_state_get_status_id(JABBER_BUDDY_STATE_CHAT);
David@0
   636
			priority = [self preferenceForKey:KEY_JABBER_PRIORITY_AVAILABLE group:GROUP_ACCOUNT_STATUS];
David@0
   637
			break;
David@0
   638
		}
David@0
   639
			
David@0
   640
		case AIAwayStatusType:
David@0
   641
		{
David@0
   642
			if (([statusName isEqualToString:STATUS_NAME_DND]) ||
David@100
   643
				([statusMessageString caseInsensitiveCompare:[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_DND]] == NSOrderedSame) ||
David@0
   644
				[statusName isEqualToString:STATUS_NAME_BUSY]) {
David@0
   645
				//Note that Jabber doesn't actually support a 'busy' status; if we have it set because some other service supports it, treat it as DND
David@0
   646
				statusID = jabber_buddy_state_get_status_id(JABBER_BUDDY_STATE_DND);
David@0
   647
David@0
   648
			} else if (([statusName isEqualToString:STATUS_NAME_EXTENDED_AWAY]) ||
David@100
   649
					 ([statusMessageString caseInsensitiveCompare:[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_EXTENDED_AWAY]] == NSOrderedSame))
David@0
   650
				statusID = jabber_buddy_state_get_status_id(JABBER_BUDDY_STATE_XA);
David@0
   651
			priority = [self preferenceForKey:KEY_JABBER_PRIORITY_AWAY group:GROUP_ACCOUNT_STATUS];
David@0
   652
			break;
David@0
   653
		}
David@0
   654
			
David@0
   655
		case AIInvisibleStatusType:
David@0
   656
			AILog(@"Warning: Invisibility is not yet supported in libpurple 2.0.0 jabber");
David@0
   657
			priority = [self preferenceForKey:KEY_JABBER_PRIORITY_AWAY group:GROUP_ACCOUNT_STATUS];
David@0
   658
			statusID = jabber_buddy_state_get_status_id(JABBER_BUDDY_STATE_AWAY);
David@0
   659
//			statusID = "Invisible";
David@0
   660
			break;
David@0
   661
			
David@0
   662
		case AIOfflineStatusType:
David@0
   663
			break;
David@0
   664
	}
David@0
   665
David@0
   666
	//Set our priority, which is actually set along with the status...Default is 0.
David@0
   667
	[arguments setObject:(priority ? priority : [NSNumber numberWithInt:0])
David@0
   668
				  forKey:@"priority"];
David@0
   669
	
David@0
   670
	//We could potentially set buzz on a per-status basis. We have no UI for this, however.
David@0
   671
	[arguments setObject:[NSNumber numberWithBool:YES] forKey:@"buzz"];
David@0
   672
David@0
   673
	//If we didn't get a purple status ID, request one from super
David@0
   674
	if (statusID == NULL) statusID = [super purpleStatusIDForStatus:statusState arguments:arguments];
David@0
   675
	
David@0
   676
	return statusID;
David@0
   677
}
David@0
   678
David@0
   679
#pragma mark Gateway Tracking
David@0
   680
David@703
   681
- (void)addContact:(AIListContact *)theContact toGroupName:(NSString *)groupName contactName:(NSString *)contactName {
David@721
   682
	NSRange atsign = [theContact.UID rangeOfString:@"@"];
David@0
   683
	if(atsign.location != NSNotFound)
David@704
   684
		[super addContact:theContact toGroupName:groupName contactName:contactName];
David@0
   685
	else {
David@0
   686
		NSDictionary *gatewaydict;
David@0
   687
		// avoid duplicates!
catfish@1821
   688
		for (gatewaydict in gateways) {
catfish@1821
   689
			if([[[gatewaydict objectForKey:@"contact"] UID] isEqualToString:theContact.UID])
David@0
   690
				break;
David@0
   691
		}
catfish@1821
   692
		
catfish@1821
   693
		if (gatewaydict)
catfish@1821
   694
			[gateways removeObjectIdenticalTo:gatewaydict];
David@0
   695
David@0
   696
		[gateways addObject:[NSDictionary dictionaryWithObjectsAndKeys:
David@0
   697
							 theContact, @"contact",
David@0
   698
							 groupName, @"remoteGroup",
David@0
   699
							 nil]];
David@0
   700
	}
David@0
   701
}
David@0
   702
David@0
   703
- (void)removeContact:(AIListContact *)theContact {
David@721
   704
	NSRange atsign = [theContact.UID rangeOfString:@"@"];
David@0
   705
	if(atsign.location != NSNotFound)
David@0
   706
		[super removeContact:theContact];
David@0
   707
	else {
David@607
   708
		for (NSDictionary *gatewaydict in [[gateways copy] autorelease]) {
David@721
   709
			if([[[gatewaydict objectForKey:@"contact"] UID] isEqualToString:theContact.UID]) {
David@721
   710
				[[self purpleAdapter] removeUID:theContact.UID onAccount:self fromGroup:[gatewaydict objectForKey:@"remoteGroup"]];
David@0
   711
				
David@0
   712
				[gateways removeObjectIdenticalTo:gatewaydict];
David@0
   713
				break;
David@0
   714
			}
David@0
   715
		}
David@0
   716
	}
David@0
   717
}
David@0
   718
David@0
   719
#pragma mark XML Console, Tooltip, AdHoc Server Integration and Gateway Integration
David@0
   720
David@0
   721
/*!
David@0
   722
* @brief Returns whether or not this account is connected via an encrypted connection.
David@0
   723
 */
David@0
   724
- (BOOL)encrypted
David@0
   725
{
David@837
   726
	return (self.online && [self secureConnection]);
David@0
   727
}
David@0
   728
David@0
   729
- (void)didConnect {
David@0
   730
	[gateways release];
David@0
   731
	gateways = [[NSMutableArray alloc] init];
David@0
   732
David@0
   733
	[adhocServer release];
David@0
   734
	adhocServer = [[AMPurpleJabberAdHocServer alloc] initWithAccount:self];
catfish@2094
   735
	[adhocServer addCommand:@"ping" delegate:(id<AMPurpleJabberAdHocServerDelegate>)[AMPurpleJabberAdHocPing class] name:@"Ping"];
David@0
   736
	
David@0
   737
    [super didConnect];
David@0
   738
	
David@0
   739
	if ([self enableXMLConsole]) {
David@0
   740
		if (!xmlConsoleController) xmlConsoleController = [[AMXMLConsoleController alloc] init];
David@0
   741
		[xmlConsoleController setPurpleConnection:purple_account_get_connection(account)];
David@0
   742
	}
David@0
   743
David@0
   744
	discoveryBrowserController = [[AMPurpleJabberServiceDiscoveryBrowsing alloc] initWithAccount:self
David@0
   745
																				purpleConnection:purple_account_get_connection(account)];
David@0
   746
}
David@0
   747
David@0
   748
- (void)didDisconnect {
David@0
   749
	[xmlConsoleController setPurpleConnection:NULL];
David@0
   750
	
David@0
   751
	[discoveryBrowserController release]; discoveryBrowserController = nil;
David@0
   752
	[adhocServer release]; adhocServer = nil;
David@0
   753
David@0
   754
	[super didDisconnect];
David@0
   755
David@0
   756
	[gateways release]; gateways = nil;
David@0
   757
}
David@0
   758
David@0
   759
- (IBAction)showXMLConsole:(id)sender {
David@0
   760
    if(xmlConsoleController)
David@0
   761
        [xmlConsoleController showWindow:sender];
David@0
   762
    else
David@0
   763
        NSBeep();
David@0
   764
}
David@0
   765
David@0
   766
- (BOOL)enableXMLConsole
David@0
   767
{
David@0
   768
	BOOL enableConsole;
David@0
   769
#ifdef DEBUG_BUILD
David@0
   770
	//Always enable the XML console for debug builds
David@0
   771
	enableConsole = YES;
David@0
   772
#else
David@0
   773
	//For non-debug builds, only enable it if the preference is set
David@0
   774
	enableConsole = [[NSUserDefaults standardUserDefaults] boolForKey:@"AMXMPPShowAdvanced"];
David@0
   775
#endif
David@0
   776
	
David@0
   777
	return enableConsole;
David@0
   778
}
David@0
   779
David@0
   780
- (IBAction)showDiscoveryBrowser:(id)sender {
David@0
   781
	[discoveryBrowserController browse:sender];
David@0
   782
}
David@0
   783
David@0
   784
- (PurpleSslConnection *)secureConnection {
David@0
   785
	// this is really ugly
David@427
   786
	PurpleConnection *gc = purple_account_get_connection(self.purpleAccount);
David@0
   787
David@427
   788
	return ((gc && gc->proto_data) ? ((JabberStream*)purple_account_get_connection(self.purpleAccount)->proto_data)->gsc : NULL);
David@0
   789
}
David@0
   790
David@0
   791
- (void)setShouldVerifyCertificates:(BOOL)yesOrNo {
David@0
   792
	[self setPreference:[NSNumber numberWithBool:yesOrNo] forKey:KEY_JABBER_VERIFY_CERTS group:GROUP_ACCOUNT_STATUS];
David@0
   793
}
David@0
   794
David@0
   795
- (BOOL)shouldVerifyCertificates {
David@0
   796
	return [[self preferenceForKey:KEY_JABBER_VERIFY_CERTS group:GROUP_ACCOUNT_STATUS] boolValue];
David@0
   797
}
David@0
   798
David@0
   799
- (NSArray *)accountActionMenuItems {
David@0
   800
	AILog(@"Getting accountActionMenuItems for %@",self);
David@0
   801
	NSMutableArray *menu = [[NSMutableArray alloc] init];
David@0
   802
	
David@0
   803
	if([gateways count] > 0) {
David@0
   804
		NSDictionary *gatewaydict;
David@75
   805
		for(gatewaydict in gateways) {
David@0
   806
			AIListContact *gateway = [gatewaydict objectForKey:@"contact"];
David@837
   807
			NSMenuItem *mitem = [[NSMenuItem alloc] initWithTitle:gateway.UID action:@selector(registerGateway:) keyEquivalent:@""];
David@837
   808
			NSMenu *submenu = [[NSMenu alloc] initWithTitle:gateway.UID];
David@0
   809
			
David@0
   810
			NSArray *menuitemarray = [self menuItemsForContact:gateway];
catfish@2104
   811
			for (NSMenuItem *m2item in menuitemarray)
David@0
   812
				[submenu addItem:m2item];
David@0
   813
			
David@0
   814
			if([submenu numberOfItems] > 0)
David@0
   815
				[submenu addItem:[NSMenuItem separatorItem]];
David@0
   816
David@0
   817
			NSMenuItem *removeItem = [[NSMenuItem alloc] initWithTitle:AILocalizedString(@"Remove gateway","gateway menu item") action:@selector(removeGateway:) keyEquivalent:@""];
David@0
   818
			[removeItem setTarget:self];
David@0
   819
			[removeItem setRepresentedObject:gateway];
David@0
   820
			[submenu addItem:removeItem];
David@0
   821
			[removeItem release];
David@0
   822
			
David@0
   823
			[mitem setSubmenu:submenu];
David@0
   824
			[submenu release];
David@0
   825
			[mitem setRepresentedObject:gateway];
David@0
   826
			[mitem setImage:[AIStatusIcons statusIconForListObject:gateway
David@0
   827
															  type:AIStatusIconTab
David@0
   828
														 direction:AIIconNormal]];
David@0
   829
			[mitem setTarget:self];
David@0
   830
			[menu addObject:mitem];
David@0
   831
			[mitem release];
David@0
   832
		}
David@0
   833
        [menu addObject:[NSMenuItem separatorItem]];
David@0
   834
	}
David@0
   835
	
David@0
   836
    NSArray *supermenu = [super accountActionMenuItems];
David@0
   837
    if(supermenu) {
David@0
   838
		[menu addObjectsFromArray:supermenu];
David@0
   839
        [menu addObject:[NSMenuItem separatorItem]];
David@0
   840
	}
David@0
   841
David@0
   842
	if ([self enableXMLConsole]) {
David@0
   843
		NSMenuItem *xmlConsoleMenuItem = [[NSMenuItem alloc] initWithTitle:AILocalizedString(@"XML Console",nil)
David@0
   844
																	action:@selector(showXMLConsole:) 
David@0
   845
															 keyEquivalent:@""];
David@0
   846
		[xmlConsoleMenuItem setTarget:self];
David@0
   847
		[menu addObject:xmlConsoleMenuItem];
David@0
   848
		[xmlConsoleMenuItem release];
David@0
   849
	}
David@0
   850
David@0
   851
	NSMenuItem *discoveryBrowserMenuItem = [[NSMenuItem alloc] initWithTitle:AILocalizedString(@"Discovery Browser",nil)
David@0
   852
																	  action:@selector(showDiscoveryBrowser:) 
David@0
   853
															   keyEquivalent:@""];
David@0
   854
    [discoveryBrowserMenuItem setTarget:self];
David@0
   855
    [menu addObject:discoveryBrowserMenuItem];
David@0
   856
    [discoveryBrowserMenuItem release];
David@0
   857
	
David@0
   858
    return [menu autorelease];
David@0
   859
}
David@0
   860
David@0
   861
- (void)registerGateway:(NSMenuItem*)mitem {
David@0
   862
	if(mitem && [mitem representedObject])
David@427
   863
		jabber_register_gateway((JabberStream*)purple_account_get_connection(self.purpleAccount)->proto_data, [[[mitem representedObject] UID] UTF8String]);
David@0
   864
	else
David@0
   865
		NSBeep();
David@0
   866
}
David@0
   867
David@0
   868
- (void)removeGateway:(NSMenuItem*)mitem {
David@0
   869
	AIListContact *gateway = [mitem representedObject];
David@0
   870
	if(![gateway isKindOfClass:[AIListContact class]])
David@0
   871
		return;
David@0
   872
	// since this is a potentially dangerous operation, get a confirmation from the user first
David@0
   873
	if([[NSAlert alertWithMessageText:AILocalizedString(@"Really remove gateway?",nil)
David@0
   874
					 defaultButton:AILocalizedString(@"Remove","alert default button")
David@0
   875
				   alternateButton:AILocalizedString(@"Cancel",nil)
David@0
   876
					   otherButton:nil
David@837
   877
					 informativeTextWithFormat:AILocalizedString(@"This operation would remove the gateway %@ itself and all contacts belonging to the gateway on your contact list. It cannot be undone.",nil), gateway.UID] runModal] == NSAlertDefaultReturn) {
David@0
   878
		// first, locate all contacts on the roster that belong to this gateway
David@837
   879
		NSString *jid = gateway.UID;
David@0
   880
		NSString *pattern = [@"@" stringByAppendingString:jid];
David@0
   881
		NSMutableArray *gatewayContacts = [[NSMutableArray alloc] init];
zacw@2131
   882
		NSMutableSet *removeGroups = [NSMutableSet set];
catfish@2104
   883
		for (AIListContact *contact in self.contacts) {
zacw@2131
   884
			if([contact.UID hasSuffix:pattern]) {
David@0
   885
				[gatewayContacts addObject:contact];
zacw@2131
   886
				[removeGroups unionSet:contact.groups];
zacw@2131
   887
			}
David@0
   888
		}
David@0
   889
		// now, remove them from the roster
zacw@2131
   890
		[self removeContacts:gatewayContacts
zacw@2131
   891
				  fromGroups:removeGroups.allObjects];
zacw@2131
   892
		
David@0
   893
		[gatewayContacts release];
David@0
   894
		
David@0
   895
		// finally, remove the gateway itself
David@0
   896
		[self removeContact:gateway];
David@0
   897
	}
David@0
   898
}
David@0
   899
David@0
   900
- (AMPurpleJabberAdHocServer*)adhocServer {
David@0
   901
	return adhocServer;
David@0
   902
}
David@0
   903
David@0
   904
@end