Plugins/Purple Service/CBPurpleAccount.m
author Zachary West <zacw@adium.im>
Sun Nov 01 14:04:11 2009 -0500 (2009-11-01)
changeset 2848 d88a4b7a70a8
parent 2693 4bcad311909f
child 2938 3294410d095f
permissions -rw-r--r--
Display unhandled purple conversation writes in the next run loop. Fixes #13190.
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 "CBPurpleAccount.h"
David@0
    18
zacw@1509
    19
#import "PurpleService.h"
zacw@1509
    20
David@0
    21
#import <libpurple/notify.h>
David@0
    22
#import <libpurple/cmds.h>
David@0
    23
#import <AdiumLibpurple/SLPurpleCocoaAdapter.h>
David@0
    24
#import <Adium/AIAccount.h>
David@0
    25
#import <Adium/AIChat.h>
David@0
    26
#import <Adium/AIContentMessage.h>
zacw@1292
    27
#import <Adium/AIContentTopic.h>
zacw@1305
    28
#import <Adium/AIContentEvent.h>
zacw@1346
    29
#import <Adium/AIContentContext.h>
David@0
    30
#import <Adium/AIContentNotification.h>
David@0
    31
#import <Adium/AIHTMLDecoder.h>
David@0
    32
#import <Adium/AIListContact.h>
David@0
    33
#import <Adium/AIListGroup.h>
David@0
    34
#import <Adium/AIListObject.h>
David@0
    35
#import <Adium/AIMetaContact.h>
David@0
    36
#import <Adium/AIService.h>
David@0
    37
#import <Adium/AIServiceIcons.h>
David@0
    38
#import <Adium/AIStatus.h>
David@0
    39
#import <Adium/ESFileTransfer.h>
David@0
    40
#import <Adium/AIWindowController.h>
David@0
    41
#import <Adium/AIEmoticon.h>
David@0
    42
#import <Adium/AIAccountControllerProtocol.h>
David@0
    43
#import <Adium/AIChatControllerProtocol.h>
David@0
    44
#import <Adium/AIContactControllerProtocol.h>
David@14
    45
#import <Adium/AIContactObserverManager.h>
David@0
    46
#import <Adium/AIContentControllerProtocol.h>
David@0
    47
#import <Adium/AIInterfaceControllerProtocol.h>
David@0
    48
#import <Adium/AIStatusControllerProtocol.h>
David@0
    49
#import <AIUtilities/AIAttributedStringAdditions.h>
David@0
    50
#import <AIUtilities/AIDictionaryAdditions.h>
David@0
    51
#import <AIUtilities/AIMenuAdditions.h>
David@0
    52
#import <AIUtilities/AIMutableOwnerArray.h>
David@0
    53
#import <AIUtilities/AIStringAdditions.h>
David@0
    54
#import <AIUtilities/AIApplicationAdditions.h>
David@0
    55
#import <AIUtilities/AIObjectAdditions.h>
David@0
    56
#import <AIUtilities/AIImageAdditions.h>
David@0
    57
#import <AIUtilities/AIImageDrawingAdditions.h>
David@0
    58
#import <AIUtilities/AIMutableStringAdditions.h>
David@0
    59
#import <AIUtilities/AISystemNetworkDefaults.h>
David@36
    60
#import <Adium/AdiumAuthorization.h>
David@0
    61
David@0
    62
#import "ESiTunesPlugin.h"
David@0
    63
#import "AMPurpleTuneTooltip.h"
David@0
    64
#import "adiumPurpleRequest.h"
David@0
    65
#import "AIDualWindowInterfacePlugin.h"
David@0
    66
zacw@998
    67
#ifdef HAVE_CDSA
zacw@998
    68
#import "AIPurpleCertificateViewer.h"
zacw@998
    69
#endif
zacw@998
    70
David@0
    71
#define NO_GROUP						@"__NoGroup__"
David@0
    72
David@0
    73
#define	PREF_GROUP_ALIASES			@"Aliases"		//Preference group to store aliases in
David@0
    74
#define NEW_ACCOUNT_DISPLAY_TEXT		AILocalizedString(@"<New Account>", "Placeholder displayed as the name of a new account")
David@0
    75
David@0
    76
#define	KEY_PRIVACY_OPTION	@"Privacy Option"
David@0
    77
David@84
    78
@interface CBPurpleAccount ()
David@0
    79
- (NSString *)_mapIncomingGroupName:(NSString *)name;
David@0
    80
- (NSString *)_mapOutgoingGroupName:(NSString *)name;
David@0
    81
- (void)setTypingFlagOfChat:(AIChat *)inChat to:(NSNumber *)typingState;
David@0
    82
- (void)_receivedMessage:(NSAttributedString *)attributedMessage inChat:(AIChat *)chat fromListContact:(AIListContact *)sourceContact flags:(PurpleMessageFlags)flags date:(NSDate *)date;
David@0
    83
- (NSNumber *)shouldCheckMail;
David@0
    84
- (void)configurePurpleAccountNotifyingTarget:(id)target selector:(SEL)selector;
David@0
    85
- (void)continueConnectWithConfiguredPurpleAccount;
David@0
    86
- (void)continueConnectWithConfiguredProxy;
David@0
    87
- (void)continueRegisterWithConfiguredPurpleAccount;
David@0
    88
- (void)promptForHostBeforeConnecting;
David@0
    89
- (void)setAccountProfileTo:(NSAttributedString *)profile configurePurpleAccountContext:(NSInvocation *)inInvocation;
David@0
    90
- (void)performAccountMenuAction:(NSMenuItem *)sender;
zacw@998
    91
zacw@999
    92
- (void)showServerCertificate;
David@0
    93
@end
David@0
    94
David@0
    95
@implementation CBPurpleAccount
David@0
    96
David@0
    97
static SLPurpleCocoaAdapter *purpleAdapter = nil;
David@0
    98
David@0
    99
// The PurpleAccount currently associated with this Adium account
David@0
   100
- (PurpleAccount*)purpleAccount
David@0
   101
{
David@0
   102
	//Create a purple account if one does not already exist
David@0
   103
	if (!account) {
David@0
   104
		[self createNewPurpleAccount];
David@427
   105
		AILog(@"Created PurpleAccount 0x%x with UID %@, protocolPlugin %s", account, self.UID, [self protocolPlugin]);
David@0
   106
	}
David@0
   107
	
David@0
   108
    return account;
David@0
   109
}
David@0
   110
David@0
   111
- (SLPurpleCocoaAdapter *)purpleAdapter
David@0
   112
{
David@0
   113
	if (!purpleAdapter) {
David@0
   114
		purpleAdapter = [[SLPurpleCocoaAdapter sharedInstance] retain];	
David@0
   115
	}	
David@0
   116
	return purpleAdapter;
David@0
   117
}
David@0
   118
David@0
   119
// Subclasses must override this
David@0
   120
- (const char*)protocolPlugin { return NULL; }
David@0
   121
zacw@2329
   122
- (PurplePluginProtocolInfo *)protocolInfo
zacw@2329
   123
{
zacw@2329
   124
	PurplePlugin				*prpl;
zacw@2329
   125
	
zacw@2330
   126
	if ((prpl = purple_find_prpl(purple_account_get_protocol_id(account)))) {
zacw@2329
   127
		return PURPLE_PLUGIN_PROTOCOL_INFO(prpl);
zacw@2329
   128
	}
zacw@2329
   129
	
zacw@2329
   130
	return NULL;
zacw@2329
   131
}
zacw@2329
   132
David@0
   133
// Contacts ------------------------------------------------------------------------------------------------
David@0
   134
#pragma mark Contacts
David@0
   135
- (void)newContact:(AIListContact *)theContact withName:(NSString *)inName
David@0
   136
{
David@0
   137
David@0
   138
}
David@0
   139
David@702
   140
- (void)addContact:(AIListContact *)theContact toGroupName:(NSString *)groupName contactName:(NSString *)contactName
David@0
   141
{
David@0
   142
	//When a new contact is created, if we aren't already silent and delayed, set it  a second to cover our initial
David@0
   143
	//status updates
David@0
   144
	if (!silentAndDelayed) {
David@0
   145
		[self silenceAllContactUpdatesForInterval:2.0];
David@14
   146
		[[AIContactObserverManager sharedManager] delayListObjectNotificationsUntilInactivity];		
David@0
   147
	}
David@0
   148
	
David@0
   149
	//If the name we were passed differs from the current formatted UID of the contact, it's itself a formatted UID
David@0
   150
	//This is important since we may get an alias ("Evan Schoenberg") from the server but also want the formatted name
David@837
   151
	if (![contactName isEqualToString:theContact.formattedUID] && ![contactName isEqualToString:theContact.UID]) {
David@0
   152
		[theContact setValue:contactName
David@0
   153
							 forProperty:@"FormattedUID"
David@0
   154
							 notify:NotifyLater];
David@0
   155
	}
David@0
   156
	
David@0
   157
	if (groupName && [groupName isEqualToString:@PURPLE_ORPHANS_GROUP_NAME]) {
David@589
   158
		[theContact addRemoteGroupName:AILocalizedString(@"Orphans","Name for the orphans group")];
David@0
   159
	} else if (groupName && [groupName length] != 0) {
David@589
   160
		[theContact addRemoteGroupName:[self _mapIncomingGroupName:groupName]];
David@0
   161
	} else {
David@0
   162
		AILog(@"Got a nil group for %@",theContact);
David@0
   163
	}
David@0
   164
	
David@0
   165
	[self gotGroupForContact:theContact];
David@0
   166
}
David@0
   167
David@702
   168
- (void)removeContact:(AIListContact *)theContact fromGroupName:(NSString *)groupName
David@702
   169
{
David@702
   170
	NSParameterAssert(groupName != nil); //is this always true?
David@702
   171
	NSParameterAssert(theContact != nil);
David@702
   172
	[theContact removeRemoteGroupName:[self _mapIncomingGroupName:groupName]];
David@702
   173
}
David@702
   174
David@0
   175
/*!
David@0
   176
 * @brief Change the UID of a contact
David@0
   177
 *
David@0
   178
 * If we're just passed a formatted version of the current UID, don't change the UID but instead use the information
David@0
   179
 * as the FormattedUID.  For example, we get sent this when an AIM contact's name formatting changes; we always want
David@0
   180
 * to use a lowercase and space-free version for the UID, however.
David@0
   181
 */
David@0
   182
- (void)renameContact:(AIListContact *)theContact toUID:(NSString *)newUID
David@0
   183
{
David@0
   184
	//If the name we were passed differs from the current formatted UID of the contact, it's itself a formatted UID
David@0
   185
	//This is important since we may get an alias ("Evan Schoenberg") from the server but also want the formatted name
David@427
   186
	NSString	*normalizedUID = [self.service normalizeUID:newUID removeIgnoredCharacters:YES];
David@0
   187
	
David@721
   188
	if ([normalizedUID isEqualToString:theContact.UID]) {
David@0
   189
		[theContact setValue:newUID
David@0
   190
							 forProperty:@"FormattedUID"
David@0
   191
							 notify:NotifyLater];		
David@0
   192
	} else {
David@0
   193
		[theContact setUID:newUID];		
David@0
   194
	}
David@0
   195
}
David@0
   196
David@0
   197
- (void)updateContact:(AIListContact *)theContact toAlias:(NSString *)purpleAlias
David@0
   198
{
David@721
   199
	if (![[purpleAlias compactedString] isEqualToString:[theContact.UID compactedString]]) {
David@0
   200
		//Store this alias as the serverside display name so long as it isn't identical when unformatted to the UID
David@0
   201
		[theContact setServersideAlias:purpleAlias
David@0
   202
							  silently:silentAndDelayed];
David@0
   203
David@0
   204
	} else {
David@0
   205
		//If it's the same characters as the UID, apply it as a formatted UID
David@837
   206
		if (![purpleAlias isEqualToString:theContact.formattedUID] && 
David@721
   207
			![purpleAlias isEqualToString:theContact.UID]) {
David@0
   208
			[theContact setFormattedUID:purpleAlias
David@0
   209
								 notify:NotifyLater];
David@0
   210
David@0
   211
			//Apply any changes
David@0
   212
			[theContact notifyOfChangedPropertiesSilently:silentAndDelayed];
David@0
   213
		}
David@0
   214
	}
David@0
   215
}
David@0
   216
David@0
   217
- (void)updateContact:(AIListContact *)theContact forEvent:(NSNumber *)event
David@0
   218
{
David@0
   219
}		
David@0
   220
David@0
   221
David@0
   222
//Signed online
David@0
   223
- (void)updateSignon:(AIListContact *)theContact withData:(void *)data
David@0
   224
{
David@0
   225
	[theContact setOnline:YES
David@0
   226
				   notify:NotifyLater
David@0
   227
				 silently:silentAndDelayed];
David@0
   228
David@0
   229
	[theContact notifyOfChangedPropertiesSilently:silentAndDelayed];
David@0
   230
}
David@0
   231
David@0
   232
//Signed offline
David@0
   233
- (void)updateSignoff:(AIListContact *)theContact withData:(void *)data
David@0
   234
{
David@0
   235
	[theContact setOnline:NO
David@0
   236
				   notify:NotifyLater
David@0
   237
				 silently:silentAndDelayed];
David@0
   238
	
David@0
   239
	[theContact notifyOfChangedPropertiesSilently:silentAndDelayed];
David@0
   240
}
David@0
   241
David@0
   242
//Signon Time
David@0
   243
- (void)updateSignonTime:(AIListContact *)theContact withData:(NSDate *)signonDate
David@0
   244
{	
David@0
   245
	[theContact setSignonDate:signonDate
David@0
   246
					   notify:NotifyLater];
David@0
   247
	
David@0
   248
	//Apply any changes
David@0
   249
	[theContact notifyOfChangedPropertiesSilently:silentAndDelayed];
David@0
   250
}
David@0
   251
David@0
   252
/*!
David@0
   253
 * @brief Status name to use for a Purple buddy
David@0
   254
 */
David@0
   255
- (NSString *)statusNameForPurpleBuddy:(PurpleBuddy *)buddy
David@0
   256
{
David@0
   257
	return nil;
David@0
   258
}
David@0
   259
David@0
   260
/*!
David@0
   261
 * @brief Status message for a contact
David@0
   262
 */
David@0
   263
- (NSAttributedString *)statusMessageForPurpleBuddy:(PurpleBuddy *)buddy
David@0
   264
{
David@0
   265
	PurplePresence		*presence = purple_buddy_get_presence(buddy);
David@0
   266
	PurpleStatus		*status = (presence ? purple_presence_get_active_status(presence) : NULL);
David@0
   267
	const char			*message = (status ? purple_status_get_attr_string(status, "message") : NULL);
zacw@2227
   268
	NSString			*statusMessage = nil;
David@0
   269
	
zacw@2110
   270
	// Get the plugin's status message for this buddy if they don't have a status message
zacw@2110
   271
	if (!message) {
zacw@2329
   272
		PurplePluginProtocolInfo  *prpl_info = self.protocolInfo;
zacw@2110
   273
		
zacw@2110
   274
		if (prpl_info && prpl_info->status_text) {
zacw@2227
   275
			char *status_text = (prpl_info->status_text)(buddy);
zacw@2120
   276
			
zacw@2120
   277
			// Don't display "Offline" as a status message.
zacw@2227
   278
			if (status_text && strcmp(status_text, _("Offline")) != 0) {
zacw@2227
   279
				statusMessage = [NSString stringWithUTF8String:status_text];				
zacw@2120
   280
			}
zacw@2227
   281
			
zacw@2227
   282
			g_free(status_text);
zacw@2110
   283
		}
zacw@2227
   284
	} else {
zacw@2227
   285
		statusMessage = [NSString stringWithUTF8String:message];
zacw@2110
   286
	}
zacw@2221
   287
	
zacw@2227
   288
	return statusMessage ? [AIHTMLDecoder decodeHTML:statusMessage] : nil;
David@0
   289
}
David@0
   290
David@0
   291
/*!
David@0
   292
 * @brief Update the status message and away state of the contact
David@0
   293
 */
Evan@153
   294
- (void)updateStatusForContact:(AIListContact *)theContact toStatusType:(NSNumber *)statusTypeNumber statusName:(NSString *)statusName statusMessage:(NSAttributedString *)statusMessage isMobile:(BOOL)isMobile
David@0
   295
{
David@0
   296
	[theContact setStatusWithName:statusName
sholt@2693
   297
					   statusType:[statusTypeNumber integerValue]
David@0
   298
						   notify:NotifyLater];
David@0
   299
	[theContact setStatusMessage:statusMessage
David@0
   300
						  notify:NotifyLater];
Evan@153
   301
	[theContact setIsMobile:isMobile notify:NotifyLater];
Evan@153
   302
David@0
   303
	//Apply the change
David@0
   304
	[theContact notifyOfChangedPropertiesSilently:silentAndDelayed];
David@0
   305
}
David@0
   306
David@0
   307
//Idle time
David@0
   308
- (void)updateWentIdle:(AIListContact *)theContact withData:(NSDate *)idleSinceDate
David@0
   309
{
David@0
   310
	[theContact setIdle:YES sinceDate:idleSinceDate notify:NotifyLater];
David@0
   311
David@0
   312
	//Apply any changes
David@0
   313
	[theContact notifyOfChangedPropertiesSilently:silentAndDelayed];
David@0
   314
}
David@0
   315
- (void)updateIdleReturn:(AIListContact *)theContact withData:(void *)data
David@0
   316
{
David@0
   317
	[theContact setIdle:NO
David@0
   318
			  sinceDate:nil
David@0
   319
				 notify:NotifyLater];
David@0
   320
David@0
   321
	//Apply any changes
David@0
   322
	[theContact notifyOfChangedPropertiesSilently:silentAndDelayed];
David@0
   323
}
David@0
   324
	
David@0
   325
//Evil level (warning level)
David@0
   326
- (void)updateEvil:(AIListContact *)theContact withData:(NSNumber *)evilNumber
David@0
   327
{
sholt@2693
   328
	[theContact setWarningLevel:[evilNumber integerValue]
David@0
   329
						 notify:NotifyLater];
David@0
   330
David@0
   331
	//Apply any changes
David@0
   332
	[theContact notifyOfChangedPropertiesSilently:silentAndDelayed];
David@0
   333
}
David@0
   334
David@0
   335
David@0
   336
- (void)clearIconForContact:(AIListContact *)theContact
David@0
   337
{
David@0
   338
	[theContact setServersideIconData:nil
David@0
   339
							   notify:NotifyLater];
David@0
   340
	
David@0
   341
	//Apply any changes
David@0
   342
	[theContact notifyOfChangedPropertiesSilently:silentAndDelayed];	
David@0
   343
}
David@0
   344
David@0
   345
//Buddy Icon
David@0
   346
- (void)updateIcon:(AIListContact *)theContact withData:(NSData *)userIconData
David@0
   347
{
David@0
   348
	[NSObject cancelPreviousPerformRequestsWithTarget:self
David@0
   349
											 selector:@selector(clearIconForContact:)
David@0
   350
											   object:theContact];
David@0
   351
	if (userIconData) {
David@0
   352
		[theContact setServersideIconData:userIconData
David@0
   353
								   notify:NotifyLater];
David@0
   354
		
David@0
   355
		//Apply any changes
David@0
   356
		[theContact notifyOfChangedPropertiesSilently:silentAndDelayed];
David@0
   357
David@0
   358
	} else {
David@0
   359
		/* We may receive an empty icon update just before an actual change. We don't want to flicker through no-icon.
David@0
   360
		 * We therefore cancel empty icon updates when we receive a new icon, and we do the actual clearing on a delay in case
David@0
   361
		 * this is what is about to happen.
David@0
   362
		 */
David@0
   363
		[self performSelector:@selector(clearIconForContact:)
David@0
   364
				   withObject:theContact
David@0
   365
				   afterDelay:10.0];
David@0
   366
	}
David@0
   367
}
David@0
   368
David@0
   369
- (NSString *)processedIncomingUserInfo:(NSString *)inString
David@0
   370
{
David@0
   371
	NSMutableString *returnString = nil;
David@0
   372
	if ([inString rangeOfString:@"Purple could not find any information in the user's profile. The user most likely does not exist."].location != NSNotFound) {
David@0
   373
		returnString = [[inString mutableCopy] autorelease];
David@0
   374
		[returnString replaceOccurrencesOfString:@"Purple could not find any information in the user's profile. The user most likely does not exist."
David@0
   375
									  withString:AILocalizedString(@"Adium could not find any information in the user's profile. This may not be a registered name.", "Message shown when a contact's profile can't be found")
David@0
   376
										 options:NSLiteralSearch
David@0
   377
										   range:NSMakeRange(0, [returnString length])];
David@0
   378
	}
David@0
   379
	
David@0
   380
	return (returnString ? returnString : inString);
David@0
   381
}
David@0
   382
Evan@660
   383
- (NSString *)webProfileStringForContact:(AIListContact *)contact
Evan@660
   384
{
Evan@660
   385
	return [NSString stringWithFormat:NSLocalizedString(@"View %@'s %@ web profile", nil), 
David@837
   386
			contact.formattedUID, [contact.service shortDescription]];
Evan@660
   387
}
Evan@660
   388
Evan@660
   389
- (NSMutableArray *)arrayOfDictionariesFromPurpleNotifyUserInfo:(PurpleNotifyUserInfo *)user_info forContact:(AIListContact *)contact
David@0
   390
{
David@0
   391
	GList *l;
David@0
   392
	NSMutableArray *array = [NSMutableArray array];
David@0
   393
	
David@0
   394
	for (l = purple_notify_user_info_get_entries(user_info); l != NULL; l = l->next) {
David@0
   395
		PurpleNotifyUserInfoEntry *user_info_entry = l->data;
David@0
   396
		
David@0
   397
		switch (purple_notify_user_info_entry_get_type(user_info_entry)) {
David@0
   398
			case PURPLE_NOTIFY_USER_INFO_ENTRY_SECTION_HEADER:
David@0
   399
				[array addObject:[NSDictionary dictionaryWithObjectsAndKeys:
David@0
   400
								  [NSString stringWithUTF8String:purple_notify_user_info_entry_get_label(user_info_entry)], KEY_KEY,
sholt@2693
   401
								  [NSNumber numberWithInteger:AIUserInfoSectionHeader], KEY_TYPE,
David@0
   402
								  nil]];
David@0
   403
				
David@0
   404
				break;
David@0
   405
			case PURPLE_NOTIFY_USER_INFO_ENTRY_SECTION_BREAK:
David@0
   406
				[array addObject:[NSDictionary dictionaryWithObjectsAndKeys:
sholt@2693
   407
								  [NSNumber numberWithInteger:AIUserInfoSectionBreak], KEY_TYPE,
David@0
   408
								  nil]];
David@0
   409
				break;
David@0
   410
				
David@0
   411
			case PURPLE_NOTIFY_USER_INFO_ENTRY_PAIR:
David@0
   412
			{
David@0
   413
				if (purple_notify_user_info_entry_get_label(user_info_entry) && purple_notify_user_info_entry_get_value(user_info_entry)) {
David@0
   414
					[array addObject:[NSDictionary dictionaryWithObjectsAndKeys:
David@0
   415
									  [NSString stringWithUTF8String:purple_notify_user_info_entry_get_label(user_info_entry)], KEY_KEY,
David@0
   416
									  processPurpleImages([NSString stringWithUTF8String:purple_notify_user_info_entry_get_value(user_info_entry)], self), KEY_VALUE,
David@0
   417
									  nil]];
David@0
   418
					
David@0
   419
				} else if (purple_notify_user_info_entry_get_label(user_info_entry)) {
David@0
   420
					[array addObject:[NSDictionary dictionaryWithObject:
David@0
   421
									  [NSString stringWithUTF8String:purple_notify_user_info_entry_get_label(user_info_entry)]
David@0
   422
																 forKey:KEY_KEY]];
David@0
   423
				} else if (purple_notify_user_info_entry_get_value(user_info_entry)) {
David@0
   424
					NSMutableString	*value = [processPurpleImages([NSString stringWithUTF8String:purple_notify_user_info_entry_get_value(user_info_entry)],
David@0
   425
																  self) mutableCopy];
David@0
   426
					[value replaceOccurrencesOfString:@"<br>" withString:@"<br/>" options:(NSCaseInsensitiveSearch | NSLiteralSearch)];
David@0
   427
					[value replaceOccurrencesOfString:@"<br />" withString:@"<br/>" options:(NSCaseInsensitiveSearch | NSLiteralSearch)];
David@0
   428
					[value replaceOccurrencesOfString:@"<B>" withString:@"<b>" options:NSLiteralSearch];
David@0
   429
Evan@166
   430
					for (NSString *valuePair in [value componentsSeparatedByString:@"<br/><b>"]) {
David@0
   431
						NSRange	firstStartBold = [valuePair rangeOfString:@"<b>"];
David@0
   432
						NSRange	firstEndBold = [valuePair rangeOfString:@"</b>"];
David@0
   433
						
David@0
   434
						if (firstEndBold.length > 0) {
David@0
   435
							// Chop off <b> from the beginning and :</b> from the end. The extra -1 is for the colon.
David@0
   436
							[array addObject:[NSDictionary dictionaryWithObjectsAndKeys:
David@0
   437
											  [valuePair substringWithRange:NSMakeRange(firstStartBold.length, firstEndBold.location-firstStartBold.length-1)], KEY_KEY,
David@0
   438
											  [valuePair substringFromIndex:NSMaxRange(firstEndBold)], KEY_VALUE,
David@0
   439
											  nil]];
David@0
   440
						} else {
David@0
   441
							[array addObject:[NSDictionary dictionaryWithObject:valuePair
David@0
   442
																		forKey:KEY_VALUE]];
David@0
   443
						}
David@0
   444
					}
David@0
   445
					[value release];
David@0
   446
				}	
David@0
   447
				break;
David@0
   448
			}
David@0
   449
		}
David@0
   450
	}
David@0
   451
Evan@660
   452
	NSString *webProfileValue = [NSString stringWithFormat:@"%s</a>", _("View web profile")];
Evan@660
   453
	
sholt@2693
   454
	NSInteger i;
sholt@2693
   455
	NSUInteger count = [array count];
Evan@660
   456
	for (i = 0; i < count; i++) {
Evan@660
   457
		NSDictionary *dict = [array objectAtIndex:i];
Evan@660
   458
		NSString *value = [dict objectForKey:KEY_VALUE];
Evan@660
   459
		if (value &&
Evan@660
   460
			[value rangeOfString:webProfileValue options:(NSBackwardsSearch | NSAnchoredSearch | NSLiteralSearch)].location != NSNotFound) {
David@1660
   461
			NSMutableString *newValue = [[value mutableCopy] autorelease];
Evan@660
   462
			[newValue replaceOccurrencesOfString:webProfileValue
Evan@660
   463
									  withString:[self webProfileStringForContact:contact]
Evan@660
   464
										 options:(NSBackwardsSearch | NSAnchoredSearch | NSLiteralSearch)];
Evan@660
   465
			
Evan@660
   466
			NSMutableDictionary *replacementDict = [dict mutableCopy];
Evan@660
   467
			[replacementDict setObject:newValue forKey:KEY_VALUE];
Evan@660
   468
			[array replaceObjectAtIndex:i withObject:replacementDict];
Evan@660
   469
			[replacementDict release];
Evan@660
   470
Evan@660
   471
			/* There will only be 1 (at most) web profile link */
Evan@660
   472
			break;
Evan@660
   473
		}
Evan@660
   474
	}
Evan@660
   475
	
David@0
   476
	return array;
David@0
   477
}
David@0
   478
David@0
   479
- (void)updateUserInfo:(AIListContact *)theContact withData:(PurpleNotifyUserInfo *)user_info
David@0
   480
{
Evan@660
   481
	NSArray		*profileContents = [self arrayOfDictionariesFromPurpleNotifyUserInfo:user_info forContact:theContact];
zacw@1435
   482
David@0
   483
	[theContact setProfileArray:profileContents
David@0
   484
					notify:NotifyLater];
zacw@1400
   485
	
zacw@1435
   486
	[self openInspectorForContactInfo:theContact];
zacw@1409
   487
	
David@0
   488
	//Apply any changes
David@0
   489
	[theContact notifyOfChangedPropertiesSilently:silentAndDelayed];
David@0
   490
}
David@0
   491
David@0
   492
/*!
zacw@1435
   493
 * @brief Open the info inspector when getting info
zacw@1435
   494
 */
zacw@1435
   495
- (void)openInspectorForContactInfo:(AIListContact *)theContact
zacw@1435
   496
{
zacw@1899
   497
zacw@1435
   498
}
zacw@1435
   499
zacw@1435
   500
/*!
David@0
   501
 * @brief Purple removed a contact from the local blist
David@0
   502
 *
David@0
   503
 * This can happen in many situations:
David@0
   504
 *	- For every contact on an account when the account signs off
David@0
   505
 *	- For a contact as it is deleted by the user
David@0
   506
 *	- For a contact as it is deleted by Purple (e.g. when Sametime refuses an addition because it is known to be invalid)
David@0
   507
 *	- In the middle of the move process as a contact moves from one group to another
David@0
   508
 *
David@0
   509
 * We need not take any action; we'll be notified of changes by Purple as necessary.
David@0
   510
 */
David@0
   511
- (void)removeContact:(AIListContact *)theContact
David@0
   512
{
David@0
   513
David@0
   514
}
David@0
   515
David@0
   516
//To allow root level buddies on protocols which don't support them, we map any buddies in a group
David@0
   517
//named after this account's UID to the root group.  These functions handle the mapping.  Group names should
David@0
   518
//be filtered through incoming before being sent to Adium - and group names from Adium should be filtered through
David@0
   519
//outgoing before being used.
David@0
   520
- (NSString *)_mapIncomingGroupName:(NSString *)name
David@0
   521
{
David@427
   522
	if (!name || ([[name compactedString] caseInsensitiveCompare:self.UID] == NSOrderedSame)) {
David@0
   523
		return ADIUM_ROOT_GROUP_NAME;
David@0
   524
	} else {
David@0
   525
		return name;
David@0
   526
	}
David@0
   527
}
David@0
   528
- (NSString *)_mapOutgoingGroupName:(NSString *)name
David@0
   529
{
David@0
   530
	if ([[name compactedString] caseInsensitiveCompare:ADIUM_ROOT_GROUP_NAME] == NSOrderedSame) {
David@427
   531
		return self.UID;
David@0
   532
	} else {
David@0
   533
		return name;
David@0
   534
	}
David@0
   535
}
David@0
   536
David@0
   537
//Update the status of a contact (Request their profile)
David@0
   538
- (void)delayedUpdateContactStatus:(AIListContact *)inContact
David@0
   539
{
David@0
   540
    //Request profile
David@0
   541
	AILogWithSignature(@"");
David@721
   542
	[purpleAdapter getInfoFor:inContact.UID onAccount:self];
David@0
   543
}
David@0
   544
David@0
   545
- (void)requestAddContactWithUID:(NSString *)contactUID
David@0
   546
{
David@89
   547
	[adium.contactController requestAddContactWithUID:contactUID
David@0
   548
												service:[self _serviceForUID:contactUID]
David@0
   549
												account:self];
David@0
   550
}
David@0
   551
David@0
   552
- (AIService *)_serviceForUID:(NSString *)contactUID
David@0
   553
{
David@427
   554
	return self.service;
David@0
   555
}
David@0
   556
David@0
   557
- (void)gotGroupForContact:(AIListContact *)listContact {};
David@0
   558
David@0
   559
/*!
David@0
   560
 * @brief Return the serverside icon for a contact
David@0
   561
 */
David@0
   562
- (NSData *)serversideIconDataForContact:(AIListContact *)contact
David@0
   563
{
David@0
   564
	PurpleBuddy		*buddy;
David@0
   565
	NSData			*data = nil;
David@0
   566
David@427
   567
	if (self.purpleAccount &&
David@715
   568
		(buddy = purple_find_buddy(account, [contact.UID UTF8String]))) {
David@0
   569
		PurpleBuddyIcon *buddyIcon;
David@0
   570
		BOOL			shouldUnref = NO;
David@0
   571
		
David@0
   572
		/* First, try to get a current buddy icon from the PurpleBuddy */
David@0
   573
		buddyIcon = purple_buddy_get_icon(buddy);
David@0
   574
		if (!buddyIcon) {
David@0
   575
			/* Failing that, load one from the cache. We'll need to unreference the returned PurpleBuddyIcon
David@0
   576
			 * when we're done.
David@0
   577
			 */
David@715
   578
			buddyIcon = purple_buddy_icons_find(account, [contact.UID UTF8String]);
David@0
   579
			shouldUnref = YES;
David@0
   580
		}
David@0
   581
		
David@0
   582
		if (buddyIcon) {
David@0
   583
			const guchar	*iconData;
David@0
   584
			size_t			len;
David@0
   585
			
David@0
   586
			iconData = purple_buddy_icon_get_data(buddyIcon, &len);
David@0
   587
			
David@0
   588
			if (iconData && len) {
David@0
   589
				data = [NSData dataWithBytes:iconData length:len];
David@0
   590
			}
David@0
   591
			
David@0
   592
			if (shouldUnref)
David@0
   593
				purple_buddy_icon_unref(buddyIcon);
David@0
   594
		}
David@0
   595
David@0
   596
	} else {
David@0
   597
		AILogWithSignature(@"Could not get serverside icon data for %@. account is %p", contact, account);
David@0
   598
	}
David@0
   599
	
David@0
   600
	return data;
David@0
   601
}
David@0
   602
David@0
   603
/*!
David@0
   604
 * @brief Libpurple manages a contact icon cache; we don't need to duplicate it.
David@0
   605
 */
David@0
   606
- (BOOL)managesOwnContactIconCache
David@0
   607
{
David@0
   608
	return YES;
David@0
   609
}
David@0
   610
David@0
   611
/*********************/
David@0
   612
/* AIAccount_Handles */
David@0
   613
/*********************/
David@0
   614
#pragma mark Contact List Editing
David@0
   615
zacw@2131
   616
- (void)removeContacts:(NSArray *)objects fromGroups:(NSArray *)groups
David@82
   617
{	
zacw@2131
   618
	for (AIListGroup *group in groups) {
zacw@2131
   619
		NSString *groupName = [self _mapOutgoingGroupName:group.UID];
zacw@2131
   620
	
zacw@2131
   621
		for (AIListContact *object in objects) {
David@714
   622
			//Have the purple thread perform the serverside actions
David@837
   623
			[purpleAdapter removeUID:object.UID onAccount:self fromGroup:groupName];
David@714
   624
			
David@714
   625
			//Remove it from Adium's list
David@714
   626
			[object removeRemoteGroupName:groupName];
David@714
   627
		}
David@0
   628
	}
David@0
   629
}
David@0
   630
David@199
   631
- (void)addContact:(AIListContact *)contact toGroup:(AIListGroup *)group
David@0
   632
{
David@721
   633
	NSString		*groupName = [self _mapOutgoingGroupName:group.UID];
David@0
   634
	
David@199
   635
	if(![group containsObject:contact]) {
David@199
   636
		AILogWithSignature(@"%@ adding %@ to %@", self, [self _UIDForAddingObject:contact], groupName);
David@199
   637
		
zacw@2249
   638
		NSString *alias = [contact.parentContact preferenceForKey:@"Alias"
zacw@2249
   639
						   group:PREF_GROUP_ALIASES];
zacw@2249
   640
		
zacw@2249
   641
		[purpleAdapter addUID:[self _UIDForAddingObject:contact] onAccount:self toGroup:groupName withAlias:alias];
David@0
   642
		
David@0
   643
		//Add it to Adium's list
David@703
   644
		[contact addRemoteGroupName:group.UID]; //Use the non-mapped group name locally
David@0
   645
	}
David@0
   646
}
David@0
   647
David@0
   648
- (NSString *)_UIDForAddingObject:(AIListContact *)object
David@0
   649
{
David@837
   650
	return object.UID;
David@0
   651
}
David@0
   652
zacw@2128
   653
- (NSSet *)mappedGroupNamesFromGroups:(NSSet *)groups
David@0
   654
{
zacw@2128
   655
	NSMutableSet *mappedNames = [NSMutableSet set];
zacw@2128
   656
	
zacw@2128
   657
	for (AIListGroup *group in groups) {
zacw@2128
   658
		[mappedNames addObject:[self _mapOutgoingGroupName:group.UID]];
David@709
   659
	}
David@0
   660
	
zacw@2128
   661
	return mappedNames;
zacw@2128
   662
}
zacw@2128
   663
zacw@2128
   664
- (void)moveListObjects:(NSArray *)objects fromGroups:(NSSet *)oldGroups toGroups:(NSSet *)groups
zacw@2128
   665
{
zacw@2128
   666
	NSSet *sourceMappedNames = [self mappedGroupNamesFromGroups:oldGroups];
zacw@2128
   667
	NSSet *destinationMappedNames = [self mappedGroupNamesFromGroups:groups];
zacw@2128
   668
David@0
   669
	//Move the objects to it
David@893
   670
	for (AIListContact *contact in objects) {
zacw@2266
   671
		if (![contact.remoteGroups intersectsSet:oldGroups] && oldGroups.count) {
zacw@2266
   672
			continue;
zacw@2266
   673
		}
zacw@2266
   674
		
zacw@2249
   675
		NSString *alias = [contact.parentContact preferenceForKey:@"Alias"
zacw@2249
   676
						   group:PREF_GROUP_ALIASES];
zacw@2249
   677
		
David@893
   678
		//Tell the purple thread to perform the serverside operation
zacw@2249
   679
		[purpleAdapter moveUID:contact.UID onAccount:self fromGroups:sourceMappedNames toGroups:destinationMappedNames withAlias:alias];
zacw@2128
   680
zacw@2128
   681
		for (AIListGroup *group in oldGroups) {
zacw@2128
   682
			[contact removeRemoteGroupName:group.UID];
zacw@2128
   683
		}
zacw@2128
   684
		
zacw@2128
   685
		for (AIListGroup *group in groups) {
zacw@2128
   686
			[contact addRemoteGroupName:group.UID];
zacw@2128
   687
		}
David@0
   688
	}		
David@0
   689
}
David@0
   690
David@0
   691
- (void)renameGroup:(AIListGroup *)inGroup to:(NSString *)newName
David@0
   692
{
David@721
   693
	NSString		*groupName = [self _mapOutgoingGroupName:inGroup.UID];
David@0
   694
David@0
   695
	//Tell the purple thread to perform the serverside operation	
David@0
   696
	[purpleAdapter renameGroup:groupName onAccount:self to:newName];
David@0
   697
David@0
   698
	//We must also update the remote grouping of all our contacts in that group
David@531
   699
	for (AIListContact *contact in [adium.contactController allContactsInObject:inGroup onAccount:self]) {
David@893
   700
		[contact removeRemoteGroupName:groupName];
David@0
   701
		//Evan: should we use groupName or newName here?
David@589
   702
		[contact addRemoteGroupName:newName];
David@0
   703
	}
David@0
   704
}
David@0
   705
David@0
   706
- (void)deleteGroup:(AIListGroup *)inGroup
David@0
   707
{
David@721
   708
	NSString		*groupName = [self _mapOutgoingGroupName:inGroup.UID];
David@0
   709
David@0
   710
	[purpleAdapter deleteGroup:groupName onAccount:self];
David@0
   711
}
David@0
   712
David@0
   713
// Return YES if the contact list is editable
David@0
   714
- (BOOL)contactListEditable
David@0
   715
{
David@837
   716
    return self.online;
David@0
   717
}
David@0
   718
zacw@1257
   719
- (id)authorizationRequestWithDict:(NSDictionary*)dict
zacw@1257
   720
{
zacw@1257
   721
	// We retain this in case libpurple wants to close the request early. It is freed below.
David@36
   722
	return [[AdiumAuthorization showAuthorizationRequestWithDict:dict forAccount:self] retain];
David@0
   723
}
David@0
   724
zacw@1257
   725
- (void)authorizationWithDict:(NSDictionary *)infoDict response:(AIAuthorizationResponse)authorizationResponse
David@0
   726
{
David@0
   727
	if (account) {
David@0
   728
		NSValue	*callback = nil;
David@0
   729
David@0
   730
		switch (authorizationResponse) {
David@0
   731
			case AIAuthorizationAllowed:
David@0
   732
				callback = [[[infoDict objectForKey:@"authorizeCB"] retain] autorelease];
David@0
   733
				break;
David@0
   734
			case AIAuthorizationDenied:
David@0
   735
				callback = [[[infoDict objectForKey:@"denyCB"] retain] autorelease];
David@0
   736
				break;
David@0
   737
			case AIAuthorizationNoResponse:
David@0
   738
				callback = nil;
David@0
   739
				break;
David@0
   740
		}
David@0
   741
		
zacw@1267
   742
		//libpurple will remove its reference to the handle for this request, which is inDict, in response to this callback invocation
David@0
   743
		if (callback) {
David@0
   744
			[purpleAdapter doAuthRequestCbValue:callback withUserDataValue:[[[infoDict objectForKey:@"userData"] retain] autorelease]];
David@0
   745
David@0
   746
			/* Retained in -[self authorizationRequestWithDict:].  We kept it around before now in case libpurle wanted us to close it early, such as because the
David@0
   747
			 * account disconnected.
David@0
   748
			 */
zacw@1257
   749
			[infoDict release];
David@0
   750
		} else {
zacw@1257
   751
			[purpleAdapter closeAuthRequestWithHandle:infoDict];
David@0
   752
			
David@0
   753
		}
David@0
   754
	}
David@0
   755
}
David@0
   756
zacw@1442
   757
#pragma mark Group chat ignore
zacw@1442
   758
- (BOOL)accountManagesGroupChatIgnore
zacw@1442
   759
{
zacw@1442
   760
	return YES;
zacw@1442
   761
}
zacw@1442
   762
zacw@1442
   763
- (BOOL)contact:(AIListContact *)inContact isIgnoredInChat:(AIChat *)chat
zacw@1442
   764
{
zacw@1649
   765
	if (self.online && chat.isGroupChat) {
zacw@1442
   766
		return [purpleAdapter contact:inContact isIgnoredInChat:chat];
zacw@1442
   767
	} else {
zacw@1442
   768
		return NO;
zacw@1442
   769
	}
zacw@1442
   770
}
zacw@1442
   771
zacw@1442
   772
- (void)setContact:(AIListContact *)inContact ignored:(BOOL)inIgnored inChat:(AIChat *)chat
zacw@1442
   773
{
zacw@1649
   774
	if (self.online && chat.isGroupChat) {
zacw@1442
   775
		[purpleAdapter setContact:inContact ignored:inIgnored inChat:chat];
zacw@1442
   776
	}
zacw@1442
   777
}
zacw@1442
   778
David@0
   779
//Chats ------------------------------------------------------------
David@0
   780
#pragma mark Chats
zacw@1406
   781
- (void)removeUser:(NSString *)contactName fromChat:(AIChat *)chat
zacw@1406
   782
{
zacw@1406
   783
	if (!chat)
zacw@1406
   784
		return;
zacw@1406
   785
	
zacw@1460
   786
	AIListContact *contact = [self contactWithUID:contactName];
zacw@1406
   787
	[chat removeObject:contact];
zacw@2412
   788
	
zacw@2412
   789
	if (contact.isStranger && 
zacw@2412
   790
		![adium.chatController allGroupChatsContainingContact:contact.parentContact].count &&
zacw@2412
   791
		[adium.chatController existingChatWithContact:contact.parentContact]) {
zacw@2412
   792
		// The contact is a stranger, not in any more group chats, but we have a message with them open.
zacw@2412
   793
		// Set their status to unknown.
zacw@2412
   794
		
zacw@2412
   795
		[contact setStatusWithName:nil
zacw@2412
   796
						statusType:AIUnknownStatus
zacw@2412
   797
							notify:NotifyLater];
zacw@2412
   798
		
zacw@2412
   799
		[contact setValue:nil
zacw@2412
   800
			  forProperty:@"Online"
zacw@2412
   801
				   notify:NotifyLater];
zacw@2412
   802
		
zacw@2412
   803
		[contact notifyOfChangedPropertiesSilently:NO];
zacw@2412
   804
	}
zacw@1406
   805
}
zacw@1406
   806
zacw@1406
   807
- (void)removeUsersArray:(NSArray *)usersArray fromChat:(AIChat *)chat
zacw@1406
   808
{
zacw@1406
   809
	for (NSString *contactName in usersArray) {
zacw@1406
   810
		[self removeUser:contactName fromChat:chat];
zacw@1406
   811
	}
zacw@1406
   812
}
David@0
   813
zacw@1460
   814
- (void)updateUserListForChat:(AIChat *)chat users:(NSArray *)users newlyAdded:(BOOL)newlyAdded
David@0
   815
{
zacw@1460
   816
	NSMutableArray *newListObjects = [NSMutableArray array];
zacw@1395
   817
	
zacw@1460
   818
	for (NSDictionary *user in users) {
zacw@1460
   819
		AIListContact *contact = [self contactWithUID:[user objectForKey:@"UID"]];
zacw@1406
   820
		
zacw@1743
   821
		[contact setOnline:YES notify:NotifyNever silently:YES];
zacw@1743
   822
		
zacw@1460
   823
		[newListObjects addObject:contact];
zacw@1395
   824
	}
zacw@1395
   825
	
zacw@1460
   826
	[chat addParticipatingListObjects:newListObjects notify:newlyAdded];
zacw@1395
   827
	
zacw@1460
   828
	for (NSDictionary *user in users) {
zacw@1460
   829
		AIListContact *contact = [self contactWithUID:[user objectForKey:@"UID"]];
zacw@1395
   830
		
zacw@1460
   831
		[chat setFlags:(AIGroupChatFlags)[[user objectForKey:@"Flags"] integerValue] forContact:contact];
zacw@1743
   832
		
zacw@1460
   833
		if ([user objectForKey:@"Alias"]) {
zacw@1460
   834
			[chat setAlias:[user objectForKey:@"Alias"] forContact:contact];
zacw@1740
   835
			
zacw@1740
   836
			if (contact.isStranger) {
zacw@1740
   837
				[contact setServersideAlias:[user objectForKey:@"Alias"] silently:NO];
zacw@1740
   838
			}
zacw@1460
   839
		}
David@1012
   840
	}
David@1732
   841
	
zacw@1395
   842
	// Post an update notification now that we've modified the flags and names.
zacw@1395
   843
	[[NSNotificationCenter defaultCenter] postNotificationName:Chat_ParticipatingListObjectsChanged
zacw@1395
   844
														object:chat];
zacw@1395
   845
}
zacw@1395
   846
zacw@1460
   847
- (void)renameParticipant:(NSString *)oldUID newName:(NSString *)newUID newAlias:(NSString *)newAlias flags:(AIGroupChatFlags)flags inChat:(AIChat *)chat
zacw@1395
   848
{
zacw@1406
   849
	[chat removeSavedValuesForContactUID:oldUID];
zacw@1404
   850
	
zacw@1406
   851
	AIListContact *contact = [adium.contactController existingContactWithService:self.service account:self UID:oldUID];
zacw@1406
   852
zacw@1404
   853
	if (contact) {
zacw@1406
   854
		[adium.contactController setUID:newUID forContact:contact];
zacw@1404
   855
	} else {
zacw@1406
   856
		contact = [self contactWithUID:newUID];
zacw@1404
   857
	}
zacw@1406
   858
zacw@1461
   859
	[chat setFlags:flags forContact:contact];
zacw@1395
   860
	[chat setAlias:newAlias forContact:contact];
zacw@1461
   861
	
zacw@1461
   862
	if (contact.isStranger) {
zacw@1461
   863
		[contact setServersideAlias:newAlias silently:NO];
zacw@1461
   864
	}
zacw@1395
   865
zacw@1395
   866
	// Post an update notification since we modified the user entirely.
zacw@1395
   867
	[[NSNotificationCenter defaultCenter] postNotificationName:Chat_ParticipatingListObjectsChanged
zacw@1395
   868
														object:chat];
zacw@1395
   869
}
zacw@1395
   870
zacw@1740
   871
- (void)setAttribute:(NSString *)name value:(NSString *)value forContact:(AIListContact *)contact
zacw@1740
   872
{
zacw@1740
   873
	NSString *property = nil;
zacw@1740
   874
	
zacw@1740
   875
	if ([name isEqualToString:@"userhost"]) {
zacw@1740
   876
		property = @"User Host";
zacw@1740
   877
	} else if ([name isEqualToString:@"realname"]) {
zacw@1740
   878
		property = @"Real Name";
zacw@1740
   879
	} else {
zacw@1740
   880
		AILog(@"Unknown attribute: %@ value %@", name, value);
zacw@1740
   881
	}
zacw@1740
   882
	
zacw@1740
   883
	if (property) {
zacw@1740
   884
		// Callsite should notify.
zacw@1740
   885
		[contact setValue:value forProperty:property notify:NotifyLater];
zacw@1740
   886
	}
zacw@1740
   887
}
zacw@1740
   888
zacw@1740
   889
zacw@1740
   890
- (void)updateUser:(NSString *)user
zacw@1740
   891
		   forChat:(AIChat *)chat
zacw@1740
   892
			 flags:(AIGroupChatFlags)flags
zacw@2231
   893
			 alias:(NSString *)alias
zacw@1740
   894
		attributes:(NSDictionary *)attributes
zacw@1395
   895
{
zacw@2231
   896
	BOOL triggerUserlistUpdate = NO;
zacw@2231
   897
	
zacw@1460
   898
	AIListContact *contact = [self contactWithUID:user];
zacw@1406
   899
	
zacw@1740
   900
	AIGroupChatFlags oldFlags = [chat flagsForContact:contact];
zacw@2231
   901
	NSString *oldAlias = [chat aliasForContact:contact];
zacw@1740
   902
	
zacw@2231
   903
	// Trigger an update if the alias or flags (ignoring away state) changes.
zacw@2231
   904
	if ((alias && !oldAlias)
zacw@2231
   905
		|| (!alias && oldAlias)
zacw@2231
   906
		|| ![[chat aliasForContact:contact] isEqualToString:alias]
zacw@2231
   907
		|| (flags & ~AIGroupChatAway) != (oldFlags & ~AIGroupChatAway)) {
zacw@2231
   908
		triggerUserlistUpdate = YES;
zacw@2231
   909
	}
zacw@2231
   910
zacw@2231
   911
	[chat setAlias:alias forContact:contact];
zacw@1406
   912
	[chat setFlags:flags forContact:contact];
zacw@1395
   913
	
zacw@1743
   914
	// Away changes only come in after the initial one, so we're safe in only updating it here.
zacw@1744
   915
	if (contact.isStranger) {
zacw@1744
   916
		[contact setStatusWithName:nil
zacw@1744
   917
						statusType:((flags & AIGroupChatAway) == AIGroupChatAway) ? AIAwayStatusType : AIAvailableStatusType
zacw@1744
   918
							notify:NotifyLater];
zacw@1744
   919
	}
zacw@1743
   920
zacw@1740
   921
	for (NSString *key in attributes.allKeys) {
zacw@1740
   922
		[self setAttribute:key value:[attributes objectForKey:key] forContact:contact];
zacw@1740
   923
	}
zacw@1740
   924
	
zacw@1749
   925
	[contact notifyOfChangedPropertiesSilently:YES];
zacw@1740
   926
	
zacw@1768
   927
	// Post an update notification if we modified the flags; don't resort for away changes.
zacw@2231
   928
	if (triggerUserlistUpdate) {
zacw@1740
   929
		[[NSNotificationCenter defaultCenter] postNotificationName:Chat_ParticipatingListObjectsChanged
zacw@1740
   930
															object:chat];
zacw@1740
   931
	}
David@0
   932
}
David@0
   933
David@0
   934
/*!
David@0
   935
 * @brief Called by Purple code when a chat should be opened by the interface
David@0
   936
 *
David@0
   937
 * If the user sent an initial message, this will be triggered and have no effect.
David@0
   938
 *
David@0
   939
 * If a remote user sent an initial message, however, a chat will be created without being opened.  This call is our
David@0
   940
 * cue to actually open chat.
David@0
   941
 *
David@0
   942
 * Another situation in which this is relevant is when we request joining a group chat; the chat should only be actually
David@0
   943
 * opened once the server notifies us that we are in the room.
David@0
   944
 *
David@0
   945
 * This will ultimately call -[CBPurpleAccount openChat:] below if the chat was not previously open.
David@0
   946
 */
zacw@1351
   947
- (void)addChat:(AIChat *)chat
David@0
   948
{
David@0
   949
	AILogWithSignature(@"");
David@0
   950
David@0
   951
	//Open the chat
David@0
   952
	if ([chat isOpen]) {
zacw@1453
   953
		if ([chat boolValueForProperty:@"Rejoining Chat"]) {
zacw@1453
   954
			[self displayYouHaveConnectedInChat:chat];
zacw@1453
   955
			
zacw@1453
   956
			[chat setValue:nil forProperty:@"Rejoining Chat" notify:NotifyNever];
zacw@1453
   957
		}
David@0
   958
	}
David@0
   959
David@100
   960
	[adium.interfaceController openChat:chat];
David@0
   961
	
zacw@2319
   962
	[chat setValue:[NSNumber numberWithBool:YES] forProperty:@"Account Joined" notify:NotifyNow];
David@0
   963
}
David@0
   964
David@0
   965
//Open a chat for Adium
David@0
   966
- (BOOL)openChat:(AIChat *)chat
David@0
   967
{
David@0
   968
	/* The #if 0'd block below causes crashes in msn_tooltip_text() on MSN */
David@0
   969
#if 0
David@0
   970
	AIListContact	*listContact;
David@0
   971
	
David@0
   972
	//Obtain the contact's information if it's a stranger
David@837
   973
	if ((listContact = chat.listObject) && (listContact.isStranger)) {
David@0
   974
		[self delayedUpdateContactStatus:listContact];
David@0
   975
	}
David@0
   976
#endif
David@0
   977
	
David@426
   978
	AILog(@"purple openChat:%@ for %@",chat,chat.uniqueChatID);
David@0
   979
David@0
   980
	//Inform purple that we have opened this chat
David@0
   981
	[purpleAdapter openChat:chat onAccount:self];
David@0
   982
	
David@0
   983
	//Created the chat successfully
David@0
   984
	return YES;
David@0
   985
}
David@0
   986
David@0
   987
- (BOOL)closeChat:(AIChat*)chat
David@0
   988
{
David@0
   989
	[purpleAdapter closeChat:chat];
David@0
   990
	
zacw@2085
   991
	if (!chat.isGroupChat) {
zacw@2085
   992
		//Be sure any remaining typing flag is cleared as the chat closes
zacw@2085
   993
		[self setTypingFlagOfChat:chat to:nil];
zacw@2085
   994
	}
zacw@2085
   995
	
David@426
   996
	AILog(@"purple closeChat:%@",chat.uniqueChatID);
David@0
   997
	
David@0
   998
    return YES;
David@0
   999
}
David@0
  1000
David@0
  1001
- (void)chatWasDestroyed:(AIChat *)chat
David@0
  1002
{
David@95
  1003
	[adium.chatController accountDidCloseChat:chat];
David@0
  1004
}
David@0
  1005
David@0
  1006
- (void)chatJoinDidFail:(AIChat *)chat
David@0
  1007
{
David@95
  1008
	[adium.chatController accountDidCloseChat:chat];
David@0
  1009
}
David@0
  1010
David@0
  1011
/* 
David@0
  1012
 * @brief Rejoin a chat
David@0
  1013
 */
David@0
  1014
- (BOOL)rejoinChat:(AIChat *)chat
David@0
  1015
{
David@0
  1016
	[chat retain];
David@0
  1017
David@0
  1018
	PurpleConversation *conv = [[chat identifier] pointerValue];
David@0
  1019
	if (conv && conv->ui_data) {
David@0
  1020
		[(AIChat *)(conv->ui_data) release];
David@0
  1021
		conv->ui_data = NULL;
David@0
  1022
	}
David@0
  1023
David@0
  1024
	/* The identifier is how we associate a PurpleConversation with an AIChat.
David@0
  1025
	 * Clear the identifier so a new PurpleConversation will be made. The ChatCreationInfo for the chat is still around, so it can join.
David@0
  1026
	 */
David@0
  1027
	[chat setIdentifier:nil];
zacw@1453
  1028
	
zacw@1453
  1029
	[chat setValue:[NSNumber numberWithBool:YES] forProperty:@"Rejoining Chat" notify:NotifyNever];
zacw@1453
  1030
	
David@0
  1031
	[purpleAdapter openChat:chat onAccount:self];
David@0
  1032
David@0
  1033
	[chat autorelease];
David@0
  1034
David@0
  1035
	//We don't get any immediate feedback as to our success; just return YES.
David@0
  1036
	return YES;
David@0
  1037
}
David@0
  1038
David@0
  1039
/*!
David@0
  1040
 * @brief A chat will be joined
David@0
  1041
 *
David@0
  1042
 * This gives the account a chance to update any information in the chat's creation dictionary if desired.
David@0
  1043
 *
David@0
  1044
 * @result The final chat creation dictionary to use.
David@0
  1045
 */
David@0
  1046
- (NSDictionary *)willJoinChatUsingDictionary:(NSDictionary *)chatCreationDictionary
David@0
  1047
{
David@0
  1048
	return chatCreationDictionary;
David@0
  1049
}
David@0
  1050
David@0
  1051
- (BOOL)chatCreationDictionary:(NSDictionary *)chatCreationDict isEqualToDictionary:(NSDictionary *)baseDict
David@0
  1052
{
David@0
  1053
	return [chatCreationDict isEqualToDictionary:baseDict];
David@0
  1054
}
David@0
  1055
David@1158
  1056
- (NSDictionary *)extractChatCreationDictionaryFromConversation:(PurpleConversation *)conv
David@1158
  1057
{
David@1158
  1058
	AILog(@"%@ needs an implementation of extractChatCreationDictionaryFromConversation to handle rejoins, bookmarks, and invitations properly", NSStringFromClass([self class]));
David@1158
  1059
	return nil;
David@1158
  1060
}
David@1158
  1061
David@0
  1062
- (AIChat *)chatWithContact:(AIListContact *)contact identifier:(id)identifier
David@0
  1063
{
David@95
  1064
	AIChat *chat = [adium.chatController chatWithContact:contact];
David@0
  1065
	[chat setIdentifier:identifier];
David@0
  1066
David@0
  1067
	return chat;
David@0
  1068
}
David@0
  1069
David@0
  1070
David@0
  1071
- (AIChat *)chatWithName:(NSString *)name identifier:(id)identifier
David@0
  1072
{
David@95
  1073
	return [adium.chatController chatWithName:name identifier:identifier onAccount:self chatCreationInfo:nil];
David@0
  1074
}
David@0
  1075
David@0
  1076
//Typing update in an IM
David@0
  1077
- (void)typingUpdateForIMChat:(AIChat *)chat typing:(NSNumber *)typingState
David@0
  1078
{
David@0
  1079
	[self setTypingFlagOfChat:chat
David@0
  1080
						   to:typingState];
David@0
  1081
}
David@0
  1082
David@0
  1083
//Multiuser chat update
David@0
  1084
- (void)convUpdateForChat:(AIChat *)chat type:(NSNumber *)type
David@0
  1085
{
David@0
  1086
David@0
  1087
}
David@0
  1088
David@0
  1089
/*!
David@0
  1090
 * @brief Called when we are informed that we left a multiuser chat
David@0
  1091
 */
David@0
  1092
- (void)leftChat:(AIChat *)chat
David@0
  1093
{
zacw@2319
  1094
	[chat setValue:nil forProperty:@"Account Joined" notify:NotifyNow];
David@0
  1095
}
David@0
  1096
zacw@1292
  1097
- (void)updateTopic:(NSString *)inTopic forChat:(AIChat *)chat withSource:(NSString *)source
zacw@1292
  1098
{	
zacw@1301
  1099
	// Update (not set) the chat's topic
zacw@1301
  1100
	[chat updateTopic:inTopic withSource:[self contactWithUID:source]];
David@0
  1101
}
zacw@1379
  1102
zacw@1379
  1103
/*!
zacw@1379
  1104
 * @brief Set a chat's topic
zacw@1379
  1105
 *
zacw@1379
  1106
 * This only has an effect on group chats.
zacw@1379
  1107
 */
zacw@1379
  1108
- (void)setTopic:(NSString *)topic forChat:(AIChat *)chat
zacw@1379
  1109
{
zacw@1379
  1110
	if (!chat.isGroupChat) {
zacw@1379
  1111
		return;
zacw@1379
  1112
	}
zacw@1379
  1113
	
zacw@2329
  1114
	PurplePluginProtocolInfo  *prpl_info = self.protocolInfo;
zacw@1379
  1115
	
zacw@1379
  1116
	if (prpl_info && prpl_info->set_chat_topic) {
zacw@1379
  1117
		(prpl_info->set_chat_topic)(purple_account_get_connection(account),
zacw@1379
  1118
									purple_conv_chat_get_id(purple_conversation_get_chat_data(convLookupFromChat(chat, self))),
zacw@1379
  1119
									[topic UTF8String]);
zacw@1379
  1120
	}
zacw@1379
  1121
}
zacw@1379
  1122
zacw@1379
  1123
David@0
  1124
- (void)updateTitle:(NSString *)inTitle forChat:(AIChat *)chat
David@0
  1125
{
David@0
  1126
	[[chat displayArrayForKey:@"Display Name"] setObject:inTitle
David@0
  1127
											   withOwner:self];
David@0
  1128
}
David@0
  1129
David@0
  1130
- (void)updateForChat:(AIChat *)chat type:(NSNumber *)type
David@0
  1131
{
sholt@2693
  1132
	AIChatUpdateType	updateType = [type integerValue];
David@0
  1133
	NSString			*key = nil;
David@0
  1134
	switch (updateType) {
David@0
  1135
		case AIChatTimedOut:
David@0
  1136
		case AIChatClosedWindow:
David@0
  1137
			break;
David@0
  1138
	}
David@0
  1139
	
David@0
  1140
	if (key) {
David@0
  1141
		[chat setValue:[NSNumber numberWithBool:YES] forProperty:key notify:NotifyNow];
David@0
  1142
		[chat setValue:nil forProperty:key notify:NotifyNever];
David@0
  1143
		
David@0
  1144
	}
David@0
  1145
}
David@0
  1146
David@0
  1147
- (void)errorForChat:(AIChat *)chat type:(NSNumber *)type
David@0
  1148
{
David@0
  1149
	[chat receivedError:type];
David@0
  1150
}
David@0
  1151
David@0
  1152
- (void)receivedIMChatMessage:(NSDictionary *)messageDict inChat:(AIChat *)chat
David@0
  1153
{
sholt@2634
  1154
	PurpleMessageFlags		flags = [[messageDict objectForKey:@"PurpleMessageFlags"] integerValue];
zacw@1353
  1155
zacw@1353
  1156
	NSAttributedString		*attributedMessage;
zacw@1353
  1157
	AIListContact			*listContact;
David@0
  1158
	
zacw@1353
  1159
	listContact = chat.listObject;
zacw@1353
  1160
zacw@1353
  1161
	attributedMessage = [adium.contentController decodedIncomingMessage:[messageDict objectForKey:@"Message"]
zacw@1353
  1162
															  fromContact:listContact
zacw@1353
  1163
																onAccount:self];
zacw@1353
  1164
	
zacw@1353
  1165
	//Clear the typing flag of the chat since a message was just received
zacw@1353
  1166
	[self setTypingFlagOfChat:chat to:nil];
zacw@1353
  1167
	
zacw@1353
  1168
	[self _receivedMessage:attributedMessage
zacw@1353
  1169
					inChat:chat 
zacw@1353
  1170
		   fromListContact:listContact
zacw@1353
  1171
					 flags:flags
zacw@1353
  1172
					  date:[messageDict objectForKey:@"Date"]];
zacw@1353
  1173
}
zacw@1353
  1174
zacw@1353
  1175
- (void)receivedEventForChat:(AIChat *)chat
zacw@1353
  1176
					 message:(NSString *)message
zacw@1353
  1177
						date:(NSDate *)date
zacw@2848
  1178
					   flags:(NSNumber *)flagsNumber
zacw@1353
  1179
{
zacw@2848
  1180
	PurpleMessageFlags flags = [flagsNumber integerValue];
zacw@2848
  1181
	
zacw@1353
  1182
	AIContentEvent *event = [AIContentEvent eventInChat:chat
zacw@1353
  1183
											 withSource:nil
zacw@1353
  1184
											destination:self
zacw@1353
  1185
												   date:date
zacw@1353
  1186
												message:[AIHTMLDecoder decodeHTML:message]
zacw@1353
  1187
											   withType:@"purple"];
zacw@1353
  1188
	
zacw@1353
  1189
	event.filterContent = (flags & PURPLE_MESSAGE_NO_LINKIFY) != PURPLE_MESSAGE_NO_LINKIFY;
zacw@1353
  1190
	
zacw@1353
  1191
	[adium.contentController receiveContentObject:event];
David@0
  1192
}
David@0
  1193
David@0
  1194
- (void)receivedMultiChatMessage:(NSDictionary *)messageDict inChat:(AIChat *)chat
David@0
  1195
{	
sholt@2634
  1196
	PurpleMessageFlags	flags = [[messageDict objectForKey:@"PurpleMessageFlags"] integerValue];
David@0
  1197
	NSAttributedString	*attributedMessage = [messageDict objectForKey:@"AttributedMessage"];;
David@0
  1198
	NSString			*source = [messageDict objectForKey:@"Source"];
zacw@1353
  1199
	
zacw@1353
  1200
	[self _receivedMessage:attributedMessage
zacw@1353
  1201
					inChat:chat 
zacw@1353
  1202
		   fromListContact:[self contactWithUID:source]
zacw@1353
  1203
					 flags:flags
zacw@1353
  1204
					  date:[messageDict objectForKey:@"Date"]];
David@0
  1205
}
David@0
  1206
David@0
  1207
- (void)_receivedMessage:(NSAttributedString *)attributedMessage inChat:(AIChat *)chat fromListContact:(AIListContact *)sourceContact flags:(PurpleMessageFlags)flags date:(NSDate *)date
David@0
  1208
{
zacw@1346
  1209
	if ((flags & PURPLE_MESSAGE_DELAYED) == PURPLE_MESSAGE_DELAYED) {
zacw@1346
  1210
		// Display delayed messages as context.
zacw@1346
  1211
zacw@1346
  1212
		AIContentContext *messageObject = [AIContentContext messageInChat:chat
zacw@1346
  1213
															   withSource:sourceContact
zacw@1346
  1214
															  destination:self
zacw@1346
  1215
																	 date:date
zacw@1346
  1216
																  message:attributedMessage
zacw@1346
  1217
																autoreply:(flags & PURPLE_MESSAGE_AUTO_RESP) != 0];
zacw@1346
  1218
		
zacw@1346
  1219
		messageObject.trackContent = NO;
zacw@1346
  1220
		
zacw@1346
  1221
		[adium.contentController receiveContentObject:messageObject];
zacw@1346
  1222
		
zacw@1346
  1223
	} else {
zacw@1346
  1224
		AIContentMessage *messageObject = [AIContentMessage messageInChat:chat
zacw@1346
  1225
															   withSource:sourceContact
zacw@1346
  1226
															  destination:self
zacw@1346
  1227
																	 date:date
zacw@1346
  1228
																  message:attributedMessage
zacw@1346
  1229
																autoreply:(flags & PURPLE_MESSAGE_AUTO_RESP) != 0];
zacw@1346
  1230
		
zacw@1346
  1231
		[adium.contentController receiveContentObject:messageObject];	
zacw@1346
  1232
	}
David@0
  1233
}
David@0
  1234
David@0
  1235
/*********************/
David@0
  1236
/* AIAccount_Content */
David@0
  1237
/*********************/
David@0
  1238
#pragma mark Content
David@0
  1239
- (void)sendTypingObject:(AIContentTyping *)inContentTyping
David@0
  1240
{
David@813
  1241
	AIChat *chat = inContentTyping.chat;
David@0
  1242
David@428
  1243
	if (!chat.isGroupChat) {
David@813
  1244
		[purpleAdapter sendTyping:inContentTyping.typingState inChat:chat];
David@0
  1245
	}
David@0
  1246
}
David@0
  1247
zacw@1716
  1248
- (BOOL)sendMessageObject:(AIContentMessage *)inContentMessage
zacw@1716
  1249
{
zacw@1716
  1250
	PurpleMessageFlags		flags = PURPLE_MESSAGE_RAW;
zacw@1716
  1251
	
zacw@1716
  1252
	if ([inContentMessage isAutoreply]) {
zacw@1716
  1253
		flags |= PURPLE_MESSAGE_AUTO_RESP;
zacw@1716
  1254
	}
zacw@1716
  1255
zacw@1716
  1256
	[purpleAdapter sendEncodedMessage:[inContentMessage encodedMessage]
zacw@1716
  1257
						 fromAccount:self
zacw@1716
  1258
							  inChat:inContentMessage.chat
zacw@1716
  1259
						   withFlags:flags];
zacw@1716
  1260
zacw@1716
  1261
	return YES;
zacw@1716
  1262
}
zacw@1716
  1263
David@0
  1264
- (BOOL)supportsSendingNotifications
David@0
  1265
{
David@0
  1266
	return (account ? ((PURPLE_PLUGIN_PROTOCOL_INFO(purple_find_prpl(purple_account_get_protocol_id(account)))->send_attention) != NULL) : NO);
David@0
  1267
}
David@0
  1268
zacw@1716
  1269
- (BOOL)sendNotificationObject:(AIContentNotification *)inContentNotification
David@0
  1270
{
zacw@1716
  1271
	[purpleAdapter sendNotificationOfType:[inContentNotification notificationType]
zacw@1716
  1272
							  fromAccount:self
zacw@1716
  1273
								   inChat:inContentNotification.chat];	
David@0
  1274
	
David@0
  1275
	return YES;
David@0
  1276
}
David@0
  1277
David@0
  1278
/*!
David@0
  1279
 * @brief Return the string encoded for sending to a remote contact
David@0
  1280
 *
David@0
  1281
 * We return nil if the string turns out to have been a / command.
David@0
  1282
 */
David@0
  1283
- (NSString *)encodedAttributedStringForSendingContentMessage:(AIContentMessage *)inContentMessage
David@0
  1284
{
David@813
  1285
	BOOL		didCommand = [purpleAdapter attemptPurpleCommandOnMessage:[inContentMessage.message string]
David@0
  1286
														 fromAccount:(AIAccount *)[inContentMessage source]
David@813
  1287
															  inChat:inContentMessage.chat];	
David@0
  1288
	
David@0
  1289
	return (didCommand ? nil : [super encodedAttributedStringForSendingContentMessage:inContentMessage]);
David@0
  1290
}
David@0
  1291
David@0
  1292
/*!
David@0
  1293
 * @brief Libpurple prints file transfer messages to the chat window. The Adium core therefore shouldn't.
David@0
  1294
 */
David@0
  1295
- (BOOL)accountDisplaysFileTransferMessages
David@0
  1296
{
David@0
  1297
	return YES;
David@0
  1298
}
David@0
  1299
David@0
  1300
/*!
David@0
  1301
 * @brief Available for sending content
David@0
  1302
 *
David@0
  1303
 * Returns YES if the contact is available for receiving content of the specified type.  If contact is nil, instead
David@0
  1304
 * check for the availiability to send any content of the given type.
David@0
  1305
 *
David@0
  1306
 * We override the default implementation to check -[self allowFileTransferWithListObject:] for file transfers
David@0
  1307
 *
David@0
  1308
 * @param inType A string content type
David@0
  1309
 * @param inContact The destination contact, or nil to check global availability
David@0
  1310
 */
David@0
  1311
- (BOOL)availableForSendingContentType:(NSString *)inType toContact:(AIListContact *)inContact
David@0
  1312
{
David@837
  1313
    if (self.online && [inType isEqualToString:CONTENT_FILE_TRANSFER_TYPE]) {
David@0
  1314
		if (inContact) {
David@0
  1315
			return ([self conformsToProtocol:@protocol(AIAccount_Files)] &&
David@837
  1316
					((inContact.online || inContact.isStranger) && [self allowFileTransferWithListObject:inContact]));
David@0
  1317
		} else {
David@0
  1318
			return [self conformsToProtocol:@protocol(AIAccount_Files)];
David@0
  1319
		}
David@0
  1320
	}
David@0
  1321
David@0
  1322
    return [super availableForSendingContentType:inType toContact:inContact];
David@0
  1323
}
David@0
  1324
David@0
  1325
- (BOOL)allowFileTransferWithListObject:(AIListObject *)inListObject
David@0
  1326
{
zacw@2329
  1327
	PurplePluginProtocolInfo *prpl_info = self.protocolInfo;
zacw@2329
  1328
David@0
  1329
	if (prpl_info && prpl_info->send_file)
David@721
  1330
		return (!prpl_info->can_receive_file || prpl_info->can_receive_file(purple_account_get_connection(account), [inListObject.UID UTF8String]));
David@0
  1331
	else
David@0
  1332
		return NO;
David@0
  1333
}
David@0
  1334
David@0
  1335
- (BOOL)supportsAutoReplies
David@0
  1336
{
David@0
  1337
	if (account && purple_account_get_connection(account)) {
David@0
  1338
		return ((purple_account_get_connection(account)->flags & PURPLE_CONNECTION_AUTO_RESP) != 0);
David@0
  1339
	}
David@0
  1340
	
David@0
  1341
	return NO;
David@0
  1342
}
David@0
  1343
David@0
  1344
- (BOOL)canSendOfflineMessageToContact:(AIListContact *)inContact
David@0
  1345
{
zacw@2329
  1346
	PurplePluginProtocolInfo *prpl_info = self.protocolInfo;
zacw@2329
  1347
David@0
  1348
	if (prpl_info && prpl_info->offline_message) {
David@0
  1349
		
David@721
  1350
		return (prpl_info->offline_message(purple_find_buddy(account, [inContact.UID UTF8String])));
David@0
  1351
David@0
  1352
	} else
David@0
  1353
		return NO;
David@0
  1354
	
David@0
  1355
}
David@0
  1356
David@0
  1357
#pragma mark Custom emoticons
David@0
  1358
- (void)chat:(AIChat *)inChat isWaitingOnCustomEmoticon:(NSString *)emoticonEquivalent
David@0
  1359
{
David@0
  1360
	AIEmoticon *emoticon;
David@0
  1361
David@0
  1362
	//Look for an existing emoticon with this equivalent
catfish@1821
  1363
	for (emoticon in inChat.customEmoticons) {
David@0
  1364
		if ([[emoticon textEquivalents] containsObject:emoticonEquivalent]) break;
David@0
  1365
	}
David@0
  1366
	
David@0
  1367
	if (!emoticon) {
David@0
  1368
		emoticon = [AIEmoticon emoticonWithIconPath:nil
David@0
  1369
										equivalents:[NSArray arrayWithObject:emoticonEquivalent]
David@0
  1370
											   name:emoticonEquivalent
David@0
  1371
											   pack:nil];
David@0
  1372
		[inChat addCustomEmoticon:emoticon];			
David@0
  1373
	}
David@0
  1374
	
David@0
  1375
	if (![emoticon path]) {
David@0
  1376
		[emoticon setPath:[[NSBundle bundleForClass:[CBPurpleAccount class]] pathForResource:@"missing_image"
David@0
  1377
																					ofType:@"png"]];
David@0
  1378
	}
David@0
  1379
}
David@0
  1380
David@0
  1381
/*!
David@0
  1382
 * @brief Return the path at which to save an emoticon
David@0
  1383
 */
David@0
  1384
- (NSString *)_emoticonCachePathForEmoticon:(NSString *)emoticonEquivalent type:(AIBitmapImageFileType)fileType inChat:(AIChat *)inChat
David@0
  1385
{
David@0
  1386
	static unsigned long long emoticonID = 0;
David@0
  1387
    NSString    *filename = [NSString stringWithFormat:@"TEMP-CustomEmoticon_%@_%@_%qu.%@",
David@0
  1388
		[inChat uniqueChatID], emoticonEquivalent, emoticonID++, [NSImage extensionForBitmapImageFileType:fileType]];
David@0
  1389
    return [[adium cachesPath] stringByAppendingPathComponent:[filename safeFilenameString]];	
David@0
  1390
}
David@0
  1391
David@0
  1392
David@0
  1393
- (void)chat:(AIChat *)inChat setCustomEmoticon:(NSString *)emoticonEquivalent withImageData:(NSData *)inImageData
David@0
  1394
{
David@0
  1395
	/* XXX Note: If we can set outgoing emoticons, this method needs to be updated to mark emoticons as incoming
David@0
  1396
	 * and AIEmoticonController needs to be able to handle that.
David@0
  1397
	 */
David@0
  1398
	AIEmoticon	*emoticon;
David@0
  1399
David@0
  1400
	//Look for an existing emoticon with this equivalent
catfish@1821
  1401
	for (emoticon in inChat.customEmoticons) {
David@0
  1402
		if ([[emoticon textEquivalents] containsObject:emoticonEquivalent]) break;
David@0
  1403
	}
David@0
  1404
	
David@0
  1405
	//Write out our image
David@0
  1406
	NSString	*path = [self _emoticonCachePathForEmoticon:emoticonEquivalent
David@0
  1407
													   type:[NSImage fileTypeOfData:inImageData]
David@0
  1408
													 inChat:inChat];
David@0
  1409
	[inImageData writeToFile:path
David@0
  1410
				  atomically:NO];
David@0
  1411
David@0
  1412
	if (emoticon) {
David@0
  1413
		//If we already have an emoticon, just update its path
David@0
  1414
		[emoticon setPath:path];
David@0
  1415
David@0
  1416
	} else {
David@0
  1417
		emoticon = [AIEmoticon emoticonWithIconPath:path
David@0
  1418
										equivalents:[NSArray arrayWithObject:emoticonEquivalent]
David@0
  1419
											   name:emoticonEquivalent
David@0
  1420
											   pack:nil];
David@0
  1421
		[inChat addCustomEmoticon:emoticon];
David@0
  1422
	}
David@0
  1423
}
David@0
  1424
David@0
  1425
- (void)chat:(AIChat *)inChat closedCustomEmoticon:(NSString *)emoticonEquivalent
David@0
  1426
{
David@0
  1427
	AIEmoticon	*emoticon;
David@0
  1428
David@0
  1429
	//Look for an existing emoticon with this equivalent
catfish@1821
  1430
	for (emoticon in inChat.customEmoticons) {
David@0
  1431
		if ([[emoticon textEquivalents] containsObject:emoticonEquivalent]) break;
David@0
  1432
	}
David@0
  1433
	
David@0
  1434
	if (emoticon) {
David@1109
  1435
		[[NSNotificationCenter defaultCenter] postNotificationName:@"AICustomEmoticonUpdated"
David@0
  1436
												  object:inChat
David@0
  1437
												userInfo:[NSDictionary dictionaryWithObject:emoticon
David@0
  1438
																					 forKey:@"AIEmoticon"]];
David@0
  1439
	} else {
David@0
  1440
		//This shouldn't happen; chat:setCustomEmoticon:withImageData: should have already been called.
David@0
  1441
		emoticon = [AIEmoticon emoticonWithIconPath:nil
David@0
  1442
										equivalents:[NSArray arrayWithObject:emoticonEquivalent]
David@0
  1443
											   name:emoticonEquivalent
David@0
  1444
											   pack:nil];
David@0
  1445
		NSLog(@"Warning: closed custom emoticon %@ without adding it to the chat", emoticon);
David@0
  1446
		AILog(@"Warning: closed custom emoticon %@ without adding it to the chat", emoticon);
David@0
  1447
	}
David@0
  1448
}
David@0
  1449
David@0
  1450
/*********************/
David@0
  1451
/* AIAccount_Privacy */
David@0
  1452
/*********************/
David@0
  1453
#pragma mark Privacy
David@0
  1454
- (BOOL)addListObject:(AIListObject *)inObject toPrivacyList:(AIPrivacyType)type
David@0
  1455
{
David@0
  1456
    if (type == AIPrivacyTypePermit)
David@715
  1457
        return (purple_privacy_permit_add(account,[inObject.UID UTF8String],FALSE));
David@0
  1458
    else
David@715
  1459
        return (purple_privacy_deny_add(account,[inObject.UID UTF8String],FALSE));
David@0
  1460
}
David@0
  1461
David@0
  1462
- (BOOL)removeListObject:(AIListObject *)inObject fromPrivacyList:(AIPrivacyType)type
David@0
  1463
{
David@0
  1464
    if (type == AIPrivacyTypePermit)
David@715
  1465
        return (purple_privacy_permit_remove(account,[inObject.UID UTF8String],FALSE));
David@0
  1466
    else
David@715
  1467
        return (purple_privacy_deny_remove(account,[inObject.UID UTF8String],FALSE));
David@0
  1468
}
David@0
  1469
David@0
  1470
- (NSArray *)listObjectsOnPrivacyList:(AIPrivacyType)type
David@0
  1471
{
David@0
  1472
	NSMutableArray	*array = [NSMutableArray array];
David@0
  1473
	if (account) {
David@0
  1474
		GSList			*list;
David@0
  1475
		GSList			*sourceList = ((type == AIPrivacyTypePermit) ? account->permit : account->deny);
David@0
  1476
		
David@0
  1477
		for (list = sourceList; (list != NULL); list=list->next) {
David@0
  1478
			[array addObject:[self contactWithUID:[NSString stringWithUTF8String:(char *)list->data]]];
David@0
  1479
		}
David@0
  1480
	}
David@0
  1481
David@0
  1482
	return array;
David@0
  1483
}
David@0
  1484
David@0
  1485
- (void)accountPrivacyList:(AIPrivacyType)type added:(NSString *)sourceUID
David@0
  1486
{
David@0
  1487
	//Can't really trust sourceUID to not be @"" or something silly like that
David@0
  1488
	if ([sourceUID length]) {
David@0
  1489
		//Get our contact
David@0
  1490
		AIListContact   *contact = [self contactWithUID:sourceUID];
David@0
  1491
David@0
  1492
		//Update Adium's knowledge of it
David@0
  1493
		[contact setIsBlocked:((type == AIPrivacyTypeDeny) ? YES : NO) updateList:NO];
David@0
  1494
	}
David@0
  1495
}
David@0
  1496
David@0
  1497
- (void)privacyPermitListAdded:(NSString *)sourceUID
David@0
  1498
{
David@0
  1499
	[self accountPrivacyList:AIPrivacyTypePermit added:sourceUID];
David@0
  1500
}
David@0
  1501
David@0
  1502
- (void)privacyDenyListAdded:(NSString *)sourceUID
David@0
  1503
{
David@0
  1504
	[self accountPrivacyList:AIPrivacyTypeDeny added:sourceUID];
David@0
  1505
}
David@0
  1506
David@0
  1507
- (void)accountPrivacyList:(AIPrivacyType)type removed:(NSString *)sourceUID
David@0
  1508
{
David@0
  1509
	//Can't really trust sourceUID to not be @"" or something silly like that
David@0
  1510
	if ([sourceUID length]) {
David@0
  1511
		if (!namesAreCaseSensitive) {
David@0
  1512
			sourceUID = [sourceUID compactedString];
David@0
  1513
		}
David@0
  1514
David@0
  1515
		//Get our contact, which must already exist for us to care about its removal
David@89
  1516
		AIListContact   *contact = [adium.contactController existingContactWithService:service
David@0
  1517
																				 account:self
David@0
  1518
																					 UID:sourceUID];
David@0
  1519
		
David@0
  1520
		if (contact) {			
David@0
  1521
			//Update Adium's knowledge of it
David@0
  1522
			[contact setIsBlocked:((type == AIPrivacyTypeDeny) ? NO : YES) updateList:NO];
David@0
  1523
		}
David@0
  1524
	}
David@0
  1525
}
David@0
  1526
David@0
  1527
- (void)privacyPermitListRemoved:(NSString *)sourceUID
David@0
  1528
{
David@0
  1529
	[self accountPrivacyList:AIPrivacyTypePermit removed:sourceUID];
David@0
  1530
}
David@0
  1531
David@0
  1532
- (void)privacyDenyListRemoved:(NSString *)sourceUID
David@0
  1533
{
David@0
  1534
	[self accountPrivacyList:AIPrivacyTypeDeny removed:sourceUID];
David@0
  1535
}
David@0
  1536
David@0
  1537
- (void)setPrivacyOptions:(AIPrivacyOption)option
David@0
  1538
{
David@0
  1539
	if (account && purple_account_get_connection(account)) {
David@0
  1540
		PurplePrivacyType privacyType;
David@0
  1541
David@0
  1542
		switch (option) {
David@0
  1543
			case AIPrivacyOptionAllowAll:
David@0
  1544
			default:
David@0
  1545
				privacyType = PURPLE_PRIVACY_ALLOW_ALL;
David@0
  1546
				break;
David@0
  1547
			case AIPrivacyOptionDenyAll:
David@0
  1548
				privacyType = PURPLE_PRIVACY_DENY_ALL;
David@0
  1549
				break;
David@0
  1550
			case AIPrivacyOptionAllowUsers:
David@0
  1551
				privacyType = PURPLE_PRIVACY_ALLOW_USERS;
David@0
  1552
				break;
David@0
  1553
			case AIPrivacyOptionDenyUsers:
David@0
  1554
				privacyType = PURPLE_PRIVACY_DENY_USERS;
David@0
  1555
				break;
David@0
  1556
			case AIPrivacyOptionAllowContactList:
David@0
  1557
				privacyType = PURPLE_PRIVACY_ALLOW_BUDDYLIST;
David@0
  1558
				break;
David@0
  1559
			
David@0
  1560
		}
David@0
  1561
		
David@0
  1562
		if (account->perm_deny != privacyType) {
David@0
  1563
			account->perm_deny = privacyType;
David@0
  1564
			serv_set_permit_deny(purple_account_get_connection(account));
David@0
  1565
			AILog(@"Set privacy options for %@ (%x %x) to %i",
David@0
  1566
				  self,account,purple_account_get_connection(account),account->perm_deny);
David@0
  1567
sholt@2693
  1568
			[self setPreference:[NSNumber numberWithInteger:option]
David@0
  1569
						 forKey:KEY_PRIVACY_OPTION
David@0
  1570
						  group:GROUP_ACCOUNT_STATUS];			
David@0
  1571
		}
David@0
  1572
	} else {
David@0
  1573
		AILog(@"Couldn't set privacy options for %@ (%x %x)",self,account,purple_account_get_connection(account));
David@0
  1574
	}
David@0
  1575
}
David@0
  1576
David@0
  1577
- (AIPrivacyOption)privacyOptions
David@0
  1578
{
David@0
  1579
	AIPrivacyOption privacyOption = -1;
David@0
  1580
	
David@0
  1581
	if (account) {
David@0
  1582
		PurplePrivacyType privacyType = account->perm_deny;
David@0
  1583
		
David@0
  1584
		switch (privacyType) {
David@0
  1585
			case PURPLE_PRIVACY_ALLOW_ALL:
David@0
  1586
			default:
David@0
  1587
				privacyOption = AIPrivacyOptionAllowAll;
David@0
  1588
				break;
David@0
  1589
			case PURPLE_PRIVACY_DENY_ALL:
David@0
  1590
				privacyOption = AIPrivacyOptionDenyAll;
David@0
  1591
				break;
David@0
  1592
			case PURPLE_PRIVACY_ALLOW_USERS:
David@0
  1593
				privacyOption = AIPrivacyOptionAllowUsers;
David@0
  1594
				break;
David@0
  1595
			case PURPLE_PRIVACY_DENY_USERS:
David@0
  1596
				privacyOption = AIPrivacyOptionDenyUsers;
David@0
  1597
				break;
David@0
  1598
			case PURPLE_PRIVACY_ALLOW_BUDDYLIST:
David@0
  1599
				privacyOption = AIPrivacyOptionAllowContactList;
David@0
  1600
				break;
David@0
  1601
		}
David@0
  1602
	}
David@0
  1603
	AILog(@"%@: privacyOptions are %i",self,privacyOption);
David@0
  1604
	return privacyOption;
David@0
  1605
}
David@0
  1606
David@0
  1607
/*****************************************************/
David@0
  1608
/* File transfer / AIAccount_Files inherited methods */
David@0
  1609
/*****************************************************/
David@0
  1610
#pragma mark File Transfer
David@0
  1611
- (BOOL)canSendFolders
David@0
  1612
{
David@0
  1613
	return NO;
David@0
  1614
}
David@0
  1615
David@0
  1616
//Create a protocol-specific xfer object, set it up as requested, and begin sending
David@0
  1617
- (void)_beginSendOfFileTransfer:(ESFileTransfer *)fileTransfer
David@0
  1618
{
David@0
  1619
	PurpleXfer *xfer = [self newOutgoingXferForFileTransfer:fileTransfer];
David@0
  1620
	
David@0
  1621
	if (xfer) {
David@0
  1622
		//Associate the fileTransfer and the xfer with each other
David@0
  1623
		[fileTransfer setAccountData:[NSValue valueWithPointer:xfer]];
David@0
  1624
		xfer->ui_data = [fileTransfer retain];
David@0
  1625
		
David@0
  1626
		//Set the filename
David@0
  1627
		purple_xfer_set_local_filename(xfer, [[fileTransfer localFilename] UTF8String]);
David@0
  1628
		purple_xfer_set_filename(xfer, [[[fileTransfer localFilename] lastPathComponent] UTF8String]);
David@0
  1629
		
David@0
  1630
		/*
David@0
  1631
		 Request that the transfer begins.
David@0
  1632
		 We will be asked to accept it via:
David@0
  1633
			- (void)acceptFileTransferRequest:(ESFileTransfer *)fileTransfer
David@0
  1634
		 below.
David@0
  1635
		 */
David@0
  1636
		[purpleAdapter xferRequest:xfer];
David@0
  1637
		[fileTransfer setStatus: Waiting_on_Remote_User_FileTransfer];
David@0
  1638
	}
David@0
  1639
}
David@0
  1640
//By default, protocols can not create PurpleXfer objects
David@0
  1641
- (PurpleXfer *)newOutgoingXferForFileTransfer:(ESFileTransfer *)fileTransfer
David@0
  1642
{
David@0
  1643
	PurpleXfer				*newPurpleXfer = NULL;
David@0
  1644
David@0
  1645
	if (account && purple_account_get_connection(account)) {
zacw@2329
  1646
		PurplePluginProtocolInfo  *prpl_info = self.protocolInfo;
David@0
  1647
David@0
  1648
		if (prpl_info && prpl_info->new_xfer) {
David@0
  1649
			char *destsn = (char *)[[[fileTransfer contact] UID] UTF8String];
David@0
  1650
			newPurpleXfer = (prpl_info->new_xfer)(purple_account_get_connection(account), destsn);
David@0
  1651
		}
David@0
  1652
	}
David@0
  1653
David@0
  1654
	return newPurpleXfer;
David@0
  1655
}
David@0
  1656
David@0
  1657
/* 
David@0
  1658
 * @brief The account requested that we received a file.
David@0
  1659
 *
David@0
  1660
 * Set up the ESFileTransfer and query the fileTransferController for a save location.
David@0
  1661
 * 
David@0
  1662
 */
David@0
  1663
- (void)requestReceiveOfFileTransfer:(ESFileTransfer *)fileTransfer
David@0
  1664
{
David@0
  1665
	AILog(@"File transfer request received: %@",fileTransfer);
David@100
  1666
	[adium.fileTransferController receiveRequestForFileTransfer:fileTransfer];
David@0
  1667
}
David@0
  1668
David@0
  1669
//Create an ESFileTransfer object from an xfer
David@0
  1670
- (ESFileTransfer *)newFileTransferObjectWith:(NSString *)destinationUID
David@0
  1671
										 size:(unsigned long long)inSize
David@0
  1672
							   remoteFilename:(NSString *)remoteFilename
David@0
  1673
{
David@0
  1674
	AIListContact   *contact = [self contactWithUID:destinationUID];
David@0
  1675
    ESFileTransfer	*fileTransfer;
David@0
  1676
	
David@100
  1677
	fileTransfer = [adium.fileTransferController newFileTransferWithContact:contact
David@0
  1678
																   forAccount:self
David@0
  1679
																		 type:Unknown_FileTransfer]; 
David@0
  1680
	[fileTransfer setSize:inSize];
David@0
  1681
	[fileTransfer setRemoteFilename:remoteFilename];
David@0
  1682
	
David@0
  1683
    return fileTransfer;
David@0
  1684
}
David@0
  1685
David@0
  1686
//Update an ESFileTransfer object progress
David@0
  1687
- (void)updateProgressForFileTransfer:(ESFileTransfer *)fileTransfer percent:(NSNumber *)percent bytesSent:(NSNumber *)bytesSent
David@0
  1688
{
sholt@2693
  1689
	CGFloat percentDone = [percent doubleValue];
David@0
  1690
    [fileTransfer setPercentDone:percentDone bytesSent:[bytesSent unsignedLongValue]];
David@0
  1691
}
David@0
  1692
David@0
  1693
//The local side cancelled the transfer.  We probably already have this status set, but set it just in case.
David@0
  1694
- (void)fileTransferCancelledLocally:(ESFileTransfer *)fileTransfer
David@0
  1695
{
David@0
  1696
	if (![fileTransfer isStopped]) {
David@0
  1697
		[fileTransfer setStatus:Cancelled_Local_FileTransfer];
David@0
  1698
	}
David@0
  1699
}
David@0
  1700
David@0
  1701
//The remote side cancelled the transfer, the fool. Update our status.
David@0
  1702
- (void)fileTransferCancelledRemotely:(ESFileTransfer *)fileTransfer
David@0
  1703
{
David@0
  1704
	if (![fileTransfer isStopped]) {
David@0
  1705
		[fileTransfer setStatus:Cancelled_Remote_FileTransfer];
David@0
  1706
	}
David@0
  1707
}
David@0
  1708
David@0
  1709
- (void)destroyFileTransfer:(ESFileTransfer *)fileTransfer
David@0
  1710
{
David@0
  1711
	AILog(@"Destroy file transfer %@",fileTransfer);
David@0
  1712
	[fileTransfer release];
David@0
  1713
}
David@0
  1714
David@0
  1715
//Accept a send or receive ESFileTransfer object, beginning the transfer.
David@0
  1716
//Subsequently inform the fileTransferController that the fun has begun.
David@0
  1717
- (void)acceptFileTransferRequest:(ESFileTransfer *)fileTransfer
David@0
  1718
{
David@0
  1719
    AILog(@"Accepted file transfer %@",fileTransfer);
David@0
  1720
	
David@0
  1721
	PurpleXfer		*xfer;
David@0
  1722
	PurpleXferType	xferType;
David@0
  1723
	
David@0
  1724
	xfer = [[fileTransfer accountData] pointerValue];
David@0
  1725
David@0
  1726
    xferType = purple_xfer_get_type(xfer);
David@0
  1727
    if (xferType == PURPLE_XFER_SEND) {
David@0
  1728
        [fileTransfer setFileTransferType:Outgoing_FileTransfer];
David@0
  1729
David@0
  1730
    } else if (xferType == PURPLE_XFER_RECEIVE) {
David@0
  1731
        [fileTransfer setFileTransferType:Incoming_FileTransfer];
David@0
  1732
		[fileTransfer setSize:purple_xfer_get_size(xfer)];
David@0
  1733
    }
David@0
  1734
    
David@0
  1735
    //accept the request
David@0
  1736
	[purpleAdapter xferRequestAccepted:xfer withFileName:[fileTransfer localFilename]];
David@0
  1737
	
David@0
  1738
	[fileTransfer setStatus:Accepted_FileTransfer];
David@0
  1739
}
David@0
  1740
David@0
  1741
//User refused a receive request.  Tell purple; we don't release the ESFileTransfer object
David@0
  1742
//since that will happen when the xfer is destroyed.  This will end up calling back on
David@0
  1743
//- (void)fileTransfercancelledLocally:(ESFileTransfer *)fileTransfer
David@0
  1744
- (void)rejectFileReceiveRequest:(ESFileTransfer *)fileTransfer
David@0
  1745
{
David@0
  1746
	PurpleXfer	*xfer = [[fileTransfer accountData] pointerValue];
David@0
  1747
	if (xfer) {
David@0
  1748
		[purpleAdapter xferRequestRejected:xfer];
David@0
  1749
	}
David@0
  1750
}
David@0
  1751
David@0
  1752
//Cancel a file transfer in progress.  Tell purple; we don't release the ESFileTransfer object
David@0
  1753
//since that will happen when the xfer is destroyed.  This will end up calling back on
David@0
  1754
//- (void)fileTransfercancelledLocally:(ESFileTransfer *)fileTransfer
David@0
  1755
- (void)cancelFileTransfer:(ESFileTransfer *)fileTransfer
David@0
  1756
{
David@0
  1757
	PurpleXfer	*xfer = [[fileTransfer accountData] pointerValue];
David@0
  1758
	if (xfer) {
David@0
  1759
		[purpleAdapter xferCancel:xfer];
David@0
  1760
	}	
David@0
  1761
}
David@0
  1762
David@0
  1763
//Account Connectivity -------------------------------------------------------------------------------------------------
David@0
  1764
#pragma mark Connect
David@0
  1765
//Connect this account (Our password should be in the instance variable 'password' all ready for us)
David@0
  1766
- (void)connect
David@0
  1767
{
David@0
  1768
	finishedConnectProcess = NO;
David@0
  1769
David@0
  1770
	[super connect];
David@0
  1771
David@0
  1772
	//Ensure we have a purple account if one does not already exist
David@0
  1773
	[self purpleAccount];
David@0
  1774
	
David@0
  1775
	//Make sure our settings are correct
David@0
  1776
	if ([self connectivityBasedOnNetworkReachability] &&
David@427
  1777
		![self.host length]) {
David@0
  1778
		//If we use the network for connectivity, and we don't have a host, we need to get ourselves one. Prompt for it!
David@0
  1779
		[self promptForHostBeforeConnecting];
David@0
  1780
	} else {
David@0
  1781
		[self configurePurpleAccountNotifyingTarget:self selector:@selector(continueConnectWithConfiguredPurpleAccount)];
David@0
  1782
	}
David@0
  1783
}
David@0
  1784
David@0
  1785
- (void)unregister
David@0
  1786
{
David@0
  1787
	finishedConnectProcess = NO;
David@0
  1788
David@0
  1789
	[purpleAdapter unregisterAccount:self];
David@0
  1790
}
David@0
  1791
David@0
  1792
static void prompt_host_cancel_cb(CBPurpleAccount *self) {
David@0
  1793
	[self disconnect];
David@0
  1794
}
David@0
  1795
David@0
  1796
David@0
  1797
static void prompt_host_ok_cb(CBPurpleAccount *self, const char *host) {
David@0
  1798
	if(host && *host) {
David@0
  1799
		[self setPreference:[NSString stringWithUTF8String:host]
David@0
  1800
					 forKey:KEY_CONNECT_HOST
David@0
  1801
					  group:GROUP_ACCOUNT_STATUS];	
David@0
  1802
David@0
  1803
		[self configurePurpleAccountNotifyingTarget:self selector:@selector(continueConnectWithConfiguredPurpleAccount)];
David@0
  1804
	} else {
David@0
  1805
		prompt_host_cancel_cb(self);
David@0
  1806
	}
David@0
  1807
}
David@0
  1808
David@0
  1809
- (void)promptForHostBeforeConnecting
David@0
  1810
{
David@0
  1811
	purple_request_input(NULL, [[NSString stringWithFormat:AILocalizedString(@"%@ (%@) Setup", "first %@ is an account name; second is a service. This is a title for a window"),
David@427
  1812
								self.formattedUID, [self.service shortDescription]] UTF8String],
David@0
  1813
						 [AILocalizedString(@"No Server Specified", nil) UTF8String],
David@0
  1814
						 [[NSString stringWithFormat:AILocalizedString(@"No server has been configured for the %@ account %@. Please enter one below to connect", nil),
David@427
  1815
						   [self.service longDescription], self.formattedUID] UTF8String],
David@0
  1816
						 /* default value */ "", /* multiline */ FALSE, /* masked */ FALSE, /* hint */ NULL,
David@0
  1817
						 [AILocalizedString(@"Connect", "Button title to connect; this is a verb") UTF8String], G_CALLBACK(prompt_host_ok_cb),
David@0
  1818
						 [AILocalizedString(@"Cancel", nil) UTF8String], G_CALLBACK(prompt_host_cancel_cb),
David@0
  1819
						 /* account */ NULL, /* who */ NULL, /* conv */ NULL,
David@0
  1820
						 self);
David@0
  1821
						 
David@0
  1822
}
David@0
  1823
David@0
  1824
David@0
  1825
- (void)continueConnectWithConfiguredPurpleAccount
David@0
  1826
{
David@0
  1827
	//Configure libpurple's proxy settings; continueConnectWithConfiguredProxy will be called once we are ready
David@0
  1828
	[self configureAccountProxyNotifyingTarget:self selector:@selector(continueConnectWithConfiguredProxy)];
David@0
  1829
}
David@0
  1830
David@0
  1831
- (void)continueConnectWithConfiguredProxy
David@0
  1832
{
David@0
  1833
	//Set password and connect
David@0
  1834
	purple_account_set_password(account, [password UTF8String]);
David@0
  1835
David@0
  1836
	//Set our current status state after filtering its statusMessage as appropriate. This will take us online in the process.
David@0
  1837
	AIStatus	*statusState = [self valueForProperty:@"StatusState"];
David@837
  1838
	if (!statusState || (statusState.statusType == AIOfflineStatusType)) {
David@100
  1839
		statusState = [adium.statusController defaultInitialStatusState];
David@0
  1840
	}
David@0
  1841
David@427
  1842
	AILog(@"Adium: Connect: %@ initiating connection using status state %@ (%@).",self.UID,statusState,
David@0
  1843
			  [statusState statusMessageString]);
David@0
  1844
David@0
  1845
	[self autoRefreshingOutgoingContentForStatusKey:@"StatusState"
David@0
  1846
										   selector:@selector(gotFilteredStatusMessage:forStatusState:)
David@0
  1847
											context:statusState];
David@0
  1848
}
David@0
  1849
David@0
  1850
//Make sure our settings are correct; notify target/selector when we're finished
David@0
  1851
- (void)configurePurpleAccountNotifyingTarget:(id)target selector:(SEL)selector
David@0
  1852
{
David@0
  1853
	NSInvocation	*contextInvocation;
David@0
  1854
	
David@0
  1855
	//Perform the synchronous configuration activities (subclasses may want to take action in this function)
David@0
  1856
	[self configurePurpleAccount];
David@0
  1857
	
David@0
  1858
	contextInvocation = [NSInvocation invocationWithMethodSignature:[target methodSignatureForSelector:selector]];
David@0
  1859
	
David@0
  1860
	[contextInvocation setTarget:target];
David@0
  1861
	[contextInvocation setSelector:selector];
David@0
  1862
	[contextInvocation retainArguments];
David@0
  1863
David@0
  1864
	//Set the text profile BEFORE beginning the connect process, to avoid problems with setting it while the
David@0
  1865
	//connect occurs. Once that's done, contextInvocation will be invoked, continuing the configurePurpleAccount process.
David@0
  1866
	[self autoRefreshingOutgoingContentForStatusKey:@"TextProfile" 
David@0
  1867
										   selector:@selector(setAccountProfileTo:configurePurpleAccountContext:)
David@0
  1868
											context:contextInvocation];
David@0
  1869
}
David@0
  1870
David@0
  1871
/*!
David@0
  1872
 * @brief The server name to be passed to libpurple
David@0
  1873
 * By default, this is the host as seen by the rest of Adium.  Subclasses may choose to override this if
David@0
  1874
 * some trickery is desired between what is told to libpurple and what the rest of Adium sees.
David@0
  1875
 */
David@0
  1876
- (NSString *)hostForPurple
David@0
  1877
{
David@427
  1878
	return self.host;
David@0
  1879
}
David@0
  1880
David@0
  1881
//Synchronous purple account configuration activites, always performed after an account is created.
David@0
  1882
//This is a definite subclassing point so prpls can apply their own account settings.
David@0
  1883
- (void)configurePurpleAccount
David@0
  1884
{
David@0
  1885
	NSString	*hostName;
sholt@2693
  1886
	NSInteger			portNumber;
David@0
  1887
David@0
  1888
	//Host (server)
David@0
  1889
	hostName = [self hostForPurple];
David@0
  1890
	if (hostName && [hostName length]) {
David@0
  1891
		purple_account_set_string(account, "server", [hostName UTF8String]);
David@0
  1892
	}
David@0
  1893
	
David@0
  1894
	//Port
David@0
  1895
	portNumber = [self port];
David@0
  1896
	if (portNumber) {
David@0
  1897
		purple_account_set_int(account, "port", portNumber);
David@0
  1898
	}
David@0
  1899
	
David@0
  1900
	//E-mail checking
David@0
  1901
	purple_account_set_check_mail(account, [[self shouldCheckMail] boolValue]);
David@0
  1902
	
zacw@1510
  1903
	//Custom Emoticons
zacw@1510
  1904
	BOOL customEmoticons = [[self preferenceForKey:KEY_DISPLAY_CUSTOM_EMOTICONS group:GROUP_ACCOUNT_STATUS] boolValue];
zacw@1510
  1905
	purple_account_set_bool(account, "custom_smileys", customEmoticons);
zacw@1510
  1906
	
David@0
  1907
	//Update a few properties before we begin connecting.  Libpurple will send these automatically
David@0
  1908
    [self updateStatusForKey:KEY_USER_ICON];
David@0
  1909
}
David@0
  1910
David@0
  1911
/*!
David@0
  1912
 * @brief Configure libpurple's proxy settings using the current system values
David@0
  1913
 *
David@0
  1914
 * target/selector are used rather than a hardcoded callback (or getProxyConfigurationNotifyingTarget: directly) because this allows code reuse
David@0
  1915
 * between the connect and register processes, which are similar in their need for proxy configuration
David@0
  1916
 */
David@0
  1917
- (void)configureAccountProxyNotifyingTarget:(id)target selector:(SEL)selector
David@0
  1918
{
David@0
  1919
	NSInvocation		*invocation; 
David@0
  1920
David@0
  1921
	//Configure the invocation we will use when we are done configuring
David@0
  1922
	invocation = [NSInvocation invocationWithMethodSignature:[target methodSignatureForSelector:selector]];
David@0
  1923
	[invocation setSelector:selector];
David@0
  1924
	[invocation setTarget:target];
David@0
  1925
	
David@0
  1926
	[self getProxyConfigurationNotifyingTarget:self
David@0
  1927
									  selector:@selector(retrievedProxyConfiguration:context:)
David@0
  1928
									   context:invocation];
David@0
  1929
}
David@0
  1930
David@0
  1931
/*!
David@0
  1932
 * @brief Callback for -[self getProxyConfigurationNotifyingTarget:selector:context:]
David@0
  1933
 */
David@0
  1934
- (void)retrievedProxyConfiguration:(NSDictionary *)proxyConfig context:(NSInvocation *)invocation
David@0
  1935
{
David@0
  1936
	PurpleProxyInfo		*proxy_info;
David@0
  1937
	
sholt@2634
  1938
	AdiumProxyType  	proxyType = [[proxyConfig objectForKey:@"AdiumProxyType"] integerValue];
David@0
  1939
	
David@0
  1940
	proxy_info = purple_proxy_info_new();
David@0
  1941
	purple_account_set_proxy_info(account, proxy_info);
David@0
  1942
David@0
  1943
	PurpleProxyType		purpleAccountProxyType;
David@0
  1944
	
David@0
  1945
	switch (proxyType) {
David@0
  1946
		case Adium_Proxy_HTTP:
David@0
  1947
		case Adium_Proxy_Default_HTTP:
David@0
  1948
			purpleAccountProxyType = PURPLE_PROXY_HTTP;
David@0
  1949
			break;
David@0
  1950
		case Adium_Proxy_SOCKS4:
David@0
  1951
		case Adium_Proxy_Default_SOCKS4:
David@0
  1952
			purpleAccountProxyType = PURPLE_PROXY_SOCKS4;
David@0
  1953
			break;
David@0
  1954
		case Adium_Proxy_SOCKS5:
David@0
  1955
		case Adium_Proxy_Default_SOCKS5:
David@0
  1956
			purpleAccountProxyType = PURPLE_PROXY_SOCKS5;
David@0
  1957
			break;
David@0
  1958
		case Adium_Proxy_None:
David@0
  1959
		default:
David@0
  1960
			purpleAccountProxyType = PURPLE_PROXY_NONE;
David@0
  1961
			break;
David@0
  1962
	}
David@0
  1963
	
David@0
  1964
	purple_proxy_info_set_type(proxy_info, purpleAccountProxyType);
David@0
  1965
David@0
  1966
	if (proxyType != Adium_Proxy_None) {
David@0
  1967
		purple_proxy_info_set_host(proxy_info, (char *)[[proxyConfig objectForKey:@"Host"] UTF8String]);
sholt@2634
  1968
		purple_proxy_info_set_port(proxy_info, [[proxyConfig objectForKey:@"Port"] integerValue]);
David@0
  1969
David@0
  1970
		purple_proxy_info_set_username(proxy_info, (char *)[[proxyConfig objectForKey:@"Username"] UTF8String]);
David@0
  1971
		purple_proxy_info_set_password(proxy_info, (char *)[[proxyConfig objectForKey:@"Password"] UTF8String]);
David@0
  1972
		
David@0
  1973
		AILog(@"Connecting with proxy type %i and proxy host %@",proxyType, [proxyConfig objectForKey:@"Host"]);
David@0
  1974
	}
David@0
  1975
David@0
  1976
	[invocation invoke];
David@0
  1977
}
David@0
  1978
David@0
  1979
//Sublcasses should override to provide a string for each progress step
sholt@2693
  1980
- (NSString *)connectionStringForStep:(NSInteger)step { return nil; };
David@0
  1981
David@0
  1982
/*!
David@0
  1983
 * @brief Should the account's status be updated as soon as it is connected?
David@0
  1984
 *
David@0
  1985
 * If YES, the StatusState and IdleSince properties will be told to update as soon as the account connects.
David@0
  1986
 * This will allow the account to send its status information to the server upon connecting.
David@0
  1987
 *
David@0
  1988
 * If this information is already known by the account at the time it connects and further prompting to send it is
David@0
  1989
 * not desired, return NO.
David@0
  1990
 *
David@0
  1991
 * libpurple should already have been told of our status before connecting began.
David@0
  1992
 */
David@0
  1993
- (BOOL)updateStatusImmediatelyAfterConnecting
David@0
  1994
{
David@0
  1995
	return NO;
David@0
  1996
}
David@0
  1997
David@0
  1998
- (void)didConnect
David@0
  1999
{
David@0
  2000
	finishedConnectProcess = YES;
David@0
  2001
David@0
  2002
	[super didConnect];
David@0
  2003
	
David@1109
  2004
	[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(iTunesDidUpdate:) name:Adium_iTunesTrackChangedNotification object:nil];
Evan@677
  2005
David@0
  2006
	//Silence updates
David@0
  2007
	[self silenceAllContactUpdatesForInterval:18.0];
David@14
  2008
	[[AIContactObserverManager sharedManager] delayListObjectNotificationsUntilInactivity];
David@0
  2009
	
David@0
  2010
	//Clear any previous disconnection error
David@0
  2011
	[self setLastDisconnectionError:nil];
David@0
  2012
David@0
  2013
	if (unregisterAfterConnecting)
David@0
  2014
		[self unregister];
David@0
  2015
}
David@0
  2016
David@0
  2017
//Our account has connected
David@0
  2018
- (void)accountConnectionConnected
David@0
  2019
{
David@427
  2020
	AILog(@"************ %@ CONNECTED ***********",self.UID);
David@0
  2021
	[self didConnect];
David@0
  2022
}
David@0
  2023
David@0
  2024
- (void)accountConnectionProgressStep:(NSNumber *)step percentDone:(NSNumber *)connectionProgressPrecent
David@0
  2025
{
sholt@2693
  2026
	NSString	*connectionProgressString = [self connectionStringForStep:[step integerValue]];
David@0
  2027
David@0
  2028
	[self setValue:connectionProgressString forProperty:@"ConnectionProgressString" notify:NO];
David@0
  2029
	[self setValue:connectionProgressPrecent forProperty:@"ConnectionProgressPercent" notify:NO];	
David@0
  2030
David@0
  2031
	//Apply any changes
David@0
  2032
	[self notifyOfChangedPropertiesSilently:NO];
David@0
  2033
	
sholt@2693
  2034
	AILog(@"************ %@ --step-- %i",self.UID,[step integerValue]);
David@0
  2035
}
David@0
  2036
David@0
  2037
/*!
David@0
  2038
 * @brief Name to use when creating a PurpleAccount for this CBPurpleAccount
David@0
  2039
 *
David@0
  2040
 * By default, we just use the formattedUID.  Subclasses can override this to provide other handling,
David@0
  2041
 * such as appending \@mac.com if necessary for dotMac accounts.
David@0
  2042
 */
David@0
  2043
- (const char *)purpleAccountName
David@0
  2044
{
David@427
  2045
	return [self.formattedUID UTF8String];
David@0
  2046
}
David@0
  2047
David@0
  2048
- (void)setPurpleAccount:(PurpleAccount *)inAccount
David@0
  2049
{
David@0
  2050
	account = inAccount;
David@0
  2051
}
David@0
  2052
David@0
  2053
- (void)createNewPurpleAccount
David@0
  2054
{
David@0
  2055
	//Ensure libpurple is loaded and initialized
David@0
  2056
	[self purpleAdapter];
David@0
  2057
	
David@0
  2058
	//If loading libpurple didn't set an account for us, tell it to create one
David@0
  2059
	if (!account)
David@0
  2060
		[[self purpleAdapter] addAdiumAccount:self];
David@0
  2061
David@0
  2062
	//-[SLPurpleCocoaAdapter addAdiumAccount:] should have immediately called back on setPurpleAccount. It's bad if it didn't.
David@0
  2063
	if (account) {
David@427
  2064
		AILog(@"Created PurpleAccount 0x%x with UID %@ and protocolPlugin %s", account, self.UID, [self protocolPlugin]);
David@0
  2065
	} else {
David@0
  2066
		AILog(@"Unable to create Libpurple account with name %s and protocol plugin %s",
David@427
  2067
			  self.purpleAccountName, [self protocolPlugin]);
David@0
  2068
		NSLog(@"Unable to create Libpurple account with name %s and protocol plugin %s",
David@427
  2069
			  self.purpleAccountName, [self protocolPlugin]);
David@0
  2070
	}
David@0
  2071
}
David@0
  2072
zacw@998
  2073
/*!
zacw@998
  2074
 * @brief Returns a PurpleSslConnection for a given account.
zacw@998
  2075
 */
zacw@999
  2076
- (PurpleSslConnection *)secureConnection
zacw@998
  2077
{
zacw@998
  2078
	return NULL;
zacw@998
  2079
}
zacw@998
  2080
David@0
  2081
#pragma mark Disconnect
David@0
  2082
David@0
  2083
/*!
David@0
  2084
 * @brief Disconnect this account
David@0
  2085
 */
David@0
  2086
- (void)disconnect
David@0
  2087
{
David@837
  2088
	if (self.online || [self boolValueForProperty:@"Connecting"]) {
David@0
  2089
		//As per AIAccount's documentation, call super's implementation
David@0
  2090
		[super disconnect];
David@0
  2091
David@14
  2092
		[[AIContactObserverManager sharedManager] delayListObjectNotificationsUntilInactivity];
David@0
  2093
David@0
  2094
		//Tell libpurple to disconnect
David@0
  2095
		[purpleAdapter disconnectAccount:self];
David@0
  2096
	}
David@0
  2097
}
David@0
  2098
David@0
  2099
- (void)setLastDisconnectionReason:(PurpleConnectionError)reason
David@0
  2100
{
David@0
  2101
	lastDisconnectionReason = reason;
David@0
  2102
}
David@0
  2103
David@0
  2104
- (PurpleConnectionError)lastDisconnectionReason
David@0
  2105
{
David@0
  2106
	return lastDisconnectionReason;
David@0
  2107
}
David@0
  2108
David@0
  2109
/*!
David@0
  2110
 * @brief Our account was unexpectedly disconnected with an error message
David@0
  2111
 */
David@0
  2112
- (void)accountConnectionReportDisconnect:(NSString *)text withReason:(PurpleConnectionError)reason
David@0
  2113
{
David@0
  2114
	[self setLastDisconnectionError:text];
David@0
  2115
	[self setLastDisconnectionReason:reason];
David@0
  2116
David@0
  2117
	if (reason == PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED)
David@0
  2118
		[self serverReportedInvalidPassword];
David@0
  2119
David@0
  2120
	//We are disconnecting
David@0
  2121
    [self setValue:[NSNumber numberWithBool:YES] forProperty:@"Disconnecting" notify:NotifyNow];
David@0
  2122
	
David@0
  2123
	AILog(@"%@ accountConnectionReportDisconnect: %@",self,lastDisconnectionError);
David@0
  2124
}
David@0
  2125
David@0
  2126
- (void)accountConnectionNotice:(NSString *)connectionNotice
David@0
  2127
{
David@427
  2128
    [adium.interfaceController handleErrorMessage:[NSString stringWithFormat:AILocalizedString(@"%@ (%@) : Connection Notice",nil),self.formattedUID,[service description]]
David@0
  2129
                                    withDescription:connectionNotice];
David@0
  2130
}
David@0
  2131
David@0
  2132
- (void)didDisconnect
David@0
  2133
{
David@0
  2134
	//Clear properties which don't make sense for a disconnected account
David@0
  2135
	[self setValue:nil forProperty:@"TextProfile" notify:NO];
David@0
  2136
	
David@0
  2137
	//Apply any changes
David@0
  2138
	[self notifyOfChangedPropertiesSilently:NO];
David@0
  2139
	
David@1109
  2140
	[[NSNotificationCenter defaultCenter] removeObserver:self
David@0
  2141
										  name:Adium_iTunesTrackChangedNotification
David@0
  2142
										object:nil];
David@0
  2143
	[tuneinfo release];
David@0
  2144
	tuneinfo = nil;
David@0
  2145
	
David@0
  2146
	if (deletePurpleAccountAfterDisconnecting) {
David@0
  2147
		deletePurpleAccountAfterDisconnecting = FALSE;
David@0
  2148
David@0
  2149
		[[self purpleAdapter] removeAdiumAccount:self];
David@0
  2150
	}
David@0
  2151
David@0
  2152
	[super didDisconnect];
David@0
  2153
}
David@0
  2154
/*!
David@0
  2155
 * @brief Our account has disconnected
David@0
  2156
 *
David@0
  2157
 * This is called after the account disconnects for any reason
David@0
  2158
 */
David@0
  2159
- (void)accountConnectionDisconnected
David@0
  2160
{
David@0
  2161
	//Report that we disconnected
David@0
  2162
	AILog(@"%@: Telling the core we disconnected", self);
David@0
  2163
	[self didDisconnect];
David@0
  2164
}
David@0
  2165
David@0
  2166
- (AIReconnectDelayType)shouldAttemptReconnectAfterDisconnectionError:(NSString **)disconnectionError
David@0
  2167
{
David@0
  2168
	AIReconnectDelayType reconnectDelayType;
David@0
  2169
David@0
  2170
	if ([self lastDisconnectionReason] == PURPLE_CONNECTION_ERROR_AUTHENTICATION_FAILED) {
David@0
  2171
		[self setLastDisconnectionError:AILocalizedString(@"Incorrect username or password","Error message displayed when the server reports username or password as being incorrect.")];
David@0
  2172
		reconnectDelayType = AIReconnectImmediately;
David@0
  2173
David@0
  2174
	} else if ([self lastDisconnectionReason] == PURPLE_CONNECTION_ERROR_INVALID_USERNAME) {
David@0
  2175
		[self setLastDisconnectionError:AILocalizedString(@"The name you entered is not registered. Check to ensure you typed it correctly.", nil)];
David@0
  2176
		reconnectDelayType = AIReconnectNever;
David@0
  2177
David@0
  2178
	} else if (disconnectionError && ([*disconnectionError isEqualToString:[NSString stringWithUTF8String:_("SSL Handshake Failed")]] ||
David@0
  2179
									  [*disconnectionError isEqualToString:[NSString stringWithUTF8String:_("SSL Connection Failed")]])) {
David@0
  2180
		/* This particular message comes with PURPLE_CONNECTION_ERROR_ENCRYPTION_ERROR, which is a 'fatal' error according to libpurple. Other problems
David@0
  2181
		 * with that message may be fatal, but this one isn't.
David@0
  2182
		 */
David@0
  2183
		reconnectDelayType = AIReconnectNormally;
David@0
  2184
David@0
  2185
	} else if (purple_connection_error_is_fatal([self lastDisconnectionReason])) {
David@0
  2186
		reconnectDelayType = AIReconnectNever;
David@0
  2187
David@0
  2188
	} else {
David@0
  2189
		reconnectDelayType = AIReconnectNormally;
David@0
  2190
	}
David@0
  2191
David@0
  2192
	return reconnectDelayType;
David@0
  2193
}
David@0
  2194
David@0
  2195
#pragma mark Registering
David@0
  2196
- (void)performRegisterWithPassword:(NSString *)inPassword
David@0
  2197
{
David@0
  2198
	//Save the new password
Andreas@26
  2199
	if (inPassword && ![password isEqualToString:inPassword]) {
David@718
  2200
		[password release]; password = [inPassword retain];
David@0
  2201
	}
David@0
  2202
David@0
  2203
	//Ensure we have a purple account if one does not already exist
David@0
  2204
	[self purpleAccount];
David@0
  2205
	
David@0
  2206
	//We are connecting
David@0
  2207
	[self setValue:[NSNumber numberWithBool:YES] forProperty:@"Connecting" notify:NotifyNow];
David@0
  2208
	
David@0
  2209
	//Make sure our settings are correct
David@0
  2210
	[self configurePurpleAccountNotifyingTarget:self selector:@selector(continueRegisterWithConfiguredPurpleAccount)];
David@0
  2211
}
David@0
  2212
David@0
  2213
- (void)continueRegisterWithConfiguredProxy
David@0
  2214
{
David@0
  2215
	//Set password and connect
David@0
  2216
	purple_account_set_password(account, [password UTF8String]);
David@0
  2217
	
David@427
  2218
	AILog(@"Adium: Register: %@ initiating connection.",self.UID);
David@0
  2219
	
David@0
  2220
	[purpleAdapter registerAccount:self];
David@0
  2221
}
David@0
  2222
David@0
  2223
- (void)continueRegisterWithConfiguredPurpleAccount
David@0
  2224
{
David@0
  2225
	//Configure libpurple's proxy settings; continueConnectWithConfiguredProxy will be called once we are ready
David@0
  2226
	[self configureAccountProxyNotifyingTarget:self selector:@selector(continueRegisterWithConfiguredProxy)];
David@0
  2227
}
David@0
  2228
David@0
  2229
- (void)purpleAccountRegistered:(BOOL)success
David@0
  2230
{
David@427
  2231
	if (success && [self.service accountViewController]) {
David@0
  2232
		NSString *username = (purple_account_get_username(account) ? [NSString stringWithUTF8String:purple_account_get_username(account)] : [NSNull null]);
David@0
  2233
		NSString *pw = (purple_account_get_password(account) ? [NSString stringWithUTF8String:purple_account_get_password(account)] : [NSNull null]);
David@0
  2234
David@1109
  2235
		[[NSNotificationCenter defaultCenter] postNotificationName:AIAccountUsernameAndPasswordRegisteredNotification
David@0
  2236
												  object:self
David@0
  2237
												userInfo:[NSDictionary dictionaryWithObjectsAndKeys:
David@0
  2238
													username, @"username",
David@0
  2239
													pw, @"password",
David@0
  2240
													nil]];
David@0
  2241
	}
David@0
  2242
}
David@0
  2243
David@0
  2244
//Account Status ------------------------------------------------------------------------------------------------------
David@0
  2245
#pragma mark Account Status
David@0
  2246
//Properties this account supports
David@0
  2247
- (NSSet *)supportedPropertyKeys
David@0
  2248
{
David@0
  2249
	static NSMutableSet *supportedPropertyKeys = nil;
David@0
  2250
	
David@0
  2251
	if (!supportedPropertyKeys) {
David@0
  2252
		supportedPropertyKeys = [[NSMutableSet alloc] initWithObjects:
David@0
  2253
			@"IdleSince",
David@0
  2254
			@"IdleManuallySet",
David@0
  2255
			@"TextProfile",
David@0
  2256
			@"DefaultUserIconFilename",
David@0
  2257
			KEY_ACCOUNT_CHECK_MAIL,
David@0
  2258
			nil];
David@0
  2259
		[supportedPropertyKeys unionSet:[super supportedPropertyKeys]];
David@0
  2260
		
David@0
  2261
	}
David@0
  2262
David@0
  2263
	return supportedPropertyKeys;
David@0
  2264
}
David@0
  2265
David@0
  2266
//Update our status
David@0
  2267
- (void)updateStatusForKey:(NSString *)key
David@0
  2268
{    
David@0
  2269
	[super updateStatusForKey:key];
David@0
  2270
	
David@0
  2271
    //Now look at keys which only make sense if we have an account
David@0
  2272
	if (account) {
David@0
  2273
		AILog(@"%@: Updating status for key: %@",self, key);
David@0
  2274
David@0
  2275
		if ([key isEqualToString:@"IdleSince"]) {
zacw@2311
  2276
			NSDate	*idleSince = [self preferenceForKey:@"IdleSince" group:GROUP_ACCOUNT_STATUS];
zacw@2311
  2277
			
zacw@2311
  2278
			if (!idleSince) {
zacw@2311
  2279
				idleSince = [adium.preferenceController preferenceForKey:@"IdleSince" group:GROUP_ACCOUNT_STATUS];
zacw@2311
  2280
			}
zacw@2311
  2281
			
David@0
  2282
			[self setAccountIdleSinceTo:idleSince];
David@0
  2283
							
David@0
  2284
		} else if ([key isEqualToString:@"TextProfile"]) {
David@0
  2285
			[self autoRefreshingOutgoingContentForStatusKey:key selector:@selector(setAccountProfileTo:) context:nil];
David@0
  2286
David@0
  2287
		} else if ([key isEqualToString:KEY_ACCOUNT_CHECK_MAIL]) {
David@0
  2288
			//Update the mail checking setting if the account is already made (if it isn't, we'll set it when it is made)
David@0
  2289
			if (account) {
David@0
  2290
				[purpleAdapter setCheckMail:[self shouldCheckMail]
David@0
  2291
							  forAccount:self];
David@0
  2292
			}
David@0
  2293
		}
David@0
  2294
	}
David@0
  2295
}
David@0
  2296
David@0
  2297
/*!
David@0
  2298
 * @brief Return the purple status type to be used for a status
David@0
  2299
 *
David@0
  2300
 * Most subclasses should override this method; these generic values may be appropriate for others.
David@0
  2301
 *
David@0
  2302
 * Active services provided nonlocalized status names.  An AIStatus is passed to this method along with a pointer
David@0
  2303
 * to the status message.  This method should handle any status whose statusNname this service set as well as any statusName
David@0
  2304
 * defined in  AIStatusController.h (which will correspond to the services handled by Adium by default).
David@0
  2305
 * 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
  2306
 * statusState.statusType for a general idea of the status's type.
David@0
  2307
 *
David@0
  2308
 * @param statusState The status for which to find the purple status ID
David@0
  2309
 * @param arguments Prpl-specific arguments which will be passed with the state. Message is handled automatically.
David@0
  2310
 *
David@0
  2311
 * @result The purple status ID
David@0
  2312
 */
David@0
  2313
- (const char *)purpleStatusIDForStatus:(AIStatus *)statusState
David@0
  2314
							arguments:(NSMutableDictionary *)arguments
David@0
  2315
{
David@0
  2316
	char	*statusID = NULL;
David@0
  2317
	
David@837
  2318
	switch (statusState.statusType) {
David@0
  2319
		case AIAvailableStatusType:
David@0
  2320
			statusID = "available";
David@0
  2321
			break;
David@0
  2322
		case AIAwayStatusType:
David@0
  2323
			statusID = "away";
David@0
  2324
			break;
David@0
  2325
			
David@0
  2326
		case AIInvisibleStatusType:
David@0
  2327
			statusID = "invisible";
David@0
  2328
			break;
David@0
  2329
			
David@0
  2330
		case AIOfflineStatusType:
David@0
  2331
			statusID = "offline";
David@0
  2332
			break;
David@0
  2333
	}
David@0
  2334
	
David@0
  2335
	return statusID;
David@0
  2336
}
David@0
  2337
David@0
  2338
- (BOOL)shouldAddMusicalNoteToNowPlayingStatus
David@0
  2339
{
David@0
  2340
	return YES;
David@0
  2341
}
David@0
  2342
David@0
  2343
- (BOOL)shouldSetITMSLinkForNowPlayingStatus
David@0
  2344
{
David@0
  2345
	return NO;
David@0
  2346
}
David@0
  2347
David@0
  2348
- (NSDictionary *)purpleSongInfoDictionary
David@0
  2349
{
Evan@677
  2350
	NSMutableDictionary *arguments = nil;
Evan@677
  2351
Evan@677
  2352
	if (tuneinfo && [[tuneinfo objectForKey:ITUNES_PLAYER_STATE] isEqualToString:@"Playing"]) {
Evan@677
  2353
		arguments = [NSMutableDictionary dictionary];
Evan@677
  2354
		
Evan@677
  2355
		NSString *artist = [tuneinfo objectForKey:ITUNES_ARTIST];
Evan@677
  2356
		NSString *name = [tuneinfo objectForKey:ITUNES_NAME];
Evan@677
  2357
		
Evan@677
  2358
		[arguments setObject:(artist ? artist : @"") forKey:[NSString stringWithUTF8String:PURPLE_TUNE_ARTIST]];
Evan@677
  2359
		[arguments setObject:(name ? name : @"") forKey:[NSString stringWithUTF8String:PURPLE_TUNE_TITLE]];
Evan@677
  2360
		[arguments setObject:([tuneinfo objectForKey:ITUNES_ALBUM] ? [tuneinfo objectForKey:ITUNES_ALBUM] : @"") forKey:[NSString stringWithUTF8String:PURPLE_TUNE_ALBUM]];
Evan@677
  2361
		[arguments setObject:([tuneinfo objectForKey:ITUNES_GENRE] ? [tuneinfo objectForKey:ITUNES_GENRE] : @"") forKey:[NSString stringWithUTF8String:PURPLE_TUNE_GENRE]];
sholt@2693
  2362
		[arguments setObject:([tuneinfo objectForKey:ITUNES_TOTAL_TIME] ? [tuneinfo objectForKey:ITUNES_TOTAL_TIME]:[NSNumber numberWithInteger:-1]) forKey:[NSString stringWithUTF8String:PURPLE_TUNE_TIME]];
sholt@2693
  2363
		[arguments setObject:([tuneinfo objectForKey:ITUNES_YEAR] ? [tuneinfo objectForKey:ITUNES_YEAR]:[NSNumber numberWithInteger:-1]) forKey:[NSString stringWithUTF8String:PURPLE_TUNE_YEAR]];
Evan@677
  2364
		[arguments setObject:([tuneinfo objectForKey:ITUNES_STORE_URL] ? [tuneinfo objectForKey:ITUNES_STORE_URL] : @"") forKey:[NSString stringWithUTF8String:PURPLE_TUNE_URL]];
Evan@677
  2365
		
Evan@677
  2366
		[arguments setObject:[NSString stringWithFormat:@"%@%@%@", (name ? name : @""), (name && artist ? @" - " : @""), (artist ? artist : @"")]
Evan@677
  2367
					  forKey:[NSString stringWithUTF8String:PURPLE_TUNE_FULL]];
David@0
  2368
	}
David@0
  2369
David@0
  2370
	return arguments;
David@0
  2371
}
David@0
  2372
David@0
  2373
- (void)iTunesDidUpdate:(NSNotification*)notification {
Evan@677
  2374
	[tuneinfo release];
Evan@677
  2375
	tuneinfo = [[notification object] retain];
Evan@677
  2376
Evan@677
  2377
	/* Only if we're including the information in all statuses do we need to do an update;
Evan@677
  2378
	 * if we just have a 'now playing' status, the dynamic stats update will call
Evan@677
  2379
	 * -[self setStatusState:usingStatusMessage:] in a moment.
Evan@677
  2380
	 */	 
Evan@677
  2381
	[purpleAdapter setSongInformation:(shouldIncludeNowPlayingInformationInAllStatuses ? [self purpleSongInfoDictionary] : nil) onAccount:self];
David@0
  2382
}
David@0
  2383
David@0
  2384
/*!
Evan@285
  2385
 * @brief Should a status message be set when using the default "Away" state?
Evan@285
  2386
 */
Evan@285
  2387
- (BOOL)shouldSetStatusMessageForDefaultAwayState
Evan@285
  2388
{
Evan@285
  2389
	return YES;
Evan@285
  2390
}
Evan@285
  2391
Evan@285
  2392
/*!
David@0
  2393
 * @brief Perform the setting of a status state
David@0
  2394
 *
David@0
  2395
 * Sets the account to a passed status state.  The account should set itself to best possible status given the return
David@0
  2396
 * values of statusState's accessors.  The passed statusMessage has been filtered; it should be used rather than
David@837
  2397
 * statusState.statusMessage, which returns an unfiltered statusMessage.
David@0
  2398
 *
David@0
  2399
 * @param statusState The state to enter
David@0
  2400
 * @param statusMessage The filtered status message to use.
David@0
  2401
 */
David@0
  2402
- (void)setStatusState:(AIStatus *)statusState usingStatusMessage:(NSAttributedString *)statusMessage
David@0
  2403
{
David@0
  2404
	NSString			*encodedStatusMessage;
David@0
  2405
	NSMutableDictionary	*arguments = [[NSMutableDictionary alloc] init];
David@0
  2406
David@0
  2407
	//Get the purple status type from this class or subclasses, which may also potentially modify or nullify our statusMessage
David@0
  2408
	const char *statusID = [self purpleStatusIDForStatus:statusState
David@0
  2409
											 arguments:arguments];
David@0
  2410
David@0
  2411
	if (![statusMessage length] &&
David@837
  2412
		(statusState.statusType == AIAwayStatusType) &&
David@837
  2413
		statusState.statusName &&
Matt@345
  2414
		(!statusID || ((strcmp(statusID, "away") == 0) && [self shouldSetStatusMessageForDefaultAwayState]))) {
David@0
  2415
		/* If we don't have a status message, and the status type is away for a non-default away such as "Do Not Disturb", and we're only setting
David@0
  2416
		 * a default away state becuse we don't know a better one for this service, get a default
David@0
  2417
		 * description of this away state. This allows, for example, an AIM user to set the "Do Not Disturb" type provided by her ICQ account
David@0
  2418
		 * and have the away message be set appropriately.
David@0
  2419
		 */
David@100
  2420
		statusMessage = [NSAttributedString stringWithString:[adium.statusController descriptionForStateOfStatus:statusState]];
David@0
  2421
	}
David@0
  2422
David@0
  2423
	BOOL isNowPlayingStatus = ([statusState specialStatusType] == AINowPlayingSpecialStatusType);
David@0
  2424
	if (isNowPlayingStatus && [statusMessage length]) {
David@0
  2425
		if ([self shouldAddMusicalNoteToNowPlayingStatus]) {
David@0
  2426
#define MUSICAL_NOTE_AND_SPACE [NSString stringWithUTF8String:"\xe2\x99\xab "]
David@0
  2427
			NSMutableAttributedString *temporaryStatusMessage;
David@0
  2428
			temporaryStatusMessage = [[[NSMutableAttributedString alloc] initWithString:MUSICAL_NOTE_AND_SPACE] autorelease];
David@0
  2429
			[temporaryStatusMessage appendAttributedString:statusMessage];
David@0
  2430
			
David@0
  2431
			statusMessage = temporaryStatusMessage;
David@0
  2432
		}
David@0
  2433
		
David@0
  2434
		if ([self shouldSetITMSLinkForNowPlayingStatus]) {
David@0
  2435
			//Grab the message's subtext, which is the song link if we're using the Current iTunes Track status
David@0
  2436
			NSString *itmsStoreLink	= [statusMessage attribute:@"AIMessageSubtext" atIndex:0 effectiveRange:NULL];
David@0
  2437
			if (itmsStoreLink) {
David@0
  2438
				[arguments setObject:itmsStoreLink
David@0
  2439
							  forKey:@"itmsurl"];
David@0
  2440
			}
David@0
  2441
		}
Evan@677
  2442
		
Evan@677
  2443
		NSDictionary *purpleSongInfoDictionary = [self purpleSongInfoDictionary];
Evan@677
  2444
		if (purpleSongInfoDictionary)
Evan@677
  2445
			[arguments addEntriesFromDictionary:purpleSongInfoDictionary];
David@0
  2446
	}
David@0
  2447
David@0
  2448
	//Encode the status message if we have one
David@0
  2449
	encodedStatusMessage = (statusMessage ? 
David@0
  2450
							[self encodedAttributedString:statusMessage
David@0
  2451
										   forStatusState:statusState]  :
David@0
  2452
							nil);
David@0
  2453
	if (encodedStatusMessage) {
David@0
  2454
		[arguments setObject:encodedStatusMessage
David@0
  2455
					  forKey:@"message"];
David@0
  2456
	}
David@0
  2457
David@0
  2458
	[self setStatusState:statusState
David@0
  2459
				statusID:statusID
David@0
  2460
				isActive:[NSNumber numberWithBool:YES] /* We're only using exclusive states for now... I hope.  */
David@0
  2461
			   arguments:arguments];
David@0
  2462
	
David@0
  2463
	[arguments release];
David@0
  2464
}
David@0
  2465
David@0
  2466
/*!
David@0
  2467
 * @brief Perform the actual setting of a state
David@0
  2468
 *
David@0
  2469
 * This is called by setStatusState.  It allows subclasses to perform any other behaviors, such as modifying a display
David@0
  2470
 * name, which are called for by the setting of the state; most of the processing has already been done, however, so
David@0
  2471
 * most subclasses will not need to implement this.
David@0
  2472
 *
David@0
  2473
 * @param statusState The AIStatus which is being set
David@0
  2474
 * @param statusID The Purple-sepcific statusID we are setting
David@0
  2475
 * @param isActive An NSNumber with a bool YES if we are activating (going to) the passed state, NO if we are deactivating (going away from) the passed state.
David@0
  2476
 * @param arguments Purple-specific arguments specified by the account. It must contain only NSString objects and keys.
David@0
  2477
 */
David@0
  2478
- (void)setStatusState:(AIStatus *)statusState statusID:(const char *)statusID isActive:(NSNumber *)isActive arguments:(NSMutableDictionary *)arguments
David@0
  2479
{
David@0
  2480
	[purpleAdapter setStatusID:statusID
David@0
  2481
				   isActive:isActive
David@0
  2482
				  arguments:arguments
David@0
  2483
				  onAccount:self];
David@0
  2484
}
David@0
  2485
David@0
  2486
//Set our idle (Pass nil for no idle)
David@0
  2487
- (void)setAccountIdleSinceTo:(NSDate *)idleSince
David@0
  2488
{
David@0
  2489
	[purpleAdapter setIdleSinceTo:idleSince onAccount:self];
David@0
  2490
	
David@0
  2491
	//We now should update our idle property
David@0
  2492
	[self setValue:([idleSince timeIntervalSinceNow] ? idleSince : nil)
David@0
  2493
				   forProperty:@"IdleSince"
David@0
  2494
				   notify:NotifyNow];
David@0
  2495
}
David@0
  2496
David@0
  2497
//Set the profile, then invoke the passed invocation to return control to the target/selector specified
David@0
  2498
//by a configurePurpleAccountNotifyingTarget:selector: call.
David@0
  2499
- (void)setAccountProfileTo:(NSAttributedString *)profile configurePurpleAccountContext:(NSInvocation *)inInvocation
David@0
  2500
{
David@0
  2501
	[self setAccountProfileTo:profile];
David@0
  2502
	
David@0
  2503
	[inInvocation invoke];
David@0
  2504
}
David@0
  2505
David@0
  2506
//Set our profile immediately on the purpleAdapter
David@0
  2507
- (void)setAccountProfileTo:(NSAttributedString *)profile
David@0
  2508
{
David@0
  2509
	if (!profile || ![[profile string] isEqualToString:[[self valueForProperty:@"TextProfile"] string]]) {
David@0
  2510
		NSString 	*profileHTML = nil;
David@0
  2511
		
David@0
  2512
		//Convert the profile to HTML, and pass it to libpurple
David@0
  2513
		if (profile) {
David@0
  2514
			profileHTML = [self encodedAttributedString:profile forListObject:nil];
David@0
  2515
		}
David@0
  2516
		
David@0
  2517
		[purpleAdapter setInfo:profileHTML onAccount:self];
David@0
  2518
		
David@0
  2519
		//We now have a profile
David@0
  2520
		[self setValue:profile forProperty:@"TextProfile" notify:NotifyNow];
David@0
  2521
	}
David@0
  2522
}
David@0
  2523
David@0
  2524
/*!
David@0
  2525
 * @brief Set our user image
David@0
  2526
 *
David@0
  2527
 * Pass nil for no image. This resizes and converts the image as needed for our protocol.
David@0
  2528
 * After setting it with purple, it sets it within Adium; if this is not called, the image will
David@0
  2529
 * show up neither locally nor remotely.
David@0
  2530
 */
Evan@202
  2531
- (void)setAccountUserImage:(NSImage *)image withData:(NSData *)originalData;
David@0
  2532
{
David@0
  2533
	if (account) {
Evan@202
  2534
		NSData		*imageData = originalData;
David@0
  2535
		NSSize		imageSize = (image ? [image size] : NSZeroSize);
David@0
  2536
		NSData		*buddyIconData = nil;
David@0
  2537
David@0
  2538
		/* Now pass libpurple the new icon. Check to be sure our image doesn't have an NSZeroSize size,
David@0
  2539
		 * which would indicate currupt data */
David@0
  2540
		if (image && !NSEqualSizes(NSZeroSize, imageSize)) {
zacw@2329
  2541
			PurplePluginProtocolInfo  *prpl_info = self.protocolInfo;
David@0
  2542
David@0
  2543
			AILog(@"Original image of size %f %f",imageSize.width,imageSize.height);
David@0
  2544
David@0
  2545
			if (prpl_info && (prpl_info->icon_spec.format)) {
David@0
  2546
				BOOL		smallEnough, prplScales;
sholt@2693
  2547
				NSUInteger	i;
David@0
  2548
				
David@0
  2549
				/* We need to scale it down if:
David@0
  2550
				 *	1) The prpl needs to scale before it sends to the server or other buddies AND
David@0
  2551
				 *	2) The image is larger than the maximum size allowed by the protocol
David@0
  2552
				 * We ignore the minimum required size, as scaling up just leads to pixellated images.
David@0
  2553
				 */
David@0
  2554
				smallEnough =  (prpl_info->icon_spec.max_width >= imageSize.width &&
David@0
  2555
								prpl_info->icon_spec.max_height >= imageSize.height);
David@0
  2556
					
David@0
  2557
				prplScales = (prpl_info->icon_spec.scale_rules & PURPLE_ICON_SCALE_SEND) || (prpl_info->icon_spec.scale_rules & PURPLE_ICON_SCALE_DISPLAY);
David@0
  2558
David@0
  2559
				if (prplScales && !smallEnough) {
sholt@2693
  2560
					gint width = (gint)imageSize.width;
sholt@2693
  2561
					gint height = (gint)imageSize.height;
David@0
  2562
					
David@0
  2563
					purple_buddy_icon_get_scale_size(&prpl_info->icon_spec, &width, &height);
David@0
  2564
					//Determine the scaled size.  If it's too big, scale to the largest permissable size
David@0
  2565
					image = [image imageByScalingToSize:NSMakeSize(width, height)];
David@0
  2566
David@0
  2567
					/* Our original data is no longer valid, since we had to scale to a different size */
Evan@202
  2568
					imageData = nil;
David@0
  2569
					AILog(@"%@: Scaled image to size %@", self, NSStringFromSize([image size]));
David@0
  2570
				}
David@0
  2571
David@0
  2572
				if (!buddyIconData) {
David@0
  2573
					char		**prpl_formats =  g_strsplit(prpl_info->icon_spec.format,",",0);
David@0
  2574
David@0
  2575
					//Look for gif first if the image is animated
David@0
  2576
					NSImageRep	*imageRep = [image bestRepresentationForDevice:nil] ;
David@0
  2577
					if ([imageRep isKindOfClass:[NSBitmapImageRep class]] &&
sholt@2634
  2578
						[[(NSBitmapImageRep *)imageRep valueForProperty:NSImageFrameCount] integerValue] > 1) {
David@0
  2579
						
David@0
  2580
						for (i = 0; prpl_formats[i]; i++) {
David@0
  2581
							if (strcmp(prpl_formats[i],"gif") == 0) {
Evan@202
  2582
								/* Try to use our original data.  If we had to scale, imageData will have been set
David@0
  2583
								* to nil and we'll continue below to convert the image. */
David@0
  2584
								AILog(@"l33t script kiddie animated GIF!!111");
David@0
  2585
								
Evan@202
  2586
								buddyIconData = imageData;
David@0
  2587
								if (buddyIconData)
David@0
  2588
									break;
David@0
  2589
							}
David@0
  2590
						}
David@0
  2591
					}
David@0
  2592
					
David@0
  2593
					if (!buddyIconData) {
David@0
  2594
						for (i = 0; prpl_formats[i]; i++) {
David@0
  2595
							if (strcmp(prpl_formats[i],"png") == 0) {
David@0
  2596
								buddyIconData = [image PNGRepresentation];
David@0
  2597
								if (buddyIconData)
David@0
  2598
									break;
David@0
  2599
								
David@0
  2600
							} else if ((strcmp(prpl_formats[i],"jpeg") == 0) || (strcmp(prpl_formats[i],"jpg") == 0)) {								
David@0
  2601
								buddyIconData = [image JPEGRepresentationWithCompressionFactor:1.0];
David@0
  2602
								if (buddyIconData)
David@0
  2603
									break;
David@0
  2604
								
David@0
  2605
							} else if ((strcmp(prpl_formats[i],"tiff") == 0) || (strcmp(prpl_formats[i],"tif") == 0)) {
David@0
  2606
								buddyIconData = [image TIFFRepresentation];
David@0
  2607
								if (buddyIconData)
David@0
  2608
									break;
David@0
  2609
								
David@0
  2610
							} else if (strcmp(prpl_formats[i],"gif") == 0) {
David@0
  2611
								buddyIconData = [image GIFRepresentation];
David@0
  2612
								if (buddyIconData)
David@0
  2613
									break;
David@0
  2614
								
David@0
  2615
							} else if (strcmp(prpl_formats[i],"bmp") == 0) {
David@0
  2616
								buddyIconData = [image BMPRepresentation];
David@0
  2617
								if (buddyIconData)
David@0
  2618
									break;
David@0
  2619
								
David@0
  2620
							}						
David@0
  2621
						}
David@0
  2622
						
David@0
  2623
						size_t maxSize = prpl_info->icon_spec.max_filesize;
David@0
  2624
						if (maxSize > 0 && ([buddyIconData length] > maxSize)) {
David@0
  2625
							AILog(@"Image %i is larger than %i!",[buddyIconData length],maxSize);
David@0
  2626
							for (i = 0; prpl_formats[i]; i++) {
David@0
  2627
								if ((strcmp(prpl_formats[i],"jpeg") == 0) || (strcmp(prpl_formats[i],"jpg") == 0)) {
Evan@638
  2628
									buddyIconData = [image JPEGRepresentationWithMaximumByteSize:maxSize];
David@0
  2629
								}
David@0
  2630
							}
David@0
  2631
						}
David@0
  2632
					}	
David@0
  2633
					//Cleanup
David@0
  2634
					g_strfreev(prpl_formats);
David@0
  2635
				}
David@0
  2636
			}
David@0
  2637
		}
David@0
  2638
David@0
  2639
		AILogWithSignature(@"%@ setting icon data of length %i", self, [buddyIconData length]);
David@0
  2640
		[purpleAdapter setBuddyIcon:buddyIconData onAccount:self];
David@0
  2641
	}
David@0
  2642
	
Evan@202
  2643
	[super setAccountUserImage:image withData:originalData];
David@0
  2644
}
David@0
  2645
David@0
  2646
#pragma mark Group Chat
David@0
  2647
- (BOOL)inviteContact:(AIListContact *)inContact toChat:(AIChat *)inChat withMessage:(NSString *)inviteMessage
David@0
  2648
{
David@0
  2649
	[purpleAdapter inviteContact:inContact toChat:inChat withMessage:inviteMessage];
David@0
  2650
	
David@0
  2651
	return YES;
David@0
  2652
}
David@0
  2653
David@0
  2654
#pragma mark Buddy Menu Items
David@0
  2655
//Action of a dynamically-generated contact menu item
David@0
  2656
- (void)performContactMenuAction:(NSMenuItem *)sender
David@0
  2657
{
David@0
  2658
	NSDictionary		*dict = [sender representedObject];
David@0
  2659
	
David@0
  2660
	[purpleAdapter performContactMenuActionFromDict:dict forAccount:self];
David@0
  2661
}
David@0
  2662
David@0
  2663
/*!
David@0
  2664
 * @brief Utility method when generating buddy-specific menu items
David@0
  2665
 *
David@0
  2666
 * Adds the menu item for act to a growing array of NSMenuItems.  If act has children (a submenu), this method is used recursively
David@0
  2667
 * to generate the submenu containing each child menu item.
David@0
  2668
 */
David@0
  2669
- (void)addMenuItemForMenuAction:(PurpleMenuAction *)act forListContact:(AIListContact *)inContact purpleBuddy:(PurpleBuddy *)buddy toArray:(NSMutableArray *)menuItemArray withServiceIcon:(NSImage *)serviceIcon
David@0
  2670
{
David@0
  2671
	NSDictionary	*dict;
David@0
  2672
	NSMenuItem		*menuItem;
David@0
  2673
	NSString		*title;
David@0
  2674
				
David@0
  2675
	//If titleForContactMenuLabel:forContact: returns nil, we don't add the menuItem
David@0
  2676
	if (act &&
David@0
  2677
		act->label &&
David@0
  2678
		(title = [self titleForContactMenuLabel:act->label
David@0
  2679
									 forContact:inContact])) { 
David@0
  2680
		menuItem = [[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:title
David@0
  2681
																		target:self
David@0
  2682
																		action:@selector(performContactMenuAction:)
David@0
  2683
																 keyEquivalent:@""];
David@0
  2684
		[menuItem setImage:serviceIcon];
David@0
  2685
David@0
  2686
		if (act->data) {
David@0
  2687
			dict = [NSDictionary dictionaryWithObjectsAndKeys:
David@0
  2688
				[NSValue valueWithPointer:act->callback],@"PurpleMenuActionCallback",
David@0
  2689
				/* act->data may be freed by purple_menu_action_free() before we use it, I'm afraid... */
David@0
  2690
				[NSValue valueWithPointer:act->data],@"PurpleMenuActionData",
David@0
  2691
				[NSValue valueWithPointer:buddy],@"PurpleBuddy",
David@0
  2692
				nil];
David@0
  2693
		} else {
David@0
  2694
			dict = [NSDictionary dictionaryWithObjectsAndKeys:
David@0
  2695
				[NSValue valueWithPointer:act->callback],@"PurpleMenuActionCallback",
David@0
  2696
				[NSValue valueWithPointer:buddy],@"PurpleBuddy",
David@0
  2697
				nil];			
David@0
  2698
		}
David@0
  2699
		
David@0
  2700
		[menuItem setRepresentedObject:dict];
David@0
  2701
		
David@0
  2702
		//If there is a submenu, generate and set it
David@0
  2703
		if (act->children) {
David@0
  2704
			NSMutableArray	*childrenArray = [NSMutableArray array];
David@0
  2705
			GList			*l, *ll;
David@0
  2706
			//Add a NSMenuItem for each child
David@0
  2707
			for (l = ll = act->children; l; l = l->next) {
David@0
  2708
				[self addMenuItemForMenuAction:(PurpleMenuAction *)l->data
David@0
  2709
								forListContact:inContact
David@0
  2710
									 purpleBuddy:buddy
David@0
  2711
									   toArray:childrenArray
David@0
  2712
							   withServiceIcon:serviceIcon];
David@0
  2713
			}
David@0
  2714
			g_list_free(act->children);
David@0
  2715
David@0
  2716
			if ([childrenArray count]) {
David@0
  2717
				NSMenu		 *submenu = [[NSMenu alloc] init];
David@0
  2718
				
David@82
  2719
				for (NSMenuItem *childMenuItem in childrenArray) {
David@0
  2720
					[submenu addItem:childMenuItem];
David@0
  2721
				}
David@0
  2722
				
David@0
  2723
				[menuItem setSubmenu:submenu];
David@0
  2724
				[submenu release];
David@0
  2725
			}
David@0
  2726
		}
David@0
  2727
David@0
  2728
		[menuItemArray addObject:menuItem];
David@0
  2729
		[menuItem release];
David@0
  2730
	}
David@0
  2731
David@0
  2732
	purple_menu_action_free(act);
David@0
  2733
}
David@0
  2734
David@0
  2735
//Returns an array of menuItems specific for this contact based on its account and potentially status
David@0
  2736
- (NSArray *)menuItemsForContact:(AIListContact *)inContact
David@0
  2737
{
David@0
  2738
	NSMutableArray			*menuItemArray = nil;
David@0
  2739
David@0
  2740
	if (account && purple_account_is_connected(account)) {
zacw@2329
  2741
		PurplePluginProtocolInfo  *prpl_info = self.protocolInfo;
David@0
  2742
		GList					*l, *ll;
David@0
  2743
		PurpleBuddy				*buddy;
David@0
  2744
		
David@0
  2745
		//Find the PurpleBuddy
David@721
  2746
		buddy = purple_find_buddy(account, [inContact.UID UTF8String]);
David@0
  2747
		
David@0
  2748
		if (prpl_info && prpl_info->blist_node_menu && buddy) {
David@427
  2749
			NSImage	*serviceIcon = [AIServiceIcons serviceIconForService:self.service
David@0
  2750
																	type:AIServiceIconSmall
David@0
  2751
															   direction:AIIconNormal];
David@0
  2752
			
David@0
  2753
			menuItemArray = [NSMutableArray array];
David@0
  2754
David@0
  2755
			//Add a NSMenuItem for each node action specified by the prpl
David@0
  2756
			for (l = ll = prpl_info->blist_node_menu((PurpleBlistNode *)buddy); l; l = l->next) {
David@0
  2757
				[self addMenuItemForMenuAction:(PurpleMenuAction *)l->data
David@0
  2758
								forListContact:inContact
David@0
  2759
									 purpleBuddy:buddy
David@0
  2760
									   toArray:menuItemArray
David@0
  2761
							   withServiceIcon:serviceIcon];
David@0
  2762
			}
David@0
  2763
			g_list_free(ll);
David@0
  2764
			
David@0
  2765
			//Don't return an empty array
David@0
  2766
			if (![menuItemArray count]) menuItemArray = nil;
David@0
  2767
		}
David@0
  2768
	}
David@0
  2769
	
David@0
  2770
	return menuItemArray;
David@0
  2771
}
David@0
  2772
David@0
  2773
//Subclasses may override to provide a localized label and/or prevent a specified label from being shown
David@0
  2774
- (NSString *)titleForContactMenuLabel:(const char *)label forContact:(AIListContact *)inContact
David@0
  2775
{
David@0
  2776
	return [NSString stringWithUTF8String:label];
David@0
  2777
}
David@0
  2778
David@0
  2779
/*!
David@0
  2780
* @brief Menu items for the account's actions
David@0
  2781
 *
David@0
  2782
 * Returns an array of menu items for account-specific actions.  This is the best place to add protocol-specific
David@0
  2783
 * actions that aren't otherwise supported by Adium.  It will only be queried if the account is online.
David@0
  2784
 * @return NSArray of NSMenuItem instances for this account
David@0
  2785
 */
David@0
  2786
- (NSArray *)accountActionMenuItems
David@0
  2787
{
David@0
  2788
	NSMutableArray			*menuItemArray = nil;
David@0
  2789
	
David@0
  2790
	if (account && purple_account_is_connected(account)) {
David@0
  2791
		PurplePlugin *plugin = purple_account_get_connection(account)->prpl;
David@0
  2792
		
David@0
  2793
		if (PURPLE_PLUGIN_HAS_ACTIONS(plugin)) {
David@0
  2794
			GList	*l, *actions;
David@0
  2795
			
David@0
  2796
			actions = PURPLE_PLUGIN_ACTIONS(plugin, purple_account_get_connection(account));
David@0
  2797
David@0
  2798
			//Avoid adding separators between nonexistant items (i.e. items which Purple shows but we don't)
David@0
  2799
			BOOL	addedAnAction = NO;
David@0
  2800
			for (l = actions; l; l = l->next) {
David@0
  2801
				
David@0
  2802
				if (l->data) {
David@0
  2803
					PurplePluginAction	*action;
David@0
  2804
					NSDictionary		*dict;
David@0
  2805
					NSMenuItem			*menuItem;
David@0
  2806
					NSString			*title;
David@0
  2807
					
David@0
  2808
					action = (PurplePluginAction *) l->data;
David@0
  2809
					
David@0
  2810
					//If titleForAccountActionMenuLabel: returns nil, we don't add the menuItem
David@0
  2811
					if (action &&
David@0
  2812
						action->label &&
David@0
  2813
						(title = [self titleForAccountActionMenuLabel:action->label])) {
David@0
  2814
David@0
  2815
						action->plugin = plugin;
David@0
  2816
						action->context = purple_account_get_connection(account);
David@0
  2817
David@0
  2818
						menuItem = [[[NSMenuItem allocWithZone:[NSMenu menuZone]] initWithTitle:title
David@0
  2819
																						 target:self
David@0
  2820
																						 action:@selector(performAccountMenuAction:)
David@0
  2821
																				  keyEquivalent:@""] autorelease];
David@0
  2822
						dict = [NSDictionary dictionaryWithObjectsAndKeys:
David@0
  2823
							[NSValue valueWithPointer:action->callback], @"PurplePluginActionCallback",
David@0
  2824
							[NSValue valueWithPointer:action->user_data], @"PurplePluginActionCallbackUserData",
David@0
  2825
							nil];
David@0
  2826
						
David@0
  2827
						[menuItem setRepresentedObject:dict];
David@0
  2828
						
David@0
  2829
						if (!menuItemArray) menuItemArray = [NSMutableArray array];
David@0
  2830
						
David@0
  2831
						[menuItemArray addObject:menuItem];
David@0
  2832
						addedAnAction = YES;
David@0
  2833
					} 
David@0
  2834
					
David@0
  2835
					purple_plugin_action_free(action);
David@0
  2836
					
David@0
  2837
				} else {
David@0
  2838
					if (addedAnAction) {
David@0
  2839
						[menuItemArray addObject:[NSMenuItem separatorItem]];
David@0
  2840
						addedAnAction = NO;
David@0
  2841
					}
David@0
  2842
				}
David@0
  2843
			} /* end for */
David@0
  2844
			
David@0
  2845
			g_list_free(actions);
David@0
  2846
		}
David@0
  2847
	}
zacw@998
  2848
	
zacw@998
  2849
#ifdef HAVE_CDSA
zacw@998
  2850
	if([self encrypted] && [self secureConnection]) {
zacw@998
  2851
		if (menuItemArray.count) {
zacw@998
  2852
			[menuItemArray addObject:[NSMenuItem separatorItem]];
zacw@998
  2853
		}
zacw@998
  2854
		
zacw@998
  2855
		NSMenuItem *showCertificateMenuItem = [[[NSMenuItem alloc] initWithTitle:AILocalizedString(@"Show Server Certificate",nil)
zacw@998
  2856
																		 target:self
zacw@998
  2857
																		 action:@selector(showServerCertificate) 
zacw@998
  2858
																  keyEquivalent:@""] autorelease];
zacw@998
  2859
		
zacw@998
  2860
		[menuItemArray addObject:showCertificateMenuItem];
zacw@998
  2861
	}
zacw@998
  2862
#endif
David@0
  2863
David@0
  2864
	return menuItemArray;
David@0
  2865
}
David@0
  2866
zacw@998
  2867
#ifdef HAVE_CDSA
zacw@998
  2868
/*!
zacw@998
  2869
 * @brief Shows the SSL certificate for the connection.
zacw@998
  2870
 */
zacw@998
  2871
- (void)showServerCertificate
zacw@998
  2872
{
zacw@998
  2873
	CFArrayRef certificates = [[self purpleAdapter] copyServerCertificates:[self secureConnection]];
zacw@998
  2874
	
zacw@998
  2875
	[AIPurpleCertificateViewer displayCertificateChain:certificates forAccount:self];
zacw@998
  2876
	
zacw@998
  2877
	CFRelease(certificates);
zacw@998
  2878
}
zacw@998
  2879
#endif
zacw@998
  2880
David@0
  2881
//Action of a dynamically-generated contact menu item
David@0
  2882
- (void)performAccountMenuAction:(NSMenuItem *)sender
David@0
  2883
{
David@0
  2884
	NSDictionary		*dict = [sender representedObject];
David@0
  2885
David@0
  2886
	[purpleAdapter performAccountMenuActionFromDict:dict forAccount:self];
David@0
  2887
}
David@0
  2888
David@0
  2889
//Subclasses may override to provide a localized label and/or prevent a specified label from being shown
David@0
  2890
- (NSString *)titleForAccountActionMenuLabel:(const char *)label
David@0
  2891
{
David@0
  2892
	if ((strcmp(label, _("Change Password...")) == 0) || (strcmp(label, _("Change Password")) == 0)) {
David@0
  2893
		return [[NSString stringWithFormat:AILocalizedString(@"Change Password", "Menu item title for changing the password of an account")] stringByAppendingEllipsis];
David@0
  2894
	} else {
David@0
  2895
		return [NSString stringWithUTF8String:label];
David@0
  2896
	}
David@0
  2897
}
David@0
  2898
David@0
  2899
/********************************/
David@0
  2900
/* AIAccount subclassed methods */
David@0
  2901
/********************************/
David@0
  2902
#pragma mark AIAccount Subclassed Methods
David@0
  2903
- (void)initAccount
David@0
  2904
{
David@721
  2905
	NSDictionary	*defaults = [NSDictionary dictionaryNamed:[NSString stringWithFormat:@"PurpleDefaults%@",self.service.serviceID]
David@0
  2906
													 forClass:[self class]];
David@0
  2907
	
David@0
  2908
	if (defaults) {
David@95
  2909
		[adium.preferenceController registerDefaults:defaults
David@0
  2910
											  forGroup:GROUP_ACCOUNT_STATUS
David@0
  2911
												object:self];
David@0
  2912
	} else {
David@721
  2913
		AILog(@"Failed to load defaults for %@",[NSString stringWithFormat:@"PurpleDefaults%@",self.service.serviceID]);
David@0
  2914
	}
David@0
  2915
	
David@0
  2916
	//Defaults
David@0
  2917
	[self setLastDisconnectionError:nil];
David@0
  2918
	
David@0
  2919
	permittedContactsArray = [[NSMutableArray alloc] init];
David@0
  2920
	deniedContactsArray = [[NSMutableArray alloc] init];
David@0
  2921
David@0
  2922
	//We will create a purpleAccount the first time we attempt to connect
David@0
  2923
	account = NULL;
David@0
  2924
David@0
  2925
	//Observe preferences changes
David@95
  2926
	[adium.preferenceController registerPreferenceObserver:self forGroup:PREF_GROUP_ALIASES];
David@95
  2927
	[adium.preferenceController registerPreferenceObserver:self forGroup:PREF_GROUP_DUAL_WINDOW_INTERFACE];
David@0
  2928
}
David@0
  2929
David@0
  2930
- (BOOL)allowAccountUnregistrationIfSupportedByLibpurple
David@0
  2931
{
David@0
  2932
	return YES;
David@0
  2933
}
David@0
  2934
David@0
  2935
/*!
David@0
  2936
 * @brief The account will be deleted, we should ask the user for confirmation. If the prpl supports it, we can also remove
David@0
  2937
 * the account from the server (if the user wants us to do that)
David@0
  2938
 */
David@0
  2939
- (NSAlert*)alertForAccountDeletion
David@0
  2940
{
zacw@2329
  2941
	PurplePluginProtocolInfo *prpl_info = self.protocolInfo;
David@0
  2942
David@0
  2943
	//Ensure libpurple has been loaded, since we need to know whether we can unregister this account
David@0
  2944
	[self purpleAdapter];
David@0
  2945
zacw@2329
  2946
	if (prpl_info && 
zacw@2329
  2947
		prpl_info->unregister_user &&
David@0
  2948
		[self allowAccountUnregistrationIfSupportedByLibpurple]) {
David@0
  2949
		return [NSAlert alertWithMessageText:AILocalizedString(@"Delete Account",nil)
David@0
  2950
							   defaultButton:AILocalizedString(@"Delete",nil)
David@0
  2951
							 alternateButton:AILocalizedString(@"Cancel",nil)
David@0
  2952
								 otherButton:AILocalizedString(@"Delete & Unregister",nil)
David@427
  2953
				   informativeTextWithFormat:AILocalizedString(@"Delete the account %@? You can also optionally unregister the account on the server if possible.",nil), ([self.formattedUID length] ? self.formattedUID : NEW_ACCOUNT_DISPLAY_TEXT)];		
David@0
  2954
David@0
  2955
	} else {
David@0
  2956
		return [super alertForAccountDeletion];
David@0
  2957
	}
David@0
  2958
}
David@0
  2959
sholt@2693
  2960
- (void)alertForAccountDeletion:(id<AIAccountControllerRemoveConfirmationDialog>)dialog didReturn:(NSInteger)returnCode
David@0
  2961
{
zacw@2329
  2962
	PurplePluginProtocolInfo *prpl_info = self.protocolInfo;
David@0
  2963
	
zacw@2329
  2964
	if (prpl_info && 
zacw@2329
  2965
		prpl_info->unregister_user) {
David@0
  2966
		switch (returnCode) {
David@0
  2967
			case NSAlertOtherReturn:
David@0
  2968
				// delete & unregister
David@837
  2969
				if (self.online)
David@0
  2970
					[self unregister];
David@0
  2971
				else {
David@0
  2972
					unregisterAfterConnecting = YES;
David@0
  2973
					[self setShouldBeOnline:YES];
David@0
  2974
				}
David@0
  2975
			
David@0
  2976
				// further progress happens in -unregisteredAccount:
David@0
  2977
				break;
David@0
  2978
			case NSAlertDefaultReturn:
David@0
  2979
				// delete without unregistering
David@0
  2980
				[self performDelete];
David@0
  2981
				break;
David@0
  2982
			default:
David@0
  2983
				// cancel
David@0
  2984
				break;
David@0
  2985
		}
David@0
  2986
		
David@0
  2987
	} else {
David@0
  2988
		switch(returnCode) {
David@0
  2989
			case NSAlertDefaultReturn:
David@0
  2990
				[self performDelete];
David@0
  2991
				break;
David@0
  2992
			default:
David@0
  2993
				// cancel
David@0
  2994
				break;
David@0
  2995
		}
David@0
  2996
	}
David@0
  2997
	
David@0
  2998
	//Release dialog as required by AIAccount's documentation since we didn't call super's implementation.
David@0
  2999
	[dialog release];
David@0
  3000
}
David@0
  3001
David@0
  3002
- (void)unregisteredAccount:(BOOL)success {
David@0
  3003
	if (success) {
David@0
  3004
		/* We're not going to be online, but we *must* not disconnect within this run loop,
David@0
  3005
		 * as libpurple may still have Things To Do with the connection and it has no concept of reference
David@0
  3006
		 * counting with which to survive the disconnection. Performing a deletion would set us offline,
David@0
  3007
		 * so wait until the next run loop.
David@0
  3008
		 */
David@0
  3009
		[self performSelector:@selector(performDelete)
David@0
  3010
				   withObject:nil
David@0
  3011
				   afterDelay:0];
David@0
  3012
	}
David@0
  3013
}
David@0
  3014
David@0
  3015
/*!
David@0
  3016
 * @brief The account's UID changed
David@0
  3017
 */
David@0
  3018
- (void)didChangeUID
David@0
  3019
{
David@0
  3020
	//Only need to take action if we have a created PurpleAccount already
David@0
  3021
	if (account != NULL) {
David@0
  3022
		//Remove our current account
David@0
  3023
		[[self purpleAdapter] removeAdiumAccount:self];
David@0
  3024
		
David@0
  3025
		//Clear the reference to the PurpleAccount... it'll be created when needed
David@0
  3026
		account = NULL;
David@0
  3027
	}
David@0
  3028
}
David@0
  3029
David@0
  3030
/*!
David@0
  3031
 * @brief The account will be deleted; it has already been told to disconnect
David@0
  3032
 */
David@0
  3033
- (void)willBeDeleted
David@0
  3034
{	
David@837
  3035
	if (self.online) {
David@0
  3036
		//Wait until we are finished disconnecting before removing ourselves from libpurple.
David@0
  3037
		deletePurpleAccountAfterDisconnecting = TRUE;
David@0
  3038
David@0
  3039
	} else {
David@0
  3040
		[[self purpleAdapter] removeAdiumAccount:self];
David@0
  3041
	}
David@0
  3042
David@0
  3043
	[super willBeDeleted];
David@0
  3044
}
David@0
  3045
David@0
  3046
- (void)dealloc
David@0
  3047
{	
David@95
  3048
	[adium.preferenceController unregisterPreferenceObserver:self];
David@0
  3049
David@0
  3050
	[permittedContactsArray release];
David@0
  3051
	[deniedContactsArray release];
David@0
  3052
	
David@0
  3053
    [super dealloc];
David@0
  3054
}
David@0
  3055
David@0
  3056
- (NSString *)unknownGroupName {
David@0
  3057
    return (@"Unknown");
David@0
  3058
}
David@0
  3059
David@0
  3060
- (NSDictionary *)defaultProperties { return [NSDictionary dictionary]; }
David@0
  3061
David@0
  3062
- (NSString *)encodedAttributedString:(NSAttributedString *)inAttributedString forStatusState:(AIStatus *)statusState
David@0
  3063
{
David@0
  3064
	return [self encodedAttributedString:inAttributedString forListObject:nil];	
David@0
  3065
}
David@0
  3066
David@0
  3067
- (void)preferencesChangedForGroup:(NSString *)group key:(NSString *)key
David@0
  3068
							object:(AIListObject *)object preferenceDict:(NSDictionary *)prefDict firstTime:(BOOL)firstTime
David@0
  3069
{
David@0
  3070
	[super preferencesChangedForGroup:group key:key object:object preferenceDict:prefDict firstTime:firstTime];
David@0
  3071
David@0
  3072
	if ([group isEqualToString:PREF_GROUP_ALIASES]) {
David@0
  3073
		//If the notification object is a listContact belonging to this account, update the serverside information
David@0
  3074
		if ((account != nil) && 
David@0
  3075
			([self shouldSetAliasesServerside]) &&
David@0
  3076
			([key isEqualToString:@"Alias"])) {
David@0
  3077
David@0
  3078
			NSString *alias = [object preferenceForKey:@"Alias"
David@0
  3079
												 group:PREF_GROUP_ALIASES 
David@740
  3080
								];
David@0
  3081
David@0
  3082
			if ([object isKindOfClass:[AIMetaContact class]]) {
David@82
  3083
				for(AIListContact *containedListContact in (AIMetaContact *)object) {
David@837
  3084
					if (containedListContact.account == self) {
David@837
  3085
						[purpleAdapter setAlias:alias forUID:containedListContact.UID onAccount:self];
David@0
  3086
					}
David@0
  3087
				}
David@0
  3088
				
David@0
  3089
			} else if ([object isKindOfClass:[AIListContact class]]) {
David@0
  3090
				if ([(AIListContact *)object account] == self) {
David@837
  3091
					[purpleAdapter setAlias:alias forUID:object.UID onAccount:self];
David@0
  3092
				}
David@0
  3093
			}
David@0
  3094
		}
Evan@677
  3095
	} else if ([group isEqualToString:PREF_GROUP_DUAL_WINDOW_INTERFACE]) {
David@0
  3096
		openPsychicChats = [[prefDict objectForKey:KEY_PSYCHIC] boolValue];
Evan@677
  3097
Evan@677
  3098
	} else if ([group isEqualToString:GROUP_ACCOUNT_STATUS]) {
Evan@677
  3099
		BOOL oldNowPlaying = shouldIncludeNowPlayingInformationInAllStatuses;
Evan@677
  3100
		
Evan@677
  3101
		shouldIncludeNowPlayingInformationInAllStatuses = [[self preferenceForKey:KEY_BROADCAST_MUSIC_INFO group:GROUP_ACCOUNT_STATUS] boolValue];
Evan@677
  3102
Evan@677
  3103
		if (oldNowPlaying && !shouldIncludeNowPlayingInformationInAllStatuses) {
Evan@677
  3104
			/* Clear any existing song info immediately if we're no longer supposed to broadcast it */
Evan@677
  3105
			[purpleAdapter setSongInformation:nil onAccount:self];
Evan@677
  3106
		}
David@0
  3107
	}
David@0
  3108
}
David@0
  3109
zacw@2318
  3110
/*!
zacw@2318
  3111
 * @brief When the account is edited, update our libpurple preferences.
zacw@2318
  3112
 */
zacw@2318
  3113
- (void)accountEdited
zacw@2318
  3114
{
zacw@2318
  3115
	// We only need to re-configure if we're online or connecting. If we're offline, our next connect will do this.
zacw@2318
  3116
	if (self.online || [self boolValueForProperty:@"Connecting"]) {
zacw@2318
  3117
		AILog(@"Re-configuring purple account due to preference changes.");
zacw@2318
  3118
		[self configurePurpleAccount];
zacw@2318
  3119
	}
zacw@2318
  3120
}
zacw@2318
  3121
David@0
  3122
#pragma mark Actions for chats
David@0
  3123
David@0
  3124
/***************************/
David@0
  3125
/* Account private methods */
David@0
  3126
/***************************/
David@0
  3127
#pragma mark Private
David@0
  3128
- (void)setTypingFlagOfChat:(AIChat *)chat to:(NSNumber *)typingStateNumber
David@0
  3129
{
zacw@2081
  3130
	NSAssert(!chat.isGroupChat, @"Chat cannot be a group chat for typing.");
zacw@2081
  3131
	
David@0
  3132
    AITypingState currentTypingState = [chat integerValueForProperty:KEY_TYPING];
sholt@2693
  3133
	AITypingState newTypingState = [typingStateNumber integerValue];
David@0
  3134
	
David@0
  3135
    if (currentTypingState != newTypingState) {
David@0
  3136
		if (newTypingState == AITyping && openPsychicChats && ![chat isOpen]) {
David@100
  3137
			[adium.interfaceController openChat:chat];
David@0
  3138
			
David@0
  3139
			/*
David@0
  3140
			 * Use the Libpurple "psychic" tagline. If this is found to be confusing, we should switch to your own version.
David@0
  3141
			 * The upside of using theirs is that clever gimmicky translations already exist.
David@0
  3142
			 */
David@0
  3143
			NSMutableString *forceString = [[NSString stringWithUTF8String:_("You feel a disturbance in the force...")] mutableCopy];
David@0
  3144
			[forceString replaceOccurrencesOfString:@"..."
David@0
  3145
										 withString:[NSString ellipsis]
David@0
  3146
											options:NSLiteralSearch];
zacw@1305
  3147
			AIContentEvent *statusMessage = [AIContentEvent eventInChat:chat
zacw@1305
  3148
															 withSource:chat.listObject
zacw@1305
  3149
															destination:self
zacw@1305
  3150
																   date:[NSDate date]
zacw@1305
  3151
																message:[NSAttributedString stringWithString:forceString]
zacw@1305
  3152
															   withType:@"psychic"];
zacw@1305
  3153
			
zacw@1305
  3154
			// Don't log the psychic message.
zacw@1305
  3155
			statusMessage.postProcessContent = NO;
zacw@1305
  3156
			
David@0
  3157
			[forceString release];
David@0
  3158
David@95
  3159
			[adium.contentController receiveContentObject:statusMessage];
David@0
  3160
		}
David@0
  3161
		
David@0
  3162
		[chat setValue:(newTypingState ? typingStateNumber : nil)
David@0
  3163
					   forProperty:KEY_TYPING
David@0
  3164
					   notify:NotifyNow];
David@0
  3165
    }
David@0
  3166
}
David@0
  3167
David@0
  3168
- (NSNumber *)shouldCheckMail
David@0
  3169
{
David@0
  3170
	return [self preferenceForKey:KEY_ACCOUNT_CHECK_MAIL group:GROUP_ACCOUNT_STATUS];
David@0
  3171
}
David@0
  3172
David@0
  3173
- (BOOL)shouldSetAliasesServerside
David@0
  3174
{
David@0
  3175
	return NO;
David@0
  3176
}
David@0
  3177
David@0
  3178
@end