Class: ZipKit::RemoteIO
- Inherits:
-
Object
- Object
- ZipKit::RemoteIO
- Defined in:
- lib/zip_kit/remote_io.rb
Overview
An object that fakes just-enough of an IO to be dangerous
- or, more precisely, to be useful as a source for the FileReader
central directory parser. Effectively we substitute an IO object
for an object that fetches parts of the remote file over HTTP using
Range:
headers. TheRemoteIO
acts as an adapter between an object that performs the actual fetches over HTTP and an object that expects a handful of IO methods to be available.
Instance Method Summary collapse
-
#initialize(url) ⇒ RemoteIO
constructor
A new instance of RemoteIO.
-
#read(n_bytes = nil) ⇒ String
Emulates IO#read, but requires the number of bytes to read The read will be limited to the size of the remote resource relative to the current offset in the IO, so if you are at offset 0 in the IO of size 10, doing a
read(20)
will only return you 10 bytes of result, and not raise any exceptions. -
#request_object_size ⇒ Integer
protected
For working with S3 it is a better idea to perform a GET request for one byte, since doing a HEAD request needs a different permission - and standard GET presigned URLs are not allowed to perform it.
-
#request_range(range) ⇒ String
protected
Only used internally when reading the remote ZIP.
-
#seek(offset, mode = IO::SEEK_SET) ⇒ Object
Emulates IO#seek.
-
#size ⇒ Integer
Emulates IO#size.
-
#tell ⇒ Integer
Returns the current pointer position within the IO.
Constructor Details
#initialize(url) ⇒ RemoteIO
Returns a new instance of RemoteIO.
14 15 16 17 18 |
# File 'lib/zip_kit/remote_io.rb', line 14 def initialize(url) @pos = 0 @uri = URI(url) @remote_size = nil end |
Instance Method Details
#read(n_bytes = nil) ⇒ String
Emulates IO#read, but requires the number of bytes to read
The read will be limited to the
size of the remote resource relative to the current offset in the IO,
so if you are at offset 0 in the IO of size 10, doing a read(20)
will only return you 10 bytes of result, and not raise any exceptions.
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
# File 'lib/zip_kit/remote_io.rb', line 45 def read(n_bytes = nil) # If the resource is empty there is nothing to read return if size.zero? maximum_avaialable = size - @pos n_bytes ||= maximum_avaialable # nil == read to the end of file return "" if n_bytes.zero? raise ArgumentError, "No negative reads(#{n_bytes})" if n_bytes < 0 n_bytes = clamp(0, n_bytes, maximum_avaialable) http_range = (@pos..(@pos + n_bytes - 1)) request_range(http_range).tap do |data| raise "Remote read returned #{data.bytesize} bytes instead of #{n_bytes} as requested" if data.bytesize != n_bytes @pos = clamp(0, @pos + data.bytesize, size) end end |
#request_object_size ⇒ Integer (protected)
For working with S3 it is a better idea to perform a GET request for one byte, since doing a HEAD request needs a different permission - and standard GET presigned URLs are not allowed to perform it
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
# File 'lib/zip_kit/remote_io.rb', line 93 def request_object_size http = Net::HTTP.start(@uri.hostname, @uri.port) request = Net::HTTP::Get.new(@uri) request.range = 0..0 response = http.request(request) case response.code when "206" content_range_header_value = response["Content-Range"] content_range_header_value.split("/").last.to_i when "200" response["Content-Length"].to_i else raise "Remote at #{@uri} replied with code #{response.code}" end end |
#request_range(range) ⇒ String (protected)
Only used internally when reading the remote ZIP.
76 77 78 79 80 81 82 83 84 85 86 87 |
# File 'lib/zip_kit/remote_io.rb', line 76 def request_range(range) http = Net::HTTP.start(@uri.hostname, @uri.port) request = Net::HTTP::Get.new(@uri) request.range = range response = http.request(request) case response.code when "206", "200" response.body else raise "Remote at #{@uri} replied with code #{response.code}" end end |
#seek(offset, mode = IO::SEEK_SET) ⇒ Object
Emulates IO#seek
23 24 25 26 27 28 |
# File 'lib/zip_kit/remote_io.rb', line 23 def seek(offset, mode = IO::SEEK_SET) raise "Unsupported read mode #{mode}" unless mode == IO::SEEK_SET @remote_size ||= request_object_size @pos = clamp(0, offset, @remote_size) 0 # always return 0! end |
#size ⇒ Integer
Emulates IO#size.
33 34 35 |
# File 'lib/zip_kit/remote_io.rb', line 33 def size @remote_size ||= request_object_size end |
#tell ⇒ Integer
Returns the current pointer position within the IO
66 67 68 |
# File 'lib/zip_kit/remote_io.rb', line 66 def tell @pos end |