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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a169969f0a6815116676b69bccfada6d9c021c00
4
- data.tar.gz: 4047279dfc9bc369a3012e495b73a5d22e5e4f59
3
+ metadata.gz: e58eb0c302ae1fdb782a4dee97571d9579b9981e
4
+ data.tar.gz: 7469c9c689d3658ffa1fe50a30ae5c7c0c9142de
5
5
  SHA512:
6
- metadata.gz: bfbbe98e30b10f1790fc29d4136df196063eccacbf7d47dd2f28bca6e62e130c88136d79f41ceb9b2d33e341b91e5a4852803f5155ec3789ca342846fcdcbf79
7
- data.tar.gz: d9d4184f527e83e14102b6347bb40906047581f84497bdc21533246c9aefeb8b9fd8e53ed4228874057cd521925630b8c40ba98199a6bd59d8d85fc864f23d9b
6
+ metadata.gz: 51c2e56da273a2984c84fe2805642b0cfe95960198a96e1392fa2716cd7d0eb1602675269074532c3745329a1e09e7c1a38e0a147ff5723c2fae0518078a1caf
7
+ data.tar.gz: 50437b7e690559f8c90bac340f7522df7fc08f159bf1946bd7aa428495ae3d18359aaff73d213340851bacd8f9d7ba0b2427f02969a4f88fe48b119c96a21f3e
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- disk_store (0.3.0)
4
+ disk_store (0.4.0)
5
5
  celluloid
6
6
  rake
7
7
 
@@ -1,3 +1,3 @@
1
1
  class DiskStore
2
- VERSION = "0.3.0"
2
+ VERSION = "0.4.0"
3
3
  end
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)
@@ -29,13 +29,39 @@ describe DiskStore do
29
29
  end
30
30
 
31
31
  context "#read" do
32
- it "reads from disk" do
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
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: disk_store
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kelly Sutton