2 * Adium is the legal property of its developers, whose names are listed in the copyright file included
3 * with this source distribution.
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.
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.
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.
17 #import "AIAccountController.h"
18 #import "AIContactController.h"
19 #import "ESAccountNetworkConnectivityPlugin.h"
20 #import <AIUtilities/AIEventAdditions.h>
21 #import <AIUtilities/AINetworkConnectivity.h>
22 #import <AIUtilities/CBApplicationAdditions.h>
23 #import <AIUtilities/AIHostReachabilityMonitor.h>
24 #import <AIUtilities/AISleepNotification.h>
25 #import <Adium/AIAccount.h>
26 #import <Adium/AIListObject.h>
28 @interface ESAccountNetworkConnectivityPlugin (PRIVATE)
29 - (void)autoConnectAccounts;
30 - (void)handleConnectivityForAccount:(AIAccount *)account reachable:(BOOL)reachable;
31 - (BOOL)_accountsAreOnlineOrDisconnecting;
32 - (void)networkConnectivityChanged:(NSNotification *)notification;
36 * @class ESAccountNetworkConnectivityPlugin
37 * @brief Handle account connection and disconnection
39 * Accounts are automatically connected and disconnected based on:
40 * - Per-account autoconnect preferences (at Adium launch if the network is available)
41 * - Network connectivity (disconnect when the Internet is not available and connect when it is available again)
42 * - System sleep (disconnect when the system sleeps and connect when it wakes up)
44 * Uses AINetworkConnectivity and AISleepNotification from AIUtilities.
46 @implementation ESAccountNetworkConnectivityPlugin
49 * @brief Install plugin
53 //Wait for Adium to finish launching to handle autoconnecting accounts
54 [[adium notificationCenter] addObserver:self
55 selector:@selector(adiumFinishedLaunching:)
56 name:Adium_CompletedApplicationLoad
59 if (![NSApp isOnPantherOrBetter]) {
60 /* Monitor network connectivity changes so we can cleanly disconnect / reconnect
61 * We only need to use the generic network connectivity monitor in 10.2.
63 accountsToConnect = [[NSMutableSet alloc] init];
64 accountsToNotConnect = nil;
66 [[NSNotificationCenter defaultCenter] addObserver:self
67 selector:@selector(networkConnectivityChanged:)
68 name:AINetwork_ConnectivityChanged
72 //Monitor system sleep so we can cleanly disconnect / reconnect
73 [[NSNotificationCenter defaultCenter] addObserver:self
74 selector:@selector(systemWillSleep:)
75 name:AISystemWillSleep_Notification
77 [[NSNotificationCenter defaultCenter] addObserver:self
78 selector:@selector(systemDidWake:)
79 name:AISystemDidWake_Notification
84 * @brief Uninstall plugin
86 - (void)uninstallPlugin
88 [[adium notificationCenter] removeObserver:self];
89 [[adium contactController] unregisterListObjectObserver:self];
90 [[NSNotificationCenter defaultCenter] removeObserver:self];
98 [accountsToConnect release];
99 [accountsToNotConnect release];
105 * @brief Adium finished launching
107 * Attempt to autoconnect accounts if shift is not being pressed
109 - (void)adiumFinishedLaunching:(NSNotification *)notification
111 BOOL shiftKeyHeld = [NSEvent shiftKey];
113 if ([NSApp isOnPantherOrBetter]) {
114 //In Panther and better, we can rely on AIHostReachabilityMonitor
115 AIHostReachabilityMonitor *monitor = [AIHostReachabilityMonitor defaultMonitor];
117 NSArray *accounts = [[adium accountController] accountArray];
119 //start off forbidding all accounts from auto-connecting.
120 accountsToConnect = [[NSMutableSet alloc] initWithArray:accounts];
121 accountsToNotConnect = [accountsToConnect mutableCopy];
122 knownHosts = [[NSMutableSet alloc] init];
124 //add ourselves to the default host-reachability monitor as an observer for each account's host.
125 //at the same time, weed accounts that are to be auto-connected out of the accountsToNotConnect set.
126 NSEnumerator *accountsEnum = [accounts objectEnumerator];
128 while ((account = [accountsEnum nextObject])) {
129 if ([account connectivityBasedOnNetworkReachability]) {
130 NSString *host = [account host];
131 if (host && ![knownHosts containsObject:host]) {
132 [monitor addObserver:self forHost:host];
133 [knownHosts addObject:host];
136 //if this is an account we should auto-connect, remove it from accountsToNotConnect so that we auto-connect it.
138 [[account supportedPropertyKeys] containsObject:@"Online"] &&
139 [[account preferenceForKey:@"AutoConnect" group:GROUP_ACCOUNT_STATUS] boolValue]) {
140 [accountsToNotConnect removeObject:account];
141 continue; //prevent the account from being removed from accountsToConnect.
144 } else if (!shiftKeyHeld &&
145 [[account supportedPropertyKeys] containsObject:@"Online"] &&
146 [[account preferenceForKey:@"AutoConnect" group:GROUP_ACCOUNT_STATUS] boolValue]) {
147 /* This account does not connect based on network reachability, but can go online
148 * and should autoconnect. Connect it immediately.
150 [account setPreference:[NSNumber numberWithBool:YES]
152 group:GROUP_ACCOUNT_STATUS];
155 [accountsToConnect removeObject:account];
158 //Watch for future changes to our account list
159 [[adium notificationCenter] addObserver:self
160 selector:@selector(accountListChanged:)
161 name:Account_ListChanged
164 //In 10.2, just auto connect 'em
166 [self autoConnectAccounts];
172 * @brief Network connectivity changed
174 * Connect or disconnect accounts as appropriate to the new network state.
176 * @param notification The object of the notification is an NSNumber indicating if the network is now available.
178 - (void)networkConnectivityChanged:(NSNotification *)notification
180 NSEnumerator *enumerator;
182 BOOL networkIsReachable;
186 networkIsReachable = [[notification object] boolValue];
188 networkIsReachable = [AINetworkConnectivity networkIsReachable];
191 //Connect or disconnect accounts in response to the connectivity change
192 enumerator = [[[adium accountController] accountArray] objectEnumerator];
193 while((account = [enumerator nextObject])){
194 if([account connectivityBasedOnNetworkReachability]){
195 [self handleConnectivityForAccount:account reachable:networkIsReachable];
201 * @brief Network connectivity changed
203 * Connect or disconnect accounts as appropriate to the new network state.
205 * @param networkIsReachable Indicates whether the given host is now reachable.
206 * @param host The host that is now reachable (or not).
208 - (void)hostReachabilityChanged:(BOOL)networkIsReachable forHost:(NSString *)host
210 NSEnumerator *enumerator;
213 //Connect or disconnect accounts in response to the connectivity change
214 enumerator = [[[adium accountController] accountArray] objectEnumerator];
215 while ((account = [enumerator nextObject])) {
216 if ([[account host] isEqualToString:host]) {
217 if (networkIsReachable && [accountsToNotConnect containsObject:account]) {
218 [accountsToNotConnect removeObject:account];
220 [self handleConnectivityForAccount:account reachable:networkIsReachable];
226 #pragma mark AIHostReachabilityObserver compliance
228 - (void)hostReachabilityMonitor:(AIHostReachabilityMonitor *)monitor hostIsReachable:(NSString *)host {
229 [self hostReachabilityChanged:YES forHost:host];
231 - (void)hostReachabilityMonitor:(AIHostReachabilityMonitor *)monitor hostIsNotReachable:(NSString *)host {
232 [self hostReachabilityChanged:NO forHost:host];
236 #pragma mark Connecting/Disconnecting Accounts
238 * @brief Connect or disconnect an account as appropriate to a new network reachable state
240 * This method uses the accountsToConnect collection to track which accounts were disconnected and should therefore be
243 * @param account The account to change if appropriate
244 * @param reachable The new network reachable state
246 - (void)handleConnectivityForAccount:(AIAccount *)account reachable:(BOOL)reachable
249 //If we are now online and are waiting to connect this account, do it if the account hasn't already
250 //been taken care of.
251 if([accountsToConnect containsObject:account]){
252 if(![account integerStatusObjectForKey:@"Online"] &&
253 ![account integerStatusObjectForKey:@"Connecting"] &&
254 ![[account preferenceForKey:@"Online" group:GROUP_ACCOUNT_STATUS] boolValue]){
256 [account setPreference:[NSNumber numberWithBool:YES] forKey:@"Online" group:GROUP_ACCOUNT_STATUS];
257 [accountsToConnect removeObject:account];
261 //If we are no longer online and this account is connected, disconnect it.
262 if (([account integerStatusObjectForKey:@"Online"] ||
263 [account integerStatusObjectForKey:@"Connecting"]) &&
264 ![account integerStatusObjectForKey:@"Disconnecting"] &&
265 [[account preferenceForKey:@"Online" group:GROUP_ACCOUNT_STATUS] boolValue]){
267 [account setPreference:[NSNumber numberWithBool:NO] forKey:@"Online" group:GROUP_ACCOUNT_STATUS];
268 [accountsToConnect addObject:account];
274 //Autoconnecting Accounts (at startup) ---------------------------------------------------------------------------------
275 #pragma mark Autoconnecting Accounts (at startup)
277 * @brief Auto connect accounts
279 * Automatically connect to accounts flagged with an auto connect property as soon as a network connection is available
281 - (void)autoConnectAccounts
283 NSEnumerator *enumerator;
286 //Determine the accounts which want to be autoconnected
287 enumerator = [[[adium accountController] accountArray] objectEnumerator];
288 while((account = [enumerator nextObject])){
289 if([[account supportedPropertyKeys] containsObject:@"Online"] &&
290 [[account preferenceForKey:@"AutoConnect" group:GROUP_ACCOUNT_STATUS] boolValue]){
292 //If basing connectivity on the network, add it to our array of accounts to connect;
293 //otherwise, sign it on immediately
294 if ([account connectivityBasedOnNetworkReachability]){
295 [accountsToConnect addObject:account];
297 [account setPreference:[NSNumber numberWithBool:YES]
299 group:GROUP_ACCOUNT_STATUS];
304 //Attempt to connect them immediately; if this fails, they will be connected when the network
306 if ([accountsToConnect count]){
307 [self networkConnectivityChanged:nil];
312 //Disconnect / Reconnect on sleep --------------------------------------------------------------------------------------
313 #pragma mark Disconnect/Reconnect On Sleep
315 * @brief System is sleeping
317 - (void)systemWillSleep:(NSNotification *)notification
319 //Disconnect all online accounts
320 if([self _accountsAreOnlineOrDisconnecting]){
321 NSEnumerator *enumerator = [[[adium accountController] accountArray] objectEnumerator];
324 while((account = [enumerator nextObject])){
325 if([[account supportedPropertyKeys] containsObject:@"Online"] &&
326 [[account preferenceForKey:@"Online" group:GROUP_ACCOUNT_STATUS] boolValue]){
328 //Disconnect the account and add it to our list to reconnect
329 [account setPreference:[NSNumber numberWithBool:NO] forKey:@"Online" group:GROUP_ACCOUNT_STATUS];
330 [accountsToConnect addObject:account];
335 //While some accounts disconnect immediately, others may need a second or two to finish the process. For
336 //these accounts we'll want to hold system sleep until they are ready. We monitor account status changes
337 //and will lift the hold once all accounts are finished.
338 if([self _accountsAreOnlineOrDisconnecting]){
339 [[NSNotificationCenter defaultCenter] postNotificationName:AISystemHoldSleep_Notification object:nil];
340 [[adium contactController] registerListObjectObserver:self];
345 * @brief Invoked when our accounts change status
347 * Once all accounts are offline we will remove our hold on system sleep
349 - (NSSet *)updateListObject:(AIListObject *)inObject keys:(NSSet *)inModifiedKeys silent:(BOOL)silent
351 if([inObject isKindOfClass:[AIAccount class]] && [inModifiedKeys containsObject:@"Online"]){
352 if(![self _accountsAreOnlineOrDisconnecting]){
353 [[adium contactController] unregisterListObjectObserver:self];
354 [[NSNotificationCenter defaultCenter] postNotificationName:AISystemContinueSleep_Notification object:nil];
362 * @brief Returns YES if any accounts are currently in the process of disconnecting
364 - (BOOL)_accountsAreOnlineOrDisconnecting
366 NSEnumerator *enumerator = [[[adium accountController] accountArray] objectEnumerator];
369 while((account = [enumerator nextObject])){
370 if([[account statusObjectForKey:@"Online"] boolValue] ||
371 [[account statusObjectForKey:@"Disconnecting"] boolValue]){
380 * @brief System is waking from sleep
382 - (void)systemDidWake:(NSNotification *)notification
384 NSEnumerator *enumerator;
387 //Immediately re-connect accounts which are ignoring the server reachability
388 enumerator = [[[adium accountController] accountArray] objectEnumerator];
389 while((account = [enumerator nextObject])){
390 if(![account connectivityBasedOnNetworkReachability] && [accountsToConnect containsObject:account]){
391 [account setPreference:[NSNumber numberWithBool:YES] forKey:@"Online" group:GROUP_ACCOUNT_STATUS];
392 [accountsToConnect removeObject:account];
396 if (![NSApp isOnPantherOrBetter]) {
397 /* Accounts which consider server reachability will re-connect when connectivity becomes available.
398 * Our callback is not always invoked upon waking, so call it manually to be safe.
399 * This is only necessary in 10.2
401 if([accountsToConnect count]){
402 [self networkConnectivityChanged:nil];
407 #pragma mark Changes to the account list
409 * @brief When the account list changes, ensure we're monitoring for each account
411 - (void)accountListChanged:(NSNotification *)notification
413 NSEnumerator *enumerator;
415 AIHostReachabilityMonitor *monitor = [AIHostReachabilityMonitor defaultMonitor];
417 //Immediately re-connect accounts which are ignoring the server reachability
418 enumerator = [[[adium accountController] accountArray] objectEnumerator];
419 while ((account = [enumerator nextObject])) {
420 if ([account connectivityBasedOnNetworkReachability]) {
421 NSString *host = [account host];
424 ![monitor observer:self isObservingHost:host]) {
425 [monitor addObserver:self forHost:host];