using System;
using System.IO;
using System.Text;
using System.Threading;
using AMWD.Common.Packing.Tar.Interfaces;
namespace AMWD.Common.Packing.Tar.Utils
{
///
/// Implements a legacy TAR writer.
///
///
/// Copied from
///
public class LegacyTarWriter : IDisposable
{
private readonly Stream outStream;
protected byte[] buffer = new byte[1024];
private bool isClosed;
public bool ReadOnZero = true;
///
/// Writes tar (see GNU tar) archive to a stream
///
/// stream to write archive to
public LegacyTarWriter(Stream outStream)
{
this.outStream = outStream;
}
protected virtual Stream OutStream
{
get { return outStream; }
}
#region IDisposable Members
public void Dispose()
{
Close();
}
#endregion IDisposable Members
public void WriteDirectoryEntry(string path, int userId, int groupId, int mode)
{
if (string.IsNullOrEmpty(path))
throw new ArgumentNullException("path");
if (path[path.Length - 1] != '/')
{
path += '/';
}
DateTime lastWriteTime;
if (Directory.Exists(path))
{
lastWriteTime = Directory.GetLastWriteTime(path);
}
else
{
lastWriteTime = DateTime.Now;
}
// handle long path names (> 99 characters)
if (path.Length > 99)
{
WriteLongName(
name: path,
userId: userId,
groupId: groupId,
mode: mode,
lastModificationTime: lastWriteTime);
// shorten the path name so it can be written properly
path = path.Substring(0, 99);
}
WriteHeader(path, lastWriteTime, 0, userId, groupId, mode, EntryType.Directory);
}
public void WriteDirectory(string directory, bool doRecursive)
{
if (string.IsNullOrEmpty(directory))
throw new ArgumentNullException("directory");
WriteDirectoryEntry(directory, 0, 0, 0755);
string[] files = Directory.GetFiles(directory);
foreach (var fileName in files)
{
Write(fileName);
}
string[] directories = Directory.GetDirectories(directory);
foreach (var dirName in directories)
{
WriteDirectoryEntry(dirName, 0, 0, 0755);
if (doRecursive)
{
WriteDirectory(dirName, true);
}
}
}
public void Write(string fileName)
{
if (string.IsNullOrEmpty(fileName))
throw new ArgumentNullException("fileName");
using (FileStream file = File.OpenRead(fileName))
{
Write(file, file.Length, fileName, 61, 61, 511, File.GetLastWriteTime(file.Name));
}
}
public void Write(FileStream file)
{
string path = Path.GetFullPath(file.Name).Replace(Path.GetPathRoot(file.Name), string.Empty);
path = path.Replace(Path.DirectorySeparatorChar, '/');
Write(file, file.Length, path, 61, 61, 511, File.GetLastWriteTime(file.Name));
}
public void Write(Stream data, long dataSizeInBytes, string name)
{
Write(data, dataSizeInBytes, name, 61, 61, 511, DateTime.Now);
}
public virtual void Write(string name, long dataSizeInBytes, int userId, int groupId, int mode, DateTime lastModificationTime, WriteDataDelegate writeDelegate)
{
IArchiveDataWriter writer = new DataWriter(OutStream, dataSizeInBytes);
WriteHeader(name, lastModificationTime, dataSizeInBytes, userId, groupId, mode, EntryType.File);
while (writer.CanWrite)
{
writeDelegate(writer);
}
AlignTo512(dataSizeInBytes, false);
}
public virtual void Write(Stream data, long dataSizeInBytes, string name, int userId, int groupId, int mode,
DateTime lastModificationTime)
{
if (isClosed)
throw new TarException("Can not write to the closed writer");
// handle long file names (> 99 characters)
if (name.Length > 99)
{
WriteLongName(
name: name,
userId: userId,
groupId: groupId,
mode: mode,
lastModificationTime: lastModificationTime);
// shorten the file name so it can be written properly
name = name.Substring(0, 99);
}
WriteHeader(name, lastModificationTime, dataSizeInBytes, userId, groupId, mode, EntryType.File);
WriteContent(dataSizeInBytes, data);
AlignTo512(dataSizeInBytes, false);
}
///
/// Handle long file or path names (> 99 characters).
/// Write header and content, which its content contain the long (complete) file/path name.
/// This handling method is adapted from https://github.com/qmfrederik/dotnet-packaging/pull/50/files#diff-f64c58cc18e8e445cee6ffed7a0d765cdb442c0ef21a3ed80bd20514057967b1
///
/// File name or path name.
/// User ID.
/// Group ID.
/// Mode.
/// Last modification time.
private void WriteLongName(string name, int userId, int groupId, int mode, DateTime lastModificationTime)
{
// must include a trailing \0
var nameLength = Encoding.UTF8.GetByteCount(name);
byte[] entryName = new byte[nameLength + 1];
Encoding.UTF8.GetBytes(name, 0, name.Length, entryName, 0);
// add a "././@LongLink" pseudo-entry which contains the full name
using (var nameStream = new MemoryStream(entryName))
{
WriteHeader("././@LongLink", lastModificationTime, entryName.Length, userId, groupId, mode, EntryType.LongName);
WriteContent(entryName.Length, nameStream);
AlignTo512(entryName.Length, false);
}
}
protected void WriteContent(long count, Stream data)
{
while (count > 0 && count > buffer.Length)
{
int bytesRead = data.Read(buffer, 0, buffer.Length);
if (bytesRead < 0)
throw new IOException("LegacyTarWriter unable to read from provided stream");
if (bytesRead == 0)
{
if (ReadOnZero)
Thread.Sleep(100);
else
break;
}
OutStream.Write(buffer, 0, bytesRead);
count -= bytesRead;
}
if (count > 0)
{
int bytesRead = data.Read(buffer, 0, (int)count);
if (bytesRead < 0)
throw new IOException("LegacyTarWriter unable to read from provided stream");
if (bytesRead == 0)
{
while (count > 0)
{
OutStream.WriteByte(0);
--count;
}
}
else
OutStream.Write(buffer, 0, bytesRead);
}
}
protected virtual void WriteHeader(string name, DateTime lastModificationTime, long count, int userId, int groupId, int mode, EntryType entryType)
{
var header = new TarHeader
{
FileName = name,
LastModification = lastModificationTime,
SizeInBytes = count,
UserId = userId,
GroupId = groupId,
Mode = mode,
EntryType = entryType
};
OutStream.Write(header.GetHeaderValue(), 0, header.HeaderSize);
}
public void AlignTo512(long size, bool acceptZero)
{
size = size % 512;
if (size == 0 && !acceptZero) return;
while (size < 512)
{
OutStream.WriteByte(0);
size++;
}
}
public virtual void Close()
{
if (isClosed) return;
AlignTo512(0, true);
AlignTo512(0, true);
isClosed = true;
}
}
}