File: reader.rb

package info (click to toggle)
ruby-ffi-libarchive 1.0.1-1
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, bullseye
  • size: 216 kB
  • sloc: ruby: 1,336; makefile: 9
file content (168 lines) | stat: -rw-r--r-- 4,911 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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
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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
module Archive
  class Reader < BaseArchive
    private_class_method :new

    def self.open_filename(file_name, command = nil)
      if block_given?
        reader = open_filename file_name, command
        begin
          yield reader
        ensure
          reader.close
        end
      else
        new file_name: file_name, command: command
      end
    end

    def self.open_memory(string, command = nil)
      if block_given?
        reader = open_memory string, command
        begin
          yield reader
        ensure
          reader.close
        end
      else
        new memory: string, command: command
      end
    end

    def self.open_stream(reader)
      if block_given?
        reader = new reader: reader
        begin
          yield reader
        ensure
          reader.close
        end
      else
        new reader: reader
      end
    end

    def initialize(params = {})
      super C.method(:archive_read_new), C.method(:archive_read_finish)

      if params[:command]
        cmd = params[:command]
        raise Error, @archive if C.archive_read_support_compression_program(archive, cmd) != C::OK
      else
        raise Error, @archive if C.archive_read_support_compression_all(archive) != C::OK
      end

      raise Error, @archive if C.archive_read_support_format_all(archive) != C::OK

      case
      when params[:file_name]
        raise Error, @archive if C.archive_read_open_filename(archive, params[:file_name], 1024) != C::OK
      when params[:memory]
        str = params[:memory]
        @data = FFI::MemoryPointer.new(str.bytesize + 1)
        @data.write_string str, str.bytesize
        raise Error, @archive if C.archive_read_open_memory(archive, @data, str.bytesize) != C::OK
      when params[:reader]
        @reader = params[:reader]
        @buffer = nil

        @read_callback = FFI::Function.new(:int, %i{pointer pointer pointer}) do |_, _, archive_data|
          data = @reader.call || ""
          @buffer = FFI::MemoryPointer.new(:char, data.size) if @buffer.nil? || @buffer.size < data.size
          @buffer.write_bytes(data)
          archive_data.write_pointer(@buffer)
          data.size
        end
        C.archive_read_set_read_callback(archive, @read_callback)

        if @reader.respond_to?(:skip)
          @skip_callback = FFI::Function.new(:int, %i{pointer pointer int64}) do |_, _, offset|
            @reader.skip(offset)
          end
          C.archive_read_set_skip_callback(archive, @skip_callback)
        end

        if @reader.respond_to?(:seek)
          @seek_callback = FFI::Function.new(:int, %i{pointer pointer int64 int}) do |_, _, offset, whence|
            @reader.seek(offset, whence)
          end
          C.archive_read_set_seek_callback(archive, @seek_callback)
        end

        # Required or open1 will segfault, even though the callback data is not used.
        C.archive_read_set_callback_data(archive, nil)
        raise Error, @archive if C.archive_read_open1(archive) != C::OK
      end
    rescue
      close
      raise
    end

    def extract(entry, flags = 0)
      raise ArgumentError, "Expected Archive::Entry as first argument" unless entry.is_a? Entry
      raise ArgumentError, "Expected Integer as second argument" unless flags.is_a? Integer

      flags |= EXTRACT_FFLAGS
      raise Error, @archive if C.archive_read_extract(archive, entry.entry, flags) != C::OK
    end

    def header_position
      raise Error, @archive if C.archive_read_header_position archive
    end

    def next_header(clone_entry: false)
      entry_ptr = FFI::MemoryPointer.new(:pointer)
      case C.archive_read_next_header(archive, entry_ptr)
      when C::OK
        Entry.from_pointer entry_ptr.read_pointer, clone: clone_entry
      when C::EOF
        @eof = true
        nil
      else
        raise Error, @archive
      end
    end

    def each_entry
      while (entry = next_header)
        yield entry
      end
    end

    def each_entry_with_data(_size = C::DATA_BUFFER_SIZE)
      while (entry = next_header)
        yield entry, read_data
      end
    end

    def read_data(size = C::DATA_BUFFER_SIZE)
      raise ArgumentError, "Buffer size must be > 0 (was: #{size})" unless size.is_a?(Integer) && size > 0

      data = nil

      buffer = FFI::MemoryPointer.new(size)
      len = 0
      while (n = C.archive_read_data(archive, buffer, size)) > 0
        case n
        when C::FATAL, C::WARN, C::RETRY
          raise Error, @archive
        else
          if block_given?
            yield buffer.get_bytes(0, n)
          else
            data ||= ""
            data.concat(buffer.get_bytes(0, n))
          end
        end
        len += n
      end

      data || len
    end

    def save_data(file_name)
      IO.sysopen(file_name, "wb") do |fd|
        raise Error, @archive if C.archive_read_data_into_fd(archive, fd) != C::OK
      end
    end
  end
end