flor 0.9.5 → 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
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