Mercurial > hg > ucis.core
view Util/TapeArchive.cs @ 54:ba4e2cb031e0
Added general purpose tar archive reader class
author | Ivo Smits <Ivo@UCIS.nl> |
---|---|
date | Wed, 02 Oct 2013 21:17:30 +0200 |
parents | |
children |
line wrap: on
line source
???using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Text; namespace UCIS.Util { public class TarchiveReader : IDisposable, IEnumerator<TarchiveEntry>, IEnumerable<TarchiveEntry> { public Stream Source { get; private set; } public TarchiveEntry CurrentEntry { get; private set; } private Int64 SourceOffsetBase = 0; private Int64 SourceOffset = 0; private Boolean CanSeek = false; public TarchiveReader(String tarFile) : this(File.OpenRead(tarFile)) { } public TarchiveReader(Type type, String resource) : this(type.Assembly.GetManifestResourceStream(type, resource)) { } public TarchiveReader(Stream tar) { this.Source = tar; CanSeek = Source.CanSeek; if (CanSeek) SourceOffset = SourceOffsetBase = Source.Position; } public void Dispose() { Source.Dispose(); } private static String ReadString(Byte[] header, int offset, int maxlength) { int end = Array.IndexOf<Byte>(header, 0, offset, maxlength); int length = (end == -1) ? maxlength : end - offset; return Encoding.UTF8.GetString(header, offset, length); } private void SeekForward(Int64 position) { if (CanSeek) { Source.Seek(position, SeekOrigin.Begin); SourceOffset = position; } else { if (position < SourceOffset) throw new ArgumentOutOfRangeException("Can not seek backwards"); Byte[] buffer = new Byte[1024]; while (position > SourceOffset) { int read = Source.Read(buffer, 0, (int)Math.Min(1024, position - SourceOffset)); if (read <= 0) throw new EndOfStreamException(); SourceOffset += read; } } } private Int32 GetPaddedSize(Int32 size) { int padding = size % 512; if (padding != 0) padding = 512 - padding; return size + padding; } private Byte[] ReadAll(int length) { Byte[] buffer = new Byte[length]; int offset = 0; while (length > 0) { int read = Source.Read(buffer, offset, length); if (read <= 0) throw new EndOfStreamException(); offset += read; length -= read; SourceOffset += read; } return buffer; } private Boolean IsAllZero(Byte[] header, int offset, int length) { length += offset; for (int i = offset; i < length; i++) if (header[i] != 0) return false; return true; } public TarchiveEntry ReadNext() { if (CurrentEntry != null) SeekForward(CurrentEntry.Offset + 512 + GetPaddedSize(CurrentEntry.Size)); CurrentEntry = null; Byte[] header = ReadAll(512); if (IsAllZero(header, 0, header.Length)) return null; Boolean ustar = (ReadString(header, 257, 6) == "ustar"); String fname = ReadString(header, 0, 100); String fsizes = ReadString(header, 124, 11); Int32 fsize = Convert.ToInt32(fsizes, 8); if (ustar) fname = ReadString(header, 345, 155) + fname; String ffname = fname.StartsWith("./") ? (fname.Length == 2 ? "/" : fname.Substring(2)) : fname; ffname = ffname.TrimEnd('/'); TarchiveEntry entry = new TarchiveEntry() { Reader = this, OriginalName = fname, Offset = SourceOffset - 512, Size = fsize, Name = ffname }; if (ustar) { entry.IsDirectory = header[156] == '5'; entry.IsDirectory = header[156] == '0' || header[156] == 0; } else { entry.IsDirectory = fname.EndsWith("/"); entry.IsFile = !entry.IsDirectory && header[156] == '0'; } return CurrentEntry = entry; } public int ReadEntryData(TarchiveEntry entry, int fileoffset, Byte[] buffer, int bufferoffset, int count) { if (entry.Reader != this) throw new ArgumentException("The specified entry is not part of this archive"); if (count < 0) throw new ArgumentOutOfRangeException("count", "Count is negative"); if (count == 0) return 0; if (!entry.IsFile) throw new ArgumentException("entry", "Specified entry is not a file"); if (fileoffset > entry.Size) throw new ArgumentOutOfRangeException("fileoffset", "File offset exceeds file size"); if (fileoffset == entry.Size) return 0; if (bufferoffset < 0) throw new ArgumentOutOfRangeException("bufferoffset", "Buffer offset is negative"); if (bufferoffset + count > buffer.Length) throw new ArgumentOutOfRangeException("count", "Buffer offset and count exceed buffer dimensions"); SeekForward(entry.Offset + 512 + fileoffset); int read = Source.Read(buffer, bufferoffset, Math.Min(count, entry.Size - fileoffset)); if (read > 0) SourceOffset += read; return read; } public void SeekToEntry(TarchiveEntry entry) { if (entry.Reader != this) throw new ArgumentException("The specified entry is not part of this archive"); SeekForward(entry.Offset); } public Stream GetFileStream(TarchiveEntry entry) { if (entry.Reader != this) throw new ArgumentException("The specified entry is not part of this archive"); return new ReaderStream(this, entry); } TarchiveEntry IEnumerator<TarchiveEntry>.Current { get { return CurrentEntry; } } object IEnumerator.Current { get { return CurrentEntry; } } bool IEnumerator.MoveNext() { return ReadNext() != null; } void IEnumerator.Reset() { SeekForward(SourceOffsetBase); } IEnumerator<TarchiveEntry> IEnumerable<TarchiveEntry>.GetEnumerator() { return this; } IEnumerator IEnumerable.GetEnumerator() { return this; } class ReaderStream : Stream { long position = 0; public TarchiveReader Source { get; private set; } public TarchiveEntry File { get; private set; } public ReaderStream(TarchiveReader source, TarchiveEntry entry) { this.Source = source; this.File = entry; } public override bool CanRead { get { return Source != null && Source.Source.CanRead && Position < Length; } } public override bool CanSeek { get { return Source != null && Source.Source.CanSeek; } } public override bool CanWrite { get { return false; } } protected override void Dispose(bool disposing) { base.Dispose(disposing); Source = null; File = null; } public override long Length { get { return File.Size; } } public override long Position { get { return position; } set { if (value < 0 || value > Length) throw new ArgumentOutOfRangeException("value"); position = value; } } public override long Seek(long offset, SeekOrigin origin) { switch (origin) { case SeekOrigin.Begin: Position = offset; break; case SeekOrigin.Current: Position += offset; break; case SeekOrigin.End: Position = Length - offset; break; } return position; } public override int Read(byte[] buffer, int offset, int count) { int read = Source.ReadEntryData(File, (int)position, buffer, offset, count); position += read; return read; } public override void Write(byte[] buffer, int offset, int count) { throw new NotSupportedException(); } public override void SetLength(long value) { throw new NotSupportedException(); } public override void Flush() { } } } public class TarchiveEntry { public TarchiveReader Reader { get; internal set; } public String Name { get; internal set; } public String OriginalName { get; internal set; } public Boolean IsDirectory { get; internal set; } public Boolean IsFile { get; internal set; } public Int32 Size { get; internal set; } public Int64 Offset { get; internal set; } public int Read(int fileoffset, Byte[] buffer, int bufferoffset, int count) { return Reader.ReadEntryData(this, fileoffset, buffer, bufferoffset, count); } public Stream GetStream() { return Reader.GetFileStream(this); } } }