Module: SpatialFeatures::Unzip
- Defined in:
- lib/spatial_features/unzip.rb
Defined Under Namespace
Classes: PathNotFound
Constant Summary collapse
- IGNORED_ENTRY_PATHS =
paths containing '__macosx' or beginning with a '.'
/(\A|\/)(__macosx|\.)/i.freeze
- NESTED_ARCHIVE_PATTERN =
Archives that may themselves hold the file we're looking for. A KMZ is a ZIP with a KML inside it, and proponents routinely forward a ZIP of per-layer ZIPs rather than the layers themselves.
/\.(zip|kmz)$/i.freeze
- MAX_NESTING_DEPTH =
How many times we'll unwrap a nested archive before giving up. Two levels covers every case we've seen in practice (a ZIP of shapefile ZIPs, a ZIP holding a KMZ) while bounding the work a deeply nested archive can cost us.
2
Class Method Summary collapse
- .entries(file_path) ⇒ Object
-
.extract(file_path, tmpdir: nil, downcase: false) ⇒ Object
Extracts the archive's entries and returns their paths, skipping entries that a name cannot place inside
tmpdir. - .is_zip?(file) ⇒ Boolean
- .matching_paths(paths, find) ⇒ Object
- .names(file_path) ⇒ Object
- .paths(file_path, find: nil, depth: 0, **extract_options) ⇒ Object
-
.paths_in_nested_archives(paths, find:, depth:, tmpdir: nil, **extract_options) ⇒ Object
Unwrap any archives the archive itself contained and search those instead.
Class Method Details
.entries(file_path) ⇒ Object
108 109 110 |
# File 'lib/spatial_features/unzip.rb', line 108 def self.entries(file_path) Zip::File.open(File.path(file_path)) end |
.extract(file_path, tmpdir: nil, downcase: false) ⇒ Object
Extracts the archive's entries and returns their paths, skipping entries that a name
cannot place inside tmpdir.
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 |
# File 'lib/spatial_features/unzip.rb', line 60 def self.extract(file_path, tmpdir: nil, downcase: false) tmpdir ||= Dir.mktmpdir root = Pathname.new(tmpdir).realpath [].tap do |paths| entries(file_path).each do |entry| next if entry.name =~ IGNORED_ENTRY_PATHS next if entry.symlink? output_filename = entry.name output_filename = output_filename.downcase if downcase path = contained_path(root, output_filename) next unless path directory = File.dirname(path) basename = File.basename(path) FileUtils.mkdir_p(directory) entry.extract(basename, destination_directory: directory) paths << path end end end |
.is_zip?(file) ⇒ Boolean
112 113 114 115 116 117 118 |
# File 'lib/spatial_features/unzip.rb', line 112 def self.is_zip?(file) zip = file.readline.start_with?('PK') file.rewind return zip rescue EOFError return false end |
.matching_paths(paths, find) ⇒ Object
34 35 36 |
# File 'lib/spatial_features/unzip.rb', line 34 def self.matching_paths(paths, find) paths.select {|path| find.any? {|pattern| path.index(pattern) } } end |
.names(file_path) ⇒ Object
104 105 106 |
# File 'lib/spatial_features/unzip.rb', line 104 def self.names(file_path) entries(file_path).collect(&:name) end |
.paths(file_path, find: nil, depth: 0, **extract_options) ⇒ Object
19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
# File 'lib/spatial_features/unzip.rb', line 19 def self.paths(file_path, find: nil, depth: 0, **) paths = extract(file_path, **) if find = Array.wrap(find).presence matches = matching_paths(paths, find) # Only unwrap nested archives once the archive has yielded nothing usable, so # archives that already import keep their current behaviour exactly. matches = paths_in_nested_archives(paths, find: find, depth: depth, **) if matches.empty? raise(PathNotFound.new("Archive did not contain a file matching #{find}", paths)) if matches.empty? paths = matches end return Array(paths) end |
.paths_in_nested_archives(paths, find:, depth:, tmpdir: nil, **extract_options) ⇒ Object
Unwrap any archives the archive itself contained and search those instead. Each one
extracts into its own directory so identically named entries (doc.kml in several
KMZs) don't overwrite one another, and so a shapefile's sibling components stay
beside it for Validation.validate_shapefile!.
42 43 44 45 46 47 48 49 50 51 52 53 |
# File 'lib/spatial_features/unzip.rb', line 42 def self.paths_in_nested_archives(paths, find:, depth:, tmpdir: nil, **) return [] unless depth < MAX_NESTING_DEPTH paths.grep(NESTED_ARCHIVE_PATTERN).flat_map do |archive| begin paths(archive, find: find, depth: depth + 1, tmpdir: Dir.mktmpdir(nil, File.dirname(archive)), **) rescue PathNotFound, Zip::Error [] # This nested archive held nothing we can use; the others may still. end end end |