Bug Summary

File:Plugins/Purple Service/SLPurpleCocoaAdapter.m
Location:line 1139, column 6
Description:dead store

Annotated Source Code

1/*
2 * Adium is the legal property of its developers, whose names are listed in the copyright file included
3 * with this source distribution.
4 *
5 * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
6 * General Public License as published by the Free Software Foundation; either version 2 of the License,
7 * or (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
10 * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
11 * Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along with this program; if not,
14 * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
15 */
16
17#import <AdiumLibpurple/SLPurpleCocoaAdapter.h>
18#import "CBPurpleAccount.h"
19#import "CBPurpleServicePlugin.h"
20#import "adiumPurpleCore.h"
21#import "adiumPurpleEventloop.h"
22
23#import <Adium/AIAccountControllerProtocol.h>
24#import <Adium/AIInterfaceControllerProtocol.h>
25#import <Adium/AILoginControllerProtocol.h>
26#import <AIUtilities/AIObjectAdditions.h>
27#import <Adium/AIAccount.h>
28#import <Adium/AICorePluginLoader.h>
29#import <Adium/AIService.h>
30#import <Adium/AIChat.h>
31#import <Adium/AIContentTyping.h>
32#import <Adium/AIHTMLDecoder.h>
33#import <Adium/AIListContact.h>
34#import <Adium/AIUserIcons.h>
35#import <AIUtilities/AIImageAdditions.h>
36
37#import <CoreFoundation/CFRunLoop.h>
38#import <CoreFoundation/CFSocket.h>
39#include <libpurple/libpurple.h>
40#include <glib.h>
41#include <stdlib.h>
42
43#import "ESPurpleAIMAccount.h"
44#import "CBPurpleOscarAccount.h"
45
46#import "ESiTunesPlugin.h"
47
48#import "adiumPurpleAccounts.h"
49
50//Purple slash command interface
51#include <libpurple/cmds.h>
52
53#include "libpurple_extensions/oscar-adium.h"
54
55@interface SLPurpleCocoaAdapter (PRIVATE)
56- (void)initLibPurple;
57- (BOOL)attemptPurpleCommandOnMessage:(NSString *)originalMessage fromAccount:(AIAccount *)sourceAccount inChat:(AIChat *)chat;
58@end
59
60/*
61 * A pointer to the single instance of this class active in the application.
62 * The purple callbacks need to be C functions with specific prototypes, so they
63 * can't be ObjC methods. The ObjC callbacks do need to be ObjC methods. This
64 * allows the C ones to call the ObjC ones.
65 **/
66static SLPurpleCocoaAdapter *sharedInstance = nil0;
67
68static NSMutableArray *libpurplePluginArray = nil0;
69
70@implementation SLPurpleCocoaAdapter
71
72/*!
73 * @brief Return the shared instance
74 */
75+ (SLPurpleCocoaAdapter *)sharedInstance
76{
77 @synchronized(self) {
78 if (!sharedInstance) {
79 sharedInstance = [[self alloc] init];
80 }
81 }
82
83 return sharedInstance;
84}
85
86/*!
87 * @brief Plugin loaded
88 *
89 * Initialize each libpurple plugin. These plugins should not do anything within libpurple itself; this should be done in
90 * -[plugin initLibpurplePlugin].
91 */
92+ (void)pluginDidLoad
93{
94 NSEnumerator *enumerator;
95 NSString *libpurplePluginPath;
96
97 libpurplePluginArray = [[NSMutableArray alloc] init];
98
99 enumerator = [[[AIObject sharedAdiumInstance] allResourcesForName:@"Plugins"
100 withExtensions:@"AdiumLibpurplePlugin"] objectEnumerator];
101 while ((libpurplePluginPath = [enumerator nextObject])) {
102 [AICorePluginLoader loadPluginAtPath:libpurplePluginPath
103 confirmLoading:YES( BOOL ) 1
104 pluginArray:libpurplePluginArray];
105 }
106}
107
108+ (NSArray *)libpurplePluginArray
109{
110 return libpurplePluginArray;
111}
112
113//Register the account purpleside in the purple thread
114- (void)addAdiumAccount:(CBPurpleAccount *)adiumAccount
115{
116 //Note that purple_account_new() calls purple_accounts_find() first, returning an existing PurpleAccount if there is one.
117 PurpleAccount *account = purple_account_new([adiumAccount purpleAccountName], [adiumAccount protocolPlugin]);
118
119 [(CBPurpleAccount *)account->ui_data autorelease];
120 account->ui_data = [adiumAccount retain];
121
122 [adiumAccount setPurpleAccount:account];
123
124 purple_accounts_add(account);
125 purple_account_set_status_list(account, "offline", YES( BOOL ) 1, NULL( ( void * ) 0 ));
126}
127
128//Remove an account purpleside
129- (void)removeAdiumAccount:(CBPurpleAccount *)adiumAccount
130{
131 PurpleAccount *account = accountLookupFromAdiumAccount(adiumAccount);
132
133 [(CBPurpleAccount *)account->ui_data release];
134 account->ui_data = nil0;
135 [adiumAccount setPurpleAccount:NULL( ( void * ) 0 )];
136
137 purple_accounts_remove(account);
138}
139
140#pragma mark Initialization
141- (id)init
142{
143 if ((self = [super init])) {
144 [self initLibPurple];
145 }
146
147 return self;
148}
149
150static void ZombieKiller_Signal(int i)
151{
152 int status;
153 pid_t child_pid;
154
155 while ((child_pid = waitpid(-1, &status, WNOHANG0x01)) > 0);
156}
157
158- (void)networkDidChange:(NSNotification *)inNotification
159{
160 purple_signal_emit(purple_network_get_handle(), "network-configuration-changed", NULL( ( void * ) 0 ));
161}
162
163- (void)initLibPurple
164{
165 //Set the gaim user directory to be within this user's directory
166 if (![[NSUserDefaults standardUserDefaults] boolForKey:@"Adium 1.0.3 moved to libpurple"]) {
167 //Remove old icons cache
168 [[NSFileManager defaultManager] removeFileAtPath:[[[[adium loginController] userDirectory] stringByAppendingPathComponent:@"libgaim"] stringByAppendingPathComponent:@"icons"]
169 handler:nil0];
170
171 //Update the rest
172 [[NSFileManager defaultManager] movePath:[[[adium loginController] userDirectory] stringByAppendingPathComponent:@"libgaim"]
173 toPath:[[[adium loginController] userDirectory] stringByAppendingPathComponent:@"libpurple"]
174 handler:nil0];
175
176 [[NSUserDefaults standardUserDefaults] setBool:YES( BOOL ) 1
177 forKey:@"Adium 1.0.3 moved to libpurple"];
178 }
179
180 //Set the purple user directory to be within this user's directory
181 NSString *purpleUserDir = [[[adium loginController] userDirectory] stringByAppendingPathComponent:@"libpurple"];
182 purple_util_set_user_dir([[purpleUserDir stringByExpandingTildeInPath] fileSystemRepresentation]);
183
184 //Set the caches path
185 purple_buddy_icons_set_cache_dir([[[adium cachesPath] stringByExpandingTildeInPath] fileSystemRepresentation]);
186
187 /* Delete blist.xml once when 1.2.4 runs to clear out any old silliness, including improperly blocked Yahoo contacts */
188 if (![[NSUserDefaults standardUserDefaults] boolForKey:@"Adium 1.2.4 deleted blist.xml"]) {
189 [[NSFileManager defaultManager] removeFileAtPath:
190 [[[NSString stringWithUTF8String:purple_user_dir()] stringByAppendingPathComponent:@"blist"] stringByAppendingPathExtension:@"xml"]
191 handler:nil0];
192 [[NSUserDefaults standardUserDefaults] setBool:YES( BOOL ) 1
193 forKey:@"Adium 1.2.4 deleted blist.xml"];
194 }
195
196 purple_core_set_ui_ops(adium_purple_core_get_ops());
197 purple_eventloop_set_ui_ops(adium_purple_eventloop_get_ui_ops());
198#if USE_PECAN
199 adium_purple_eventloop_enable_glib_runloop();
200#endif
201
202 //Initialize the libpurple core; this will call back on the function specified in our core UI ops for us to finish configuring libpurple
203 if (!purple_core_init("Adium")) {
204 NSLog(@"*** FATAL ***: Failed to initialize purple core");
205 AILog(@"*** FATAL ***: Failed to initialize purple core");
206 }
207
208 //Libpurple's async DNS lookup tends to create zombies.
209 {
210 struct sigaction act;
211
212 act.sa_handler__sigaction_u . __sa_handler = ZombieKiller_Signal;
213 //Send for terminated but not stopped children
214 act.sa_flags = SA_NOCLDWAIT0x0020;
215
216 sigaction(SIGCHLD20, &act, NULL( ( void * ) 0 ));
217 }
218
219 //Observe for network changes to tell libpurple about it
220 [[adium notificationCenter] addObserver:self
221 selector:@selector(networkDidChange:)
222 name:AINetworkDidChangeNotification@ "AINetworkDidChange"
223 object:nil0];
224}
225
226#pragma mark Lookup functions
227
228NSString* serviceClassForPurpleProtocolID(const char *protocolID)
229{
230 NSString *serviceClass = nil0;
231 if (protocolID) {
232 if (!strcmp(protocolID, "prpl-oscar"))
233 serviceClass = @"AIM-compatible";
234 else if (!strcmp(protocolID, "prpl-gg"))
235 serviceClass = @"Gadu-Gadu";
236 else if (!strcmp(protocolID, "prpl-jabber"))
237 serviceClass = @"Jabber";
238 else if (!strcmp(protocolID, "prpl-meanwhile"))
239 serviceClass = @"Sametime";
240#if USE_PECAN
241 else if (!strcmp(protocolID, PRPL_MSN"prpl-msn_pecan"))
242#else
243 else if (!strcmp(protocolID, "prpl-msn"))
244#endif
245 serviceClass = @"MSN";
246 else if (!strcmp(protocolID, "prpl-novell"))
247 serviceClass = @"GroupWise";
248 else if (!strcmp(protocolID, "prpl-yahoo"))
249 serviceClass = @"Yahoo!";
250 else if (!strcmp(protocolID, "prpl-zephyr"))
251 serviceClass = @"Zephyr";
252 }
253
254 return serviceClass;
255}
256
257/*
258 * Finds an instance of CBPurpleAccount for a pointer to a PurpleAccount struct.
259 */
260CBPurpleAccount* accountLookup(PurpleAccount *acct)
261{
262 CBPurpleAccount *adiumPurpleAccount = (acct ? (CBPurpleAccount *)acct->ui_data : nil0);
263 /* If the account doesn't have its ui_data associated yet (we haven't tried to connect) but we want this
264 * lookup data, we have to do some manual parsing. This is used for example from the OTR preferences.
265 */
266 if (!adiumPurpleAccount && acct) {
267 const char *protocolID = acct->protocol_id;
268 NSString *serviceClass = serviceClassForPurpleProtocolID(protocolID);
269
270 NSEnumerator *enumerator = [[[[AIObject sharedAdiumInstance] accountController] accounts] objectEnumerator];
271 while ((adiumPurpleAccount = [enumerator nextObject])) {
272 if ([adiumPurpleAccount isKindOfClass:[CBPurpleAccount class]] &&
273 [[[adiumPurpleAccount service] serviceClass] isEqualToString:serviceClass] &&
274 [[adiumPurpleAccount UID] caseInsensitiveCompare:[NSString stringWithUTF8String:acct->username]] == NSOrderedSame) {
275 break;
276 }
277 }
278 }
279 return adiumPurpleAccount;
280}
281
282PurpleAccount* accountLookupFromAdiumAccount(CBPurpleAccount *adiumAccount)
283{
284 return [adiumAccount purpleAccount];
285}
286
287AIListContact* contactLookupFromBuddy(PurpleBuddy *buddy)
288{
289 //Get the node's ui_data
290 AIListContact *theContact = (buddy ? (AIListContact *)buddy->node.ui_data : nil0);
291
292 //If the node does not have ui_data yet, we need to create a contact and associate it
293 if (!theContact && buddy) {
294 NSString *UID;
295
296 UID = [NSString stringWithUTF8String:purple_normalize(purple_buddy_get_account(buddy), purple_buddy_get_name(buddy))];
297
298 theContact = [accountLookup(purple_buddy_get_account(buddy)) contactWithUID:UID];
299
300 //Associate the handle with ui_data and the buddy with our statusDictionary
301 buddy->node.ui_data = [theContact retain];
302
303 //This is the first time the contact has been accessed from the buddy; reset the icon cache for it
304 [AIUserIcons flushCacheForObject:theContact];
305 }
306
307 return theContact;
308}
309
310AIListContact* contactLookupFromIMConv(PurpleConversation *conv)
311{
312 return nil0;
313}
314
315AIChat* groupChatLookupFromConv(PurpleConversation *conv)
316{
317 AIChat *chat;
318
319 chat = (AIChat *)conv->ui_data;
320 if (!chat) {
321 NSString *name = [NSString stringWithUTF8String:purple_conversation_get_name(conv)];
322
323 chat = [accountLookup(purple_conversation_get_account(conv)) chatWithName:name identifier:[NSValue valueWithPointer:conv]];
324 conv->ui_data = [chat retain];
325 AILog(@"group chat lookup assigned %@ to %p (%s)",chat,conv, purple_conversation_get_name(conv));
326 }
327
328 return chat;
329}
330
331AIChat* existingChatLookupFromConv(PurpleConversation *conv)
332{
333 return (conv ? conv->ui_data : nil0);
334}
335
336AIChat* chatLookupFromConv(PurpleConversation *conv)
337{
338 switch(purple_conversation_get_type(conv)) {
339 case PURPLE_CONV_TYPE_CHAT:
340 return groupChatLookupFromConv(conv);
341 break;
342 case PURPLE_CONV_TYPE_IM:
343 return imChatLookupFromConv(conv);
344 break;
345 default:
346 return existingChatLookupFromConv(conv);
347 break;
348 }
349}
350
351AIChat* imChatLookupFromConv(PurpleConversation *conv)
352{
353 AIChat *chat;
354
355 chat = (AIChat *)conv->ui_data;
356
357 if (!chat) {
358 //No chat is associated with the IM conversation
359 AIListContact *sourceContact;
360 PurpleBuddy *buddy;
361 PurpleAccount *account;
362
363 account = purple_conversation_get_account(conv);
364// AILog(@"%x purple_conversation_get_name(conv) %s; normalizes to %s",account,purple_conversation_get_name(conv),purple_normalize(account,purple_conversation_get_name(conv)));
365
366 //First, find the PurpleBuddy with whom we are conversing
367 buddy = purple_find_buddy(account, purple_conversation_get_name(conv));
368 if (!buddy) {
369 AILog(@"imChatLookupFromConv: Creating %s %s",purple_account_get_username(account),purple_normalize(account,purple_conversation_get_name(conv)));
370 //No purple_buddy corresponding to the purple_conversation_get_name(conv) is on our list, so create one
371 buddy = purple_buddy_new(account, purple_normalize(account, purple_conversation_get_name(conv)), NULL( ( void * ) 0 )); //create a PurpleBuddy
372 }
373
374 NSCAssertdo { if ( ! ( ( buddy != 0 ) ) ) { [ [ NSAssertionHandler currentHandler
] handleFailureInFunction : [ NSString stringWithCString : __PRETTY_FUNCTION__
] file : [ NSString stringWithCString : "/Users/ryan/Projects/Adium/Plugins/Purple Service/SLPurpleCocoaAdapter.m"
] lineNumber : 374 description : ( ( @ "buddy was nil" ) ) ,
( 0 ) , ( 0 ) , ( 0 ) , ( 0 ) , ( 0 ) ] ; } } while ( 0 )
(buddy != nil, @"buddy was nil");
375
376 sourceContact = contactLookupFromBuddy(buddy);
377
378 // Need to start a new chat, associating with the PurpleConversation
379 chat = [accountLookup(account) chatWithContact:sourceContact identifier:[NSValue valueWithPointer:conv]];
380
381 if (!chat) {
382 NSString *errorString;
383
384 errorString = [NSString stringWithFormat:@"conv %x: Got nil chat in lookup for sourceContact %@ (%x ; \"%s\" ; \"%s\") on adiumAccount %@ (%x ; \"%s\")",
385 conv,
386 sourceContact,
387 buddy,
388 (buddy ? purple_buddy_get_name(buddy) : ""),
389 ((buddy && purple_buddy_get_account(buddy) && purple_buddy_get_name(buddy)) ? purple_normalize(purple_buddy_get_account(buddy), purple_buddy_get_name(buddy)) : ""),
390 accountLookup(account),
391 account,
392 (account ? purple_account_get_username(account) : "")];
393
394 NSCAssertdo { if ( ! ( ( chat != 0 ) ) ) { [ [ NSAssertionHandler currentHandler
] handleFailureInFunction : [ NSString stringWithCString : __PRETTY_FUNCTION__
] file : [ NSString stringWithCString : "/Users/ryan/Projects/Adium/Plugins/Purple Service/SLPurpleCocoaAdapter.m"
] lineNumber : 394 description : ( ( errorString ) ) , ( 0 )
, ( 0 ) , ( 0 ) , ( 0 ) , ( 0 ) ] ; } } while ( 0 )
(chat != nil, errorString);
395 }
396
397 //Associate the PurpleConversation with the AIChat
398 conv->ui_data = [chat retain];
399 }
400
401 return chat;
402}
403
404PurpleConversation* convLookupFromChat(AIChat *chat, id adiumAccount)
405{
406 PurpleConversation *conv = [[chat identifier] pointerValue];
407 PurpleAccount *account = accountLookupFromAdiumAccount(adiumAccount);
408
409 if (!conv && adiumAccount) {
410 AIListObject *listObject = [chat listObject];
411
412 //If we have a listObject, we are dealing with a one-on-one chat, so proceed accordingly
413 if (listObject) {
414 char *destination;
415
416 destination = g_strdup(purple_normalize(account, [[listObject UID] UTF8String]));
417
418 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, account, destination);
419
420 //associate the AIChat with the purple conv
421 if (conv) imChatLookupFromConv(conv);
422
423 g_free(destination);
424
425 } else {
426 //Otherwise, we have a multiuser chat.
427
428 //All multiuser chats should have a non-nil name.
429 NSString *chatName = [chat name];
430 if (chatName) {
431 const char *name = [chatName UTF8String];
432
433 /*
434 Look for an existing purpleChat. If we find one, our job is complete.
435
436 We will never find one if we are joining a chat on our own (via the Join Chat dialogue).
437
438 We should never get to this point if we were invited to a chat, as groupChatLookupFromConv(),
439 which was called when we accepted the invitation and got the chat information from Purple,
440 will have associated the PurpleConversation with the chat and we would have stopped after
441 [[chat identifier] pointerValue] above.
442
443 However, there's no reason not to check just in case.
444 */
445 PurpleChat *purpleChat = purple_blist_find_chat(account, name);
446 if (!purpleChat) {
447
448 /*
449 If we don't have a PurpleChat with this name on this account, we need to create one.
450 Our chat, which should have been created via the Adium Join Chat API, should have
451 a ChatCreationInfo property with the information we need to ask Purple to
452 perform the join.
453 */
454 NSDictionary *chatCreationInfo = [chat valueForProperty:@"ChatCreationInfo"];
455 chatCreationInfo = [(CBPurpleAccount *)[chat account] willJoinChatUsingDictionary:chatCreationInfo];
456
457 if (!chatCreationInfo) {
458 AILog(@"*** No chat creation info for %@ on %@",chat,adiumAccount);
459 return NULL( ( void * ) 0 );
460 }
461
462 AILog(@"Creating a chat with name %s (Creation info: %@).", name, chatCreationInfo);
463
464 GHashTable *components;
465
466 //Prpl Info
467 PurpleConnection *gc = purple_account_get_connection(account);
468 GList *list, *tmp;
469 struct proto_chat_entry *pce;
470 NSString *identifier;
471 NSEnumerator *enumerator;
472
473 g_return_val_if_fail( void ) __extension__ ( { if ( gc != ( ( void * ) 0 ) ) { } else
{ g_return_if_fail_warning ( ( ( gchar * ) 0 ) , __PRETTY_FUNCTION__
, "gc != NULL" ) ; return ( ( ( void * ) 0 ) ) ; } ; } )
(gc != NULL, NULL);
474
475 //Create a hash table
476 //The hash table should contain char* objects created via a g_strdup method
477 components = g_hash_table_new_full(g_str_hash, g_str_equal,
478 g_free, g_free);
479
480 enumerator = [chatCreationInfo keyEnumerator];
481 while ((identifier = [enumerator nextObject])) {
482 id value = [chatCreationInfo objectForKey:identifier];
483 char *valueUTF8String = NULL( ( void * ) 0 );
484
485 if ([value isKindOfClass:[NSNumber class]]) {
486 valueUTF8String = g_strdup_printf("%d",[value intValue]);
487
488 } else if ([value isKindOfClass:[NSString class]]) {
489 valueUTF8String = g_strdup([value UTF8String]);
490
491 } else {
492 AILog(@"Invalid value %@ for identifier %@",value,identifier);
493 }
494
495 //Store our chatCreationInfo-supplied value in the compnents hash table
496 if (valueUTF8String) {
497 g_hash_table_replace(components,
498 g_strdup([identifier UTF8String]),
499 valueUTF8String);
500 }
501 }
502
503 //In debug mode, verify we didn't miss any required values
504 if (PURPLE_DEBUG1) {
505 /* Get the chat_info for our desired account. This will be a GList of proto_chat_entry
506 * objects, each of which has a label and identifier. Each may also have is_int, with a minimum
507 * and a maximum integer value.
508 */
509 if ((PURPLE_PLUGIN_PROTOCOL_INFO( ( PurplePluginProtocolInfo * ) ( gc -> prpl ) -> info
-> extra_info )
(gc->prpl))->chat_info)
510 {
511 list = (PURPLE_PLUGIN_PROTOCOL_INFO( ( PurplePluginProtocolInfo * ) ( gc -> prpl ) -> info
-> extra_info )
(gc->prpl))->chat_info(gc);
512
513 //Look at each proto_chat_entry in the list to verify we have it in chatCreationInfo
514 for (tmp = list; tmp; tmp = tmp->next)
515 {
516 pce = tmp->data;
517 char *identifier = g_strdup(pce->identifier);
518
519 NSString *value = [chatCreationInfo objectForKey:[NSString stringWithUTF8String:identifier]];
520 if (!value) {
521 AILog(@"Danger, Will Robinson! %s is in the proto_info but can't be found in %@",identifier,chatCreationInfo);
522 }
523 }
524 }
525 }
526
527 /* Join the chat serverside - the GHashTable components, coupled with the originating PurpleConnection,
528 * now contains all the information the prpl will need to process our request.
529 */
530 AILog(@"In the event of an emergency, your GHashTable may be used as a flotation device...");
531 serv_join_chat(gc, components);
532 g_hash_table_unref(components);
533 }
534 }
535 }
536 }
537
538 return conv;
539}
540
541PurpleConversation* existingConvLookupFromChat(AIChat *chat)
542{
543 return (PurpleConversation *)[[chat identifier] pointerValue];
544}
545
546void* adium_purple_get_handle(void)
547{
548 static int adium_purple_handle;
549
550 return &adium_purple_handle;
551}
552
553#pragma mark Images
554
555static NSString *_messageImageCachePathWithoutExtension(int imageID, AIAccount* adiumAccount)
556{
557 NSString *messageImageCacheFilename = [NSString stringWithFormat:@"TEMP-Image_%@_%i", [adiumAccount internalObjectID], imageID];
558 return [[[AIObject sharedAdiumInstance] cachesPath] stringByAppendingPathComponent:messageImageCacheFilename];
559}
560
561NSString *processPurpleImages(NSString* inString, AIAccount* adiumAccount)
562{
563 NSScanner *scanner;
564 NSString *chunkString = nil0;
565 NSMutableString *newString;
566 NSString *targetString = @"<IMG ID=";
567 NSCharacterSet *quoteApostropheCharacterSet = [NSCharacterSet characterSetWithCharactersInString:@"\"\'"];
568 int imageID;
569
570 if ([inString rangeOfString:targetString options:NSCaseInsensitiveSearch].location == NSNotFound) {
571 return inString;
572 }
573
574 //set up
575 newString = [[NSMutableString alloc] init];
576
577 scanner = [NSScanner scannerWithString:inString];
578 [scanner setCharactersToBeSkipped:[NSCharacterSet characterSetWithCharactersInString:@""]];
579
580 //A purple image tag takes the form <IMG ID='12'></IMG> where 12 is the reference for use in PurpleStoredImage* purple_imgstore_get(int)
581
582 //Parse the incoming HTML
583 while (![scanner isAtEnd]) {
584 //Find the beginning of a purple IMG ID tag
585 if ([scanner scanUpToString:targetString intoString:&chunkString]) {
586 [newString appendString:chunkString];
587 }
588
589 if ([scanner scanString:targetString intoString:&chunkString]) {
590 //Skip past a quote or apostrophe
591 [scanner scanCharactersFromSet:quoteApostropheCharacterSet intoString:NULL( ( void * ) 0 )];
592
593 //Get the image ID from the tag
594 [scanner scanInt:&imageID];
595
596 //Skip past a quote or apostrophe
597 [scanner scanCharactersFromSet:quoteApostropheCharacterSet intoString:NULL( ( void * ) 0 )];
598
599 //Scan past a >
600 [scanner scanString:@">" intoString:nil0];
601
602 //Get the image, then write it out as a png
603 PurpleStoredImage *purpleImage = purple_imgstore_find_by_id(imageID);
604 if (purpleImage) {
605 NSString *filename = (purple_imgstore_get_filename(purpleImage) ?
606 [NSString stringWithUTF8String:purple_imgstore_get_filename(purpleImage)] :
607 @"Image");
608 NSString *imagePath = _messageImageCachePathWithoutExtension(imageID, adiumAccount);
609
610 //First make an NSImage, then request a TIFFRepresentation to avoid an obscure bug in the PNG writing routines
611 //Exception: PNG writer requires compacted components (bits/component * components/pixel = bits/pixel)
612 NSData *data = [NSData dataWithBytes:purple_imgstore_get_data(purpleImage)
613 length:purple_imgstore_get_size(purpleImage)];
614
615 NSString *extension = [NSImage extensionForBitmapImageFileType:[NSImage fileTypeOfData:data]];
616 if (!extension) {
617 //We don't know what it is; try to make a png out of it
618 NSImage *image = [[NSImage alloc] initWithData:data];
619 NSData *imageTIFFData = [image TIFFRepresentation];
620 NSBitmapImageRep *bitmapRep = [NSBitmapImageRep imageRepWithData:imageTIFFData];
621
622 data = [bitmapRep representationUsingType:NSPNGFileType properties:nil0];
623 extension = @"png";
624 [image release];
625 }
626
627 filename = [filename stringByAppendingPathExtension:extension];
628 imagePath = [imagePath stringByAppendingPathExtension:extension];
629
630 //If writing the file is successful, write an <IMG SRC="filepath"> tag to our string; the 'scaledToFitImage' class lets us apply CSS to directIM images only
631 if ([data writeToFile:imagePath atomically:YES( BOOL ) 1]) {
632 [newString appendString:[NSString stringWithFormat:@"<IMG CLASS=\"scaledToFitImage\" SRC=\"%@\" ALT=\"%@\">",
633 imagePath, filename]];
634 }
635
636 } else {
637 //If we didn't get a purpleImage, just leave the tag for now.. maybe it was important?
638 [newString appendFormat:@"<IMG ID=\"%i\">",chunkString];
639 }
640 }
641 }
642
643 return ([newString autorelease]);
644}
645
646#pragma mark Notify
647// Notify ----------------------------------------------------------------------------------------------------------
648// We handle the notify messages within SLPurpleCocoaAdapter so we can use our localized string macro
649- (void *)handleNotifyMessageOfType:(PurpleNotifyType)type withTitle:(const char *)title primary:(const char *)primary secondary:(const char *)secondary;
650{
651
652 NSString *primaryString = [NSString stringWithUTF8String:primary];
653 NSString *secondaryString = secondary ? [NSString stringWithUTF8String:secondary] : nil0;
654
655 NSString *titleString;
656 if (title) {
657 titleString = [NSString stringWithFormat:AILocalizedString[ [ NSBundle bundleForClass : [ self class ] ] localizedStringForKey
: ( @ "Adium Notice: %@" ) value : @ "" table : ( 0 ) ]
(@"Adium Notice: %@",nil),[NSString stringWithUTF8String:title]];
658 } else {
659 titleString = AILocalizedString[ [ NSBundle bundleForClass : [ self class ] ] localizedStringForKey
: ( @ "Adium : Notice" ) value : @ "" table : ( 0 ) ]
(@"Adium : Notice", nil);
660 }
661
662 NSString *errorMessage = nil0;
663 NSString *description = nil0;
664
665 if (primary && strcmp(primary, _( ( const char * ) libintl_dgettext ( "pidgin" , "Already there"
) )
("Already there")) == 0)
666 return NULL( ( void * ) 0 );
667
668 //Suppress notification warnings we have no interest in seeing
669 if (secondaryString) {
670 if ((strcmp(secondary, _( ( const char * ) libintl_dgettext ( "pidgin" , "Not supported by host"
) )
("Not supported by host")) == 0) || /* OSCAR */
671 (strcmp(secondary, _( ( const char * ) libintl_dgettext ( "pidgin" , "Not logged in"
) )
("Not logged in")) == 0) || /* OSCAR */
672 (strcmp(secondary, _( ( const char * ) libintl_dgettext ( "pidgin" , "Your buddy list was downloaded from the server."
) )
("Your buddy list was downloaded from the server.")) == 0) || /* Gadu-gadu */
673 (strcmp(secondary, _( ( const char * ) libintl_dgettext ( "pidgin" , "Your buddy list was stored on the server."
) )
("Your buddy list was stored on the server.")) == 0) /* Gadu-gadu */) {
674 return NULL( ( void * ) 0 );
675 }
676
677 if ([secondaryString isEqualToString:
678 [NSString stringWithFormat:[NSString stringWithUTF8String:_( ( const char * ) libintl_dgettext ( "pidgin" , "Could not add the buddy %s for an unknown reason."
) )
("Could not add the buddy %s for an unknown reason.")], "1"]]) {
679 /* Rather random error displayed by OSCAR (since forever, as of libpurple 2.4.0) for some clients while connecting */
680 return NULL( ( void * ) 0 );
681 }
682
683 if ([secondaryString rangeOfString:@"Your contact is using Windows Live"].location != NSNotFound) {
684 /* Yahoo without MSN support - English string from the server */
685 return NULL( ( void * ) 0 );
686 }
687
688 } else if ([primaryString rangeOfString: @"did not get sent"].location != NSNotFound) {
689 //Oscar send error
690 //This may not ever occur as of libpurple 2.4.0; I can't find the phrase 'did not get sent' in any of the code. -evands
691 NSString *targetUserName = [[[[primaryString componentsSeparatedByString:@" message to "] objectAtIndex:1] componentsSeparatedByString:@" did not get "] objectAtIndex:0];
692
693 errorMessage = [NSString stringWithFormat:AILocalizedString[ [ NSBundle bundleForClass : [ self class ] ] localizedStringForKey
: ( @ "Your message to %@ did not get sent" ) value : @ "" table
: ( 0 ) ]
(@"Your message to %@ did not get sent",nil),targetUserName];
694
695 if ([secondaryString rangeOfString:[NSString stringWithUTF8String:_( ( const char * ) libintl_dgettext ( "pidgin" , "Rate" ) )("Rate")]].location != NSNotFound) {
696 description = AILocalizedString[ [ NSBundle bundleForClass : [ self class ] ] localizedStringForKey
: ( @ "You are sending messages too quickly; wait a moment and try again."
) value : @ "" table : ( 0 ) ]
(@"You are sending messages too quickly; wait a moment and try again.",nil);
697 } else if ([secondaryString rangeOfString:[NSString stringWithUTF8String:_( ( const char * ) libintl_dgettext ( "pidgin" , "Service unavailable"
) )
("Service unavailable")]].location != NSNotFound ||
698 [secondaryString rangeOfString:[NSString stringWithUTF8String:_( ( const char * ) libintl_dgettext ( "pidgin" , "Not logged in"
) )
("Not logged in")]].location != NSNotFound) {
699 description = AILocalizedString[ [ NSBundle bundleForClass : [ self class ] ] localizedStringForKey
: ( @ "Connection error." ) value : @ "" table : ( 0 ) ]
(@"Connection error.",nil);
700
701 } else if ([secondaryString rangeOfString:[NSString stringWithUTF8String:_( ( const char * ) libintl_dgettext ( "pidgin" , "Refused by client"
) )
("Refused by client")]].location != NSNotFound) {
702 description = AILocalizedString[ [ NSBundle bundleForClass : [ self class ] ] localizedStringForKey
: ( @ "Your message was refused by the other user." ) value :
@ "" table : ( 0 ) ]
(@"Your message was refused by the other user.",nil);
703
704 } else if ([secondaryString rangeOfString:[NSString stringWithUTF8String:_( ( const char * ) libintl_dgettext ( "pidgin" , "Reply too big"
) )
("Reply too big")]].location != NSNotFound) {
705 description = AILocalizedString[ [ NSBundle bundleForClass : [ self class ] ] localizedStringForKey
: ( @ "Your message was too big." ) value : @ "" table : ( 0
) ]
(@"Your message was too big.",nil);
706
707 } else if ([secondaryString rangeOfString:[NSString stringWithUTF8String:_( ( const char * ) libintl_dgettext ( "pidgin" , "In local permit/deny"
) )
("In local permit/deny")]].location != NSNotFound) {
708 description = AILocalizedString[ [ NSBundle bundleForClass : [ self class ] ] localizedStringForKey
: ( @ "The other user is in your deny list." ) value : @ "" table
: ( 0 ) ]
(@"The other user is in your deny list.",nil);
709
710 } else if ([secondaryString rangeOfString:[NSString stringWithUTF8String:_( ( const char * ) libintl_dgettext ( "pidgin" , "Too evil" )
)
("Too evil")]].location != NSNotFound) {
711 description = AILocalizedString[ [ NSBundle bundleForClass : [ self class ] ] localizedStringForKey
: ( @ "Warning level is too high." ) value : @ "" table : ( 0
) ]
(@"Warning level is too high.",nil);
712
713 } else if ([secondaryString rangeOfString:[NSString stringWithUTF8String:_( ( const char * ) libintl_dgettext ( "pidgin" , "User temporarily unavailable"
) )
("User temporarily unavailable")]].location != NSNotFound) {
714 description = AILocalizedString[ [ NSBundle bundleForClass : [ self class ] ] localizedStringForKey
: ( @ "The other user is temporarily unavailable." ) value :
@ "" table : ( 0 ) ]
(@"The other user is temporarily unavailable.",nil);
715
716 } else {
717 description = AILocalizedString[ [ NSBundle bundleForClass : [ self class ] ] localizedStringForKey
: ( @ "No reason was given." ) value : @ "" table : ( 0 ) ]
(@"No reason was given.",nil);
718 }
719 }
720
721 //If we didn't grab a translated version, at least display the English version Purple supplied
722 [[adium interfaceController] handleMessage:([errorMessage length] ? errorMessage : primaryString)
723 withDescription:([description length] ? description : ([secondaryString length] ? secondaryString : @"") )
724 withWindowTitle:titleString];
725
726 return NULL( ( void * ) 0 );
727}
728
729/* XXX ugly */
730- (void *)handleNotifyFormattedWithTitle:(const char *)title primary:(const char *)primary secondary:(const char *)secondary text:(const char *)text
731{
732 NSString *titleString = (title ? [NSString stringWithUTF8String:title] : nil0);
733 NSString *primaryString = (primary ? [NSString stringWithUTF8String:primary] : nil0);
734
735 if (!titleString) {
736 titleString = primaryString;
737 primaryString = nil0;
738 }
739
740 NSString *secondaryString = (secondary ? [NSString stringWithUTF8String:secondary] : nil0);
741 if (!primaryString) {
742 primaryString = secondaryString;
743 secondaryString = nil0;
744 }
745
746 static AIHTMLDecoder *notifyFormattedHTMLDecoder = nil0;
747 if (!notifyFormattedHTMLDecoder) notifyFormattedHTMLDecoder = [[AIHTMLDecoder decoder] retain];
748
749 NSString *textString = (text ? [NSString stringWithUTF8String:text] : nil0);
750 if (textString) textString = [[notifyFormattedHTMLDecoder decodeHTML:textString] string];
751
752 NSString *description = nil0;
753 if ([textString length] && [secondaryString length]) {
754 description = [NSString stringWithFormat:@"%@\n\n%@",secondaryString,textString];
755
756 } else if (textString) {
757 description = textString;
758
759 } else if (secondaryString) {
760 description = secondaryString;
761
762 }
763
764 NSString *message = primaryString;
765
766 [[adium interfaceController] handleMessage:(message ? message : @"")
767 withDescription:(description ? description : @"")
768 withWindowTitle:(titleString ? titleString : @"")];
769
770 return NULL( ( void * ) 0 );
771}
772
773
774#pragma mark File transfers
775- (void)displayFileSendError
776{
777 [[adium interfaceController] handleMessage:AILocalizedString[ [ NSBundle bundleForClass : [ self class ] ] localizedStringForKey
: ( @ "File Send Error" ) value : @ "" table : ( 0 ) ]
(@"File Send Error",nil)
778 withDescription:AILocalizedString[ [ NSBundle bundleForClass : [ self class ] ] localizedStringForKey
: ( @ "An error was encoutered sending the file." ) value : @
"" table : ( 0 ) ]
(@"An error was encoutered sending the file.",nil)
779 withWindowTitle:AILocalizedString[ [ NSBundle bundleForClass : [ self class ] ] localizedStringForKey
: ( @ "File Send Error" ) value : @ "" table : ( 0 ) ]
(@"File Send Error",nil)];
780}
781
782#pragma mark Thread accessors
783- (void)disconnectAccount:(id)adiumAccount
784{
785 PurpleAccount *account = accountLookupFromAdiumAccount(adiumAccount);
786 AILog(@"Setting %x disabled and offline (%s)...",account,
787 purple_status_type_get_id(purple_account_get_status_type_with_primitive(account, PURPLE_STATUS_OFFLINE)));
788
789 purple_account_set_enabled(account, "Adium", NO( BOOL ) 0);
790}
791
792- (void)registerAccount:(id)adiumAccount
793{
794 purple_account_set_register_callback(accountLookupFromAdiumAccount(adiumAccount), adiumPurpleAccountRegisterCb, adiumAccount);
795 purple_account_register(accountLookupFromAdiumAccount(adiumAccount));
796}
797
798static void purpleUnregisterCb(PurpleAccount *account, gboolean success, void *user_data) {
799 [(CBPurpleAccount*)user_data unregisteredAccount:success?YES( BOOL ) 1:NO( BOOL ) 0];
800}
801
802- (void)unregisterAccount:(id)adiumAccount
803{
804 purple_account_unregister(accountLookupFromAdiumAccount(adiumAccount), purpleUnregisterCb, adiumAccount);
805}
806
807//Called on the purple thread, actually performs the specified command (it should have already been tested by
808//attemptPurpleCommandOnMessage:... below.
809- (BOOL)doCommand:(NSString *)originalMessage
810 fromAccount:(id)sourceAccount
811 inChat:(AIChat *)chat
812{
813 PurpleConversation *conv = convLookupFromChat(chat, sourceAccount);
814 PurpleCmdStatus status;
815 char *markup, *error;
816 const char *cmd;
817 BOOL didCommand = NO( BOOL ) 0;
818
819 if (!conv || ([originalMessage length] < 2)) return NO( BOOL ) 0;
820
821 cmd = [originalMessage UTF8String];
822
823 //cmd+1 will be the cmd without the leading character, which should be "/"
824 markup = g_markup_escape_text(cmd+1, -1);
825 status = purple_cmd_do_command(conv, cmd+1, markup, &error);
826
827 //The only error status which is possible now is either
828 switch (status) {
829 case PURPLE_CMD_STATUS_FAILED:
830 {
831 purple_conv_present_error(purple_conversation_get_name(conv), purple_conversation_get_account(conv), "Command failed");
832 didCommand = YES( BOOL ) 1;
833 break;
834 }
835 case PURPLE_CMD_STATUS_WRONG_ARGS:
836 {
837 purple_conv_present_error(purple_conversation_get_name(conv), purple_conversation_get_account(conv), "Wrong number of arguments");
838 didCommand = YES( BOOL ) 1;
839 break;
840 }
841 case PURPLE_CMD_STATUS_OK:
842 didCommand = YES( BOOL ) 1;
843 break;
844 case PURPLE_CMD_STATUS_NOT_FOUND:
845 case PURPLE_CMD_STATUS_WRONG_TYPE:
846 case PURPLE_CMD_STATUS_WRONG_PRPL:
847 /* Ignore this command and let the message send; the user probably doesn't even know what they typed is a command */
848 didCommand = NO( BOOL ) 0;
849 break;
850 }
851
852 return didCommand;
853}
854
855/*!
856 * @brief Check a message for purple / commands=
857 *
858 * @result YES if a command was performed; NO if it was not
859 */
860- (BOOL)attemptPurpleCommandOnMessage:(NSString *)originalMessage fromAccount:(AIAccount *)sourceAccount inChat:(AIChat *)chat
861{
862 BOOL didCommand = NO( BOOL ) 0;
863
864 if ([originalMessage hasPrefix:@"/"]) {
865 didCommand = [self doCommand:originalMessage
866 fromAccount:sourceAccount
867 inChat:chat];
868 }
869
870 return didCommand;
871}
872
873/*!
874 * @brief Send a notification over a service which supports that
875 *
876 * This should not be called for an account whose service doesn't support sending notifications (check before calling).
877 * Doing so will return without displaying an error; the message should be sent as a normal message in this case.
878 *
879 * @param type An AINotificationType.
880 * @param sourceAccount The account from which to send
881 * @param chat The chat in which to send the notification
882 */
883- (void)sendNotificationOfType:(AINotificationType)type
884 fromAccount:(id)sourceAccount
885 inChat:(AIChat *)chat
886{
887 PurpleConversation *conv = convLookupFromChat(chat,sourceAccount);
888
889 serv_send_attention(purple_conversation_get_gc(conv),
890 purple_conversation_get_name(conv),
891 type);
892}
893
894//Returns YES if the message was sent (and should therefore be displayed). Returns NO if it was not sent or was otherwise used.
895- (void)sendEncodedMessage:(NSString *)encodedMessage
896 fromAccount:(id)sourceAccount
897 inChat:(AIChat *)chat
898 withFlags:(PurpleMessageFlags)flags
899{
900 const char *encodedMessageUTF8String;
901
902 if (encodedMessage && (encodedMessageUTF8String = [encodedMessage UTF8String])) {
903 PurpleConversation *conv = convLookupFromChat(chat,sourceAccount);
904
905 switch (purple_conversation_get_type(conv)) {
906 case PURPLE_CONV_TYPE_IM: {
907 PurpleConvIm *im = purple_conversation_get_im_data(conv);
908 purple_conv_im_send_with_flags(im, encodedMessageUTF8String, flags);
909 break;
910 }
911
912 case PURPLE_CONV_TYPE_CHAT: {
913 PurpleConvChat *purpleChat = purple_conversation_get_chat_data(conv);
914 purple_conv_chat_send(purpleChat, encodedMessageUTF8String);
915 break;
916 }
917
918