flor 0.9.5 → 0.10.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.
Files changed (91) hide show
  1. data/CHANGELOG.md +10 -0
  2. data/Makefile +13 -5
  3. data/README.md +0 -35
  4. data/flor.gemspec +1 -0
  5. data/lib/flor.rb +6 -24
  6. data/lib/flor/changes.rb +26 -0
  7. data/lib/flor/colours.rb +65 -31
  8. data/lib/flor/conf.rb +84 -54
  9. data/lib/flor/core.rb +0 -23
  10. data/lib/flor/core/executor.rb +12 -42
  11. data/lib/flor/core/node.rb +19 -24
  12. data/lib/flor/core/procedure.rb +13 -24
  13. data/lib/flor/core/texecutor.rb +10 -28
  14. data/lib/flor/deep.rb +152 -0
  15. data/lib/flor/djan.rb +200 -0
  16. data/lib/flor/dollar.rb +0 -24
  17. data/lib/flor/errors.rb +0 -24
  18. data/lib/flor/flor.rb +43 -296
  19. data/lib/flor/id.rb +90 -0
  20. data/lib/flor/log.rb +12 -35
  21. data/lib/flor/migrations/0002_cunit_and_munit.rb +86 -0
  22. data/lib/flor/parser.rb +40 -46
  23. data/lib/flor/pcore/_arr.rb +0 -24
  24. data/lib/flor/pcore/_atom.rb +0 -24
  25. data/lib/flor/pcore/_att.rb +3 -25
  26. data/lib/flor/pcore/_dump.rb +0 -24
  27. data/lib/flor/pcore/_err.rb +0 -24
  28. data/lib/flor/pcore/_happly.rb +0 -24
  29. data/lib/flor/pcore/_obj.rb +0 -24
  30. data/lib/flor/pcore/_skip.rb +0 -24
  31. data/lib/flor/pcore/arith.rb +0 -24
  32. data/lib/flor/pcore/break.rb +0 -24
  33. data/lib/flor/pcore/case.rb +127 -0
  34. data/lib/flor/pcore/cmp.rb +0 -24
  35. data/lib/flor/pcore/cond.rb +24 -24
  36. data/lib/flor/pcore/cursor.rb +0 -24
  37. data/lib/flor/pcore/define.rb +0 -24
  38. data/lib/flor/pcore/fail.rb +0 -24
  39. data/lib/flor/pcore/if.rb +39 -0
  40. data/lib/flor/pcore/loop.rb +0 -24
  41. data/lib/flor/pcore/map.rb +0 -24
  42. data/lib/flor/pcore/match.rb +0 -24
  43. data/lib/flor/pcore/move.rb +0 -24
  44. data/lib/flor/pcore/noeval.rb +0 -24
  45. data/lib/flor/pcore/noret.rb +0 -24
  46. data/lib/flor/pcore/push.rb +1 -25
  47. data/lib/flor/pcore/rand.rb +59 -0
  48. data/lib/flor/pcore/sequence.rb +0 -24
  49. data/lib/flor/pcore/set.rb +0 -24
  50. data/lib/flor/pcore/stall.rb +0 -24
  51. data/lib/flor/pcore/until.rb +0 -24
  52. data/lib/flor/pcore/val.rb +0 -24
  53. data/lib/flor/punit/cancel.rb +0 -24
  54. data/lib/flor/punit/cmap.rb +0 -24
  55. data/lib/flor/punit/concurrence.rb +54 -24
  56. data/lib/flor/punit/every.rb +0 -24
  57. data/lib/flor/punit/graft.rb +41 -0
  58. data/lib/flor/punit/on.rb +0 -24
  59. data/lib/flor/punit/schedule.rb +0 -24
  60. data/lib/flor/punit/signal.rb +0 -24
  61. data/lib/flor/punit/sleep.rb +0 -24
  62. data/lib/flor/punit/task.rb +0 -26
  63. data/lib/flor/punit/trace.rb +0 -24
  64. data/lib/flor/punit/trap.rb +0 -24
  65. data/lib/flor/to_string.rb +4 -25
  66. data/lib/flor/tools/env.rb +0 -23
  67. data/lib/flor/tools/shell.rb +810 -0
  68. data/lib/flor/unit.rb +0 -23
  69. data/lib/flor/unit/executor.rb +35 -31
  70. data/lib/flor/unit/ganger.rb +9 -34
  71. data/lib/flor/unit/hooker.rb +5 -25
  72. data/lib/flor/unit/journal.rb +0 -23
  73. data/lib/flor/unit/loader.rb +63 -94
  74. data/lib/flor/unit/logger.rb +8 -27
  75. data/lib/flor/unit/models.rb +0 -24
  76. data/lib/flor/unit/models/execution.rb +13 -24
  77. data/lib/flor/unit/models/pointer.rb +0 -24
  78. data/lib/flor/unit/models/timer.rb +0 -24
  79. data/lib/flor/unit/models/trace.rb +0 -24
  80. data/lib/flor/unit/models/trap.rb +0 -24
  81. data/lib/flor/unit/scheduler.rb +157 -128
  82. data/lib/flor/unit/storage.rb +224 -167
  83. data/lib/flor/unit/taskers.rb +38 -25
  84. data/lib/flor/unit/waiter.rb +7 -26
  85. data/lib/flor/unit/wlist.rb +8 -24
  86. metadata +28 -7
  87. data/fail.txt +0 -16
  88. data/intercepted.txt +0 -123
  89. data/lib/flor/pcore/ife.rb +0 -56
  90. data/lib/flor/tools/repl.rb +0 -231
  91. data/out.txt +0 -206
@@ -1,26 +1,3 @@
1
- #--
2
- # Copyright (c) 2015-2017, John Mettraux, jmettraux+flor@gmail.com
3
- #
4
- # Permission is hereby granted, free of charge, to any person obtaining a copy
5
- # of this software and associated documentation files (the "Software"), to deal
6
- # in the Software without restriction, including without limitation the rights
7
- # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
- # copies of the Software, and to permit persons to whom the Software is
9
- # furnished to do so, subject to the following conditions:
10
- #
11
- # The above copyright notice and this permission notice shall be included in
12
- # all copies or substantial portions of the Software.
13
- #
14
- # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15
- # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16
- # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17
- # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18
- # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19
- # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20
- # THE SOFTWARE.
21
- #
22
- # Made in Japan.
23
- #++
24
1
 
25
2
  require 'zlib'
26
3
 
@@ -119,41 +96,13 @@ module Flor
119
96
  @db.tables.each { |t| @db[t].delete if t.to_s.match(/^flor_/) }
120
97
  end
121
98
 
122
- def load_exids
123
-
124
- synchronize do
125
-
126
- # only take messages that are 'created' and for whose exid
127
- # there are no loaded messages
128
-
129
- # TODO update status to 'created' for messages that have been
130
- # 'loaded' for too long
131
-
132
- @db[:flor_messages]
133
- .select(:exid)
134
- .where(exid:
135
- @db[:flor_messages].select(:exid).where(status: 'created'))
136
- .exclude(exid:
137
- @db[:flor_messages].select(:exid).where(status: 'loaded'))
138
- .order(:mtime)
139
- .collect { |r| r[:exid] }
140
- end
141
-
142
- rescue => err
143
-
144
- @unit.logger.warn("#{self.class}#load_exids", err, '(returning [])')
145
-
146
- []
147
- end
148
-
149
99
  def load_execution(exid)
150
100
 
151
101
  synchronize do
152
102
 
153
103
  e = @db[:flor_executions]
154
104
  .select(:id, :content)
155
- .where(exid: exid) # status active or terminated doesn't matter
156
- .first
105
+ .first(exid: exid) # status active or terminated doesn't matter
157
106
 
158
107
  return {
159
108
  'exid' => exid, 'nodes' => {}, 'errors' => [], 'tasks' => {},
@@ -163,9 +112,9 @@ module Flor
163
112
 
164
113
  ex = from_blob(e[:content])
165
114
 
166
- fail("couldn't parse execution (db id #{e[:id]})") unless ex
115
+ fail("couldn't parse execution (db id #{e[:id].to_i})") unless ex
167
116
 
168
- ex['id'] = e[:id]
117
+ ex['id'] = e[:id].to_i
169
118
  ex['size'] = e[:content].length
170
119
 
171
120
  ex
@@ -193,6 +142,7 @@ module Flor
193
142
 
194
143
  data = to_blob(ex)
195
144
  ex['size'] = data.length
145
+ u = @unit.identifier
196
146
 
197
147
  transync do
198
148
 
@@ -201,11 +151,12 @@ module Flor
201
151
  if id
202
152
 
203
153
  @db[:flor_executions]
204
- .where(id: id)
154
+ .where(id: id.to_i)
205
155
  .update(
206
156
  content: data,
207
157
  status: status,
208
- mtime: now)
158
+ mtime: now,
159
+ munit: u)
209
160
 
210
161
  else
211
162
 
@@ -217,7 +168,10 @@ module Flor
217
168
  content: data,
218
169
  status: 'active',
219
170
  ctime: now,
220
- mtime: now)
171
+ mtime: now,
172
+ cunit: u,
173
+ munit: u)
174
+ .to_i
221
175
  end
222
176
 
223
177
  remove_nodes(ex, status, now)
@@ -232,37 +186,83 @@ module Flor
232
186
  raise err
233
187
  end
234
188
 
235
- def fetch_messages(exid)
189
+ def load_messages(exe_count)
190
+
191
+ exe_count += 2
192
+ # load two more, could prove useful if they vanish like "petits pains"
193
+
194
+ synchronize do
195
+
196
+ _exids_being_processed =
197
+ @db[:flor_messages]
198
+ .select(:exid)
199
+ .exclude(status: %w[ created consumed ])
200
+ _exids =
201
+ @db[:flor_messages]
202
+ .select(:exid)
203
+ .exclude(exid: _exids_being_processed)
204
+ .limit(exe_count)
205
+ @db[:flor_messages]
206
+ .where(exid: _exids, status: 'created')
207
+ .inject({}) { |h, m| (h[m[:exid]] ||= []) << m; h }
208
+ end
209
+
210
+ rescue => err
211
+
212
+ @unit.logger.warn(
213
+ "#{self.class}#load_messages()", err, '(returning {})')
214
+
215
+ {}
216
+ end
217
+
218
+ def reserve_all_messages(messages)
219
+
220
+ now = Flor.tstamp
221
+ count = 0
236
222
 
237
223
  transync do
238
224
 
239
- # TODO weave in [some] optimistic locking here
225
+ messages.each do |m|
240
226
 
241
- mids = []
227
+ c = @db[:flor_messages]
228
+ .where(
229
+ id: m[:id].to_i, status: 'created',
230
+ mtime: m[:mtime], munit: m[:munit])
231
+ .update(
232
+ status: 'reserved', mtime: now, munit: @unit.identifier)
242
233
 
243
- ms = @db[:flor_messages]
244
- .select(:id, :content)
245
- .where(status: 'created', exid: exid)
246
- .order(:id)
247
- .collect { |m|
248
- r = from_blob(m[:content]) || {}
249
- mid = m[:id]; r['mid'] = mid; mids << mid;
250
- r }
234
+ raise Sequel::Rollback if c != 1
251
235
 
252
- @db[:flor_messages]
253
- .where(id: mids)
254
- .update(status: 'loaded', mtime: Flor.tstamp)
255
- #
256
- # flag them as "loaded" so that other scheduler don't pick them
236
+ count += 1
237
+ end
238
+ end
257
239
 
258
- ms
240
+ count == messages.size
241
+ # true means success: all the messages could be reserved,
242
+ # executor is clear to work on the execution
243
+
244
+ rescue => err
245
+
246
+ @unit.logger.warn(
247
+ "#{self.class}#reserve_all_messages()", err, '(returning false)')
248
+
249
+ false
250
+ # failure
251
+ end
252
+
253
+ def any_message?
254
+
255
+ synchronize do
256
+
257
+ @db[:flor_messages].count(status: 'created') > 0
259
258
  end
260
259
 
261
260
  rescue => err
262
261
 
263
- @unit.logger.warn("#{self.class}#fetch_messages()", err, '(returning [])')
262
+ @unit.logger.warn(
263
+ "#{self.class}#any_message?()", err, '(returning false)')
264
264
 
265
- []
265
+ false
266
266
  end
267
267
 
268
268
  def fetch_traps(exid)
@@ -277,7 +277,8 @@ module Flor
277
277
 
278
278
  rescue => err
279
279
 
280
- @unit.logger.warn("#{self.class}#fetch_traps()", err, '(returning [])')
280
+ @unit.logger.warn(
281
+ "#{self.class}#fetch_traps()", err, '(returning [])')
281
282
 
282
283
  []
283
284
  end
@@ -288,11 +289,14 @@ module Flor
288
289
 
289
290
  if @archive
290
291
  @db[:flor_messages]
291
- .where(id: messages.collect { |m| m['mid'] }.compact)
292
- .update(status: 'consumed', mtime: Flor.tstamp)
292
+ .where(
293
+ id: messages.collect { |m| m['mid'] }.compact)
294
+ .update(
295
+ status: 'consumed', mtime: Flor.tstamp, munit: @unit.identifier)
293
296
  else
294
297
  @db[:flor_messages]
295
- .where(id: messages.collect { |m| m['mid'] }.compact)
298
+ .where(
299
+ id: messages.collect { |m| m['mid'] }.compact)
296
300
  .delete
297
301
  end
298
302
  end
@@ -303,45 +307,29 @@ module Flor
303
307
  raise err
304
308
  end
305
309
 
306
- def load_timers
307
-
308
- synchronize do
309
-
310
- timers
311
- .select(:id, :content)
312
- .where(status: 'active')
313
- .order(:id)
314
- .all
315
- end
316
-
317
- rescue => err
318
-
319
- @unit.logger.warn("#{self.class}#load_timers()", err, '(returning [])')
320
-
321
- []
322
- end
323
-
324
310
  def put_messages(ms, syn=true)
325
311
 
326
312
  return if ms.empty?
327
313
 
328
314
  n = Flor.tstamp
315
+ u = @unit.identifier
329
316
 
330
317
  synchronize(syn) do
331
318
 
332
319
  @db[:flor_messages]
333
320
  .import(
334
321
  [ :domain, :exid, :point, :content,
335
- :status, :ctime, :mtime ],
322
+ :status, :ctime, :mtime, :cunit, :munit ],
336
323
  ms.map { |m|
337
324
  [ Flor.domain(m['exid']), m['exid'], m['point'], to_blob(m),
338
- 'created', n, n ]
325
+ 'created', n, n, u, u ]
339
326
  })
340
327
  end
341
328
 
342
- @unit.wake_up_executions(ms.collect { |m| m['exid'] }.uniq)
329
+ @unit.wake_up
343
330
 
344
331
  rescue => err
332
+
345
333
  Thread.current[:sto_errored_items] = ms
346
334
  raise err
347
335
  end
@@ -351,6 +339,20 @@ module Flor
351
339
  put_messages([ m ])
352
340
  end
353
341
 
342
+ def unreserve_messages(max_sec)
343
+
344
+ tstamp = Flor.tstamp(Time.now - max_sec)
345
+ tstamp = tstamp[0..tstamp.rindex('.')]
346
+
347
+ synchronize do
348
+
349
+ @db[:flor_messages]
350
+ .where(status: 'reserved')
351
+ .where { mtime < tstamp }
352
+ .update(status: 'created')
353
+ end
354
+ end
355
+
354
356
  def put_timer(message)
355
357
 
356
358
  type, string = determine_type_and_schedule(message)
@@ -358,24 +360,27 @@ module Flor
358
360
  next_time = compute_next_time(type, string)
359
361
 
360
362
  now = Flor.tstamp
363
+ u = @unit.identifier
361
364
 
362
- id =
363
- synchronize do
364
- @db[:flor_timers].insert(
365
- domain: Flor.domain(message['exid']),
366
- exid: message['exid'],
367
- nid: message['nid'],
368
- type: type,
369
- schedule: string,
370
- ntime: next_time,
371
- content: to_blob(message),
372
- count: 0,
373
- status: 'active',
374
- ctime: now,
375
- mtime: now)
376
- end
365
+ synchronize do
366
+
367
+ @db[:flor_timers].insert(
368
+ domain: Flor.domain(message['exid']),
369
+ exid: message['exid'],
370
+ nid: message['nid'],
371
+ type: type,
372
+ schedule: string,
373
+ ntime: next_time,
374
+ content: to_blob(message),
375
+ count: 0,
376
+ status: 'active',
377
+ ctime: now,
378
+ mtime: now,
379
+ cunit: u,
380
+ munit: u)
381
+ end
377
382
 
378
- @unit.timers[id]
383
+ @unit.wake_up
379
384
 
380
385
  rescue => err
381
386
 
@@ -383,51 +388,20 @@ module Flor
383
388
  raise err
384
389
  end
385
390
 
386
- # Returns the timer if it is rescheduling
387
- #
388
- def trigger_timer(timer)
391
+ def trigger_timers
389
392
 
390
- r = nil
391
-
392
- transync do
393
+ synchronize do
393
394
 
394
- # TODO: cron/every stop conditions maybe?
395
+ load_timers.each do |t|
395
396
 
396
- if timer.type != 'at' && timer.type != 'in'
397
+ @db.transaction do
397
398
 
398
- @db[:flor_timers]
399
- .where(id: timer.id)
400
- .update(
401
- count: timer.count + 1,
402
- ntime: compute_next_time(timer.type, timer.schedule),
403
- mtime: Flor.tstamp)
404
- r = timers[timer.id]
405
-
406
- elsif @archive
407
-
408
- @db[:flor_timers]
409
- .where(id: timer.id)
410
- .update(
411
- count: timer.count + 1,
412
- status: 'triggered',
413
- mtime: Flor.tstamp)
399
+ next unless reschedule_timer(t) == 1
414
400
 
415
- else
416
-
417
- @db[:flor_timers]
418
- .where(id: timer.id)
419
- .delete
401
+ trigger_timer(t)
402
+ end
420
403
  end
421
-
422
- put_messages([ timer.to_trigger_message ], false)
423
404
  end
424
-
425
- r
426
-
427
- rescue => err
428
-
429
- Thread.current[:sto_errored_items] = [ timer ]
430
- raise err
431
405
  end
432
406
 
433
407
  def put_trap(node, tra)
@@ -435,9 +409,10 @@ module Flor
435
409
  exid = node['exid']
436
410
  dom = Flor.domain(exid)
437
411
  now = Flor.tstamp
412
+ u = @unit.identifier
438
413
 
439
414
  id =
440
- transync do
415
+ synchronize do
441
416
 
442
417
  @db[:flor_traps].insert(
443
418
  domain: dom,
@@ -452,7 +427,9 @@ module Flor
452
427
  content: to_blob(tra),
453
428
  status: 'active',
454
429
  ctime: now,
455
- mtime: now)
430
+ mtime: now,
431
+ cunit: u,
432
+ munit: u)
456
433
  end
457
434
 
458
435
  traps[id]
@@ -475,7 +452,8 @@ module Flor
475
452
  nid: nid,
476
453
  tracer: tracer,
477
454
  text: text,
478
- ctime: Flor.tstamp)
455
+ ctime: Flor.tstamp,
456
+ cunit: @unit.identifier)
479
457
  end
480
458
  end
481
459
 
@@ -493,12 +471,89 @@ module Flor
493
471
  nid: msg['nid'],
494
472
  type: 'tasker',
495
473
  name: tname,
496
- ctime: Flor.tstamp)
474
+ ctime: Flor.tstamp,
475
+ cunit: @unit.identifier)
497
476
  end
498
477
  end
499
478
 
479
+ def fetch_next_time
480
+
481
+ t =
482
+ synchronize do
483
+ @db[:flor_timers]
484
+ .select(:ntime)
485
+ .order(:ntime)
486
+ .first(status: 'active')
487
+ end
488
+
489
+ t ? t[:ntime].split('.').first : nil
490
+
491
+ rescue => err
492
+
493
+ @unit.logger.warn(
494
+ "#{self.class}#fetch_next_time()", err, '(returning nil)')
495
+
496
+ nil
497
+ end
498
+
500
499
  protected
501
500
 
501
+ def load_timers
502
+
503
+ now = Flor.tstamp
504
+ no = now[0, now.rindex('.')]
505
+
506
+ timers
507
+ .where(status: 'active')
508
+ .where { ntime <= no }
509
+ .order(:ntime)
510
+ .all
511
+
512
+ rescue => err
513
+
514
+ @unit.logger.warn("#{self.class}#load_timers()", err, '(returning [])')
515
+
516
+ []
517
+ end
518
+
519
+ def trigger_timer(t)
520
+
521
+ put_messages([ t.to_trigger_message ], false)
522
+ end
523
+
524
+ def reschedule_timer(t)
525
+
526
+ w = { id: t.id.to_i, status: 'active', mtime: t.mtime, munit: t.munit }
527
+
528
+ if t.type != 'at' && t.type != 'in'
529
+
530
+ @db[:flor_timers]
531
+ .where(w)
532
+ .update(
533
+ count: t.count.to_i + 1,
534
+ status: 'active',
535
+ ntime: compute_next_time(t.type, t.schedule, t.ntime_t),
536
+ mtime: Flor.tstamp,
537
+ munit: @unit.identifier)
538
+
539
+ elsif @archive
540
+
541
+ @db[:flor_timers]
542
+ .where(w)
543
+ .update(
544
+ count: t.count.to_i + 1,
545
+ status: 'triggered',
546
+ mtime: Flor.tstamp,
547
+ munit: @unit.identifier)
548
+
549
+ else
550
+
551
+ @db[:flor_timers]
552
+ .where(w)
553
+ .delete
554
+ end
555
+ end
556
+
502
557
  def remove_nodes(exe, status, now)
503
558
 
504
559
  exid = exe['exid']
@@ -520,6 +575,7 @@ module Flor
520
575
 
521
576
  def update_pointers(exe, status, now)
522
577
 
578
+ # TODO archive old pointers???
523
579
  exid = exe['exid']
524
580
 
525
581
  if status == 'terminated'
@@ -537,20 +593,21 @@ module Flor
537
593
  # Delete pointers to gone nodes.
538
594
 
539
595
  dom = Flor.domain(exid)
596
+ u = @unit.identifier
540
597
 
541
598
  pointers =
542
599
  exe['nodes'].inject([]) { |a, (nid, node)|
543
600
  ts = node['tags']
544
- ts.each { |t| a << [ dom, exid, nid, 'tag', t, nil, now ] } if ts
601
+ ts.each { |t| a << [ dom, exid, nid, 'tag', t, nil, now, u ] } if ts
545
602
  a
546
603
  }
547
604
 
548
605
  pointers +=
549
606
  (exe['nodes']['0'] || { 'vars' => {} })['vars'].collect { |k, v|
550
607
  case v; when Integer, String, TrueClass, FalseClass
551
- [ dom, exid, '0', 'var', k, v.to_s, now ]
608
+ [ dom, exid, '0', 'var', k, v.to_s, now, u ]
552
609
  when NilClass
553
- [ dom, exid, '0', 'var', k, nil, now ]
610
+ [ dom, exid, '0', 'var', k, nil, now, u ]
554
611
  else
555
612
  nil
556
613
  end
@@ -558,21 +615,21 @@ module Flor
558
615
 
559
616
  pointers +=
560
617
  exe['tasks'].collect { |nid, v|
561
- [ dom, exid, nid, 'tasker', v['tasker'], v['name'], now ]
618
+ [ dom, exid, nid, 'tasker', v['tasker'], v['name'], now, u ]
562
619
  }
563
620
 
564
621
  cps = @db[:flor_pointers] # current pointers
565
622
  .where(exid: exid)
566
623
  .select(:nid, :type, :name)
567
624
  .all
568
- pointers.reject! { |_, _, ni, ty, na, _, _|
625
+ pointers.reject! { |_, _, ni, ty, na, _, _, _|
569
626
  cps.find { |cp| cp[:nid] == ni && cp[:type] == ty && cp[:name] == na } }
570
627
  #
571
628
  # don't insert when already inserted
572
629
 
573
630
  @db[:flor_pointers]
574
631
  .import(
575
- [ :domain, :exid, :nid, :type, :name, :value, :ctime ],
632
+ [ :domain, :exid, :nid, :type, :name, :value, :ctime, :cunit ],
576
633
  pointers)
577
634
  end
578
635
 
@@ -590,7 +647,7 @@ module Flor
590
647
  nil
591
648
  end
592
649
 
593
- def compute_next_time(type, string)
650
+ def compute_next_time(type, string, from=nil)
594
651
 
595
652
  f =
596
653
  case type
@@ -601,7 +658,7 @@ module Flor
601
658
  else Fugit.parse(string)
602
659
  end
603
660
 
604
- nt = f.is_a?(Time) ? f : f.next_time(Time.now) # local...
661
+ nt = f.is_a?(Time) ? f : f.next_time(from || Time.now) # local...
605
662
 
606
663
  Flor.tstamp(nt.utc)
607
664
  end