mirror of https://github.com/perkeep/perkeep.git
262 lines
6.4 KiB
Go
262 lines
6.4 KiB
Go
/*
|
|
Copyright 2013 Google Inc.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package schema
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
"unicode/utf8"
|
|
|
|
"camlistore.org/pkg/blobref"
|
|
)
|
|
|
|
// AnyBlob represents any type of schema blob.
|
|
type AnyBlob interface {
|
|
Blob() *Blob
|
|
}
|
|
|
|
// Buildable returns a Builder from a base.
|
|
type Buildable interface {
|
|
Builder() *Builder
|
|
}
|
|
|
|
// A Blob represents a Camlistore schema blob.
|
|
// It is immutable.
|
|
type Blob struct {
|
|
br *blobref.BlobRef
|
|
str string
|
|
ss *Superset
|
|
}
|
|
|
|
// Type returns the blob's "camliType" field.
|
|
func (b *Blob) Type() string { return b.ss.Type }
|
|
|
|
// BlobRef returns the schema blob's blobref.
|
|
func (b *Blob) BlobRef() *blobref.BlobRef { return b.br }
|
|
|
|
// JSON returns the JSON bytes of the schema blob.
|
|
func (b *Blob) JSON() string { return b.str }
|
|
|
|
// Blob returns itself, so it satisifies the AnyBlob interface.
|
|
func (b *Blob) Blob() *Blob { return b }
|
|
|
|
// PartsSize returns the number of bytes represented by the "parts" field.
|
|
func (b *Blob) PartsSize() int64 {
|
|
n := int64(0)
|
|
for _, part := range b.ss.Parts {
|
|
n += int64(part.Size)
|
|
}
|
|
return n
|
|
}
|
|
|
|
// ByteParts returns the "parts" field. The caller owns the returned
|
|
// slice.
|
|
func (b *Blob) ByteParts() []BytesPart {
|
|
// TODO: move this method off Blob, and make the caller go
|
|
// through a (*Blob).ByteBackedBlob() comma-ok accessor first.
|
|
s := make([]BytesPart, len(b.ss.Parts))
|
|
for i, part := range b.ss.Parts {
|
|
s[i] = *part
|
|
}
|
|
return s
|
|
}
|
|
|
|
func (b *Blob) Builder() *Builder {
|
|
var m map[string]interface{}
|
|
err := json.Unmarshal([]byte(b.str), &m)
|
|
if err != nil {
|
|
panic("failed to decode previously-thought-valid Blob's JSON: " + err.Error())
|
|
}
|
|
return &Builder{m}
|
|
}
|
|
|
|
// A Claim is a Blob that is signed.
|
|
type Claim struct {
|
|
b *Blob
|
|
}
|
|
|
|
// Blob returns the claim's Blob.
|
|
func (c Claim) Blob() *Blob { return c.b }
|
|
|
|
// A Builder builds a JSON blob.
|
|
// After mutating the Builder, call Blob to get the built blob.
|
|
type Builder struct {
|
|
m map[string]interface{}
|
|
}
|
|
|
|
// NewBuilder returns a new blob schema builder.
|
|
// The "camliVersion" field is set to "1" by default and the required
|
|
// "camliType" field is NOT set.
|
|
func NewBuilder() *Builder {
|
|
return &Builder{map[string]interface{}{
|
|
"camliVersion": "1",
|
|
}}
|
|
}
|
|
|
|
// SetRawStringField sets a raw string field in the underlying map.
|
|
func (bb *Builder) SetRawStringField(key, value string) *Builder {
|
|
bb.m[key] = value
|
|
return bb
|
|
}
|
|
|
|
// Blob builds the Blob. The builder continues to be usable after a call to Build.
|
|
func (bb *Builder) Blob() *Blob {
|
|
json, err := mapJSON(bb.m)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
ss, err := ParseSuperset(strings.NewReader(json))
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
h := blobref.NewHash()
|
|
h.Write([]byte(json))
|
|
return &Blob{
|
|
str: json,
|
|
ss: ss,
|
|
br: blobref.FromHash(h),
|
|
}
|
|
}
|
|
|
|
// Builder returns a clone of itself and satisifies the Buildable interface.
|
|
func (bb *Builder) Builder() *Builder {
|
|
return &Builder{clone(bb.m).(map[string]interface{})}
|
|
}
|
|
|
|
// JSON returns the JSON of the blob as built so far.
|
|
func (bb *Builder) JSON() (string, error) {
|
|
return mapJSON(bb.m)
|
|
}
|
|
|
|
// SetSigner sets the camliSigner field.
|
|
func (bb *Builder) SetSigner(signer *blobref.BlobRef) *Builder {
|
|
bb.m["camliSigner"] = signer.String()
|
|
return bb
|
|
}
|
|
|
|
// SetType sets the camliType field.
|
|
func (bb *Builder) SetType(t string) *Builder {
|
|
bb.m["camliType"] = t
|
|
return bb
|
|
}
|
|
|
|
// Type returns the camliType value.
|
|
func (bb *Builder) Type() string {
|
|
if s, ok := bb.m["camliType"].(string); ok {
|
|
return s
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// SetFileName sets the fileName or fileNameBytes field.
|
|
// The filename is truncated to just the base.
|
|
func (bb *Builder) SetFileName(name string) *Builder {
|
|
baseName := filepath.Base(name)
|
|
if utf8.ValidString(baseName) {
|
|
bb.m["fileName"] = baseName
|
|
} else {
|
|
bb.m["fileNameBytes"] = []uint8(baseName)
|
|
}
|
|
return bb
|
|
}
|
|
|
|
// SetSymlinkTarget sets bb to be of type "symlink" and sets the symlink's target.
|
|
func (bb *Builder) SetSymlinkTarget(target string) *Builder {
|
|
bb.SetType("symlink")
|
|
if utf8.ValidString(target) {
|
|
bb.m["symlinkTarget"] = target
|
|
} else {
|
|
bb.m["symlinkTargetBytes"] = []uint8(target)
|
|
}
|
|
return bb
|
|
}
|
|
|
|
// SetClaimDate sets the "claimDate" on a claim.
|
|
// It is a fatal error to call SetClaimDate if the Map isn't of Type "claim".
|
|
func (bb *Builder) SetClaimDate(t time.Time) *Builder {
|
|
if t := bb.Type(); t != "claim" {
|
|
// This is a little gross, using panic here, but I
|
|
// don't want all callers to check errors. This is
|
|
// really a programming error, not a runtime error
|
|
// that would arise from e.g. random user data.
|
|
panic("SetClaimDate called on non-claim *Builder; camliType=" + t)
|
|
}
|
|
bb.m["claimDate"] = RFC3339FromTime(t)
|
|
return bb
|
|
}
|
|
|
|
// SetModTime sets the "unixMtime" field.
|
|
func (bb *Builder) SetModTime(t time.Time) *Builder {
|
|
bb.m["unixMtime"] = RFC3339FromTime(t)
|
|
return bb
|
|
}
|
|
|
|
// ModTime returns the "unixMtime" modtime field, if set.
|
|
func (bb *Builder) ModTime() (t time.Time, ok bool) {
|
|
s, ok := bb.m["unixMtime"].(string)
|
|
if !ok {
|
|
return
|
|
}
|
|
t, err := time.Parse(time.RFC3339, s)
|
|
if err != nil {
|
|
return
|
|
}
|
|
return t, true
|
|
}
|
|
|
|
// PopulateDirectoryMap sets the type of *Builder to "directory" and sets
|
|
// the "entries" field to the provided staticSet blobref.
|
|
func (bb *Builder) PopulateDirectoryMap(staticSetRef *blobref.BlobRef) *Builder {
|
|
bb.m["camliType"] = "directory"
|
|
bb.m["entries"] = staticSetRef.String()
|
|
return bb
|
|
}
|
|
|
|
// PartsSize returns the number of bytes represented by the "parts" field.
|
|
func (bb *Builder) PartsSize() int64 {
|
|
n := int64(0)
|
|
if parts, ok := bb.m["parts"].([]BytesPart); ok {
|
|
for _, part := range parts {
|
|
n += int64(part.Size)
|
|
}
|
|
}
|
|
return n
|
|
}
|
|
|
|
func clone(i interface{}) interface{} {
|
|
switch t := i.(type) {
|
|
case map[string]interface{}:
|
|
m2 := make(map[string]interface{})
|
|
for k, v := range t {
|
|
m2[k] = clone(v)
|
|
}
|
|
return m2
|
|
case string, int, int64, float64:
|
|
return t
|
|
case []interface{}:
|
|
s2 := make([]interface{}, len(t))
|
|
for i, v := range t {
|
|
s2[i] = clone(v)
|
|
}
|
|
return s2
|
|
}
|
|
panic(fmt.Sprintf("unsupported clone type %T", i))
|
|
}
|