Merged [15040]: Trying some magic: 5 seconds after the last unreachable host is repor...
[adiumx.git] / Source / ESAccountNetworkConnectivityPlugin.m
blobf206fe6971d9e7a92700380cb9a330b7ceacea87
1 /* 
2  * Adium is the legal property of its developers, whose names are listed in the copyright file included
3  * with this source distribution.
4  * 
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.
8  * 
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.
12  * 
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.
15  */
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;
33 @end
35 /*!
36  * @class ESAccountNetworkConnectivityPlugin
37  * @brief Handle account connection and disconnection
38  *
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)
43  *
44  * Uses AINetworkConnectivity and AISleepNotification from AIUtilities.
45  */
46 @implementation ESAccountNetworkConnectivityPlugin
48 /*!
49  * @brief Install plugin
50  */
51 - (void)installPlugin
53         //Wait for Adium to finish launching to handle autoconnecting accounts
54         [[adium notificationCenter] addObserver:self
55                                                                    selector:@selector(adiumFinishedLaunching:)
56                                                                            name:Adium_CompletedApplicationLoad
57                                                                          object:nil];
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.
62                  */
63                 accountsToConnect = [[NSMutableSet alloc] init];
64                 accountsToNotConnect = nil;
65                 
66                 [[NSNotificationCenter defaultCenter] addObserver:self
67                                                                                                  selector:@selector(networkConnectivityChanged:)
68                                                                                                          name:AINetwork_ConnectivityChanged
69                                                                                                    object:nil]; 
70         }
71         
72         //Monitor system sleep so we can cleanly disconnect / reconnect
73     [[NSNotificationCenter defaultCenter] addObserver:self
74                                              selector:@selector(systemWillSleep:)
75                                                  name:AISystemWillSleep_Notification
76                                                object:nil];
77     [[NSNotificationCenter defaultCenter] addObserver:self
78                                              selector:@selector(systemDidWake:)
79                                                  name:AISystemDidWake_Notification
80                                                object:nil];
83 /*!
84  * @brief Uninstall plugin
85  */
86 - (void)uninstallPlugin
88         [[adium notificationCenter] removeObserver:self];
89         [[adium contactController] unregisterListObjectObserver:self];
90         [[NSNotificationCenter defaultCenter] removeObserver:self];
93 /*!
94  * @brief Deallocate
95  */
96 - (void)dealloc
98         [accountsToConnect release];
99         [accountsToNotConnect release];
101         [super dealloc];
105 * @brief Adium finished launching
107  * Attempt to autoconnect accounts if shift is not being pressed
108  */
109 - (void)adiumFinishedLaunching:(NSNotification *)notification
111         BOOL shiftKeyHeld = [NSEvent shiftKey];
112         
113         if ([NSApp isOnPantherOrBetter]) {
114                 //In Panther and better, we can rely on AIHostReachabilityMonitor
115                 AIHostReachabilityMonitor *monitor = [AIHostReachabilityMonitor defaultMonitor];
116                 
117                 NSArray *accounts = [[adium accountController] accountArray];
118                 
119                 //start off forbidding all accounts from auto-connecting.
120                 accountsToConnect    = [[NSMutableSet alloc] initWithArray:accounts];
121                 accountsToNotConnect = [accountsToConnect mutableCopy];
122                 knownHosts                       = [[NSMutableSet alloc] init];
123                 
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];
127                 AIAccount *account;
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];
134                                 }
135                                 
136                                 //if this is an account we should auto-connect, remove it from accountsToNotConnect so that we auto-connect it.
137                                 if (!shiftKeyHeld &&
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.
142                                 }
143                                 
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.
149                                 */
150                                 [account setPreference:[NSNumber numberWithBool:YES] 
151                                                                 forKey:@"Online"
152                                                                  group:GROUP_ACCOUNT_STATUS];                                   
153                         }
154                         
155                         [accountsToConnect removeObject:account];
156                 }
157         
158                 //Watch for future changes to our account list
159                 [[adium notificationCenter] addObserver:self
160                                                                            selector:@selector(accountListChanged:)
161                                                                                    name:Account_ListChanged
162                                                                                  object:nil];           
163         } else {
164                 //In 10.2, just auto connect 'em
165                 if (!shiftKeyHeld) {
166                         [self autoConnectAccounts];
167                 }
168         }       
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.
177  */
178 - (void)networkConnectivityChanged:(NSNotification *)notification
180         NSEnumerator    *enumerator;
181         AIAccount               *account;
182         BOOL                    networkIsReachable;
184         //
185         if(notification){
186                 networkIsReachable = [[notification object] boolValue];
187         }else{
188                 networkIsReachable = [AINetworkConnectivity networkIsReachable];
189         }
190         
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];
196                 }
197         }       
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).
207  */
208 - (void)hostReachabilityChanged:(BOOL)networkIsReachable forHost:(NSString *)host
210         NSEnumerator    *enumerator;
211         AIAccount               *account;
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];
219                         } else {
220                                 [self handleConnectivityForAccount:account reachable:networkIsReachable];
221                         }
222                 }
223         }
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
241  * later reconnected.
243  * @param account The account to change if appropriate
244  * @param reachable The new network reachable state
245  */
246 - (void)handleConnectivityForAccount:(AIAccount *)account reachable:(BOOL)reachable
248         if(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];
258                         }
259                 }
260         }else{
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];
269                 }
270         }
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
280  */
281 - (void)autoConnectAccounts
283     NSEnumerator        *enumerator;
284     AIAccount           *account;
285         
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];
296                         }else{
297                                 [account setPreference:[NSNumber numberWithBool:YES] 
298                                                                 forKey:@"Online"
299                                                                  group:GROUP_ACCOUNT_STATUS];
300                         }
301                 }
302         }
304         //Attempt to connect them immediately; if this fails, they will be connected when the network
305         //becomes available.
306         if ([accountsToConnect count]){                 
307                 [self networkConnectivityChanged:nil];
308         }
312 //Disconnect / Reconnect on sleep --------------------------------------------------------------------------------------
313 #pragma mark Disconnect/Reconnect On Sleep
315  * @brief System is sleeping
316  */
317 - (void)systemWillSleep:(NSNotification *)notification
319         //Disconnect all online accounts
320         if([self _accountsAreOnlineOrDisconnecting]){
321                 NSEnumerator    *enumerator = [[[adium accountController] accountArray] objectEnumerator];
322                 AIAccount               *account;
323                 
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];
331                         }
332                 }
333         }
334                 
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];
341         }
345  * @brief Invoked when our accounts change status
347  * Once all accounts are offline we will remove our hold on system sleep
348  */
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];
355                 }
356         }
357         
358         return(nil);
362  * @brief Returns YES if any accounts are currently in the process of disconnecting
363  */
364 - (BOOL)_accountsAreOnlineOrDisconnecting
366     NSEnumerator        *enumerator = [[[adium accountController] accountArray] objectEnumerator];
367         AIAccount               *account;
368     
369         while((account = [enumerator nextObject])){
370                 if([[account statusObjectForKey:@"Online"] boolValue] ||
371                    [[account statusObjectForKey:@"Disconnecting"] boolValue]){
372                         return(YES);
373                 }
374         }
375         
376         return(NO);
380  * @brief System is waking from sleep
381  */
382 - (void)systemDidWake:(NSNotification *)notification
384         NSEnumerator    *enumerator;
385         AIAccount               *account;
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];
393                 }
394         }
395         
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
400                 */
401                 if([accountsToConnect count]){
402                         [self networkConnectivityChanged:nil];
403                 }
404         }
407 #pragma mark Changes to the account list
409  * @brief When the account list changes, ensure we're monitoring for each account
410  */
411 - (void)accountListChanged:(NSNotification *)notification
413         NSEnumerator                            *enumerator;
414         AIAccount                                       *account;
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];
422                         
423                         if (host &&
424                                 ![monitor observer:self isObservingHost:host]) {
425                                 [monitor addObserver:self forHost:host];
426                         }
427                 }
428         }
431 @end