1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
16
17 package schema
18
19 import (
20 "context"
21 "encoding/json"
22 "fmt"
23 "io"
24 "path/filepath"
25 "strings"
26 "time"
27 "unicode/utf8"
28
29 "perkeep.org/pkg/blob"
30 )
31
32
33 type MissingFieldError string
34
35 func (e MissingFieldError) Error() string {
36 return fmt.Sprintf("schema: missing field %q", string(e))
37 }
38
39
40 func IsMissingField(err error) bool {
41 _, ok := err.(MissingFieldError)
42 return ok
43 }
44
45
46 type AnyBlob interface {
47 Blob() *Blob
48 }
49
50
51 type Buildable interface {
52 Builder() *Builder
53 }
54
55
56
57 type Blob struct {
58 br blob.Ref
59 str string
60 ss *superset
61 }
62
63
64 func (b *Blob) Type() CamliType { return b.ss.Type }
65
66
67 func (b *Blob) BlobRef() blob.Ref { return b.br }
68
69
70 func (b *Blob) JSON() string { return b.str }
71
72
73 func (b *Blob) Blob() *Blob { return b }
74
75
76
77 func (b *Blob) PartsSize() int64 {
78 n := int64(0)
79 for _, part := range b.ss.Parts {
80 n += int64(part.Size)
81 }
82 return n
83 }
84
85
86
87 func (b *Blob) FileName() string {
88 return b.ss.FileNameString()
89 }
90
91
92
93 func (b *Blob) ClaimDate() (time.Time, error) {
94 var ct time.Time
95 claimDate := b.ss.ClaimDate
96 if claimDate.IsAnyZero() {
97 return ct, MissingFieldError("claimDate")
98 }
99 return claimDate.Time(), nil
100 }
101
102
103
104 func (b *Blob) ByteParts() []BytesPart {
105
106
107 s := make([]BytesPart, len(b.ss.Parts))
108 for i, part := range b.ss.Parts {
109 s[i] = *part
110 }
111 return s
112 }
113
114 func (b *Blob) Builder() *Builder {
115 var m map[string]interface{}
116 dec := json.NewDecoder(strings.NewReader(b.str))
117 dec.UseNumber()
118 err := dec.Decode(&m)
119 if err != nil {
120 panic("failed to decode previously-thought-valid Blob's JSON: " + err.Error())
121 }
122 return &Builder{m}
123 }
124
125
126 func (b *Blob) AsClaim() (c Claim, ok bool) {
127 if b.ss.Signer.Valid() && b.ss.Sig != "" && b.ss.ClaimType != "" && !b.ss.ClaimDate.IsAnyZero() {
128 return Claim{b}, true
129 }
130 return
131 }
132
133
134 func (b *Blob) AsShare() (s Share, ok bool) {
135 c, isClaim := b.AsClaim()
136 if !isClaim {
137 return
138 }
139
140 if ClaimType(b.ss.ClaimType) == ShareClaim && b.ss.AuthType == ShareHaveRef && (b.ss.Target.Valid() || b.ss.Search != nil) {
141 return Share{c}, true
142 }
143 return s, false
144 }
145
146
147 func (b *Blob) DirectoryEntries() (br blob.Ref, ok bool) {
148 if b.Type() != TypeDirectory {
149 return
150 }
151 return b.ss.Entries, true
152 }
153
154
155
156
157 func (b *Blob) StaticSetMembers() []blob.Ref {
158 if b.Type() != TypeStaticSet {
159 return nil
160 }
161
162 s := make([]blob.Ref, 0, len(b.ss.Members))
163 for _, ref := range b.ss.Members {
164 if ref.Valid() {
165 s = append(s, ref)
166 }
167 }
168 return s
169 }
170
171
172
173 func (b *Blob) StaticSetMergeSets() []blob.Ref {
174 if b.Type() != TypeStaticSet {
175 return nil
176 }
177
178 s := make([]blob.Ref, 0, len(b.ss.MergeSets))
179 for _, ref := range b.ss.MergeSets {
180 if ref.Valid() {
181 s = append(s, ref)
182 }
183 }
184 return s
185 }
186
187 func (b *Blob) ShareAuthType() string {
188 s, ok := b.AsShare()
189 if !ok {
190 return ""
191 }
192 return s.AuthType()
193 }
194
195 func (b *Blob) ShareTarget() blob.Ref {
196 s, ok := b.AsShare()
197 if !ok {
198 return blob.Ref{}
199 }
200 return s.Target()
201 }
202
203
204 func (b *Blob) ModTime() time.Time { return b.ss.ModTime() }
205
206
207 type Claim struct {
208 b *Blob
209 }
210
211
212 func (c Claim) Blob() *Blob { return c.b }
213
214
215 func (c Claim) ClaimDateString() string { return c.b.ss.ClaimDate.String() }
216
217
218 func (c Claim) ClaimType() string { return c.b.ss.ClaimType }
219
220
221 func (c Claim) Attribute() string { return c.b.ss.Attribute }
222
223
224 func (c Claim) Value() string { return c.b.ss.Value }
225
226
227 func (c Claim) Signer() blob.Ref { return c.b.ss.Signer }
228
229
230 func (c Claim) Signature() string { return c.b.ss.Sig }
231
232
233
234
235 func (c Claim) ModifiedPermanode() blob.Ref {
236 return c.b.ss.Permanode
237 }
238
239
240
241
242
243 func (c Claim) Target() blob.Ref {
244 return c.b.ss.Target
245 }
246
247
248
249
250 type Share struct {
251 Claim
252 }
253
254
255 func (s Share) AuthType() string {
256 return s.b.ss.AuthType
257 }
258
259
260
261
262 func (s Share) IsTransitive() bool {
263 return s.b.ss.Transitive
264 }
265
266
267 func (s Share) IsExpired() bool {
268 t := time.Time(s.b.ss.Expires)
269 return !t.IsZero() && clockNow().After(t)
270 }
271
272
273
274 type StaticFile struct {
275 b *Blob
276 }
277
278
279 func (sf StaticFile) FileName() string {
280 return sf.b.ss.FileNameString()
281 }
282
283
284
285
286 func (b *Blob) AsStaticFile() (sf StaticFile, ok bool) {
287
288
289
290 t := b.ss.Type
291 if t == TypeFile || t == TypeSymlink || t == TypeFIFO || t == TypeSocket {
292 return StaticFile{b}, true
293 }
294
295 return
296 }
297
298
299 type StaticFIFO struct {
300 StaticFile
301 }
302
303
304 type StaticSocket struct {
305 StaticFile
306 }
307
308
309 type StaticSymlink struct {
310
311
312 StaticFile
313 }
314
315
316
317
318 func (sl StaticSymlink) SymlinkTargetString() string {
319 return sl.StaticFile.b.ss.SymlinkTargetString()
320 }
321
322
323
324
325 func (sf StaticFile) AsStaticSymlink() (s StaticSymlink, ok bool) {
326 if sf.b.ss.Type == TypeSymlink {
327 return StaticSymlink{sf}, true
328 }
329
330 return
331 }
332
333
334
335
336 func (sf StaticFile) AsStaticFIFO() (fifo StaticFIFO, ok bool) {
337 if sf.b.ss.Type == TypeFIFO {
338 return StaticFIFO{sf}, true
339 }
340
341 return
342 }
343
344
345
346
347 func (sf StaticFile) AsStaticSocket() (ss StaticSocket, ok bool) {
348 if sf.b.ss.Type == TypeSocket {
349 return StaticSocket{sf}, true
350 }
351
352 return
353 }
354
355
356
357 type Builder struct {
358 m map[string]interface{}
359 }
360
361
362
363
364 func NewBuilder() *Builder {
365 return &Builder{map[string]interface{}{
366 "camliVersion": "1",
367 }}
368 }
369
370
371
372 func (bb *Builder) SetShareTarget(t blob.Ref) *Builder {
373 if bb.Type() != TypeClaim || bb.ClaimType() != ShareClaim {
374 panic("called SetShareTarget on non-share")
375 }
376 bb.m["target"] = t.String()
377 return bb
378 }
379
380
381
382
383 func (bb *Builder) SetShareSearch(q SearchQuery) *Builder {
384 if bb.Type() != TypeClaim || bb.ClaimType() != ShareClaim {
385 panic("called SetShareSearch on non-share")
386 }
387 bb.m["search"] = q
388 return bb
389 }
390
391
392
393
394 func (bb *Builder) SetShareExpiration(t time.Time) *Builder {
395 if bb.Type() != TypeClaim || bb.ClaimType() != ShareClaim {
396 panic("called SetShareExpiration on non-share")
397 }
398 if t.IsZero() {
399 delete(bb.m, "expires")
400 } else {
401 bb.m["expires"] = RFC3339FromTime(t)
402 }
403 return bb
404 }
405
406 func (bb *Builder) SetShareIsTransitive(b bool) *Builder {
407 if bb.Type() != TypeClaim || bb.ClaimType() != ShareClaim {
408 panic("called SetShareIsTransitive on non-share")
409 }
410 if !b {
411 delete(bb.m, "transitive")
412 } else {
413 bb.m["transitive"] = true
414 }
415 return bb
416 }
417
418
419 func (bb *Builder) SetRawStringField(key, value string) *Builder {
420 bb.m[key] = value
421 return bb
422 }
423
424
425 func (bb *Builder) Blob() *Blob {
426 json, err := mapJSON(bb.m)
427 if err != nil {
428 panic(err)
429 }
430 ss, err := parseSuperset(strings.NewReader(json))
431 if err != nil {
432 panic(err)
433 }
434 h := blob.NewHash()
435 io.WriteString(h, json)
436 return &Blob{
437 str: json,
438 ss: ss,
439 br: blob.RefFromHash(h),
440 }
441 }
442
443
444 func (bb *Builder) Builder() *Builder {
445 return &Builder{clone(bb.m).(map[string]interface{})}
446 }
447
448
449 func (bb *Builder) JSON() (string, error) {
450 return mapJSON(bb.m)
451 }
452
453
454
455 func (bb *Builder) SetSigner(signer blob.Ref) *Builder {
456 bb.m["camliSigner"] = signer.String()
457 return bb
458 }
459
460
461
462 func (bb *Builder) Sign(ctx context.Context, signer *Signer) (string, error) {
463 return bb.SignAt(ctx, signer, time.Time{})
464 }
465
466
467
468
469
470 func (bb *Builder) SignAt(ctx context.Context, signer *Signer, sigTime time.Time) (string, error) {
471 switch bb.Type() {
472 case TypePermanode, TypeClaim:
473 default:
474 return "", fmt.Errorf("can't sign camliType %q", bb.Type())
475 }
476 if sigTime.IsZero() {
477 sigTime = time.Now()
478 }
479 bb.SetClaimDate(sigTime)
480 return signer.SignJSON(ctx, bb.SetSigner(signer.pubref).Blob().JSON(), sigTime)
481 }
482
483
484 func (bb *Builder) SetType(t CamliType) *Builder {
485 bb.m["camliType"] = string(t)
486 return bb
487 }
488
489
490 func (bb *Builder) Type() CamliType {
491 if s, ok := bb.m["camliType"].(string); ok {
492 return CamliType(s)
493 }
494 return ""
495 }
496
497
498 func (bb *Builder) ClaimType() ClaimType {
499 if s, ok := bb.m["claimType"].(string); ok {
500 return ClaimType(s)
501 }
502 return ""
503 }
504
505
506
507 func (bb *Builder) SetFileName(name string) *Builder {
508 baseName := filepath.Base(name)
509 if utf8.ValidString(baseName) {
510 bb.m["fileName"] = baseName
511 } else {
512 bb.m["fileNameBytes"] = mixedArrayFromString(baseName)
513 }
514 return bb
515 }
516
517
518 func (bb *Builder) SetSymlinkTarget(target string) *Builder {
519 bb.SetType(TypeSymlink)
520 if utf8.ValidString(target) {
521 bb.m["symlinkTarget"] = target
522 } else {
523 bb.m["symlinkTargetBytes"] = mixedArrayFromString(target)
524 }
525 return bb
526 }
527
528
529
530 func (bb *Builder) IsClaimType() bool {
531 switch bb.Type() {
532 case TypeClaim, TypePermanode:
533 return true
534 }
535 return false
536 }
537
538
539
540 func (bb *Builder) SetClaimDate(t time.Time) *Builder {
541 if !bb.IsClaimType() {
542
543
544
545
546 panic("SetClaimDate called on non-claim *Builder; camliType=" + bb.Type())
547 }
548 bb.m["claimDate"] = RFC3339FromTime(t)
549 return bb
550 }
551
552
553 func (bb *Builder) SetModTime(t time.Time) *Builder {
554 bb.m["unixMtime"] = RFC3339FromTime(t)
555 return bb
556 }
557
558
559 func (bb *Builder) CapCreationTime() *Builder {
560 ctime, ok := bb.m["unixCtime"].(string)
561 if !ok {
562 return bb
563 }
564 mtime, ok := bb.m["unixMtime"].(string)
565 if ok && ctime > mtime {
566 bb.m["unixCtime"] = mtime
567 }
568 return bb
569 }
570
571
572 func (bb *Builder) ModTime() (t time.Time, ok bool) {
573 s, ok := bb.m["unixMtime"].(string)
574 if !ok {
575 return
576 }
577 t, err := time.Parse(time.RFC3339, s)
578 if err != nil {
579 return
580 }
581 return t, true
582 }
583
584
585
586 func (bb *Builder) PopulateDirectoryMap(staticSetRef blob.Ref) *Builder {
587 bb.m["camliType"] = string(TypeDirectory)
588 bb.m["entries"] = staticSetRef.String()
589 return bb
590 }
591
592
593 func (bb *Builder) PartsSize() int64 {
594 n := int64(0)
595 if parts, ok := bb.m["parts"].([]BytesPart); ok {
596 for _, part := range parts {
597 n += int64(part.Size)
598 }
599 }
600 return n
601 }
602
603 func clone(i interface{}) interface{} {
604 switch t := i.(type) {
605 case map[string]interface{}:
606 m2 := make(map[string]interface{})
607 for k, v := range t {
608 m2[k] = clone(v)
609 }
610 return m2
611 case string, int, int64, float64, json.Number:
612 return t
613 case []interface{}:
614 s2 := make([]interface{}, len(t))
615 for i, v := range t {
616 s2[i] = clone(v)
617 }
618 return s2
619 }
620 panic(fmt.Sprintf("unsupported clone type %T", i))
621 }