diff --git a/lib/go/camli/index/index.go b/lib/go/camli/index/index.go index 1e87c4ef9..4799b9fc1 100644 --- a/lib/go/camli/index/index.go +++ b/lib/go/camli/index/index.go @@ -283,9 +283,55 @@ func (x *Index) PermanodeOfSignerAttrValue(signer *blobref.BlobRef, attr, val st return nil, os.ENOENT } -func (x *Index) PathsOfSignerTarget(signer, target *blobref.BlobRef) ([]*search.Path, os.Error) { - log.Printf("index: TODO PathsOfSignerTarget") - return nil, os.NewError("TODO: PathsOfSignerTarget") +func (x *Index) PathsOfSignerTarget(signer, target *blobref.BlobRef) (paths []*search.Path, err os.Error) { + paths = []*search.Path{} + keyId, err := x.keyId(signer) + if err != nil { + if err == ErrNotFound { + err = nil + } + return + } + + mostRecent := make(map[string]*search.Path) + maxClaimDates := make(map[string]string) + + it := x.queryPrefix(keySignerTargetPaths, keyId, target) + defer it.Close() + for it.Next() { + keyPart := strings.Split(it.Key(), "|") + valPart := strings.Split(it.Value(), "|") + if len(keyPart) < 3 || len(valPart) < 4 { + continue + } + claimRef := blobref.Parse(keyPart[2]) + baseRef := blobref.Parse(valPart[1]) + if claimRef == nil || baseRef == nil { + continue + } + claimDate := valPart[0] + active := valPart[2] + suffix := urld(valPart[3]) + key := baseRef.String() + "/" + suffix + + if claimDate > maxClaimDates[key] { + maxClaimDates[key] = claimDate + if active == "Y" { + mostRecent[key] = &search.Path{ + Claim: claimRef, + ClaimDate: claimDate, + Base: baseRef, + Suffix: suffix, + } + } else { + mostRecent[key] = nil, false + } + } + } + for _, v := range mostRecent { + paths = append(paths, v) + } + return paths, nil } func (x *Index) PathsLookup(signer, base *blobref.BlobRef, suffix string) ([]*search.Path, os.Error) { diff --git a/lib/go/camli/index/index_test.go b/lib/go/camli/index/index_test.go index 6d7f51268..f3e6b12d7 100644 --- a/lib/go/camli/index/index_test.go +++ b/lib/go/camli/index/index_test.go @@ -148,6 +148,7 @@ func TestIndex(t *testing.T) { rootClaim := id.SetAttribute(pn, "camliRoot", "rootval") rootClaimTime := id.lastTimeNanos() t.Logf("set attribute %q", rootClaim) + id.dumpIndex(t) key := "signerkeyid:sha1-ad87ca5c78bd0ce1195c46f7c98e6025abbaf007" @@ -277,6 +278,39 @@ func TestIndex(t *testing.T) { } } +func TestPathsOfSignerTarget(t *testing.T) { + id := NewIndexDeps() + pn := id.NewPermanode() + t.Logf("uploaded permanode %q", pn) + + claim1 := id.SetAttribute(pn, "camliPath:somedir", "targ-123") + claim2 := id.SetAttribute(pn, "camliPath:with|pipe", "targ-124") + t.Logf("made path claims %q and %q", claim1, claim2) + + id.dumpIndex(t) + + type test struct { + blobref string + want int + } + tests := []test{ + {"targ-123", 1}, + {"targ-124", 1}, + {"targ-125", 0}, + } + for _, tt := range tests { + signer := id.SignerBlobRef + paths, err := id.Index.PathsOfSignerTarget(signer, blobref.Parse(tt.blobref)) + if err != nil { + t.Fatalf("PathsOfSignerTarget(%q): %v", tt.blobref, err) + } + if len(paths) != tt.want { + t.Fatalf("PathsOfSignerTarget(%q) got %d results; want %d", + tt.blobref, len(paths), tt.want) + } + } +} + func TestReverseTimeString(t *testing.T) { in := "2011-11-27T01:23:45Z" got := reverseTimeString(in) diff --git a/lib/go/camli/index/keys.go b/lib/go/camli/index/keys.go index 39964aedf..cef611980 100644 --- a/lib/go/camli/index/keys.go +++ b/lib/go/camli/index/keys.go @@ -22,29 +22,50 @@ import ( ) type keyType struct { - name string - parts []keyPart + name string + keyParts []part + valParts []part } func (k *keyType) Prefix(args ...interface{}) string { - return k.build(true, args...) + return k.build(true, true, k.keyParts, args...) } func (k *keyType) Key(args ...interface{}) string { - return k.build(false, args...) + return k.build(false, true, k.keyParts, args...) } -func (k *keyType) build(finalPipe bool, args ...interface{}) string { +func (k *keyType) Val(args ...interface{}) string { + return k.build(false, false, k.valParts, args...) +} + +func (k *keyType) build(isPrefix, isKey bool, parts []part, args ...interface{}) string { var buf bytes.Buffer - buf.WriteString(k.name) + if isKey { + buf.WriteString(k.name) + } + if !isPrefix && len(args) != len(parts) { + panic("wrong number of arguments") + } + if len(args) > len(parts) { + panic("too many arguments") + } for i, arg := range args { - buf.WriteString("|") - switch k.parts[i].typ { - case typeReverseTime: + if isKey || i > 0 { + buf.WriteString("|") + } + asStr := func() string { s, ok := arg.(string) if !ok { s = arg.(fmt.Stringer).String() } + return s + } + switch parts[i].typ { + case typeStr: + buf.WriteString(urle(asStr())) + case typeReverseTime: + s := asStr() const example = "2011-01-23T05:23:12" if len(s) < len(example) || s[4] != '-' && s[10] != 'T' { panic("doesn't look like a time: " + s) @@ -56,16 +77,16 @@ func (k *keyType) build(finalPipe bool, args ...interface{}) string { buf.WriteString(s) } else { buf.WriteString(arg.(fmt.Stringer).String()) - } + } } } - if finalPipe { + if isPrefix { buf.WriteString("|") } return buf.String() } -type keyPart struct { +type part struct { name string typ partType } @@ -77,15 +98,32 @@ const ( typeTime typeReverseTime // time prepended with "rt" + each numeric digit reversed from '9' typeBlobRef + typeStr ) var ( keyRecentPermanode = &keyType{ "recpn", - []keyPart{ + []part{ {"owner", typeKeyId}, {"modtime", typeReverseTime}, {"claim", typeBlobRef}, }, + nil, + } + + keySignerTargetPaths = &keyType{ + "signertargetpath", + []part{ + {"signer", typeKeyId}, + {"target", typeBlobRef}, + {"claim", typeBlobRef}, // for key uniqueness + }, + []part{ + {"claimDate", typeTime}, + {"base", typeBlobRef}, + {"active", typeStr}, // 'Y', or 'N' for deleted + {"suffix", typeStr}, + }, } ) diff --git a/lib/go/camli/index/receive.go b/lib/go/camli/index/receive.go index 3b57dfc28..17d156e8b 100644 --- a/lib/go/camli/index/receive.go +++ b/lib/go/camli/index/receive.go @@ -22,6 +22,7 @@ import ( "io" "log" "os" + "strings" "camli/blobref" "camli/blobserver" @@ -127,6 +128,25 @@ func (ix *Index) populateClaim(br *blobref.BlobRef, ss *schema.Superset, sniffer claimKey := pipes("claim", pnbr, verifiedKeyId, ss.ClaimDate, br) bm.Set(claimKey, pipes(urle(ss.ClaimType), urle(ss.Attribute), urle(ss.Value))) + if strings.HasPrefix(ss.Attribute, "camliPath:") { + targetRef := blobref.Parse(ss.Value) + if targetRef != nil { + // TODO: deal with set-attribute vs. del-attribute + // properly? I think we get it for free when + // del-attribute has no Value, but we need to deal + // with the case where they explicitly delete the + // current value. + suffix := ss.Attribute[len("camliPath:"):] + active := "Y" + if ss.ClaimType == "del-attribute" { + active = "N" + } + stpKey := keySignerTargetPaths.Key(verifiedKeyId, targetRef, br) + stpVal := keySignerTargetPaths.Val(ss.ClaimDate, pnbr, active, suffix) + bm.Set(stpKey, stpVal) + } + } + if search.IsIndexedAttribute(ss.Attribute) { savKey := pipes("signerattrvalue", verifiedKeyId, urle(ss.Attribute), urle(ss.Value), diff --git a/lib/go/camli/search/search.go b/lib/go/camli/search/search.go index 54c7ea2ac..ce153e3a9 100644 --- a/lib/go/camli/search/search.go +++ b/lib/go/camli/search/search.go @@ -156,6 +156,16 @@ type Index interface { // Only attributes white-listed by IsIndexedAttribute are valid. PermanodeOfSignerAttrValue(signer *blobref.BlobRef, attr, val string) (*blobref.BlobRef, os.Error) + // PathsOfSignerTarget queries the index about "camliPath:" + // URL-dispatch attributes. + // + // It returns a list of all the path claims that have been signed + // by the provided signer and point at the given target. + // + // This is used when editing a permanode, to figure work up + // the name resolution tree backwards ultimately to a + // camliRoot permanode (which should know its base URL), and + // then the complete URL(s) of a target can be found. PathsOfSignerTarget(signer, target *blobref.BlobRef) ([]*Path, os.Error) // All Path claims for (signer, base, suffix)