/// <summary>
        ///
        /// </summary>
        /// <param name="Action"></param>
        private void RunAction(RemoteActionClientState Action, CancellationToken CancelToken)
        {
            // TODO: we should figure out how to handle cancellation for installs, not sure the safest way to do this though hum.

            switch (Action.Type)
            {
            case RemoteActionType.Install:
            {
                Guid   ManifestId      = Guid.Parse(Action.Settings["ManifestId"]);
                string DeviceName      = Action.Settings["DeviceName"];
                string InstallLocation = Action.Settings["InstallLocation"];

                ScriptBuildProgressDelegate Callback = (string InState, float InProgress) =>
                {
                    if (Action.Progress != InProgress || Action.ProgressText != InState)
                    {
                        Action.Progress     = InProgress;
                        Action.ProgressText = InState;
                        Action.Dirty        = true;
                    }
                };

                DownloadManager.BlockDownload(ManifestId);
                try
                {
                    if (DownloadManager.PerformInstallation(ManifestId, DeviceName, InstallLocation, Callback))
                    {
                        Action.Failed    = false;
                        Action.Completed = true;
                        Action.Dirty     = true;
                    }
                    else
                    {
                        Action.ResultMessage = "Failed with generic error.";
                        Action.Failed        = true;
                        Action.Completed     = true;
                        Action.Dirty         = true;
                    }
                }
                catch (Exception Ex)
                {
                    Logger.Log(LogLevel.Error, LogCategory.Manifest, "Failed to install with error: {0}", Ex.Message);

                    Action.ResultMessage = Ex.Message;
                    Action.Failed        = true;
                    Action.Completed     = true;
                    Action.Dirty         = true;
                }
                finally
                {
                    DownloadManager.UnblockDownload(ManifestId);
                }

                break;
            }
            }
        }
        /// <summary>
        ///
        /// </summary>
        /// <param name="Msg"></param>
        private void RequestRecieved(NetMessage_RequestRemoteAction Msg)
        {
            Logger.Log(LogLevel.Info, LogCategory.Main, "Server is requesting us to start a remote action '{0}'.", Msg.Id.ToString());

            RemoteActionClientState State = AllocateState(Msg.Type);

            State.Id = Msg.ActionId;
            State.RemoteInitiated = true;
            State.Settings        = Msg.Settings;
            State.Work            = Task.Run(() => { RunAction(State, State.WorkTokenSource.Token); }, State.WorkTokenSource.Token);
        }
        /// <summary>
        ///
        /// </summary>
        /// <returns></returns>
        public RemoteActionClientState AllocateState(RemoteActionType Type)
        {
            RemoteActionClientState State = new RemoteActionClientState();

            State.Id   = Guid.NewGuid();
            State.Type = Type;
            States.Add(State);

            Logger.Log(LogLevel.Info, LogCategory.Main, "Allocated new remote action '{0}' of type {1}.", State.Id.ToString(), State.Type.ToString());

            return(State);
        }
        /// <summary>
        ///
        /// </summary>
        /// <param name="Client"></param>
        /// <param name="ActionId"></param>
        private void CancelRecieved(Guid ActionId)
        {
            RemoteActionClientState State = GetActionState(ActionId);

            if (State == null)
            {
                return;
            }

            Logger.Log(LogLevel.Info, LogCategory.Main, "Server requested that we cancel remote action '{0}'.", State.Id.ToString());

            State.WorkTokenSource.Cancel();
        }
        /// <summary>
        ///
        /// </summary>
        /// <param name="ActionId"></param>
        public void CancelRemoteAction(Guid ActionId)
        {
            if (!Client.Connection.IsReadyForData)
            {
                Logger.Log(LogLevel.Info, LogCategory.Main, "Failed to start cancel remote action. No connection.");
                return;
            }

            RemoteActionClientState State = GetActionState(ActionId);

            if (State != null && !State.Completed)
            {
                NetMessage_CancelRemoteAction Msg = new NetMessage_CancelRemoteAction();
                Msg.ActionId = ActionId;
                Client.Connection.Send(Msg);
            }

            RemoveActionState(ActionId);
        }
        /// <summary>
        ///
        /// </summary>
        /// <param name="ManifestId"></param>
        /// <param name="DeviceName"></param>
        /// <param name="DeviceLocation"></param>
        /// <returns></returns>
        public Guid RequestRemoteInstall(Guid ManifestId, string DeviceName, string InstallLocation)
        {
            if (!Client.Connection.IsReadyForData)
            {
                Logger.Log(LogLevel.Info, LogCategory.Main, "Failed to start remote install. No connection.");
                return(Guid.Empty);
            }

            RemoteActionClientState State = AllocateState(RemoteActionType.Install);

            NetMessage_RequestRemoteAction Msg = new NetMessage_RequestRemoteAction();

            Msg.ActionId = State.Id;
            Msg.Type     = RemoteActionType.Install;
            Msg.Settings["ManifestId"]      = ManifestId.ToString();
            Msg.Settings["DeviceName"]      = DeviceName;
            Msg.Settings["InstallLocation"] = InstallLocation;
            Client.Connection.Send(Msg);

            return(State.Id);
        }
 /// <summary>
 ///
 /// </summary>
 /// <param name="Client"></param>
 public RemoteActionClient(Client InClient, ManifestDownloadManager InDownloadManager)
 {
     DownloadManager = InDownloadManager;
     Client          = InClient;
     Client.OnRequestRemoteActionRecieved  += RequestRecieved;
     Client.OnCancelRemoteActionRecieved   += CancelRecieved;
     Client.OnSolicitRemoteActionRecieved  += SolicitRemoteActionRecieved;
     Client.OnRemoteActionProgressRecieved += (NetMessage_RemoteActionProgress Msg) =>
     {
         RemoteActionClientState Action = GetActionState(Msg.ActionId);
         if (Action != null)
         {
             Action.Completed          = Msg.Completed;
             Action.Failed             = Msg.Failed;
             Action.ResultMessage      = Msg.ResultMessage;
             Action.Progress           = Msg.Progress;
             Action.ProgressText       = Msg.ProgressText;
             Action.LastUpdateRecieved = TimeUtils.Ticks;
         }
     };
 }
        /// <summary>
        ///
        /// </summary>
        /// <returns></returns>
        public void Poll()
        {
            for (int i = 0; i < States.Count; i++)
            {
                RemoteActionClientState State = States[i];

                // Something we are running locally.
                if (State.RemoteInitiated)
                {
                    // Handled by task runner.
                }

                // Something a remote peer is running.
                else
                {
                    // We lost connection, aborted.
                    if (!Client.IsConnected)
                    {
                        Logger.Log(LogLevel.Info, LogCategory.Main, "Removed remote action '{0}' as lost server connection.", State.Id.ToString());

                        State.ResultMessage = "Lost connection to server.";
                        State.Completed     = true;
                        State.Failed        = true;
                        State.Dirty         = true;
                    }

                    // Timed out waiting for progress update from server.
                    else if (TimeUtils.Ticks - State.LastUpdateRecieved > RemoteActionClientState.LastUpdateTimeout)
                    {
                        Logger.Log(LogLevel.Info, LogCategory.Main, "Removed remote action '{0}' as timed out.", State.Id.ToString());

                        State.ResultMessage = "Timed out waiting for update from server.";
                        State.Completed     = true;
                        State.Failed        = true;
                        State.Dirty         = true;
                    }
                }

                // Send progress update to the server if we are running it.
                if (TimeUtils.Ticks - State.LastUpdateSent > RemoteActionClientState.UpdateInterval || State.Dirty)
                {
                    // Send update to server if required.
                    if (Client.IsConnected && State.RemoteInitiated)
                    {
                        if (State.Completed || TimeUtils.Ticks - State.LastUpdateSent > RemoteActionClientState.MinUpdateInterval)
                        {
                            NetMessage_RemoteActionProgress Msg = new NetMessage_RemoteActionProgress();
                            Msg.ActionId      = State.Id;
                            Msg.Completed     = State.Completed;
                            Msg.Failed        = State.Failed;
                            Msg.ResultMessage = State.ResultMessage;
                            Msg.Progress      = State.Progress;
                            Msg.ProgressText  = State.ProgressText;
                            Client.Connection.Send(Msg);

                            State.LastUpdateSent = TimeUtils.Ticks;
                        }
                    }

                    State.Dirty = false;

                    // If completed (and remote initiated), clean up state.
                    // If local initiated, the code starting the action is responsible for cleaning it up.
                    if (State.Completed && State.RemoteInitiated)
                    {
                        States.RemoveAt(i);
                        i--;
                    }
                }
            }
        }