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.
10 #import <AISharedAdium.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"
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
37 - (NSString *)hostForPurple
39 NSString *server = [self preferenceForKey:KEY_SIPE_CONNECT_HOST group:GROUP_ACCOUNT_STATUS];
40 if (!server || [server length] == 0)
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];
50 struct sip_transport {
51 struct sipe_transport_connection *connection;
55 gchar *server_version;
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);
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 )
74 [self setPreference:host forKey:KEY_CONNECT_HOST group:GROUP_ACCOUNT_STATUS];
76 [self setPreference:host forKey:KEY_SIPE_CONNECT_HOST group:GROUP_ACCOUNT_STATUS];
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];
88 #pragma mark Account Configuration
89 - (void)configurePurpleAccount
91 [super configurePurpleAccount];
93 // Account preferences
94 AILog(@"Configuring account: %s\n", self.purpleAccountName);
96 // !!! ------ HACK/Kludge alert! ------
98 * Adium's CBPurpleAccount class's implementation of configurePurpleAccount (called above)
99 * has the following line:
101 * if (hostName && [hostName length]) {
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.
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];
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:@""])
117 purple_account_set_string(account,"server", "");
119 // NOP. Superclass already set this via the [self hostForPurple] response.
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]);
130 purple_account_set_username(account, self.purpleAccountName);
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);
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
143 [self setPasswordTemporarily:@"placeholder"];
146 // Connection preferences
147 id connType = [self preferenceForKey:KEY_SIPE_CONNECTION_TYPE group:GROUP_ACCOUNT_STATUS];
148 if([connType isKindOfClass:[NSNumber class]])
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]];
154 purple_account_set_string(account, "transport", [connType UTF8String]);
156 NSString *authScheme = [self preferenceForKey:KEY_SIPE_AUTH_SCHEME group:GROUP_ACCOUNT_STATUS];
157 purple_account_set_string(account, "authentication", [authScheme UTF8String]);
159 NSString *userAgent = [self preferenceForKey:KEY_SIPE_USER_AGENT group:GROUP_ACCOUNT_STATUS];
160 purple_account_set_string(account, "useragent", (!userAgent.length) ? [userAgent UTF8String] : "" );
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] : "" );
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] : "" );
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] : "" );
184 #pragma mark File transfer
186 - (BOOL)canSendFolders
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
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);
222 if (!purpleStatusID) return nil;
224 switch (sipe_status_token_to_activity(purpleStatusID))
226 case SIPE_ACTIVITY_AVAILABLE:
227 case SIPE_ACTIVITY_ONLINE:
228 statusName = STATUS_NAME_AVAILABLE;
230 case SIPE_ACTIVITY_AWAY:
231 case SIPE_ACTIVITY_INACTIVE:
232 statusName = STATUS_NAME_AWAY;
234 case SIPE_ACTIVITY_BRB:
235 statusName = STATUS_NAME_BRB;
237 case SIPE_ACTIVITY_BUSY:
238 case SIPE_ACTIVITY_BUSYIDLE:
239 statusName = STATUS_NAME_BUSY;
241 case SIPE_ACTIVITY_DND:
242 statusName = STATUS_NAME_DND;
244 case SIPE_ACTIVITY_LUNCH:
245 statusName = STATUS_NAME_LUNCH;
247 case SIPE_ACTIVITY_INVISIBLE:
248 statusName = STATUS_NAME_INVISIBLE;
250 case SIPE_ACTIVITY_OFFLINE:
251 statusName = STATUS_NAME_OFFLINE;
253 case SIPE_ACTIVITY_ON_PHONE:
254 statusName = STATUS_NAME_PHONE;
256 case SIPE_ACTIVITY_IN_CONF:
257 case SIPE_ACTIVITY_IN_MEETING:
258 statusName = STATUS_NAME_NOT_AT_DESK;
260 case SIPE_ACTIVITY_OOF:
261 statusName = STATUS_NAME_NOT_IN_OFFICE;
263 case SIPE_ACTIVITY_URGENT_ONLY:
264 statusName = STATUS_NAME_AWAY_FRIENDS_ONLY;
267 statusName = STATUS_NAME_OFFLINE;
275 * @brief Maps purple status IDs to Adium statuses
277 - (const char *)purpleStatusIDForStatus:(AIStatus *)statusState arguments:(NSMutableDictionary *)arguments
279 const gchar *statusID;
280 NSString *statusName = statusState.statusName;
281 NSString *statusMessageString = [statusState statusMessageString];
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);
292 case AIAwayStatusType:
293 if (([statusName isEqualToString:STATUS_NAME_AWAY]) ||
294 ([statusMessageString caseInsensitiveCompare:[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_AWAY]] == NSOrderedSame))
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))
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))
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))
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))
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))
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))
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))
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))
338 //statusID = sipe_status_activity_to_token(SIPE_ACTIVITY_URGENT_ONLY);
339 statusID = sipe_activity_map[SIPE_ACTIVITY_URGENT_ONLY].status_id;
342 case AIInvisibleStatusType:
343 //statusID = sipe_status_activity_to_token(SIPE_ACTIVITY_INVISIBLE);
344 statusID = sipe_activity_map[SIPE_ACTIVITY_INVISIBLE].status_id;
347 case AIOfflineStatusType:
348 //statusID = sipe_status_activity_to_token(SIPE_ACTIVITY_OFFLINE);
349 statusID = sipe_activity_map[SIPE_ACTIVITY_OFFLINE].status_id;
354 //If we didn't get a purple status type, request one from super
355 if (statusID == NULL) statusID = [super purpleStatusIDForStatus:statusState arguments:arguments];