Files
kopia/internal/server/api_restore.go
Ali Dowair eddde91f2d chore(snapshots): unify sparse and normal FS output paths (#1981)
* Unify sparse and normal IO output

This commit refactors the code paths that excercise normal and sparse
writing of restored content. The goal is to expose sparsefile.Copy()
and iocopy.Copy() to be interchangeable, thereby allowing us to wrap
or transform their behavior more easily in the future.

* Introduce getStreamCopier()

* Pull ioCopy() into getStreamCopier()

* Fix small nit in E2E test

We should be getting the block size of the destination file, not
the source file.

* Call stat.GetBlockSize() once per FilesystemOutput

A tiny refactor to pull this call out of the generated stream copier,
as the block size should not change from one file to the next within
a restore entry.

NOTE: as a side effect, if block size could not be found (an error
is returned), we will return the default stream copier instead of
letting the sparse copier fail. A warning will be logged, but this
error will not cause the restore to fail; it will proceed silently.
2022-06-14 18:09:45 +00:00

126 lines
3.2 KiB
Go

package server
import (
"archive/zip"
"context"
"encoding/json"
"os"
"github.com/pkg/errors"
"github.com/kopia/kopia/internal/serverapi"
"github.com/kopia/kopia/internal/uitask"
"github.com/kopia/kopia/snapshot/restore"
"github.com/kopia/kopia/snapshot/snapshotfs"
)
func restoreCounters(s restore.Stats) map[string]uitask.CounterValue {
return map[string]uitask.CounterValue{
"Restored Files": uitask.SimpleCounter(int64(s.RestoredFileCount)),
"Restored Directories": uitask.SimpleCounter(int64(s.RestoredDirCount)),
"Restored Symlinks": uitask.SimpleCounter(int64(s.RestoredSymlinkCount)),
"Restored Bytes": uitask.BytesCounter(s.RestoredTotalFileSize),
"Ignored Errors": uitask.SimpleCounter(int64(s.IgnoredErrorCount)),
"Skipped Files": uitask.SimpleCounter(int64(s.SkippedCount)),
"Skipped Bytes": uitask.BytesCounter(s.SkippedTotalFileSize),
}
}
func handleRestore(ctx context.Context, rc requestContext) (interface{}, *apiError) {
var req serverapi.RestoreRequest
if err := json.Unmarshal(rc.body, &req); err != nil {
return nil, requestError(serverapi.ErrorMalformedRequest, "malformed request body")
}
rep := rc.rep
if req.Root == "" {
return nil, requestError(serverapi.ErrorMalformedRequest, "root not specified")
}
rootEntry, err := snapshotfs.FilesystemEntryFromIDWithPath(ctx, rep, req.Root, false)
if err != nil {
return nil, internalServerError(err)
}
var (
out restore.Output
description string
)
switch {
case req.Filesystem != nil:
out := req.Filesystem
if err := out.Init(); err != nil {
return nil, internalServerError(err)
}
description = "Destination: " + req.Filesystem.TargetPath
case req.ZipFile != "":
f, err := os.Create(req.ZipFile)
if err != nil {
return nil, internalServerError(err)
}
if req.UncompressedZip {
out = restore.NewZipOutput(f, zip.Store)
description = "Uncompressed ZIP File: " + req.ZipFile
} else {
out = restore.NewZipOutput(f, zip.Deflate)
description = "ZIP File: " + req.ZipFile
}
case req.TarFile != "":
f, err := os.Create(req.TarFile)
if err != nil {
return nil, internalServerError(err)
}
out = restore.NewTarOutput(f)
description = "TAR File: " + req.TarFile
default:
return nil, requestError(serverapi.ErrorMalformedRequest, "output not specified")
}
taskIDChan := make(chan string)
// launch a goroutine that will continue the restore and can be observed in the Tasks UI.
// nolint:errcheck
go rc.srv.taskManager().Run(ctx, "Restore", description, func(ctx context.Context, ctrl uitask.Controller) error {
taskIDChan <- ctrl.CurrentTaskID()
opt := req.Options
opt.ProgressCallback = func(ctx context.Context, s restore.Stats) {
ctrl.ReportCounters(restoreCounters(s))
}
cancelChan := make(chan struct{})
opt.Cancel = cancelChan
ctrl.OnCancel(func() {
close(opt.Cancel)
})
st, err := restore.Entry(ctx, rep, out, rootEntry, opt)
if err == nil {
ctrl.ReportCounters(restoreCounters(st))
}
return errors.Wrap(err, "error restoring")
})
taskID := <-taskIDChan
task, ok := rc.srv.taskManager().GetTask(taskID)
if !ok {
return nil, internalServerError(errors.Errorf("task not found"))
}
return task, nil
}