Skip to content

Instantly share code, notes, and snippets.

@jdeng
Created July 25, 2020 04:27
Show Gist options
  • Save jdeng/81f64666c61d143d5d3d9fb88f1f64dd to your computer and use it in GitHub Desktop.
Save jdeng/81f64666c61d143d5d3d9fb88f1f64dd to your computer and use it in GitHub Desktop.
restic + rclone as a library example
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