disk_store 0.3.0 → 0.4.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/Gemfile.lock +1 -1
- data/lib/disk_store/version.rb +1 -1
- data/lib/disk_store.rb +28 -8
- data/spec/disk_store_spec.rb +50 -1
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e58eb0c302ae1fdb782a4dee97571d9579b9981e
|
4
|
+
data.tar.gz: 7469c9c689d3658ffa1fe50a30ae5c7c0c9142de
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 51c2e56da273a2984c84fe2805642b0cfe95960198a96e1392fa2716cd7d0eb1602675269074532c3745329a1e09e7c1a38e0a147ff5723c2fae0518078a1caf
|
7
|
+
data.tar.gz: 50437b7e690559f8c90bac340f7522df7fc08f159bf1946bd7aa428495ae3d18359aaff73d213340851bacd8f9d7ba0b2427f02969a4f88fe48b119c96a21f3e
|
data/Gemfile.lock
CHANGED
data/lib/disk_store/version.rb
CHANGED
data/lib/disk_store.rb
CHANGED
@@ -2,6 +2,8 @@ require 'open-uri'
|
|
2
2
|
require 'disk_store/reaper'
|
3
3
|
|
4
4
|
class DiskStore
|
5
|
+
class MD5DidNotMatch < StandardError; end
|
6
|
+
|
5
7
|
DIR_FORMATTER = "%03X"
|
6
8
|
FILENAME_MAX_SIZE = 228 # max filename size on file system is 255, minus room for timestamp and random characters appended by Tempfile (used by atomic write)
|
7
9
|
EXCLUDED_DIRS = ['.', '..'].freeze
|
@@ -15,15 +17,20 @@ class DiskStore
|
|
15
17
|
@reaper = Reaper.spawn_for(@root_path, @options)
|
16
18
|
end
|
17
19
|
|
18
|
-
def read(key)
|
19
|
-
File.open(key_file_path(key), 'rb')
|
20
|
+
def read(key, md5 = nil)
|
21
|
+
fd = File.open(key_file_path(key), 'rb')
|
22
|
+
validate_file!(key_file_path(key), md5) if !md5.nil?
|
23
|
+
fd
|
24
|
+
rescue MD5DidNotMatch => e
|
25
|
+
delete(key)
|
26
|
+
raise e
|
20
27
|
end
|
21
28
|
|
22
|
-
def write(key, io)
|
29
|
+
def write(key, io, md5 = nil)
|
23
30
|
file_path = key_file_path(key)
|
24
31
|
ensure_cache_path(File.dirname(file_path))
|
25
32
|
|
26
|
-
File.open(file_path, 'wb') do |f|
|
33
|
+
fd = File.open(file_path, 'wb') do |f|
|
27
34
|
begin
|
28
35
|
f.flock File::LOCK_EX
|
29
36
|
IO::copy_stream(io, f)
|
@@ -34,6 +41,12 @@ class DiskStore
|
|
34
41
|
f.flock File::LOCK_UN
|
35
42
|
end
|
36
43
|
end
|
44
|
+
|
45
|
+
validate_file!(file_path, md5) if !md5.nil?
|
46
|
+
fd
|
47
|
+
rescue MD5DidNotMatch => e
|
48
|
+
delete(key)
|
49
|
+
raise e
|
37
50
|
end
|
38
51
|
|
39
52
|
def exist?(key)
|
@@ -49,17 +62,17 @@ class DiskStore
|
|
49
62
|
true
|
50
63
|
end
|
51
64
|
|
52
|
-
def fetch(key)
|
65
|
+
def fetch(key, md5 = nil)
|
53
66
|
if block_given?
|
54
67
|
if exist?(key)
|
55
|
-
read(key)
|
68
|
+
read(key, md5)
|
56
69
|
else
|
57
70
|
io = yield
|
58
|
-
write(key, io)
|
71
|
+
write(key, io, md5)
|
59
72
|
read(key)
|
60
73
|
end
|
61
74
|
else
|
62
|
-
read(key)
|
75
|
+
read(key, md5)
|
63
76
|
end
|
64
77
|
end
|
65
78
|
|
@@ -84,6 +97,13 @@ private
|
|
84
97
|
File.join(@root_path, DIR_FORMATTER % dir_1, DIR_FORMATTER % dir_2, *fname_paths)
|
85
98
|
end
|
86
99
|
|
100
|
+
def validate_file!(file_path, md5)
|
101
|
+
real_md5 = Digest::MD5.file(file_path).hexdigest
|
102
|
+
if md5 != real_md5
|
103
|
+
raise MD5DidNotMatch.new("MD5 mismatch. Expected: #{md5}, Actual: #{real_md5}")
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
87
107
|
# Make sure a file path's directories exist.
|
88
108
|
def ensure_cache_path(path)
|
89
109
|
FileUtils.makedirs(path) unless File.exist?(path)
|
data/spec/disk_store_spec.rb
CHANGED
@@ -29,13 +29,39 @@ describe DiskStore do
|
|
29
29
|
end
|
30
30
|
|
31
31
|
context "#read" do
|
32
|
-
|
32
|
+
before(:each) do
|
33
33
|
cache.write(key, file)
|
34
34
|
file.close
|
35
35
|
file.unlink # ensure the tempfile is deleted
|
36
|
+
end
|
37
|
+
|
38
|
+
it "reads from disk" do
|
36
39
|
expect(cache.read(key)).to be_an_instance_of(File)
|
37
40
|
expect(cache.read(key).read).to eq file_contents
|
38
41
|
end
|
42
|
+
|
43
|
+
describe "md5 validation" do
|
44
|
+
let(:expected_md5) { Digest::MD5.hexdigest(file_contents) }
|
45
|
+
|
46
|
+
it "validates the md5 when reading from disk" do
|
47
|
+
expect(cache).to receive(:validate_file!)
|
48
|
+
cache.read(key, expected_md5)
|
49
|
+
end
|
50
|
+
|
51
|
+
it "does not throw an exception when the md5 is correct" do
|
52
|
+
expect {
|
53
|
+
cache.read(key, expected_md5)
|
54
|
+
}.to_not raise_error
|
55
|
+
end
|
56
|
+
|
57
|
+
it "raises an exception and deletes the file when the md5 is incorrect" do
|
58
|
+
expect {
|
59
|
+
cache.read(key, "naw")
|
60
|
+
}.to raise_error(DiskStore::MD5DidNotMatch)
|
61
|
+
|
62
|
+
expect(cache.exist?(key)).to be_false
|
63
|
+
end
|
64
|
+
end
|
39
65
|
end
|
40
66
|
|
41
67
|
context "#write" do
|
@@ -47,6 +73,29 @@ describe DiskStore do
|
|
47
73
|
file_path = Dir[File.join(@tmpdir, "**/*")].last
|
48
74
|
expect(File.binread(file_path)).to eq file_contents
|
49
75
|
end
|
76
|
+
|
77
|
+
describe "md5 validation" do
|
78
|
+
let(:expected_md5) { Digest::MD5.hexdigest(file_contents) }
|
79
|
+
|
80
|
+
it "validates md5 after writing to disk" do
|
81
|
+
expect(cache).to receive(:validate_file!)
|
82
|
+
cache.write(key, file, expected_md5)
|
83
|
+
end
|
84
|
+
|
85
|
+
it "does not throw an exception when the md5 is correct" do
|
86
|
+
expect {
|
87
|
+
cache.write(key, file, expected_md5)
|
88
|
+
}.to_not raise_error
|
89
|
+
end
|
90
|
+
|
91
|
+
it "raises an exception and deletes the file when the md5 is incorrect" do
|
92
|
+
expect {
|
93
|
+
cache.write(key, file, "i am incorrect")
|
94
|
+
}.to raise_error(DiskStore::MD5DidNotMatch)
|
95
|
+
|
96
|
+
expect(cache.exist?(key)).to be_false
|
97
|
+
end
|
98
|
+
end
|
50
99
|
end
|
51
100
|
|
52
101
|
context "#delete" do
|