Class: Cabriolet::LIT::Decompressor

Inherits:
Object
  • Object
show all
Defined in:
lib/cabriolet/lit/decompressor.rb

Overview

Decompressor is the main interface for LIT file operations

LIT files are Microsoft Reader eBook files that use LZX compression.

NOTE: This implementation handles non-encrypted LIT files only. DES-encrypted (DRM-protected) LIT files are not supported. For encrypted files, use Microsoft Reader or convert to another format first.

Constant Summary collapse

DEFAULT_BUFFER_SIZE =

Input buffer size for decompression

32_768

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(io_system = nil) ⇒ Decompressor

Initialize a new LIT decompressor

Parameters:

  • io_system (System::IOSystem, nil) (defaults to: nil)

    Custom I/O system or nil for default



24
25
26
27
# File 'lib/cabriolet/lit/decompressor.rb', line 24

def initialize(io_system = nil)
  @io_system = io_system || System::IOSystem.new
  @buffer_size = DEFAULT_BUFFER_SIZE
end

Instance Attribute Details

#buffer_sizeObject

Returns the value of attribute buffer_size.



15
16
17
# File 'lib/cabriolet/lit/decompressor.rb', line 15

def buffer_size
  @buffer_size
end

#io_systemObject (readonly)

Returns the value of attribute io_system.



14
15
16
# File 'lib/cabriolet/lit/decompressor.rb', line 14

def io_system
  @io_system
end

Instance Method Details

#close(_header) ⇒ void

This method returns an undefined value.

Close a LIT file (no-op for compatibility)

Parameters:



53
54
55
56
57
# File 'lib/cabriolet/lit/decompressor.rb', line 53

def close(_header)
  # No resources to free in the header itself
  # File handles are managed separately during extraction
  nil
end

#extract(header, file, output_path) ⇒ Integer

Extract a file from LIT archive

Parameters:

  • header (Models::LITHeader)

    LIT header from open()

  • file (Models::LITFile)

    File entry to extract

  • output_path (String)

    Where to write the decompressed file

Returns:

  • (Integer)

    Number of bytes written

Raises:

  • (Errors::DecompressionError)

    if decompression fails

  • (NotImplementedError)

    if the file is encrypted



67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/cabriolet/lit/decompressor.rb', line 67

def extract(header, file, output_path)
  raise ArgumentError, "Header must not be nil" unless header
  raise ArgumentError, "File must not be nil" unless file
  raise ArgumentError, "Output path must not be nil" unless output_path

  if file.encrypted?
    raise NotImplementedError,
          "DES-encrypted files not yet supported. " \
          "Use Microsoft Reader or another tool to decrypt first."
  end

  input_handle = @io_system.open(header.filename, Constants::MODE_READ)
  output_handle = @io_system.open(output_path, Constants::MODE_WRITE)

  begin
    # Seek to file data
    @io_system.seek(input_handle, file.offset, Constants::SEEK_START)

    bytes_written = if file.compressed?
                      # Decompress using LZX
                      decompress_lzx(
                        input_handle, output_handle, file.length
                      )
                    else
                      # Direct copy
                      copy_data(
                        input_handle, output_handle, file.length
                      )
                    end

    bytes_written
  ensure
    @io_system.close(input_handle) if input_handle
    @io_system.close(output_handle) if output_handle
  end
end

#extract_all(header, output_dir) ⇒ Integer

Extract all files from LIT archive

Parameters:

  • header (Models::LITHeader)

    LIT header from open()

  • output_dir (String)

    Directory to extract files to

Returns:

  • (Integer)

    Number of files extracted

Raises:

  • (Errors::DecompressionError)

    if extraction fails



110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/cabriolet/lit/decompressor.rb', line 110

def extract_all(header, output_dir)
  raise ArgumentError, "Header must not be nil" unless header
  raise ArgumentError, "Output dir must not be nil" unless output_dir

  # Create output directory if it doesn't exist
  ::FileUtils.mkdir_p(output_dir)

  extracted = 0
  header.files.each do |file|
    output_path = ::File.join(output_dir, file.filename)

    # Create subdirectories if needed
    file_dir = ::File.dirname(output_path)
    ::FileUtils.mkdir_p(file_dir) unless ::File.directory?(file_dir)

    extract(header, file, output_path)
    extracted += 1
  end

  extracted
end

#open(filename) ⇒ Models::LITHeader

Open and parse a LIT file

Parameters:

  • filename (String)

    Path to the LIT file

Returns:

Raises:

  • (Errors::ParseError)

    if the file is not a valid LIT

  • (NotImplementedError)

    if the file is DES-encrypted



35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/cabriolet/lit/decompressor.rb', line 35

def open(filename)
  header = parse_header(filename)
  header.filename = filename

  # Check for encryption
  if header.encrypted?
    raise NotImplementedError,
          "DES-encrypted LIT files not yet supported. " \
          "Use Microsoft Reader or another tool to decrypt first."
  end

  header
end