コード例 #1
0
 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);
     }
 }
コード例 #2
0
        public static void Save(Book.Book book, BookServer bookServer, Color backColor, WebSocketProgress progress)
        {
            var progressWithL10N = progress.WithL10NPrefix("PublishTab.Android.File.Progress.");

            using (var dlg = new DialogAdapters.SaveFileDialogAdapter())
            {
                dlg.DefaultExt = BookCompressor.ExtensionForDeviceBloomBook;
                var bloomdFileDescription = LocalizationManager.GetString("PublishTab.Android.bloomdFileFormatLabel", "Bloom Book for Devices", "This is shown in the 'Save' dialog when you save a bloom book in the format that works with the Bloom Reader Android App");
                dlg.Filter   = $"{bloomdFileDescription}|*{BookCompressor.ExtensionForDeviceBloomBook}";
                dlg.FileName = Path.GetFileName(book.FolderPath) + BookCompressor.ExtensionForDeviceBloomBook;
                if (!string.IsNullOrWhiteSpace(Settings.Default.BloomDeviceFileExportFolder) &&
                    Directory.Exists(Settings.Default.BloomDeviceFileExportFolder))
                {
                    dlg.InitialDirectory = Settings.Default.BloomDeviceFileExportFolder;
                    //(otherwise leave to default save location)
                }
                if (DialogResult.OK == dlg.ShowDialog())
                {
                    Settings.Default.BloomDeviceFileExportFolder = Path.GetDirectoryName(dlg.FileName);
                    AndroidView.CheckBookLayout(book, progress);
                    PublishToAndroidApi.SendBook(book, bookServer, dlg.FileName, null,
                                                 progressWithL10N,
                                                 (publishedFileName, bookTitle) => progressWithL10N.GetMessageWithParams("Saving", "{0} is a file path", "Saving as {0}", dlg.FileName),
                                                 null,
                                                 backColor);
                    PublishToAndroidApi.ReportAnalytics("file", book);
                }
            }
        }
コード例 #3
0
        public void UpdateAndSave(EpubPublishUiSettings newSettings, string path, bool force, WebSocketProgress progress = null)
        {
            bool succeeded;

            do
            {
                lock (this)
                {
                    succeeded = UpdatePreview(newSettings, force, progress);
                    if (succeeded)
                    {
                        EpubMaker.SaveEpub(path, _progress);
                        _webSocketServer.SendString(kWebsocketContext, kWebsocketEventId_epubReady, _previewSrc);
                    }
                }
            } while (!succeeded && !EpubMaker.AbortRequested);             // try until we get a complete epub, not interrupted by user changing something.
        }
コード例 #4
0
        public bool UpdatePreview(EpubPublishUiSettings newSettings, bool force, WebSocketProgress progress = null)
        {
            _progress = progress ?? _standardProgress.WithL10NPrefix("PublishTab.Epub.");
            if (Program.RunningOnUiThread)
            {
                // There's some stuff inside this lock that has to run on the UI thread.
                // If we lock the UI thread here, we can deadlock the whole program.
                throw new ApplicationException(@"Must not attempt to make epubs on UI thread...will produce deadlocks");
            }

            lock (_epubMakerLock)
            {
                if (EpubMaker != null)
                {
                    EpubMaker.AbortRequested = false;
                }
                _stagingEpub = true;
            }

            // For some unknown reason, if the accessibility window is showing, some of the browser navigation
            // that is needed to accurately determine which content is visible simply doesn't happen.
            // It would be disconcerting if it popped to the top after we close it and reopen it.
            // So, we just close the window if it is showing when we do this. See BL-7807.
            // Except that opening the Ace Checker tab invokes this code path in a way that works without the
            // deadlock (or whatever causes the failure).  This call can be detected by the progress argument not
            // being null.  The Refresh button on the AccessibilityCheckWindow also uses this code path in the
            // same way, so the next two lines also allow that Refresh button to work.  See BL-9341 for why
            // the original fix is inadequate.
            if (progress == null)
            {
                AccessibilityChecker.AccessibilityCheckWindow.StaticClose();
            }

            try
            {
                _webSocketServer.SendString(kWebsocketContext, "startingEbookCreation", _previewSrc);

                var  htmlPath   = _bookSelection.CurrentSelection.GetPathHtmlFile();
                var  newVersion = Book.Book.ComputeHashForAllBookRelatedFiles(htmlPath);
                bool previewIsAlreadyCurrent;
                lock (_epubMakerLock)
                {
                    previewIsAlreadyCurrent = _desiredEpubSettings == newSettings && EpubMaker != null && newVersion == _bookVersion &&
                                              !EpubMaker.AbortRequested && !force;
                }

                if (previewIsAlreadyCurrent)
                {
                    SaveAsEpub();                    // just in case there's a race condition where we haven't already saved it.
                    return(true);                    // preview is already up to date.
                }

                _desiredEpubSettings = newSettings;

                // clear the obsolete preview, if any; this also ensures that when the new one gets done,
                // we will really be changing the src attr in the preview iframe so the display will update.
                _webSocketServer.SendEvent(kWebsocketContext, kWebsocketEventId_epubReady);
                _bookVersion = newVersion;
                ReportProgress(LocalizationManager.GetString("PublishTab.Epub.PreparingPreview", "Preparing Preview"));

                // This three-tries loop is an attempt to recover from a weird state the system sometimes gets into
                // where a browser won't navigate to a temporary page that the EpubMaker uses. I'm not sure it actually
                // helps, once the system gets into this state even a brand new browser seems to have the same problem.
                // Usually there will be no exception, and the loop breaks at the end of the first iteration.
                for (int i = 0; i < 3; i++)
                {
                    try
                    {
                        if (!PublishHelper.InPublishTab)
                        {
                            return(false);
                        }
                        _previewSrc = UpdateEpubControlContent();
                    }
                    catch (ApplicationException ex)
                    {
                        Bloom.Utils.MiscUtils.SuppressUnusedExceptionVarWarning(ex);

                        if (i >= 2)
                        {
                            throw;
                        }
                        ReportProgress("Something went wrong, trying again");
                        continue;
                    }

                    break;                     // normal case, no exception
                }

                lock (_epubMakerLock)
                {
                    if (EpubMaker.AbortRequested)
                    {
                        return(false);                        // the code that set the abort flag will request a new preview.
                    }
                }
            }
            finally
            {
                lock (_epubMakerLock)
                {
                    _stagingEpub = false;
                }
            }

            // Do pending save if the user requested it while the preview was still in progress.
            SaveAsEpub();
            ReportProgress(LocalizationManager.GetString("PublishTab.Epub.Done", "Done"));
            return(true);
        }
コード例 #5
0
        public static Book.Book PrepareBookForBloomReader(string bookFolderPath, BookServer bookServer,
                                                          TemporaryFolder temp,
                                                          WebSocketProgress progress, bool isTemplateBook,
                                                          string creator = kCreatorBloom,
                                                          AndroidPublishSettings settings = null)
        {
            // MakeDeviceXmatterTempBook needs to be able to copy customCollectionStyles.css etc into parent of bookFolderPath
            // And bloom-player expects folder name to match html file name.
            var htmPath = BookStorage.FindBookHtmlInFolder(bookFolderPath);
            var tentativeBookFolderPath = Path.Combine(temp.FolderPath, Path.GetFileNameWithoutExtension(htmPath));

            Directory.CreateDirectory(tentativeBookFolderPath);
            var modifiedBook = PublishHelper.MakeDeviceXmatterTempBook(bookFolderPath, bookServer, tentativeBookFolderPath, isTemplateBook);

            // Although usually tentativeBookFolderPath and modifiedBook.FolderPath are the same, there are some exceptions
            // In the process of bringing a book up-to-date (called by MakeDeviceXmatterTempBook), the folder path may change.
            // For example, it could change if the original folder path contains punctuation marks now deemed dangerous.
            //    The book will be moved to the sanitized version of the file name instead.
            // It can also happen if we end up picking a different version of the title (i.e. in a different language)
            //    than the one written to the .htm file.
            string modifiedBookFolderPath = modifiedBook.FolderPath;

            if (modifiedBook.CollectionSettings.HaveEnterpriseFeatures)
            {
                ProcessQuizzes(modifiedBookFolderPath, modifiedBook.RawDom);
            }

            // Right here, let's maintain the history of what the BloomdVersion signifies to a reader.
            // Version 1 (as opposed to no BloomdVersion field): the bookFeatures property may be
            // used to report features analytics (with earlier bloomd's, the reader must use its own logic)
            modifiedBook.Storage.BookInfo.MetaData.BloomdVersion = 1;

            if (settings?.LanguagesToInclude != null)
            {
                PublishModel.RemoveUnwantedLanguageData(modifiedBook.OurHtmlDom, settings.LanguagesToInclude, modifiedBook.BookData.MetadataLanguage1IsoCode);
            }
            else if (Program.RunningHarvesterMode && modifiedBook.OurHtmlDom.SelectSingleNode(BookStorage.ComicalXpath) != null)
            {
                // This indicates that we are harvesting a book with comic speech bubbles.
                // For comical books, we only publish a single language. It's not currently feasible to
                // allow the reader to switch language in a Comical book, because typically that requires
                // adjusting the positions of the bubbles, and we don't yet support having more than one
                // set of bubble locations in a single book. See BL-7912 for some ideas on how we might
                // eventually improve this. In the meantime, switching language would have bad effects,
                // and if you can't switch language, there's no point in the book containing more than one.
                var languagesToInclude = new string[1] {
                    modifiedBook.BookData.Language1.Iso639Code
                };
                PublishModel.RemoveUnwantedLanguageData(modifiedBook.OurHtmlDom, languagesToInclude, modifiedBook.BookData.MetadataLanguage1IsoCode);
            }

            // Do this after processing interactive pages, as they can satisfy the criteria for being 'blank'
            HashSet <string> fontsUsed = null;

            using (var helper = new PublishHelper())
            {
                helper.ControlForInvoke = ControlForInvoke;
                ISet <string> warningMessages = new HashSet <string>();
                helper.RemoveUnwantedContent(modifiedBook.OurHtmlDom, modifiedBook, false, warningMessages);
                PublishHelper.SendBatchedWarningMessagesToProgress(warningMessages, progress);
                fontsUsed = helper.FontsUsed;
            }
            if (!modifiedBook.IsTemplateBook)
            {
                modifiedBook.RemoveBlankPages(settings?.LanguagesToInclude);
            }

            // See https://issues.bloomlibrary.org/youtrack/issue/BL-6835.
            RemoveInvisibleImageElements(modifiedBook);
            modifiedBook.Storage.CleanupUnusedSupportFiles(/*isForPublish:*/ true, settings?.AudioLanguagesToExclude);
            if (!modifiedBook.IsTemplateBook && RobustFile.Exists(Path.Combine(modifiedBookFolderPath, "placeHolder.png")))
            {
                RobustFile.Delete(Path.Combine(modifiedBookFolderPath, "placeHolder.png"));
            }
            modifiedBook.RemoveObsoleteAudioMarkup();

            // We want these to run after RemoveUnwantedContent() so that the metadata will more accurately reflect
            // the subset of contents that are included in the .bloomd
            // Note that we generally want to disable features here, but not enable them, especially while
            // running harvester!  See https://issues.bloomlibrary.org/youtrack/issue/BL-8995.
            var enableBlind = modifiedBook.BookInfo.MetaData.Feature_Blind || !Program.RunningHarvesterMode;
            // BloomReader and BloomPlayer are not using the SignLanguage feature, and it's misleading to
            // assume the existence of videos implies sign language.  There is a separate "Video" feature
            // now that gets set automatically.  (Automated setting of the Blind feature is imperfect, but
            // more meaningful than trying to automate sign language just based on one video existing.)
            var enableSignLanguage = modifiedBook.BookInfo.MetaData.Feature_SignLanguage;

            modifiedBook.UpdateMetadataFeatures(
                isBlindEnabled: enableBlind,
                isSignLanguageEnabled: enableSignLanguage,
                isTalkingBookEnabled: true);                    // talkingBook is only ever set automatically as far as I can tell.

            modifiedBook.SetAnimationDurationsFromAudioDurations();

            modifiedBook.OurHtmlDom.SetMedia("bloomReader");
            modifiedBook.OurHtmlDom.AddOrReplaceMetaElement("bloom-digital-creator", creator);
            EmbedFonts(modifiedBook, progress, fontsUsed, FontFileFinder.GetInstance(Program.RunningUnitTests));

            var bookFile = BookStorage.FindBookHtmlInFolder(modifiedBook.FolderPath);

            StripImgIfWeCannotFindFile(modifiedBook.RawDom, bookFile);
            StripContentEditableAndTabIndex(modifiedBook.RawDom);
            InsertReaderStylesheet(modifiedBook.RawDom);
            RobustFile.Copy(FileLocationUtilities.GetFileDistributedWithApplication(BloomFileLocator.BrowserRoot, "publish", "ReaderPublish", "readerStyles.css"),
                            Path.Combine(modifiedBookFolderPath, "readerStyles.css"));
            ConvertImagesToBackground(modifiedBook.RawDom);

            AddDistributionFile(modifiedBookFolderPath, creator);

            modifiedBook.Save();

            return(modifiedBook);
        }
コード例 #6
0
 internal WiFiAdvertiser(WebSocketProgress progress)
 {
     _progress = progress;
 }
コード例 #7
0
 public static void CreateBloomPub(string outputPath, Book.Book book, BookServer bookServer, Color backColor, WebSocketProgress progress, AndroidPublishSettings settings = null)
 {
     CreateBloomPub(outputPath, book.FolderPath, bookServer, backColor, progress, book.IsTemplateBook, settings: settings);
 }
コード例 #8
0
        /// <summary>
        /// Given a book, typically one in a temporary folder made just for exporting (or testing),
        /// and given the set of fonts found while creating that book and removing hidden elements,
        /// find the files needed for those fonts.
        /// Copy the font file for the normal style of that font family from the system font folder,
        /// if permitted; or post a warning in progress if we can't embed it.
        /// Create an extra css file (fonts.css) which tells the book to find the font files for those font families
        /// in the local folder, and insert a link to it into the book.
        /// </summary>
        /// <param name="book"></param>
        /// <param name="progress"></param>
        /// <param name="fontFileFinder">use new FontFinder() for real, or a stub in testing</param>
        public static void EmbedFonts(Book.Book book, WebSocketProgress progress, HashSet <string> fontsWanted, IFontFinder fontFileFinder)
        {
            const string defaultFont = "Andika New Basic";             // already in BR, don't need to embed or make rule.

            fontsWanted.Remove(defaultFont);
            fontFileFinder.NoteFontsWeCantInstall = true;
            var filesToEmbed = new List <string>();

            foreach (var font in fontsWanted)
            {
                var fontFiles = fontFileFinder.GetFilesForFont(font);
                if (fontFiles.Count() > 0)
                {
                    filesToEmbed.AddRange(fontFiles);
                    progress.MessageWithParams("PublishTab.Android.File.Progress.CheckFontOK", "{0} is a font name", "Checking {0} font: License OK for embedding.", ProgressKind.Progress, font);
                    // Assumes only one font file per font; if we embed multiple ones will need to enhance this.
                    var size         = new FileInfo(fontFiles.First()).Length;
                    var sizeToReport = (size / 1000000.0).ToString("F1");                     // purposely locale-specific; might be e.g. 1,2
                    progress.MessageWithParams("PublishTab.Android.File.Progress.Embedding",
                                               "{1} is a number with one decimal place, the number of megabytes the font file takes up",
                                               "Embedding font {0} at a cost of {1} megs",
                                               ProgressKind.Note,
                                               font, sizeToReport);
                    continue;
                }
                if (fontFileFinder.FontsWeCantInstall.Contains(font))
                {
                    //progress.Error("Common.Warning", "Warning");
                    progress.MessageWithParams("LicenseForbids", "{0} is a font name", "This book has text in a font named \"{0}\". The license for \"{0}\" does not permit Bloom to embed the font in the book.", ProgressKind.Error, font);
                }
                else
                {
                    progress.MessageWithParams("NoFontFound", "{0} is a font name", "This book has text in a font named \"{0}\", but Bloom could not find that font on this computer.", ProgressKind.Error, font);
                }
                progress.MessageWithParams("SubstitutingAndika", "{0} is a font name", "Bloom will substitute \"{0}\" instead.", ProgressKind.Error, defaultFont, font);
            }
            foreach (var file in filesToEmbed)
            {
                // Enhance: do we need to worry about problem characters in font file names?
                var dest = Path.Combine(book.FolderPath, Path.GetFileName(file));
                RobustFile.Copy(file, dest);
            }
            // Create the fonts.css file, which tells the browser where to find the fonts for those families.
            var sb = new StringBuilder();

            foreach (var font in fontsWanted)
            {
                var group = fontFileFinder.GetGroupForFont(font);
                if (group != null)
                {
                    EpubMaker.AddFontFace(sb, font, "normal", "normal", group.Normal);
                }
                // We don't need (or want) a rule to use Andika instead.
                // The reader typically WILL use Andika, because we have a rule making it the default font
                // for the whole body of the document, and BloomReader always has it available.
                // However, it's possible that although we aren't allowed to embed the desired font,
                // the device actually has it installed. In that case, we want to use it.
            }
            RobustFile.WriteAllText(Path.Combine(book.FolderPath, "fonts.css"), sb.ToString());
            // Tell the document to use the new stylesheet.
            book.OurHtmlDom.AddStyleSheet("fonts.css");
        }
コード例 #9
0
        /// <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)
        {
            var bookTitle = book.Title;

            progress.MessageUsingTitle("PackagingBook", "Packaging \"{0}\" for use with Bloom Reader...", bookTitle);

            // 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 = BookStorage.SanitizeNameForFileSystem(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))
                {
                    BookCompressor.CompressBookForDevice(bloomdTempFile.Path, book, bookServer, backColor, progress);
                    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);
                }
            }
            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");
                BookCompressor.CompressBookForDevice(destFileName, book, bookServer, backColor, progress);
            }
        }
コード例 #10
0
 public WiFiPublisher(WebSocketProgress progress, BookServer bookServer)
 {
     _bookServer = bookServer;
     _progress   = progress.WithL10NPrefix("PublishTab.Android.Wifi.Progress.");
 }
コード例 #11
0
        public static void DoWorkWithProgressDialog(IBloomWebSocketServer socketServer, string socketContext, Func <Form> makeDialog,
                                                    Func <IWebSocketProgress, BackgroundWorker, bool> doWhat, Action <Form> doWhenMainActionFalse = null, IWin32Window owner = 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.WorkerSupportsCancellation = true;
                worker.DoWork += (sender, args) =>
                {
                    ProgressDialogApi.SetCancelHandler(() =>
                    {
                        worker.CancelAsync();
                    });

                    // 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, worker);
                    }
                    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,
                                                          ex is FatalException? ProgressKind.Fatal: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(owner);                 // returns when dialog closed
                if (progress.HasFatalProblemBeenReported)
                {
                    Application.Exit();
                }

                ProgressDialogApi.SetCancelHandler(null);
            }
        }
コード例 #12
0
 public MockUsbPublisher(WebSocketProgress progress, BookServer bookServer)
     : base(progress, bookServer)
 {
     Stopped           = () => SetState("dummy");
     _exceptionToThrow = HR_ERROR_DISK_FULL;
 }
コード例 #13
0
 public UsbPublisher(WebSocketProgress progress, BookServer bookServer)
 {
     _bookServer = bookServer;
     _progress   = progress.WithL10NPrefix("PublishTab.Android.Usb.Progress.");
     _androidDeviceUsbConnection = new AndroidDeviceUsbConnection();
 }