< Summary

Class:GDX.IO.Compression.TarFile
Assembly:GDX
File(s):./Packages/com.dotbunny.gdx/GDX/IO/Compression/TarFile.cs
Covered lines:0
Uncovered lines:63
Coverable lines:63
Total lines:133
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)

./Packages/com.dotbunny.gdx/GDX/IO/Compression/TarFile.cs

#LineLine coverage
 1// Copyright (c) 2020-2024 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;
 9using UnityEngine;
 10
 11namespace GDX.IO.Compression
 12{
 13    /// <summary>
 14    ///     Provides static methods for extracting tar files and tarballs.
 15    /// </summary>
 16    public static class TarFile
 17    {
 18        /// <summary>
 19        ///     Extracts all the files in the specified tar/tarball to a directory on the file system.
 20        /// </summary>
 21        /// <example>
 22        ///     A synchronous approach to extracting the contents of a file, to a folder:
 23        ///     <code>TarFile.ExtractToDirectory("C:\Temp\DownloadCache.tar.gz", "C:\Saved");</code>
 24        /// </example>
 25        /// <param name="sourceArchiveFileName">The path to the archive that is to be extracted.</param>
 26        /// <param name="destinationDirectoryName">
 27        ///     The path to the directory in which to place the extracted files, specified as a
 28        ///     relative or absolute path. A relative path is interpreted as relative to the current working directory.
 29        /// </param>
 30        /// <param name="forceGZipDataFormat">Enforce inflating the file via a <see cref="GZipStream" />.</param>
 31        public static void ExtractToDirectory(string sourceArchiveFileName, string destinationDirectoryName,
 32            bool forceGZipDataFormat = false)
 033        {
 34            const int k_ReadBufferSize = 4096;
 35
 36            // We need to handle the gzip first before we address the archive itself
 037            if (forceGZipDataFormat ||
 38                sourceArchiveFileName.EndsWith(".gz", StringComparison.InvariantCultureIgnoreCase))
 039            {
 040                using FileStream stream = File.OpenRead(sourceArchiveFileName);
 041                using GZipStream gzip = new GZipStream(stream, CompressionMode.Decompress);
 042                using MemoryStream memoryStream = new MemoryStream();
 43                // Loop through the stream
 44                int readByteCount;
 045                byte[] readBuffer = new byte[k_ReadBufferSize];
 46                do
 047                {
 048                    readByteCount = gzip.Read(readBuffer, 0, k_ReadBufferSize);
 049                    memoryStream.Write(readBuffer, 0, readByteCount);
 050                } while (readByteCount == k_ReadBufferSize);
 51
 052                memoryStream.Seek(0, SeekOrigin.Begin);
 053                ExtractStream(memoryStream, destinationDirectoryName);
 054            }
 55            else
 056            {
 057                using FileStream fileStream = File.OpenRead(sourceArchiveFileName);
 058                ExtractStream(fileStream, destinationDirectoryName);
 059            }
 060        }
 61
 62
 63        /// <summary>
 64        ///     Extract a tar formatted <see cref="Stream" /> to the <paramref name="destinationDirectoryName" />.
 65        /// </summary>
 66        /// <param name="sourceStream">The <see cref="Stream" /> which to extract from.</param>
 67        /// <param name="destinationDirectoryName">Output directory to write the files.</param>
 68        public static void ExtractStream(Stream sourceStream, string destinationDirectoryName)
 069        {
 70            const int k_ReadBufferSize = 100;
 71            const int k_ContentOffset = 512;
 072            byte[] readBuffer = new byte[k_ReadBufferSize];
 073            while (true)
 074            {
 75                // ReSharper disable once MustUseReturnValue
 076                sourceStream.Read(readBuffer, 0, k_ReadBufferSize);
 077                string currentName = Encoding.ASCII.GetString(readBuffer).Trim('\0');
 78
 079                if (string.IsNullOrWhiteSpace(currentName))
 080                {
 081                    break;
 82                }
 83
 084                string destinationFilePath = Path.Combine(destinationDirectoryName, currentName);
 085                sourceStream.Seek(24, SeekOrigin.Current);
 086                int readByteCount = sourceStream.Read(readBuffer, 0, 12);
 087                if (readByteCount != 12)
 088                {
 089                    Debug.LogError($"Unable to read filesize from header. {readByteCount.ToString()} read, expected 12."
 090                    break;
 91                }
 92
 093                long fileSize = Convert.ToInt64(Encoding.UTF8.GetString(readBuffer, 0, 12).Trim('\0').Trim(), 8);
 094                sourceStream.Seek(376L, SeekOrigin.Current);
 95
 96                // Do we need to make a directory?
 097                string parentDirectory = Path.GetDirectoryName(destinationFilePath);
 098                if (parentDirectory != null && !Directory.Exists(parentDirectory))
 099                {
 0100                    DirectoryInfo directoryInfo = Directory.CreateDirectory(parentDirectory);
 0101                    directoryInfo.Attributes &= ~FileAttributes.ReadOnly;
 0102                }
 103
 104                // Don't try to make directories as files
 0105                if (!currentName.Equals("./", StringComparison.InvariantCulture) &&
 106                    !currentName.EndsWith("/") &&
 107                    !currentName.EndsWith("\\"))
 0108                {
 0109                    using FileStream newFileStream =
 110                        File.Open(destinationFilePath, FileMode.OpenOrCreate, FileAccess.Write);
 0111                    byte[] fileContentBuffer = new byte[fileSize];
 0112                    int newFileContentBufferLength = fileContentBuffer.Length;
 0113                    readByteCount = sourceStream.Read(fileContentBuffer, 0, newFileContentBufferLength);
 0114                    if (readByteCount != newFileContentBufferLength)
 0115                    {
 0116                        Debug.LogWarning(
 117                            $"Read file size of {readByteCount.ToString()} does not match the expected {newFileContentBu
 0118                    }
 119
 0120                    newFileStream.Write(fileContentBuffer, 0, newFileContentBufferLength);
 0121                }
 122
 0123                long nextOffset = k_ContentOffset - sourceStream.Position % k_ContentOffset;
 0124                if (nextOffset == k_ContentOffset)
 0125                {
 0126                    nextOffset = 0;
 0127                }
 128
 0129                sourceStream.Seek(nextOffset, SeekOrigin.Current);
 0130            }
 0131        }
 132    }
 133}