private void SendRemoteControlCommandDesktop(ref BrowserSender bs)
        {
            string txtHTML = FileCache.ReadSkinTextFile("page_remote_command.htm");

            // CHECK PROCESS IS RUNNING AND FOCUSSED?
            bool doCommand = (qsParams.HasParameter("command"));
            string strCommand = "";
            string strResponse = "No Command";

            if (doCommand)
            {
                strCommand = qsParams["command"];
                if (strCommand == "none")
                {
                    strResponse = "Click a button.";
                }
                else
                {
                    string strResult = IRCommunicator.Default.SendIRCommand(strCommand); // No return (Async)
                    if (strResult == "OK")
                        strResponse = "Sent " + strCommand + " OK";
                    else
                    {
                        if (strResult == "HELPER_NOT_RUNNING")
                            strResult = "The IR Helper application is not running on the server - check your settings.";

                        strResponse = "Not OK: " + strResult;
                    }
                }
            }

            // Replace Caption within iFrame with response
            txtHTML = txtHTML.Replace("**REMOTE_RESPONSE**", strResponse);

            // Send entire page to browser.
            bs.SendNormalHTMLPageToBrowser(txtHTML);
        }
        private bool SendRemoteControlCommandMobile(ref BrowserSender bs)
        {
            // CHECK PROCESS IS RUNNING AND FOCUSSED?
            bool doCommand = (qsParams.HasParameter("command"));
            string strCommand = "";
            string strResponse = "No Command";

            if (doCommand)
            {
                strCommand = qsParams["command"];
                if (strCommand == "none") // Default page
                {
                    // First command - always displayed within an iFrame, even on iPhone
                    string txtHTML = FileCache.ReadSkinTextFile("page_remote_command.htm");

                    strResponse = "Click a button.";

                    // Replace Caption within iFrame with response
                    txtHTML = txtHTML.Replace("**REMOTE_RESPONSE**", strResponse);

                    // Send entire page to browser.
                    bs.SendNormalHTMLPageToBrowser(txtHTML);

                    return true;
                }
                else
                {
                    string strResult = IRCommunicator.Default.SendIRCommand(strCommand); // No return (Async)
                    if (strResult == "OK")
                        strResponse = "Sent " + strCommand + " OK";
                    else
                    {
                        if (strResult == "HELPER_NOT_RUNNING")
                            strResult = "The IR Helper application is not running on the server - check your settings.";

                        strResponse = "Not OK: " + strResult;
                    }
                }
            }

            // iPhone workaround - send the whole page again. (opens it in a new window)
            txtResponse += FileCache.ReadSkinTextFile("page_remote.htm");
            txtResponse += "<br />" + strResponse;
            return false;
        }
        public void Run()
        {
            #if !DEBUG
            try
            {
            #endif
            // To store headers and styles
            List<string> AdditionalStyles = new List<string>();

            // Set HTTP response version to 1.1 (experimental)
            //  Context.Response.ProtocolVersion = new Version("1.1");

            BrowserSender browserSender = new BrowserSender(Context);
            currentClientIP = Request.RemoteEndPoint.Address.ToString();
            qsParams = Request.QueryString;

            if (Settings.Default.DebugServer) spoolMessage("Client Connected (" + Request.RemoteEndPoint.Address.ToString() + ")");

            if ((Settings.Default.DebugAdvanced) && (Settings.Default.DebugServer))
            {
                spoolMessage("Headers from client:");
                for (int i = 0; i < Request.Headers.Count; ++i)
                    spoolMessage(string.Format("{0}: {1}", Request.Headers.Keys[i], Request.Headers[i]));
            }

            // Split request into lines
            string txtPostObjects = "";
            if (Request.HttpMethod.Equals("POST"))
            {
                StreamReader sr = new StreamReader(Request.InputStream);
                txtPostObjects = sr.ReadToEnd();
            }

            // User agent - detect mobile
            processUserAgentStringFromRequestHeaders();

            // Get action string from Url
            string txtAction = GetActionFromBrowserRequest();

            if (Settings.Default.DebugServer)
            {
                Functions.WriteLineToLogFile("From Client: " + txtAction);
            }

            // Build response
            txtResponse = "";
            txtPageTitle = Settings.Default.MainMenuTitle;
            bool foo;

            // R.I.P. Open server  (keep this for legacy compatibility)
            if (txtAction.StartsWith("open"))
                txtAction = txtAction.Substring(5);

            // Special cases / conversions
            if (txtAction.ToLowerInvariant().Equals("apple-touch-icon.png"))
                txtAction = "static/images/apple-touch-icon.png";

            // Querystring authentication is one possible method that overrides all others if true: check for token (and renew)
            if (txtAction.StartsWith("xml/checktoken")) // Special open method - check a token
            {
                bool ignore = CheckForTokenAuthentication();
                string checkForTokenResult = AuthenticatedByToken ? "GOOD" : "BAD";
                string xCheckResponse = "<?xml version=\"1.0\"?><checktokenresponse result=\"" + checkForTokenResult +  "\" />";
                browserSender.SendXMLToBrowser(xCheckResponse);
                return;
            }
            else if (!CheckForTokenAuthentication())
            {
                // invalid token
                browserSender.SendGenericStatusCodePage("403", "Incorrect authentication");
                spoolMessage("API: failed authentication via token.");
                return;
            }

            // XML METHODS - no HTTP authentication required (uses token-based auth)
            if (txtAction.StartsWith("xml"))
            {
                XMLresponse = "";
                WebServiceProcess(txtAction, ref browserSender, ref txtPostObjects);
                return;
            }

            // Any other non-authenticated methods
            switch (txtAction)
            {
                // SPECIAL FILE NAME SHORTCUTS - NO AUTH REQUIRED **************
                case "robots.txt":
                    if (!browserSender.SendFileToBrowser("static\\robots.txt"))
                        Functions.WriteLineToLogFile("Could not send robots.txt to browser");
                    return;
                case "clientaccesspolicy.xml":
                    if (!browserSender.SendFileToBrowser("static\\clientaccesspolicy.xml"))
                        Functions.WriteLineToLogFile("Could not send clientaccesspolicy.xml to browser (presumably to Silverlight)");
                    return;
                case "silverlightsource":
                    if (!browserSender.SendFileToBrowser("static\\silverlight\\SilverPotato.xap"))
                        Functions.WriteLineToLogFile("Could not send SilverPotato XAP to browser");
                    return;

                //Ping is allowed
                case "ping":
                    Version v = Functions.ServerVersion;
                    string xResponse = "<?xml version=\"1.0\"?><pingresponse result=\"PING_RESULT\" serverversion=\"SERVER_VERSION\" serverrevision=\"SERVER_REVISION\" serverosversionstring=\"SERVER_OS_VERSION_STRING\" serverosversion=\"SERVER_OS_VERSION\" servercapabilities=\"CAP_FLAGS\" />";
                    xResponse = xResponse.Replace("PING_RESULT", Settings.Default.RequirePassword ? "NEED_PASSWORD" : "OK");
                    xResponse = xResponse.Replace("SERVER_VERSION", v.Major.ToString() + "." +
                    v.Minor.ToString() );  // This is culture invariant
                    xResponse = xResponse.Replace("SERVER_OS_VERSION_STRING", Environment.OSVersion.VersionString);
                    xResponse = xResponse.Replace("SERVER_OS_VERSION", Environment.OSVersion.Version.ToString(2) );
                    xResponse = xResponse.Replace("SERVER_REVISION", v.Build.ToString());
                    xResponse = xResponse.Replace("CAP_FLAGS", Functions.ServerCapabilities);
                    browserSender.SendXMLToBrowser(xResponse);
                    return;

                // Fav Icon is allowed
                case "favicon.ico":
                    browserSender.SendFileToBrowser(HttpUtility.UrlDecode("static\\images\\remotepotatoicon.ico"));
                    return;

                default:
                    break;
            }

            // Channel logos are allowed
            if ((txtAction.StartsWith("logo")))
            {
                int hashlocation = txtAction.LastIndexOf("/");
                if (hashlocation < 1)
                {
                    bool fooa = browserSender.Send404Page();
                }
                else
                {
                    txtAction = txtAction.Replace("logo/", "");
                    string logoSvcID = HttpUtility.UrlDecode(txtAction);

                    // Send logo to browser
                    browserSender.SendLogoToBrowser(logoSvcID);
                }
                return;
            }

            // Special case 'static' files that aren't => legacy support for streaming
            if (txtAction.StartsWith("httplivestream"))
            {
                ProcessHTTPLSURL(txtAction, ref browserSender);
                return;
            }

            // Static Files
            if ( (txtAction.StartsWith("static")) )
            {
                int hashlocation = txtAction.LastIndexOf("/");
                if (hashlocation < 1)
                {
                    bool fooa = browserSender.Send404Page();
                }
                else
                {
                    // Send file
                    browserSender.SendFileToBrowser(HttpUtility.UrlDecode(txtAction));
                }
                return;
            }

            // Skin files
            if ( (txtAction.StartsWith("skin")))
            {
                int hashlocation = txtAction.LastIndexOf("/");
                if (hashlocation < 1)
                {
                    bool fooa = browserSender.Send404Page();
                }
                else
                {
                    // Send file
                    browserSender.SendFileToBrowser(HttpUtility.UrlDecode(txtAction), true, false);
                }
                return;
            }

            // Thumbnails are allowed
            if (txtAction == "rectvthumbnail64")
            {
                GetRecTVThumbnail(ref browserSender, true);
                return;
            }
            else if (txtAction == "rectvthumbnail")
            {
                GetRecTVThumbnail(ref browserSender, false);
                return;
            }

            if (txtAction.StartsWith("getfilethumbnail64"))
            {
                GetFileThumbnailUsingQueryString(ref browserSender, true);
                return;
            }
            else if (txtAction.StartsWith("getfilethumbnail"))
            {
                GetFileThumbnailUsingQueryString(ref browserSender, false);
                return;
            }

            if (txtAction.StartsWith("filethumbnail"))
            {
                string txtSize = txtAction.Replace("filethumbnail/","");
                FatAttitude.ThumbnailSizes size = (FatAttitude.ThumbnailSizes) Enum.Parse( (new FatAttitude.ThumbnailSizes().GetType() ), txtSize, true);

                SendFileThumbnail(txtPostObjects, size, ref browserSender);
                return;
            }
            if (txtAction.StartsWith("musicalbumthumbnail"))
            {
                GetAlbumThumbnail(ref browserSender, txtAction.Contains("musicalbumthumbnail64") );
                return;
            }
            if (txtAction.StartsWith("musicsongthumbnail"))
            {
                GetSongThumbnail(ref browserSender, txtAction.Contains("musicsongthumbnail64"));
                return;
            }

            // Silverlight is allowed (no longer contains password info)
            bool showSilverlight = (txtAction.StartsWith("silverlight"));
            if (Settings.Default.SilverlightIsDefault)
                showSilverlight = showSilverlight | (txtAction.Trim().Equals(""));

            if (showSilverlight)
            {
                string silverTemplate = FileCache.ReadTextFile("static\\silverlight\\default_template.htm");
                browserSender.SendNormalHTMLPageToBrowser(silverTemplate);
                return;
            }

            // MORE OPEN METHODS...
            if (txtAction.StartsWith("streamsong"))
            {

                bool isBase64Encoded = (txtAction.StartsWith("streamsong64"));

                if (!SendSongToBrowser(ref browserSender, isBase64Encoded,  true, false))
                    browserSender.Send404Page();
                return;
            }

            // MORE OPEN METHODS...
            if (txtAction.StartsWith("downloadsong"))
            {

                bool isBase64Encoded = (txtAction.StartsWith("downloadsong64"));

                if (!SendSongToBrowser(ref browserSender, isBase64Encoded, true, true))
                    browserSender.Send404Page();
                return;
            }

            // ********************************************************************************************
            // Cookie Authentication Required for all Methods below here **********************************
            // ********************************************************************************************

            bool processMoreActions = false;
            if (canProceedAuthenticatedByHTTPCookie())
            {
                processMoreActions = true;
            }
            else
            {
                spoolMessage("Webserver: requesting login.");

                bool LoginSuccess = false;
                string destURL = "";
                string destQueryString = "";
                ViewLoginPage(txtPostObjects, ref LoginSuccess, ref destURL, ref destQueryString);

                // Successful login
                if (LoginSuccess)
                {
                    processMoreActions = true;
                    txtPageTitle = "";
                    // Assign new (old) action and querystring for further processing
                    txtAction = destURL;
                    qsParams = HttpUtility.ParseQueryString(destQueryString);

                    // We've missed the silverlight check (it's up above), so check again
                    if (Settings.Default.SilverlightIsDefault)
                    {
                        string silverTemplate = FileCache.ReadTextFile("static\\silverlight\\default_template.htm");
                        browserSender.SendNormalHTMLPageToBrowser(silverTemplate);
                        return;
                    }

                }

            }

            bool sentWholePage = false;
            if (processMoreActions)
            {
                switch (txtAction)
                {
                    // Legacy Streamsong  (secured)
                    case "streamsong.mp3":
                        if (!SendSongToBrowser(ref browserSender, false, true, false))
                            browserSender.Send404Page();
                        return;
                    case "streamsong":
                        if (!SendSongToBrowser(ref browserSender, false, true, false))
                            browserSender.Send404Page();
                        return;

                    // MANUAL RECORDING ======================================================
                    case "recordmanual":
                        foo = TryManualRecording();
                        break;

                        // Remote Control
                    case "remotecontrol":
                        foo = ViewRemoteControl();
                        break;

                    // Remote Control
                    case "rc":
                        bool haveSentHTMLPage = SendRemoteControlCommand(ref browserSender);
                        if (haveSentHTMLPage) return;  // Don't continue; this method sends a blank page
                        break;

                    // RECORD A SERIES
                    case "recordshow_series":
                        foo = RecordSeries();
                        break;

                    // RECORD (FROM RecordingRequest): MULTIPURPOSE
                    case "recordfromqueue":
                        foo = RecordFromQueue();
                        break;

                        // PICS
                    case "browsepics":
                        ViewPicturesLibrary();
                        break;

                    case "viewpic":
                        foo = ViewPicture(ref browserSender, ref sentWholePage);
                        if (sentWholePage) return; // no more processing required
                        break;

                    case "picfolderaszip":
                        foo = GetPicturesFolderAsZip(ref browserSender);
                        return; // Don't continue, no Reponse left to output

                    // VIDEOS
                    case "browsevideos":
                        ViewVideoLibrary();
                        break;

                    case "streamvideo":
                        foo = StreamVideo();
                        break;

                    // MUSIC
                    case "musicroot":
                        ViewMusic();
                        break;

                    case "musicartists":
                        ViewMusicArtists(false);
                        break;

                    case "musicartist":
                        ViewMusicArtist();
                        break;

                    case "musicalbumartists":
                        ViewMusicArtists(true);
                        break;

                    case "musicalbums":
                        ViewMusicAlbums();
                        break;

                    case "musicalbum":
                        ViewMusicAlbum();
                        break;

                    case "musicgenres":
                        ViewMusicGenres();
                        break;

                    case "musicgenre":
                        ViewMusicGenre();
                        break;

                    case "musicsong":
                        ViewMusicSong();
                        break;

                    // LIST RECORDINGS
                    case "scheduledrecordings":
                        foo = ViewScheduledRecordings();
                        break;

                    case "log-out":
                        DoLogOut();
                        break;

                    // LIST RECORDINGS
                    case "recordedtv":
                        foo = ViewRecordedTVList();
                        AdditionalStyles.Add("rectv");
                        break;

                    // VIEW A SPECIFIC SERIES
                    case "viewseriesrequest":
                        foo = ViewSeriesRequest();
                        AdditionalStyles.Add("showdetails");
                        break;

                    // MANAGE ALL SERIES
                    case "viewseriesrequests":
                        foo = ViewSeriesRequests();
                        break;

                    // VIEW AN EPG PAGE
                    case "viewepglist":
                        foo = ViewEPGList();
                        AdditionalStyles.Add("epg-list");
                        break;

                    // VIEW AN EPG PAGE - GRID
                    case "viewepggrid":
                        Functions.WriteLineToLogFile("RP: (VEPG)");
                        foo = ViewEPGGrid();
                        AdditionalStyles.Add("epg-grid");
                        break;

                    // Shift EPG Grid Up
                    case "epgnavup":
                        foo = EPGGridChannelRetreat();
                        AdditionalStyles.Add("epg-grid");
                        break;

                    // Shift EPG Grid Down
                    case "epgnavdown":
                        foo = EPGGridChannelAdvance();
                        AdditionalStyles.Add("epg-grid");
                        break;

                    // Shift EPG Grid Right
                    case "epgnavright":
                        foo = EPGGridTimeWindowShiftByMinutes(EPGManager.TimespanMinutes);
                        AdditionalStyles.Add("epg-grid");
                        break;

                    // Shift EPG Grid Left
                    case "epgnavleft":
                        foo = EPGGridTimeWindowShiftByMinutes(0 - EPGManager.TimespanMinutes);
                        AdditionalStyles.Add("epg-grid");
                        break;

                    // Shift EPG Grid Left
                    case "epgnavtop":
                        foo = EPGGridChannelSetAbsolute(true, false);
                        AdditionalStyles.Add("epg-grid");
                        break;

                    // Shift EPG Grid Left
                    case "epgnavbottom":
                        foo = EPGGridChannelSetAbsolute(false, true);
                        AdditionalStyles.Add("epg-grid");
                        break;

                    // Shift EPG To Page
                    case "epgjumptopage":
                        foo = EPGGridChannelJump();
                        AdditionalStyles.Add("epg-grid");
                        break;

                    // VIEW AN EPG SHOW
                    case "viewepgprogramme":
                        foo = ViewEPGProgramme();
                        AdditionalStyles.Add("showdetails");
                        break;

                    // STREAM A SHOW
                    case "streamprogramme":
                        foo = StreamRecordedProgramme();
                        AdditionalStyles.Add("showdetails");
                        break;

                    // SEARCH BY TITLE
                    case "searchbytitle":
                        foo = SearchShowsByText();
                        break;

                    // DELETE A RECORDING
                    case "deletefile":
                        foo = DeleteFileFromFilePath(false);
                        break;

                    case "deletefile64":
                        foo = DeleteFileFromFilePath(true);
                        break;

                    // CANCEL A RECORDING
                    case "cancelseriesrequest":
                        foo = CancelRequest();
                        break;

                    // CANCEL A RECORDING
                    case "cancelrecording":
                        foo = CancelRecording();
                        break;

                    // VIEW MOVIES
                    case "movies":
                        foo = ViewMovies();
                        AdditionalStyles.Add("movies");
                        break;

                    // VIEW MOVIES
                    case "viewmovie":
                        foo = ViewMovie();
                        AdditionalStyles.Add("showdetails");
                        AdditionalStyles.Add("movies");
                        break;

                    case "info":
                        txtResponse += "This is the Remote Potato Server v" + Functions.VersionText + " running on " + Environment.OSVersion.VersionString + ".";
                        txtResponse += "<br/><br/>For help and support please visit the <a href='http://forums.fatattitude.com'>FatAttitude Forums</a>.";
                        break;

                    case "mainmenu":
                        ShowMainMenu();
                        break;

                    default:
                        ShowMainMenu();
                        break;

                }
            }

            // Finalise response: convert to master page
            string txtOutputPage = FileCache.ReadSkinTextFile("masterpage.htm");

            // Commit response
            txtOutputPage = txtOutputPage.Replace("**PAGECONTENT**", txtResponse);
            txtResponse = "";

            // Style inclusion?  (this line must be before the Skin section, as the returned string includes **SKINFOLDER** to be replaced
            txtOutputPage = txtOutputPage.Replace("**PAGEADDITIONALSTYLES**", AdditionalStyleLinks(AdditionalStyles));
            // Orientation
            txtOutputPage = txtOutputPage.Replace("**PAGEORIENTATION**", txtOutputPage.Contains("PAGEORIENTATION=LANDSCAPE") ? "landscape" : "portrait");

            // Skin
            txtOutputPage = txtOutputPage.Replace("**SKINFOLDER**", "/static/skins/" + Themes.ActiveThemeName);
            txtOutputPage = txtOutputPage.Replace("**HEADER**", "Remote Potato");
            // Default Page Title
            txtOutputPage = txtOutputPage.Replace("**PAGETITLE**", txtPageTitle);

            // Copyright / Timestamp
            txtOutputPage = txtOutputPage.Replace("**TIMEANDVERSIONSTRING**", DateTime.Now.ToLongTimeString() + ", v" + Functions.VersionText);

            if (!browserSender.SendNormalHTMLPageToBrowser(txtOutputPage))
            {
                spoolMessage("Webserver failed to send data.");
            }

            #if !DEBUG
            }

            catch (Exception e)
            {
                Functions.WriteExceptionToLogFile(e);
                spoolMessage("EXCEPTION OCCURRED: " + e.Message);

                BrowserSender exceptionBrowserSender = new BrowserSender(Context);
                exceptionBrowserSender.SendNormalHTMLPageToBrowser("<h1>Remote Potato Error</h1>An error occurred and remote potato was unable to serve this web page.<br /><br />Check the debug logs for more information.");
            }
            #endif
        }