bull 0.0.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/bin/bull +8 -0
- data/lib/bull/bcaptcha.rb +0 -0
- data/lib/bull/client.rb +280 -0
- data/lib/bull/encode_times.rb +35 -0
- data/lib/bull/mrelogin.rb +8 -0
- data/lib/bull/mreport.rb +26 -0
- data/lib/bull/notification.rb +87 -0
- data/lib/bull/reactive_var.rb +94 -0
- data/lib/bull/server.rb +468 -0
- data/lib/bull/start.rb +43 -0
- data/lib/bull/symbolize.rb +10 -0
- data/lib/bull/ui_common.rb +18 -0
- data/lib/bull/ui_core.rb +796 -0
- data/lib/bull/utils.rb +45 -0
- data/template/client/main.rb +8 -0
- metadata +115 -0
@@ -0,0 +1,94 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
class RVar
|
4
|
+
|
5
|
+
attr_reader :value
|
6
|
+
@@ticket = 0
|
7
|
+
@@group = nil
|
8
|
+
@@backup = []
|
9
|
+
|
10
|
+
def initialize value
|
11
|
+
@value = value
|
12
|
+
@blocks = {}
|
13
|
+
@forms = Set.new
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.raise_if_dirty
|
17
|
+
@@group = Set.new
|
18
|
+
@@backup = []
|
19
|
+
raised = false
|
20
|
+
begin
|
21
|
+
yield
|
22
|
+
rescue
|
23
|
+
@@backup.each do |v|
|
24
|
+
v.call
|
25
|
+
end
|
26
|
+
raised = true
|
27
|
+
raise
|
28
|
+
#else
|
29
|
+
# @@group.each do |blk|
|
30
|
+
# blk.call
|
31
|
+
# end
|
32
|
+
ensure
|
33
|
+
if !raised
|
34
|
+
@@group.each do |blk|
|
35
|
+
blk.call
|
36
|
+
end
|
37
|
+
end
|
38
|
+
@@group = nil
|
39
|
+
@@backup = []
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.rgrouping
|
44
|
+
@@group = Set.new
|
45
|
+
yield
|
46
|
+
@@group.each {|blk| blk.call}
|
47
|
+
@@group = nil
|
48
|
+
@@backup = []
|
49
|
+
end
|
50
|
+
|
51
|
+
def value= value
|
52
|
+
if value != @value
|
53
|
+
if @@group.nil?
|
54
|
+
@value = value
|
55
|
+
@blocks.each_value {|b| b.call}
|
56
|
+
else
|
57
|
+
@forms.each { |form| raise Exception.new('dirty form') if form.dirty?}
|
58
|
+
old_value = @value
|
59
|
+
@value = value
|
60
|
+
@blocks.each_value {|b| @@group.add b; @@backup << lambda{@value = old_value}}
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def add block
|
66
|
+
id = @@ticket
|
67
|
+
@@ticket += 1
|
68
|
+
@blocks[id] = block
|
69
|
+
id
|
70
|
+
end
|
71
|
+
|
72
|
+
def remove id
|
73
|
+
@blocks.delete id
|
74
|
+
end
|
75
|
+
|
76
|
+
def add_form form
|
77
|
+
@forms.add form
|
78
|
+
end
|
79
|
+
|
80
|
+
def remove_form form
|
81
|
+
@forms.delete form
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def reactive(*args, &block)
|
86
|
+
ret = {}
|
87
|
+
args.each do |v|
|
88
|
+
id = v.add(block)
|
89
|
+
ret[id] = v
|
90
|
+
end
|
91
|
+
block.call
|
92
|
+
ret
|
93
|
+
end
|
94
|
+
|
data/lib/bull/server.rb
ADDED
@@ -0,0 +1,468 @@
|
|
1
|
+
#$LOAD_PATH.unshift '..'
|
2
|
+
require 'eventmachine'
|
3
|
+
require 'json'
|
4
|
+
require 'time'
|
5
|
+
require 'bcrypt'
|
6
|
+
require_relative 'encode_times' #..
|
7
|
+
require_relative 'symbolize' #..
|
8
|
+
require 'em-http-request'
|
9
|
+
require 'logger'
|
10
|
+
require 'fiber'
|
11
|
+
#require 'liquid'
|
12
|
+
require_relative 'mreport'
|
13
|
+
|
14
|
+
class EMLogger < Logger
|
15
|
+
def initialize(file, count: 1, size: 1024000, level: Logger::DEBUG)
|
16
|
+
super file, count, size
|
17
|
+
@level = level
|
18
|
+
end
|
19
|
+
|
20
|
+
def error msg
|
21
|
+
#puts 'error:', msg
|
22
|
+
EventMachine.defer(proc {super msg})
|
23
|
+
end
|
24
|
+
|
25
|
+
def info msg
|
26
|
+
#puts 'info:', msg
|
27
|
+
EventMachine.defer(proc {super msg})
|
28
|
+
end
|
29
|
+
|
30
|
+
def debug msg
|
31
|
+
#puts 'debug:', msg
|
32
|
+
EventMachine.defer(proc {super msg})
|
33
|
+
end
|
34
|
+
|
35
|
+
def warn msg
|
36
|
+
#puts 'warn:', msg
|
37
|
+
EventMachine.defer(proc {super msg})
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
module Logging
|
42
|
+
def logger
|
43
|
+
Logging.logger
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.logger
|
47
|
+
@logger ||= EMLogger.new(File.join(File.expand_path(File.dirname(__FILE__)), 'log', 'log.txt'), count: 10, size: 1024000, level: Logger::DEBUG)
|
48
|
+
end
|
49
|
+
|
50
|
+
def stdout_logger
|
51
|
+
Logging.stdout_logger
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.stdout_logger
|
55
|
+
@stdout_logger ||= EMLogger.new(STDOUT, level: Logger::DEBUG)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
#module Bull
|
60
|
+
class BullServerController
|
61
|
+
|
62
|
+
include Logging
|
63
|
+
include MReport
|
64
|
+
|
65
|
+
def initialize(ws, conn)
|
66
|
+
@ws = ws
|
67
|
+
@conn = conn
|
68
|
+
@watch = {}
|
69
|
+
@user_id = nil
|
70
|
+
@user_doc = nil
|
71
|
+
@root = Fiber.current
|
72
|
+
end
|
73
|
+
|
74
|
+
def notify(msg)
|
75
|
+
msg = JSON.parse msg
|
76
|
+
#logger.info msg
|
77
|
+
stdout_logger.info msg
|
78
|
+
command = msg['command']
|
79
|
+
kwargs = symbolize_keys(msg['kwargs'])
|
80
|
+
resolve_times kwargs, msg['times']
|
81
|
+
|
82
|
+
if command.start_with? 'rpc_'
|
83
|
+
handle_rpc command, msg['id'], *msg['args'], **kwargs
|
84
|
+
elsif command.start_with? 'task_'
|
85
|
+
handle_task command, *msg['args'], **kwargs
|
86
|
+
elsif command.start_with? 'file_'
|
87
|
+
handle_file command, *msg['args'], **kwargs
|
88
|
+
elsif command.start_with? 'watch_'
|
89
|
+
handle_watch command, msg['id'], *msg['args'], **kwargs
|
90
|
+
elsif command == 'stop_watch'
|
91
|
+
handle_stop_watch msg['id']
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def close
|
96
|
+
@watch.each_value {|w| w.close}
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
def check arg, type
|
102
|
+
raise Exception.new("#{arg} is not a #{type}") if !arg.nil? && !arg.is_a?(type)
|
103
|
+
end
|
104
|
+
|
105
|
+
def get_unique pred #table, filter
|
106
|
+
count = rsync pred.count # $r.table(table).filter(filter).count
|
107
|
+
if count == 0
|
108
|
+
return nil #Hash.new
|
109
|
+
else
|
110
|
+
docs = rmsync pred #$r.table(table).filter(filter)
|
111
|
+
doc = docs[0]
|
112
|
+
doc['owner'] = owner? doc
|
113
|
+
return symbolize_keys doc
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def get_array predicate
|
118
|
+
ret = []
|
119
|
+
docs_with_count(predicate) do |count, row|
|
120
|
+
if count == 0
|
121
|
+
yield []
|
122
|
+
else
|
123
|
+
ret << symbolize_keys(row)
|
124
|
+
yield ret if ret.length == count
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def file_send id, predicate, keys
|
130
|
+
ret = ""
|
131
|
+
size = 0
|
132
|
+
total = 0
|
133
|
+
@ws.send({response: 'file', id: id, data: keys.join(';'), end: false, times: []}.to_json)
|
134
|
+
docs_with_count(predicate) do |count, row|
|
135
|
+
if count == 0
|
136
|
+
@ws.send({response: 'file', id: id, data: '', end: true, times: []}.to_json)
|
137
|
+
else
|
138
|
+
total += 1
|
139
|
+
size += 1
|
140
|
+
aux = keys.inject([]){|r, k| r << row[k]}
|
141
|
+
ret << aux.join(';') << "\n"
|
142
|
+
if total == count || size == 10
|
143
|
+
@ws.send({response: 'file', id: id, data: ret, end: total==count, times: []}.to_json)
|
144
|
+
size = 0
|
145
|
+
ret = ""
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def docs_with_count predicate
|
152
|
+
predicate.count.em_run(@conn) do |count|
|
153
|
+
if count == 0
|
154
|
+
yield 0, {}
|
155
|
+
else
|
156
|
+
predicate.em_run(@conn) do |doc|
|
157
|
+
doc['owner'] = owner? doc
|
158
|
+
yield count, doc
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def rpc_user_exist? user
|
165
|
+
check user, String
|
166
|
+
if user == ''
|
167
|
+
return true # false
|
168
|
+
else
|
169
|
+
count = rsync $r.table('user').filter(user: user).count
|
170
|
+
if count == 0
|
171
|
+
return false
|
172
|
+
else
|
173
|
+
return true
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def rpc_login user, password
|
179
|
+
check user, String
|
180
|
+
check password, String
|
181
|
+
count = rsync $r.table('user').filter(user: user).count
|
182
|
+
if count == 0
|
183
|
+
return false
|
184
|
+
else
|
185
|
+
response = rmsync $r.table('user').filter(user: user)
|
186
|
+
response = response[0]
|
187
|
+
pass = response['password']
|
188
|
+
pass = BCrypt::Password.new(pass)
|
189
|
+
if response['secondary_password']
|
190
|
+
secondary_password = response['secondary_password']
|
191
|
+
secondary_password = BCrypt::Password.new(secondary_password)
|
192
|
+
end
|
193
|
+
if pass == password || (response['secondary_password'] && pass == secondary_password)
|
194
|
+
@user_id = user
|
195
|
+
@user_doc = response
|
196
|
+
@roles = response['roles']
|
197
|
+
return response['roles']
|
198
|
+
else
|
199
|
+
return false
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
def rpc_change_password new_password
|
205
|
+
check new_password, String
|
206
|
+
pass = BCrypt::Password.new(new_password)
|
207
|
+
ret = rsync $r.table('user').filter(user: @user_id).update(password: pass, secondary_password: nil)
|
208
|
+
ret['replaced']
|
209
|
+
#$r.table('user').filter(user: @user_id).update(password: pass, secondary_password: nil).em_run(@conn){|ret| yield ret['replaced']}
|
210
|
+
end
|
211
|
+
|
212
|
+
def task_send_code_to_email user, answer
|
213
|
+
check user, String
|
214
|
+
check answer, String
|
215
|
+
if test_answer(answer) && !@email_code
|
216
|
+
code = ('a'..'z').to_a.sample(8).join
|
217
|
+
@email_code = code
|
218
|
+
puts code
|
219
|
+
t = reports['mail_code_new_user']
|
220
|
+
html = t.render('code' => code)
|
221
|
+
body = {to: user, subject: 'code', html: html, from: $from}
|
222
|
+
EventMachine::HttpRequest.new($mail_key).post :body => body
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
def task_forgotten_password user
|
227
|
+
check user, String
|
228
|
+
secondary_password = ('a'..'z').to_a.sample(8).join
|
229
|
+
puts secondary_password
|
230
|
+
t = reports['mail_forgotten_password']
|
231
|
+
html = t.render('password' => secondary_password)
|
232
|
+
body = {to: user, subject: 'new password', html: html, from: $from}
|
233
|
+
EventMachine::HttpRequest.new($mail_key).post :body => body
|
234
|
+
pass = BCrypt::Password.new(secondary_password)
|
235
|
+
#$r.table('user').filter(user: user).update(secondary_password: pass).em_run(@conn){}
|
236
|
+
rsync $r.table('user').filter(user: user).update(secondary_password: pass)
|
237
|
+
end
|
238
|
+
|
239
|
+
def rpc_logout
|
240
|
+
close
|
241
|
+
@user_id = nil
|
242
|
+
@roles = nil
|
243
|
+
true
|
244
|
+
end
|
245
|
+
|
246
|
+
#def user_is_owner? doc
|
247
|
+
# doc[:owner] == @user_id
|
248
|
+
#end
|
249
|
+
|
250
|
+
def owner? doc
|
251
|
+
doc[:owner] == @user_id
|
252
|
+
end
|
253
|
+
|
254
|
+
def before_update_user old, new, merged
|
255
|
+
@roles.include? 'admin'
|
256
|
+
end
|
257
|
+
|
258
|
+
def i_timestamp! doc
|
259
|
+
doc[:i_timestamp] = Time.now
|
260
|
+
end
|
261
|
+
|
262
|
+
def u_timestamp! doc
|
263
|
+
doc[:u_timestamp] = Time.now
|
264
|
+
end
|
265
|
+
|
266
|
+
def owner! doc
|
267
|
+
doc[:owner] = @user_id
|
268
|
+
end
|
269
|
+
|
270
|
+
def rpc_insert(table, value:)
|
271
|
+
check table, String
|
272
|
+
new_val = value
|
273
|
+
new_val.delete :u_timestamp
|
274
|
+
new_val.delete :i_timestamp
|
275
|
+
new_val.delete :owner
|
276
|
+
new_val.delete :id
|
277
|
+
|
278
|
+
if !self.send('before_insert_'+table, new_val)
|
279
|
+
ret = nil
|
280
|
+
else
|
281
|
+
ret = rsync $r.table(table).insert(new_val)
|
282
|
+
ret = ret['generated_keys'][0]
|
283
|
+
self.send('after_insert_'+table, new_val) if respond_to?('after_insert_'+table) && !ret.nil?
|
284
|
+
end
|
285
|
+
ret
|
286
|
+
end
|
287
|
+
|
288
|
+
def rpc_delete(table, id)
|
289
|
+
check table, String
|
290
|
+
check id, String
|
291
|
+
doc = rsync $r.table(table).get(id)
|
292
|
+
doc = symbolize_keys doc
|
293
|
+
if doc.nil? || !respond_to?('before_delete_'+table) || !self.send('before_delete_'+table, doc)
|
294
|
+
ret = 0
|
295
|
+
else
|
296
|
+
ret = rsync $r.table(table).get(id).delete
|
297
|
+
ret = ret['deleted']
|
298
|
+
self.send('after_delete_'+table, doc) if respond_to?('after_delete_'+table) && ret == 1
|
299
|
+
end
|
300
|
+
ret
|
301
|
+
end
|
302
|
+
|
303
|
+
def rsync pred
|
304
|
+
fb = Fiber.current
|
305
|
+
pred.em_run(@conn) do |doc|
|
306
|
+
fb.transfer doc
|
307
|
+
end
|
308
|
+
@root.transfer
|
309
|
+
end
|
310
|
+
|
311
|
+
def rmsync pred
|
312
|
+
fb = Fiber.current
|
313
|
+
get_array(pred){|docs| fb.transfer docs}
|
314
|
+
@root.transfer
|
315
|
+
end
|
316
|
+
|
317
|
+
def rsync_ pred
|
318
|
+
helper = Fiber.new do |parent|
|
319
|
+
pred.em_run(@conn) do |doc|
|
320
|
+
parent.transfer doc
|
321
|
+
end
|
322
|
+
end
|
323
|
+
helper.transfer Fiber.current
|
324
|
+
end
|
325
|
+
|
326
|
+
def rmsync_ pred
|
327
|
+
helper = Fiber.new do |parent|
|
328
|
+
get_array(pred){|docs| parent.transfer docs}
|
329
|
+
end
|
330
|
+
helper.transfer Fiber.current
|
331
|
+
end
|
332
|
+
|
333
|
+
def rpc_update(table, id, value:)
|
334
|
+
check table, String
|
335
|
+
check id, String
|
336
|
+
value.delete :u_timestamp
|
337
|
+
value.delete :i_timestamp
|
338
|
+
value.delete :owner
|
339
|
+
value.delete :id
|
340
|
+
|
341
|
+
old_doc = rsync $r.table(table).get(id)
|
342
|
+
if old_doc.nil? || !respond_to?('before_update_'+table)
|
343
|
+
return 0
|
344
|
+
end
|
345
|
+
old_doc = symbolize_keys old_doc
|
346
|
+
merged = old_doc.merge(value)
|
347
|
+
if !self.send('before_update_'+table, old_doc, value, merged)
|
348
|
+
ret = 0
|
349
|
+
else
|
350
|
+
response = rsync $r.table(table).get(id).update(merged)
|
351
|
+
ret = response['replaced']
|
352
|
+
self.send('after_update_'+table, merged) if respond_to?('after_update_'+table) && ret == 1
|
353
|
+
end
|
354
|
+
ret
|
355
|
+
end
|
356
|
+
|
357
|
+
def handle_watch command, id, *args, **kwargs
|
358
|
+
if kwargs.empty?
|
359
|
+
w = self.send command, *args
|
360
|
+
else
|
361
|
+
w = self.send command, *args, **kwargs
|
362
|
+
end
|
363
|
+
return if !w
|
364
|
+
w = w.changes({include_initial: true})
|
365
|
+
#EventMachine.run do
|
366
|
+
@watch[id] = w.em_run(@conn) do |doc|
|
367
|
+
doc['owner'] = owner? doc
|
368
|
+
ret = {}
|
369
|
+
ret[:response] = 'watch'
|
370
|
+
ret[:id] = id
|
371
|
+
ret[:data] = doc
|
372
|
+
ret[:times] = times doc
|
373
|
+
#begin
|
374
|
+
@ws.send ret.to_json
|
375
|
+
#rescue
|
376
|
+
#end
|
377
|
+
end
|
378
|
+
#end
|
379
|
+
end
|
380
|
+
|
381
|
+
def handle_stop_watch id
|
382
|
+
check id, Integer
|
383
|
+
w = @watch[id]
|
384
|
+
if w
|
385
|
+
w.close
|
386
|
+
@watch.delete id
|
387
|
+
end
|
388
|
+
#@watch[id].close
|
389
|
+
#@watch.delete id
|
390
|
+
end
|
391
|
+
|
392
|
+
def handle_task command, *args, **kwargs
|
393
|
+
helper = Fiber.new do
|
394
|
+
begin
|
395
|
+
if kwargs.empty?
|
396
|
+
self.send(command, *args)
|
397
|
+
else
|
398
|
+
self.send(command, *args, **kwargs)
|
399
|
+
end
|
400
|
+
rescue Exception => e
|
401
|
+
#logger.debug e
|
402
|
+
stdout_logger.debug e
|
403
|
+
end
|
404
|
+
end
|
405
|
+
helper.transfer
|
406
|
+
end
|
407
|
+
|
408
|
+
def handle_rpc command, id, *args, **kwargs
|
409
|
+
helper = Fiber.new do
|
410
|
+
begin
|
411
|
+
if kwargs.empty?
|
412
|
+
v = self.send(command, *args)
|
413
|
+
else
|
414
|
+
v = self.send(command, *args, **kwargs)
|
415
|
+
end
|
416
|
+
@ws.send({response: 'rpc', id: id, result: v, times: times(v)}.to_json)
|
417
|
+
rescue Exception => e
|
418
|
+
#logger.debug e
|
419
|
+
stdout_logger.debug e
|
420
|
+
end
|
421
|
+
end
|
422
|
+
helper.transfer
|
423
|
+
end
|
424
|
+
|
425
|
+
def handle_file command, id, *args, **kwargs
|
426
|
+
helper = Fiber.new do
|
427
|
+
begin
|
428
|
+
if kwargs.empty?
|
429
|
+
predicate, keys = self.send(command, *args)
|
430
|
+
else
|
431
|
+
predicate, keys = self.send(command, *args, **kwargs)
|
432
|
+
end
|
433
|
+
file_send id, predicate, keys
|
434
|
+
rescue Exception => e
|
435
|
+
#logger.debug e
|
436
|
+
stdout_logger.debug e
|
437
|
+
end
|
438
|
+
end
|
439
|
+
helper.transfer
|
440
|
+
end
|
441
|
+
|
442
|
+
def get table, id, symbolize=true
|
443
|
+
if id.nil?
|
444
|
+
return nil # Hash.new # nil
|
445
|
+
else
|
446
|
+
doc = rsync $r.table(table).get(id)
|
447
|
+
doc['owner'] = owner? doc
|
448
|
+
if symbolize
|
449
|
+
return symbolize_keys doc
|
450
|
+
else
|
451
|
+
return doc
|
452
|
+
end
|
453
|
+
end
|
454
|
+
end
|
455
|
+
|
456
|
+
def times ret
|
457
|
+
if !ret.respond_to? :each_pair
|
458
|
+
if ret.instance_of? Time
|
459
|
+
['result']
|
460
|
+
else
|
461
|
+
[]
|
462
|
+
end
|
463
|
+
else
|
464
|
+
encode_times ret, ''
|
465
|
+
end
|
466
|
+
end
|
467
|
+
end
|
468
|
+
#end
|