omnizip 0.3.9 → 0.3.10
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/TODO.refactor/00-overview.md +67 -0
- data/TODO.refactor/01-registry-base-class.md +81 -0
- data/TODO.refactor/02-error-hierarchy.md +57 -0
- data/TODO.refactor/03-autoload-entry-point.md +69 -0
- data/TODO.refactor/04-filter-base-consolidation.md +49 -0
- data/TODO.refactor/05-send-private-methods.md +64 -0
- data/TODO.refactor/06-instance-variable-access.md +69 -0
- data/TODO.refactor/07-respond-to-replacement.md +91 -0
- data/TODO.refactor/08-spec-doubles.md +27 -0
- data/TODO.refactor/09-cli-shared-module.md +35 -0
- data/TODO.refactor/10-format-detector-ocp.md +34 -0
- data/TODO.refactor/11-convenience-decoupling.md +38 -0
- data/TODO.refactor/12-thread-safety.md +8 -0
- data/TODO.refactor/13-lutaml-model-migration.md +82 -0
- data/TODO.refactor/14-add-missing-specs.md +25 -0
- data/lib/omnizip/algorithm.rb +0 -2
- data/lib/omnizip/algorithm_registry.rb +29 -57
- data/lib/omnizip/algorithms/lzma/dictionary.rb +4 -3
- data/lib/omnizip/algorithms/lzma/lzip_decoder.rb +2 -0
- data/lib/omnizip/algorithms/lzma/range_decoder.rb +1 -1
- data/lib/omnizip/algorithms/lzma/xz_utils_decoder.rb +6 -5
- data/lib/omnizip/algorithms/lzma2/encoder.rb +0 -2
- data/lib/omnizip/algorithms/lzma2/lzma2_chunk.rb +0 -2
- data/lib/omnizip/algorithms/lzma2/properties.rb +0 -2
- data/lib/omnizip/algorithms/ppmd7/model.rb +1 -1
- data/lib/omnizip/algorithms/ppmd8/context.rb +0 -2
- data/lib/omnizip/algorithms/ppmd8/model.rb +1 -1
- data/lib/omnizip/algorithms/ppmd8.rb +0 -2
- data/lib/omnizip/algorithms/ppmd_base.rb +0 -2
- data/lib/omnizip/algorithms/sevenzip_lzma2.rb +0 -5
- data/lib/omnizip/algorithms/xz_lzma2.rb +0 -5
- data/lib/omnizip/algorithms/zstandard/frame/header.rb +3 -5
- data/lib/omnizip/algorithms.rb +1 -0
- data/lib/omnizip/archive_handler.rb +75 -0
- data/lib/omnizip/archive_handlers/tar_handler.rb +51 -0
- data/lib/omnizip/archive_handlers/zip_handler.rb +73 -0
- data/lib/omnizip/archive_handlers.rb +11 -0
- data/lib/omnizip/checksum_registry.rb +23 -96
- data/lib/omnizip/cli/shared.rb +31 -0
- data/lib/omnizip/cli.rb +6 -43
- data/lib/omnizip/commands/archive_create_command.rb +0 -6
- data/lib/omnizip/commands/archive_extract_command.rb +0 -4
- data/lib/omnizip/commands/archive_list_command.rb +0 -4
- data/lib/omnizip/commands/archive_repair_command.rb +0 -2
- data/lib/omnizip/commands/archive_verify_command.rb +0 -2
- data/lib/omnizip/commands/compress_command.rb +0 -2
- data/lib/omnizip/commands/decompress_command.rb +0 -2
- data/lib/omnizip/commands/list_command.rb +0 -2
- data/lib/omnizip/commands/metadata_command.rb +0 -5
- data/lib/omnizip/commands/parity_create_command.rb +0 -2
- data/lib/omnizip/commands/parity_repair_command.rb +0 -2
- data/lib/omnizip/commands/parity_verify_command.rb +0 -2
- data/lib/omnizip/commands/profile_list_command.rb +0 -2
- data/lib/omnizip/commands/profile_show_command.rb +0 -2
- data/lib/omnizip/convenience.rb +111 -234
- data/lib/omnizip/converter/seven_zip_to_zip_strategy.rb +1 -1
- data/lib/omnizip/crypto/aes256/cipher.rb +6 -1
- data/lib/omnizip/error.rb +22 -18
- data/lib/omnizip/filter.rb +5 -3
- data/lib/omnizip/filter_registry.rb +74 -98
- data/lib/omnizip/filters/bcj2.rb +0 -2
- data/lib/omnizip/filters/bcj_x86.rb +0 -2
- data/lib/omnizip/filters/filter_base.rb +4 -70
- data/lib/omnizip/filters/registry.rb +0 -2
- data/lib/omnizip/format_detector.rb +21 -12
- data/lib/omnizip/format_registry.rb +69 -53
- data/lib/omnizip/formats/bzip2_file.rb +0 -2
- data/lib/omnizip/formats/cpio/entry.rb +0 -2
- data/lib/omnizip/formats/cpio/reader.rb +0 -1
- data/lib/omnizip/formats/cpio/writer.rb +0 -1
- data/lib/omnizip/formats/cpio.rb +0 -1
- data/lib/omnizip/formats/format_spec_loader.rb +1 -1
- data/lib/omnizip/formats/gzip.rb +0 -2
- data/lib/omnizip/formats/iso/directory_record.rb +6 -3
- data/lib/omnizip/formats/iso/reader.rb +2 -2
- data/lib/omnizip/formats/iso.rb +0 -1
- data/lib/omnizip/formats/lzip.rb +5 -25
- data/lib/omnizip/formats/lzma_alone.rb +7 -25
- data/lib/omnizip/formats/msi/reader.rb +1 -0
- data/lib/omnizip/formats/msi/string_pool.rb +2 -3
- data/lib/omnizip/formats/msi.rb +1 -4
- data/lib/omnizip/formats/ole/allocation_table.rb +0 -1
- data/lib/omnizip/formats/ole/dirent.rb +9 -4
- data/lib/omnizip/formats/ole/header.rb +0 -1
- data/lib/omnizip/formats/ole/ranges_io.rb +1 -1
- data/lib/omnizip/formats/ole/storage.rb +5 -6
- data/lib/omnizip/formats/ole.rb +0 -1
- data/lib/omnizip/formats/rar/block_parser.rb +0 -1
- data/lib/omnizip/formats/rar/compression/ppmd/encoder.rb +1 -1
- data/lib/omnizip/formats/rar/decompressor.rb +1 -0
- data/lib/omnizip/formats/rar/header.rb +0 -1
- data/lib/omnizip/formats/rar/rar5/writer.rb +0 -3
- data/lib/omnizip/formats/rar/reader.rb +2 -3
- data/lib/omnizip/formats/rar/writer.rb +0 -1
- data/lib/omnizip/formats/rar.rb +0 -1
- data/lib/omnizip/formats/rpm/entry.rb +0 -1
- data/lib/omnizip/formats/rpm/header.rb +1 -6
- data/lib/omnizip/formats/rpm/lead.rb +37 -26
- data/lib/omnizip/formats/rpm/tag.rb +0 -1
- data/lib/omnizip/formats/rpm/writer.rb +0 -1
- data/lib/omnizip/formats/seven_zip/bcj2_stream_decompressor.rb +0 -1
- data/lib/omnizip/formats/seven_zip/coder_chain.rb +1 -2
- data/lib/omnizip/formats/seven_zip/encoded_header.rb +0 -1
- data/lib/omnizip/formats/seven_zip/encrypted_header.rb +0 -1
- data/lib/omnizip/formats/seven_zip/header.rb +0 -1
- data/lib/omnizip/formats/seven_zip/header_encryptor.rb +0 -1
- data/lib/omnizip/formats/seven_zip/header_writer.rb +0 -1
- data/lib/omnizip/formats/seven_zip/models/file_entry.rb +1 -1
- data/lib/omnizip/formats/seven_zip/parser.rb +1 -2
- data/lib/omnizip/formats/seven_zip/reader.rb +13 -2
- data/lib/omnizip/formats/seven_zip/split_archive_reader.rb +0 -1
- data/lib/omnizip/formats/seven_zip/split_archive_writer.rb +0 -1
- data/lib/omnizip/formats/seven_zip/writer.rb +4 -6
- data/lib/omnizip/formats/seven_zip.rb +2 -4
- data/lib/omnizip/formats/tar/entry.rb +0 -2
- data/lib/omnizip/formats/tar/header.rb +0 -2
- data/lib/omnizip/formats/tar/reader.rb +0 -2
- data/lib/omnizip/formats/tar/writer.rb +0 -2
- data/lib/omnizip/formats/tar.rb +0 -1
- data/lib/omnizip/formats/xar/entry.rb +0 -1
- data/lib/omnizip/formats/xar/header.rb +0 -1
- data/lib/omnizip/formats/xar/reader.rb +0 -1
- data/lib/omnizip/formats/xar/toc.rb +1 -2
- data/lib/omnizip/formats/xar/writer.rb +11 -3
- data/lib/omnizip/formats/xar.rb +0 -1
- data/lib/omnizip/formats/xz/reader.rb +7 -3
- data/lib/omnizip/formats/xz.rb +3 -40
- data/lib/omnizip/formats/xz_impl/block_decoder.rb +4 -10
- data/lib/omnizip/formats/xz_impl/block_encoder.rb +0 -2
- data/lib/omnizip/formats/xz_impl/block_header_parser.rb +0 -2
- data/lib/omnizip/formats/xz_impl/index_decoder.rb +0 -1
- data/lib/omnizip/formats/xz_impl/index_encoder.rb +0 -1
- data/lib/omnizip/formats/xz_impl/stream_encoder.rb +1 -1
- data/lib/omnizip/formats/xz_impl/stream_footer.rb +0 -1
- data/lib/omnizip/formats/xz_impl/stream_footer_parser.rb +0 -1
- data/lib/omnizip/formats/xz_impl/stream_header.rb +0 -1
- data/lib/omnizip/formats/xz_impl/stream_header_parser.rb +0 -2
- data/lib/omnizip/formats/zip/central_directory_header.rb +2 -3
- data/lib/omnizip/formats/zip/end_of_central_directory.rb +0 -2
- data/lib/omnizip/formats/zip/local_file_header.rb +0 -2
- data/lib/omnizip/formats/zip/reader.rb +12 -2
- data/lib/omnizip/formats/zip/writer.rb +73 -1
- data/lib/omnizip/formats/zip/zip64_end_of_central_directory.rb +0 -2
- data/lib/omnizip/formats/zip/zip64_end_of_central_directory_locator.rb +0 -2
- data/lib/omnizip/formats/zip/zip64_extra_field.rb +0 -2
- data/lib/omnizip/formats/zip.rb +0 -1
- data/lib/omnizip/formats.rb +8 -24
- data/lib/omnizip/implementations/seven_zip/lzma/range_decoder.rb +2 -2
- data/lib/omnizip/implementations/seven_zip/lzma/range_encoder.rb +1 -1
- data/lib/omnizip/implementations/seven_zip/lzma2/encoder.rb +0 -6
- data/lib/omnizip/implementations/xz_utils/lzma2/decoder.rb +3 -4
- data/lib/omnizip/implementations/xz_utils/lzma2/encoder.rb +0 -1
- data/lib/omnizip/io/source.rb +136 -0
- data/lib/omnizip/io.rb +2 -0
- data/lib/omnizip/link_handler.rb +7 -3
- data/lib/omnizip/metadata/metadata_editor.rb +1 -1
- data/lib/omnizip/models/algorithm_metadata.rb +33 -60
- data/lib/omnizip/models/compression_options.rb +40 -53
- data/lib/omnizip/models/conversion_options.rb +9 -18
- data/lib/omnizip/models/parallel_options.rb +25 -0
- data/lib/omnizip/optimization_registry.rb +8 -44
- data/lib/omnizip/parallel/job_scheduler.rb +26 -8
- data/lib/omnizip/parallel/parallel_compressor.rb +10 -20
- data/lib/omnizip/parallel/parallel_extractor.rb +20 -11
- data/lib/omnizip/parity/par2_repairer.rb +5 -5
- data/lib/omnizip/parity/par2_verifier.rb +27 -22
- data/lib/omnizip/parity.rb +4 -4
- data/lib/omnizip/password/encryption_registry.rb +11 -50
- data/lib/omnizip/password/zip_crypto_strategy.rb +1 -4
- data/lib/omnizip/pipe/stream_decompressor.rb +2 -3
- data/lib/omnizip/profile/archive_profile.rb +0 -2
- data/lib/omnizip/profile/binary_profile.rb +0 -2
- data/lib/omnizip/profile/compression_profile.rb +0 -1
- data/lib/omnizip/profile/profile_detector.rb +0 -2
- data/lib/omnizip/profile/text_profile.rb +0 -2
- data/lib/omnizip/profile.rb +0 -2
- data/lib/omnizip/registry.rb +112 -0
- data/lib/omnizip/version.rb +1 -1
- data/lib/omnizip/zip/file.rb +20 -9
- data/lib/omnizip.rb +26 -58
- metadata +24 -2
data/lib/omnizip/convenience.rb
CHANGED
|
@@ -1,309 +1,191 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Omnizip
|
|
4
|
-
# Convenience methods for common archive operations
|
|
4
|
+
# Convenience methods for common archive operations.
|
|
5
|
+
#
|
|
6
|
+
# All public methods accept an optional +format:+ keyword (default
|
|
7
|
+
# +:zip+) that selects the underlying archive handler via
|
|
8
|
+
# +Omnizip::ArchiveHandler+. New formats gain access to the convenience
|
|
9
|
+
# API by registering a handler; no edits to this file are needed.
|
|
5
10
|
module Convenience
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
#
|
|
9
|
-
# @param options [Hash] Compression options
|
|
10
|
-
# @option options [Symbol, Omnizip::Profile::CompressionProfile] :profile
|
|
11
|
-
# Compression profile (:fast, :balanced, :maximum, :text, :binary, :archive, :auto)
|
|
12
|
-
# @option options [Symbol] :compression Compression method (:deflate, :bzip2, :lzma, :zstandard)
|
|
13
|
-
# @option options [Integer] :level Compression level (1-9)
|
|
14
|
-
# @option options [Boolean] :chunked Use chunked processing for large files
|
|
15
|
-
# @option options [Integer] :chunk_size Chunk size in bytes (default: 64MB)
|
|
16
|
-
# @option options [Integer] :max_memory Maximum memory usage (default: 256MB)
|
|
17
|
-
# @option options [Proc] :progress Progress callback
|
|
18
|
-
# @return [String] Path to created archive
|
|
11
|
+
DEFAULT_FORMAT = :zip
|
|
12
|
+
|
|
13
|
+
# Compress a single file into an archive.
|
|
19
14
|
#
|
|
20
|
-
# @
|
|
21
|
-
#
|
|
22
|
-
#
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
|
|
26
|
-
def compress_file(input_path, output_path, **options)
|
|
15
|
+
# @param input_path [String] Path to input file
|
|
16
|
+
# @param output_path [String] Path to output archive
|
|
17
|
+
# @param format [Symbol] Archive format (default +:zip+)
|
|
18
|
+
# @param options [Hash] Compression / format-specific options
|
|
19
|
+
# @return [String] +output_path+
|
|
20
|
+
def compress_file(input_path, output_path, format: DEFAULT_FORMAT, **options)
|
|
27
21
|
unless ::File.exist?(input_path)
|
|
28
|
-
raise Errno::ENOENT,
|
|
29
|
-
"Input file not found: #{input_path}"
|
|
22
|
+
raise Errno::ENOENT, "Input file not found: #{input_path}"
|
|
30
23
|
end
|
|
31
24
|
if ::File.directory?(input_path)
|
|
32
|
-
raise ArgumentError,
|
|
33
|
-
"Input is a directory: #{input_path}"
|
|
25
|
+
raise ArgumentError, "Input is a directory: #{input_path}"
|
|
34
26
|
end
|
|
35
27
|
|
|
36
|
-
# Apply profile settings if specified
|
|
37
28
|
options = apply_profile(input_path, options) if options[:profile]
|
|
38
29
|
|
|
39
|
-
# Use chunked processing for large files if requested
|
|
40
30
|
if options[:chunked]
|
|
41
|
-
return Omnizip::Chunked.compress_file(input_path, output_path,
|
|
42
|
-
**options)
|
|
31
|
+
return Omnizip::Chunked.compress_file(input_path, output_path, **options)
|
|
43
32
|
end
|
|
44
33
|
|
|
45
|
-
Omnizip::
|
|
46
|
-
|
|
47
|
-
zip.add(basename, input_path)
|
|
34
|
+
Omnizip::ArchiveHandler.for(format).create(output_path) do |archive|
|
|
35
|
+
archive.add(::File.basename(input_path), input_path)
|
|
48
36
|
end
|
|
49
37
|
|
|
50
38
|
output_path
|
|
51
39
|
end
|
|
52
40
|
|
|
53
|
-
# Compress a directory
|
|
54
|
-
# @param input_dir [String] Path to input directory
|
|
55
|
-
# @param output_path [String] Path to output ZIP file
|
|
56
|
-
# @param options [Hash] Compression options
|
|
57
|
-
# @option options [Symbol, Omnizip::Profile::CompressionProfile] :profile
|
|
58
|
-
# Compression profile (:fast, :balanced, :maximum, etc.)
|
|
59
|
-
# @option options [Symbol] :compression Compression method
|
|
60
|
-
# @option options [Integer] :level Compression level (1-9)
|
|
61
|
-
# @option options [Boolean] :recursive Include subdirectories (default: true)
|
|
62
|
-
# @option options [Integer] :max_memory Maximum memory usage for large files
|
|
63
|
-
# @option options [Proc] :progress Progress callback
|
|
64
|
-
# @return [String] Path to created archive
|
|
41
|
+
# Compress a directory into an archive.
|
|
65
42
|
#
|
|
66
|
-
# @
|
|
67
|
-
#
|
|
68
|
-
#
|
|
69
|
-
#
|
|
70
|
-
#
|
|
71
|
-
|
|
43
|
+
# @param input_dir [String] Path to input directory
|
|
44
|
+
# @param output_path [String] Path to output archive
|
|
45
|
+
# @param format [Symbol] Archive format (default +:zip+)
|
|
46
|
+
# @param recursive [Boolean] Include subdirectories (default +true+)
|
|
47
|
+
# @param options [Hash] Compression / format-specific options
|
|
48
|
+
# @return [String] +output_path+
|
|
49
|
+
def compress_directory(input_dir, output_path, format: DEFAULT_FORMAT,
|
|
50
|
+
recursive: true, **options)
|
|
72
51
|
unless ::File.exist?(input_dir)
|
|
73
|
-
raise Errno::ENOENT,
|
|
74
|
-
"Input directory not found: #{input_dir}"
|
|
52
|
+
raise Errno::ENOENT, "Input directory not found: #{input_dir}"
|
|
75
53
|
end
|
|
76
54
|
unless ::File.directory?(input_dir)
|
|
77
|
-
raise ArgumentError,
|
|
78
|
-
"Input is not a directory: #{input_dir}"
|
|
55
|
+
raise ArgumentError, "Input is not a directory: #{input_dir}"
|
|
79
56
|
end
|
|
80
57
|
|
|
81
|
-
# Apply profile settings if specified (use first file for auto-detection)
|
|
82
58
|
if options[:profile]
|
|
83
59
|
first_file = find_first_file(input_dir)
|
|
84
60
|
apply_profile(first_file, options)
|
|
85
61
|
end
|
|
86
62
|
|
|
87
|
-
Omnizip::
|
|
88
|
-
add_directory_contents(
|
|
63
|
+
Omnizip::ArchiveHandler.for(format).create(output_path) do |archive|
|
|
64
|
+
add_directory_contents(archive, input_dir, "", recursive: recursive)
|
|
89
65
|
end
|
|
90
66
|
|
|
91
67
|
output_path
|
|
92
68
|
end
|
|
93
69
|
|
|
94
|
-
# Extract
|
|
95
|
-
# @param archive_path [String] Path to ZIP archive
|
|
96
|
-
# @param output_dir [String] Path to output directory
|
|
97
|
-
# @param options [Hash] Extraction options
|
|
98
|
-
# @option options [Boolean] :overwrite Overwrite existing files (default: false)
|
|
99
|
-
# @return [Array<String>] List of extracted file paths
|
|
70
|
+
# Extract an archive to a directory.
|
|
100
71
|
#
|
|
101
|
-
# @
|
|
102
|
-
#
|
|
103
|
-
#
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
zip.each do |entry|
|
|
114
|
-
dest_path = ::File.join(output_dir, entry.name)
|
|
115
|
-
|
|
116
|
-
# Handle overwrite option
|
|
117
|
-
on_exists = if overwrite
|
|
118
|
-
proc { true }
|
|
119
|
-
else
|
|
120
|
-
proc { |_e, path| raise "File exists: #{path}" }
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
zip.extract(entry, dest_path, &on_exists)
|
|
124
|
-
extracted_files << dest_path
|
|
125
|
-
end
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
extracted_files
|
|
72
|
+
# @param archive_path [String] Path to archive
|
|
73
|
+
# @param output_dir [String] Output directory
|
|
74
|
+
# @param format [Symbol] Archive format (default +:zip+)
|
|
75
|
+
# @param overwrite [Boolean] Overwrite existing files (default +false+)
|
|
76
|
+
# @param options [Hash] Format-specific extraction options
|
|
77
|
+
# @return [Array<String>] Extracted file paths
|
|
78
|
+
def extract_archive(archive_path, output_dir, format: DEFAULT_FORMAT,
|
|
79
|
+
overwrite: false, **options)
|
|
80
|
+
require_archive!(archive_path)
|
|
81
|
+
Omnizip::ArchiveHandler.for(format)
|
|
82
|
+
.extract_to(archive_path, output_dir,
|
|
83
|
+
overwrite: overwrite, **options)
|
|
129
84
|
end
|
|
130
85
|
|
|
131
|
-
# List contents of
|
|
132
|
-
# @param archive_path [String] Path to ZIP archive
|
|
133
|
-
# @param options [Hash] Listing options
|
|
134
|
-
# @option options [Boolean] :details Include detailed information (default: false)
|
|
135
|
-
# @return [Array<String>, Array<Hash>] List of entry names or detailed info
|
|
136
|
-
#
|
|
137
|
-
# @example
|
|
138
|
-
# Omnizip.list_archive('backup.zip')
|
|
139
|
-
# # => ["file1.txt", "file2.txt", "dir/"]
|
|
86
|
+
# List contents of an archive.
|
|
140
87
|
#
|
|
141
|
-
#
|
|
142
|
-
#
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
Omnizip::
|
|
150
|
-
|
|
151
|
-
zip.entries.map do |entry|
|
|
152
|
-
{
|
|
153
|
-
name: entry.name,
|
|
154
|
-
size: entry.size,
|
|
155
|
-
compressed_size: entry.compressed_size,
|
|
156
|
-
compression_method: entry.compression_method,
|
|
157
|
-
crc: entry.crc,
|
|
158
|
-
time: entry.time,
|
|
159
|
-
directory: entry.directory?,
|
|
160
|
-
}
|
|
161
|
-
end
|
|
162
|
-
else
|
|
163
|
-
zip.names
|
|
164
|
-
end
|
|
165
|
-
end
|
|
88
|
+
# @param archive_path [String] Path to archive
|
|
89
|
+
# @param format [Symbol] Archive format (default +:zip+)
|
|
90
|
+
# @param details [Boolean] Include detailed info (default +false+)
|
|
91
|
+
# @param options [Hash] Format-specific listing options
|
|
92
|
+
# @return [Array<String>, Array<Hash>]
|
|
93
|
+
def list_archive(archive_path, format: DEFAULT_FORMAT, details: false,
|
|
94
|
+
**options)
|
|
95
|
+
require_archive!(archive_path)
|
|
96
|
+
Omnizip::ArchiveHandler.for(format)
|
|
97
|
+
.list(archive_path, details: details, **options)
|
|
166
98
|
end
|
|
167
99
|
|
|
168
|
-
# Read a single
|
|
169
|
-
# @param archive_path [String] Path to ZIP archive
|
|
170
|
-
# @param entry_name [String] Name of entry to read
|
|
171
|
-
# @return [String] Contents of the file
|
|
100
|
+
# Read a single entry from an archive.
|
|
172
101
|
#
|
|
173
|
-
# @
|
|
174
|
-
#
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
Omnizip::Zip::File.open(archive_path) do |zip|
|
|
182
|
-
entry = zip.get_entry(entry_name)
|
|
183
|
-
raise Errno::ENOENT, "Entry not found: #{entry_name}" unless entry
|
|
184
|
-
|
|
185
|
-
zip.read(entry)
|
|
186
|
-
end
|
|
102
|
+
# @param archive_path [String] Path to archive
|
|
103
|
+
# @param entry_name [String] Entry to read
|
|
104
|
+
# @param format [Symbol] Archive format (default +:zip+)
|
|
105
|
+
# @return [String] Entry contents
|
|
106
|
+
def read_from_archive(archive_path, entry_name, format: DEFAULT_FORMAT)
|
|
107
|
+
require_archive!(archive_path)
|
|
108
|
+
Omnizip::ArchiveHandler.for(format).read_entry(archive_path, entry_name)
|
|
187
109
|
end
|
|
188
110
|
|
|
189
|
-
# Add a file to an existing
|
|
190
|
-
# @param archive_path [String] Path to ZIP archive
|
|
191
|
-
# @param entry_name [String] Name for entry in archive
|
|
192
|
-
# @param source_path [String] Path to source file
|
|
193
|
-
# @return [String] Path to archive
|
|
111
|
+
# Add a file to an existing archive.
|
|
194
112
|
#
|
|
195
|
-
# @
|
|
196
|
-
#
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
113
|
+
# @param archive_path [String] Path to archive
|
|
114
|
+
# @param entry_name [String] Entry name in archive
|
|
115
|
+
# @param source_path [String] Path to source file
|
|
116
|
+
# @param format [Symbol] Archive format (default +:zip+)
|
|
117
|
+
# @return [String] +archive_path+
|
|
118
|
+
def add_to_archive(archive_path, entry_name, source_path,
|
|
119
|
+
format: DEFAULT_FORMAT)
|
|
120
|
+
require_archive!(archive_path)
|
|
202
121
|
unless ::File.exist?(source_path)
|
|
203
|
-
raise Errno::ENOENT,
|
|
204
|
-
"Source file not found: #{source_path}"
|
|
122
|
+
raise Errno::ENOENT, "Source file not found: #{source_path}"
|
|
205
123
|
end
|
|
206
124
|
|
|
207
|
-
Omnizip::
|
|
208
|
-
|
|
125
|
+
handler = Omnizip::ArchiveHandler.for(format)
|
|
126
|
+
if handler.respond_to?(:add_entry)
|
|
127
|
+
handler.add_entry(archive_path, entry_name, source_path)
|
|
128
|
+
else
|
|
129
|
+
raise Omnizip::UnsupportedFormatError,
|
|
130
|
+
"Format #{format.inspect} does not support adding entries"
|
|
209
131
|
end
|
|
210
132
|
|
|
211
133
|
archive_path
|
|
212
134
|
end
|
|
213
135
|
|
|
214
|
-
# Remove
|
|
215
|
-
# @param archive_path [String] Path to ZIP archive
|
|
216
|
-
# @param entry_name [String] Name of entry to remove
|
|
217
|
-
# @return [String] Path to archive
|
|
136
|
+
# Remove an entry from an existing archive.
|
|
218
137
|
#
|
|
219
|
-
# @
|
|
220
|
-
#
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
end
|
|
230
|
-
|
|
138
|
+
# @param archive_path [String] Path to archive
|
|
139
|
+
# @param entry_name [String] Entry to remove
|
|
140
|
+
# @param format [Symbol] Archive format (default +:zip+)
|
|
141
|
+
# @return [String] +archive_path+
|
|
142
|
+
def remove_from_archive(archive_path, entry_name, format: DEFAULT_FORMAT)
|
|
143
|
+
require_archive!(archive_path)
|
|
144
|
+
handler = Omnizip::ArchiveHandler.for(format)
|
|
145
|
+
unless handler.respond_to?(:remove_entry)
|
|
146
|
+
raise Omnizip::UnsupportedFormatError,
|
|
147
|
+
"Format #{format.inspect} does not support removing entries"
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
handler.remove_entry(archive_path, entry_name)
|
|
231
151
|
archive_path
|
|
232
152
|
end
|
|
233
153
|
|
|
234
|
-
# Create a RAR archive
|
|
235
|
-
# @param archive_path [String] Path to output RAR file
|
|
236
|
-
# @param options [Hash] Creation options
|
|
237
|
-
# @option options [Integer] :version RAR version (4 or 5, default: 5 for pure Ruby)
|
|
238
|
-
# @option options [Symbol] :compression For RAR5: :store, :lzma, :auto (default: :store)
|
|
239
|
-
# @option options [Integer] :level For RAR5: LZMA compression level 1-5 (default: 3)
|
|
240
|
-
# @option options [Boolean] :include_mtime Include modification time (RAR5 only, default: false)
|
|
241
|
-
# @option options [Boolean] :include_crc32 Include CRC32 checksum (RAR5 only, default: false)
|
|
242
|
-
# @yield [writer] RAR writer instance
|
|
243
|
-
# @return [String] Path to created archive
|
|
244
|
-
#
|
|
245
|
-
# @example Create RAR5 archive with STORE compression
|
|
246
|
-
# Omnizip.create_rar('backup.rar') do |rar|
|
|
247
|
-
# rar.add_file('document.pdf')
|
|
248
|
-
# end
|
|
249
|
-
#
|
|
250
|
-
# @example Create RAR5 with LZMA compression
|
|
251
|
-
# Omnizip.create_rar('backup.rar', compression: :lzma, level: 5) do |rar|
|
|
252
|
-
# rar.add_file('data.txt')
|
|
253
|
-
# end
|
|
254
|
-
#
|
|
255
|
-
# @example Create RAR5 with optional fields
|
|
256
|
-
# Omnizip.create_rar('backup.rar',
|
|
257
|
-
# compression: :lzma,
|
|
258
|
-
# level: 3,
|
|
259
|
-
# include_mtime: true,
|
|
260
|
-
# include_crc32: true
|
|
261
|
-
# ) do |rar|
|
|
262
|
-
# rar.add_file('important.doc')
|
|
263
|
-
# end
|
|
154
|
+
# Create a RAR archive (requires RAR license — see NotLicensedError).
|
|
264
155
|
# rubocop:disable Naming/BlockForwarding, Style/ArgumentsForwarding -- Ruby 3.0 compatibility
|
|
265
156
|
def create_rar(archive_path, **options, &block)
|
|
266
|
-
# Default to RAR5 (pure Ruby) unless explicitly specified
|
|
267
157
|
options[:version] ||= 5
|
|
268
|
-
|
|
269
158
|
Omnizip::Formats::Rar.create(archive_path, options, &block)
|
|
270
159
|
end
|
|
271
160
|
# rubocop:enable Naming/BlockForwarding, Style/ArgumentsForwarding
|
|
272
161
|
|
|
273
162
|
private
|
|
274
163
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
164
|
+
def require_archive!(archive_path)
|
|
165
|
+
return if ::File.exist?(archive_path)
|
|
166
|
+
|
|
167
|
+
raise Errno::ENOENT, "Archive not found: #{archive_path}"
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Apply compression profile to options.
|
|
280
171
|
def apply_profile(file_path, options)
|
|
281
172
|
profile_spec = options.delete(:profile)
|
|
282
173
|
return options unless profile_spec
|
|
283
174
|
|
|
284
|
-
# Get the profile
|
|
285
175
|
profile = case profile_spec
|
|
286
176
|
when :auto
|
|
287
|
-
# Auto-detect based on file
|
|
288
177
|
file_path ? Omnizip::Profile.detect(file_path) : Omnizip::Profile.get(:balanced)
|
|
289
178
|
when Symbol
|
|
290
|
-
# Get by name
|
|
291
179
|
Omnizip::Profile.get(profile_spec) || Omnizip::Profile.get(:balanced)
|
|
292
180
|
when Omnizip::Profile::CompressionProfile
|
|
293
|
-
# Use the profile directly
|
|
294
181
|
profile_spec
|
|
295
182
|
else
|
|
296
183
|
Omnizip::Profile.get(:balanced)
|
|
297
184
|
end
|
|
298
185
|
|
|
299
|
-
# Apply profile to options
|
|
300
186
|
profile.apply_to(options)
|
|
301
187
|
end
|
|
302
188
|
|
|
303
|
-
# Find first file in directory for profile detection
|
|
304
|
-
#
|
|
305
|
-
# @param dir_path [String] Directory path
|
|
306
|
-
# @return [String, nil] Path to first file or nil
|
|
307
189
|
def find_first_file(dir_path)
|
|
308
190
|
Dir.foreach(dir_path) do |entry|
|
|
309
191
|
next if [".", ".."].include?(entry)
|
|
@@ -311,7 +193,6 @@ module Omnizip
|
|
|
311
193
|
full_path = ::File.join(dir_path, entry)
|
|
312
194
|
return full_path if ::File.file?(full_path)
|
|
313
195
|
|
|
314
|
-
# Check subdirectories
|
|
315
196
|
if ::File.directory?(full_path)
|
|
316
197
|
result = find_first_file(full_path)
|
|
317
198
|
return result if result
|
|
@@ -320,16 +201,17 @@ module Omnizip
|
|
|
320
201
|
nil
|
|
321
202
|
end
|
|
322
203
|
|
|
323
|
-
# Recursively add directory contents to archive
|
|
324
|
-
|
|
204
|
+
# Recursively add directory contents to an archive. The +archive+
|
|
205
|
+
# argument is whatever the handler's +#create+ block yields (e.g.
|
|
206
|
+
# +Omnizip::Zip::File+ or +Omnizip::Formats::Tar::Writer+) and must
|
|
207
|
+
# respond to +#add(entry_name, src_path = nil)+.
|
|
208
|
+
def add_directory_contents(archive, base_dir, relative_path, recursive: true)
|
|
325
209
|
dir_path = ::File.join(base_dir, relative_path)
|
|
326
210
|
|
|
327
211
|
Dir.foreach(dir_path) do |entry|
|
|
328
212
|
next if [".", ".."].include?(entry)
|
|
329
213
|
|
|
330
214
|
full_path = ::File.join(dir_path, entry)
|
|
331
|
-
|
|
332
|
-
# Create archive path (strip leading separator if at root)
|
|
333
215
|
archive_path = if relative_path.empty?
|
|
334
216
|
entry
|
|
335
217
|
else
|
|
@@ -337,23 +219,18 @@ module Omnizip
|
|
|
337
219
|
end
|
|
338
220
|
|
|
339
221
|
if ::File.directory?(full_path)
|
|
340
|
-
# Add directory entry with trailing slash
|
|
341
222
|
dir_entry_name = archive_path.end_with?("/") ? archive_path : "#{archive_path}/"
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
# Recursively add contents if requested
|
|
223
|
+
archive.add(dir_entry_name)
|
|
345
224
|
if recursive
|
|
346
|
-
add_directory_contents(
|
|
225
|
+
add_directory_contents(archive, base_dir, archive_path,
|
|
347
226
|
recursive: recursive)
|
|
348
227
|
end
|
|
349
228
|
else
|
|
350
|
-
|
|
351
|
-
zip.add(archive_path, full_path)
|
|
229
|
+
archive.add(archive_path, full_path)
|
|
352
230
|
end
|
|
353
231
|
end
|
|
354
232
|
end
|
|
355
233
|
end
|
|
356
234
|
|
|
357
|
-
# Extend Omnizip module with convenience methods
|
|
358
235
|
extend Convenience
|
|
359
236
|
end
|
|
@@ -87,7 +87,12 @@ module Omnizip
|
|
|
87
87
|
# @return [OpenSSL::Cipher] Configured cipher
|
|
88
88
|
def create_cipher(mode)
|
|
89
89
|
cipher = OpenSSL::Cipher.new("AES-256-CBC")
|
|
90
|
-
|
|
90
|
+
case mode
|
|
91
|
+
when :encrypt then cipher.encrypt
|
|
92
|
+
when :decrypt then cipher.decrypt
|
|
93
|
+
else
|
|
94
|
+
raise ArgumentError, "Unknown cipher mode: #{mode.inspect}"
|
|
95
|
+
end
|
|
91
96
|
cipher.key = @key # Then set key
|
|
92
97
|
cipher.iv = @iv # Then set IV
|
|
93
98
|
cipher.padding = 1 # PKCS7 padding
|
data/lib/omnizip/error.rb
CHANGED
|
@@ -3,59 +3,64 @@
|
|
|
3
3
|
# Copyright (C) 2025 Ribose Inc.
|
|
4
4
|
|
|
5
5
|
module Omnizip
|
|
6
|
-
# Base error class for all Omnizip errors
|
|
7
6
|
class Error < StandardError
|
|
8
7
|
end
|
|
9
8
|
|
|
10
|
-
# Error raised when compression fails
|
|
11
9
|
class CompressionError < Error
|
|
12
10
|
end
|
|
13
11
|
|
|
14
|
-
# Error raised when decompression fails
|
|
15
12
|
class DecompressionError < Error
|
|
16
13
|
end
|
|
17
14
|
|
|
18
|
-
# Error raised when an algorithm is not found
|
|
19
15
|
class AlgorithmNotFoundError < Error
|
|
20
16
|
end
|
|
21
17
|
|
|
22
|
-
#
|
|
23
|
-
|
|
18
|
+
# Canonical alias of +AlgorithmNotFoundError+. Older code referenced
|
|
19
|
+
# +UnknownAlgorithmError+; both names are kept for backward
|
|
20
|
+
# compatibility.
|
|
21
|
+
UnknownAlgorithmError = AlgorithmNotFoundError
|
|
22
|
+
|
|
23
|
+
class UnknownChecksumError < Error
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
class UnknownFilterError < Error
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
class UnknownEncryptionStrategyError < Error
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
class ConversionNotSupportedError < Error
|
|
24
33
|
end
|
|
25
34
|
|
|
26
|
-
# Error raised when a format is not supported
|
|
27
35
|
class UnsupportedFormatError < Error
|
|
28
36
|
end
|
|
29
37
|
|
|
30
|
-
# Error raised when format parsing fails
|
|
31
38
|
class FormatError < Error
|
|
32
39
|
end
|
|
33
40
|
|
|
34
|
-
# Error raised when archive is invalid
|
|
35
41
|
class InvalidArchiveError < Error
|
|
36
42
|
end
|
|
37
43
|
|
|
38
|
-
#
|
|
39
|
-
|
|
44
|
+
# Renamed from +IOError+ to avoid shadowing Ruby's built-in IOError.
|
|
45
|
+
# The legacy name is kept as an alias.
|
|
46
|
+
class IOOperationError < Error
|
|
40
47
|
end
|
|
48
|
+
IOError = IOOperationError
|
|
41
49
|
|
|
42
|
-
# Error raised when checksum validation fails
|
|
43
50
|
class ChecksumError < Error
|
|
44
51
|
end
|
|
45
52
|
|
|
46
|
-
#
|
|
47
|
-
class
|
|
53
|
+
# Canonical name. +OptimizationNotFound+ kept as alias.
|
|
54
|
+
class OptimizationNotFoundError < Error
|
|
48
55
|
end
|
|
56
|
+
OptimizationNotFound = OptimizationNotFoundError
|
|
49
57
|
|
|
50
|
-
# Error raised when progress tracking fails
|
|
51
58
|
class ProgressError < Error
|
|
52
59
|
end
|
|
53
60
|
|
|
54
|
-
# Error raised when ETA calculation fails
|
|
55
61
|
class ETAError < Error
|
|
56
62
|
end
|
|
57
63
|
|
|
58
|
-
# Error raised when RAR write is attempted without license
|
|
59
64
|
class NotLicensedError < Error
|
|
60
65
|
def initialize(message = default_message)
|
|
61
66
|
super
|
|
@@ -82,7 +87,6 @@ module Omnizip
|
|
|
82
87
|
end
|
|
83
88
|
end
|
|
84
89
|
|
|
85
|
-
# Error raised when RAR executable is not found
|
|
86
90
|
class RarNotAvailableError < Error
|
|
87
91
|
def initialize(message = default_message)
|
|
88
92
|
super
|
data/lib/omnizip/filter.rb
CHANGED
|
@@ -57,7 +57,8 @@ module Omnizip
|
|
|
57
57
|
# end
|
|
58
58
|
# end
|
|
59
59
|
class Filter
|
|
60
|
-
# @return [Symbol] Architecture identifier (:x86, :arm, :arm64,
|
|
60
|
+
# @return [Symbol, nil] Architecture identifier (:x86, :arm, :arm64,
|
|
61
|
+
# :powerpc, :ia64, :sparc) or nil for architecture-agnostic filters
|
|
61
62
|
attr_reader :architecture
|
|
62
63
|
|
|
63
64
|
# @return [String] Human-readable filter name
|
|
@@ -65,9 +66,10 @@ module Omnizip
|
|
|
65
66
|
|
|
66
67
|
# Initialize filter
|
|
67
68
|
#
|
|
68
|
-
# @param architecture [Symbol] Target architecture
|
|
69
|
+
# @param architecture [Symbol, nil] Target architecture (optional;
|
|
70
|
+
# filters that don't vary by architecture leave this as nil)
|
|
69
71
|
# @param name [String] Human-readable name
|
|
70
|
-
def initialize(architecture
|
|
72
|
+
def initialize(architecture: nil, name: "Unknown")
|
|
71
73
|
@architecture = architecture
|
|
72
74
|
@name = name
|
|
73
75
|
end
|