/// <summary>
        /// Connects to the specified hub (or another hub if this hub is redirecting)
        /// </summary>
        /// <returns>The last web channel it was able to connect to after processing redirections.</returns>
        private static async Task <HubConnectionStatus> Connect(ServerConfiguration configuration)
        {
            WebChannel               channel            = null;
            bool                     canConnect         = true;
            HubStatus                status             = HubStatus.Maintenance; //a reasonable default.
            string                   statusMessage      = null;
            Guid?                    serverRepositoryId = null;
            DateTimeOffset?          expirationDt       = null;
            Version                  protocolVersion    = new Version(0, 0);
            NetworkConnectionOptions agentLiveStream    = null;
            NetworkConnectionOptions clientLiveStream   = null;

            //first, is it a valid config?  No point in trying to connect if it's a bum config.
            HubConnectionStatus connectionStatus;

            try
            {
                configuration.Validate();
            }
            catch (Exception ex)
            {
                canConnect       = false;
                statusMessage    = "Invalid configuration: " + ex.Message;
                connectionStatus = new HubConnectionStatus(configuration, false, status, statusMessage);
                return(connectionStatus);
            }

            //and now try to connect to the server
            try
            {
                channel = CreateChannel(configuration);
                var configurationGetRequest = new HubConfigurationGetRequest();
                await channel.ExecuteRequest(configurationGetRequest, 1)
                .ConfigureAwait(false);     //we'd like it to succeed, so we'll give it one retry

                var configurationXml = configurationGetRequest.Configuration;

                //now, if we got back a redirect we need to go THERE to get the status.
                if (configurationXml.redirectRequested)
                {
                    //recursively try again.
                    channel.Dispose();
                    connectionStatus = await Connect(configurationXml.ToServerConfiguration()).ConfigureAwait(false);
                }
                else
                {
                    //set the right status message
                    status = (HubStatus)configurationXml.status;

                    switch (status)
                    {
                    case HubStatus.Available:
                        break;

                    case HubStatus.Expired:
                        statusMessage = "The Server's license has expired.  " + (configuration.UseGibraltarService
                                    ? "You can reactivate your license in seconds at www.GibraltarSoftware.com."
                                    : "To renew your license, run the Administration tool on the Loupe Server."
                                                                                 );
                        break;

                    case HubStatus.Maintenance:
                        statusMessage =
                            "The Server is currently undergoing maintenance and can't process requests.";
                        break;

                    default:
                        throw new ArgumentOutOfRangeException("status");
                    }

                    if (configurationXml.id != null)
                    {
                        serverRepositoryId = new Guid(configurationXml.id);
                    }

                    if (configurationXml.expirationDt != null)
                    {
                        expirationDt = DataConverter.FromDateTimeOffsetXml(configurationXml.expirationDt);
                    }

                    string publicKey = configurationXml.publicKey;

                    if (string.IsNullOrEmpty(configurationXml.protocolVersion) == false)
                    {
                        protocolVersion = new Version(configurationXml.protocolVersion);
                    }

                    LiveStreamServerXml liveStreamConfig = configurationXml.liveStream;
                    if (liveStreamConfig != null)
                    {
                        agentLiveStream = new NetworkConnectionOptions
                        {
                            HostName = channel.HostName, Port = liveStreamConfig.agentPort,
                            UseSsl   = liveStreamConfig.useSsl
                        };
                        clientLiveStream = new NetworkConnectionOptions
                        {
                            HostName = channel.HostName, Port = liveStreamConfig.clientPort,
                            UseSsl   = liveStreamConfig.useSsl
                        };
                    }

                    //We've connected for sure, time to set up our connection status to return to our caller with the full connection info
                    connectionStatus = new HubConnectionStatus(configuration, channel,
                                                               new HubRepository(expirationDt, serverRepositoryId, protocolVersion, publicKey, agentLiveStream,
                                                                                 clientLiveStream),
                                                               true, status, statusMessage);
                }
            }
            catch (WebChannelFileNotFoundException)
            {
                canConnect = false;
                if (configuration.UseGibraltarService)
                {
                    //we'll treat file not found (e.g. customer never existed) as expired to get the right UI behavior.
                    status        = HubStatus.Expired;
                    statusMessage = "The specified customer name is not valid";
                }
                else
                {
                    statusMessage = "No service could be found with the provided information";
                }

                connectionStatus = new HubConnectionStatus(configuration, false, status, statusMessage);
            }
            catch (WebChannelBadRequestException)
            {
                canConnect = false;
                //we want to be somewhat more intelligent in our responses to decode what these might MEAN.
                if (configuration.UseGibraltarService)
                {
                    status        = HubStatus.Expired;
                    statusMessage = "The specified customer name is not valid";
                }
                else
                {
                    statusMessage = "The server does not support this service or the specified directory is not valid";
                }

                connectionStatus = new HubConnectionStatus(configuration, false, status, statusMessage);
            }
            catch (Exception ex)
            {
                canConnect    = false;
                statusMessage = ex.Message;

                connectionStatus = new HubConnectionStatus(configuration, false, status, statusMessage);
            }

            //before we return make sure we clean up an errant channel if we don't need it.
            if ((canConnect == false) && (channel != null))
            {
                channel.Dispose();
                channel = null;
            }

            return(connectionStatus);
        }
        /// <summary>
        /// Attempts to connect to the server and returns information about the connection status.
        /// </summary>
        /// <remarks>This method will keep the connection if it is made, improving efficiency if you are then going to use the connection.</remarks>
        /// <returns>True if the configuration is valid and the server is available, false otherwise.</returns>
        public async Task <HubConnectionStatus> CanConnect()
        {
            WebChannel currentChannel;

            lock (m_ChannelLock)
            {
                currentChannel = m_CurrentChannel;
                System.Threading.Monitor.PulseAll(m_ChannelLock);
            }

            //if we have a current connection we'll need to see if we can keep using it
            if (currentChannel != null)
            {
                var status = HubStatus.Maintenance;
                try
                {
                    var configurationGetRequest = new HubConfigurationGetRequest();
                    await currentChannel.ExecuteRequest(configurationGetRequest, 1).ConfigureAwait(false); //we'd like it to succeed, so we'll give it one retry

                    //now, if we got back a redirect we need to go THERE to get the status.
                    HubConfigurationXml configurationXml = configurationGetRequest.Configuration;
                    if ((configurationXml.redirectRequested == false) && (configurationXml.status == HubStatusXml.available))
                    {
                        //we can just keep using this connection, so lets do that.
                        return(new HubConnectionStatus(null, true, HubStatus.Available, null));
                    }

                    status = (HubStatus)configurationXml.status; //we define these to be equal.
                }
                catch (Exception ex)
                {
                    if (!Logger.SilentMode)
                    {
                        Logger.Write(LogMessageSeverity.Information, ex, false, LogCategory, "Unable to get server configuration, connection will be assumed unavailable.", "Due to an exception we were unable to retrieve the server configuration.  We'll assume the server is in maintenance until we can succeed.  Exception: {0}\r\n", ex.Message);
                    }
                }

                //drop the connection - we might do better, unless we're already at the root.
                if (IsRootHub(currentChannel.HostName, currentChannel.Port, currentChannel.UseSsl, currentChannel.ApplicationBaseDirectory))
                {
                    //we are the root - what we are is the best we are.
                    string message = null;
                    switch (status)
                    {
                    case HubStatus.Expired:
                        message = "your subscription is expired";
                        break;

                    case HubStatus.Maintenance:
                        message = "the repository is in maintenance";
                        break;
                    }
                    return(new HubConnectionStatus(null, false, status, message));
                }
            }

            //if we don't have a connection (either we didn't before or we just invalidated the current connection) get a new one.
            var connectionStatus = await Connect().ConfigureAwait(false);

            SetCurrentChannel(connectionStatus.Channel);

            //before we return, lets set our status to track what we just calculated.
            lock (m_StatusLock)
            {
                //make a copy of the connection status so the caller does NOT get our connection object.
                m_HubStatus     = new HubConnectionStatus(connectionStatus.Configuration, null, connectionStatus.Repository, connectionStatus.IsValid, connectionStatus.Status, connectionStatus.Message);
                m_HubRepository = connectionStatus.Repository;
            }

            return(connectionStatus);
        }