2016-06-03 01:09:14 +00:00
package command
2013-01-21 03:44:23 +00:00
import (
"archive/tar"
2013-01-22 23:07:19 +00:00
"bytes"
2014-10-26 18:25:02 +00:00
"fmt"
2013-01-21 03:44:23 +00:00
"os"
"path"
2015-07-09 06:19:54 +00:00
"path/filepath"
2013-01-21 03:44:23 +00:00
"strconv"
"strings"
2013-01-22 23:07:19 +00:00
"text/template"
2013-01-21 03:44:23 +00:00
"time"
2014-10-26 18:34:55 +00:00
2016-06-03 01:09:14 +00:00
"github.com/chrislusf/seaweedfs/weed/glog"
"github.com/chrislusf/seaweedfs/weed/storage"
2018-07-08 09:28:04 +00:00
"github.com/chrislusf/seaweedfs/weed/storage/types"
2018-07-15 03:51:17 +00:00
"io"
2013-01-21 03:44:23 +00:00
)
2013-01-22 23:07:19 +00:00
const (
defaultFnFormat = ` {{ .Mime }} / {{ .Id }} : {{ .Name }} `
2015-05-26 06:53:45 +00:00
timeFormat = "2006-01-02T15:04:05"
2013-01-22 23:07:19 +00:00
)
2015-06-02 02:25:01 +00:00
var (
export ExportOptions
)
type ExportOptions struct {
dir * string
collection * string
volumeId * int
}
2013-01-21 03:44:23 +00:00
var cmdExport = & Command {
2015-05-26 06:53:45 +00:00
UsageLine : "export -dir=/tmp -volumeId=234 -o=/dir/name.tar -fileNameFormat={{.Name}} -newer='" + timeFormat + "'" ,
2013-01-21 03:51:27 +00:00
Short : "list or export files from one volume data file" ,
Long : ` List all files in a volume , or Export all files in a volume to a tar file if the output is specified .
2014-08-25 18:37:00 +00:00
2013-03-19 04:29:25 +00:00
The format of file name in the tar file can be customized . Default is { { . Mime } } / { { . Id } } : { { . Name } } . Also available is { { . Key } } .
2013-01-21 03:44:23 +00:00
` ,
}
2015-06-02 02:25:01 +00:00
func init ( ) {
cmdExport . Run = runExport // break init cycle
export . dir = cmdExport . Flag . String ( "dir" , "." , "input data directory to store volume data files" )
export . collection = cmdExport . Flag . String ( "collection" , "" , "the volume collection name" )
export . volumeId = cmdExport . Flag . Int ( "volumeId" , - 1 , "a volume id. The volume .dat and .idx files should already exist in the dir." )
}
2013-01-21 03:44:23 +00:00
var (
2018-07-15 03:51:17 +00:00
output = cmdExport . Flag . String ( "o" , "" , "output tar file name, must ends with .tar, or just a \"-\" for stdout" )
format = cmdExport . Flag . String ( "fileNameFormat" , defaultFnFormat , "filename formatted with {{.Mime}} {{.Id}} {{.Name}} {{.Ext}}" )
newer = cmdExport . Flag . String ( "newer" , "" , "export only files newer than this time, default is all files. Must be specified in RFC3339 without timezone, e.g. 2006-01-02T15:04:05" )
showDeleted = cmdExport . Flag . Bool ( "deleted" , false , "export deleted files. only applies if -o is not specified" )
limit = cmdExport . Flag . Int ( "limit" , 0 , "only show first n entries if specified" )
2015-06-02 07:23:41 +00:00
2015-06-02 07:33:13 +00:00
tarOutputFile * tar . Writer
tarHeader tar . Header
fileNameTemplate * template . Template
fileNameTemplateBuffer = bytes . NewBuffer ( nil )
newerThan time . Time
newerThanUnix int64 = - 1
localLocation , _ = time . LoadLocation ( "Local" )
2013-01-21 03:44:23 +00:00
)
2018-07-15 03:51:17 +00:00
func printNeedle ( vid storage . VolumeId , n * storage . Needle , version storage . Version , deleted bool ) {
key := storage . NewFileIdFromNeedle ( vid , n ) . String ( )
size := n . DataSize
if version == storage . Version1 {
size = n . Size
}
fmt . Printf ( "%s\t%s\t%d\t%t\t%s\t%s\t%s\t%t\n" ,
key ,
n . Name ,
size ,
n . IsGzipped ( ) ,
n . Mime ,
n . LastModifiedString ( ) ,
n . Ttl . String ( ) ,
deleted ,
)
}
2019-01-16 09:48:59 +00:00
type VolumeFileScanner4Export struct {
version storage . Version
counter int
needleMap * storage . NeedleMap
vid storage . VolumeId
}
func ( scanner * VolumeFileScanner4Export ) VisitSuperBlock ( superBlock storage . SuperBlock ) error {
scanner . version = superBlock . Version ( )
return nil
}
func ( scanner * VolumeFileScanner4Export ) ReadNeedleBody ( ) bool {
return true
}
func ( scanner * VolumeFileScanner4Export ) VisitNeedle ( n * storage . Needle , offset int64 ) error {
needleMap := scanner . needleMap
vid := scanner . vid
nv , ok := needleMap . Get ( n . Id )
glog . V ( 3 ) . Infof ( "key %d offset %d size %d disk_size %d gzip %v ok %v nv %+v" ,
n . Id , offset , n . Size , n . DiskSize ( scanner . version ) , n . IsGzipped ( ) , ok , nv )
2019-04-09 02:40:56 +00:00
if ok && nv . Size > 0 && nv . Size != types . TombstoneFileSize && nv . Offset . ToAcutalOffset ( ) == offset {
2019-01-16 09:48:59 +00:00
if newerThanUnix >= 0 && n . HasLastModifiedDate ( ) && n . LastModified < uint64 ( newerThanUnix ) {
glog . V ( 3 ) . Infof ( "Skipping this file, as it's old enough: LastModified %d vs %d" ,
n . LastModified , newerThanUnix )
return nil
}
scanner . counter ++
if * limit > 0 && scanner . counter > * limit {
return io . EOF
}
if tarOutputFile != nil {
return writeFile ( vid , n )
} else {
printNeedle ( vid , n , scanner . version , false )
return nil
}
}
if ! ok {
if * showDeleted && tarOutputFile == nil {
if n . DataSize > 0 {
printNeedle ( vid , n , scanner . version , true )
} else {
n . Name = [ ] byte ( "*tombstone" )
printNeedle ( vid , n , scanner . version , true )
}
}
glog . V ( 2 ) . Infof ( "This seems deleted %d size %d" , n . Id , n . Size )
} else {
glog . V ( 2 ) . Infof ( "Skipping later-updated Id %d size %d" , n . Id , n . Size )
}
return nil
}
2013-01-21 03:44:23 +00:00
func runExport ( cmd * Command , args [ ] string ) bool {
2015-05-20 12:11:12 +00:00
var err error
if * newer != "" {
if newerThan , err = time . ParseInLocation ( timeFormat , * newer , localLocation ) ; err != nil {
fmt . Println ( "cannot parse 'newer' argument: " + err . Error ( ) )
return false
}
newerThanUnix = newerThan . Unix ( )
}
2015-06-02 07:23:41 +00:00
if * export . volumeId == - 1 {
2013-01-21 03:44:23 +00:00
return false
}
2015-06-02 07:33:13 +00:00
if * output != "" {
if * output != "-" && ! strings . HasSuffix ( * output , ".tar" ) {
fmt . Println ( "the output file" , * output , "should be '-' or end with .tar" )
2013-01-22 01:50:10 +00:00
return false
}
2013-01-22 23:07:19 +00:00
2015-06-02 07:33:13 +00:00
if fileNameTemplate , err = template . New ( "name" ) . Parse ( * format ) ; err != nil {
2013-01-22 23:07:19 +00:00
fmt . Println ( "cannot parse format " + * format + ": " + err . Error ( ) )
return false
}
2015-06-02 07:33:13 +00:00
var outputFile * os . File
if * output == "-" {
outputFile = os . Stdout
2013-01-21 03:44:23 +00:00
} else {
2015-06-02 07:33:13 +00:00
if outputFile , err = os . Create ( * output ) ; err != nil {
glog . Fatalf ( "cannot open output tar %s: %s" , * output , err )
2013-01-21 03:44:23 +00:00
}
}
2015-06-02 07:33:13 +00:00
defer outputFile . Close ( )
tarOutputFile = tar . NewWriter ( outputFile )
defer tarOutputFile . Close ( )
2013-01-21 03:44:23 +00:00
t := time . Now ( )
tarHeader = tar . Header { Mode : 0644 ,
ModTime : t , Uid : os . Getuid ( ) , Gid : os . Getgid ( ) ,
2018-07-22 00:39:10 +00:00
Typeflag : tar . TypeReg ,
2013-01-21 03:44:23 +00:00
AccessTime : t , ChangeTime : t }
}
2015-06-02 07:23:41 +00:00
fileName := strconv . Itoa ( * export . volumeId )
if * export . collection != "" {
fileName = * export . collection + "_" + fileName
2014-01-22 04:51:07 +00:00
}
2015-06-02 07:23:41 +00:00
vid := storage . VolumeId ( * export . volumeId )
indexFile , err := os . OpenFile ( path . Join ( * export . dir , fileName + ".idx" ) , os . O_RDONLY , 0644 )
2013-01-21 03:44:23 +00:00
if err != nil {
2013-08-09 06:57:22 +00:00
glog . Fatalf ( "Create Volume Index [ERROR] %s\n" , err )
2013-01-21 03:44:23 +00:00
}
defer indexFile . Close ( )
2017-05-27 05:51:25 +00:00
needleMap , err := storage . LoadBtreeNeedleMap ( indexFile )
2013-02-10 21:41:25 +00:00
if err != nil {
2014-04-17 07:16:44 +00:00
glog . Fatalf ( "cannot load needle map from %s: %s" , indexFile . Name ( ) , err )
2013-02-10 21:41:25 +00:00
}
2013-01-21 03:44:23 +00:00
2019-01-16 09:48:59 +00:00
volumeFileScanner := & VolumeFileScanner4Export {
needleMap : needleMap ,
vid : vid ,
}
2013-01-22 01:50:10 +00:00
2018-07-15 03:51:17 +00:00
if tarOutputFile == nil {
fmt . Printf ( "key\tname\tsize\tgzip\tmime\tmodified\tttl\tdeleted\n" )
}
2019-01-16 09:48:59 +00:00
err = storage . ScanVolumeFile ( * export . dir , * export . collection , vid , storage . NeedleMapInMemory , volumeFileScanner )
2018-07-15 03:51:17 +00:00
if err != nil && err != io . EOF {
2013-08-09 06:57:22 +00:00
glog . Fatalf ( "Export Volume File [ERROR] %s\n" , err )
2013-01-21 03:44:23 +00:00
}
return true
}
2013-01-22 23:07:19 +00:00
type nameParams struct {
Name string
2018-07-08 09:28:04 +00:00
Id types . NeedleId
2013-01-22 23:07:19 +00:00
Mime string
Key string
2015-07-09 06:19:54 +00:00
Ext string
2013-01-22 23:07:19 +00:00
}
2018-07-15 03:51:17 +00:00
func writeFile ( vid storage . VolumeId , n * storage . Needle ) ( err error ) {
2014-03-24 04:57:10 +00:00
key := storage . NewFileIdFromNeedle ( vid , n ) . String ( )
2018-07-15 03:51:17 +00:00
fileNameTemplateBuffer . Reset ( )
if err = fileNameTemplate . Execute ( fileNameTemplateBuffer ,
nameParams {
Name : string ( n . Name ) ,
Id : n . Id ,
Mime : string ( n . Mime ) ,
Key : key ,
Ext : filepath . Ext ( string ( n . Name ) ) ,
} ,
) ; err != nil {
return err
}
2013-01-22 23:07:19 +00:00
2018-07-15 03:51:17 +00:00
fileName := fileNameTemplateBuffer . String ( )
2015-06-02 07:33:13 +00:00
2018-07-15 03:51:17 +00:00
if n . IsGzipped ( ) && path . Ext ( fileName ) != ".gz" {
fileName = fileName + ".gz"
}
2013-01-22 23:07:19 +00:00
2018-07-15 03:51:17 +00:00
tarHeader . Name , tarHeader . Size = fileName , int64 ( len ( n . Data ) )
if n . HasLastModifiedDate ( ) {
tarHeader . ModTime = time . Unix ( int64 ( n . LastModified ) , 0 )
2013-01-21 03:44:23 +00:00
} else {
2018-07-15 03:51:17 +00:00
tarHeader . ModTime = time . Unix ( 0 , 0 )
}
tarHeader . ChangeTime = tarHeader . ModTime
if err = tarOutputFile . WriteHeader ( & tarHeader ) ; err != nil {
return err
2013-01-21 03:44:23 +00:00
}
2018-07-15 03:51:17 +00:00
_ , err = tarOutputFile . Write ( n . Data )
2013-01-21 03:44:23 +00:00
return
}