// Extract a file from our archive public void ExtractFile(TARMetaData entry, string output) { // Check we're extracting a normal file if (entry.GetTypeFlags() == TARMetaData.TypeFlags.NormalFile) { //Create any directories as required string directory = Path.GetDirectoryName(output); if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) { Directory.CreateDirectory(directory); } { // Open relevant streams and extract using (var stream = File.OpenRead(Filename)) if (isCompressed) { using (var gzip = new GZipStream(stream, CompressionMode.Decompress)) ExtractFile(entry, gzip, output); } else { ExtractFile(entry, stream, output); } } } }
// Add a file to our archive using streams // For simplicity, we always add to the start of the archive private void AddFile(string filePath, Stream streamIn, Stream streamOut) { // For the user name and user group, we use Environment.UserName // I haven't found a simple, platform independent way of getting the // User Group correctly string userName = Environment.UserName; string userGroup = Environment.UserName; // Read all the file data to add byte[] fileData = File.ReadAllBytes(filePath); // Use the File's Last Write time as the file's time in the archive long currentTime = new DateTimeOffset(File.GetLastWriteTime(filePath)).ToUnixTimeSeconds(); // Create the metadata structure TARMetaData fileMD = TARMetaData.CreateDefaultFileMetaData(Path.GetFileName(filePath), fileData.Length, currentTime, userName, userGroup); // Copy the metadata to a byte array and write it byte[] bAssetMD = fileMD.ToByteArray(true); streamOut.Write(bAssetMD, 0, bAssetMD.Length); // Then write out the file following this streamOut.Write(fileData, 0, fileData.Length); // Pad after the file data until we reach a 512 byte boundary if needed if (fileData.Length % 512 > 0) { int paddingBytes = 512 - (fileData.Length % 512); byte[] bPaddingBytes = new byte[paddingBytes]; streamOut.Write(bPaddingBytes, 0, paddingBytes); } // Then loop through each of the original entries foreach (TARMetaData entry in Entries) { //Read the original meta data from the input and write to output var metadata = new byte[512]; streamIn.Read(metadata, 0, metadata.Length); streamOut.Write(metadata, 0, metadata.Length); //Read the original file data from the input and write to output var file = new byte[entry.fileSize]; streamIn.Read(file, 0, file.Length); streamOut.Write(file, 0, file.Length); // Pad after the file data until we reach a 512 byte boundary if needed if (file.Length % 512 > 0) { int paddingBytes = 512 - (file.Length % 512); byte[] bPaddingBytes = new byte[paddingBytes]; streamIn.Read(bPaddingBytes, 0, paddingBytes); streamOut.Write(bPaddingBytes, 0, paddingBytes); } } }
// Remove a file from our archive using the entry name // We cannot do in place overwrites, as we read in the original file // And then write it out public void RemoveFile(string entryName, string output, bool outputCompressed) { // Look up the entry based on filename TARMetaData entry = Entries.Find(e => e.fileName == entryName); //If we found one, extract it if (entry != null) { RemoveFile(entry, output, outputCompressed); } }
// Extract a file from our archive using the entry name public void ExtractFile(string entryName, string output) { // Look up the entry based on filename TARMetaData entry = Entries.Find(e => e.fileName == entryName); //If we found one, extract it if (entry != null) { ExtractFile(entry, output); } }
// Extract a file from a stream private void ExtractFile(TARMetaData entry, Stream stream, string output) { // Open the output using (var str = File.Open(output, FileMode.OpenOrCreate, FileAccess.Write)) { // Create a buffer to hold the file var buf = new byte[entry.fileSize]; // Seek to the relevant place in the stream StreamSeek(stream, entry.FileBufferPositionStart); // Read from the stream, write to the file stream.Read(buf, 0, buf.Length); str.Write(buf, 0, buf.Length); } }
// Remove a file from our archive // We cannot do in place overwrites, as we read in the original file // And then write it out public void RemoveFile(TARMetaData entryToRemove, string output, bool outputCompressed) { //Create any directories as required string directory = Path.GetDirectoryName(output); if (!string.IsNullOrEmpty(directory) && !Directory.Exists(directory)) { Directory.CreateDirectory(directory); } { // Open relevant streams and add file as necessary // Code is a bit messy, but essentially we have four conditions // where the input stream can be compressed or not, and the output // stream can be compressed or not using (var streamIn = File.OpenRead(Filename)) { using (var streamOut = File.Open(output, FileMode.OpenOrCreate, FileAccess.Write)) { if (isCompressed) { using (var gzipIn = new GZipStream(streamIn, CompressionMode.Decompress)) { if (outputCompressed) { using (var gzipOut = new GZipStream(streamOut, CompressionMode.Compress)) RemoveFile(entryToRemove, gzipIn, gzipOut); } else { RemoveFile(entryToRemove, gzipIn, streamOut); } } } else { if (outputCompressed) { using (var gzipOut = new GZipStream(streamOut, CompressionMode.Compress)) RemoveFile(entryToRemove, streamIn, gzipOut); } else { RemoveFile(entryToRemove, streamIn, streamOut); } } } } } }
// Load a TAR archive from a Stream private void LoadFromStream(Stream stream) { // TAR Entry MetaData is stored in 512 byte buffers var metaDataBuffer = new byte[512]; int readCount = 0; long bufferPosition = 0; do { // Read 512 bytes readCount = stream.Read(metaDataBuffer, 0, 512); // If we were successful if (readCount >= 512) { // Unpack the MetaData and update the buffer positions TARMetaData fileMetaData = new TARMetaData(metaDataBuffer); fileMetaData.MetaDataBufferPositionStart = bufferPosition; bufferPosition += readCount; fileMetaData.FileBufferPositionStart = bufferPosition; // If the entry has file data following it if (fileMetaData.GetTypeFlags() == TARMetaData.TypeFlags.NormalFile || fileMetaData.GetTypeFlags() == TARMetaData.TypeFlags.ExtendedHeaderWithMetaDataForNextFileInArchive) { // Seek past the data StreamSeek(stream, fileMetaData.fileSize); bufferPosition += fileMetaData.fileSize; // Make sure we seek past by a multiple of 512 if (fileMetaData.fileSize % 512 > 0) { long offset = 512 - (fileMetaData.fileSize % 512); StreamSeek(stream, offset); bufferPosition += offset; } } // Store the meta data Entries.Add(fileMetaData); } //Loop until we've run out of entries } while (readCount >= 512); }
// Remove a file from our archive private void RemoveFile(TARMetaData entryToRemove, Stream inputStream, Stream outputStream) { // Loop through every entry foreach (TARMetaData entry in Entries) { // Check to see if this is the entry we are removing // If not, set write to true, else set to fale bool write = (entry.fileName != entryToRemove.fileName); // Read the metadata from the input file var metadata = new byte[512]; inputStream.Read(metadata, 0, metadata.Length); //If this file is not being deleted, write out the metadata if (write) { outputStream.Write(metadata, 0, metadata.Length); } // Read the file data from the input file var file = new byte[entry.fileSize]; inputStream.Read(file, 0, file.Length); //If this file is not being deleted, write out the file if (write) { outputStream.Write(file, 0, file.Length); } // Pad after the file data until we reach a 512 byte boundary if needed if (file.Length % 512 > 0) { int paddingBytes = 512 - (file.Length % 512); byte[] bPaddingBytes = new byte[paddingBytes]; inputStream.Read(bPaddingBytes, 0, paddingBytes); // We only need to pad the output if the file is not being deleted if (write) { outputStream.Write(bPaddingBytes, 0, paddingBytes); } } } }