Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Newer
Older
100644 545 lines (512 sloc) 16.163 kb
511dc44 initial import
Laurent Sansonetti authored
1 # = PStore -- Transactional File Storage for Ruby Objects
2 #
3 # pstore.rb -
4 # originally by matz
5 # documentation by Kev Jackson and James Edward Gray II
eee9d7b sync with ruby trunk r15665
Laurent Sansonetti authored
6 # improved by Hongli Lai
511dc44 initial import
Laurent Sansonetti authored
7 #
8 # See PStore for documentation.
9
10
11 require "fileutils"
12 require "digest/md5"
eee9d7b sync with ruby trunk r15665
Laurent Sansonetti authored
13 require "thread"
511dc44 initial import
Laurent Sansonetti authored
14
15 #
8f21162 @richkilmer bring lib up to r22701 (ruby 1.9.1_0 tag). there are build issues us…
richkilmer authored
16 # PStore implements a file based persistence mechanism based on a Hash. User
511dc44 initial import
Laurent Sansonetti authored
17 # code can store hierarchies of Ruby objects (values) into the data store file
467bc1b Update library and removing working tags
Thibault Martin-Lagardette authored
18 # by name (keys). An object hierarchy may be just a single object. User code
511dc44 initial import
Laurent Sansonetti authored
19 # may later read values back from the data store or even update data, as needed.
467bc1b Update library and removing working tags
Thibault Martin-Lagardette authored
20 #
511dc44 initial import
Laurent Sansonetti authored
21 # The transactional behavior ensures that any changes succeed or fail together.
22 # This can be used to ensure that the data store is not left in a transitory
8f21162 @richkilmer bring lib up to r22701 (ruby 1.9.1_0 tag). there are build issues us…
richkilmer authored
23 # state, where some values were updated but others were not.
467bc1b Update library and removing working tags
Thibault Martin-Lagardette authored
24 #
25 # Behind the scenes, Ruby objects are stored to the data store file with
26 # Marshal. That carries the usual limitations. Proc objects cannot be
511dc44 initial import
Laurent Sansonetti authored
27 # marshalled, for example.
28 #
29 # == Usage example:
467bc1b Update library and removing working tags
Thibault Martin-Lagardette authored
30 #
511dc44 initial import
Laurent Sansonetti authored
31 # require "pstore"
467bc1b Update library and removing working tags
Thibault Martin-Lagardette authored
32 #
511dc44 initial import
Laurent Sansonetti authored
33 # # a mock wiki object...
34 # class WikiPage
35 # def initialize( page_name, author, contents )
36 # @page_name = page_name
37 # @revisions = Array.new
467bc1b Update library and removing working tags
Thibault Martin-Lagardette authored
38 #
511dc44 initial import
Laurent Sansonetti authored
39 # add_revision(author, contents)
40 # end
467bc1b Update library and removing working tags
Thibault Martin-Lagardette authored
41 #
511dc44 initial import
Laurent Sansonetti authored
42 # attr_reader :page_name
467bc1b Update library and removing working tags
Thibault Martin-Lagardette authored
43 #
511dc44 initial import
Laurent Sansonetti authored
44 # def add_revision( author, contents )
45 # @revisions << { :created => Time.now,
46 # :author => author,
47 # :contents => contents }
48 # end
467bc1b Update library and removing working tags
Thibault Martin-Lagardette authored
49 #
511dc44 initial import
Laurent Sansonetti authored
50 # def wiki_page_references
51 # [@page_name] + @revisions.last[:contents].scan(/\b(?:[A-Z]+[a-z]+){2,}/)
52 # end
467bc1b Update library and removing working tags
Thibault Martin-Lagardette authored
53 #
511dc44 initial import
Laurent Sansonetti authored
54 # # ...
55 # end
467bc1b Update library and removing working tags
Thibault Martin-Lagardette authored
56 #
511dc44 initial import
Laurent Sansonetti authored
57 # # create a new page...
58 # home_page = WikiPage.new( "HomePage", "James Edward Gray II",
59 # "A page about the JoysOfDocumentation..." )
467bc1b Update library and removing working tags
Thibault Martin-Lagardette authored
60 #
511dc44 initial import
Laurent Sansonetti authored
61 # # then we want to update page data and the index together, or not at all...
62 # wiki = PStore.new("wiki_pages.pstore")
63 # wiki.transaction do # begin transaction; do all of this or none of it
64 # # store page...
65 # wiki[home_page.page_name] = home_page
66 # # ensure that an index has been created...
67 # wiki[:wiki_index] ||= Array.new
68 # # update wiki index...
69 # wiki[:wiki_index].push(*home_page.wiki_page_references)
70 # end # commit changes to wiki data store file
467bc1b Update library and removing working tags
Thibault Martin-Lagardette authored
71 #
511dc44 initial import
Laurent Sansonetti authored
72 # ### Some time later... ###
467bc1b Update library and removing working tags
Thibault Martin-Lagardette authored
73 #
511dc44 initial import
Laurent Sansonetti authored
74 # # read wiki data...
75 # wiki.transaction(true) do # begin read-only transaction, no changes allowed
76 # wiki.roots.each do |data_root_name|
77 # p data_root_name
78 # p wiki[data_root_name]
79 # end
80 # end
81 #
eee9d7b sync with ruby trunk r15665
Laurent Sansonetti authored
82 # == Transaction modes
83 #
84 # By default, file integrity is only ensured as long as the operating system
85 # (and the underlying hardware) doesn't raise any unexpected I/O errors. If an
86 # I/O error occurs while PStore is writing to its file, then the file will
87 # become corrupted.
88 #
89 # You can prevent this by setting <em>pstore.ultra_safe = true</em>.
90 # However, this results in a minor performance loss, and only works on platforms
91 # that support atomic file renames. Please consult the documentation for
92 # +ultra_safe+ for details.
93 #
94 # Needless to say, if you're storing valuable data with PStore, then you should
95 # backup the PStore files from time to time.
511dc44 initial import
Laurent Sansonetti authored
96 class PStore
97 binmode = defined?(File::BINARY) ? File::BINARY : 0
98 RDWR_ACCESS = File::RDWR | File::CREAT | binmode
99 RD_ACCESS = File::RDONLY | binmode
100 WR_ACCESS = File::WRONLY | File::CREAT | File::TRUNC | binmode
101
102 # The error type thrown by all PStore methods.
103 class Error < StandardError
104 end
467bc1b Update library and removing working tags
Thibault Martin-Lagardette authored
105
eee9d7b sync with ruby trunk r15665
Laurent Sansonetti authored
106 # Whether PStore should do its best to prevent file corruptions, even when under
107 # unlikely-to-occur error conditions such as out-of-space conditions and other
108 # unusual OS filesystem errors. Setting this flag comes at the price in the form
109 # of a performance loss.
110 #
111 # This flag only has effect on platforms on which file renames are atomic (e.g.
112 # all POSIX platforms: Linux, MacOS X, FreeBSD, etc). The default value is false.
113 attr_accessor :ultra_safe
511dc44 initial import
Laurent Sansonetti authored
114
467bc1b Update library and removing working tags
Thibault Martin-Lagardette authored
115 #
116 # To construct a PStore object, pass in the _file_ path where you would like
511dc44 initial import
Laurent Sansonetti authored
117 # the data to be stored.
eee9d7b sync with ruby trunk r15665
Laurent Sansonetti authored
118 #
119 # PStore objects are always reentrant. But if _thread_safe_ is set to true,
120 # then it will become thread-safe at the cost of a minor performance hit.
467bc1b Update library and removing working tags
Thibault Martin-Lagardette authored
121 #
eee9d7b sync with ruby trunk r15665
Laurent Sansonetti authored
122 def initialize(file, thread_safe = false)
511dc44 initial import
Laurent Sansonetti authored
123 dir = File::dirname(file)
124 unless File::directory? dir
125 raise PStore::Error, format("directory %s does not exist", dir)
126 end
127 if File::exist? file and not File::readable? file
128 raise PStore::Error, format("file %s not readable", file)
129 end
130 @transaction = false
131 @filename = file
132 @abort = false
eee9d7b sync with ruby trunk r15665
Laurent Sansonetti authored
133 @ultra_safe = false
467bc1b Update library and removing working tags
Thibault Martin-Lagardette authored
134 @thread_safe = thread_safe
eee9d7b sync with ruby trunk r15665
Laurent Sansonetti authored
135 if @thread_safe
136 @lock = Mutex.new
137 else
138 @lock = DummyMutex.new
139 end
511dc44 initial import
Laurent Sansonetti authored
140 end
141
142 # Raises PStore::Error if the calling code is not in a PStore#transaction.
143 def in_transaction
144 raise PStore::Error, "not in transaction" unless @transaction
145 end
467bc1b Update library and removing working tags
Thibault Martin-Lagardette authored
146 #
511dc44 initial import
Laurent Sansonetti authored
147 # Raises PStore::Error if the calling code is not in a PStore#transaction or
148 # if the code is in a read-only PStore#transaction.
467bc1b Update library and removing working tags
Thibault Martin-Lagardette authored
149 #
511dc44 initial import
Laurent Sansonetti authored
150 def in_transaction_wr()
151 in_transaction()
152 raise PStore::Error, "in read-only transaction" if @rdonly
153 end
154 private :in_transaction, :in_transaction_wr
155
156 #
467bc1b Update library and removing working tags
Thibault Martin-Lagardette authored
157 # Retrieves a value from the PStore file data, by _name_. The hierarchy of
511dc44 initial import
Laurent Sansonetti authored
158 # Ruby objects stored under that root _name_ will be returned.
467bc1b Update library and removing working tags
Thibault Martin-Lagardette authored
159 #
511dc44 initial import
Laurent Sansonetti authored
160 # *WARNING*: This method is only valid in a PStore#transaction. It will
161 # raise PStore::Error if called at any other time.
162 #
163 def [](name)
164 in_transaction
165 @table[name]
166 end
167 #
467bc1b Update library and removing working tags
Thibault Martin-Lagardette authored
168 # This method is just like PStore#[], save that you may also provide a
169 # _default_ value for the object. In the event the specified _name_ is not
170 # found in the data store, your _default_ will be returned instead. If you do
171 # not specify a default, PStore::Error will be raised if the object is not
511dc44 initial import
Laurent Sansonetti authored
172 # found.
467bc1b Update library and removing working tags
Thibault Martin-Lagardette authored
173 #
511dc44 initial import
Laurent Sansonetti authored
174 # *WARNING*: This method is only valid in a PStore#transaction. It will
175 # raise PStore::Error if called at any other time.
176 #
177 def fetch(name, default=PStore::Error)
178 in_transaction
179 unless @table.key? name
eee9d7b sync with ruby trunk r15665
Laurent Sansonetti authored
180 if default == PStore::Error
181 raise PStore::Error, format("undefined root name `%s'", name)
511dc44 initial import
Laurent Sansonetti authored
182 else
eee9d7b sync with ruby trunk r15665
Laurent Sansonetti authored
183 return default
511dc44 initial import
Laurent Sansonetti authored
184 end
185 end
186 @table[name]
187 end
188 #
189 # Stores an individual Ruby object or a hierarchy of Ruby objects in the data
190 # store file under the root _name_. Assigning to a _name_ already in the data
191 # store clobbers the old data.
467bc1b Update library and removing working tags
Thibault Martin-Lagardette authored
192 #
511dc44 initial import
Laurent Sansonetti authored
193 # == Example:
467bc1b Update library and removing working tags
Thibault Martin-Lagardette authored
194 #
511dc44 initial import
Laurent Sansonetti authored
195 # require "pstore"
467bc1b Update library and removing working tags
Thibault Martin-Lagardette authored
196 #
511dc44 initial import
Laurent Sansonetti authored
197 # store = PStore.new("data_file.pstore")
198 # store.transaction do # begin transaction
199 # # load some data into the store...
200 # store[:single_object] = "My data..."
201 # store[:obj_heirarchy] = { "Kev Jackson" => ["rational.rb", "pstore.rb"],
202 # "James Gray" => ["erb.rb", "pstore.rb"] }
203 # end # commit changes to data store file
467bc1b Update library and removing working tags
Thibault Martin-Lagardette authored
204 #
511dc44 initial import
Laurent Sansonetti authored
205 # *WARNING*: This method is only valid in a PStore#transaction and it cannot
206 # be read-only. It will raise PStore::Error if called at any other time.
207 #
208 def []=(name, value)
209 in_transaction_wr()
210 @table[name] = value
211 end
212 #
213 # Removes an object hierarchy from the data store, by _name_.
467bc1b Update library and removing working tags
Thibault Martin-Lagardette authored
214 #
511dc44 initial import
Laurent Sansonetti authored
215 # *WARNING*: This method is only valid in a PStore#transaction and it cannot
216 # be read-only. It will raise PStore::Error if called at any other time.
217 #
218 def delete(name)
219 in_transaction_wr()
220 @table.delete name
221 end
222
223 #
224 # Returns the names of all object hierarchies currently in the store.
467bc1b Update library and removing working tags
Thibault Martin-Lagardette authored
225 #
511dc44 initial import
Laurent Sansonetti authored
226 # *WARNING*: This method is only valid in a PStore#transaction. It will
227 # raise PStore::Error if called at any other time.
228 #
229 def roots
230 in_transaction
231 @table.keys
232 end
233 #
234 # Returns true if the supplied _name_ is currently in the data store.
467bc1b Update library and removing working tags
Thibault Martin-Lagardette authored
235 #
511dc44 initial import
Laurent Sansonetti authored
236 # *WARNING*: This method is only valid in a PStore#transaction. It will
237 # raise PStore::Error if called at any other time.
238 #
239 def root?(name)
240 in_transaction
241 @table.key? name
242 end
243 # Returns the path to the data store file.
244 def path
245 @filename
246 end
247
248 #
249 # Ends the current PStore#transaction, committing any changes to the data
250 # store immediately.
467bc1b Update library and removing working tags
Thibault Martin-Lagardette authored
251 #
511dc44 initial import
Laurent Sansonetti authored
252 # == Example:
467bc1b Update library and removing working tags
Thibault Martin-Lagardette authored
253 #
511dc44 initial import
Laurent Sansonetti authored
254 # require "pstore"
467bc1b Update library and removing working tags
Thibault Martin-Lagardette authored
255 #
511dc44 initial import
Laurent Sansonetti authored
256 # store = PStore.new("data_file.pstore")
257 # store.transaction do # begin transaction
258 # # load some data into the store...
259 # store[:one] = 1
260 # store[:two] = 2
467bc1b Update library and removing working tags
Thibault Martin-Lagardette authored
261 #
511dc44 initial import
Laurent Sansonetti authored
262 # store.commit # end transaction here, committing changes
467bc1b Update library and removing working tags
Thibault Martin-Lagardette authored
263 #
511dc44 initial import
Laurent Sansonetti authored
264 # store[:three] = 3 # this change is never reached
265 # end
467bc1b Update library and removing working tags
Thibault Martin-Lagardette authored
266 #
511dc44 initial import
Laurent Sansonetti authored
267 # *WARNING*: This method is only valid in a PStore#transaction. It will
268 # raise PStore::Error if called at any other time.
269 #
270 def commit
271 in_transaction
272 @abort = false
273 throw :pstore_abort_transaction
274 end
275 #
276 # Ends the current PStore#transaction, discarding any changes to the data
277 # store.
467bc1b Update library and removing working tags
Thibault Martin-Lagardette authored
278 #
511dc44 initial import
Laurent Sansonetti authored
279 # == Example:
467bc1b Update library and removing working tags
Thibault Martin-Lagardette authored
280 #
511dc44 initial import
Laurent Sansonetti authored
281 # require "pstore"
467bc1b Update library and removing working tags
Thibault Martin-Lagardette authored
282 #
511dc44 initial import
Laurent Sansonetti authored
283 # store = PStore.new("data_file.pstore")
284 # store.transaction do # begin transaction
285 # store[:one] = 1 # this change is not applied, see below...
286 # store[:two] = 2 # this change is not applied, see below...
467bc1b Update library and removing working tags
Thibault Martin-Lagardette authored
287 #
511dc44 initial import
Laurent Sansonetti authored
288 # store.abort # end transaction here, discard all changes
467bc1b Update library and removing working tags
Thibault Martin-Lagardette authored
289 #
511dc44 initial import
Laurent Sansonetti authored
290 # store[:three] = 3 # this change is never reached
291 # end
467bc1b Update library and removing working tags
Thibault Martin-Lagardette authored
292 #
511dc44 initial import
Laurent Sansonetti authored
293 # *WARNING*: This method is only valid in a PStore#transaction. It will
294 # raise PStore::Error if called at any other time.
295 #
296 def abort
297 in_transaction
298 @abort = true
299 throw :pstore_abort_transaction
300 end
301
302 #
303 # Opens a new transaction for the data store. Code executed inside a block
467bc1b Update library and removing working tags
Thibault Martin-Lagardette authored
304 # passed to this method may read and write data to and from the data store
511dc44 initial import
Laurent Sansonetti authored
305 # file.
467bc1b Update library and removing working tags
Thibault Martin-Lagardette authored
306 #
511dc44 initial import
Laurent Sansonetti authored
307 # At the end of the block, changes are committed to the data store
467bc1b Update library and removing working tags
Thibault Martin-Lagardette authored
308 # automatically. You may exit the transaction early with a call to either
511dc44 initial import
Laurent Sansonetti authored
309 # PStore#commit or PStore#abort. See those methods for details about how
467bc1b Update library and removing working tags
Thibault Martin-Lagardette authored
310 # changes are handled. Raising an uncaught Exception in the block is
511dc44 initial import
Laurent Sansonetti authored
311 # equivalent to calling PStore#abort.
467bc1b Update library and removing working tags
Thibault Martin-Lagardette authored
312 #
511dc44 initial import
Laurent Sansonetti authored
313 # If _read_only_ is set to +true+, you will only be allowed to read from the
314 # data store during the transaction and any attempts to change the data will
315 # raise a PStore::Error.
467bc1b Update library and removing working tags
Thibault Martin-Lagardette authored
316 #
511dc44 initial import
Laurent Sansonetti authored
317 # Note that PStore does not support nested transactions.
318 #
eee9d7b sync with ruby trunk r15665
Laurent Sansonetti authored
319 def transaction(read_only = false, &block) # :yields: pstore
320 value = nil
511dc44 initial import
Laurent Sansonetti authored
321 raise PStore::Error, "nested transaction" if @transaction
eee9d7b sync with ruby trunk r15665
Laurent Sansonetti authored
322 @lock.synchronize do
511dc44 initial import
Laurent Sansonetti authored
323 @rdonly = read_only
324 @transaction = true
eee9d7b sync with ruby trunk r15665
Laurent Sansonetti authored
325 @abort = false
326 file = open_and_lock_file(@filename, read_only)
327 if file
328 begin
329 @table, checksum, original_data_size = load_data(file, read_only)
467bc1b Update library and removing working tags
Thibault Martin-Lagardette authored
330
eee9d7b sync with ruby trunk r15665
Laurent Sansonetti authored
331 catch(:pstore_abort_transaction) do
332 value = yield(self)
333 end
467bc1b Update library and removing working tags
Thibault Martin-Lagardette authored
334
eee9d7b sync with ruby trunk r15665
Laurent Sansonetti authored
335 if !@abort && !read_only
336 save_data(checksum, original_data_size, file)
337 end
338 ensure
339 file.close if !file.closed?
340 end
511dc44 initial import
Laurent Sansonetti authored
341 else
eee9d7b sync with ruby trunk r15665
Laurent Sansonetti authored
342 # This can only occur if read_only == true.
343 @table = {}
344 catch(:pstore_abort_transaction) do
345 value = yield(self)
346 end
347 end
348 end
8f21162 @richkilmer bring lib up to r22701 (ruby 1.9.1_0 tag). there are build issues us…
richkilmer authored
349 value
eee9d7b sync with ruby trunk r15665
Laurent Sansonetti authored
350 ensure
351 @transaction = false
352 end
467bc1b Update library and removing working tags
Thibault Martin-Lagardette authored
353
eee9d7b sync with ruby trunk r15665
Laurent Sansonetti authored
354 private
355 # Constant for relieving Ruby's garbage collector.
356 EMPTY_STRING = ""
357 EMPTY_MARSHAL_DATA = Marshal.dump({})
358 EMPTY_MARSHAL_CHECKSUM = Digest::MD5.digest(EMPTY_MARSHAL_DATA)
467bc1b Update library and removing working tags
Thibault Martin-Lagardette authored
359
eee9d7b sync with ruby trunk r15665
Laurent Sansonetti authored
360 class DummyMutex
361 def synchronize
362 yield
363 end
364 end
467bc1b Update library and removing working tags
Thibault Martin-Lagardette authored
365
eee9d7b sync with ruby trunk r15665
Laurent Sansonetti authored
366 #
367 # Open the specified filename (either in read-only mode or in
368 # read-write mode) and lock it for reading or writing.
369 #
370 # The opened File object will be returned. If _read_only_ is true,
371 # and the file does not exist, then nil will be returned.
372 #
373 # All exceptions are propagated.
374 #
375 def open_and_lock_file(filename, read_only)
376 if read_only
377 begin
378 file = File.new(filename, RD_ACCESS)
511dc44 initial import
Laurent Sansonetti authored
379 begin
380 file.flock(File::LOCK_SH)
eee9d7b sync with ruby trunk r15665
Laurent Sansonetti authored
381 return file
382 rescue
383 file.close
384 raise
511dc44 initial import
Laurent Sansonetti authored
385 end
eee9d7b sync with ruby trunk r15665
Laurent Sansonetti authored
386 rescue Errno::ENOENT
387 return nil
511dc44 initial import
Laurent Sansonetti authored
388 end
eee9d7b sync with ruby trunk r15665
Laurent Sansonetti authored
389 else
390 file = File.new(filename, RDWR_ACCESS)
391 file.flock(File::LOCK_EX)
392 return file
393 end
394 end
467bc1b Update library and removing working tags
Thibault Martin-Lagardette authored
395
eee9d7b sync with ruby trunk r15665
Laurent Sansonetti authored
396 # Load the given PStore file.
397 # If +read_only+ is true, the unmarshalled Hash will be returned.
398 # If +read_only+ is false, a 3-tuple will be returned: the unmarshalled
399 # Hash, an MD5 checksum of the data, and the size of the data.
400 def load_data(file, read_only)
401 if read_only
402 begin
403 table = load(file)
404 if !table.is_a?(Hash)
405 raise Error, "PStore file seems to be corrupted."
511dc44 initial import
Laurent Sansonetti authored
406 end
eee9d7b sync with ruby trunk r15665
Laurent Sansonetti authored
407 rescue EOFError
408 # This seems to be a newly-created file.
409 table = {}
410 end
411 table
412 else
413 data = file.read
414 if data.empty?
415 # This seems to be a newly-created file.
416 table = {}
417 checksum = empty_marshal_checksum
418 size = empty_marshal_data.size
511dc44 initial import
Laurent Sansonetti authored
419 else
eee9d7b sync with ruby trunk r15665
Laurent Sansonetti authored
420 table = load(data)
421 checksum = Digest::MD5.digest(data)
422 size = data.size
423 if !table.is_a?(Hash)
424 raise Error, "PStore file seems to be corrupted."
425 end
511dc44 initial import
Laurent Sansonetti authored
426 end
eee9d7b sync with ruby trunk r15665
Laurent Sansonetti authored
427 data.replace(EMPTY_STRING)
428 [table, checksum, size]
429 end
430 end
467bc1b Update library and removing working tags
Thibault Martin-Lagardette authored
431
eee9d7b sync with ruby trunk r15665
Laurent Sansonetti authored
432 def on_windows?
433 is_windows = RUBY_PLATFORM =~ /mswin/ ||
434 RUBY_PLATFORM =~ /mingw/ ||
467bc1b Update library and removing working tags
Thibault Martin-Lagardette authored
435 RUBY_PLATFORM =~ /bccwin/ ||
eee9d7b sync with ruby trunk r15665
Laurent Sansonetti authored
436 RUBY_PLATFORM =~ /wince/
437 self.class.__send__(:define_method, :on_windows?) do
438 is_windows
439 end
440 is_windows
441 end
467bc1b Update library and removing working tags
Thibault Martin-Lagardette authored
442
eee9d7b sync with ruby trunk r15665
Laurent Sansonetti authored
443 # Check whether Marshal.dump supports the 'canonical' option. This option
444 # makes sure that Marshal.dump always dumps data structures in the same order.
445 # This is important because otherwise, the checksums that we generate may differ.
446 def marshal_dump_supports_canonical_option?
447 begin
448 Marshal.dump(nil, -1, true)
449 result = true
450 rescue
451 result = false
452 end
453 self.class.__send__(:define_method, :marshal_dump_supports_canonical_option?) do
454 result
455 end
456 result
457 end
467bc1b Update library and removing working tags
Thibault Martin-Lagardette authored
458
eee9d7b sync with ruby trunk r15665
Laurent Sansonetti authored
459 def save_data(original_checksum, original_file_size, file)
460 # We only want to save the new data if the size or checksum has changed.
461 # This results in less filesystem calls, which is good for performance.
462 if marshal_dump_supports_canonical_option?
463 new_data = Marshal.dump(@table, -1, true)
464 else
465 new_data = dump(@table)
466 end
467 new_checksum = Digest::MD5.digest(new_data)
467bc1b Update library and removing working tags
Thibault Martin-Lagardette authored
468
eee9d7b sync with ruby trunk r15665
Laurent Sansonetti authored
469 if new_data.size != original_file_size || new_checksum != original_checksum
470 if @ultra_safe && !on_windows?
471 # Windows doesn't support atomic file renames.
472 save_data_with_atomic_file_rename_strategy(new_data, file)
473 else
474 save_data_with_fast_strategy(new_data, file)
511dc44 initial import
Laurent Sansonetti authored
475 end
eee9d7b sync with ruby trunk r15665
Laurent Sansonetti authored
476 end
467bc1b Update library and removing working tags
Thibault Martin-Lagardette authored
477
eee9d7b sync with ruby trunk r15665
Laurent Sansonetti authored
478 new_data.replace(EMPTY_STRING)
479 end
467bc1b Update library and removing working tags
Thibault Martin-Lagardette authored
480
eee9d7b sync with ruby trunk r15665
Laurent Sansonetti authored
481 def save_data_with_atomic_file_rename_strategy(data, file)
482 temp_filename = "#{@filename}.tmp.#{Process.pid}.#{rand 1000000}"
483 temp_file = File.new(temp_filename, WR_ACCESS)
484 begin
485 temp_file.flock(File::LOCK_EX)
486 temp_file.write(data)
487 temp_file.flush
488 File.rename(temp_filename, @filename)
489 rescue
490 File.unlink(temp_file) rescue nil
491 raise
511dc44 initial import
Laurent Sansonetti authored
492 ensure
eee9d7b sync with ruby trunk r15665
Laurent Sansonetti authored
493 temp_file.close
511dc44 initial import
Laurent Sansonetti authored
494 end
eee9d7b sync with ruby trunk r15665
Laurent Sansonetti authored
495 end
467bc1b Update library and removing working tags
Thibault Martin-Lagardette authored
496
eee9d7b sync with ruby trunk r15665
Laurent Sansonetti authored
497 def save_data_with_fast_strategy(data, file)
498 file.rewind
499 file.truncate(0)
500 file.write(data)
511dc44 initial import
Laurent Sansonetti authored
501 end
502
eee9d7b sync with ruby trunk r15665
Laurent Sansonetti authored
503
504 # This method is just a wrapped around Marshal.dump
505 # to allow subclass overriding used in YAML::Store.
511dc44 initial import
Laurent Sansonetti authored
506 def dump(table) # :nodoc:
507 Marshal::dump(table)
508 end
509
510 # This method is just a wrapped around Marshal.load.
eee9d7b sync with ruby trunk r15665
Laurent Sansonetti authored
511 # to allow subclass overriding used in YAML::Store.
511dc44 initial import
Laurent Sansonetti authored
512 def load(content) # :nodoc:
513 Marshal::load(content)
514 end
515
eee9d7b sync with ruby trunk r15665
Laurent Sansonetti authored
516 def empty_marshal_data
517 EMPTY_MARSHAL_DATA
511dc44 initial import
Laurent Sansonetti authored
518 end
eee9d7b sync with ruby trunk r15665
Laurent Sansonetti authored
519 def empty_marshal_checksum
520 EMPTY_MARSHAL_CHECKSUM
511dc44 initial import
Laurent Sansonetti authored
521 end
522 end
523
524 # :enddoc:
525
526 if __FILE__ == $0
527 db = PStore.new("/tmp/foo")
528 db.transaction do
529 p db.roots
530 ary = db["root"] = [1,2,3,4]
531 ary[1] = [1,1.5]
532 end
533
534 1000.times do
535 db.transaction do
536 db["root"][0] += 1
537 p db["root"][0]
538 end
539 end
540
541 db.transaction(true) do
542 p db["root"]
543 end
544 end
Something went wrong with that request. Please try again.