Class: Mix1::MemoryBuffer

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

Instance Method Summary collapse

Constructor Details

#initialize(file_path:, mode:, buffer_size:, encoding: Encoding::ASCII_8BIT) ⇒ MemoryBuffer

Returns a new instance of MemoryBuffer.



16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/mix1.rb', line 16

def initialize(file_path:, mode:, buffer_size:, encoding: Encoding::ASCII_8BIT)
  @mode = mode

  @file = File.open(file_path, (mode == :read) ? "rb" : "wb")
  @file.pos = 0
  @file_size = File.size(file_path)

  @buffer_size = buffer_size
  @chunk = 0
  @last_chunk = 0
  @max_chunks = @file_size / @buffer_size
  @last_cached_chunk = nil

  @encoding = encoding

  @last_buffer_pos = 0
  @buffer = (@mode == :read) ? StringIO.new(@file.read(@buffer_size)) : StringIO.new
  @buffer.set_encoding(encoding)

  # Cache frequently accessed chunks to reduce disk hits
  @cache = {}
end

Instance Method Details

#bufferedObject



189
190
191
# File 'lib/mix1.rb', line 189

def buffered
  @buffer.string.length
end

#cache_file_data_chunk!Object

This is accessed quite often, keep it around



166
167
168
169
170
171
172
173
# File 'lib/mix1.rb', line 166

def cache_file_data_chunk!
  @file_data_chunk = @chunk

  last_buffer_pos = @buffer.pos
  @buffer.pos = 0
  @cache[@chunk] = @buffer.read
  @buffer.pos = last_buffer_pos
end

#closeObject



193
194
195
# File 'lib/mix1.rb', line 193

def close
  @file&.close
end

#fetch_chunk(chunk) ⇒ Object

Raises:

  • (ArgumentError)


140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/mix1.rb', line 140

def fetch_chunk(chunk)
  raise ArgumentError, "Cannot fetch chunk #{chunk}, only #{@max_chunks} exist!" if chunk > @max_chunks
  @last_chunk = @chunk
  @chunk = chunk
  @last_buffer_pos = @buffer.pos

  cached = @cache[chunk]

  if cached
    @buffer.string = cached
  else
    @file.pos = chunk * @buffer_size
    buff = @buffer.string = @file.read(@buffer_size)

    # Cache the active chunk (implementation bounces from @file_data_chunk and back to this for each 'file' processed)
    if @chunk != @file_data_chunk && @chunk != @last_cached_chunk
      @cache.delete(@last_cached_chunk) unless @last_cached_chunk == @file_data_chunk
      @cache[@chunk] = buff
      @last_cached_chunk = @chunk
    end

    buff
  end
end

#flush_chunkObject



175
176
177
178
179
180
181
182
183
# File 'lib/mix1.rb', line 175

def flush_chunk
  @last_chunk = @chunk
  @chunk += 1

  @file.pos = @last_chunk * @buffer_size
  @file.write(string)

  @buffer.string = ""
end

#posObject



39
40
41
# File 'lib/mix1.rb', line 39

def pos
  @chunk * @buffer_size + @buffer.pos
end

#pos=(offset) ⇒ Object



43
44
45
46
47
48
49
50
51
52
# File 'lib/mix1.rb', line 43

def pos=(offset)
  last_chunk = @chunk
  @chunk = offset / @buffer_size

  raise "No backsies! #{offset} (#{@chunk}/#{last_chunk})" if @mode == :write && @chunk < last_chunk

  fetch_chunk(@chunk) if @mode == :read

  @buffer.pos = offset % @buffer_size
end

#read(bytes = nil) ⇒ Object

Raises:

  • (ArgumentError)


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
# File 'lib/mix1.rb', line 100

def read(bytes = nil)
  raise ArgumentError, "Cannot read whole file" if bytes.nil?
  raise ArgumentError, "Cannot under read buffer" if bytes.negative?

  # Long read, need to fetch next chunk while reading, mostly defeats this class...?
  if @buffer.pos + bytes > buffered
    buff = string[@buffer.pos..buffered]

    bytes_to_read = bytes - buff.length
    chunks_to_read = (bytes_to_read / @buffer_size.to_f).ceil

    chunks_to_read.times do |i|
      i += 1

      fetch_chunk(@chunk + 1)

      if i == chunks_to_read # read partial
        already_read_bytes = (chunks_to_read - 1) * @buffer_size
        bytes_more_to_read = bytes_to_read - already_read_bytes

        buff << @buffer.read(bytes_more_to_read)
      else
        buff << @buffer.read
      end
    end

    buff
  else
    fetch_chunk(@chunk) if @last_chunk != @chunk

    @buffer.read(bytes)
  end
end

#readbyteObject



134
135
136
137
138
# File 'lib/mix1.rb', line 134

def readbyte
  fetch_chunk(@chunk + 1) if @buffer.pos + 1 > buffered

  @buffer.readbyte
end

#stringObject



185
186
187
# File 'lib/mix1.rb', line 185

def string
  @buffer.string
end

#write(bytes) ⇒ Object

string of bytes



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
# File 'lib/mix1.rb', line 55

def write(bytes)
  length = bytes.length

  # Crossing buffer boundry
  if @buffer.pos + length > @buffer_size

    edge_size = @buffer_size - @buffer.pos
    buffer_edge = bytes[0...edge_size]

    bytes_to_write = bytes.length - buffer_edge.length
    chunks_to_write = (bytes_to_write / @buffer_size.to_f).ceil
    bytes_written = buffer_edge.length

    @buffer.write(buffer_edge)
    flush_chunk

    chunks_to_write.times do |i|
      i += 1

      @buffer.write(bytes[bytes_written...bytes_written + @buffer_size])
      bytes_written += @buffer_size

      flush_chunk if string.length == @buffer_size
    end
  else
    @buffer.write(bytes)
  end

  bytes
end

#write_header(data_offset:, name_offset:) ⇒ Object



86
87
88
89
90
91
92
93
94
# File 'lib/mix1.rb', line 86

def write_header(data_offset:, name_offset:)
  flush_chunk

  @file.pos = 4
  write_i32(data_offset)
  write_i32(name_offset)

  @file.pos = 0
end

#write_i32(int) ⇒ Object



96
97
98
# File 'lib/mix1.rb', line 96

def write_i32(int)
  @file.write([int].pack("l"))
end