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;
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:Adium_CompletedApplicationLoad
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
69 * @brief Uninstall plugin
71 - (void)uninstallPlugin
73 [[adium notificationCenter] removeObserver:self];
74 [[NSNotificationCenter defaultCenter] removeObserver:self];
75 [[adium contactController] unregisterListObjectObserver:self];
83 [accountsToConnect release];
84 [accountsToNotConnect release];
90 * @brief Adium finished launching
92 * Attempt to autoconnect accounts if shift is not being pressed
94 - (void)adiumFinishedLaunching:(NSNotification *)notification
96 NSArray *accounts = [[adium accountController] accounts];
97 AIHostReachabilityMonitor *monitor = [AIHostReachabilityMonitor defaultMonitor];
98 BOOL shiftHeld = [NSEvent shiftKey];
100 //Start off forbidding all accounts from auto-connecting.
101 accountsToConnect = [[NSMutableSet alloc] initWithArray:accounts];
102 accountsToNotConnect = [accountsToConnect mutableCopy];
103 knownHosts = [[NSMutableSet alloc] init];
105 /* Add ourselves to the default host-reachability monitor as an observer for each account's host.
106 * At the same time, weed accounts that are to be auto-connected out of the accountsToNotConnect set.
108 NSEnumerator *accountsEnum;
111 accountsEnum = [accounts objectEnumerator];
112 while ((account = [accountsEnum nextObject])) {
113 BOOL connectAccount = (!shiftHeld &&
115 [[account preferenceForKey:KEY_AUTOCONNECT
116 group:GROUP_ACCOUNT_STATUS] boolValue]);
118 if ([account connectivityBasedOnNetworkReachability]) {
119 NSString *host = [account host];
120 if (host && ![knownHosts containsObject:host]) {
121 [monitor addObserver:self forHost:host];
122 [knownHosts addObject:host];
125 //If this is an account we should auto-connect, remove it from accountsToNotConnect so that we auto-connect it.
126 if (connectAccount) {
127 [accountsToNotConnect removeObject:account];
128 continue; //prevent the account from being removed from accountsToConnect.
131 } else if (connectAccount) {
132 /* This account does not connect based on network reachability, but should autoconnect.
133 * Connect it immediately.
135 [account setShouldBeOnline:YES];
138 [accountsToConnect removeObject:account];
141 [knownHosts release];
143 //Watch for future changes to our account list
144 [[adium notificationCenter] addObserver:self
145 selector:@selector(accountListChanged:)
146 name:Account_ListChanged
151 * @brief Network connectivity changed
153 * Connect or disconnect accounts as appropriate to the new network state.
155 * @param networkIsReachable Indicates whether the given host is now reachable.
156 * @param host The host that is now reachable (or not).
158 - (void)hostReachabilityChanged:(BOOL)networkIsReachable forHost:(NSString *)host
160 NSEnumerator *enumerator;
163 //Connect or disconnect accounts in response to the connectivity change
164 enumerator = [[[adium accountController] accounts] objectEnumerator];
165 while ((account = [enumerator nextObject])) {
166 if (networkIsReachable && [accountsToNotConnect containsObject:account]) {
167 [accountsToNotConnect removeObject:account];
169 if ([[account host] isEqualToString:host]) {
170 [self handleConnectivityForAccount:account reachable:networkIsReachable];
176 #pragma mark AIHostReachabilityObserver compliance
178 - (void)hostReachabilityMonitor:(AIHostReachabilityMonitor *)monitor hostIsReachable:(NSString *)host {
179 [self hostReachabilityChanged:YES forHost:host];
181 - (void)hostReachabilityMonitor:(AIHostReachabilityMonitor *)monitor hostIsNotReachable:(NSString *)host {
182 [self hostReachabilityChanged:NO forHost:host];
185 #pragma mark Connecting/Disconnecting Accounts
187 * @brief Connect or disconnect an account as appropriate to a new network reachable state
189 * This method uses the accountsToConnect collection to track which accounts were disconnected and should therefore be
192 * @param account The account to change if appropriate
193 * @param reachable The new network reachable state
195 - (void)handleConnectivityForAccount:(AIAccount *)account reachable:(BOOL)reachable
197 AILog(@"handleConnectivityForAccount: %@ reachable: %i",account,reachable);
200 //If we are now online and are waiting to connect this account, do it if the account hasn't already
201 //been taken care of.
202 if ([accountsToConnect containsObject:account]) {
203 if (![account online] &&
204 ![account integerStatusObjectForKey:@"Connecting"]) {
205 [account setShouldBeOnline:YES];
206 [accountsToConnect removeObject:account];
210 //If we are no longer online and this account is connected, disconnect it.
211 if (([account online] ||
212 [account integerStatusObjectForKey:@"Connecting"]) &&
213 ![account integerStatusObjectForKey:@"Disconnecting"]) {
214 [account disconnectFromDroppedNetworkConnection];
215 [accountsToConnect addObject:account];
220 //Disconnect / Reconnect on sleep --------------------------------------------------------------------------------------
221 #pragma mark Disconnect/Reconnect On Sleep
223 * @brief System is sleeping
225 - (void)systemWillSleep:(NSNotification *)notification
227 AILog(@"***** System sleeping...");
228 //Disconnect all online accounts
229 if ([self _accountsAreOnlineOrDisconnecting]) {
230 NSEnumerator *enumerator = [[[adium accountController] accounts] objectEnumerator];
233 while ((account = [enumerator nextObject])) {
234 if ([account online]) {
235 //Disconnect the account and add it to our list to reconnect
236 [account disconnect];
237 [accountsToConnect addObject:account];
242 //While some accounts disconnect immediately, others may need a second or two to finish the process. For
243 //these accounts we'll want to hold system sleep until they are ready. We monitor account status changes
244 //and will lift the hold once all accounts are finished.
245 if ([self _accountsAreOnlineOrDisconnecting]) {
246 [[NSNotificationCenter defaultCenter] postNotificationName:AISystemHoldSleep_Notification object:nil];
247 [[adium contactController] registerListObjectObserver:self];
252 * @brief Invoked when our accounts change status
254 * Once all accounts are offline we will remove our hold on system sleep
256 - (NSSet *)updateListObject:(AIListObject *)inObject keys:(NSSet *)inModifiedKeys silent:(BOOL)silent
258 if ([inObject isKindOfClass:[AIAccount class]] && [inModifiedKeys containsObject:@"Online"]) {
259 if (![self _accountsAreOnlineOrDisconnecting]) {
260 [[adium contactController] unregisterListObjectObserver:self];
261 [[NSNotificationCenter defaultCenter] postNotificationName:AISystemContinueSleep_Notification object:nil];
269 * @brief Returns YES if any accounts are currently in the process of disconnecting
271 - (BOOL)_accountsAreOnlineOrDisconnecting
273 NSEnumerator *enumerator = [[[adium accountController] accounts] objectEnumerator];
276 while ((account = [enumerator nextObject])) {
277 if ([account online] ||
278 [[account statusObjectForKey:@"Disconnecting"] boolValue]) {
287 * @brief System is waking from sleep
289 - (void)systemDidWake:(NSNotification *)notification
291 NSEnumerator *enumerator;
294 AILog(@"***** System did wake...");
296 //Immediately re-connect accounts which are ignoring the server reachability
297 enumerator = [[[adium accountController] accounts] objectEnumerator];
298 while ((account = [enumerator nextObject])) {
299 if (![account connectivityBasedOnNetworkReachability] && [accountsToConnect containsObject:account]) {
300 [account setShouldBeOnline:YES];
301 [accountsToConnect removeObject:account];
306 #pragma mark Changes to the account list
308 * @brief When the account list changes, ensure we're monitoring for each account
310 - (void)accountListChanged:(NSNotification *)notification
312 NSEnumerator *enumerator;
314 AIHostReachabilityMonitor *monitor = [AIHostReachabilityMonitor defaultMonitor];
316 //Immediately re-connect accounts which are ignoring the server reachability
317 enumerator = [[[adium accountController] accounts] objectEnumerator];
318 while ((account = [enumerator nextObject])) {
319 if ([account connectivityBasedOnNetworkReachability]) {
320 NSString *host = [account host];
323 ![monitor observer:self isObservingHost:host]) {
324 [monitor addObserver:self forHost:host];