Class: Cabriolet::Repairer

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

Overview

Archive repair and recovery functionality

Defined Under Namespace

Classes: RecoveredFile

Instance Method Summary collapse

Constructor Details

#initialize(path, **options) ⇒ Repairer

Returns a new instance of Repairer.



6
7
8
9
10
11
# File 'lib/cabriolet/repairer.rb', line 6

def initialize(path, **options)
  @path = path
  @options = options
  @format = FormatDetector.detect(path)
  @recovery_stats = { recovered: 0, failed: 0, partial: 0 }
end

Instance Method Details

#repair(output:, **options) ⇒ RepairReport

Attempt to repair the archive

Examples:

repairer = Cabriolet::Repairer.new('corrupted.cab')
report = repairer.repair(output: 'repaired.cab')

Parameters:

  • output (String)

    Output path for repaired archive

  • options (Hash)

    Repair options

Options Hash (**options):

  • :salvage_mode (Boolean) — default: true

    Enable salvage mode

  • :skip_corrupted (Boolean) — default: true

    Skip corrupted files

  • :rebuild_index (Boolean) — default: true

    Rebuild file index

Returns:



25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/cabriolet/repairer.rb', line 25

def repair(output:, **options)
  salvage_mode = options.fetch(:salvage_mode, true)
  skip_corrupted = options.fetch(:skip_corrupted, true)
  rebuild_index = options.fetch(:rebuild_index, true)

  begin
    # Parse with salvage mode enabled
    parser_class = FormatDetector.format_to_parser(@format)
    unless parser_class
      raise UnsupportedFormatError,
            "No parser for format: #{@format}"
    end

    archive = parser_class.new(
      salvage_mode: salvage_mode,
      skip_checksum: true,
      continue_on_error: true,
    ).parse(@path)

    # Extract recoverable files
    recovered_files = extract_recoverable_files(archive, skip_corrupted)

    # Rebuild archive
    rebuild_archive(recovered_files, output) if rebuild_index

    RepairReport.new(
      success: true,
      original_file: @path,
      repaired_file: output,
      stats: @recovery_stats,
      recovered_files: recovered_files.map(&:name),
    )
  rescue StandardError => e
    RepairReport.new(
      success: false,
      original_file: @path,
      repaired_file: output,
      stats: @recovery_stats,
      error: e.message,
    )
  end
end

#salvage(output_dir:) ⇒ SalvageReport

Salvage files from corrupted archive

Examples:

repairer = Cabriolet::Repairer.new('corrupted.cab')
report = repairer.salvage(output_dir: 'recovered/')

Parameters:

  • output_dir (String)

    Directory to save recovered files

Returns:



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
103
104
105
106
# File 'lib/cabriolet/repairer.rb', line 76

def salvage(output_dir:)
  FileUtils.mkdir_p(output_dir)

  parser_class = FormatDetector.format_to_parser(@format)
  archive = parser_class.new(
    salvage_mode: true,
    skip_checksum: true,
    continue_on_error: true,
  ).parse(@path)

  salvaged_files = []

  archive.files.each do |file|
    data = file.data
    output_path = File.join(output_dir, sanitize_filename(file.name))
    FileUtils.mkdir_p(File.dirname(output_path))
    File.write(output_path, data, mode: "wb")

    @recovery_stats[:recovered] += 1
    salvaged_files << file.name
  rescue StandardError => e
    @recovery_stats[:failed] += 1
    warn "Could not salvage #{file.name}: #{e.message}"
  end

  SalvageReport.new(
    output_dir: output_dir,
    stats: @recovery_stats,
    salvaged_files: salvaged_files,
  )
end