sgfa 0.1.0 → 0.1.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d45c0bbd41d4e9b39a6c9b13f47fff3e0b2de18e
4
- data.tar.gz: 47f07e0899c32bff1374af1e0529d70e5f508221
3
+ metadata.gz: e56f45ef9e949c37978cee9dfc394769eeb6baa2
4
+ data.tar.gz: c4be3d1955e1700dcfa59c02a75cc6259c9ed3b0
5
5
  SHA512:
6
- metadata.gz: 3347b66a768fae632d618c35656b70e813fb1ff8ca5cc207dfb074acfc4c7e85f414115c7d4cc343ac9c185420177f55c9d6624cd4890c7821c52b09056c7ef3
7
- data.tar.gz: 34e8c399c39e436150091609fb0a4ac7c7d53869fe16f7b5b8903e81f5414d69989d6ec96a09efcd3bda0ef70d3fd02d2980d96c5eef25118161fc8b43c1aa7d
6
+ metadata.gz: 5e888580f637df355364aad7e997e1d3c548693bf584f7eaedec7fdc3cefd72b195117d9d8a87073866f545f28ad11dc7cbfe5f4f2b5348a1522feea7e68cc25
7
+ data.tar.gz: 9df09e1204ae56856b24bf30ad6075be1bbad8728ae38bfde47487f21c01c313cbbeead33cf8e07c08d61e6e57d2f1630f1ab708c15d310bfaa289a69e8f9a0d
@@ -119,7 +119,17 @@ div#sgfa_web table.list td.hash {
119
119
  }
120
120
 
121
121
 
122
- /* edit tables */
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: 15em;
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
  }
@@ -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
- def read_tag(tr, tag, offs, max)
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
- [ent.entry, ent.revision, ent.time, ent.title, ent.tags.size,
293
- ent.attachments.size]
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
  #
@@ -26,16 +26,15 @@ module Sgfa
26
26
  class BinderFs < Binder
27
27
 
28
28
  #####################################
29
- # Create a new binder
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 tr (see Binder#jacket_create)
33
- # @param init [Hash] The binder initialization options
34
- # @return [String] The hash ID of the new binder
35
- def create(path, tr, init)
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, init[:id_text])
49
- jck = JacketFs.new(dn_ctrl)
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)