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/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];
89 - (BOOL)shouldAutoconnectAllEnabled
91 NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
92 NSNumber *didAutoconnectAll = [userDefaults objectForKey:@"Adium 1.0 First Time:Autoconnected All"];
93 BOOL shouldAutoconnectAllEnabled = NO;
95 if (!didAutoconnectAll) {
96 [userDefaults setObject:[NSNumber numberWithBool:YES] forKey:@"Adium 1.0 First Time:Autoconnected All"];
97 [userDefaults synchronize];
98 shouldAutoconnectAllEnabled = YES;
101 return shouldAutoconnectAllEnabled;
105 * @brief Adium finished launching
107 * Attempt to autoconnect accounts if shift is not being pressed
109 - (void)adiumFinishedLaunching:(NSNotification *)notification
111 NSArray *accounts = [[adium accountController] accounts];
112 AIHostReachabilityMonitor *monitor = [AIHostReachabilityMonitor defaultMonitor];
113 BOOL shouldAutoconnectAll = [self shouldAutoconnectAllEnabled];
114 BOOL shiftHeld = [NSEvent shiftKey];
116 //Start off forbidding all accounts from auto-connecting.
117 accountsToConnect = [[NSMutableSet alloc] initWithArray:accounts];
118 accountsToNotConnect = [accountsToConnect mutableCopy];
119 knownHosts = [[NSMutableSet alloc] init];
121 /* Add ourselves to the default host-reachability monitor as an observer for each account's host.
122 * At the same time, weed accounts that are to be auto-connected out of the accountsToNotConnect set.
124 NSEnumerator *accountsEnum;
127 accountsEnum = [accounts objectEnumerator];
128 while ((account = [accountsEnum nextObject])) {
129 BOOL connectAccount = (!shiftHeld &&
131 ([account shouldBeOnline] ||
132 shouldAutoconnectAll));
134 if ([account connectivityBasedOnNetworkReachability]) {
135 NSString *host = [account host];
136 if (host && ![knownHosts containsObject:host]) {
137 [monitor addObserver:self forHost:host];
138 [knownHosts addObject:host];
141 //If this is an account we should auto-connect, remove it from accountsToNotConnect so that we auto-connect it.
142 if (connectAccount) {
143 [accountsToNotConnect removeObject:account];
144 continue; //prevent the account from being removed from accountsToConnect.
147 } else if (connectAccount) {
148 /* This account does not connect based on network reachability, but should autoconnect.
149 * Connect it immediately.
151 [account setShouldBeOnline:YES];
154 [accountsToConnect removeObject:account];
157 [knownHosts release];
159 //Watch for future changes to our account list
160 [[adium notificationCenter] addObserver:self
161 selector:@selector(accountListChanged:)
162 name:Account_ListChanged
167 * @brief Network connectivity changed
169 * Connect or disconnect accounts as appropriate to the new network state.
171 * @param networkIsReachable Indicates whether the given host is now reachable.
172 * @param host The host that is now reachable (or not).
174 - (void)hostReachabilityChanged:(BOOL)networkIsReachable forHost:(NSString *)host
176 NSEnumerator *enumerator;
179 //Connect or disconnect accounts in response to the connectivity change
180 enumerator = [[[adium accountController] accounts] objectEnumerator];
181 while ((account = [enumerator nextObject])) {
182 if (networkIsReachable && [accountsToNotConnect containsObject:account]) {
183 [accountsToNotConnect removeObject:account];
185 if ([[account host] isEqualToString:host]) {
186 [self handleConnectivityForAccount:account reachable:networkIsReachable];
192 #pragma mark AIHostReachabilityObserver compliance
194 - (void)hostReachabilityMonitor:(AIHostReachabilityMonitor *)monitor hostIsReachable:(NSString *)host {
195 [self hostReachabilityChanged:YES forHost:host];
197 - (void)hostReachabilityMonitor:(AIHostReachabilityMonitor *)monitor hostIsNotReachable:(NSString *)host {
198 [self hostReachabilityChanged:NO forHost:host];
201 #pragma mark Connecting/Disconnecting Accounts
203 * @brief Connect or disconnect an account as appropriate to a new network reachable state
205 * This method uses the accountsToConnect collection to track which accounts were disconnected and should therefore be
208 * @param account The account to change if appropriate
209 * @param reachable The new network reachable state
211 - (void)handleConnectivityForAccount:(AIAccount *)account reachable:(BOOL)reachable
213 AILog(@"handleConnectivityForAccount: %@ reachable: %i",account,reachable);
216 //If we are now online and are waiting to connect this account, do it if the account hasn't already
217 //been taken care of.
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 if (([account online] ||
228 [account integerStatusObjectForKey:@"Connecting"]) &&
229 ![account integerStatusObjectForKey:@"Disconnecting"]) {
230 [account disconnect];
231 [accountsToConnect addObject:account];
236 //Disconnect / Reconnect on sleep --------------------------------------------------------------------------------------
237 #pragma mark Disconnect/Reconnect On Sleep
239 * @brief System is sleeping
241 - (void)systemWillSleep:(NSNotification *)notification
243 //Disconnect all online accounts
244 if ([self _accountsAreOnlineOrDisconnecting]) {
245 NSEnumerator *enumerator = [[[adium accountController] accounts] objectEnumerator];
248 while ((account = [enumerator nextObject])) {
249 if ([account online]) {
250 //Disconnect the account and add it to our list to reconnect
251 [account disconnect];
252 [accountsToConnect addObject:account];
257 //While some accounts disconnect immediately, others may need a second or two to finish the process. For
258 //these accounts we'll want to hold system sleep until they are ready. We monitor account status changes
259 //and will lift the hold once all accounts are finished.
260 if ([self _accountsAreOnlineOrDisconnecting]) {
261 [[NSNotificationCenter defaultCenter] postNotificationName:AISystemHoldSleep_Notification object:nil];
262 [[adium contactController] registerListObjectObserver:self];
267 * @brief Invoked when our accounts change status
269 * Once all accounts are offline we will remove our hold on system sleep
271 - (NSSet *)updateListObject:(AIListObject *)inObject keys:(NSSet *)inModifiedKeys silent:(BOOL)silent
273 if ([inObject isKindOfClass:[AIAccount class]] && [inModifiedKeys containsObject:@"Online"]) {
274 if (![self _accountsAreOnlineOrDisconnecting]) {
275 [[adium contactController] unregisterListObjectObserver:self];
276 [[NSNotificationCenter defaultCenter] postNotificationName:AISystemContinueSleep_Notification object:nil];
284 * @brief Returns YES if any accounts are currently in the process of disconnecting
286 - (BOOL)_accountsAreOnlineOrDisconnecting
288 NSEnumerator *enumerator = [[[adium accountController] accounts] objectEnumerator];
291 while ((account = [enumerator nextObject])) {
292 if ([account online] ||
293 [[account statusObjectForKey:@"Disconnecting"] boolValue]) {
302 * @brief System is waking from sleep
304 - (void)systemDidWake:(NSNotification *)notification
306 NSEnumerator *enumerator;
309 //Immediately re-connect accounts which are ignoring the server reachability
310 enumerator = [[[adium accountController] accounts] objectEnumerator];
311 while ((account = [enumerator nextObject])) {
312 if (![account connectivityBasedOnNetworkReachability] && [accountsToConnect containsObject:account]) {
313 [account setShouldBeOnline:YES];
314 [accountsToConnect removeObject:account];
319 #pragma mark Changes to the account list
321 * @brief When the account list changes, ensure we're monitoring for each account
323 - (void)accountListChanged:(NSNotification *)notification
325 NSEnumerator *enumerator;
327 AIHostReachabilityMonitor *monitor = [AIHostReachabilityMonitor defaultMonitor];
329 //Immediately re-connect accounts which are ignoring the server reachability
330 enumerator = [[[adium accountController] accounts] objectEnumerator];
331 while ((account = [enumerator nextObject])) {
332 if ([account connectivityBasedOnNetworkReachability]) {
333 NSString *host = [account host];
336 ![monitor observer:self isObservingHost:host]) {
337 [monitor addObserver:self forHost:host];