lib/sprout/archive_unpacker.rb in sprout-1.0.32.pre vs lib/sprout/archive_unpacker.rb in sprout-1.0.35.pre
- old
+ new
@@ -1,175 +1,241 @@
require 'zip/zip'
+require 'sprout/version'
require 'archive/tar/minitar'
-module Sprout
+##
+# Given a source, destination and archive type (or ability to infer it),
+# unpack the provided archive.
+#
+# unpacker = Sprout::ArchiveUnpacker.new
+# unpacker.unpack "Foo.zip", "unpacked/"
+#
+class Sprout::ArchiveUnpacker
- # Given a source, destination and type (or ability to infer it),
- # unpack downloaded archives.
- class ArchiveUnpacker
+ ##
+ # Unpack the provided +archive+ into the provided +destination+.
+ #
+ # If a +type+ is not provided, a type will be inferred from the file name suffix.
+ #
+ # @param archive [File] Path to the archive that will be unpacked (or copied)
+ # @param destination [Path] Path to the folder where unpacked files should be placed (or copied).
+ # @param type [Symbol] The type of the archive in cases where it can't be inferred
+ # from the name. Acceptable values are: :zip, :tgz, :swc, :exe or :rb
+ # @param clobber [Boolean] If the destination already contains the expected file(s),
+ # the unpacker will not run unless +clobber+ is true.
+ # @return [String] path to the unpacked files (usually same as destination).
+ # @raise Sprout::Errors::UnknownArchiveType If the archive type cannot be inferred and a valid type is not provided.
+ 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)
- # 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)
- # 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
- raise Sprout::Errors::UnknownArchiveType.new("Unsupported or unknown archive type encountered with: #{archive}")
- end
+ ##
+ # Unpack zip archives on any platform using whatever strategy is most
+ # efficient and reliable.
+ #
+ # @param archive [File] Path to the archive that will be unpacked.
+ # @param destination [Path] Path to the folder where unpacked files should be placed.
+ # @param clobber [Boolean] If the destination already contains the expected file(s),
+ # the unpacker will not run unless +clobber+ is true.
+ # @return [File] the file or directory that was created.
+ def unpack_zip archive, destination, clobber=nil
+ validate archive, destination
- # Unpack zip archives on any platform.
- #
- # In case you're wondering... Ruby sucks...
- # This code corrupts the FlashPlayer executable
- # on OSX but if the file is manually unpacked,
- # it works fine.
- #
- def unpack_zip archive, destination, clobber=nil
- validate archive, destination
-
- if is_darwin?
- unpack_zip_on_darwin archive, destination, clobber
- else
- Zip::ZipFile.open archive do |zipfile|
- zipfile.each do |entry|
- next if entry.name =~ /__MACOSX/ or entry.name =~ /\.DS_Store/
+ ##
+ # As it turns out, the Rubyzip library corrupts
+ # binary files (like the Flash Player) on OSX and is also
+ # horribly slow for large archives (like the ~120MB Flex SDK)
+ # on all platforms.
+ if is_darwin?
+ unpack_zip_on_darwin archive, destination, clobber
+ else
+ 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
end
+ end
- def is_darwin?
- Sprout.current_system.is_a?(Sprout::System::OSXSystem)
- end
+ ##
+ # Return true if we're on a Darwin native system (OSX).
+ # @return [Boolean]
+ def is_darwin?
+ Sprout.current_system.is_a?(Sprout::System::OSXSystem)
+ end
- def unpack_zip_on_darwin archive, destination, clobber
- # Unzipping on OS X
- FileUtils.makedirs destination
- zip_dir = File.expand_path File.dirname(archive)
- zip_name = File.basename archive
- output = File.expand_path destination
- # puts ">> zip_dir: #{zip_dir} zip_name: #{zip_name} output: #{output}"
- %x(cd #{zip_dir};unzip #{zip_name} -d #{output})
- end
+ ##
+ # Optimization for zip files on OSX. Uses the native
+ # 'unzip' utility which is much faster (and more reliable)
+ # than Ruby for large archives (like the Flex SDK) and
+ # binaries that Ruby corrupts (like the Flash Player).
+ #
+ # @return [File] the file or directory that was created.
+ def unpack_zip_on_darwin archive, destination, clobber
+ # Unzipping on OS X
+ FileUtils.makedirs destination
+ zip_dir = File.expand_path File.dirname(archive)
+ zip_name = File.basename archive
+ output = File.expand_path destination
+ # puts ">> zip_dir: #{zip_dir} zip_name: #{zip_name} output: #{output}"
+ %x(cd #{zip_dir};unzip #{zip_name} -d #{output})
+ end
- # Unpack tar.gz or .tgz files on any platform.
- def unpack_tgz archive, destination, clobber=nil
- validate archive, destination
+ ##
+ # Unpack tar.gz or .tgz files on any platform.
+ #
+ # @return [File] the file or directory that was created.
+ def unpack_tgz archive, destination, clobber=nil
+ validate archive, destination
- 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(archive, 'rb'))
+ if(!should_unpack_tgz?(destination, clobber))
+ raise Sprout::Errors::DestinationExistsError.new "Unable to unpack #{archive} into #{destination} without explicit :clobber argument"
+ end
- Archive::Tar::Minitar.unpack(tar, destination)
+ Archive::Tar::Minitar.unpack(tar, destination)
- # Recurse and unpack gzipped children (Adobe did this double
- # 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
+ # Recurse and unpack gzipped children (Adobe did this double
+ # 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
- # 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
- FileUtils.mkdir_p destination
- FileUtils.cp_r file, destination
-
- destination
+ ##
+ # Rather than unpacking, safely copy the file from one location
+ # to another.
+ # This method is generally used when .exe files are downloaded
+ # directly.
+ #
+ # @return [File] the file or directory that was created.
+ 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
+ FileUtils.mkdir_p destination
+ FileUtils.cp_r file, destination
- # 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
+ destination
+ 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
+ ##
+ # Returns true if the provided file name looks like a zip file or the +type+ argument is +:zip+.
+ # @return [Boolean]
+ def is_zip? archive, type=nil
+ type == :zip || !archive.match(/\.zip$/).nil?
+ end
- def is_exe? archive, type=nil
- type == :exe || !archive.match(/\.exe$/).nil?
- end
-
- def is_swc? archive, type=nil
- type == :swc || !archive.match(/\.swc$/).nil?
- end
+ ##
+ # Return true if the provided file name looks like a tar.gz file or the +type+ argument is +:tgz+.
+ # @return [Boolean]
+ def is_tgz? archive, type=nil
+ type == :tgz || !archive.match(/\.tgz$/).nil? || !archive.match(/\.tar.gz$/).nil?
+ end
- def is_rb? archive, type=nil
- type == :rb || !archive.match(/\.rb$/).nil?
- end
+ ##
+ # Return true if the downloaded archive is a .exe file or the +type+ argument is +:exe+.
+ # @return [Boolean]
+ def is_exe? archive, type=nil
+ type == :exe || !archive.match(/\.exe$/).nil?
+ end
- private
+ ##
+ # Return true if the downloaded archive is a .swc file or the +type+ argument is +:swc+.
+ # @return [Boolean]
+ def is_swc? archive, type=nil
+ type == :swc || !archive.match(/\.swc$/).nil?
+ end
- def is_copyable? archive
- (is_exe?(archive) || is_swc?(archive) || is_rb?(archive))
- end
+ ##
+ # Return true if the downloaded archive is a .rb file or the +type+ argument is +:rb+.
+ # @return [Boolean]
+ def is_rb? archive, type=nil
+ type == :rb || !archive.match(/\.rb$/).nil?
+ end
- def should_unpack_tgz? dir, clobber=nil
- return !directory_has_children?(dir) || clobber == :clobber
+ private
- end
+ ##
+ # Return true if the provided archive should be copied as-is, rather
+ # than being unpacked first.
+ # @return [Boolean]
+ def is_copyable? archive
+ (is_exe?(archive) || is_swc?(archive) || is_rb?(archive))
+ end
- def directory_has_children? dir
- (Dir.entries(dir) - ['.', '..']).size > 0
- end
+ ##
+ # Return true if the tgz should be unpacked.
+ # @return [Boolean]
+ def should_unpack_tgz? dir, clobber=nil
+ return !directory_has_children?(dir) || clobber == :clobber
- def validate archive, destination
- validate_archive archive
- validate_destination destination
- end
+ 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
+ ##
+ # Return true if the provided directory has one or more chidren.
+ # @return [Boolean]
+ def directory_has_children? dir
+ (Dir.entries(dir) - ['.', '..']).size > 0
+ 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
+ ##
+ # @return [Boolean] true if the +archive+ and +destination+ exist.
+ # @raise Sprout::Errors::ArchiveUnpackerError if the +archive+ or +destination+ don't exist.
+ def validate archive, destination
+ archive_message = "Archive could not be found at: #{archive}"
+ raise Sprout::Errors::ArchiveUnpackerError.new(archive_message) unless valid_path?(archive)
+ destination_message = "Destination could not be found at: #{destination}"
+ raise Sprout::Errors::ArchiveUnpackerError.new(destination_message) unless valid_path?(destination)
+ true
+ end
- def unpack_zip_entry entry, destination, clobber
- # Ensure hidden mac files don't get written to disk:
- path = File.join destination, entry.name
+ ##
+ # @return [Boolean] true if the provided +path+ is not nil and exists on disk.
+ def valid_path? path
+ !path.nil? && File.exists?(path)
+ end
- 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
+ ##
+ # Unpack an entry from a zip archive. This is an _inconvenience_ method
+ # thanks to the way Ruby zip handles zip archives.
+ 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
- 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
+ else
+ raise Sprout::Errors::DestinationExistsError.new zip_dest_error.message
end
end
end
-
end
end