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