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 <Adium/AIAccountControllerProtocol.h>
18 #import <Adium/AIContactControllerProtocol.h>
19 #import "ESAccountNetworkConnectivityPlugin.h"
20 #import <AIUtilities/AIEventAdditions.h>
21 #import <AIUtilities/AIHostReachabilityMonitor.h>
22 #import <AIUtilities/AISleepNotification.h>
23 #import <Adium/AIAccount.h>
24 #import <Adium/AIListObject.h>
26 @interface ESAccountNetworkConnectivityPlugin (PRIVATE)
27 - (void)handleConnectivityForAccount:(AIAccount *)account reachable:(BOOL)reachable;
28 - (BOOL)_accountsAreOnlineOrDisconnecting:(BOOL)considerConnecting;
32 * @class ESAccountNetworkConnectivityPlugin
33 * @brief Handle account connection and disconnection
35 * Accounts are automatically connected and disconnected based on:
36 * - If the account is enabled (at Adium launch if the network is available)
37 * - Network connectivity (disconnect when the Internet is not available and connect when it is available again)
38 * - System sleep (disconnect when the system sleeps and connect when it wakes up)
40 * Uses AIHostReachabilityMonitor and AISleepNotification from AIUtilities.
42 @implementation ESAccountNetworkConnectivityPlugin
45 * @brief Install plugin
49 //Wait for Adium to finish launching to handle autoconnecting enabled accounts
50 [[adium notificationCenter] addObserver:self
51 selector:@selector(adiumFinishedLaunching:)
52 name:AIApplicationDidFinishLoadingNotification
55 NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
57 //Monitor system sleep so we can cleanly disconnect / reconnect
58 [notificationCenter addObserver:self
59 selector:@selector(systemWillSleep:)
60 name:AISystemWillSleep_Notification
62 [notificationCenter addObserver:self
63 selector:@selector(systemDidWake:)
64 name:AISystemDidWake_Notification
66 [[adium contactController] registerListObjectObserver:self];
70 * @brief Uninstall plugin
72 - (void)uninstallPlugin
74 [[adium notificationCenter] removeObserver:self];
75 [[NSNotificationCenter defaultCenter] removeObserver:self];
76 [[adium contactController] unregisterListObjectObserver:self];
84 [accountsToConnect release];
85 [accountsToNotConnect release];
91 * @brief Adium finished launching
93 * Attempt to autoconnect accounts if shift is not being pressed
95 - (void)adiumFinishedLaunching:(NSNotification *)notification
97 NSArray *accounts = [[adium accountController] accounts];
98 AIHostReachabilityMonitor *monitor = [AIHostReachabilityMonitor defaultMonitor];
99 BOOL shiftHeld = [NSEvent shiftKey];
101 //Start off forbidding all accounts from auto-connecting.
102 accountsToConnect = [[NSMutableSet alloc] initWithArray:accounts];
103 accountsToNotConnect = [accountsToConnect mutableCopy];
104 knownHosts = [[NSMutableSet alloc] init];
106 /* Add ourselves to the default host-reachability monitor as an observer for each account's host.
107 * At the same time, weed accounts that are to be auto-connected out of the accountsToNotConnect set.
109 NSEnumerator *enumerator;
112 enumerator = [accounts objectEnumerator];
113 while ((account = [enumerator nextObject])) {
114 BOOL connectAccount = (!shiftHeld &&
116 [[account preferenceForKey:KEY_AUTOCONNECT
117 group:GROUP_ACCOUNT_STATUS] boolValue]);
119 if ([account enabled] &&
120 [account connectivityBasedOnNetworkReachability]) {
121 NSString *host = [account host];
122 if (host && ![knownHosts containsObject:host]) {
123 [monitor addObserver:self forHost:host];
124 [knownHosts addObject:host];
127 //If this is an account we should auto-connect, remove it from accountsToNotConnect so that we auto-connect it.
128 if (connectAccount) {
129 [accountsToNotConnect removeObject:account];
130 [account setStatusObject:[NSNumber numberWithBool:YES] forKey:@"Waiting for Network" notify:NotifyNow];
131 continue; //prevent the account from being removed from accountsToConnect.
134 } else if (connectAccount) {
135 /* This account does not connect based on network reachability, but should autoconnect.
136 * Connect it immediately.
138 [account setShouldBeOnline:YES];
141 [accountsToConnect removeObject:account];
144 [knownHosts release];
146 //Watch for future changes to our account list
147 [[adium notificationCenter] addObserver:self
148 selector:@selector(accountListChanged:)
149 name:Account_ListChanged
153 - (void)networkDidChange
155 [[adium notificationCenter] postNotificationName:AINetworkDidChangeNotification
160 * @brief Network connectivity changed
162 * Connect or disconnect accounts as appropriate to the new network state.
164 * @param networkIsReachable Indicates whether the given host is now reachable.
165 * @param host The host that is now reachable (or not).
167 - (void)hostReachabilityChanged:(BOOL)networkIsReachable forHost:(NSString *)host
169 NSEnumerator *enumerator;
172 //Connect or disconnect accounts in response to the connectivity change
173 enumerator = [[[adium accountController] accounts] objectEnumerator];
174 while ((account = [enumerator nextObject])) {
175 if (networkIsReachable && [accountsToNotConnect containsObject:account]) {
176 [accountsToNotConnect removeObject:account];
178 if ([[account host] isEqualToString:host]) {
179 [self handleConnectivityForAccount:account reachable:networkIsReachable];
184 //Collate reachability changes for multiple hosts into a single notification
185 [NSObject cancelPreviousPerformRequestsWithTarget:self
186 selector:@selector(networkDidChange)
188 [self performSelector:@selector(networkDidChange) withObject:nil afterDelay:1.0];
191 #pragma mark AIHostReachabilityObserver compliance
193 - (void)hostReachabilityMonitor:(AIHostReachabilityMonitor *)monitor hostIsReachable:(NSString *)host {
194 [self hostReachabilityChanged:YES forHost:host];
196 - (void)hostReachabilityMonitor:(AIHostReachabilityMonitor *)monitor hostIsNotReachable:(NSString *)host {
197 [self hostReachabilityChanged:NO forHost:host];
200 #pragma mark Connecting/Disconnecting Accounts
202 * @brief Connect or disconnect an account as appropriate to a new network reachable state
204 * This method uses the accountsToConnect collection to track which accounts were disconnected and should therefore be
207 * @param account The account to change if appropriate
208 * @param reachable The new network reachable state
210 - (void)handleConnectivityForAccount:(AIAccount *)account reachable:(BOOL)reachable
212 AILog(@"handleConnectivityForAccount: %@ reachable: %i",account,reachable);
215 //If we are now online and are waiting to connect this account, do it if the account hasn't already
216 //been taken care of.
217 [account setStatusObject:nil forKey:@"Waiting for Network" notify:NotifyNow];
218 if ([accountsToConnect containsObject:account]) {
219 if (![account online] &&
220 ![account integerStatusObjectForKey:@"Connecting"]) {
221 [account setShouldBeOnline:YES];
222 [accountsToConnect removeObject:account];
226 //If we are no longer online and this account is connected, disconnect it.
227 [account setStatusObject:[NSNumber numberWithBool:YES] forKey:@"Waiting for Network" notify:NotifyNow];
228 if (([account online] ||
229 [account integerStatusObjectForKey:@"Connecting"]) &&
230 ![account integerStatusObjectForKey:@"Disconnecting"]) {
231 [account disconnectFromDroppedNetworkConnection];
232 [accountsToConnect addObject:account];
237 //Disconnect / Reconnect on sleep --------------------------------------------------------------------------------------
238 #pragma mark Disconnect/Reconnect On Sleep
240 * @brief System is sleeping
242 - (void)systemWillSleep:(NSNotification *)notification
244 AILog(@"***** System sleeping...");
245 //Disconnect all online or connecting accounts
246 if ([self _accountsAreOnlineOrDisconnecting:YES]) {
247 NSEnumerator *enumerator = [[[adium accountController] accounts] objectEnumerator];
250 while ((account = [enumerator nextObject])) {
251 if ([account online] ||
252 [[account statusObjectForKey:@"Connecting"] boolValue] ||
253 [account statusObjectForKey:@"Waiting to Reconnect"]) {
255 // Disconnect the account if it's online
256 if ([account online]) {
257 [account disconnect];
258 // Cancel any reconnect attempts
259 } else if ([account statusObjectForKey:@"Waiting to Reconnect"]) {
260 [account cancelAutoReconnect];
262 // Add it to our list to reconnect
263 [accountsToConnect addObject:account];
268 //While some accounts disconnect immediately, others may need a second or two to finish the process. For
269 //these accounts we'll want to hold system sleep until they are ready. We monitor account status changes
270 //and will lift the hold once all accounts are finished.
271 //Don't delay sleep for connecting or reconnecting accounts
272 if ([self _accountsAreOnlineOrDisconnecting:NO]) {
273 AILog(@"Posting AISystemHoldSleep_Notification...");
274 [[NSNotificationCenter defaultCenter] postNotificationName:AISystemHoldSleep_Notification object:nil];
275 waitingToSleep = YES;
280 * @brief Invoked when our accounts change status
282 * Once all accounts are offline we will remove our hold on system sleep
284 - (NSSet *)updateListObject:(AIListObject *)inObject keys:(NSSet *)inModifiedKeys silent:(BOOL)silent
286 if ([inObject isKindOfClass:[AIAccount class]]) {
287 if (waitingToSleep &&
288 [inModifiedKeys containsObject:@"Online"]) {
289 //Don't delay sleep for connecting or reconnecting accounts
290 if (![self _accountsAreOnlineOrDisconnecting:NO]) {
291 AILog(@"Posting AISystemContinueSleep_Notification...");
292 [[NSNotificationCenter defaultCenter] postNotificationName:AISystemContinueSleep_Notification object:nil];
296 AILog(@"Continuing to wait to sleep...");
299 if ([inModifiedKeys containsObject:@"Enabled"]) {
300 AIAccount *account = (AIAccount *)inObject;
302 if ([account enabled]) {
303 //Start observing for this host if we're not already
304 if ([account connectivityBasedOnNetworkReachability]) {
305 NSString *host = [account host];
306 AIHostReachabilityMonitor *monitor = [AIHostReachabilityMonitor defaultMonitor];
308 [account setStatusObject:[NSNumber numberWithBool:YES] forKey:@"Waiting for Network" notify:NotifyNow];
310 ![monitor observer:self isObservingHost:host]) {
311 [monitor addObserver:self forHost:host];
316 NSEnumerator *enumerator;
317 AIAccount *anAccount;
318 BOOL enabledAccountUsingThisHost = NO;
319 NSString *thisHost = [account host];
321 [account setStatusObject:nil forKey:@"Waiting for Network" notify:NotifyNow];
323 //Check if any enabled accounts are still using this now-disabled account's host
324 enumerator = [[[adium accountController] accounts] objectEnumerator];
325 while ((anAccount = [enumerator nextObject])) {
326 if ([anAccount enabled] &&
327 [anAccount connectivityBasedOnNetworkReachability]) {
328 if ([thisHost caseInsensitiveCompare:[anAccount host]] == NSOrderedSame) {
329 enabledAccountUsingThisHost = YES;
335 //If not, stop observing it entirely
336 if (!enabledAccountUsingThisHost) {
337 AIHostReachabilityMonitor *monitor = [AIHostReachabilityMonitor defaultMonitor];
338 [monitor removeObserver:self forHost:thisHost];
348 * @brief Returns YES if any accounts are currently in the process of disconnecting
350 * @param considerConnecting Consider accounts which are connecting or waiting to reconnect
352 - (BOOL)_accountsAreOnlineOrDisconnecting:(BOOL)considerConnecting
354 NSEnumerator *enumerator = [[[adium accountController] accounts] objectEnumerator];
357 while ((account = [enumerator nextObject])) {
358 if ([account online] ||
359 [[account statusObjectForKey:@"Disconnecting"] boolValue]) {
360 AILog(@"%@ (and possibly others) is still %@",account, ([account online] ? @"online" : @"disconnecting"));
362 } else if (considerConnecting &&
363 ([[account statusObjectForKey:@"Connecting"] boolValue] ||
364 [account statusObjectForKey:@"Waiting to Reconnect"])) {
373 * @brief System is waking from sleep
375 - (void)systemDidWake:(NSNotification *)notification
377 NSEnumerator *enumerator;
380 AILog(@"***** System did wake...");
382 /* We could have been waiting to sleep but then timed out and slept anyways; clear the flag if that happened and it wasn't cleared
383 * in updateListObject::: above.
387 //Immediately re-connect accounts which are ignoring the server reachability
388 enumerator = [[[adium accountController] accounts] objectEnumerator];
389 while ((account = [enumerator nextObject])) {
390 if (![account connectivityBasedOnNetworkReachability] && [accountsToConnect containsObject:account]) {
391 [account setShouldBeOnline:YES];
392 [accountsToConnect removeObject:account];
393 } else if ([accountsToConnect containsObject:account]) {
394 [account setStatusObject:[NSNumber numberWithBool:YES] forKey:@"Waiting for Network" notify:NotifyNow];
399 #pragma mark Changes to the account list
401 * @brief When the account list changes, ensure we're monitoring for each account
403 - (void)accountListChanged:(NSNotification *)notification
405 NSEnumerator *enumerator;
407 AIHostReachabilityMonitor *monitor = [AIHostReachabilityMonitor defaultMonitor];
409 //Immediately re-connect accounts which are ignoring the server reachability
410 enumerator = [[[adium accountController] accounts] objectEnumerator];
411 while ((account = [enumerator nextObject])) {
412 if ([account enabled] &&
413 [account connectivityBasedOnNetworkReachability]) {
414 NSString *host = [account host];
417 ![monitor observer:self isObservingHost:host]) {
418 [monitor addObserver:self forHost:host];