Class: Omnizip::Formats::Xar::Reader

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

Overview

XAR archive reader

Provides read access to XAR archives with support for:

  • Multiple compression algorithms (gzip, bzip2, lzma, xz, none)
  • Checksum verification
  • Extended attributes
  • Hardlinks and symlinks

Constant Summary

Constants included from Constants

Constants::CHECKSUM_ALGORITHMS, Constants::CHECKSUM_NAMES, Constants::CHECKSUM_SIZES, Constants::CKSUM_MD5, Constants::CKSUM_NONE, Constants::CKSUM_OTHER, Constants::CKSUM_SHA1, Constants::COMPRESSION_BZIP2, Constants::COMPRESSION_GZIP, Constants::COMPRESSION_LZMA, Constants::COMPRESSION_MIME_TYPES, Constants::COMPRESSION_NONE, Constants::COMPRESSION_XZ, Constants::DEFAULT_COMPRESSION, Constants::DEFAULT_COMPRESSION_LEVEL, Constants::DEFAULT_FILE_CHECKSUM, Constants::DEFAULT_TOC_CHECKSUM, Constants::HEADER_CKSUM_ALG_OFFSET, Constants::HEADER_MAGIC_OFFSET, Constants::HEADER_SIZE, Constants::HEADER_SIZE_EX, Constants::HEADER_SIZE_OFFSET, Constants::HEADER_TOC_COMPRESSED_OFFSET, Constants::HEADER_TOC_UNCOMPRESSED_OFFSET, Constants::HEADER_VERSION_OFFSET, Constants::MAGIC, Constants::MAGIC_BYTES, Constants::MAX_PATH_LENGTH, Constants::MIME_TYPE_TO_COMPRESSION, Constants::TOC_XML_DECLARATION, Constants::TYPE_BLOCK, Constants::TYPE_CHAR, Constants::TYPE_DIRECTORY, Constants::TYPE_FIFO, Constants::TYPE_FILE, Constants::TYPE_HARDLINK, Constants::TYPE_SOCKET, Constants::TYPE_SYMLINK, Constants::XAR_VERSION

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(file_path) ⇒ Reader

Initialize reader

Parameters:

  • file_path (String)

    Path to XAR file



61
62
63
64
65
66
67
68
# File 'lib/omnizip/formats/xar/reader.rb', line 61

def initialize(file_path)
  @file_path = file_path
  @file = nil
  @header = nil
  @toc = nil
  @entries = []
  @heap_offset = 0
end

Instance Attribute Details

#entriesObject (readonly)

Returns the value of attribute entries.



19
20
21
# File 'lib/omnizip/formats/xar/reader.rb', line 19

def entries
  @entries
end

#file_pathObject (readonly)

Returns the value of attribute file_path.



19
20
21
# File 'lib/omnizip/formats/xar/reader.rb', line 19

def file_path
  @file_path
end

#headerObject (readonly)

Returns the value of attribute header.



19
20
21
# File 'lib/omnizip/formats/xar/reader.rb', line 19

def header
  @header
end

#tocObject (readonly)

Returns the value of attribute toc.



19
20
21
# File 'lib/omnizip/formats/xar/reader.rb', line 19

def toc
  @toc
end

Class Method Details

.extract(file_path, output_dir) ⇒ Object

Extract XAR archive to directory

Parameters:

  • file_path (String)

    Path to XAR file

  • output_dir (String)

    Output directory



52
53
54
55
56
# File 'lib/omnizip/formats/xar/reader.rb', line 52

def self.extract(file_path, output_dir)
  open(file_path) do |reader| # rubocop:disable Security/Open
    reader.extract_all(output_dir)
  end
end

.list(file_path) ⇒ Array<Entry>

Read and list entries from XAR archive

Parameters:

  • file_path (String)

    Path to XAR file

Returns:

  • (Array<Entry>)

    List of entries



44
45
46
# File 'lib/omnizip/formats/xar/reader.rb', line 44

def self.list(file_path)
  open(file_path, &:entries) # rubocop:disable Security/Open
end

.open(file_path) ⇒ Reader

Open a XAR archive for reading

Parameters:

  • file_path (String)

    Path to XAR file

Returns:

  • (Reader)

    Reader instance



25
26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/omnizip/formats/xar/reader.rb', line 25

def self.open(file_path)
  reader = new(file_path)
  reader.open

  if block_given?
    begin
      yield reader
    ensure
      reader.close
    end
  else
    reader
  end
end

Instance Method Details

#closeObject

Close the archive



81
82
83
84
# File 'lib/omnizip/formats/xar/reader.rb', line 81

def close
  @file&.close
  @file = nil
end

#extract_all(output_dir) ⇒ Object

Extract all entries to directory

Parameters:

  • output_dir (String)

    Output directory



128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/omnizip/formats/xar/reader.rb', line 128

def extract_all(output_dir)
  FileUtils.mkdir_p(output_dir)

  # Sort entries to ensure directories are created first
  sorted_entries = @entries.sort_by do |e|
    [e.directory? ? 0 : 1, e.name]
  end

  sorted_entries.each do |entry|
    extract_entry(entry, output_dir)
  end
end

#extract_entry(entry, output_dir) ⇒ Object

Extract single entry to directory

Parameters:

  • entry (Entry)

    Entry to extract

  • output_dir (String)

    Output directory



145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
# File 'lib/omnizip/formats/xar/reader.rb', line 145

def extract_entry(entry, output_dir)
  full_path = File.join(output_dir, entry.name)

  case entry.type
  when TYPE_DIRECTORY
    FileUtils.mkdir_p(full_path)
  when TYPE_SYMLINK
    extract_symlink(entry, full_path)
  when TYPE_HARDLINK
    extract_hardlink(entry, full_path, output_dir)
  when TYPE_FILE
    extract_file(entry, full_path)
  when TYPE_BLOCK, TYPE_CHAR
    extract_device(entry, full_path)
  when TYPE_FIFO
    extract_fifo(entry, full_path)
  else
    # Unknown type, try to extract as file
    extract_file(entry, full_path) if entry.data_size&.positive?
  end

  # Set file metadata
  (entry, full_path)
end

#get_entry(name) ⇒ Entry?

Get entry by name

Parameters:

  • name (String)

    Entry name

Returns:

  • (Entry, nil)

    Entry or nil if not found



104
105
106
# File 'lib/omnizip/formats/xar/reader.rb', line 104

def get_entry(name)
  @entries.find { |e| e.name == name }
end

#infoHash

Get archive information

Returns:

  • (Hash)

    Archive info



173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/omnizip/formats/xar/reader.rb', line 173

def info
  {
    file_path: @file_path,
    header: {
      version: @header&.version,
      checksum_algorithm: @header&.checksum_algorithm_name,
      toc_compressed_size: @header&.toc_compressed_size,
      toc_uncompressed_size: @header&.toc_uncompressed_size,
    },
    entry_count: @entries.size,
    file_count: @entries.count(&:file?),
    directory_count: @entries.count(&:directory?),
    symlink_count: @entries.count(&:symlink?),
    total_size: @entries.sum { |e| e.size || 0 },
  }
end

#listArray<Entry>

Get all entries

Returns:

  • (Array<Entry>)

    List of entries



96
97
98
# File 'lib/omnizip/formats/xar/reader.rb', line 96

def list
  @entries
end

#openReader

Open and parse the archive

Returns:



73
74
75
76
77
78
# File 'lib/omnizip/formats/xar/reader.rb', line 73

def open
  @file = File.open(@file_path, "rb")
  read_header
  read_toc
  self
end

#open?Boolean

Check if archive is open

Returns:

  • (Boolean)

    true if open



89
90
91
# File 'lib/omnizip/formats/xar/reader.rb', line 89

def open?
  !@file.nil?
end

#read_data(entry) ⇒ String?

Read entry data

Parameters:

  • entry (Entry)

    Entry to read

Returns:

  • (String, nil)

    Entry data or nil if no data



112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/omnizip/formats/xar/reader.rb', line 112

def read_data(entry)
  return nil unless entry.data_length&.positive?
  return nil unless @file

  @file.seek(@heap_offset + entry.data_offset)
  # In XAR format:
  # - data_length is the compressed (archived) size (what to read from heap)
  # - data_size is the uncompressed (extracted) size (decompressed size)
  compressed_data = @file.read(entry.data_length)

  decompress_data(compressed_data, entry.data_encoding, entry.data_size)
end