restic/internal/dump/common.go

143 lines
3.0 KiB
Go
Raw Normal View History

2020-11-09 22:22:27 +00:00
package dump
import (
"context"
"io"
"path"
2021-09-24 13:38:23 +00:00
"github.com/restic/restic/internal/bloblru"
2020-11-09 22:22:27 +00:00
"github.com/restic/restic/internal/errors"
"github.com/restic/restic/internal/restic"
"github.com/restic/restic/internal/walker"
)
// A Dumper writes trees and files from a repository to a Writer
// in an archive format.
type Dumper struct {
cache *bloblru.Cache
format string
repo restic.Repository
w io.Writer
2020-11-09 22:22:27 +00:00
}
func New(format string, repo restic.Repository, w io.Writer) *Dumper {
return &Dumper{
cache: bloblru.New(64 << 20),
format: format,
repo: repo,
w: w,
}
}
2021-09-24 13:38:23 +00:00
func (d *Dumper) DumpTree(ctx context.Context, tree *restic.Tree, rootPath string) error {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
// ch is buffered to deal with variable download/write speeds.
ch := make(chan *restic.Node, 10)
go sendTrees(ctx, d.repo, tree, rootPath, ch)
switch d.format {
case "tar":
return d.dumpTar(ctx, ch)
case "zip":
return d.dumpZip(ctx, ch)
default:
panic("unknown dump format")
}
2021-09-24 13:38:23 +00:00
}
func sendTrees(ctx context.Context, repo restic.Repository, tree *restic.Tree, rootPath string, ch chan *restic.Node) {
defer close(ch)
2020-12-18 23:42:46 +00:00
for _, root := range tree.Nodes {
root.Path = path.Join(rootPath, root.Name)
if sendNodes(ctx, repo, root, ch) != nil {
break
2020-11-09 22:22:27 +00:00
}
}
}
func sendNodes(ctx context.Context, repo restic.Repository, root *restic.Node, ch chan *restic.Node) error {
select {
case ch <- root:
case <-ctx.Done():
return ctx.Err()
2020-11-09 22:22:27 +00:00
}
// If this is no directory we are finished
if !IsDir(root) {
2020-11-09 22:22:27 +00:00
return nil
}
err := walker.Walk(ctx, repo, *root.Subtree, nil, func(_ restic.ID, nodepath string, node *restic.Node, err error) (bool, error) {
2020-11-09 22:22:27 +00:00
if err != nil {
return false, err
}
if node == nil {
return false, nil
}
node.Path = path.Join(root.Path, nodepath)
2020-11-09 22:22:27 +00:00
if !IsFile(node) && !IsDir(node) && !IsLink(node) {
return false, nil
}
select {
case ch <- node:
case <-ctx.Done():
return false, ctx.Err()
2020-11-09 22:22:27 +00:00
}
return false, nil
})
return err
}
// WriteNode writes a file node's contents directly to d's Writer,
// without caring about d's format.
func (d *Dumper) WriteNode(ctx context.Context, node *restic.Node) error {
return d.writeNode(ctx, d.w, node)
}
func (d *Dumper) writeNode(ctx context.Context, w io.Writer, node *restic.Node) error {
2020-11-09 22:22:27 +00:00
var (
buf []byte
err error
)
for _, id := range node.Content {
blob, ok := d.cache.Get(id)
2021-09-24 13:38:23 +00:00
if !ok {
blob, err = d.repo.LoadBlob(ctx, restic.DataBlob, id, buf)
2021-09-24 13:38:23 +00:00
if err != nil {
return err
}
buf = d.cache.Add(id, blob) // Reuse evicted buffer.
2020-11-09 22:22:27 +00:00
}
2021-09-24 13:38:23 +00:00
if _, err := w.Write(blob); err != nil {
2020-11-09 22:22:27 +00:00
return errors.Wrap(err, "Write")
}
}
return nil
}
// IsDir checks if the given node is a directory.
func IsDir(node *restic.Node) bool {
return node.Type == "dir"
}
// IsLink checks if the given node as a link.
func IsLink(node *restic.Node) bool {
return node.Type == "symlink"
}
// IsFile checks if the given node is a file.
func IsFile(node *restic.Node) bool {
return node.Type == "file"
}