Merged [13632]: Patch from Gayle Laakmann to not append @gmail.com if @googlemail...
[adiumx.git] / Source / ESAccountNetworkConnectivityPlugin.m
blob1e961e882e422a2112f8a61667fc73df2fd6c12e
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         if (![NSEvent shiftKey]) {
112                 if ([NSApp isOnPantherOrBetter]) {
113                         //In Panther and better, we can rely on AIHostReachabilityMonitor
114                         AIHostReachabilityMonitor *monitor = [AIHostReachabilityMonitor defaultMonitor];
115                         
116                         NSArray *accounts = [[adium accountController] accountArray];
117                         
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];
126                         AIAccount *account;
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];
133                                         }
134                                         
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.
140                                         }
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.
146                                          */
147                                         [account setPreference:[NSNumber numberWithBool:YES] 
148                                                                         forKey:@"Online"
149                                                                          group:GROUP_ACCOUNT_STATUS];                                   
150                                 }
151                                 
152                                 [accountsToConnect removeObject:account];
153                         }
154                         
155                 } else {
156                         //In 10.2, just auto connect 'em
157                         [self autoConnectAccounts];
158                 }
159         }
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.
168  */
169 - (void)networkConnectivityChanged:(NSNotification *)notification
171         NSEnumerator    *enumerator;
172         AIAccount               *account;
173         BOOL                    networkIsReachable;
175         //
176         if(notification){
177                 networkIsReachable = [[notification object] boolValue];
178         }else{
179                 networkIsReachable = [AINetworkConnectivity networkIsReachable];
180         }
181         
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];
187                 }
188         }       
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).
198  */
199 - (void)hostReachabilityChanged:(BOOL)networkIsReachable forHost:(NSString *)host
201         NSEnumerator    *enumerator;
202         AIAccount               *account;
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];
210                         } else {
211                                 [self handleConnectivityForAccount:account reachable:networkIsReachable];
212                         }
213                 }
214         }
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
232  * later reconnected.
234  * @param account The account to change if appropriate
235  * @param reachable The new network reachable state
236  */
237 - (void)handleConnectivityForAccount:(AIAccount *)account reachable:(BOOL)reachable
239         if(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];
249                         }
250                 }
251         }else{
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];
260                 }
261         }
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
271  */
272 - (void)autoConnectAccounts
274     NSEnumerator        *enumerator;
275     AIAccount           *account;
276         
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];
287                         }else{
288                                 [account setPreference:[NSNumber numberWithBool:YES] 
289                                                                 forKey:@"Online"
290                                                                  group:GROUP_ACCOUNT_STATUS];
291                         }
292                 }
293         }
295         //Attempt to connect them immediately; if this fails, they will be connected when the network
296         //becomes available.
297         if ([accountsToConnect count]){                 
298                 [self networkConnectivityChanged:nil];
299         }
303 //Disconnect / Reconnect on sleep --------------------------------------------------------------------------------------
304 #pragma mark Disconnect/Reconnect On Sleep
306  * @brief System is sleeping
307  */
308 - (void)systemWillSleep:(NSNotification *)notification
310         //Disconnect all online accounts
311         if([self _accountsAreOnlineOrDisconnecting]){
312                 NSEnumerator    *enumerator = [[[adium accountController] accountArray] objectEnumerator];
313                 AIAccount               *account;
314                 
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];
322                         }
323                 }
324         }
325                 
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];
332         }
336  * @brief Invoked when our accounts change status
338  * Once all accounts are offline we will remove our hold on system sleep
339  */
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];
346                 }
347         }
348         
349         return(nil);
353  * @brief Returns YES if any accounts are currently in the process of disconnecting
354  */
355 - (BOOL)_accountsAreOnlineOrDisconnecting
357     NSEnumerator        *enumerator = [[[adium accountController] accountArray] objectEnumerator];
358         AIAccount               *account;
359     
360         while((account = [enumerator nextObject])){
361                 if([[account statusObjectForKey:@"Online"] boolValue] ||
362                    [[account statusObjectForKey:@"Disconnecting"] boolValue]){
363                         return(YES);
364                 }
365         }
366         
367         return(NO);
371  * @brief System is waking from sleep
372  */
373 - (void)systemDidWake:(NSNotification *)notification
375         NSEnumerator    *enumerator;
376         AIAccount               *account;
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];
384                 }
385         }
386         
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
391                 */
392                 if([accountsToConnect count]){
393                         [self networkConnectivityChanged:nil];
394                 }
395         }
398 @end