perobs 2.3.1 → 2.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e2167d14af9d60510dd03e04309d7ca87a281624
4
- data.tar.gz: 849c7b4c782ad6de0b7c0f49e26e91bebc86fdb0
3
+ metadata.gz: 2cb73944d6f6f8968d868d89e19e229dfecd5fc8
4
+ data.tar.gz: d9c7598cd06bd8b88678eaec6f71f494eb1a0c1e
5
5
  SHA512:
6
- metadata.gz: 5469262c566ffe7dca585a4b2d4316a664b756224b321455993422f67e2e8ce3928c8edc5c4d625a87c316b06018a084c6989c3369d9636c19d0db3fd5cddda0
7
- data.tar.gz: b66d1656c1a2967e098c4f8fa6c6eb4dd58c2dfc1ffa7cc6711c559dc16225b844b045c4053914a7180acc010c572687bc59378bfd14a9b1f775a726bccfe344
6
+ metadata.gz: befb35a3bc54b7f261889bd7b6495aaaa51c75f50cbb91fef4a657fda323bacb8080f91934062d2ae28e751ab23bc6130ec033fd802d54ae4c67a86b092442a0
7
+ data.tar.gz: 013e98cd4fe0fce2063ac8a204a33152f1e96c8e22786870046a66a6aa5d6d941444c3b7caaff319404d5d59a0ad2449fec5638266b883e833833a76131b90b2
data/README.md CHANGED
@@ -127,7 +127,7 @@ jim.father = joe
127
127
  joe.kids << jim
128
128
  jim.mother = jane
129
129
  jane.kids << jim
130
- store.sync
130
+ store.exit
131
131
  ```
132
132
 
133
133
  When you run this script, a folder named 'family' will be created. It
data/lib/perobs/Array.rb CHANGED
@@ -2,7 +2,7 @@
2
2
  #
3
3
  # = Array.rb -- Persistent Ruby Object Store
4
4
  #
5
- # Copyright (c) 2015 by Chris Schlaeger <chris@taskjuggler.org>
5
+ # Copyright (c) 2015, 2016 by Chris Schlaeger <chris@taskjuggler.org>
6
6
  #
7
7
  # MIT License
8
8
  #
@@ -25,6 +25,7 @@
25
25
  # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26
26
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27
27
 
28
+ require 'perobs/Log'
28
29
  require 'perobs/ObjectBase'
29
30
 
30
31
  module PEROBS
@@ -49,7 +50,7 @@ module PEROBS
49
50
  :&, :*, :+, :-, :==, :[], :<=>, :at, :abbrev, :assoc, :bsearch, :collect,
50
51
  :combination, :compact, :count, :cycle, :dclone, :drop, :drop_while,
51
52
  :each, :each_index, :empty?, :eql?, :fetch, :find_index, :first,
52
- :flatten, :frozen?, :hash, :include?, :index, :inspect, :join, :last,
53
+ :flatten, :frozen?, :hash, :include?, :index, :join, :last,
53
54
  :length, :map, :pack, :permutation, :pretty_print, :pretty_print_cycle,
54
55
  :product, :rassoc, :reject, :repeated_combination,
55
56
  :repeated_permutation, :reverse, :reverse_each, :rindex, :rotate,
@@ -107,6 +108,7 @@ module PEROBS
107
108
  @data.delete_if do |v|
108
109
  v && v.respond_to?(:is_poxreference?) && v.id == id
109
110
  end
111
+ @store.cache.cache_write(self)
110
112
  end
111
113
 
112
114
  # Restore the persistent data from a single data structure.
@@ -118,6 +120,17 @@ module PEROBS
118
120
  POXReference.new(@store, v.id) : v }
119
121
  end
120
122
 
123
+ # Textual dump for debugging purposes
124
+ # @return [String]
125
+ def inspect
126
+ "<#{self.class}:#{@_id}>\n[\n" +
127
+ @data.map do |v|
128
+ " " + (v.respond_to?(:is_poxreference?) ?
129
+ "<PEROBS::ObjectBase:#{v._id}>" : v.inspect)
130
+ end.join(",\n") +
131
+ "\n]\n"
132
+ end
133
+
121
134
  private
122
135
 
123
136
  def _serialize
@@ -129,9 +142,9 @@ module PEROBS
129
142
  # objects should not be used directly. The library only exposes them
130
143
  # via POXReference proxy objects.
131
144
  if v.is_a?(ObjectBase)
132
- raise RuntimeError, 'A PEROBS::ObjectBase object escaped! ' +
145
+ PEROBS.log.fatal 'A PEROBS::ObjectBase object escaped! ' +
133
146
  "It is stored in a PEROBS::Array at index #{@data.index(v)}. " +
134
- 'Have you used self() instead of myself() to' +
147
+ 'Have you used self() instead of myself() to ' +
135
148
  "get the reference of this PEROBS object?\n" +
136
149
  v.inspect
137
150
  end
@@ -27,6 +27,8 @@
27
27
 
28
28
  require 'zlib'
29
29
 
30
+ require 'perobs/Log'
31
+
30
32
  module PEROBS
31
33
 
32
34
  # This class manages the usage of the data blobs in the corresponding
@@ -77,7 +79,7 @@ module PEROBS
77
79
  crc32 = Zlib.crc32(raw, 0)
78
80
  start_address = reserve_bytes(id, bytes, crc32)
79
81
  if write_to_blobs_file(raw, start_address) != bytes
80
- raise RuntimeError, 'Object length does not match written bytes'
82
+ PEROBS.log.fatal 'Object length does not match written bytes'
81
83
  end
82
84
  write_index
83
85
  end
@@ -118,8 +120,8 @@ module PEROBS
118
120
  end
119
121
 
120
122
  unless found
121
- raise ArgumentError,
122
- "Cannot find an entry for ID #{'%016X' % id} #{id} to mark"
123
+ PEROBS.log.fatal "Cannot find an entry for ID #{'%016X' % id} " +
124
+ "#{id} to mark"
123
125
  end
124
126
 
125
127
  write_index
@@ -136,8 +138,7 @@ module PEROBS
136
138
  end
137
139
 
138
140
  return false if ignore_errors
139
- raise ArgumentError,
140
- "Cannot find an entry for ID #{'%016X' % id} to check"
141
+ PEROBS.log.fatal "Cannot find an entry for ID #{'%016X' % id} to check"
141
142
  end
142
143
 
143
144
  # Remove all entries from the index that have not been marked.
@@ -165,7 +166,7 @@ module PEROBS
165
166
  # @return [TrueClass/FalseClass] Always true right now
166
167
  def check(repair = false)
167
168
  # Determine size of the data blobs file.
168
- data_file_size = File.exists?(@blobs_file_name) ?
169
+ data_file_size = File.exist?(@blobs_file_name) ?
169
170
  File.size(@blobs_file_name) : 0
170
171
 
171
172
  next_start = 0
@@ -173,7 +174,7 @@ module PEROBS
173
174
  @entries.each do |entry|
174
175
  # Entries should never overlap
175
176
  if prev_entry && next_start > entry[START]
176
- raise RuntimeError,
177
+ PEROBS.log.fatal
177
178
  "#{@dir}: Index entries are overlapping\n" +
178
179
  "ID: #{'%016X' % prev_entry[ID]} " +
179
180
  "Start: #{prev_entry[START]} " +
@@ -185,7 +186,7 @@ module PEROBS
185
186
 
186
187
  # Entries must fit within the data file
187
188
  if next_start > data_file_size
188
- raise RuntimeError,
189
+ PEROBS.log.fatal
189
190
  "#{@dir}: Entry for ID #{'%016X' % entry[ID]} " +
190
191
  "goes beyond 'data' file " +
191
192
  "size (#{data_file_size})\n" +
@@ -209,8 +210,8 @@ module PEROBS
209
210
  begin
210
211
  File.write(@blobs_file_name, raw, address)
211
212
  rescue => e
212
- raise IOError,
213
- "Cannot write blobs file #{@blobs_file_name}: #{e.message}"
213
+ PEROBS.log.fatal "Cannot write blobs file #{@blobs_file_name}: " +
214
+ e.message
214
215
  end
215
216
  end
216
217
 
@@ -221,13 +222,12 @@ module PEROBS
221
222
  begin
222
223
  raw = File.read(@blobs_file_name, entry[BYTES], entry[START])
223
224
  rescue => e
224
- raise IOError,
225
- "Cannot read blobs file #{@blobs_file_name}: #{e.message}"
225
+ PEROBS.log.fatal "Cannot read blobs file #{@blobs_file_name}: " +
226
+ e.message
226
227
  end
227
228
  if Zlib.crc32(raw, 0) != entry[CRC]
228
- raise RuntimeError,
229
- "BTreeBlob for object #{entry[ID]} has been corrupted: " +
230
- "Checksum mismatch"
229
+ PEROBS.log.fatal "BTreeBlob for object #{entry[ID]} has been " +
230
+ "corrupted: Checksum mismatch"
231
231
  end
232
232
 
233
233
  raw
@@ -296,7 +296,7 @@ module PEROBS
296
296
  entry_bytes = 29
297
297
  entry_format = 'QQQCL'
298
298
  restore_crc = false
299
- if File.exists?(@index_file_name)
299
+ if File.exist?(@index_file_name)
300
300
  begin
301
301
  File.open(@index_file_name, 'rb') do |f|
302
302
  # Since version 2.3.0, all index files start with a header.
@@ -338,8 +338,8 @@ module PEROBS
338
338
  begin
339
339
  raw = File.read(@blobs_file_name, e[BYTES], e[START])
340
340
  rescue => e
341
- raise IOError,
342
- "Cannot read blobs file #{@blobs_file_name}: #{e.message}"
341
+ PEROBS.log.fatal "Cannot read blobs file " +
342
+ "#{@blobs_file_name}: #{e.message}"
343
343
  end
344
344
  e[CRC] = Zlib.crc32(raw)
345
345
  end
@@ -348,8 +348,8 @@ module PEROBS
348
348
  end
349
349
  end
350
350
  rescue => e
351
- raise RuntimeError,
352
- "BTreeBlob file #{@index_file_name} corrupted: #{e.message}"
351
+ PEROBS.log.fatal "BTreeBlob file #{@index_file_name} corrupted: " +
352
+ e.message
353
353
  end
354
354
  end
355
355
  end
@@ -364,9 +364,8 @@ module PEROBS
364
364
  end
365
365
  end
366
366
  rescue => e
367
- raise RuntimeError,
368
- "Cannot write BTreeBlob index file #{@index_file_name}: " +
369
- e.message
367
+ PEROBS.log.fatal "Cannot write BTreeBlob index file " +
368
+ "#{@index_file_name}: " + e.message
370
369
  end
371
370
  end
372
371
 
@@ -27,6 +27,7 @@
27
27
 
28
28
  require 'fileutils'
29
29
 
30
+ require 'perobs/Log'
30
31
  require 'perobs/DataBase'
31
32
  require 'perobs/BTreeBlob'
32
33
 
@@ -69,15 +70,15 @@ module PEROBS
69
70
  # Check and set @dir_bits, the number of bits used for each tree level.
70
71
  @dir_bits = options[:dir_bits] || 12
71
72
  if @dir_bits < 4 || @dir_bits > 14
72
- raise ArgumentError,
73
- "dir_bits option (#{@dir_bits}) must be between 4 and 12"
73
+ PEROBS.log.fatal "dir_bits option (#{@dir_bits}) must be between 4 " +
74
+ "and 12"
74
75
  end
75
76
  check_option('dir_bits')
76
77
 
77
78
  @max_blob_size = options[:max_blob_size] || 32
78
79
  if @max_blob_size < 4 || @max_blob_size > 128
79
- raise ArgumentError,
80
- "max_blob_size option (#{@max_blob_size}) must be between 4 and 128"
80
+ PEROBS.log.fatal "max_blob_size option (#{@max_blob_size}) must be " +
81
+ "between 4 and 128"
81
82
  end
82
83
  check_option('max_blob_size')
83
84
 
@@ -115,8 +116,7 @@ module PEROBS
115
116
  begin
116
117
  File.write(file_name, hash.to_json)
117
118
  rescue => e
118
- raise RuntimeError,
119
- "Cannot write hash file '#{file_name}': #{e.message}"
119
+ PEROBS.log.fatal "Cannot write hash file '#{file_name}': #{e.message}"
120
120
  end
121
121
  end
122
122
 
@@ -125,13 +125,12 @@ module PEROBS
125
125
  # @return [Hash] A Hash that maps String objects to strings or numbers.
126
126
  def get_hash(name)
127
127
  file_name = File.join(@db_dir, name + '.json')
128
- return ::Hash.new unless File.exists?(file_name)
128
+ return ::Hash.new unless File.exist?(file_name)
129
129
 
130
130
  begin
131
131
  json = File.read(file_name)
132
132
  rescue => e
133
- raise RuntimeError,
134
- "Cannot read hash file '#{file_name}': #{e.message}"
133
+ PEROBS.log.fatal "Cannot read hash file '#{file_name}': #{e.message}"
135
134
  end
136
135
  JSON.parse(json, :create_additions => true)
137
136
  end
@@ -219,8 +218,8 @@ module PEROBS
219
218
  dir_bits = id & @dir_mask
220
219
  dir_name = File.join(dir_name, @dir_format_string % dir_bits)
221
220
 
222
- if Dir.exists?(dir_name)
223
- if File.exists?(File.join(dir_name, 'index'))
221
+ if Dir.exist?(dir_name)
222
+ if File.exist?(File.join(dir_name, 'index'))
224
223
  # The directory is a blob directory and not a BTree node dir.
225
224
  return BTreeBlob.new(dir_name, self)
226
225
  end
@@ -260,7 +259,7 @@ module PEROBS
260
259
  # contrast to BTree node directories that only contain other
261
260
  # directories.
262
261
  index_file = File.join(dir_name, 'index')
263
- File.exists?(index_file)
262
+ File.exist?(index_file)
264
263
  end
265
264
 
266
265
  end
data/lib/perobs/Cache.rb CHANGED
@@ -25,6 +25,7 @@
25
25
  # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26
26
  # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27
27
 
28
+ require 'perobs/Log'
28
29
  require 'perobs/Store'
29
30
 
30
31
  module PEROBS
@@ -55,7 +56,7 @@ module PEROBS
55
56
  # to increase performance.
56
57
  if obj.respond_to?(:is_poxreference?)
57
58
  # If this condition triggers, we have a bug in the library.
58
- raise RuntimeError, "POXReference objects should never be cached"
59
+ PEROBS.log.fatal "POXReference objects should never be cached"
59
60
  end
60
61
  @reads[index(obj)] = obj
61
62
  end
@@ -67,7 +68,7 @@ module PEROBS
67
68
  # to increase performance.
68
69
  if obj.respond_to?(:is_poxreference?)
69
70
  # If this condition triggers, we have a bug in the library.
70
- raise RuntimeError, "POXReference objects should never be cached"
71
+ PEROBS.log.fatal "POXReference objects should never be cached"
71
72
  end
72
73
 
73
74
  if @transaction_stack.empty?
@@ -147,7 +148,7 @@ module PEROBS
147
148
  def end_transaction
148
149
  case @transaction_stack.length
149
150
  when 0
150
- raise RuntimeError, 'No ongoing transaction to end'
151
+ PEROBS.log.fatal 'No ongoing transaction to end'
151
152
  when 1
152
153
  # All transactions completed successfully. Write all modified objects
153
154
  # into the backend storage.
@@ -169,7 +170,7 @@ module PEROBS
169
170
  # the transaction started.
170
171
  def abort_transaction
171
172
  if @transaction_stack.empty?
172
- raise RuntimeError, 'No ongoing transaction to abort'
173
+ PEROBS.log.fatal 'No ongoing transaction to abort'
173
174
  end
174
175
  @transaction_stack.pop.each do |id|
175
176
  @transaction_objects[id]._restore(@transaction_stack.length)
@@ -32,6 +32,7 @@ require 'json/add/struct'
32
32
  require 'yaml'
33
33
  require 'fileutils'
34
34
 
35
+ require 'perobs/Log'
35
36
  require 'perobs/ObjectBase'
36
37
 
37
38
  module PEROBS
@@ -39,11 +40,23 @@ module PEROBS
39
40
  # Base class for all storage back-ends.
40
41
  class DataBase
41
42
 
43
+ # Create a new DataBase object. This method must be overwritten by the
44
+ # deriving classes and then called via their constructor.
42
45
  def initialize(serializer = :json)
43
46
  @serializer = serializer
44
47
  @config = {}
45
48
  end
46
49
 
50
+ # A dummy open method. Deriving classes must overload them to insert their
51
+ # open/close semantics.
52
+ def open
53
+ end
54
+
55
+ # A dummy close method. Deriving classes must overload them to insert their
56
+ # open/close semantics.
57
+ def close
58
+ end
59
+
47
60
  # Serialize the given object using the object serializer.
48
61
  # @param obj [ObjectBase] Object to serialize
49
62
  # @return [String] Serialized version
@@ -58,8 +71,8 @@ module PEROBS
58
71
  YAML.dump(obj)
59
72
  end
60
73
  rescue => e
61
- raise RuntimeError,
62
- "Cannot serialize object as #{@serializer}: #{e.message}"
74
+ PEROBS.log.fatal "Cannot serialize object as #{@serializer}: " +
75
+ e.message
63
76
  end
64
77
  end
65
78
 
@@ -77,9 +90,8 @@ module PEROBS
77
90
  YAML.load(raw)
78
91
  end
79
92
  rescue => e
80
- raise RuntimeError,
81
- "Cannot de-serialize object with #{@serializer} parser: " +
82
- e.message
93
+ PEROBS.log.fatal "Cannot de-serialize object with #{@serializer} " +
94
+ "parser: " + e.message
83
95
  end
84
96
  end
85
97
 
@@ -106,11 +118,11 @@ module PEROBS
106
118
 
107
119
  # Ensure that we have a directory to store the DB items.
108
120
  def ensure_dir_exists(dir)
109
- unless Dir.exists?(dir)
121
+ unless Dir.exist?(dir)
110
122
  begin
111
123
  Dir.mkdir(dir)
112
124
  rescue IOError => e
113
- raise IOError, "Cannote create DB directory '#{dir}': #{e.message}"
125
+ PEROBS.log.fatal "Cannote create DB directory '#{dir}': #{e.message}"
114
126
  end
115
127
  end
116
128
  end
@@ -176,7 +176,7 @@ module PEROBS
176
176
  begin
177
177
  get_object(id)
178
178
  rescue => e
179
- $stderr.puts "Cannot read object with ID #{id}: #{e.message}"
179
+ PEROBS.log.error "Cannot read object with ID #{id}: #{e.message}"
180
180
  return false
181
181
  end
182
182
 
@@ -0,0 +1,189 @@
1
+ # encoding: UTF-8
2
+ #
3
+ # = FixedSizeBlobFile.rb -- Persistent Ruby Object Store
4
+ #
5
+ # Copyright (c) 2016 by Chris Schlaeger <chris@taskjuggler.org>
6
+ #
7
+ # MIT License
8
+ #
9
+ # Permission is hereby granted, free of charge, to any person obtaining
10
+ # a copy of this software and associated documentation files (the
11
+ # "Software"), to deal in the Software without restriction, including
12
+ # without limitation the rights to use, copy, modify, merge, publish,
13
+ # distribute, sublicense, and/or sell copies of the Software, and to
14
+ # permit persons to whom the Software is furnished to do so, subject to
15
+ # the following conditions:
16
+ #
17
+ # The above copyright notice and this permission notice shall be
18
+ # included in all copies or substantial portions of the Software.
19
+ #
20
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27
+
28
+ require 'perobs/Log'
29
+ require 'perobs/StackFile'
30
+
31
+ module PEROBS
32
+
33
+ # This class implements persistent storage space for fixed size data blobs.
34
+ # The blobs can be stored and retrieved and can be deleted again. The
35
+ # FixedSizeBlobFile manages the storage of the blobs and free storage
36
+ # spaces. The files grows and shrinks as needed. A blob is referenced by its
37
+ # address.
38
+ class FixedSizeBlobFile
39
+
40
+ # Create a new stack file in the given directory with the given file name.
41
+ # @param dir [String] Directory
42
+ # @param name [String] File name
43
+ # @param entry_bytes [Fixnum] Number of bytes each entry must have
44
+ def initialize(dir, name, entry_bytes)
45
+ @file_name = File.join(dir, name + '.blobs')
46
+ @entry_bytes = entry_bytes
47
+ @free_list = StackFile.new(dir, name + '-freelist', 8)
48
+ @f = nil
49
+ end
50
+
51
+ # Open the blob file.
52
+ def open
53
+ begin
54
+ if File.exist?(@file_name)
55
+ @f = File.open(@file_name, 'rb+')
56
+ else
57
+ @f = File.open(@file_name, 'wb+')
58
+ end
59
+ rescue IOError => e
60
+ PEROBS.log.fatal "Cannot open blob file #{@file_name}: #{e.message}"
61
+ end
62
+ @free_list.open
63
+ end
64
+
65
+ # Close the blob file. This method must be called before the program is
66
+ # terminated to avoid data loss.
67
+ def close
68
+ @free_list.close
69
+ begin
70
+ @f.flush
71
+ @f.close
72
+ rescue IOError => e
73
+ PEROBS.log.fatal "Cannot close blob file #{@file_name}: #{e.message}"
74
+ end
75
+ end
76
+
77
+ # Flush out all unwritten data.
78
+ def sync
79
+ @free_list.sync
80
+ begin
81
+ @f.sync
82
+ rescue IOError => e
83
+ PEROBS.log.fatal "Cannot sync blob file #{@file_name}: #{e.message}"
84
+ end
85
+ end
86
+
87
+ # Delete all data.
88
+ def clear
89
+ @f.truncate(0)
90
+ @f.flush
91
+ @free_list.clear
92
+ end
93
+
94
+ def empty?
95
+ sync
96
+ @f.size == 0
97
+ end
98
+
99
+ # Return the address of a free blob storage space. Addresses start at 0
100
+ # and increase linearly.
101
+ # @return [Fixnum] address of a free blob space
102
+ def free_address
103
+ if (bytes = @free_list.pop)
104
+ # Return an entry from the free list.
105
+ return bytes.unpack('Q')[0]
106
+ else
107
+ # There is currently no free entry. Return the address at the end of
108
+ # the file.
109
+ offset_to_address(@f.size)
110
+ end
111
+ end
112
+
113
+ # Store the given byte blob at the specified address. If the blob space is
114
+ # already in use the content will be overwritten.
115
+ # @param address [Fixnum] Address to store the blob
116
+ # @param bytes [String] bytes to store
117
+ def store_blob(address, bytes)
118
+ if bytes.length != @entry_bytes
119
+ PEROBS.log.fatal "All stack entries must be #{@entry_bytes} " +
120
+ "long. This entry is #{bytes.length} bytes long."
121
+ end
122
+ begin
123
+ @f.seek(address_to_offset(address))
124
+ # The first byte is tha flag byte. It's set to 1 for cells that hold a
125
+ # blob. 0 for empty cells.
126
+ @f.write([ 1 ].pack('C'))
127
+ @f.write(bytes)
128
+ @f.flush
129
+ rescue IOError => e
130
+ PEROBS.log.fatal "Cannot store blob at address #{address}: #{e.message}"
131
+ end
132
+ end
133
+
134
+ # Retrieve a blob from the given address.
135
+ # @param address [Fixnum] Address to store the blob
136
+ # @return [String] blob bytes
137
+ def retrieve_blob(address)
138
+ begin
139
+ if (offset = address_to_offset(address)) >= @f.size
140
+ return nil
141
+ end
142
+
143
+ @f.seek(address_to_offset(address))
144
+ if (@f.read(1).unpack('C')[0] != 1)
145
+ return nil
146
+ end
147
+ bytes = @f.read(@entry_bytes)
148
+ rescue IOError => e
149
+ PEROBS.log.fatal "Cannot retrieve blob at adress #{address}: " +
150
+ e.message
151
+ end
152
+
153
+ bytes
154
+ end
155
+
156
+ # Delete the blob at the given address.
157
+ # @param address [Fixnum] Address of blob to delete
158
+ def delete_blob(address)
159
+ begin
160
+ @f.seek(address_to_offset(address))
161
+ if (@f.read(1).unpack('C')[0] != 1)
162
+ PEROBS.log.fatal "There is no blob stored at address #{address}"
163
+ end
164
+ @f.seek(address_to_offset(address))
165
+ @f.write([ 0 ].pack('C'))
166
+ rescue IOError => e
167
+ PEROBS.log.fatal "Cannot delete blob at address #{address}: " +
168
+ e.message
169
+ end
170
+ # Add the address to the free list.
171
+ @free_list.push([ address ].pack('Q'))
172
+ end
173
+
174
+ private
175
+
176
+ # Translate a blob address to the actual offset in the file.
177
+ def address_to_offset(address)
178
+ address * (1 + @entry_bytes)
179
+ end
180
+
181
+ # Translate the file offset to the address of a blob.
182
+ def offset_to_address(offset)
183
+ offset / (1 + @entry_bytes)
184
+ end
185
+
186
+ end
187
+
188
+ end
189
+