diff --git a/pkg/index/corpus.go b/pkg/index/corpus.go index b78221afd..d0ef64a6a 100644 --- a/pkg/index/corpus.go +++ b/pkg/index/corpus.go @@ -1008,41 +1008,7 @@ func (c *Corpus) PermanodeAttrValue(permaNode blob.Ref, } return "" } - if at.IsZero() { - at = time.Now() - } - var v []string - for _, cl := range pm.Claims { - if cl.Attr != attr || cl.Date.After(at) { - continue - } - if signerFilter.Valid() && signerFilter != cl.Signer { - continue - } - switch cl.Type { - case string(schema.DelAttributeClaim): - if cl.Value == "" { - v = v[:0] - } else { - i := 0 - for _, w := range v { - if w != cl.Value { - v[i] = w - i++ - } - } - v = v[:i] - } - case string(schema.SetAttributeClaim): - v = append(v[:0], cl.Value) - case string(schema.AddAttributeClaim): - v = append(v, cl.Value) - } - } - if len(v) != 0 { - return v[0] - } - return "" + return claimPtrsAttrValue(pm.Claims, attr, at, signerFilter) } // AppendPermanodeAttrValues appends to dst all the values for the attribute diff --git a/pkg/index/util.go b/pkg/index/util.go index 6ac352e17..c9b42c9c5 100644 --- a/pkg/index/util.go +++ b/pkg/index/util.go @@ -18,6 +18,11 @@ package index import ( "net/url" + "time" + + "camlistore.org/pkg/blob" + "camlistore.org/pkg/schema" + "camlistore.org/pkg/types/camtypes" ) var urle = url.QueryEscape @@ -42,3 +47,71 @@ func (s *dupSkipper) Dup(v string) bool { s.m[v] = true return false } + +// ClaimsAttrValue returns the value of attr from claims, +// or the empty string if not found. +// Claims should be sorted by claim.Date. +func ClaimsAttrValue(claims []camtypes.Claim, attr string, at time.Time, signerFilter blob.Ref) string { + return claimsIntfAttrValue(claimSlice(claims), attr, at, signerFilter) +} + +// claimPtrsAttrValue returns the value of attr from claims, +// or the empty string if not found. +// Claims should be sorted by claim.Date. +func claimPtrsAttrValue(claims []*camtypes.Claim, attr string, at time.Time, signerFilter blob.Ref) string { + return claimsIntfAttrValue(claimPtrSlice(claims), attr, at, signerFilter) +} + +type claimsIntf interface { + Len() int + Claim(i int) *camtypes.Claim +} + +type claimSlice []camtypes.Claim + +func (s claimSlice) Len() int { return len(s) } +func (s claimSlice) Claim(i int) *camtypes.Claim { return &s[i] } + +type claimPtrSlice []*camtypes.Claim + +func (s claimPtrSlice) Len() int { return len(s) } +func (s claimPtrSlice) Claim(i int) *camtypes.Claim { return s[i] } + +func claimsIntfAttrValue(claims claimsIntf, attr string, at time.Time, signerFilter blob.Ref) string { + if at.IsZero() { + at = time.Now() + } + var v []string + for i := 0; i < claims.Len(); i++ { + cl := claims.Claim(i) + if cl.Attr != attr || cl.Date.After(at) { + continue + } + if signerFilter.Valid() && signerFilter != cl.Signer { + continue + } + switch cl.Type { + case string(schema.DelAttributeClaim): + if cl.Value == "" { + v = v[:0] + } else { + i := 0 + for _, w := range v { + if w != cl.Value { + v[i] = w + i++ + } + } + v = v[:i] + } + case string(schema.SetAttributeClaim): + v = append(v[:0], cl.Value) + case string(schema.AddAttributeClaim): + v = append(v, cl.Value) + } + } + if len(v) != 0 { + return v[0] + } + return "" +} diff --git a/pkg/index/util_test.go b/pkg/index/util_test.go new file mode 100644 index 000000000..ba5c6bbfa --- /dev/null +++ b/pkg/index/util_test.go @@ -0,0 +1,87 @@ +/* +Copyright 2016 The Camlistore AUTHORS + +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 index + +import ( + "testing" + "time" + + "camlistore.org/pkg/blob" + "camlistore.org/pkg/types/camtypes" +) + +func TestClaimsAttrValue(t *testing.T) { + tm := time.Unix(99, 0) + claim := func(verb, attr, val string) camtypes.Claim { + tm = tm.Add(time.Second) + return camtypes.Claim{ + Type: verb + "-attribute", + Attr: attr, + Value: val, + Date: tm, + } + } + + claims := []camtypes.Claim{ + claim("set", "foo", "foov"), // time 100 + + claim("add", "tag", "a"), // time 101 + claim("add", "tag", "b"), // time 102 + claim("del", "tag", ""), + claim("add", "tag", "c"), + claim("add", "tag", "d"), + claim("add", "tag", "e"), + claim("del", "tag", "d"), + + claim("add", "DelAll", "a"), + claim("add", "DelAll", "b"), + claim("add", "DelAll", "c"), + claim("del", "DelAll", ""), + + claim("add", "DelOne", "a"), + claim("add", "DelOne", "b"), + claim("add", "DelOne", "c"), + claim("add", "DelOne", "d"), + claim("del", "DelOne", "d"), + claim("del", "DelOne", "a"), + + claim("add", "SetAfterAdd", "a"), + claim("add", "SetAfterAdd", "b"), + claim("set", "SetAfterAdd", "setv"), + } + + tests := []struct { + attr string + want string + t time.Time + }{ + {attr: "foo", want: "foov"}, + {attr: "tag", want: "c"}, + {attr: "tag", want: "a", t: time.Unix(102, 0)}, + {attr: "DelAll", want: ""}, + {attr: "DelOne", want: "b"}, + {attr: "SetAfterAdd", want: "setv"}, + } + + for i, tt := range tests { + got := ClaimsAttrValue(claims, tt.attr, tt.t, blob.Ref{}) + if got != tt.want { + t.Errorf("%d. attr %q = %v; want %v", + i, tt.attr, got, tt.want) + } + } +}