Class: Bundler::CompactIndexClient::CacheFile
- Inherits:
-
Object
- Object
- Bundler::CompactIndexClient::CacheFile
- Defined in:
- lib/bundler/compact_index_client/cache_file.rb
Overview
write cache files in a way that is robust to concurrent modifications if digests are given, the checksums will be verified
Defined Under Namespace
Classes: ClosedError, DigestMismatchError, Error
Instance Attribute Summary collapse
-
#original_path ⇒ Object
readonly
Returns the value of attribute original_path.
-
#path ⇒ Object
readonly
Returns the value of attribute path.
Class Method Summary collapse
-
.copy(path, &block) ⇒ Object
Initialize with a copy of the original file, then yield the instance.
-
.write(path, data, digests = nil) ⇒ Object
Write data to a temp file, then replace the original file with it verifying the digests if given.
Instance Method Summary collapse
-
#append(data) ⇒ Object
Returns false without appending when no digests since appending is too error prone to do without digests.
-
#close ⇒ Object
Remove the temp file without replacing the original file.
-
#commit ⇒ Object
Replace the original file with the temp file without verifying digests.
- #commit! ⇒ Object
-
#digests=(expected_digests) ⇒ Object
set the digests that will be verified at the end.
- #digests? ⇒ Boolean
-
#initialize(original_path, &block) ⇒ CacheFile
constructor
A new instance of CacheFile.
-
#initialize_digests(keys = nil) ⇒ Object
initialize the digests using CompactIndexClient::SUPPORTED_DIGESTS, or a subset based on keys.
-
#md5 ⇒ Object
remove this method when we stop generating md5 digests for legacy etags.
-
#open(write_mode = "wb", perm = @perm, &block) ⇒ Object
Open the temp file for writing, reusing original permissions, yielding the IO object.
-
#reset_digests ⇒ Object
reset the digests so they don’t contain any previously read data.
- #size ⇒ Object
-
#verify ⇒ Object
Verify the digests, returning true on match, false on mismatch.
- #write(data) ⇒ Object
Constructor Details
#initialize(original_path, &block) ⇒ CacheFile
Returns a new instance of CacheFile.
49 50 51 52 53 54 55 56 57 58 59 |
# File 'lib/bundler/compact_index_client/cache_file.rb', line 49 def initialize(original_path, &block) @original_path = original_path @perm = original_path.file? ? original_path.stat.mode : DEFAULT_FILE_MODE @path = original_path.sub(/$/, ".#{$$}.tmp") return unless block_given? begin yield self ensure close end end |
Instance Attribute Details
#original_path ⇒ Object (readonly)
Returns the value of attribute original_path.
47 48 49 |
# File 'lib/bundler/compact_index_client/cache_file.rb', line 47 def original_path @original_path end |
#path ⇒ Object (readonly)
Returns the value of attribute path.
47 48 49 |
# File 'lib/bundler/compact_index_client/cache_file.rb', line 47 def path @path end |
Class Method Details
.copy(path, &block) ⇒ Object
Initialize with a copy of the original file, then yield the instance.
24 25 26 27 28 29 30 31 32 33 34 35 36 |
# File 'lib/bundler/compact_index_client/cache_file.rb', line 24 def self.copy(path, &block) new(path) do |file| file.initialize_digests SharedHelpers.filesystem_access(path, :read) do path.open("rb") do |s| file.open {|f| IO.copy_stream(s, f) } end end yield file end end |
.write(path, data, digests = nil) ⇒ Object
Write data to a temp file, then replace the original file with it verifying the digests if given.
39 40 41 42 43 44 45 |
# File 'lib/bundler/compact_index_client/cache_file.rb', line 39 def self.write(path, data, digests = nil) return unless data new(path) do |file| file.digests = digests file.write(data) end end |
Instance Method Details
#append(data) ⇒ Object
Returns false without appending when no digests since appending is too error prone to do without digests.
109 110 111 112 113 |
# File 'lib/bundler/compact_index_client/cache_file.rb', line 109 def append(data) return false unless digests? open("a") {|f| f.write data } verify && commit end |
#close ⇒ Object
Remove the temp file without replacing the original file. The file is permanently closed.
146 147 148 149 150 |
# File 'lib/bundler/compact_index_client/cache_file.rb', line 146 def close return if @closed FileUtils.remove_file(path) if @path&.file? @closed = true end |
#commit ⇒ Object
Replace the original file with the temp file without verifying digests. The file is permanently closed.
136 137 138 139 140 141 142 |
# File 'lib/bundler/compact_index_client/cache_file.rb', line 136 def commit raise ClosedError, "Cannot commit closed file" if @closed SharedHelpers.filesystem_access(original_path, :write) do FileUtils.mv(path, original_path) end @closed = true end |
#commit! ⇒ Object
121 122 123 124 |
# File 'lib/bundler/compact_index_client/cache_file.rb', line 121 def commit! verify || raise(DigestMismatchError.new(@base64digests, @expected_digests)) commit end |
#digests=(expected_digests) ⇒ Object
set the digests that will be verified at the end
77 78 79 80 81 82 83 84 85 86 87 |
# File 'lib/bundler/compact_index_client/cache_file.rb', line 77 def digests=(expected_digests) @expected_digests = expected_digests if @expected_digests.nil? @digests = nil elsif @digests @digests = @digests.slice(*@expected_digests.keys) else initialize_digests(@expected_digests.keys) end end |
#digests? ⇒ Boolean
94 95 96 |
# File 'lib/bundler/compact_index_client/cache_file.rb', line 94 def digests? @digests&.any? end |
#initialize_digests(keys = nil) ⇒ Object
initialize the digests using CompactIndexClient::SUPPORTED_DIGESTS, or a subset based on keys.
66 67 68 69 |
# File 'lib/bundler/compact_index_client/cache_file.rb', line 66 def initialize_digests(keys = nil) @digests = keys ? SUPPORTED_DIGESTS.slice(*keys) : SUPPORTED_DIGESTS.dup @digests.transform_values! {|algo_class| SharedHelpers.digest(algo_class).new } end |
#md5 ⇒ Object
remove this method when we stop generating md5 digests for legacy etags
90 91 92 |
# File 'lib/bundler/compact_index_client/cache_file.rb', line 90 def md5 @digests && @digests["md5"] end |
#open(write_mode = "wb", perm = @perm, &block) ⇒ Object
Open the temp file for writing, reusing original permissions, yielding the IO object.
99 100 101 102 103 104 105 106 |
# File 'lib/bundler/compact_index_client/cache_file.rb', line 99 def open(write_mode = "wb", perm = @perm, &block) raise ClosedError, "Cannot reopen closed file" if @closed SharedHelpers.filesystem_access(path, :write) do path.open(write_mode, perm) do |f| yield digests? ? Gem::Package::DigestIO.new(f, @digests) : f end end end |
#reset_digests ⇒ Object
reset the digests so they don’t contain any previously read data
72 73 74 |
# File 'lib/bundler/compact_index_client/cache_file.rb', line 72 def reset_digests @digests&.each_value(&:reset) end |
#size ⇒ Object
61 62 63 |
# File 'lib/bundler/compact_index_client/cache_file.rb', line 61 def size path.size end |
#verify ⇒ Object
Verify the digests, returning true on match, false on mismatch.
127 128 129 130 131 132 |
# File 'lib/bundler/compact_index_client/cache_file.rb', line 127 def verify return true unless @expected_digests && digests? @base64digests = @digests.transform_values!(&:base64digest) @digests = nil @base64digests.all? {|algo, digest| @expected_digests[algo] == digest } end |
#write(data) ⇒ Object
115 116 117 118 119 |
# File 'lib/bundler/compact_index_client/cache_file.rb', line 115 def write(data) reset_digests open {|f| f.write data } commit! end |