Mercurial > hg > ucis.core
comparison 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 |
comparison
equal
deleted
inserted
replaced
53:df5534af12e9 | 54:ba4e2cb031e0 |
---|---|
1 using System; | |
2 using System.Collections; | |
3 using System.Collections.Generic; | |
4 using System.IO; | |
5 using System.Text; | |
6 | |
7 namespace UCIS.Util { | |
8 public class TarchiveReader : IDisposable, IEnumerator<TarchiveEntry>, IEnumerable<TarchiveEntry> { | |
9 public Stream Source { get; private set; } | |
10 public TarchiveEntry CurrentEntry { get; private set; } | |
11 private Int64 SourceOffsetBase = 0; | |
12 private Int64 SourceOffset = 0; | |
13 private Boolean CanSeek = false; | |
14 public TarchiveReader(String tarFile) : this(File.OpenRead(tarFile)) { } | |
15 public TarchiveReader(Type type, String resource) : this(type.Assembly.GetManifestResourceStream(type, resource)) { } | |
16 public TarchiveReader(Stream tar) { | |
17 this.Source = tar; | |
18 CanSeek = Source.CanSeek; | |
19 if (CanSeek) SourceOffset = SourceOffsetBase = Source.Position; | |
20 } | |
21 public void Dispose() { | |
22 Source.Dispose(); | |
23 } | |
24 private static String ReadString(Byte[] header, int offset, int maxlength) { | |
25 int end = Array.IndexOf<Byte>(header, 0, offset, maxlength); | |
26 int length = (end == -1) ? maxlength : end - offset; | |
27 return Encoding.UTF8.GetString(header, offset, length); | |
28 } | |
29 private void SeekForward(Int64 position) { | |
30 if (CanSeek) { | |
31 Source.Seek(position, SeekOrigin.Begin); | |
32 SourceOffset = position; | |
33 } else { | |
34 if (position < SourceOffset) throw new ArgumentOutOfRangeException("Can not seek backwards"); | |
35 Byte[] buffer = new Byte[1024]; | |
36 while (position > SourceOffset) { | |
37 int read = Source.Read(buffer, 0, (int)Math.Min(1024, position - SourceOffset)); | |
38 if (read <= 0) throw new EndOfStreamException(); | |
39 SourceOffset += read; | |
40 } | |
41 } | |
42 } | |
43 private Int32 GetPaddedSize(Int32 size) { | |
44 int padding = size % 512; | |
45 if (padding != 0) padding = 512 - padding; | |
46 return size + padding; | |
47 } | |
48 private Byte[] ReadAll(int length) { | |
49 Byte[] buffer = new Byte[length]; | |
50 int offset = 0; | |
51 while (length > 0) { | |
52 int read = Source.Read(buffer, offset, length); | |
53 if (read <= 0) throw new EndOfStreamException(); | |
54 offset += read; | |
55 length -= read; | |
56 SourceOffset += read; | |
57 } | |
58 return buffer; | |
59 } | |
60 private Boolean IsAllZero(Byte[] header, int offset, int length) { | |
61 length += offset; | |
62 for (int i = offset; i < length; i++) if (header[i] != 0) return false; | |
63 return true; | |
64 } | |
65 public TarchiveEntry ReadNext() { | |
66 if (CurrentEntry != null) SeekForward(CurrentEntry.Offset + 512 + GetPaddedSize(CurrentEntry.Size)); | |
67 CurrentEntry = null; | |
68 Byte[] header = ReadAll(512); | |
69 if (IsAllZero(header, 0, header.Length)) return null; | |
70 Boolean ustar = (ReadString(header, 257, 6) == "ustar"); | |
71 String fname = ReadString(header, 0, 100); | |
72 String fsizes = ReadString(header, 124, 11); | |
73 Int32 fsize = Convert.ToInt32(fsizes, 8); | |
74 if (ustar) fname = ReadString(header, 345, 155) + fname; | |
75 String ffname = fname.StartsWith("./") ? (fname.Length == 2 ? "/" : fname.Substring(2)) : fname; | |
76 ffname = ffname.TrimEnd('/'); | |
77 TarchiveEntry entry = new TarchiveEntry() { Reader = this, OriginalName = fname, Offset = SourceOffset - 512, Size = fsize, Name = ffname }; | |
78 if (ustar) { | |
79 entry.IsDirectory = header[156] == '5'; | |
80 entry.IsDirectory = header[156] == '0' || header[156] == 0; | |
81 } else { | |
82 entry.IsDirectory = fname.EndsWith("/"); | |
83 entry.IsFile = !entry.IsDirectory && header[156] == '0'; | |
84 } | |
85 return CurrentEntry = entry; | |
86 } | |
87 public int ReadEntryData(TarchiveEntry entry, int fileoffset, Byte[] buffer, int bufferoffset, int count) { | |
88 if (entry.Reader != this) throw new ArgumentException("The specified entry is not part of this archive"); | |
89 if (count < 0) throw new ArgumentOutOfRangeException("count", "Count is negative"); | |
90 if (count == 0) return 0; | |
91 if (!entry.IsFile) throw new ArgumentException("entry", "Specified entry is not a file"); | |
92 if (fileoffset > entry.Size) throw new ArgumentOutOfRangeException("fileoffset", "File offset exceeds file size"); | |
93 if (fileoffset == entry.Size) return 0; | |
94 if (bufferoffset < 0) throw new ArgumentOutOfRangeException("bufferoffset", "Buffer offset is negative"); | |
95 if (bufferoffset + count > buffer.Length) throw new ArgumentOutOfRangeException("count", "Buffer offset and count exceed buffer dimensions"); | |
96 SeekForward(entry.Offset + 512 + fileoffset); | |
97 int read = Source.Read(buffer, bufferoffset, Math.Min(count, entry.Size - fileoffset)); | |
98 if (read > 0) SourceOffset += read; | |
99 return read; | |
100 } | |
101 public void SeekToEntry(TarchiveEntry entry) { | |
102 if (entry.Reader != this) throw new ArgumentException("The specified entry is not part of this archive"); | |
103 SeekForward(entry.Offset); | |
104 } | |
105 public Stream GetFileStream(TarchiveEntry entry) { | |
106 if (entry.Reader != this) throw new ArgumentException("The specified entry is not part of this archive"); | |
107 return new ReaderStream(this, entry); | |
108 } | |
109 TarchiveEntry IEnumerator<TarchiveEntry>.Current { | |
110 get { return CurrentEntry; } | |
111 } | |
112 object IEnumerator.Current { | |
113 get { return CurrentEntry; } | |
114 } | |
115 bool IEnumerator.MoveNext() { | |
116 return ReadNext() != null; | |
117 } | |
118 void IEnumerator.Reset() { | |
119 SeekForward(SourceOffsetBase); | |
120 } | |
121 IEnumerator<TarchiveEntry> IEnumerable<TarchiveEntry>.GetEnumerator() { | |
122 return this; | |
123 } | |
124 IEnumerator IEnumerable.GetEnumerator() { | |
125 return this; | |
126 } | |
127 | |
128 class ReaderStream : Stream { | |
129 long position = 0; | |
130 public TarchiveReader Source { get; private set; } | |
131 public TarchiveEntry File { get; private set; } | |
132 public ReaderStream(TarchiveReader source, TarchiveEntry entry) { | |
133 this.Source = source; | |
134 this.File = entry; | |
135 } | |
136 public override bool CanRead { get { return Source != null && Source.Source.CanRead && Position < Length; } } | |
137 public override bool CanSeek { get { return Source != null && Source.Source.CanSeek; } } | |
138 public override bool CanWrite { get { return false; } } | |
139 protected override void Dispose(bool disposing) { | |
140 base.Dispose(disposing); | |
141 Source = null; | |
142 File = null; | |
143 } | |
144 public override long Length { get { return File.Size; } } | |
145 public override long Position { | |
146 get { return position; } | |
147 set { | |
148 if (value < 0 || value > Length) throw new ArgumentOutOfRangeException("value"); | |
149 position = value; | |
150 } | |
151 } | |
152 public override long Seek(long offset, SeekOrigin origin) { | |
153 switch (origin) { | |
154 case SeekOrigin.Begin: Position = offset; break; | |
155 case SeekOrigin.Current: Position += offset; break; | |
156 case SeekOrigin.End: Position = Length - offset; break; | |
157 } | |
158 return position; | |
159 } | |
160 public override int Read(byte[] buffer, int offset, int count) { | |
161 int read = Source.ReadEntryData(File, (int)position, buffer, offset, count); | |
162 position += read; | |
163 return read; | |
164 } | |
165 public override void Write(byte[] buffer, int offset, int count) { throw new NotSupportedException(); } | |
166 public override void SetLength(long value) { throw new NotSupportedException(); } | |
167 public override void Flush() { } | |
168 } | |
169 } | |
170 public class TarchiveEntry { | |
171 public TarchiveReader Reader { get; internal set; } | |
172 public String Name { get; internal set; } | |
173 public String OriginalName { get; internal set; } | |
174 public Boolean IsDirectory { get; internal set; } | |
175 public Boolean IsFile { get; internal set; } | |
176 public Int32 Size { get; internal set; } | |
177 public Int64 Offset { get; internal set; } | |
178 | |
179 public int Read(int fileoffset, Byte[] buffer, int bufferoffset, int count) { | |
180 return Reader.ReadEntryData(this, fileoffset, buffer, bufferoffset, count); | |
181 } | |
182 | |
183 public Stream GetStream() { | |
184 return Reader.GetFileStream(this); | |
185 } | |
186 } | |
187 } |