2022-09-06 20:30:45 +00:00
package walker
import (
"context"
2022-10-15 08:14:50 +00:00
"fmt"
2022-09-06 20:30:45 +00:00
"path"
"github.com/restic/restic/internal/debug"
"github.com/restic/restic/internal/restic"
)
2022-12-28 10:04:28 +00:00
type NodeRewriteFunc func ( node * restic . Node , path string ) * restic . Node
2022-12-28 10:34:55 +00:00
type FailedTreeRewriteFunc func ( nodeID restic . ID , path string , err error ) ( restic . ID , error )
2024-07-11 14:24:00 +00:00
type QueryRewrittenSizeFunc func ( ) SnapshotSize
type SnapshotSize struct {
FileCount uint
FileSize uint64
}
2022-09-06 20:30:45 +00:00
2022-12-28 10:04:28 +00:00
type RewriteOpts struct {
// return nil to remove the node
RewriteNode NodeRewriteFunc
2022-12-28 10:34:55 +00:00
// decide what to do with a tree that could not be loaded. Return nil to remove the node. By default the load error is returned which causes the operation to fail.
RewriteFailedTree FailedTreeRewriteFunc
AllowUnstableSerialization bool
DisableNodeCache bool
2022-12-28 10:04:28 +00:00
}
2022-12-28 10:34:55 +00:00
type idMap map [ restic . ID ] restic . ID
2022-12-28 10:04:28 +00:00
type TreeRewriter struct {
opts RewriteOpts
2022-12-28 10:34:55 +00:00
replaces idMap
2022-12-28 10:04:28 +00:00
}
func NewTreeRewriter ( opts RewriteOpts ) * TreeRewriter {
rw := & TreeRewriter {
opts : opts ,
}
2022-12-28 10:34:55 +00:00
if ! opts . DisableNodeCache {
rw . replaces = make ( idMap )
}
2022-12-28 10:04:28 +00:00
// setup default implementations
if rw . opts . RewriteNode == nil {
2024-02-10 21:58:10 +00:00
rw . opts . RewriteNode = func ( node * restic . Node , _ string ) * restic . Node {
2022-12-28 10:04:28 +00:00
return node
}
}
2022-12-28 10:34:55 +00:00
if rw . opts . RewriteFailedTree == nil {
// fail with error by default
2024-02-10 21:58:10 +00:00
rw . opts . RewriteFailedTree = func ( _ restic . ID , _ string , err error ) ( restic . ID , error ) {
2022-12-28 10:34:55 +00:00
return restic . ID { } , err
}
}
2022-12-28 10:04:28 +00:00
return rw
2022-09-06 20:30:45 +00:00
}
2024-07-11 14:24:00 +00:00
func NewSnapshotSizeRewriter ( rewriteNode NodeRewriteFunc ) ( * TreeRewriter , QueryRewrittenSizeFunc ) {
var count uint
var size uint64
t := NewTreeRewriter ( RewriteOpts {
RewriteNode : func ( node * restic . Node , path string ) * restic . Node {
node = rewriteNode ( node , path )
if node != nil && node . Type == "file" {
count ++
size += node . Size
}
return node
} ,
DisableNodeCache : true ,
} )
ss := func ( ) SnapshotSize {
return SnapshotSize { count , size }
}
return t , ss
}
2022-10-14 21:26:13 +00:00
type BlobLoadSaver interface {
restic . BlobSaver
restic . BlobLoader
}
2022-12-28 10:04:28 +00:00
func ( t * TreeRewriter ) RewriteTree ( ctx context . Context , repo BlobLoadSaver , nodepath string , nodeID restic . ID ) ( newNodeID restic . ID , err error ) {
2022-12-28 10:34:55 +00:00
// check if tree was already changed
newID , ok := t . replaces [ nodeID ]
if ok {
return newID , nil
2022-09-06 20:30:45 +00:00
}
2022-12-28 10:34:55 +00:00
// a nil nodeID will lead to a load error
curTree , err := restic . LoadTree ( ctx , repo , nodeID )
2022-10-15 08:14:50 +00:00
if err != nil {
2022-12-28 10:34:55 +00:00
return t . opts . RewriteFailedTree ( nodeID , nodepath , err )
2022-10-15 08:14:50 +00:00
}
2022-12-28 10:34:55 +00:00
if ! t . opts . AllowUnstableSerialization {
// check that we can properly encode this tree without losing information
// The alternative of using json/Decoder.DisallowUnknownFields() doesn't work as we use
// a custom UnmarshalJSON to decode trees, see also https://github.com/golang/go/issues/41144
testID , err := restic . SaveTree ( ctx , repo , curTree )
if err != nil {
return restic . ID { } , err
}
if nodeID != testID {
return restic . ID { } , fmt . Errorf ( "cannot encode tree at %q without losing information" , nodepath )
}
2022-10-15 08:14:50 +00:00
}
2022-09-06 20:30:45 +00:00
debug . Log ( "filterTree: %s, nodeId: %s\n" , nodepath , nodeID . Str ( ) )
tb := restic . NewTreeJSONBuilder ( )
for _ , node := range curTree . Nodes {
path := path . Join ( nodepath , node . Name )
2022-12-28 10:04:28 +00:00
node = t . opts . RewriteNode ( node , path )
if node == nil {
2022-09-06 20:30:45 +00:00
continue
}
2022-12-28 09:42:21 +00:00
if node . Type != "dir" {
2022-09-06 20:30:45 +00:00
err = tb . AddNode ( node )
if err != nil {
return restic . ID { } , err
}
continue
}
2022-12-28 10:34:55 +00:00
// treat nil as null id
var subtree restic . ID
if node . Subtree != nil {
subtree = * node . Subtree
}
newID , err := t . RewriteTree ( ctx , repo , path , subtree )
2022-09-06 20:30:45 +00:00
if err != nil {
return restic . ID { } , err
}
node . Subtree = & newID
err = tb . AddNode ( node )
if err != nil {
return restic . ID { } , err
}
}
2022-12-28 09:38:40 +00:00
tree , err := tb . Finalize ( )
if err != nil {
return restic . ID { } , err
}
2022-09-06 20:30:45 +00:00
2022-12-28 09:38:40 +00:00
// Save new tree
newTreeID , _ , _ , err := repo . SaveBlob ( ctx , restic . TreeBlob , tree , restic . ID { } , false )
2022-12-28 10:34:55 +00:00
if t . replaces != nil {
t . replaces [ nodeID ] = newTreeID
}
2022-12-28 09:38:40 +00:00
if ! newTreeID . Equal ( nodeID ) {
2022-09-06 20:30:45 +00:00
debug . Log ( "filterTree: save new tree for %s as %v\n" , nodepath , newTreeID )
}
2022-12-28 09:38:40 +00:00
return newTreeID , err
2022-09-06 20:30:45 +00:00
}