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 label = ""; if (endpointRegistration.DoMeasure && (endpointRegistration.FunctionToGetLabel != null)) { label = endpointRegistration.FunctionToGetLabel(); } else if (endpointRegistration.DoMeasure) { label = endpointRegistration.MeasurementLabel; } using (endpointRegistration.DoMeasure ? PerformanceMeasurement.Global?.Measure(label) : null) { // Note: If the user is still interacting with the application, openForms could change and become empty var formForSynchronizing = Application.OpenForms.Cast <Form>().LastOrDefault(); if (endpointRegistration.HandleOnUIThread && formForSynchronizing != null && formForSynchronizing.InvokeRequired) { InvokeWithErrorHandling(endpointRegistration, formForSynchronizing, 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 (System.IO.IOException e) { var shortMsg = String.Format(L10NSharp.LocalizationManager.GetDynamicString("Bloom", "Errors.CannotAccessFile", "Cannot access {0}"), info.RawUrl); var longMsg = String.Format("Bloom could not access {0}. The file may be open in another program.", info.RawUrl); NonFatalProblem.Report(ModalIf.None, PassiveIf.All, shortMsg, longMsg, e); request.Failed(shortMsg); return(false); } catch (Exception e) { //Hard to reproduce, but I got one of these supertooltip disposal errors in a yellow box //while switching between publish tabs (e.g. /bloom/api/publish/android/cleanup). //I don't think these are worth alarming the user about, so let's be sensitive to what channel we're on. NonFatalProblem.Report(ModalIf.Alpha, PassiveIf.All, "Error in " + info.RawUrl, exception: e); request.Failed("Error in " + info.RawUrl); return(false); } return(true); }
/// <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); }
public void ProcessDirectoryWatcher(ApiRequest request) { // thread synchronization is done in the calling BloomApiHandler. var dirName = request.RequiredPostValue("dir"); if (dirName == "Sample Texts") { CheckForSampleTextChanges(request); } else { request.Failed(); } }
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; } }
/// <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); } }
public void HandleI18nRequest(ApiRequest request) { var lastSegment = request.LocalPath().Split(new char[] { '/' }).Last(); switch (lastSegment) { case "loadStrings": var d = new Dictionary <string, string>(); var post = request.GetPostDataWhenFormEncoded(); if (post != null) { foreach (string key in post.Keys) { try { if (d.ContainsKey(key)) { continue; } var translation = GetTranslationDefaultMayNotBeEnglish(key, post[key]); d.Add(key, translation); } catch (Exception error) { Debug.Fail("Debug Only:" + error.Message + Environment.NewLine + "A bug reported at this location is BL-923"); //Until BL-923 is fixed (hard... it's a race condition, it's better to swallow this for users } } } request.ReplyWithJson(JsonConvert.SerializeObject(d)); break; case "translate": var parameters = request.Parameters; string id = parameters["key"]; string englishText = parameters["englishText"]; string langId = parameters["langId"]; langId = langId.Replace("V", request.CurrentCollectionSettings.Language1Iso639Code); langId = langId.Replace("N1", request.CurrentCollectionSettings.Language2Iso639Code); langId = langId.Replace("N2", request.CurrentCollectionSettings.Language3Iso639Code); langId = langId.Replace("UI", LocalizationManager.UILanguageId); string localizedString; if (GetSomeTranslation(id, langId, out localizedString)) { // Ensure that we actually have a value for localized string. (This should already be true, but I'm paranoid.) if (localizedString == null) { localizedString = englishText; } request.ReplyWithJson(new { text = localizedString, success = true }); } else { var idFound = true; // Don't report missing strings if they are numbers // Enhance: We might get the Javascript to do locale specific numbers someday // The C# side doesn't currently have the smarts to do DigitSubstitution // See Remark at https://msdn.microsoft.com/en-us/library/system.globalization.numberformatinfo.digitsubstitution(v=vs.110).aspx if (IsInteger(id)) { englishText = id; } else { // Now that end users can create templates, it's annoying to report that their names, // page labels, and page descriptions don't have localizations. if (IsTemplateBookKey(id)) { englishText = englishText.Trim(); } else { // it's ok if we don't have a translation, but if the string isn't even in the list of things that need translating, // then we want to remind the developer to add it to the english xlf file. if (!LocalizationManager.GetIsStringAvailableForLangId(id, "en")) { ReportL10NMissingString(id, englishText, UrlPathString.CreateFromUrlEncodedString(parameters["comment"] ?? "").NotEncoded); idFound = false; } else { //ok, so we don't have it translated yet. Make sure it's at least listed in the things that can be translated. // And return the English string, which is what we would do the next time anyway. (BL-3374) LocalizationManager.GetDynamicString("Bloom", id, englishText); } } } request.ReplyWithJson(new { text = englishText, success = idFound }); } break; default: request.Failed(); break; } }
public void HandleRequest(ApiRequest request) { if (CurrentBook == null) { Debug.Fail("BL-836 reproduction?"); // ReSharper disable once HeuristicUnreachableCode request.Failed("CurrentBook is null"); return; } if (request.CurrentCollectionSettings == null) { Debug.Fail("BL-836 reproduction?"); // ReSharper disable once HeuristicUnreachableCode request.Failed("CurrentBook.CollectionSettings is null"); return; } var lastSegment = request.LocalPath().Split(new char[] { '/' }).Last(); switch (lastSegment) { case "test": request.Succeeded(); break; case "readerToolSettings": if (request.HttpMethod == HttpMethods.Get) { request.ReplyWithJson(GetReaderSettings(request.CurrentCollectionSettings)); } else { var path = DecodableReaderTool.GetReaderToolsSettingsFilePath(request.CurrentCollectionSettings); var content = request.RequiredPostJson(); RobustFile.WriteAllText(path, content, Encoding.UTF8); request.Succeeded(); } break; //note, this endpoint is confusing because it appears that ultimately we only use the word list out of this file (see "sampleTextsList"). //This ends up being written to a ReaderToolsWords-xyz.json (matching its use, if not it contents). case "synphonyLanguageData": //This is the "post". There is no direct "get", but the name of the file is given in the "sampleTextList" reply, below: SaveSynphonyLanguageData(request.RequiredPostJson()); request.Succeeded(); break; case "sampleTextsList": //note, as part of this reply, we send the path of the "ReaderToolsWords-xyz.json" which is *written* by the "synphonyLanguageData" endpoint above request.ReplyWithText(GetSampleTextsList(request.CurrentCollectionSettings.SettingsFilePath)); break; case "sampleFileContents": request.ReplyWithText(GetTextFileContents(request.RequiredParam("fileName"), WordFileType.SampleFile)); break; case "textOfContentPages": request.ReplyWithText(GetTextOfContentPagesAsJson()); break; case "makeLetterAndWordList": MakeLetterAndWordList(request.RequiredPostValue("settings"), request.RequiredPostValue("allWords")); request.Succeeded(); break; case "openTextsFolder": OpenTextsFolder(); request.Succeeded(); break; case "chooseAllowedWordsListFile": lock (request) { request.ReplyWithText(ShowSelectAllowedWordsFileDialog()); } break; case "allowedWordsList": switch (request.HttpMethod) { case HttpMethods.Delete: RecycleAllowedWordListFile(request.RequiredParam("fileName")); request.Succeeded(); break; case HttpMethods.Get: var fileName = request.RequiredParam("fileName"); request.ReplyWithText(RemoveEmptyAndDupes(GetTextFileContents(fileName, WordFileType.AllowedWordsFile))); break; default: request.Failed("Http verb not handled"); break; } break; default: request.Failed("Don't understand '" + lastSegment + "' in " + request.LocalPath()); break; } }
public void HandleRequest(ApiRequest request) { if (CurrentBook == null) { Debug.Fail("BL-836 reproduction?"); // ReSharper disable once HeuristicUnreachableCode request.Failed("CurrentBook is null"); return; } if (request.CurrentCollectionSettings == null) { Debug.Fail("BL-836 reproduction?"); // ReSharper disable once HeuristicUnreachableCode request.Failed("CurrentBook.CollectionSettings is null"); return; } var lastSegment = request.LocalPath().Split(new char[] { '/' }).Last(); switch (lastSegment) { case "test": request.PostSucceeded(); break; case "readerToolSettings": if (request.HttpMethod == HttpMethods.Get) { request.ReplyWithJson(GetReaderSettings(request.CurrentCollectionSettings)); } else { var path = DecodableReaderToolSettings.GetReaderToolsSettingsFilePath(request.CurrentCollectionSettings); var content = request.RequiredPostJson(); RobustFile.WriteAllText(path, content, Encoding.UTF8); request.PostSucceeded(); } break; //note, this endpoint is confusing because it appears that ultimately we only use the word list out of this file (see "sampleTextsList"). //This ends up being written to a ReaderToolsWords-xyz.json (matching its use, if not it contents). case "synphonyLanguageData": //This is the "post". There is no direct "get", but the name of the file is given in the "sampleTextList" reply, below. // We've had situations (BL-4313 and friends) where reading the posted data fails. This seems to be due to situations // where we have a very large block of data and are rapidly switching between books. But as far as I can tell, the only // case where it's at all important to capture the new language data is if the user has been changing settings and // in particular editing the word list. Timing out the save in that situation seems very unlikely to fail. // So, in the interests of preventing the crash when switching books fast, we will ignore failure to read all the // json, and just not update the file. We would in any case keep only the version of the data sent to us by // the last book which sends it, and that one is unlikely to get interrupted. string langdata; try { langdata = request.RequiredPostJson(); } catch (IOException e) { SIL.Reporting.Logger.WriteError("Saving synphonyLanguageData failed to get Json", e); break; } SaveSynphonyLanguageData(langdata); request.PostSucceeded(); break; case "sampleTextsList": //note, as part of this reply, we send the path of the "ReaderToolsWords-xyz.json" which is *written* by the "synphonyLanguageData" endpoint above request.ReplyWithText(GetSampleTextsList(request.CurrentCollectionSettings.SettingsFilePath)); break; case "sampleFileContents": request.ReplyWithText(GetTextFileContents(request.RequiredParam("fileName"), WordFileType.SampleFile)); break; case "textOfContentPages": request.ReplyWithText(GetTextOfContentPagesAsJson()); break; case "makeLetterAndWordList": MakeLetterAndWordList(request.RequiredPostValue("settings"), request.RequiredPostValue("allWords")); request.PostSucceeded(); break; case "openTextsFolder": OpenTextsFolder(); request.PostSucceeded(); break; case "chooseAllowedWordsListFile": lock (request) { request.ReplyWithText(ShowSelectAllowedWordsFileDialog()); } break; case "allowedWordsList": switch (request.HttpMethod) { case HttpMethods.Delete: RecycleAllowedWordListFile(request.RequiredParam("fileName")); request.PostSucceeded(); break; case HttpMethods.Get: var fileName = request.RequiredParam("fileName"); request.ReplyWithText(RemoveEmptyAndDupes(GetTextFileContents(fileName, WordFileType.AllowedWordsFile))); break; default: request.Failed("Http verb not handled"); break; } break; case "defaultLevel": if (request.HttpMethod == HttpMethods.Get) { request.ReplyWithText(Settings.Default.CurrentLevel.ToString()); } else { int level; if (int.TryParse(request.RequiredParam("level"), out level)) { Settings.Default.CurrentLevel = level; Settings.Default.Save(); } else { // Don't think any sort of runtime failure is worthwhile here. Debug.Fail("could not parse level number"); } request.PostSucceeded(); // technically it didn't if we didn't parse the number } break; case "defaultStage": if (request.HttpMethod == HttpMethods.Get) { request.ReplyWithText(Settings.Default.CurrentStage.ToString()); } else { int stage; if (int.TryParse(request.RequiredParam("stage"), out stage)) { Settings.Default.CurrentStage = stage; Settings.Default.Save(); } else { // Don't think any sort of runtime failure is worthwhile here. Debug.Fail("could not parse stage number"); } request.PostSucceeded(); // technically it didn't if we didn't parse the number } break; default: request.Failed("Don't understand '" + lastSegment + "' in " + request.LocalPath()); break; } }
public void HandleRequest(ApiRequest request) { if (CurrentBook == null) { Debug.Fail("BL-836 reproduction?"); // ReSharper disable once HeuristicUnreachableCode request.Failed("CurrentBook is null"); return; } if (request.CurrentCollectionSettings == null) { Debug.Fail("BL-836 reproduction?"); // ReSharper disable once HeuristicUnreachableCode request.Failed("CurrentBook.CollectionSettings is null"); return; } var lastSegment = request.LocalPath().Split(new char[] { '/' }).Last(); switch (lastSegment) { case "test": request.PostSucceeded(); break; case "readerSettingsEditForbidden": request.ReplyWithText(_tcManager.OkToEditCollectionSettings ? "" : WorkspaceView.MustBeAdminMessage); break; case "readerToolSettings": if (request.HttpMethod == HttpMethods.Get) { request.ReplyWithJson(GetReaderSettings(request.CurrentBook.BookData)); } else { var path = DecodableReaderToolSettings.GetReaderToolsSettingsFilePath(request.CurrentCollectionSettings); var content = request.RequiredPostJson(); RobustFile.WriteAllText(path, content, Encoding.UTF8); request.PostSucceeded(); } break; //note, this endpoint is confusing because it appears that ultimately we only use the word list out of this file (see "sampleTextsList"). //This ends up being written to a ReaderToolsWords-xyz.json (matching its use, if not it contents). case "synphonyLanguageData": //This is the "post". There is no direct "get", but the name of the file is given in the "sampleTextList" reply, below. // We've had situations (BL-4313 and friends) where reading the posted data fails. This seems to be due to situations // where we have a very large block of data and are rapidly switching between books. But as far as I can tell, the only // case where it's at all important to capture the new language data is if the user has been changing settings and // in particular editing the word list. Timing out the save in that situation seems very unlikely to fail. // So, in the interests of preventing the crash when switching books fast, we will ignore failure to read all the // json, and just not update the file. We would in any case keep only the version of the data sent to us by // the last book which sends it, and that one is unlikely to get interrupted. string langdata; try { langdata = request.RequiredPostJson(); } catch (IOException e) { SIL.Reporting.Logger.WriteError("Saving synphonyLanguageData failed to get Json", e); break; } SaveSynphonyLanguageData(langdata); request.PostSucceeded(); break; case "sampleTextsList": //note, as part of this reply, we send the path of the "ReaderToolsWords-xyz.json" which is *written* by the "synphonyLanguageData" endpoint above request.ReplyWithText(GetSampleTextsList(request.CurrentCollectionSettings.SettingsFilePath)); break; case "sampleFileContents": request.ReplyWithText(GetTextFileContents(request.RequiredParam("fileName"), WordFileType.SampleFile)); break; case "textOfContentPages": request.ReplyWithText(GetTextOfContentPagesAsJson()); break; case "makeLetterAndWordList": MakeLetterAndWordList(request.RequiredPostString("settings"), request.RequiredPostString("allWords")); request.PostSucceeded(); break; case "openTextsFolder": OpenTextsFolder(); request.PostSucceeded(); break; case "chooseAllowedWordsListFile": lock (request) { request.ReplyWithText(ShowSelectAllowedWordsFileDialog()); } break; case "allowedWordsList": switch (request.HttpMethod) { case HttpMethods.Delete: RecycleAllowedWordListFile(request.RequiredParam("fileName")); request.PostSucceeded(); break; case HttpMethods.Get: var fileName = request.RequiredParam("fileName"); request.ReplyWithText(RemoveEmptyAndDupes(GetTextFileContents(fileName, WordFileType.AllowedWordsFile))); break; default: request.Failed("Http verb not handled"); break; } break; case "defaultLevel": if (request.HttpMethod == HttpMethods.Get) { request.ReplyWithText(Settings.Default.CurrentLevel.ToString()); } else { int level; if (int.TryParse(request.RequiredParam("level"), out level)) { Settings.Default.CurrentLevel = level; Settings.Default.Save(); } else { // Don't think any sort of runtime failure is worthwhile here. Debug.Fail("could not parse level number"); } request.PostSucceeded(); // technically it didn't if we didn't parse the number } break; case "defaultStage": if (request.HttpMethod == HttpMethods.Get) { request.ReplyWithText(Settings.Default.CurrentStage.ToString()); } else { int stage; if (int.TryParse(request.RequiredParam("stage"), out stage)) { Settings.Default.CurrentStage = stage; Settings.Default.Save(); } else { // Don't think any sort of runtime failure is worthwhile here. Debug.Fail("could not parse stage number"); } request.PostSucceeded(); // technically it didn't if we didn't parse the number } break; case "copyBookStatsToClipboard": // See https://issues.bloomlibrary.org/youtrack/issue/BL-10018. string bookStatsString; try { bookStatsString = request.RequiredPostJson(); dynamic bookStats = DynamicJson.Parse(bookStatsString); var headerBldr = new StringBuilder(); var dataBldr = new StringBuilder(); headerBldr.Append("Book Title"); var title = _bookSelection.CurrentSelection.Title; title = title.Replace("\"", "\"\""); // Double double quotes to get Excel to recognize them. dataBldr.AppendFormat("\"{0}\"", title); headerBldr.Append("\tLevel"); dataBldr.AppendFormat("\t\"Level {0}\"", bookStats["levelNumber"]); headerBldr.Append("\tNumber of Pages with Text"); dataBldr.AppendFormat("\t{0}", bookStats["pageCount"]); headerBldr.Append("\tTotal Number of Words"); dataBldr.AppendFormat("\t{0}", bookStats["actualWordCount"]); headerBldr.Append("\tTotal Number of Sentences"); dataBldr.AppendFormat("\t{0}", bookStats["actualSentenceCount"]); headerBldr.Append("\tAverage No of Words per Page with Text"); dataBldr.AppendFormat("\t{0:0.#}", bookStats["actualAverageWordsPerPage"]); headerBldr.Append("\tAverage No of Sentences per Page with Text"); dataBldr.AppendFormat("\t{0:0.#}", bookStats["actualAverageSentencesPerPage"]); headerBldr.Append("\tNumber of Unique Words"); dataBldr.AppendFormat("\t{0}", bookStats["actualUniqueWords"]); headerBldr.Append("\tAverage Word Length"); dataBldr.AppendFormat("\t{0:0.#}", bookStats["actualAverageGlyphsPerWord"]); headerBldr.Append("\tAverage Sentence Length"); dataBldr.AppendFormat("\t{0:0.#}", bookStats["actualAverageWordsPerSentence"]); headerBldr.Append("\tMaximum Word Length"); dataBldr.AppendFormat("\t{0}", bookStats["actualMaxGlyphsPerWord"]); headerBldr.Append("\tMaximum Sentence Length"); dataBldr.AppendFormat("\t{0}", bookStats["actualMaxWordsPerSentence"]); // "actualWordsPerPageBook" is the maximum number of words on a page in the book // It's in the json data, but not asked for in the clipboard copying. var stringToSave = headerBldr.ToString() + Environment.NewLine + dataBldr.ToString(); PortableClipboard.SetText(stringToSave); } catch (IOException e) { SIL.Reporting.Logger.WriteError("Copying book statistics to clipboard failed to get Json", e); break; } request.PostSucceeded(); break; default: request.Failed("Don't understand '" + lastSegment + "' in " + request.LocalPath()); break; } }
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 }
// 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"); }
/// <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 }
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 }
/// <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 }
/// <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> /// 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); } }
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; }