* Adium is the legal property of its developers, whose names are listed in the copyright file included * with this source distribution. * This program is free software; you can redistribute it and/or modify it under the terms of the GNU * General Public License as published by the Free Software Foundation; either version 2 of the License, * or (at your option) any later version. * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * Public License for more details. * You should have received a copy of the GNU General Public License along with this program; if not, * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #import <Adium/AIAccountControllerProtocol.h> #import <Adium/AIContactControllerProtocol.h> #import "ESAccountNetworkConnectivityPlugin.h" #import <AIUtilities/AIEventAdditions.h> #import <AIUtilities/AIHostReachabilityMonitor.h> #import <AIUtilities/AISleepNotification.h> #import <Adium/AIAccount.h> #import <Adium/AIListObject.h> @interface ESAccountNetworkConnectivityPlugin () - (void)handleConnectivityForAccount:(AIAccount *)account reachable:(BOOL)reachable; - (BOOL)_accountsAreOnlineOrDisconnecting:(BOOL)considerConnecting; - (void)adiumFinishedLaunching:(NSNotification *)notification; - (void)systemWillSleep:(NSNotification *)notification; - (void)systemDidWake:(NSNotification *)notification; - (void)accountListChanged:(NSNotification *)notification; * @class ESAccountNetworkConnectivityPlugin * @brief Handle account connection and disconnection * Accounts are automatically connected and disconnected based on: * | If the account is enabled (at Adium launch if the network is available) * | Network connectivity (disconnect when the Internet is not available and connect when it is available again) * | System sleep (disconnect when the system sleeps and connect when it wakes up) * Uses AIHostReachabilityMonitor and AISleepNotification from AIUtilities. @implementation ESAccountNetworkConnectivityPlugin //Wait for Adium to finish launching to handle autoconnecting enabled accounts [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(adiumFinishedLaunching:) name:AIApplicationDidFinishLoadingNotification NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter]; //Monitor system sleep so we can cleanly disconnect / reconnect [notificationCenter addObserver:self selector:@selector(systemWillSleep:) name:AISystemWillSleep_Notification [notificationCenter addObserver:self selector:@selector(systemDidWake:) name:AISystemDidWake_Notification [[AIContactObserverManager sharedManager] registerListObjectObserver:self]; * @brief Uninstall plugin [[NSNotificationCenter defaultCenter] removeObserver:self]; [[AIContactObserverManager sharedManager] unregisterListObjectObserver:self]; [accountsToConnect release]; [accountsToNotConnect release]; * @brief Adium finished launching * Attempt to autoconnect accounts if shift is not being pressed - (void)adiumFinishedLaunching:(NSNotification *)notification NSArray *accounts = adium.accountController.accounts; AIHostReachabilityMonitor *monitor = [AIHostReachabilityMonitor defaultMonitor]; BOOL shiftHeld = [NSEvent shiftKey]; //Start off forbidding all accounts from auto-connecting. accountsToConnect = [[NSMutableSet alloc] initWithArray:accounts]; accountsToNotConnect = [accountsToConnect mutableCopy]; knownHosts = [[NSMutableSet alloc] init]; /* Add ourselves to the default host-reachability monitor as an observer for each account's host. * At the same time, weed accounts that are to be auto-connected out of the accountsToNotConnect set. for (account in accounts) { BOOL connectAccount = (!shiftHeld && [[account preferenceForKey:KEY_AUTOCONNECT group:GROUP_ACCOUNT_STATUS] boolValue]); [account connectivityBasedOnNetworkReachability]) { NSString *host = [account host]; if (host && ![knownHosts containsObject:host]) { [monitor addObserver:self forHost:host]; [knownHosts addObject:host]; //If this is an account we should auto-connect, remove it from accountsToNotConnect so that we auto-connect it. [accountsToNotConnect removeObject:account]; [account setValue:[NSNumber numberWithBool:YES] forProperty:@"isWaitingForNetwork" notify:NotifyNow]; continue; //prevent the account from being removed from accountsToConnect. } else if (connectAccount) { /* This account does not connect based on network reachability, but should autoconnect. * Connect it immediately. [account setShouldBeOnline:YES]; [accountsToConnect removeObject:account]; //Watch for future changes to our account list [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(accountListChanged:) [[NSNotificationCenter defaultCenter] postNotificationName:AINetworkDidChangeNotification * @brief Network connectivity changed * Connect or disconnect accounts as appropriate to the new network state. * @param networkIsReachable Indicates whether the given host is now reachable. * @param host The host that is now reachable (or not). - (void)hostReachabilityChanged:(BOOL)networkIsReachable forHost:(NSString *)host //Connect or disconnect accounts in response to the connectivity change for (AIAccount *account in adium.accountController.accounts) { if (networkIsReachable && [accountsToNotConnect containsObject:account]) { [accountsToNotConnect removeObject:account]; if ([[account host] isEqualToString:host]) { [self handleConnectivityForAccount:account reachable:networkIsReachable]; //Collate reachability changes for multiple hosts into a single notification [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(networkDidChange) [self performSelector:@selector(networkDidChange) withObject:nil afterDelay:1.0]; #pragma mark AIHostReachabilityObserver compliance - (void)hostReachabilityMonitor:(AIHostReachabilityMonitor *)monitor hostIsReachable:(NSString *)host { [self hostReachabilityChanged:YES forHost:host]; - (void)hostReachabilityMonitor:(AIHostReachabilityMonitor *)monitor hostIsNotReachable:(NSString *)host { [self hostReachabilityChanged:NO forHost:host]; #pragma mark Connecting/Disconnecting Accounts * @brief Connect or disconnect an account as appropriate to a new network reachable state * This method uses the accountsToConnect collection to track which accounts were disconnected and should therefore be * @param account The account to change if appropriate * @param reachable The new network reachable state - (void)handleConnectivityForAccount:(AIAccount *)account reachable:(BOOL)reachable AILog(@"handleConnectivityForAccount: %@ reachable: %i",account,reachable); //If we are now online and are waiting to connect this account, do it if the account hasn't already [account setValue:nil forProperty:@"isWaitingForNetwork" notify:NotifyNow]; if ([accountsToConnect containsObject:account] || [account valueForProperty:@"waitingToReconnect"]) { ![account boolValueForProperty:@"isConnecting"]) { [account setShouldBeOnline:YES]; [accountsToConnect removeObject:account]; //If we are no longer online and this account is connected, disconnect it. [account setValue:[NSNumber numberWithBool:YES] forProperty:@"isWaitingForNetwork" notify:NotifyNow]; [account boolValueForProperty:@"isConnecting"]) && ![account boolValueForProperty:@"isDisconnecting"]) { [account disconnectFromDroppedNetworkConnection]; [accountsToConnect addObject:account]; //Disconnect / Reconnect on sleep -------------------------------------------------------------------------------------- #pragma mark Disconnect/Reconnect On Sleep * @brief System is sleeping - (void)systemWillSleep:(NSNotification *)notification AILog(@"***** System sleeping..."); //Disconnect all online or connecting accounts if ([self _accountsAreOnlineOrDisconnecting:YES]) { for (AIAccount *account in adium.accountController.accounts) { [account boolValueForProperty:@"isConnecting"] || [account valueForProperty:@"waitingToReconnect"]) { // Disconnect the account if it's online // Cancel any reconnect attempts } else if ([account valueForProperty:@"waitingToReconnect"]) { [account cancelAutoReconnect]; // Add it to our list to reconnect [accountsToConnect addObject:account]; //While some accounts disconnect immediately, others may need a second or two to finish the process. For //these accounts we'll want to hold system sleep until they are ready. We monitor account status changes //and will lift the hold once all accounts are finished. //Don't delay sleep for connecting or reconnecting accounts if ([self _accountsAreOnlineOrDisconnecting:NO]) { AILog(@"Posting AISystemHoldSleep_Notification..."); [[NSNotificationCenter defaultCenter] postNotificationName:AISystemHoldSleep_Notification object:nil]; * @brief Invoked when our accounts change status * Once all accounts are offline we will remove our hold on system sleep - (NSSet *)updateListObject:(AIListObject *)inObject keys:(NSSet *)inModifiedKeys silent:(BOOL)silent if ([inObject isKindOfClass:[AIAccount class]]) { [inModifiedKeys containsObject:@"isOnline"]) { //Don't delay sleep for connecting or reconnecting accounts if (![self _accountsAreOnlineOrDisconnecting:NO]) { AILog(@"Posting AISystemContinueSleep_Notification..."); [[NSNotificationCenter defaultCenter] postNotificationName:AISystemContinueSleep_Notification object:nil]; AILog(@"Continuing to wait to sleep..."); if ([inModifiedKeys containsObject:@"Enabled"]) { AIAccount *account = (AIAccount *)inObject; //Start observing for this host if we're not already if ([account connectivityBasedOnNetworkReachability]) { NSString *host = [account host]; AIHostReachabilityMonitor *monitor = [AIHostReachabilityMonitor defaultMonitor]; [account setValue:[NSNumber numberWithBool:YES] forProperty:@"isWaitingForNetwork" notify:NotifyNow]; ![monitor observer:self isObservingHost:host]) { [monitor addObserver:self forHost:host]; BOOL enabledAccountUsingThisHost = NO; NSString *thisHost = [account host]; [account setValue:nil forProperty:@"isWaitingForNetwork" notify:NotifyNow]; //Check if any enabled accounts are still using this now-disabled account's host for (AIAccount *loopAccount in adium.accountController.accounts) { if (loopAccount.enabled && loopAccount.connectivityBasedOnNetworkReachability) { if ([thisHost caseInsensitiveCompare:loopAccount.host] == NSOrderedSame) { enabledAccountUsingThisHost = YES; //If not, stop observing it entirely if (!enabledAccountUsingThisHost) { AIHostReachabilityMonitor *monitor = [AIHostReachabilityMonitor defaultMonitor]; [monitor removeObserver:self forHost:thisHost]; * @brief Returns YES if any accounts are currently in the process of disconnecting * @param considerConnecting Consider accounts which are connecting or waiting to reconnect - (BOOL)_accountsAreOnlineOrDisconnecting:(BOOL)considerConnecting for (AIAccount *account in adium.accountController.accounts) { [account boolValueForProperty:@"isDisconnecting"]) { AILog(@"%@ (and possibly others) is still %@",account, (account.online ? @"online" : @"disconnecting")); } else if (considerConnecting && ([account boolValueForProperty:@"isConnecting"] || [account valueForProperty:@"waitingToReconnect"])) { * @brief System is waking from sleep - (void)systemDidWake:(NSNotification *)notification AILog(@"***** System did wake..."); /* We could have been waiting to sleep but then timed out and slept anyways; clear the flag if that happened and it wasn't cleared * in updateListObject::: above. //Immediately re-connect accounts which are ignoring the server reachability for (AIAccount *account in adium.accountController.accounts) { if (![account connectivityBasedOnNetworkReachability] && [accountsToConnect containsObject:account]) { [account setShouldBeOnline:YES]; [accountsToConnect removeObject:account]; } else if ([accountsToConnect containsObject:account]) { [account setValue:[NSNumber numberWithBool:YES] forProperty:@"isWaitingForNetwork" notify:NotifyNow]; #pragma mark Changes to the account list * @brief When the account list changes, ensure we're monitoring for each account - (void)accountListChanged:(NSNotification *)notification AIHostReachabilityMonitor *monitor = [AIHostReachabilityMonitor defaultMonitor]; //Immediately re-connect accounts which are ignoring the server reachability for (AIAccount *account in adium.accountController.accounts) { [account connectivityBasedOnNetworkReachability]) { NSString *host = [account host]; ![monitor observer:self isObservingHost:host]) { [monitor addObserver:self forHost:host];