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.
- checksums.yaml +4 -4
- data/.rubocop-oss.yml +814 -0
- data/.rubocop.yml +15 -1
- data/.rubocop_todo.yml +7 -1
- data/CHANGELOG.md +17 -0
- data/Gemfile +23 -1
- data/README.adoc +101 -1282
- data/docs/compatibility.adoc +1 -2
- data/docs/lychee.toml +1 -7
- data/lib/omnizip/algorithms/deflate/constants.rb +5 -3
- data/lib/omnizip/implementations/rust/archive.rb +9 -41
- data/lib/omnizip/implementations/rust/ffi_library.rb +127 -0
- data/lib/omnizip/implementations/rust/fiddle_library.rb +144 -0
- data/lib/omnizip/implementations/rust/library.rb +39 -104
- data/lib/omnizip/implementations/rust.rb +2 -0
- data/lib/omnizip/version.rb +1 -1
- data/lib/tasks/docs.rake +146 -0
- data/readme-docs/rar-archives.adoc +505 -0
- data/scripts/generate_rar_oracle_fixtures.rb +177 -0
- data/vendor/libomnizip_ffi.so +0 -0
- metadata +21 -74
- data/TODO.refactor/00-overview.md +0 -67
- data/TODO.refactor/01-registry-base-class.md +0 -81
- data/TODO.refactor/02-error-hierarchy.md +0 -57
- data/TODO.refactor/03-autoload-entry-point.md +0 -69
- data/TODO.refactor/04-filter-base-consolidation.md +0 -49
- data/TODO.refactor/05-send-private-methods.md +0 -64
- data/TODO.refactor/06-instance-variable-access.md +0 -69
- data/TODO.refactor/07-respond-to-replacement.md +0 -237
- data/TODO.refactor/08-spec-doubles.md +0 -27
- data/TODO.refactor/09-cli-shared-module.md +0 -35
- data/TODO.refactor/10-format-detector-ocp.md +0 -34
- data/TODO.refactor/11-convenience-decoupling.md +0 -38
- data/TODO.refactor/12-thread-safety.md +0 -8
- data/TODO.refactor/13-lutaml-model-migration.md +0 -158
- data/TODO.refactor/14-add-missing-specs.md +0 -25
- data/docs/.github/workflows/docs.yml +0 -142
- data/docs/.gitignore +0 -21
- data/docs/Gemfile +0 -13
- data/docs/README.md +0 -101
- data/docs/_config.yml +0 -112
- data/docs/comparison/feature-matrix.adoc +0 -694
- data/docs/comparison/index.adoc +0 -113
- data/docs/comparison/vs-7zip.adoc +0 -309
- data/docs/comparison/vs-peazip.adoc +0 -77
- data/docs/comparison/vs-rubyzip.adoc +0 -342
- data/docs/comparison/vs-winrar.adoc +0 -100
- data/docs/concepts/index.adoc +0 -129
- data/docs/developer/architecture.adoc +0 -256
- data/docs/developer/contributing.adoc +0 -158
- data/docs/developer/index.adoc +0 -25
- data/docs/developer/testing.adoc +0 -212
- data/docs/getting-started/basic-usage.adoc +0 -263
- data/docs/getting-started/index.adoc +0 -42
- data/docs/getting-started/installation.adoc +0 -138
- data/docs/getting-started/quick-start.adoc +0 -183
- data/docs/getting-started/your-first-archive.adoc +0 -219
- data/docs/guides/advanced-features/encryption.adoc +0 -277
- data/docs/guides/advanced-features/index.adoc +0 -59
- data/docs/guides/advanced-features/parallel-processing.adoc +0 -144
- data/docs/guides/advanced-features/progress-tracking.adoc +0 -124
- data/docs/guides/advanced-features/streaming.adoc +0 -142
- data/docs/guides/archive-formats/gzip-format.adoc +0 -105
- data/docs/guides/archive-formats/index.adoc +0 -160
- data/docs/guides/archive-formats/ole-format.adoc +0 -151
- data/docs/guides/archive-formats/rar-format.adoc +0 -104
- data/docs/guides/archive-formats/rar5.adoc +0 -521
- data/docs/guides/archive-formats/rpm-format.adoc +0 -108
- data/docs/guides/archive-formats/seven-zip-format.adoc +0 -35
- data/docs/guides/archive-formats/tar-format.adoc +0 -106
- data/docs/guides/archive-formats/xz-format.adoc +0 -118
- data/docs/guides/archive-formats/zip-format.adoc +0 -35
- data/docs/guides/compression-algorithms/bzip2.adoc +0 -114
- data/docs/guides/compression-algorithms/deflate.adoc +0 -313
- data/docs/guides/compression-algorithms/index.adoc +0 -190
- data/docs/guides/compression-algorithms/lzma.adoc +0 -394
- data/docs/guides/compression-algorithms/lzma2.adoc +0 -244
- data/docs/guides/compression-algorithms/ppmd.adoc +0 -313
- data/docs/guides/compression-algorithms/zstandard.adoc +0 -296
- data/docs/guides/creating-archives.adoc +0 -348
- data/docs/guides/extracting-archives.adoc +0 -53
- data/docs/guides/format-conversion.adoc +0 -64
- data/docs/guides/index.adoc +0 -49
- data/docs/guides/migration-rubyzip.adoc +0 -217
- data/docs/guides/parity-archives.adoc +0 -605
- data/docs/guides/performance-tuning.adoc +0 -88
- data/docs/index.adoc +0 -228
- data/docs/reference/api/overview.adoc +0 -187
- data/docs/reference/cli/compress-command.adoc +0 -114
- data/docs/reference/cli/overview.adoc +0 -140
- data/docs/reference/index.adoc +0 -26
- data/docs/resources/faq.adoc +0 -185
- data/docs/resources/quick-reference.adoc +0 -222
- data/docs/troubleshooting/index.adoc +0 -207
data/lib/tasks/docs.rake
ADDED
|
@@ -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(/[<>&]/, '&' => '&', '<' => '<', '>' => '>')}</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.
|