1# -*- coding: utf-8 -*- 2#-- 3# Copyright (C) 2004 Mauricio Julio Fernández Pradier 4# See LICENSE.txt for additional licensing information. 5#++ 6 7## 8# TarReader reads tar files and allows iteration over their items 9 10class Gem::Package::TarReader 11 12 include Enumerable 13 14 ## 15 # Raised if the tar IO is not seekable 16 17 class UnexpectedEOF < StandardError; end 18 19 ## 20 # Creates a new TarReader on +io+ and yields it to the block, if given. 21 22 def self.new(io) 23 reader = super 24 25 return reader unless block_given? 26 27 begin 28 yield reader 29 ensure 30 reader.close 31 end 32 33 nil 34 end 35 36 ## 37 # Creates a new tar file reader on +io+ which needs to respond to #pos, 38 # #eof?, #read, #getc and #pos= 39 40 def initialize(io) 41 @io = io 42 @init_pos = io.pos 43 end 44 45 ## 46 # Close the tar file 47 48 def close 49 end 50 51 ## 52 # Iterates over files in the tarball yielding each entry 53 54 def each 55 return enum_for __method__ unless block_given? 56 57 until @io.eof? do 58 header = Gem::Package::TarHeader.from @io 59 return if header.empty? 60 61 entry = Gem::Package::TarReader::Entry.new header, @io 62 size = entry.header.size 63 64 yield entry 65 66 skip = (512 - (size % 512)) % 512 67 pending = size - entry.bytes_read 68 69 begin 70 # avoid reading... 71 @io.seek pending, IO::SEEK_CUR 72 pending = 0 73 rescue Errno::EINVAL, NameError 74 while pending > 0 do 75 bytes_read = @io.read([pending, 4096].min).size 76 raise UnexpectedEOF if @io.eof? 77 pending -= bytes_read 78 end 79 end 80 81 @io.read skip # discard trailing zeros 82 83 # make sure nobody can use #read, #getc or #rewind anymore 84 entry.close 85 end 86 end 87 88 alias each_entry each 89 90 ## 91 # NOTE: Do not call #rewind during #each 92 93 def rewind 94 if @init_pos == 0 then 95 raise Gem::Package::NonSeekableIO unless @io.respond_to? :rewind 96 @io.rewind 97 else 98 raise Gem::Package::NonSeekableIO unless @io.respond_to? :pos= 99 @io.pos = @init_pos 100 end 101 end 102 103 ## 104 # Seeks through the tar file until it finds the +entry+ with +name+ and 105 # yields it. Rewinds the tar file to the beginning when the block 106 # terminates. 107 108 def seek name # :yields: entry 109 found = find do |entry| 110 entry.full_name == name 111 end 112 113 return unless found 114 115 return yield found 116 ensure 117 rewind 118 end 119 120end 121 122require 'rubygems/package/tar_reader/entry' 123 124