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 "purple-private.h"
24 extern void AILog(NSString *fmt, ...);
26 @class AICoreComponentLoader;
28 @implementation ESPurpleSIPEAccount
30 - (const char*)protocolPlugin
35 - (NSString *)hostForPurple
37 NSString *server = [self preferenceForKey:KEY_SIPE_CONNECT_HOST group:GROUP_ACCOUNT_STATUS];
38 if (!server || [server length] == 0)
40 // set the KEY_CONNECT_HOST to "autodetect" just to make sure we don't lose it from the defaults
41 [self setPreference:@"autodetect" forKey:KEY_CONNECT_HOST group:GROUP_ACCOUNT_STATUS];
50 PurpleConnection *gc = purple_account_get_connection(account);
51 struct sipe_core_public *sipe_public = PURPLE_GC_TO_SIPE_CORE_PUBLIC;
53 // get the resolved hostname from sipe_private
54 NSString *host = [NSString stringWithUTF8String:sipe_core_transport_sip_server_name(sipe_public)];
55 AILog(@"(didConnect) server: %@",host);
57 // if we autodetected, the KEY_CONNECT_HOST will be "autodetect", we need to replace it with the real hostname
58 if([[self preferenceForKey:KEY_CONNECT_HOST group:GROUP_ACCOUNT_STATUS] compare:@"autodetect" options:NSCaseInsensitiveSearch] == NSOrderedSame )
60 [self setPreference:host forKey:KEY_CONNECT_HOST group:GROUP_ACCOUNT_STATUS];
64 // Adium Host reachability depends on having KEY_CONNECT_HOST filled in earlier, so we have to hack ourselves into the reachabilityMonitor
65 // TODO: Check with Adium team to see if there is a more elegant way to do this.
66 AIHostReachabilityMonitor *monitor = [AIHostReachabilityMonitor defaultMonitor];
67 AICoreComponentLoader *theComponentLoader = adium.componentLoader;
68 [monitor addObserver:[theComponentLoader pluginWithClassName:@"ESAccountNetworkConnectivityPlugin"] forHost:host];
73 #pragma mark Account Configuration
74 - (void)configurePurpleAccount
76 [super configurePurpleAccount];
78 // Account preferences
79 AILog(@"Configuring account: %s\n", self.purpleAccountName);
81 // !!! ------ HACK/Kludge alert! ------
83 * Adium's CBPurpleAccount class's implementation of configurePurpleAccount (called above)
84 * has the following line:
86 * if (hostName && [hostName length]) {
88 * Which doesn't allow us to leave the KEY_CONNECT_HOST preference empty (Adium prompts for the user to fill it out)
89 * sipe-core is expecting the server account setting to be empty to engage the auto-detection piece. The only way
90 * to fix this is to fake out Adium by storing the servername in a different key (KEY_SIPE_CONNECT_HOST) and setting a
91 * default KEY_CONNECT_HOST to something. We then need to detect that we have an empty servername here, and
92 * "overwrite" the default placeholder with an empty string.
95 // if there is no host specified, we're looking to auto-detect
96 // TODO: change this to a checkbox, and enable/disable based on that. Leaving a field blank is bad UI design.
97 NSString *server = [self preferenceForKey:KEY_SIPE_CONNECT_HOST group:GROUP_ACCOUNT_STATUS];
99 // 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
100 if([server isEqualToString:@""])
102 // force preference back to "autodetect" so that we can replace it when we get a hostname resolved
103 [self setPreference:@"autodetect" forKey:KEY_CONNECT_HOST group:GROUP_ACCOUNT_STATUS];
104 purple_account_set_string(account,"server", "");
106 // NOP. Superclass already set this via the [self hostForPurple] response.
109 NSString *winLogin = [self preferenceForKey:KEY_SIPE_WINDOWS_LOGIN group:GROUP_ACCOUNT_STATUS];
110 NSString *completeUserName = [NSString stringWithUTF8String:[self purpleAccountName]];
112 if (winLogin && [winLogin length]) {
113 // Configure the complete username ("user@domain.com,DOMAIN\user")
114 completeUserName = [NSString stringWithFormat:@"%@,%@",completeUserName, winLogin];
115 purple_account_set_username(account, [completeUserName UTF8String]);
117 purple_account_set_username(account, self.purpleAccountName);
120 NSString *thePassword = [self preferenceForKey:KEY_SIPE_PASSWORD group:GROUP_ACCOUNT_STATUS];
121 if (thePassword && [thePassword length])
122 purple_account_set_password(account, [thePassword UTF8String]);
124 BOOL sso = [[self preferenceForKey:KEY_SIPE_SINGLE_SIGN_ON group:GROUP_ACCOUNT_STATUS] boolValue];
125 purple_account_set_bool(account, "sso", sso);
128 // 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
130 [self setPasswordTemporarily:@"placeholder"];
133 BOOL dontPublish = [[self preferenceForKey:KEY_SIPE_DONT_PUBLISH group:GROUP_ACCOUNT_STATUS] boolValue];
134 purple_account_set_bool(account, "dont-publish", dontPublish);
136 // Connection preferences
137 id connType = [self preferenceForKey:KEY_SIPE_CONNECTION_TYPE group:GROUP_ACCOUNT_STATUS];
138 if([connType isKindOfClass:[NSNumber class]])
140 // For backwards compatibility, we pick from an array if the preference is a NSNumber
141 NSMutableArray *myArray = [[NSMutableArray alloc] initWithObjects:@"auto", @"tls", @"tcp", nil];
142 connType = (NSString *)[myArray objectAtIndex:(NSUInteger)[self preferenceForKey:KEY_SIPE_CONNECTION_TYPE group:GROUP_ACCOUNT_STATUS]];
144 purple_account_set_string(account, "transport", [connType UTF8String]);
146 NSString *authScheme = [self preferenceForKey:KEY_SIPE_AUTH_SCHEME group:GROUP_ACCOUNT_STATUS];
147 purple_account_set_string(account, "authentication", [authScheme UTF8String]);
149 NSString *userAgent = [self preferenceForKey:KEY_SIPE_USER_AGENT group:GROUP_ACCOUNT_STATUS];
150 purple_account_set_string(account, "useragent", (userAgent.length != 0) ? [userAgent UTF8String] : "" );
153 NSString *emailURL = [self preferenceForKey:KEY_SIPE_EMAIL_URL group:GROUP_ACCOUNT_STATUS];
154 purple_account_set_string(account, "email_usr", (!emailURL.length) ? [emailURL UTF8String] : "" );
156 // TODO: Use account name (user@domain) as default for this
157 NSString *email = [self preferenceForKey:KEY_SIPE_EMAIL group:GROUP_ACCOUNT_STATUS];
158 purple_account_set_string(account, "email", (!email.length) ? [email UTF8String] : "" );
160 // TODO: Use Windows Login (DOMAIN\user) as default for this
161 NSString *emailLogin = [self preferenceForKey:KEY_SIPE_EMAIL_LOGIN group:GROUP_ACCOUNT_STATUS];
162 purple_account_set_string(account, "email_login", (!emailLogin.length) ? [emailLogin UTF8String] : "" );
164 // TODO: Use default password as default for this
165 NSString *emailPassword = [self preferenceForKey:KEY_SIPE_EMAIL_PASSWORD group:GROUP_ACCOUNT_STATUS];
166 purple_account_set_string(account, "email_password", (!emailPassword.length) ? [emailPassword UTF8String] : "" );
168 // Group chat preferences
169 NSString *groupchatUser = [self preferenceForKey:KEY_SIPE_GROUP_CHAT_PROXY group:GROUP_ACCOUNT_STATUS];
170 purple_account_set_string(account, "groupchat_user", (!groupchatUser.length) ? [groupchatUser UTF8String] : "" );
174 #pragma mark File transfer
176 - (BOOL)canSendFolders
181 - (void)beginSendOfFileTransfer:(ESFileTransfer *)fileTransfer
183 [super _beginSendOfFileTransfer:fileTransfer];
186 - (void)acceptFileTransferRequest:(ESFileTransfer *)fileTransfer
188 [super acceptFileTransferRequest:fileTransfer];
191 - (void)rejectFileReceiveRequest:(ESFileTransfer *)fileTransfer
193 [super rejectFileReceiveRequest:fileTransfer];
196 - (void)cancelFileTransfer:(ESFileTransfer *)fileTransfer
198 [super cancelFileTransfer:fileTransfer];
201 #pragma mark Status Messages
203 * @brief Status name to use for a Purple buddy
205 - (NSString *)statusNameForPurpleBuddy:(PurpleBuddy *)buddy
207 NSString *statusName = [super statusNameForPurpleBuddy:buddy];
208 PurplePresence *presence = purple_buddy_get_presence(buddy);
209 PurpleStatus *status = purple_presence_get_active_status(presence);
210 const char *purpleStatusID = purple_status_get_id(status);
212 if (!purpleStatusID) return nil;
214 switch (sipe_purple_token_to_activity(purpleStatusID))
216 case SIPE_ACTIVITY_AVAILABLE:
217 case SIPE_ACTIVITY_ONLINE:
218 statusName = STATUS_NAME_AVAILABLE;
220 case SIPE_ACTIVITY_AWAY:
221 case SIPE_ACTIVITY_INACTIVE:
222 statusName = STATUS_NAME_AWAY;
224 case SIPE_ACTIVITY_BRB:
225 statusName = STATUS_NAME_BRB;
227 case SIPE_ACTIVITY_BUSY:
228 case SIPE_ACTIVITY_BUSYIDLE:
229 statusName = STATUS_NAME_BUSY;
231 case SIPE_ACTIVITY_DND:
232 statusName = STATUS_NAME_DND;
234 case SIPE_ACTIVITY_LUNCH:
235 statusName = STATUS_NAME_LUNCH;
237 case SIPE_ACTIVITY_INVISIBLE:
238 statusName = STATUS_NAME_INVISIBLE;
240 case SIPE_ACTIVITY_OFFLINE:
241 statusName = STATUS_NAME_OFFLINE;
243 case SIPE_ACTIVITY_ON_PHONE:
244 statusName = STATUS_NAME_PHONE;
246 case SIPE_ACTIVITY_IN_CONF:
247 case SIPE_ACTIVITY_IN_MEETING:
248 statusName = STATUS_NAME_NOT_AT_DESK;
250 case SIPE_ACTIVITY_OOF:
251 statusName = STATUS_NAME_NOT_IN_OFFICE;
253 case SIPE_ACTIVITY_URGENT_ONLY:
254 statusName = STATUS_NAME_AWAY_FRIENDS_ONLY;
257 statusName = STATUS_NAME_OFFLINE;
265 * @brief Maps purple status IDs to Adium statuses
267 - (const char *)purpleStatusIDForStatus:(AIStatus *)statusState arguments:(NSMutableDictionary *)arguments
269 const gchar *statusID;
270 NSString *statusName = statusState.statusName;
271 NSString *statusMessageString = [statusState statusMessageString];
273 if (!statusMessageString) statusMessageString = @"";
275 // TODO: figure out why sipe_status_activity_to_token calls return junk, instead of a gchar*
276 switch (statusState.statusType) {
277 case AIAvailableStatusType:
278 statusID = sipe_activity_map[SIPE_ACTIVITY_AVAILABLE].status_id;
279 //statusID = sipe_status_activity_to_token(SIPE_ACTIVITY_AVAILABLE);
282 case AIAwayStatusType:
283 if (([statusName isEqualToString:STATUS_NAME_AWAY]) ||
284 ([statusMessageString caseInsensitiveCompare:[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_AWAY]] == NSOrderedSame))
286 //statusID = sipe_status_activity_to_token(SIPE_ACTIVITY_AWAY);
287 statusID = sipe_activity_map[SIPE_ACTIVITY_AWAY].status_id;
288 } else if (([statusName isEqualToString:STATUS_NAME_BRB]) ||
289 ([statusMessageString caseInsensitiveCompare:[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_BRB]] == NSOrderedSame))
291 //statusID = sipe_status_activity_to_token(SIPE_ACTIVITY_BRB);
292 statusID = sipe_activity_map[SIPE_ACTIVITY_BRB].status_id;
293 } else if (([statusName isEqualToString:STATUS_NAME_BUSY]) ||
294 ([statusMessageString caseInsensitiveCompare:[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_BUSY]] == NSOrderedSame))
296 // TODO: Figure out how to determine if they should be "busy" or "busyidle"
297 //statusID = sipe_status_activity_to_token(SIPE_ACTIVITY_BUSY);
298 statusID = sipe_activity_map[SIPE_ACTIVITY_BUSY].status_id;
299 } else if (([statusName isEqualToString:STATUS_NAME_DND]) ||
300 ([statusMessageString caseInsensitiveCompare:[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_DND]] == NSOrderedSame))
302 //statusID = sipe_status_activity_to_token(SIPE_ACTIVITY_DND);
303 statusID = sipe_activity_map[SIPE_ACTIVITY_DND].status_id;
304 } else if (([statusName isEqualToString:STATUS_NAME_LUNCH]) ||
305 ([statusMessageString caseInsensitiveCompare:[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_LUNCH]] == NSOrderedSame))
307 //statusID = sipe_status_activity_to_token(SIPE_ACTIVITY_LUNCH);
308 statusID = sipe_activity_map[SIPE_ACTIVITY_LUNCH].status_id;
309 } else if (([statusName isEqualToString:STATUS_NAME_PHONE]) ||
310 ([statusMessageString caseInsensitiveCompare:[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_PHONE]] == NSOrderedSame))
312 //statusID = sipe_status_activity_to_token(SIPE_ACTIVITY_ON_PHONE);
313 statusID = sipe_activity_map[SIPE_ACTIVITY_ON_PHONE].status_id;
314 } else if (([statusName isEqualToString:STATUS_NAME_NOT_AT_DESK]) ||
315 ([statusMessageString caseInsensitiveCompare:[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_NOT_AT_DESK]] == NSOrderedSame))
317 // TODO: Figure out how to determine if they should be "In a meeting" or "In a conference"
318 //statusID = sipe_status_activity_to_token(SIPE_ACTIVITY_IN_MEETING);
319 statusID = sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].status_id;
320 } else if (([statusName isEqualToString:STATUS_NAME_NOT_IN_OFFICE]) ||
321 ([statusMessageString caseInsensitiveCompare:[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_NOT_IN_OFFICE]] == NSOrderedSame))
323 //statusID = sipe_status_activity_to_token(SIPE_ACTIVITY_OOF);
324 statusID = sipe_activity_map[SIPE_ACTIVITY_OOF].status_id;
325 } else if (([statusName isEqualToString:STATUS_NAME_AWAY_FRIENDS_ONLY]) ||
326 ([statusMessageString caseInsensitiveCompare:[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_AWAY_FRIENDS_ONLY]] == NSOrderedSame))
328 //statusID = sipe_status_activity_to_token(SIPE_ACTIVITY_URGENT_ONLY);
329 statusID = sipe_activity_map[SIPE_ACTIVITY_URGENT_ONLY].status_id;
332 case AIInvisibleStatusType:
333 //statusID = sipe_status_activity_to_token(SIPE_ACTIVITY_INVISIBLE);
334 statusID = sipe_activity_map[SIPE_ACTIVITY_INVISIBLE].status_id;
337 case AIOfflineStatusType:
338 //statusID = sipe_status_activity_to_token(SIPE_ACTIVITY_OFFLINE);
339 statusID = sipe_activity_map[SIPE_ACTIVITY_OFFLINE].status_id;
344 //If we didn't get a purple status type, request one from super
345 if (statusID == NULL) statusID = [super purpleStatusIDForStatus:statusState arguments:arguments];