Class: PEROBS::BTreeBlob

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

Overview

This class manages the usage of the data blobs in the corresponding HashedBlobsDB object.

Constant Summary collapse

ID =

For performance reasons we use an Array for the entries instead of a Hash. These constants specify the Array index for the corresponding value.

0
BYTES =

Number of bytes

1
START =

Start Address

2
MARKED =

Mark/Unmarked flag

3

Instance Method Summary collapse

Constructor Details

#initialize(dir, btreedb) ⇒ BTreeBlob

Create a new BTreeBlob object.

Parameters:

  • dir (String)

    Fully qualified directory name

  • btreedb (BTreeDB)

    Reference to the DB that owns this blob



49
50
51
52
53
54
55
56
# File 'lib/perobs/BTreeBlob.rb', line 49

def initialize(dir, btreedb)
  @dir = dir
  @btreedb = btreedb

  @index_file_name = File.join(dir, 'index')
  @blobs_file_name = File.join(dir, 'data')
  read_index
end

Instance Method Details

#check(repair = false) ⇒ TrueClass/FalseClass

Run a basic consistency check.

Parameters:

  • repair (TrueClass/FalseClass) (defaults to: false)

    Not used right now

Returns:

  • (TrueClass/FalseClass)

    Always true right now



162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
# File 'lib/perobs/BTreeBlob.rb', line 162

def check(repair = false)
  # Determine size of the data blobs file.
  data_file_size = File.exists?(@blobs_file_name) ?
    File.size(@blobs_file_name) : 0

  next_start = 0
  prev_entry = nil
  @entries.each do |entry|
    # Entries should never overlap
    if prev_entry && next_start > entry[START]
      raise RuntimeError,
            "#{@dir}: Index entries are overlapping\n" +
            "ID: #{'%016X' % prev_entry[ID]}  " +
            "Start: #{prev_entry[START]}  " +
            "Bytes: #{prev_entry[BYTES]}\n" +
            "ID: #{'%016X' % entry[ID]}  Start: #{entry[START]}  " +
            "Bytes: #{entry[BYTES]}"
    end
    next_start = entry[START] + entry[BYTES]

    # Entries must fit within the data file
    if next_start > data_file_size
      raise RuntimeError,
            "#{@dir}: Entry for ID #{'%016X' % entry[ID]} " +
            "goes beyond 'data' file " +
            "size (#{data_file_size})\n" +
            "ID: #{'%016X' % entry[ID]}  Start: #{entry[START]}  " +
            "Bytes: #{entry[BYTES]}"
    end

    prev_entry = entry
  end

  true
end

#clear_marksObject

Clear the mark on all entries in the index.



102
103
104
105
# File 'lib/perobs/BTreeBlob.rb', line 102

def clear_marks
  @entries.each { |e| e[MARKED] = 0 }
  write_index
end

#delete_unmarked_entriesArray

Remove all entries from the index that have not been marked.

Returns:

  • (Array)

    List of deleted object IDs.



141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/perobs/BTreeBlob.rb', line 141

def delete_unmarked_entries
  deleted_ids = []
  # First remove the entry from the hash table.
  @entries_by_id.delete_if do |id, e|
    if e[MARKED] == 0
      deleted_ids << id
      true
    else
      false
    end
  end
  # Then delete the entry itself.
  @entries.delete_if { |e| e[MARKED] == 0 }
  write_index

  deleted_ids
end

#find(id) ⇒ Array

Find the data for the object with given id.

Parameters:

  • id (Fixnum or Bignum)

    Object ID

Returns:

  • (Array)

    Returns an Array with two Fixnum entries. The first is the number of bytes and the second is the starting offset in the blob storage file.



93
94
95
96
97
98
99
# File 'lib/perobs/BTreeBlob.rb', line 93

def find(id)
  if (entry = @entries_by_id[id])
    return [ entry[BYTES], entry[START] ]
  end

  nil
end

#is_marked?(id) ⇒ TrueClass or FalseClass

Check if the entry for a given ID is marked.

Parameters:

  • id (Fixnum or Bignum)

    ID of the entry

Returns:

  • (TrueClass or FalseClass)

    true if marked, false otherwise

Raises:

  • (ArgumentError)


130
131
132
133
134
135
136
137
# File 'lib/perobs/BTreeBlob.rb', line 130

def is_marked?(id)
  @entries.each do |entry|
    return entry[MARKED] != 0 if entry[ID] == id
  end

  raise ArgumentError,
        "Cannot find an entry for ID #{'%016X' % id} to check"
end

#mark(id) ⇒ Object

Set a mark on the entry with the given ID.

Parameters:

  • id (Fixnum or Bignum)

    ID of the entry



109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/perobs/BTreeBlob.rb', line 109

def mark(id)
  found = false
  @entries.each do |entry|
    if entry[ID] == id
      entry[MARKED] = 1
      found = true
      break
    end
  end

  unless found
    raise ArgumentError,
          "Cannot find an entry for ID #{'%016X' % id} to mark"
  end

  write_index
end

#read_object(id) ⇒ String

Read the entry for the given ID and return it as bytes.

Parameters:

  • id (Fixnum or Bignum)

    ID

Returns:

  • (String)

    sequence of bytes or nil if ID is unknown



82
83
84
85
# File 'lib/perobs/BTreeBlob.rb', line 82

def read_object(id)
  return nil unless (bytes_and_start = find(id))
  read_from_blobs_file(*bytes_and_start)
end

#write_object(id, raw) ⇒ Object

Write the given bytes with the given ID into the DB.

Parameters:

  • id (Fixnum or Bignum)

    ID

  • raw (String)

    sequence of bytes



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/perobs/BTreeBlob.rb', line 61

def write_object(id, raw)
  if @entries.length > @btreedb.max_blob_size
    # The blob has reached the maximum size. Replace the blob with a BTree
    # node directory and distribute the blob entires into the sub-blobs of
    # the new BTree node.
    split_blob
    # Insert the passed object into the newly created BTree node.
    @btreedb.put_raw_object(raw, id)
  else
    bytes = raw.bytesize
    start_address = reserve_bytes(id, bytes)
    if write_to_blobs_file(raw, start_address) != bytes
      raise RuntimeError, 'Object length does not match written bytes'
    end
    write_index
  end
end