/* Copyright 2011 Google Inc. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package main import ( "flag" "fmt" "io" "log" "net/http" "net/url" "os" "sort" "strconv" "strings" "camlistore.org/pkg/buildinfo" "camlistore.org/pkg/client" "camlistore.org/pkg/httputil" "camlistore.org/pkg/jsonsign" ) const buffered = 16 // arbitrary var ( flagProxyLocal = false flagVersion = flag.Bool("version", false, "show version") flagHelp = flag.Bool("help", false, "print usage") flagVerbose = flag.Bool("verbose", false, "extra debug logging") flagHTTP = flag.Bool("verbose_http", false, "show HTTP request summaries") ) var ErrUsage = UsageError("invalid command usage") type UsageError string func (ue UsageError) Error() string { return "Usage error: " + string(ue) } type CommandRunner interface { Usage() RunCommand(up *Uploader, args []string) error } type Exampler interface { Examples() []string } var modeCommand = make(map[string]CommandRunner) var modeFlags = make(map[string]*flag.FlagSet) func init() { if debug, _ := strconv.ParseBool(os.Getenv("CAMLI_DEBUG")); debug { flag.BoolVar(&flagProxyLocal, "proxy_local", false, "If true, the HTTP_PROXY environment is also used for localhost requests. This can be helpful during debugging.") } } func RegisterCommand(mode string, makeCmd func(Flags *flag.FlagSet) CommandRunner) { if _, dup := modeCommand[mode]; dup { log.Fatalf("duplicate command %q registered", mode) } flags := flag.NewFlagSet(mode+" options", flag.ContinueOnError) flags.Usage = func() {} modeFlags[mode] = flags modeCommand[mode] = makeCmd(flags) } // wereErrors gets set to true if any error was encountered, which // changes the os.Exit value var wereErrors = false type namedMode struct { Name string Command CommandRunner } func allModes(startModes []string) <-chan namedMode { ch := make(chan namedMode) go func() { defer close(ch) done := map[string]bool{} for _, name := range startModes { done[name] = true cmd := modeCommand[name] if cmd == nil { panic("bogus mode: " + name) } ch <- namedMode{name, cmd} } var rest []string for name := range modeCommand { if !done[name] { rest = append(rest, name) } } sort.Strings(rest) for _, name := range rest { ch <- namedMode{name, modeCommand[name]} } }() return ch } func errf(format string, args ...interface{}) { fmt.Fprintf(stderr, format, args...) } func usage(msg string) { if msg != "" { errf("Error: %v\n", msg) } errf(` Usage: camput [globalopts] [commandopts] [commandargs] Examples: `) order := []string{"init", "file", "permanode", "blob", "attr"} for mode := range allModes(order) { errf("\n") if ex, ok := mode.Command.(Exampler); ok { for _, example := range ex.Examples() { errf(" camput %s %s\n", mode.Name, example) } } else { errf(" camput %s ...\n", mode.Name) } } errf(` For mode-specific help: camput -help Global options: `) flag.PrintDefaults() exit(1) } func handleResult(what string, pr *client.PutResult, err error) error { if err != nil { log.Printf("Error putting %s: %s", what, err) wereErrors = true return err } fmt.Println(pr.BlobRef.String()) return nil } func getenvEitherCase(k string) string { if v := os.Getenv(strings.ToUpper(k)); v != "" { return v } return os.Getenv(strings.ToLower(k)) } // proxyFromEnvironment is similar to http.ProxyFromEnvironment but it skips // $NO_PROXY blacklist so it proxies every requests, including localhost // requests. func proxyFromEnvironment(req *http.Request) (*url.URL, error) { proxy := getenvEitherCase("HTTP_PROXY") if proxy == "" { return nil, nil } proxyURL, err := url.Parse(proxy) if err != nil || proxyURL.Scheme == "" { if u, err := url.Parse("http://" + proxy); err == nil { proxyURL = u err = nil } } if err != nil { return nil, fmt.Errorf("invalid proxy address %q: %v", proxy, err) } return proxyURL, nil } func newUploader() *Uploader { cc := client.NewOrFail() if !*flagVerbose { cc.SetLogger(nil) } var transport http.RoundTripper proxy := http.ProxyFromEnvironment if flagProxyLocal { proxy = proxyFromEnvironment } transport = &http.Transport{ Dial: dialFunc(), TLSClientConfig: tlsClientConfig(), Proxy: proxy, } httpStats := &httputil.StatsTransport{ VerboseLog: *flagHTTP, Transport: transport, } transport = httpStats if androidOutput { transport = androidStatsTransport{transport} } cc.SetHTTPClient(&http.Client{Transport: transport}) pwd, err := os.Getwd() if err != nil { log.Fatalf("os.Getwd: %v", err) } return &Uploader{ Client: cc, transport: httpStats, pwd: pwd, entityFetcher: &jsonsign.CachingEntityFetcher{ Fetcher: &jsonsign.FileEntityFetcher{File: cc.SecretRingFile()}, }, } } func hasFlags(flags *flag.FlagSet) bool { any := false flags.VisitAll(func(*flag.Flag) { any = true }) return any } func main() { jsonsign.AddFlags() client.AddFlags() flag.Parse() camputMain(flag.Args()...) } func realExit(code int) { os.Exit(code) } // Indirections for replacement by tests: var ( stderr io.Writer = os.Stderr stdout io.Writer = os.Stdout stdin io.Reader = os.Stdin exit = realExit // TODO: abstract out vfs operation. should never call os.Stat, os.Open, os.Create, etc. // Only use fs.Stat, fs.Open, where vs is an interface type. // TODO: switch from using the global flag FlagSet and use our own. right now // running "go test -v" dumps the flag usage data to the global stderr. ) // camputMain is separated from main for testing from camput func camputMain(args ...string) { if *flagVersion { fmt.Fprintf(stderr, "camget version: %s\n", buildinfo.Version()) return } if *flagHelp { usage("") } if len(args) == 0 { usage("No mode given.") } mode := args[0] cmd, ok := modeCommand[mode] if !ok { usage(fmt.Sprintf("Unknown mode %q", mode)) } var up *Uploader if mode != "init" { up = newUploader() } cmdFlags := modeFlags[mode] err := cmdFlags.Parse(args[1:]) if err != nil { err = ErrUsage } else { err = cmd.RunCommand(up, cmdFlags.Args()) } if ue, isUsage := err.(UsageError); isUsage { if isUsage { errf("%s\n", ue) } cmd.Usage() errf("\nGlobal options:\n") flag.PrintDefaults() if hasFlags(cmdFlags) { errf("\nMode-specific options for mode %q:\n", mode) cmdFlags.PrintDefaults() } exit(1) } if *flagVerbose { stats := up.Stats() log.Printf("Client stats: %s", stats.String()) log.Printf(" #HTTP reqs: %d", up.transport.Requests()) } previousErrors := wereErrors if err != nil { wereErrors = true if !previousErrors { log.Printf("Error: %v", err) } } if wereErrors { exit(2) } }