blobby 1.0.1 → 1.1.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: 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