класс PStore
PStore реализует механизм сохранения данных в файле на основе Hash. Пользовательский код может сохранять иерархии объектов Ruby (значения) в файле хранилища по имени (ключи). Иерархия объектов может состоять всего из одного объекта. Пользовательский код может позже считывать значения из хранилища данных или даже обновлять данные по необходимости.
Транзакционное поведение гарантирует, что все изменения происходят успешно или терпят неудачу вместе. Это позволяет гарантировать, что хранилище данных не останется в переходном состоянии, где некоторые значения были обновлены, а другие — нет.
За кулисами объекты Ruby сохраняются в файле хранилища с помощью Marshal. Это влечёт за собой обычные ограничения. Например, объекты Proc не могут быть сериализованы.
Пример использования:
require "pstore"
# a mock wiki object...
class WikiPage
def initialize( page_name, author, contents )
@page_name = page_name
@revisions = Array.new
add_revision(author, contents)
end
attr_reader :page_name
def add_revision( author, contents )
@revisions << { :created => Time.now,
:author => author,
:contents => contents }
end
def wiki_page_references
[@page_name] + @revisions.last[:contents].scan(/\b(?:[A-Z]+[a-z]+){2,}/)
end
# ...
end
# create a new page...
home_page = WikiPage.new( "HomePage", "James Edward Gray II",
"A page about the JoysOfDocumentation..." )
# then we want to update page data and the index together, or not at all...
wiki = PStore.new("wiki_pages.pstore")
wiki.transaction do # begin transaction; do all of this or none of it
# store page...
wiki[home_page.page_name] = home_page
# ensure that an index has been created...
wiki[:wiki_index] ||= Array.new
# update wiki index...
wiki[:wiki_index].push(*home_page.wiki_page_references)
end # commit changes to wiki data store file
### Some time later... ###
# read wiki data...
wiki.transaction(true) do # begin read-only transaction, no changes allowed
wiki.roots.each do |data_root_name|
p data_root_name
p wiki[data_root_name]
end
end
Режимы транзакций
По умолчанию целостность файла гарантируется только до тех пор, пока операционная система (и лежащее в основе оборудование) не выдаст никаких неожиданных ошибок ввода-вывода. Если при записи в файл PStore произойдёт ошибка ввода-вывода, файл может быть повреждён.
Вы можете предотвратить это, установив pstore.ultra_safe = true. Однако это приводит к небольшому снижению производительности и работает только на платформах, поддерживающих атомарное переименование файлов. Подробности см. в документации для ultra_safe.
Излишне говорить, что если вы сохраняете ценные данные с помощью PStore, то время от времени следует делать резервные копии файлов PStore.
Константы
- CHECKSUM_ALGO
-
Константа для освобождения сборщика мусора Ruby.
- EMPTY_MARSHAL_CHECKSUM
- EMPTY_MARSHAL_DATA
- EMPTY_STRING
- RDWR_ACCESS
- RD_ACCESS
- VERSION
- WR_ACCESS
Атрибуты
Должен ли PStore делать все возможное, чтобы предотвратить повреждение файлов, даже в маловероятных условиях возникновения ошибок, таких как нехватка места и другие необычные ошибки файловой системы ОС. Установка этого флага приводит к потере производительности.
Этот флаг работает только на платформах, на которых переименование файлов является атомарным (например, все POSIX-платформы: Linux, MacOS X, FreeBSD и т. д.). Значение по умолчанию — false.
Публичные методы класса
# File lib/pstore.rb, line 121
def initialize(file, thread_safe = false)
dir = File::dirname(file)
unless File::directory? dir
raise PStore::Error, format("directory %s does not exist", dir)
end
if File::exist? file and not File::readable? file
raise PStore::Error, format("file %s not readable", file)
end
@filename = file
@abort = false
@ultra_safe = false
@thread_safe = thread_safe
@lock = Thread::Mutex.new
end Для создания объекта PStore передайте путь к файлу, где вы хотите хранить данные.
Объекты PStore всегда являются реентерабельными. Но если thread_safe установлен в true, то он станет потокобезопасным ценой незначительной потери производительности.
Публичные методы экземпляра
# File lib/pstore.rb, line 157 def [](name) in_transaction @table[name] end
Извлекает значение из данных файла PStore по имени. Будет возвращена иерархия объектов Ruby, хранящихся под этим корневым именем.
ПРЕДУПРЕЖДЕНИЕ: Этот метод действителен только в PStore#transaction. В противном случае будет выброшено исключение PStore::Error.
# File lib/pstore.rb, line 202 def []=(name, value) in_transaction_wr @table[name] = value end
Сохраняет отдельный объект Ruby или иерархию объектов Ruby в файле хранилища данных под корневым именем. Присваивание имени, уже присутствующему в хранилище данных, перезаписывает старые данные.
Пример:
require "pstore"
store = PStore.new("data_file.pstore")
store.transaction do # begin transaction
# load some data into the store...
store[:single_object] = "My data..."
store[:obj_hierarchy] = { "Kev Jackson" => ["rational.rb", "pstore.rb"],
"James Gray" => ["erb.rb", "pstore.rb"] }
end # commit changes to data store file
ПРЕДУПРЕЖДЕНИЕ: Этот метод действителен только в PStore#transaction и не может быть доступен только для чтения. В противном случае будет выброшено исключение PStore::Error.
# File lib/pstore.rb, line 290 def abort in_transaction @abort = true throw :pstore_abort_transaction end
Завершает текущую PStore#transaction, отбрасывая все изменения в хранилище данных.
Пример:
require "pstore"
store = PStore.new("data_file.pstore")
store.transaction do # begin transaction
store[:one] = 1 # this change is not applied, see below...
store[:two] = 2 # this change is not applied, see below...
store.abort # end transaction here, discard all changes
store[:three] = 3 # this change is never reached
end
ПРЕДУПРЕЖДЕНИЕ: Этот метод действителен только в PStore#transaction. В противном случае будет выброшено исключение PStore::Error.
# File lib/pstore.rb, line 264 def commit in_transaction @abort = false throw :pstore_abort_transaction end
Завершает текущую PStore#transaction, немедленно сохраняя все изменения в хранилище данных.
Пример:
require "pstore"
store = PStore.new("data_file.pstore")
store.transaction do # begin transaction
# load some data into the store...
store[:one] = 1
store[:two] = 2
store.commit # end transaction here, committing changes
store[:three] = 3 # this change is never reached
end
ПРЕДУПРЕЖДЕНИЕ: Этот метод действителен только в PStore#transaction. В противном случае будет выброшено исключение PStore::Error.
# File lib/pstore.rb, line 212 def delete(name) in_transaction_wr @table.delete name end
Удаляет иерархию объектов из хранилища данных по имени.
ПРЕДУПРЕЖДЕНИЕ: Этот метод действителен только в PStore#transaction и не может быть доступен только для чтения. В противном случае будет выброшено исключение PStore::Error.
# File lib/pstore.rb, line 171
def fetch(name, default=PStore::Error)
in_transaction
unless @table.key? name
if default == PStore::Error
raise PStore::Error, format("undefined root name `%s'", name)
else
return default
end
end
@table[name]
end Этот метод аналогичен PStore#[], за исключением того, что вы также можете указать значение по умолчанию для объекта. Если указанное имя не найдено в хранилище данных, вместо него будет возвращено ваше значение по умолчанию. Если вы не укажете значение по умолчанию, будет выброшено исключение PStore::Error, если объект не найден.
ПРЕДУПРЕЖДЕНИЕ: Этот метод действителен только в PStore#transaction. В противном случае будет выброшено исключение PStore::Error.
# File lib/pstore.rb, line 238 def path @filename end
Возвращает путь к файлу хранилища данных.
# File lib/pstore.rb, line 233 def root?(name) in_transaction @table.key? name end
Возвращает true, если указанное имя в данный момент находится в хранилище данных.
ПРЕДУПРЕЖДЕНИЕ: Этот метод действителен только в PStore#transaction. В противном случае будет выброшено исключение PStore::Error.
# File lib/pstore.rb, line 223 def roots in_transaction @table.keys end
Возвращает имена всех иерархий объектов, которые в данный момент находятся в хранилище.
ПРЕДУПРЕЖДЕНИЕ: Этот метод действителен только в PStore#transaction. В противном случае будет выброшено исключение PStore::Error.
# File lib/pstore.rb, line 313
def transaction(read_only = false) # :yields: pstore
value = nil
if !@thread_safe
raise PStore::Error, "nested transaction" unless @lock.try_lock
else
begin
@lock.lock
rescue ThreadError
raise PStore::Error, "nested transaction"
end
end
begin
@rdonly = read_only
@abort = false
file = open_and_lock_file(@filename, read_only)
if file
begin
@table, checksum, original_data_size = load_data(file, read_only)
catch(:pstore_abort_transaction) do
value = yield(self)
end
if !@abort && !read_only
save_data(checksum, original_data_size, file)
end
ensure
file.close
end
else
# This can only occur if read_only == true.
@table = {}
catch(:pstore_abort_transaction) do
value = yield(self)
end
end
ensure
@lock.unlock
end
value
end Открывает новую транзакцию для хранилища данных. Код, выполняемый внутри блока, переданного этому методу, может читать и записывать данные в файл хранилища данных.
В конце блока изменения автоматически сохраняются в хранилище данных. Вы можете преждевременно завершить транзакцию, вызвав PStore#commit или PStore#abort. См. эти методы для получения подробных сведений о том, как обрабатываются изменения. Вызов неперехваченного исключения Exception в блоке эквивалентен вызову PStore#abort.
Если read_only установлен в true, вам будет разрешено только читать данные из хранилища данных во время транзакции, и любые попытки изменить данные приведут к выбросу исключения PStore::Error.
Обратите внимание, что PStore не поддерживает вложенные транзакции.
Методы закрытого экземпляра
# File lib/pstore.rb, line 490 def empty_marshal_checksum EMPTY_MARSHAL_CHECKSUM end
# File lib/pstore.rb, line 487 def empty_marshal_data EMPTY_MARSHAL_DATA end
# File lib/pstore.rb, line 137 def in_transaction raise PStore::Error, "not in transaction" unless @lock.locked? end
Вызывает PStore::Error, если вызывающий код не находится в PStore#transaction.
# File lib/pstore.rb, line 144 def in_transaction_wr in_transaction raise PStore::Error, "in read-only transaction" if @rdonly end
Вызывает PStore::Error, если вызывающий код не находится в PStore#transaction или если код находится в режиме только для чтения PStore#transaction.
# File lib/pstore.rb, line 401
def load_data(file, read_only)
if read_only
begin
table = load(file)
raise Error, "PStore file seems to be corrupted." unless table.is_a?(Hash)
rescue EOFError
# This seems to be a newly-created file.
table = {}
end
table
else
data = file.read
if data.empty?
# This seems to be a newly-created file.
table = {}
checksum = empty_marshal_checksum
size = empty_marshal_data.bytesize
else
table = load(data)
checksum = CHECKSUM_ALGO.digest(data)
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
end Загружает указанный файл PStore. Если read_only имеет значение true, будет возвращён распарсенный Hash. Если read_only имеет значение false, будет возвращена тройка: распарсенный Hash, контрольная сумма данных и размер данных.
# File lib/pstore.rb, line 429
def on_windows?
is_windows = RUBY_PLATFORM =~ /mswin|mingw|bccwin|wince/
self.class.__send__(:define_method, :on_windows?) do
is_windows
end
is_windows
end # File lib/pstore.rb, line 376
def open_and_lock_file(filename, read_only)
if read_only
begin
file = File.new(filename, **RD_ACCESS)
begin
file.flock(File::LOCK_SH)
return file
rescue
file.close
raise
end
rescue Errno::ENOENT
return nil
end
else
file = File.new(filename, **RDWR_ACCESS)
file.flock(File::LOCK_EX)
return file
end
end Открывает указанный файл (в режиме только для чтения или чтения-записи) и блокирует его для чтения или записи.
Будет возвращён открытый объект File. Если read_only имеет значение true, а файл не существует, будет возвращено nil.
Все исключения передаются дальше.
# File lib/pstore.rb, line 437
def save_data(original_checksum, original_file_size, file)
new_data = dump(@table)
if new_data.bytesize != original_file_size || CHECKSUM_ALGO.digest(new_data) != original_checksum
if @ultra_safe && !on_windows?
# Windows doesn't support atomic file renames.
save_data_with_atomic_file_rename_strategy(new_data, file)
else
save_data_with_fast_strategy(new_data, file)
end
end
new_data.replace(EMPTY_STRING)
end # File lib/pstore.rb, line 452
def save_data_with_atomic_file_rename_strategy(data, file)
temp_filename = "#{@filename}.tmp.#{Process.pid}.#{rand 1000000}"
temp_file = File.new(temp_filename, **WR_ACCESS)
begin
temp_file.flock(File::LOCK_EX)
temp_file.write(data)
temp_file.flush
File.rename(temp_filename, @filename)
rescue
File.unlink(temp_file) rescue nil
raise
ensure
temp_file.close
end
end # File lib/pstore.rb, line 468 def save_data_with_fast_strategy(data, file) file.rewind file.write(data) file.truncate(data.bytesize) end
Ruby Core © 1993–2022 Yukihiro Matsumoto
Licensed under the Ruby License.
Ruby Standard Library © contributors
Licensed under their own licenses.