rubysl-pstore 1.0.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.travis.yml +5 -6
- data/lib/rubysl/pstore/pstore.rb +256 -119
- data/lib/rubysl/pstore/version.rb +1 -1
- data/rubysl-pstore.gemspec +0 -1
- metadata +2 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 24fe72b1e61ec8763d53e14ec608eb963d9e049b
|
4
|
+
data.tar.gz: b45b7683494348f8980131dff596fd4432c59231
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 255a2feb817a01b585498b9305bb46216d3dc5dd57c2ace302358cf02d1052a6174e5f9c18a760d2485b00d5736056c8e1da14c277f354c7a2d9f9d6adb53583
|
7
|
+
data.tar.gz: d46f755a5168f42e7aaee2083314b1a7a03aa35594e838df4612241e1948809729746c804d581f19c51de8ede835242eade7c947673cb694c665ce5bf31d4e3f
|
data/.travis.yml
CHANGED
data/lib/rubysl/pstore/pstore.rb
CHANGED
@@ -3,59 +3,61 @@
|
|
3
3
|
# pstore.rb -
|
4
4
|
# originally by matz
|
5
5
|
# documentation by Kev Jackson and James Edward Gray II
|
6
|
+
# improved by Hongli Lai
|
6
7
|
#
|
7
8
|
# See PStore for documentation.
|
8
9
|
|
9
10
|
|
10
11
|
require "fileutils"
|
11
12
|
require "digest/md5"
|
13
|
+
require "thread"
|
12
14
|
|
13
15
|
#
|
14
16
|
# PStore implements a file based persistence mechanism based on a Hash. User
|
15
17
|
# code can store hierarchies of Ruby objects (values) into the data store file
|
16
|
-
# by name (keys). An object hierarchy may be just a single object. User code
|
18
|
+
# by name (keys). An object hierarchy may be just a single object. User code
|
17
19
|
# may later read values back from the data store or even update data, as needed.
|
18
|
-
#
|
20
|
+
#
|
19
21
|
# The transactional behavior ensures that any changes succeed or fail together.
|
20
22
|
# This can be used to ensure that the data store is not left in a transitory
|
21
23
|
# state, where some values were updated but others were not.
|
22
|
-
#
|
23
|
-
# Behind the scenes, Ruby objects are stored to the data store file with
|
24
|
-
# Marshal. That carries the usual limitations. Proc objects cannot be
|
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
|
25
27
|
# marshalled, for example.
|
26
28
|
#
|
27
29
|
# == Usage example:
|
28
|
-
#
|
30
|
+
#
|
29
31
|
# require "pstore"
|
30
|
-
#
|
32
|
+
#
|
31
33
|
# # a mock wiki object...
|
32
34
|
# class WikiPage
|
33
35
|
# def initialize( page_name, author, contents )
|
34
36
|
# @page_name = page_name
|
35
37
|
# @revisions = Array.new
|
36
|
-
#
|
38
|
+
#
|
37
39
|
# add_revision(author, contents)
|
38
40
|
# end
|
39
|
-
#
|
41
|
+
#
|
40
42
|
# attr_reader :page_name
|
41
|
-
#
|
43
|
+
#
|
42
44
|
# def add_revision( author, contents )
|
43
45
|
# @revisions << { :created => Time.now,
|
44
46
|
# :author => author,
|
45
47
|
# :contents => contents }
|
46
48
|
# end
|
47
|
-
#
|
49
|
+
#
|
48
50
|
# def wiki_page_references
|
49
51
|
# [@page_name] + @revisions.last[:contents].scan(/\b(?:[A-Z]+[a-z]+){2,}/)
|
50
52
|
# end
|
51
|
-
#
|
53
|
+
#
|
52
54
|
# # ...
|
53
55
|
# end
|
54
|
-
#
|
56
|
+
#
|
55
57
|
# # create a new page...
|
56
58
|
# home_page = WikiPage.new( "HomePage", "James Edward Gray II",
|
57
59
|
# "A page about the JoysOfDocumentation..." )
|
58
|
-
#
|
60
|
+
#
|
59
61
|
# # then we want to update page data and the index together, or not at all...
|
60
62
|
# wiki = PStore.new("wiki_pages.pstore")
|
61
63
|
# wiki.transaction do # begin transaction; do all of this or none of it
|
@@ -66,9 +68,9 @@ require "digest/md5"
|
|
66
68
|
# # update wiki index...
|
67
69
|
# wiki[:wiki_index].push(*home_page.wiki_page_references)
|
68
70
|
# end # commit changes to wiki data store file
|
69
|
-
#
|
71
|
+
#
|
70
72
|
# ### Some time later... ###
|
71
|
-
#
|
73
|
+
#
|
72
74
|
# # read wiki data...
|
73
75
|
# wiki.transaction(true) do # begin read-only transaction, no changes allowed
|
74
76
|
# wiki.roots.each do |data_root_name|
|
@@ -77,6 +79,20 @@ require "digest/md5"
|
|
77
79
|
# end
|
78
80
|
# end
|
79
81
|
#
|
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.
|
80
96
|
class PStore
|
81
97
|
binmode = defined?(File::BINARY) ? File::BINARY : 0
|
82
98
|
RDWR_ACCESS = File::RDWR | File::CREAT | binmode
|
@@ -87,11 +103,23 @@ class PStore
|
|
87
103
|
class Error < StandardError
|
88
104
|
end
|
89
105
|
|
90
|
-
#
|
91
|
-
#
|
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
|
114
|
+
|
115
|
+
#
|
116
|
+
# To construct a PStore object, pass in the _file_ path where you would like
|
92
117
|
# the data to be stored.
|
93
|
-
#
|
94
|
-
|
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.
|
121
|
+
#
|
122
|
+
def initialize(file, thread_safe = false)
|
95
123
|
dir = File::dirname(file)
|
96
124
|
unless File::directory? dir
|
97
125
|
raise PStore::Error, format("directory %s does not exist", dir)
|
@@ -99,19 +127,21 @@ class PStore
|
|
99
127
|
if File::exist? file and not File::readable? file
|
100
128
|
raise PStore::Error, format("file %s not readable", file)
|
101
129
|
end
|
102
|
-
@transaction = false
|
103
130
|
@filename = file
|
104
131
|
@abort = false
|
132
|
+
@ultra_safe = false
|
133
|
+
@thread_safe = thread_safe
|
134
|
+
@lock = Mutex.new
|
105
135
|
end
|
106
136
|
|
107
137
|
# Raises PStore::Error if the calling code is not in a PStore#transaction.
|
108
138
|
def in_transaction
|
109
|
-
raise PStore::Error, "not in transaction" unless @
|
139
|
+
raise PStore::Error, "not in transaction" unless @lock.locked?
|
110
140
|
end
|
111
|
-
#
|
141
|
+
#
|
112
142
|
# Raises PStore::Error if the calling code is not in a PStore#transaction or
|
113
143
|
# if the code is in a read-only PStore#transaction.
|
114
|
-
#
|
144
|
+
#
|
115
145
|
def in_transaction_wr()
|
116
146
|
in_transaction()
|
117
147
|
raise PStore::Error, "in read-only transaction" if @rdonly
|
@@ -119,9 +149,9 @@ class PStore
|
|
119
149
|
private :in_transaction, :in_transaction_wr
|
120
150
|
|
121
151
|
#
|
122
|
-
# Retrieves a value from the PStore file data, by _name_. The hierarchy of
|
152
|
+
# Retrieves a value from the PStore file data, by _name_. The hierarchy of
|
123
153
|
# Ruby objects stored under that root _name_ will be returned.
|
124
|
-
#
|
154
|
+
#
|
125
155
|
# *WARNING*: This method is only valid in a PStore#transaction. It will
|
126
156
|
# raise PStore::Error if called at any other time.
|
127
157
|
#
|
@@ -130,22 +160,22 @@ class PStore
|
|
130
160
|
@table[name]
|
131
161
|
end
|
132
162
|
#
|
133
|
-
# This method is just like PStore#[], save that you may also provide a
|
134
|
-
# _default_ value for the object. In the event the specified _name_ is not
|
135
|
-
# found in the data store, your _default_ will be returned instead. If you do
|
136
|
-
# not specify a default, PStore::Error will be raised if the object is not
|
163
|
+
# This method is just like PStore#[], save that you may also provide a
|
164
|
+
# _default_ value for the object. In the event the specified _name_ is not
|
165
|
+
# found in the data store, your _default_ will be returned instead. If you do
|
166
|
+
# not specify a default, PStore::Error will be raised if the object is not
|
137
167
|
# found.
|
138
|
-
#
|
168
|
+
#
|
139
169
|
# *WARNING*: This method is only valid in a PStore#transaction. It will
|
140
170
|
# raise PStore::Error if called at any other time.
|
141
171
|
#
|
142
172
|
def fetch(name, default=PStore::Error)
|
143
173
|
in_transaction
|
144
174
|
unless @table.key? name
|
145
|
-
if default==PStore::Error
|
146
|
-
|
175
|
+
if default == PStore::Error
|
176
|
+
raise PStore::Error, format("undefined root name `%s'", name)
|
147
177
|
else
|
148
|
-
|
178
|
+
return default
|
149
179
|
end
|
150
180
|
end
|
151
181
|
@table[name]
|
@@ -154,11 +184,11 @@ class PStore
|
|
154
184
|
# Stores an individual Ruby object or a hierarchy of Ruby objects in the data
|
155
185
|
# store file under the root _name_. Assigning to a _name_ already in the data
|
156
186
|
# store clobbers the old data.
|
157
|
-
#
|
187
|
+
#
|
158
188
|
# == Example:
|
159
|
-
#
|
189
|
+
#
|
160
190
|
# require "pstore"
|
161
|
-
#
|
191
|
+
#
|
162
192
|
# store = PStore.new("data_file.pstore")
|
163
193
|
# store.transaction do # begin transaction
|
164
194
|
# # load some data into the store...
|
@@ -166,7 +196,7 @@ class PStore
|
|
166
196
|
# store[:obj_heirarchy] = { "Kev Jackson" => ["rational.rb", "pstore.rb"],
|
167
197
|
# "James Gray" => ["erb.rb", "pstore.rb"] }
|
168
198
|
# end # commit changes to data store file
|
169
|
-
#
|
199
|
+
#
|
170
200
|
# *WARNING*: This method is only valid in a PStore#transaction and it cannot
|
171
201
|
# be read-only. It will raise PStore::Error if called at any other time.
|
172
202
|
#
|
@@ -176,7 +206,7 @@ class PStore
|
|
176
206
|
end
|
177
207
|
#
|
178
208
|
# Removes an object hierarchy from the data store, by _name_.
|
179
|
-
#
|
209
|
+
#
|
180
210
|
# *WARNING*: This method is only valid in a PStore#transaction and it cannot
|
181
211
|
# be read-only. It will raise PStore::Error if called at any other time.
|
182
212
|
#
|
@@ -187,7 +217,7 @@ class PStore
|
|
187
217
|
|
188
218
|
#
|
189
219
|
# Returns the names of all object hierarchies currently in the store.
|
190
|
-
#
|
220
|
+
#
|
191
221
|
# *WARNING*: This method is only valid in a PStore#transaction. It will
|
192
222
|
# raise PStore::Error if called at any other time.
|
193
223
|
#
|
@@ -197,7 +227,7 @@ class PStore
|
|
197
227
|
end
|
198
228
|
#
|
199
229
|
# Returns true if the supplied _name_ is currently in the data store.
|
200
|
-
#
|
230
|
+
#
|
201
231
|
# *WARNING*: This method is only valid in a PStore#transaction. It will
|
202
232
|
# raise PStore::Error if called at any other time.
|
203
233
|
#
|
@@ -213,22 +243,22 @@ class PStore
|
|
213
243
|
#
|
214
244
|
# Ends the current PStore#transaction, committing any changes to the data
|
215
245
|
# store immediately.
|
216
|
-
#
|
246
|
+
#
|
217
247
|
# == Example:
|
218
|
-
#
|
248
|
+
#
|
219
249
|
# require "pstore"
|
220
|
-
#
|
250
|
+
#
|
221
251
|
# store = PStore.new("data_file.pstore")
|
222
252
|
# store.transaction do # begin transaction
|
223
253
|
# # load some data into the store...
|
224
254
|
# store[:one] = 1
|
225
255
|
# store[:two] = 2
|
226
|
-
#
|
256
|
+
#
|
227
257
|
# store.commit # end transaction here, committing changes
|
228
|
-
#
|
258
|
+
#
|
229
259
|
# store[:three] = 3 # this change is never reached
|
230
260
|
# end
|
231
|
-
#
|
261
|
+
#
|
232
262
|
# *WARNING*: This method is only valid in a PStore#transaction. It will
|
233
263
|
# raise PStore::Error if called at any other time.
|
234
264
|
#
|
@@ -240,21 +270,21 @@ class PStore
|
|
240
270
|
#
|
241
271
|
# Ends the current PStore#transaction, discarding any changes to the data
|
242
272
|
# store.
|
243
|
-
#
|
273
|
+
#
|
244
274
|
# == Example:
|
245
|
-
#
|
275
|
+
#
|
246
276
|
# require "pstore"
|
247
|
-
#
|
277
|
+
#
|
248
278
|
# store = PStore.new("data_file.pstore")
|
249
279
|
# store.transaction do # begin transaction
|
250
280
|
# store[:one] = 1 # this change is not applied, see below...
|
251
281
|
# store[:two] = 2 # this change is not applied, see below...
|
252
|
-
#
|
282
|
+
#
|
253
283
|
# store.abort # end transaction here, discard all changes
|
254
|
-
#
|
284
|
+
#
|
255
285
|
# store[:three] = 3 # this change is never reached
|
256
286
|
# end
|
257
|
-
#
|
287
|
+
#
|
258
288
|
# *WARNING*: This method is only valid in a PStore#transaction. It will
|
259
289
|
# raise PStore::Error if called at any other time.
|
260
290
|
#
|
@@ -266,109 +296,216 @@ class PStore
|
|
266
296
|
|
267
297
|
#
|
268
298
|
# Opens a new transaction for the data store. Code executed inside a block
|
269
|
-
# passed to this method may read and write data to and from the data store
|
299
|
+
# passed to this method may read and write data to and from the data store
|
270
300
|
# file.
|
271
|
-
#
|
301
|
+
#
|
272
302
|
# At the end of the block, changes are committed to the data store
|
273
|
-
# automatically. You may exit the transaction early with a call to either
|
303
|
+
# automatically. You may exit the transaction early with a call to either
|
274
304
|
# PStore#commit or PStore#abort. See those methods for details about how
|
275
|
-
# changes are handled. Raising an uncaught Exception in the block is
|
305
|
+
# changes are handled. Raising an uncaught Exception in the block is
|
276
306
|
# equivalent to calling PStore#abort.
|
277
|
-
#
|
307
|
+
#
|
278
308
|
# If _read_only_ is set to +true+, you will only be allowed to read from the
|
279
309
|
# data store during the transaction and any attempts to change the data will
|
280
310
|
# raise a PStore::Error.
|
281
|
-
#
|
311
|
+
#
|
282
312
|
# Note that PStore does not support nested transactions.
|
283
313
|
#
|
284
|
-
def transaction(read_only=false) # :yields: pstore
|
285
|
-
|
286
|
-
|
314
|
+
def transaction(read_only = false, &block) # :yields: pstore
|
315
|
+
value = nil
|
316
|
+
raise PStore::Error, "nested transaction" if !@thread_safe && @lock.locked?
|
317
|
+
@lock.synchronize do
|
287
318
|
@rdonly = read_only
|
288
319
|
@abort = false
|
289
|
-
|
290
|
-
|
291
|
-
|
320
|
+
file = open_and_lock_file(@filename, read_only)
|
321
|
+
if file
|
322
|
+
begin
|
323
|
+
@table, checksum, original_data_size = load_data(file, read_only)
|
292
324
|
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
325
|
+
catch(:pstore_abort_transaction) do
|
326
|
+
value = yield(self)
|
327
|
+
end
|
328
|
+
|
329
|
+
if !@abort && !read_only
|
330
|
+
save_data(checksum, original_data_size, file)
|
331
|
+
end
|
332
|
+
ensure
|
333
|
+
file.close if !file.closed?
|
334
|
+
end
|
299
335
|
else
|
336
|
+
# This can only occur if read_only == true.
|
337
|
+
@table = {}
|
338
|
+
catch(:pstore_abort_transaction) do
|
339
|
+
value = yield(self)
|
340
|
+
end
|
341
|
+
end
|
342
|
+
end
|
343
|
+
value
|
344
|
+
rescue ThreadError
|
345
|
+
raise PStore::Error, "nested transaction"
|
346
|
+
end
|
347
|
+
|
348
|
+
private
|
349
|
+
# Constant for relieving Ruby's garbage collector.
|
350
|
+
EMPTY_STRING = ""
|
351
|
+
EMPTY_MARSHAL_DATA = Marshal.dump({})
|
352
|
+
EMPTY_MARSHAL_CHECKSUM = Digest::MD5.digest(EMPTY_MARSHAL_DATA)
|
353
|
+
|
354
|
+
#
|
355
|
+
# Open the specified filename (either in read-only mode or in
|
356
|
+
# read-write mode) and lock it for reading or writing.
|
357
|
+
#
|
358
|
+
# The opened File object will be returned. If _read_only_ is true,
|
359
|
+
# and the file does not exist, then nil will be returned.
|
360
|
+
#
|
361
|
+
# All exceptions are propagated.
|
362
|
+
#
|
363
|
+
def open_and_lock_file(filename, read_only)
|
364
|
+
if read_only
|
365
|
+
begin
|
366
|
+
file = File.new(filename, RD_ACCESS)
|
300
367
|
begin
|
301
|
-
file = File.open(@filename, RD_ACCESS)
|
302
368
|
file.flock(File::LOCK_SH)
|
303
|
-
|
304
|
-
rescue
|
305
|
-
|
369
|
+
return file
|
370
|
+
rescue
|
371
|
+
file.close
|
372
|
+
raise
|
306
373
|
end
|
374
|
+
rescue Errno::ENOENT
|
375
|
+
return nil
|
307
376
|
end
|
377
|
+
else
|
378
|
+
file = File.new(filename, RDWR_ACCESS)
|
379
|
+
file.flock(File::LOCK_EX)
|
380
|
+
return file
|
381
|
+
end
|
382
|
+
end
|
308
383
|
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
384
|
+
# Load the given PStore file.
|
385
|
+
# If +read_only+ is true, the unmarshalled Hash will be returned.
|
386
|
+
# If +read_only+ is false, a 3-tuple will be returned: the unmarshalled
|
387
|
+
# Hash, an MD5 checksum of the data, and the size of the data.
|
388
|
+
def load_data(file, read_only)
|
389
|
+
if read_only
|
390
|
+
begin
|
391
|
+
table = load(file)
|
392
|
+
if !table.is_a?(Hash)
|
393
|
+
raise Error, "PStore file seems to be corrupted."
|
314
394
|
end
|
395
|
+
rescue EOFError
|
396
|
+
# This seems to be a newly-created file.
|
397
|
+
table = {}
|
398
|
+
end
|
399
|
+
table
|
400
|
+
else
|
401
|
+
data = file.read
|
402
|
+
if data.empty?
|
403
|
+
# This seems to be a newly-created file.
|
404
|
+
table = {}
|
405
|
+
checksum = empty_marshal_checksum
|
406
|
+
size = empty_marshal_data.size
|
315
407
|
else
|
316
|
-
|
408
|
+
table = load(data)
|
409
|
+
checksum = Digest::MD5.digest(data)
|
410
|
+
size = data.size
|
411
|
+
if !table.is_a?(Hash)
|
412
|
+
raise Error, "PStore file seems to be corrupted."
|
413
|
+
end
|
317
414
|
end
|
318
|
-
|
415
|
+
data.replace(EMPTY_STRING)
|
416
|
+
[table, checksum, size]
|
417
|
+
end
|
418
|
+
end
|
319
419
|
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
420
|
+
def on_windows?
|
421
|
+
is_windows = RUBY_PLATFORM =~ /mswin/ ||
|
422
|
+
RUBY_PLATFORM =~ /mingw/ ||
|
423
|
+
RUBY_PLATFORM =~ /bccwin/ ||
|
424
|
+
RUBY_PLATFORM =~ /wince/
|
425
|
+
self.class.__send__(:define_method, :on_windows?) do
|
426
|
+
is_windows
|
427
|
+
end
|
428
|
+
is_windows
|
429
|
+
end
|
430
|
+
|
431
|
+
# Check whether Marshal.dump supports the 'canonical' option. This option
|
432
|
+
# makes sure that Marshal.dump always dumps data structures in the same order.
|
433
|
+
# This is important because otherwise, the checksums that we generate may differ.
|
434
|
+
def marshal_dump_supports_canonical_option?
|
435
|
+
begin
|
436
|
+
Marshal.dump(nil, -1, true)
|
437
|
+
result = true
|
438
|
+
rescue
|
439
|
+
result = false
|
440
|
+
end
|
441
|
+
self.class.__send__(:define_method, :marshal_dump_supports_canonical_option?) do
|
442
|
+
result
|
443
|
+
end
|
444
|
+
result
|
445
|
+
end
|
446
|
+
|
447
|
+
def save_data(original_checksum, original_file_size, file)
|
448
|
+
# We only want to save the new data if the size or checksum has changed.
|
449
|
+
# This results in less filesystem calls, which is good for performance.
|
450
|
+
if marshal_dump_supports_canonical_option?
|
451
|
+
new_data = Marshal.dump(@table, -1, true)
|
452
|
+
else
|
453
|
+
new_data = dump(@table)
|
454
|
+
end
|
455
|
+
new_checksum = Digest::MD5.digest(new_data)
|
456
|
+
|
457
|
+
if new_data.size != original_file_size || new_checksum != original_checksum
|
458
|
+
if @ultra_safe && !on_windows?
|
459
|
+
# Windows doesn't support atomic file renames.
|
460
|
+
save_data_with_atomic_file_rename_strategy(new_data, file)
|
461
|
+
else
|
462
|
+
save_data_with_fast_strategy(new_data, file)
|
338
463
|
end
|
464
|
+
end
|
465
|
+
|
466
|
+
new_data.replace(EMPTY_STRING)
|
467
|
+
end
|
468
|
+
|
469
|
+
def save_data_with_atomic_file_rename_strategy(data, file)
|
470
|
+
temp_filename = "#{@filename}.tmp.#{Process.pid}.#{rand 1000000}"
|
471
|
+
temp_file = File.new(temp_filename, WR_ACCESS)
|
472
|
+
begin
|
473
|
+
temp_file.flock(File::LOCK_EX)
|
474
|
+
temp_file.write(data)
|
475
|
+
temp_file.flush
|
476
|
+
File.rename(temp_filename, @filename)
|
477
|
+
rescue
|
478
|
+
File.unlink(temp_file) rescue nil
|
479
|
+
raise
|
339
480
|
ensure
|
340
|
-
|
341
|
-
@transaction = false
|
342
|
-
file.close if file
|
481
|
+
temp_file.close
|
343
482
|
end
|
344
|
-
value
|
345
483
|
end
|
346
484
|
|
347
|
-
|
485
|
+
def save_data_with_fast_strategy(data, file)
|
486
|
+
file.rewind
|
487
|
+
file.truncate(0)
|
488
|
+
file.write(data)
|
489
|
+
end
|
490
|
+
|
491
|
+
|
492
|
+
# This method is just a wrapped around Marshal.dump
|
493
|
+
# to allow subclass overriding used in YAML::Store.
|
348
494
|
def dump(table) # :nodoc:
|
349
495
|
Marshal::dump(table)
|
350
496
|
end
|
351
497
|
|
352
498
|
# This method is just a wrapped around Marshal.load.
|
499
|
+
# to allow subclass overriding used in YAML::Store.
|
353
500
|
def load(content) # :nodoc:
|
354
501
|
Marshal::load(content)
|
355
502
|
end
|
356
503
|
|
357
|
-
|
358
|
-
|
359
|
-
Marshal::load(file)
|
504
|
+
def empty_marshal_data
|
505
|
+
EMPTY_MARSHAL_DATA
|
360
506
|
end
|
361
|
-
|
362
|
-
|
363
|
-
# Commits changes to the data store file.
|
364
|
-
def commit_new(f)
|
365
|
-
f.truncate(0)
|
366
|
-
f.rewind
|
367
|
-
new_file = @filename + ".new"
|
368
|
-
File.open(new_file, RD_ACCESS) do |nf|
|
369
|
-
FileUtils.copy_stream(nf, f)
|
370
|
-
end
|
371
|
-
File.unlink(new_file)
|
507
|
+
def empty_marshal_checksum
|
508
|
+
EMPTY_MARSHAL_CHECKSUM
|
372
509
|
end
|
373
510
|
end
|
374
511
|
|
data/rubysl-pstore.gemspec
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rubysl-pstore
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Brian Shirai
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-
|
11
|
+
date: 2013-09-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -52,20 +52,6 @@ dependencies:
|
|
52
52
|
- - ~>
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '1.5'
|
55
|
-
- !ruby/object:Gem::Dependency
|
56
|
-
name: rubysl-prettyprint
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
58
|
-
requirements:
|
59
|
-
- - ~>
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: '1.0'
|
62
|
-
type: :development
|
63
|
-
prerelease: false
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
65
|
-
requirements:
|
66
|
-
- - ~>
|
67
|
-
- !ruby/object:Gem::Version
|
68
|
-
version: '1.0'
|
69
55
|
description: Ruby standard library pstore.
|
70
56
|
email:
|
71
57
|
- brixen@gmail.com
|