Created
July 25, 2020 04:27
-
-
Save jdeng/81f64666c61d143d5d3d9fb88f1f64dd to your computer and use it in GitHub Desktop.
restic + rclone as a library example
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"bytes" | |
"context" | |
"flag" | |
"fmt" | |
// "os" | |
"path" | |
"path/filepath" | |
"time" | |
restic "github.com/restic/restic/cmd/global" | |
) | |
func splitPath(p string) []string { | |
d, f := path.Split(p) | |
if d == "" || d == "/" { | |
return []string{f} | |
} | |
s := splitPath(path.Clean(d)) | |
return append(s, f) | |
} | |
func getFileBytesFromNode(ctx context.Context, repo restic.Repository, node *restic.Node) ([]byte, error) { | |
var out bytes.Buffer | |
var buf []byte | |
for _, id := range node.Content { | |
size, found := repo.LookupBlobSize(id, restic.DataBlob) | |
if !found { | |
return nil, fmt.Errorf("id %v not found in repository", id) | |
} | |
buf = buf[:cap(buf)] | |
if len(buf) < restic.CiphertextLength(int(size)) { | |
buf = restic.NewBlobBuffer(int(size)) | |
} | |
n, err := repo.LoadBlob(ctx, restic.DataBlob, id, buf) | |
if err != nil { | |
return nil, err | |
} | |
buf = n | |
_, err = out.Write(buf) | |
if err != nil { | |
return nil, err | |
} | |
} | |
return out.Bytes(), nil | |
} | |
func getFileBytes(ctx context.Context, tree *restic.Tree, repo restic.Repository, prefix string, pathComponents []string) ([]byte, error) { | |
if tree == nil { | |
return nil, fmt.Errorf("called with a nil tree") | |
} | |
if repo == nil { | |
return nil, fmt.Errorf("called with a nil repository") | |
} | |
l := len(pathComponents) | |
if l == 0 { | |
return nil, fmt.Errorf("empty path components") | |
} | |
item := filepath.Join(prefix, pathComponents[0]) | |
for _, node := range tree.Nodes { | |
if node.Name == pathComponents[0] { | |
switch { | |
case l == 1 && node.Type == "file": | |
return getFileBytesFromNode(ctx, repo, node) | |
case l > 1 && node.Type == "dir": | |
subtree, err := repo.LoadTree(ctx, *node.Subtree) | |
if err != nil { | |
return nil, err | |
} | |
return getFileBytes(ctx, subtree, repo, item, pathComponents[1:]) | |
case l > 1: | |
return nil, fmt.Errorf("%q should be a dir, but s a %q", item, node.Type) | |
case node.Type != "file": | |
return nil, fmt.Errorf("%q should be a file, but is a %q", item, node.Type) | |
} | |
} | |
} | |
return nil, fmt.Errorf("path %q not found in snapshot", item) | |
} | |
func LoadRepository(ctx context.Context, repository, password, rootPath string) (repo restic.Repository, tree *restic.Tree, err error) { | |
opts := restic.GlobalOptions{Repo: repository, Ctx: ctx} | |
repo, err = OpenRepository(opts, password) | |
if err != nil { | |
return | |
} | |
err = repo.LoadIndex(ctx) | |
if err != nil { | |
return | |
} | |
id, err := restic.FindLatestSnapshot(ctx, repo, []string{rootPath}, []restic.TagList{}, []string{}) | |
if err != nil { | |
return | |
} | |
sn, err := restic.LoadSnapshot(ctx, repo, id) | |
if err != nil { | |
return | |
} | |
tree, err = repo.LoadTree(ctx, *sn.Tree) | |
return | |
} | |
func elapsed(what string) func() { | |
start := time.Now() | |
return func() { | |
fmt.Printf("%s took %v\n", what, time.Since(start)) | |
} | |
} | |
func GetFileBytes(ctx context.Context, repo restic.Repository, tree *restic.Tree, p string) ([]byte, error) { | |
defer elapsed("GetFileBytes: " + p)() | |
prefix := "" | |
splittedPath := splitPath(p) | |
return getFileBytes(ctx, tree, repo, prefix, splittedPath) | |
} | |
const maxKeys = 20 | |
// OpenRepository reads the password and opens the repository. | |
func OpenRepository(opts restic.GlobalOptions, password string) (restic.Repository, error) { | |
be, err := restic.Open(opts, restic.Options{}) | |
if err != nil { | |
return nil, err | |
} | |
s := restic.NewRepository(be) | |
err = s.SearchKey(opts.Ctx, password, maxKeys, opts.KeyHint) | |
if err != nil { | |
return nil, err | |
} | |
c, err := restic.NewCache(s.Config().ID, opts.CacheDir) | |
if err != nil { | |
fmt.Printf("unable to open cache: %v\n", err) | |
return s, nil | |
} | |
// start using the cache | |
s.UseCache(c) | |
return s, nil | |
} | |
var ( | |
repo = flag.String("repo", "", "repository address") | |
password = flag.String("password", "", "password") | |
root = flag.String("root", "", "root path") | |
) | |
func main() { | |
flag.Parse() | |
r, tree, err := LoadRepository(context.Background(), *repo, *password, *root) | |
fmt.Printf("repo: %v, tree: %v, err: %v\n", r, tree, err) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment