Skip to content
78 changes: 76 additions & 2 deletions lib/remote_input.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,80 @@
require_relative "remote_input/downloader"
require_relative "remote_input/cache-path"
require_relative "remote_input/downloader"
require_relative "remote_input/zip-extractor"

module RemoteInput
class RemoteInput
class << self
def open(...)
input = new(...)
if block_given?
begin
yield(input)
ensure
input.close
end
else
input
end
end
end

def initialize(url,
*fallback_urls,
encoding: nil,
internal_encoding: nil,
external_encoding: nil,
**http_options)
@url = URI(url)
@encoding = encoding
@internal_encoding = internal_encoding
@external_encoding = external_encoding
@downloader = Downloader.new(url, *fallback_urls, **http_options)
@cache_path = nil
@local_file = nil
@closed = false
end

def read(maxlen=nil, out_string=nil)
local_file.read(maxlen, out_string)
end

def close
@local_file.close if @local_file and not @local_file.closed?
@closed = true
end

def clear_cache
cache_path.remove
end

private

def path
cache_path.base_dir + File.basename(normalize_path)
end

def cache_path
return @cache_path if @cache_path
dirname = File.dirname(normalize_path).delete_suffix("/")
cache_id = "#{@url.host}#{dirname}".tr("/", "-")
Comment thread
tikkss marked this conversation as resolved.
@cache_path = CachePath.new(cache_id)
end

def normalize_path
url_path = @url.path
url_path = "/" if url_path.empty?
url_path += "data" if url_path.end_with?("/")
url_path
end

def local_file
raise IOError, "closed stream" if @closed
return @local_file if @local_file
@downloader.download(path)
options = {}
options[:encoding] = @encoding if @encoding
options[:internal_encoding] = @internal_encoding if @internal_encoding
options[:external_encoding] = @external_encoding if @external_encoding
@local_file = path.open(**options)
end
end
2 changes: 1 addition & 1 deletion lib/remote_input/cache-path.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module RemoteInput
class RemoteInput
class CachePath
def initialize(id)
@id = id
Expand Down
2 changes: 1 addition & 1 deletion lib/remote_input/downloader.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
require_relative "error"
require_relative "version"

module RemoteInput
class RemoteInput
class Downloader
class TooManyRedirects < Error; end

Expand Down
2 changes: 1 addition & 1 deletion lib/remote_input/error.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module RemoteInput
class RemoteInput
class Error < StandardError
end
end
2 changes: 1 addition & 1 deletion lib/remote_input/version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module RemoteInput
class RemoteInput
VERSION = "0.0.0"
end
2 changes: 1 addition & 1 deletion lib/remote_input/zip-extractor.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
require 'zip'

module RemoteInput
class RemoteInput
class ZipExtractor
def initialize(path)
@path = path
Expand Down
99 changes: 99 additions & 0 deletions test/test-remote-input.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
require_relative "helper"

class RemoteInputTest < Test::Unit::TestCase
def test_read_once
open_input do |input|
input.send(:path).parent.mkpath
input.send(:path).write("12")
assert_equal("12", input.read)
end
end

def test_read_twice
open_input do |input|
input.send(:path).parent.mkpath
input.send(:path).write("12")
assert_equal("1", input.read(1))
assert_equal("2", input.read(1))
end
end

def test_read_with_encoding
open_input(encoding: "Windows-31J:UTF-8") do |input|
input.send(:path).parent.mkpath
input.send(:path).write("入力", encoding: "Windows-31J:UTF-8")
assert_equal("入力", input.read)
end
end

def test_read_after_close
open_input do |input|
input.close
assert_raise(IOError.new("closed stream")) do
input.read
end
end
end

def test_open_with_block
opened_input = nil
open_input do |input|
opened_input = input
end
assert_raise(IOError.new("closed stream")) do
opened_input.read
end
end

def test_open_with_block_raised
opened_input = nil
assert_raise(RuntimeError.new("error in block")) do
open_input do |input|
opened_input = input
raise "error in block"
end
end
assert_raise(IOError.new("closed stream")) do
opened_input.read
end
end

data("no path", ["/example.com/data", "https://example.com"])
data("root", ["/example.com/data", "https://example.com/"])
data("file", ["/example.com/file", "https://example.com/file"])
data("query", ["/example.com/file", "https://example.com/file?a=b"])
data("directory", ["/example.com-a/data", "https://example.com/a/"])
data("nested file", ["/example.com-a/file", "https://example.com/a/file"])
data("deeply nested", ["/example.com-a-b/file", "https://example.com/a/b/file"])
def test_path(data)
expected, url = data
RemoteInput.open(url) do |input|
assert do
input.send(:path).to_s.end_with?(expected)
end
end
end

def test_clear_cache
open_input do |input|
input.send(:path).parent.mkpath
input.send(:path).write("1")
input.clear_cache
assert do
not input.send(:path).parent.exist?
end
end
end

private

def open_input(**options)
input = RemoteInput.open("https://example.com/file", **options)
begin
yield(input)
ensure
input.close
input.clear_cache
end
end
end
Loading