diff --git a/lib/sqldef.rb b/lib/sqldef.rb index 9c45491..84c1592 100644 --- a/lib/sqldef.rb +++ b/lib/sqldef.rb @@ -7,6 +7,7 @@ require 'stringio' require 'uri' require 'zlib' +require 'zip' require_relative 'sqldef/version' module Sqldef @@ -24,6 +25,13 @@ module Sqldef ] private_constant :COMMANDS + OS_ARCHIVE = { + 'linux' => 'tar.gz', + 'windows' => 'zip', + 'darwin' => 'zip', + } + private_constant :OS_ARCHIVE + @bin = Dir.pwd class << self @@ -79,16 +87,24 @@ def download(command) return path if File.executable?(path) print("Downloading '#{command}' under '#{bin}'... ") - resp = get(build_url(command), code: 302) # Latest - resp = get(resp['location'], code: 302) # vX.Y.Z - resp = get(resp['location'], code: 200) # Binary - - gzip = Zlib::GzipReader.new(StringIO.new(resp.body)) - Gem::Package::TarReader.new(gzip) do |tar| - unless file = tar.find { |f| f.full_name == command } - raise "'#{command}' was not found in the archive" + url = build_url(command) + resp = get(url, code: 200, max_retries: 4) + + if url.end_with?('.zip') + Zip::File.open_buffer(resp.body) do |zip| + unless entry = zip.find_entry(command) + raise "'#{command}' was not found in the archive" + end + File.binwrite(path, zip.read(entry)) + end + else + gzip = Zlib::GzipReader.new(StringIO.new(resp.body)) + Gem::Package::TarReader.new(gzip) do |tar| + unless file = tar.find { |f| f.full_name == command } + raise "'#{command}' was not found in the archive" + end + File.binwrite(path, file.read) end - File.binwrite(path, file.read) end FileUtils.chmod('+x', path) @@ -109,20 +125,26 @@ def build_url(command) raise "Unexpected sqldef command: #{command}" end os = Etc.uname.fetch(:sysname).downcase - arch = GOARCH.fetch(Etc.uname.fetch(:machine)) - "https://github.com/k0kubun/sqldef/releases/latest/download/#{command}_#{os}_#{arch}.tar.gz" + archive = OS_ARCHIVE.fetch(os) + arch = Etc.uname.fetch(:machine) + goarch = GOARCH.fetch(arch, arch) + "https://github.com/sqldef/sqldef/releases/latest/download/#{command}_#{os}_#{goarch}.#{archive}" end # TODO: Retry transient errors - def get(url, code: nil) + def get(url, code: nil, max_retries:) uri = URI.parse(url) - Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http| + resp = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http| http.get("#{uri.path}?#{uri.query}") - end.tap do |resp| - if code && resp.code != code.to_s - raise "Expected '#{url}' to return #{code}, but got #{resp.code}: #{resp.body}" - end end + if resp.is_a?(Net::HTTPRedirection) && max_retries > 0 + # Follow redirects that lead to the current repository (if sqldef/sqldef is moved), + # Latest, vX.Y.Z, and to the binary + return get(resp['location'], code: code, max_retries: max_retries - 1) + elsif code && resp.code != code.to_s + raise "Expected '#{url}' to return #{code}, but got #{resp.code}: #{resp.body}" + end + resp end end end diff --git a/sqldef.gemspec b/sqldef.gemspec index 633c066..bc17682 100644 --- a/sqldef.gemspec +++ b/sqldef.gemspec @@ -17,6 +17,8 @@ Gem::Specification.new do |spec| spec.metadata['homepage_uri'] = spec.homepage spec.metadata['source_code_uri'] = spec.homepage + spec.add_runtime_dependency 'rubyzip' + # Specify which files should be added to the gem when it is released. # The `git ls-files -z` loads the files in the RubyGem that have been added into git. spec.files = Dir.chdir(File.expand_path(__dir__)) do