Class: Omnizip::Formats::SevenZip::Writer

Inherits:
Object
  • Object
show all
Includes:
Constants
Defined in:
lib/omnizip/formats/seven_zip/writer.rb

Overview

.7z archive writer - 7-Zip compatible implementation

Creates archives that are fully compatible with official 7-Zip (7zz command).

Archive structure:

  • Start Header (32 bytes)
  • LZMA2 compressed file data
  • UNCOMPRESSED Next Header metadata (properties: kHeader, kPackInfo, etc.)
  • Metadata footer (filename, attributes, timestamps)

Constant Summary collapse

COPY_MAIN_BYTE =

Constants for array literals used in loops (RuboCop Performance/CollectionLiteralInLoop)

[0x01].pack("C").freeze
COPY_METHOD_ID =
[0x00].pack("C").freeze
NULL_TERMINATOR =
[0x00, 0x00].pack("CC").freeze
FILE_ATTRIBUTE_ARCHIVE =
[0x20].pack("V").freeze

Constants included from Constants

Constants::MAJOR_VERSION, Constants::MAX_NUM_BONDS, Constants::MAX_NUM_CODERS, Constants::MAX_NUM_PACK_STREAMS, Constants::MINOR_VERSION, Constants::SIGNATURE, Constants::SIGNATURE_SIZE, Constants::START_HEADER_SIZE

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(output_path, options = {}) ⇒ Writer

Returns a new instance of Writer.



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/omnizip/formats/seven_zip/writer.rb', line 28

def initialize(output_path, options = {})
  @output_path = output_path
  @options = {
    algorithm: :lzma2,
    level: 5,
    dict_size: 8 * 1024 * 1024, # 8MB default dictionary
    solid: true, # Solid mode for LZMA2 compression
    filters: [],
    encrypt_headers: false,
  }.merge(options)
  @collector = FileCollector.new
  @entries = []

  # Preprocessing filters are decode-side only: the write
  # path does not apply them, so say so instead of silently
  # ignoring the option
  filters = @options[:filters]
  return unless filters && !filters.empty?

  warn "omnizip: the 7z writer does not apply filters " \
       "(#{Array(filters).join(', ')}); writing unfiltered data. " \
       "Filters apply when reading archives that contain them."
end

Instance Attribute Details

#entriesObject (readonly)

Returns the value of attribute entries.



26
27
28
# File 'lib/omnizip/formats/seven_zip/writer.rb', line 26

def entries
  @entries
end

#optionsObject (readonly)

Returns the value of attribute options.



26
27
28
# File 'lib/omnizip/formats/seven_zip/writer.rb', line 26

def options
  @options
end

#output_pathObject (readonly)

Returns the value of attribute output_path.



26
27
28
# File 'lib/omnizip/formats/seven_zip/writer.rb', line 26

def output_path
  @output_path
end

Instance Method Details

#add_data(archive_path, data, options = {}) ⇒ Object



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/omnizip/formats/seven_zip/writer.rb', line 65

def add_data(archive_path, data, options = {})
  data_str = data.is_a?(String) ? data : data.read

  entry = Models::FileEntry.new
  entry.name = archive_path
  entry.source_path = nil
  entry.size = data_str.bytesize
  entry.mtime = Time.now
  # Zero-length data has no stream: readers identify it as an
  # empty file through kEmptyStream + kEmptyFile.
  entry.has_stream = data_str.bytesize.positive?
  entry.inline_data = data_str
  entry.compression_options = options
  @entries << entry
end

#add_directory(dir_path, recursive = true) ⇒ Object



57
58
59
# File 'lib/omnizip/formats/seven_zip/writer.rb', line 57

def add_directory(dir_path, recursive = true)
  @collector.add_path(dir_path, recursive: recursive)
end

#add_directory_entry(archive_path) ⇒ self

Add an explicit directory entry with no data or stream

Parameters:

  • archive_path (String)

    Directory path inside the archive (trailing slash optional)

Returns:

  • (self)


86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/omnizip/formats/seven_zip/writer.rb', line 86

def add_directory_entry(archive_path)
  entry = Models::FileEntry.new
  # Trailing slash is preserved: explicit directory entries
  # carry it ("testdir/"), matching the collector's tree walk
  entry.name = archive_path
  entry.source_path = nil
  entry.size = 0
  entry.is_dir = true
  entry.has_stream = false
  entry.mtime = Time.now
  @entries << entry
  self
end

#add_file(file_path, archive_path = nil) ⇒ Object



52
53
54
55
# File 'lib/omnizip/formats/seven_zip/writer.rb', line 52

def add_file(file_path, archive_path = nil)
  @collector.add_path(file_path, archive_path: archive_path,
                                 recursive: false)
end

#add_files(pattern) ⇒ Object



61
62
63
# File 'lib/omnizip/formats/seven_zip/writer.rb', line 61

def add_files(pattern)
  @collector.add_glob(pattern)
end

#writeObject



100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/omnizip/formats/seven_zip/writer.rb', line 100

def write
  # Collect any files from the collector (if add_file/add_directory was used)
  collected_entries = @collector.collect_files
  # Merge with entries added via add_data
  @entries.concat(collected_entries)

  # Check if split archive requested
  if @options[:volume_size]
    write_split_archive
  else
    File.open(@output_path, "wb") do |io|
      write_archive(io)
    end
  end
end