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  }