adium: Fixes in prep for 1.15.1 release
[siplcs.git] / src / adium / ESPurpleSIPEAccount.m
blobf98b3b0af4bda6baf17b1f2bb900d7154bcc3056
1 //
2 //  ESSIPEAccount.m
3 //  SIPEAdiumPlugin
4 //
5 //  Created by Matt Meissner on 10/30/09.
6 //  Modified by Michael Lamb on 2/27/13
7 //  Copyright 2013 Michael Lamb/Harris Kauffman. All rights reserved.
8 //
10 #import <AISharedAdium.h>
11 #import <AIAdium.h>
12 #import <Adium/AIStatus.h>
13 #import <Adium/AIStatusControllerProtocol.h>
14 #import <AIUtilities/AIHostReachabilityMonitor.h>
16 #import "ESPurpleSIPEAccount.h"
17 #import "ESSIPEService.h"
19 #include "sipe-core.h"
20 #include "sipe-backend.h"
21 #include "sipe-core-private.h"
22 #include "purple-private.h"
24 // C Declarations
25 extern void AILog(NSString *fmt, ...);
26 extern guint sipe_status_token_to_activity(const gchar *token);
28 @class AICoreComponentLoader; 
30 @implementation ESPurpleSIPEAccount
32 - (const char*)protocolPlugin
34         return "prpl-sipe";
37 - (NSString *)hostForPurple
39     NSString *server = [self preferenceForKey:KEY_SIPE_CONNECT_HOST group:GROUP_ACCOUNT_STATUS];
40     if (!server || [server length] == 0)
41     {
42         // set the KEY_CONNECT_HOST to "autodetect" just to make sure we don't lose it from the defaults
43         [self setPreference:@"autodetect" forKey:KEY_CONNECT_HOST group:GROUP_ACCOUNT_STATUS];
44         return @"autodetect";
45     } else {
46         return server;
47     }
50 struct sip_transport {
51         struct sipe_transport_connection *connection;
52     
53         gchar *server_name;
54         guint  server_port;
55         gchar *server_version;
56     
57         gchar *user_agent;
61 -(void) didConnect
63     PurpleConnection *gc = purple_account_get_connection(account);
64     struct sipe_core_public *sipe_public = PURPLE_GC_TO_SIPE_CORE_PUBLIC;    
65     struct sipe_core_private *sipe_private = SIPE_CORE_PRIVATE;
67     // get the resolved hostname from sipe_private
68     NSString *host = [NSString stringWithUTF8String:(sipe_private->transport)->server_name];
69     AILog(@"(didConnect) server: %@",host);
70     
71     // if we autodetected, the KEY_CONNECT_HOST will be "autodetect", we need to replace it with the real hostname
72     if([[self preferenceForKey:KEY_CONNECT_HOST group:GROUP_ACCOUNT_STATUS] compare:@"autodetect" options:NSCaseInsensitiveSearch] == NSOrderedSame )
73     {
74         [self setPreference:host forKey:KEY_CONNECT_HOST group:GROUP_ACCOUNT_STATUS];
75     }
76         [self setPreference:host forKey:KEY_SIPE_CONNECT_HOST group:GROUP_ACCOUNT_STATUS];
78     
79     // Adium Host reachability depends on having KEY_CONNECT_HOST filled in earlier, so we have to hack ourselves into the reachabilityMonitor
80     // TODO: Check with Adium team to see if there is a more elegant way to do this.
81     AIHostReachabilityMonitor   *monitor = [AIHostReachabilityMonitor defaultMonitor];
82     AICoreComponentLoader       *theComponentLoader = adium.componentLoader;
83     [monitor addObserver:[theComponentLoader pluginWithClassName:@"ESAccountNetworkConnectivityPlugin"] forHost:host];
84     
85     [super didConnect];
88 #pragma mark Account Configuration
89 - (void)configurePurpleAccount
91         [super configurePurpleAccount];
92     
93     // Account preferences
94     AILog(@"Configuring account: %s\n", self.purpleAccountName);
95     
96     // !!! ------  HACK/Kludge alert!  ------
97     /*
98      * Adium's CBPurpleAccount class's implementation of configurePurpleAccount (called above)
99      * has the following line:
100      *
101      *         if (hostName && [hostName length]) {
102      * 
103      * Which doesn't allow us to leave the KEY_CONNECT_HOST preference empty (Adium prompts for the user to fill it out)
104      * sipe-core is expecting the server account setting to be empty to engage the auto-detection piece.  The only way
105      * to fix this is to fake out Adium by storing the servername in a different key (KEY_SIPE_CONNECT_HOST) and setting a
106      * default KEY_CONNECT_HOST to something.  We then need to detect that we have an empty servername here, and 
107      * "overwrite" the default placeholder with an empty string.
108      */
109     
110     // if there is no host specified, we're looking to auto-detect
111     // TODO: change this to a checkbox, and enable/disable based on that.  Leaving a field blank is bad UI design.  
112     NSString *server = [self preferenceForKey:KEY_SIPE_CONNECT_HOST group:GROUP_ACCOUNT_STATUS];
113     
114     // if the server is empty, clear it in purple account (because the defaults hack for the superclass set it to "autodetect").  Otherwise, use the one provided
115     if([server isEqualToString:@""])
116     {
117         purple_account_set_string(account,"server", "");
118     } else {
119         // NOP.  Superclass already set this via the [self hostForPurple] response.
120     }
121         
122     NSString *winLogin  = [self preferenceForKey:KEY_SIPE_WINDOWS_LOGIN group:GROUP_ACCOUNT_STATUS];
123     NSString *completeUserName = [NSString stringWithUTF8String:[self purpleAccountName]];
125     if (winLogin && [winLogin length]) {
126         // Configure the complete username ("user@domain.com,DOMAIN\user")
127         completeUserName = [NSString stringWithFormat:@"%@,%@",completeUserName, winLogin];
128         purple_account_set_username(account, [completeUserName UTF8String]);
129     } else {
130         purple_account_set_username(account, self.purpleAccountName);
131     }
132     
133     NSString *thePassword = [self preferenceForKey:KEY_SIPE_PASSWORD group:GROUP_ACCOUNT_STATUS];
134     if (thePassword && [thePassword length])
135         purple_account_set_password(account, [thePassword UTF8String]);
137         BOOL sso = [[self preferenceForKey:KEY_SIPE_SINGLE_SIGN_ON group:GROUP_ACCOUNT_STATUS] boolValue];
138         purple_account_set_bool(account, "sso", sso);
139     
140     if (sso) {
141             // Adium doesn't honor our "optional" password on account creation and will prompt if the password field is left blank, so we must force it to think there is one, but only if there isn't already a password saved
142             if (!thePassword)
143                 [self setPasswordTemporarily:@"placeholder"];
144     }
145     
146     // Connection preferences
147     id connType = [self preferenceForKey:KEY_SIPE_CONNECTION_TYPE group:GROUP_ACCOUNT_STATUS];
148     if([connType isKindOfClass:[NSNumber class]])
149         {
150         // For backwards compatibility, we pick from an array if the preference is a NSNumber
151         NSMutableArray *myArray = [[NSMutableArray alloc] initWithObjects:@"auto", @"tls", @"tcp", nil];
152         connType = (NSString *)[myArray objectAtIndex:(NSUInteger)[self preferenceForKey:KEY_SIPE_CONNECTION_TYPE group:GROUP_ACCOUNT_STATUS]];
153         } 
154     purple_account_set_string(account, "transport", [connType UTF8String]);
155     
156     NSString *authScheme = [self preferenceForKey:KEY_SIPE_AUTH_SCHEME group:GROUP_ACCOUNT_STATUS];
157     purple_account_set_string(account, "authentication", [authScheme UTF8String]);
158     
159         NSString *userAgent = [self preferenceForKey:KEY_SIPE_USER_AGENT group:GROUP_ACCOUNT_STATUS];
160     purple_account_set_string(account, "useragent", (!userAgent.length) ?  [userAgent UTF8String] : "" );
161     
162     // Email preferences
163     NSString *emailURL = [self preferenceForKey:KEY_SIPE_EMAIL_URL group:GROUP_ACCOUNT_STATUS];
164     purple_account_set_string(account, "email_usr", (!emailURL.length) ?  [emailURL UTF8String] : "" );
166     // TODO: Use account name (user@domain) as default for this
167     NSString *email = [self preferenceForKey:KEY_SIPE_EMAIL group:GROUP_ACCOUNT_STATUS];
168     purple_account_set_string(account, "email", (!email.length) ?  [email UTF8String] : "" );
169     
170     // TODO: Use Windows Login (DOMAIN\user) as default for this
171     NSString *emailLogin = [self preferenceForKey:KEY_SIPE_EMAIL_LOGIN group:GROUP_ACCOUNT_STATUS];
172     purple_account_set_string(account, "email_login", (!emailLogin.length) ?  [emailLogin UTF8String] : "" );
174     // TODO: Use default password as default for this
175     NSString *emailPassword = [self preferenceForKey:KEY_SIPE_EMAIL_PASSWORD group:GROUP_ACCOUNT_STATUS];
176     purple_account_set_string(account, "email_password", (!emailPassword.length) ?  [emailPassword UTF8String] : "" );
177     
178     // Group chat preferences
179     NSString *groupchatUser = [self preferenceForKey:KEY_SIPE_GROUP_CHAT_PROXY group:GROUP_ACCOUNT_STATUS];
180     purple_account_set_string(account, "groupchat_user", (!groupchatUser.length) ? [groupchatUser UTF8String] : "" );
181     
184 #pragma mark File transfer
186 - (BOOL)canSendFolders
188         return NO;
191 - (void)beginSendOfFileTransfer:(ESFileTransfer *)fileTransfer
193         [super _beginSendOfFileTransfer:fileTransfer];
196 - (void)acceptFileTransferRequest:(ESFileTransfer *)fileTransfer
198     [super acceptFileTransferRequest:fileTransfer];    
201 - (void)rejectFileReceiveRequest:(ESFileTransfer *)fileTransfer
203     [super rejectFileReceiveRequest:fileTransfer];    
206 - (void)cancelFileTransfer:(ESFileTransfer *)fileTransfer
208         [super cancelFileTransfer:fileTransfer];
211 #pragma mark Status Messages
213  * @brief Status name to use for a Purple buddy
214  */
215 - (NSString *)statusNameForPurpleBuddy:(PurpleBuddy *)buddy
217     NSString *statusName = [super statusNameForPurpleBuddy:buddy];
218     PurplePresence  *presence = purple_buddy_get_presence(buddy);
219     PurpleStatus    *status = purple_presence_get_active_status(presence);
220     const char      *purpleStatusID = purple_status_get_id(status);
221     
222     if (!purpleStatusID) return nil;
223     
224     switch (sipe_status_token_to_activity(purpleStatusID))
225     {
226         case SIPE_ACTIVITY_AVAILABLE:
227         case SIPE_ACTIVITY_ONLINE:
228             statusName = STATUS_NAME_AVAILABLE;
229             break;
230         case SIPE_ACTIVITY_AWAY:
231         case SIPE_ACTIVITY_INACTIVE:
232             statusName = STATUS_NAME_AWAY;
233             break;
234         case SIPE_ACTIVITY_BRB:
235             statusName = STATUS_NAME_BRB;
236             break;
237         case SIPE_ACTIVITY_BUSY:
238         case SIPE_ACTIVITY_BUSYIDLE:
239             statusName = STATUS_NAME_BUSY;
240             break;
241         case SIPE_ACTIVITY_DND:
242             statusName = STATUS_NAME_DND;
243             break;
244         case SIPE_ACTIVITY_LUNCH:
245             statusName = STATUS_NAME_LUNCH;
246             break;
247         case SIPE_ACTIVITY_INVISIBLE:
248             statusName = STATUS_NAME_INVISIBLE;
249             break;
250         case SIPE_ACTIVITY_OFFLINE:
251             statusName = STATUS_NAME_OFFLINE;
252             break;
253         case SIPE_ACTIVITY_ON_PHONE:
254             statusName = STATUS_NAME_PHONE;
255             break;
256         case SIPE_ACTIVITY_IN_CONF:
257         case SIPE_ACTIVITY_IN_MEETING:
258             statusName = STATUS_NAME_NOT_AT_DESK;
259             break;
260         case SIPE_ACTIVITY_OOF:
261             statusName = STATUS_NAME_NOT_IN_OFFICE;
262             break;
263         case SIPE_ACTIVITY_URGENT_ONLY:
264             statusName = STATUS_NAME_AWAY_FRIENDS_ONLY;
265             break;
266         default:
267             statusName = STATUS_NAME_OFFLINE;
268     }
269    
270     return statusName; 
271     
275  * @brief Maps purple status IDs to Adium statuses
276  */
277  - (const char *)purpleStatusIDForStatus:(AIStatus *)statusState arguments:(NSMutableDictionary *)arguments
279      const gchar    *statusID;
280      NSString           *statusName = statusState.statusName;
281      NSString           *statusMessageString = [statusState statusMessageString];
282      
283      if (!statusMessageString) statusMessageString = @"";
285      // TODO: figure out why sipe_status_activity_to_token calls return junk, instead of a gchar*
286      switch (statusState.statusType) {
287          case AIAvailableStatusType:
288              statusID = sipe_activity_map[SIPE_ACTIVITY_AVAILABLE].status_id;
289              //statusID = sipe_status_activity_to_token(SIPE_ACTIVITY_AVAILABLE);
290              break;
291              
292          case AIAwayStatusType:
293              if (([statusName isEqualToString:STATUS_NAME_AWAY]) ||
294                  ([statusMessageString caseInsensitiveCompare:[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_AWAY]] == NSOrderedSame))
295              {
296                  //statusID = sipe_status_activity_to_token(SIPE_ACTIVITY_AWAY);
297                  statusID = sipe_activity_map[SIPE_ACTIVITY_AWAY].status_id;
298              } else if (([statusName isEqualToString:STATUS_NAME_BRB]) ||
299                         ([statusMessageString caseInsensitiveCompare:[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_BRB]] == NSOrderedSame))
300              {
301                  //statusID = sipe_status_activity_to_token(SIPE_ACTIVITY_BRB);
302                  statusID = sipe_activity_map[SIPE_ACTIVITY_BRB].status_id;
303              } else if (([statusName isEqualToString:STATUS_NAME_BUSY]) ||
304                         ([statusMessageString caseInsensitiveCompare:[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_BUSY]] == NSOrderedSame))
305              {
306                  // TODO: Figure out how to determine if they should be "busy" or "busyidle"
307                  //statusID = sipe_status_activity_to_token(SIPE_ACTIVITY_BUSY);
308                  statusID = sipe_activity_map[SIPE_ACTIVITY_BUSY].status_id;
309              } else if (([statusName isEqualToString:STATUS_NAME_DND]) ||
310                       ([statusMessageString caseInsensitiveCompare:[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_DND]] == NSOrderedSame))
311              {
312                  //statusID = sipe_status_activity_to_token(SIPE_ACTIVITY_DND);
313                  statusID = sipe_activity_map[SIPE_ACTIVITY_DND].status_id;
314              } else if (([statusName isEqualToString:STATUS_NAME_LUNCH]) ||
315                       ([statusMessageString caseInsensitiveCompare:[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_LUNCH]] == NSOrderedSame))
316              {
317                  //statusID = sipe_status_activity_to_token(SIPE_ACTIVITY_LUNCH);
318                  statusID = sipe_activity_map[SIPE_ACTIVITY_LUNCH].status_id;
319              } else if (([statusName isEqualToString:STATUS_NAME_PHONE]) ||
320                         ([statusMessageString caseInsensitiveCompare:[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_PHONE]] == NSOrderedSame))
321              {
322                  //statusID = sipe_status_activity_to_token(SIPE_ACTIVITY_ON_PHONE);
323                  statusID = sipe_activity_map[SIPE_ACTIVITY_ON_PHONE].status_id;
324              } else if (([statusName isEqualToString:STATUS_NAME_NOT_AT_DESK]) ||
325                         ([statusMessageString caseInsensitiveCompare:[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_NOT_AT_DESK]] == NSOrderedSame))
326              {
327                  // TODO: Figure out how to determine if they should be "In a meeting" or "In a conference"
328                  //statusID = sipe_status_activity_to_token(SIPE_ACTIVITY_IN_MEETING);
329                  statusID = sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].status_id;
330              } else if (([statusName isEqualToString:STATUS_NAME_NOT_IN_OFFICE]) ||
331                         ([statusMessageString caseInsensitiveCompare:[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_NOT_IN_OFFICE]] == NSOrderedSame))
332              {
333                  //statusID = sipe_status_activity_to_token(SIPE_ACTIVITY_OOF);
334                  statusID = sipe_activity_map[SIPE_ACTIVITY_OOF].status_id;
335              } else if (([statusName isEqualToString:STATUS_NAME_AWAY_FRIENDS_ONLY]) ||
336                         ([statusMessageString caseInsensitiveCompare:[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_AWAY_FRIENDS_ONLY]] == NSOrderedSame))
337              {
338                  //statusID = sipe_status_activity_to_token(SIPE_ACTIVITY_URGENT_ONLY);
339                  statusID = sipe_activity_map[SIPE_ACTIVITY_URGENT_ONLY].status_id;
340              }
341          
342          case AIInvisibleStatusType:
343              //statusID = sipe_status_activity_to_token(SIPE_ACTIVITY_INVISIBLE);
344              statusID = sipe_activity_map[SIPE_ACTIVITY_INVISIBLE].status_id;
345              break;
346          
347          case AIOfflineStatusType:
348              //statusID = sipe_status_activity_to_token(SIPE_ACTIVITY_OFFLINE);
349              statusID = sipe_activity_map[SIPE_ACTIVITY_OFFLINE].status_id;
350              break;
351      }
352      
353          
354      //If we didn't get a purple status type, request one from super
355      if (statusID == NULL) statusID = [super purpleStatusIDForStatus:statusState arguments:arguments];
356      
357      return statusID;
361 @end