github.com/slspeek/camlistore_namedsearch@v0.0.0-20140519202248-ed6f70f7721a/cmd/camtool/list.go (about) 1 /* 2 Copyright 2014 The Camlistore Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package main 18 19 import ( 20 "bufio" 21 "flag" 22 "fmt" 23 "log" 24 "os" 25 "strings" 26 27 "camlistore.org/pkg/blob" 28 "camlistore.org/pkg/client" 29 "camlistore.org/pkg/cmdmain" 30 "camlistore.org/pkg/search" 31 ) 32 33 type listCmd struct { 34 *syncCmd 35 36 describe bool // whether to describe each blob. 37 cl *client.Client // client used for the describe requests. 38 } 39 40 func init() { 41 cmdmain.RegisterCommand("list", func(flags *flag.FlagSet) cmdmain.CommandRunner { 42 cmd := &listCmd{ 43 syncCmd: &syncCmd{ 44 dest: "stdout", 45 }, 46 describe: false, 47 } 48 flags.StringVar(&cmd.syncCmd.src, "src", "", "Source blobserver is either a URL prefix (with optional path), a host[:port], a path (starting with /, ./, or ../), or blank to use the Camlistore client config's default host.") 49 flags.BoolVar(&cmd.verbose, "verbose", false, "Be verbose.") 50 flags.BoolVar(&cmd.describe, "describe", false, "Use describe requests to get each blob's type. Requires a source server with a search endpoint. Mostly used for demos. Requires many extra round-trips to the server currently.") 51 return cmd 52 }) 53 } 54 55 const describeBatchSize = 50 56 57 func (c *listCmd) Describe() string { 58 return "List blobs on a server." 59 } 60 61 func (c *listCmd) Usage() { 62 fmt.Fprintf(os.Stderr, "Usage: camtool [globalopts] list [listopts] \n") 63 } 64 65 func (c *listCmd) Examples() []string { 66 return nil 67 } 68 69 func (c *listCmd) RunCommand(args []string) error { 70 if !c.describe { 71 return c.syncCmd.RunCommand(args) 72 } 73 74 stdout := cmdmain.Stdout 75 defer func() { cmdmain.Stdout = stdout }() 76 pr, pw, err := os.Pipe() 77 if err != nil { 78 return fmt.Errorf("Could not create pipe to read from stdout: %v", err) 79 } 80 defer pr.Close() 81 cmdmain.Stdout = pw 82 83 if err := c.setClient(); err != nil { 84 return err 85 } 86 87 scanner := bufio.NewScanner(pr) 88 go func() { 89 err := c.syncCmd.RunCommand(args) 90 if err != nil { 91 log.Printf("Error when enumerating source with sync: %v", err) 92 } 93 pw.Close() 94 }() 95 96 blobRefs := make([]blob.Ref, 0, describeBatchSize) 97 describe := func() error { 98 if len(blobRefs) == 0 { 99 return nil 100 } 101 // TODO(mpl): setting depth to 1, not 0, because otherwise r.depth() in pkg/search/handler.go defaults to 4. Can't remember why we disallowed 0 right now, and I do not want to change that in pkg/search/handler.go and risk breaking things. 102 described, err := c.cl.Describe(&search.DescribeRequest{ 103 BlobRefs: blobRefs, 104 Depth: 1, 105 }) 106 if err != nil { 107 return fmt.Errorf("Error when describing blobs %v: %v", blobRefs, err) 108 } 109 for _, v := range blobRefs { 110 blob, ok := described.Meta[v.String()] 111 if !ok { 112 // This can happen if the index is out of sync with the storage we enum from. 113 fmt.Fprintf(stdout, "%v <not described>\n", v) 114 continue 115 } 116 detailed := detail(blob) 117 if detailed != "" { 118 detailed = fmt.Sprintf("\t%v", detailed) 119 } 120 fmt.Fprintf(stdout, "%v %v%v\n", v, blob.Size, detailed) 121 } 122 blobRefs = make([]blob.Ref, 0, describeBatchSize) 123 return nil 124 } 125 for scanner.Scan() { 126 fields := strings.Fields(scanner.Text()) 127 if len(fields) != 2 { 128 return fmt.Errorf("Bogus output from sync: got %q, wanted \"blobref size\"", scanner.Text()) 129 } 130 blobRefs = append(blobRefs, blob.MustParse(fields[0])) 131 if len(blobRefs) == describeBatchSize { 132 if err := describe(); err != nil { 133 return err 134 } 135 } 136 } 137 if err := describe(); err != nil { 138 return err 139 } 140 if err := scanner.Err(); err != nil { 141 return fmt.Errorf("Error reading on pipe from stdout: %v", err) 142 } 143 return nil 144 } 145 146 // setClient configures a client for c, for the describe requests. 147 func (c *listCmd) setClient() error { 148 ss, err := c.syncCmd.storageFromParam("src", c.syncCmd.src) 149 if err != nil { 150 fmt.Errorf("Could not set client for describe requests: %v", err) 151 } 152 var ok bool 153 c.cl, ok = ss.(*client.Client) 154 if !ok { 155 return fmt.Errorf("storageFromParam returned a %T, was expecting a *client.Client", ss) 156 } 157 return nil 158 } 159 160 func detail(blob *search.DescribedBlob) string { 161 // TODO(mpl): attrType, value for claim. but I don't think they're accessible just with a describe req. 162 if blob.CamliType == "file" { 163 return fmt.Sprintf("%v (%v size=%v)", blob.CamliType, blob.File.FileName, blob.File.Size) 164 } 165 return blob.CamliType 166 }