Feature #4495 » patch2.diff
| lib/pstore.rb | ||
|---|---|---|
|
#
|
||
|
# See PStore for documentation.
|
||
|
require "fileutils"
|
||
|
require "digest/md5"
|
||
|
require "thread"
|
||
|
#
|
||
|
# PStore implements a file based persistence mechanism based on a Hash. User
|
||
| ... | ... | |
|
# Raises PStore::Error if the calling code is not in a PStore#transaction or
|
||
|
# if the code is in a read-only PStore#transaction.
|
||
|
#
|
||
|
def in_transaction_wr()
|
||
|
in_transaction()
|
||
|
def in_transaction_wr
|
||
|
in_transaction
|
||
|
raise PStore::Error, "in read-only transaction" if @rdonly
|
||
|
end
|
||
|
private :in_transaction, :in_transaction_wr
|
||
| ... | ... | |
|
# be read-only. It will raise PStore::Error if called at any other time.
|
||
|
#
|
||
|
def []=(name, value)
|
||
|
in_transaction_wr()
|
||
|
in_transaction_wr
|
||
|
@table[name] = value
|
||
|
end
|
||
|
#
|
||
| ... | ... | |
|
# be read-only. It will raise PStore::Error if called at any other time.
|
||
|
#
|
||
|
def delete(name)
|
||
|
in_transaction_wr()
|
||
|
in_transaction_wr
|
||
|
@table.delete name
|
||
|
end
|
||
| ... | ... | |
|
# Constant for relieving Ruby's garbage collector.
|
||
|
EMPTY_STRING = ""
|
||
|
EMPTY_MARSHAL_DATA = Marshal.dump({})
|
||
|
EMPTY_MARSHAL_CHECKSUM = Digest::MD5.digest(EMPTY_MARSHAL_DATA)
|
||
|
EMPTY_MARSHAL_CHECKSUM = EMPTY_MARSHAL_DATA.sum
|
||
|
#
|
||
|
# Open the specified filename (either in read-only mode or in
|
||
| ... | ... | |
|
if read_only
|
||
|
begin
|
||
|
table = load(file)
|
||
|
if !table.is_a?(Hash)
|
||
|
raise Error, "PStore file seems to be corrupted."
|
||
|
end
|
||
|
raise Error, "PStore file seems to be corrupted." unless table.is_a?(Hash)
|
||
|
rescue EOFError
|
||
|
# This seems to be a newly-created file.
|
||
|
table = {}
|
||
| ... | ... | |
|
# This seems to be a newly-created file.
|
||
|
table = {}
|
||
|
checksum = empty_marshal_checksum
|
||
|
size = empty_marshal_data.size
|
||
|
size = empty_marshal_data.bytesize
|
||
|
else
|
||
|
table = load(data)
|
||
|
checksum = Digest::MD5.digest(data)
|
||
|
size = data.size
|
||
|
if !table.is_a?(Hash)
|
||
|
raise Error, "PStore file seems to be corrupted."
|
||
|
end
|
||
|
checksum = data.sum
|
||
|
size = data.bytesize
|
||
|
raise Error, "PStore file seems to be corrupted." unless table.is_a?(Hash)
|
||
|
end
|
||
|
data.replace(EMPTY_STRING)
|
||
|
[table, checksum, size]
|
||
| ... | ... | |
|
end
|
||
|
def on_windows?
|
||
|
is_windows = RUBY_PLATFORM =~ /mswin/ ||
|
||
|
RUBY_PLATFORM =~ /mingw/ ||
|
||
|
RUBY_PLATFORM =~ /bccwin/ ||
|
||
|
RUBY_PLATFORM =~ /wince/
|
||
|
is_windows = RUBY_PLATFORM =~ /mswin|mingw|bccwin|wince/
|
||
|
self.class.__send__(:define_method, :on_windows?) do
|
||
|
is_windows
|
||
|
end
|
||
|
is_windows
|
||
|
end
|
||
|
# Check whether Marshal.dump supports the 'canonical' option. This option
|
||
|
# makes sure that Marshal.dump always dumps data structures in the same order.
|
||
|
# This is important because otherwise, the checksums that we generate may differ.
|
||
|
def marshal_dump_supports_canonical_option?
|
||
|
begin
|
||
|
Marshal.dump(nil, -1, true)
|
||
|
result = true
|
||
|
rescue
|
||
|
result = false
|
||
|
end
|
||
|
self.class.__send__(:define_method, :marshal_dump_supports_canonical_option?) do
|
||
|
result
|
||
|
end
|
||
|
result
|
||
|
end
|
||
|
def save_data(original_checksum, original_file_size, file)
|
||
|
# We only want to save the new data if the size or checksum has changed.
|
||
|
# This results in less filesystem calls, which is good for performance.
|
||
|
if marshal_dump_supports_canonical_option?
|
||
|
new_data = Marshal.dump(@table, -1, true)
|
||
|
else
|
||
|
new_data = dump(@table)
|
||
|
end
|
||
|
new_checksum = Digest::MD5.digest(new_data)
|
||
|
new_data = dump(@table)
|
||
|
if new_data.size != original_file_size || new_checksum != original_checksum
|
||
|
if new_data.bytesize != original_file_size || new_data.sum != original_checksum
|
||
|
if @ultra_safe && !on_windows?
|
||
|
# Windows doesn't support atomic file renames.
|
||
|
save_data_with_atomic_file_rename_strategy(new_data, file)
|
||
| ... | ... | |
|
def save_data_with_fast_strategy(data, file)
|
||
|
file.rewind
|
||
|
file.truncate(0)
|
||
|
file.write(data)
|
||
|
file.truncate(data.bytesize)
|
||
|
end
|
||
|
# This method is just a wrapped around Marshal.dump
|
||
|
# to allow subclass overriding used in YAML::Store.
|
||
|
def dump(table) # :nodoc:
|
||
| ... | ... | |
|
def empty_marshal_data
|
||
|
EMPTY_MARSHAL_DATA
|
||
|
end
|
||
|
def empty_marshal_checksum
|
||
|
EMPTY_MARSHAL_CHECKSUM
|
||
|
end
|
||
| ... | ... | |
|
# :enddoc:
|
||
|
if __FILE__ == $0
|
||
|
if __FILE__ == $PROGRAM_NAME
|
||
|
db = PStore.new("/tmp/foo")
|
||
|
db.transaction do
|
||
|
p db.roots
|
||
- « Previous
- 1
- 2
- Next »