Merge branch 'master' into 4823-python-sdk-writable-collection-api
[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         "fmt"
9         "git.curoverse.com/arvados.git/sdk/go/blockdigest"
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 blockdigest.BlockDigest
25         Size   int
26         Hints  []string
27 }
28
29 // Represents a single line from a manifest.
30 type ManifestStream struct {
31         StreamName string
32         Blocks     []string
33         Files      []string
34 }
35
36 func ParseBlockLocator(s string) (b BlockLocator, err error) {
37         if !LocatorPattern.MatchString(s) {
38                 err = fmt.Errorf("String \"%s\" does not match BlockLocator pattern "+
39                         "\"%s\".",
40                         s,
41                         LocatorPattern.String())
42         } else {
43                 tokens := strings.Split(s, "+")
44                 var blockSize int64
45                 var blockDigest blockdigest.BlockDigest
46                 // We expect both of the following to succeed since LocatorPattern
47                 // restricts the strings appropriately.
48                 blockDigest, err = blockdigest.FromString(tokens[0])
49                 if err != nil {
50                         return
51                 }
52                 blockSize, err = strconv.ParseInt(tokens[1], 10, 0)
53                 if err != nil {
54                         return
55                 }
56                 b.Digest = blockDigest
57                 b.Size = int(blockSize)
58                 b.Hints = tokens[2:]
59         }
60         return
61 }
62
63 func parseManifestStream(s string) (m ManifestStream) {
64         tokens := strings.Split(s, " ")
65         m.StreamName = tokens[0]
66         tokens = tokens[1:]
67         var i int
68         for i = range tokens {
69                 if !LocatorPattern.MatchString(tokens[i]) {
70                         break
71                 }
72         }
73         m.Blocks = tokens[:i]
74         m.Files = tokens[i:]
75         return
76 }
77
78 func (m *Manifest) StreamIter() <-chan ManifestStream {
79         ch := make(chan ManifestStream)
80         go func(input string) {
81                 // This slice holds the current line and the remainder of the
82                 // manifest.  We parse one line at a time, to save effort if we
83                 // only need the first few lines.
84                 lines := []string{"", input}
85                 for {
86                         lines = strings.SplitN(lines[1], "\n", 2)
87                         if len(lines[0]) > 0 {
88                                 // Only parse non-blank lines
89                                 ch <- parseManifestStream(lines[0])
90                         }
91                         if len(lines) == 1 {
92                                 break
93                         }
94                 }
95                 close(ch)
96         }(m.Text)
97         return ch
98 }
99
100 // Blocks may appear mulitple times within the same manifest if they
101 // are used by multiple files. In that case this Iterator will output
102 // the same block multiple times.
103 func (m *Manifest) BlockIterWithDuplicates() <-chan BlockLocator {
104         blockChannel := make(chan BlockLocator)
105         go func(streamChannel <-chan ManifestStream) {
106                 for m := range streamChannel {
107                         for _, block := range m.Blocks {
108                                 if b, err := ParseBlockLocator(block); err == nil {
109                                         blockChannel <- b
110                                 } else {
111                                         log.Printf("ERROR: Failed to parse block: %v", err)
112                                 }
113                         }
114                 }
115                 close(blockChannel)
116         }(m.StreamIter())
117         return blockChannel
118 }