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 // Connection preferences
134 id connType = [self preferenceForKey:KEY_SIPE_CONNECTION_TYPE group:GROUP_ACCOUNT_STATUS];
135 if([connType isKindOfClass:[NSNumber class]])
137 // For backwards compatibility, we pick from an array if the preference is a NSNumber
138 NSMutableArray *myArray = [[NSMutableArray alloc] initWithObjects:@"auto", @"tls", @"tcp", nil];
139 connType = (NSString *)[myArray objectAtIndex:(NSUInteger)[self preferenceForKey:KEY_SIPE_CONNECTION_TYPE group:GROUP_ACCOUNT_STATUS]];
141 purple_account_set_string(account, "transport", [connType UTF8String]);
143 NSString *authScheme = [self preferenceForKey:KEY_SIPE_AUTH_SCHEME group:GROUP_ACCOUNT_STATUS];
144 purple_account_set_string(account, "authentication", [authScheme UTF8String]);
146 NSString *userAgent = [self preferenceForKey:KEY_SIPE_USER_AGENT group:GROUP_ACCOUNT_STATUS];
147 purple_account_set_string(account, "useragent", (userAgent.length != 0) ? [userAgent UTF8String] : "" );
150 NSString *emailURL = [self preferenceForKey:KEY_SIPE_EMAIL_URL group:GROUP_ACCOUNT_STATUS];
151 purple_account_set_string(account, "email_usr", (!emailURL.length) ? [emailURL UTF8String] : "" );
153 // TODO: Use account name (user@domain) as default for this
154 NSString *email = [self preferenceForKey:KEY_SIPE_EMAIL group:GROUP_ACCOUNT_STATUS];
155 purple_account_set_string(account, "email", (!email.length) ? [email UTF8String] : "" );
157 // TODO: Use Windows Login (DOMAIN\user) as default for this
158 NSString *emailLogin = [self preferenceForKey:KEY_SIPE_EMAIL_LOGIN group:GROUP_ACCOUNT_STATUS];
159 purple_account_set_string(account, "email_login", (!emailLogin.length) ? [emailLogin UTF8String] : "" );
161 // TODO: Use default password as default for this
162 NSString *emailPassword = [self preferenceForKey:KEY_SIPE_EMAIL_PASSWORD group:GROUP_ACCOUNT_STATUS];
163 purple_account_set_string(account, "email_password", (!emailPassword.length) ? [emailPassword UTF8String] : "" );
165 // Group chat preferences
166 NSString *groupchatUser = [self preferenceForKey:KEY_SIPE_GROUP_CHAT_PROXY group:GROUP_ACCOUNT_STATUS];
167 purple_account_set_string(account, "groupchat_user", (!groupchatUser.length) ? [groupchatUser UTF8String] : "" );
171 #pragma mark File transfer
173 - (BOOL)canSendFolders
178 - (void)beginSendOfFileTransfer:(ESFileTransfer *)fileTransfer
180 [super _beginSendOfFileTransfer:fileTransfer];
183 - (void)acceptFileTransferRequest:(ESFileTransfer *)fileTransfer
185 [super acceptFileTransferRequest:fileTransfer];
188 - (void)rejectFileReceiveRequest:(ESFileTransfer *)fileTransfer
190 [super rejectFileReceiveRequest:fileTransfer];
193 - (void)cancelFileTransfer:(ESFileTransfer *)fileTransfer
195 [super cancelFileTransfer:fileTransfer];
198 #pragma mark Status Messages
200 * @brief Status name to use for a Purple buddy
202 - (NSString *)statusNameForPurpleBuddy:(PurpleBuddy *)buddy
204 NSString *statusName = [super statusNameForPurpleBuddy:buddy];
205 PurplePresence *presence = purple_buddy_get_presence(buddy);
206 PurpleStatus *status = purple_presence_get_active_status(presence);
207 const char *purpleStatusID = purple_status_get_id(status);
209 if (!purpleStatusID) return nil;
211 switch (sipe_purple_token_to_activity(purpleStatusID))
213 case SIPE_ACTIVITY_AVAILABLE:
214 case SIPE_ACTIVITY_ONLINE:
215 statusName = STATUS_NAME_AVAILABLE;
217 case SIPE_ACTIVITY_AWAY:
218 case SIPE_ACTIVITY_INACTIVE:
219 statusName = STATUS_NAME_AWAY;
221 case SIPE_ACTIVITY_BRB:
222 statusName = STATUS_NAME_BRB;
224 case SIPE_ACTIVITY_BUSY:
225 case SIPE_ACTIVITY_BUSYIDLE:
226 statusName = STATUS_NAME_BUSY;
228 case SIPE_ACTIVITY_DND:
229 statusName = STATUS_NAME_DND;
231 case SIPE_ACTIVITY_LUNCH:
232 statusName = STATUS_NAME_LUNCH;
234 case SIPE_ACTIVITY_INVISIBLE:
235 statusName = STATUS_NAME_INVISIBLE;
237 case SIPE_ACTIVITY_OFFLINE:
238 statusName = STATUS_NAME_OFFLINE;
240 case SIPE_ACTIVITY_ON_PHONE:
241 statusName = STATUS_NAME_PHONE;
243 case SIPE_ACTIVITY_IN_CONF:
244 case SIPE_ACTIVITY_IN_MEETING:
245 statusName = STATUS_NAME_NOT_AT_DESK;
247 case SIPE_ACTIVITY_OOF:
248 statusName = STATUS_NAME_NOT_IN_OFFICE;
250 case SIPE_ACTIVITY_URGENT_ONLY:
251 statusName = STATUS_NAME_AWAY_FRIENDS_ONLY;
254 statusName = STATUS_NAME_OFFLINE;
262 * @brief Maps purple status IDs to Adium statuses
264 - (const char *)purpleStatusIDForStatus:(AIStatus *)statusState arguments:(NSMutableDictionary *)arguments
266 const gchar *statusID;
267 NSString *statusName = statusState.statusName;
268 NSString *statusMessageString = [statusState statusMessageString];
270 if (!statusMessageString) statusMessageString = @"";
272 // TODO: figure out why sipe_status_activity_to_token calls return junk, instead of a gchar*
273 switch (statusState.statusType) {
274 case AIAvailableStatusType:
275 statusID = sipe_activity_map[SIPE_ACTIVITY_AVAILABLE].status_id;
276 //statusID = sipe_status_activity_to_token(SIPE_ACTIVITY_AVAILABLE);
279 case AIAwayStatusType:
280 if (([statusName isEqualToString:STATUS_NAME_AWAY]) ||
281 ([statusMessageString caseInsensitiveCompare:[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_AWAY]] == NSOrderedSame))
283 //statusID = sipe_status_activity_to_token(SIPE_ACTIVITY_AWAY);
284 statusID = sipe_activity_map[SIPE_ACTIVITY_AWAY].status_id;
285 } else if (([statusName isEqualToString:STATUS_NAME_BRB]) ||
286 ([statusMessageString caseInsensitiveCompare:[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_BRB]] == NSOrderedSame))
288 //statusID = sipe_status_activity_to_token(SIPE_ACTIVITY_BRB);
289 statusID = sipe_activity_map[SIPE_ACTIVITY_BRB].status_id;
290 } else if (([statusName isEqualToString:STATUS_NAME_BUSY]) ||
291 ([statusMessageString caseInsensitiveCompare:[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_BUSY]] == NSOrderedSame))
293 // TODO: Figure out how to determine if they should be "busy" or "busyidle"
294 //statusID = sipe_status_activity_to_token(SIPE_ACTIVITY_BUSY);
295 statusID = sipe_activity_map[SIPE_ACTIVITY_BUSY].status_id;
296 } else if (([statusName isEqualToString:STATUS_NAME_DND]) ||
297 ([statusMessageString caseInsensitiveCompare:[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_DND]] == NSOrderedSame))
299 //statusID = sipe_status_activity_to_token(SIPE_ACTIVITY_DND);
300 statusID = sipe_activity_map[SIPE_ACTIVITY_DND].status_id;
301 } else if (([statusName isEqualToString:STATUS_NAME_LUNCH]) ||
302 ([statusMessageString caseInsensitiveCompare:[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_LUNCH]] == NSOrderedSame))
304 //statusID = sipe_status_activity_to_token(SIPE_ACTIVITY_LUNCH);
305 statusID = sipe_activity_map[SIPE_ACTIVITY_LUNCH].status_id;
306 } else if (([statusName isEqualToString:STATUS_NAME_PHONE]) ||
307 ([statusMessageString caseInsensitiveCompare:[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_PHONE]] == NSOrderedSame))
309 //statusID = sipe_status_activity_to_token(SIPE_ACTIVITY_ON_PHONE);
310 statusID = sipe_activity_map[SIPE_ACTIVITY_ON_PHONE].status_id;
311 } else if (([statusName isEqualToString:STATUS_NAME_NOT_AT_DESK]) ||
312 ([statusMessageString caseInsensitiveCompare:[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_NOT_AT_DESK]] == NSOrderedSame))
314 // TODO: Figure out how to determine if they should be "In a meeting" or "In a conference"
315 //statusID = sipe_status_activity_to_token(SIPE_ACTIVITY_IN_MEETING);
316 statusID = sipe_activity_map[SIPE_ACTIVITY_IN_MEETING].status_id;
317 } else if (([statusName isEqualToString:STATUS_NAME_NOT_IN_OFFICE]) ||
318 ([statusMessageString caseInsensitiveCompare:[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_NOT_IN_OFFICE]] == NSOrderedSame))
320 //statusID = sipe_status_activity_to_token(SIPE_ACTIVITY_OOF);
321 statusID = sipe_activity_map[SIPE_ACTIVITY_OOF].status_id;
322 } else if (([statusName isEqualToString:STATUS_NAME_AWAY_FRIENDS_ONLY]) ||
323 ([statusMessageString caseInsensitiveCompare:[adium.statusController localizedDescriptionForCoreStatusName:STATUS_NAME_AWAY_FRIENDS_ONLY]] == NSOrderedSame))
325 //statusID = sipe_status_activity_to_token(SIPE_ACTIVITY_URGENT_ONLY);
326 statusID = sipe_activity_map[SIPE_ACTIVITY_URGENT_ONLY].status_id;
329 case AIInvisibleStatusType:
330 //statusID = sipe_status_activity_to_token(SIPE_ACTIVITY_INVISIBLE);
331 statusID = sipe_activity_map[SIPE_ACTIVITY_INVISIBLE].status_id;
334 case AIOfflineStatusType:
335 //statusID = sipe_status_activity_to_token(SIPE_ACTIVITY_OFFLINE);
336 statusID = sipe_activity_map[SIPE_ACTIVITY_OFFLINE].status_id;
341 //If we didn't get a purple status type, request one from super
342 if (statusID == NULL) statusID = [super purpleStatusIDForStatus:statusState arguments:arguments];