/// <summary>
        /// FileAgent OnComplete
        /// </summary>
        /// <param name="fileId"></param>
        void fileAgent_OnComplete(int fileId, string fileType)
        {
            // Notify the player thread using another event (chained events? bad idea?)
            Trace.WriteLine(new LogMessage("RequiredFilesAgent - fileAgent_OnComplete", "FileId finished downloading" + fileId.ToString()));

            // Get the required file associated with this ID
            RequiredFile rf = _requiredFiles.GetRequiredFile(fileId, fileType);

            // Set the status on the client info screen
            if (_requiredFiles.FilesDownloading == 0)
            {
                ClientInfo.Instance.RequiredFilesStatus = "Sleeping";

                // If we are the last download thread to complete, then we should report media inventory and raise an event to say we've got everything
                _requiredFiles.ReportInventory();

                // Raise an event to say we've completed
                OnFullyProvisioned?.Invoke();
            }
            else
            {
                ClientInfo.Instance.RequiredFilesStatus = string.Format("{0} files to download", _requiredFiles.FilesDownloading.ToString());
            }

            // Update the RequiredFiles TextBox
            ClientInfo.Instance.UpdateRequiredFiles(RequiredFilesString());

            // Write the Cache Manager to Disk
            CacheManager.Instance.WriteCacheManager();

            if (rf.FileType == "layout")
            {
                // Reset the safe list for this file.
                CacheManager.Instance.RemoveUnsafeLayout(rf.Id);

                // Raise an event to say it is completed
                OnComplete?.Invoke(rf.SaveAs);
            }
        }
        /// <summary>
        /// Run Thread
        /// </summary>
        public void Run()
        {
            Trace.WriteLine(new LogMessage("RequiredFilesAgent - Run", "Thread Started"), LogType.Info.ToString());

            int retryAfterSeconds = 0;

            while (!_forceStop)
            {
                // If we are restarting, reset
                _manualReset.Reset();

                // Reset backOff
                retryAfterSeconds = 0;

                lock (_locker)
                {
                    // Run the schedule Agent thread
                    scheduleAgent();

                    if (ApplicationSettings.Default.InDownloadWindow)
                    {
                        try
                        {
                            int filesToDownload = _requiredFiles.FilesDownloading;

                            // If we are currently downloading something, we have to wait
                            if (filesToDownload > 0)
                            {
                                ClientInfo.Instance.RequiredFilesStatus = string.Format("Waiting: {0} Active Downloads", filesToDownload.ToString());

                                Trace.WriteLine(new LogMessage("RequiredFilesAgent - Run", "Currently Downloading Files, skipping collect"), LogType.Audit.ToString());
                            }
                            else
                            {
                                ClientInfo.Instance.RequiredFilesStatus = "Running: Requesting connection to Xibo Server";

                                using (xmds.xmds xmds = new xmds.xmds())
                                {
                                    xmds.Credentials           = null;
                                    xmds.Url                   = ApplicationSettings.Default.XiboClient_xmds_xmds + "&method=requiredFiles";
                                    xmds.UseDefaultCredentials = false;

                                    // Get required files from XMDS
                                    string requiredFilesXml = xmds.RequiredFiles(ApplicationSettings.Default.ServerKey, _hardwareKey);

                                    // Set the flag to indicate we have a connection to XMDS
                                    ApplicationSettings.Default.XmdsLastConnection = DateTime.Now;

                                    ClientInfo.Instance.RequiredFilesStatus = "Running: Data received from Xibo Server";

                                    // Load the XML file RF call
                                    XmlDocument xml = new XmlDocument();
                                    xml.LoadXml(requiredFilesXml);

                                    // Create a required files object and set it to contain the RF returned this tick
                                    _requiredFiles = new RequiredFiles();
                                    _requiredFiles.RequiredFilesXml = xml;

                                    // List of Threads to start
                                    // TODO: Track these threads so that we can abort them if the application closes
                                    List <Thread> threadsToStart = new List <Thread>();

                                    // Required files now contains a list of files to download (this will be updated by the various worker threads)
                                    foreach (RequiredFile fileToDownload in _requiredFiles.RequiredFileList)
                                    {
                                        // Skip downloaded files
                                        if (fileToDownload.Complete)
                                        {
                                            continue;
                                        }

                                        // Spawn a thread to download this file.
                                        FileAgent fileAgent = new FileAgent();
                                        fileAgent.FileDownloadLimit = _fileDownloadLimit;
                                        fileAgent.HardwareKey       = _hardwareKey;
                                        fileAgent.RequiredFiles     = _requiredFiles;
                                        fileAgent.RequiredFileId    = fileToDownload.Id;
                                        fileAgent.RequiredFileType  = fileToDownload.FileType;
                                        fileAgent.OnComplete       += new FileAgent.OnCompleteDelegate(fileAgent_OnComplete);
                                        fileAgent.OnPartComplete   += new FileAgent.OnPartCompleteDelegate(fileAgent_OnPartComplete);

                                        // Create the thread and add it to the list of threads to start
                                        Thread thread = new Thread(new ThreadStart(fileAgent.Run));
                                        thread.Name = "FileAgent_" + fileToDownload.FileType + "_Id_" + fileToDownload.Id.ToString();
                                        threadsToStart.Add(thread);
                                    }

                                    // Start the threads after we have built them all - otherwise they will modify the collection we
                                    // are iterating over.
                                    foreach (Thread thread in threadsToStart)
                                    {
                                        thread.Start();
                                    }

                                    // Report what we are doing back to MediaInventory
                                    _requiredFiles.ReportInventory();

                                    // Write Required Files
                                    _requiredFiles.WriteRequiredFiles();

                                    // Write the Cache Manager to Disk
                                    CacheManager.Instance.WriteCacheManager();

                                    // Set the status on the client info screen
                                    if (threadsToStart.Count == 0)
                                    {
                                        ClientInfo.Instance.RequiredFilesStatus = "Sleeping (inside download window)";

                                        // Raise an event to say we've completed
                                        OnFullyProvisioned?.Invoke();
                                    }
                                    else
                                    {
                                        ClientInfo.Instance.RequiredFilesStatus = string.Format("{0} files to download", threadsToStart.Count.ToString());
                                    }

                                    ClientInfo.Instance.UpdateRequiredFiles(RequiredFilesString());
                                }
                            }
                        }
                        catch (WebException webEx) when(webEx.Response is HttpWebResponse httpWebResponse && (int)httpWebResponse.StatusCode == 429)
                        {
                            // Get the header for how long we ought to wait
                            retryAfterSeconds = webEx.Response.Headers["Retry-After"] != null?int.Parse(webEx.Response.Headers["Retry-After"]) : 120;

                            // Log it.
                            Trace.WriteLine(new LogMessage("LogAgent", "Run: 429 received, waiting for " + retryAfterSeconds + " seconds."), LogType.Info.ToString());
                        }
                        catch (WebException webEx)
                        {
                            // Increment the quantity of XMDS failures and bail out
                            ApplicationSettings.Default.IncrementXmdsErrorCount();

                            // Log this message, but dont abort the thread
                            Trace.WriteLine(new LogMessage("RequiredFilesAgent - Run", "WebException in Run: " + webEx.Message), LogType.Info.ToString());

                            ClientInfo.Instance.RequiredFilesStatus = "Error: " + webEx.Message;
                        }
                        catch (Exception ex)
                        {
                            // Log this message, but dont abort the thread
                            Trace.WriteLine(new LogMessage("RequiredFilesAgent - Run", "Exception in Run: " + ex.Message), LogType.Error.ToString());

                            ClientInfo.Instance.RequiredFilesStatus = "Error: " + ex.Message;
                        }
                    }
                    else
                    {
                        ClientInfo.Instance.RequiredFilesStatus = string.Format("Outside Download Window {0} - {1}", ApplicationSettings.Default.DownloadStartWindowTime.ToString("HH:mm", CultureInfo.InvariantCulture), ApplicationSettings.Default.DownloadEndWindowTime.ToString("HH:mm", CultureInfo.InvariantCulture));
                    }
                }

                if (retryAfterSeconds > 0)
                {
                    // Sleep this thread until we've fulfilled our try after
                    _manualReset.WaitOne(retryAfterSeconds * 1000);
                }
                else
                {
                    // Sleep this thread until the next collection interval
                    _manualReset.WaitOne((int)(ApplicationSettings.Default.CollectInterval * ApplicationSettings.Default.XmdsCollectionIntervalFactor() * 1000));
                }
            }

            Trace.WriteLine(new LogMessage("RequiredFilesAgent - Run", "Thread Stopped"), LogType.Info.ToString());
        }