sgfa 0.1.0 → 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/data/sgfa_web.css +37 -34
- data/lib/sgfa/binder.rb +205 -3
- data/lib/sgfa/binder_fs.rb +55 -20
- data/lib/sgfa/cli/binder.rb +248 -31
- data/lib/sgfa/cli/jacket.rb +206 -0
- data/lib/sgfa/entry.rb +96 -0
- data/lib/sgfa/error.rb +2 -2
- data/lib/sgfa/jacket.rb +314 -9
- data/lib/sgfa/jacket_fs.rb +18 -4
- data/lib/sgfa/state_fs.rb +33 -9
- data/lib/sgfa/store_s3.rb +119 -0
- data/lib/sgfa/web/base.rb +7 -0
- data/lib/sgfa/web/binder.rb +294 -85
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e56f45ef9e949c37978cee9dfc394769eeb6baa2
|
4
|
+
data.tar.gz: c4be3d1955e1700dcfa59c02a75cc6259c9ed3b0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5e888580f637df355364aad7e997e1d3c548693bf584f7eaedec7fdc3cefd72b195117d9d8a87073866f545f28ad11dc7cbfe5f4f2b5348a1522feea7e68cc25
|
7
|
+
data.tar.gz: 9df09e1204ae56856b24bf30ad6075be1bbad8728ae38bfde47487f21c01c313cbbeead33cf8e07c08d61e6e57d2f1630f1ab708c15d310bfaa289a69e8f9a0d
|
data/data/sgfa_web.css
CHANGED
@@ -119,7 +119,17 @@ div#sgfa_web table.list td.hash {
|
|
119
119
|
}
|
120
120
|
|
121
121
|
|
122
|
-
/* edit
|
122
|
+
/* edit */
|
123
|
+
div#sgfa_web form.edit input {
|
124
|
+
font-family: inherit;
|
125
|
+
font-size: inherit;
|
126
|
+
}
|
127
|
+
|
128
|
+
div#sgfa_web form.edit textarea {
|
129
|
+
font-family: inherit;
|
130
|
+
font-size: inherit;
|
131
|
+
}
|
132
|
+
|
123
133
|
div#sgfa_web form.edit fieldset {
|
124
134
|
width: 98%;
|
125
135
|
margin: auto;
|
@@ -130,40 +140,12 @@ div#sgfa_web form.edit fieldset legend {
|
|
130
140
|
font-weight: bold;
|
131
141
|
}
|
132
142
|
|
133
|
-
div#sgfa_web form.edit p {
|
134
|
-
clear: both;
|
135
|
-
padding: 3px;
|
136
|
-
}
|
137
|
-
|
138
143
|
div#sgfa_web form.edit label {
|
139
144
|
float: left;
|
140
145
|
width: 8em;
|
141
146
|
text-align: right;
|
142
147
|
}
|
143
148
|
|
144
|
-
/* edit tag table */
|
145
|
-
div#sgfa_web table.edit_tag th {
|
146
|
-
text-align: center;
|
147
|
-
text-decoration: underline;
|
148
|
-
}
|
149
|
-
|
150
|
-
div#sgfa_web table.edit_tag tr {
|
151
|
-
border: thick solid red;
|
152
|
-
}
|
153
|
-
|
154
|
-
|
155
|
-
/* edit file table */
|
156
|
-
div#sgfa_web table.edit_file th {
|
157
|
-
text-align: center;
|
158
|
-
text-decoration: underline;
|
159
|
-
}
|
160
|
-
|
161
|
-
div#sgfa_web table.edit_file td.fname {
|
162
|
-
min-width: 10em;
|
163
|
-
}
|
164
|
-
|
165
|
-
|
166
|
-
/* edit form */
|
167
149
|
div#sgfa_web form.edit input[type=submit] {
|
168
150
|
border: 1px solid grey;
|
169
151
|
border-radius: 5px;
|
@@ -172,22 +154,30 @@ div#sgfa_web form.edit input[type=submit] {
|
|
172
154
|
}
|
173
155
|
|
174
156
|
div#sgfa_web form.edit textarea {
|
175
|
-
font-family: inherit;
|
176
|
-
font-size: inherit;
|
177
157
|
width: 50em;
|
178
158
|
height: 15em;
|
179
159
|
}
|
180
160
|
|
181
161
|
div#sgfa_web form.edit input.title {
|
182
|
-
font-family: inherit;
|
183
|
-
font-size: inherit;
|
184
162
|
width: 50em;
|
185
163
|
}
|
186
164
|
|
165
|
+
div#sgfa_web form.edit input.attname {
|
166
|
+
width: 30em;
|
167
|
+
}
|
168
|
+
|
187
169
|
div#sgfa_web form.edit input.tag {
|
188
|
-
width:
|
170
|
+
width: 30em;
|
189
171
|
}
|
190
172
|
|
173
|
+
div#sgfa_web form.edit button.add_row {
|
174
|
+
border: 1px solid grey;
|
175
|
+
border-radius: 5px;
|
176
|
+
padding: 2px;
|
177
|
+
background: radial-gradient(rgb(245, 245, 245), rgb(200, 200, 200));
|
178
|
+
}
|
179
|
+
|
180
|
+
|
191
181
|
|
192
182
|
/* entry */
|
193
183
|
div#sgfa_web div.entry_left {
|
@@ -195,6 +185,17 @@ div#sgfa_web div.entry_left {
|
|
195
185
|
width: 80%;
|
196
186
|
}
|
197
187
|
|
188
|
+
div#sgfa_web div.mainbody div.pages {
|
189
|
+
width: 100%;
|
190
|
+
border-top: 2px solid;
|
191
|
+
}
|
192
|
+
|
193
|
+
div#sgfa_web div.mainbody div.tagname {
|
194
|
+
width: 100%;
|
195
|
+
font-size: 150%;
|
196
|
+
border-bottom: 2px solid;
|
197
|
+
}
|
198
|
+
|
198
199
|
div#sgfa_web div.mainbody div.title {
|
199
200
|
width: 100%;
|
200
201
|
font-size: 125%;
|
@@ -225,6 +226,7 @@ div#sgfa_web div.mainbody div.sidebar div.tags {
|
|
225
226
|
}
|
226
227
|
|
227
228
|
div#sgfa_web div.attach {
|
229
|
+
border-bottom: thin solid;
|
228
230
|
}
|
229
231
|
|
230
232
|
div#sgfa_web div.mainbody div.sidebar div.user {
|
@@ -235,6 +237,7 @@ div#sgfa_web div.hash {
|
|
235
237
|
width: 100%;
|
236
238
|
clear: both;
|
237
239
|
border-top: thin solid;
|
240
|
+
padding-bottom: 3px;
|
238
241
|
font-family: monospace;
|
239
242
|
font-size: 75%;
|
240
243
|
}
|
data/lib/sgfa/binder.rb
CHANGED
@@ -285,12 +285,28 @@ class Binder
|
|
285
285
|
# @param tag [String] Tag name
|
286
286
|
# @param offs [Integer] Offset to begin reading
|
287
287
|
# @param max [Integer] Maximum number of entries to read
|
288
|
-
|
288
|
+
# @param opts [Hash] Options hash
|
289
|
+
# @option opts [Boolean] :raw Get the raw entry when permissions allow
|
290
|
+
# @return [Array] \[ enum, rnum, hnum, time, title, num_tags, num_attach,
|
291
|
+
# \] Or, if opts[:raw], each Array item will consist of the Entry or,
|
292
|
+
# if user does not have read permission, the info array.
|
293
|
+
def read_tag(tr, tag, offs, max, opts={})
|
294
|
+
raw = opts[:raw]
|
289
295
|
_jacket(tr, 'read') do |jck|
|
290
296
|
size, ents = jck.read_tag(tag, offs, max)
|
291
297
|
lst = ents.map do |ent|
|
292
|
-
|
293
|
-
ent.
|
298
|
+
if raw
|
299
|
+
pl = ent.perms
|
300
|
+
begin
|
301
|
+
_perms(tr, pl)
|
302
|
+
item = ent
|
303
|
+
rescue Error::Permission
|
304
|
+
item = nil
|
305
|
+
end
|
306
|
+
end
|
307
|
+
item ||= [ent.entry, ent.revision, ent.history, ent.time, ent.title,
|
308
|
+
ent.tags.size, ent.attachments.size]
|
309
|
+
item
|
294
310
|
end
|
295
311
|
[size, lst]
|
296
312
|
end
|
@@ -398,9 +414,195 @@ class Binder
|
|
398
414
|
end # def write()
|
399
415
|
|
400
416
|
|
417
|
+
#########################################################
|
418
|
+
# @!group Backup
|
419
|
+
|
420
|
+
|
421
|
+
#####################################
|
422
|
+
# Push to a backup store
|
423
|
+
#
|
424
|
+
# @param bsto [Store] Backup store
|
425
|
+
# @param opts [Hash] Options
|
426
|
+
# @option opts [Hash] :log The log. Defaults to STDERR at warn level.
|
427
|
+
# @option opts [Hash] :prev Jacket id_hash to previously pushed max history
|
428
|
+
# @return [Hash] Jacket id_hash to max history backed up
|
429
|
+
def backup_push(bsto, opts={})
|
430
|
+
|
431
|
+
stat = {}
|
432
|
+
prev = opts[:prev] || {}
|
433
|
+
log = opts[:log]
|
434
|
+
if !log
|
435
|
+
log = Logger.new(STDERR)
|
436
|
+
log.level = Logger::WARN
|
437
|
+
end
|
438
|
+
|
439
|
+
# control jacket push
|
440
|
+
jcks = nil
|
441
|
+
_shared do
|
442
|
+
ctl = _jacket_open(0)
|
443
|
+
begin
|
444
|
+
min = (prev[@id_hash] || 0) + 1
|
445
|
+
log.info('Backup push control %s at %d' % [@id_hash, min])
|
446
|
+
stat[@id_hash] = ctl.backup(bsto, min_history: min, log: log)
|
447
|
+
ensure
|
448
|
+
ctl.close
|
449
|
+
end
|
450
|
+
jcks = @jackets.values
|
451
|
+
end
|
452
|
+
|
453
|
+
# all other jackets
|
454
|
+
jcks.each do |info|
|
455
|
+
jck = _jacket_open(info[:num])
|
456
|
+
begin
|
457
|
+
id = info[:id_hash]
|
458
|
+
min = (prev[id] || 0) + 1
|
459
|
+
log.info('Backup push jacket %s at %d' % [id, min])
|
460
|
+
stat[id] = jck.backup(bsto, min_history: min, log: log)
|
461
|
+
ensure
|
462
|
+
jck.close
|
463
|
+
end
|
464
|
+
end
|
465
|
+
|
466
|
+
return stat
|
467
|
+
end # def backup_push()
|
468
|
+
|
469
|
+
|
470
|
+
#####################################
|
471
|
+
# Pull from backup store
|
472
|
+
#
|
473
|
+
# @param bsto [Store] Backup store
|
474
|
+
# @param opts [Hash] Options
|
475
|
+
# @option opts [Hash] :log The log. Defaults to SDTERR at warn level
|
476
|
+
def backup_pull(bsto, opts={})
|
477
|
+
|
478
|
+
log = opts[:log]
|
479
|
+
if !log
|
480
|
+
log = Logger.new(STDERR)
|
481
|
+
log.level = Logger::WARN
|
482
|
+
end
|
483
|
+
|
484
|
+
@lock.do_ex do
|
485
|
+
|
486
|
+
# control jacket
|
487
|
+
ctl = _jacket_open(0)
|
488
|
+
begin
|
489
|
+
log.info('Backup pull control %s' % @id_hash)
|
490
|
+
ctl.restore(bsto, log: log)
|
491
|
+
log.info('Backup pull update cache state')
|
492
|
+
_update(ctl)
|
493
|
+
ensure
|
494
|
+
ctl.close()
|
495
|
+
end
|
496
|
+
_cache_write()
|
497
|
+
|
498
|
+
# all other jackets
|
499
|
+
@jackets.values.each do |info|
|
500
|
+
begin
|
501
|
+
jck = _jacket_open(info[:num])
|
502
|
+
rescue Error::NonExistent
|
503
|
+
log.info('Backup pull create jacket %s' % info[:id_hash])
|
504
|
+
_jacket_create_raw(info)
|
505
|
+
jck = _jacket_open(info[:num])
|
506
|
+
end
|
507
|
+
begin
|
508
|
+
log.info('Backup pull jacket %s' % info[:id_hash])
|
509
|
+
jck.restore(bsto, log: log)
|
510
|
+
ensure
|
511
|
+
jck.close
|
512
|
+
end
|
513
|
+
end
|
514
|
+
|
515
|
+
end
|
516
|
+
|
517
|
+
end # def backup_pull()
|
518
|
+
|
519
|
+
|
401
520
|
private
|
402
521
|
|
403
522
|
|
523
|
+
#####################################
|
524
|
+
# Update cache
|
525
|
+
def _update(ctl)
|
526
|
+
|
527
|
+
values = {}
|
528
|
+
users = {}
|
529
|
+
jackets = {}
|
530
|
+
|
531
|
+
ctl.read_list.each do |tag|
|
532
|
+
|
533
|
+
# values
|
534
|
+
if tag == 'values'
|
535
|
+
|
536
|
+
# process all values entries
|
537
|
+
offs = 0
|
538
|
+
while true
|
539
|
+
size, ary = ctl.read_tag(tag, offs, 2)
|
540
|
+
break if ary.empty?
|
541
|
+
ary.each do |ent|
|
542
|
+
info = _get_json(ent)
|
543
|
+
info.each do |val, sta|
|
544
|
+
next if values.has_key?(val)
|
545
|
+
values[val] = sta
|
546
|
+
end
|
547
|
+
end
|
548
|
+
offs += 2
|
549
|
+
end
|
550
|
+
|
551
|
+
# clear unset values
|
552
|
+
values.delete_if{ |val, sta| !sta.is_a?(String) }
|
553
|
+
|
554
|
+
# jacket
|
555
|
+
elsif /^jacket:/.match(tag)
|
556
|
+
size, ary = ctl.read_tag(tag, 0, 1)
|
557
|
+
info = _get_json(ary.first)
|
558
|
+
jackets[info['name']] = {
|
559
|
+
num: info['num'],
|
560
|
+
name: info['name'],
|
561
|
+
id_hash: info['id_hash'],
|
562
|
+
id_text: info['id_text'],
|
563
|
+
title: info['title'],
|
564
|
+
perms: info['perms'],
|
565
|
+
}
|
566
|
+
|
567
|
+
# user
|
568
|
+
elsif /^user:/.match(tag)
|
569
|
+
size, ary = ctl.read_tag(tag, 0, 1)
|
570
|
+
info = _get_json(ary.first)
|
571
|
+
name = info['name']
|
572
|
+
perms = info['perms']
|
573
|
+
users[name] = perms
|
574
|
+
|
575
|
+
end
|
576
|
+
end
|
577
|
+
|
578
|
+
@users = users
|
579
|
+
@values = values
|
580
|
+
@jackets = jackets
|
581
|
+
|
582
|
+
end # def _update()
|
583
|
+
|
584
|
+
|
585
|
+
#####################################
|
586
|
+
# Get json from a body
|
587
|
+
def _get_json(ent)
|
588
|
+
lines = ent.body.lines
|
589
|
+
st = lines.index{|li| li[0] == '{' || li[0] == '[' }
|
590
|
+
if !st || (lines[-1][0] != '}' && lines[-1][0] != ']')
|
591
|
+
puts ent.body.inspect
|
592
|
+
raise Error::Corrupt, 'Control jacket entry does not contain JSON'
|
593
|
+
end
|
594
|
+
json = lines[st..-1].join
|
595
|
+
|
596
|
+
info = nil
|
597
|
+
begin
|
598
|
+
info = JSON.parse(json)
|
599
|
+
rescue
|
600
|
+
raise Error::Corrupt, 'Control jacket entry JSON parse error'
|
601
|
+
end
|
602
|
+
return info
|
603
|
+
end # def _get_json()
|
604
|
+
|
605
|
+
|
404
606
|
#####################################
|
405
607
|
# Shared creation stuff
|
406
608
|
#
|
data/lib/sgfa/binder_fs.rb
CHANGED
@@ -26,16 +26,15 @@ module Sgfa
|
|
26
26
|
class BinderFs < Binder
|
27
27
|
|
28
28
|
#####################################
|
29
|
-
# Create a
|
29
|
+
# Create a raw binder
|
30
|
+
#
|
31
|
+
# @note You almost certainly want to use {#create} not this method
|
30
32
|
#
|
31
33
|
# @param path [String] Path to the binder
|
32
|
-
# @param
|
33
|
-
# @
|
34
|
-
|
35
|
-
|
36
|
-
raise Error::Sanity, 'Binder already open' if @path
|
37
|
-
Binder.limits_create(init)
|
38
|
-
|
34
|
+
# @param id_text [String] The text ID
|
35
|
+
# @return [String] The hash ID
|
36
|
+
def create_raw(path, id_text)
|
37
|
+
|
39
38
|
# create directory
|
40
39
|
begin
|
41
40
|
Dir.mkdir(path)
|
@@ -45,8 +44,45 @@ class BinderFs < Binder
|
|
45
44
|
|
46
45
|
# create control binder
|
47
46
|
dn_ctrl = File.join(path, '0')
|
48
|
-
id_hash = JacketFs.create(dn_ctrl,
|
49
|
-
|
47
|
+
id_hash = JacketFs.create(dn_ctrl, id_text)
|
48
|
+
|
49
|
+
# write info
|
50
|
+
info = {
|
51
|
+
'sgfa_binder_ver' => 1,
|
52
|
+
'id_hash' => id_hash,
|
53
|
+
'id_text' => id_text,
|
54
|
+
}
|
55
|
+
json = JSON.pretty_generate(info) + "\n"
|
56
|
+
fn_info = File.join(path, 'sgfa_binder.json')
|
57
|
+
File.open(fn_info, 'w', :encoding => 'utf-8'){|fi| fi.write json }
|
58
|
+
|
59
|
+
# create empty cache
|
60
|
+
@path = path
|
61
|
+
@id_hash = id_hash
|
62
|
+
@jackets = {}
|
63
|
+
@users = {}
|
64
|
+
@values = {}
|
65
|
+
_cache_write
|
66
|
+
@path = nil
|
67
|
+
@id_hash = nil
|
68
|
+
|
69
|
+
return id_hash
|
70
|
+
end # def create_raw()
|
71
|
+
|
72
|
+
|
73
|
+
#####################################
|
74
|
+
# Create a new binder
|
75
|
+
#
|
76
|
+
# @param path [String] Path to the binder
|
77
|
+
# @param tr (see Binder#jacket_create)
|
78
|
+
# @param init [Hash] The binder initialization options
|
79
|
+
# @return [String] The hash ID of the new binder
|
80
|
+
def create(path, tr, init)
|
81
|
+
raise Error::Sanity, 'Binder already open' if @path
|
82
|
+
Binder.limits_create(init)
|
83
|
+
|
84
|
+
# create completely empty binder
|
85
|
+
id_hash = create_raw(path, init[:id_text])
|
50
86
|
|
51
87
|
# do the common creation
|
52
88
|
@path = path
|
@@ -54,22 +90,13 @@ class BinderFs < Binder
|
|
54
90
|
@jackets = {}
|
55
91
|
@users = {}
|
56
92
|
@values = {}
|
93
|
+
jck = _jacket_open(0)
|
57
94
|
_create(jck, tr, init)
|
58
95
|
jck.close
|
59
96
|
_cache_write
|
60
97
|
@path = nil
|
61
98
|
@id_hash = nil
|
62
99
|
|
63
|
-
# write info
|
64
|
-
info = {
|
65
|
-
'sgfa_binder_ver' => 1,
|
66
|
-
'id_hash' => id_hash,
|
67
|
-
'id_text' => init[:id_text],
|
68
|
-
}
|
69
|
-
json = JSON.pretty_generate(info) + "\n"
|
70
|
-
fn_info = File.join(path, 'sgfa_binder.json')
|
71
|
-
File.open(fn_info, 'w', :encoding => 'utf-8'){|fi| fi.write json }
|
72
|
-
|
73
100
|
return id_hash
|
74
101
|
end # def create()
|
75
102
|
|
@@ -143,6 +170,14 @@ class BinderFs < Binder
|
|
143
170
|
end # def _jacket_create()
|
144
171
|
|
145
172
|
|
173
|
+
#####################################
|
174
|
+
# Create a jacket from backup
|
175
|
+
def _jacket_create_raw(info)
|
176
|
+
jp = File.join(@path, info[:num].to_s)
|
177
|
+
JacketFs.create_raw(jp, info[:id_text], info[:id_hash])
|
178
|
+
end # def _jacket_create_raw()
|
179
|
+
|
180
|
+
|
146
181
|
#####################################
|
147
182
|
# Open a jacket
|
148
183
|
def _jacket_open(num)
|