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.

Revisions

  1. jdeng created this gist Jul 25, 2020.
    166 changes: 166 additions & 0 deletions test.go
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,166 @@
    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)
    }