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.
Files changed (182) hide show
  1. checksums.yaml +4 -4
  2. data/TODO.refactor/00-overview.md +67 -0
  3. data/TODO.refactor/01-registry-base-class.md +81 -0
  4. data/TODO.refactor/02-error-hierarchy.md +57 -0
  5. data/TODO.refactor/03-autoload-entry-point.md +69 -0
  6. data/TODO.refactor/04-filter-base-consolidation.md +49 -0
  7. data/TODO.refactor/05-send-private-methods.md +64 -0
  8. data/TODO.refactor/06-instance-variable-access.md +69 -0
  9. data/TODO.refactor/07-respond-to-replacement.md +91 -0
  10. data/TODO.refactor/08-spec-doubles.md +27 -0
  11. data/TODO.refactor/09-cli-shared-module.md +35 -0
  12. data/TODO.refactor/10-format-detector-ocp.md +34 -0
  13. data/TODO.refactor/11-convenience-decoupling.md +38 -0
  14. data/TODO.refactor/12-thread-safety.md +8 -0
  15. data/TODO.refactor/13-lutaml-model-migration.md +82 -0
  16. data/TODO.refactor/14-add-missing-specs.md +25 -0
  17. data/lib/omnizip/algorithm.rb +0 -2
  18. data/lib/omnizip/algorithm_registry.rb +29 -57
  19. data/lib/omnizip/algorithms/lzma/dictionary.rb +4 -3
  20. data/lib/omnizip/algorithms/lzma/lzip_decoder.rb +2 -0
  21. data/lib/omnizip/algorithms/lzma/range_decoder.rb +1 -1
  22. data/lib/omnizip/algorithms/lzma/xz_utils_decoder.rb +6 -5
  23. data/lib/omnizip/algorithms/lzma2/encoder.rb +0 -2
  24. data/lib/omnizip/algorithms/lzma2/lzma2_chunk.rb +0 -2
  25. data/lib/omnizip/algorithms/lzma2/properties.rb +0 -2
  26. data/lib/omnizip/algorithms/ppmd7/model.rb +1 -1
  27. data/lib/omnizip/algorithms/ppmd8/context.rb +0 -2
  28. data/lib/omnizip/algorithms/ppmd8/model.rb +1 -1
  29. data/lib/omnizip/algorithms/ppmd8.rb +0 -2
  30. data/lib/omnizip/algorithms/ppmd_base.rb +0 -2
  31. data/lib/omnizip/algorithms/sevenzip_lzma2.rb +0 -5
  32. data/lib/omnizip/algorithms/xz_lzma2.rb +0 -5
  33. data/lib/omnizip/algorithms/zstandard/frame/header.rb +3 -5
  34. data/lib/omnizip/algorithms.rb +1 -0
  35. data/lib/omnizip/archive_handler.rb +75 -0
  36. data/lib/omnizip/archive_handlers/tar_handler.rb +51 -0
  37. data/lib/omnizip/archive_handlers/zip_handler.rb +73 -0
  38. data/lib/omnizip/archive_handlers.rb +11 -0
  39. data/lib/omnizip/checksum_registry.rb +23 -96
  40. data/lib/omnizip/cli/shared.rb +31 -0
  41. data/lib/omnizip/cli.rb +6 -43
  42. data/lib/omnizip/commands/archive_create_command.rb +0 -6
  43. data/lib/omnizip/commands/archive_extract_command.rb +0 -4
  44. data/lib/omnizip/commands/archive_list_command.rb +0 -4
  45. data/lib/omnizip/commands/archive_repair_command.rb +0 -2
  46. data/lib/omnizip/commands/archive_verify_command.rb +0 -2
  47. data/lib/omnizip/commands/compress_command.rb +0 -2
  48. data/lib/omnizip/commands/decompress_command.rb +0 -2
  49. data/lib/omnizip/commands/list_command.rb +0 -2
  50. data/lib/omnizip/commands/metadata_command.rb +0 -5
  51. data/lib/omnizip/commands/parity_create_command.rb +0 -2
  52. data/lib/omnizip/commands/parity_repair_command.rb +0 -2
  53. data/lib/omnizip/commands/parity_verify_command.rb +0 -2
  54. data/lib/omnizip/commands/profile_list_command.rb +0 -2
  55. data/lib/omnizip/commands/profile_show_command.rb +0 -2
  56. data/lib/omnizip/convenience.rb +111 -234
  57. data/lib/omnizip/converter/seven_zip_to_zip_strategy.rb +1 -1
  58. data/lib/omnizip/crypto/aes256/cipher.rb +6 -1
  59. data/lib/omnizip/error.rb +22 -18
  60. data/lib/omnizip/filter.rb +5 -3
  61. data/lib/omnizip/filter_registry.rb +74 -98
  62. data/lib/omnizip/filters/bcj2.rb +0 -2
  63. data/lib/omnizip/filters/bcj_x86.rb +0 -2
  64. data/lib/omnizip/filters/filter_base.rb +4 -70
  65. data/lib/omnizip/filters/registry.rb +0 -2
  66. data/lib/omnizip/format_detector.rb +21 -12
  67. data/lib/omnizip/format_registry.rb +69 -53
  68. data/lib/omnizip/formats/bzip2_file.rb +0 -2
  69. data/lib/omnizip/formats/cpio/entry.rb +0 -2
  70. data/lib/omnizip/formats/cpio/reader.rb +0 -1
  71. data/lib/omnizip/formats/cpio/writer.rb +0 -1
  72. data/lib/omnizip/formats/cpio.rb +0 -1
  73. data/lib/omnizip/formats/format_spec_loader.rb +1 -1
  74. data/lib/omnizip/formats/gzip.rb +0 -2
  75. data/lib/omnizip/formats/iso/directory_record.rb +6 -3
  76. data/lib/omnizip/formats/iso/reader.rb +2 -2
  77. data/lib/omnizip/formats/iso.rb +0 -1
  78. data/lib/omnizip/formats/lzip.rb +5 -25
  79. data/lib/omnizip/formats/lzma_alone.rb +7 -25
  80. data/lib/omnizip/formats/msi/reader.rb +1 -0
  81. data/lib/omnizip/formats/msi/string_pool.rb +2 -3
  82. data/lib/omnizip/formats/msi.rb +1 -4
  83. data/lib/omnizip/formats/ole/allocation_table.rb +0 -1
  84. data/lib/omnizip/formats/ole/dirent.rb +9 -4
  85. data/lib/omnizip/formats/ole/header.rb +0 -1
  86. data/lib/omnizip/formats/ole/ranges_io.rb +1 -1
  87. data/lib/omnizip/formats/ole/storage.rb +5 -6
  88. data/lib/omnizip/formats/ole.rb +0 -1
  89. data/lib/omnizip/formats/rar/block_parser.rb +0 -1
  90. data/lib/omnizip/formats/rar/compression/ppmd/encoder.rb +1 -1
  91. data/lib/omnizip/formats/rar/decompressor.rb +1 -0
  92. data/lib/omnizip/formats/rar/header.rb +0 -1
  93. data/lib/omnizip/formats/rar/rar5/writer.rb +0 -3
  94. data/lib/omnizip/formats/rar/reader.rb +2 -3
  95. data/lib/omnizip/formats/rar/writer.rb +0 -1
  96. data/lib/omnizip/formats/rar.rb +0 -1
  97. data/lib/omnizip/formats/rpm/entry.rb +0 -1
  98. data/lib/omnizip/formats/rpm/header.rb +1 -6
  99. data/lib/omnizip/formats/rpm/lead.rb +37 -26
  100. data/lib/omnizip/formats/rpm/tag.rb +0 -1
  101. data/lib/omnizip/formats/rpm/writer.rb +0 -1
  102. data/lib/omnizip/formats/seven_zip/bcj2_stream_decompressor.rb +0 -1
  103. data/lib/omnizip/formats/seven_zip/coder_chain.rb +1 -2
  104. data/lib/omnizip/formats/seven_zip/encoded_header.rb +0 -1
  105. data/lib/omnizip/formats/seven_zip/encrypted_header.rb +0 -1
  106. data/lib/omnizip/formats/seven_zip/header.rb +0 -1
  107. data/lib/omnizip/formats/seven_zip/header_encryptor.rb +0 -1
  108. data/lib/omnizip/formats/seven_zip/header_writer.rb +0 -1
  109. data/lib/omnizip/formats/seven_zip/models/file_entry.rb +1 -1
  110. data/lib/omnizip/formats/seven_zip/parser.rb +1 -2
  111. data/lib/omnizip/formats/seven_zip/reader.rb +13 -2
  112. data/lib/omnizip/formats/seven_zip/split_archive_reader.rb +0 -1
  113. data/lib/omnizip/formats/seven_zip/split_archive_writer.rb +0 -1
  114. data/lib/omnizip/formats/seven_zip/writer.rb +4 -6
  115. data/lib/omnizip/formats/seven_zip.rb +2 -4
  116. data/lib/omnizip/formats/tar/entry.rb +0 -2
  117. data/lib/omnizip/formats/tar/header.rb +0 -2
  118. data/lib/omnizip/formats/tar/reader.rb +0 -2
  119. data/lib/omnizip/formats/tar/writer.rb +0 -2
  120. data/lib/omnizip/formats/tar.rb +0 -1
  121. data/lib/omnizip/formats/xar/entry.rb +0 -1
  122. data/lib/omnizip/formats/xar/header.rb +0 -1
  123. data/lib/omnizip/formats/xar/reader.rb +0 -1
  124. data/lib/omnizip/formats/xar/toc.rb +1 -2
  125. data/lib/omnizip/formats/xar/writer.rb +11 -3
  126. data/lib/omnizip/formats/xar.rb +0 -1
  127. data/lib/omnizip/formats/xz/reader.rb +7 -3
  128. data/lib/omnizip/formats/xz.rb +3 -40
  129. data/lib/omnizip/formats/xz_impl/block_decoder.rb +4 -10
  130. data/lib/omnizip/formats/xz_impl/block_encoder.rb +0 -2
  131. data/lib/omnizip/formats/xz_impl/block_header_parser.rb +0 -2
  132. data/lib/omnizip/formats/xz_impl/index_decoder.rb +0 -1
  133. data/lib/omnizip/formats/xz_impl/index_encoder.rb +0 -1
  134. data/lib/omnizip/formats/xz_impl/stream_encoder.rb +1 -1
  135. data/lib/omnizip/formats/xz_impl/stream_footer.rb +0 -1
  136. data/lib/omnizip/formats/xz_impl/stream_footer_parser.rb +0 -1
  137. data/lib/omnizip/formats/xz_impl/stream_header.rb +0 -1
  138. data/lib/omnizip/formats/xz_impl/stream_header_parser.rb +0 -2
  139. data/lib/omnizip/formats/zip/central_directory_header.rb +2 -3
  140. data/lib/omnizip/formats/zip/end_of_central_directory.rb +0 -2
  141. data/lib/omnizip/formats/zip/local_file_header.rb +0 -2
  142. data/lib/omnizip/formats/zip/reader.rb +12 -2
  143. data/lib/omnizip/formats/zip/writer.rb +73 -1
  144. data/lib/omnizip/formats/zip/zip64_end_of_central_directory.rb +0 -2
  145. data/lib/omnizip/formats/zip/zip64_end_of_central_directory_locator.rb +0 -2
  146. data/lib/omnizip/formats/zip/zip64_extra_field.rb +0 -2
  147. data/lib/omnizip/formats/zip.rb +0 -1
  148. data/lib/omnizip/formats.rb +8 -24
  149. data/lib/omnizip/implementations/seven_zip/lzma/range_decoder.rb +2 -2
  150. data/lib/omnizip/implementations/seven_zip/lzma/range_encoder.rb +1 -1
  151. data/lib/omnizip/implementations/seven_zip/lzma2/encoder.rb +0 -6
  152. data/lib/omnizip/implementations/xz_utils/lzma2/decoder.rb +3 -4
  153. data/lib/omnizip/implementations/xz_utils/lzma2/encoder.rb +0 -1
  154. data/lib/omnizip/io/source.rb +136 -0
  155. data/lib/omnizip/io.rb +2 -0
  156. data/lib/omnizip/link_handler.rb +7 -3
  157. data/lib/omnizip/metadata/metadata_editor.rb +1 -1
  158. data/lib/omnizip/models/algorithm_metadata.rb +33 -60
  159. data/lib/omnizip/models/compression_options.rb +40 -53
  160. data/lib/omnizip/models/conversion_options.rb +9 -18
  161. data/lib/omnizip/models/parallel_options.rb +25 -0
  162. data/lib/omnizip/optimization_registry.rb +8 -44
  163. data/lib/omnizip/parallel/job_scheduler.rb +26 -8
  164. data/lib/omnizip/parallel/parallel_compressor.rb +10 -20
  165. data/lib/omnizip/parallel/parallel_extractor.rb +20 -11
  166. data/lib/omnizip/parity/par2_repairer.rb +5 -5
  167. data/lib/omnizip/parity/par2_verifier.rb +27 -22
  168. data/lib/omnizip/parity.rb +4 -4
  169. data/lib/omnizip/password/encryption_registry.rb +11 -50
  170. data/lib/omnizip/password/zip_crypto_strategy.rb +1 -4
  171. data/lib/omnizip/pipe/stream_decompressor.rb +2 -3
  172. data/lib/omnizip/profile/archive_profile.rb +0 -2
  173. data/lib/omnizip/profile/binary_profile.rb +0 -2
  174. data/lib/omnizip/profile/compression_profile.rb +0 -1
  175. data/lib/omnizip/profile/profile_detector.rb +0 -2
  176. data/lib/omnizip/profile/text_profile.rb +0 -2
  177. data/lib/omnizip/profile.rb +0 -2
  178. data/lib/omnizip/registry.rb +112 -0
  179. data/lib/omnizip/version.rb +1 -1
  180. data/lib/omnizip/zip/file.rb +20 -9
  181. data/lib/omnizip.rb +26 -58
  182. metadata +24 -2
@@ -74,14 +74,14 @@ module Omnizip
74
74
  # UPDATE_0: bit is 0
75
75
  @range = bound & 0xFFFFFFFF
76
76
  new_prob = prob + ((BIT_MODEL_TOTAL - prob) >> MOVE_BITS)
77
- model.instance_variable_set(:@probability, new_prob)
77
+ model.probability = new_prob
78
78
  0
79
79
  else
80
80
  # UPDATE_1: bit is 1
81
81
  @range = (@range - bound) & 0xFFFFFFFF
82
82
  @code = (@code - bound) & 0xFFFFFFFF
83
83
  new_prob = prob - (prob >> MOVE_BITS)
84
- model.instance_variable_set(:@probability, new_prob)
84
+ model.probability = new_prob
85
85
  1
86
86
  end
87
87
  end
@@ -91,7 +91,7 @@ module Omnizip
91
91
  # Update probability: ttt -= ttt >> kNumMoveBits
92
92
  prob - (prob >> MOVE_BITS)
93
93
  end
94
- model.instance_variable_set(:@probability, new_prob)
94
+ model.probability = new_prob
95
95
 
96
96
  # RC_NORM: Normalize AFTER encoding (key SDK difference!)
97
97
  normalize
@@ -71,12 +71,6 @@ module Omnizip
71
71
 
72
72
  # Initialize shared state across all chunks
73
73
  # Using XZ Utils components (tested and working)
74
- require "omnizip/algorithms/lzma/dictionary"
75
- require "omnizip/algorithms/lzma/lzma_state"
76
- require "omnizip/algorithms/lzma/xz_probability_models"
77
- require "omnizip/algorithms/lzma/match_finder"
78
- require "omnizip/algorithms/lzma/optimal_encoder"
79
- require "omnizip/algorithms/lzma/xz_range_encoder_exact"
80
74
 
81
75
  @dictionary = Omnizip::Algorithms::LZMA::Dictionary.new(dict_size)
82
76
  @state = Omnizip::Algorithms::LZMA::LZMAState.new(0)
@@ -21,7 +21,6 @@
21
21
  # DEALINGS IN THE SOFTWARE.
22
22
 
23
23
  require "stringio"
24
- require "omnizip/algorithms"
25
24
 
26
25
  module Omnizip
27
26
  module Implementations
@@ -418,10 +417,10 @@ module Omnizip
418
417
  if @lzma2_debug
419
418
  warn "DEBUG: decompress_lzma_chunk - After set_input, checking range_decoder..."
420
419
  # Check if the decoder has a range_decoder variable
421
- if @lzma_decoder.instance_variable_defined?(:@range_decoder)
422
- range_decoder = @lzma_decoder.instance_variable_get(:@range_decoder)
420
+ if @lzma_decoder.respond_to?(:range_decoder)
421
+ range_decoder = @lzma_decoder.range_decoder
423
422
  if range_decoder
424
- warn " range_decoder exists: code=0x#{range_decoder.instance_variable_get(:@code).to_s(16)}, range=0x#{range_decoder.instance_variable_get(:@range).to_s(16)}, init_bytes_remaining=#{range_decoder.instance_variable_get(:@init_bytes_remaining)}"
423
+ warn " range_decoder exists: code=0x#{range_decoder.code.to_s(16)}, range=0x#{range_decoder.range.to_s(16)}, init_bytes_remaining=#{range_decoder.init_bytes_remaining}"
425
424
  else
426
425
  warn " range_decoder is nil"
427
426
  end
@@ -21,7 +21,6 @@
21
21
  # DEALINGS IN THE SOFTWARE.
22
22
 
23
23
  require "stringio"
24
- require "omnizip/implementations/base"
25
24
 
26
25
  module Omnizip
27
26
  module Implementations
@@ -0,0 +1,136 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Omnizip
4
+ module IO
5
+ # Polymorphic adapter for "things we can read bytes from".
6
+ #
7
+ # Callers that previously did:
8
+ #
9
+ # data = input.respond_to?(:read) ? input.read : File.binread(input)
10
+ #
11
+ # should now do:
12
+ #
13
+ # data = Omnizip::IO::Source.for(input).read
14
+ #
15
+ # Adapters:
16
+ # - +IO+/+StringIO+/+Tempfile+ → wrapped as-is
17
+ # - +String+ → treated as a file path; read with +File.binread+
18
+ # unless the string contains no NUL bytes and does not exist on
19
+ # disk, in which case it is treated as literal data
20
+ # - +Object+ responding to +:read+ → delegated to
21
+ class Source
22
+ # Build a Source wrapper appropriate for +input+.
23
+ #
24
+ # @param input [IO, StringIO, String, #read] the read target
25
+ # @return [Source]
26
+ def self.for(input)
27
+ case input
28
+ when ::IO, ::StringIO, ::Tempfile then new(input)
29
+ when String then StringSource.new(input)
30
+ else
31
+ unless input.respond_to?(:read)
32
+ raise ArgumentError,
33
+ "Cannot adapt #{input.inspect} to Omnizip::IO::Source"
34
+ end
35
+
36
+ new(input)
37
+ end
38
+ end
39
+
40
+ def initialize(io)
41
+ @io = io
42
+ end
43
+
44
+ # Delegate reading to the underlying IO-like object.
45
+ def read(length = nil, outbuf = nil)
46
+ if length.nil?
47
+ @io.read
48
+ elsif outbuf
49
+ @io.read(length, outbuf)
50
+ else
51
+ @io.read(length)
52
+ end
53
+ end
54
+
55
+ def close
56
+ @io.close if @io.respond_to?(:close)
57
+ end
58
+
59
+ # String input adapter. Treats the value as a file path if it
60
+ # exists on disk and contains no NUL bytes, otherwise as raw
61
+ # bytes.
62
+ class StringSource < Source
63
+ def initialize(value)
64
+ super(nil)
65
+ @value = value
66
+ end
67
+
68
+ def read(_length = nil, _outbuf = nil)
69
+ if file_path?
70
+ File.binread(@value)
71
+ else
72
+ @value.b
73
+ end
74
+ end
75
+
76
+ def close; end
77
+
78
+ private
79
+
80
+ def file_path?
81
+ !@value.include?("\0") && File.exist?(@value)
82
+ end
83
+ end
84
+ end
85
+
86
+ # Polymorphic adapter for "things we can write bytes to".
87
+ #
88
+ # Adapters:
89
+ # - +IO+/+StringIO+/+Tempfile+ → wrapped as-is
90
+ # - +String+ → treated as a file path; writes via +File.binwrite+
91
+ # - +Object+ responding to +:write+ → delegated to
92
+ class Sink
93
+ def self.for(output)
94
+ case output
95
+ when ::IO, ::StringIO, ::Tempfile then new(output)
96
+ when String then PathSink.new(output)
97
+ else
98
+ unless output.respond_to?(:write)
99
+ raise ArgumentError,
100
+ "Cannot adapt #{output.inspect} to Omnizip::IO::Sink"
101
+ end
102
+
103
+ new(output)
104
+ end
105
+ end
106
+
107
+ def initialize(io)
108
+ @io = io
109
+ end
110
+
111
+ def write(data)
112
+ @io.write(data)
113
+ end
114
+
115
+ def close
116
+ @io.close if @io.respond_to?(:close)
117
+ end
118
+
119
+ # File-path sink: opens the file for binary write on #write and
120
+ # closes after.
121
+ class PathSink < Sink
122
+ def initialize(path)
123
+ super(nil)
124
+ @path = path
125
+ @file = nil
126
+ end
127
+
128
+ def write(data)
129
+ File.binwrite(@path, data)
130
+ end
131
+
132
+ def close; end
133
+ end
134
+ end
135
+ end
136
+ end
data/lib/omnizip/io.rb CHANGED
@@ -9,5 +9,7 @@ module Omnizip
9
9
  autoload :BufferedInput, "omnizip/io/buffered_input"
10
10
  autoload :BufferedOutput, "omnizip/io/buffered_output"
11
11
  autoload :StreamManager, "omnizip/io/stream_manager"
12
+ autoload :Source, "omnizip/io/source"
13
+ autoload :Sink, "omnizip/io/source"
12
14
  end
13
15
  end
@@ -11,14 +11,18 @@ module Omnizip
11
11
  !windows_platform?
12
12
  end
13
13
 
14
- # Check if symbolic links are supported
14
+ # Check if symbolic links are supported on this platform.
15
+ # Delegates to Platform but also gates on LinkHandler#supported?
16
+ # (Windows is treated as unsupported here for historical
17
+ # compatibility — even if Windows Developer Mode would allow
18
+ # symlinks, callers should opt into that explicitly via Platform).
15
19
  def symlink_supported?
16
- File.respond_to?(:symlink) && supported?
20
+ supported? && Omnizip::Platform.supports_symlinks?
17
21
  end
18
22
 
19
23
  # Check if hard links are supported
20
24
  def hardlink_supported?
21
- File.respond_to?(:link) && supported?
25
+ supported? && Omnizip::Platform.supports_hardlinks?
22
26
  end
23
27
 
24
28
  # Detect if a path is a symbolic link
@@ -160,7 +160,7 @@ module Omnizip
160
160
  def commit
161
161
  # Changes are already applied to entries
162
162
  # Just mark archive as modified
163
- archive.instance_variable_set(:@modified, true) if modified?
163
+ archive.modified! if modified?
164
164
  @changes = []
165
165
  self
166
166
  end
@@ -1,72 +1,45 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- #
4
- # Copyright (C) 2024 Ribose Inc.
5
- #
6
- # This file is part of Omnizip.
7
- #
8
- # Omnizip is a pure Ruby port of 7-Zip compression algorithms.
9
- # Based on the 7-Zip LZMA SDK by Igor Pavlov.
10
- #
11
- # This library is free software; you can redistribute it and/or
12
- # modify it under the terms of the GNU Lesser General Public
13
- # License as published by the Free Software Foundation; either
14
- # version 2.1 of the License, or (at your option) any later version.
15
- #
16
- # See the COPYING file for the complete text of the license.
17
- #
18
-
19
- require "json"
3
+ require "lutaml/model"
20
4
 
21
5
  module Omnizip
22
6
  module Models
23
- # Model representing metadata about a compression algorithm.
7
+ # Metadata describing a compression algorithm.
24
8
  #
25
- # This class encapsulates information about a compression algorithm,
26
- # including its name, description, version, and capabilities.
27
- class AlgorithmMetadata
28
- attr_accessor :name, :description, :version, :author,
29
- :max_compression_level, :min_compression_level,
30
- :default_compression_level, :supports_streaming,
31
- :supports_multithreading
32
-
33
- def initialize(**kwargs)
34
- @supports_streaming = false
35
- @supports_multithreading = false
36
-
37
- kwargs.each do |key, value|
38
- instance_variable_set("@#{key}", value)
39
- end
40
- end
41
-
42
- def to_h
43
- {
44
- name: @name,
45
- description: @description,
46
- version: @version,
47
- author: @author,
48
- max_compression_level: @max_compression_level,
49
- min_compression_level: @min_compression_level,
50
- default_compression_level: @default_compression_level,
51
- supports_streaming: @supports_streaming,
52
- supports_multithreading: @supports_multithreading,
53
- }.compact
9
+ # Serialized via lutaml-model no hand-rolled +to_h+ / +to_json+.
10
+ class AlgorithmMetadata < Lutaml::Model::Serializable
11
+ attribute :name, :string
12
+ attribute :description, :string
13
+ attribute :version, :string
14
+ attribute :author, :string
15
+ attribute :max_compression_level, :integer
16
+ attribute :min_compression_level, :integer
17
+ attribute :default_compression_level, :integer
18
+ attribute :supports_streaming, :boolean, default: false
19
+ attribute :supports_multithreading, :boolean, default: false
20
+
21
+ json do
22
+ map :name, to: :name
23
+ map :description, to: :description
24
+ map :version, to: :version
25
+ map :author, to: :author
26
+ map :max_compression_level, to: :max_compression_level
27
+ map :min_compression_level, to: :min_compression_level
28
+ map :default_compression_level, to: :default_compression_level
29
+ map :supports_streaming, to: :supports_streaming
30
+ map :supports_multithreading, to: :supports_multithreading
54
31
  end
55
32
 
56
- # Serialize to JSON
33
+ # Apply a hash of attributes (whitelisted to declared attributes).
57
34
  #
58
- # @return [String] JSON representation
59
- def to_json(*args)
60
- to_h.to_json(*args)
61
- end
62
-
63
- # Deserialize from JSON
64
- #
65
- # @param json [String] JSON string
66
- # @return [AlgorithmMetadata] Deserialized instance
67
- def self.from_json(json)
68
- data = JSON.parse(json)
69
- new(**data.transform_keys(&:to_sym))
35
+ # @param attributes [Hash{Symbol=>Object}]
36
+ # @return [self]
37
+ def apply(attributes)
38
+ self.class.attributes.each do |attr|
39
+ name = attr.name
40
+ public_send("#{name}=", attributes[name]) if attributes.key?(name)
41
+ end
42
+ self
70
43
  end
71
44
  end
72
45
  end
@@ -1,70 +1,57 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- #
4
- # Copyright (C) 2024 Ribose Inc.
5
- #
6
- # This file is part of Omnizip.
7
- #
8
- # Omnizip is a pure Ruby port of 7-Zip compression algorithms.
9
- # Based on the 7-Zip LZMA SDK by Igor Pavlov.
10
- #
11
- # This library is free software; you can redistribute it and/or
12
- # modify it under the terms of the GNU Lesser General Public
13
- # License as published by the Free Software Foundation; either
14
- # version 2.1 of the License, or (at your option) any later version.
15
- #
16
- # See the COPYING file for the complete text of the license.
17
- #
18
-
3
+ require "lutaml/model"
19
4
  require "json"
20
5
 
21
6
  module Omnizip
22
7
  module Models
23
8
  # Model representing compression operation options.
24
9
  #
25
- # This class encapsulates configuration options for compression
26
- # operations, including compression level, threading, and buffer sizes.
27
- class CompressionOptions
28
- attr_accessor :level, :dictionary_size, :num_fast_bytes, :match_finder,
29
- :num_threads, :solid, :buffer_size
30
-
31
- def initialize(**kwargs)
32
- @level = 5
33
- @num_threads = 1
34
- @solid = false
35
- @buffer_size = 65_536
36
-
37
- kwargs.each do |key, value|
38
- instance_variable_set("@#{key}", value)
39
- end
10
+ # Serialized via lutaml-model callers should not write hand-rolled
11
+ # +to_h+ / +to_json+ / +from_json+ on this class. The framework
12
+ # provides +#to_hash+, +#to_json+, and +.from_hash+ / +.from_json+
13
+ # automatically from the +attribute+ declarations below.
14
+ class CompressionOptions < Lutaml::Model::Serializable
15
+ attribute :level, :integer, default: 5
16
+ attribute :dictionary_size, :integer
17
+ attribute :num_fast_bytes, :integer
18
+ attribute :match_finder, :string
19
+ attribute :num_threads, :integer, default: 1
20
+ attribute :solid, :boolean, default: false
21
+ attribute :buffer_size, :integer, default: 65_536
22
+
23
+ # JSON / hash mappings (1:1, no renames needed).
24
+ json do
25
+ map :level, to: :level
26
+ map :dictionary_size, to: :dictionary_size
27
+ map :num_fast_bytes, to: :num_fast_bytes
28
+ map :match_finder, to: :match_finder
29
+ map :num_threads, to: :num_threads
30
+ map :solid, to: :solid
31
+ map :buffer_size, to: :buffer_size
40
32
  end
41
33
 
42
- def to_h
43
- {
44
- level: @level,
45
- dictionary_size: @dictionary_size,
46
- num_fast_bytes: @num_fast_bytes,
47
- match_finder: @match_finder,
48
- num_threads: @num_threads,
49
- solid: @solid,
50
- buffer_size: @buffer_size,
51
- }.compact
52
- end
53
-
54
- # Serialize to JSON
34
+ # Apply a hash of attributes. Only keys declared as +attribute+s
35
+ # above are applied; unknown keys are silently ignored.
55
36
  #
56
- # @return [String] JSON representation
57
- def to_json(*args)
58
- to_h.to_json(*args)
37
+ # @param attributes [Hash{Symbol=>Object}]
38
+ # @return [self]
39
+ def apply(attributes)
40
+ self.class.attributes.each do |attr|
41
+ name = attr.name
42
+ public_send("#{name}=", attributes[name]) if attributes.key?(name)
43
+ end
44
+ self
59
45
  end
60
46
 
61
- # Deserialize from JSON
47
+ # Validate that all set values are within their type's domain.
62
48
  #
63
- # @param json [String] JSON string
64
- # @return [CompressionOptions] Deserialized instance
65
- def self.from_json(json)
66
- data = JSON.parse(json)
67
- new(**data.transform_keys(&:to_sym))
49
+ # @raise [ArgumentError] if any value is invalid
50
+ # @return [Boolean] true if valid
51
+ def validate!
52
+ raise ArgumentError, "level must be 0-9" unless (0..9).cover?(level)
53
+
54
+ true
68
55
  end
69
56
  end
70
57
  end
@@ -2,22 +2,18 @@
2
2
 
3
3
  module Omnizip
4
4
  module Models
5
- # Model for format conversion options
5
+ # Options for converting between archive formats.
6
+ #
7
+ # Plain Ruby value object. (Track 13 of TODO.refactor recommends
8
+ # migrating this to lutaml-model once symbol-typed attributes are
9
+ # available.)
6
10
  class ConversionOptions
11
+ VALID_FORMATS = %i[zip seven_zip 7z].freeze
12
+
7
13
  attr_accessor :source_format, :target_format, :compression,
8
14
  :compression_level, :filter, :preserve_metadata,
9
15
  :temp_directory, :solid, :delete_source
10
16
 
11
- # Initialize conversion options
12
- # @param source_format [Symbol, nil] Source format (auto-detect if nil)
13
- # @param target_format [Symbol] Target format
14
- # @param compression [Symbol, nil] Compression algorithm
15
- # @param compression_level [Integer] Compression level (1-9)
16
- # @param filter [Symbol, nil] Filter to apply
17
- # @param preserve_metadata [Boolean] Preserve metadata
18
- # @param temp_directory [String, nil] Temporary directory
19
- # @param solid [Boolean] Use solid compression (7z only)
20
- # @param delete_source [Boolean] Delete source after conversion
21
17
  def initialize(
22
18
  source_format: nil,
23
19
  target_format: :seven_zip,
@@ -40,8 +36,6 @@ module Omnizip
40
36
  @delete_source = delete_source
41
37
  end
42
38
 
43
- # Convert to hash
44
- # @return [Hash] Options as hash
45
39
  def to_h
46
40
  {
47
41
  source_format: source_format,
@@ -56,8 +50,6 @@ module Omnizip
56
50
  }
57
51
  end
58
52
 
59
- # Validate options
60
- # @raise [ArgumentError] If options are invalid
61
53
  def validate
62
54
  validate_format(target_format, "target")
63
55
  validate_format(source_format, "source") if source_format
@@ -69,11 +61,10 @@ module Omnizip
69
61
  private
70
62
 
71
63
  def validate_format(format, type)
72
- valid_formats = %i[zip seven_zip 7z]
73
- return if valid_formats.include?(format)
64
+ return if VALID_FORMATS.include?(format)
74
65
 
75
66
  raise ArgumentError, "Invalid #{type} format: #{format}. " \
76
- "Valid formats: #{valid_formats.join(', ')}"
67
+ "Valid formats: #{VALID_FORMATS.join(', ')}"
77
68
  end
78
69
 
79
70
  def validate_compression_level
@@ -60,6 +60,31 @@ module Omnizip
60
60
  true
61
61
  end
62
62
 
63
+ # Apply a hash of attributes to this options object. Only keys
64
+ # that correspond to known writers (+threads=+, +queue_size=+,
65
+ # etc.) are applied; unknown keys are silently ignored.
66
+ #
67
+ # @param attributes [Hash{Symbol=>Object}] attributes to set
68
+ # @return [self]
69
+ def apply(attributes)
70
+ SETTERS.each do |key, setter|
71
+ next unless attributes.key?(key)
72
+
73
+ public_send(setter, attributes[key])
74
+ end
75
+ self
76
+ end
77
+
78
+ SETTERS = {
79
+ threads: :threads=,
80
+ queue_size: :queue_size=,
81
+ chunk_size: :chunk_size=,
82
+ strategy: :strategy=,
83
+ verbose: :verbose=,
84
+ batch_size: :batch_size=,
85
+ }.freeze
86
+ private_constant :SETTERS
87
+
63
88
  # Create a copy of options
64
89
  #
65
90
  # @return [ParallelOptions] new instance with same values
@@ -1,59 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Omnizip
4
- # Registry for performance optimization strategies using the Registry pattern
5
- class OptimizationRegistry
4
+ class OptimizationRegistry < Omnizip::Registry
6
5
  class << self
7
- # Register an optimization strategy
8
- def register(name, strategy_class)
9
- strategies[name] = strategy_class
6
+ def not_found_error_class
7
+ Omnizip::OptimizationNotFoundError
10
8
  end
11
9
 
12
- # Get an optimization strategy by name
13
- def get(name)
14
- strategies[name] || raise(
15
- Omnizip::OptimizationNotFound,
16
- "Optimization strategy not found: #{name}",
17
- )
10
+ def label
11
+ "Optimization strategy"
18
12
  end
19
13
 
20
- # Check if an optimization strategy is registered
21
- def registered?(name)
22
- strategies.key?(name)
23
- end
24
-
25
- # List all registered optimization strategies
26
- def all
27
- strategies.keys
28
- end
29
-
30
- # Get all optimization strategies as a hash
31
- def strategies
32
- @strategies ||= {}
33
- end
34
-
35
- # Clear all registered strategies (useful for testing)
36
- def clear!
37
- @strategies = {}
38
- end
39
-
40
- # Apply an optimization strategy to a target
41
14
  def apply(name, target, **options)
42
- strategy_class = get(name)
43
- strategy = strategy_class.new(**options)
44
- strategy.optimize(target)
15
+ get(name).new(**options).optimize(target)
45
16
  end
46
17
 
47
- # Get optimization metadata
48
18
  def metadata(name)
49
- strategy_class = get(name)
50
- return {} unless strategy_class.respond_to?(:metadata)
51
-
52
- strategy_class.metadata
19
+ get(name).metadata
53
20
  end
54
21
  end
55
22
 
56
- # Base class for optimization strategies
57
23
  class Strategy
58
24
  attr_reader :options
59
25
 
@@ -61,13 +27,11 @@ module Omnizip
61
27
  @options = options
62
28
  end
63
29
 
64
- # Override in subclasses to implement optimization logic
65
- def optimize(target)
30
+ def optimize(_target)
66
31
  raise NotImplementedError,
67
32
  "#{self.class} must implement #optimize"
68
33
  end
69
34
 
70
- # Override in subclasses to provide strategy metadata
71
35
  def self.metadata
72
36
  {
73
37
  name: name,