pstore 0.2.0 → 0.3.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/.document +4 -0
- data/.github/release.yml +4 -0
- data/.github/workflows/push_gem.yml +5 -5
- data/.github/workflows/test.yml +6 -2
- data/lib/pstore.rb +62 -42
- data/pstore.gemspec +1 -0
- metadata +6 -7
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: be4c744cb12983612ebef3ae585d25571252869cfef51a8799a0c3328be339c8
|
|
4
|
+
data.tar.gz: 9527c38bdea78cfee23874de9ecfdfee3ce9beefb541bd6e26abfa3ba55e3a3c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7fa202004bbd5761b8a457fda173f69d2b0574d1b995ef3e2800c2881213719fb6e9cacd4d0cccfb29b0864c28f149a0520b4102f0172ecfccf7ae277367d023
|
|
7
|
+
data.tar.gz: b2724f28ea0ede1bac0675f77bc00665771b88b6e946da3dfd00ab0e946adef9589a350bcce05732c4568b867714b98fc7133810083a5af60ee62fdd67936efc
|
data/.document
ADDED
data/.github/release.yml
ADDED
|
@@ -23,24 +23,24 @@ jobs:
|
|
|
23
23
|
|
|
24
24
|
steps:
|
|
25
25
|
- name: Harden Runner
|
|
26
|
-
uses: step-security/harden-runner@
|
|
26
|
+
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
|
|
27
27
|
with:
|
|
28
28
|
egress-policy: audit
|
|
29
29
|
|
|
30
|
-
- uses: actions/checkout@
|
|
30
|
+
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
|
31
31
|
|
|
32
32
|
- name: Set up Ruby
|
|
33
|
-
uses: ruby/setup-ruby@
|
|
33
|
+
uses: ruby/setup-ruby@d5126b9b3579e429dd52e51e68624dda2e05be25 # v1.267.0
|
|
34
34
|
with:
|
|
35
35
|
bundler-cache: true
|
|
36
36
|
ruby-version: ruby
|
|
37
37
|
|
|
38
38
|
- name: Publish to RubyGems
|
|
39
|
-
uses: rubygems/release-gem@
|
|
39
|
+
uses: rubygems/release-gem@7f9650160c1a4e7989fdc9855807bdbd421d8b6b # v1.4.1
|
|
40
40
|
|
|
41
41
|
- name: Create GitHub release
|
|
42
42
|
run: |
|
|
43
43
|
tag_name="$(git describe --tags --abbrev=0)"
|
|
44
44
|
gh release create "${tag_name}" --verify-tag --generate-notes
|
|
45
45
|
env:
|
|
46
|
-
GITHUB_TOKEN: ${{ secrets.
|
|
46
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
data/.github/workflows/test.yml
CHANGED
|
@@ -15,13 +15,17 @@ jobs:
|
|
|
15
15
|
strategy:
|
|
16
16
|
matrix:
|
|
17
17
|
ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }}
|
|
18
|
-
os: [ ubuntu-latest, macos-latest ]
|
|
18
|
+
os: [ ubuntu-latest, macos-latest, windows-latest ]
|
|
19
19
|
exclude:
|
|
20
20
|
- { os: macos-latest, ruby: '2.4' }
|
|
21
21
|
- { os: macos-latest, ruby: '2.5' }
|
|
22
|
+
- { os: windows-latest, ruby: head }
|
|
23
|
+
include:
|
|
24
|
+
- { os: windows-latest, ruby: mingw }
|
|
25
|
+
- { os: windows-latest, ruby: mswin }
|
|
22
26
|
runs-on: ${{ matrix.os }}
|
|
23
27
|
steps:
|
|
24
|
-
- uses: actions/checkout@
|
|
28
|
+
- uses: actions/checkout@v7
|
|
25
29
|
- name: Set up Ruby
|
|
26
30
|
uses: ruby/setup-ruby@v1
|
|
27
31
|
with:
|
data/lib/pstore.rb
CHANGED
|
@@ -326,11 +326,15 @@ require "digest"
|
|
|
326
326
|
# end
|
|
327
327
|
#
|
|
328
328
|
class PStore
|
|
329
|
-
|
|
329
|
+
# :stopdoc:
|
|
330
|
+
VERSION = "0.3.0"
|
|
330
331
|
|
|
331
|
-
RDWR_ACCESS =
|
|
332
|
-
RD_ACCESS =
|
|
333
|
-
WR_ACCESS =
|
|
332
|
+
RDWR_ACCESS = IO::RDWR | IO::CREAT | IO::BINARY
|
|
333
|
+
RD_ACCESS = IO::RDONLY | IO::BINARY
|
|
334
|
+
WR_ACCESS = IO::WRONLY | IO::CREAT | IO::TRUNC | IO::BINARY
|
|
335
|
+
NOFOLLOW = IO.const_defined?(:NOFOLLOW) ? IO::NOFOLLOW : 0
|
|
336
|
+
private_constant :RDWR_ACCESS, :RD_ACCESS, :WR_ACCESS, :NOFOLLOW
|
|
337
|
+
# :startdoc:
|
|
334
338
|
|
|
335
339
|
# The error type thrown by all PStore methods.
|
|
336
340
|
class Error < StandardError
|
|
@@ -364,12 +368,26 @@ class PStore
|
|
|
364
368
|
#
|
|
365
369
|
# A \PStore object is
|
|
366
370
|
# {reentrant}[https://en.wikipedia.org/wiki/Reentrancy_(computing)].
|
|
367
|
-
# If argument +thread_safe+ is given as +true+,
|
|
371
|
+
# If argument or keyword argument +thread_safe+ is given as +true+,
|
|
368
372
|
# the object is also thread-safe (at the cost of a small performance penalty):
|
|
369
373
|
#
|
|
370
374
|
# store = PStore.new(path, true)
|
|
375
|
+
# store = PStore.new(path, thread_safe: true)
|
|
371
376
|
#
|
|
372
|
-
|
|
377
|
+
# If keyword argument +ultra_safe+ is given as +true+, the object is
|
|
378
|
+
# set to +ultra_safe+ mode.
|
|
379
|
+
#
|
|
380
|
+
# store = PStore.new(path, ultra_safe: true)
|
|
381
|
+
#
|
|
382
|
+
# If keyword argument +follow_symlink+ is given as +false+, the store file
|
|
383
|
+
# itself must not be a symbolic link. This option has no effect on platforms
|
|
384
|
+
# that do not support IO::NOFOLLOW. Symbolic links in parent directories are
|
|
385
|
+
# not affected.
|
|
386
|
+
#
|
|
387
|
+
# store = PStore.new(path, follow_symlink: false)
|
|
388
|
+
#
|
|
389
|
+
def initialize(file, _thread_safe = false, thread_safe: _thread_safe,
|
|
390
|
+
ultra_safe: false, follow_symlink: true)
|
|
373
391
|
dir = File::dirname(file)
|
|
374
392
|
unless File::directory? dir
|
|
375
393
|
raise PStore::Error, format("directory %s does not exist", dir)
|
|
@@ -377,16 +395,17 @@ class PStore
|
|
|
377
395
|
if File::exist? file and not File::readable? file
|
|
378
396
|
raise PStore::Error, format("file %s not readable", file)
|
|
379
397
|
end
|
|
380
|
-
@filename = file
|
|
398
|
+
@filename = File.path(file)
|
|
381
399
|
@abort = false
|
|
382
|
-
@ultra_safe =
|
|
400
|
+
@ultra_safe = ultra_safe
|
|
383
401
|
@thread_safe = thread_safe
|
|
402
|
+
@nofollow = follow_symlink ? 0 : NOFOLLOW
|
|
384
403
|
@lock = Thread::Mutex.new
|
|
385
404
|
end
|
|
386
405
|
|
|
387
406
|
# Raises PStore::Error if the calling code is not in a PStore#transaction.
|
|
388
407
|
def in_transaction
|
|
389
|
-
raise PStore::Error, "not in transaction" unless @lock.
|
|
408
|
+
raise PStore::Error, "not in transaction" unless @lock.owned?
|
|
390
409
|
end
|
|
391
410
|
#
|
|
392
411
|
# Raises PStore::Error if the calling code is not in a PStore#transaction or
|
|
@@ -436,7 +455,7 @@ class PStore
|
|
|
436
455
|
def fetch(key, default=PStore::Error)
|
|
437
456
|
in_transaction
|
|
438
457
|
unless @table.key? key
|
|
439
|
-
if
|
|
458
|
+
if PStore::Error.equal?(default)
|
|
440
459
|
raise PStore::Error, format("undefined key '%s'", key)
|
|
441
460
|
else
|
|
442
461
|
return default
|
|
@@ -524,7 +543,7 @@ class PStore
|
|
|
524
543
|
def commit
|
|
525
544
|
in_transaction
|
|
526
545
|
@abort = false
|
|
527
|
-
throw
|
|
546
|
+
throw self
|
|
528
547
|
end
|
|
529
548
|
|
|
530
549
|
# Exits the current transaction block, discarding any changes
|
|
@@ -535,7 +554,7 @@ class PStore
|
|
|
535
554
|
def abort
|
|
536
555
|
in_transaction
|
|
537
556
|
@abort = true
|
|
538
|
-
throw
|
|
557
|
+
throw self
|
|
539
558
|
end
|
|
540
559
|
|
|
541
560
|
# Opens a transaction block for the store.
|
|
@@ -567,7 +586,7 @@ class PStore
|
|
|
567
586
|
begin
|
|
568
587
|
@table, checksum, original_data_size = load_data(file, read_only)
|
|
569
588
|
|
|
570
|
-
catch(
|
|
589
|
+
catch(self) do
|
|
571
590
|
value = yield(self)
|
|
572
591
|
end
|
|
573
592
|
|
|
@@ -580,7 +599,7 @@ class PStore
|
|
|
580
599
|
else
|
|
581
600
|
# This can only occur if read_only == true.
|
|
582
601
|
@table = {}
|
|
583
|
-
catch(
|
|
602
|
+
catch(self) do
|
|
584
603
|
value = yield(self)
|
|
585
604
|
end
|
|
586
605
|
end
|
|
@@ -591,6 +610,8 @@ class PStore
|
|
|
591
610
|
end
|
|
592
611
|
|
|
593
612
|
private
|
|
613
|
+
# :stopdoc:
|
|
614
|
+
|
|
594
615
|
# Constant for relieving Ruby's garbage collector.
|
|
595
616
|
CHECKSUM_ALGO = %w[SHA512 SHA384 SHA256 SHA1 RMD160 MD5].each do |algo|
|
|
596
617
|
begin
|
|
@@ -604,6 +625,7 @@ class PStore
|
|
|
604
625
|
|
|
605
626
|
EMPTY_MARSHAL_DATA.freeze
|
|
606
627
|
EMPTY_MARSHAL_CHECKSUM.freeze
|
|
628
|
+
private_constant :CHECKSUM_ALGO, :EMPTY_STRING, :EMPTY_MARSHAL_DATA, :EMPTY_MARSHAL_CHECKSUM
|
|
607
629
|
|
|
608
630
|
#
|
|
609
631
|
# Open the specified filename (either in read-only mode or in
|
|
@@ -615,23 +637,26 @@ class PStore
|
|
|
615
637
|
# All exceptions are propagated.
|
|
616
638
|
#
|
|
617
639
|
def open_and_lock_file(filename, read_only)
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
640
|
+
filename = File.path(filename)
|
|
641
|
+
loop do
|
|
642
|
+
if read_only
|
|
621
643
|
begin
|
|
622
|
-
file.
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
file.close
|
|
626
|
-
raise
|
|
644
|
+
file = File.new(filename, mode: RD_ACCESS | @nofollow, encoding: Encoding::ASCII_8BIT)
|
|
645
|
+
rescue Errno::ENOENT
|
|
646
|
+
return nil
|
|
627
647
|
end
|
|
628
|
-
|
|
629
|
-
|
|
648
|
+
else
|
|
649
|
+
file = File.new(filename, mode: RDWR_ACCESS | @nofollow, encoding: Encoding::ASCII_8BIT)
|
|
650
|
+
end
|
|
651
|
+
current = false
|
|
652
|
+
begin
|
|
653
|
+
file.flock(read_only ? File::LOCK_SH : File::LOCK_EX)
|
|
654
|
+
# An atomic save may have replaced the file while this lock was pending.
|
|
655
|
+
current = File.identical?(file, filename)
|
|
656
|
+
return file if current
|
|
657
|
+
ensure
|
|
658
|
+
file.close unless current
|
|
630
659
|
end
|
|
631
|
-
else
|
|
632
|
-
file = File.new(filename, **RDWR_ACCESS)
|
|
633
|
-
file.flock(File::LOCK_EX)
|
|
634
|
-
return file
|
|
635
660
|
end
|
|
636
661
|
end
|
|
637
662
|
|
|
@@ -645,6 +670,7 @@ class PStore
|
|
|
645
670
|
table = load(file)
|
|
646
671
|
raise Error, "PStore file seems to be corrupted." unless table.is_a?(Hash)
|
|
647
672
|
rescue EOFError
|
|
673
|
+
raise unless file.size == 0
|
|
648
674
|
# This seems to be a newly-created file.
|
|
649
675
|
table = {}
|
|
650
676
|
end
|
|
@@ -667,23 +693,16 @@ class PStore
|
|
|
667
693
|
end
|
|
668
694
|
end
|
|
669
695
|
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
on_windows_proc = Proc.new do
|
|
674
|
-
is_windows
|
|
675
|
-
end
|
|
676
|
-
Ractor.make_shareable(on_windows_proc) if defined?(Ractor)
|
|
677
|
-
self.class.__send__(:define_method, :on_windows?, &on_windows_proc)
|
|
678
|
-
is_windows
|
|
679
|
-
end
|
|
696
|
+
ON_WINDOWS = true & (/mswin|mingw|bccwin|wince/ =~ RUBY_PLATFORM) # :nodoc:
|
|
697
|
+
private_constant :ON_WINDOWS
|
|
680
698
|
|
|
681
699
|
def save_data(original_checksum, original_file_size, file)
|
|
682
700
|
new_data = dump(@table)
|
|
683
701
|
|
|
684
702
|
if new_data.bytesize != original_file_size || CHECKSUM_ALGO.digest(new_data) != original_checksum
|
|
685
|
-
if @ultra_safe && !
|
|
686
|
-
# Windows
|
|
703
|
+
if @ultra_safe && !ON_WINDOWS
|
|
704
|
+
# Once a file is locked, Windows does not guarantee that the
|
|
705
|
+
# lock will be released until the file is closed.
|
|
687
706
|
save_data_with_atomic_file_rename_strategy(new_data, file)
|
|
688
707
|
else
|
|
689
708
|
save_data_with_fast_strategy(new_data, file)
|
|
@@ -695,13 +714,14 @@ class PStore
|
|
|
695
714
|
|
|
696
715
|
def save_data_with_atomic_file_rename_strategy(data, file)
|
|
697
716
|
temp_filename = "#{@filename}.tmp.#{Process.pid}.#{rand 1000000}"
|
|
698
|
-
temp_file = File.new(temp_filename,
|
|
717
|
+
temp_file = File.new(temp_filename, mode: WR_ACCESS | @nofollow, encoding: Encoding::ASCII_8BIT, perm: 0o000)
|
|
699
718
|
begin
|
|
700
719
|
temp_file.flock(File::LOCK_EX)
|
|
701
720
|
temp_file.write(data)
|
|
702
721
|
temp_file.flush
|
|
722
|
+
temp_file.chmod(file.stat.mode)
|
|
703
723
|
File.rename(temp_filename, @filename)
|
|
704
|
-
rescue
|
|
724
|
+
rescue Exception
|
|
705
725
|
File.unlink(temp_file) rescue nil
|
|
706
726
|
raise
|
|
707
727
|
ensure
|
data/pstore.gemspec
CHANGED
|
@@ -17,6 +17,7 @@ Gem::Specification.new do |spec|
|
|
|
17
17
|
spec.description = spec.summary
|
|
18
18
|
spec.homepage = "https://github.com/ruby/pstore"
|
|
19
19
|
spec.licenses = ["Ruby", "BSD-2-Clause"]
|
|
20
|
+
spec.required_ruby_version = ">= 2.4"
|
|
20
21
|
|
|
21
22
|
spec.metadata["homepage_uri"] = spec.homepage
|
|
22
23
|
spec.metadata["source_code_uri"] = "https://github.com/ruby/pstore"
|
metadata
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: pstore
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Yukihiro Matsumoto
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: exe
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies: []
|
|
13
12
|
description: Transactional File Storage for Ruby Objects
|
|
14
13
|
email:
|
|
@@ -17,7 +16,9 @@ executables: []
|
|
|
17
16
|
extensions: []
|
|
18
17
|
extra_rdoc_files: []
|
|
19
18
|
files:
|
|
19
|
+
- ".document"
|
|
20
20
|
- ".github/dependabot.yml"
|
|
21
|
+
- ".github/release.yml"
|
|
21
22
|
- ".github/workflows/push_gem.yml"
|
|
22
23
|
- ".github/workflows/test.yml"
|
|
23
24
|
- ".gitignore"
|
|
@@ -37,7 +38,6 @@ licenses:
|
|
|
37
38
|
metadata:
|
|
38
39
|
homepage_uri: https://github.com/ruby/pstore
|
|
39
40
|
source_code_uri: https://github.com/ruby/pstore
|
|
40
|
-
post_install_message:
|
|
41
41
|
rdoc_options: []
|
|
42
42
|
require_paths:
|
|
43
43
|
- lib
|
|
@@ -45,15 +45,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
45
45
|
requirements:
|
|
46
46
|
- - ">="
|
|
47
47
|
- !ruby/object:Gem::Version
|
|
48
|
-
version: '
|
|
48
|
+
version: '2.4'
|
|
49
49
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
50
50
|
requirements:
|
|
51
51
|
- - ">="
|
|
52
52
|
- !ruby/object:Gem::Version
|
|
53
53
|
version: '0'
|
|
54
54
|
requirements: []
|
|
55
|
-
rubygems_version: 3.
|
|
56
|
-
signing_key:
|
|
55
|
+
rubygems_version: 3.6.9
|
|
57
56
|
specification_version: 4
|
|
58
57
|
summary: Transactional File Storage for Ruby Objects
|
|
59
58
|
test_files: []
|