Exemplo n.º 1
0
        /// <summary>
        /// Create a Bloom Digital book (the zipped .bloomd file) as used by BloomReader (and Bloom Library etc)
        /// </summary>
        /// <param name="outputPath">The path to create the zipped .bloomd output file at</param>
        /// <param name="bookFolderPath">The path to the input book</param>
        /// <param name="bookServer"></param>
        /// <param name="backColor"></param>
        /// <param name="progress"></param>
        /// <param name="tempFolder">A temporary folder. This function will not dispose of it when done</param>
        /// <param name="creator">value for &lt;meta name="creator" content="..."/&gt; (defaults to "bloom")</param>
        /// <param name="isTemplateBook"></param>
        /// <param name="settings"></param>
        /// <returns>Path to the unzipped .bloomd</returns>
        public static string CreateBloomPub(string outputPath, string bookFolderPath, BookServer bookServer,
                                            Color backColor,
                                            WebSocketProgress progress, TemporaryFolder tempFolder, string creator = kCreatorBloom, bool isTemplateBook = false,
                                            AndroidPublishSettings settings = null)
        {
            var modifiedBook = PrepareBookForBloomReader(bookFolderPath, bookServer, tempFolder, progress, isTemplateBook, creator, settings);

            // We want at least 256 for Bloom Reader, because the screens have a high pixel density. And (at the moment) we are asking for
            // 64dp in Bloom Reader.

            BookCompressor.MakeSizedThumbnail(modifiedBook, backColor, modifiedBook.FolderPath, 256);

            BookCompressor.CompressBookDirectory(outputPath, modifiedBook.FolderPath, "", reduceImages: true, omitMetaJson: false, wrapWithFolder: false,
                                                 pathToFileForSha: BookStorage.FindBookHtmlInFolder(bookFolderPath));

            return(modifiedBook.FolderPath);
        }
Exemplo n.º 2
0
        private void ProcessVideosInTempDirectory(string destDirName)
        {
            var htmlFilePath = BookStorage.FindBookHtmlInFolder(destDirName);

            if (string.IsNullOrEmpty(htmlFilePath))
            {
                return;
            }
            var xmlDomFromHtmlFile     = XmlHtmlConverter.GetXmlDomFromHtmlFile(htmlFilePath);
            var domForVideoProcessing  = new HtmlDom(xmlDomFromHtmlFile);
            var videoContainerElements = HtmlDom.SelectChildVideoElements(domForVideoProcessing.RawDom.DocumentElement).Cast <XmlElement>();

            if (!videoContainerElements.Any())
            {
                return;
            }
            SignLanguageApi.ProcessVideos(videoContainerElements, destDirName);
            XmlHtmlConverter.SaveDOMAsHtml5(domForVideoProcessing.RawDom, htmlFilePath);
        }
Exemplo n.º 3
0
        [TestCase("foobar", "avoid conflict.html")]                                      // only this one file with conflict in the name
        public void FindBookHtmlInFolder_MayHaveOtherFiles_ChoosesCorrectOne(string folderName, string expected, string decoy1 = null, string decoy2 = null)
        {
            using (var outerFolder = new TemporaryFolder())            // intentionally using different name each time to avoid conflicts when tests run in parallel
            {
                using (var folder = new TemporaryFolder(outerFolder, folderName))
                {
                    File.CreateText(folder.Combine(expected));
                    if (decoy1 != null)
                    {
                        File.CreateText(folder.Combine(decoy1));
                    }
                    if (decoy2 != null)
                    {
                        File.CreateText(folder.Combine(decoy2));
                    }

                    var path = BookStorage.FindBookHtmlInFolder(folder.Path);
                    Assert.AreEqual(expected, Path.GetFileName(path));
                }
            }
        }
Exemplo n.º 4
0
        private void ReplaceBookWithTemplate(ZipFile zip, string directory, string rootName)
        {
            var bookFile = BookStorage.FindBookHtmlInFolder(directory);

            if (string.IsNullOrEmpty(bookFile))
            {
                return;
            }
            var text = File.ReadAllText(bookFile, Encoding.UTF8);
            // Note that we're getting rid of preceding newline but not following one. Hopefully we cleanly remove a whole line.
            // I'm not sure the </meta> ever occurs in html files, but just in case we'll match if present.
            var regex = new Regex("\\s*<meta\\s+name=(['\\\"])lockedDownAsShell\\1 content=(['\\\"])true\\2>(</meta>)? *");
            var match = regex.Match(text);

            if (!match.Success)
            {
                return;                 // nothing to remove
            }
            var newText = text.Substring(0, match.Index) + text.Substring(match.Index + match.Length);
            var zipName = Path.Combine(rootName, Path.GetFileName(directory), Path.GetFileName(bookFile));

            zip.RemoveEntry(zipName);
            zip.AddEntry(zipName, newText);
        }
Exemplo n.º 5
0
        private string UploadBook(string bookFolder, IProgress progress, out string parseId, string pdfToInclude = null, bool excludeAudio = true)
        {
            // Books in the library should generally show as locked-down, so new users are automatically in localization mode.
            // Occasionally we may want to upload a new authoring template, that is, a 'book' that is suitableForMakingShells.
            // Such books must never be locked.
            // So, typically we will try to lock it. What we want to do is Book.RecordedAsLockedDown = true; Book.Save().
            // But all kinds of things have to be set up before we can create a Book. So we duplicate a few bits of code.
            var     htmlFile      = BookStorage.FindBookHtmlInFolder(bookFolder);
            bool    wasLocked     = false;
            bool    allowLocking  = false;
            HtmlDom domForLocking = null;
            var     metaDataText  = MetaDataText(bookFolder);
            var     metadata      = BookMetaData.FromString(metaDataText);

            if (!string.IsNullOrEmpty(htmlFile))
            {
                var xmlDomFromHtmlFile = XmlHtmlConverter.GetXmlDomFromHtmlFile(htmlFile, false);
                domForLocking = new HtmlDom(xmlDomFromHtmlFile);
                wasLocked     = domForLocking.RecordedAsLockedDown;
                allowLocking  = !metadata.IsSuitableForMakingShells;
                if (allowLocking && !wasLocked)
                {
                    domForLocking.RecordAsLockedDown(true);
                    XmlHtmlConverter.SaveDOMAsHtml5(domForLocking.RawDom, htmlFile);
                }
            }
            string s3BookId;

            try
            {
                // In case we somehow have a book with no ID, we must have one to upload it.
                if (string.IsNullOrEmpty(metadata.Id))
                {
                    metadata.Id = Guid.NewGuid().ToString();
                }
                // And similarly it should have SOME title.
                if (string.IsNullOrEmpty(metadata.Title))
                {
                    metadata.Title = Path.GetFileNameWithoutExtension(bookFolder);
                }
                metadata.SetUploader(UserId);
                s3BookId = S3BookId(metadata);
                metadata.DownloadSource = s3BookId;
                // Any updated ID at least needs to become a permanent part of the book.
                // The file uploaded must also contain the correct DownloadSource data, so that it can be used
                // as an 'order' to download the book.
                // It simplifies unit testing if the metadata file is also updated with the uploadedBy value.
                // Not sure if there is any other reason to do it (or not do it).
                // For example, do we want to send/receive who is the latest person to upload?
                metadata.WriteToFolder(bookFolder);
                // The metadata is also a book order...but we need it on the server with the desired file name,
                // because we can't rename on download. The extension must be the one Bloom knows about,
                // and we want the file name to indicate which book, so use the name of the book folder.
                var metadataPath = BookMetaData.MetaDataPath(bookFolder);
                var orderPath    = Path.Combine(bookFolder, Path.GetFileName(bookFolder) + BookOrderExtension);
                RobustFile.Copy(metadataPath, orderPath, true);
                parseId = "";
                try
                {
                    _s3Client.UploadBook(s3BookId, bookFolder, progress, pdfToInclude, excludeAudio);
                    metadata.BaseUrl   = _s3Client.BaseUrl;
                    metadata.BookOrder = _s3Client.BookOrderUrlOfRecentUpload;
                    progress.WriteStatus(LocalizationManager.GetString("PublishTab.Upload.UploadingBookMetadata", "Uploading book metadata", "In this step, Bloom is uploading things like title, languages, and topic tags to the BloomLibrary.org database."));
                    // Do this after uploading the books, since the ThumbnailUrl is generated in the course of the upload.
                    var response = _parseClient.SetBookRecord(metadata.WebDataJson);
                    parseId = response.ResponseUri.LocalPath;
                    int index = parseId.LastIndexOf('/');
                    parseId = parseId.Substring(index + 1);
                    if (parseId == "books")
                    {
                        // For NEW books the response URL is useless...need to do a new query to get the ID.
                        var json = _parseClient.GetSingleBookRecord(metadata.Id);
                        parseId = json.objectId.Value;
                    }
                    //   if (!UseSandbox) // don't make it seem like there are more uploads than their really are if this a tester pushing to the sandbox
                    {
                        Analytics.Track("UploadBook-Success", new Dictionary <string, string>()
                        {
                            { "url", metadata.BookOrder }, { "title", metadata.Title }
                        });
                    }
                }
                catch (WebException e)
                {
                    DisplayNetworkUploadProblem(e, progress);
                    if (!UseSandbox)                     // don't make it seem like there are more upload failures than their really are if this a tester pushing to the sandbox
                    {
                        Analytics.Track("UploadBook-Failure", new Dictionary <string, string>()
                        {
                            { "url", metadata.BookOrder }, { "title", metadata.Title }, { "error", e.Message }
                        });
                    }
                    return("");
                }
                catch (AmazonS3Exception e)
                {
                    if (e.Message.Contains("The difference between the request time and the current time is too large"))
                    {
                        progress.WriteError(LocalizationManager.GetString("PublishTab.Upload.TimeProblem",
                                                                          "There was a problem uploading your book. This is probably because your computer is set to use the wrong timezone or your system time is badly wrong. See http://www.di-mgt.com.au/wclock/help/wclo_setsysclock.html for how to fix this."));
                        if (!UseSandbox)
                        {
                            Analytics.Track("UploadBook-Failure-SystemTime");
                        }
                    }
                    else
                    {
                        DisplayNetworkUploadProblem(e, progress);
                        if (!UseSandbox)
                        {
                            // don't make it seem like there are more upload failures than their really are if this a tester pushing to the sandbox
                            Analytics.Track("UploadBook-Failure",
                                            new Dictionary <string, string>()
                            {
                                { "url", metadata.BookOrder }, { "title", metadata.Title }, { "error", e.Message }
                            });
                        }
                    }
                    return("");
                }
                catch (AmazonServiceException e)
                {
                    DisplayNetworkUploadProblem(e, progress);
                    if (!UseSandbox)                     // don't make it seem like there are more upload failures than their really are if this a tester pushing to the sandbox
                    {
                        Analytics.Track("UploadBook-Failure", new Dictionary <string, string>()
                        {
                            { "url", metadata.BookOrder }, { "title", metadata.Title }, { "error", e.Message }
                        });
                    }
                    return("");
                }
                catch (Exception e)
                {
                    progress.WriteError(LocalizationManager.GetString("PublishTab.Upload.UploadProblemNotice",
                                                                      "There was a problem uploading your book. You may need to restart Bloom or get technical help."));
                    progress.WriteError(e.Message.Replace("{", "{{").Replace("}", "}}"));
                    progress.WriteVerbose(e.StackTrace);
                    if (!UseSandbox)                     // don't make it seem like there are more upload failures than their really are if this a tester pushing to the sandbox
                    {
                        Analytics.Track("UploadBook-Failure", new Dictionary <string, string>()
                        {
                            { "url", metadata.BookOrder }, { "title", metadata.Title }, { "error", e.Message }
                        });
                    }
                    return("");
                }
            }
            finally
            {
                if (domForLocking != null && allowLocking && !wasLocked)
                {
                    domForLocking.RecordAsLockedDown(false);
                    XmlHtmlConverter.SaveDOMAsHtml5(domForLocking.RawDom, htmlFile);
                }
            }
            return(s3BookId);
        }
Exemplo n.º 6
0
        private string UploadBook(string bookFolder, IProgress progress, out string parseId,
                                  string pdfToInclude = null, ISet <string> audioFilesToInclude = null, IEnumerable <string> videoFilesToInclude = null, string[] languages = null,
                                  CollectionSettings collectionSettings = null)
        {
            // Books in the library should generally show as locked-down, so new users are automatically in localization mode.
            // Occasionally we may want to upload a new authoring template, that is, a 'book' that is suitableForMakingShells.
            // Such books must never be locked.
            // So, typically we will try to lock it. What we want to do is Book.RecordedAsLockedDown = true; Book.Save().
            // But all kinds of things have to be set up before we can create a Book. So we duplicate a few bits of code.
            var     htmlFile      = BookStorage.FindBookHtmlInFolder(bookFolder);
            bool    wasLocked     = false;
            bool    allowLocking  = false;
            HtmlDom domForLocking = null;
            var     metaDataText  = MetaDataText(bookFolder);
            var     metadata      = BookMetaData.FromString(metaDataText);

            if (!String.IsNullOrEmpty(htmlFile))
            {
                var xmlDomFromHtmlFile = XmlHtmlConverter.GetXmlDomFromHtmlFile(htmlFile, false);
                domForLocking = new HtmlDom(xmlDomFromHtmlFile);
                wasLocked     = domForLocking.RecordedAsLockedDown;
                allowLocking  = !metadata.IsSuitableForMakingShells;
                if (allowLocking && !wasLocked)
                {
                    domForLocking.RecordAsLockedDown(true);
                    XmlHtmlConverter.SaveDOMAsHtml5(domForLocking.RawDom, htmlFile);
                }
            }
            string s3BookId;

            try
            {
                // In case we somehow have a book with no ID, we must have one to upload it.
                if (String.IsNullOrEmpty(metadata.Id))
                {
                    metadata.Id = Guid.NewGuid().ToString();
                }
                // And similarly it should have SOME title.
                if (String.IsNullOrEmpty(metadata.Title))
                {
                    metadata.Title = Path.GetFileNameWithoutExtension(bookFolder);
                }
                metadata.SetUploader(UserId);
                s3BookId = S3BookId(metadata);
#if DEBUG
                // S3 URL can be reasonably deduced, as long as we have the S3 ID, so print that out in Debug mode.
                // Format: $"https://s3.amazonaws.com/BloomLibraryBooks{isSandbox}/{s3BookId}/{title}"
                // Example: https://s3.amazonaws.com/BloomLibraryBooks-Sandbox/[email protected]/8d0d9043-a1bb-422d-aa5b-29726cdcd96a/AutoSplit+Timings
                var msgBookId = "s3BookId: " + s3BookId;
                progress.WriteMessage(msgBookId);
#endif
                metadata.DownloadSource = s3BookId;
                // If the collection has a default bookshelf, make sure the book has that tag.
                // Also make sure it doesn't have any other bookshelf tags (which would typically be
                // from a previous default bookshelf upload), including a duplicate of the one
                // we may be about to add.
                var tags = (metadata.Tags ?? new string[0]).Where(t => !t.StartsWith("bookshelf:"));

                if (!String.IsNullOrEmpty(collectionSettings?.DefaultBookshelf))
                {
                    tags = tags.Concat(new [] { "bookshelf:" + collectionSettings.DefaultBookshelf });
                }
                metadata.Tags = tags.ToArray();

                // Any updated ID at least needs to become a permanent part of the book.
                // The file uploaded must also contain the correct DownloadSource data, so that it can be used
                // as an 'order' to download the book.
                // It simplifies unit testing if the metadata file is also updated with the uploadedBy value.
                // Not sure if there is any other reason to do it (or not do it).
                // For example, do we want to send/receive who is the latest person to upload?
                metadata.WriteToFolder(bookFolder);
                // The metadata is also a book order...but we need it on the server with the desired file name,
                // because we can't rename on download. The extension must be the one Bloom knows about,
                // and we want the file name to indicate which book, so use the name of the book folder.
                var metadataPath = BookMetaData.MetaDataPath(bookFolder);
                RobustFile.Copy(metadataPath, BookInfo.BookOrderPath(bookFolder), true);
                parseId = "";
                try
                {
                    _s3Client.UploadBook(s3BookId, bookFolder, progress, pdfToInclude, audioFilesToInclude, videoFilesToInclude, languages);
                    metadata.BaseUrl   = _s3Client.BaseUrl;
                    metadata.BookOrder = _s3Client.BookOrderUrlOfRecentUpload;
                    var metaMsg = LocalizationManager.GetString("PublishTab.Upload.UploadingBookMetadata", "Uploading book metadata", "In this step, Bloom is uploading things like title, languages, and topic tags to the BloomLibrary.org database.");
                    if (IsDryRun)
                    {
                        metaMsg = "(Dry run) Would upload book metadata";                               // TODO: localize?
                    }
                    progress.WriteStatus(metaMsg);
                    // Do this after uploading the books, since the ThumbnailUrl is generated in the course of the upload.
                    if (!IsDryRun)
                    {
                        var response = ParseClient.SetBookRecord(metadata.WebDataJson);
                        parseId = response.ResponseUri.LocalPath;
                        int index = parseId.LastIndexOf('/');
                        parseId = parseId.Substring(index + 1);
                        if (parseId == "books")
                        {
                            // For NEW books the response URL is useless...need to do a new query to get the ID.
                            var json = ParseClient.GetSingleBookRecord(metadata.Id);
                            parseId = json.objectId.Value;
                        }
                        //   if (!UseSandbox) // don't make it seem like there are more uploads than their really are if this a tester pushing to the sandbox
                        {
                            Analytics.Track("UploadBook-Success", new Dictionary <string, string>()
                            {
                                { "url", metadata.BookOrder }, { "title", metadata.Title }
                            });
                        }
                    }
                }
                catch (WebException e)
                {
                    DisplayNetworkUploadProblem(e, progress);
                    if (IsProductionRun)                     // don't make it seem like there are more upload failures than their really are if this a tester pushing to the sandbox
                    {
                        Analytics.Track("UploadBook-Failure", new Dictionary <string, string>()
                        {
                            { "url", metadata.BookOrder }, { "title", metadata.Title }, { "error", e.Message }
                        });
                    }
                    return("");
                }
                catch (AmazonS3Exception e)
                {
                    if (e.Message.Contains("The difference between the request time and the current time is too large"))
                    {
                        progress.WriteError(LocalizationManager.GetString("PublishTab.Upload.TimeProblem",
                                                                          "There was a problem uploading your book. This is probably because your computer is set to use the wrong timezone or your system time is badly wrong. See http://www.di-mgt.com.au/wclock/help/wclo_setsysclock.html for how to fix this."));
                        if (IsProductionRun)
                        {
                            Analytics.Track("UploadBook-Failure-SystemTime");
                        }
                    }
                    else
                    {
                        DisplayNetworkUploadProblem(e, progress);
                        if (IsProductionRun)
                        {
                            // don't make it seem like there are more upload failures than there really are if this a tester pushing to the sandbox
                            Analytics.Track("UploadBook-Failure",
                                            new Dictionary <string, string>()
                            {
                                { "url", metadata.BookOrder }, { "title", metadata.Title }, { "error", e.Message }
                            });
                        }
                    }
                    return("");
                }
                catch (AmazonServiceException e)
                {
                    DisplayNetworkUploadProblem(e, progress);
                    if (IsProductionRun)                     // don't make it seem like there are more upload failures than there really are if this a tester pushing to the sandbox
                    {
                        Analytics.Track("UploadBook-Failure", new Dictionary <string, string>()
                        {
                            { "url", metadata.BookOrder }, { "title", metadata.Title }, { "error", e.Message }
                        });
                    }
                    return("");
                }
                catch (Exception e)
                {
                    var msg1 = LocalizationManager.GetString("PublishTab.Upload.UploadProblemNotice",
                                                             "There was a problem uploading your book. You may need to restart Bloom or get technical help.");
                    var msg2 = e.Message.Replace("{", "{{").Replace("}", "}}");
                    progress.WriteError(msg1);
                    progress.WriteError(msg2);
                    progress.WriteVerbose(e.StackTrace);

                    if (IsProductionRun)                     // don't make it seem like there are more upload failures than there really are if this a tester pushing to the sandbox
                    {
                        Analytics.Track("UploadBook-Failure", new Dictionary <string, string>()
                        {
                            { "url", metadata.BookOrder }, { "title", metadata.Title }, { "error", e.Message }
                        });
                    }
                    return("");
                }
            }
            finally
            {
                if (domForLocking != null && allowLocking && !wasLocked)
                {
                    domForLocking.RecordAsLockedDown(false);
                    XmlHtmlConverter.SaveDOMAsHtml5(domForLocking.RawDom, htmlFile);
                }
            }
            return(s3BookId);
        }
Exemplo n.º 7
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);
        }
Exemplo n.º 8
0
        /// <summary>
        /// Adds a directory, along with all files and subdirectories, to the ZipStream.
        /// </summary>
        /// <param name="directoryPath">The directory to add recursively</param>
        /// <param name="zipStream">The ZipStream to which the files and directories will be added</param>
        /// <param name="dirNameOffset">This number of characters will be removed from the full directory or file name
        /// before creating the zip entry name</param>
        /// <param name="dirNamePrefix">string to prefix to the zip entry name</param>
        /// <param name="forReaderTools">If True, then some pre-processing will be done to the contents of decodable
        /// and leveled readers before they are added to the ZipStream</param>
        /// <remarks>Protected for testing purposes</remarks>
        protected static void CompressDirectory(string directoryPath, ZipOutputStream zipStream, int dirNameOffset, string dirNamePrefix,
                                                bool forReaderTools)
        {
            if (Path.GetFileName(directoryPath).ToLowerInvariant() == "audio")
            {
                return;                 // don't want audio in bloompack. todo: test
            }
            var files    = Directory.GetFiles(directoryPath);
            var bookFile = BookStorage.FindBookHtmlInFolder(directoryPath);

            foreach (var filePath in files)
            {
                if (_excludedFileExtensionsLowerCase.Contains(Path.GetExtension(filePath.ToLowerInvariant())))
                {
                    continue;                     // BL-2246: skip putting this one into the BloomPack
                }
                if (Path.GetFileName(filePath).StartsWith(BookStorage.PrefixForCorruptHtmFiles))
                {
                    continue;
                }

                FileInfo fi = new FileInfo(filePath);

                var entryName = dirNamePrefix + filePath.Substring(dirNameOffset);  // Makes the name in zip based on the folder
                entryName = ZipEntry.CleanName(entryName);                          // Removes drive from name and fixes slash direction
                ZipEntry newEntry = new ZipEntry(entryName)
                {
                    DateTime = fi.LastWriteTime
                };
                newEntry.IsUnicodeText = true;                 // encode filename and comment in UTF8
                byte[] modifiedContent = {};

                // if this is a ReaderTools book, call GetBookReplacedWithTemplate() to get the contents
                if (forReaderTools && (bookFile == filePath))
                {
                    modifiedContent = Encoding.UTF8.GetBytes(GetBookReplacedWithTemplate(filePath));
                    newEntry.Size   = modifiedContent.Length;
                }
                else if (forReaderTools && (Path.GetFileName(filePath) == "meta.json"))
                {
                    modifiedContent = Encoding.UTF8.GetBytes(GetMetaJsonModfiedForTemplate(filePath));
                    newEntry.Size   = modifiedContent.Length;
                }
                else
                {
                    newEntry.Size = fi.Length;
                }

                zipStream.PutNextEntry(newEntry);

                if (modifiedContent.Length > 0)
                {
                    using (var memStream = new MemoryStream(modifiedContent))
                    {
                        StreamUtils.Copy(memStream, zipStream, new byte[modifiedContent.Length]);
                    }
                }
                else
                {
                    // Zip the file in buffered chunks
                    byte[] buffer = new byte[4096];
                    using (var streamReader = RobustFile.OpenRead(filePath))
                    {
                        StreamUtils.Copy(streamReader, zipStream, buffer);
                    }
                }

                zipStream.CloseEntry();
            }

            var folders = Directory.GetDirectories(directoryPath);

            foreach (var folder in folders)
            {
                var dirName = Path.GetFileName(folder);
                if ((dirName == null) || (dirName.ToLowerInvariant() == "sample texts"))
                {
                    continue;                     // Don't want to bundle these up
                }
                CompressDirectory(folder, zipStream, dirNameOffset, dirNamePrefix, forReaderTools);
            }
        }
Exemplo n.º 9
0
        public static Book.Book PrepareBookForBloomReader(string bookFolderPath, BookServer bookServer, TemporaryFolder temp,
                                                          WebSocketProgress progress, string creator = "bloom", 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);

            // 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.CollectionSettings.Language2.Iso639Code);
            }
            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.CollectionSettings.Language1.Iso639Code
                };
                PublishModel.RemoveUnwantedLanguageData(modifiedBook.OurHtmlDom, languagesToInclude, modifiedBook.CollectionSettings.Language2.Iso639Code);
            }

            // 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;
            }
            modifiedBook.RemoveBlankPages(settings?.LanguagesToInclude);

            // See https://issues.bloomlibrary.org/youtrack/issue/BL-6835.
            RemoveInvisibleImageElements(modifiedBook);
            modifiedBook.Storage.CleanupUnusedImageFiles(keepFilesForEditing: false);
            if (RobustFile.Exists(Path.Combine(modifiedBookFolderPath, "placeHolder.png")))
            {
                RobustFile.Delete(Path.Combine(modifiedBookFolderPath, "placeHolder.png"));
            }

            modifiedBook.Storage.CleanupUnusedAudioFiles(isForPublish: true);
            modifiedBook.RemoveObsoleteAudioMarkup();
            modifiedBook.Storage.CleanupUnusedVideoFiles();

            // 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
            modifiedBook.UpdateMetadataFeatures(
                isBlindEnabled: true,
                isSignLanguageEnabled: true,
                isTalkingBookEnabled: true);

            modifiedBook.SetAnimationDurationsFromAudioDurations();

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

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

            modifiedBook.Save();

            return(modifiedBook);
        }