| // Copyright 2015 The LUCI 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 dm |
| |
| import ( |
| "fmt" |
| "strconv" |
| "strings" |
| |
| "google.golang.org/protobuf/proto" |
| |
| "go.chromium.org/luci/common/data/sortby" |
| "go.chromium.org/luci/gae/service/datastore" |
| ) |
| |
| const flipMask uint32 = 0xFFFFFFFF |
| |
| var _ datastore.PropertyConverter = (*Attempt_ID)(nil) |
| |
| // NewQuestID is a shorthand to New a new *Quest_ID |
| func NewQuestID(qst string) *Quest_ID { |
| return &Quest_ID{Id: qst} |
| } |
| |
| // NewAttemptID is a shorthand to New a new *Attempt_ID |
| func NewAttemptID(qst string, aid uint32) *Attempt_ID { |
| return &Attempt_ID{Quest: qst, Id: aid} |
| } |
| |
| // NewExecutionID is a shorthand to New a new *Execution_ID |
| func NewExecutionID(qst string, aid, eid uint32) *Execution_ID { |
| return &Execution_ID{Quest: qst, Attempt: aid, Id: eid} |
| } |
| |
| // ToProperty implements datastore.PropertyConverter for the purpose of |
| // embedding this Attempt_ID as the ID of a luci/gae compatible datastore |
| // object. The numerical id field is stored as an inverted, hex-encoded string, |
| // so that Attempt_ID{"quest", 1} would encode as "quest|fffffffe". This is done |
| // so that the __key__ ordering in the dm application prefers to order the most |
| // recent attempts first. |
| // |
| // The go representation will always have the normal non-flipped numerical id. |
| func (a *Attempt_ID) ToProperty() (datastore.Property, error) { |
| return datastore.MkPropertyNI(a.DMEncoded()), nil |
| } |
| |
| // FromProperty implements datastore.PropertyConverter |
| func (a *Attempt_ID) FromProperty(p datastore.Property) error { |
| if p.Type() != datastore.PTString { |
| return fmt.Errorf("wrong type for property: %s", p.Type()) |
| } |
| return a.SetDMEncoded(p.Value().(string)) |
| } |
| |
| // DMEncoded returns the encoded string id for this Attempt. Numeric values are |
| // inverted if flip is true. |
| func (a *Attempt_ID) DMEncoded() string { |
| return fmt.Sprintf("%s|%08x", a.Quest, flipMask^a.Id) |
| } |
| |
| // SetDMEncoded decodes val into this Attempt_ID, returning an error if |
| // there's a problem. Numeric values are inverted if flip is true. |
| func (a *Attempt_ID) SetDMEncoded(val string) error { |
| toks := strings.SplitN(val, "|", 2) |
| if len(toks) != 2 { |
| return fmt.Errorf("unable to parse Attempt id: %q", val) |
| } |
| an, err := strconv.ParseUint(toks[1], 16, 32) |
| if err != nil { |
| return err |
| } |
| |
| a.Quest = toks[0] |
| a.Id = flipMask ^ uint32(an) |
| return nil |
| } |
| |
| // GetQuest gets the specified quest from GraphData, if it's already there. If |
| // it's not, then a new Quest will be created, added, and returned. |
| // |
| // If the Quests map is uninitialized, this will initialize it. |
| func (g *GraphData) GetQuest(qid string) (*Quest, bool) { |
| cur, ok := g.Quests[qid] |
| if !ok { |
| cur = &Quest{ |
| Id: NewQuestID(qid), |
| Attempts: map[uint32]*Attempt{}, |
| } |
| if g.Quests == nil { |
| g.Quests = map[string]*Quest{} |
| } |
| g.Quests[qid] = cur |
| } |
| return cur, ok |
| } |
| |
| // NewQuestDesc is a shorthand method for building a new *Quest_Desc. |
| func NewQuestDesc(cfg string, params, distParams string, meta *Quest_Desc_Meta) *Quest_Desc { |
| return &Quest_Desc{ |
| DistributorConfigName: cfg, |
| Parameters: params, |
| DistributorParameters: distParams, |
| Meta: meta, |
| } |
| } |
| |
| // NewTemplateSpec is a shorthand method for building a new *Quest_TemplateSpec. |
| func NewTemplateSpec(project, ref, version, name string) *Quest_TemplateSpec { |
| return &Quest_TemplateSpec{ |
| Project: project, |
| Ref: ref, |
| Version: version, |
| Name: name, |
| } |
| } |
| |
| // Equals returns true iff this Quest_TemplateSpec matches all of the fields of |
| // the `o` Quest_TemplateSpec. |
| func (t *Quest_TemplateSpec) Equals(o *Quest_TemplateSpec) bool { |
| return proto.Equal(t, o) |
| } |
| |
| // QuestTemplateSpecs is a sortable slice of *Quest_TemplateSpec. |
| type QuestTemplateSpecs []*Quest_TemplateSpec |
| |
| func (s QuestTemplateSpecs) Len() int { return len(s) } |
| func (s QuestTemplateSpecs) Less(i, j int) bool { |
| return sortby.Chain{ |
| func(i, j int) bool { return s[i].Project < s[j].Project }, |
| func(i, j int) bool { return s[i].Ref < s[j].Ref }, |
| func(i, j int) bool { return s[i].Version < s[j].Version }, |
| func(i, j int) bool { return s[i].Name < s[j].Name }, |
| }.Use(i, j) |
| } |
| func (s QuestTemplateSpecs) Swap(i, j int) { s[i], s[j] = s[j], s[i] } |