datalackeytools 0.3.4
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/LICENSE.txt +17 -0
- data/bin/datalackey-make +1014 -0
- data/bin/datalackey-run +234 -0
- data/bin/datalackey-shell +1505 -0
- data/bin/datalackey-state +1005 -0
- data/bin/files2object +56 -0
- data/bin/object2files +44 -0
- data/lib/common.rb +9 -0
- data/lib/datalackeylib.rb +638 -0
- metadata +68 -0
@@ -0,0 +1,638 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Copyright © 2019-2021 Ismo Kärkkäinen
|
4
|
+
# Licensed under Universal Permissive License. See LICENSE.txt.
|
5
|
+
|
6
|
+
require 'json'
|
7
|
+
require 'open3'
|
8
|
+
|
9
|
+
class DatalackeyProcess
|
10
|
+
attr_reader :exit_code, :stdout, :stderr, :stdin, :executable
|
11
|
+
|
12
|
+
def initialize(exe, directory, permissions, memory)
|
13
|
+
@exit_code = 0
|
14
|
+
if exe.nil?
|
15
|
+
exe = DatalackeyProcess.locate_executable(
|
16
|
+
'datalackey', [ '/usr/local/libexec', '/usr/libexec' ])
|
17
|
+
raise ArgumentError, 'datalackey not found' if exe.nil?
|
18
|
+
elsif !File.exist?(exe) || !File.executable?(exe)
|
19
|
+
raise ArgumentError, "Executable not found or not executable: #{exe}"
|
20
|
+
end
|
21
|
+
@executable = exe
|
22
|
+
args = [ exe,
|
23
|
+
'--command-in', 'stdin', 'JSON', '--command-out', 'stdout', 'JSON' ]
|
24
|
+
args.push('--memory') unless memory.nil?
|
25
|
+
args.concat([ '--directory', directory ]) unless directory.nil?
|
26
|
+
args.concat([ '--permissions', permissions ]) unless permissions.nil?
|
27
|
+
@stdin, @stdout, @stderr, @wait_thread = Open3.popen3(*args)
|
28
|
+
end
|
29
|
+
|
30
|
+
def finish
|
31
|
+
@stdin.close
|
32
|
+
@wait_thread.join
|
33
|
+
@exit_code = @wait_thread.value.exitstatus
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def DatalackeyProcess.options_for_OptionParser(parser, separator,
|
38
|
+
exe_callable, mem_callable, dir_callable, perm_callable, echo_callable)
|
39
|
+
unless separator.nil?
|
40
|
+
unless separator.is_a? String
|
41
|
+
separator = 'Options for case where this process runs datalackey:'
|
42
|
+
end
|
43
|
+
parser.separator separator
|
44
|
+
end
|
45
|
+
unless exe_callable.nil?
|
46
|
+
parser.on('-l', '--lackey PROGRAM', 'Use specified datalackey executable.') do |e|
|
47
|
+
exe_callable.call(e)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
unless mem_callable.nil?
|
51
|
+
parser.on('-m', '--memory', 'Store data in memory.') do
|
52
|
+
mem_callable.call(true)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
unless dir_callable.nil?
|
56
|
+
parser.on('-d', '--directory [DIR]', 'Store data under (working) directory.') do |d|
|
57
|
+
dir_callable.call(d || Dir.pwd)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
unless perm_callable.nil?
|
61
|
+
parser.on('-p', '--permissions MODE', %i[user group other], 'File permissions cover (user, group, other).') do |p|
|
62
|
+
perm_callable.call({ user: '600', group: '660', other: '666' }[p])
|
63
|
+
end
|
64
|
+
end
|
65
|
+
unless echo_callable.nil?
|
66
|
+
parser.on('--echo', 'Echo communication with datalackey.') do
|
67
|
+
echo_callable.call(true)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def DatalackeyProcess.locate_executable(exe_name, dirs_outside_path = [])
|
73
|
+
# Absolute file name or found in current working directory.
|
74
|
+
return exe_name if File.exist?(exe_name) && File.executable?(exe_name)
|
75
|
+
dirs = []
|
76
|
+
dirs_outside_path = [ dirs_outside_path ] unless dirs_outside_path.is_a? Array
|
77
|
+
dirs.concat dirs_outside_path
|
78
|
+
dirs.concat ENV['PATH'].split(File::PATH_SEPARATOR)
|
79
|
+
dirs.each do |d|
|
80
|
+
exe = File.join(d, exe_name)
|
81
|
+
return exe if File.exist?(exe) && File.executable?(exe)
|
82
|
+
end
|
83
|
+
nil
|
84
|
+
end
|
85
|
+
|
86
|
+
def DatalackeyProcess.verify_directory_permissions_memory(
|
87
|
+
directory, permissions, memory)
|
88
|
+
if !memory.nil? && !(directory.nil? && permissions.nil?)
|
89
|
+
raise ArgumentError, 'Cannot use both memory and directory/permissions.'
|
90
|
+
end
|
91
|
+
if memory.nil?
|
92
|
+
if directory.nil?
|
93
|
+
directory = Dir.pwd
|
94
|
+
elsif !Dir.exist? directory
|
95
|
+
raise ArgumentError, "Given directory does not exist: #{directory}"
|
96
|
+
end
|
97
|
+
if permissions.nil?
|
98
|
+
if (File.umask & 0o77).zero?
|
99
|
+
permissions = '666'
|
100
|
+
elsif (File.umask & 0o70).zero?
|
101
|
+
permissions = '660'
|
102
|
+
else
|
103
|
+
permissions = '600'
|
104
|
+
end
|
105
|
+
elsif permissions != '600' && permissions != '660' && permissions != '666'
|
106
|
+
raise ArgumentError, 'Permissions not in {600, 660, 666}.'
|
107
|
+
end
|
108
|
+
end
|
109
|
+
[ directory, permissions, memory ]
|
110
|
+
end
|
111
|
+
|
112
|
+
|
113
|
+
class DatalackeyParentProcess
|
114
|
+
attr_reader :exit_code, :stdout, :stderr, :stdin, :executable
|
115
|
+
|
116
|
+
def initialize(to_lackey, from_lackey)
|
117
|
+
@exit_code = 0
|
118
|
+
@stdout = from_lackey
|
119
|
+
@stdin = to_lackey
|
120
|
+
@stderr = nil
|
121
|
+
@executable = nil
|
122
|
+
end
|
123
|
+
|
124
|
+
def finish
|
125
|
+
@stdin.close
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
|
130
|
+
# Intended to be used internaly when there are no patterns to act on.
|
131
|
+
# Instead of using this, pass nil to DatalakceyIO.send
|
132
|
+
class NoPatternNoAction
|
133
|
+
attr_reader :identifier
|
134
|
+
attr_accessor :exit, :command, :status, :message, :generators
|
135
|
+
|
136
|
+
def initialize
|
137
|
+
@exit = nil
|
138
|
+
@command = nil
|
139
|
+
@status = nil
|
140
|
+
@message = nil
|
141
|
+
@generators = []
|
142
|
+
end
|
143
|
+
|
144
|
+
def set_identifier(identifier)
|
145
|
+
@identifier = identifier
|
146
|
+
end
|
147
|
+
|
148
|
+
def best_match(_)
|
149
|
+
[ nil, [] ]
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
|
154
|
+
class PatternAction < NoPatternNoAction
|
155
|
+
def initialize(action_maps_array, message_callables = [])
|
156
|
+
raise ArgumentError, 'action_maps_array is empty' unless action_maps_array.is_a?(Array) && !action_maps_array.empty?
|
157
|
+
super()
|
158
|
+
@pattern2act = { }
|
159
|
+
@fixed2act = { }
|
160
|
+
@generators = message_callables.is_a?(Array) ? message_callables.clone : [ message_callables ]
|
161
|
+
action_maps_array.each do |m|
|
162
|
+
raise ArgumentError, 'Action map is not a map.' unless m.is_a? Hash
|
163
|
+
fill_pattern2action_maps([], m)
|
164
|
+
end
|
165
|
+
@pattern2act.each_value(&:uniq!)
|
166
|
+
@fixed2act.each_value(&:uniq!)
|
167
|
+
raise ArgumentError, 'No patterns.' if @pattern2act.empty? && @fixed2act.empty?
|
168
|
+
end
|
169
|
+
|
170
|
+
def fill_pattern2action_maps(actionlist, item)
|
171
|
+
if item.is_a? Array
|
172
|
+
unless item.first.is_a?(Array) || item.first.is_a?(Hash)
|
173
|
+
# item is a pattern.
|
174
|
+
raise ArgumentError, "Pattern-array must be under action: #{item}" if actionlist.empty?
|
175
|
+
wildcards = false
|
176
|
+
pattern = [ :identifier ]
|
177
|
+
item.each do |element|
|
178
|
+
case element
|
179
|
+
when '?'
|
180
|
+
wildcards = true
|
181
|
+
pattern.push :one
|
182
|
+
when '*'
|
183
|
+
wildcards = true
|
184
|
+
pattern.push :rest
|
185
|
+
break
|
186
|
+
else pattern.push element
|
187
|
+
end
|
188
|
+
end
|
189
|
+
tgt = wildcards ? @pattern2act : @fixed2act
|
190
|
+
tgt[pattern] = [] unless tgt.key? pattern
|
191
|
+
tgt[pattern].push actionlist
|
192
|
+
return
|
193
|
+
end
|
194
|
+
item.each { |sub| fill_pattern2action_maps(actionlist, sub) }
|
195
|
+
elsif item.is_a? Hash
|
196
|
+
item.each_pair do |action, sub|
|
197
|
+
acts = actionlist.clone
|
198
|
+
acts.push action
|
199
|
+
fill_pattern2action_maps(acts, sub)
|
200
|
+
end
|
201
|
+
else
|
202
|
+
raise ArgumentError, 'Item not a mapping, array, or pattern-array.'
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
def clone
|
207
|
+
gens = @generators
|
208
|
+
@generators = nil
|
209
|
+
copy = Marshal.load(Marshal.dump(self))
|
210
|
+
@generators = gens
|
211
|
+
copy.generators = gens.clone
|
212
|
+
copy
|
213
|
+
end
|
214
|
+
|
215
|
+
def replace_identifier(identifier, p2a)
|
216
|
+
altered = { }
|
217
|
+
p2a.each_pair do |pattern, a|
|
218
|
+
p = []
|
219
|
+
pattern.each { |item| p.push(item == :identifier ? identifier : item) }
|
220
|
+
altered[p] = a
|
221
|
+
end
|
222
|
+
altered
|
223
|
+
end
|
224
|
+
|
225
|
+
def set_identifier(identifier)
|
226
|
+
@pattern2act = replace_identifier(identifier, @pattern2act)
|
227
|
+
@fixed2act = replace_identifier(identifier, @fixed2act)
|
228
|
+
@identifier = identifier
|
229
|
+
end
|
230
|
+
|
231
|
+
def best_match(message_array)
|
232
|
+
return [ @fixed2act[message_array], [] ] if @fixed2act.key? message_array
|
233
|
+
best_length = 0
|
234
|
+
best = nil
|
235
|
+
best_vars = []
|
236
|
+
@pattern2act.each_pair do |pattern, act|
|
237
|
+
next if message_array.length + 1 < pattern.length
|
238
|
+
next if pattern.last != :rest && message_array.length != pattern.length
|
239
|
+
length = 0
|
240
|
+
exact_length = 0
|
241
|
+
found = true
|
242
|
+
vars = []
|
243
|
+
pattern.each_index do |idx|
|
244
|
+
if pattern[idx] == :rest
|
245
|
+
vars.concat message_array[idx...message_array.length]
|
246
|
+
break
|
247
|
+
end
|
248
|
+
if pattern[idx] == :one
|
249
|
+
vars.push message_array[idx]
|
250
|
+
length += 1
|
251
|
+
next
|
252
|
+
end
|
253
|
+
found = pattern[idx] == message_array[idx]
|
254
|
+
break unless found
|
255
|
+
exact_length += 1
|
256
|
+
end
|
257
|
+
next unless found
|
258
|
+
if best_length < exact_length
|
259
|
+
best_length = exact_length
|
260
|
+
best = act
|
261
|
+
best_vars = vars
|
262
|
+
elsif best_length < length
|
263
|
+
best_length = length
|
264
|
+
best = act
|
265
|
+
best_vars = vars
|
266
|
+
end
|
267
|
+
end
|
268
|
+
[ best, best_vars ]
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
|
273
|
+
class DatalackeyIO
|
274
|
+
@@internal_notification_map = {
|
275
|
+
error: {
|
276
|
+
user_id: [ 'error', 'identifier', '?' ],
|
277
|
+
format: [ 'error', 'format' ]
|
278
|
+
},
|
279
|
+
stored: [ 'data', 'stored', '?', '?' ],
|
280
|
+
deleted: [ 'data', 'deleted', '?', '?' ],
|
281
|
+
data_error: [ 'data', 'error', '?', '?' ],
|
282
|
+
started: [ 'process', 'started', '?', '?' ],
|
283
|
+
ended: [ 'process', 'ended', '?', '?' ]
|
284
|
+
}
|
285
|
+
|
286
|
+
@@internal_generic_map = {
|
287
|
+
error: {
|
288
|
+
syntax: [
|
289
|
+
[ 'error', 'missing', '*' ],
|
290
|
+
[ 'error', 'not-string', '*' ],
|
291
|
+
[ 'error', 'not-string-null', '*' ],
|
292
|
+
[ 'error', 'pairless', '*' ],
|
293
|
+
[ 'error', 'unexpected', '*' ],
|
294
|
+
[ 'error', 'unknown', '*' ],
|
295
|
+
[ 'error', 'command', 'missing', '?' ],
|
296
|
+
[ 'error', 'command', 'not-string', '?' ],
|
297
|
+
[ 'error', 'command', 'unknown', '?' ]
|
298
|
+
]
|
299
|
+
},
|
300
|
+
done: [ 'done', '' ],
|
301
|
+
child: [ 'run', 'running', '?' ]
|
302
|
+
}
|
303
|
+
|
304
|
+
def self.internal_notification_map
|
305
|
+
Marshal.load(Marshal.dump(@@internal_notification_map))
|
306
|
+
end
|
307
|
+
|
308
|
+
def self.internal_generic_map
|
309
|
+
Marshal.load(Marshal.dump(@@internal_generic_map))
|
310
|
+
end
|
311
|
+
|
312
|
+
attr_reader :syntax, :version
|
313
|
+
|
314
|
+
def initialize(to_datalackey, from_datalackey, notification_callable = nil,
|
315
|
+
to_datalackey_echo_callable = nil, from_datalackey_echo_callable = nil)
|
316
|
+
@to_datalackey_mutex = Mutex.new
|
317
|
+
@to_datalackey = to_datalackey
|
318
|
+
@to_datalackey_echo = to_datalackey_echo_callable
|
319
|
+
@from_datalackey = from_datalackey
|
320
|
+
@identifier = 0
|
321
|
+
@tracked_mutex = Mutex.new
|
322
|
+
# Handling of notifications.
|
323
|
+
@notify_tracker = PatternAction.new([ @@internal_notification_map ])
|
324
|
+
@notify_tracker.set_identifier(nil)
|
325
|
+
@internal = PatternAction.new([ @@internal_generic_map ])
|
326
|
+
@tracked = Hash.new(nil)
|
327
|
+
@waiting = nil
|
328
|
+
@return_mutex = Mutex.new
|
329
|
+
@return_condition = ConditionVariable.new
|
330
|
+
@dataprocess_mutex = Mutex.new
|
331
|
+
@data = Hash.new(0)
|
332
|
+
@process = { }
|
333
|
+
@children = { }
|
334
|
+
@version = { }
|
335
|
+
@read_datalackey = Thread.new do
|
336
|
+
accum = []
|
337
|
+
loop do
|
338
|
+
begin
|
339
|
+
raw = @from_datalackey.readpartial(32768)
|
340
|
+
rescue IOError
|
341
|
+
break
|
342
|
+
rescue EOFError
|
343
|
+
break
|
344
|
+
end
|
345
|
+
loc = raw.index("\n")
|
346
|
+
until loc.nil?
|
347
|
+
accum.push(raw[0, loc]) if loc.positive? # Newline at start ends line.
|
348
|
+
raw = raw[loc + 1, raw.size - loc - 1]
|
349
|
+
loc = raw.index("\n")
|
350
|
+
joined = accum.join
|
351
|
+
accum.clear
|
352
|
+
next if joined.empty?
|
353
|
+
from_datalackey_echo_callable.call(joined) unless from_datalackey_echo_callable.nil?
|
354
|
+
msg = JSON.parse joined
|
355
|
+
# See if we are interested in it.
|
356
|
+
if msg.first.nil?
|
357
|
+
act, vars = @notify_tracker.best_match(msg)
|
358
|
+
next if act.nil?
|
359
|
+
# We know there is only one action that matches.
|
360
|
+
act = act.first
|
361
|
+
actionable = nil
|
362
|
+
name = vars.first
|
363
|
+
id = vars.last
|
364
|
+
# Messages from different threads may arrive out of order so
|
365
|
+
# new data/process may be in book-keeping when previous should
|
366
|
+
# be removed. With data these imply over-writing immediately,
|
367
|
+
# with processes re-use of identifier and running back to back.
|
368
|
+
case act.first
|
369
|
+
when :stored
|
370
|
+
@dataprocess_mutex.synchronize do
|
371
|
+
if @data[name] < id
|
372
|
+
@data[name] = id
|
373
|
+
actionable = act
|
374
|
+
end
|
375
|
+
end
|
376
|
+
when :deleted
|
377
|
+
@dataprocess_mutex.synchronize do
|
378
|
+
if @data.key?(name) && @data[name] <= id
|
379
|
+
@data.delete name
|
380
|
+
actionable = act
|
381
|
+
end
|
382
|
+
end
|
383
|
+
when :data_error
|
384
|
+
@dataprocess_mutex.synchronize do
|
385
|
+
@data.delete(name) if @data[name] == id
|
386
|
+
end
|
387
|
+
actionable = act
|
388
|
+
when :started
|
389
|
+
@dataprocess_mutex.synchronize { @process[name] = id }
|
390
|
+
actionable = act
|
391
|
+
when :ended
|
392
|
+
@dataprocess_mutex.synchronize do
|
393
|
+
if @process[name] == id
|
394
|
+
@process.delete(name)
|
395
|
+
@children.delete(name)
|
396
|
+
end
|
397
|
+
end
|
398
|
+
actionable = act
|
399
|
+
when :error
|
400
|
+
case act[1]
|
401
|
+
when :format
|
402
|
+
@to_datalackey_mutex.synchronize { @to_datalackey.putc 0 }
|
403
|
+
when :user_id
|
404
|
+
unless @waiting.nil?
|
405
|
+
# Does the waited command have invalid id?
|
406
|
+
begin
|
407
|
+
int = Integer(@waiting)
|
408
|
+
fract = @waiting - int
|
409
|
+
raise ArgumentError, '' unless fract.zero?
|
410
|
+
rescue ArgumentError, TypeError
|
411
|
+
unless @waiting.is_a? String
|
412
|
+
@tracked_mutex.synchronize do
|
413
|
+
trackers = @tracked[@waiting]
|
414
|
+
trackers.first.message = msg
|
415
|
+
trackers.first.exit = [ act ]
|
416
|
+
@tracked.delete(@waiting)
|
417
|
+
@waiting = nil
|
418
|
+
end
|
419
|
+
@return_mutex.synchronize { @return_condition.signal }
|
420
|
+
end
|
421
|
+
end
|
422
|
+
end
|
423
|
+
end
|
424
|
+
actionable = act
|
425
|
+
end
|
426
|
+
next if notification_callable.nil? || actionable.nil?
|
427
|
+
notification_callable.call(actionable, msg, vars)
|
428
|
+
next
|
429
|
+
end
|
430
|
+
# Not a notification.
|
431
|
+
trackers = @tracked_mutex.synchronize { @tracked[msg[0]] }
|
432
|
+
next if trackers.nil?
|
433
|
+
finish = false
|
434
|
+
last = nil
|
435
|
+
# Deal with user-provided PatternAction (or NoPatternNoAction).
|
436
|
+
tracker = trackers.first
|
437
|
+
act, vars = tracker.best_match(msg)
|
438
|
+
unless act.nil?
|
439
|
+
act.each do |item|
|
440
|
+
tracker.generators.each do |p|
|
441
|
+
break if p.call(item, msg, vars)
|
442
|
+
end
|
443
|
+
next unless msg.first == @waiting
|
444
|
+
case item.first
|
445
|
+
when :return, 'return'
|
446
|
+
finish = true
|
447
|
+
last = act if last.nil?
|
448
|
+
when :error, 'error'
|
449
|
+
finish = true
|
450
|
+
last = act
|
451
|
+
end
|
452
|
+
end
|
453
|
+
end
|
454
|
+
# Check internal PatternAction.
|
455
|
+
internal = trackers.last
|
456
|
+
act, vars = internal.best_match(msg)
|
457
|
+
unless act.nil?
|
458
|
+
act = act.first # We know patterns are all unique in mapping.
|
459
|
+
if act.first == :child
|
460
|
+
@dataprocess_mutex.synchronize { @children[msg[0]] = vars.first }
|
461
|
+
elsif msg.first == @waiting
|
462
|
+
finish = true
|
463
|
+
if act.first == :done
|
464
|
+
@tracked_mutex.synchronize { @tracked.delete(msg[0]) }
|
465
|
+
elsif act.first == :error
|
466
|
+
last = [ act ]
|
467
|
+
end
|
468
|
+
end
|
469
|
+
end
|
470
|
+
if finish
|
471
|
+
tracker.message = msg
|
472
|
+
tracker.exit = last
|
473
|
+
@tracked_mutex.synchronize { @waiting = nil }
|
474
|
+
@return_mutex.synchronize { @return_condition.signal }
|
475
|
+
end
|
476
|
+
end
|
477
|
+
accum.push(raw) unless raw.empty?
|
478
|
+
end
|
479
|
+
@from_datalackey.close
|
480
|
+
@return_mutex.synchronize { @return_condition.signal }
|
481
|
+
end
|
482
|
+
# Outside thread block.
|
483
|
+
send(PatternAction.new([{ version: [ 'version', '', '?' ] }], [
|
484
|
+
proc do |action, message, vars|
|
485
|
+
if action.first == :version
|
486
|
+
@syntax = vars.first['commands']
|
487
|
+
@version = { }
|
488
|
+
vars.first.each_pair do |key, value|
|
489
|
+
@version[key] = value if value.is_a? Integer
|
490
|
+
end
|
491
|
+
true
|
492
|
+
else false
|
493
|
+
end
|
494
|
+
end
|
495
|
+
]), ['version'])
|
496
|
+
end
|
497
|
+
|
498
|
+
def data
|
499
|
+
@dataprocess_mutex.synchronize { return @data.clone }
|
500
|
+
end
|
501
|
+
|
502
|
+
def process
|
503
|
+
@dataprocess_mutex.synchronize { return @process.clone }
|
504
|
+
end
|
505
|
+
|
506
|
+
def launched
|
507
|
+
@dataprocess_mutex.synchronize { return @children.clone }
|
508
|
+
end
|
509
|
+
|
510
|
+
def closed?
|
511
|
+
@from_datalackey.closed?
|
512
|
+
end
|
513
|
+
|
514
|
+
def close
|
515
|
+
@to_datalackey_mutex.synchronize { @to_datalackey.close }
|
516
|
+
end
|
517
|
+
|
518
|
+
def finish
|
519
|
+
@read_datalackey.join
|
520
|
+
end
|
521
|
+
|
522
|
+
# Pass nil pattern_action if you are not interested in doing anything.
|
523
|
+
def send(pattern_action, command, user_id = false)
|
524
|
+
return nil if @to_datalackey_mutex.synchronize { @to_datalackey.closed? }
|
525
|
+
if user_id
|
526
|
+
id = command[0]
|
527
|
+
else
|
528
|
+
id = @identifier
|
529
|
+
@identifier += 1
|
530
|
+
command.prepend id
|
531
|
+
end
|
532
|
+
tracker = pattern_action.nil? ? NoPatternNoAction.new : pattern_action.clone
|
533
|
+
tracker.set_identifier(id)
|
534
|
+
tracker.command = JSON.generate(command)
|
535
|
+
internal = @internal.clone
|
536
|
+
internal.set_identifier(id)
|
537
|
+
@tracked_mutex.synchronize do
|
538
|
+
@tracked[id] = [ tracker, internal ] unless id.nil?
|
539
|
+
@waiting = id
|
540
|
+
end
|
541
|
+
dump(tracker.command)
|
542
|
+
return tracker if id.nil? # There will be no responses.
|
543
|
+
@return_mutex.synchronize { @return_condition.wait(@return_mutex) }
|
544
|
+
tracker.status = true
|
545
|
+
unless tracker.exit.nil?
|
546
|
+
tracker.exit.each do |item|
|
547
|
+
tracker.status = false if item.first == :error || item.first == 'error'
|
548
|
+
end
|
549
|
+
end
|
550
|
+
tracker
|
551
|
+
end
|
552
|
+
|
553
|
+
def dump(json_as_string)
|
554
|
+
@to_datalackey_mutex.synchronize do
|
555
|
+
@to_datalackey.write json_as_string
|
556
|
+
@to_datalackey.flush
|
557
|
+
@to_datalackey_echo.call(json_as_string) unless @to_datalackey_echo.nil?
|
558
|
+
rescue Errno::EPIPE
|
559
|
+
# Should do something in this case. Child process died?
|
560
|
+
end
|
561
|
+
end
|
562
|
+
|
563
|
+
def verify(command)
|
564
|
+
@syntax.nil? ? nil : true
|
565
|
+
end
|
566
|
+
end
|
567
|
+
|
568
|
+
|
569
|
+
class StoringReader
|
570
|
+
def initialize(input)
|
571
|
+
@input = input
|
572
|
+
@output_mutex = Mutex.new
|
573
|
+
@output = [] # Contains list of lines from input.
|
574
|
+
@reader = Thread.new do
|
575
|
+
accum = []
|
576
|
+
loop do
|
577
|
+
begin
|
578
|
+
raw = @input.readpartial(32768)
|
579
|
+
rescue IOError
|
580
|
+
break # It is possible that close happens in another thread.
|
581
|
+
rescue EOFError
|
582
|
+
break
|
583
|
+
end
|
584
|
+
loc = raw.index("\n")
|
585
|
+
until loc.nil?
|
586
|
+
accum.push(raw[0, loc]) if loc.positive? # Newline begins?
|
587
|
+
unless accum.empty?
|
588
|
+
@output_mutex.synchronize { @output.push(accum.join) }
|
589
|
+
accum.clear
|
590
|
+
end
|
591
|
+
raw = raw[loc + 1, raw.size - loc - 1]
|
592
|
+
loc = raw.index("\n")
|
593
|
+
end
|
594
|
+
accum.push(raw) unless raw.empty?
|
595
|
+
end
|
596
|
+
end
|
597
|
+
end
|
598
|
+
|
599
|
+
def close
|
600
|
+
@input.close
|
601
|
+
@reader.join
|
602
|
+
end
|
603
|
+
|
604
|
+
def getlines
|
605
|
+
@output_mutex.synchronize do
|
606
|
+
result = @output
|
607
|
+
@output = []
|
608
|
+
return result
|
609
|
+
end
|
610
|
+
end
|
611
|
+
end
|
612
|
+
|
613
|
+
|
614
|
+
class DiscardReader
|
615
|
+
def initialize(input)
|
616
|
+
@input = input
|
617
|
+
return if input.nil?
|
618
|
+
@reader = Thread.new do
|
619
|
+
loop do
|
620
|
+
@input.readpartial(32768)
|
621
|
+
rescue IOError
|
622
|
+
break # It is possible that close happens in another thread.
|
623
|
+
rescue EOFError
|
624
|
+
break
|
625
|
+
end
|
626
|
+
end
|
627
|
+
end
|
628
|
+
|
629
|
+
def close
|
630
|
+
return if @input.nil?
|
631
|
+
@input.close
|
632
|
+
@reader.join
|
633
|
+
end
|
634
|
+
|
635
|
+
def getlines
|
636
|
+
[]
|
637
|
+
end
|
638
|
+
end
|
metadata
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: datalackeytools
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.3.4
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ismo Kärkkäinen
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-10-15 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: |
|
14
|
+
Tools for using datalackey.
|
15
|
+
|
16
|
+
For examples of use, see https://github.com/ismo-karkkainen/datalackeytools
|
17
|
+
directory examples.
|
18
|
+
|
19
|
+
Requires separate datalackey executable installed into
|
20
|
+
/usr/local/libexec, /usr/libexec, or into a directory in $PATH.
|
21
|
+
|
22
|
+
Datalackey: https://github.com/ismo-karkkainen/datalackey
|
23
|
+
|
24
|
+
Licensed under Universal Permissive License, see LICENSE.txt.
|
25
|
+
email: ismokarkkainen@icloud.com
|
26
|
+
executables:
|
27
|
+
- datalackey-make
|
28
|
+
- datalackey-run
|
29
|
+
- datalackey-shell
|
30
|
+
- datalackey-state
|
31
|
+
- files2object
|
32
|
+
- object2files
|
33
|
+
extensions: []
|
34
|
+
extra_rdoc_files: []
|
35
|
+
files:
|
36
|
+
- LICENSE.txt
|
37
|
+
- bin/datalackey-make
|
38
|
+
- bin/datalackey-run
|
39
|
+
- bin/datalackey-shell
|
40
|
+
- bin/datalackey-state
|
41
|
+
- bin/files2object
|
42
|
+
- bin/object2files
|
43
|
+
- lib/common.rb
|
44
|
+
- lib/datalackeylib.rb
|
45
|
+
homepage: https://xn--ismo-krkkinen-gfbd.fi/datalackeytools/index.html
|
46
|
+
licenses:
|
47
|
+
- UPL-1.0
|
48
|
+
metadata: {}
|
49
|
+
post_install_message:
|
50
|
+
rdoc_options: []
|
51
|
+
require_paths:
|
52
|
+
- lib
|
53
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: '0'
|
58
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
63
|
+
requirements: []
|
64
|
+
rubygems_version: 3.1.2
|
65
|
+
signing_key:
|
66
|
+
specification_version: 4
|
67
|
+
summary: Tools for using datalackey.
|
68
|
+
test_files: []
|