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 if (![NSEvent shiftKey]) {
112 if ([NSApp isOnPantherOrBetter]) {
113 //In Panther and better, we can rely on AIHostReachabilityMonitor
114 AIHostReachabilityMonitor *monitor = [AIHostReachabilityMonitor defaultMonitor];
116 NSArray *accounts = [[adium accountController] accountArray];
118 //start off forbidding all accounts from auto-connecting.
119 accountsToConnect = [[NSMutableSet alloc] initWithArray:accounts];
120 accountsToNotConnect = [accountsToConnect mutableCopy];
121 knownHosts = [[NSMutableSet alloc] init];
123 //add ourselves to the default host-reachability monitor as an observer for each account's host.
124 //at the same time, weed accounts that are to be auto-connected out of the accountsToNotConnect set.
125 NSEnumerator *accountsEnum = [accounts objectEnumerator];
127 while ((account = [accountsEnum nextObject])) {
128 if ([account connectivityBasedOnNetworkReachability]) {
129 NSString *host = [account host];
130 if (host && ![knownHosts containsObject:host]) {
131 [monitor addObserver:self forHost:host];
132 [knownHosts addObject:host];
135 //if this is an account we should auto-connect, remove it from accountsToNotConnect so that we auto-connect it.
136 if ([[account supportedPropertyKeys] containsObject:@"Online"]
137 && [[account preferenceForKey:@"AutoConnect" group:GROUP_ACCOUNT_STATUS] boolValue]) {
138 [accountsToNotConnect removeObject:account];
139 continue; //prevent the account from being removed from accountsToConnect.
142 } else if ([[account supportedPropertyKeys] containsObject:@"Online"]
143 && [[account preferenceForKey:@"AutoConnect" group:GROUP_ACCOUNT_STATUS] boolValue]) {
144 /* This account does not connect based on network reachability, but can go online
145 * and should autoconnect. Connect it immediately.
147 [account setPreference:[NSNumber numberWithBool:YES]
149 group:GROUP_ACCOUNT_STATUS];
152 [accountsToConnect removeObject:account];
156 //In 10.2, just auto connect 'em
157 [self autoConnectAccounts];
163 * @brief Network connectivity changed
165 * Connect or disconnect accounts as appropriate to the new network state.
167 * @param notification The object of the notification is an NSNumber indicating if the network is now available.
169 - (void)networkConnectivityChanged:(NSNotification *)notification
171 NSEnumerator *enumerator;
173 BOOL networkIsReachable;
177 networkIsReachable = [[notification object] boolValue];
179 networkIsReachable = [AINetworkConnectivity networkIsReachable];
182 //Connect or disconnect accounts in response to the connectivity change
183 enumerator = [[[adium accountController] accountArray] objectEnumerator];
184 while((account = [enumerator nextObject])){
185 if([account connectivityBasedOnNetworkReachability]){
186 [self handleConnectivityForAccount:account reachable:networkIsReachable];
192 * @brief Network connectivity changed
194 * Connect or disconnect accounts as appropriate to the new network state.
196 * @param networkIsReachable Indicates whether the given host is now reachable.
197 * @param host The host that is now reachable (or not).
199 - (void)hostReachabilityChanged:(BOOL)networkIsReachable forHost:(NSString *)host
201 NSEnumerator *enumerator;
204 //Connect or disconnect accounts in response to the connectivity change
205 enumerator = [[[adium accountController] accountArray] objectEnumerator];
206 while ((account = [enumerator nextObject])) {
207 if ([[account host] isEqualToString:host]) {
208 if (networkIsReachable && [accountsToNotConnect containsObject:account]) {
209 [accountsToNotConnect removeObject:account];
211 [self handleConnectivityForAccount:account reachable:networkIsReachable];
217 #pragma mark AIHostReachabilityObserver compliance
219 - (void)hostReachabilityMonitor:(AIHostReachabilityMonitor *)monitor hostIsReachable:(NSString *)host {
220 [self hostReachabilityChanged:YES forHost:host];
222 - (void)hostReachabilityMonitor:(AIHostReachabilityMonitor *)monitor hostIsNotReachable:(NSString *)host {
223 [self hostReachabilityChanged:NO forHost:host];
227 #pragma mark Connecting/Disconnecting Accounts
229 * @brief Connect or disconnect an account as appropriate to a new network reachable state
231 * This method uses the accountsToConnect collection to track which accounts were disconnected and should therefore be
234 * @param account The account to change if appropriate
235 * @param reachable The new network reachable state
237 - (void)handleConnectivityForAccount:(AIAccount *)account reachable:(BOOL)reachable
240 //If we are now online and are waiting to connect this account, do it if the account hasn't already
241 //been taken care of.
242 if([accountsToConnect containsObject:account]){
243 if(![account integerStatusObjectForKey:@"Online"] &&
244 ![account integerStatusObjectForKey:@"Connecting"] &&
245 ![[account preferenceForKey:@"Online" group:GROUP_ACCOUNT_STATUS] boolValue]){
247 [account setPreference:[NSNumber numberWithBool:YES] forKey:@"Online" group:GROUP_ACCOUNT_STATUS];
248 [accountsToConnect removeObject:account];
252 //If we are no longer online and this account is connected, disconnect it.
253 if (([account integerStatusObjectForKey:@"Online"] ||
254 [account integerStatusObjectForKey:@"Connecting"]) &&
255 ![account integerStatusObjectForKey:@"Disconnecting"] &&
256 [[account preferenceForKey:@"Online" group:GROUP_ACCOUNT_STATUS] boolValue]){
258 [account setPreference:[NSNumber numberWithBool:NO] forKey:@"Online" group:GROUP_ACCOUNT_STATUS];
259 [accountsToConnect addObject:account];
265 //Autoconnecting Accounts (at startup) ---------------------------------------------------------------------------------
266 #pragma mark Autoconnecting Accounts (at startup)
268 * @brief Auto connect accounts
270 * Automatically connect to accounts flagged with an auto connect property as soon as a network connection is available
272 - (void)autoConnectAccounts
274 NSEnumerator *enumerator;
277 //Determine the accounts which want to be autoconnected
278 enumerator = [[[adium accountController] accountArray] objectEnumerator];
279 while((account = [enumerator nextObject])){
280 if([[account supportedPropertyKeys] containsObject:@"Online"] &&
281 [[account preferenceForKey:@"AutoConnect" group:GROUP_ACCOUNT_STATUS] boolValue]){
283 //If basing connectivity on the network, add it to our array of accounts to connect;
284 //otherwise, sign it on immediately
285 if ([account connectivityBasedOnNetworkReachability]){
286 [accountsToConnect addObject:account];
288 [account setPreference:[NSNumber numberWithBool:YES]
290 group:GROUP_ACCOUNT_STATUS];
295 //Attempt to connect them immediately; if this fails, they will be connected when the network
297 if ([accountsToConnect count]){
298 [self networkConnectivityChanged:nil];
303 //Disconnect / Reconnect on sleep --------------------------------------------------------------------------------------
304 #pragma mark Disconnect/Reconnect On Sleep
306 * @brief System is sleeping
308 - (void)systemWillSleep:(NSNotification *)notification
310 //Disconnect all online accounts
311 if([self _accountsAreOnlineOrDisconnecting]){
312 NSEnumerator *enumerator = [[[adium accountController] accountArray] objectEnumerator];
315 while((account = [enumerator nextObject])){
316 if([[account supportedPropertyKeys] containsObject:@"Online"] &&
317 [[account preferenceForKey:@"Online" group:GROUP_ACCOUNT_STATUS] boolValue]){
319 //Disconnect the account and add it to our list to reconnect
320 [account setPreference:[NSNumber numberWithBool:NO] forKey:@"Online" group:GROUP_ACCOUNT_STATUS];
321 [accountsToConnect addObject:account];
326 //While some accounts disconnect immediately, others may need a second or two to finish the process. For
327 //these accounts we'll want to hold system sleep until they are ready. We monitor account status changes
328 //and will lift the hold once all accounts are finished.
329 if([self _accountsAreOnlineOrDisconnecting]){
330 [[NSNotificationCenter defaultCenter] postNotificationName:AISystemHoldSleep_Notification object:nil];
331 [[adium contactController] registerListObjectObserver:self];
336 * @brief Invoked when our accounts change status
338 * Once all accounts are offline we will remove our hold on system sleep
340 - (NSSet *)updateListObject:(AIListObject *)inObject keys:(NSSet *)inModifiedKeys silent:(BOOL)silent
342 if([inObject isKindOfClass:[AIAccount class]] && [inModifiedKeys containsObject:@"Online"]){
343 if(![self _accountsAreOnlineOrDisconnecting]){
344 [[adium contactController] unregisterListObjectObserver:self];
345 [[NSNotificationCenter defaultCenter] postNotificationName:AISystemContinueSleep_Notification object:nil];
353 * @brief Returns YES if any accounts are currently in the process of disconnecting
355 - (BOOL)_accountsAreOnlineOrDisconnecting
357 NSEnumerator *enumerator = [[[adium accountController] accountArray] objectEnumerator];
360 while((account = [enumerator nextObject])){
361 if([[account statusObjectForKey:@"Online"] boolValue] ||
362 [[account statusObjectForKey:@"Disconnecting"] boolValue]){
371 * @brief System is waking from sleep
373 - (void)systemDidWake:(NSNotification *)notification
375 NSEnumerator *enumerator;
378 //Immediately re-connect accounts which are ignoring the server reachability
379 enumerator = [[[adium accountController] accountArray] objectEnumerator];
380 while((account = [enumerator nextObject])){
381 if(![account connectivityBasedOnNetworkReachability] && [accountsToConnect containsObject:account]){
382 [account setPreference:[NSNumber numberWithBool:YES] forKey:@"Online" group:GROUP_ACCOUNT_STATUS];
383 [accountsToConnect removeObject:account];
387 if (![NSApp isOnPantherOrBetter]) {
388 /* Accounts which consider server reachability will re-connect when connectivity becomes available.
389 * Our callback is not always invoked upon waking, so call it manually to be safe.
390 * This is only necessary in 10.2
392 if([accountsToConnect count]){
393 [self networkConnectivityChanged:nil];