{{{
[adiumx.git] / Source / ESAccountNetworkConnectivityPlugin.m
blobf8cbfc4d6ebd73ab0e75d55e91ce0c37b4478de9
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 <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;
29 @end
31 /*!
32  * @class ESAccountNetworkConnectivityPlugin
33  * @brief Handle account connection and disconnection
34  *
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)
39  *
40  * Uses AIHostReachabilityMonitor and AISleepNotification from AIUtilities.
41  */
42 @implementation ESAccountNetworkConnectivityPlugin
44 /*!
45  * @brief Install plugin
46  */
47 - (void)installPlugin
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
53                                                                          object:nil];
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
61                                                          object:nil];
62     [notificationCenter addObserver:self
63                                                    selector:@selector(systemDidWake:)
64                                                            name:AISystemDidWake_Notification
65                                                          object:nil];
68 /*!
69  * @brief Uninstall plugin
70  */
71 - (void)uninstallPlugin
73         [[adium           notificationCenter] removeObserver:self];
74         [[NSNotificationCenter defaultCenter] removeObserver:self];
75         [[adium contactController] unregisterListObjectObserver:self];
78 /*!
79  * @brief Deallocate
80  */
81 - (void)dealloc
83         [accountsToConnect    release];
84         [accountsToNotConnect release];
86         [super dealloc];
89 /*!
90  * @brief Adium finished launching
91  *
92  * Attempt to autoconnect accounts if shift is not being pressed
93  */
94 - (void)adiumFinishedLaunching:(NSNotification *)notification
96         NSArray                                         *accounts = [[adium accountController] accounts];
97         AIHostReachabilityMonitor       *monitor = [AIHostReachabilityMonitor defaultMonitor];
98         BOOL                                            shiftHeld = [NSEvent shiftKey];
99         
100         //Start off forbidding all accounts from auto-connecting.
101         accountsToConnect    = [[NSMutableSet alloc] initWithArray:accounts];
102         accountsToNotConnect = [accountsToConnect mutableCopy];
103         knownHosts                       = [[NSMutableSet alloc] init];
104         
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.
107          */
108         NSEnumerator    *accountsEnum;
109         AIAccount               *account;
110         
111         accountsEnum = [accounts objectEnumerator];
112         while ((account = [accountsEnum nextObject])) {
113                 BOOL    connectAccount = (!shiftHeld  &&
114                                                                   [account enabled] &&
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];
123                         }
124                         
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.
129                         }
130                         
131                 }  else if (connectAccount) {
132                         /* This account does not connect based on network reachability, but should autoconnect.
133                          * Connect it immediately.
134                          */
135                         [account setShouldBeOnline:YES];
136                 }
137                 
138                 [accountsToConnect removeObject:account];
139         }
140         
141         [knownHosts release];
142         
143         //Watch for future changes to our account list
144         [[adium notificationCenter] addObserver:self
145                                                                    selector:@selector(accountListChanged:)
146                                                                            name:Account_ListChanged
147                                                                          object:nil];
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).
157  */
158 - (void)hostReachabilityChanged:(BOOL)networkIsReachable forHost:(NSString *)host
160         NSEnumerator    *enumerator;
161         AIAccount               *account;
162         
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];
168                 } else {
169                         if ([[account host] isEqualToString:host]) {
170                                 [self handleConnectivityForAccount:account reachable:networkIsReachable];
171                         }
172                 }
173         }
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
190  * later reconnected.
192  * @param account The account to change if appropriate
193  * @param reachable The new network reachable state
194  */
195 - (void)handleConnectivityForAccount:(AIAccount *)account reachable:(BOOL)reachable
197         AILog(@"handleConnectivityForAccount: %@ reachable: %i",account,reachable);
199         if (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];
207                         }
208                 }
209         } else {
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];
216                 }
217         }
220 //Disconnect / Reconnect on sleep --------------------------------------------------------------------------------------
221 #pragma mark Disconnect/Reconnect On Sleep
223  * @brief System is sleeping
224  */
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];
231                 AIAccount               *account;
232                 
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];
238                         }
239                 }
240         }
241                 
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];
248         }
252  * @brief Invoked when our accounts change status
254  * Once all accounts are offline we will remove our hold on system sleep
255  */
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];
262                 }
263         }
264         
265         return nil;
269  * @brief Returns YES if any accounts are currently in the process of disconnecting
270  */
271 - (BOOL)_accountsAreOnlineOrDisconnecting
273     NSEnumerator        *enumerator = [[[adium accountController] accounts] objectEnumerator];
274         AIAccount               *account;
275     
276         while ((account = [enumerator nextObject])) {
277                 if ([account online] ||
278                    [[account statusObjectForKey:@"Disconnecting"] boolValue]) {
279                         return YES;
280                 }
281         }
282         
283         return NO;
287  * @brief System is waking from sleep
288  */
289 - (void)systemDidWake:(NSNotification *)notification
291         NSEnumerator    *enumerator;
292         AIAccount               *account;
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];
302                 }
303         }
306 #pragma mark Changes to the account list
308  * @brief When the account list changes, ensure we're monitoring for each account
309  */
310 - (void)accountListChanged:(NSNotification *)notification
312         NSEnumerator    *enumerator;
313         AIAccount               *account;
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];
321                         
322                         if (host &&
323                                 ![monitor observer:self isObservingHost:host]) {
324                                 [monitor addObserver:self forHost:host];
325                         }
326                 }
327         }
330 @end