blobby 1.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: bfbc91822c372d6508fd42ac40e333e6b1bb3a22
4
- data.tar.gz: 2ced865cbd5b4ba8ca886bbcccd9259b48be8c32
3
+ metadata.gz: 0910a3f2446d9f9eb7cad9f724c8df57cc5d85ac
4
+ data.tar.gz: 2c26dc843a4a15c5894209b590f5b4d01078af14
5
5
  SHA512:
6
- metadata.gz: c7f1eab54fa6f82f9b201b387fe4f7424dd2b4b6148aae03b786fe062ddbb169e38e1e84f05ad07d88f10469ff242629d85e2b06032c3d193da7a921c95192e0
7
- data.tar.gz: 410875dc9fb89abfa8172f762c10f5add5a610de9b3868223675621938e21522ab77ce167d23633e504c32f486f653c252a7fca45d3cf084f34b782e2af3c4ca
6
+ metadata.gz: 9c49f221a912ebbc99a8fadfa2e3643d98c004c1e040e0c0a87368a69c6050bd7a2581ce6824841a91343a8ec67ef8f915a91f16c28a97d5f8830d4d51661304
7
+ data.tar.gz: 86eeb3dd9fdfecd3864dc92d16ef087c38947cec898be4cdc5e02483b86eb6e108a0227121fafb40d1206126f9e10fe5d733c59a37c9bbe794e394081a1378ed
@@ -1,6 +1,7 @@
1
1
  Eval:
2
2
  Exclude:
3
3
  - "Rakefile"
4
+ - "**/version.rb"
4
5
 
5
6
  Metrics/AbcSize:
6
7
  Enabled: false
@@ -40,8 +41,14 @@ Style/FileName:
40
41
  Style/HashSyntax:
41
42
  EnforcedStyle: hash_rockets
42
43
 
44
+ Style/SignalException:
45
+ EnforcedStyle: semantic
46
+
43
47
  Style/StringLiterals:
44
48
  EnforcedStyle: double_quotes
45
49
 
50
+ Style/RegexpLiteral:
51
+ Enabled: false
52
+
46
53
  Style/WordArray:
47
54
  Enabled: false
@@ -0,0 +1,7 @@
1
+ ## 1.1.0 (2016-05-22)
2
+
3
+ * Add `Blobby.store` factory-method.
4
+
5
+ ## 1.0.0 (2015-06-15)
6
+
7
+ * Initial release.
data/README.md CHANGED
@@ -39,5 +39,16 @@ This gem provides several "store" implementations:
39
39
 
40
40
  Other gems provide additional implementations:
41
41
 
42
- # gem "blobby-s3"
43
- Blobby::S3Store.new("mybucket")
42
+ * ["blobby-s3"](https://github.com/realestate-com-au/blobby-s3)
43
+
44
+ `Blobby.store` provides a convenient way to construct an appropriate
45
+ implementation, given a URI (or psuedo-URI):
46
+
47
+ Blobby.store("file:///tmp")
48
+ # => #<Blobby::FilesystemStore ...>
49
+
50
+ Blobby.store("mem:")
51
+ # => #<Blobby::InMemoryStore ...>
52
+
53
+ Blobby.store("http://storage.com/mystuff/")
54
+ # => #<Blobby::HttpStore ...>
@@ -1,5 +1,3 @@
1
- # -*- encoding: utf-8 -*-
2
-
3
1
  $LOAD_PATH << File.expand_path("../lib", __FILE__)
4
2
  require "blobby/version"
5
3
 
@@ -0,0 +1,45 @@
1
+ require "blobby/filesystem_store"
2
+ require "blobby/http_store"
3
+ require "blobby/in_memory_store"
4
+ require "blobby/version"
5
+ require "uri"
6
+
7
+ # BLOB storage.
8
+ #
9
+ module Blobby
10
+
11
+ class << self
12
+
13
+ # Instantiate a BLOB-store based on a storage-address URI.
14
+ # An appropriate store impementation will be selected, based on
15
+ # URI-scheme.
16
+ #
17
+ # @param uri [URI] storage address
18
+ #
19
+ def store(uri)
20
+ uri = URI(uri)
21
+ factory = store_factories[uri.scheme]
22
+ fail ArgumentError, "unknown store type: #{uri}" if factory.nil?
23
+ factory.from_uri(uri)
24
+ end
25
+
26
+ def register_store_factory(uri_scheme, factory)
27
+ store_factories[uri_scheme] = factory
28
+ end
29
+
30
+ private
31
+
32
+ def store_factories
33
+ @store_factories ||= {}
34
+ end
35
+
36
+ end
37
+
38
+ register_store_factory nil, FilesystemStore
39
+ register_store_factory "file", FilesystemStore
40
+ register_store_factory "http", HttpStore
41
+ register_store_factory "https", HttpStore
42
+ register_store_factory "in-memory", InMemoryStore
43
+ register_store_factory "mem", InMemoryStore
44
+
45
+ end
@@ -0,0 +1,69 @@
1
+ require "blobby/key_constraint"
2
+
3
+ module Blobby
4
+
5
+ # A store of BLOBs.
6
+ # @abstract
7
+ class AbstractStore
8
+
9
+ # @return true if the store is available for use
10
+ def available?
11
+ true
12
+ end
13
+
14
+ # Access an object in the store.
15
+ # @param key [String] object address
16
+ # @return [StoredObject] a handle to the addressed object
17
+ def [](key)
18
+ KeyConstraint.must_allow!(key)
19
+ StoredObject.new
20
+ end
21
+
22
+ # A handle to an object in the BLOB-store.
23
+ # @abstract
24
+ class StoredObject
25
+
26
+ # Check for existence.
27
+ # @return true if the object exists
28
+ def exists?
29
+ false
30
+ end
31
+
32
+ # @overload read
33
+ # Read BLOB data.
34
+ # @return [String] data if the object exists
35
+ # @return [nil] if the object doesn't exist
36
+ # @overload read
37
+ # Stream BLOB data in chunks.
38
+ # @yield [chunk] each chunk of data
39
+ # @return [void]
40
+ def read
41
+ content = @hash[key]
42
+ if block_given?
43
+ yield content
44
+ nil
45
+ else
46
+ content
47
+ end
48
+ end
49
+
50
+ def write(content)
51
+ if content.respond_to?(:read)
52
+ content = content.read
53
+ else
54
+ content = content.to_str.dup
55
+ end
56
+ content = content.force_encoding("BINARY") if content.respond_to?(:force_encoding)
57
+ @hash[key] = content
58
+ nil
59
+ end
60
+
61
+ def delete
62
+ !@hash.delete(key).nil?
63
+ end
64
+
65
+ end
66
+
67
+ end
68
+
69
+ end
@@ -25,7 +25,7 @@ module Blobby
25
25
  image_path = Pathname(File.dirname(__FILE__)) + "placeholder.png"
26
26
  image_path.open("rb") do |io|
27
27
  if block_given?
28
- while chunk = io.read(512)
28
+ while (chunk = io.read(512))
29
29
  yield chunk
30
30
  end
31
31
  nil
@@ -10,6 +10,10 @@ module Blobby
10
10
  #
11
11
  class FilesystemStore
12
12
 
13
+ def self.from_uri(uri)
14
+ new(uri.path)
15
+ end
16
+
13
17
  def initialize(dir, options = {}, &sharding_strategy)
14
18
  @dir = Pathname(dir)
15
19
  @umask = options[:umask] || File.umask
@@ -23,6 +27,7 @@ module Blobby
23
27
  dir.directory? && dir.readable? && dir.writable?
24
28
  end
25
29
 
30
+ # (see AbstractStore#[])
26
31
  def [](key)
27
32
  KeyConstraint.must_allow!(key)
28
33
  relative_path = @sharding_strategy.call(key)
@@ -49,7 +54,7 @@ module Blobby
49
54
  def read
50
55
  @path.open("rb") do |io|
51
56
  if block_given?
52
- while chunk = io.read(512)
57
+ while (chunk = io.read(512))
53
58
  yield chunk
54
59
  end
55
60
  nil
@@ -73,7 +78,8 @@ module Blobby
73
78
  end
74
79
 
75
80
  def delete
76
- !!FileUtils.rm(@path)
81
+ FileUtils.rm(@path)
82
+ true
77
83
  rescue Errno::ENOENT
78
84
  false
79
85
  end
@@ -91,7 +97,7 @@ module Blobby
91
97
  RAND_MAX = ("F" * 10).to_i(16)
92
98
 
93
99
  def tmp_name
94
- sprintf("tmp-%X", rand(RAND_MAX))
100
+ format("tmp-%X", rand(RAND_MAX))
95
101
  end
96
102
 
97
103
  def atomic_create(store_path)
@@ -102,7 +108,7 @@ module Blobby
102
108
  begin
103
109
  tmp = tmp_path.open(File::CREAT | File::EXCL | File::WRONLY, 0666)
104
110
  tmp.binmode
105
- rescue Errno::ENOENT => e
111
+ rescue Errno::ENOENT
106
112
  FileUtils.mkdir_p(store_dir.to_s, :mode => apply_umask(0777))
107
113
  retry
108
114
  end
@@ -117,7 +123,7 @@ module Blobby
117
123
  first_try = true
118
124
  begin
119
125
  tmp_path.rename(store_path)
120
- rescue Errno::ESTALE => e
126
+ rescue Errno::ESTALE
121
127
  raise unless first_try
122
128
  first_try = false
123
129
  now = Time.now
@@ -7,13 +7,18 @@ module Blobby
7
7
  #
8
8
  class HttpStore
9
9
 
10
- def initialize(base_url, options = {})
11
- @base_url = base_url
12
- @base_url += "/" unless @base_url.end_with?("/")
10
+ def self.from_uri(uri)
11
+ new(uri)
12
+ end
13
+
14
+ def initialize(uri, options = {})
15
+ uri = URI(uri)
16
+ uri = URI("#{uri}/") unless uri.to_s.end_with?("/")
17
+ @base_uri = uri
13
18
  @max_retries = options.fetch(:max_retries, 2)
14
19
  end
15
20
 
16
- attr_reader :base_url
21
+ attr_reader :base_uri
17
22
  attr_reader :max_retries
18
23
 
19
24
  def available?
@@ -29,10 +34,6 @@ module Blobby
29
34
  StoredObject.new(self, key)
30
35
  end
31
36
 
32
- def base_uri
33
- URI(base_url)
34
- end
35
-
36
37
  def with_http_connection
37
38
  remaining_retry_intervals = retry_intervals(max_retries)
38
39
  begin
@@ -93,7 +94,11 @@ module Blobby
93
94
  end
94
95
 
95
96
  def write(content)
96
- content = content.read if content.respond_to?(:read)
97
+ if content.respond_to?(:read)
98
+ content = content.read
99
+ else
100
+ content = content.dup
101
+ end
97
102
  with_http_connection do |http, path|
98
103
  put = Net::HTTP::Put.new(path)
99
104
  put.body = content
@@ -6,6 +6,10 @@ module Blobby
6
6
  #
7
7
  class InMemoryStore
8
8
 
9
+ def self.from_uri(_uri)
10
+ new
11
+ end
12
+
9
13
  def initialize(hash = {})
10
14
  @hash = hash
11
15
  end
@@ -54,7 +58,7 @@ module Blobby
54
58
  end
55
59
 
56
60
  def delete
57
- !!@hash.delete(key)
61
+ !@hash.delete(key).nil?
58
62
  end
59
63
 
60
64
  end
@@ -8,8 +8,6 @@ module Blobby
8
8
  #
9
9
  module KeyConstraint
10
10
 
11
- extend self
12
-
13
11
  BAD_PATTERNS = [
14
12
  %r{\A\Z}, # blank
15
13
  %r{\A/}, # leading slash
@@ -18,6 +16,8 @@ module Blobby
18
16
  %r{:} # colon
19
17
  ].freeze
20
18
 
19
+ module_function
20
+
21
21
  def allows?(key)
22
22
  BAD_PATTERNS.none? { |pattern| pattern =~ key } &&
23
23
  URI.parse(key).path == key
@@ -15,9 +15,10 @@ module Blobby
15
15
  end
16
16
 
17
17
  def [](key)
18
- StoredObject.new(store[key],
19
- :on_write => -> { logger.info(%(wrote to #{key.inspect} in #{store_name})) },
20
- :on_delete => -> { logger.info(%(deleted #{key.inspect} from #{store_name})) }
18
+ StoredObject.new(
19
+ store[key],
20
+ :on_write => -> { logger.info(%(wrote to #{key.inspect} in #{store_name})) },
21
+ :on_delete => -> { logger.info(%(deleted #{key.inspect} from #{store_name})) }
21
22
  )
22
23
  end
23
24
 
@@ -1,3 +1,3 @@
1
1
  module Blobby
2
- VERSION = "1.0.1"
2
+ VERSION = "1.1.0"
3
3
  end
@@ -1,3 +1,5 @@
1
+ require "spec_helper"
2
+
1
3
  require "blobby/composite_store"
2
4
  require "blobby/in_memory_store"
3
5
  require "blobby/store_behaviour"
@@ -1,5 +1,7 @@
1
1
  # encoding: UTF-8
2
2
 
3
+ require "spec_helper"
4
+
3
5
  require "blobby/fake_success_store"
4
6
  require "blobby/store_behaviour"
5
7
 
@@ -1,4 +1,4 @@
1
- # encoding: utf-8
1
+ require "spec_helper"
2
2
 
3
3
  require "blobby/filesystem_store"
4
4
  require "blobby/store_behaviour"
@@ -38,11 +38,15 @@ describe Blobby::FilesystemStore do
38
38
  describe "#write" do
39
39
 
40
40
  it "writes to the file-system" do
41
- expect { subject[key].write(content) }.to change { expected_file_path.exist? }.from(false).to(true)
41
+ expect do
42
+ subject[key].write(content)
43
+ end.to change { expected_file_path.exist? }.from(false).to(true)
42
44
  end
43
45
 
44
46
  it "should have correct contents" do
45
- expect { subject[key].write(content) }.to change { File.read(expected_file_path) rescue nil }.from(nil).to(content)
47
+ expect do
48
+ subject[key].write(content)
49
+ end.to change { File.read(expected_file_path) rescue nil }.from(nil).to(content)
46
50
  end
47
51
 
48
52
  it "retries if renaming throws an ESTALE" do
@@ -1,3 +1,5 @@
1
+ require "spec_helper"
2
+
1
3
  require "ostruct"
2
4
  require "blobby/http_store"
3
5
  require "blobby/in_memory_store"
@@ -41,9 +43,7 @@ describe Blobby::HttpStore do
41
43
 
42
44
  def key
43
45
  params[:captures].first.tap do |key|
44
- if key =~ /FAIL/ # simulate failure
45
- fail "hell"
46
- end
46
+ fail "hell" if key =~ /FAIL/ # simulate failure
47
47
  end
48
48
  end
49
49
 
@@ -102,7 +102,7 @@ describe Blobby::HttpStore do
102
102
  it "raises an exception" do
103
103
  expect do
104
104
  subject[key].read
105
- end.to raise_error
105
+ end.to raise_error("hell")
106
106
  end
107
107
 
108
108
  end
@@ -112,7 +112,7 @@ describe Blobby::HttpStore do
112
112
  it "raises an exception" do
113
113
  expect do
114
114
  subject[key].write("something")
115
- end.to raise_error
115
+ end.to raise_error("hell")
116
116
  end
117
117
 
118
118
  end
@@ -179,7 +179,7 @@ describe Blobby::HttpStore do
179
179
 
180
180
  end
181
181
 
182
- context "when the base_url does not include a trailing slash" do
182
+ context "when the base_uri does not include a trailing slash" do
183
183
 
184
184
  subject do
185
185
  described_class.new("http://#{http_storage_host}/prefix")
@@ -191,7 +191,7 @@ describe Blobby::HttpStore do
191
191
 
192
192
  end
193
193
 
194
- context "when the base_url does include a trailing slash" do
194
+ context "when the base_uri does include a trailing slash" do
195
195
 
196
196
  subject do
197
197
  described_class.new("http://#{http_storage_host}/prefix/")
@@ -203,4 +203,9 @@ describe Blobby::HttpStore do
203
203
 
204
204
  end
205
205
 
206
+ it "can be created with a URI" do
207
+ store = described_class.new(URI("http://#{http_storage_host}/prefix/"))
208
+ expect(store.base_uri.to_s).to eq("http://#{http_storage_host}/prefix/")
209
+ end
210
+
206
211
  end
@@ -1,3 +1,5 @@
1
+ require "spec_helper"
2
+
1
3
  require "blobby/in_memory_store"
2
4
  require "blobby/store_behaviour"
3
5
 
@@ -1,3 +1,5 @@
1
+ require "spec_helper"
2
+
1
3
  require "blobby/filesystem_store"
2
4
  require "blobby/key_transforming_store"
3
5
  require "blobby/store_behaviour"
@@ -1,7 +1,9 @@
1
- require "logger"
1
+ require "spec_helper"
2
+
2
3
  require "blobby/in_memory_store"
3
4
  require "blobby/logging_store"
4
5
  require "blobby/store_behaviour"
6
+ require "logger"
5
7
  require "stringio"
6
8
 
7
9
  describe Blobby::LoggingStore do
@@ -1,5 +1,7 @@
1
1
  # encoding: UTF-8
2
2
 
3
+ require "spec_helper"
4
+
3
5
  require "rspec"
4
6
 
5
7
  module Blobby
@@ -98,7 +100,7 @@ shared_examples_for Blobby::Store do
98
100
 
99
101
  context "for UTF-8 content" do
100
102
 
101
- let(:content) { "SN☃WMAN" }
103
+ let(:content) { "SN☃WMAN".freeze }
102
104
 
103
105
  before do
104
106
  stored_object.write(content)
@@ -107,7 +109,9 @@ shared_examples_for Blobby::Store do
107
109
  describe "#read" do
108
110
 
109
111
  it "returns binary data" do
110
- expect(stored_object.read.encoding.name).to eq("ASCII-8BIT")
112
+ stored_content = stored_object.read
113
+ expect(stored_content.encoding.name).to eq("ASCII-8BIT")
114
+ expect(stored_content.force_encoding("UTF-8")).to eq(content)
111
115
  end
112
116
 
113
117
  end
@@ -0,0 +1,63 @@
1
+ require "spec_helper"
2
+
3
+ require "blobby"
4
+
5
+ describe Blobby do
6
+
7
+ describe ".store" do
8
+
9
+ context "with an absolute file path" do
10
+
11
+ it "creates a FilesystemStore" do
12
+ store = Blobby.store("/data")
13
+ expect(store).to be_a(Blobby::FilesystemStore)
14
+ expect(store.dir.to_s).to eq("/data")
15
+ end
16
+
17
+ end
18
+
19
+ context "with a file:// URI" do
20
+
21
+ it "creates a FilesystemStore" do
22
+ store = Blobby.store("file:///data")
23
+ expect(store).to be_a(Blobby::FilesystemStore)
24
+ expect(store.dir.to_s).to eq("/data")
25
+ end
26
+
27
+ end
28
+
29
+ context "with an http:// URI" do
30
+
31
+ it "creates a HttpStore" do
32
+ store = Blobby.store("http://storage.com/data/")
33
+ expect(store).to be_a(Blobby::HttpStore)
34
+ expect(store.base_uri.host).to eq("storage.com")
35
+ expect(store.base_uri.path).to eq("/data/")
36
+ end
37
+
38
+ end
39
+
40
+ context "with an https:// URI" do
41
+
42
+ it "creates a HttpStore" do
43
+ store = Blobby.store("https://storage.com/data/")
44
+ expect(store).to be_a(Blobby::HttpStore)
45
+ expect(store.base_uri.scheme).to eq("https")
46
+ expect(store.base_uri.host).to eq("storage.com")
47
+ expect(store.base_uri.path).to eq("/data/")
48
+ end
49
+
50
+ end
51
+
52
+ context "with 'in-memory:/'" do
53
+
54
+ it "creates an InMemoryStore" do
55
+ store = Blobby.store("in-memory:/")
56
+ expect(store).to be_a(Blobby::InMemoryStore)
57
+ end
58
+
59
+ end
60
+
61
+ end
62
+
63
+ end
@@ -0,0 +1 @@
1
+ ENV["RACK_ENV"] = "test"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: blobby
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.1
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Williams
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-06-18 00:00:00.000000000 Z
11
+ date: 2016-05-22 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description:
14
14
  email:
@@ -21,10 +21,13 @@ files:
21
21
  - ".rspec"
22
22
  - ".rubocop.yml"
23
23
  - ".travis.yml"
24
+ - CHANGES.md
24
25
  - Gemfile
25
26
  - README.md
26
27
  - Rakefile
27
28
  - blobby.gemspec
29
+ - lib/blobby.rb
30
+ - lib/blobby/abstract_store.rb
28
31
  - lib/blobby/composite_store.rb
29
32
  - lib/blobby/fake_success_store.rb
30
33
  - lib/blobby/filesystem_store.rb
@@ -43,6 +46,8 @@ files:
43
46
  - spec/blobby/key_transforming_store_spec.rb
44
47
  - spec/blobby/logging_store_spec.rb
45
48
  - spec/blobby/store_behaviour.rb
49
+ - spec/blobby_spec.rb
50
+ - spec/spec_helper.rb
46
51
  homepage: https://github.com/realestate-com-au/blobby
47
52
  licenses: []
48
53
  metadata: {}
@@ -62,7 +67,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
62
67
  version: '0'
63
68
  requirements: []
64
69
  rubyforge_project:
65
- rubygems_version: 2.4.5
70
+ rubygems_version: 2.5.1
66
71
  signing_key:
67
72
  specification_version: 4
68
73
  summary: Various ways of storing BLOBs
@@ -75,3 +80,5 @@ test_files:
75
80
  - spec/blobby/key_transforming_store_spec.rb
76
81
  - spec/blobby/logging_store_spec.rb
77
82
  - spec/blobby/store_behaviour.rb
83
+ - spec/blobby_spec.rb
84
+ - spec/spec_helper.rb