< Summary

Class:GDX.IO.Compression.TarFile
Assembly:GDX
File(s):D:/BuildAgent/work/GDX-Documentation/Projects/GDX_Development/Packages/com.dotbunny.gdx/GDX/IO/Compression/TarFile.cs
Covered lines:0
Uncovered lines:63
Coverable lines:63
Total lines:131
Line coverage:0% (0 of 63)
Covered branches:0
Total branches:0
Covered methods:0
Total methods:2
Method coverage:0% (0 of 2)

Coverage History

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity NPath complexity Sequence coverage
ExtractToDirectory(...)0%72800%
ExtractStream(...)0%1321100%

File(s)

D:/BuildAgent/work/GDX-Documentation/Projects/GDX_Development/Packages/com.dotbunny.gdx/GDX/IO/Compression/TarFile.cs

#LineLine coverage
 1// Copyright (c) 2020-2022 dotBunny Inc.
 2// dotBunny licenses this file to you under the BSL-1.0 license.
 3// See the LICENSE file in the project root for more information.
 4
 5using System;
 6using System.IO;
 7using System.IO.Compression;
 8using System.Text;
 9
 10namespace GDX.IO.Compression
 11{
 12    /// <summary>
 13    /// Provides static methods for extracting tar files and tarballs.
 14    /// </summary>
 15    public static class TarFile
 16    {
 17        /// <summary>
 18        ///     Extracts all the files in the specified tar/tarball to a directory on the file system.
 19        /// </summary>
 20        /// <example>
 21        ///     A synchronous approach to extracting the contents of a file, to a folder:
 22        ///     <code>TarFile.ExtractToDirectory("C:\Temp\DownloadCache.tar.gz", "C:\Saved");</code>
 23        /// </example>
 24        /// <param name="sourceArchiveFileName">The path to the archive that is to be extracted.</param>
 25        /// <param name="destinationDirectoryName">
 26        ///     The path to the directory in which to place the extracted files, specified as a
 27        ///     relative or absolute path. A relative path is interpreted as relative to the current working directory.
 28        /// </param>
 29        /// <param name="forceGZipDataFormat">Enforce inflating the file via a <see cref="GZipStream" />.</param>
 30        public static void ExtractToDirectory(string sourceArchiveFileName, string destinationDirectoryName,
 31            bool forceGZipDataFormat = false)
 032        {
 33            const int k_ReadBufferSize = 4096;
 34
 35            // We need to handle the gzip first before we address the archive itself
 036            if (forceGZipDataFormat ||
 37                sourceArchiveFileName.EndsWith(".gz", StringComparison.InvariantCultureIgnoreCase))
 038            {
 039                using FileStream stream = File.OpenRead(sourceArchiveFileName);
 040                using GZipStream gzip = new GZipStream(stream, CompressionMode.Decompress);
 041                using MemoryStream memoryStream = new MemoryStream();
 42                // Loop through the stream
 43                int readByteCount;
 044                byte[] readBuffer = new byte[k_ReadBufferSize];
 45                do
 046                {
 047                    readByteCount = gzip.Read(readBuffer, 0, k_ReadBufferSize);
 048                    memoryStream.Write(readBuffer, 0, readByteCount);
 049                } while (readByteCount == k_ReadBufferSize);
 50
 051                memoryStream.Seek(0, SeekOrigin.Begin);
 052                ExtractStream(memoryStream, destinationDirectoryName);
 053            }
 54            else
 055            {
 056                using FileStream fileStream = File.OpenRead(sourceArchiveFileName);
 057                ExtractStream(fileStream, destinationDirectoryName);
 058            }
 059        }
 60
 61
 62        /// <summary>
 63        ///     Extract a tar formatted <see cref="Stream"/> to the <paramref name="destinationDirectoryName" />.
 64        /// </summary>
 65        /// <param name="sourceStream">The <see cref="Stream" /> which to extract from.</param>
 66        /// <param name="destinationDirectoryName">Output directory to write the files.</param>
 67        public static void ExtractStream(Stream sourceStream, string destinationDirectoryName)
 068        {
 69            const int k_ReadBufferSize = 100;
 70            const int k_ContentOffset = 512;
 071            byte[] readBuffer = new byte[k_ReadBufferSize];
 072            while (true)
 073            {
 74                // ReSharper disable once MustUseReturnValue
 075                sourceStream.Read(readBuffer, 0, k_ReadBufferSize);
 076                string currentName = Encoding.ASCII.GetString(readBuffer).Trim('\0');
 77
 078                if (string.IsNullOrWhiteSpace(currentName))
 079                {
 080                    break;
 81                }
 82
 083                string destinationFilePath = Path.Combine(destinationDirectoryName, currentName);
 084                sourceStream.Seek(24, SeekOrigin.Current);
 085                int readByteCount = sourceStream.Read(readBuffer, 0, 12);
 086                if (readByteCount != 12)
 087                {
 088                    Trace.Output(Trace.TraceLevel.Error,
 89                        $"Unable to read filesize from header. {readByteCount.ToString()} read, expected 12.");
 090                    break;
 91                }
 092                long fileSize = Convert.ToInt64(Encoding.UTF8.GetString(readBuffer, 0, 12).Trim('\0').Trim(), 8);
 093                sourceStream.Seek(376L, SeekOrigin.Current);
 94
 95                // Do we need to make a directory?
 096                string parentDirectory = Path.GetDirectoryName(destinationFilePath);
 097                if (parentDirectory != null && !Directory.Exists(parentDirectory))
 098                {
 099                    DirectoryInfo directoryInfo = Directory.CreateDirectory(parentDirectory);
 0100                    directoryInfo.Attributes &= ~FileAttributes.ReadOnly;
 0101                }
 102
 103                // Don't try to make directories as files
 0104                if (!currentName.Equals("./", StringComparison.InvariantCulture) &&
 105                    !currentName.EndsWith("/") &&
 106                    !currentName.EndsWith("\\"))
 0107                {
 0108                    using FileStream newFileStream =
 109                        File.Open(destinationFilePath, FileMode.OpenOrCreate, FileAccess.Write);
 0110                    byte[] fileContentBuffer = new byte[fileSize];
 0111                    int newFileContentBufferLength = fileContentBuffer.Length;
 0112                    readByteCount = sourceStream.Read(fileContentBuffer, 0, newFileContentBufferLength);
 0113                    if (readByteCount != newFileContentBufferLength)
 0114                    {
 0115                        Trace.Output(Trace.TraceLevel.Warning,
 116                            $"Read file size of {readByteCount.ToString()} does not match the expected {newFileContentBu
 0117                    }
 0118                    newFileStream.Write(fileContentBuffer, 0, newFileContentBufferLength);
 0119                }
 120
 0121                long nextOffset = k_ContentOffset - sourceStream.Position % k_ContentOffset;
 0122                if (nextOffset == k_ContentOffset)
 0123                {
 0124                    nextOffset = 0;
 0125                }
 126
 0127                sourceStream.Seek(nextOffset, SeekOrigin.Current);
 0128            }
 0129        }
 130    }
 131}