lib/sprout/archive_unpacker.rb in sprout-0.7.246-darwin vs lib/sprout/archive_unpacker.rb in sprout-1.0.0.pre
- old
+ new
@@ -1,235 +1,151 @@
+require 'zip/zip'
+require 'archive/tar/minitar'
module Sprout
- class ArchiveUnpackerError < StandardError #:nodoc:
- end
- # Unpack downloaded files from a variety of common archive types.
- # This library should efficiently extract archived files
- # on OS X, Win XP, Vista, DOS, Cygwin, and Linux.
- #
- # It will attempt to infer the archive type by standard mime-type file
- # extensions, but if there is a file with no extension, the unpack_archive
- # method can be provided with an @archive_type symbol argument that is one
- # of the following values:
- # :exe
- # :zip
- # :targz
- # :gzip
- # :swc
- # :rb
- # :dmg
- class ArchiveUnpacker #:nodoc:
- include Archive::Tar
-
- def unpack_archive(file_name, dir, force=false, archive_type=nil)
- archive_type ||= inferred_archive_type(file_name)
- suffix = suffix_for_archive_type(archive_type)
-
- unpacked = unpacked_file_name(file_name, dir, suffix)
- if(File.exists?(unpacked) && force)
- FileUtils.rm_rf(unpacked)
- end
-
- if(!File.exists?(unpacked))
- case archive_type.to_s
- when 'zip'
- unpack_zip(file_name, dir)
- when 'targz'
- unpack_targz(file_name, dir)
- when 'tbz2'
- unpack_tbz2(file_name, dir)
- when 'dmg'
- unpack_dmg(file_name, dir)
- when 'exe'
- FileUtils.mkdir_p(dir)
- FileUtils.move(file_name, dir)
- when 'swc' || 'rb'
- return
- else
- raise ArchiveUnpackerError.new("ArchiveUnpacker does not know how to unpack files of type: #{archive_type} for file_name: #{file_name}")
- end
- end
+ # Given a source, destination and type (or ability to infer it),
+ # unpack downloaded archives.
+ class ArchiveUnpacker
+
+ # Figure out what kind of archive you have from the file name,
+ # and unpack it using the appropriate scheme.
+ def unpack archive, destination, type=nil, clobber=nil
+ return unpack_zip(archive, destination, clobber) if is_zip?(archive, type)
+ return unpack_tgz(archive, destination, clobber) if is_tgz?(archive, type)
+
+ # This is definitely debatable, should we copy the file even if it's
+ # not an archive that we're about to unpack?
+ # If so, why would we only do this with some subset of file types?
+ # Opinions welcome here...
+ return copy_file(archive, destination, clobber) if is_copyable?(archive)
+
+ raise Sprout::Errors::UnknownArchiveType.new("Unsupported or unknown archive type encountered with: #{archive}")
end
-
- def unpack_zip(zip_file, dir)
- # Avoid the rubyzip Segmentation Fault bug
- # at least on os x...
- if(RUBY_PLATFORM =~ /darwin/)
- # Unzipping on OS X
- FileUtils.makedirs(dir)
- zip_dir = File.expand_path(File.dirname(zip_file))
- zip_name = File.basename(zip_file)
- output = File.expand_path(dir)
- # puts ">> zip_dir: #{zip_dir} zip_name: #{zip_name} output: #{output}"
- %x(cd #{zip_dir};unzip #{zip_name} -d #{output})
- else
- retries = 0
- begin
- retries += 1
- Zip::ZipFile::open(zip_file) do |zf|
- zf.each do |e|
- fpath = File.join(dir, e.name)
- FileUtils.mkdir_p(File.dirname(fpath))
- # Disgusting, Gross Hack to fix DOS/Ruby Bug
- # That makes the zip library throw a ZipDestinationFileExistsError
- # When the zip archive includes two files whose names
- # differ only by extension.
- # This bug actually appears in the File.exists? implementation
- # throwing false positives!
- # If you're going to use this code, be sure you extract
- # into a new, empty directory as existing files will be
- # clobbered...
- begin
- if(File.exists?(fpath) && !File.directory?(fpath))
- hackpath = fpath + 'hack'
- zf.extract(e, hackpath)
- File.copy(hackpath, fpath)
- File.delete(hackpath)
- else
- zf.extract(e, fpath)
- end
- rescue NotImplementedError => ni_err
- puts "[WARNING] #{ni_err} for: #{e}"
- end
- end
- end
- rescue StandardError => err
- FileUtils.rm_rf(dir)
- if(retries < 3)
- FileUtils.makedirs(dir)
- retry
- end
- raise err
+
+ # Unpack zip archives on any platform.
+ def unpack_zip archive, destination, clobber=nil
+ validate archive, destination
+
+ Zip::ZipFile.open archive do |zipfile|
+ zipfile.each do |entry|
+ next if entry.name =~ /__MACOSX/ or entry.name =~ /\.DS_Store/
+ unpack_zip_entry entry, destination, clobber
end
end
end
-
- def unpacked_file_name(file, dir, suffix=nil)
- basename = File.basename(file, suffix)
- path = File.expand_path(dir)
- return File.join(path, basename)
- end
- def unpack_tbz2(tgz_file, dir)
- unpack_tar_zip tgz_file, dir, 'tar.bz2'
- end
+ # Unpack tar.gz or .tgz files on any platform.
+ def unpack_tgz archive, destination, clobber=nil
+ validate archive, destination
- def unpack_targz(tgz_file, dir)
- unpack_tar_zip tgz_file, dir, 'tar.gz'
- end
-
- def unpack_tar_zip(tgz_file, dir, ext)
- if(!File.exists?(dir))
- FileUtils.makedirs(dir)
+ tar = Zlib::GzipReader.new(File.open(archive, 'rb'))
+ if(!should_unpack_tgz?(destination, clobber))
+ raise Sprout::Errors::DestinationExistsError.new "Unable to unpack #{archive} into #{destination} without explicit :clobber argument"
end
- tar = Zlib::GzipReader.new(File.open(tgz_file, 'rb'))
- Minitar.unpack(tar, dir)
-
+
+ Archive::Tar::Minitar.unpack(tar, destination)
+
# Recurse and unpack gzipped children (Adobe did this double
- # gzip with the Linux FlashPlayer for some reason)
- Dir.glob("#{dir}/**/*.#{ext}").each do |child|
- if(child != tgz_file && dir != File.dirname(child))
- unpack_targz(child, File.dirname(child))
+ # gzip with the Linux FlashPlayer for some weird reason)
+ ["#{destination}/**/*.tgz", "#{destination}/**/*.tar.gz"].each do |pattern|
+ Dir.glob(pattern).each do |child|
+ if(child != archive && dir != File.dirname(child))
+ unpack_tgz(child, File.dirname(child))
+ end
end
end
-
end
-
- # This is actually not unpacking the FlashPlayer
- # Binary file as expected...
- # OSX is treated the player binary as if it is
- # a regular text file, but if it is copied manually,
- # the command works fine!?
- def unpack_dmg(dmg_file, dir)
- # 1) Mount the dmg in place
- # 2) Recursively Copy its contents to asproject_home
- # 3) Unmount the dmg
- if(mounted_path.nil?)
- raise StandardError.new('DMG file downloaded, but the RemoteFileTask needs a mounted_path in order to mount it')
- end
- if(!File.exists?(full_mounted_path))
- system("hdiutil mount #{dmg_file}")
+ # Rather than unpacking, safely copy the file from one location
+ # to another.
+ def copy_file file, destination, clobber=nil
+ validate file, destination
+ target = File.expand_path( File.join(destination, File.basename(file)) )
+ if(File.exists?(target) && clobber != :clobber)
+ raise Sprout::Errors::DestinationExistsError.new "Unable to copy #{file} to #{target} because target already exists and we were not asked to :clobber it"
end
-
- begin
- mounted_target = File.join(full_mounted_path, extracted_file)
-
- # Copy the DMG contents using system copy rather than ruby utils
- # Because OS X does something special with .app files that the
- # Ruby FileUtils and File classes break...
- from = mounted_target
-# from = File.join(full_mounted_path, extracted_file)
- to = File.join(@user.downloads, @name.to_s, extracted_file)
- FileUtils.makedirs(File.dirname(to))
-
- if(File.exists?(from))
- `ditto '#{from}' '#{to}'`
- end
- rescue StandardError => e
- if(File.exists?(full_mounted_path))
- system("hdiutil unmount -force \"#{full_mounted_path}\"")
- end
- end
+ FileUtils.mkdir_p destination
+ FileUtils.cp_r file, destination
+
+ destination
end
-
- def suffix_for_archive_type(type)
- if(type == :targz)
- return '.tar.gz'
- else
- return ".#{type.to_s}"
- end
+
+ # Return true if the provided file name looks like a zip file.
+ def is_zip? archive, type=nil
+ type == :zip || !archive.match(/\.zip$/).nil?
end
-
- def inferred_archive_type(file_name)
- if is_zip?(file_name)
- return :zip
- elsif is_targz?(file_name)
- return :targz
- elsif is_gzip?(file_name)
- return :gz
- elsif is_swc?(file_name)
- return :swc
- elsif is_rb?(file_name)
- return :rb
- elsif is_dmg?(file_name)
- return :dmg
- elsif is_exe?(file_name)
- return :exe
- else
- return nil
- end
-
+
+ # Return true if the provided file name looks like a tar.gz file.
+ def is_tgz? archive, type=nil
+ type == :tgz || !archive.match(/\.tgz$/).nil? || !archive.match(/\.tar.gz$/).nil?
end
-
- def is_zip?(file)
- return (file.split('.').pop == 'zip')
+
+ def is_exe? archive, type=nil
+ type == :exe || !archive.match(/\.exe$/).nil?
end
- def is_targz?(file)
- parts = file.split('.')
- part = parts.pop
- return (part == 'tgz' || part == 'gz' && parts.pop == 'tar')
+ def is_swc? archive, type=nil
+ type == :swc || !archive.match(/\.swc$/).nil?
end
-
- def is_gzip?(file)
- return (file.split('.').pop == 'gz')
+
+ def is_rb? archive, type=nil
+ type == :rb || !archive.match(/\.rb$/).nil?
end
-
- def is_swc?(file)
- return (file.split('.').pop == 'swc')
+
+ private
+
+ def is_copyable? archive
+ (is_exe?(archive) || is_swc?(archive) || is_rb?(archive))
end
-
- def is_rb?(file)
- return (file.split('.').pop == 'rb')
+
+ def should_unpack_tgz? dir, clobber=nil
+ return !directory_has_children?(dir) || clobber == :clobber
+
end
-
- def is_dmg?(file)
- return (file.split('.').pop == 'dmg')
- end
- def is_exe?(file)
- return (file.split('.').pop == 'exe')
+ def directory_has_children? dir
+ (Dir.entries(dir) - ['.', '..']).size > 0
end
+
+ def validate archive, destination
+ validate_archive archive
+ validate_destination destination
+ end
+
+ def validate_archive archive
+ message = "Archive could not be found at: #{archive}"
+ raise Sprout::Errors::ArchiveUnpackerError.new(message) if archive.nil? || !File.exists?(archive)
+ end
+
+ def validate_destination path
+ message = "Archive destination could not be found at: #{path}"
+ raise Sprout::Errors::ArchiveUnpackerError.new(message) if path.nil? || !File.exists?(path)
+ end
+
+ def unpack_zip_entry entry, destination, clobber
+ # Ensure hidden mac files don't get written to disk:
+ path = File.join destination, entry.name
+
+ if entry.directory?
+ # If an archive has empty directories:
+ FileUtils.mkdir_p path
+ elsif entry.file?
+ # On Windows, we don't get the entry for
+ # each parent directory:
+ FileUtils.mkdir_p File.dirname(path)
+ begin
+ entry.extract path
+ rescue Zip::ZipDestinationFileExistsError => zip_dest_error
+ if(clobber == :clobber)
+ FileUtils.rm_rf path
+ entry.extract path
+ else
+ raise Sprout::Errors::DestinationExistsError.new zip_dest_error.message
+ end
+ end
+ end
+ end
+
end
end
+