private void UsbFailConnect(Exception e) { Stop(disposing: false); _progress.Message(idSuffix: "UnableToConnect", message: "Unable to connect to any Android device."); _progress.MessageWithoutLocalizing("\tTechnical details to share with the development team: " + e, ProgressKind.Error); Logger.WriteError(e); Stopped(); }
private void HandleOneReadyDeviceNotFound(DeviceNotFoundReportType reportType, List <string> deviceNames) { // Don't report the same thing over and over if (_previousDeviceNotFoundReportType == reportType) { return; } _previousDeviceNotFoundReportType = reportType; switch (reportType) { case DeviceNotFoundReportType.NoDeviceFound: _progress.Message("NoDeviceFound", "No device found. Still looking..."); break; case DeviceNotFoundReportType.MoreThanOneReadyDevice: _progress.Message(idSuffix: "MoreThanOne", message: "Please connect only one of the following devices."); foreach (var deviceName in deviceNames) { _progress.MessageWithoutLocalizing($"\t{deviceName}"); } break; } }
private void Work() { _progress.Message(idSuffix: "beginAdvertising", message: "Advertising book to Bloom Readers on local network..."); try { while (true) { if (!Paused) { UpdateAdvertisementBasedOnCurrentIpAddress(); _client.BeginSend(_sendBytes, _sendBytes.Length, _endPoint, SendCallback, _client); } Thread.Sleep(1000); } } catch (ThreadAbortException) { _progress.Message(idSuffix: "Stopped", message: "Stopped Advertising."); _client.Close(); } catch (Exception error) { // not worth localizing _progress.MessageWithoutLocalizing($"Error in Advertiser: {error.Message}", ProgressKind.Error); } }
public static string StageBloomD(Book.Book book, BookServer bookServer, WebSocketProgress progress, Color backColor, AndroidPublishSettings settings = null) { progress.Message("PublishTab.Epub.PreparingPreview", "Preparing Preview"); // message shared with Epub publishing if (settings?.LanguagesToInclude != null) { var message = new LicenseChecker().CheckBook(book, settings.LanguagesToInclude.ToArray()); if (message != null) { progress.MessageWithoutLocalizing(message, MessageKind.Error); return(null); } } _stagingFolder?.Dispose(); if (AudioProcessor.IsAnyCompressedAudioMissing(book.FolderPath, book.RawDom)) { progress.Message("CompressingAudio", "Compressing audio files"); AudioProcessor.TryCompressingAudioAsNeeded(book.FolderPath, book.RawDom); } // We don't use the folder found here, but this method does some checks we want done. BookStorage.FindBookHtmlInFolder(book.FolderPath); _stagingFolder = new TemporaryFolder(StagingFolder); var modifiedBook = BloomReaderFileMaker.PrepareBookForBloomReader(book.FolderPath, bookServer, _stagingFolder, progress, settings: settings); progress.Message("Common.Done", "Shown in a list of messages when Bloom has completed a task.", "Done"); return(modifiedBook.FolderPath.ToLocalhost()); }
public static void SendBatchedWarningMessagesToProgress(ISet <string> warningMessages, WebSocketProgress progress) { if (warningMessages.Any()) { progress.Message("Common.Warning", "Warning", ProgressKind.Warning, false); } foreach (var warningMessage in warningMessages) { // Messages are already localized progress.MessageWithoutLocalizing(warningMessage, ProgressKind.Warning); } }
private void HandleFoundOneNonReadyDevice(DeviceNotFoundReportType reportType, List <string> deviceNames) { // Don't report the same thing over and over if (_previousDeviceNotFoundReportType == reportType) { return; } _previousDeviceNotFoundReportType = reportType; switch (reportType) { case DeviceNotFoundReportType.NoDeviceFound: _progress.Message("NoDeviceFound", "No device found. Still looking..."); break; case DeviceNotFoundReportType.NoBloomDirectory: // I made this "running" instead of "installed" because I'm assuming // we wouldn't get a bloom directory just from installing. We don't actually need it to be // running, but this keeps the instructions simple. _progress.Message(id: "DeviceWithoutBloomReader", message: "The following devices are connected but do not seem to have Bloom Reader running:"); foreach (var deviceName in deviceNames) { _progress.MessageWithoutLocalizing($"\t{deviceName}"); } break; case DeviceNotFoundReportType.MoreThanOneReadyDevice: _progress.Message(id: "MoreThanOne", message: "The following connected devices all have Bloom Reader installed. Please connect only one of these devices."); foreach (var deviceName in deviceNames) { _progress.MessageWithoutLocalizing($"\t{deviceName}"); } break; } }
/// <summary> /// Check for either "Device16x9Portrait" or "Device16x9Landscape" layout. /// Complain to the user if another layout is currently chosen. /// </summary> /// <remarks> /// See https://issues.bloomlibrary.org/youtrack/issue/BL-5274. /// </remarks> public static void CheckBookLayout(Bloom.Book.Book book, WebSocketProgress progress) { var layout = book.GetLayout(); var desiredLayoutSize = "Device16x9"; if (layout.SizeAndOrientation.PageSizeName != desiredLayoutSize) { // The progress object has been initialized to use an id prefix. So we'll access L10NSharp explicitly here. We also want to make the string blue, // which requires a special argument. // var msgFormat = L10NSharp.LocalizationManager.GetString("Common.Note", // "Note", "A heading shown above some messages."); // progress.MessageWithoutLocalizing(msgFormat, MessageKind.Note); var msgFormat = L10NSharp.LocalizationManager.GetString("PublishTab.Android.WrongLayout.Message", "The layout of this book is currently \"{0}\". Bloom Reader will display it using \"{1}\", so text might not fit. To see if anything needs adjusting, go back to the Edit Tab and change the layout to \"{1}\".", "{0} and {1} are book layout tags."); var desiredLayout = desiredLayoutSize + layout.SizeAndOrientation.OrientationName; var msg = String.Format(msgFormat, layout.SizeAndOrientation.ToString(), desiredLayout, Environment.NewLine); progress.MessageWithoutLocalizing(msg, MessageKind.Note); } }
/// <summary> /// This is the core of sending a book to a device. We need a book and a bookServer in order to come up /// with the .bloomd file. /// We are either simply saving the .bloomd to destFileName, or else we will make a temporary .bloomd file and /// actually send it using sendAction. /// We report important progress on the progress control. This includes reporting that we are starting /// the actual transmission using startingMessageAction, which is passed the safe file name (for checking pre-existence /// in UsbPublisher) and the book title (typically inserted into the message). /// If a confirmAction is passed (currently only by UsbPublisher), we use it check for a successful transfer /// before reporting completion (except for file save, where the current message is inappropriate). /// This is an awkward case where the three ways of publishing are similar enough that /// it's annoying and dangerous to have three entirely separate methods but somewhat awkward to combine them. /// Possibly we could eventually make them more similar, e.g., it would simplify things if they all said /// "Sending X to Y", though I'm not sure that would be good i18n if Y is sometimes a device name /// and sometimes a path. /// </summary> /// <param name="book"></param> /// <param name="destFileName"></param> /// <param name="sendAction"></param> /// <param name="progress"></param> /// <param name="bookServer"></param> /// <param name="startingMessageFunction"></param> public static void SendBook(Book.Book book, BookServer bookServer, string destFileName, Action <string, string> sendAction, WebSocketProgress progress, Func <string, string, string> startingMessageFunction, Func <string, bool> confirmFunction, Color backColor, AndroidPublishSettings settings = null) { var bookTitle = book.Title; progress.MessageUsingTitle("PackagingBook", "Packaging \"{0}\" for use with Bloom Reader...", bookTitle, MessageKind.Progress); // compress audio if needed, with progress message if (AudioProcessor.IsAnyCompressedAudioMissing(book.FolderPath, book.RawDom)) { progress.Message("CompressingAudio", "Compressing audio files"); AudioProcessor.TryCompressingAudioAsNeeded(book.FolderPath, book.RawDom); } var publishedFileName = bookTitle + BookCompressor.ExtensionForDeviceBloomBook; if (startingMessageFunction != null) { progress.MessageWithoutLocalizing(startingMessageFunction(publishedFileName, bookTitle)); } if (destFileName == null) { // wifi or usb...make the .bloomd in a temp folder. using (var bloomdTempFile = TempFile.WithFilenameInTempFolder(publishedFileName)) { BloomReaderFileMaker.CreateBloomDigitalBook(bloomdTempFile.Path, book, bookServer, backColor, progress, settings); sendAction(publishedFileName, bloomdTempFile.Path); if (confirmFunction != null && !confirmFunction(publishedFileName)) { throw new ApplicationException("Book does not exist after write operation."); } progress.MessageUsingTitle("BookSent", "You can now read \"{0}\" in Bloom Reader!", bookTitle, MessageKind.Note); } } else { // save file...user has supplied name, there is no further action. Debug.Assert(sendAction == null, "further actions are not supported when passing a path name"); BloomReaderFileMaker.CreateBloomDigitalBook(destFileName, book, bookServer, backColor, progress, settings); progress.Message("PublishTab.Epub.Done", "Done", useL10nIdPrefix: false); // share message string with epub publishing } }
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); }
private void MakeAceByDaisyReport(ApiRequest request) { // First check whether ace has been installed. var daisyDirectory = FindAceByDaisyOrTellUser(request); // this method does the request.fail() if needed if (string.IsNullOrEmpty(daisyDirectory)) { return; } // As of version 1.0.2, the ace report has stylesheets on the internet. See https://issues.bloomlibrary.org/youtrack/issue/BL-6118. // To be specific, https://cdn.datatables.net/1.10.15/css/dataTables.bootstrap4.min.css and // https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta.2/css/bootstrap.min.css. if (!UrlLookup.CheckGeneralInternetAvailability(true)) { _webSocketProgress.ErrorWithoutLocalizing( "Sorry, you must have an internet connection in order to view the Ace by DAISY report."); request.Failed(); return; } var reportRootDirectory = Path.Combine(System.IO.Path.GetTempPath(), "daisy-ace-reports"); // Do our best at clearing out previous runs. // This call is ok if the directory does not exist at all. SIL.IO.RobustIO.DeleteDirectoryAndContents(reportRootDirectory); // This call is ok if the above failed and it still exists Directory.CreateDirectory(reportRootDirectory); // was having a problem with some files from previous reports getting locked. // so give new folder names if needed var haveReportedError = false; var errorMessage = "Unknown Error"; var version = GetAceByDaisyVersion(daisyDirectory); if (version.old) { _webSocketProgress.MessageWithoutLocalizing( $"You appear to have an older version ({version.version}) of ACE by Daisy. This may cause problems.", ProgressKind.Warning); _webSocketProgress.MessageWithoutLocalizing( "We recommend you run \"npm install @daisy/ace -g\" from a command line to get the latest.", ProgressKind.Warning); } var started = DateTime.Now; var epubPath = MakeEpub(reportRootDirectory, _webSocketProgress); // Try 3 times. It could be that this is no longer needed, but working on a developer // machine isn't proof. for (var i = 0; i < 3; i++) { var randomName = Guid.NewGuid().ToString(); var reportDirectory = Path.Combine(reportRootDirectory, randomName); var arguments = $"ace.js --verbose -o \"{reportDirectory}\" \"{epubPath}\""; const int kSecondsBeforeTimeout = 60; var progress = new NullProgress(); _webSocketProgress.MessageWithoutLocalizing("Running Ace by DAISY"); ExecutionResult res = null; string ldpath = null; try { // Without this variable switching on Linux, the chrome inside ace finds the // wrong version of a library as part of our mozilla code. ldpath = Environment.GetEnvironmentVariable("LD_LIBRARY_PATH"); Environment.SetEnvironmentVariable("LD_LIBRARY_PATH", null); res = CommandLineRunner.Run("node", arguments, Encoding.UTF8, daisyDirectory, kSecondsBeforeTimeout, progress, (dummy) => { }); } finally { // Restore the variable for our next geckofx browser to find. if (!String.IsNullOrEmpty(ldpath)) { Environment.SetEnvironmentVariable("LD_LIBRARY_PATH", ldpath); } } if (res.DidTimeOut) { errorMessage = $"Daisy Ace timed out after {kSecondsBeforeTimeout} seconds."; _webSocketProgress.ErrorWithoutLocalizing(errorMessage); continue; } var answerPath = Path.Combine(reportDirectory, "report.html"); if (!File.Exists(answerPath)) { // This hasn't been effectively reproduced, but there was a case where this would fail at least // half the time on a book, reproducable. That book had 2 pages pointing at placeholder.png, // and we were getting an error related to it being locked. So we deduce that ace was trying // to copy the file twice, at the same time (normal nodejs code is highly async). // Now the problem is not reproducable, but I'm leaving in this code that tried to deal with it. errorMessage = $"Exit code{res.ExitCode}{Environment.NewLine}" + $"Standard Error{Environment.NewLine}{res.StandardError}{Environment.NewLine}" + $"Standard Out{res.StandardOutput}"; _webSocketProgress.ErrorWithoutLocalizing(errorMessage); continue; // something went wrong, try again } // The html client is set to treat a text reply as a url of the report. Make sure it's valid for being a URL. // See https://silbloom.myjetbrains.com/youtrack/issue/BL-6197. request.ReplyWithText("/bloom/" + answerPath.EscapeFileNameForHttp()); if (version.old) { // If we displayed an important message about updating ACE, make sure the user // has SOME time to see it. // Review: is this long enough? Should we look for some other way to show the // problem? At present the problems caused by an old version (1.1 is the oldest // contemporary with Bloom) is fairly minor. while (DateTime.Now - started < new TimeSpan(0, 0, 0, 5)) { Thread.Sleep(100); } } return; } // If we get this far, we give up. ReportErrorAndFailTheRequest(request, errorMessage); }
public void Start(Book.Book book, CollectionSettings collectionSettings, Color backColor, AndroidPublishSettings publishSettings = null) { if (_wifiAdvertiser != null) { Stop(); } // This listens for a BloomReader to request a book. // It requires a firewall hole allowing Bloom to receive messages on _portToListen. // We initialize it before starting the Advertiser to avoid any chance of a race condition // where a BloomReader manages to request an advertised book before we start the listener. _wifiListener = new BloomReaderUDPListener(); _wifiListener.NewMessageReceived += (sender, args) => { var json = Encoding.UTF8.GetString(args.Data); try { dynamic settings = JsonConvert.DeserializeObject(json); // The property names used here must match the ones in BloomReader, doInBackground method of SendMessage, // a private class of NewBookListenerService. var androidIpAddress = (string)settings.deviceAddress; var androidName = (string)settings.deviceName; // This prevents the device (or other devices) from queuing up requests while we're busy with this one. // In effect, the Android is only allowed to request a retry after we've given up this try at sending. // Of course, there are async effects from network latency. But if we do get another request while // handling this one, we will ignore it, since StartSendBook checks for a transfer in progress. _wifiAdvertiser.Paused = true; StartSendBookOverWiFi(book, androidIpAddress, androidName, backColor, publishSettings); // Returns immediately. But we don't resume advertisements until the async send completes. } // If there's something wrong with the JSON (maybe an obsolete or newer version of reader?) // just ignore the request. catch (Exception ex) when(ex is JsonReaderException || ex is JsonSerializationException) { _progress.Message(idSuffix: "BadBookRequest", message: "Got a book request we could not process. Possibly the device is running an incompatible version of BloomReader?", progressKind: ProgressKind.Error); //this is too technical/hard to translate _progress.MessageWithoutLocalizing($" Request contains {json}; trying to interpret as JSON we got {ex.Message}", kind: ProgressKind.Error); } }; var pathHtmlFile = book.GetPathHtmlFile(); _wifiAdvertiser = new WiFiAdvertiser(_progress) { BookTitle = BookStorage.SanitizeNameForFileSystem(book.Title), // must be the exact same name as the file we will send if requested TitleLanguage = book.BookData.Language1.Iso639Code, BookVersion = Book.Book.MakeVersionCode(File.ReadAllText(pathHtmlFile), pathHtmlFile) }; PublishToAndroidApi.CheckBookLayout(book, _progress); _wifiAdvertiser.Start(); var part1 = LocalizationManager.GetDynamicString(appId: "Bloom", id: "PublishTab.Android.Wifi.Progress.WifiInstructions1", englishText: "On the Android, run Bloom Reader, open the menu and choose 'Receive Books via WiFi'."); var part2 = LocalizationManager.GetDynamicString(appId: "Bloom", id: "PublishTab.Android.Wifi.Progress.WifiInstructions2", englishText: "You can do this on as many devices as you like. Make sure each device is connected to the same network as this computer."); // can only have one instruction up at a time, so we concatenate these _progress.MessageWithoutLocalizing(part1 + " " + part2, ProgressKind.Instruction); }
public static void DoWorkWithProgressDialog(BloomWebSocketServer socketServer, string socketContext, Func <Form> makeDialog, Func <IWebSocketProgress, bool> doWhat, Action <Form> doWhenMainActionFalse = null) { var progress = new WebSocketProgress(socketServer, socketContext); // NOTE: This (specifically ShowDialog) blocks the main thread until the dialog is closed. // Be careful to avoid deadlocks. using (var dlg = makeDialog()) { // For now let's not try to handle letting the user abort. dlg.ControlBox = false; var worker = new BackgroundWorker(); worker.DoWork += (sender, args) => { // A way of waiting until the dialog is ready to receive progress messages while (!socketServer.IsSocketOpen(socketContext)) { Thread.Sleep(50); } bool waitForUserToCloseDialogOrReportProblems; try { waitForUserToCloseDialogOrReportProblems = doWhat(progress); } catch (Exception ex) { // depending on the nature of the problem, we might want to do more or less than this. // But at least this lets the dialog reach one of the states where it can be closed, // and gives the user some idea things are not right. socketServer.SendEvent(socketContext, "finished"); waitForUserToCloseDialogOrReportProblems = true; progress.MessageWithoutLocalizing("Something went wrong: " + ex.Message, ProgressKind.Error); } // stop the spinner socketServer.SendEvent(socketContext, "finished"); if (waitForUserToCloseDialogOrReportProblems) { // Now the user is allowed to close the dialog or report problems. // (ProgressDialog in JS-land is watching for this message, which causes it to turn // on the buttons that allow the dialog to be manually closed (or a problem to be reported). socketServer.SendBundle(socketContext, "show-buttons", new DynamicJson()); } else { // Just close the dialog dlg.Invoke((Action)(() => { if (doWhenMainActionFalse != null) { doWhenMainActionFalse(dlg); } else { dlg.Close(); } })); } }; worker.RunWorkerAsync(); dlg.ShowDialog(); // returns when dialog closed } }
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); }
public void RegisterWithServer(EnhancedImageServer server) { // 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. server.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); server.RegisterEndpointHandler(kApiUrlPart + "backColor", request => { if (request.HttpMethod == HttpMethods.Get) { if (request.CurrentBook != _coverColorSourceBook) { _coverColorSourceBook = request.CurrentBook; 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 (TryCssColorFromString(newColorAsString, out newColor)) { _thumbnailBackgroundColor = newColor; request.CurrentBook.SetCoverColor(newColorAsString); } request.PostSucceeded(); } }, true); server.RegisterEndpointHandler(kApiUrlPart + "photoStoryMode", request => { if (request.HttpMethod == HttpMethods.Get) { // this is temporary, just trying to get support for full screen pan & zoom out quickly in 4.2 request.ReplyWithText(request.CurrentBook.UsePhotoStoryModeInBloomReader.ToString() .ToLowerInvariant()); // "false", not "False" } else // post { request.CurrentBook.UsePhotoStoryModeInBloomReader = bool.Parse(request.RequiredPostString()); request.PostSucceeded(); } }, true); server.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) { TryCssColorFromString(request.CurrentBook?.GetCoverColor(), out _thumbnailBackgroundColor); } RuntimeImageProcessor.GenerateEBookThumbnail(coverImage, thumbnail.Path, 256, 256, _thumbnailBackgroundColor); request.ReplyWithImage(thumbnail.Path); } } }, true); server.RegisterEndpointHandler(kApiUrlPart + "usb/start", request => { #if !__MonoCS__ SetState("UsbStarted"); _usbPublisher.Connect(request.CurrentBook, _thumbnailBackgroundColor); #endif request.PostSucceeded(); }, true); server.RegisterEndpointHandler(kApiUrlPart + "usb/stop", request => { #if !__MonoCS__ _usbPublisher.Stop(); SetState("stopped"); #endif request.PostSucceeded(); }, true); server.RegisterEndpointHandler(kApiUrlPart + "wifi/start", request => { _wifiPublisher.Start(request.CurrentBook, request.CurrentCollectionSettings, _thumbnailBackgroundColor); SetState("ServingOnWifi"); request.PostSucceeded(); }, true); server.RegisterEndpointHandler(kApiUrlPart + "wifi/stop", request => { _wifiPublisher.Stop(); SetState("stopped"); request.PostSucceeded(); }, true); server.RegisterEndpointHandler(kApiUrlPart + "file/save", request => { FilePublisher.Save(request.CurrentBook, _bookServer, _thumbnailBackgroundColor, _progress); SetState("stopped"); request.PostSucceeded(); }, true); server.RegisterEndpointHandler(kApiUrlPart + "cleanup", request => { Stop(); request.PostSucceeded(); }, true); server.RegisterEndpointHandler(kApiUrlPart + "textToClipboard", request => { PortableClipboard.SetText(request.RequiredPostString()); request.PostSucceeded(); }, true); }