archive-tar2 1.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,80 @@
1
+ =begin license
2
+ Copyright (c) 2010 Fritz Grimpen
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ of this software and associated documentation files (the "Software"), to deal
6
+ in the Software without restriction, including without limitation the rights
7
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is
9
+ furnished to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in
12
+ all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ THE SOFTWARE.
21
+ =end
22
+
23
+ module Archive
24
+ module Tar
25
+ VERSION = "1.5.0"
26
+
27
+ def normalize_path(path)
28
+ path = path.gsub("\\", "/")
29
+
30
+ while path[-1] == "/"
31
+ path = path[0..-2]
32
+ end
33
+
34
+ while path[0] == "/"
35
+ path = path[1..-1]
36
+ end
37
+
38
+ solve_path(path.gsub(/[\/]{2,}/, "/"))
39
+ end
40
+
41
+ def solve_path(path)
42
+ path_parts = path.split("/")
43
+ realpath = []
44
+
45
+ path_parts.each do |i|
46
+ if i == "."
47
+ next
48
+ end
49
+
50
+ if i == ".."
51
+ realpath = realpath[1..-2]
52
+ realpath = [] if realpath == nil
53
+ next
54
+ end
55
+
56
+ realpath << i
57
+ end
58
+
59
+ realpath.join("/")
60
+ end
61
+
62
+ def join_path(*files)
63
+ absolute = files[0][0] == "/"
64
+ files = files.map do |element|
65
+ normalize_path element
66
+ end
67
+
68
+ new_path = files.join("/")
69
+ new_path = "/" + new_path if absolute
70
+
71
+ new_path
72
+ end
73
+ end
74
+ end
75
+
76
+ require "archive/tar/format.rb"
77
+ require "archive/tar/reader.rb"
78
+ require "archive/tar/writer.rb"
79
+ require "archive/tar/stat.rb"
80
+ require "archive/tar/stream_reader.rb"
@@ -0,0 +1,147 @@
1
+ =begin license
2
+ Copyright (c) 2010 Fritz Grimpen
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ of this software and associated documentation files (the "Software"), to deal
6
+ in the Software without restriction, including without limitation the rights
7
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is
9
+ furnished to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in
12
+ all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ THE SOFTWARE.
21
+ =end
22
+
23
+ require "archive/tar/stat"
24
+
25
+ class Archive::Tar::Format
26
+ DEC_TYPES = {
27
+ "\0" => :normal,
28
+ "0" => :normal,
29
+ "1" => :link,
30
+ "2" => :symbolic,
31
+ "3" => :character,
32
+ "4" => :block,
33
+ "5" => :directory,
34
+ "6" => :fifo,
35
+ "7" => :reserved,
36
+ "I" => :index,
37
+ "g" => :pax_global_header
38
+ }
39
+
40
+ ENC_TYPES = DEC_TYPES.invert
41
+
42
+ class << self
43
+ # Remove all NUL bytes at the end of a string
44
+ def strip_nuls(string)
45
+ until string[-1] != "\0"
46
+ string = string[0..-2]
47
+ end
48
+
49
+ string
50
+ end
51
+
52
+ # Transform tar header to Stat
53
+ def unpack_header(header)
54
+ new_obj = Archive::Tar::Stat.new
55
+
56
+ new_obj.path = strip_nuls(header[0, 100])
57
+ new_obj.mode = header[100, 8].oct
58
+ new_obj.uid = header[108, 8].oct
59
+ new_obj.gid = header[116, 8].oct
60
+ new_obj.size = header[124, 12].oct
61
+ new_obj.mtime = Time.at(header[136, 12].oct)
62
+ new_obj.checksum = header[148, 8].oct
63
+ new_obj.type = DEC_TYPES[header[156]]
64
+ new_obj.dest = strip_nuls(header[157, 100])
65
+ new_obj.format = header[257, 5] == "ustar" ?
66
+ ( header[257, 6] == "ustar " ? :gnu : :ustar ) : :other
67
+ new_obj.user = strip_nuls(header[265, 32])
68
+ new_obj.group = strip_nuls(header[297, 32])
69
+ new_obj.major = header[329, 8].oct
70
+ new_obj.minor = header[337, 8].oct
71
+
72
+ new_obj.path = header[345, 155].strip + new_obj.path if new_obj.ustar?
73
+
74
+ if new_obj.gnu?
75
+ new_obj.atime = Time.at(header[345, 12].oct)
76
+ new_obj.ctime = Time.at(header[357, 12].oct)
77
+ end
78
+
79
+ new_obj
80
+ end
81
+
82
+ # Detect type of tar file by header
83
+ def detect_type(header)
84
+ return :ustar if header[257, 6] == "ustar0"
85
+ return :gnu if header[257, 6] == "ustar "
86
+
87
+ :other
88
+ end
89
+
90
+ # Generate checksum with header
91
+ def calculate_checksum(header)
92
+ checksum = 0
93
+
94
+ header.each_byte do |byte|
95
+ checksum += byte
96
+ end
97
+
98
+ checksum.to_s(8).rjust(6, " ") + "\0 "
99
+ end
100
+
101
+ # Pack header from Stat
102
+ def pack_header(header)
103
+ blob = ""
104
+
105
+ blob += header.path.ljust(100, "\0")
106
+ blob += header.mode.to_s(8).rjust(8, "0")
107
+ blob += header.uid.to_s(8).rjust(8, "0")
108
+ blob += header.gid.to_s(8).rjust(8, "0")
109
+ blob += header.size.to_s(8).rjust(12, "0")
110
+ blob += header.mtime.to_i.to_s(8).rjust(12, "0")
111
+ blob += " " * 8
112
+ blob += ENC_TYPES[header.type]
113
+ blob += header.dest.ljust(100, "\0")
114
+
115
+ case header.format
116
+ when :ustar
117
+ blob += "ustar\000"
118
+ when :gnu
119
+ blob += "ustar \0"
120
+ end
121
+
122
+ if header.gnu? || header.ustar?
123
+ blob += header.user.ljust(32, "\0")
124
+ blob += header.group.ljust(32, "\0")
125
+ blob += header.major.to_s(8).rjust(8, "0")
126
+ blob += header.minor.to_s(8).rjust(8, "0")
127
+
128
+ if header.gnu?
129
+ blob += header.atime.to_i.to_s(8).rjust(12, "0")
130
+ blob += header.ctime.to_i.to_s(8).rjust(12, "0")
131
+ end
132
+ end
133
+
134
+ pad_length = 512 - blob.bytesize
135
+ blob += "\0" * pad_length
136
+
137
+ blob[148, 8] = calculate_checksum(blob)
138
+
139
+ blob
140
+ end
141
+
142
+ # Calculate quantity of blocks
143
+ def blocks_for_bytes(bytes)
144
+ bytes % 512 == 0 ? bytes / 512 : (bytes + 512 - bytes % 512) / 512
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,327 @@
1
+ =begin license
2
+ Copyright (c) 2010 Fritz Grimpen
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ of this software and associated documentation files (the "Software"), to deal
6
+ in the Software without restriction, including without limitation the rights
7
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is
9
+ furnished to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in
12
+ all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ THE SOFTWARE.
21
+ =end
22
+
23
+ require "fileutils"
24
+ require "archive/tar/format"
25
+
26
+ class Archive::Tar::NoSuchEntryError < RuntimeError
27
+ end
28
+
29
+ class Archive::Tar::Reader
30
+ include Archive::Tar
31
+
32
+ def initialize(stream, options = {})
33
+ options = {
34
+ compression: :auto,
35
+ tmpdir: "/tmp",
36
+ block_size: 2 ** 19,
37
+ read_limit: 2 ** 19,
38
+ cache: true,
39
+ cache_size: 16,
40
+ max_cache_size: 2 ** 19,
41
+ generate_index: true,
42
+ use_normalized_paths: true,
43
+ }.merge(options)
44
+
45
+ if stream.is_a? String
46
+ stream = File.new(stream)
47
+ end
48
+
49
+ @options = options
50
+ @stream = _generate_compressed_stream(stream, options[:compression])
51
+ @cache_candidates = []
52
+ @cache = {}
53
+
54
+ build_index if options[:generate_index]
55
+ end
56
+
57
+ def stream
58
+ @stream
59
+ end
60
+
61
+ def index
62
+ @index
63
+ end
64
+
65
+ def stat(file, exception = true)
66
+ if @options[:use_normalized_paths]
67
+ result = @index[normalize_path(file)]
68
+ else
69
+ result = @index[file]
70
+ end
71
+ raise NoSuchEntryError.new(file) if result == nil && exception
72
+
73
+ result
74
+ end
75
+
76
+ def [](file)
77
+ stat(file, false)
78
+ end
79
+
80
+ def has_entry?(file)
81
+ @index.key? file
82
+ end
83
+
84
+ def entry?(file)
85
+ has_entry? file
86
+ end
87
+
88
+ def each(&block)
89
+ @index.each do |key, value|
90
+ block.call(*(value))
91
+ end
92
+ end
93
+
94
+ def read(name, no_cache = false)
95
+ header, offset = stat(name)
96
+
97
+ if @options[:cache] && header[:size] <= @options[:max_cache_size] && !no_cache
98
+ @cache_candidates << name
99
+ rebuild_cache
100
+ end
101
+
102
+ if @cache.key? name && !no_cache
103
+ return @cache[name]
104
+ end
105
+
106
+ @stream.seek(offset)
107
+ @stream.read(header[:size])
108
+ end
109
+
110
+ # Extract all files to /dest/ directory
111
+ def extract_all(dest, options = {})
112
+ options = {
113
+ :preserve => false,
114
+ :override => false
115
+ }.merge(options)
116
+
117
+ unless File::exists? dest
118
+ FileUtils::mkdir_p dest
119
+ end
120
+
121
+ unless File.directory? dest
122
+ raise "No such directory: #{dest}"
123
+ end
124
+
125
+ @index.each_key do |entry|
126
+ ndest = File.join(dest, entry)
127
+ header, offset = @index[entry]
128
+
129
+ _extract(header, offset, ndest, options)
130
+ end
131
+ end
132
+
133
+ # Extract file from /source/ to /dest/
134
+ def extract(source, dest, options = {})
135
+ options = {
136
+ recursive: true,
137
+ preserve: false,
138
+ override: false
139
+ }.merge(options)
140
+ unless @index.key? source
141
+ raise NoSuchEntryError.new(source)
142
+ end
143
+
144
+ header, offset = @index[source]
145
+ _extract(header, offset, dest, options)
146
+
147
+ if header[:type] == :directory && options[:recursive]
148
+ @index.each_key do |entry|
149
+ if entry[0, source.length] == source && entry != source
150
+ extract(entry, File.join(dest, entry.sub(source, "")), options)
151
+ end
152
+ end
153
+ end
154
+ end
155
+
156
+ # Build new index of files
157
+ def build_index
158
+ new_index = {}
159
+
160
+ @stream.rewind
161
+
162
+ until @stream.eof?
163
+ raw_header = @stream.read(512)
164
+ break if raw_header == "\0" * 512
165
+
166
+ header = Archive::Tar::Format::unpack_header(raw_header)
167
+ if @options[:use_normalized_paths]
168
+ header[:path] = normalize_path(header[:path])
169
+ end
170
+
171
+ unless header[:type] == :pax_global_header
172
+ new_index[header[:path]] = [ header, @stream.tell ]
173
+ end
174
+
175
+ @stream.seek(header[:blocks] * 512, IO::SEEK_CUR)
176
+ end
177
+
178
+ @index = new_index
179
+ end
180
+
181
+ protected
182
+ def rebuild_cache
183
+ return nil unless @options[:cache]
184
+
185
+ cache_count = {}
186
+ @cache_candidates.each do |candidate|
187
+ cache_count[candidate] = 0 unless cache_count.key? candidate
188
+ cache_count[candidate] += 1
189
+ end
190
+ cache_count_sorted = cache_count.sort do |pair_1, pair_2|
191
+ (pair_1[1] <=> pair_2[1]) * -1
192
+ end
193
+
194
+ puts cache_count_sorted
195
+
196
+ @cache = {}
197
+ i = 0
198
+ cache_count_sorted.each do |tupel|
199
+ if i >= @options[:cache_size]
200
+ break
201
+ end
202
+
203
+ @cache[tupel[0]] = read(tupel[0], true)
204
+ i += 1
205
+ end
206
+ end
207
+
208
+ def export_to_file(offset, length, source, destination)
209
+ destination = File.new(destination, "w+b") if destination.is_a?(String)
210
+
211
+ if @options[:max_cache_size] >= length && @options[:cache]
212
+ @cache_candidates << source
213
+ rebuild_cache
214
+ end
215
+
216
+ if @cache.key? source
217
+ destination.write(@cache[source])
218
+ return true
219
+ end
220
+
221
+ @stream.seek(offset)
222
+
223
+ if length <= @options[:read_limit]
224
+ destination.write(@stream.read(length))
225
+ return true
226
+ end
227
+
228
+ i = 0
229
+ while i < length
230
+ destination.write(@stream.read(@options[:block_size]))
231
+ i += @options[:block_size]
232
+ end
233
+
234
+ true
235
+ end
236
+
237
+ def _extract(header, offset, dest, options)
238
+ if !options[:override] && File::exists?(dest)
239
+ return
240
+ end
241
+
242
+ case header[:type]
243
+ when :normal
244
+ export_to_file(offset, header[:size], header[:path], dest)
245
+ when :directory
246
+ if !File.exists? dest
247
+ Dir.mkdir(dest)
248
+ end
249
+ when :symbolic
250
+ File.symlink(header[:dest], dest)
251
+ when :link
252
+ if header[:dest][0] == "/"
253
+ FileUtils.touch(header[:dest])
254
+ else
255
+ FileUtils.touch(realpath("#{dest}/../#{header[:dest]}"))
256
+ end
257
+
258
+ File.link(header[:dest], dest)
259
+ when :block
260
+ system("mknod '#{dest}' b #{header[:major]} #{header[:minor]}")
261
+ when :character
262
+ system("mknod '#{dest}' c #{header[:major]} #{header[:minor]}")
263
+ when :fifo
264
+ system("mknod '#{dest}' p #{header[:major]} #{header[:minor]}")
265
+ end
266
+
267
+ if options[:preserve]
268
+ File.chmod(header[:mode], dest)
269
+ File.chown(header[:uid], header[:gid], dest)
270
+ end
271
+ end
272
+
273
+ private
274
+ def _detect_compression(filename)
275
+ return :none unless filename.include? "."
276
+
277
+ case filename.slice(Range.new(filename.rindex(".") + 1, -1))
278
+ when "gz", "tgz"
279
+ return :gzip
280
+ when "bz2", "tbz", "tb2"
281
+ return :bzip2
282
+ when "xz", "txz"
283
+ return :xz
284
+ when "lz", "lzma", "tlz"
285
+ return :lzma
286
+ end
287
+
288
+ return :none
289
+ end
290
+
291
+ def _generate_compressed_stream(stream, compression)
292
+ if compression == :auto
293
+ if stream.is_a? File
294
+ compression = _detect_compression(stream.path)
295
+ else
296
+ compression = :none
297
+ end
298
+ end
299
+
300
+ programs = {
301
+ gzip: "gzip -d -c -f",
302
+ bzip2: "bzip2 -d -c -f",
303
+ lzma: "lzma -d -c -f",
304
+ xz: "xz -d -c -f"
305
+ }
306
+
307
+ unless programs.key? compression
308
+ return stream
309
+ end
310
+
311
+ io = IO::popen("/usr/bin/env #{programs[compression]}", "a+b")
312
+ new_file = File.open("#{@options[:tmpdir]}/#{rand(500512)}", "w+b")
313
+ Thread.new do
314
+ until stream.eof?
315
+ io.write(stream.read(@options[:block_size]))
316
+ end
317
+
318
+ io.close_write
319
+ end
320
+
321
+ until io.eof?
322
+ new_file.write(io.read(@options[:block_size]))
323
+ end
324
+
325
+ new_file
326
+ end
327
+ end
@@ -0,0 +1,130 @@
1
+ =begin license
2
+ Copyright (c) 2010 Fritz Grimpen
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ of this software and associated documentation files (the "Software"), to deal
6
+ in the Software without restriction, including without limitation the rights
7
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is
9
+ furnished to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in
12
+ all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ THE SOFTWARE.
21
+ =end
22
+
23
+ class Archive::Tar::Stat
24
+ attr_accessor :checksum, :path, :mode, :uid, :gid, :size, :mtime, :type,
25
+ :dest, :format, :user, :group, :atime, :ctime, :major, :minor
26
+ attr_accessor :path
27
+ attr_accessor :mode
28
+
29
+ def initialize
30
+ @path = ""
31
+ @mode = 0777
32
+ @uid = 0
33
+ @gid = 0
34
+ @size = 0
35
+ @mtime = Time.at(0)
36
+ @type = :normal
37
+ @dest = ""
38
+ @format = :ustar
39
+ @user = ""
40
+ @group = ""
41
+ @atime = Time.at(0)
42
+ @ctime = Time.at(0)
43
+ @major = 0
44
+ @minor = 0
45
+ @checksum = 0
46
+ end
47
+
48
+ def self.from_file(file)
49
+ file = File.new(file.to_s) unless file.is_a? File
50
+ stat = Archive::Tar::Stat.new
51
+
52
+ file_stat = file.stat
53
+ path = file.path
54
+
55
+ stat.path = path
56
+ stat.mode = file_stat.mode
57
+ stat.uid = file_stat.uid
58
+ stat.gid = file_stat.gid
59
+ stat.size = file_stat.size
60
+ stat.mtime = file_stat.mtime
61
+
62
+ if file_stat.blockdev?
63
+ stat.type = :block
64
+ stat.size = 0
65
+ elsif file_stat.chardev?
66
+ stat.type = :character
67
+ stat.size = 0
68
+ elsif file_stat.directory?
69
+ stat.type = :directory
70
+ stat.size = 0
71
+ elsif file_stat.pipe?
72
+ stat.type = :fifo
73
+ stat.size = 0
74
+ elsif file_stat.symlink?
75
+ stat.type = :symbolic
76
+ stat.size = 0
77
+ else
78
+ stat.type = :normal
79
+ end
80
+
81
+ stat.dest = File.readlink(path) if stat.type == :symbolic
82
+ stat.format = :ustar
83
+ stat.atime = file_stat.atime
84
+ stat.ctime = file_stat.ctime
85
+
86
+ stat.major = file_stat.rdev_major
87
+ stat.minor = file_stat.rdev_minor
88
+
89
+ stat
90
+ end
91
+
92
+ def blocks
93
+ size % 512 == 0 ? size / 512 : (size + 512 - size % 512) / 512
94
+ end
95
+
96
+ def is_ustar?
97
+ format == :ustar
98
+ end
99
+
100
+ def ustar?
101
+ is_ustar?
102
+ end
103
+
104
+ def is_gnu?
105
+ format == :gnu
106
+ end
107
+
108
+ def gnu?
109
+ is_gnu?
110
+ end
111
+
112
+ def version
113
+ "00"
114
+ end
115
+
116
+ def [](name)
117
+ self.method(name.to_sym).call
118
+ end
119
+
120
+ def []=(name, value)
121
+ self.method((name.to_s + "=").to_sym).call(value)
122
+ end
123
+
124
+ def each(&block)
125
+ [ :path, :mode, :uid, :gid, :size, :mtime, :type, :dest, :format, :user,
126
+ :group, :major, :minor, :atime, :ctime, :checksum ].each do |elem|
127
+ block.call(elem, self[elem])
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,54 @@
1
+ =begin license
2
+ Copyright (c) 2010 Fritz Grimpen
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ of this software and associated documentation files (the "Software"), to deal
6
+ in the Software without restriction, including without limitation the rights
7
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is
9
+ furnished to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in
12
+ all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ THE SOFTWARE.
21
+ =end
22
+
23
+ require "archive/tar/reader"
24
+
25
+ class Archive::Tar::StreamReader < Archive::Tar::Reader
26
+ def initialize(stream, options = {})
27
+ options = {
28
+ block_size: 2 ** 19,
29
+ reload_time: 32
30
+ }.merge(options)
31
+
32
+ stream = IO.new(stream) if io.is_a? Integer
33
+
34
+ if options[:compression] == :auto
35
+ raise "Automatic compression is not available for streams"
36
+ end
37
+
38
+ tmp_file = File.new("/tmp/" + rand(500000).to_s(16) + ".tar", "w+b")
39
+ Thread.new do
40
+ i = 0
41
+
42
+ until stream.eof?
43
+ read = stream.read(options[:block_size])
44
+ tmp_file.write(read)
45
+ self.build_index if i % options[:reload_time] == 0
46
+ i += 1
47
+ end
48
+
49
+ self.build_index
50
+ end
51
+
52
+ super(tmp_file, options)
53
+ end
54
+ end
@@ -0,0 +1,117 @@
1
+ =begin license
2
+ Copyright (c) 2010 Fritz Grimpen
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy
5
+ of this software and associated documentation files (the "Software"), to deal
6
+ in the Software without restriction, including without limitation the rights
7
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is
9
+ furnished to do so, subject to the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in
12
+ all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
+ THE SOFTWARE.
21
+ =end
22
+
23
+ require "archive/tar/stat.rb"
24
+ require "archive/tar/format.rb"
25
+
26
+ class Archive::Tar::WriterError < RuntimeError
27
+ end
28
+
29
+ class Archive::Tar::Writer
30
+ include Archive::Tar
31
+
32
+ def initialize(stream, options = {})
33
+ options = {
34
+ block_size: 2 ** 19,
35
+ format: :gnu
36
+ }.merge(options)
37
+
38
+ @options = options
39
+ @stream = stream
40
+ @inodes = {}
41
+ end
42
+
43
+ def add_entry(header, content)
44
+ @atream.write(Archive::Tar::Format::pack_header(header))
45
+
46
+ content = content[0, header.size].ljust(header.blocks * 512, "\0")
47
+ @stream.write(content)
48
+ end
49
+
50
+ def add_file(file, path = nil)
51
+ file = File.is_a?(File) ? file : File.new(file)
52
+ path = path == nil ? file.path : path
53
+
54
+ ino = File::Stat.new(file).ino
55
+ stat = Archive::Tar::Stat::from_file(file)
56
+ stat.path = path
57
+ stat.format = @options[:format]
58
+ if @inodes.has_key? ino && path != @inodes[ino]
59
+ stat.type = :link
60
+ stat.size = 0
61
+ stat.dest = @inodes[ino]
62
+ else
63
+ @inodes[ino] = path
64
+ end
65
+
66
+ header = Archive::Tar::Format::pack_header(stat)
67
+ @stream.write(header)
68
+
69
+ if stat.type == :normal
70
+ num_of_nils = stat.blocks * 512 - stat.size
71
+ until file.eof?
72
+ @stream.write(file.read(@options[:block_size]))
73
+ end
74
+
75
+ @stream.write("\0" * num_of_nils)
76
+ end
77
+ end
78
+
79
+ def add_directory(dir, options = {})
80
+ options = {
81
+ full_path: false,
82
+ recursive: true,
83
+ archive_base: ""
84
+ }.merge(options)
85
+
86
+ real_base = dir
87
+ archive_base = options[:archive_base]
88
+
89
+ unless dir.is_a? Dir
90
+ dir = Dir.open(dir)
91
+ end
92
+
93
+ #add_file(dir.path, archive_base)
94
+
95
+ dir.each do |entry|
96
+ if entry == "." || entry == ".."
97
+ next
98
+ end
99
+
100
+ realpath = File.join(real_base, entry)
101
+ real_archive_path = join_path(archive_base, entry)
102
+
103
+ add_file(realpath, real_archive_path)
104
+
105
+ if File::Stat.new(realpath).directory? && options[:recursive]
106
+ new_options = options
107
+ new_options[:archive_base] = real_archive_path
108
+
109
+ add_directory(realpath, new_options)
110
+ end
111
+ end
112
+ end
113
+
114
+ def close()
115
+ @stream.write("\0" * 1024)
116
+ end
117
+ end
metadata ADDED
@@ -0,0 +1,51 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: archive-tar2
3
+ version: !ruby/object:Gem::Version
4
+ version: '1.5'
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Fritz Grimpen
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-01-02 00:00:00.000000000 Z
13
+ dependencies: []
14
+ description:
15
+ email: fritz+archive-tar@grimpen.net
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - lib/archive/tar/format.rb
21
+ - lib/archive/tar/reader.rb
22
+ - lib/archive/tar/stream_reader.rb
23
+ - lib/archive/tar/writer.rb
24
+ - lib/archive/tar/stat.rb
25
+ - lib/archive/tar.rb
26
+ homepage: http://grimpen.net/archive-tar
27
+ licenses:
28
+ - MIT
29
+ post_install_message:
30
+ rdoc_options: []
31
+ require_paths:
32
+ - lib
33
+ required_ruby_version: !ruby/object:Gem::Requirement
34
+ none: false
35
+ requirements:
36
+ - - ! '>='
37
+ - !ruby/object:Gem::Version
38
+ version: '0'
39
+ required_rubygems_version: !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ! '>='
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ requirements: []
46
+ rubyforge_project:
47
+ rubygems_version: 1.8.11
48
+ signing_key:
49
+ specification_version: 3
50
+ summary: Improved TAR implementation in Ruby
51
+ test_files: []