Home Download Docs Code Community
     1	/*
     2	Copyright 2013 The Perkeep Authors
     3	
     4	Licensed under the Apache License, Version 2.0 (the "License");
     5	you may not use this file except in compliance with the License.
     6	You may obtain a copy of the License at
     7	
     8	     http://www.apache.org/licenses/LICENSE-2.0
     9	
    10	Unless required by applicable law or agreed to in writing, software
    11	distributed under the License is distributed on an "AS IS" BASIS,
    12	WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13	See the License for the specific language governing permissions and
    14	limitations under the License.
    15	*/
    16	
    17	package schema
    18	
    19	import (
    20		"context"
    21		"encoding/json"
    22		"fmt"
    23		"path/filepath"
    24		"strings"
    25		"time"
    26		"unicode/utf8"
    27	
    28		"perkeep.org/pkg/blob"
    29	)
    30	
    31	// A MissingFieldError represents a missing JSON field in a schema blob.
    32	type MissingFieldError string
    33	
    34	func (e MissingFieldError) Error() string {
    35		return fmt.Sprintf("schema: missing field %q", string(e))
    36	}
    37	
    38	// IsMissingField returns whether error is of type MissingFieldError.
    39	func IsMissingField(err error) bool {
    40		_, ok := err.(MissingFieldError)
    41		return ok
    42	}
    43	
    44	// AnyBlob represents any type of schema blob.
    45	type AnyBlob interface {
    46		Blob() *Blob
    47	}
    48	
    49	// Buildable returns a Builder from a base.
    50	type Buildable interface {
    51		Builder() *Builder
    52	}
    53	
    54	// A Blob represents a Perkeep schema blob.
    55	// It is immutable.
    56	type Blob struct {
    57		br  blob.Ref
    58		str string
    59		ss  *superset
    60	}
    61	
    62	// Type returns the blob's "camliType" field.
    63	func (b *Blob) Type() string { return b.ss.Type }
    64	
    65	// BlobRef returns the schema blob's blobref.
    66	func (b *Blob) BlobRef() blob.Ref { return b.br }
    67	
    68	// JSON returns the JSON bytes of the schema blob.
    69	func (b *Blob) JSON() string { return b.str }
    70	
    71	// Blob returns itself, so it satisifies the AnyBlob interface.
    72	func (b *Blob) Blob() *Blob { return b }
    73	
    74	// PartsSize returns the number of bytes represented by the "parts" field.
    75	// TODO: move this off *Blob to a specialized type.
    76	func (b *Blob) PartsSize() int64 {
    77		n := int64(0)
    78		for _, part := range b.ss.Parts {
    79			n += int64(part.Size)
    80		}
    81		return n
    82	}
    83	
    84	// FileName returns the file, directory, or symlink's filename, or the empty string.
    85	// TODO: move this off *Blob to a specialized type.
    86	func (b *Blob) FileName() string {
    87		return b.ss.FileNameString()
    88	}
    89	
    90	// ClaimDate returns the "claimDate" field.
    91	// If there is no claimDate, the error will be a MissingFieldError.
    92	func (b *Blob) ClaimDate() (time.Time, error) {
    93		var ct time.Time
    94		claimDate := b.ss.ClaimDate
    95		if claimDate.IsAnyZero() {
    96			return ct, MissingFieldError("claimDate")
    97		}
    98		return claimDate.Time(), nil
    99	}
   100	
   101	// ByteParts returns the "parts" field. The caller owns the returned
   102	// slice.
   103	func (b *Blob) ByteParts() []BytesPart {
   104		// TODO: move this method off Blob, and make the caller go
   105		// through a (*Blob).ByteBackedBlob() comma-ok accessor first.
   106		s := make([]BytesPart, len(b.ss.Parts))
   107		for i, part := range b.ss.Parts {
   108			s[i] = *part
   109		}
   110		return s
   111	}
   112	
   113	func (b *Blob) Builder() *Builder {
   114		var m map[string]interface{}
   115		dec := json.NewDecoder(strings.NewReader(b.str))
   116		dec.UseNumber()
   117		err := dec.Decode(&m)
   118		if err != nil {
   119			panic("failed to decode previously-thought-valid Blob's JSON: " + err.Error())
   120		}
   121		return &Builder{m}
   122	}
   123	
   124	// AsClaim returns a Claim if the receiver Blob has all the required fields.
   125	func (b *Blob) AsClaim() (c Claim, ok bool) {
   126		if b.ss.Signer.Valid() && b.ss.Sig != "" && b.ss.ClaimType != "" && !b.ss.ClaimDate.IsAnyZero() {
   127			return Claim{b}, true
   128		}
   129		return
   130	}
   131	
   132	// AsShare returns a Share if the receiver Blob has all the required fields.
   133	func (b *Blob) AsShare() (s Share, ok bool) {
   134		c, isClaim := b.AsClaim()
   135		if !isClaim {
   136			return
   137		}
   138	
   139		if ClaimType(b.ss.ClaimType) == ShareClaim && b.ss.AuthType == ShareHaveRef && (b.ss.Target.Valid() || b.ss.Search != nil) {
   140			return Share{c}, true
   141		}
   142		return s, false
   143	}
   144	
   145	// DirectoryEntries the "entries" field if valid and b's type is "directory".
   146	func (b *Blob) DirectoryEntries() (br blob.Ref, ok bool) {
   147		if b.Type() != "directory" {
   148			return
   149		}
   150		return b.ss.Entries, true
   151	}
   152	
   153	// StaticSetMembers returns the refs in the "members" field if b is a valid
   154	// "static-set" schema. Note that if it is a large static-set, the members are
   155	// actually spread as subsets in "mergeSets". See StaticSetMergeSets.
   156	func (b *Blob) StaticSetMembers() []blob.Ref {
   157		if b.Type() != "static-set" {
   158			return nil
   159		}
   160	
   161		s := make([]blob.Ref, 0, len(b.ss.Members))
   162		for _, ref := range b.ss.Members {
   163			if ref.Valid() {
   164				s = append(s, ref)
   165			}
   166		}
   167		return s
   168	}
   169	
   170	// StaticSetMergeSets returns the refs of the static-sets in "mergeSets". These
   171	// are the subsets of all the static-set members in the case of a large directory.
   172	func (b *Blob) StaticSetMergeSets() []blob.Ref {
   173		if b.Type() != "static-set" {
   174			return nil
   175		}
   176	
   177		s := make([]blob.Ref, 0, len(b.ss.MergeSets))
   178		for _, ref := range b.ss.MergeSets {
   179			if ref.Valid() {
   180				s = append(s, ref)
   181			}
   182		}
   183		return s
   184	}
   185	
   186	func (b *Blob) ShareAuthType() string {
   187		s, ok := b.AsShare()
   188		if !ok {
   189			return ""
   190		}
   191		return s.AuthType()
   192	}
   193	
   194	func (b *Blob) ShareTarget() blob.Ref {
   195		s, ok := b.AsShare()
   196		if !ok {
   197			return blob.Ref{}
   198		}
   199		return s.Target()
   200	}
   201	
   202	// ModTime returns the "unixMtime" field, or the zero time.
   203	func (b *Blob) ModTime() time.Time { return b.ss.ModTime() }
   204	
   205	// A Claim is a Blob that is signed.
   206	type Claim struct {
   207		b *Blob
   208	}
   209	
   210	// Blob returns the claim's Blob.
   211	func (c Claim) Blob() *Blob { return c.b }
   212	
   213	// ClaimDateString returns the blob's "claimDate" field.
   214	func (c Claim) ClaimDateString() string { return c.b.ss.ClaimDate.String() }
   215	
   216	// ClaimType returns the blob's "claimType" field.
   217	func (c Claim) ClaimType() string { return c.b.ss.ClaimType }
   218	
   219	// Attribute returns the "attribute" field, if set.
   220	func (c Claim) Attribute() string { return c.b.ss.Attribute }
   221	
   222	// Value returns the "value" field, if set.
   223	func (c Claim) Value() string { return c.b.ss.Value }
   224	
   225	// Signer returns the ref of the blob containing the signing key that signed the claim.
   226	func (c Claim) Signer() blob.Ref { return c.b.ss.Signer }
   227	
   228	// Signature returns the claim's signature.
   229	func (c Claim) Signature() string { return c.b.ss.Sig }
   230	
   231	// ModifiedPermanode returns the claim's "permaNode" field, if it's
   232	// a claim that modifies a permanode. Otherwise a zero blob.Ref is
   233	// returned.
   234	func (c Claim) ModifiedPermanode() blob.Ref {
   235		return c.b.ss.Permanode
   236	}
   237	
   238	// Target returns the blob referenced by the Share if it's
   239	// a ShareClaim claim, or the object being deleted if it's a
   240	// DeleteClaim claim.
   241	// Otherwise a zero blob.Ref is returned.
   242	func (c Claim) Target() blob.Ref {
   243		return c.b.ss.Target
   244	}
   245	
   246	// A Share is a claim for giving access to a user's blob(s).
   247	// When returned from (*Blob).AsShare, it always represents
   248	// a valid share with all required fields.
   249	type Share struct {
   250		Claim
   251	}
   252	
   253	// AuthType returns the AuthType of the Share.
   254	func (s Share) AuthType() string {
   255		return s.b.ss.AuthType
   256	}
   257	
   258	// IsTransitive returns whether the Share transitively
   259	// gives access to everything reachable from the referenced
   260	// blob.
   261	func (s Share) IsTransitive() bool {
   262		return s.b.ss.Transitive
   263	}
   264	
   265	// IsExpired reports whether this share has expired.
   266	func (s Share) IsExpired() bool {
   267		t := time.Time(s.b.ss.Expires)
   268		return !t.IsZero() && clockNow().After(t)
   269	}
   270	
   271	// A StaticFile is a Blob representing a file, symlink fifo or socket
   272	// (or device file, when support for these is added).
   273	type StaticFile struct {
   274		b *Blob
   275	}
   276	
   277	// FileName returns the StaticFile's FileName if is not the empty string, otherwise it returns its FileNameBytes concatenated into a string.
   278	func (sf StaticFile) FileName() string {
   279		return sf.b.ss.FileNameString()
   280	}
   281	
   282	// AsStaticFile returns the Blob as a StaticFile if it represents
   283	// one. Otherwise, it returns false in the boolean parameter and the
   284	// zero value of StaticFile.
   285	func (b *Blob) AsStaticFile() (sf StaticFile, ok bool) {
   286		// TODO (marete) Add support for device files to
   287		// Perkeep and change the implementation of StaticFile to
   288		// reflect that.
   289		t := b.ss.Type
   290		if t == "file" || t == "symlink" || t == "fifo" || t == "socket" {
   291			return StaticFile{b}, true
   292		}
   293	
   294		return
   295	}
   296	
   297	// A StaticFIFO is a StaticFile that is also a fifo.
   298	type StaticFIFO struct {
   299		StaticFile
   300	}
   301	
   302	// A StaticSocket is a StaticFile that is also a socket.
   303	type StaticSocket struct {
   304		StaticFile
   305	}
   306	
   307	// A StaticSymlink is a StaticFile that is also a symbolic link.
   308	type StaticSymlink struct {
   309		// We name it `StaticSymlink' rather than just `Symlink' since
   310		// a type called Symlink is already in schema.go.
   311		StaticFile
   312	}
   313	
   314	// SymlinkTargetString returns the field symlinkTarget if is
   315	// non-empty. Otherwise it returns the contents of symlinkTargetBytes
   316	// concatenated as a string.
   317	func (sl StaticSymlink) SymlinkTargetString() string {
   318		return sl.StaticFile.b.ss.SymlinkTargetString()
   319	}
   320	
   321	// AsStaticSymlink returns the StaticFile as a StaticSymlink if the
   322	// StaticFile represents a symlink. Othwerwise, it returns the zero
   323	// value of StaticSymlink and false.
   324	func (sf StaticFile) AsStaticSymlink() (s StaticSymlink, ok bool) {
   325		if sf.b.ss.Type == "symlink" {
   326			return StaticSymlink{sf}, true
   327		}
   328	
   329		return
   330	}
   331	
   332	// AsStaticFIFO returns the StatifFile as a StaticFIFO if the
   333	// StaticFile represents a fifo. Otherwise, it returns the zero value
   334	// of StaticFIFO and false.
   335	func (sf StaticFile) AsStaticFIFO() (fifo StaticFIFO, ok bool) {
   336		if sf.b.ss.Type == "fifo" {
   337			return StaticFIFO{sf}, true
   338		}
   339	
   340		return
   341	}
   342	
   343	// AsStaticSocket returns the StaticFile as a StaticSocket if the
   344	// StaticFile represents a socket. Otherwise, it returns the zero
   345	// value of StaticSocket and false.
   346	func (sf StaticFile) AsStaticSocket() (ss StaticSocket, ok bool) {
   347		if sf.b.ss.Type == "socket" {
   348			return StaticSocket{sf}, true
   349		}
   350	
   351		return
   352	}
   353	
   354	// A Builder builds a JSON blob.
   355	// After mutating the Builder, call Blob to get the built blob.
   356	type Builder struct {
   357		m map[string]interface{}
   358	}
   359	
   360	// NewBuilder returns a new blob schema builder.
   361	// The "camliVersion" field is set to "1" by default and the required
   362	// "camliType" field is NOT set.
   363	func NewBuilder() *Builder {
   364		return &Builder{map[string]interface{}{
   365			"camliVersion": "1",
   366		}}
   367	}
   368	
   369	// SetShareTarget sets the target of share claim.
   370	// It panics if bb isn't a "share" claim type.
   371	func (bb *Builder) SetShareTarget(t blob.Ref) *Builder {
   372		if bb.Type() != "claim" || bb.ClaimType() != ShareClaim {
   373			panic("called SetShareTarget on non-share")
   374		}
   375		bb.m["target"] = t.String()
   376		return bb
   377	}
   378	
   379	// SetShareSearch sets the search of share claim.
   380	// q is assumed to be of type *search.SearchQuery.
   381	// It panics if bb isn't a "share" claim type.
   382	func (bb *Builder) SetShareSearch(q SearchQuery) *Builder {
   383		if bb.Type() != "claim" || bb.ClaimType() != ShareClaim {
   384			panic("called SetShareSearch on non-share")
   385		}
   386		bb.m["search"] = q
   387		return bb
   388	}
   389	
   390	// SetShareExpiration sets the expiration time on share claim.
   391	// It panics if bb isn't a "share" claim type.
   392	// If t is zero, the expiration is removed.
   393	func (bb *Builder) SetShareExpiration(t time.Time) *Builder {
   394		if bb.Type() != "claim" || bb.ClaimType() != ShareClaim {
   395			panic("called SetShareExpiration on non-share")
   396		}
   397		if t.IsZero() {
   398			delete(bb.m, "expires")
   399		} else {
   400			bb.m["expires"] = RFC3339FromTime(t)
   401		}
   402		return bb
   403	}
   404	
   405	func (bb *Builder) SetShareIsTransitive(b bool) *Builder {
   406		if bb.Type() != "claim" || bb.ClaimType() != ShareClaim {
   407			panic("called SetShareIsTransitive on non-share")
   408		}
   409		if !b {
   410			delete(bb.m, "transitive")
   411		} else {
   412			bb.m["transitive"] = true
   413		}
   414		return bb
   415	}
   416	
   417	// SetRawStringField sets a raw string field in the underlying map.
   418	func (bb *Builder) SetRawStringField(key, value string) *Builder {
   419		bb.m[key] = value
   420		return bb
   421	}
   422	
   423	// Blob builds the Blob. The builder continues to be usable after a call to Build.
   424	func (bb *Builder) Blob() *Blob {
   425		json, err := mapJSON(bb.m)
   426		if err != nil {
   427			panic(err)
   428		}
   429		ss, err := parseSuperset(strings.NewReader(json))
   430		if err != nil {
   431			panic(err)
   432		}
   433		h := blob.NewHash()
   434		h.Write([]byte(json))
   435		return &Blob{
   436			str: json,
   437			ss:  ss,
   438			br:  blob.RefFromHash(h),
   439		}
   440	}
   441	
   442	// Builder returns a clone of itself and satisifies the Buildable interface.
   443	func (bb *Builder) Builder() *Builder {
   444		return &Builder{clone(bb.m).(map[string]interface{})}
   445	}
   446	
   447	// JSON returns the JSON of the blob as built so far.
   448	func (bb *Builder) JSON() (string, error) {
   449		return mapJSON(bb.m)
   450	}
   451	
   452	// SetSigner sets the camliSigner field.
   453	// Calling SetSigner is unnecessary if using Sign.
   454	func (bb *Builder) SetSigner(signer blob.Ref) *Builder {
   455		bb.m["camliSigner"] = signer.String()
   456		return bb
   457	}
   458	
   459	// Sign sets the blob builder's camliSigner field with SetSigner
   460	// and returns the signed JSON using the provided signer.
   461	func (bb *Builder) Sign(ctx context.Context, signer *Signer) (string, error) {
   462		return bb.SignAt(ctx, signer, time.Time{})
   463	}
   464	
   465	// SignAt sets the blob builder's camliSigner field with SetSigner
   466	// and returns the signed JSON using the provided signer.
   467	// The provided sigTime is the time of the signature, used mostly
   468	// for planned permanodes. If the zero value, the current time is used.
   469	func (bb *Builder) SignAt(ctx context.Context, signer *Signer, sigTime time.Time) (string, error) {
   470		switch bb.Type() {
   471		case "permanode", "claim":
   472		default:
   473			return "", fmt.Errorf("can't sign camliType %q", bb.Type())
   474		}
   475		if sigTime.IsZero() {
   476			sigTime = time.Now()
   477		}
   478		bb.SetClaimDate(sigTime)
   479		return signer.SignJSON(ctx, bb.SetSigner(signer.pubref).Blob().JSON(), sigTime)
   480	}
   481	
   482	// SetType sets the camliType field.
   483	func (bb *Builder) SetType(t string) *Builder {
   484		bb.m["camliType"] = t
   485		return bb
   486	}
   487	
   488	// Type returns the camliType value.
   489	func (bb *Builder) Type() string {
   490		if s, ok := bb.m["camliType"].(string); ok {
   491			return s
   492		}
   493		return ""
   494	}
   495	
   496	// ClaimType returns the claimType value, or the empty string.
   497	func (bb *Builder) ClaimType() ClaimType {
   498		if s, ok := bb.m["claimType"].(string); ok {
   499			return ClaimType(s)
   500		}
   501		return ""
   502	}
   503	
   504	// SetFileName sets the fileName or fileNameBytes field.
   505	// The filename is truncated to just the base.
   506	func (bb *Builder) SetFileName(name string) *Builder {
   507		baseName := filepath.Base(name)
   508		if utf8.ValidString(baseName) {
   509			bb.m["fileName"] = baseName
   510		} else {
   511			bb.m["fileNameBytes"] = mixedArrayFromString(baseName)
   512		}
   513		return bb
   514	}
   515	
   516	// SetSymlinkTarget sets bb to be of type "symlink" and sets the symlink's target.
   517	func (bb *Builder) SetSymlinkTarget(target string) *Builder {
   518		bb.SetType("symlink")
   519		if utf8.ValidString(target) {
   520			bb.m["symlinkTarget"] = target
   521		} else {
   522			bb.m["symlinkTargetBytes"] = mixedArrayFromString(target)
   523		}
   524		return bb
   525	}
   526	
   527	// IsClaimType returns whether this blob builder is for a type
   528	// which should be signed. (a "claim" or "permanode")
   529	func (bb *Builder) IsClaimType() bool {
   530		switch bb.Type() {
   531		case "claim", "permanode":
   532			return true
   533		}
   534		return false
   535	}
   536	
   537	// SetClaimDate sets the "claimDate" on a claim.
   538	// It is a fatal error to call SetClaimDate if the Map isn't of Type "claim".
   539	func (bb *Builder) SetClaimDate(t time.Time) *Builder {
   540		if !bb.IsClaimType() {
   541			// This is a little gross, using panic here, but I
   542			// don't want all callers to check errors.  This is
   543			// really a programming error, not a runtime error
   544			// that would arise from e.g. random user data.
   545			panic("SetClaimDate called on non-claim *Builder; camliType=" + bb.Type())
   546		}
   547		bb.m["claimDate"] = RFC3339FromTime(t)
   548		return bb
   549	}
   550	
   551	// SetModTime sets the "unixMtime" field.
   552	func (bb *Builder) SetModTime(t time.Time) *Builder {
   553		bb.m["unixMtime"] = RFC3339FromTime(t)
   554		return bb
   555	}
   556	
   557	// CapCreationTime caps the "unixCtime" field to be less or equal than "unixMtime"
   558	func (bb *Builder) CapCreationTime() *Builder {
   559		ctime, ok := bb.m["unixCtime"].(string)
   560		if !ok {
   561			return bb
   562		}
   563		mtime, ok := bb.m["unixMtime"].(string)
   564		if ok && ctime > mtime {
   565			bb.m["unixCtime"] = mtime
   566		}
   567		return bb
   568	}
   569	
   570	// ModTime returns the "unixMtime" modtime field, if set.
   571	func (bb *Builder) ModTime() (t time.Time, ok bool) {
   572		s, ok := bb.m["unixMtime"].(string)
   573		if !ok {
   574			return
   575		}
   576		t, err := time.Parse(time.RFC3339, s)
   577		if err != nil {
   578			return
   579		}
   580		return t, true
   581	}
   582	
   583	// PopulateDirectoryMap sets the type of *Builder to "directory" and sets
   584	// the "entries" field to the provided staticSet blobref.
   585	func (bb *Builder) PopulateDirectoryMap(staticSetRef blob.Ref) *Builder {
   586		bb.m["camliType"] = "directory"
   587		bb.m["entries"] = staticSetRef.String()
   588		return bb
   589	}
   590	
   591	// PartsSize returns the number of bytes represented by the "parts" field.
   592	func (bb *Builder) PartsSize() int64 {
   593		n := int64(0)
   594		if parts, ok := bb.m["parts"].([]BytesPart); ok {
   595			for _, part := range parts {
   596				n += int64(part.Size)
   597			}
   598		}
   599		return n
   600	}
   601	
   602	func clone(i interface{}) interface{} {
   603		switch t := i.(type) {
   604		case map[string]interface{}:
   605			m2 := make(map[string]interface{})
   606			for k, v := range t {
   607				m2[k] = clone(v)
   608			}
   609			return m2
   610		case string, int, int64, float64, json.Number:
   611			return t
   612		case []interface{}:
   613			s2 := make([]interface{}, len(t))
   614			for i, v := range t {
   615				s2[i] = clone(v)
   616			}
   617			return s2
   618		}
   619		panic(fmt.Sprintf("unsupported clone type %T", i))
   620	}
Website layout inspired by memcached.
Content by the authors.