When the Bloom UI makes an API call, a method that has been registered to handle that endpoint is called and given one of these. That method uses this class to get information on the request, and also to reply to the caller.
The goal here is to reduce code while increasing clarity and error catching.
Пример #1
0
 /// <summary>
 /// Get a json of the book's settings.
 /// </summary>
 private void HandleBookSettings(ApiRequest request)
 {
     switch (request.HttpMethod)
     {
         case HttpMethods.Get:
             dynamic settings = new ExpandoObject();
             settings.isRecordedAsLockedDown = _bookSelection.CurrentSelection.RecordedAsLockedDown;
             settings.unlockShellBook = _bookSelection.CurrentSelection.TemporarilyUnlocked;
             settings.currentToolBoxTool = _bookSelection.CurrentSelection.BookInfo.CurrentTool;
             settings.isTemplateBook = GetIsBookATemplate();
             request.ReplyWithJson((object)settings);
             break;
         case HttpMethods.Post:
             //note: since we only have this one value, it's not clear yet whether the panel involved here will be more of a
             //an "edit settings", or a "book settings", or a combination of them.
             settings = DynamicJson.Parse(request.RequiredPostJson());
             _bookSelection.CurrentSelection.TemporarilyUnlocked = settings["unlockShellBook"];
             _pageRefreshEvent.Raise(PageRefreshEvent.SaveBehavior.SaveBeforeRefresh);
             if(((DynamicJson)settings).IsDefined("isTemplateBook"))
             {
                 UpdateBookTemplateMode(settings.isTemplateBook);
             }
             request.Succeeded();
             break;
         default:
             throw new ArgumentOutOfRangeException();
     }
 }
Пример #2
0
 public static bool Handle(EndpointRegistration endpointRegistration, IRequestInfo info, CollectionSettings collectionSettings, Book.Book currentBook)
 {
     var request = new ApiRequest(info, collectionSettings, currentBook);
     try
     {
         if(Program.RunningUnitTests)
         {
             endpointRegistration.Handler(request);
         }
         else
         {
             var formForSynchronizing = Application.OpenForms.Cast<Form>().Last();
             if (endpointRegistration.HandleOnUIThread && formForSynchronizing.InvokeRequired)
             {
                 formForSynchronizing.Invoke(endpointRegistration.Handler, request);
             }
             else
             {
                 endpointRegistration.Handler(request);
             }
         }
         if(!info.HaveOutput)
         {
             throw new ApplicationException(string.Format("The EndpointHandler for {0} never called a Succeeded(), Failed(), or ReplyWith() Function.", info.RawUrl.ToString()));
         }
     }
     catch (Exception e)
     {
         SIL.Reporting.ErrorReport.ReportNonFatalExceptionWithMessage(e, info.RawUrl);
         return false;
     }
     return true;
 }
Пример #3
0
        /// <summary>
        /// Get a json of stats about the image. It is used to populate a tooltip when you hover over an image container
        /// </summary>
        private void HandleImageInfo(ApiRequest request)
        {
            try
            {
                var fileName = request.RequiredFileNameOrPath("image");
                Guard.AgainstNull(_bookSelection.CurrentSelection, "CurrentBook");
                var plainfilename = fileName.NotEncoded;
                // The fileName might be URL encoded.  See https://silbloom.myjetbrains.com/youtrack/issue/BL-3901.
                var path = UrlPathString.GetFullyDecodedPath(_bookSelection.CurrentSelection.FolderPath, ref plainfilename);
                RequireThat.File(path).Exists();
                var fileInfo = new FileInfo(path);
                dynamic result = new ExpandoObject();
                result.name = plainfilename;
                result.bytes = fileInfo.Length;

                // Using a stream this way, according to one source,
                // http://stackoverflow.com/questions/552467/how-do-i-reliably-get-an-image-dimensions-in-net-without-loading-the-image,
                // supposedly avoids loading the image into memory when we only want its dimensions
                using(var stream = RobustFile.OpenRead(path))
                using(var img = Image.FromStream(stream, false, false))
                {
                    result.width = img.Width;
                    result.height = img.Height;
                    switch(img.PixelFormat)
                    {
                        case PixelFormat.Format32bppArgb:
                        case PixelFormat.Format32bppRgb:
                        case PixelFormat.Format32bppPArgb:
                            result.bitDepth = "32";
                            break;
                        case PixelFormat.Format24bppRgb:
                            result.bitDepth = "24";
                            break;
                        case PixelFormat.Format16bppArgb1555:
                        case PixelFormat.Format16bppGrayScale:
                            result.bitDepth = "16";
                            break;
                        case PixelFormat.Format8bppIndexed:
                            result.bitDepth = "8";
                            break;
                        case PixelFormat.Format1bppIndexed:
                            result.bitDepth = "1";
                            break;
                        default:
                            result.bitDepth = "unknown";
                            break;
                    }
                }
                request.ReplyWithJson((object) result);
            }
            catch(Exception e)
            {
                Logger.WriteEvent("Error in server imageInfo/: url was " + request.LocalPath());
                Logger.WriteEvent("Error in server imageInfo/: exception is " + e.Message);
                request.Failed(e.Message);
                NonFatalProblem.Report(ModalIf.None, PassiveIf.Alpha, "Request Error", request.LocalPath(), e);
            }
        }
Пример #4
0
 private void HandleAddPage(ApiRequest request)
 {
     var page = GetPageTemplate(request);
     if (page != null)
     {
         _templateInsertionCommand.Insert(page as Page);
         request.Succeeded();
         return;
     }
 }
Пример #5
0
 /// <summary>
 /// Called by the server to handle API calls for page thumbnails.
 /// </summary>
 public void HandleThumbnailRequest(ApiRequest request)
 {
     var filePath = request.LocalPath().Replace("api/pageTemplateThumbnail/","");
     var pathToExistingOrGeneratedThumbnail = FindOrGenerateThumbnail(filePath);
     if(string.IsNullOrEmpty(pathToExistingOrGeneratedThumbnail) || !File.Exists(pathToExistingOrGeneratedThumbnail))
     {
         request.Failed("Could not make a page thumbnail for "+filePath);
         return;
     }
     request.ReplyWithImage(pathToExistingOrGeneratedThumbnail);
 }
Пример #6
0
        /// <summary>
        /// Returns a json string for initializing the AddPage dialog. It gives paths to our current TemplateBook
        /// and specifies whether the dialog is to be used for adding pages or choosing a different layout.
        /// </summary>
        public void HandleTemplatesRequest(ApiRequest request)
        {
            dynamic addPageSettings = new ExpandoObject();
            addPageSettings.defaultPageToSelect = _templateInsertionCommand.MostRecentInsertedTemplatePage == null ? "" : _templateInsertionCommand.MostRecentInsertedTemplatePage.Id;
            addPageSettings.orientation = _bookSelection.CurrentSelection.GetLayout().SizeAndOrientation.IsLandScape ? "landscape" : "portrait";

            addPageSettings.groups = GetBookTemplatePaths(GetPathToCurrentTemplateHtml(), _sourceCollectionsList.GetSourceBookPaths())
                .Select(bookTemplatePath => GetPageGroup(bookTemplatePath));
            addPageSettings.currentLayout = _pageSelection.CurrentSelection.IdOfFirstAncestor;

            request.ReplyWithJson(JsonConvert.SerializeObject(addPageSettings));
        }
Пример #7
0
        private void HandleChangeLayout(ApiRequest request)
        {
            var templatePage = GetPageTemplate(request);
            if (templatePage != null)
            {
                var pageToChange = /*PageChangingLayout ??*/ _pageSelection.CurrentSelection;
                var book = _pageSelection.CurrentSelection.Book;
                book.UpdatePageToTemplate(book.OurHtmlDom, templatePage.GetDivNodeForThisPage(), pageToChange.Id);
                // The Page objects are cached in the page list and may be used if we issue another
                // change layout command. We must update their lineage so the right "current layout"
                // will be shown if the user changes the layout of the same page again.
                var pageChanged = pageToChange as Page;
                if (pageChanged != null)
                    pageChanged.UpdateLineage(new[] { templatePage.Id });

                _pageRefreshEvent.Raise(PageRefreshEvent.SaveBehavior.JustRedisplay);
                request.Succeeded();
            }
        }
Пример #8
0
        private IPage GetPageTemplate(ApiRequest request)
        {
            var requestData = DynamicJson.Parse(request.RequiredPostJson());
            //var templateBookUrl = request.RequiredParam("templateBookUrl");
            var templateBookPath = HttpUtility.HtmlDecode(requestData.templateBookPath);
            var templateBook = _sourceCollectionsList.FindAndCreateTemplateBookByFullPath(templateBookPath);
            if(templateBook == null)
            {
                request.Failed("Could not find template book " + requestData.templateBookUrl);
                return null;
            }

            var pageDictionary = templateBook.GetTemplatePagesIdDictionary();
            IPage page = null;
            if(pageDictionary.TryGetValue(requestData.pageId, out page))
            {
                return page;
            }
            else
            {
                request.Failed("Could not find the page " + requestData.pageId + " in the template book " + requestData.templateBookUrl);
                return null;
            }
        }
Пример #9
0
 /// <summary>
 /// Delete a file (typically a recording, as requested by the Clear button in the talking book tool)
 /// </summary>
 /// <param name="fileUrl"></param>
 private void HandleDeleteSegment(ApiRequest request)
 {
     var path = GetPathToSegment(request.RequiredParam("id"));
     if(!RobustFile.Exists(path))
     {
         request.Succeeded();
     }
     else
     {
         try
         {
             RobustFile.Delete(path);
             request.Succeeded();
         }
         catch(IOException e)
         {
             var msg =
                 string.Format(
                     LocalizationManager.GetString("Errors.ProblemDeletingFile", "Bloom had a problem deleting this file: {0}"), path);
             ErrorReport.NotifyUserOfProblem(e, msg + Environment.NewLine + e.Message);
         }
     }
 }
Пример #10
0
        /// <summary>
        /// Get a json of stats about the image. It is used to populate a tooltip when you hover over an image container
        /// </summary>
        private void HandleImageInfo(ApiRequest request)
        {
            try
            {
                var fileName = request.RequiredParam("image");
                Guard.AgainstNull(_bookSelection.CurrentSelection, "CurrentBook");
                var path = Path.Combine(_bookSelection.CurrentSelection.FolderPath, fileName);
                if (!File.Exists(path))
                {
                    // We can be fed doubly-encoded filenames.  So try to decode a second time and see if that works.
                    // See https://silbloom.myjetbrains.com/youtrack/issue/BL-3749.
                    fileName = System.Web.HttpUtility.UrlDecode(fileName);
                    path = Path.Combine(_bookSelection.CurrentSelection.FolderPath, fileName);
                }
                RequireThat.File(path).Exists();
                var fileInfo = new FileInfo(path);
                dynamic result = new ExpandoObject();
                result.name = fileName;
                result.bytes = fileInfo.Length;

                // Using a stream this way, according to one source,
                // http://stackoverflow.com/questions/552467/how-do-i-reliably-get-an-image-dimensions-in-net-without-loading-the-image,
                // supposedly avoids loading the image into memory when we only want its dimensions
                using (var stream = File.OpenRead(path))
                using (var img = Image.FromStream(stream, false, false))
                {
                    result.width = img.Width;
                    result.height = img.Height;
                    switch (img.PixelFormat)
                    {
                        case PixelFormat.Format32bppArgb:
                        case PixelFormat.Format32bppRgb:
                        case PixelFormat.Format32bppPArgb:
                            result.bitDepth = "32";
                            break;
                        case PixelFormat.Format24bppRgb:
                            result.bitDepth = "24";
                            break;
                        case PixelFormat.Format16bppArgb1555:
                        case PixelFormat.Format16bppGrayScale:
                            result.bitDepth = "16";
                            break;
                        case PixelFormat.Format8bppIndexed:
                            result.bitDepth = "8";
                            break;
                        case PixelFormat.Format1bppIndexed:
                            result.bitDepth = "1";
                            break;
                        default:
                            result.bitDepth = "unknown";
                            break;
                    }
                }
                request.ReplyWithJson((object)result);
            }
            catch (Exception e)
            {
                Logger.WriteEvent("Error in server imageInfo/: url was " + request.LocalPath());
                Logger.WriteEvent("Error in server imageInfo/: exception is " + e.Message);
                request.Failed(e.Message);
            }
        }
        /// <summary>
        /// Handles a url starting with api/kPrefix by stripping off that prefix, searching for the file
        /// named in the remainder of the url, and opening it in some browser (passing on any anchor specified).
        /// </summary>
        public static void HandleRequest(ApiRequest request)
        {
            //NB: be careful not to lose case, as at least chrome is case-sensitive with anchors (e.g. #ChoiceOfTopic)
            var localPath = Regex.Replace(request.LocalPath(), "api/"+ kPrefix+"/", "", RegexOptions.IgnoreCase);
            var langCode = LocalizationManager.UILanguageId;
            var completeEnglishPath = FileLocator.GetFileDistributedWithApplication(localPath);
            var completeUiLangPath = completeEnglishPath.Replace("-en.htm", "-" + langCode + ".htm");

            string url;
            if (langCode != "en" && RobustFile.Exists(completeUiLangPath))
                url = completeUiLangPath;
            else
                url = completeEnglishPath;
            var cleanUrl = url.Replace("\\", "/"); // allows jump to file to work

            //we would like to get something like foo.htm#secondPart but the browser strips off that fragment part
            //so we require that to be written as foo.htm?fragment=secondPart so it gets to us, then we convert
            //it back to the normal format before sending it to a parser
            if (!string.IsNullOrEmpty(request.Parameters["fragment"]))
            {
                cleanUrl += "#" + request.Parameters["fragment"];
                request.Parameters.Remove("fragment");
            }

            string browser = string.Empty;
            if (SIL.PlatformUtilities.Platform.IsLinux)
            {
                // REVIEW: This opens HTML files in the browser. Do we have any non-html
                // files that this code needs to open in the browser? Currently they get
                // opened in whatever application the user has selected for that file type
                // which might well be an editor.
                browser = "xdg-open";
            }
            else
            {
                // If we don't provide the path of the browser, i.e. Process.Start(url + queryPart), we get file not found exception.
                // If we prepend "file:///", the anchor part of the link (#xxx) is not sent unless we provide the browser path too.
                // This is the same behavior when simply typing a url into the Run command on Windows.
                // If we fail to get the browser path for some reason, we still load the page, just without navigating to the anchor.
                string defaultBrowserPath;
                if (TryGetDefaultBrowserPathWindowsOnly(out defaultBrowserPath))
                {
                    browser = defaultBrowserPath;
                }
            }

            //Note, we don't currently use this, since this is only used for our own html. I added it for completeness... maybe
            //someday when we are running the BloomLibrary locally for the user, we'll have links that require a query part.
            var queryPart = "";
            if (request.Parameters.Count > 0)
            {
                //reconstruct the query part, this time minus any fragment parameter (which we removed previously, if it was there)
                queryPart = "?" + request.Parameters.AllKeys.Aggregate("", (total, key) => total + key + "=" + request.Parameters.Get(key) + "&");
                queryPart = queryPart.TrimEnd(new[] { '&' });
            }

            if (!string.IsNullOrEmpty(browser))
            {
                try
                {
                    Process.Start(browser, "\"file:///" + cleanUrl + queryPart + "\"");
                    request.SucceededDoNotNavigate();
                    return;
                }
                catch (Exception)
                {
                    Debug.Fail("Jumping to browser with anchor failed.");
                    // Don't crash Bloom because we can't open an external file.
                }
            }
            // If the above failed, either for lack of default browser or exception, try this:
            Process.Start("\"" + cleanUrl + "\"");

            request.SucceededDoNotNavigate();
        }
Пример #12
0
        private static void HandleSettings(ApiRequest request)
        {
            if(request.HttpMethod != HttpMethods.Get)
                throw new ApplicationException(request.LocalPath()+" only implements 'get'");

            var settings = new Dictionary<string, object>
            {
                {"current", request.CurrentBook.BookInfo.CurrentTool}
            };

            foreach (var tool in GetToolsToDisplay(request.CurrentBook, IdsOfToolsThisVersionKnowsAbout))
            {
                if (!String.IsNullOrEmpty(tool.State))
                    settings.Add(tool.StateName, tool.State);
            }

            request.ReplyWithJson(settings);
        }
Пример #13
0
        private void HandleEndRecord(ApiRequest request)
        {
            #if __MonoCS__
            #else
            if (Recorder.RecordingState != RecordingState.Recording)
            {
                //usually, this is a result of us getting the "end" before we actually started, because it was too quick
                if(TestForTooShortAndSendFailIfSo(request))
                {
                    _startRecordingTimer.Enabled = false;//we don't want it firing in a few milliseconds from now
                    return;
                }

                //but this would handle it if there was some other reason
                request.Failed("Got endRecording, but was not recording");
                return;
            }
            try
            {
                Debug.WriteLine("Stop recording");
                Recorder.Stopped += Recorder_Stopped;
                //note, this doesn't actually stop... more like... starts the stopping. It does mark the time
                //we requested to stop. A few seconds later (2, looking at the library code today), it will
                //actually close the file and raise the Stopped event
                Recorder.Stop();
                request.Succeeded();
                //ReportSuccessfulRecordingAnalytics();
            }
            catch (Exception)
            {
                //swallow it. One reason (based on HearThis comment) is that they didn't hold it down long enough, we detect this below.
            }

            TestForTooShortAndSendFailIfSo(request);
            #endif
        }
Пример #14
0
 private bool TestForTooShortAndSendFailIfSo(ApiRequest request)
 {
     if ((DateTime.Now - _startRecording) < TimeSpan.FromSeconds(0.5))
     {
         CleanUpAfterPressTooShort();
         var msg = LocalizationManager.GetString("EditTab.Toolbox.TalkingBook.PleaseHoldMessage",
             "Please hold the button down until you have finished recording",
             "Appears when the speak/record button is pressed very briefly");
         request.Failed(msg);
         return true;
     }
     return false;
 }
Пример #15
0
 // Does this page have any audio at all? Used to enable 'Listen to the whole page'.
 private void HandleEnableListenButton(ApiRequest request)
 {
     var ids = request.RequiredParam("ids");
     foreach (var id in ids.Split(','))
     {
         if (RobustFile.Exists(GetPathToSegment(id)))
         {
             request.Succeeded();
             return;
         }
     }
     request.Failed("no audio");
 }
Пример #16
0
        /// <summary>
        /// Returns a json string like {"devices":["microphone", "Logitech Headset"], "productName":"Logitech Headset", "genericName":"Headset"},
        /// except that in practice currrently the generic and product names are the same and not as helpful as the above.
        /// Devices is a list of product names (of available recording devices), the productName and genericName refer to the
        /// current selection (or will be null, if no current device).
        /// </summary>
        public void HandleAudioDevices(ApiRequest request)
        {
            #if __MonoCS__
            request.Failed("Not supported on Linux");
            #else
            var sb = new StringBuilder("{\"devices\":[");
            sb.Append(string.Join(",", RecordingDevice.Devices.Select(d => "\""+d.ProductName+"\"")));
            sb.Append("],\"productName\":");
            if (CurrentRecording.RecordingDevice != null)
                sb.Append("\"" + CurrentRecording.RecordingDevice.ProductName + "\"");
            else
                sb.Append("null");

            sb.Append(",\"genericName\":");
            if (CurrentRecording.RecordingDevice != null)
                sb.Append("\"" + CurrentRecording.RecordingDevice.GenericName + "\"");
            else
                sb.Append("null");

            sb.Append("}");
            request.ReplyWithJson(sb.ToString());
            #endif
        }
Пример #17
0
 private void HandleCheckForSegment(ApiRequest request)
 {
     var path = GetPathToSegment(request.RequiredParam("id"));
     request.ReplyWithText(RobustFile.Exists(path) ? "exists" : "not found");
 }
Пример #18
0
        /// <returns>true if the recording started successfully</returns>
        public void HandleStartRecording(ApiRequest request)
        {
            #if __MonoCS__
                        MessageBox.Show("Recording does not yet work on Linux", "Cannot record");
                        return;
            #else
            if(Recording)
            {
                request.Failed("Already recording");
                return;
            }

            string segmentId = request.RequiredParam("id");
            PathToCurrentAudioSegment = GetPathToSegment(segmentId);
            PathToTemporaryWav = Path.GetTempFileName();

            if (Recorder.RecordingState == RecordingState.RequestedStop)
            {
                request.Failed(LocalizationManager.GetString("EditTab.Toolbox.TalkingBook.BadState",
                    "Bloom recording is in an unusual state, possibly caused by unplugging a microphone. You will need to restart.","This is very low priority for translation."));
            }

            // If someone unplugged the microphone we were planning to use switch to another.
            // This also triggers selecting the first one initially.
            if (!RecordingDevice.Devices.Contains(RecordingDevice))
            {
                RecordingDevice = RecordingDevice.Devices.FirstOrDefault();
            }
            if (RecordingDevice == null)
            {
                ReportNoMicrophone();
                request.Failed("No Microphone");
                return ;
            }

            if(Recording)
            {
                request.Failed( "Already Recording");
                return;
            }

            if (RobustFile.Exists(PathToCurrentAudioSegment))
            {
                //Try to deal with _backPath getting locked (BL-3160)
                try
                {
                    RobustFile.Delete(_backupPath);
                }
                catch(IOException)
                {
                    _backupPath = System.IO.Path.GetTempFileName();
                }
                try
                {
                    RobustFile.Copy(PathToCurrentAudioSegment, _backupPath, true);
                }
                catch (Exception err)
                {
                    ErrorReport.NotifyUserOfProblem(err,
                        "Bloom cold not copy "+PathToCurrentAudioSegment+" to "+_backupPath+" If things remains stuck, you may need to restart your computer.");
                    request.Failed( "Problem with backup file");
                    return;
                }
                try
                {
                    RobustFile.Delete(PathToCurrentAudioSegment);
                    //DesktopAnalytics.Analytics.Track("Re-recorded a clip", ContextForAnalytics);
                }
                catch (Exception err)
                {
                    ErrorReport.NotifyUserOfProblem(err,
                        "The old copy of the recording at " + PathToCurrentAudioSegment + " is locked up, so Bloom can't record over it at the moment. If it remains stuck, you may need to restart your computer.");
                    request.Failed( "Audio file locked");
                    return;
                }
            }
            else
            {
                RobustFile.Delete(_backupPath);
                //DesktopAnalytics.Analytics.Track("Recording clip", ContextForAnalytics);
            }
            _startRecording = DateTime.Now;
            _startRecordingTimer.Start();
            request.ReplyWithText("starting record soon");
            return;
            #endif
        }
Пример #19
0
 public void HandleCurrentRecordingDevice(ApiRequest request)
 {
     #if __MonoCS__
     #else
     if(request.HttpMethod == HttpMethods.Post)
     {
         var name = request.RequiredPostString();
         foreach (var dev in RecordingDevice.Devices)
         {
             if(dev.ProductName == name)
             {
                 RecordingDevice = dev;
                 request.Succeeded();
                 return;
             }
         }
         request.Failed("Could not find the device named " + name);
     }
     else request.Failed("Only Post is currently supported");
     #endif
 }