Class: PEROBS::FixedSizeBlobFile

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

Overview

This class implements persistent storage space for fixed size data blobs. The blobs can be stored and retrieved and can be deleted again. The FixedSizeBlobFile manages the storage of the blobs and free storage spaces. The files grows and shrinks as needed. A blob is referenced by its address.

Instance Method Summary collapse

Constructor Details

#initialize(dir, name, entry_bytes) ⇒ FixedSizeBlobFile

Create a new stack file in the given directory with the given file name.

Parameters:

  • dir (String)

    Directory

  • name (String)

    File name

  • entry_bytes (Fixnum)

    Number of bytes each entry must have



44
45
46
47
48
49
# File 'lib/perobs/FixedSizeBlobFile.rb', line 44

def initialize(dir, name, entry_bytes)
  @file_name = File.join(dir, name + '.blobs')
  @entry_bytes = entry_bytes
  @free_list = StackFile.new(dir, name + '-freelist', 8)
  @f = nil
end

Instance Method Details

#clearObject

Delete all data.



92
93
94
95
96
# File 'lib/perobs/FixedSizeBlobFile.rb', line 92

def clear
  @f.truncate(0)
  @f.flush
  @free_list.clear
end

#closeObject

Close the blob file. This method must be called before the program is terminated to avoid data loss.



70
71
72
73
74
75
76
77
78
79
# File 'lib/perobs/FixedSizeBlobFile.rb', line 70

def close
  @free_list.close
  begin
    @f.flush
    @f.flock(File::LOCK_UN)
    @f.close
  rescue IOError => e
    PEROBS.log.fatal "Cannot close blob file #{@file_name}: #{e.message}"
  end
end

#delete_blob(address) ⇒ Object

Delete the blob at the given address.

Parameters:

  • address (Fixnum)

    Address of blob to delete



162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/perobs/FixedSizeBlobFile.rb', line 162

def delete_blob(address)
  begin
    @f.seek(address_to_offset(address))
    if (@f.read(1).unpack('C')[0] != 1)
      PEROBS.log.fatal "There is no blob stored at address #{address}"
    end
    @f.seek(address_to_offset(address))
    @f.write([ 0 ].pack('C'))
  rescue IOError => e
    PEROBS.log.fatal "Cannot delete blob at address #{address}: " +
      e.message
  end
  # Add the address to the free list.
  @free_list.push([ address ].pack('Q'))
end

#empty?Boolean

Returns:

  • (Boolean)


98
99
100
101
# File 'lib/perobs/FixedSizeBlobFile.rb', line 98

def empty?
  sync
  @f.size == 0
end

#free_addressFixnum

Return the address of a free blob storage space. Addresses start at 0 and increase linearly.

Returns:

  • (Fixnum)

    address of a free blob space



106
107
108
109
110
111
112
113
114
115
# File 'lib/perobs/FixedSizeBlobFile.rb', line 106

def free_address
  if (bytes = @free_list.pop)
    # Return an entry from the free list.
    return bytes.unpack('Q')[0]
  else
    # There is currently no free entry. Return the address at the end of
    # the file.
    offset_to_address(@f.size)
  end
end

#openObject

Open the blob file.



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/perobs/FixedSizeBlobFile.rb', line 52

def open
  begin
    if File.exist?(@file_name)
      @f = File.open(@file_name, 'rb+')
    else
      @f = File.open(@file_name, 'wb+')
    end
  rescue IOError => e
    PEROBS.log.fatal "Cannot open blob file #{@file_name}: #{e.message}"
  end
  unless @f.flock(File::LOCK_NB | File::LOCK_EX)
    PEROBS.log.fatal 'Database blob file is locked by another process'
  end
  @free_list.open
end

#retrieve_blob(address) ⇒ String

Retrieve a blob from the given address.

Parameters:

  • address (Fixnum)

    Address to store the blob

Returns:

  • (String)

    blob bytes



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

def retrieve_blob(address)
  begin
    if (offset = address_to_offset(address)) >= @f.size
      return nil
    end

    @f.seek(address_to_offset(address))
    if (@f.read(1).unpack('C')[0] != 1)
      return nil
    end
    bytes = @f.read(@entry_bytes)
  rescue IOError => e
    PEROBS.log.fatal "Cannot retrieve blob at adress #{address}: " +
      e.message
  end

  bytes
end

#store_blob(address, bytes) ⇒ Object

Store the given byte blob at the specified address. If the blob space is already in use the content will be overwritten.

Parameters:

  • address (Fixnum)

    Address to store the blob

  • bytes (String)

    bytes to store



121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/perobs/FixedSizeBlobFile.rb', line 121

def store_blob(address, bytes)
  if bytes.length != @entry_bytes
    PEROBS.log.fatal "All stack entries must be #{@entry_bytes} " +
      "long. This entry is #{bytes.length} bytes long."
  end
  begin
    @f.seek(address_to_offset(address))
    # The first byte is tha flag byte. It's set to 1 for cells that hold a
    # blob. 0 for empty cells.
    @f.write([ 1 ].pack('C'))
    @f.write(bytes)
    @f.flush
  rescue IOError => e
    PEROBS.log.fatal "Cannot store blob at address #{address}: #{e.message}"
  end
end

#syncObject

Flush out all unwritten data.



82
83
84
85
86
87
88
89
# File 'lib/perobs/FixedSizeBlobFile.rb', line 82

def sync
  @free_list.sync
  begin
    @f.sync
  rescue IOError => e
    PEROBS.log.fatal "Cannot sync blob file #{@file_name}: #{e.message}"
  end
end