Added the Adium IB palette. Currently contains one object: {{{AITextColorPreviewView}}}.
[adiumx.git] / Source / ESAccountNetworkConnectivityPlugin.m
blob91297f69463a6b3832c8d37d018cc151d74354e3
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/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 - (BOOL)shouldAutoconnectAllEnabled
91         NSUserDefaults  *userDefaults = [NSUserDefaults standardUserDefaults];
92         NSNumber                *didAutoconnectAll = [userDefaults objectForKey:@"Adium 1.0 First Time:Autoconnected All"];
93         BOOL                    shouldAutoconnectAllEnabled = NO;
94         
95         if (!didAutoconnectAll) {
96                 [userDefaults setObject:[NSNumber numberWithBool:YES] forKey:@"Adium 1.0 First Time:Autoconnected All"];
97                 [userDefaults synchronize];
98                 shouldAutoconnectAllEnabled = YES;
99         }
100         
101         return shouldAutoconnectAllEnabled;
105  * @brief Adium finished launching
107  * Attempt to autoconnect accounts if shift is not being pressed
108  */
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];
115         
116         //Start off forbidding all accounts from auto-connecting.
117         accountsToConnect    = [[NSMutableSet alloc] initWithArray:accounts];
118         accountsToNotConnect = [accountsToConnect mutableCopy];
119         knownHosts                       = [[NSMutableSet alloc] init];
120         
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.
123          */
124         NSEnumerator    *accountsEnum;
125         AIAccount               *account;
126         
127         accountsEnum = [accounts objectEnumerator];
128         while ((account = [accountsEnum nextObject])) {
129                 BOOL    connectAccount = (!shiftHeld  &&
130                                                                   [account enabled] &&
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];
139                         }
140                         
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.
145                         }
146                         
147                 }  else if (connectAccount) {
148                         /* This account does not connect based on network reachability, but should autoconnect.
149                          * Connect it immediately.
150                          */
151                         [account setShouldBeOnline:YES];
152                 }
153                 
154                 [accountsToConnect removeObject:account];
155         }
156         
157         [knownHosts release];
158         
159         //Watch for future changes to our account list
160         [[adium notificationCenter] addObserver:self
161                                                                    selector:@selector(accountListChanged:)
162                                                                            name:Account_ListChanged
163                                                                          object:nil];
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).
173  */
174 - (void)hostReachabilityChanged:(BOOL)networkIsReachable forHost:(NSString *)host
176         NSEnumerator    *enumerator;
177         AIAccount               *account;
178         
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];
184                 } else {
185                         if ([[account host] isEqualToString:host]) {
186                                 [self handleConnectivityForAccount:account reachable:networkIsReachable];
187                         }
188                 }
189         }
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
206  * later reconnected.
208  * @param account The account to change if appropriate
209  * @param reachable The new network reachable state
210  */
211 - (void)handleConnectivityForAccount:(AIAccount *)account reachable:(BOOL)reachable
213         AILog(@"handleConnectivityForAccount: %@ reachable: %i",account,reachable);
215         if (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];
223                         }
224                 }
225         } else {
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];
232                 }
233         }
236 //Disconnect / Reconnect on sleep --------------------------------------------------------------------------------------
237 #pragma mark Disconnect/Reconnect On Sleep
239  * @brief System is sleeping
240  */
241 - (void)systemWillSleep:(NSNotification *)notification
243         //Disconnect all online accounts
244         if ([self _accountsAreOnlineOrDisconnecting]) {
245                 NSEnumerator    *enumerator = [[[adium accountController] accounts] objectEnumerator];
246                 AIAccount               *account;
247                 
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];
253                         }
254                 }
255         }
256                 
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];
263         }
267  * @brief Invoked when our accounts change status
269  * Once all accounts are offline we will remove our hold on system sleep
270  */
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];
277                 }
278         }
279         
280         return nil;
284  * @brief Returns YES if any accounts are currently in the process of disconnecting
285  */
286 - (BOOL)_accountsAreOnlineOrDisconnecting
288     NSEnumerator        *enumerator = [[[adium accountController] accounts] objectEnumerator];
289         AIAccount               *account;
290     
291         while ((account = [enumerator nextObject])) {
292                 if ([account online] ||
293                    [[account statusObjectForKey:@"Disconnecting"] boolValue]) {
294                         return YES;
295                 }
296         }
297         
298         return NO;
302  * @brief System is waking from sleep
303  */
304 - (void)systemDidWake:(NSNotification *)notification
306         NSEnumerator    *enumerator;
307         AIAccount               *account;
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];
315                 }
316         }
319 #pragma mark Changes to the account list
321  * @brief When the account list changes, ensure we're monitoring for each account
322  */
323 - (void)accountListChanged:(NSNotification *)notification
325         NSEnumerator    *enumerator;
326         AIAccount               *account;
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];
334                         
335                         if (host &&
336                                 ![monitor observer:self isObservingHost:host]) {
337                                 [monitor addObserver:self forHost:host];
338                         }
339                 }
340         }
343 @end