perobs 2.3.1 → 2.4.0

Sign up to get free protection for your applications and to get access to all the features.
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
+