Ejemplo n.º 1
0
        public void AddDirectoryContents_GivenSubfolder_WritesContentAtExpectedPath()
        {
            ///////////
            // Setup //
            ///////////
            string fileContents = "Hello.";

            // When disposing, deletes the temporary folder including its contents.
            using (var testFolder = new TemporaryFolder(kTestFolderName))
            {
                var subFolder    = new TemporaryFolder(testFolder, "subFolder");
                var testFilePath = Path.Combine(subFolder.Path, Path.ChangeExtension(Path.GetRandomFileName(), ".txt"));
                RobustFile.WriteAllText(testFilePath, fileContents);

                // This is not under the test folder. It makes creating the archive less thorny that way. make sure it gets cleaned up.
                using (var archiveFile = TempFile.WithExtension(".tar"))
                {
                    ///////////////////////
                    // System under test //
                    ///////////////////////
                    var archive = new BloomTarArchive(archiveFile.Path);
                    archive.AddDirectoryContents(testFolder.Path);
                    archive.Save();

                    //////////////////
                    // Verification //
                    //////////////////
                    var        inStream          = RobustFile.OpenRead(archiveFile.Path);
                    TarArchive archiveForReading = TarArchive.CreateInputTarArchive(inStream, Encoding.UTF8);
                    using (var extractedFolder = new TemporaryFolder(testFolder, "extractedTarContents"))
                    {
                        archiveForReading.ExtractContents(extractedFolder.Path, false);

                        string expectedFilePath = Path.Combine(extractedFolder.Path, "subFolder", Path.GetFileName(testFilePath));
                        FileAssert.Exists(expectedFilePath);
                        FileAssert.AreEqual(expectedFilePath, testFilePath, "Incorrect file contents.");

                        archiveForReading.Close();
                    }
                }

                /////////////
                // Cleanup //
                /////////////

                // Nothing else necessary, disposing the TemporaryFolder cleans up all its subcontents.
            }
        }
Ejemplo n.º 2
0
        private void AddFile(string path, string entryName)
        {
            var fi       = new FileInfo(path);
            var newEntry = new ZipEntry(entryName)
            {
                DateTime = fi.LastWriteTime, Size = fi.Length, IsUnicodeText = true
            };

            _zipStream.PutNextEntry(newEntry);

            // Zip the file in buffered chunks
            var buffer = new byte[4096];

            using (var streamReader = RobustFile.OpenRead(path))
            {
                StreamUtils.Copy(streamReader, _zipStream, buffer);
            }

            _zipStream.CloseEntry();
        }
Ejemplo n.º 3
0
        protected override void AddFile(string path, string entryName, bool compressIfAble = true)
        {
            var fi       = new FileInfo(path);
            var newEntry = new ZipEntry(entryName)
            {
                DateTime          = fi.LastWriteTime, Size = fi.Length, IsUnicodeText = true,
                CompressionMethod = compressIfAble?CompressionMethod.Deflated:CompressionMethod.Stored
            };

            _zipStream.PutNextEntry(newEntry);

            // Zip the file in buffered chunks
            var buffer = new byte[4096];

            using (var streamReader = RobustFile.OpenRead(path))
            {
                StreamUtils.Copy(streamReader, _zipStream, buffer);
            }

            _zipStream.CloseEntry();
        }
Ejemplo n.º 4
0
        /// <summary>
        /// Adds a file to the archive
        /// </summary>
        /// <param name="compressIfAble">This parameter is ignored because tar files don't support compression. It is recommended to pass "false" though.</param>
        protected override void AddFile(string path, string entryName, bool compressIfAble = true)
        {
            var fi = new FileInfo(path);

            TarEntry newEntry = TarEntry.CreateEntryFromFile(path);

            newEntry.TarHeader.Name = entryName;
            newEntry.Size           = fi.Length;

            _tarStream.PutNextEntry(newEntry);

            // Add to the archive in buffered chunks
            var buffer = new byte[4096];

            using (var streamReader = RobustFile.OpenRead(path))
            {
                StreamUtils.Copy(streamReader, _tarStream, buffer);
            }

            _tarStream.CloseEntry();
        }
Ejemplo n.º 5
0
        public void ReplyWithFileContent(string path, string originalPath = null)
        {
            //Deal with BL-3153, where the file was still open in another thread
            FileStream fs;

            if (!RobustFile.Exists(path))
            {
                //for audio, at least, this is not really an error. We constantly are asking if audio already exists for the current segment
                //enhance: maybe audio should go through a different path, e.g. "/bloom/audio/somefile.wav"
                //then this path COULD write and error
                //Logger.WriteError("Server could not find" + path);
                _actualContext.Response.StatusCode = 404;
                return;
            }

            try
            {
                fs = RobustFile.OpenRead(path);
            }
            catch (Exception error)
            {
                Logger.WriteError("Server could not read " + path, error);
                _actualContext.Response.StatusCode = 500;
                return;
            }

            using (fs)
            {
                _actualContext.Response.ContentLength64 = fs.Length;
                _actualContext.Response.AppendHeader("PathOnDisk", HttpUtility.UrlEncode(path));
                if (ShouldCache(path, originalPath))
                {
                    _actualContext.Response.AppendHeader("Cache-Control",
                                                         "max-age=600000"); // about a week...if someone spends longer editing one book, well, files will get loaded one more time...
                }

                // A HEAD request (rather than a GET or POST request) is a request for just headers, and nothing can be written
                // to the OutputStream. It is normally used to check if the contents of the file have changed without taking the
                // time and bandwidth needed to download the full contents of the file. The 2 pieces of information being returned
                // are the Content-Length and Last-Modified headers. The requestor can use this information to determine if the
                // contents of the file have changed, and if they have changed the requestor can then decide if the file needs to
                // be reloaded. It is useful when debugging with tools which automatically reload the page when something changes.
                if (_actualContext.Request.HttpMethod == "HEAD")
                {
                    var lastModified = RobustFile.GetLastWriteTimeUtc(path).ToString("R");

                    // Originally we were returning the Last-Modified header with every response, but we discovered that this was
                    // causing Geckofx to cache the contents of the files. This made debugging difficult because, even if the file
                    // changed, Geckofx would use the cached file rather than requesting the updated file from the localhost.
                    _actualContext.Response.AppendHeader("Last-Modified", lastModified);
                }
                else if (fs.Length < 2 * 1024 * 1024)
                {
                    // This buffer size was picked to be big enough for any of the standard files we load in every page.
                    // Profiling indicates it is MUCH faster to use Response.Close() rather than writing to the output stream,
                    // though the gain may be illusory since the final 'false' argument allows our code to proceed without waiting
                    // for the complete data transfer. At a minimum, it makes this thread available to work on another
                    // request sooner.
                    var buffer = new byte[fs.Length];
                    fs.Read(buffer, 0, (int)fs.Length);
                    _actualContext.Response.Close(buffer, false);
                }
                else
                {
                    // For really big (typically image) files, use the old buffered approach
                    try
                    {
                        var buffer = new byte[1024 * 512];                         //512KB
                        int read;
                        while ((read = fs.Read(buffer, 0, buffer.Length)) > 0)
                        {
                            _actualContext.Response.OutputStream.Write(buffer, 0, read);
                        }
                        _actualContext.Response.OutputStream.Close();
                    }
                    catch (HttpListenerException e)
                    {
                        // If the page is gone and no longer able to accept the data, just log it.
                        ReportHttpListenerProblem(e);
                    }
                }
            }

            HaveOutput = true;
        }
Ejemplo n.º 6
0
        public void ReplyWithFileContent(string path)
        {
            //Deal with BL-3153, where the file was still open in another thread
            FileStream fs;

            if (!RobustFile.Exists(path))
            {
                //for audio, at least, this is not really an error. We constantly are asking if audio already exists for the current segment
                //enhance: maybe audio should go through a different path, e.g. "/bloom/audio/somefile.wav"
                //then this path COULD write and error
                //Logger.WriteError("Server could not find" + path);
                _actualContext.Response.StatusCode = 404;
                return;
            }

            try
            {
                fs = RobustFile.OpenRead(path);
            }
            catch (Exception error)
            {
                Logger.WriteError("Server could not read " + path, error);
                _actualContext.Response.StatusCode = 500;
                return;
            }

            using (fs)
            {
                _actualContext.Response.ContentLength64 = fs.Length;
                _actualContext.Response.AppendHeader("PathOnDisk", HttpUtility.UrlEncode(path));
                //helps with debugging what file is being chosen

                // A HEAD request (rather than a GET or POST request) is a request for just headers, and nothing can be written
                // to the OutputStream. It is normally used to check if the contents of the file have changed without taking the
                // time and bandwidth needed to download the full contents of the file. The 2 pieces of information being returned
                // are the Content-Length and Last-Modified headers. The requestor can use this information to determine if the
                // contents of the file have changed, and if they have changed the requestor can then decide if the file needs to
                // be reloaded. It is useful when debugging with tools which automatically reload the page when something changes.
                if (_actualContext.Request.HttpMethod == "HEAD")
                {
                    var lastModified = RobustFile.GetLastWriteTimeUtc(path).ToString("R");

                    // Originally we were returning the Last-Modified header with every response, but we discovered that this was
                    // causing Geckofx to cache the contents of the files. This made debugging difficult because, even if the file
                    // changed, Geckofx would use the cached file rather than requesting the updated file from the localhost.
                    _actualContext.Response.AppendHeader("Last-Modified", lastModified);
                }
                else
                {
                    var buffer = new byte[1024 * 512];                   //512KB
                    int read;
                    while ((read = fs.Read(buffer, 0, buffer.Length)) > 0)
                    {
                        _actualContext.Response.OutputStream.Write(buffer, 0, read);
                    }
                }
            }

            _actualContext.Response.OutputStream.Close();
            HaveOutput = true;
        }
Ejemplo n.º 7
0
        /// <summary>
        /// Get a json of stats about the image. It is used to populate a tooltip when you hover over an image container
        /// </summary>
        private void HandleImageInfo(ApiRequest request)
        {
            try
            {
                var fileName = request.RequiredFileNameOrPath("image");
                Guard.AgainstNull(_bookSelection.CurrentSelection, "CurrentBook");
                var plainfilename = fileName.NotEncoded;
                // The fileName might be URL encoded.  See https://silbloom.myjetbrains.com/youtrack/issue/BL-3901.
                var path = UrlPathString.GetFullyDecodedPath(_bookSelection.CurrentSelection.FolderPath, ref plainfilename);
                RequireThat.File(path).Exists();
                var     fileInfo = new FileInfo(path);
                dynamic result   = new ExpandoObject();
                result.name  = plainfilename;
                result.bytes = fileInfo.Length;

                // Using a stream this way, according to one source,
                // http://stackoverflow.com/questions/552467/how-do-i-reliably-get-an-image-dimensions-in-net-without-loading-the-image,
                // supposedly avoids loading the image into memory when we only want its dimensions
                using (var stream = RobustFile.OpenRead(path))
                    using (var img = Image.FromStream(stream, false, false))
                    {
                        result.width  = img.Width;
                        result.height = img.Height;
                        switch (img.PixelFormat)
                        {
                        case PixelFormat.Format32bppArgb:
                        case PixelFormat.Format32bppRgb:
                        case PixelFormat.Format32bppPArgb:
                            result.bitDepth = "32";
                            break;

                        case PixelFormat.Format24bppRgb:
                            result.bitDepth = "24";
                            break;

                        case PixelFormat.Format16bppArgb1555:
                        case PixelFormat.Format16bppGrayScale:
                            result.bitDepth = "16";
                            break;

                        case PixelFormat.Format8bppIndexed:
                            result.bitDepth = "8";
                            break;

                        case PixelFormat.Format1bppIndexed:
                            result.bitDepth = "1";
                            break;

                        default:
                            result.bitDepth = "unknown";
                            break;
                        }
                    }
                request.ReplyWithJson((object)result);
            }
            catch (Exception e)
            {
                Logger.WriteEvent("Error in server imageInfo/: url was " + request.LocalPath());
                Logger.WriteEvent("Error in server imageInfo/: exception is " + e.Message);
                request.Failed(e.Message);
                NonFatalProblem.Report(ModalIf.None, PassiveIf.Alpha, "Request Error", request.LocalPath(), e);
            }
        }
Ejemplo n.º 8
0
        /// <summary>
        /// Adds a directory, along with all files and subdirectories, to the ZipStream.
        /// </summary>
        /// <param name="directoryToCompress">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>
        /// <param name="excludeAudio">If true, the contents of the audio directory will not be included</param>
        /// <param name="reduceImages">If true, images will be compressed before being added to the zip file</param>
        /// <param name="omitMetaJson">If true, meta.json is excluded (typically for HTML readers).</param>
        /// <para> name="reduceImages">If true, image files are reduced in size to no larger than 300x300 before saving</para>
        /// <remarks>Protected for testing purposes</remarks>
        private static void CompressDirectory(string directoryToCompress, ZipOutputStream zipStream, int dirNameOffset, string dirNamePrefix,
                                              bool forReaderTools, bool excludeAudio, bool reduceImages, bool omitMetaJson = false, string pathToFileForSha = null)
        {
            if (excludeAudio && Path.GetFileName(directoryToCompress).ToLowerInvariant() == "audio")
            {
                return;
            }
            var files    = Directory.GetFiles(directoryToCompress);
            var bookFile = BookStorage.FindBookHtmlInFolder(directoryToCompress);

            foreach (var filePath in files)
            {
                if (ExcludedFileExtensionsLowerCase.Contains(Path.GetExtension(filePath.ToLowerInvariant())))
                {
                    continue;                     // BL-2246: skip putting this one into the BloomPack
                }
                var fileName = Path.GetFileName(filePath).ToLowerInvariant();
                if (fileName.StartsWith(BookStorage.PrefixForCorruptHtmFiles))
                {
                    continue;
                }
                // Various stuff we keep in the book folder that is useful for editing or bloom library
                // or displaying collections but not needed by the reader. The most important is probably
                // eliminating the pdf, which can be very large. Note that we do NOT eliminate the
                // basic thumbnail.png, as we want eventually to extract that to use in the Reader UI.
                if (fileName == "thumbnail-70.png" || fileName == "thumbnail-256.png")
                {
                    continue;
                }
                if (fileName == "meta.json" && omitMetaJson)
                {
                    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,
                    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 if (reduceImages && ImageFileExtensions.Contains(Path.GetExtension(filePath.ToLowerInvariant())))
                {
                    modifiedContent = GetBytesOfReducedImage(filePath);
                    newEntry.Size   = modifiedContent.Length;
                }
                else if (reduceImages && (bookFile == filePath))
                {
                    var originalContent = File.ReadAllText(bookFile, Encoding.UTF8);
                    var dom             = XmlHtmlConverter.GetXmlDomFromHtml(originalContent);
                    StripImagesWithMissingSrc(dom, bookFile);
                    StripContentEditable(dom);
                    InsertReaderStylesheet(dom);
                    ConvertImagesToBackground(dom);
                    var newContent = XmlHtmlConverter.ConvertDomToHtml5(dom);
                    modifiedContent = Encoding.UTF8.GetBytes(newContent);
                    newEntry.Size   = modifiedContent.Length;

                    if (pathToFileForSha != null)
                    {
                        // Make an extra entry containing the sha
                        var sha  = Book.MakeVersionCode(File.ReadAllText(pathToFileForSha, Encoding.UTF8), pathToFileForSha);
                        var name = "version.txt";                         // must match what BloomReader is looking for in NewBookListenerService.IsBookUpToDate()
                        MakeExtraEntry(zipStream, name, sha);
                    }
                    MakeExtraEntry(zipStream, "readerStyles.css",
                                   File.ReadAllText(FileLocator.GetFileDistributedWithApplication(Path.Combine(BloomFileLocator.BrowserRoot, "publish", "android", "readerStyles.css"))));
                }
                else
                {
                    newEntry.Size = fi.Length;
                }

                zipStream.PutNextEntry(newEntry);

                if (modifiedContent.Length > 0)
                {
                    using (var memStream = new MemoryStream(modifiedContent))
                    {
                        // There is some minimum buffer size (44 was too small); I don't know exactly what it is,
                        // but 1024 makes it happy.
                        StreamUtils.Copy(memStream, zipStream, new byte[Math.Max(modifiedContent.Length, 1024)]);
                    }
                }
                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(directoryToCompress);

            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, excludeAudio, reduceImages);
            }
        }
Ejemplo n.º 9
0
        /// <summary>
        /// Get a json of stats about the image. It is used to populate a tooltip when you hover over an image container
        /// </summary>
        private void HandleImageInfo(ApiRequest request)
        {
            try
            {
                var fileName = request.RequiredParam("image");
                Guard.AgainstNull(_bookSelection.CurrentSelection, "CurrentBook");
                var path = Path.Combine(_bookSelection.CurrentSelection.FolderPath, fileName);
                while (!RobustFile.Exists(path) && fileName.Contains('%'))
                {
                    var fileName1 = fileName;
                    // We can be fed doubly-encoded filenames.  So try to decode a second time and see if that works.
                    // See https://silbloom.myjetbrains.com/youtrack/issue/BL-3749.
                    // Effectively triple-encoded filenames have cropped up for particular books.  Such files are
                    // already handled okay by EnhancedImageServer.ProcessAnyFileContent().  This code can handle
                    // any depth of url-encoding.
                    // See https://silbloom.myjetbrains.com/youtrack/issue/BL-5757.
                    fileName = System.Web.HttpUtility.UrlDecode(fileName);
                    if (fileName == fileName1)
                    {
                        break;
                    }
                    path = Path.Combine(_bookSelection.CurrentSelection.FolderPath, fileName);
                }
                RequireThat.File(path).Exists();
                var     fileInfo = new FileInfo(path);
                dynamic result   = new ExpandoObject();
                result.name  = fileName;
                result.bytes = fileInfo.Length;

                // Using a stream this way, according to one source,
                // http://stackoverflow.com/questions/552467/how-do-i-reliably-get-an-image-dimensions-in-net-without-loading-the-image,
                // supposedly avoids loading the image into memory when we only want its dimensions
                using (var stream = RobustFile.OpenRead(path))
                    using (var img = Image.FromStream(stream, false, false))
                    {
                        result.width  = img.Width;
                        result.height = img.Height;
                        switch (img.PixelFormat)
                        {
                        case PixelFormat.Format32bppArgb:
                        case PixelFormat.Format32bppRgb:
                        case PixelFormat.Format32bppPArgb:
                            result.bitDepth = "32";
                            break;

                        case PixelFormat.Format24bppRgb:
                            result.bitDepth = "24";
                            break;

                        case PixelFormat.Format16bppArgb1555:
                        case PixelFormat.Format16bppGrayScale:
                            result.bitDepth = "16";
                            break;

                        case PixelFormat.Format8bppIndexed:
                            result.bitDepth = "8";
                            break;

                        case PixelFormat.Format1bppIndexed:
                            result.bitDepth = "1";
                            break;

                        default:
                            result.bitDepth = "unknown";
                            break;
                        }
                    }
                request.ReplyWithJson((object)result);
            }
            catch (Exception e)
            {
                Logger.WriteError("Error in server imageInfo/: url was " + request.LocalPath(), e);
                request.Failed(e.Message);
            }
        }
Ejemplo n.º 10
0
        /// <summary>
        /// Adds a directory, along with all files and subdirectories, to the ZipStream.
        /// </summary>
        /// <param name="directoryToCompress">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="depthFromCollection">int with the number of folders away it is from the collection folder. The collection folder itself is 0,
        /// a book is 1, a subfolder of the book is 2, etc.</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>
        /// <param name="excludeAudio">If true, the contents of the audio directory will not be included</param>
        /// <param name="reduceImages">If true, image files are reduced in size to no larger than the max size before saving</para>
        /// <param name="omitMetaJson">If true, meta.json is excluded (typically for HTML readers).</param>
        private static void CompressDirectory(string directoryToCompress, ZipOutputStream zipStream, int dirNameOffset, string dirNamePrefix,
                                              int depthFromCollection, bool forReaderTools, bool excludeAudio, bool reduceImages, bool omitMetaJson = false, string pathToFileForSha = null)
        {
            if (excludeAudio && Path.GetFileName(directoryToCompress).ToLowerInvariant() == "audio")
            {
                return;
            }
            var files = Directory.GetFiles(directoryToCompress);

            // Don't get distracted by HTML files in any folder other than the book folder.
            // These HTML files in other locations aren't generated by Bloom. They may not have the format Bloom expects,
            // causing needless parsing errors to be thrown if we attempt to read them using Bloom code.
            bool          shouldScanHtml = depthFromCollection == 1;    // 1 means 1 level below the collection level, i.e. this is the book level
            var           bookFile       = shouldScanHtml ? BookStorage.FindBookHtmlInFolder(directoryToCompress) : null;
            XmlDocument   dom            = null;
            List <string> imagesToGiveTransparentBackgrounds = null;
            List <string> imagesToPreserveResolution         = null;

            // Tests can also result in bookFile being null.
            if (!String.IsNullOrEmpty(bookFile))
            {
                var originalContent = File.ReadAllText(bookFile, Encoding.UTF8);
                dom = XmlHtmlConverter.GetXmlDomFromHtml(originalContent);
                var fullScreenAttr = dom.GetElementsByTagName("body").Cast <XmlElement>().First().Attributes["data-bffullscreenpicture"]?.Value;
                if (fullScreenAttr != null && fullScreenAttr.IndexOf("bloomReader", StringComparison.InvariantCulture) >= 0)
                {
                    // This feature (currently used for motion books in landscape mode) triggers an all-black background,
                    // due to a rule in bookFeatures.less.
                    // Making white pixels transparent on an all-black background makes line-art disappear,
                    // which is bad (BL-6564), so just make an empty list in this case.
                    imagesToGiveTransparentBackgrounds = new List <string>();
                }
                else
                {
                    imagesToGiveTransparentBackgrounds = FindCoverImages(dom);
                }
                imagesToPreserveResolution = FindImagesToPreserveResolution(dom);
                FindBackgroundAudioFiles(dom);
            }
            else
            {
                imagesToGiveTransparentBackgrounds = new List <string>();
                imagesToPreserveResolution         = new List <string>();
            }

            // Some of the knowledge about ExcludedFileExtensions might one day move into this method.
            // But we'd have to check carefully the other places it is used.
            var localOnlyFiles = BookStorage.LocalOnlyFiles(directoryToCompress);

            foreach (var filePath in files)
            {
                if (ExcludedFileExtensionsLowerCase.Contains(Path.GetExtension(filePath.ToLowerInvariant())))
                {
                    continue;                     // BL-2246: skip putting this one into the BloomPack
                }
                if (IsUnneededWaveFile(filePath, depthFromCollection))
                {
                    continue;
                }
                if (localOnlyFiles.Contains(filePath))
                {
                    continue;
                }
                var fileName = Path.GetFileName(filePath).ToLowerInvariant();
                if (fileName.StartsWith(BookStorage.PrefixForCorruptHtmFiles))
                {
                    continue;
                }
                // Various stuff we keep in the book folder that is useful for editing or bloom library
                // or displaying collections but not needed by the reader. The most important is probably
                // eliminating the pdf, which can be very large. Note that we do NOT eliminate the
                // basic thumbnail.png, as we want eventually to extract that to use in the Reader UI.
                if (fileName == "thumbnail-70.png" || fileName == "thumbnail-256.png")
                {
                    continue;
                }
                if (fileName == "meta.json" && omitMetaJson)
                {
                    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,
                    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 if (reduceImages && ImageFileExtensions.Contains(Path.GetExtension(filePath.ToLowerInvariant())))
                {
                    fileName = Path.GetFileName(filePath);                      // restore original capitalization
                    if (imagesToPreserveResolution.Contains(fileName))
                    {
                        modifiedContent = RobustFile.ReadAllBytes(filePath);
                    }
                    else
                    {
                        // Cover images should be transparent if possible.  Others don't need to be.
                        var makeBackgroundTransparent = imagesToGiveTransparentBackgrounds.Contains(fileName);
                        modifiedContent = GetImageBytesForElectronicPub(filePath, makeBackgroundTransparent);
                    }
                    newEntry.Size = modifiedContent.Length;
                }
                else if (Path.GetExtension(filePath).ToLowerInvariant() == ".bloomcollection")
                {
                    modifiedContent = Encoding.UTF8.GetBytes(GetBloomCollectionModifiedForTemplate(filePath));
                    newEntry.Size   = modifiedContent.Length;
                }
                // CompressBookForDevice is always called with reduceImages set.
                else if (reduceImages && bookFile == filePath)
                {
                    SignLanguageApi.ProcessVideos(HtmlDom.SelectChildVideoElements(dom.DocumentElement).Cast <XmlElement>(), directoryToCompress);
                    var newContent = XmlHtmlConverter.ConvertDomToHtml5(dom);
                    modifiedContent = Encoding.UTF8.GetBytes(newContent);
                    newEntry.Size   = modifiedContent.Length;

                    if (pathToFileForSha != null)
                    {
                        // Make an extra entry containing the sha
                        var sha  = Book.ComputeHashForAllBookRelatedFiles(pathToFileForSha);
                        var name = "version.txt";                         // must match what BloomReader is looking for in NewBookListenerService.IsBookUpToDate()
                        MakeExtraEntry(zipStream, name, sha);
                        LastVersionCode = sha;
                    }
                }
                else
                {
                    newEntry.Size = fi.Length;
                }

                zipStream.PutNextEntry(newEntry);

                if (modifiedContent.Length > 0)
                {
                    using (var memStream = new MemoryStream(modifiedContent))
                    {
                        // There is some minimum buffer size (44 was too small); I don't know exactly what it is,
                        // but 1024 makes it happy.
                        StreamUtils.Copy(memStream, zipStream, new byte[Math.Max(modifiedContent.Length, 1024)]);
                    }
                }
                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(directoryToCompress);

            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, depthFromCollection + 1, forReaderTools, excludeAudio, reduceImages);
            }
        }
Ejemplo n.º 11
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);
            }
        }