Class: Uniword::Infrastructure::ZipPackager
- Inherits:
-
Object
- Object
- Uniword::Infrastructure::ZipPackager
- Defined in:
- lib/uniword/infrastructure/zip_packager.rb
Overview
Packages content into ZIP archives (e.g., DOCX files).
Responsibility: Handle ZIP file creation and packaging operations. Does NOT handle: Document serialization or format-specific logic.
DOCX files are ZIP archives containing XML files and media. This class provides low-level ZIP packaging functionality.
Instance Method Summary collapse
-
#add_file(zip_path, entry_path, entry_content) ⇒ void
Add a file to an existing ZIP archive.
-
#package(content, output_path) ⇒ void
Package content into a ZIP file.
-
#remove_file(zip_path, entry_path) ⇒ Boolean
Remove a file from a ZIP archive.
Instance Method Details
#add_file(zip_path, entry_path, entry_content) ⇒ void
On Windows, we must close the original ZIP handle before
This method returns an undefined value.
Add a file to an existing ZIP archive.
attempting to overwrite it. We extract content first, then close the handle, then package to a temp file and move.
90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 |
# File 'lib/uniword/infrastructure/zip_packager.rb', line 90 def add_file(zip_path, entry_path, entry_content) validate_zip_path(zip_path) raise ArgumentError, "Entry path cannot be nil" if entry_path.nil? raise ArgumentError, "Entry path cannot be empty" if entry_path.empty? # Extract existing content into a local variable content = {} Zip::File.open(zip_path) do |zip_file| zip_file.each do |entry| next if entry.directory? content[entry.name] = entry.get_input_stream.read end end # Handle is now fully closed before we modify the file # Add new entry and write to a temp file first, then move content[entry_path] = entry_content write_to_zip_file(content, zip_path) end |
#package(content, output_path) ⇒ void
On Windows, Zip::File::CREATE mode fails when target exists
This method returns an undefined value.
Package content into a ZIP file.
because Rubyzip tries to atomically rename a temp file over it. We use a temp file approach to avoid this issue.
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
# File 'lib/uniword/infrastructure/zip_packager.rb', line 34 def package(content, output_path) validate_content(content) validate_output_path(output_path) # Ensure output directory exists FileUtils.mkdir_p(File.dirname(output_path)) # Use Zip::OutputStream directly (not Zip::File) so that Entry objects # with internal_file_attributes=0 are preserved through serialization. # Zip::File wraps entries in StreamableStream which is NOT kind_of?(Entry), # causing put_next_entry to discard our Entry and create a fresh one. temp_path = "#{output_path}.#{Process.pid}.tmp" was_zip64 = Zip.write_zip64_support Zip.write_zip64_support = false begin Zip::OutputStream.open(temp_path) do |zos| content.each do |entry_path, entry_content| entry = Zip::Entry.new(temp_path, entry_path.to_s) entry.internal_file_attributes = 0 entry.external_file_attributes = 0 entry.fstype = Zip::FSTYPE_FAT zos.put_next_entry(entry) final_content = if entry_content.encoding == Encoding::ASCII_8BIT entry_content else entry_content.encode( "UTF-8", invalid: :replace, undef: :replace ) end zos.write(final_content) end end ensure Zip.write_zip64_support = was_zip64 end move_temp_to_output(temp_path, output_path) ensure FileUtils.rm_f(temp_path) if defined?(temp_path) && temp_path && File.exist?(temp_path) end |
#remove_file(zip_path, entry_path) ⇒ Boolean
On Windows, we must close the original ZIP handle before
Remove a file from a ZIP archive.
attempting to overwrite it. We extract content first, then close the handle, then write to a temp file and move.
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 |
# File 'lib/uniword/infrastructure/zip_packager.rb', line 121 def remove_file(zip_path, entry_path) validate_zip_path(zip_path) # Extract existing content into a local variable content = {} found = false Zip::File.open(zip_path) do |zip_file| zip_file.each do |entry| next if entry.directory? if entry.name == entry_path found = true else content[entry.name] = entry.get_input_stream.read end end end # Handle is now fully closed before we modify the file return false unless found write_to_zip_file(content, zip_path) true end |