Skip to content

Commit 18c6728

Browse files
committed
v0.5.0: parallel uploads
1 parent 9e36c79 commit 18c6728

9 files changed

Lines changed: 773 additions & 395 deletions

File tree

‎download.go‎

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"log"
7+
"os"
8+
"path/filepath"
9+
"strconv"
10+
"sync"
11+
12+
"github.com/fxamacker/cbor/v2"
13+
"github.com/xmit-co/xmit/protocol"
14+
"github.com/zeebo/blake3"
15+
)
16+
17+
func download(key, domain, id, destination string) error {
18+
downloadParallelism := 3
19+
if s := os.Getenv("DOWNLOAD_PARALLELISM"); s != "" {
20+
if v, err := strconv.Atoi(s); err == nil {
21+
downloadParallelism = v
22+
}
23+
}
24+
25+
// Discover endpoint
26+
log.Print("🔍 Discovering endpoint…")
27+
discovery, err := protocol.Discover()
28+
if err != nil {
29+
return fmt.Errorf("discovering endpoint: %w", err)
30+
}
31+
log.Printf("🌐 Using URL: %s", discovery.URL)
32+
33+
downloader, err := protocol.NewParallelDownloader(discovery.URL, downloadParallelism)
34+
if err != nil {
35+
return fmt.Errorf("creating parallel downloader: %w", err)
36+
}
37+
38+
resp, err := downloader.DownloadBundle(key, domain, id)
39+
if err != nil {
40+
return fmt.Errorf("downloading bundle: %w", err)
41+
}
42+
if !resp.Response.Success {
43+
return fmt.Errorf("downloading bundle, server-side: %v", resp.Response.Errors)
44+
}
45+
var node protocol.Node
46+
if err := cbor.NewDecoder(bytes.NewReader(resp.Bundle)).Decode(&node); err != nil {
47+
return fmt.Errorf("unmarshaling bundle: %w", err)
48+
}
49+
50+
return downloadTraversal(downloader, key, domain, &node, destination)
51+
}
52+
53+
func downloadTraversal(downloader *protocol.ParallelDownloader, key, domain string, node *protocol.Node, destination string) error {
54+
if node.Hash != nil {
55+
hash := *node.Hash
56+
if b, err := os.ReadFile(destination); err == nil {
57+
h2 := protocol.Hash(blake3.Sum256(b))
58+
if bytes.Equal(h2[:], hash[:]) {
59+
return nil
60+
}
61+
}
62+
log.Printf("🎁 Downloading %s", destination)
63+
resp, err := downloader.DownloadParts(key, domain, []protocol.Hash{hash})
64+
if err != nil {
65+
return fmt.Errorf("downloading part: %w", err)
66+
}
67+
if !resp.Response.Success {
68+
return fmt.Errorf("downloading part, server-side: %v", resp.Response.Errors)
69+
}
70+
if len(resp.Parts) == 0 {
71+
return fmt.Errorf("no part found for %s", hash)
72+
}
73+
if err := os.WriteFile(destination, resp.Parts[0], 0644); err != nil {
74+
return fmt.Errorf("writing file: %w", err)
75+
}
76+
log.Printf("✅ Downloaded %s", destination)
77+
} else {
78+
if err := os.MkdirAll(destination, 0755); err != nil {
79+
return err
80+
}
81+
var errors []error
82+
var mu sync.Mutex
83+
var wg sync.WaitGroup
84+
for name, child := range node.Children {
85+
wg.Add(1)
86+
go func() {
87+
defer wg.Done()
88+
if err := downloadTraversal(downloader, key, domain, child, filepath.Join(destination, name)); err != nil {
89+
mu.Lock()
90+
errors = append(errors, err)
91+
mu.Unlock()
92+
}
93+
}()
94+
}
95+
wg.Wait()
96+
if len(errors) > 0 {
97+
for _, err := range errors {
98+
log.Printf("🛑 Failed to download: %v", err)
99+
}
100+
return fmt.Errorf("%d subtraversals failed", len(errors))
101+
}
102+
}
103+
return nil
104+
}

‎flake.nix‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
};
1616
packages.default = pkgs.buildGoModule {
1717
pname = "xmit";
18-
version = "0.4.0";
18+
version = "0.5.0";
1919
src = ./.;
2020
vendorHash = "sha256-beSUbtBgJHlXmgv+cLSOaGP4evXdxN0sOpasw+v8xgs=";
2121
};

‎ingest.go‎

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package main
2+
3+
import (
4+
"log"
5+
"os"
6+
"path/filepath"
7+
8+
"github.com/xmit-co/xmit/protocol"
9+
"github.com/zeebo/blake3"
10+
)
11+
12+
type ingestion struct {
13+
protocol.Node
14+
contents map[protocol.Hash][]byte
15+
}
16+
17+
func ingest(directory string) (*ingestion, error) {
18+
bundle := ingestion{
19+
contents: make(map[protocol.Hash][]byte),
20+
}
21+
err := traverse(directory, &bundle.Node, &bundle.contents)
22+
return &bundle, err
23+
}
24+
25+
func traverse(directory string, node *protocol.Node, contents *map[protocol.Hash][]byte) error {
26+
entries, err := os.ReadDir(directory)
27+
if err != nil {
28+
return err
29+
}
30+
if node.Children == nil {
31+
node.Children = make(map[string]*protocol.Node)
32+
}
33+
for _, entry := range entries {
34+
p := filepath.Join(directory, entry.Name())
35+
if entry.IsDir() {
36+
if entry.Name() == ".git" {
37+
log.Printf("😇 Skipping %s", p)
38+
continue
39+
}
40+
child := protocol.Node{}
41+
err := traverse(p, &child, contents)
42+
if err != nil {
43+
return err
44+
}
45+
node.Children[entry.Name()] = &child
46+
} else {
47+
bytes, err := os.ReadFile(p)
48+
if err != nil {
49+
return err
50+
}
51+
hash := protocol.Hash(blake3.Sum256(bytes))
52+
node.Children[entry.Name()] = &protocol.Node{
53+
Hash: &hash,
54+
}
55+
(*contents)[hash] = bytes
56+
}
57+
}
58+
return nil
59+
}

‎key.go‎

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package main
2+
3+
import (
4+
"os"
5+
"path"
6+
"path/filepath"
7+
"strings"
8+
9+
"github.com/kirsle/configdir"
10+
)
11+
12+
var (
13+
keyPath = path.Join(configdir.LocalConfig("xmit"), "key")
14+
)
15+
16+
func findKey() string {
17+
if key, found := os.LookupEnv("XMIT_KEY"); found {
18+
return key
19+
}
20+
if b, err := os.ReadFile(keyPath); err == nil {
21+
return strings.TrimSpace(string(b))
22+
}
23+
return ""
24+
}
25+
26+
func storeKey(key string) error {
27+
if err := os.MkdirAll(filepath.Dir(keyPath), 0700); err != nil {
28+
return err
29+
}
30+
return os.WriteFile(keyPath, []byte(key), 0600)
31+
}

0 commit comments

Comments
 (0)