Updated code in response to Tim's Review.
[arvados.git] / services / datamanager / manifest / manifest.go
1 /* Deals with parsing Manifest Text. */
2
3 // Inspired by the Manifest class in arvados/sdk/ruby/lib/arvados/keep.rb
4
5 package manifest
6
7 import (
8         "fmt"
9         "log"
10         "regexp"
11         "strconv"
12         "strings"
13 )
14
15 var LocatorPattern = regexp.MustCompile(
16         "^[0-9a-fA-F]{32}\\+[0-9]+(\\+[A-Z][A-Za-z0-9@_-]+)*$")
17
18 type Manifest struct {
19         Text string
20 }
21
22 type BlockLocator struct {
23         Digest  string
24         Size    int
25         Hints   []string
26 }
27
28 type ManifestLine struct {
29         StreamName  string
30         Blocks       []string
31         Files        []string
32 }
33
34 func parseBlockLocator(s string) (b BlockLocator, err error) {
35         if !LocatorPattern.MatchString(s) {
36                 fmt.Errorf("String \"%s\" does not match BlockLocator pattern \"%s\".",
37                         s,
38                         LocatorPattern.String())
39         } else {
40                 tokens := strings.Split(s, "+")
41                 var blockSize int64
42                 // We expect ParseInt to succeed since LocatorPattern restricts
43                 // tokens[1] to contain exclusively digits.
44                 blockSize, err = strconv.ParseInt(tokens[1], 10, 0)
45                 if err == nil {
46                         b.Digest = tokens[0]
47                         b.Size = int(blockSize)
48                         b.Hints = tokens[2:]
49                 }
50         }
51         return
52 }
53
54 func parseManifestLine(s string) (m ManifestLine) {
55         tokens := strings.Split(s, " ")
56         m.StreamName = tokens[0]
57         tokens = tokens[1:]
58         var i int
59         var token string
60         for i, token = range tokens {
61                 if !LocatorPattern.MatchString(token) {
62                         break
63                 }
64         }
65         m.Blocks = tokens[:i]
66         m.Files = tokens[i:]
67         return
68 }
69
70 func (m *Manifest) LineIter() <-chan ManifestLine {
71         ch := make(chan ManifestLine)
72         go func(remaining string) {
73                 for {
74                         // We parse one line at a time, to save effort if we only need
75                         // the first few lines.
76                         splitsies := strings.SplitN(remaining, "\n", 2)
77                         ch <- parseManifestLine(splitsies[0])
78                         if len(splitsies) == 1 {
79                                 break
80                         }
81                         remaining = splitsies[1]
82                 }
83                 close(ch)
84         }(m.Text)
85         return ch
86 }
87
88
89 // Blocks may appear mulitple times within the same manifest if they
90 // are used by multiple files. In that case this Iterator will output
91 // the same block multiple times.
92 func (m *Manifest) DuplicateBlockIter() <-chan BlockLocator {
93         blockChannel := make(chan BlockLocator)
94         go func(lineChannel <-chan ManifestLine) {
95                 for m := range lineChannel {
96                         for _, block := range m.Blocks {
97                                 if b, err := parseBlockLocator(block); err == nil {
98                                         blockChannel <- b
99                                 } else {
100                                         log.Printf("ERROR: Failed to parse block: %v", err)
101                                 }
102                         }
103                 }
104                 close(blockChannel)
105         }(m.LineIter())
106         return blockChannel
107 }