using System;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text;
namespace UnityEditor.Build.Pipeline.Utilities
{
///
/// Since C# only has GZipStream until .NET 4.0, we are forced to implement our own packing system
/// for artifact files. We compact all artifacts into a single GZip Stream with the header before
/// the file name and contents. The header contains the file name length and file length (in bytes).
///
public class FileCompressor
{
///
/// Compresses all artifacts located at a specified directory.
///
/// The directory containing the artifacts.
/// The file path at which the archive will be created.
/// Returns true if the directory was found and compressed. Returns false otherwise.
public static bool Compress(string directoryPath, string archiveFilePath)
{
if (!directoryPath.EndsWith("/") && !directoryPath.EndsWith("\\"))
directoryPath += Path.DirectorySeparatorChar.ToString();
var directory = new DirectoryInfo(directoryPath);
if (!directory.Exists)
return false;
var files = directory.GetFiles("*", SearchOption.AllDirectories);
files = files.Where(x => (File.GetAttributes(x.FullName) & FileAttributes.Hidden) != FileAttributes.Hidden && x.Extension != ".sbpGz").ToArray();
if (files.Length == 0)
return false;
using (var archiveStream = new FileStream(archiveFilePath, FileMode.OpenOrCreate, FileAccess.Write))
{
using (var gZipStream = new GZipStream(archiveStream, CompressionMode.Compress))
{
foreach (var file in files)
{
var relativePath = file.FullName.Substring(directory.FullName.Length);
relativePath = relativePath.Replace("\\", "/");
using (var fileStream = file.OpenRead())
{
byte[] filePathBytes = Encoding.ASCII.GetBytes(relativePath);
// Write chunk header
gZipStream.Write(BitConverter.GetBytes(filePathBytes.Length), 0, sizeof(int));
gZipStream.Write(BitConverter.GetBytes(fileStream.Length), 0, sizeof(long));
// Write chunk body
// Write file path
gZipStream.Write(filePathBytes, 0, filePathBytes.Length);
// Write file contents
byte[] readBuffer = new byte[4096];
int readSize = readBuffer.Length;
while (readSize == readBuffer.Length)
{
readSize = fileStream.Read(readBuffer, 0, readBuffer.Length);
gZipStream.Write(readBuffer, 0, readSize);
}
}
}
}
}
return true;
}
///
/// Extracts all artifacts compressed in an archive.
///
/// The archive to decompress.
/// The path where the extracted artifacts will be stored.
/// Returns true if the archive was found and decompressed. Returns false otherwise.
public static bool Decompress(string archiveFilePath, string directoryPath)
{
var archiveFile = new FileInfo(archiveFilePath);
if (!archiveFile.Exists)
return false;
var directory = new DirectoryInfo(directoryPath);
if (!directory.Exists)
directory.Create();
using (var archiveStream = archiveFile.OpenRead())
{
using (var gZipStream = new GZipStream(archiveStream, CompressionMode.Decompress))
{
while (true)
{
// Read chunk header
byte[] header = new byte[sizeof(int) + sizeof(long)];
int readSize = gZipStream.Read(header, 0, header.Length);
if (readSize != header.Length)
break;
int filePathLength = BitConverter.ToInt32(header, 0);
long fileLenth = BitConverter.ToInt64(header, sizeof(int));
// Read chunk body
// Read file path
byte[] filePathBytes = new byte[filePathLength];
gZipStream.Read(filePathBytes, 0, filePathLength);
string filePath = Encoding.ASCII.GetString(filePathBytes);
var pathSeperator = filePath.LastIndexOf("/");
if (pathSeperator > -1)
Directory.CreateDirectory(string.Format("{0}/{1}", directoryPath, filePath.Substring(0, pathSeperator)));
// Read file contents
using (var fileStream = new FileStream(directoryPath + "/" + filePath, FileMode.OpenOrCreate, FileAccess.Write))
{
byte[] readBuffer = new byte[4096];
long readRemaining = fileLenth;
while (readRemaining > 0)
{
readSize = readBuffer.Length < readRemaining ? readBuffer.Length : (int)readRemaining;
gZipStream.Read(readBuffer, 0, readSize);
fileStream.Write(readBuffer, 0, readSize);
readRemaining -= readSize;
}
}
}
}
}
return true;
}
}
}