From a576379cb58040bb8e5872ad4b1f97fa70818e1e Mon Sep 17 00:00:00 2001 From: Brad Fitzpatrick Date: Tue, 31 Dec 2013 18:30:58 -0800 Subject: [PATCH] search: PermanodeConstraint.Time time constraint, 'before:' and 'after:' operators Work in progress, but works enough to commit now. Determing the time of things has many TODOs, and there's some performance work to be done (although it still appears to be instant... it just uses more CPU than it should) Change-Id: I4b04b5805353dfbde0b841a3a557fd0b7c297780 --- pkg/index/corpus.go | 54 +++++++++++++++++++++++++++++++++++++++++++++ pkg/search/expr.go | 32 +++++++++++++++++++++++++++ pkg/search/query.go | 17 ++++++++++++++ 3 files changed, 103 insertions(+) diff --git a/pkg/index/corpus.go b/pkg/index/corpus.go index f5bc4e650..12c6b95e2 100644 --- a/pkg/index/corpus.go +++ b/pkg/index/corpus.go @@ -659,6 +659,60 @@ func (c *Corpus) isDeletedLocked(br blob.Ref) bool { return false } +// PermanodeTimeLocked returns the time of the content in permanode. +func (c *Corpus) PermanodeTimeLocked(pn blob.Ref) (t time.Time, ok bool) { + // TODO(bradfitz): keep this time property cached on the permanode / files + + // TODO(bradfitz): finish implmenting all these + + // Priorities: + // -- Permanode explicit "camliTime" property + // -- EXIF GPS time + // -- Exif camera time + // -- File time + // -- File modtime + // -- camliContent claim set time + ccRef, ccTime, ok := c.pnCamliContentLocked(pn) + if !ok { + return + } + + fi, ok := c.files[ccRef] + if ok { + if fi.Time != nil { + return time.Time(*fi.Time), true + } + if fi.ModTime != nil { + return time.Time(*fi.ModTime), true + } + } + return ccTime, true +} + +func (c *Corpus) pnCamliContentLocked(pn blob.Ref) (cc blob.Ref, t time.Time, ok bool) { + // TODO(bradfitz): keep this property cached + pm, ok := c.permanodes[pn] + if !ok { + return + } + for _, cl := range pm.Claims { + if cl.Attr != "camliContent" { + continue + } + // TODO: pass down the 'PermanodeConstraint.At' parameter, and then do: if cl.Date.After(at) { continue } + switch cl.Type { + case string(schema.DelAttributeClaim): + cc = blob.Ref{} + t = time.Time{} + case string(schema.SetAttributeClaim): + cc = blob.ParseOrZero(cl.Value) + t = cl.Date + } + } + return cc, t, cc.Valid() + +} + // PermanodeModtime returns the latest modification time of the given // permanode. // diff --git a/pkg/search/expr.go b/pkg/search/expr.go index 8b1e97258..263c79e8e 100644 --- a/pkg/search/expr.go +++ b/pkg/search/expr.go @@ -23,9 +23,11 @@ import ( "regexp" "strconv" "strings" + "time" "camlistore.org/pkg/context" "camlistore.org/pkg/geocode" + "camlistore.org/pkg/types" ) var ( @@ -183,6 +185,36 @@ func parseExpression(ctx *context.Context, exp string) (*SearchQuery, error) { }) continue } + if strings.HasPrefix(word, "before:") || strings.HasPrefix(word, "after:") { + before := false + when := "" + if strings.HasPrefix(word, "before:") { + before = true + when = strings.TrimPrefix(word, "before:") + } else { + when = strings.TrimPrefix(word, "after:") + } + base := "0000-01-01T00:00:00Z" + if len(when) < len(base) { + when += base[len(when):] + } + t, err := time.Parse(time.RFC3339, when) + if err != nil { + return nil, err + } + tc := &TimeConstraint{} + if before { + tc.Before = types.Time3339(t) + } else { + tc.After = types.Time3339(t) + } + and(&Constraint{ + Permanode: &PermanodeConstraint{ + Time: tc, + }, + }) + continue + } if strings.HasPrefix(word, "loc:") { where := strings.TrimPrefix(word, "loc:") rects, err := geocode.Lookup(ctx, where) diff --git a/pkg/search/query.go b/pkg/search/query.go index 9517d1a72..b1f9001a3 100644 --- a/pkg/search/query.go +++ b/pkg/search/query.go @@ -521,6 +521,12 @@ type PermanodeConstraint struct { // ModTime optionally matches on the last modtime of the permanode. ModTime *TimeConstraint `json:"modTime,omitempty"` + // Time optionally matches the permanode's time. A Permanode + // may not have a known time. If the permanode does not have a + // known time, one may be guessed if the top-level search + // parameters request so. + Time *TimeConstraint `json:"time,omitempty"` + // Attr optionally specifies the attribute to match. // e.g. "camliContent", "camliMember", "tag" // This is required if any of the items below are used. @@ -1032,6 +1038,17 @@ func (c *PermanodeConstraint) blobMatches(s *search, br blob.Ref, bm camtypes.Bl } } + if c.Time != nil { + if corpus != nil { + t, ok := corpus.PermanodeTimeLocked(br) + if !ok || !c.Time.timeMatches(t) { + return false, nil + } + } else { + panic("TODO: not yet supported") + } + } + if cc := c.Continue; cc != nil { if corpus == nil { // Requires an in-memory index for infinite