omnizip 0.4.9-arm-linux → 0.4.11-arm-linux

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop-oss.yml +814 -0
  3. data/.rubocop.yml +15 -1
  4. data/.rubocop_todo.yml +7 -1
  5. data/CHANGELOG.md +17 -0
  6. data/Gemfile +23 -1
  7. data/README.adoc +101 -1282
  8. data/docs/compatibility.adoc +1 -2
  9. data/docs/lychee.toml +1 -7
  10. data/lib/omnizip/algorithms/deflate/constants.rb +5 -3
  11. data/lib/omnizip/implementations/rust/archive.rb +9 -41
  12. data/lib/omnizip/implementations/rust/ffi_library.rb +127 -0
  13. data/lib/omnizip/implementations/rust/fiddle_library.rb +144 -0
  14. data/lib/omnizip/implementations/rust/library.rb +39 -104
  15. data/lib/omnizip/implementations/rust.rb +2 -0
  16. data/lib/omnizip/version.rb +1 -1
  17. data/lib/tasks/docs.rake +146 -0
  18. data/readme-docs/rar-archives.adoc +505 -0
  19. data/scripts/generate_rar_oracle_fixtures.rb +177 -0
  20. data/vendor/libomnizip_ffi.so +0 -0
  21. metadata +21 -74
  22. data/TODO.refactor/00-overview.md +0 -67
  23. data/TODO.refactor/01-registry-base-class.md +0 -81
  24. data/TODO.refactor/02-error-hierarchy.md +0 -57
  25. data/TODO.refactor/03-autoload-entry-point.md +0 -69
  26. data/TODO.refactor/04-filter-base-consolidation.md +0 -49
  27. data/TODO.refactor/05-send-private-methods.md +0 -64
  28. data/TODO.refactor/06-instance-variable-access.md +0 -69
  29. data/TODO.refactor/07-respond-to-replacement.md +0 -237
  30. data/TODO.refactor/08-spec-doubles.md +0 -27
  31. data/TODO.refactor/09-cli-shared-module.md +0 -35
  32. data/TODO.refactor/10-format-detector-ocp.md +0 -34
  33. data/TODO.refactor/11-convenience-decoupling.md +0 -38
  34. data/TODO.refactor/12-thread-safety.md +0 -8
  35. data/TODO.refactor/13-lutaml-model-migration.md +0 -158
  36. data/TODO.refactor/14-add-missing-specs.md +0 -25
  37. data/docs/.github/workflows/docs.yml +0 -142
  38. data/docs/.gitignore +0 -21
  39. data/docs/Gemfile +0 -13
  40. data/docs/README.md +0 -101
  41. data/docs/_config.yml +0 -112
  42. data/docs/comparison/feature-matrix.adoc +0 -694
  43. data/docs/comparison/index.adoc +0 -113
  44. data/docs/comparison/vs-7zip.adoc +0 -309
  45. data/docs/comparison/vs-peazip.adoc +0 -77
  46. data/docs/comparison/vs-rubyzip.adoc +0 -342
  47. data/docs/comparison/vs-winrar.adoc +0 -100
  48. data/docs/concepts/index.adoc +0 -129
  49. data/docs/developer/architecture.adoc +0 -256
  50. data/docs/developer/contributing.adoc +0 -158
  51. data/docs/developer/index.adoc +0 -25
  52. data/docs/developer/testing.adoc +0 -212
  53. data/docs/getting-started/basic-usage.adoc +0 -263
  54. data/docs/getting-started/index.adoc +0 -42
  55. data/docs/getting-started/installation.adoc +0 -138
  56. data/docs/getting-started/quick-start.adoc +0 -183
  57. data/docs/getting-started/your-first-archive.adoc +0 -219
  58. data/docs/guides/advanced-features/encryption.adoc +0 -277
  59. data/docs/guides/advanced-features/index.adoc +0 -59
  60. data/docs/guides/advanced-features/parallel-processing.adoc +0 -144
  61. data/docs/guides/advanced-features/progress-tracking.adoc +0 -124
  62. data/docs/guides/advanced-features/streaming.adoc +0 -142
  63. data/docs/guides/archive-formats/gzip-format.adoc +0 -105
  64. data/docs/guides/archive-formats/index.adoc +0 -160
  65. data/docs/guides/archive-formats/ole-format.adoc +0 -151
  66. data/docs/guides/archive-formats/rar-format.adoc +0 -104
  67. data/docs/guides/archive-formats/rar5.adoc +0 -521
  68. data/docs/guides/archive-formats/rpm-format.adoc +0 -108
  69. data/docs/guides/archive-formats/seven-zip-format.adoc +0 -35
  70. data/docs/guides/archive-formats/tar-format.adoc +0 -106
  71. data/docs/guides/archive-formats/xz-format.adoc +0 -118
  72. data/docs/guides/archive-formats/zip-format.adoc +0 -35
  73. data/docs/guides/compression-algorithms/bzip2.adoc +0 -114
  74. data/docs/guides/compression-algorithms/deflate.adoc +0 -313
  75. data/docs/guides/compression-algorithms/index.adoc +0 -190
  76. data/docs/guides/compression-algorithms/lzma.adoc +0 -394
  77. data/docs/guides/compression-algorithms/lzma2.adoc +0 -244
  78. data/docs/guides/compression-algorithms/ppmd.adoc +0 -313
  79. data/docs/guides/compression-algorithms/zstandard.adoc +0 -296
  80. data/docs/guides/creating-archives.adoc +0 -348
  81. data/docs/guides/extracting-archives.adoc +0 -53
  82. data/docs/guides/format-conversion.adoc +0 -64
  83. data/docs/guides/index.adoc +0 -49
  84. data/docs/guides/migration-rubyzip.adoc +0 -217
  85. data/docs/guides/parity-archives.adoc +0 -605
  86. data/docs/guides/performance-tuning.adoc +0 -88
  87. data/docs/index.adoc +0 -228
  88. data/docs/reference/api/overview.adoc +0 -187
  89. data/docs/reference/cli/compress-command.adoc +0 -114
  90. data/docs/reference/cli/overview.adoc +0 -140
  91. data/docs/reference/index.adoc +0 -26
  92. data/docs/resources/faq.adoc +0 -185
  93. data/docs/resources/quick-reference.adoc +0 -222
  94. data/docs/troubleshooting/index.adoc +0 -207
@@ -0,0 +1,146 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Documentation pipeline — NO Jekyll. AsciiDoc sources (readme-docs/ and
4
+ # docs/) are rendered to static HTML by Coradoc (coradoc-html's Static
5
+ # converter: TOC, HTML5 layout, templates). Output lands in docs/site/.
6
+ #
7
+ # rake docs:build — render all .adoc pages into docs/site/
8
+ # rake docs:index — regenerate docs/site/index.html only
9
+ # rake docs:links — lychee link check over the built site
10
+ #
11
+ # Coradoc is a transformation library, not an SSG; this task is the thin
12
+ # multi-page orchestrator (page walk + nav index) per its architecture
13
+ # decision (FEATURE-template-renderer: emitters live downstream).
14
+
15
+ require "fileutils"
16
+ require "pathname"
17
+
18
+ DOCS_PAGES = %w[
19
+ README.adoc
20
+ readme-docs/installation.adoc
21
+ readme-docs/cli-usage.adoc
22
+ readme-docs/api-usage.adoc
23
+ readme-docs/compression-algorithms.adoc
24
+ readme-docs/compression-profiles.adoc
25
+ readme-docs/format-converter.adoc
26
+ readme-docs/advanced-features.adoc
27
+ readme-docs/archive-formats.adoc
28
+ readme-docs/rar-archives.adoc
29
+ readme-docs/par2-archives.adoc
30
+ readme-docs/encryption-checksums.adoc
31
+ readme-docs/preprocessing-filters.adoc
32
+ readme-docs/architecture.adoc
33
+ readme-docs/performance-profiler.adoc
34
+ docs/compatibility.adoc
35
+ docs/rust-backend.md
36
+ docs/RAR_WRITE_SUPPORT.md
37
+ docs/xar_format.md
38
+ ].freeze
39
+
40
+ DOCS_SITE = File.expand_path("docs/site", "#{__dir__}/../..")
41
+
42
+ def docs_render_page(src, dest, title)
43
+ require "coradoc/asciidoc" # registers the :asciidoc parse format
44
+ require "coradoc/html"
45
+
46
+ html =
47
+ if src.end_with?(".md")
48
+ # Markdown sources ride along verbatim (pre-wrapped) until they are
49
+ # converted to AsciiDoc.
50
+ <<~HTML
51
+ <!DOCTYPE html>
52
+ <html lang="en"><head><meta charset="utf-8"><title>#{title}</title></head>
53
+ <body><pre>#{File.read(src).gsub(/[<>&]/, '&' => '&amp;', '<' => '&lt;', '>' => '&gt;')}</pre></body></html>
54
+ HTML
55
+ else
56
+ doc = Coradoc.parse(File.read(src), format: :asciidoc)
57
+ Coradoc::Html::Static.convert(
58
+ doc,
59
+ include_toc: true,
60
+ toc_levels: 2,
61
+ meta_tags: { title: title },
62
+ )
63
+ end
64
+ # AsciiDoc sources cross-link *.adoc siblings; the site serves .html.
65
+ # The repo README renders as the site root index.html. Hrefs are emitted
66
+ # relative to each page's own directory (the depth prefix), which is what
67
+ # both browsers and lychee-without-base-url resolve against.
68
+ depth_prefix = Pathname.new(DOCS_SITE).relative_path_from(
69
+ Pathname.new(dest).dirname,
70
+ ).to_s
71
+ depth_prefix = "" if depth_prefix == "."
72
+ depth_prefix = "#{depth_prefix}/" unless depth_prefix.empty?
73
+ html = html.gsub(/href="([^"]+?)\.(?:adoc|md)(#[^"]*)?"/) do
74
+ target = Regexp.last_match(1)
75
+ anchor = Regexp.last_match(2).to_s.delete_prefix("#")
76
+ anchor = "##{anchor}" unless anchor.empty?
77
+ stripped = target.sub(%r{\A(?:\.\./)+}, "")
78
+ if stripped.end_with?("/README", "README") && !stripped.include?("docs/")
79
+ %(href="#{depth_prefix}index.html#{anchor}")
80
+ elsif File.exist?("#{DOCS_SITE}/#{stripped}.html")
81
+ %(href="#{depth_prefix}#{stripped}.html#{anchor}")
82
+ else
83
+ Regexp.last_match(0)
84
+ end
85
+ end
86
+ File.write(dest, html)
87
+ end
88
+
89
+ def docs_page_title(path)
90
+ File.read(path).lines.each do |line|
91
+ return Regexp.last_match(1).strip if line =~ /\A=+\s+(.+)\z/
92
+ break if line !~ /\A\s*\z/ && !line.start_with?("=")
93
+ end
94
+ File.basename(path, ".*")
95
+ end
96
+
97
+ namespace :docs do
98
+ desc "Render all documentation pages into docs/site/ (Coradoc, no Jekyll)"
99
+ task :build do
100
+ FileUtils.rm_rf(DOCS_SITE)
101
+ FileUtils.mkdir_p(DOCS_SITE)
102
+
103
+ nav = []
104
+ DOCS_PAGES.each do |page|
105
+ next unless File.exist?(page)
106
+
107
+ out = File.join(DOCS_SITE,
108
+ page == "README.adoc" ? "index.html" : "#{page.sub('.adoc', '').sub('.md', '')}.html")
109
+ FileUtils.mkdir_p(File.dirname(out))
110
+ title = docs_page_title(page)
111
+ docs_render_page(page, out, title)
112
+ nav << [title, out.delete_prefix("#{DOCS_SITE}/")]
113
+ puts "rendered #{page} -> #{out}"
114
+ end
115
+
116
+ Rake::Task["docs:index"].reenable
117
+ Rake::Task["docs:index"].invoke(nav)
118
+ end
119
+
120
+ desc "Regenerate the site index page"
121
+ task :index, [:nav] do |_t, args|
122
+ nav = args[:nav] || DOCS_PAGES.map do |p|
123
+ rel = p == "README.adoc" ? "index" : p.sub(".adoc", "").sub(".md", "")
124
+ [docs_page_title(p), "#{rel}.html"]
125
+ end
126
+ links = nav.map { |title, href| %(<li><a href="#{href}">#{title}</a></li>) }.join("\n")
127
+ html = <<~HTML
128
+ <!DOCTYPE html>
129
+ <html lang="en">
130
+ <head><meta charset="utf-8"><title>Omnizip documentation</title></head>
131
+ <body>
132
+ <h1>Omnizip documentation</h1>
133
+ <ul>
134
+ #{links}
135
+ </ul>
136
+ </body>
137
+ </html>
138
+ HTML
139
+ File.write(File.join(DOCS_SITE, "index.html"), html)
140
+ end
141
+
142
+ desc "Link-check the built site with lychee"
143
+ task :links do
144
+ sh "lychee --config docs/lychee.toml docs/site || true"
145
+ end
146
+ end
@@ -0,0 +1,505 @@
1
+ = RAR archives — RAR4 and RAR5
2
+
3
+ Extracted from the README; covers reading, writing, compression methods,
4
+ encryption, multi-volume, solid archives, and PAR2 recovery records.
5
+
6
+ ==== RAR Archives
7
+
8
+ ==== RAR4 Archives (v0.3.0)
9
+
10
+ RAR4 reading and writing against the on-disk format unrar accepts:
11
+ headers carry the low 16 bits of a CRC32, HEAD_SIZE counts the whole
12
+ block, and the dictionary mask 0xE0 marks directory entries. STORE
13
+ archives round-trip through official unrar byte-identically.
14
+
15
+ ====== Reading RAR4 Archives
16
+
17
+ [source,ruby]
18
+ ----
19
+ require 'omnizip'
20
+
21
+ reader = Omnizip::Formats::Rar::Reader.new('archive.rar')
22
+ reader.open
23
+
24
+ reader.list_files.each do |entry|
25
+ puts "#{entry.name}: #{entry.size} bytes (#{entry.compressed_size} compressed)"
26
+ puts " Directory: #{entry.is_dir}"
27
+ puts " Modified: #{entry.mtime}"
28
+ end
29
+
30
+ # STORE data decodes natively; WinRAR-compressed entries fall back to
31
+ # the unrar command after a CRC check rejects foreign streams
32
+ reader.extract_entry('test.txt', 'output/test.txt')
33
+ reader.extract_all('output/')
34
+ ----
35
+
36
+ **RAR4 Reader Features:**
37
+
38
+ * ✅ Block header parsing (MAIN/FILE/ENDARC), directories, empty files
39
+ * ✅ Native STORE decompression with stored-CRC32 verification
40
+ * ✅ unrar fallback for real WinRAR-compressed entries (byte-exact)
41
+ * ✅ DOS-style backslash name normalization to forward slashes
42
+ * ✅ Multi-volume archive detection
43
+ * ✅ Parsing verified against libarchive WinRAR fixtures
44
+
45
+ ====== Writing RAR4 Archives
46
+
47
+ [source,ruby]
48
+ ----
49
+ require 'omnizip'
50
+
51
+ # STORE output is verified interoperable with unrar
52
+ writer = Omnizip::Formats::Rar::Writer.new('archive.rar',
53
+ compression: :store
54
+ )
55
+ writer.add_file('document.txt')
56
+ writer.add_file('image.png')
57
+ writer.add_directory('photos/')
58
+ writer.write
59
+ ----
60
+
61
+ Writer options: `compression:` (see below), `test_after_create: true`
62
+ (reads the archive back and verifies every file). RAR4 encryption is
63
+ not implemented — `password:` raises `NotImplementedError`. The
64
+ `solid:`/`recovery:`/`volume_size:` options are accepted but not
65
+ implemented; they are ignored with a warning and never advertised in
66
+ header flags.
67
+
68
+ ====== RAR4 Compression Methods
69
+
70
+ [cols="2,2,2,4",options="header"]
71
+ |===
72
+ |Method |Speed |unrar interop |Notes
73
+ |`:store` |Instant |✅ verified |Byte-identical round-trip incl. directories
74
+ |`:fastest` |Very Fast |❌ Omnizip-only |LZ77+Huffman, readable by Omnizip
75
+ |`:normal` (default) |Fast |❌ Omnizip-only |LZ77+Huffman, readable by Omnizip
76
+ |`:good` |Medium |❌ Omnizip-only |LZ77+Huffman, readable by Omnizip
77
+ |`:best` |Slow |❌ Omnizip-only |PPMd, readable by Omnizip
78
+ |===
79
+
80
+ Official RAR LZ/PPMd streams are proprietary; methods above `:store`
81
+ produce archives Omnizip can read back but unrar reports as checksum
82
+ errors. Use `:store` for interoperability.
83
+
84
+ ===== RAR5 Archives (v0.3.0)
85
+
86
+ Read/write support for RAR5 archives with STORE compression,
87
+ AES-256 encryption, and multi-volume archives — all verified against
88
+ official unrar.
89
+
90
+ ====== Reading RAR5 Archives
91
+
92
+ [source,ruby]
93
+ ----
94
+ require 'omnizip'
95
+
96
+ reader = Omnizip::Formats::Rar5::Reader.new
97
+ File.open('archive.rar', 'rb') do |io|
98
+ entries = reader.read_archive(io)
99
+
100
+ entries.each do |entry|
101
+ puts "#{entry.name}: #{entry.uncompressed_size} bytes"
102
+ puts " CRC32: #{entry.crc32.to_s(16)}" if entry.crc32
103
+ puts " Modified: #{entry.modified_time}" if entry.modified_time
104
+ end
105
+ end
106
+ ----
107
+
108
+ **RAR5 Reader Features:**
109
+
110
+ * ✅ STORE file data, header CRC32 verification
111
+ * ✅ 32-bit Unix mtime decoding (matches unrar; nil when absent)
112
+ * ✅ Unicode filenames (UTF-8)
113
+ * ✅ Multi-file archives
114
+ * ✅ VInt (variable-length integer) parsing
115
+ * ✅ Graceful error handling for truncated/invalid files
116
+ * ✅ libarchive compatibility fixtures
117
+
118
+ ====== Writing RAR5 Archives
119
+
120
+ [source,ruby]
121
+ ----
122
+ require 'omnizip'
123
+
124
+ # STORE compression (default) — unrar-verified
125
+ writer = Omnizip::Formats::Rar::Rar5::Writer.new('archive.rar')
126
+ writer.add_file('document.txt')
127
+ writer.add_file('image.png')
128
+ writer.write
129
+
130
+ # AES-256 encryption
131
+ writer = Omnizip::Formats::Rar::Rar5::Writer.new('archive.rar',
132
+ password: 'secret'
133
+ )
134
+ writer.add_file('data.json')
135
+ writer.write
136
+
137
+ # Multi-volume archives split at 64 KB boundaries
138
+ writer = Omnizip::Formats::Rar::Rar5::Writer.new('archive.rar',
139
+ multi_volume: true,
140
+ volume_size: 65536
141
+ )
142
+ writer.add_file('big1.bin')
143
+ writer.add_file('big2.bin')
144
+ writer.write # writes archive.part1.rar, archive.part2.rar, ...
145
+ ----
146
+
147
+ ====== RAR5 Compression Methods
148
+
149
+ [cols="2,2,4",options="header"]
150
+ |===
151
+ |Method |Interop |Notes
152
+ |`:store` (default) |✅ unrar-verified |Uncompressed passthrough
153
+ |`:lzss` / `:lzma` |falls back to STORE |Encoder is not official-RAR compatible; warns and stores
154
+ |`:auto` |STORE |Size-based selection
155
+ |===
156
+
157
+ ====== RAR5 Optional Fields
158
+
159
+ The writer can embed metadata that readers use without decompressing:
160
+
161
+ [source,ruby]
162
+ ----
163
+ writer = Omnizip::Formats::Rar::Rar5::Writer.new('archive.rar',
164
+ include_mtime: true, # 32-bit Unix mtime per file header
165
+ include_crc32: true # CRC32 per file header
166
+ )
167
+ ----
168
+
169
+ ====== RAR5 Optional Fields
170
+
171
+ RAR5 supports optional metadata fields for enhanced archive information:
172
+
173
+ [source,ruby]
174
+ ----
175
+ require 'omnizip/formats/rar/rar5/writer'
176
+
177
+ # Include modification time in archive
178
+ writer = Omnizip::Formats::Rar::Rar5::Writer.new('archive.rar',
179
+ include_mtime: true # Preserves file modification timestamps
180
+ )
181
+ writer.add_file('document.txt')
182
+ writer.write
183
+
184
+ # Include CRC32 checksums (STORE compression only)
185
+ writer = Omnizip::Formats::Rar::Rar5::Writer.new('archive.rar',
186
+ compression: :store,
187
+ include_crc32: true # Only works with STORE compression
188
+ )
189
+ writer.add_file('data.bin')
190
+ writer.write
191
+
192
+ # IMPORTANT: CRC32 with LZMA is automatically disabled
193
+ writer = Omnizip::Formats::Rar::Rar5::Writer.new('archive.rar',
194
+ compression: :lzma,
195
+ level: 5,
196
+ include_crc32: true # Will be auto-disabled, no error
197
+ )
198
+ writer.add_file('document.txt')
199
+ writer.write
200
+ # CRC32 will be silently disabled - archive uses BLAKE2sp only
201
+ ----
202
+
203
+ **CRC32 Limitation Explained:**
204
+
205
+ RAR5's optional CRC32 field is incompatible with compression algorithms. The RAR5 format specification requires that when compression is used (LZMA, LZMA2), only the BLAKE2sp checksum (always present in the main file header) should be used for integrity verification. The optional CRC32 field is designed for uncompressed (STORE) files only.
206
+
207
+ When you request `include_crc32: true` with LZMA compression, Omnizip automatically disables CRC32 to ensure format compliance and compatibility with official unrar tools.
208
+
209
+ ====== RAR5 Multi-Volume Archives
210
+
211
+ Create split archives when file size exceeds volume limit. Volumes are automatically created and numbered according to the chosen naming pattern.
212
+
213
+ [source,ruby]
214
+ ----
215
+ require 'omnizip/formats/rar/rar5/writer'
216
+
217
+ # Create multi-volume archive with default settings
218
+ writer = Omnizip::Formats::Rar::Rar5::Writer.new('archive.rar',
219
+ multi_volume: true,
220
+ volume_size: '10M' # Human-readable size
221
+ )
222
+ writer.add_directory('data/')
223
+ volumes = writer.write # Returns array of volume paths
224
+ # => ['archive.part1.rar', 'archive.part2.rar', ...]
225
+
226
+ # Custom volume naming pattern
227
+ writer = Omnizip::Formats::Rar::Rar5::Writer.new('backup.rar',
228
+ multi_volume: true,
229
+ volume_size: '100M',
230
+ volume_naming: 'volume' # backup.volume1.rar, backup.volume2.rar
231
+ )
232
+ writer.add_directory('large_dataset/')
233
+ volumes = writer.write
234
+
235
+ # Numeric naming pattern
236
+ writer = Omnizip::Formats::Rar::Rar5::Writer.new('data.rar',
237
+ multi_volume: true,
238
+ volume_size: '50M',
239
+ volume_naming: 'numeric' # data.001.rar, data.002.rar
240
+ )
241
+ writer.add_file('huge_file.bin')
242
+ volumes = writer.write
243
+ ----
244
+
245
+ **Volume Naming Patterns:**
246
+
247
+ * `part` (default): archive.part1.rar, archive.part2.rar, ...
248
+ * `volume`: archive.volume1.rar, archive.volume2.rar, ...
249
+ * `numeric`: archive.001.rar, archive.002.rar, ...
250
+
251
+ **Human-Readable Sizes:**
252
+
253
+ * Bytes: `1024`, `2048`
254
+ * Kilobytes: `10K`, `100KB`
255
+ * Megabytes: `10M`, `100MB`
256
+ * Gigabytes: `1G`, `5GB`
257
+
258
+ **Minimum volume size:** 64 KB (65,536 bytes)
259
+
260
+ ====== RAR5 Solid Compression
261
+
262
+ Compress multiple files with a shared dictionary for significantly better compression ratios. Ideal for similar files such as source code, logs, or document collections.
263
+
264
+ [source,ruby]
265
+ ----
266
+ require 'omnizip/formats/rar/rar5/writer'
267
+
268
+ # Enable solid compression (default: 10-30% better ratio)
269
+ writer = Omnizip::Formats::Rar::Rar5::Writer.new('archive.rar',
270
+ compression: :lzma,
271
+ level: 5,
272
+ solid: true # Enable solid mode
273
+ )
274
+ writer.add_directory('project/')
275
+ writer.write
276
+
277
+ # Combine with high compression level
278
+ writer = Omnizip::Formats::Rar::Rar5::Writer.new('backup.rar',
279
+ compression: :lzma,
280
+ level: 5, # Best compression
281
+ solid: true # Shared dictionary
282
+ )
283
+ writer.add_file('log1.txt')
284
+ writer.add_file('log2.txt')
285
+ writer.add_file('log3.txt')
286
+ writer.write
287
+ ----
288
+
289
+ **Benefits:**
290
+
291
+ * 10-30% better compression ratios for similar files
292
+ * Larger LZMA dictionaries (16-64 MB vs 1-16 MB)
293
+ * Particularly effective for:
294
+ - Source code repositories
295
+ - Log files and text documents
296
+ - Similar structured data
297
+
298
+ **Trade-offs:**
299
+
300
+ * Cannot extract individual files without decompressing entire solid block
301
+ * Corruption in one file may affect subsequent files in the block
302
+ * Slightly longer extraction time for single files
303
+ * Best for archiving complete collections
304
+
305
+ ====== RAR5 AES-256 Encryption
306
+
307
+ Protect archives with industry-standard AES-256-CBC encryption and PBKDF2-HMAC-SHA256 key derivation.
308
+
309
+ [source,ruby]
310
+ ----
311
+ require 'omnizip/formats/rar/rar5/writer'
312
+
313
+ # Basic password protection
314
+ writer = Omnizip::Formats::Rar::Rar5::Writer.new('archive.rar',
315
+ compression: :lzma,
316
+ password: 'SecurePassword123!'
317
+ )
318
+ writer.add_file('confidential.pdf')
319
+ writer.write
320
+
321
+ # Custom key derivation iterations
322
+ writer = Omnizip::Formats::Rar::Rar5::Writer.new('secure.rar',
323
+ compression: :lzma,
324
+ level: 5,
325
+ password: 'VerySecurePassword2025!',
326
+ kdf_iterations: 524_288 # Higher = more secure, slower
327
+ )
328
+ writer.add_directory('sensitive_data/')
329
+ writer.write
330
+
331
+ # Minimum security (faster but less secure)
332
+ writer = Omnizip::Formats::Rar::Rar5::Writer.new('quick.rar',
333
+ password: 'FastPassword',
334
+ kdf_iterations: 65_536 # Minimum allowed
335
+ )
336
+ writer.add_file('temp.txt')
337
+ writer.write
338
+ ----
339
+
340
+ **Security Features:**
341
+
342
+ * **AES-256-CBC encryption** with PKCS#7 padding
343
+ * **PBKDF2-HMAC-SHA256** key derivation function
344
+ * **Configurable KDF iterations:**
345
+ - Minimum: 65,536 (2^16) - fast but less secure
346
+ - Default: 262,144 (2^18) - balanced security/performance
347
+ - Maximum: 1,048,576 (2^20) - maximum security
348
+ * **Per-file IV generation** for enhanced security
349
+ * **Password verification** before decryption attempts
350
+
351
+ **Performance Impact:**
352
+
353
+ * Encryption overhead: < 2x slower than unencrypted
354
+ * KDF computation time varies with iteration count:
355
+ - 65,536 iterations: ~50-100ms
356
+ - 262,144 iterations: ~200-400ms
357
+ - 1,048,576 iterations: ~800-1600ms
358
+
359
+ ====== RAR5 PAR2 Recovery Records
360
+
361
+ Generate PAR2 parity files for archive recovery and error correction using Reed-Solomon codes.
362
+
363
+ [source,ruby]
364
+ ----
365
+ require 'omnizip/formats/rar/rar5/writer'
366
+
367
+ # Enable recovery with default 5% redundancy
368
+ writer = Omnizip::Formats::Rar::Rar5::Writer.new('archive.rar',
369
+ compression: :lzma,
370
+ recovery: true
371
+ )
372
+ writer.add_directory('important_data/')
373
+ files = writer.write
374
+ # => ['archive.rar', 'archive.par2', 'archive.vol00+01.par2', ...]
375
+
376
+ # Custom redundancy percentage
377
+ writer = Omnizip::Formats::Rar::Rar5::Writer.new('backup.rar',
378
+ compression: :lzma,
379
+ level: 5,
380
+ recovery: true,
381
+ recovery_percent: 10 # 10% redundancy (can recover up to 10% data loss)
382
+ )
383
+ writer.add_file('critical.db')
384
+ files = writer.write
385
+
386
+ # Maximum redundancy for critical data
387
+ writer = Omnizip::Formats::Rar::Rar5::Writer.new('critical.rar',
388
+ compression: :lzma,
389
+ recovery: true,
390
+ recovery_percent: 50 # 50% redundancy (maximum protection)
391
+ )
392
+ writer.add_directory('mission_critical/')
393
+ files = writer.write
394
+ ----
395
+
396
+ **Recovery Capabilities:**
397
+
398
+ * **Detect corruption** at block level
399
+ * **Repair damaged archives** automatically
400
+ * **Recover from partial data loss** up to redundancy percentage
401
+ * **Works with all features:**
402
+ - Multi-volume archives
403
+ - Solid compression
404
+ - Encrypted archives
405
+ * **Reed-Solomon error correction** over GF(2^16)
406
+
407
+ **Redundancy Guidelines:**
408
+
409
+ * 5% (default): Suitable for general backups
410
+ * 10%: Recommended for important data
411
+ * 20-30%: High-value data requiring extra protection
412
+ * 50-100%: Critical data with maximum recovery needs
413
+
414
+ **PAR2 File Size:**
415
+
416
+ PAR2 files add approximately the redundancy percentage to total archive size. For example, a 100MB archive with 10% redundancy will generate ~10MB of PAR2 files.
417
+
418
+ ====== RAR5 Combined Features
419
+
420
+ All RAR5 features can be used together for comprehensive archive protection:
421
+
422
+ [source,ruby]
423
+ ----
424
+ require 'omnizip/formats/rar/rar5/writer'
425
+
426
+ # Complete feature demonstration
427
+ writer = Omnizip::Formats::Rar::Rar5::Writer.new('complete.rar',
428
+ # Compression
429
+ compression: :lzma,
430
+ level: 5, # Best compression
431
+ solid: true, # Shared dictionary for better ratios
432
+
433
+ # Security
434
+ password: 'SecureBackup2025!',
435
+ kdf_iterations: 524_288, # Enhanced security
436
+
437
+ # Multi-volume
438
+ multi_volume: true,
439
+ volume_size: '100M',
440
+ volume_naming: 'part',
441
+
442
+ # Recovery
443
+ recovery: true,
444
+ recovery_percent: 10,
445
+
446
+ # Optional fields
447
+ include_mtime: true
448
+ )
449
+
450
+ writer.add_directory('/critical/data')
451
+ files = writer.write
452
+ # => ['complete.part1.rar', 'complete.part2.rar', ...,
453
+ # 'complete.par2', 'complete.vol00+01.par2', ...]
454
+ ----
455
+
456
+ **Best Practices:**
457
+
458
+ 1. **Solid + LZMA level 5** for maximum compression on similar files
459
+ 2. **10-20% PAR2** for important data protection
460
+ 3. **262,144 KDF iterations** for balanced security/performance
461
+ 4. **Multi-volume** for large archives or optical media
462
+ 5. **Always include mtime** to preserve file timestamps
463
+
464
+ **Example: Secure Backup Archive**
465
+
466
+ [source,ruby]
467
+ ----
468
+ # Production-ready backup configuration
469
+ writer = Omnizip::Formats::Rar::Rar5::Writer.new('backup_2025-12-24.rar',
470
+ compression: :lzma,
471
+ level: 5,
472
+ solid: true, # 10-30% better compression
473
+ password: ENV['BACKUP_PASSWORD'] || 'DefaultSecure123!',
474
+ kdf_iterations: 262_144, # Balanced security
475
+ multi_volume: true,
476
+ volume_size: '4G', # DVD-sized volumes
477
+ recovery: true,
478
+ recovery_percent: 15, # 15% redundancy
479
+ include_mtime: true
480
+ )
481
+
482
+ writer.add_directory('/home/user/documents')
483
+ writer.add_directory('/home/user/projects')
484
+ files = writer.write
485
+
486
+ puts "Backup created: #{files.size} files"
487
+ puts "Total size: #{files.sum { |f| File.size(f) } / 1024 / 1024}MB"
488
+ ----
489
+
490
+ **Important:**
491
+ - Ensure you have set the `BACKUP_PASSWORD` environment variable before running the secure backup example.
492
+ - This example assumes a Linux/Unix environment; file paths may need adjustments for Windows.
493
+
494
+ **Security Note:**
495
+ - Use a strong, complex password for `BACKUP_PASSWORD`.
496
+ - Consider using a password manager to store and retrieve your backup password securely.
497
+ - If using this code in production, review the security implications and adjust as needed.
498
+
499
+ **Performance Note:**
500
+ - Encryption and KDF computations can be CPU-intensive.
501
+ - The `kdf_iterations` value affects security; higher values are more secure but slower.
502
+ - The `volume_naming` option can impact the efficiency and naming of multi-volume archives.
503
+
504
+ **Error Handling:**
505
+ - Enhance this example by adding error handling for file operations and encryption failures.