mirror of https://github.com/perkeep/perkeep.git
331 lines
7.1 KiB
Go
331 lines
7.1 KiB
Go
/*
|
|
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] <mode> [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 <mode> -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)
|
|
}
|
|
}
|