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/restic"
|
|
|
|
"github.com/restic/restic/internal/walker"
|
2024-05-04 16:57:37 +00:00
|
|
|
"golang.org/x/sync/errgroup"
|
2020-11-09 22:22:27 +00:00
|
|
|
)
|
|
|
|
|
2021-09-24 21:51:51 +00:00
|
|
|
// A Dumper writes trees and files from a repository to a Writer
|
|
|
|
// in an archive format.
|
|
|
|
type Dumper struct {
|
|
|
|
cache *bloblru.Cache
|
|
|
|
format string
|
2024-05-04 16:57:37 +00:00
|
|
|
repo restic.Loader
|
2021-09-24 21:51:51 +00:00
|
|
|
w io.Writer
|
2020-11-09 22:22:27 +00:00
|
|
|
}
|
|
|
|
|
2024-05-04 16:57:37 +00:00
|
|
|
func New(format string, repo restic.Loader, w io.Writer) *Dumper {
|
2021-09-24 21:51:51 +00:00
|
|
|
return &Dumper{
|
|
|
|
cache: bloblru.New(64 << 20),
|
|
|
|
format: format,
|
|
|
|
repo: repo,
|
|
|
|
w: w,
|
|
|
|
}
|
|
|
|
}
|
2021-09-24 13:38:23 +00:00
|
|
|
|
2021-09-24 21:51:51 +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
|
|
|
}
|
|
|
|
|
2024-01-19 21:44:50 +00:00
|
|
|
func sendTrees(ctx context.Context, repo restic.BlobLoader, tree *restic.Tree, rootPath string, ch chan *restic.Node) {
|
2021-09-24 21:51:51 +00:00
|
|
|
defer close(ch)
|
2020-12-18 23:42:46 +00:00
|
|
|
|
2021-09-24 21:51:51 +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
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-01-19 21:44:50 +00:00
|
|
|
func sendNodes(ctx context.Context, repo restic.BlobLoader, root *restic.Node, ch chan *restic.Node) error {
|
2021-09-24 21:51:51 +00:00
|
|
|
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
|
2024-07-09 17:51:44 +00:00
|
|
|
if root.Type != restic.NodeTypeDir {
|
2020-11-09 22:22:27 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-01-20 22:36:08 +00:00
|
|
|
err := walker.Walk(ctx, repo, *root.Subtree, walker.WalkVisitor{ProcessNode: func(_ restic.ID, nodepath string, node *restic.Node, err error) error {
|
2020-11-09 22:22:27 +00:00
|
|
|
if err != nil {
|
2024-01-06 12:59:47 +00:00
|
|
|
return err
|
2020-11-09 22:22:27 +00:00
|
|
|
}
|
|
|
|
if node == nil {
|
2024-01-06 12:59:47 +00:00
|
|
|
return nil
|
2020-11-09 22:22:27 +00:00
|
|
|
}
|
|
|
|
|
2021-09-24 21:51:51 +00:00
|
|
|
node.Path = path.Join(root.Path, nodepath)
|
2020-11-09 22:22:27 +00:00
|
|
|
|
2024-07-09 17:51:44 +00:00
|
|
|
if node.Type != restic.NodeTypeFile && node.Type != restic.NodeTypeDir && node.Type != restic.NodeTypeSymlink {
|
2024-01-06 12:59:47 +00:00
|
|
|
return nil
|
2021-09-24 21:51:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
select {
|
|
|
|
case ch <- node:
|
|
|
|
case <-ctx.Done():
|
2024-01-06 12:59:47 +00:00
|
|
|
return ctx.Err()
|
2020-11-09 22:22:27 +00:00
|
|
|
}
|
|
|
|
|
2024-01-06 12:59:47 +00:00
|
|
|
return nil
|
2024-01-20 22:36:08 +00:00
|
|
|
}})
|
2020-11-09 22:22:27 +00:00
|
|
|
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2021-09-24 21:51:51 +00:00
|
|
|
// 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 {
|
2024-05-04 16:57:37 +00:00
|
|
|
wg, ctx := errgroup.WithContext(ctx)
|
2024-09-30 15:24:05 +00:00
|
|
|
limit := d.repo.Connections() - 1 // See below for the -1.
|
|
|
|
blobs := make(chan (<-chan []byte), limit)
|
2024-05-04 16:57:37 +00:00
|
|
|
|
|
|
|
wg.Go(func() error {
|
2024-09-30 15:24:05 +00:00
|
|
|
for ch := range blobs {
|
2024-05-04 16:57:37 +00:00
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
|
|
|
return ctx.Err()
|
2024-09-30 15:24:05 +00:00
|
|
|
case blob := <-ch:
|
|
|
|
if _, err := w.Write(blob); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2024-05-04 16:57:37 +00:00
|
|
|
}
|
2020-11-09 22:22:27 +00:00
|
|
|
}
|
2024-05-04 16:57:37 +00:00
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
2024-09-30 15:24:05 +00:00
|
|
|
// Start short-lived goroutines to load blobs.
|
|
|
|
// There will be at most 1+cap(blobs) calling LoadBlob at any moment.
|
|
|
|
loop:
|
|
|
|
for _, id := range node.Content {
|
|
|
|
// This needs to be buffered, so that loaders can quit
|
|
|
|
// without waiting for the writer.
|
|
|
|
ch := make(chan []byte, 1)
|
|
|
|
|
2024-05-04 16:57:37 +00:00
|
|
|
wg.Go(func() error {
|
2024-09-30 15:24:05 +00:00
|
|
|
blob, err := d.cache.GetOrCompute(id, func() ([]byte, error) {
|
|
|
|
return d.repo.LoadBlob(ctx, restic.DataBlob, id, nil)
|
|
|
|
})
|
2024-05-04 16:57:37 +00:00
|
|
|
|
2024-09-30 15:24:05 +00:00
|
|
|
if err == nil {
|
|
|
|
ch <- blob
|
2024-05-04 16:57:37 +00:00
|
|
|
}
|
2024-09-30 15:24:05 +00:00
|
|
|
return err
|
2024-05-04 16:57:37 +00:00
|
|
|
})
|
2020-11-09 22:22:27 +00:00
|
|
|
|
2024-09-30 15:24:05 +00:00
|
|
|
select {
|
|
|
|
case blobs <- ch:
|
|
|
|
case <-ctx.Done():
|
|
|
|
break loop
|
2020-11-09 22:22:27 +00:00
|
|
|
}
|
2024-09-30 15:24:05 +00:00
|
|
|
}
|
2020-11-09 22:22:27 +00:00
|
|
|
|
2024-09-30 15:24:05 +00:00
|
|
|
close(blobs)
|
2024-05-04 16:57:37 +00:00
|
|
|
return wg.Wait()
|
2020-11-09 22:22:27 +00:00
|
|
|
}
|