cabriolet 0.2.0 → 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 +3 -0
- data/lib/cabriolet/binary/bitstream.rb +32 -21
- data/lib/cabriolet/binary/bitstream_writer.rb +21 -4
- data/lib/cabriolet/cab/compressor.rb +85 -53
- data/lib/cabriolet/cab/decompressor.rb +2 -1
- data/lib/cabriolet/cab/extractor.rb +2 -35
- 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/collections/file_collection.rb +175 -0
- data/lib/cabriolet/compressors/quantum.rb +3 -51
- data/lib/cabriolet/decompressors/quantum.rb +81 -52
- 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/format_base.rb +79 -0
- data/lib/cabriolet/hlp/quickhelp/compressor.rb +28 -503
- data/lib/cabriolet/hlp/quickhelp/file_writer.rb +125 -0
- data/lib/cabriolet/hlp/quickhelp/offset_calculator.rb +61 -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/huffman/encoder.rb +15 -12
- data/lib/cabriolet/lit/compressor.rb +45 -689
- data/lib/cabriolet/lit/content_encoder.rb +76 -0
- data/lib/cabriolet/lit/content_type_detector.rb +50 -0
- 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/piece_builder.rb +74 -0
- data/lib/cabriolet/lit/structure_builder.rb +252 -0
- data/lib/cabriolet/quantum_shared.rb +105 -0
- data/lib/cabriolet/version.rb +1 -1
- data/lib/cabriolet.rb +114 -3
- metadata +38 -4
- data/lib/cabriolet/auto.rb +0 -173
- data/lib/cabriolet/parallel.rb +0 -333
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fractor"
|
|
4
|
+
|
|
5
|
+
module Cabriolet
|
|
6
|
+
module Extraction
|
|
7
|
+
# Work item for file extraction using Fractor
|
|
8
|
+
class FileExtractionWork < Fractor::Work
|
|
9
|
+
# Initialize work item for extracting a single file
|
|
10
|
+
#
|
|
11
|
+
# @param file [Object] File object from archive (responds to :name, :data)
|
|
12
|
+
# @param output_dir [String] Output directory path
|
|
13
|
+
# @param preserve_paths [Boolean] Whether to preserve directory structure
|
|
14
|
+
# @param overwrite [Boolean] Whether to overwrite existing files
|
|
15
|
+
def initialize(file, output_dir:, preserve_paths: true, overwrite: false)
|
|
16
|
+
super({
|
|
17
|
+
file: file,
|
|
18
|
+
output_dir: output_dir,
|
|
19
|
+
preserve_paths: preserve_paths,
|
|
20
|
+
overwrite: overwrite,
|
|
21
|
+
})
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# The file object to extract
|
|
25
|
+
#
|
|
26
|
+
# @return [Object] File from archive
|
|
27
|
+
def file
|
|
28
|
+
input[:file]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Output directory for extraction
|
|
32
|
+
#
|
|
33
|
+
# @return [String] Directory path
|
|
34
|
+
def output_dir
|
|
35
|
+
input[:output_dir]
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Whether to preserve directory structure
|
|
39
|
+
#
|
|
40
|
+
# @return [Boolean]
|
|
41
|
+
def preserve_paths
|
|
42
|
+
input[:preserve_paths]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Whether to overwrite existing files
|
|
46
|
+
#
|
|
47
|
+
# @return [Boolean]
|
|
48
|
+
def overwrite
|
|
49
|
+
input[:overwrite]
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Unique identifier for this work item (filename based)
|
|
53
|
+
#
|
|
54
|
+
# @return [String] Unique identifier
|
|
55
|
+
def id
|
|
56
|
+
file.name
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "fileutils"
|
|
4
|
+
|
|
5
|
+
module Cabriolet
|
|
6
|
+
module Extraction
|
|
7
|
+
# Worker for extracting files using Fractor
|
|
8
|
+
class FileExtractionWorker < Fractor::Worker
|
|
9
|
+
# Process a file extraction work item
|
|
10
|
+
#
|
|
11
|
+
# @param work [FileExtractionWork] Work item to process
|
|
12
|
+
# @return [Fractor::WorkResult] Result of extraction
|
|
13
|
+
def process(work)
|
|
14
|
+
output_path = build_output_path(work)
|
|
15
|
+
|
|
16
|
+
# Check if file exists and skip if not overwriting
|
|
17
|
+
if ::File.exist?(output_path) && !work.overwrite
|
|
18
|
+
return skipped_result(work, "File already exists")
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Create parent directory
|
|
22
|
+
dir = ::File.dirname(output_path)
|
|
23
|
+
FileUtils.mkdir_p(dir) unless ::File.directory?(dir)
|
|
24
|
+
|
|
25
|
+
# Get file data
|
|
26
|
+
data = work.file.data
|
|
27
|
+
unless data
|
|
28
|
+
return skipped_result(work, "No data available")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Write file data
|
|
32
|
+
::File.binwrite(output_path, data)
|
|
33
|
+
|
|
34
|
+
# Preserve file attributes if available
|
|
35
|
+
preserve_file_attributes(output_path, work.file)
|
|
36
|
+
|
|
37
|
+
# Return success result
|
|
38
|
+
Fractor::WorkResult.new(
|
|
39
|
+
result: {
|
|
40
|
+
path: output_path,
|
|
41
|
+
size: data.bytesize,
|
|
42
|
+
name: work.file.name,
|
|
43
|
+
},
|
|
44
|
+
work: work,
|
|
45
|
+
)
|
|
46
|
+
rescue StandardError => e
|
|
47
|
+
# Return error result
|
|
48
|
+
Fractor::WorkResult.new(
|
|
49
|
+
error: {
|
|
50
|
+
message: e.message,
|
|
51
|
+
class: e.class.name,
|
|
52
|
+
backtrace: e.backtrace.first(5),
|
|
53
|
+
},
|
|
54
|
+
work: work,
|
|
55
|
+
)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
# Build the output path for a file
|
|
61
|
+
#
|
|
62
|
+
# @param work [FileExtractionWork] Work item containing file and options
|
|
63
|
+
# @return [String] Full output path
|
|
64
|
+
def build_output_path(work)
|
|
65
|
+
# Normalize path separators (Windows archives use backslashes)
|
|
66
|
+
clean_name = work.file.name.gsub("\\", "/")
|
|
67
|
+
|
|
68
|
+
if work.preserve_paths
|
|
69
|
+
::File.join(work.output_dir, clean_name)
|
|
70
|
+
else
|
|
71
|
+
::File.join(work.output_dir, ::File.basename(clean_name))
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Preserve file attributes (timestamps, etc.)
|
|
76
|
+
#
|
|
77
|
+
# @param path [String] Path to extracted file
|
|
78
|
+
# @param file [Object] File object from archive
|
|
79
|
+
def preserve_file_attributes(path, file)
|
|
80
|
+
# Try various timestamp attributes that different formats use
|
|
81
|
+
if file.respond_to?(:datetime) && file.datetime
|
|
82
|
+
::File.utime(::File.atime(path), file.datetime, path)
|
|
83
|
+
elsif file.respond_to?(:mtime) && file.mtime
|
|
84
|
+
atime = file.respond_to?(:atime) ? file.atime : ::File.atime(path)
|
|
85
|
+
::File.utime(atime, file.mtime, path)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Create a skipped result
|
|
90
|
+
#
|
|
91
|
+
# @param work [FileExtractionWork] Work item that was skipped
|
|
92
|
+
# @param reason [String] Reason for skipping
|
|
93
|
+
# @return [Fractor::WorkResult] Skipped result
|
|
94
|
+
def skipped_result(work, reason)
|
|
95
|
+
Fractor::WorkResult.new(
|
|
96
|
+
result: {
|
|
97
|
+
status: :skipped,
|
|
98
|
+
name: work.file.name,
|
|
99
|
+
reason: reason,
|
|
100
|
+
},
|
|
101
|
+
work: work,
|
|
102
|
+
)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Cabriolet
|
|
4
|
+
# FormatBase provides common functionality for all format-specific compressors
|
|
5
|
+
# and decompressors, reducing code duplication and establishing consistent patterns.
|
|
6
|
+
class FormatBase
|
|
7
|
+
# Initialize a format handler with common dependencies
|
|
8
|
+
#
|
|
9
|
+
# @param io_system [System::IOSystem, nil] I/O system for file operations
|
|
10
|
+
# @param algorithm_factory [AlgorithmFactory, nil] Factory for compression algorithms
|
|
11
|
+
def initialize(io_system = nil, algorithm_factory = nil)
|
|
12
|
+
@io_system = io_system || System::IOSystem.new
|
|
13
|
+
@algorithm_factory = algorithm_factory || Cabriolet.algorithm_factory
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
protected
|
|
17
|
+
|
|
18
|
+
# Execute a block with file handles, automatically closing them after completion
|
|
19
|
+
#
|
|
20
|
+
# @param input_path [String] Path to input file
|
|
21
|
+
# @param output_path [String, nil] Path to output file (optional)
|
|
22
|
+
# @yield [Array<System::FileHandle>] File handles for input and output
|
|
23
|
+
# @return [Object] Return value of the block
|
|
24
|
+
def with_file_handles(input_path, output_path = nil)
|
|
25
|
+
input_handle = @io_system.open(input_path, Constants::MODE_READ)
|
|
26
|
+
output_handle = if output_path
|
|
27
|
+
@io_system.open(output_path,
|
|
28
|
+
Constants::MODE_WRITE)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
begin
|
|
32
|
+
yield [input_handle, output_handle].compact
|
|
33
|
+
ensure
|
|
34
|
+
@io_system.close(input_handle) if input_handle
|
|
35
|
+
@io_system.close(output_handle) if output_handle
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Create a compressor using the algorithm factory
|
|
40
|
+
#
|
|
41
|
+
# @param algorithm [Symbol] Compression algorithm type
|
|
42
|
+
# @param input [System::FileHandle, System::MemoryHandle] Input handle
|
|
43
|
+
# @param output [System::FileHandle, System::MemoryHandle] Output handle
|
|
44
|
+
# @param size [Integer] Data size
|
|
45
|
+
# @param options [Hash] Additional options for the compressor
|
|
46
|
+
# @return [Object] Compressor instance
|
|
47
|
+
def create_compressor(algorithm, input, output, size, **options)
|
|
48
|
+
@algorithm_factory.create(
|
|
49
|
+
algorithm,
|
|
50
|
+
:compressor,
|
|
51
|
+
@io_system,
|
|
52
|
+
input,
|
|
53
|
+
output,
|
|
54
|
+
size,
|
|
55
|
+
**options,
|
|
56
|
+
)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Create a decompressor using the algorithm factory
|
|
60
|
+
#
|
|
61
|
+
# @param algorithm [Symbol] Compression algorithm type
|
|
62
|
+
# @param input [System::FileHandle, System::MemoryHandle] Input handle
|
|
63
|
+
# @param output [System::FileHandle, System::MemoryHandle] Output handle
|
|
64
|
+
# @param size [Integer] Data size
|
|
65
|
+
# @param options [Hash] Additional options for the decompressor
|
|
66
|
+
# @return [Object] Decompressor instance
|
|
67
|
+
def create_decompressor(algorithm, input, output, size, **options)
|
|
68
|
+
@algorithm_factory.create(
|
|
69
|
+
algorithm,
|
|
70
|
+
:decompressor,
|
|
71
|
+
@io_system,
|
|
72
|
+
input,
|
|
73
|
+
output,
|
|
74
|
+
size,
|
|
75
|
+
**options,
|
|
76
|
+
)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|