2012-12-25 18:20:14 +00:00
// The camget tool fetches blobs, files, and directories.
//
// Examples
//
// Writes to stdout by default:
//
// camget <blobref> // dump raw blob
// camget -contents <file-blobref> // dump file contents
//
// Like curl, lets you set output file/directory with -o:
//
// camget -o <dir> <blobref>
// (if <dir> exists and is directory, <blobref> must be a directory;
// use -f to overwrite any files)
//
// camget -o <filename> <file-blobref>
//
// TODO(bradfitz): camget isn't very fleshed out. In general, using 'cammount' to just
// mount a tree is an easier way to get files back.
package main
2011-01-28 07:07:18 +00:00
/ *
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 .
* /
2011-01-02 22:36:03 +00:00
import (
2012-01-02 04:23:39 +00:00
"bytes"
Update from r60 to [almost] Go 1.
A lot is still broken, but most stuff at least compiles now.
The directory tree has been rearranged now too. Go libraries are now
under "pkg". Fully qualified, they are e.g. "camlistore.org/pkg/jsonsign".
The go tool cannot yet fetch from arbitrary domains, but discussion is
happening now on which mechanism to use to allow that.
For now, put the camlistore root under $GOPATH/src. Typically $GOPATH
is $HOME, so Camlistore should be at $HOME/src/camlistore.org.
Then you can:
$ go build ./server/camlistored
... etc
The build.pl script is currently disabled. It'll be resurrected at
some point, but with a very different role (helping create a fake
GOPATH and running the go build command, if things are installed at
the wrong place, and/or running fileembed generators).
Many things are certainly broken.
Many things are disabled. (MySQL, all indexing, etc).
Many things need to be moved into
camlistore.org/third_party/{code.google.com,github.com} and updated
from their r60 to Go 1 versions, where applicable.
The GoMySQL stuff should be updated to use database/sql and the ziutek
library implementing database/sql/driver.
Help wanted.
Change-Id: If71217dc5c8f0e70dbe46e9504ca5131c6eeacde
2012-02-19 05:53:06 +00:00
"errors"
2011-01-02 22:36:03 +00:00
"flag"
2012-01-02 04:23:39 +00:00
"fmt"
2011-01-15 01:25:37 +00:00
"io"
2011-01-02 22:36:03 +00:00
"log"
2013-01-06 00:44:34 +00:00
"net"
2013-01-03 04:32:13 +00:00
"net/http"
2011-01-15 01:25:37 +00:00
"os"
2012-01-02 05:07:58 +00:00
"path/filepath"
2011-01-02 22:36:03 +00:00
Update from r60 to [almost] Go 1.
A lot is still broken, but most stuff at least compiles now.
The directory tree has been rearranged now too. Go libraries are now
under "pkg". Fully qualified, they are e.g. "camlistore.org/pkg/jsonsign".
The go tool cannot yet fetch from arbitrary domains, but discussion is
happening now on which mechanism to use to allow that.
For now, put the camlistore root under $GOPATH/src. Typically $GOPATH
is $HOME, so Camlistore should be at $HOME/src/camlistore.org.
Then you can:
$ go build ./server/camlistored
... etc
The build.pl script is currently disabled. It'll be resurrected at
some point, but with a very different role (helping create a fake
GOPATH and running the go build command, if things are installed at
the wrong place, and/or running fileembed generators).
Many things are certainly broken.
Many things are disabled. (MySQL, all indexing, etc).
Many things need to be moved into
camlistore.org/third_party/{code.google.com,github.com} and updated
from their r60 to Go 1 versions, where applicable.
The GoMySQL stuff should be updated to use database/sql and the ziutek
library implementing database/sql/driver.
Help wanted.
Change-Id: If71217dc5c8f0e70dbe46e9504ca5131c6eeacde
2012-02-19 05:53:06 +00:00
"camlistore.org/pkg/blobref"
2013-02-02 18:59:39 +00:00
"camlistore.org/pkg/buildinfo"
2013-01-04 01:55:30 +00:00
"camlistore.org/pkg/cacher"
Update from r60 to [almost] Go 1.
A lot is still broken, but most stuff at least compiles now.
The directory tree has been rearranged now too. Go libraries are now
under "pkg". Fully qualified, they are e.g. "camlistore.org/pkg/jsonsign".
The go tool cannot yet fetch from arbitrary domains, but discussion is
happening now on which mechanism to use to allow that.
For now, put the camlistore root under $GOPATH/src. Typically $GOPATH
is $HOME, so Camlistore should be at $HOME/src/camlistore.org.
Then you can:
$ go build ./server/camlistored
... etc
The build.pl script is currently disabled. It'll be resurrected at
some point, but with a very different role (helping create a fake
GOPATH and running the go build command, if things are installed at
the wrong place, and/or running fileembed generators).
Many things are certainly broken.
Many things are disabled. (MySQL, all indexing, etc).
Many things need to be moved into
camlistore.org/third_party/{code.google.com,github.com} and updated
from their r60 to Go 1 versions, where applicable.
The GoMySQL stuff should be updated to use database/sql and the ziutek
library implementing database/sql/driver.
Help wanted.
Change-Id: If71217dc5c8f0e70dbe46e9504ca5131c6eeacde
2012-02-19 05:53:06 +00:00
"camlistore.org/pkg/client"
2013-01-03 04:32:13 +00:00
"camlistore.org/pkg/httputil"
Update from r60 to [almost] Go 1.
A lot is still broken, but most stuff at least compiles now.
The directory tree has been rearranged now too. Go libraries are now
under "pkg". Fully qualified, they are e.g. "camlistore.org/pkg/jsonsign".
The go tool cannot yet fetch from arbitrary domains, but discussion is
happening now on which mechanism to use to allow that.
For now, put the camlistore root under $GOPATH/src. Typically $GOPATH
is $HOME, so Camlistore should be at $HOME/src/camlistore.org.
Then you can:
$ go build ./server/camlistored
... etc
The build.pl script is currently disabled. It'll be resurrected at
some point, but with a very different role (helping create a fake
GOPATH and running the go build command, if things are installed at
the wrong place, and/or running fileembed generators).
Many things are certainly broken.
Many things are disabled. (MySQL, all indexing, etc).
Many things need to be moved into
camlistore.org/third_party/{code.google.com,github.com} and updated
from their r60 to Go 1 versions, where applicable.
The GoMySQL stuff should be updated to use database/sql and the ziutek
library implementing database/sql/driver.
Help wanted.
Change-Id: If71217dc5c8f0e70dbe46e9504ca5131c6eeacde
2012-02-19 05:53:06 +00:00
"camlistore.org/pkg/index"
"camlistore.org/pkg/schema"
2011-10-11 00:38:18 +00:00
)
2011-01-02 22:36:03 +00:00
2011-10-11 00:38:18 +00:00
var (
2013-02-02 18:59:39 +00:00
flagVersion = flag . Bool ( "version" , false , "show version" )
2012-12-25 18:20:14 +00:00
flagVerbose = flag . Bool ( "verbose" , false , "be verbose" )
2013-01-03 04:32:13 +00:00
flagHTTP = flag . Bool ( "verbose_http" , false , "show HTTP request summaries" )
2012-12-25 18:20:14 +00:00
flagCheck = flag . Bool ( "check" , false , "just check for the existence of listed blobs; returning 0 if all are present" )
flagOutput = flag . String ( "o" , "-" , "Output file/directory to create. Use -f to overwrite." )
flagGraph = flag . Bool ( "graph" , false , "Output a graphviz directed graph .dot file of the provided root schema blob, to be rendered with 'dot -Tsvg -o graph.svg graph.dot'" )
flagContents = flag . Bool ( "contents" , false , "If true and the target blobref is a 'bytes' or 'file' schema blob, the contents of that file are output instead." )
2013-01-02 20:55:12 +00:00
flagShared = flag . String ( "shared" , "" , "If non-empty, the URL of a \"share\" blob. The URL will be used as the root of future fetches. Only \"haveref\" shares are currently supported." )
2011-10-11 00:38:18 +00:00
)
2011-01-02 22:36:03 +00:00
func main ( ) {
2011-10-11 00:38:18 +00:00
client . AddFlags ( )
2011-01-02 22:36:03 +00:00
flag . Parse ( )
2013-02-02 18:59:39 +00:00
if * flagVersion {
fmt . Fprintf ( os . Stderr , "camget version: %s\n" , buildinfo . Version ( ) )
return
}
2012-10-28 18:32:25 +00:00
if * flagGraph && flag . NArg ( ) != 1 {
log . Fatalf ( "The --graph option requires exactly one parameter." )
}
2013-01-02 20:55:12 +00:00
var cl * client . Client
var items [ ] * blobref . BlobRef
2011-01-15 01:25:37 +00:00
2013-01-02 20:55:12 +00:00
if * flagShared != "" {
if client . ExplicitServer ( ) != "" {
log . Fatal ( "Can't use --shared with an explicit blobserver; blobserver is implicit from the --shared URL." )
}
if flag . NArg ( ) != 0 {
log . Fatal ( "No arguments permitted when using --shared" )
2011-01-02 22:36:03 +00:00
}
2013-01-02 20:55:12 +00:00
cl1 , target , err := client . NewFromShareRoot ( * flagShared )
if err != nil {
log . Fatal ( err )
}
cl = cl1
items = append ( items , target )
} else {
cl = client . NewOrFail ( )
for n := 0 ; n < flag . NArg ( ) ; n ++ {
arg := flag . Arg ( n )
br := blobref . Parse ( arg )
if br == nil {
log . Fatalf ( "Failed to parse argument %q as a blobref." , arg )
}
items = append ( items , br )
}
}
2013-01-03 04:32:13 +00:00
httpStats := & httputil . StatsTransport {
VerboseLog : * flagHTTP ,
}
2013-01-06 00:44:34 +00:00
if * flagHTTP {
httpStats . Transport = & http . Transport {
Dial : func ( net_ , addr string ) ( net . Conn , error ) {
log . Printf ( "Dialing %s" , addr )
return net . Dial ( net_ , addr )
} ,
}
}
2013-01-03 04:32:13 +00:00
cl . SetHTTPClient ( & http . Client { Transport : httpStats } )
2013-01-20 17:52:03 +00:00
diskCacheFetcher , err := cacher . NewDiskCache ( cl )
2013-01-04 01:55:30 +00:00
if err != nil {
log . Fatalf ( "Error setting up local disk cache: %v" , err )
}
2013-01-20 17:52:03 +00:00
defer diskCacheFetcher . Clean ( )
2013-01-06 18:44:50 +00:00
if * flagVerbose {
2013-01-20 17:52:03 +00:00
log . Printf ( "Using temp blob cache directory %s" , diskCacheFetcher . Root )
2013-01-06 18:44:50 +00:00
}
2013-01-04 01:55:30 +00:00
2013-01-02 20:55:12 +00:00
for _ , br := range items {
2012-10-28 18:32:25 +00:00
if * flagGraph {
2013-01-20 17:52:03 +00:00
printGraph ( diskCacheFetcher , br )
2012-10-28 18:32:25 +00:00
return
}
2012-01-02 04:23:39 +00:00
if * flagCheck {
// TODO: do HEAD requests checking if the blobs exists.
log . Fatal ( "not implemented" )
return
2011-01-26 05:33:19 +00:00
}
2012-01-02 04:23:39 +00:00
if * flagOutput == "-" {
2012-12-25 18:20:14 +00:00
var rc io . ReadCloser
var err error
if * flagContents {
2013-01-20 17:52:03 +00:00
rc , err = schema . NewFileReader ( diskCacheFetcher , br )
2013-01-06 07:12:42 +00:00
if err == nil {
rc . ( * schema . FileReader ) . LoadAllChunks ( )
}
2012-12-25 18:20:14 +00:00
} else {
2013-01-20 17:52:03 +00:00
rc , err = fetch ( diskCacheFetcher , br )
2012-12-25 18:20:14 +00:00
}
2012-01-02 04:23:39 +00:00
if err != nil {
log . Fatal ( err )
}
defer rc . Close ( )
if _ , err := io . Copy ( os . Stdout , rc ) ; err != nil {
log . Fatalf ( "Failed reading %q: %v" , br , err )
2011-01-26 05:33:19 +00:00
}
2013-01-03 04:32:13 +00:00
} else {
2013-01-20 17:52:03 +00:00
if err := smartFetch ( diskCacheFetcher , * flagOutput , br ) ; err != nil {
2013-01-03 04:32:13 +00:00
log . Fatal ( err )
}
2011-01-15 01:25:37 +00:00
}
2012-01-02 04:23:39 +00:00
}
2013-01-03 04:32:13 +00:00
if * flagVerbose {
log . Printf ( "HTTP requests: %d\n" , httpStats . Requests ( ) )
}
2012-01-02 04:23:39 +00:00
}
2013-01-04 01:55:30 +00:00
func fetch ( src blobref . StreamingFetcher , br * blobref . BlobRef ) ( r io . ReadCloser , err error ) {
2012-01-02 04:23:39 +00:00
if * flagVerbose {
log . Printf ( "Fetching %s" , br . String ( ) )
}
2013-01-04 01:55:30 +00:00
r , _ , err = src . FetchStreaming ( br )
2012-01-02 04:23:39 +00:00
if err != nil {
2013-01-02 20:55:12 +00:00
return nil , fmt . Errorf ( "Failed to fetch %s: %s" , br , err )
2012-01-02 04:23:39 +00:00
}
return r , err
}
2012-01-02 05:07:58 +00:00
// A little less than the sniffer will take, so we don't truncate.
const sniffSize = 900 * 1024
2012-01-02 04:23:39 +00:00
2012-12-25 18:20:14 +00:00
// smartFetch the things that blobs point to, not just blobs.
2013-01-04 01:55:30 +00:00
func smartFetch ( src blobref . StreamingFetcher , targ string , br * blobref . BlobRef ) error {
rc , err := fetch ( src , br )
2012-01-02 04:23:39 +00:00
if err != nil {
return err
}
defer rc . Close ( )
2013-01-22 17:32:40 +00:00
sniffer := index . NewBlobSniffer ( br )
Update from r60 to [almost] Go 1.
A lot is still broken, but most stuff at least compiles now.
The directory tree has been rearranged now too. Go libraries are now
under "pkg". Fully qualified, they are e.g. "camlistore.org/pkg/jsonsign".
The go tool cannot yet fetch from arbitrary domains, but discussion is
happening now on which mechanism to use to allow that.
For now, put the camlistore root under $GOPATH/src. Typically $GOPATH
is $HOME, so Camlistore should be at $HOME/src/camlistore.org.
Then you can:
$ go build ./server/camlistored
... etc
The build.pl script is currently disabled. It'll be resurrected at
some point, but with a very different role (helping create a fake
GOPATH and running the go build command, if things are installed at
the wrong place, and/or running fileembed generators).
Many things are certainly broken.
Many things are disabled. (MySQL, all indexing, etc).
Many things need to be moved into
camlistore.org/third_party/{code.google.com,github.com} and updated
from their r60 to Go 1 versions, where applicable.
The GoMySQL stuff should be updated to use database/sql and the ziutek
library implementing database/sql/driver.
Help wanted.
Change-Id: If71217dc5c8f0e70dbe46e9504ca5131c6eeacde
2012-02-19 05:53:06 +00:00
_ , err = io . CopyN ( sniffer , rc , sniffSize )
if err != nil && err != io . EOF {
2012-01-02 04:23:39 +00:00
return err
}
sniffer . Parse ( )
2013-01-22 17:32:40 +00:00
blob , ok := sniffer . SchemaBlob ( )
2012-01-02 04:23:39 +00:00
if ! ok {
2013-01-02 19:23:00 +00:00
if * flagVerbose {
log . Printf ( "Fetching opaque data %v into %q" , br , targ )
}
2012-01-02 04:23:39 +00:00
// opaque data - put it in a file
2012-01-02 05:07:58 +00:00
f , err := os . Create ( targ )
2011-01-15 01:25:37 +00:00
if err != nil {
2012-01-02 05:07:58 +00:00
return fmt . Errorf ( "opaque: %v" , err )
2011-01-15 01:25:37 +00:00
}
2012-01-02 04:23:39 +00:00
defer f . Close ( )
body , _ := sniffer . Body ( )
2013-01-02 17:20:35 +00:00
r := io . MultiReader ( bytes . NewReader ( body ) , rc )
2012-01-02 04:23:39 +00:00
_ , err = io . Copy ( f , r )
return err
2011-01-02 22:36:03 +00:00
}
2013-01-22 17:32:40 +00:00
switch blob . Type ( ) {
2012-01-02 05:07:58 +00:00
case "directory" :
2013-01-22 17:32:40 +00:00
dir := filepath . Join ( targ , blob . FileName ( ) )
2013-01-02 19:23:00 +00:00
if * flagVerbose {
log . Printf ( "Fetching directory %v into %s" , br , dir )
}
2013-01-22 17:32:40 +00:00
if err := os . MkdirAll ( dir , blob . FileMode ( ) ) ; err != nil {
2012-01-02 05:07:58 +00:00
return err
}
2013-01-22 17:32:40 +00:00
if err := setFileMeta ( dir , blob ) ; err != nil {
2012-01-02 05:07:58 +00:00
log . Print ( err )
}
2013-01-22 17:32:40 +00:00
entries := blob . DirectoryEntries ( )
2012-01-02 05:07:58 +00:00
if entries == nil {
2013-01-22 17:32:40 +00:00
return fmt . Errorf ( "bad entries blobref in dir %v" , blob . BlobRef ( ) )
2012-01-02 05:07:58 +00:00
}
2013-01-04 01:55:30 +00:00
return smartFetch ( src , dir , entries )
2012-01-02 05:07:58 +00:00
case "static-set" :
2013-01-02 19:23:00 +00:00
if * flagVerbose {
log . Printf ( "Fetching directory entries %v into %s" , br , targ )
}
2012-01-02 05:07:58 +00:00
// directory entries
2013-01-02 23:13:22 +00:00
const numWorkers = 10
type work struct {
2013-01-03 04:32:13 +00:00
br * blobref . BlobRef
2013-01-02 23:13:22 +00:00
errc chan <- error
}
2013-01-22 17:32:40 +00:00
members := blob . StaticSetMembers ( )
workc := make ( chan work , len ( members ) )
2013-01-02 23:13:22 +00:00
defer close ( workc )
for i := 0 ; i < numWorkers ; i ++ {
go func ( ) {
for wi := range workc {
2013-01-04 01:55:30 +00:00
wi . errc <- smartFetch ( src , targ , wi . br )
2013-01-02 23:13:22 +00:00
}
} ( )
}
var errcs [ ] <- chan error
2013-01-22 17:32:40 +00:00
for _ , mref := range members {
2013-01-02 23:13:22 +00:00
errc := make ( chan error , 1 )
errcs = append ( errcs , errc )
2013-01-22 17:32:40 +00:00
workc <- work { mref , errc }
2013-01-02 23:13:22 +00:00
}
for _ , errc := range errcs {
if err := <- errc ; err != nil {
2012-01-02 05:07:58 +00:00
return err
}
}
return nil
case "file" :
2013-01-04 01:55:30 +00:00
seekFetcher := blobref . SeekerFromStreamingFetcher ( src )
2012-12-25 18:20:14 +00:00
fr , err := schema . NewFileReader ( seekFetcher , br )
if err != nil {
return fmt . Errorf ( "NewFileReader: %v" , err )
2012-01-02 05:07:58 +00:00
}
2013-01-06 07:12:42 +00:00
fr . LoadAllChunks ( )
2012-12-25 18:20:14 +00:00
defer fr . Close ( )
2013-01-22 17:32:40 +00:00
name := filepath . Join ( targ , blob . FileName ( ) )
2013-01-02 19:23:00 +00:00
if fi , err := os . Stat ( name ) ; err == nil && fi . Size ( ) == fi . Size ( ) {
if * flagVerbose {
log . Printf ( "Skipping %s; already exists." , name )
return nil
}
}
if * flagVerbose {
log . Printf ( "Writing %s to %s ..." , br , name )
}
f , err := os . Create ( name )
if err != nil {
return fmt . Errorf ( "file type: %v" , err )
}
defer f . Close ( )
if _ , err := io . Copy ( f , fr ) ; err != nil {
return fmt . Errorf ( "Copying %s to %s: %v" , br , name , err )
}
2013-01-22 17:32:40 +00:00
if err := setFileMeta ( name , blob ) ; err != nil {
2012-01-02 05:07:58 +00:00
log . Print ( err )
}
return nil
default :
2013-01-22 17:32:40 +00:00
return errors . New ( "unknown blob type: " + blob . Type ( ) )
2012-01-02 05:07:58 +00:00
}
panic ( "unreachable" )
}
2012-01-02 04:23:39 +00:00
2013-01-22 17:32:40 +00:00
func setFileMeta ( name string , blob * schema . Blob ) error {
err1 := os . Chmod ( name , blob . FileMode ( ) )
2012-12-25 18:20:14 +00:00
var err2 error
2013-01-22 17:32:40 +00:00
if mt := blob . ModTime ( ) ; ! mt . IsZero ( ) {
2012-12-25 18:20:14 +00:00
err2 = os . Chtimes ( name , mt , mt )
2012-01-02 05:07:58 +00:00
}
2013-01-22 17:32:40 +00:00
// TODO: we previously did os.Chown here, but it's rarely wanted,
// then the schema.Blob refactor broke it, so it's gone.
// Add it back later once we care?
for _ , err := range [ ] error { err1 , err2 } {
2012-12-25 18:20:14 +00:00
if err != nil {
return err
}
2012-01-02 05:07:58 +00:00
}
2012-12-25 18:20:14 +00:00
return nil
2011-01-02 22:36:03 +00:00
}