oflow 1.0.2 → 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 +7 -0
- data/README.md +4 -0
- data/lib/oflow/actors.rb +1 -0
- data/lib/oflow/actors/httpserver.rb +2 -2
- data/lib/oflow/actors/recorder.rb +184 -0
- data/lib/oflow/actors/timer.rb +45 -19
- data/lib/oflow/graffle.rb +6 -2
- data/lib/oflow/version.rb +1 -1
- metadata +14 -15
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 5d34d97bd5ac308c65b62516297d16c06b41fa0e091bcb21a395a9b7564b023b
|
4
|
+
data.tar.gz: bd541cf3747b7d10a82fa297148475b6e52173eef8fc5ed455bb1d124c70f26a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 795edf243b99e83917dd6382c7874357eef78ab73419f36aa32b0fba943c6416418d5b7afc4641d8e89612dedaa8b8fc1757fbb51e5b07345cf590c3c15e05e6
|
7
|
+
data.tar.gz: 9c0566dec233c2ed6465db6a79196369578636e2d7aacbe48fc61e2a002dc361697cb68f4494fd67d426dbd97a20c0bf539e6152a4739ed86820af69219f46c2
|
data/README.md
CHANGED
@@ -25,6 +25,10 @@ Follow [@peterohler on Twitter](http://twitter.com/#!/peterohler) for announceme
|
|
25
25
|
|
26
26
|
## Release Notes
|
27
27
|
|
28
|
+
### Release 1.1.0
|
29
|
+
|
30
|
+
- Changed the gemchart example to use a new Recorder actor.
|
31
|
+
|
28
32
|
### Release 1.0.2
|
29
33
|
|
30
34
|
- Updated GemChart sample to handle poor connections better.
|
data/lib/oflow/actors.rb
CHANGED
@@ -13,6 +13,7 @@ require 'oflow/actors/balancer'
|
|
13
13
|
require 'oflow/actors/trigger'
|
14
14
|
require 'oflow/actors/merger'
|
15
15
|
require 'oflow/actors/persister'
|
16
|
+
require 'oflow/actors/recorder'
|
16
17
|
require 'oflow/actors/timer'
|
17
18
|
require 'oflow/actors/httpserver'
|
18
19
|
require 'oflow/actors/shellone'
|
@@ -45,9 +45,9 @@ module OFlow
|
|
45
45
|
continue if :success == key || 'success' == key
|
46
46
|
begin
|
47
47
|
task.ship(key, box)
|
48
|
-
rescue BlockedError
|
48
|
+
rescue BlockedError
|
49
49
|
task.warn("Failed to ship timer #{box.contents} to #{key}. Task blocked.")
|
50
|
-
rescue BusyError
|
50
|
+
rescue BusyError
|
51
51
|
task.warn("Failed to ship timer #{box.contents} to #{key}. Task busy.")
|
52
52
|
end
|
53
53
|
end
|
@@ -0,0 +1,184 @@
|
|
1
|
+
require 'oj'
|
2
|
+
|
3
|
+
module OFlow
|
4
|
+
module Actors
|
5
|
+
|
6
|
+
# Actor that saves records to the local file system as JSON
|
7
|
+
# representations of the records as lines in a single file associated with
|
8
|
+
# one of the elements of the JSON record. The message that triggers the
|
9
|
+
# store must have a 'table' element, a 'key', and a 'rec' element.
|
10
|
+
class Recorder < Actor
|
11
|
+
|
12
|
+
attr_reader :dir
|
13
|
+
|
14
|
+
# Initializes the recorder with options of:
|
15
|
+
# @param [Hash] options with keys of
|
16
|
+
# - :dir [String] directory to store the persisted records
|
17
|
+
# - :results_path [String] path to where the results should be placed in
|
18
|
+
# the request (default: nil or ship only results)
|
19
|
+
def initialize(task, options)
|
20
|
+
super
|
21
|
+
@cache = {}
|
22
|
+
@dir = options[:dir]
|
23
|
+
if @dir.nil?
|
24
|
+
@dir = File.join('db', task.full_name.gsub(':', '/'))
|
25
|
+
end
|
26
|
+
@dir = File.expand_path(@dir.strip)
|
27
|
+
@results_path = options[:results_path]
|
28
|
+
@results_path.strip! unless @results_path.nil?
|
29
|
+
|
30
|
+
if Dir.exist?(@dir)
|
31
|
+
Dir.glob(File.join(@dir, '*.json')).each do |path|
|
32
|
+
load(path)
|
33
|
+
end
|
34
|
+
else
|
35
|
+
`mkdir -p #{@dir}`
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def perform(op, box)
|
40
|
+
dest = box.contents[:dest]
|
41
|
+
result = nil
|
42
|
+
case op
|
43
|
+
when :insert, :create
|
44
|
+
result = insert(box)
|
45
|
+
when :get, :read
|
46
|
+
result = read(box)
|
47
|
+
when :update
|
48
|
+
result = update(box)
|
49
|
+
when :insert_update
|
50
|
+
result = insert_update(box)
|
51
|
+
when :delete, :remove
|
52
|
+
result = delete(box)
|
53
|
+
when :query
|
54
|
+
result = query(box)
|
55
|
+
when :clear
|
56
|
+
result = clear(box)
|
57
|
+
else
|
58
|
+
raise OpError.new(task.full_name, op)
|
59
|
+
end
|
60
|
+
unless dest.nil?
|
61
|
+
if @results_path.nil?
|
62
|
+
box = Box.new(result, box.tracker)
|
63
|
+
else
|
64
|
+
box = box.set(@results_path, result)
|
65
|
+
end
|
66
|
+
task.ship(dest, box)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def insert(box)
|
71
|
+
table = box.get('table')
|
72
|
+
key = box.get('key')
|
73
|
+
rec = box.get('rec')
|
74
|
+
raise KeyError.new(:insert) if table.nil?
|
75
|
+
raise KeyError.new(:insert) if key.nil?
|
76
|
+
|
77
|
+
tc = @cache[table]
|
78
|
+
if tc.nil?
|
79
|
+
tc = {}
|
80
|
+
@cache[table] = tc
|
81
|
+
end
|
82
|
+
tc[key] = rec
|
83
|
+
write(table)
|
84
|
+
end
|
85
|
+
|
86
|
+
alias :update :insert
|
87
|
+
alias :insert_update :insert
|
88
|
+
|
89
|
+
def read(box)
|
90
|
+
table = box.get('table')
|
91
|
+
key = box.get('key')
|
92
|
+
raise KeyError.new(:read) if table.nil?
|
93
|
+
raise KeyError.new(:read) if key.nil?
|
94
|
+
|
95
|
+
tc = @cache[table]
|
96
|
+
return nil if tc.nil?
|
97
|
+
|
98
|
+
rec = tc[key]
|
99
|
+
rec
|
100
|
+
end
|
101
|
+
|
102
|
+
def delete(box)
|
103
|
+
table = box.get('table')
|
104
|
+
key = box.get('key')
|
105
|
+
raise KeyError.new(:read) if table.nil?
|
106
|
+
raise KeyError.new(:read) if key.nil?
|
107
|
+
|
108
|
+
tc = @cache[table]
|
109
|
+
unless tc.nil?
|
110
|
+
tc.delete(key)
|
111
|
+
write(table)
|
112
|
+
end
|
113
|
+
nil
|
114
|
+
end
|
115
|
+
|
116
|
+
def query(box)
|
117
|
+
recs = {}
|
118
|
+
expr = box.get('expr')
|
119
|
+
table = box.get('table')
|
120
|
+
raise KeyError.new(:query) if table.nil?
|
121
|
+
|
122
|
+
tc = @cache[table]
|
123
|
+
tc.each do |key,rec|
|
124
|
+
recs[key] = rec if (expr.nil? || expr.call(rec, key))
|
125
|
+
end
|
126
|
+
recs
|
127
|
+
end
|
128
|
+
|
129
|
+
def clear(box)
|
130
|
+
@cache = {}
|
131
|
+
`rm -rf #{@dir}`
|
132
|
+
# remake the dir in preparation for future inserts
|
133
|
+
`mkdir -p #{@dir}`
|
134
|
+
nil
|
135
|
+
end
|
136
|
+
|
137
|
+
private
|
138
|
+
|
139
|
+
def write(table)
|
140
|
+
filename = "#{table}.json"
|
141
|
+
path = File.join(@dir, filename)
|
142
|
+
Oj.to_file(path, @cache[table], :mode => :strict)
|
143
|
+
end
|
144
|
+
|
145
|
+
def load(path)
|
146
|
+
return nil unless File.exist?(path)
|
147
|
+
tc = Oj.load_file(path, :mode => :strict, symbol_keys: true)
|
148
|
+
name = File.basename(path)[0..-File.extname(path).size - 1]
|
149
|
+
@cache[name] = tc
|
150
|
+
end
|
151
|
+
|
152
|
+
class TableError < Exception
|
153
|
+
def initialize(table)
|
154
|
+
super("No Table found for #{table}")
|
155
|
+
end
|
156
|
+
end # TableError
|
157
|
+
|
158
|
+
class KeyError < Exception
|
159
|
+
def initialize(key)
|
160
|
+
super("No key found for #{key}")
|
161
|
+
end
|
162
|
+
end # KeyError
|
163
|
+
|
164
|
+
class SeqError < Exception
|
165
|
+
def initialize(op, key)
|
166
|
+
super("No sequence number found for #{op} of #{key}")
|
167
|
+
end
|
168
|
+
end # SeqError
|
169
|
+
|
170
|
+
class ExistsError < Exception
|
171
|
+
def initialize(key, seq)
|
172
|
+
super("#{key}:#{seq} already exists")
|
173
|
+
end
|
174
|
+
end # ExistsError
|
175
|
+
|
176
|
+
class NotFoundError < Exception
|
177
|
+
def initialize(key)
|
178
|
+
super("#{key} not found")
|
179
|
+
end
|
180
|
+
end # NotFoundError
|
181
|
+
|
182
|
+
end # Recorder
|
183
|
+
end # Actors
|
184
|
+
end # OFlow
|
data/lib/oflow/actors/timer.rb
CHANGED
@@ -12,10 +12,11 @@ module OFlow
|
|
12
12
|
|
13
13
|
# When to trigger the first event. nil means start now.
|
14
14
|
attr_reader :start
|
15
|
-
# The stop time. If nil then there is
|
16
|
-
# kicks in.
|
15
|
+
# The stop time. If nil then there is no stopping unless the repeat
|
16
|
+
# limit kicks in.
|
17
17
|
attr_reader :stop
|
18
|
-
# How long to wait between each trigger. nil indicates as fast as
|
18
|
+
# How long to wait between each trigger. nil indicates as fast as
|
19
|
+
# possible,
|
19
20
|
attr_reader :period
|
20
21
|
# How many time to repeat before stopping. nil mean go forever.
|
21
22
|
attr_reader :repeat
|
@@ -23,11 +24,13 @@ module OFlow
|
|
23
24
|
attr_reader :label
|
24
25
|
# The number of time the timer has fired or shipped.
|
25
26
|
attr_reader :count
|
26
|
-
# Boolean flag indicating a tracker should be added to the trigger
|
27
|
-
# if true.
|
27
|
+
# Boolean flag indicating a tracker should be added to the trigger
|
28
|
+
# content if true.
|
28
29
|
attr_reader :with_tracker
|
29
30
|
# Time of next or pending trigger.
|
30
31
|
attr_reader :pending
|
32
|
+
# Flag indicating a trigger should be fired on start as well.
|
33
|
+
attr_reader :on_start
|
31
34
|
|
32
35
|
def initialize(task, options={})
|
33
36
|
@count = 0
|
@@ -35,6 +38,7 @@ module OFlow
|
|
35
38
|
@stop = nil
|
36
39
|
@period = nil
|
37
40
|
@repeat = nil
|
41
|
+
@on_start = false
|
38
42
|
set_options(options)
|
39
43
|
@pending = @start
|
40
44
|
super
|
@@ -70,6 +74,9 @@ module OFlow
|
|
70
74
|
when :with_tracker
|
71
75
|
set_with_tracker(box.nil? ? nil : box.contents)
|
72
76
|
end
|
77
|
+
if @on_start
|
78
|
+
trigger(Time.now())
|
79
|
+
end
|
73
80
|
while true
|
74
81
|
now = Time.now()
|
75
82
|
# If past stop time then it is done. A future change in options can
|
@@ -87,22 +94,14 @@ module OFlow
|
|
87
94
|
unless Task::STOPPED == task.state
|
88
95
|
@count += 1
|
89
96
|
now = Time.now()
|
90
|
-
|
91
|
-
box = Box.new([@label, @count, now.utc()], tracker)
|
92
|
-
task.links.each_key do |key|
|
93
|
-
begin
|
94
|
-
task.ship(key, box)
|
95
|
-
rescue BlockedError => e
|
96
|
-
task.warn("Failed to ship timer #{box.contents} to #{key}. Task blocked.")
|
97
|
-
rescue BusyError => e
|
98
|
-
task.warn("Failed to ship timer #{box.contents} to #{key}. Task busy.")
|
99
|
-
end
|
100
|
-
end
|
97
|
+
trigger(now)
|
101
98
|
end
|
102
99
|
if @period.nil? || @period == 0
|
103
100
|
@pending = now
|
104
101
|
else
|
105
|
-
|
102
|
+
diff = now - @pending
|
103
|
+
@pending += @period * diff.to_i/@period.to_i
|
104
|
+
@pending += @period if @pending <= now
|
106
105
|
end
|
107
106
|
end
|
108
107
|
# If there is a request waiting then return so it can be handled. It
|
@@ -122,12 +121,27 @@ module OFlow
|
|
122
121
|
end
|
123
122
|
end
|
124
123
|
|
124
|
+
def trigger(now)
|
125
|
+
tracker = @with_tracker ? Tracker.create(@label) : nil
|
126
|
+
box = Box.new([@label, @count, now.utc()], tracker)
|
127
|
+
task.links.each_key do |key|
|
128
|
+
begin
|
129
|
+
task.ship(key, box)
|
130
|
+
rescue BlockedError
|
131
|
+
task.warn("Failed to ship timer #{box.contents} to #{key}. Task blocked.")
|
132
|
+
rescue BusyError
|
133
|
+
task.warn("Failed to ship timer #{box.contents} to #{key}. Task busy.")
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
125
138
|
def set_options(options)
|
126
139
|
set_start(options[:start]) # if nil let start get set to now
|
127
140
|
set_stop(options[:stop]) if options.has_key?(:stop)
|
128
141
|
set_period(options[:period]) if options.has_key?(:period)
|
129
142
|
set_repeat(options[:repeat]) if options.has_key?(:repeat)
|
130
143
|
set_with_tracker(options[:with_tracker])
|
144
|
+
set_on_start(options[:on_start])
|
131
145
|
@label = options[:label].to_s
|
132
146
|
end
|
133
147
|
|
@@ -135,7 +149,7 @@ module OFlow
|
|
135
149
|
if v.is_a?(String)
|
136
150
|
begin
|
137
151
|
v = DateTime.parse(v).to_time
|
138
|
-
v = v - v.gmtoff
|
152
|
+
v = v - v.localtime.gmtoff
|
139
153
|
rescue Exception
|
140
154
|
v = Time.now() + v.to_i
|
141
155
|
end
|
@@ -193,6 +207,18 @@ module OFlow
|
|
193
207
|
@label = v
|
194
208
|
end
|
195
209
|
|
210
|
+
def set_on_start(v)
|
211
|
+
if v.is_a?(TrueClass)
|
212
|
+
@on_start = true
|
213
|
+
elsif v.is_a?(FalseClass)
|
214
|
+
@on_start = false
|
215
|
+
elsif v.nil?
|
216
|
+
# no change
|
217
|
+
else
|
218
|
+
@on_start = ('true' == v.to_s.strip.downcase)
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
196
222
|
def set_with_tracker(v)
|
197
223
|
v = false if v.nil?
|
198
224
|
unless true == v || false == v
|
@@ -200,7 +226,7 @@ module OFlow
|
|
200
226
|
end
|
201
227
|
@with_tracker = v
|
202
228
|
end
|
203
|
-
|
229
|
+
|
204
230
|
end # Timer
|
205
231
|
end # Actors
|
206
232
|
end # OFlow
|
data/lib/oflow/graffle.rb
CHANGED
@@ -20,8 +20,12 @@ module OFlow
|
|
20
20
|
@name = File.basename(filename, '.graffle')
|
21
21
|
|
22
22
|
nodes = doc.locate('plist/dict')[0].nodes
|
23
|
-
|
24
|
-
|
23
|
+
sheets = Graffle.get_key_value(nodes, 'Sheets')
|
24
|
+
if sheets.nil?
|
25
|
+
nodes = Graffle.get_key_value(nodes, 'GraphicsList')
|
26
|
+
else
|
27
|
+
nodes = Graffle.get_key_value(sheets[0].nodes, 'GraphicsList')
|
28
|
+
end
|
25
29
|
raise ConfigError.new("Empty flow.") if nodes.nil?
|
26
30
|
|
27
31
|
nodes.each do |node|
|
data/lib/oflow/version.rb
CHANGED
metadata
CHANGED
@@ -1,15 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: oflow
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
5
|
-
prerelease:
|
4
|
+
version: 1.1.0
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Peter Ohler
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date:
|
11
|
+
date: 2018-06-21 00:00:00.000000000 Z
|
13
12
|
dependencies: []
|
14
13
|
description: Operations Workflow in Ruby. This implements a workflow/process flow
|
15
14
|
using multiple task nodes that each have their own queues and execution thread.
|
@@ -19,7 +18,11 @@ extensions: []
|
|
19
18
|
extra_rdoc_files:
|
20
19
|
- README.md
|
21
20
|
files:
|
21
|
+
- LICENSE
|
22
|
+
- README.md
|
23
|
+
- lib/oflow.rb
|
22
24
|
- lib/oflow/actor.rb
|
25
|
+
- lib/oflow/actors.rb
|
23
26
|
- lib/oflow/actors/balancer.rb
|
24
27
|
- lib/oflow/actors/errorhandler.rb
|
25
28
|
- lib/oflow/actors/httpserver.rb
|
@@ -27,12 +30,12 @@ files:
|
|
27
30
|
- lib/oflow/actors/log.rb
|
28
31
|
- lib/oflow/actors/merger.rb
|
29
32
|
- lib/oflow/actors/persister.rb
|
33
|
+
- lib/oflow/actors/recorder.rb
|
30
34
|
- lib/oflow/actors/relay.rb
|
31
35
|
- lib/oflow/actors/shellone.rb
|
32
36
|
- lib/oflow/actors/shellrepeat.rb
|
33
37
|
- lib/oflow/actors/timer.rb
|
34
38
|
- lib/oflow/actors/trigger.rb
|
35
|
-
- lib/oflow/actors.rb
|
36
39
|
- lib/oflow/box.rb
|
37
40
|
- lib/oflow/env.rb
|
38
41
|
- lib/oflow/errors.rb
|
@@ -45,12 +48,11 @@ files:
|
|
45
48
|
- lib/oflow/pattern.rb
|
46
49
|
- lib/oflow/stamp.rb
|
47
50
|
- lib/oflow/task.rb
|
51
|
+
- lib/oflow/test.rb
|
48
52
|
- lib/oflow/test/action.rb
|
49
53
|
- lib/oflow/test/actorwrap.rb
|
50
|
-
- lib/oflow/test.rb
|
51
54
|
- lib/oflow/tracker.rb
|
52
55
|
- lib/oflow/version.rb
|
53
|
-
- lib/oflow.rb
|
54
56
|
- test/actors/bad.rb
|
55
57
|
- test/actors/balancer_test.rb
|
56
58
|
- test/actors/doubler.rb
|
@@ -77,33 +79,30 @@ files:
|
|
77
79
|
- test/stutter.rb
|
78
80
|
- test/task_test.rb
|
79
81
|
- test/tracker_test.rb
|
80
|
-
- LICENSE
|
81
|
-
- README.md
|
82
82
|
homepage: http://www.ohler.com/oflow
|
83
83
|
licenses:
|
84
84
|
- MIT
|
85
|
+
metadata: {}
|
85
86
|
post_install_message:
|
86
87
|
rdoc_options:
|
87
|
-
- --main
|
88
|
+
- "--main"
|
88
89
|
- README.md
|
89
90
|
require_paths:
|
90
91
|
- lib
|
91
92
|
required_ruby_version: !ruby/object:Gem::Requirement
|
92
|
-
none: false
|
93
93
|
requirements:
|
94
|
-
- -
|
94
|
+
- - ">="
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: '0'
|
97
97
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
98
|
-
none: false
|
99
98
|
requirements:
|
100
|
-
- -
|
99
|
+
- - ">="
|
101
100
|
- !ruby/object:Gem::Version
|
102
101
|
version: '0'
|
103
102
|
requirements: []
|
104
103
|
rubyforge_project: oflow
|
105
|
-
rubygems_version:
|
104
|
+
rubygems_version: 2.7.6
|
106
105
|
signing_key:
|
107
|
-
specification_version:
|
106
|
+
specification_version: 4
|
108
107
|
summary: Operations Workflow in Ruby
|
109
108
|
test_files: []
|