pstore 0.1.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 3de8bbd117d11303d2372e9fd0b9db5a15fb4d6b566905101e70689cd82fbe4d
4
+ data.tar.gz: bbab746d38d3e6fc07bfaf54235bcb6e34727a2a20ae586620064a16de834dc2
5
+ SHA512:
6
+ metadata.gz: 4a4f1df2cafeee50e8c82a1755e8f0836f7c2b685f5372df1461cdac183d43cf3ad19b1dfa9a516ab3698e0ad1837f050be0e86b1b4944dbd5d3c625665f2b5d
7
+ data.tar.gz: f2b9d2cb5172cc4e09eba1ae2ec499bce9972b54028e0b89e6d26e6f66f16918254d3d643dc49d4caed17fe45ffc3278737431864de1d9a87bc429bf4c345c5b
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ Gemfile.lock
@@ -0,0 +1,7 @@
1
+ ---
2
+ sudo: false
3
+ language: ruby
4
+ cache: bundler
5
+ rvm:
6
+ - 2.6.3
7
+ before_install: gem install bundler -v 2.0.2
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ group :development do
6
+ gem "bundler"
7
+ gem "rake"
8
+ gem "test-unit"
9
+ end
@@ -0,0 +1,22 @@
1
+ Copyright (C) 1993-2013 Yukihiro Matsumoto. All rights reserved.
2
+
3
+ Redistribution and use in source and binary forms, with or without
4
+ modification, are permitted provided that the following conditions
5
+ are met:
6
+ 1. Redistributions of source code must retain the above copyright
7
+ notice, this list of conditions and the following disclaimer.
8
+ 2. Redistributions in binary form must reproduce the above copyright
9
+ notice, this list of conditions and the following disclaimer in the
10
+ documentation and/or other materials provided with the distribution.
11
+
12
+ THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
13
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
14
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
15
+ ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
16
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
17
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
18
+ OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
19
+ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
20
+ LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
21
+ OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
22
+ SUCH DAMAGE.
@@ -0,0 +1,95 @@
1
+ # Pstore
2
+
3
+ PStore implements a file based persistence mechanism based on a Hash. User
4
+ code can store hierarchies of Ruby objects (values) into the data store file
5
+ by name (keys). An object hierarchy may be just a single object. User code
6
+ may later read values back from the data store or even update data, as needed.
7
+
8
+ The transactional behavior ensures that any changes succeed or fail together.
9
+ This can be used to ensure that the data store is not left in a transitory
10
+ state, where some values were updated but others were not.
11
+
12
+ Behind the scenes, Ruby objects are stored to the data store file with
13
+ Marshal. That carries the usual limitations. Proc objects cannot be
14
+ marshalled, for example.
15
+
16
+ ## Installation
17
+
18
+ Add this line to your application's Gemfile:
19
+
20
+ ```ruby
21
+ gem 'pstore'
22
+ ```
23
+
24
+ And then execute:
25
+
26
+ $ bundle
27
+
28
+ Or install it yourself as:
29
+
30
+ $ gem install pstore
31
+
32
+ ## Usage
33
+
34
+ ```ruby
35
+ require "pstore"
36
+
37
+ # a mock wiki object...
38
+ class WikiPage
39
+ def initialize( page_name, author, contents )
40
+ @page_name = page_name
41
+ @revisions = Array.new
42
+
43
+ add_revision(author, contents)
44
+ end
45
+
46
+ attr_reader :page_name
47
+
48
+ def add_revision( author, contents )
49
+ @revisions << { :created => Time.now,
50
+ :author => author,
51
+ :contents => contents }
52
+ end
53
+
54
+ def wiki_page_references
55
+ [@page_name] + @revisions.last[:contents].scan(/\b(?:[A-Z]+[a-z]+){2,}/)
56
+ end
57
+
58
+ # ...
59
+ end
60
+
61
+ # create a new page...
62
+ home_page = WikiPage.new( "HomePage", "James Edward Gray II",
63
+ "A page about the JoysOfDocumentation..." )
64
+
65
+ # then we want to update page data and the index together, or not at all...
66
+ wiki = PStore.new("wiki_pages.pstore")
67
+ wiki.transaction do # begin transaction; do all of this or none of it
68
+ # store page...
69
+ wiki[home_page.page_name] = home_page
70
+ # ensure that an index has been created...
71
+ wiki[:wiki_index] ||= Array.new
72
+ # update wiki index...
73
+ wiki[:wiki_index].push(*home_page.wiki_page_references)
74
+ end # commit changes to wiki data store file
75
+
76
+ ### Some time later... ###
77
+
78
+ # read wiki data...
79
+ wiki.transaction(true) do # begin read-only transaction, no changes allowed
80
+ wiki.roots.each do |data_root_name|
81
+ p data_root_name
82
+ p wiki[data_root_name]
83
+ end
84
+ end
85
+ ```
86
+
87
+ ## Development
88
+
89
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
90
+
91
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
92
+
93
+ ## Contributing
94
+
95
+ Bug reports and pull requests are welcome on GitHub at https://github.com/ruby/pstore.
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test/lib"
6
+ t.ruby_opts << "-rhelper"
7
+ t.test_files = FileList["test/**/test_*.rb"]
8
+ end
9
+
10
+ task :default => :test
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "pstore"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,491 @@
1
+ # frozen_string_literal: true
2
+ # = PStore -- Transactional File Storage for Ruby Objects
3
+ #
4
+ # pstore.rb -
5
+ # originally by matz
6
+ # documentation by Kev Jackson and James Edward Gray II
7
+ # improved by Hongli Lai
8
+ #
9
+ # See PStore for documentation.
10
+
11
+ require "digest"
12
+
13
+ #
14
+ # PStore implements a file based persistence mechanism based on a Hash. User
15
+ # 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
17
+ # may later read values back from the data store or even update data, as needed.
18
+ #
19
+ # The transactional behavior ensures that any changes succeed or fail together.
20
+ # This can be used to ensure that the data store is not left in a transitory
21
+ # 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
25
+ # marshalled, for example.
26
+ #
27
+ # == Usage example:
28
+ #
29
+ # require "pstore"
30
+ #
31
+ # # a mock wiki object...
32
+ # class WikiPage
33
+ # def initialize( page_name, author, contents )
34
+ # @page_name = page_name
35
+ # @revisions = Array.new
36
+ #
37
+ # add_revision(author, contents)
38
+ # end
39
+ #
40
+ # attr_reader :page_name
41
+ #
42
+ # def add_revision( author, contents )
43
+ # @revisions << { :created => Time.now,
44
+ # :author => author,
45
+ # :contents => contents }
46
+ # end
47
+ #
48
+ # def wiki_page_references
49
+ # [@page_name] + @revisions.last[:contents].scan(/\b(?:[A-Z]+[a-z]+){2,}/)
50
+ # end
51
+ #
52
+ # # ...
53
+ # end
54
+ #
55
+ # # create a new page...
56
+ # home_page = WikiPage.new( "HomePage", "James Edward Gray II",
57
+ # "A page about the JoysOfDocumentation..." )
58
+ #
59
+ # # then we want to update page data and the index together, or not at all...
60
+ # wiki = PStore.new("wiki_pages.pstore")
61
+ # wiki.transaction do # begin transaction; do all of this or none of it
62
+ # # store page...
63
+ # wiki[home_page.page_name] = home_page
64
+ # # ensure that an index has been created...
65
+ # wiki[:wiki_index] ||= Array.new
66
+ # # update wiki index...
67
+ # wiki[:wiki_index].push(*home_page.wiki_page_references)
68
+ # end # commit changes to wiki data store file
69
+ #
70
+ # ### Some time later... ###
71
+ #
72
+ # # read wiki data...
73
+ # wiki.transaction(true) do # begin read-only transaction, no changes allowed
74
+ # wiki.roots.each do |data_root_name|
75
+ # p data_root_name
76
+ # p wiki[data_root_name]
77
+ # end
78
+ # end
79
+ #
80
+ # == Transaction modes
81
+ #
82
+ # By default, file integrity is only ensured as long as the operating system
83
+ # (and the underlying hardware) doesn't raise any unexpected I/O errors. If an
84
+ # I/O error occurs while PStore is writing to its file, then the file will
85
+ # become corrupted.
86
+ #
87
+ # You can prevent this by setting <em>pstore.ultra_safe = true</em>.
88
+ # However, this results in a minor performance loss, and only works on platforms
89
+ # that support atomic file renames. Please consult the documentation for
90
+ # +ultra_safe+ for details.
91
+ #
92
+ # Needless to say, if you're storing valuable data with PStore, then you should
93
+ # backup the PStore files from time to time.
94
+ class PStore
95
+ RDWR_ACCESS = {mode: IO::RDWR | IO::CREAT | IO::BINARY, encoding: Encoding::ASCII_8BIT}.freeze
96
+ RD_ACCESS = {mode: IO::RDONLY | IO::BINARY, encoding: Encoding::ASCII_8BIT}.freeze
97
+ WR_ACCESS = {mode: IO::WRONLY | IO::CREAT | IO::TRUNC | IO::BINARY, encoding: Encoding::ASCII_8BIT}.freeze
98
+
99
+ # The error type thrown by all PStore methods.
100
+ class Error < StandardError
101
+ end
102
+
103
+ # Whether PStore should do its best to prevent file corruptions, even when under
104
+ # unlikely-to-occur error conditions such as out-of-space conditions and other
105
+ # unusual OS filesystem errors. Setting this flag comes at the price in the form
106
+ # of a performance loss.
107
+ #
108
+ # This flag only has effect on platforms on which file renames are atomic (e.g.
109
+ # all POSIX platforms: Linux, MacOS X, FreeBSD, etc). The default value is false.
110
+ attr_accessor :ultra_safe
111
+
112
+ #
113
+ # To construct a PStore object, pass in the _file_ path where you would like
114
+ # the data to be stored.
115
+ #
116
+ # PStore objects are always reentrant. But if _thread_safe_ is set to true,
117
+ # then it will become thread-safe at the cost of a minor performance hit.
118
+ #
119
+ def initialize(file, thread_safe = false)
120
+ dir = File::dirname(file)
121
+ unless File::directory? dir
122
+ raise PStore::Error, format("directory %s does not exist", dir)
123
+ end
124
+ if File::exist? file and not File::readable? file
125
+ raise PStore::Error, format("file %s not readable", file)
126
+ end
127
+ @filename = file
128
+ @abort = false
129
+ @ultra_safe = false
130
+ @thread_safe = thread_safe
131
+ @lock = Thread::Mutex.new
132
+ end
133
+
134
+ # Raises PStore::Error if the calling code is not in a PStore#transaction.
135
+ def in_transaction
136
+ raise PStore::Error, "not in transaction" unless @lock.locked?
137
+ end
138
+ #
139
+ # Raises PStore::Error if the calling code is not in a PStore#transaction or
140
+ # if the code is in a read-only PStore#transaction.
141
+ #
142
+ def in_transaction_wr
143
+ in_transaction
144
+ raise PStore::Error, "in read-only transaction" if @rdonly
145
+ end
146
+ private :in_transaction, :in_transaction_wr
147
+
148
+ #
149
+ # Retrieves a value from the PStore file data, by _name_. The hierarchy of
150
+ # Ruby objects stored under that root _name_ will be returned.
151
+ #
152
+ # *WARNING*: This method is only valid in a PStore#transaction. It will
153
+ # raise PStore::Error if called at any other time.
154
+ #
155
+ def [](name)
156
+ in_transaction
157
+ @table[name]
158
+ end
159
+ #
160
+ # This method is just like PStore#[], save that you may also provide a
161
+ # _default_ value for the object. In the event the specified _name_ is not
162
+ # found in the data store, your _default_ will be returned instead. If you do
163
+ # not specify a default, PStore::Error will be raised if the object is not
164
+ # found.
165
+ #
166
+ # *WARNING*: This method is only valid in a PStore#transaction. It will
167
+ # raise PStore::Error if called at any other time.
168
+ #
169
+ def fetch(name, default=PStore::Error)
170
+ in_transaction
171
+ unless @table.key? name
172
+ if default == PStore::Error
173
+ raise PStore::Error, format("undefined root name `%s'", name)
174
+ else
175
+ return default
176
+ end
177
+ end
178
+ @table[name]
179
+ end
180
+ #
181
+ # Stores an individual Ruby object or a hierarchy of Ruby objects in the data
182
+ # store file under the root _name_. Assigning to a _name_ already in the data
183
+ # store clobbers the old data.
184
+ #
185
+ # == Example:
186
+ #
187
+ # require "pstore"
188
+ #
189
+ # store = PStore.new("data_file.pstore")
190
+ # store.transaction do # begin transaction
191
+ # # load some data into the store...
192
+ # store[:single_object] = "My data..."
193
+ # store[:obj_hierarchy] = { "Kev Jackson" => ["rational.rb", "pstore.rb"],
194
+ # "James Gray" => ["erb.rb", "pstore.rb"] }
195
+ # end # commit changes to data store file
196
+ #
197
+ # *WARNING*: This method is only valid in a PStore#transaction and it cannot
198
+ # be read-only. It will raise PStore::Error if called at any other time.
199
+ #
200
+ def []=(name, value)
201
+ in_transaction_wr
202
+ @table[name] = value
203
+ end
204
+ #
205
+ # Removes an object hierarchy from the data store, by _name_.
206
+ #
207
+ # *WARNING*: This method is only valid in a PStore#transaction and it cannot
208
+ # be read-only. It will raise PStore::Error if called at any other time.
209
+ #
210
+ def delete(name)
211
+ in_transaction_wr
212
+ @table.delete name
213
+ end
214
+
215
+ #
216
+ # Returns the names of all object hierarchies currently in the store.
217
+ #
218
+ # *WARNING*: This method is only valid in a PStore#transaction. It will
219
+ # raise PStore::Error if called at any other time.
220
+ #
221
+ def roots
222
+ in_transaction
223
+ @table.keys
224
+ end
225
+ #
226
+ # Returns true if the supplied _name_ is currently in the data store.
227
+ #
228
+ # *WARNING*: This method is only valid in a PStore#transaction. It will
229
+ # raise PStore::Error if called at any other time.
230
+ #
231
+ def root?(name)
232
+ in_transaction
233
+ @table.key? name
234
+ end
235
+ # Returns the path to the data store file.
236
+ def path
237
+ @filename
238
+ end
239
+
240
+ #
241
+ # Ends the current PStore#transaction, committing any changes to the data
242
+ # store immediately.
243
+ #
244
+ # == Example:
245
+ #
246
+ # require "pstore"
247
+ #
248
+ # store = PStore.new("data_file.pstore")
249
+ # store.transaction do # begin transaction
250
+ # # load some data into the store...
251
+ # store[:one] = 1
252
+ # store[:two] = 2
253
+ #
254
+ # store.commit # end transaction here, committing changes
255
+ #
256
+ # store[:three] = 3 # this change is never reached
257
+ # end
258
+ #
259
+ # *WARNING*: This method is only valid in a PStore#transaction. It will
260
+ # raise PStore::Error if called at any other time.
261
+ #
262
+ def commit
263
+ in_transaction
264
+ @abort = false
265
+ throw :pstore_abort_transaction
266
+ end
267
+ #
268
+ # Ends the current PStore#transaction, discarding any changes to the data
269
+ # store.
270
+ #
271
+ # == Example:
272
+ #
273
+ # require "pstore"
274
+ #
275
+ # store = PStore.new("data_file.pstore")
276
+ # store.transaction do # begin transaction
277
+ # store[:one] = 1 # this change is not applied, see below...
278
+ # store[:two] = 2 # this change is not applied, see below...
279
+ #
280
+ # store.abort # end transaction here, discard all changes
281
+ #
282
+ # store[:three] = 3 # this change is never reached
283
+ # end
284
+ #
285
+ # *WARNING*: This method is only valid in a PStore#transaction. It will
286
+ # raise PStore::Error if called at any other time.
287
+ #
288
+ def abort
289
+ in_transaction
290
+ @abort = true
291
+ throw :pstore_abort_transaction
292
+ end
293
+
294
+ #
295
+ # Opens a new transaction for the data store. Code executed inside a block
296
+ # passed to this method may read and write data to and from the data store
297
+ # file.
298
+ #
299
+ # At the end of the block, changes are committed to the data store
300
+ # automatically. You may exit the transaction early with a call to either
301
+ # PStore#commit or PStore#abort. See those methods for details about how
302
+ # changes are handled. Raising an uncaught Exception in the block is
303
+ # equivalent to calling PStore#abort.
304
+ #
305
+ # If _read_only_ is set to +true+, you will only be allowed to read from the
306
+ # data store during the transaction and any attempts to change the data will
307
+ # raise a PStore::Error.
308
+ #
309
+ # Note that PStore does not support nested transactions.
310
+ #
311
+ def transaction(read_only = false) # :yields: pstore
312
+ value = nil
313
+ if !@thread_safe
314
+ raise PStore::Error, "nested transaction" unless @lock.try_lock
315
+ else
316
+ begin
317
+ @lock.lock
318
+ rescue ThreadError
319
+ raise PStore::Error, "nested transaction"
320
+ end
321
+ end
322
+ begin
323
+ @rdonly = read_only
324
+ @abort = false
325
+ file = open_and_lock_file(@filename, read_only)
326
+ if file
327
+ begin
328
+ @table, checksum, original_data_size = load_data(file, read_only)
329
+
330
+ catch(:pstore_abort_transaction) do
331
+ value = yield(self)
332
+ end
333
+
334
+ if !@abort && !read_only
335
+ save_data(checksum, original_data_size, file)
336
+ end
337
+ ensure
338
+ file.close
339
+ end
340
+ else
341
+ # This can only occur if read_only == true.
342
+ @table = {}
343
+ catch(:pstore_abort_transaction) do
344
+ value = yield(self)
345
+ end
346
+ end
347
+ ensure
348
+ @lock.unlock
349
+ end
350
+ value
351
+ end
352
+
353
+ private
354
+ # Constant for relieving Ruby's garbage collector.
355
+ CHECKSUM_ALGO = %w[SHA512 SHA384 SHA256 SHA1 RMD160 MD5].each do |algo|
356
+ begin
357
+ break Digest(algo)
358
+ rescue LoadError
359
+ end
360
+ end
361
+ EMPTY_STRING = ""
362
+ EMPTY_MARSHAL_DATA = Marshal.dump({})
363
+ EMPTY_MARSHAL_CHECKSUM = CHECKSUM_ALGO.digest(EMPTY_MARSHAL_DATA)
364
+
365
+ #
366
+ # Open the specified filename (either in read-only mode or in
367
+ # read-write mode) and lock it for reading or writing.
368
+ #
369
+ # The opened File object will be returned. If _read_only_ is true,
370
+ # and the file does not exist, then nil will be returned.
371
+ #
372
+ # All exceptions are propagated.
373
+ #
374
+ def open_and_lock_file(filename, read_only)
375
+ if read_only
376
+ begin
377
+ file = File.new(filename, **RD_ACCESS)
378
+ begin
379
+ file.flock(File::LOCK_SH)
380
+ return file
381
+ rescue
382
+ file.close
383
+ raise
384
+ end
385
+ rescue Errno::ENOENT
386
+ return nil
387
+ end
388
+ else
389
+ file = File.new(filename, **RDWR_ACCESS)
390
+ file.flock(File::LOCK_EX)
391
+ return file
392
+ end
393
+ end
394
+
395
+ # Load the given PStore file.
396
+ # If +read_only+ is true, the unmarshalled Hash will be returned.
397
+ # If +read_only+ is false, a 3-tuple will be returned: the unmarshalled
398
+ # Hash, a checksum of the data, and the size of the data.
399
+ def load_data(file, read_only)
400
+ if read_only
401
+ begin
402
+ table = load(file)
403
+ raise Error, "PStore file seems to be corrupted." unless table.is_a?(Hash)
404
+ rescue EOFError
405
+ # This seems to be a newly-created file.
406
+ table = {}
407
+ end
408
+ table
409
+ else
410
+ data = file.read
411
+ if data.empty?
412
+ # This seems to be a newly-created file.
413
+ table = {}
414
+ checksum = empty_marshal_checksum
415
+ size = empty_marshal_data.bytesize
416
+ else
417
+ table = load(data)
418
+ checksum = CHECKSUM_ALGO.digest(data)
419
+ size = data.bytesize
420
+ raise Error, "PStore file seems to be corrupted." unless table.is_a?(Hash)
421
+ end
422
+ data.replace(EMPTY_STRING)
423
+ [table, checksum, size]
424
+ end
425
+ end
426
+
427
+ def on_windows?
428
+ is_windows = RUBY_PLATFORM =~ /mswin|mingw|bccwin|wince/
429
+ self.class.__send__(:define_method, :on_windows?) do
430
+ is_windows
431
+ end
432
+ is_windows
433
+ end
434
+
435
+ def save_data(original_checksum, original_file_size, file)
436
+ new_data = dump(@table)
437
+
438
+ if new_data.bytesize != original_file_size || CHECKSUM_ALGO.digest(new_data) != original_checksum
439
+ if @ultra_safe && !on_windows?
440
+ # Windows doesn't support atomic file renames.
441
+ save_data_with_atomic_file_rename_strategy(new_data, file)
442
+ else
443
+ save_data_with_fast_strategy(new_data, file)
444
+ end
445
+ end
446
+
447
+ new_data.replace(EMPTY_STRING)
448
+ end
449
+
450
+ def save_data_with_atomic_file_rename_strategy(data, file)
451
+ temp_filename = "#{@filename}.tmp.#{Process.pid}.#{rand 1000000}"
452
+ temp_file = File.new(temp_filename, **WR_ACCESS)
453
+ begin
454
+ temp_file.flock(File::LOCK_EX)
455
+ temp_file.write(data)
456
+ temp_file.flush
457
+ File.rename(temp_filename, @filename)
458
+ rescue
459
+ File.unlink(temp_file) rescue nil
460
+ raise
461
+ ensure
462
+ temp_file.close
463
+ end
464
+ end
465
+
466
+ def save_data_with_fast_strategy(data, file)
467
+ file.rewind
468
+ file.write(data)
469
+ file.truncate(data.bytesize)
470
+ end
471
+
472
+
473
+ # This method is just a wrapped around Marshal.dump
474
+ # to allow subclass overriding used in YAML::Store.
475
+ def dump(table) # :nodoc:
476
+ Marshal::dump(table)
477
+ end
478
+
479
+ # This method is just a wrapped around Marshal.load.
480
+ # to allow subclass overriding used in YAML::Store.
481
+ def load(content) # :nodoc:
482
+ Marshal::load(content)
483
+ end
484
+
485
+ def empty_marshal_data
486
+ EMPTY_MARSHAL_DATA
487
+ end
488
+ def empty_marshal_checksum
489
+ EMPTY_MARSHAL_CHECKSUM
490
+ end
491
+ end
@@ -0,0 +1,3 @@
1
+ class PStore
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,27 @@
1
+ lib = File.expand_path("lib", __dir__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require "pstore/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "pstore"
7
+ spec.version = PStore::VERSION
8
+ spec.authors = ["Hiroshi SHIBATA"]
9
+ spec.email = ["hsbt@ruby-lang.org"]
10
+
11
+ spec.summary = %q{Transactional File Storage for Ruby Objects}
12
+ spec.description = spec.summary
13
+ spec.homepage = "https://github.com/ruby/pstore"
14
+ spec.license = "BSD-2-Clause"
15
+
16
+ spec.metadata["homepage_uri"] = spec.homepage
17
+ spec.metadata["source_code_uri"] = "https://github.com/ruby/pstore"
18
+
19
+ # Specify which files should be added to the gem when it is released.
20
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
21
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
22
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
23
+ end
24
+ spec.bindir = "exe"
25
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
26
+ spec.require_paths = ["lib"]
27
+ end
metadata ADDED
@@ -0,0 +1,56 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pstore
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Hiroshi SHIBATA
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2019-11-06 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Transactional File Storage for Ruby Objects
14
+ email:
15
+ - hsbt@ruby-lang.org
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - ".gitignore"
21
+ - ".travis.yml"
22
+ - Gemfile
23
+ - LICENSE.txt
24
+ - README.md
25
+ - Rakefile
26
+ - bin/console
27
+ - bin/setup
28
+ - lib/pstore.rb
29
+ - lib/pstore/version.rb
30
+ - pstore.gemspec
31
+ homepage: https://github.com/ruby/pstore
32
+ licenses:
33
+ - BSD-2-Clause
34
+ metadata:
35
+ homepage_uri: https://github.com/ruby/pstore
36
+ source_code_uri: https://github.com/ruby/pstore
37
+ post_install_message:
38
+ rdoc_options: []
39
+ require_paths:
40
+ - lib
41
+ required_ruby_version: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ required_rubygems_version: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - ">="
49
+ - !ruby/object:Gem::Version
50
+ version: '0'
51
+ requirements: []
52
+ rubygems_version: 3.0.3
53
+ signing_key:
54
+ specification_version: 4
55
+ summary: Transactional File Storage for Ruby Objects
56
+ test_files: []