fragmenter 0.5.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.
@@ -0,0 +1,9 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ Gemfile.lock
6
+ doc/
7
+ bin/
8
+ vendor/ruby
9
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
@@ -0,0 +1,3 @@
1
+ # 0.5.0
2
+
3
+ * Initial release!
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Dscout, Inc
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,51 @@
1
+ # Fragmenter
2
+
3
+ Fragmenter stores and rebuilds binary data in a distributed fashion. The only
4
+ engine currently provided is Redis.
5
+
6
+ ## Why Fragments?
7
+
8
+ It alleviates the problems posed by uploading large blocks of data from slow
9
+ clients, notably mobile apps, by allowing the device to send multiple smaller
10
+ blocks of data independently. Once all of the smaller blocks have been received
11
+ they can quickly be rebuilt into the original file on the server.
12
+
13
+ ## Requirements
14
+
15
+ Fragmenter is tested on Ruby 1.9.3, but any ruby implementation with 1.9 syntax
16
+ should be supported.
17
+
18
+ Redis 2.0 or greater is required and version 2.6 is recommended.
19
+
20
+ ## Installation
21
+
22
+ $ gem install fragmenter
23
+
24
+ ## Getting Started
25
+
26
+ ### Configuration
27
+
28
+ Fragmenter.configure do |config|
29
+ config.redis = $redis
30
+ config.logger = Rails.logger
31
+ config.expiration = 2.days.to_i
32
+ end
33
+
34
+ ### Usage
35
+
36
+ fragmenter = Fragmenter::Base.new(record)
37
+
38
+ fragmenter.store(binary_data, number: 1, total: 12, content_type: 'image/jpeg')
39
+ fragmenter.complete? # => false
40
+
41
+ fragmenter.store(binary_data, number: 12, total: 12, content_type: 'image/jpeg')
42
+ fragmenter.complete? # => true
43
+
44
+ rebuilt = fragmenter.rebuild # => binary data
45
+ fragmenter.clean!
46
+
47
+ More detailed examples will be added soon.
48
+
49
+ ## License
50
+
51
+ Please see LICENSE for licensing details.
@@ -0,0 +1,7 @@
1
+ require 'bundler/setup'
2
+ require 'bundler/gem_tasks'
3
+ require 'rspec/core/rake_task'
4
+
5
+ RSpec::Core::RakeTask.new :spec
6
+
7
+ task :default => :spec
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'fragmenter/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = 'fragmenter'
8
+ gem.version = Fragmenter::VERSION
9
+ gem.authors = ['Parker Selbert']
10
+ gem.email = ['parker@sorentwo.com']
11
+ gem.description = %q{Fragmentize and rebuild data}
12
+ gem.summary = %q{Fragmentize and rebuild data}
13
+
14
+ gem.files = `git ls-files`.split($/)
15
+ gem.test_files = gem.files.grep(%r{^(spec)/})
16
+ gem.require_paths = ['lib']
17
+
18
+ gem.add_dependency 'redis', '~> 3.0.0'
19
+
20
+ gem.add_development_dependency 'rspec', '~> 2.11.0'
21
+ end
@@ -0,0 +1,37 @@
1
+ require 'logger'
2
+ require 'redis'
3
+ require 'fragmenter/version'
4
+ require 'fragmenter/base'
5
+ require 'fragmenter/redis'
6
+
7
+ module Fragmenter
8
+ def self.configure(&block)
9
+ yield self
10
+ end
11
+
12
+ def self.logger
13
+ @logger ||= Logger.new(STDOUT).tap do |logger|
14
+ logger.level = Logger::INFO
15
+ end
16
+ end
17
+
18
+ def self.logger=(logger)
19
+ @logger = logger
20
+ end
21
+
22
+ def self.redis
23
+ @redis ||= ::Redis.new
24
+ end
25
+
26
+ def self.redis=(redis)
27
+ @redis = redis
28
+ end
29
+
30
+ def self.expiration=(expiration)
31
+ @expiration = expiration
32
+ end
33
+
34
+ def self.expiration
35
+ @expiration || 60 * 60 * 24
36
+ end
37
+ end
@@ -0,0 +1,27 @@
1
+ module Fragmenter
2
+ class Base
3
+ extend Forwardable
4
+
5
+ attr_reader :object, :engine
6
+
7
+ delegate clean!: :engine
8
+ delegate complete?: :engine
9
+ delegate fragments: :engine
10
+ delegate meta: :engine
11
+ delegate rebuild: :engine
12
+ delegate store: :engine
13
+
14
+ def initialize(object, engine_class = Fragmenter::Redis)
15
+ @object = object
16
+ @engine = engine_class.new(self)
17
+ end
18
+
19
+ def key
20
+ [object.class.to_s.downcase, object.id].join('-')
21
+ end
22
+
23
+ def as_json
24
+ engine.meta.merge('fragments' => engine.fragments)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,50 @@
1
+ module Fragmenter
2
+ class Fragment
3
+ attr_reader :blob, :options
4
+
5
+ def initialize(blob, options)
6
+ @blob = blob
7
+ @options = options
8
+ end
9
+
10
+ def number
11
+ @number ||= options.fetch(:number, 1).to_i
12
+ end
13
+
14
+ def total
15
+ @total ||= options.fetch(:total, 1).to_i
16
+ end
17
+
18
+ def content_type
19
+ @content_type ||= options[:content_type] || 'application/octet-stream'
20
+ end
21
+
22
+ def padded_number
23
+ digits = total.to_s.length
24
+
25
+ "%0#{digits}d" % number.to_s
26
+ end
27
+
28
+ def valid?
29
+ valid_blob? && valid_number? && valid_total? && valid_content_type?
30
+ end
31
+
32
+ private
33
+
34
+ def valid_blob?
35
+ blob.size > 0
36
+ end
37
+
38
+ def valid_number?
39
+ number.kind_of?(Integer) && number > 0
40
+ end
41
+
42
+ def valid_total?
43
+ total.kind_of?(Integer) && total > 0 && total >= number
44
+ end
45
+
46
+ def valid_content_type?
47
+ content_type =~ /\w+\/\w+/
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,102 @@
1
+ require 'fragmenter/fragment'
2
+
3
+ module Fragmenter
4
+ class Redis
5
+ extend Forwardable
6
+
7
+ delegate expiration: Fragmenter
8
+ delegate logger: Fragmenter
9
+ delegate redis: Fragmenter
10
+
11
+ attr_reader :fragmenter
12
+
13
+ def initialize(fragmenter)
14
+ @fragmenter = fragmenter
15
+ end
16
+
17
+ def store_key
18
+ fragmenter.key
19
+ end
20
+
21
+ def meta_key
22
+ [store_key, 'options'].join('-')
23
+ end
24
+
25
+ def store(blob, options)
26
+ fragment = Fragmenter::Fragment.new(blob, options)
27
+
28
+ if fragment.valid?
29
+ persist_fragment(fragment)
30
+ else
31
+ false
32
+ end
33
+ end
34
+
35
+ def meta
36
+ redis.hgetall meta_key
37
+ end
38
+
39
+ def fragments
40
+ redis.hkeys(store_key).sort
41
+ end
42
+
43
+ def complete?
44
+ redis.hlen(store_key).to_s == redis.hget(meta_key, :total)
45
+ end
46
+
47
+ def rebuild
48
+ benchmark_rebuilding do
49
+ redis.hmget(store_key, *fragments).join('')
50
+ end
51
+ rescue ::Redis::CommandError
52
+ log 'Failure rebuilding, most likely there are no fragments to rebuild'
53
+
54
+ ''
55
+ end
56
+
57
+ def clean!
58
+ redis.del store_key, meta_key
59
+ end
60
+
61
+ private
62
+
63
+ def log(message)
64
+ logger.info "Fragmenter: #{message}"
65
+ end
66
+
67
+ def persist_fragment(fragment)
68
+ benchmark_persistence(fragment) do
69
+ redis.multi do
70
+ redis.hset store_key, fragment.padded_number, fragment.blob
71
+ redis.hset meta_key, :content_type, fragment.content_type
72
+ redis.hset meta_key, :total, fragment.total
73
+
74
+ redis.expire store_key, expiration
75
+ redis.expire meta_key, expiration
76
+ end
77
+ end
78
+ end
79
+
80
+ def benchmark_persistence(fragment, &block)
81
+ log %(Storing #{fragment.number}/#{fragment.total}...)
82
+ start_time = Time.now
83
+
84
+ yield
85
+
86
+ end_time = Time.now
87
+ log %(Stored (#{end_time - start_time}) #{fragment.number}/#{fragment.total} #{fragment.blob.size} bytes)
88
+ end
89
+
90
+ def benchmark_rebuilding(&block)
91
+ log %(Rebuilding #{fragments.length} fragments...)
92
+ start_time = Time.now
93
+
94
+ rebuilt = yield
95
+
96
+ end_time = Time.now
97
+ log %(Rebuilt (#{end_time - start_time}) #{fragments.length} fragments #{rebuilt.size} bytes)
98
+
99
+ rebuilt
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,3 @@
1
+ module Fragmenter
2
+ VERSION = '0.5.0'
3
+ end
@@ -0,0 +1,43 @@
1
+ require 'fragmenter/base'
2
+
3
+ describe Fragmenter::Base do
4
+ let(:object) { mock('object', id: 1001) }
5
+ let(:engine_class) { mock('engine_class', new: engine) }
6
+ let(:engine) { mock('engine') }
7
+
8
+ subject { described_class.new(object, engine_class) }
9
+
10
+ describe '#key' do
11
+ it 'composes a key from the object class and id value' do
12
+ subject.key.should match(/[a-z]+-\d+/)
13
+ end
14
+ end
15
+
16
+ describe 'engine delegation' do
17
+ let(:blob) { '0101' }
18
+ let(:headers) { {} }
19
+
20
+ it 'delegates #store to the storage engine' do
21
+ engine.should_receive(:store).with(blob, headers)
22
+
23
+ subject.store(blob, headers)
24
+ end
25
+
26
+ it 'delegates #fragments to the storage engine' do
27
+ engine.should_receive(:fragments)
28
+ subject.fragments
29
+ end
30
+ end
31
+
32
+ describe '#as_json' do
33
+ it 'merges the stored meta and fragments' do
34
+ engine.stub('meta' => { 'content_type' => 'application/octet-stream' },
35
+ 'fragments' => ['1', '2'])
36
+
37
+ subject.as_json.tap do |json|
38
+ json.should have_key('content_type')
39
+ json.should have_key('fragments')
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,58 @@
1
+ require 'fragmenter/fragment'
2
+
3
+ describe Fragmenter::Fragment do
4
+ let(:blob) { '1010101' }
5
+
6
+ describe '#number' do
7
+ it 'defaults the number to 1' do
8
+ described_class.new(blob, {}).number.should == 1
9
+ end
10
+ end
11
+
12
+ describe '#total' do
13
+ it 'defaults the total to 1' do
14
+ described_class.new(blob, {}).total.should == 1
15
+ end
16
+ end
17
+
18
+ describe '#content_type' do
19
+ it 'defaults the content_type to a binary format' do
20
+ described_class.new(blob, {}).content_type.should == 'application/octet-stream'
21
+ end
22
+ end
23
+
24
+ describe '#padded_number' do
25
+ it 'zero pads the number with as many zeros as the total has places' do
26
+ described_class.new(blob, number: 1, total: 2000).padded_number.should == '0001'
27
+ end
28
+ end
29
+
30
+ describe '#valid?' do
31
+ it 'is valid with a complete blob and sensible options' do
32
+ described_class.new(blob, number: 1, total: 2).should be_valid
33
+ end
34
+
35
+ it 'is not valid with an empty blob' do
36
+ described_class.new('', number: 1, total: 2).should_not be_valid
37
+ end
38
+
39
+ it 'is not valid without an integer part number greater than 1' do
40
+ described_class.new(blob, number: -1, total: 2).should_not be_valid
41
+ described_class.new(blob, number: 'one', total: 2).should_not be_valid
42
+ end
43
+
44
+ it 'is not valid without an integer part total' do
45
+ described_class.new(blob, number: 1, total: -2).should_not be_valid
46
+ described_class.new(blob, number: 1, total: 'two').should_not be_valid
47
+ end
48
+
49
+ it 'is not valid when the number is greater the total' do
50
+ described_class.new(blob, number: 2, total: 1).should_not be_valid
51
+ described_class.new(blob, number: 2, total: 2).should be_valid
52
+ end
53
+
54
+ it 'is not valid without a content type resembling a mime type' do
55
+ described_class.new(blob, content_type: 'jpg').should_not be_valid
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,153 @@
1
+ require 'fragmenter'
2
+ require 'fragmenter/redis'
3
+
4
+ describe Fragmenter::Redis do
5
+ let(:blob_1) { '00010110' }
6
+ let(:blob_2) { '11101110' }
7
+ let(:fragmenter) { mock(:fragmenter, key: 'abcdefg') }
8
+ let(:redis) { Fragmenter.redis }
9
+
10
+ subject(:engine) { described_class.new(fragmenter) }
11
+
12
+ before do
13
+ Fragmenter.logger = Logger.new('/dev/null')
14
+ end
15
+
16
+ after do
17
+ redis.del engine.store_key, engine.meta_key
18
+ end
19
+
20
+ describe '#store_key' do
21
+ it 'delegates store key to the fragmenter key' do
22
+ engine.store_key.should == fragmenter.key
23
+ end
24
+ end
25
+
26
+ describe '#meta_key' do
27
+ it 'combines the base store key with options' do
28
+ engine.meta_key.should include(fragmenter.key)
29
+ engine.meta_key.should include('options')
30
+ end
31
+ end
32
+
33
+ describe '#store' do
34
+ it 'does not store empty blobs' do
35
+ engine.store('', number: 1, total: 2).should be_false
36
+ end
37
+
38
+ it 'writes the provided blob to the fragmenter key and the provided number' do
39
+ engine.store(blob_1, number: 1, total: 48)
40
+
41
+ redis.hget(fragmenter.key, '01').should == blob_1
42
+ end
43
+
44
+ it 'overwrites existing data at the key + number location' do
45
+ engine.store(blob_1, number: 1, total: 48)
46
+ engine.store(blob_2, number: 1, total: 48)
47
+
48
+ redis.hget(fragmenter.key, '01').should == blob_2
49
+ end
50
+
51
+ it 'sets the fragment to expire' do
52
+ engine.store(blob_1, number: 1, total: 48)
53
+
54
+ redis.ttl(engine.store_key).should == Fragmenter.expiration
55
+ end
56
+
57
+ it 'stores meta-data in a spearate key' do
58
+ subject.store(blob_1, number: 1, total: 48)
59
+
60
+ redis.exists(engine.meta_key).should be_true
61
+ end
62
+
63
+ it 'sets the meta to expire' do
64
+ engine.store(blob_1, number: 1, total: 48)
65
+
66
+ redis.ttl(engine.meta_key).should == Fragmenter.expiration
67
+ end
68
+
69
+ it 'defaults the stored content-type to application/octet-stream' do
70
+ subject.store(blob_1, number: 1, total: 48)
71
+
72
+ redis.hget(engine.meta_key, :content_type).should == 'application/octet-stream'
73
+ end
74
+ end
75
+
76
+ describe '#meta' do
77
+ it 'returns an empty hash when nothing has been stored' do
78
+ engine.meta.should == {}
79
+ end
80
+
81
+ it 'returns the accumulated metadata when data has been stored' do
82
+ engine.store(blob_1, content_type: 'image/jpeg', number: 1, total: 2)
83
+ engine.meta.should eq(
84
+ 'content_type' => 'image/jpeg',
85
+ 'total' => '2'
86
+ )
87
+ end
88
+ end
89
+
90
+ describe '#fragments' do
91
+ context 'without any fragments' do
92
+ it 'returns an empty array' do
93
+ engine.fragments.should == []
94
+ end
95
+ end
96
+
97
+ context 'when fragments have been stored' do
98
+ before do
99
+ engine.store(blob_2, number: 3, total: 30)
100
+ engine.store(blob_1, number: 1, total: 30)
101
+ end
102
+
103
+ it 'returns an array of the stored fragment indecies' do
104
+ engine.fragments.should == ['01', '03']
105
+ end
106
+ end
107
+ end
108
+
109
+ describe '#complete?' do
110
+ it 'is incomplete if the number of stored fragments does not match the total' do
111
+ engine.store(blob_1, number: 1, total: 2)
112
+
113
+ engine.should_not be_complete
114
+ end
115
+
116
+ it 'is complete if the stored fragments matches all values between 1 and the total' do
117
+ engine.store(blob_1, number: 1, total: 2)
118
+ engine.store(blob_2, number: 2, total: 2)
119
+
120
+ engine.should be_complete
121
+ end
122
+ end
123
+
124
+ describe '#rebuild' do
125
+ before do
126
+ engine.store(blob_1, number: 1, total: 2)
127
+ engine.store(blob_2, number: 2, total: 2)
128
+ end
129
+
130
+ it 'returns the aggregated values from all stored fragments' do
131
+ engine.rebuild.should == [blob_1, blob_2].join('')
132
+ end
133
+
134
+ it 'returns nothing when no fragments are present' do
135
+ redis.del engine.store_key
136
+
137
+ engine.rebuild.should == ''
138
+ end
139
+ end
140
+
141
+ describe '#clean!' do
142
+ before do
143
+ engine.store(blob_1, number: 1, total: 2)
144
+ end
145
+
146
+ it 'deletes the storage and meta data' do
147
+ engine.clean!
148
+
149
+ redis.exists(engine.meta_key).should be_false
150
+ redis.exists(engine.store_key).should be_false
151
+ end
152
+ end
153
+ end
@@ -0,0 +1,68 @@
1
+ require 'fragmenter'
2
+
3
+ describe Fragmenter do
4
+ after do
5
+ Fragmenter.redis = nil
6
+ Fragmenter.logger = nil
7
+ Fragmenter.expiration = nil
8
+ end
9
+
10
+ describe '.logger' do
11
+ it 'attempts to instantiate a standard logger to STDOUT' do
12
+ Fragmenter.logger.should be_instance_of(Logger)
13
+ Fragmenter.logger.level.should == Logger::INFO
14
+ end
15
+ end
16
+
17
+ describe '.logger=' do
18
+ it 'stores the logger instance on the module' do
19
+ logger = mock(:logger)
20
+
21
+ Fragmenter.logger = logger
22
+ Fragmenter.logger.should be(logger)
23
+ end
24
+ end
25
+
26
+ describe '.redis' do
27
+ it 'attempts to create a redis connection with default values' do
28
+ Fragmenter.redis.should be_instance_of(Redis)
29
+ end
30
+ end
31
+
32
+ describe '.redis=' do
33
+ it 'stores the redis instance on the module' do
34
+ redis = mock(:redis)
35
+
36
+ Fragmenter.redis = redis
37
+ Fragmenter.redis.should be(redis)
38
+ end
39
+ end
40
+
41
+ describe '.expiration' do
42
+ it 'defaults expiration to one day' do
43
+ Fragmenter.expiration.should == 86400
44
+ end
45
+ end
46
+
47
+ describe '.expiration=' do
48
+ it 'stoires the expiration value on the module' do
49
+ Fragmenter.expiration = 10000
50
+ Fragmenter.expiration.should eq(10000)
51
+ end
52
+ end
53
+
54
+ describe '.configure' do
55
+ let(:redis) { mock(:redis) }
56
+ let(:logger) { mock(:logger) }
57
+
58
+ it 'allows customization via passing a block' do
59
+ Fragmenter.configure do |config|
60
+ config.redis = redis
61
+ config.logger = logger
62
+ end
63
+
64
+ Fragmenter.redis.should == redis
65
+ Fragmenter.logger.should == logger
66
+ end
67
+ end
68
+ end
File without changes
metadata ADDED
@@ -0,0 +1,105 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fragmenter
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.5.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Parker Selbert
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-10-29 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: redis
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 3.0.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 3.0.0
30
+ - !ruby/object:Gem::Dependency
31
+ name: rspec
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: 2.11.0
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: 2.11.0
46
+ description: Fragmentize and rebuild data
47
+ email:
48
+ - parker@sorentwo.com
49
+ executables: []
50
+ extensions: []
51
+ extra_rdoc_files: []
52
+ files:
53
+ - .gitignore
54
+ - Gemfile
55
+ - HISTORY.md
56
+ - LICENSE.txt
57
+ - README.md
58
+ - Rakefile
59
+ - fragmenter.gemspec
60
+ - lib/fragmenter.rb
61
+ - lib/fragmenter/base.rb
62
+ - lib/fragmenter/fragment.rb
63
+ - lib/fragmenter/redis.rb
64
+ - lib/fragmenter/version.rb
65
+ - spec/fragmenter/base_spec.rb
66
+ - spec/fragmenter/fragment_spec.rb
67
+ - spec/fragmenter/redis_spec.rb
68
+ - spec/fragmenter_spec.rb
69
+ - spec/spec_helper.rb
70
+ homepage:
71
+ licenses: []
72
+ post_install_message:
73
+ rdoc_options: []
74
+ require_paths:
75
+ - lib
76
+ required_ruby_version: !ruby/object:Gem::Requirement
77
+ none: false
78
+ requirements:
79
+ - - ! '>='
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ segments:
83
+ - 0
84
+ hash: -818571163033621587
85
+ required_rubygems_version: !ruby/object:Gem::Requirement
86
+ none: false
87
+ requirements:
88
+ - - ! '>='
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ segments:
92
+ - 0
93
+ hash: -818571163033621587
94
+ requirements: []
95
+ rubyforge_project:
96
+ rubygems_version: 1.8.23
97
+ signing_key:
98
+ specification_version: 3
99
+ summary: Fragmentize and rebuild data
100
+ test_files:
101
+ - spec/fragmenter/base_spec.rb
102
+ - spec/fragmenter/fragment_spec.rb
103
+ - spec/fragmenter/redis_spec.rb
104
+ - spec/fragmenter_spec.rb
105
+ - spec/spec_helper.rb