public void RegisterWithApiHandler(BloomApiHandler apiHandler) { // Must not require sync, because it launches a Bloom dialog, which will make other api requests // that will be blocked if this is locked. apiHandler.RegisterEndpointHandler("bookCommand/exportToSpreadsheet", (request) => { _spreadsheetApi.ShowExportToSpreadsheetUI(GetBookObjectFromPost(request)); request.PostSucceeded(); }, true, false); apiHandler.RegisterEndpointHandler("bookCommand/enhanceLabel", (request) => { // We want this to be fast...many things are competing for api handling threads while // Bloom is starting up, including many buttons sending this request... // so we'll postpone until idle even searching for the right BookInfo. var collection = request.RequiredParam("collection-id").Trim(); var id = request.RequiredParam("id"); RequestButtonLabelUpdate(collection, id); request.PostSucceeded(); }, false, false); apiHandler.RegisterBooleanEndpointHandler("bookCommand/decodable", request => { return(_collectionModel.IsBookDecodable); }, (request, b) => { _collectionModel.SetIsBookDecodable(b); }, true); apiHandler.RegisterBooleanEndpointHandler("bookCommand/leveled", request => { return(_collectionModel.IsBookLeveled); }, (request, b) => { _collectionModel.SetIsBookLeveled(b); }, true); apiHandler.RegisterEndpointLegacy("bookCommand/", HandleBookCommand, true); }
public void RegisterWithApiHandler(BloomApiHandler apiHandler) { apiHandler.RegisterEndpointHandler("teamCollection/repoFolderPath", HandleRepoFolderPath, false); apiHandler.RegisterEndpointHandler("teamCollection/isTeamCollectionEnabled", HandleIsTeamCollectionEnabled, false); apiHandler.RegisterEndpointHandler("teamCollection/bookStatus", HandleBookStatus, false, false); apiHandler.RegisterEndpointHandler("teamCollection/selectedBookStatus", HandleSelectedBookStatus, false); apiHandler.RegisterEndpointHandler("teamCollection/attemptLockOfCurrentBook", HandleAttemptLockOfCurrentBook, true); apiHandler.RegisterEndpointHandler("teamCollection/checkInCurrentBook", HandleCheckInCurrentBook, true); apiHandler.RegisterEndpointHandler("teamCollection/forgetChangesInSelectedBook", HandleForgetChangesInSelectedBook, true); apiHandler.RegisterEndpointHandler("teamCollection/chooseFolderLocation", HandleChooseFolderLocation, true); apiHandler.RegisterEndpointHandler("teamCollection/createTeamCollection", HandleCreateTeamCollection, true); apiHandler.RegisterEndpointHandler("teamCollection/joinTeamCollection", HandleJoinTeamCollection, true); apiHandler.RegisterEndpointHandler("teamCollection/getLog", HandleGetLog, false); apiHandler.RegisterEndpointHandler("teamCollection/getCollectionName", HandleGetCollectionName, false); apiHandler.RegisterEndpointHandler("teamCollection/showCreateTeamCollectionDialog", HandleShowCreateTeamCollectionDialog, true); apiHandler.RegisterEndpointHandler("teamCollection/reportBadZip", HandleReportBadZip, true); apiHandler.RegisterEndpointHandler("teamCollection/showRegistrationDialog", HandleShowRegistrationDialog, true, false); apiHandler.RegisterEndpointHandler("teamCollection/getHistory", HandleGetHistory, true); apiHandler.RegisterEndpointHandler("teamCollection/checkinMessage", HandleCheckinMessage, false); apiHandler.RegisterEndpointHandler("teamCollection/forceUnlock", HandleForceUnlock, false); apiHandler.RegisterBooleanEndpointHandler("teamCollection/logImportant", HandleLogImportant, null, false); }
public void RegisterWithApiHandler(BloomApiHandler apiHandler) { // This is just for storing the user preference of method // If we had a couple of these, we could just have a generic preferences api // that browser-side code could use. apiHandler.RegisterEndpointLegacy(kApiUrlPart + "method", request => { if (request.HttpMethod == HttpMethods.Get) { var method = Settings.Default.PublishAndroidMethod; if (!new string[] { "wifi", "usb", "file" }.Contains(method)) { method = "wifi"; } request.ReplyWithText(method); } else // post { Settings.Default.PublishAndroidMethod = request.RequiredPostString(); #if __MonoCS__ if (Settings.Default.PublishAndroidMethod == "usb") { _progress.MessageWithoutLocalizing("Sorry, this method is not available on Linux yet."); } #endif request.PostSucceeded(); } }, true); apiHandler.RegisterEndpointLegacy(kApiUrlPart + "backColor", request => { if (request.HttpMethod == HttpMethods.Get) { if (request.CurrentBook != _coverColorSourceBook) { _coverColorSourceBook = request.CurrentBook; ImageUtils.TryCssColorFromString(request.CurrentBook?.GetCoverColor() ?? "", out _thumbnailBackgroundColor); } request.ReplyWithText(ToCssColorString(_thumbnailBackgroundColor)); } else // post { // ignore invalid colors (very common while user is editing hex) Color newColor; var newColorAsString = request.RequiredPostString(); if (ImageUtils.TryCssColorFromString(newColorAsString, out newColor)) { _thumbnailBackgroundColor = newColor; request.CurrentBook.SetCoverColor(newColorAsString); } request.PostSucceeded(); } }, true); apiHandler.RegisterBooleanEndpointHandler(kApiUrlPart + "motionBookMode", readRequest => { // If the user has taken off all possible motion, force not having motion in the // Bloom Reader book. See https://issues.bloomlibrary.org/youtrack/issue/BL-7680. if (!readRequest.CurrentBook.HasMotionPages) { readRequest.CurrentBook.BookInfo.PublishSettings.BloomPub.Motion = false; } return(readRequest.CurrentBook.BookInfo.PublishSettings.BloomPub.Motion); }, (writeRequest, value) => { writeRequest.CurrentBook.BookInfo.PublishSettings.BloomPub.Motion = value; writeRequest.CurrentBook.BookInfo.SavePublishSettings(); _webSocketServer.SendEvent("publish", "motionChanged"); } , true); apiHandler.RegisterEndpointHandler(kApiUrlPart + "updatePreview", request => { MakeBloompubPreview(request, false); }, false); apiHandler.RegisterEndpointLegacy(kApiUrlPart + "thumbnail", request => { var coverImage = request.CurrentBook.GetCoverImagePath(); if (coverImage == null) { request.Failed("no cover image"); } else { // We don't care as much about making it resized as making its background transparent. using (var thumbnail = TempFile.CreateAndGetPathButDontMakeTheFile()) { if (_thumbnailBackgroundColor == Color.Transparent) { ImageUtils.TryCssColorFromString(request.CurrentBook?.GetCoverColor(), out _thumbnailBackgroundColor); } RuntimeImageProcessor.GenerateEBookThumbnail(coverImage, thumbnail.Path, 256, 256, _thumbnailBackgroundColor); request.ReplyWithImage(thumbnail.Path); } } }, true); apiHandler.RegisterEndpointHandler(kApiUrlPart + "usb/start", request => { #if !__MonoCS__ SetState("UsbStarted"); UpdatePreviewIfNeeded(request); _usbPublisher.Connect(request.CurrentBook, _thumbnailBackgroundColor, GetSettings()); #endif request.PostSucceeded(); }, true); apiHandler.RegisterEndpointHandler(kApiUrlPart + "usb/stop", request => { #if !__MonoCS__ _usbPublisher.Stop(disposing: false); SetState("stopped"); #endif request.PostSucceeded(); }, true); apiHandler.RegisterEndpointLegacy(kApiUrlPart + "wifi/start", request => { SetState("ServingOnWifi"); UpdatePreviewIfNeeded(request); _wifiPublisher.Start(request.CurrentBook, request.CurrentCollectionSettings, _thumbnailBackgroundColor, GetSettings()); request.PostSucceeded(); }, true); apiHandler.RegisterEndpointLegacy(kApiUrlPart + "wifi/stop", request => { _wifiPublisher.Stop(); SetState("stopped"); request.PostSucceeded(); }, true); apiHandler.RegisterEndpointLegacy(kApiUrlPart + "file/save", request => { UpdatePreviewIfNeeded(request); FilePublisher.Save(request.CurrentBook, _bookServer, _thumbnailBackgroundColor, _progress, GetSettings()); SetState("stopped"); request.PostSucceeded(); }, true); apiHandler.RegisterEndpointLegacy(kApiUrlPart + "file/bulkSaveBloomPubsParams", request => { request.ReplyWithJson(JsonConvert.SerializeObject(_collectionSettings.BulkPublishBloomPubSettings)); }, true); apiHandler.RegisterEndpointLegacy(kApiUrlPart + "file/bulkSaveBloomPubs", request => { // update what's in the collection so that we remember for next time _collectionSettings.BulkPublishBloomPubSettings = request.RequiredPostObject <BulkBloomPubPublishSettings>(); _collectionSettings.Save(); _bulkBloomPubCreator.PublishAllBooks(_collectionSettings.BulkPublishBloomPubSettings); SetState("stopped"); request.PostSucceeded(); }, true); apiHandler.RegisterEndpointLegacy(kApiUrlPart + "textToClipboard", request => { PortableClipboard.SetText(request.RequiredPostString()); request.PostSucceeded(); }, true); apiHandler.RegisterBooleanEndpointHandler(kApiUrlPart + "canHaveMotionMode", request => { return(request.CurrentBook.HasMotionPages); }, null, // no write action false, true); // we don't really know, just safe default apiHandler.RegisterBooleanEndpointHandler(kApiUrlPart + "canRotate", request => { return(request.CurrentBook.BookInfo.PublishSettings.BloomPub.Motion && request.CurrentBook.HasMotionPages); }, null, // no write action false, true); // we don't really know, just safe default apiHandler.RegisterBooleanEndpointHandler(kApiUrlPart + "defaultLandscape", request => { return(request.CurrentBook.GetLayout().SizeAndOrientation.IsLandScape); }, null, // no write action false, true); // we don't really know, just safe default apiHandler.RegisterEndpointLegacy(kApiUrlPart + "languagesInBook", request => { try { InitializeLanguagesInBook(request); Dictionary <string, InclusionSetting> textLangsToPublish = request.CurrentBook.BookInfo.PublishSettings.BloomPub.TextLangs; Dictionary <string, InclusionSetting> audioLangsToPublish = request.CurrentBook.BookInfo.PublishSettings.BloomPub.AudioLangs; var result = "[" + string.Join(",", _allLanguages.Select(kvp => { string langCode = kvp.Key; bool includeText = false; if (textLangsToPublish != null && textLangsToPublish.TryGetValue(langCode, out InclusionSetting includeTextSetting)) { includeText = includeTextSetting.IsIncluded(); } bool includeAudio = false; if (audioLangsToPublish != null && audioLangsToPublish.TryGetValue(langCode, out InclusionSetting includeAudioSetting)) { includeAudio = includeAudioSetting.IsIncluded(); } var value = new LanguagePublishInfo() { code = kvp.Key, name = request.CurrentBook.PrettyPrintLanguage(langCode), complete = kvp.Value, includeText = includeText, containsAnyAudio = _languagesWithAudio.Contains(langCode), includeAudio = includeAudio }; var json = JsonConvert.SerializeObject(value); return(json); })) + "]"; request.ReplyWithText(result); }
public void RegisterWithApiHandler(BloomApiHandler apiHandler) { apiHandler.RegisterEndpointHandler(kApiUrlPart + "save", request => { { string suggestedName = string.Format("{0}-{1}.epub", Path.GetFileName(_bookSelection.CurrentSelection.FolderPath), _bookSelection.CurrentSelection.BookData.Language1.GetNameInLanguage("en")); using (var dlg = new DialogAdapters.SaveFileDialogAdapter()) { if (!string.IsNullOrEmpty(_lastDirectory) && Directory.Exists(_lastDirectory)) { dlg.InitialDirectory = _lastDirectory; } dlg.FileName = suggestedName; dlg.Filter = "EPUB|*.epub"; dlg.OverwritePrompt = true; if (DialogResult.OK == dlg.ShowDialog()) { _lastDirectory = Path.GetDirectoryName(dlg.FileName); lock (_epubMakerLock) { _pendingSaveAsPath = dlg.FileName; if (!_stagingEpub) { // we can do it right now. No need to check version etc., because anything // that will change the epub we want to save will immediately trigger a new // preview, and we will be staging it until we have it. SaveAsEpub(); } // If we ARE in the middle of staging the epub...quite possible since this // handler is registered with permission to execute in parallel with other // API handlers, the user just has to click Save before the preview is finished... // then we need not do any more here. A call to SaveAsEpub at the end of the // preview generation process will pick up the pending request in _pendingSaveAsPath // and complete the Save. } ReportProgress(LocalizationManager.GetString("PublishTab.Epub.Done", "Done")); ReportAnalytics("Save ePUB"); } } request.PostSucceeded(); } }, true, false); apiHandler.RegisterEndpointHandler(kApiUrlPart + "epubSettings", request => { if (request.HttpMethod == HttpMethods.Get) { request.ReplyWithJson(GetEpubSettings()); } else { // post is deprecated. throw new ApplicationException("epubSettings POST is deprecated"); } }, false); // The backend here was written with an enum that had two choices for how to publish descriptions, but we only ever // have used one of them so far in the UI. So this is a boolean api that converts to an enum underlying value. apiHandler.RegisterBooleanEndpointHandler(kApiUrlPart + "imageDescriptionSetting", request => request.CurrentBook.BookInfo.MetaData.Epub_HowToPublishImageDescriptions == BookInfo.HowToPublishImageDescriptions.OnPage, (request, onPage) => { request.CurrentBook.BookInfo.MetaData.Epub_HowToPublishImageDescriptions = onPage ? BookInfo.HowToPublishImageDescriptions.OnPage : BookInfo.HowToPublishImageDescriptions.None; request.CurrentBook.BookInfo.Save(); var newSettings = _desiredEpubSettings.Clone(); newSettings.howToPublishImageDescriptions = request.CurrentBook.BookInfo.MetaData.Epub_HowToPublishImageDescriptions; RefreshPreview(newSettings); }, false); // Saving a checkbox setting that the user ticks to say "Use my E-reader's font sizes" apiHandler.RegisterBooleanEndpointHandler(kApiUrlPart + "removeFontSizesSetting", request => request.CurrentBook.BookInfo.MetaData.Epub_RemoveFontSizes, (request, booleanSetting) => { request.CurrentBook.BookInfo.MetaData.Epub_RemoveFontSizes = booleanSetting; request.CurrentBook.BookInfo.Save(); var newSettings = _desiredEpubSettings.Clone(); newSettings.removeFontSizes = booleanSetting; RefreshPreview(newSettings); }, false); apiHandler.RegisterEndpointHandler(kApiUrlPart + "updatePreview", request => { RefreshPreview(_desiredEpubSettings); request.PostSucceeded(); }, false); // in fact, must NOT be on UI thread apiHandler.RegisterEndpointHandler(kApiUrlPart + "abortPreview", request => { AbortMakingEpub(); request.PostSucceeded(); }, false, false); }
public void RegisterWithApiHandler(BloomApiHandler apiHandler) { apiHandler.RegisterEndpointHandler(kApiUrlPart + "bookName", request => { request.ReplyWithText(request.CurrentBook.TitleBestForUserDisplay); }, false); apiHandler.RegisterEndpointHandler(kApiUrlPart + "showAccessibilityChecker", request => { AccessibilityCheckWindow.StaticShow(() => _webSocketServer.SendEvent(kWebSocketContext, kWindowActivated)); request.PostSucceeded(); }, true); apiHandler.RegisterEndpointHandler(kApiUrlPart + "descriptionsForAllImages", request => { var problems = AccessibilityCheckers.CheckDescriptionsForAllImages(request.CurrentBook); var resultClass = problems.Any() ? "failed" : "passed"; request.ReplyWithJson(new { resultClass = resultClass, problems = problems }); }, false); apiHandler.RegisterEndpointHandler(kApiUrlPart + "audioForAllImageDescriptions", request => { var problems = AccessibilityCheckers.CheckAudioForAllImageDescriptions(request.CurrentBook); var resultClass = problems.Any() ? "failed" : "passed"; request.ReplyWithJson(new { resultClass = resultClass, problems = problems }); }, false); apiHandler.RegisterEndpointHandler(kApiUrlPart + "audioForAllText", request => { var problems = AccessibilityCheckers.CheckAudioForAllText(request.CurrentBook); var resultClass = problems.Any() ? "failed" : "passed"; request.ReplyWithJson(new { resultClass = resultClass, problems = problems }); }, false); // Just a checkbox that the user ticks to say "yes, I checked this" // At this point, we don't have a way to clear that when the book changes. apiHandler.RegisterBooleanEndpointHandler(kApiUrlPart + "noEssentialInfoByColor", request => request.CurrentBook.BookInfo.MetaData.A11y_NoEssentialInfoByColor, (request, b) => { request.CurrentBook.BookInfo.MetaData.A11y_NoEssentialInfoByColor = b; request.CurrentBook.Save(); }, false); // Just a checkbox that the user ticks to say "yes, I checked this" // At this point, we don't have a way to clear that when the book changes. apiHandler.RegisterBooleanEndpointHandler(kApiUrlPart + "noTextIncludedInAnyImages", request => request.CurrentBook.BookInfo.MetaData.A11y_NoTextIncludedInAnyImages, (request, b) => { request.CurrentBook.BookInfo.MetaData.A11y_NoTextIncludedInAnyImages = b; request.CurrentBook.Save(); }, false); //enhance: this might have to become async to work on large books on slow computers apiHandler.RegisterEndpointHandler(kApiUrlPart + "aceByDaisyReportUrl", request => { MakeAceByDaisyReport(request); }, false, false ); // A checkbox that the user ticks in the Accessible Image tool to request a preview // of how things might look with cataracts. // For now this doesn't seem worth persisting, except for the session so it sticks from page to page. apiHandler.RegisterBooleanEndpointHandler(kApiUrlPart + "cataracts", request => _simulateCataracts, (request, b) => { _simulateCataracts = b; }, false); // A checkbox that the user ticks in the Accessible Image tool to request a preview // of how things might look with color-blindness, and a set of radio buttons // for choosing different kinds of color-blindness. // For now these doesn't seem worth persisting, except for the session so it sticks from page to page. apiHandler.RegisterBooleanEndpointHandler(kApiUrlPart + "colorBlindness", request => _simulateColorBlindness, (request, b) => { _simulateColorBlindness = b; }, false); apiHandler.RegisterEnumEndpointHandler(kApiUrlPart + "kindOfColorBlindness", request => _kindOfColorBlindness, (request, kind) => _kindOfColorBlindness = kind, false); }
public void RegisterWithApiHandler(BloomApiHandler apiHandler) { apiHandler.RegisterEndpointHandler(kApiUrlPart + "recordVideo", request => { RecordVideo(request); request.PostSucceeded(); }, true, false); // This is sent directly from BloomPlayer when it gets to the end of making the recording. // The player gives Bloom a list of all the sounds it played and their timings so we can // merge them into the captured video. apiHandler.RegisterEndpointHandler(kApiUrlPart + "soundLog", request => { var soundLog = request.RequiredPostJson(); _recordVideoWindow.StopRecording(soundLog); request.PostSucceeded(); }, true, false); apiHandler.RegisterEndpointHandler(kApiUrlPart + "playVideo", request => { _recordVideoWindow.PlayVideo(); request.PostSucceeded(); }, true, false); apiHandler.RegisterEndpointHandler(kApiUrlPart + "settings", request => { if (request.HttpMethod == HttpMethods.Get) { var settings = request.CurrentBook.BookInfo.PublishSettings.AudioVideo; var lastPageIndex = request.CurrentBook.GetPages().Count() - 1; // this default might be too high but I don't think it will ever be too low. // The HTML side will handle it being too high. var pageRange = new[] { 0, lastPageIndex }; if (settings.PageRange != null && settings.PageRange.Length == 2) { // I wanted to do something like this as validation, in case the book changed // since we saved. But we don't have an accurate page count of the modified version // of the book that the range applies to yet. (e.g., switch to device xmatter, strip // activities,... //pageRange[1] = Math.Min(settings.PageRange[1], lastPageIndex); // not more than the pages we have //pageRange[0] = Math.Min(settings.PageRange[0], pageRange[1] - 1); // at least one less than lim //if (pageRange[0] < 0) // pageRange[0] = 0; //if (pageRange[1] < pageRange[0] + 1) // pageRange[1] = Math.Min(pageRange[0] + 1, lastPageIndex); pageRange = settings.PageRange; } request.ReplyWithJson(new { format = settings.Format, pageTurnDelay = settings.PageTurnDelayDouble, motion = settings.Motion, pageRange }); } else { var data = DynamicJson.Parse(request.RequiredPostJson()); var settings = request.CurrentBook.BookInfo.PublishSettings.AudioVideo; var oldMotion = settings.Motion; settings.Format = data.format; settings.PageTurnDelayDouble = data.pageTurnDelay; settings.Motion = data.motion; // pageRange also comes in as doubles. "as double[]" does not work, so have to do them individually. settings.PageRange = new int[0]; // Typescript passes an empty array if all pages are selected. // This allows us naturally to keep everything selected until the user explicitly changes something. if (data.pageRange.IsArray && data.pageRange.Count == 2) { var start = (int)data.pageRange[0]; var end = (int)data.pageRange[1]; settings.PageRange = new[] { start, end }; } request.CurrentBook.BookInfo.SavePublishSettings(); _recordVideoWindow?.SetPageReadTime(settings.PageTurnDelayDouble.ToString()); _recordVideoWindow?.SetFormat(request.CurrentBook.BookInfo.PublishSettings.AudioVideo.Format, ShouldRecordAsLandscape(request.CurrentBook), request.CurrentBook.GetLayout()); if (settings.Motion != oldMotion) { UpdatePreview(request); // does its own success/fail } else { request.PostSucceeded(); } } }, true, false); apiHandler.RegisterEndpointHandler(kApiUrlPart + "videoSettings", request => { if (request.HttpMethod == HttpMethods.Get) { request.ReplyWithText(request.CurrentBook.BookInfo.PublishSettings.AudioVideo.PlayerSettings ?? ""); } else { request.CurrentBook.BookInfo.PublishSettings.AudioVideo.PlayerSettings = request.RequiredPostString(); request.CurrentBook.BookInfo.SavePublishSettings(); request.PostSucceeded(); } }, true, false); apiHandler.RegisterBooleanEndpointHandler(kApiUrlPart + "hasActivities", request => { return(request.CurrentBook.HasActivities); }, null, // no write action false, true); // we don't really know, just safe default // Returns true if publish to MP3 is supported for this book, false otherwise. // To be eligible to publish to MP3, the book must contain narration audio. // (There's not any point to an MP3 if the book has absolutely no audio... // Even a book with background music but no narration, there's not too much point to an MP3 either) apiHandler.RegisterBooleanEndpointHandler(kApiUrlPart + "isMP3FormatSupported", request => { // ENHANCE: If desired, you can make it only consider languages in the book that are currently relevant, // instead of any language in the book. var narrationAudioLangs = request.CurrentBook.GetLanguagesWithAudio(); return(narrationAudioLangs.Any()); }, null, false, false); apiHandler.RegisterEndpointHandler(kApiUrlPart + "startRecording", request => { var videoList = request.GetPostStringOrNull(); var anyVideoHasAudio = _recordVideoWindow?.AnyVideoHasAudio(videoList, request.CurrentBook.FolderPath) ?? false; if (anyVideoHasAudio) { var messageBoxButtons = new[] { new MessageBoxButton() { Text = LocalizationManager.GetString("Common.Continue", "Continue"), Id = "continue" }, new MessageBoxButton() { Text = LocalizationManager.GetString("Common.Cancel", "Cancel"), Id = "cancel", Default = true } }; if (BloomMessageBox.Show(null, LocalizationManager.GetString("PublishTab.RecordVideo.NoAudioInVideo", "Currently, Bloom does not support including audio from Sign Language videos in video output."), messageBoxButtons) == "cancel") { request.Failed("canceled"); _recordVideoWindow.Close(); return; } } _recordVideoWindow?.StartFfmpegForVideoCapture(); request.PostSucceeded(); }, true, false); apiHandler.RegisterEndpointHandler(kApiUrlPart + "shouldUseOriginalPageSize", request => { RecordVideoWindow.GetDataForFormat(request.CurrentBook.BookInfo.PublishSettings.AudioVideo.Format, ShouldRecordAsLandscape(request.CurrentBook), request.CurrentBook.GetLayout(), out _, out _, out _, out bool useOriginalPageSize); request.ReplyWithBoolean(useOriginalPageSize); }, true, // has to be on UI thread because it uses Bloom's main window to find the right screen false); apiHandler.RegisterEndpointHandler(kApiUrlPart + "tooBigForScreenMsg", request => { request.ReplyWithText(RecordVideoWindow.GetDataForFormat(request.CurrentBook.BookInfo.PublishSettings.AudioVideo.Format, ShouldRecordAsLandscape(request.CurrentBook), request.CurrentBook.GetLayout(), out _, out _, out _, out _)); }, true, // has to be on UI thread because it uses Bloom's main window to find the right screen false); apiHandler.RegisterEndpointHandler(kApiUrlPart + "saveVideo", request => { if (_recordVideoWindow == null) { // This shouldn't be possible, but just in case, we'll kick off the recording now. RecordVideo(request); } _recordVideoWindow?.SetBook(request.CurrentBook); // If we bring up the dialog inside this API call, it may time out. Application.Idle += SaveVideoOnIdle; request.PostSucceeded(); }, true, false); apiHandler.RegisterEndpointHandler(kApiUrlPart + "updatePreview", request => { UpdatePreview(request); }, false); apiHandler.RegisterEndpointHandler(kApiUrlPart + "displaySettings", request => { Process.Start("desk.cpl"); request.PostSucceeded(); }, false); apiHandler.RegisterBooleanEndpointHandler(kApiUrlPart + "isScalingActive", request => IsScalingActive(), null, true); }
public void RegisterWithApiHandler(BloomApiHandler apiHandler) { apiHandler.RegisterEndpointHandler(kApiUrlPart + "save", HandleEpubSave, true, false); apiHandler.RegisterEndpointLegacy(kApiUrlPart + "epubSettings", request => { if (request.HttpMethod == HttpMethods.Get) { request.ReplyWithJson(GetEpubSettings()); } else { // post is deprecated. throw new ApplicationException("epubSettings POST is deprecated"); } }, false); // The backend here was written with an enum that had two choices for how to publish descriptions, but we only ever // have used one of them so far in the UI. So this is a boolean api that converts to an enum underlying value. apiHandler.RegisterBooleanEndpointHandler(kApiUrlPart + "imageDescriptionSetting", request => request.CurrentBook.BookInfo.PublishSettings.Epub.HowToPublishImageDescriptions == BookInfo.HowToPublishImageDescriptions.OnPage, (request, onPage) => { request.CurrentBook.BookInfo.PublishSettings.Epub.HowToPublishImageDescriptions = onPage ? BookInfo.HowToPublishImageDescriptions.OnPage : BookInfo.HowToPublishImageDescriptions.None; request.CurrentBook.BookInfo.Save(); var newSettings = _desiredEpubSettings.Clone(); newSettings.howToPublishImageDescriptions = request.CurrentBook.BookInfo.PublishSettings.Epub.HowToPublishImageDescriptions; RefreshPreview(newSettings); }, false); // Saving a checkbox setting that the user ticks to say "Use my E-reader's font sizes" apiHandler.RegisterBooleanEndpointHandler(kApiUrlPart + "removeFontSizesSetting", request => request.CurrentBook.BookInfo.PublishSettings.Epub.RemoveFontSizes, (request, booleanSetting) => { request.CurrentBook.BookInfo.PublishSettings.Epub.RemoveFontSizes = booleanSetting; request.CurrentBook.BookInfo.Save(); var newSettings = _desiredEpubSettings.Clone(); newSettings.removeFontSizes = booleanSetting; RefreshPreview(newSettings); }, false); apiHandler.RegisterEndpointLegacy(kApiUrlPart + "updatePreview", request => { RefreshPreview(_desiredEpubSettings); request.PostSucceeded(); if (request.CurrentBook?.ActiveLanguages != null) { var message = new LicenseChecker().CheckBook(request.CurrentBook, request.CurrentBook.ActiveLanguages.ToArray()); _webSocketServer.SendString(kWebsocketContext, kWebsocketState_LicenseOK, (message == null) ? "true" : "false"); } }, false); // in fact, must NOT be on UI thread apiHandler.RegisterEndpointLegacy(kApiUrlPart + "abortPreview", request => { AbortMakingEpub(); request.PostSucceeded(); }, false, false); }
public void RegisterWithApiHandler(BloomApiHandler apiHandler) { // This is just for storing the user preference of method // If we had a couple of these, we could just have a generic preferences api // that browser-side code could use. apiHandler.RegisterEndpointHandler(kApiUrlPart + "method", request => { if (request.HttpMethod == HttpMethods.Get) { var method = Settings.Default.PublishAndroidMethod; if (!new string[] { "wifi", "usb", "file" }.Contains(method)) { method = "wifi"; } request.ReplyWithText(method); } else // post { Settings.Default.PublishAndroidMethod = request.RequiredPostString(); #if __MonoCS__ if (Settings.Default.PublishAndroidMethod == "usb") { _progress.MessageWithoutLocalizing("Sorry, this method is not available on Linux yet."); } #endif request.PostSucceeded(); } }, true); apiHandler.RegisterEndpointHandler(kApiUrlPart + "backColor", request => { if (request.HttpMethod == HttpMethods.Get) { if (request.CurrentBook != _coverColorSourceBook) { _coverColorSourceBook = request.CurrentBook; ImageUtils.TryCssColorFromString(request.CurrentBook?.GetCoverColor() ?? "", out _thumbnailBackgroundColor); } request.ReplyWithText(ToCssColorString(_thumbnailBackgroundColor)); } else // post { // ignore invalid colors (very common while user is editing hex) Color newColor; var newColorAsString = request.RequiredPostString(); if (ImageUtils.TryCssColorFromString(newColorAsString, out newColor)) { _thumbnailBackgroundColor = newColor; request.CurrentBook.SetCoverColor(newColorAsString); } request.PostSucceeded(); } }, true); apiHandler.RegisterBooleanEndpointHandler(kApiUrlPart + "motionBookMode", readRequest => { // If the user has taken off all possible motion, force not having motion in the // Bloom Reader book. See https://issues.bloomlibrary.org/youtrack/issue/BL-7680. if (!readRequest.CurrentBook.HasMotionPages) { readRequest.CurrentBook.MotionMode = false; } return(readRequest.CurrentBook.MotionMode); }, (writeRequest, value) => { writeRequest.CurrentBook.MotionMode = value; } , true); apiHandler.RegisterEndpointHandler(kApiUrlPart + "updatePreview", request => { if (request.HttpMethod == HttpMethods.Post) { // This is already running on a server thread, so there doesn't seem to be any need to kick off // another background one and return before the preview is ready. But in case something in C# // might one day kick of a new preview, or we find we do need a background thread, // I've made it a websocket broadcast when it is ready. // If we've already left the publish tab...we can get a few of these requests queued up when // a tester rapidly toggles between views...abandon the attempt if (!PublishHelper.InPublishTab) { request.Failed("aborted, no longer in publish tab"); return; } try { UpdatePreview(request); request.PostSucceeded(); } catch (Exception e) { request.Failed("Error while updating preview. Message: " + e.Message); NonFatalProblem.Report(ModalIf.Alpha, PassiveIf.All, "Error while updating preview.", null, e, true); } } }, false); apiHandler.RegisterEndpointHandler(kApiUrlPart + "thumbnail", request => { var coverImage = request.CurrentBook.GetCoverImagePath(); if (coverImage == null) { request.Failed("no cover image"); } else { // We don't care as much about making it resized as making its background transparent. using (var thumbnail = TempFile.CreateAndGetPathButDontMakeTheFile()) { if (_thumbnailBackgroundColor == Color.Transparent) { ImageUtils.TryCssColorFromString(request.CurrentBook?.GetCoverColor(), out _thumbnailBackgroundColor); } RuntimeImageProcessor.GenerateEBookThumbnail(coverImage, thumbnail.Path, 256, 256, _thumbnailBackgroundColor); request.ReplyWithImage(thumbnail.Path); } } }, true); apiHandler.RegisterEndpointHandler(kApiUrlPart + "usb/start", request => { #if !__MonoCS__ SetState("UsbStarted"); UpdatePreviewIfNeeded(request); _usbPublisher.Connect(request.CurrentBook, _thumbnailBackgroundColor, GetSettings()); #endif request.PostSucceeded(); }, true); apiHandler.RegisterEndpointHandler(kApiUrlPart + "usb/stop", request => { #if !__MonoCS__ _usbPublisher.Stop(); SetState("stopped"); #endif request.PostSucceeded(); }, true); apiHandler.RegisterEndpointHandler(kApiUrlPart + "wifi/start", request => { SetState("ServingOnWifi"); UpdatePreviewIfNeeded(request); _wifiPublisher.Start(request.CurrentBook, request.CurrentCollectionSettings, _thumbnailBackgroundColor, GetSettings()); request.PostSucceeded(); }, true); apiHandler.RegisterEndpointHandler(kApiUrlPart + "wifi/stop", request => { _wifiPublisher.Stop(); SetState("stopped"); request.PostSucceeded(); }, true); apiHandler.RegisterEndpointHandler(kApiUrlPart + "file/save", request => { UpdatePreviewIfNeeded(request); FilePublisher.Save(request.CurrentBook, _bookServer, _thumbnailBackgroundColor, _progress, GetSettings()); SetState("stopped"); request.PostSucceeded(); }, true); apiHandler.RegisterEndpointHandler(kApiUrlPart + "cleanup", request => { Stop(); request.PostSucceeded(); }, true); apiHandler.RegisterEndpointHandler(kApiUrlPart + "textToClipboard", request => { PortableClipboard.SetText(request.RequiredPostString()); request.PostSucceeded(); }, true); apiHandler.RegisterBooleanEndpointHandler(kApiUrlPart + "canHaveMotionMode", request => { return(request.CurrentBook.HasMotionPages); }, null, // no write action false, true); // we don't really know, just safe default apiHandler.RegisterBooleanEndpointHandler(kApiUrlPart + "canRotate", request => { return(request.CurrentBook.MotionMode && request.CurrentBook.HasMotionPages); }, null, // no write action false, true); // we don't really know, just safe default apiHandler.RegisterBooleanEndpointHandler(kApiUrlPart + "defaultLandscape", request => { return(request.CurrentBook.GetLayout().SizeAndOrientation.IsLandScape); }, null, // no write action false, true); // we don't really know, just safe default apiHandler.RegisterEndpointHandler(kApiUrlPart + "languagesInBook", request => { try { InitializeLanguagesInBook(request); Dictionary <string, InclusionSetting> textLangsToPublish = request.CurrentBook.BookInfo.MetaData.TextLangsToPublish.ForBloomPUB; Dictionary <string, InclusionSetting> audioLangsToPublish = request.CurrentBook.BookInfo.MetaData.AudioLangsToPublish.ForBloomPUB; var result = "[" + string.Join(",", _allLanguages.Select(kvp => { string langCode = kvp.Key; bool includeText = false; if (textLangsToPublish != null && textLangsToPublish.TryGetValue(langCode, out InclusionSetting includeTextSetting)) { includeText = includeTextSetting.IsIncluded(); } bool includeAudio = false; if (audioLangsToPublish != null && audioLangsToPublish.TryGetValue(langCode, out InclusionSetting includeAudioSetting)) { includeAudio = includeAudioSetting.IsIncluded(); } var value = new LanguagePublishInfo() { code = kvp.Key, name = request.CurrentBook.PrettyPrintLanguage(langCode), complete = kvp.Value, includeText = includeText, containsAnyAudio = _languagesWithAudio.Contains(langCode), includeAudio = includeAudio }; var json = JsonConvert.SerializeObject(value); return(json); })) + "]"; request.ReplyWithText(result); }