Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(100)

Side by Side Diff: logdog/appengine/coordinator/hierarchy/componentID.go

Issue 2991253004: [logdog] Remove list functionality. (Closed)
Patch Set: fix test Created 3 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2015 The LUCI Authors.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 package hierarchy
16
17 import (
18 "bytes"
19 "encoding/hex"
20 "errors"
21 "fmt"
22 "strconv"
23 "strings"
24 "unicode/utf8"
25
26 ds "github.com/luci/gae/service/datastore"
27 "github.com/luci/luci-go/common/data/cmpbin"
28 )
29
30 type componentIDType string
31
32 const (
33 numericStreamID componentIDType = "n"
34 textStreamID componentIDType = "s"
35 numericPathID componentIDType = "y"
36 textPathID componentIDType = "z"
37 )
38
39 // componentID is the datastore ID for a component.
40 //
41 // A component represents a single component, and is keyed on the
42 // component's value and whether the component is a stream or path component.
43 //
44 // A log stream path is broken into several components. For example,
45 // "foo/bar/+/baz/qux" is broken into ["foo", "bar", "+", "baz", "qux"]. The
46 // intermediate pieces, "foo", "bar", and "baz" are "path components" (e.g.,
47 // directory name). The terminating component, "qux", is the stream component
48 // (e.g., file names), since adding it generates a valid stream name.
49 //
50 // Note that two streams, "foo/+/bar" and "foo/+/bar/baz", result in components
51 // "bar". In the first, "bar" is a path component, and in the second "bar" is a
52 // stream component.
53 //
54 // Path component IDs have "~" prepended to them, since "~" is not a valid
55 // initial stream name character and "~" comes bytewise after all valid
56 // characters, this will cause paths to sort after streams for a given hierarchy
57 // level.
58 type componentID struct {
59 // parent is the name of this component's parent.
60 parent string
61 // name is the name of this component.
62 name string
63 // stream is true if this is a stream component.
64 stream bool
65 }
66
67 var _ ds.PropertyConverter = (*componentID)(nil)
68
69 // ToProperty implements ds.PropertyConverter
70 func (id *componentID) ToProperty() (ds.Property, error) {
71 return ds.MkPropertyNI(id.key()), nil
72 }
73
74 // FromProperty implements ds.PropertyConverter
75 func (id *componentID) FromProperty(p ds.Property) error {
76 if p.Type() != ds.PTString {
77 return fmt.Errorf("wrong type for property: %s", p.Type())
78 }
79 return id.setID(p.Value().(string))
80 }
81
82 func (id *componentID) key() string {
83 name, numeric := id.maybeNumericID()
84 return fmt.Sprintf("%s~%s~%s", id.sortedPrefix(numeric), name, id.parent )
85 }
86
87 func (id *componentID) setID(s string) error {
88 parts := strings.SplitN(s, "~", 3)
89 if len(parts) != 3 {
90 return errors.New("missing minimal key")
91 }
92
93 id.name, id.parent = parts[1], parts[2]
94 numeric := false
95 switch p := componentIDType(parts[0]); p {
96 case numericStreamID:
97 id.stream = true
98 numeric = true
99
100 case textStreamID:
101 id.stream = true
102
103 case numericPathID:
104 id.stream = false
105 numeric = true
106
107 case textPathID:
108 id.stream = false
109
110 default:
111 return fmt.Errorf("unknown type prefix %q", p)
112 }
113
114 if numeric {
115 // Split numeric encoding from leading zero count.
116 numParts := strings.SplitN(id.name, ":", 2)
117
118 // Render numeric value.
119 value, err := decodeCmpbinHexUint(numParts[0])
120 if err != nil {
121 return fmt.Errorf("failed to decode value: %v", err)
122 }
123
124 id.name = strconv.FormatUint(value, 10)
125
126 // Re-add leading zeroes, if any were present.
127 if len(numParts) == 2 {
128 leadingZeroes, err := decodeCmpbinHexUint(numParts[1])
129 if err != nil {
130 return fmt.Errorf("failed to decode leading zero es: %v", err)
131 }
132
133 id.name = strings.Repeat("0", int(leadingZeroes)) + id.n ame
134 }
135 }
136
137 return nil
138 }
139
140 func (id *componentID) maybeNumericID() (name string, numeric bool) {
141 name = id.name
142
143 // Is our name entirely numeric?
144 v, err := strconv.ParseUint(name, 10, 64)
145 if err != nil {
146 return
147 }
148
149 // This is a numeric value. Generate a numeric-sortable string.
150 //
151 // Count the leading zeroes. We do this because the same numeric value c an be
152 // expressed with different strings, and we need to differentiate them.
153 //
154 // For example, "000123" vs. "123".
155 //
156 // We do this by appending ":#", where # is the hex-encoded cmpbin-encod ed
157 // count of leading zeroes.
158 var leadingZeroes uint64
159 for _, r := range name {
160 if r != '0' {
161 break
162 }
163 leadingZeroes++
164 }
165 if len(name) > 0 && leadingZeroes == uint64(utf8.RuneCountInString(name) ) {
166 leadingZeroes--
167 }
168
169 var buf bytes.Buffer
170 name = encodeCmpbinHexUint(&buf, v)
171 if leadingZeroes > 0 {
172 name += ":" + encodeCmpbinHexUint(&buf, leadingZeroes)
173 }
174 numeric = true
175 return
176 }
177
178 func (id *componentID) sortedPrefix(numeric bool) componentIDType {
179 switch {
180 case id.stream && numeric:
181 // Numeric stream element.
182 return numericStreamID
183 case id.stream:
184 // Non-numeric stream element.
185 return textStreamID
186 case numeric:
187 // Numeric path component.
188 return numericPathID
189 default:
190 // Non-numeric path component.
191 return textPathID
192 }
193 }
194
195 func encodeCmpbinHexUint(b *bytes.Buffer, v uint64) string {
196 b.Reset()
197 if _, err := cmpbin.WriteUint(b, v); err != nil {
198 // Cannot happen when writing to a Buffer.
199 panic(err)
200 }
201 return hex.EncodeToString(b.Bytes())
202 }
203
204 func decodeCmpbinHexUint(s string) (uint64, error) {
205 buf := make([]byte, hex.DecodedLen(len(s)))
206 if _, err := hex.Decode(buf, []byte(s)); err != nil {
207 return 0, err
208 }
209
210 v, _, err := cmpbin.ReadUint(bytes.NewReader(buf))
211 return v, err
212 }
OLDNEW
« no previous file with comments | « logdog/appengine/coordinator/hierarchy/component.go ('k') | logdog/appengine/coordinator/hierarchy/component_test.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698