Exemple #1
0
        private string GetPdfPath(string fname)
        {
            string path = null;

            // Sanitize fileName first
            string fileName = BookStorage.SanitizeNameForFileSystem(fname);

            for (int i = 0; i < 100; i++)
            {
                path = Path.Combine(Path.GetTempPath(), string.Format("{0}-{1}.pdf", fileName, i));
                if (!RobustFile.Exists(path))
                {
                    break;
                }

                try
                {
                    RobustFile.Delete(path);
                    break;
                }
                catch (Exception)
                {
                    //couldn't delete it? then increment the suffix and try again
                }
            }
            return(path);
        }
Exemple #2
0
        public void Start(Book.Book book, CollectionSettings collectionSettings, Color backColor)
        {
            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);
                    // 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.Error(id: "BadBookRequest",
                                    message: "Got a book request we could not process. Possibly the device is running an incompatible version of BloomReader?");

                    //this is too technical/hard to translate
                    _progress.ErrorWithoutLocalizing($" Request contains {json}; trying to interpret as JSON we got {ex.Message}");
                }
            };

            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 = collectionSettings.Language1Iso639Code,
                BookVersion   = Book.Book.MakeVersionCode(File.ReadAllText(pathHtmlFile), pathHtmlFile)
            };

            AndroidView.CheckBookLayout(book, _progress);
            _wifiAdvertiser.Start();

            _progress.Message(id: "WifiInstructions1",
                              message: "On the Android, run Bloom Reader, open the menu and choose 'Receive Books from computer'.");
            _progress.Message(id: "WifiInstructions2",
                              message: "You can do this on as many devices as you like. Make sure each device is connected to the same network as this computer.");
        }
        /// <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);
            }
        }
Exemple #4
0
        public static Book.Book PrepareBookForBloomReader(string bookFolderPath, BookServer bookServer,
                                                          TemporaryFolder temp,
                                                          IWebSocketProgress 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,
                                                       // Windows directory names cannot have trailing periods, but FileNameWithoutExtension can have these.  (BH-6097)
                                                       BookStorage.SanitizeNameForFileSystem(Path.GetFileNameWithoutExtension(htmPath)));

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

            modifiedBook.SetMotionAttributesOnBody(settings?.Motion ?? false);

            // 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 bloompub's, the reader must use its own logic)
            modifiedBook.Storage.BookInfo.MetaData.BloomdVersion = 1;

            modifiedBook.Storage.BookInfo.UpdateOneSingletonTag("distribution", settings?.DistributionTag);
            if (!string.IsNullOrEmpty(settings?.BookshelfTag))
            {
                modifiedBook.Storage.BookInfo.UpdateOneSingletonTag("bookshelf", settings.BookshelfTag);
            }

            if (settings?.RemoveInteractivePages ?? false)
            {
                var activities = modifiedBook.GetPageElements().Cast <XmlNode>()
                                 .Where(x => x is XmlElement elt && HtmlDom.IsActivityPage(elt)).ToArray();
                foreach (var page in activities)
                {
                    page.ParentNode.RemoveChild(page);
                }
            }

            if (settings?.LanguagesToInclude != null)
            {
                PublishModel.RemoveUnwantedLanguageData(modifiedBook.OurHtmlDom, settings.LanguagesToInclude, modifiedBook.BookData.MetadataLanguage1IsoCode);
                PublishModel.RemoveUnwantedLanguageRulesFromCssFiles(modifiedBook.FolderPath, settings.LanguagesToInclude);
            }
            else if (Program.RunningHarvesterMode && modifiedBook.OurHtmlDom.SelectSingleNode(BookStorage.ComicalXpath) != null)
            {
                // This indicates that we are harvesting a book with comic speech bubbles or other overlays (Overlay Tool).
                // For books with overlays, we only publish a single language. It's not currently feasible to
                // allow the reader to switch language in a book with overlays, because typically that requires
                // adjusting the positions of the overlays, and we don't yet support having more than one
                // set of overlay 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, keepPageLabels: settings?.WantPageLabels ?? false);
                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 .bloompub
            // 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.
                allowedLanguages: null                 // allow all because we've already filtered out the unwanted ones from the dom above.
                );

            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, settings);

            modifiedBook.Save();

            return(modifiedBook);
        }