cabriolet 0.1.2 → 0.2.1
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/README.adoc +703 -38
- data/lib/cabriolet/algorithm_factory.rb +250 -0
- data/lib/cabriolet/base_compressor.rb +206 -0
- data/lib/cabriolet/binary/bitstream.rb +167 -16
- data/lib/cabriolet/binary/bitstream_writer.rb +150 -21
- data/lib/cabriolet/binary/chm_structures.rb +2 -2
- data/lib/cabriolet/binary/hlp_structures.rb +258 -37
- data/lib/cabriolet/binary/lit_structures.rb +231 -65
- data/lib/cabriolet/binary/oab_structures.rb +17 -1
- data/lib/cabriolet/cab/command_handler.rb +226 -0
- data/lib/cabriolet/cab/compressor.rb +108 -84
- data/lib/cabriolet/cab/decompressor.rb +16 -20
- data/lib/cabriolet/cab/extractor.rb +142 -66
- data/lib/cabriolet/cab/file_compression_work.rb +52 -0
- data/lib/cabriolet/cab/file_compression_worker.rb +89 -0
- data/lib/cabriolet/checksum.rb +49 -0
- data/lib/cabriolet/chm/command_handler.rb +227 -0
- data/lib/cabriolet/chm/compressor.rb +7 -3
- data/lib/cabriolet/chm/decompressor.rb +39 -21
- data/lib/cabriolet/chm/parser.rb +5 -2
- data/lib/cabriolet/cli/base_command_handler.rb +127 -0
- data/lib/cabriolet/cli/command_dispatcher.rb +140 -0
- data/lib/cabriolet/cli/command_registry.rb +83 -0
- data/lib/cabriolet/cli.rb +356 -607
- data/lib/cabriolet/collections/file_collection.rb +175 -0
- data/lib/cabriolet/compressors/base.rb +1 -1
- data/lib/cabriolet/compressors/lzx.rb +241 -54
- data/lib/cabriolet/compressors/mszip.rb +35 -3
- data/lib/cabriolet/compressors/quantum.rb +36 -95
- data/lib/cabriolet/decompressors/base.rb +1 -1
- data/lib/cabriolet/decompressors/lzss.rb +13 -3
- data/lib/cabriolet/decompressors/lzx.rb +70 -33
- data/lib/cabriolet/decompressors/mszip.rb +126 -39
- data/lib/cabriolet/decompressors/quantum.rb +83 -53
- data/lib/cabriolet/errors.rb +3 -0
- data/lib/cabriolet/extraction/base_extractor.rb +88 -0
- data/lib/cabriolet/extraction/extractor.rb +171 -0
- data/lib/cabriolet/extraction/file_extraction_work.rb +60 -0
- data/lib/cabriolet/extraction/file_extraction_worker.rb +106 -0
- data/lib/cabriolet/file_entry.rb +156 -0
- data/lib/cabriolet/file_manager.rb +144 -0
- data/lib/cabriolet/format_base.rb +79 -0
- data/lib/cabriolet/hlp/command_handler.rb +282 -0
- data/lib/cabriolet/hlp/compressor.rb +28 -238
- data/lib/cabriolet/hlp/decompressor.rb +107 -147
- data/lib/cabriolet/hlp/parser.rb +52 -101
- data/lib/cabriolet/hlp/quickhelp/compression_stream.rb +138 -0
- data/lib/cabriolet/hlp/quickhelp/compressor.rb +151 -0
- data/lib/cabriolet/hlp/quickhelp/decompressor.rb +558 -0
- data/lib/cabriolet/hlp/quickhelp/file_writer.rb +125 -0
- data/lib/cabriolet/hlp/quickhelp/huffman_stream.rb +74 -0
- data/lib/cabriolet/hlp/quickhelp/huffman_tree.rb +167 -0
- data/lib/cabriolet/hlp/quickhelp/offset_calculator.rb +61 -0
- data/lib/cabriolet/hlp/quickhelp/parser.rb +274 -0
- data/lib/cabriolet/hlp/quickhelp/structure_builder.rb +93 -0
- data/lib/cabriolet/hlp/quickhelp/topic_builder.rb +52 -0
- data/lib/cabriolet/hlp/quickhelp/topic_compressor.rb +83 -0
- data/lib/cabriolet/hlp/winhelp/btree_builder.rb +289 -0
- data/lib/cabriolet/hlp/winhelp/compressor.rb +400 -0
- data/lib/cabriolet/hlp/winhelp/decompressor.rb +192 -0
- data/lib/cabriolet/hlp/winhelp/parser.rb +484 -0
- data/lib/cabriolet/hlp/winhelp/zeck_lz77.rb +271 -0
- data/lib/cabriolet/huffman/encoder.rb +15 -12
- data/lib/cabriolet/huffman/tree.rb +85 -1
- data/lib/cabriolet/kwaj/command_handler.rb +213 -0
- data/lib/cabriolet/kwaj/compressor.rb +7 -3
- data/lib/cabriolet/kwaj/decompressor.rb +18 -12
- data/lib/cabriolet/lit/command_handler.rb +221 -0
- data/lib/cabriolet/lit/compressor.rb +119 -168
- data/lib/cabriolet/lit/content_encoder.rb +76 -0
- data/lib/cabriolet/lit/content_type_detector.rb +50 -0
- data/lib/cabriolet/lit/decompressor.rb +518 -152
- data/lib/cabriolet/lit/directory_builder.rb +153 -0
- data/lib/cabriolet/lit/guid_generator.rb +16 -0
- data/lib/cabriolet/lit/header_writer.rb +124 -0
- data/lib/cabriolet/lit/parser.rb +670 -0
- data/lib/cabriolet/lit/piece_builder.rb +74 -0
- data/lib/cabriolet/lit/structure_builder.rb +252 -0
- data/lib/cabriolet/models/hlp_file.rb +130 -29
- data/lib/cabriolet/models/hlp_header.rb +105 -17
- data/lib/cabriolet/models/lit_header.rb +212 -25
- data/lib/cabriolet/models/szdd_header.rb +10 -2
- data/lib/cabriolet/models/winhelp_header.rb +127 -0
- data/lib/cabriolet/oab/command_handler.rb +257 -0
- data/lib/cabriolet/oab/compressor.rb +17 -8
- data/lib/cabriolet/oab/decompressor.rb +41 -10
- data/lib/cabriolet/offset_calculator.rb +81 -0
- data/lib/cabriolet/plugin.rb +233 -0
- data/lib/cabriolet/plugin_manager.rb +453 -0
- data/lib/cabriolet/plugin_validator.rb +422 -0
- data/lib/cabriolet/quantum_shared.rb +105 -0
- data/lib/cabriolet/system/io_system.rb +3 -0
- data/lib/cabriolet/system/memory_handle.rb +17 -4
- data/lib/cabriolet/szdd/command_handler.rb +217 -0
- data/lib/cabriolet/szdd/compressor.rb +15 -11
- data/lib/cabriolet/szdd/decompressor.rb +18 -9
- data/lib/cabriolet/version.rb +1 -1
- data/lib/cabriolet.rb +181 -20
- metadata +69 -4
- data/lib/cabriolet/auto.rb +0 -173
- data/lib/cabriolet/parallel.rb +0 -333
|
@@ -32,8 +32,10 @@ module Cabriolet
|
|
|
32
32
|
# Initialize OAB compressor
|
|
33
33
|
#
|
|
34
34
|
# @param io_system [System::IOSystem, nil] I/O system or nil for default
|
|
35
|
-
|
|
35
|
+
# @param algorithm_factory [AlgorithmFactory, nil] Custom algorithm factory or nil for default
|
|
36
|
+
def initialize(io_system = nil, algorithm_factory = nil)
|
|
36
37
|
@io_system = io_system || System::IOSystem.new
|
|
38
|
+
@algorithm_factory = algorithm_factory || Cabriolet.algorithm_factory
|
|
37
39
|
@buffer_size = DEFAULT_BUFFER_SIZE
|
|
38
40
|
@block_size = DEFAULT_BLOCK_SIZE
|
|
39
41
|
end
|
|
@@ -47,7 +49,7 @@ module Cabriolet
|
|
|
47
49
|
# @return [Integer] Bytes written
|
|
48
50
|
# @raise [Error] if compression fails
|
|
49
51
|
def compress(input_file, output_file, **options)
|
|
50
|
-
block_size = options
|
|
52
|
+
block_size = options[:block_size] || @block_size
|
|
51
53
|
|
|
52
54
|
input_handle = @io_system.open(input_file, Constants::MODE_READ)
|
|
53
55
|
output_handle = @io_system.open(output_file, Constants::MODE_WRITE)
|
|
@@ -93,7 +95,7 @@ module Cabriolet
|
|
|
93
95
|
# @return [Integer] Bytes written
|
|
94
96
|
# @raise [Error] if compression fails
|
|
95
97
|
def compress_data(data, output_file, **options)
|
|
96
|
-
block_size = options
|
|
98
|
+
block_size = options[:block_size] || @block_size
|
|
97
99
|
|
|
98
100
|
input_handle = System::MemoryHandle.new(data, Constants::MODE_READ)
|
|
99
101
|
output_handle = @io_system.open(output_file, Constants::MODE_WRITE)
|
|
@@ -138,7 +140,7 @@ module Cabriolet
|
|
|
138
140
|
# @return [Integer] Bytes written
|
|
139
141
|
# @raise [Error] if compression fails
|
|
140
142
|
def compress_incremental(input_file, base_file, output_file, **options)
|
|
141
|
-
block_size = options
|
|
143
|
+
block_size = options[:block_size] || @block_size
|
|
142
144
|
|
|
143
145
|
# For now, just compress the new file with patch header
|
|
144
146
|
# A full implementation would generate binary diffs
|
|
@@ -249,7 +251,8 @@ module Cabriolet
|
|
|
249
251
|
compressed_data = compress_with_lzx(block_data)
|
|
250
252
|
|
|
251
253
|
# Use compressed data (or original if compression fails)
|
|
252
|
-
|
|
254
|
+
is_compressed = compressed_data && compressed_data.bytesize < block_data.bytesize
|
|
255
|
+
patch_data = is_compressed ? compressed_data : block_data
|
|
253
256
|
patch_size = patch_data.bytesize
|
|
254
257
|
|
|
255
258
|
# Calculate CRC
|
|
@@ -257,6 +260,7 @@ module Cabriolet
|
|
|
257
260
|
|
|
258
261
|
# Write patch block header
|
|
259
262
|
block_header = Binary::OABStructures::PatchBlockHeader.new
|
|
263
|
+
block_header.flags = is_compressed ? 1 : 0
|
|
260
264
|
block_header.patch_size = patch_size
|
|
261
265
|
block_header.target_size = block_size
|
|
262
266
|
block_header.source_size = source_size
|
|
@@ -287,9 +291,14 @@ module Cabriolet
|
|
|
287
291
|
output_mem = System::MemoryHandle.new("", Constants::MODE_WRITE)
|
|
288
292
|
|
|
289
293
|
# Compress with LZX
|
|
290
|
-
compressor =
|
|
291
|
-
|
|
292
|
-
|
|
294
|
+
compressor = @algorithm_factory.create(
|
|
295
|
+
Constants::COMP_TYPE_LZX,
|
|
296
|
+
:compressor,
|
|
297
|
+
@io_system,
|
|
298
|
+
input_mem,
|
|
299
|
+
output_mem,
|
|
300
|
+
@buffer_size,
|
|
301
|
+
window_bits: window_bits,
|
|
293
302
|
)
|
|
294
303
|
|
|
295
304
|
compressor.compress
|
|
@@ -25,8 +25,10 @@ module Cabriolet
|
|
|
25
25
|
# Initialize OAB decompressor
|
|
26
26
|
#
|
|
27
27
|
# @param io_system [System::IOSystem, nil] I/O system or nil for default
|
|
28
|
-
|
|
28
|
+
# @param algorithm_factory [AlgorithmFactory, nil] Custom algorithm factory or nil for default
|
|
29
|
+
def initialize(io_system = nil, algorithm_factory = nil)
|
|
29
30
|
@io_system = io_system || System::IOSystem.new
|
|
31
|
+
@algorithm_factory = algorithm_factory || Cabriolet.algorithm_factory
|
|
30
32
|
@buffer_size = DEFAULT_BUFFER_SIZE
|
|
31
33
|
end
|
|
32
34
|
|
|
@@ -161,9 +163,9 @@ target_remaining)
|
|
|
161
163
|
# @return [Integer] Bytes written
|
|
162
164
|
def decompress_patch_block(patch_handle, base_handle, output_handle,
|
|
163
165
|
block_max, target_remaining)
|
|
164
|
-
# Read patch block header
|
|
165
|
-
block_data = @io_system.read(patch_handle,
|
|
166
|
-
if block_data.length <
|
|
166
|
+
# Read patch block header (20 bytes with flags field)
|
|
167
|
+
block_data = @io_system.read(patch_handle, 20)
|
|
168
|
+
if block_data.length < 20
|
|
167
169
|
raise Error,
|
|
168
170
|
"Failed to read patch block header"
|
|
169
171
|
end
|
|
@@ -177,6 +179,25 @@ target_remaining)
|
|
|
177
179
|
raise Error, "Invalid patch block header"
|
|
178
180
|
end
|
|
179
181
|
|
|
182
|
+
# Check if data is compressed or uncompressed
|
|
183
|
+
if block_header.uncompressed?
|
|
184
|
+
# Uncompressed data - read and write directly
|
|
185
|
+
data = @io_system.read(patch_handle, block_header.patch_size)
|
|
186
|
+
if data.length < block_header.patch_size
|
|
187
|
+
raise Error, "Failed to read uncompressed patch data"
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# Verify CRC
|
|
191
|
+
actual_crc = Zlib.crc32(data)
|
|
192
|
+
if actual_crc != block_header.crc
|
|
193
|
+
raise Error, "CRC mismatch in patch block"
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
@io_system.write(output_handle, data)
|
|
197
|
+
return block_header.target_size
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# Compressed data - use LZX decompression
|
|
180
201
|
# Calculate window size for LZX
|
|
181
202
|
window_size = ((block_header.source_size + 32_767) & ~32_767) +
|
|
182
203
|
block_header.target_size
|
|
@@ -245,12 +266,17 @@ target_remaining)
|
|
|
245
266
|
output_mem = System::MemoryHandle.new("", Constants::MODE_WRITE)
|
|
246
267
|
|
|
247
268
|
# Decompress with LZX
|
|
248
|
-
lzx =
|
|
249
|
-
|
|
269
|
+
lzx = @algorithm_factory.create(
|
|
270
|
+
Constants::COMP_TYPE_LZX,
|
|
271
|
+
:decompressor,
|
|
272
|
+
@io_system,
|
|
273
|
+
input_mem,
|
|
274
|
+
output_mem,
|
|
275
|
+
@buffer_size,
|
|
250
276
|
window_bits: window_bits,
|
|
251
277
|
reset_interval: 0,
|
|
252
278
|
output_length: block_size,
|
|
253
|
-
is_delta: false
|
|
279
|
+
is_delta: false,
|
|
254
280
|
)
|
|
255
281
|
|
|
256
282
|
bytes_decompressed = lzx.decompress(block_size)
|
|
@@ -286,12 +312,17 @@ target_remaining)
|
|
|
286
312
|
output_mem = System::MemoryHandle.new("", Constants::MODE_WRITE)
|
|
287
313
|
|
|
288
314
|
# Decompress with LZX DELTA (includes reference data)
|
|
289
|
-
lzx =
|
|
290
|
-
|
|
315
|
+
lzx = @algorithm_factory.create(
|
|
316
|
+
Constants::COMP_TYPE_LZX,
|
|
317
|
+
:decompressor,
|
|
318
|
+
@io_system,
|
|
319
|
+
input_mem,
|
|
320
|
+
output_mem,
|
|
321
|
+
@buffer_size,
|
|
291
322
|
window_bits: window_bits,
|
|
292
323
|
reset_interval: 0,
|
|
293
324
|
output_length: block_header.target_size,
|
|
294
|
-
is_delta: true
|
|
325
|
+
is_delta: true,
|
|
295
326
|
)
|
|
296
327
|
|
|
297
328
|
# For patches, we'd need to set reference data in the LZX window
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Cabriolet
|
|
4
|
+
# Abstract base class for offset calculators
|
|
5
|
+
#
|
|
6
|
+
# Single responsibility: Calculate file positions within archive.
|
|
7
|
+
# Strategy pattern: Different formats implement different calculation strategies.
|
|
8
|
+
#
|
|
9
|
+
# Subclasses must implement:
|
|
10
|
+
# - calculate(structure) - Returns hash of offsets
|
|
11
|
+
#
|
|
12
|
+
# @example Creating a calculator
|
|
13
|
+
# class MyFormatCalculator < OffsetCalculator
|
|
14
|
+
# def calculate(structure)
|
|
15
|
+
# { header: 0, data: 100 }
|
|
16
|
+
# end
|
|
17
|
+
# end
|
|
18
|
+
class OffsetCalculator
|
|
19
|
+
# Calculate all offsets in archive structure
|
|
20
|
+
#
|
|
21
|
+
# @param structure [Hash] Archive structure with files, folders, etc.
|
|
22
|
+
# @return [Hash] Offset information
|
|
23
|
+
# @raise [NotImplementedError] if not implemented by subclass
|
|
24
|
+
def calculate(structure)
|
|
25
|
+
raise NotImplementedError,
|
|
26
|
+
"#{self.class.name} must implement calculate(structure)"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
protected
|
|
30
|
+
|
|
31
|
+
# Helper: Calculate cumulative offsets for items
|
|
32
|
+
#
|
|
33
|
+
# @param items [Array] Items to calculate offsets for
|
|
34
|
+
# @param initial_offset [Integer] Starting offset
|
|
35
|
+
# @yield [item] Block that returns size for each item
|
|
36
|
+
# @return [Array<Hash>] Items with their offsets
|
|
37
|
+
def cumulative_offsets(items, initial_offset = 0)
|
|
38
|
+
offset = initial_offset
|
|
39
|
+
items.map do |item|
|
|
40
|
+
current_offset = offset
|
|
41
|
+
item_size = yield(item)
|
|
42
|
+
offset += item_size
|
|
43
|
+
{ item: item, offset: current_offset, size: item_size }
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# CAB-specific offset calculator
|
|
49
|
+
#
|
|
50
|
+
# Calculates offsets for CFHEADER, CFFOLDER entries, CFFILE entries,
|
|
51
|
+
# and CFDATA blocks in Microsoft Cabinet files.
|
|
52
|
+
class CABOffsetCalculator < OffsetCalculator
|
|
53
|
+
# Calculate CAB file offsets
|
|
54
|
+
#
|
|
55
|
+
# @param structure [Hash] Must contain :folders and :files
|
|
56
|
+
# @return [Hash] Offset information
|
|
57
|
+
def calculate(structure)
|
|
58
|
+
offset = Constants::CFHEADER_SIZE
|
|
59
|
+
|
|
60
|
+
# Folders section
|
|
61
|
+
folders_offset = offset
|
|
62
|
+
offset += Constants::CFFOLDER_SIZE * structure[:folders].size
|
|
63
|
+
|
|
64
|
+
# Files section
|
|
65
|
+
files_offset = offset
|
|
66
|
+
structure[:files].each do |file_info|
|
|
67
|
+
offset += Constants::CFFILE_SIZE
|
|
68
|
+
offset += file_info[:name].bytesize + 1 # null-terminated
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Data blocks section
|
|
72
|
+
data_offset = offset
|
|
73
|
+
|
|
74
|
+
{
|
|
75
|
+
folders: folders_offset,
|
|
76
|
+
files: files_offset,
|
|
77
|
+
data: data_offset,
|
|
78
|
+
}
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Cabriolet
|
|
4
|
+
# Base class for all Cabriolet plugins
|
|
5
|
+
#
|
|
6
|
+
# Plugins extend Cabriolet's functionality by providing custom compression
|
|
7
|
+
# algorithms, format handlers, or other enhancements. All plugins must
|
|
8
|
+
# inherit from this base class and implement required methods.
|
|
9
|
+
#
|
|
10
|
+
# @abstract Subclass and implement {#metadata} and {#setup} to create
|
|
11
|
+
# a plugin
|
|
12
|
+
#
|
|
13
|
+
# @example Creating a simple plugin
|
|
14
|
+
# class MyPlugin < Cabriolet::Plugin
|
|
15
|
+
# def metadata
|
|
16
|
+
# {
|
|
17
|
+
# name: "my-plugin",
|
|
18
|
+
# version: "1.0.0",
|
|
19
|
+
# author: "Your Name",
|
|
20
|
+
# description: "Adds custom compression algorithm",
|
|
21
|
+
# cabriolet_version: "~> 0.1"
|
|
22
|
+
# }
|
|
23
|
+
# end
|
|
24
|
+
#
|
|
25
|
+
# def setup
|
|
26
|
+
# register_algorithm(:custom, CustomAlgorithm,
|
|
27
|
+
# category: :compressor)
|
|
28
|
+
# end
|
|
29
|
+
# end
|
|
30
|
+
class Plugin
|
|
31
|
+
# Plugin states
|
|
32
|
+
STATES = %i[discovered loaded active failed disabled].freeze
|
|
33
|
+
|
|
34
|
+
# @return [Symbol] Current plugin state
|
|
35
|
+
attr_reader :state
|
|
36
|
+
|
|
37
|
+
# Initialize a new plugin
|
|
38
|
+
#
|
|
39
|
+
# @param manager [PluginManager] The plugin manager instance
|
|
40
|
+
def initialize(manager = nil)
|
|
41
|
+
@manager = manager
|
|
42
|
+
@state = :discovered
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Get plugin metadata
|
|
46
|
+
#
|
|
47
|
+
# @abstract Must be implemented by subclasses
|
|
48
|
+
#
|
|
49
|
+
# @return [Hash] Plugin metadata containing:
|
|
50
|
+
# @option return [String] :name Plugin name (required)
|
|
51
|
+
# @option return [String] :version Plugin version (required)
|
|
52
|
+
# @option return [String] :author Plugin author (required)
|
|
53
|
+
# @option return [String] :description Plugin description (required)
|
|
54
|
+
# @option return [String] :cabriolet_version Compatible Cabriolet
|
|
55
|
+
# version (required)
|
|
56
|
+
# @option return [String] :homepage Plugin homepage URL (optional)
|
|
57
|
+
# @option return [String] :license Plugin license (optional)
|
|
58
|
+
# @option return [Array<String>] :dependencies Plugin dependencies
|
|
59
|
+
# (optional)
|
|
60
|
+
# @option return [Array<String>] :tags Search tags (optional)
|
|
61
|
+
# @option return [Hash] :provides What the plugin provides (optional)
|
|
62
|
+
#
|
|
63
|
+
# @raise [NotImplementedError] If not implemented by subclass
|
|
64
|
+
#
|
|
65
|
+
# @example Minimal metadata
|
|
66
|
+
# def metadata
|
|
67
|
+
# {
|
|
68
|
+
# name: "my-plugin",
|
|
69
|
+
# version: "1.0.0",
|
|
70
|
+
# author: "Your Name",
|
|
71
|
+
# description: "Plugin description",
|
|
72
|
+
# cabriolet_version: "~> 0.1"
|
|
73
|
+
# }
|
|
74
|
+
# end
|
|
75
|
+
#
|
|
76
|
+
# @example Full metadata
|
|
77
|
+
# def metadata
|
|
78
|
+
# {
|
|
79
|
+
# name: "advanced-plugin",
|
|
80
|
+
# version: "2.0.0",
|
|
81
|
+
# author: "Developer",
|
|
82
|
+
# description: "Advanced features",
|
|
83
|
+
# cabriolet_version: ">= 0.1.0",
|
|
84
|
+
# homepage: "https://example.com",
|
|
85
|
+
# license: "MIT",
|
|
86
|
+
# dependencies: ["other-plugin >= 1.0"],
|
|
87
|
+
# tags: ["compression", "algorithm"],
|
|
88
|
+
# provides: { algorithms: [:custom], formats: [:special] }
|
|
89
|
+
# }
|
|
90
|
+
# end
|
|
91
|
+
def metadata
|
|
92
|
+
raise NotImplementedError,
|
|
93
|
+
"#{self.class} must implement metadata method"
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Setup the plugin
|
|
97
|
+
#
|
|
98
|
+
# Called when the plugin is loaded. Use this method to register
|
|
99
|
+
# algorithms, formats, or perform other initialization tasks.
|
|
100
|
+
#
|
|
101
|
+
# @abstract Must be implemented by subclasses
|
|
102
|
+
#
|
|
103
|
+
# @raise [NotImplementedError] If not implemented by subclass
|
|
104
|
+
#
|
|
105
|
+
# @example Register an algorithm
|
|
106
|
+
# def setup
|
|
107
|
+
# register_algorithm(:myalgo, MyAlgorithm,
|
|
108
|
+
# category: :compressor)
|
|
109
|
+
# end
|
|
110
|
+
#
|
|
111
|
+
# @example Register multiple items
|
|
112
|
+
# def setup
|
|
113
|
+
# register_algorithm(:algo1, Algo1, category: :compressor)
|
|
114
|
+
# register_algorithm(:algo2, Algo2, category: :decompressor)
|
|
115
|
+
# register_format(:myformat, MyFormatHandler)
|
|
116
|
+
# end
|
|
117
|
+
def setup
|
|
118
|
+
raise NotImplementedError,
|
|
119
|
+
"#{self.class} must implement setup method"
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# Activate the plugin
|
|
123
|
+
#
|
|
124
|
+
# Called when the plugin is activated. Override to perform actions
|
|
125
|
+
# when the plugin becomes active.
|
|
126
|
+
#
|
|
127
|
+
# @return [void]
|
|
128
|
+
#
|
|
129
|
+
# @example Add hooks on activation
|
|
130
|
+
# def activate
|
|
131
|
+
# puts "#{metadata[:name]} activated"
|
|
132
|
+
# # Additional activation logic...
|
|
133
|
+
# end
|
|
134
|
+
def activate
|
|
135
|
+
# Default implementation does nothing
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
# Deactivate the plugin
|
|
139
|
+
#
|
|
140
|
+
# Called when the plugin is deactivated. Override to perform cleanup
|
|
141
|
+
# when the plugin is deactivated.
|
|
142
|
+
#
|
|
143
|
+
# @return [void]
|
|
144
|
+
#
|
|
145
|
+
# @example Cleanup on deactivation
|
|
146
|
+
# def deactivate
|
|
147
|
+
# # Cleanup resources...
|
|
148
|
+
# puts "#{metadata[:name]} deactivated"
|
|
149
|
+
# end
|
|
150
|
+
def deactivate
|
|
151
|
+
# Default implementation does nothing
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Cleanup the plugin
|
|
155
|
+
#
|
|
156
|
+
# Called when the plugin is unloaded. Override to perform final
|
|
157
|
+
# cleanup tasks.
|
|
158
|
+
#
|
|
159
|
+
# @return [void]
|
|
160
|
+
#
|
|
161
|
+
# @example Final cleanup
|
|
162
|
+
# def cleanup
|
|
163
|
+
# # Release resources...
|
|
164
|
+
# # Close connections...
|
|
165
|
+
# end
|
|
166
|
+
def cleanup
|
|
167
|
+
# Default implementation does nothing
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
protected
|
|
171
|
+
|
|
172
|
+
# Register a compression or decompression algorithm
|
|
173
|
+
#
|
|
174
|
+
# @param type [Symbol] Algorithm type identifier
|
|
175
|
+
# @param klass [Class] Algorithm class
|
|
176
|
+
# @param options [Hash] Registration options
|
|
177
|
+
# @option options [Symbol] :category Required - :compressor or
|
|
178
|
+
# :decompressor
|
|
179
|
+
# @option options [Integer] :priority Algorithm priority (default: 0)
|
|
180
|
+
# @option options [Symbol, nil] :format Format restriction (optional)
|
|
181
|
+
#
|
|
182
|
+
# @return [void]
|
|
183
|
+
#
|
|
184
|
+
# @raise [PluginError] If manager is not available
|
|
185
|
+
#
|
|
186
|
+
# @example Register a compressor
|
|
187
|
+
# register_algorithm(:myalgo, MyCompressor,
|
|
188
|
+
# category: :compressor, priority: 10)
|
|
189
|
+
#
|
|
190
|
+
# @example Register a format-specific decompressor
|
|
191
|
+
# register_algorithm(:special, SpecialDecompressor,
|
|
192
|
+
# category: :decompressor, format: :cab)
|
|
193
|
+
def register_algorithm(type, klass, **options)
|
|
194
|
+
raise PluginError, "Plugin manager not available" unless @manager
|
|
195
|
+
|
|
196
|
+
Cabriolet.algorithm_factory.register(type, klass, **options)
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# Register a format handler
|
|
200
|
+
#
|
|
201
|
+
# @param format [Symbol] Format identifier
|
|
202
|
+
# @param handler [Class] Format handler class
|
|
203
|
+
#
|
|
204
|
+
# @return [void]
|
|
205
|
+
#
|
|
206
|
+
# @raise [PluginError] If manager is not available
|
|
207
|
+
#
|
|
208
|
+
# @example Register a format handler
|
|
209
|
+
# register_format(:myformat, MyFormatHandler)
|
|
210
|
+
def register_format(format, handler)
|
|
211
|
+
raise PluginError, "Plugin manager not available" unless @manager
|
|
212
|
+
|
|
213
|
+
# Format registration will be implemented when format registry exists
|
|
214
|
+
# For now, store in manager's format registry
|
|
215
|
+
@manager.register_format(format, handler)
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# Update plugin state
|
|
219
|
+
#
|
|
220
|
+
# @param new_state [Symbol] New state (must be in STATES)
|
|
221
|
+
#
|
|
222
|
+
# @return [void]
|
|
223
|
+
#
|
|
224
|
+
# @raise [ArgumentError] If state is invalid
|
|
225
|
+
def update_state(new_state)
|
|
226
|
+
unless STATES.include?(new_state)
|
|
227
|
+
raise ArgumentError, "Invalid state: #{new_state}"
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
@state = new_state
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
end
|