private void CheckForSampleTextChanges(ApiRequest request) { if (_sampleTextsWatcher == null) { if (string.IsNullOrEmpty(request.CurrentCollectionSettings?.SettingsFilePath)) { // We've had cases (BL-4744) where this is apparently called before CurrentCollectionSettings is // established. I'm not sure how this can happen but if we haven't even established a current collection // yet I think it's pretty safe to say its sample texts haven't changed since we last read them. request.ReplyWithText("no"); return; } var path = Path.Combine(Path.GetDirectoryName(request.CurrentCollectionSettings.SettingsFilePath), "Sample Texts"); if (!Directory.Exists(path)) { Directory.CreateDirectory(path); } _sampleTextsWatcher = new FileSystemWatcher { Path = path }; _sampleTextsWatcher.Created += SampleTextsOnChange; _sampleTextsWatcher.Changed += SampleTextsOnChange; _sampleTextsWatcher.Renamed += SampleTextsOnChange; _sampleTextsWatcher.Deleted += SampleTextsOnChange; _sampleTextsWatcher.EnableRaisingEvents = true; } lock (_sampleTextsWatcher) { var hasChanged = _sampleTextsChanged; // Reset the changed flag. // NOTE: we are only resetting the flag if it was "true" when we checked in case the FileSystemWatcher detects a change // after we check the flag but we reset it to false before we check again. if (hasChanged) { _sampleTextsChanged = false; } request.ReplyWithText(hasChanged ? "yes" : "no"); } }
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 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"]; string dontWarnIfMissing = parameters["dontWarnIfMissing"]; bool isDontWarnIfMissing = dontWarnIfMissing != null && dontWarnIfMissing.Equals("true"); string isoCode1; string isoCodeM1; string isoCodeM2; if (request.CurrentBook != null) { isoCode1 = request.CurrentBook.BookData.Language1IsoCode; isoCodeM1 = request.CurrentBook.BookData.MetadataLanguage1IsoCode; isoCodeM2 = request.CurrentBook.BookData.MetadataLanguage2IsoCode; } else { isoCode1 = request.CurrentCollectionSettings.Language1.Iso639Code; isoCodeM1 = request.CurrentCollectionSettings.Language2.Iso639Code; isoCodeM2 = request.CurrentCollectionSettings.Language3?.Iso639Code ?? ""; } langId = langId.Replace("V", isoCode1); langId = langId.Replace("N1", isoCodeM1); langId = langId.Replace("N2", isoCodeM2); langId = langId.Replace("UI", LocalizationManager.UILanguageId); if (GetSomeTranslation(id, langId, out var 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")) { if (!isDontWarnIfMissing) { 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; case "uilang": request.ReplyWithText(LocalizationManager.UILanguageId); 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.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 HandleCheckForSegment(ApiRequest request) { var path = GetPathToSegment(request.RequiredParam("id")); request.ReplyWithText(RobustFile.Exists(path) ? "exists" : "not found"); }
/// <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 }