diff --git a/Gemfile b/Gemfile index 13ac913..7a16f0a 100644 --- a/Gemfile +++ b/Gemfile @@ -18,4 +18,5 @@ group :development do gem 'rubocop' gem 'codeclimate-test-reporter', :require => nil gem 'simplecov' + gem 'zstd-ruby', '~> 1.5' end diff --git a/cocoapods-downloader.gemspec b/cocoapods-downloader.gemspec index 25f2dce..153e076 100644 --- a/cocoapods-downloader.gemspec +++ b/cocoapods-downloader.gemspec @@ -17,4 +17,6 @@ Gem::Specification.new do |s| s.required_ruby_version = '>= 2.6' s.specification_version = 3 if s.respond_to? :specification_version + + s.add_runtime_dependency 'zstd-ruby', '~> 1.5' end diff --git a/lib/cocoapods-downloader/remote_file.rb b/lib/cocoapods-downloader/remote_file.rb index 3388342..6a82fcb 100644 --- a/lib/cocoapods-downloader/remote_file.rb +++ b/lib/cocoapods-downloader/remote_file.rb @@ -1,6 +1,8 @@ require 'fileutils' require 'uri' require 'zlib' +require 'zstd-ruby' +require 'pathname' module Pod module Downloader @@ -49,7 +51,7 @@ def headers def should_flatten? if options.key?(:flatten) options[:flatten] - elsif [:tgz, :tar, :tbz, :txz].include?(type) + elsif [:tgz, :tar, :tbz, :txz, :zst].include?(type) true # those archives flatten by default else false # all others (actually only .zip) default not to flatten @@ -68,6 +70,8 @@ def type_with_url(url) :tbz when /\.(txz|tar\.xz)$/ :txz + when /\.(tzst|tar\.zst)$/ + :zst when /\.dmg$/ :dmg end @@ -75,7 +79,7 @@ def type_with_url(url) def filename_with_type(type = :zip) case type - when :zip, :tgz, :tar, :tbz, :txz, :dmg + when :zip, :tgz, :tar, :tbz, :txz, :zst, :dmg "file.#{type}" else raise UnsupportedFileTypeError, "Unsupported file type: #{type}" @@ -95,6 +99,8 @@ def extract_with_type(full_filename, type = :zip) unzip! unpack_from, '-d', unpack_to when :tar, :tgz, :tbz, :txz tar! 'xf', unpack_from, '-C', unpack_to + when :zst + extract_tar_zstd(unpack_from, unpack_to) when :dmg extract_dmg(unpack_from, unpack_to) else @@ -132,6 +138,29 @@ def extract_dmg(unpack_from, unpack_to) hdiutil! 'detach', mount_point end + def extract_tar_zstd(unpack_from, unpack_to) + tmp_tar_file = unpack_from.sub_ext(".tmp.tar") + + begin + stream = Zstd::StreamingDecompress.new + + File.open(unpack_from, 'rb') do |io| + buffer_size = 1024 * 1024 + File.open(tmp_tar_file, 'wb') do |output| + until io.eof? + output.write(stream.decompress(io.read(buffer_size))) + end + end + end + + tar! 'xf', tmp_tar_file, '-C', unpack_to + rescue StandardError => e + raise "Failed to decompress file: #{e}" + ensure + FileUtils.rm(tmp_tar_file) if File.exist?(tmp_tar_file) + end + end + def compare_hash(filename, hasher, hash) incremental_hash = hasher.new diff --git a/spec/fixtures/remote_file/lib_multiple.tar.zst b/spec/fixtures/remote_file/lib_multiple.tar.zst new file mode 100644 index 0000000..af3e0ad Binary files /dev/null and b/spec/fixtures/remote_file/lib_multiple.tar.zst differ diff --git a/spec/remote_file_spec.rb b/spec/remote_file_spec.rb index efe71ab..217f9eb 100644 --- a/spec/remote_file_spec.rb +++ b/spec/remote_file_spec.rb @@ -98,6 +98,14 @@ def download_file(full_filename) downloader.head_supported?.should.be.false end + it 'does not move unpacked contents to parent dir when archive contains multiple children' do + downloader = MockRemoteFile.new(tmp_folder, "#{@fixtures_url}/lib_multiple.tar.zst", {}) + downloader.download + tmp_folder('lib_1/file.txt').should.exist + tmp_folder('lib_2/file.txt').should.exist + tmp_folder('lib_multiple.tar.zst').should.not.exist + end + describe 'concerning archive validation' do it 'verifies that the downloaded file matches a sha1 hash' do options = { :sha1 => 'be62f423e2afde57ae7d79ba7bd3443df73e0021' } @@ -166,6 +174,11 @@ def download_file(full_filename) downloader.send(:type).should == :txz end + it 'detects zst files' do + downloader = MockRemoteFile.new(tmp_folder, 'file:///path/to/file.tar.zst', {}) + downloader.send(:type).should == :zst + end + it 'detects dmg files' do downloader = MockRemoteFile.new(tmp_folder, 'file:///path/to/file.dmg', {}) downloader.send(:type).should == :dmg