obstore 0.0.1
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 +7 -0
- data/lib/obstore/data.rb +92 -0
- data/lib/obstore/filestore.rb +91 -0
- data/lib/obstore/lockable.rb +21 -0
- data/lib/obstore/version.rb +9 -0
- data/lib/obstore.rb +28 -0
- data/spec/lib/data_spec.rb +101 -0
- data/spec/lib/filestore_spec.rb +103 -0
- data/spec/lib/version_spec.rb +7 -0
- data/spec/spec_helper.rb +16 -0
- metadata +72 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 189bceb51e975e4cee66730f5eda40d6d2e5f70a
|
4
|
+
data.tar.gz: 6064191922ce26a55d7b2cb386e37ee066e31ac2
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2bfaf6545bec341b3d79bf94592dd94ad275e1d0e2e53e533a827c7bff55fe14f4db796f0040e4b0ac636744ec73f10002d8c82b6f09a5807322bee586cf8787
|
7
|
+
data.tar.gz: ecf26c593a83171c30090ab75c8c846942b87b6181083bbcaf3decec610b272bf80b27cf1657ef8aa53b0306034067a8f53a4c14dfabc2387b80b034aebdf51c
|
data/lib/obstore/data.rb
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
# This provides the data object that obstore uses to save your data
|
2
|
+
# and retrieve your data with metadata
|
3
|
+
#
|
4
|
+
# Author:: Michael Heijmans (mailto:parabuzzle@gmail.com)
|
5
|
+
# Copyright:: Copyright (c) 2014 Michael Heijmans
|
6
|
+
# License:: MIT
|
7
|
+
|
8
|
+
require 'yaml'
|
9
|
+
require 'obstore/lockable'
|
10
|
+
|
11
|
+
module ObStore
|
12
|
+
class Data
|
13
|
+
|
14
|
+
include ObStore::Lockable
|
15
|
+
|
16
|
+
attr_accessor :expiry
|
17
|
+
attr_reader :updated, :data
|
18
|
+
|
19
|
+
def initialize(data=nil, options={})
|
20
|
+
@expiry = options[:expiry] # in seconds
|
21
|
+
@data = {}
|
22
|
+
store_data_by_key :data, data
|
23
|
+
@updated = Time.now
|
24
|
+
if options[:metadata]
|
25
|
+
options[:metadata].each do |key, value|
|
26
|
+
store_data_by_key key, value
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
# returns the object you had saved
|
32
|
+
def fetch
|
33
|
+
fetch_data_by_key(:data)
|
34
|
+
end
|
35
|
+
|
36
|
+
# returns boolean value if data has expired
|
37
|
+
def stale?
|
38
|
+
if @expiry
|
39
|
+
if ts < Time.now.to_i - @expiry
|
40
|
+
return true
|
41
|
+
end
|
42
|
+
end
|
43
|
+
return false
|
44
|
+
end
|
45
|
+
|
46
|
+
# Saves the object
|
47
|
+
def save(data)
|
48
|
+
self.data=data
|
49
|
+
end
|
50
|
+
|
51
|
+
# custom setter for data attribute that sets the update time
|
52
|
+
def data=(data)
|
53
|
+
store_data_by_key(:data, data)
|
54
|
+
end
|
55
|
+
|
56
|
+
# custom getter for retrieving data from the data hash
|
57
|
+
def data
|
58
|
+
fetch_data_by_key(:data)
|
59
|
+
end
|
60
|
+
|
61
|
+
# helper method to return the timestamp as an int
|
62
|
+
def ts
|
63
|
+
with_mutex { @updated.to_i }
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
# method used by method_missing to store meta data
|
69
|
+
def store_data_by_key(key, value)
|
70
|
+
with_mutex {
|
71
|
+
@data.store key.to_sym, value
|
72
|
+
@updated = Time.now
|
73
|
+
}
|
74
|
+
end
|
75
|
+
|
76
|
+
# method used by method_missing to fetch meta data
|
77
|
+
def fetch_data_by_key(key)
|
78
|
+
with_mutex { @data[key.to_sym] }
|
79
|
+
end
|
80
|
+
|
81
|
+
def method_missing(meth, *args, &block)
|
82
|
+
if meth.to_s =~ /^(.+)=$/
|
83
|
+
store_data_by_key($1, *args)
|
84
|
+
elsif meth.to_s =~ /^(.+)$/
|
85
|
+
fetch_data_by_key($1)
|
86
|
+
else
|
87
|
+
super
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# Provides the wrapper for pstore
|
2
|
+
#
|
3
|
+
# Author:: Michael Heijmans (mailto:parabuzzle@gmail.com)
|
4
|
+
# Copyright:: Copyright (c) 2014 Michael Heijmans
|
5
|
+
# License:: MIT
|
6
|
+
|
7
|
+
require 'pstore'
|
8
|
+
require 'yaml'
|
9
|
+
require 'obstore/data'
|
10
|
+
|
11
|
+
module ObStore
|
12
|
+
class FileStore
|
13
|
+
|
14
|
+
attr_accessor :store
|
15
|
+
|
16
|
+
def initialize(opts={})
|
17
|
+
opts[:database] ||= "./tmp/obstore.db"
|
18
|
+
opts[:threadsafe] ||= true
|
19
|
+
opts[:atomic_writes] ||= false
|
20
|
+
@store = PStore.new(opts[:database], opts[:threadsafe])
|
21
|
+
@store.ultra_safe = opts[:atomic_writes]
|
22
|
+
end
|
23
|
+
|
24
|
+
# removes stale records from the pstore db
|
25
|
+
def compact!
|
26
|
+
keys = []
|
27
|
+
@store.transaction do
|
28
|
+
keys = @store.roots
|
29
|
+
end
|
30
|
+
keys.each do |key|
|
31
|
+
fetch_data_by_key key.to_sym # just fetching the stale items deletes them
|
32
|
+
end
|
33
|
+
return true
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
# marshals the data object to a string
|
39
|
+
def marshal(obj)
|
40
|
+
YAML.dump obj
|
41
|
+
end
|
42
|
+
|
43
|
+
# un-marshals the passed string into an object
|
44
|
+
def unmarshal(str)
|
45
|
+
YAML.load str
|
46
|
+
end
|
47
|
+
|
48
|
+
# method used by method_missing to store data
|
49
|
+
def store_data_by_key(key, args)
|
50
|
+
options = {}
|
51
|
+
if args.class == Array
|
52
|
+
value = args.shift
|
53
|
+
options = args.shift
|
54
|
+
else
|
55
|
+
value = args
|
56
|
+
end
|
57
|
+
@store.transaction do
|
58
|
+
if value.nil?
|
59
|
+
@store.delete key.to_sym
|
60
|
+
else
|
61
|
+
@store[key.to_sym] = marshal ObStore::Data.new(value, options)
|
62
|
+
end
|
63
|
+
@store.commit
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# method used by method_missing to fetch data
|
68
|
+
def fetch_data_by_key(key)
|
69
|
+
@store.transaction do
|
70
|
+
data = unmarshal(@store[key.to_sym])
|
71
|
+
if data.stale?
|
72
|
+
data = nil
|
73
|
+
@store.delete key.to_sym
|
74
|
+
@store.commit
|
75
|
+
end
|
76
|
+
return data
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def method_missing(meth, *args, &block)
|
81
|
+
if meth.to_s =~ /^(.+)=$/
|
82
|
+
store_data_by_key($1, *args)
|
83
|
+
elsif meth.to_s =~ /^(.+)$/
|
84
|
+
fetch_data_by_key($1)
|
85
|
+
else
|
86
|
+
super
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# Provides semaphore support to an object
|
2
|
+
#
|
3
|
+
# Author:: Michael Heijmans (mailto:parabuzzle@gmail.com)
|
4
|
+
# Copyright:: Copyright (c) 2014 Michael Heijmans
|
5
|
+
# License:: MIT
|
6
|
+
|
7
|
+
require 'thread'
|
8
|
+
|
9
|
+
module ObStore
|
10
|
+
module Lockable
|
11
|
+
|
12
|
+
def mutex
|
13
|
+
@mutex ||= Mutex.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def with_mutex
|
17
|
+
mutex.synchronize { yield }
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
data/lib/obstore.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# This library allows you to persist objects with meta data
|
2
|
+
# ObStore supports data expiry and is designed with thread safety
|
3
|
+
#
|
4
|
+
# Author:: Michael Heijmans (mailto:parabuzzle@gmail.com)
|
5
|
+
# Copyright:: Copyright (c) 2014 Michael Heijmans
|
6
|
+
# License:: MIT
|
7
|
+
|
8
|
+
module ObStore
|
9
|
+
|
10
|
+
# require library file that was passed
|
11
|
+
def self.require_lib(lib)
|
12
|
+
require lib
|
13
|
+
end
|
14
|
+
|
15
|
+
# iterates through the passed in array of
|
16
|
+
# library paths and requires each of them
|
17
|
+
def self.require_libs(libs)
|
18
|
+
libs.each do |lib|
|
19
|
+
self.require_lib(lib)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
$:.concat [File.expand_path('../', __FILE__),File.expand_path('../obstore', __FILE__)]
|
26
|
+
|
27
|
+
# Require all ruby files in the obstore directory
|
28
|
+
ObStore.require_libs Dir.glob(File.expand_path('../obstore', __FILE__) + '/*.rb')
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'obstore/data'
|
2
|
+
|
3
|
+
describe ObStore::Data do
|
4
|
+
before(:all) do
|
5
|
+
begin
|
6
|
+
Dir.mkdir './tmp'
|
7
|
+
rescue
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
after(:all) do
|
12
|
+
begin
|
13
|
+
File.delete('./tmp/obstore.db')
|
14
|
+
rescue
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe "#initialize(data=nil, options={})" do
|
19
|
+
it 'initalizes an object with nil data' do
|
20
|
+
expect(ObStore::Data.new.data).to eq(nil)
|
21
|
+
end
|
22
|
+
it 'lets you initialize it with a passed in object' do
|
23
|
+
expect(ObStore::Data.new(Hash.new).data.class).to eq(Hash)
|
24
|
+
end
|
25
|
+
it 'sets a nil expiry by default' do
|
26
|
+
expect(ObStore::Data.new.expiry).to eq(nil)
|
27
|
+
end
|
28
|
+
it 'lets you set expiry' do
|
29
|
+
expect(ObStore::Data.new(Hash.new, {:expiry=>300}).expiry).to eq(300)
|
30
|
+
end
|
31
|
+
it 'lets you set any extra metadata you want' do
|
32
|
+
expect(ObStore::Data.new(Hash.new, {:metadata=>{:foo=>"bar"}}).foo).to eq("bar")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "#fetch" do
|
37
|
+
it 'returns the object we are tracking' do
|
38
|
+
expect(ObStore::Data.new(Hash.new).fetch.class).to eq(Hash)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe "#save(data)" do
|
43
|
+
it 'is a wrapper that sets the data we are tracking' do
|
44
|
+
o = ObStore::Data.new(Hash.new)
|
45
|
+
expect(o.fetch.class).to eq(Hash)
|
46
|
+
o.save "test"
|
47
|
+
expect(o.fetch).to eq("test")
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe "#data=(data)" do
|
52
|
+
before(:each) do
|
53
|
+
@o = ObStore::Data.new("test")
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'sets the data we are tracking' do
|
57
|
+
expect(@o.fetch).to eq("test")
|
58
|
+
@o.data = "test2"
|
59
|
+
expect(@o.fetch).to eq("test2")
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'updates the timestamp along with the save' do
|
63
|
+
ts = @o.ts
|
64
|
+
sleep 1
|
65
|
+
@o.data = "test2"
|
66
|
+
expect(@o.ts).not_to eq(ts)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe "#stale?" do
|
71
|
+
before(:each) do
|
72
|
+
@o = ObStore::Data.new("test")
|
73
|
+
end
|
74
|
+
it 'returns false if there is no expiry set' do
|
75
|
+
expect(@o.stale?).to eq(false)
|
76
|
+
end
|
77
|
+
it 'returns false if the data is not stale' do
|
78
|
+
@o.expiry = 300
|
79
|
+
expect(@o.stale?).to eq(false)
|
80
|
+
end
|
81
|
+
it 'return true if the data is stale' do
|
82
|
+
@o.expiry = 1
|
83
|
+
sleep 2
|
84
|
+
expect(@o.stale?).to eq(true)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
describe "#data" do
|
89
|
+
it 'returns the object we are tracking' do
|
90
|
+
expect(ObStore::Data.new("test").data).to eq("test")
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
describe "#ts" do
|
95
|
+
it 'returns the int version of the timestamp' do
|
96
|
+
expect(ObStore::Data.new("test").ts.class).to eq(Fixnum)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
|
101
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
require 'obstore/filestore'
|
2
|
+
|
3
|
+
describe ObStore::FileStore do
|
4
|
+
before(:all) do
|
5
|
+
begin
|
6
|
+
Dir.mkdir './tmp'
|
7
|
+
rescue
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
after(:all) do
|
12
|
+
begin
|
13
|
+
File.delete('./tmp/obstore.db')
|
14
|
+
rescue
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe "#initialize(options)" do
|
19
|
+
it 'initializes a storage object' do
|
20
|
+
s = ObStore::FileStore.new
|
21
|
+
expect(s.store.path).to eq("./tmp/obstore.db")
|
22
|
+
expect(s.store.ultra_safe).to eq(false)
|
23
|
+
end
|
24
|
+
it 'sets pstore to be ultra_safe when setting atomic_writes' do
|
25
|
+
s = ObStore::FileStore.new(:atomic_writes=>true)
|
26
|
+
expect(s.store.ultra_safe).to eq(true)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "#data=(foo)" do
|
31
|
+
before(:each) do
|
32
|
+
@s = ObStore::FileStore.new
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'allows you to set values for keys using the dot operator' do
|
36
|
+
@s.data = "foo"
|
37
|
+
expect(@s.data.fetch).to eq("foo")
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'allows you to set expiry for the value' do
|
41
|
+
@s.data = Hash.new, {:expiry=>300}
|
42
|
+
expect(@s.data.expiry).to eq(300)
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'allows you to pass a metadata hash' do
|
46
|
+
@s.data = {:foo=>"bar"}, {:metadata=>{:baz=>"foo"}}
|
47
|
+
expect(@s.data.baz).to eq("foo")
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'allows you to set expiry and metadata' do
|
51
|
+
@s.data = {:foo=>"bar"}, {:expiry=>600, :metadata=>{:baz=>"foo"}}
|
52
|
+
expect(@s.data.expiry).to eq(600)
|
53
|
+
expect(@s.data.baz).to eq("foo")
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'returns nil if the data has expired' do
|
57
|
+
@s.data = {:foo=>"bar"}, {:expiry=>-3}
|
58
|
+
expect(@s.data).to eq(nil)
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'removes the data from the file if the data is stale' do
|
62
|
+
@s.data = {:foo=>"bar"}, {:expiry=>-3}
|
63
|
+
expect(@s.data).to eq(nil)
|
64
|
+
@s.store.transaction do
|
65
|
+
expect(@s.store.root?(:data)).to eq(false)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'removes the object from the store when you nil it out' do
|
70
|
+
@s.data = "foo"
|
71
|
+
expect(@s.data.fetch).to eq("foo")
|
72
|
+
@s.data = nil
|
73
|
+
@s.store.transaction do
|
74
|
+
expect(@s.store.root?(:data)).to eq(false)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
describe "#compact!" do
|
80
|
+
before(:each) do
|
81
|
+
@s = ObStore::FileStore.new
|
82
|
+
@s.data = {:foo=>"bar"}, {:expiry=>-3}
|
83
|
+
@s.more_data = {:foo=>"bar"}, {:expiry=>-3}
|
84
|
+
@s.keep = {:foo=>"bar"}, {:expiry=>300}
|
85
|
+
@s.never = {:foo=>"bar"}
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'removes all expired records from the file' do
|
89
|
+
@s.store.transaction do
|
90
|
+
expect(@s.store.roots.length).to eq(4)
|
91
|
+
end
|
92
|
+
@s.compact!
|
93
|
+
@s.store.transaction do
|
94
|
+
expect(@s.store.roots.length).to eq(2)
|
95
|
+
expect(@s.store.root?(:keep)).to eq(true)
|
96
|
+
expect(@s.store.root?(:never)).to eq(true)
|
97
|
+
expect(@s.store.root?(:data)).to eq(false)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# This file is loaded before rspec tests are run
|
2
|
+
require 'simplecov'
|
3
|
+
require 'simplecov-rcov'
|
4
|
+
require 'coveralls'
|
5
|
+
|
6
|
+
SimpleCov.start do
|
7
|
+
add_filter '/spec/'
|
8
|
+
add_group 'lib', 'lib'
|
9
|
+
#SimpleCov.minimum_coverage 75
|
10
|
+
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
|
11
|
+
SimpleCov::Formatter::HTMLFormatter,
|
12
|
+
SimpleCov::Formatter::RcovFormatter
|
13
|
+
]
|
14
|
+
end if ENV["COVERAGE"]
|
15
|
+
|
16
|
+
Coveralls.wear!
|
metadata
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: obstore
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Michael Heijmans
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-06-29 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rspec
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '3.0'
|
27
|
+
description: ObStore allows you to save any object to a file along with metadata about
|
28
|
+
that object. You can also set an expiry for an object and ObStore will lazily delete
|
29
|
+
the data for you.
|
30
|
+
email: parabuzzle@gmail.com
|
31
|
+
executables: []
|
32
|
+
extensions: []
|
33
|
+
extra_rdoc_files: []
|
34
|
+
files:
|
35
|
+
- lib/obstore.rb
|
36
|
+
- lib/obstore/data.rb
|
37
|
+
- lib/obstore/filestore.rb
|
38
|
+
- lib/obstore/lockable.rb
|
39
|
+
- lib/obstore/version.rb
|
40
|
+
- spec/lib/data_spec.rb
|
41
|
+
- spec/lib/filestore_spec.rb
|
42
|
+
- spec/lib/version_spec.rb
|
43
|
+
- spec/spec_helper.rb
|
44
|
+
homepage: https://github.com/parabuzzle/obstore
|
45
|
+
licenses:
|
46
|
+
- MIT
|
47
|
+
metadata: {}
|
48
|
+
post_install_message:
|
49
|
+
rdoc_options: []
|
50
|
+
require_paths:
|
51
|
+
- lib
|
52
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: '0'
|
57
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
requirements: []
|
63
|
+
rubyforge_project:
|
64
|
+
rubygems_version: 2.2.2
|
65
|
+
signing_key:
|
66
|
+
specification_version: 4
|
67
|
+
summary: ObStore is a smart persistent Object store.
|
68
|
+
test_files:
|
69
|
+
- spec/lib/data_spec.rb
|
70
|
+
- spec/lib/filestore_spec.rb
|
71
|
+
- spec/lib/version_spec.rb
|
72
|
+
- spec/spec_helper.rb
|