oflow 1.0.2 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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.
@@ -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 => e
48
+ rescue BlockedError
49
49
  task.warn("Failed to ship timer #{box.contents} to #{key}. Task blocked.")
50
- rescue BusyError => e
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
@@ -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 not stopping unless the repeat limit
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 possible,
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 content
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
- tracker = @with_tracker ? Tracker.create(@label) : nil
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
- @pending += period
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
@@ -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
- nodes = Graffle.get_key_value(nodes, 'GraphicsList')
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|
@@ -1,5 +1,5 @@
1
1
 
2
2
  module OFlow
3
3
  # Current version of the module.
4
- VERSION = '1.0.2'
4
+ VERSION = '1.1.0'
5
5
  end
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.2
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: 2016-06-26 00:00:00.000000000 Z
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: 1.8.23.2
104
+ rubygems_version: 2.7.6
106
105
  signing_key:
107
- specification_version: 3
106
+ specification_version: 4
108
107
  summary: Operations Workflow in Ruby
109
108
  test_files: []