cache_box 0.0.1.pre.preview5 → 0.0.1.pre.preview10
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 +4 -4
- data/cache_box.gemspec +21 -5
- data/lib/cache_box.rb +41 -161
- data/lib/cache_box/box.rb +17 -0
- data/lib/cache_box/chain.rb +90 -0
- data/lib/cache_box/graph.rb +87 -0
- data/lib/cache_box/helper/validate.rb +139 -0
- data/lib/cache_box/scheduler/base.rb +109 -0
- data/lib/cache_box/scheduler/concurrent.rb +91 -0
- data/lib/cache_box/scheduler/serial.rb +66 -0
- data/lib/cache_box/stash.rb +43 -0
- data/lib/cache_box/storage/file.rb +130 -0
- data/lib/cache_box/storage/memory.rb +207 -0
- data/lib/cache_box/unit.rb +199 -0
- metadata +34 -8
- data/lib/cache_box_chain.rb +0 -177
@@ -0,0 +1,130 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class CacheBox
|
4
|
+
module Storage
|
5
|
+
# A storage backed by files.
|
6
|
+
class File
|
7
|
+
LOCK = Mutex.new
|
8
|
+
|
9
|
+
# Input:
|
10
|
+
#
|
11
|
+
# namespace = Symbol | String # Default: 'namespace'
|
12
|
+
# path = String # Path; Default: nil
|
13
|
+
#
|
14
|
+
# Output: N/A
|
15
|
+
def initialize(namespace: nil, path: nil)
|
16
|
+
validate_symbol_or_string!(namespace, 'namespace')
|
17
|
+
validate_string!(path, 'path')
|
18
|
+
|
19
|
+
namespace = namespace.to_s || 'namespace'
|
20
|
+
|
21
|
+
root = path || ::File.join(Dir.pwd, '.cache')
|
22
|
+
@path = ::File.join(root, namespace)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Output: self
|
26
|
+
def reset!
|
27
|
+
LOCK.synchronize do
|
28
|
+
FileUtils.remove_entry_secure(@path, true)
|
29
|
+
end
|
30
|
+
|
31
|
+
self
|
32
|
+
end
|
33
|
+
|
34
|
+
# Reads the content.
|
35
|
+
#
|
36
|
+
# Input:
|
37
|
+
#
|
38
|
+
# name = String
|
39
|
+
#
|
40
|
+
# Output: Object # Anything
|
41
|
+
def read!(name)
|
42
|
+
validate_string!(name, 'name')
|
43
|
+
|
44
|
+
LOCK.synchronize do
|
45
|
+
file = ::File.join(@path, name)
|
46
|
+
return unless ::File.exist?(file)
|
47
|
+
|
48
|
+
Marshal.load(::File.read(file))
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Input:
|
53
|
+
#
|
54
|
+
# name = String
|
55
|
+
# data = Object # Anything
|
56
|
+
#
|
57
|
+
# Output: self
|
58
|
+
def write!(name, data)
|
59
|
+
validate_string!(name, 'name')
|
60
|
+
|
61
|
+
content = Marshal.dump(data)
|
62
|
+
file = ::File.join(@path, name)
|
63
|
+
|
64
|
+
LOCK.synchronize do
|
65
|
+
FileUtils.mkdir_p(@path)
|
66
|
+
|
67
|
+
::File.write(file, content)
|
68
|
+
end
|
69
|
+
|
70
|
+
self
|
71
|
+
end
|
72
|
+
|
73
|
+
# Input:
|
74
|
+
#
|
75
|
+
# name = String
|
76
|
+
#
|
77
|
+
# Output: self
|
78
|
+
def delete!(name)
|
79
|
+
validate_string!(name, 'name')
|
80
|
+
|
81
|
+
LOCK.synchronize do
|
82
|
+
file = ::File.join(@path, name)
|
83
|
+
FileUtils.remove_entry_secure(file, true)
|
84
|
+
end
|
85
|
+
|
86
|
+
self
|
87
|
+
end
|
88
|
+
|
89
|
+
# Input:
|
90
|
+
#
|
91
|
+
# name = String
|
92
|
+
#
|
93
|
+
# Output: true | false
|
94
|
+
def has?(name)
|
95
|
+
validate_string!(name, 'name')
|
96
|
+
|
97
|
+
LOCK.synchronize do
|
98
|
+
file = ::File.join(@path, name)
|
99
|
+
::File.file?(file)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
private
|
104
|
+
|
105
|
+
def validate_symbol_or_string!(arg, name)
|
106
|
+
return if arg.nil? || arg.is_a?(Symbol) || arg.is_a?(String)
|
107
|
+
|
108
|
+
klass = arg.class
|
109
|
+
value = arg.inspect
|
110
|
+
|
111
|
+
raise(
|
112
|
+
ArgumentError,
|
113
|
+
"#{name} must be a Symbol or String, got #{klass}: #{value}"
|
114
|
+
)
|
115
|
+
end
|
116
|
+
|
117
|
+
def validate_string!(arg, name)
|
118
|
+
return if arg.nil? || arg.is_a?(String)
|
119
|
+
|
120
|
+
klass = arg.class
|
121
|
+
value = arg.inspect
|
122
|
+
|
123
|
+
raise(
|
124
|
+
ArgumentError,
|
125
|
+
"#{name} must be a String, got #{klass}: #{value}"
|
126
|
+
)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,207 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class CacheBox
|
4
|
+
module Storage
|
5
|
+
# A storage backed by an in memory Hash.
|
6
|
+
class Memory
|
7
|
+
LOCK = Mutex.new
|
8
|
+
|
9
|
+
# Accepts a Hash or any Hash-like object as argument. Will use a plain Hash
|
10
|
+
# if none provided.
|
11
|
+
#
|
12
|
+
# Input:
|
13
|
+
#
|
14
|
+
# state = Hash{...Object => Object} # Default: nil
|
15
|
+
#
|
16
|
+
# Output: N/A
|
17
|
+
def initialize(state = nil)
|
18
|
+
validate!(state)
|
19
|
+
|
20
|
+
LOCK.synchronize do
|
21
|
+
@state = state || {}
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Accepts a Hash or any Hash-like object as argument. Will use a plain Hash
|
26
|
+
# if none provided.
|
27
|
+
#
|
28
|
+
# Input:
|
29
|
+
#
|
30
|
+
# state = Hash{...Object => Object}
|
31
|
+
#
|
32
|
+
# Output: self
|
33
|
+
def reset!(state = nil)
|
34
|
+
initialize(state)
|
35
|
+
|
36
|
+
self
|
37
|
+
end
|
38
|
+
|
39
|
+
# Input:
|
40
|
+
#
|
41
|
+
# name = String
|
42
|
+
#
|
43
|
+
# Output: Object # Anything
|
44
|
+
def read!(name)
|
45
|
+
validate_string!(name, 'name')
|
46
|
+
|
47
|
+
LOCK.synchronize do
|
48
|
+
@state[name]
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Input:
|
53
|
+
#
|
54
|
+
# name = String
|
55
|
+
# data = Object # Anything
|
56
|
+
#
|
57
|
+
# Output: self
|
58
|
+
def write!(name, data)
|
59
|
+
validate_string!(name, 'name')
|
60
|
+
|
61
|
+
LOCK.synchronize do
|
62
|
+
@state[name] = data
|
63
|
+
end
|
64
|
+
|
65
|
+
self
|
66
|
+
end
|
67
|
+
|
68
|
+
# Input:
|
69
|
+
#
|
70
|
+
# name = String
|
71
|
+
#
|
72
|
+
# Output: self
|
73
|
+
def delete!(name)
|
74
|
+
validate_string!(name, 'name')
|
75
|
+
|
76
|
+
LOCK.synchronize do
|
77
|
+
@state.delete(name)
|
78
|
+
end
|
79
|
+
|
80
|
+
self
|
81
|
+
end
|
82
|
+
|
83
|
+
# Input:
|
84
|
+
#
|
85
|
+
# name = String
|
86
|
+
#
|
87
|
+
# Output: true | false
|
88
|
+
def has?(name)
|
89
|
+
validate_string!(name, 'name')
|
90
|
+
|
91
|
+
LOCK.synchronize do
|
92
|
+
@state.key?(name)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
private
|
97
|
+
|
98
|
+
# Input:
|
99
|
+
#
|
100
|
+
# state = Object # Anything
|
101
|
+
#
|
102
|
+
# Output: N/A
|
103
|
+
def validate!(state)
|
104
|
+
return if state.nil? || state.is_a?(Hash)
|
105
|
+
|
106
|
+
validate_get!(state)
|
107
|
+
validate_set!(state)
|
108
|
+
validate_delete!(state)
|
109
|
+
validate_key!(state)
|
110
|
+
end
|
111
|
+
|
112
|
+
# Input:
|
113
|
+
#
|
114
|
+
# state = Object # Anything
|
115
|
+
#
|
116
|
+
# Output: N/A
|
117
|
+
def validate_get!(state)
|
118
|
+
unless state.respond_to?(:[])
|
119
|
+
raise(ArgumentError, 'Given state object does not respond to `:[]`')
|
120
|
+
end
|
121
|
+
|
122
|
+
arity = state.method(:[]).arity
|
123
|
+
unless arity == 1
|
124
|
+
raise(
|
125
|
+
ArgumentError,
|
126
|
+
"Given state object's `:[]` method arity must be 1, got: #{arity}"
|
127
|
+
)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# Input:
|
132
|
+
#
|
133
|
+
# state = Object # Anything
|
134
|
+
#
|
135
|
+
# Output: N/A
|
136
|
+
def validate_set!(state)
|
137
|
+
unless state.respond_to?(:[]=)
|
138
|
+
raise(ArgumentError, 'Given state object does not respond to `:[]=`')
|
139
|
+
end
|
140
|
+
|
141
|
+
arity = state.method(:[]=).arity
|
142
|
+
unless arity == 2
|
143
|
+
raise(
|
144
|
+
ArgumentError,
|
145
|
+
"Given state object's `:[]=` method arity must be 2, got: #{arity}"
|
146
|
+
)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
# Input:
|
151
|
+
#
|
152
|
+
# state = Object # Anything
|
153
|
+
#
|
154
|
+
# Output: N/A
|
155
|
+
def validate_delete!(state)
|
156
|
+
unless state.respond_to?(:delete)
|
157
|
+
raise(ArgumentError, 'Given state object does not respond to `delete`')
|
158
|
+
end
|
159
|
+
|
160
|
+
arity = state.method(:delete).arity
|
161
|
+
unless arity == 1
|
162
|
+
raise(
|
163
|
+
ArgumentError,
|
164
|
+
"Given state object's `:delete` method arity must be 1, got: #{arity}"
|
165
|
+
)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
# Input:
|
170
|
+
#
|
171
|
+
# state = Object # Anything
|
172
|
+
#
|
173
|
+
# Output: N/A
|
174
|
+
def validate_key!(state)
|
175
|
+
unless state.respond_to?(:key?)
|
176
|
+
raise(ArgumentError, 'Given state object does not respond to `:key?`')
|
177
|
+
end
|
178
|
+
|
179
|
+
arity = state.method(:key?).arity
|
180
|
+
unless arity == 1
|
181
|
+
raise(
|
182
|
+
ArgumentError,
|
183
|
+
"Given state object's `:key?` method arity must be 1, got: #{arity}"
|
184
|
+
)
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
# Input:
|
189
|
+
#
|
190
|
+
# arg = String
|
191
|
+
# name = String
|
192
|
+
#
|
193
|
+
# Output: N/A
|
194
|
+
def validate_string!(arg, name)
|
195
|
+
return if arg.nil? || arg.is_a?(String)
|
196
|
+
|
197
|
+
klass = arg.class
|
198
|
+
value = arg.inspect
|
199
|
+
|
200
|
+
raise(
|
201
|
+
ArgumentError,
|
202
|
+
"#{name} must be a String, got #{klass}: #{value}"
|
203
|
+
)
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
@@ -0,0 +1,199 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class CacheBox
|
4
|
+
class Unit
|
5
|
+
include CacheBox::Helper::Validate
|
6
|
+
|
7
|
+
LOCK = Mutex.new
|
8
|
+
|
9
|
+
# Input:
|
10
|
+
#
|
11
|
+
# namespace = String | Symbol # Default: :namespace
|
12
|
+
#
|
13
|
+
# Output: N/A
|
14
|
+
def initialize(namespace = nil, logger: nil, storage: nil)
|
15
|
+
namespace ||= :namespace
|
16
|
+
validate_string_or_symbol!(namespace, 'namespace')
|
17
|
+
|
18
|
+
@namespace_o = namespace
|
19
|
+
@namespace_s = namespace.to_s
|
20
|
+
@logger = logger || Logger.new(STDOUT, level: Logger::INFO)
|
21
|
+
@storage = storage || ::CacheBox::Storage::File.new(namespace: @namespace_o)
|
22
|
+
|
23
|
+
LOCK.synchronize do
|
24
|
+
@state = { result: ::CacheBox::Stash.new, stash: ::CacheBox::Stash.new }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Input:
|
29
|
+
#
|
30
|
+
# name = String | Symbol # Default: 'name'
|
31
|
+
# args = Object
|
32
|
+
# input = Object
|
33
|
+
# &block = Proc(CacheBox::Box)
|
34
|
+
#
|
35
|
+
# Output: Object # Anything the &block returns.
|
36
|
+
def with(name = nil, args = nil, input = nil, &block)
|
37
|
+
name ||= 'name'
|
38
|
+
validate_string_or_symbol!(name, 'name')
|
39
|
+
validate_block_presence!(block, '#with')
|
40
|
+
|
41
|
+
name_o = name
|
42
|
+
name_s = name.to_s
|
43
|
+
box = nil
|
44
|
+
|
45
|
+
LOCK.synchronize do
|
46
|
+
return @state[:result][name_s] if @state[:result].key?(name_s)
|
47
|
+
|
48
|
+
data = @storage.read!(name_s)
|
49
|
+
if data&.key?(:result)
|
50
|
+
@state[:result][name_s] = data[:result]
|
51
|
+
|
52
|
+
return data[:result]
|
53
|
+
elsif data&.key?(:stash)
|
54
|
+
@state[:stash][name_s] = data[:stash]
|
55
|
+
end
|
56
|
+
|
57
|
+
box = ::CacheBox::Box.new(
|
58
|
+
namespace: @namespace_o.dup,
|
59
|
+
name: name_o.dup,
|
60
|
+
args: args,
|
61
|
+
input: input,
|
62
|
+
stash: @state[:stash][name] || ::CacheBox::Stash.new,
|
63
|
+
logger: @logger
|
64
|
+
)
|
65
|
+
end
|
66
|
+
|
67
|
+
begin
|
68
|
+
result = block.call(box)
|
69
|
+
|
70
|
+
LOCK.synchronize do
|
71
|
+
@state[:result][name_s] = result
|
72
|
+
end
|
73
|
+
ensure
|
74
|
+
LOCK.synchronize do
|
75
|
+
if @state[:result].key?(name_s)
|
76
|
+
@state[:stash].delete(name_s)
|
77
|
+
@storage.write!(name_s, { result: @state[:result][name_s] })
|
78
|
+
else
|
79
|
+
stash = box.stash
|
80
|
+
stash = ::CacheBox::Stash.new(stash) if stash.is_a?(Hash)
|
81
|
+
|
82
|
+
stash.with do |state|
|
83
|
+
dump = { stash: state }
|
84
|
+
|
85
|
+
@state[:stash][name_s] = stash
|
86
|
+
@storage.write!(name_s, dump)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Input:
|
94
|
+
#
|
95
|
+
# name = String | Symbol # Default: 'name'
|
96
|
+
#
|
97
|
+
# Output: true | false
|
98
|
+
def has?(name = nil)
|
99
|
+
name ||= 'name'
|
100
|
+
validate_string_or_symbol!(name, 'name')
|
101
|
+
name = name.to_s
|
102
|
+
|
103
|
+
LOCK.synchronize do
|
104
|
+
return true if @state[:result].key?(name)
|
105
|
+
|
106
|
+
data = @storage.read!(name)
|
107
|
+
|
108
|
+
if data&.key?(:result)
|
109
|
+
@state[:result][name] = data[:result]
|
110
|
+
elsif data&.key?(:stash)
|
111
|
+
@state[:stash][name] = data[:stash]
|
112
|
+
end
|
113
|
+
|
114
|
+
@state[:result].key?(name)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# Input:
|
119
|
+
#
|
120
|
+
# name = String | Symbol # Default: 'name'
|
121
|
+
#
|
122
|
+
# Output: true | false
|
123
|
+
def result(name = nil)
|
124
|
+
name ||= 'name'
|
125
|
+
validate_string_or_symbol!(name, 'name')
|
126
|
+
name = name.to_s
|
127
|
+
|
128
|
+
LOCK.synchronize do
|
129
|
+
return @state[:result][name] if @state[:result].key?(name)
|
130
|
+
|
131
|
+
data = @storage.read!(name)
|
132
|
+
|
133
|
+
if data&.key?(:result)
|
134
|
+
@state[:result][name] = data[:result]
|
135
|
+
elsif data&.key?(:stash)
|
136
|
+
@state[:stash][name] = data[:stash]
|
137
|
+
end
|
138
|
+
|
139
|
+
@state[:result][name]
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# Input:
|
144
|
+
#
|
145
|
+
# name = Array[String | Symbol] # Default: []
|
146
|
+
#
|
147
|
+
# Output: self
|
148
|
+
def expire!(*names)
|
149
|
+
validate_array_of_string_or_symbol!(names, 'names')
|
150
|
+
|
151
|
+
if names.empty?
|
152
|
+
LOCK.synchronize do
|
153
|
+
@state = { result: ::CacheBox::Stash.new, stash: ::CacheBox::Stash.new }
|
154
|
+
|
155
|
+
@storage.reset!
|
156
|
+
end
|
157
|
+
else
|
158
|
+
LOCK.synchronize do
|
159
|
+
names.each do |name_o|
|
160
|
+
name_s = name_o.to_s
|
161
|
+
|
162
|
+
@state[:result].delete(name_s)
|
163
|
+
@state[:stash].delete(name_s)
|
164
|
+
|
165
|
+
@storage.delete!(name_s)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
self
|
171
|
+
end
|
172
|
+
|
173
|
+
# Input:
|
174
|
+
#
|
175
|
+
# name = Array[String | Symbol] # Default: []
|
176
|
+
#
|
177
|
+
# Output: self
|
178
|
+
def clear(*names)
|
179
|
+
validate_array_of_string_or_symbol!(names, 'names')
|
180
|
+
|
181
|
+
if names.empty?
|
182
|
+
LOCK.synchronize do
|
183
|
+
@state = { result: ::CacheBox::Stash.new, stash: ::CacheBox::Stash.new }
|
184
|
+
end
|
185
|
+
else
|
186
|
+
LOCK.synchronize do
|
187
|
+
names.each do |name_o|
|
188
|
+
name_s = name_o.to_s
|
189
|
+
|
190
|
+
@state[:result].delete(name_s)
|
191
|
+
@state[:stash].delete(name_s)
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
self
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|