Skip to content
This repository was archived by the owner on Jan 28, 2026. It is now read-only.

Commit 0e2ce0e

Browse files
authored
Merge pull request #33 from tablelandnetwork/bcalza/uptretrieve
updates retrieve command to read from cache
2 parents 4043f3b + 4c566d2 commit 0e2ce0e

File tree

8 files changed

+293
-77
lines changed

8 files changed

+293
-77
lines changed

cmd/vaults/commands.go

Lines changed: 33 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import (
66
"encoding/json"
77
"errors"
88
"fmt"
9-
"io"
109
"os"
1110
"path"
1211
"regexp"
@@ -16,13 +15,7 @@ import (
1615
"github.com/ethereum/go-ethereum/common"
1716
"github.com/ethereum/go-ethereum/common/hexutil"
1817
"github.com/ethereum/go-ethereum/crypto"
19-
"github.com/filecoin-project/lassie/pkg/lassie"
20-
"github.com/filecoin-project/lassie/pkg/storage"
21-
"github.com/filecoin-project/lassie/pkg/types"
2218
"github.com/ipfs/go-cid"
23-
"github.com/ipld/go-car/v2"
24-
"github.com/ipld/go-car/v2/storage/deferred"
25-
trustlessutils "github.com/ipld/go-trustless-utils"
2619
"github.com/jackc/pgx/v5"
2720
"github.com/jackc/pgx/v5/pgconn"
2821
"github.com/olekukonko/tablewriter"
@@ -596,7 +589,9 @@ func newListEventsCommand() *cli.Command {
596589
}
597590

598591
func newRetrieveCommand() *cli.Command {
599-
var output string
592+
var output, provider string
593+
var cache bool
594+
var timeout int64
600595

601596
return &cli.Command{
602597
Name: "retrieve",
@@ -614,6 +609,33 @@ func newRetrieveCommand() *cli.Command {
614609
DefaultText: "current directory",
615610
Destination: &output,
616611
},
612+
&cli.StringFlag{
613+
Name: "provider",
614+
Aliases: []string{"p"},
615+
Category: "OPTIONAL:",
616+
Usage: "The provider's address and port (e.g., localhost:8080)",
617+
DefaultText: DefaultProviderHost,
618+
Destination: &provider,
619+
Value: DefaultProviderHost,
620+
},
621+
&cli.BoolFlag{
622+
Name: "cache",
623+
Aliases: []string{"c"},
624+
Category: "OPTIONAL:",
625+
Usage: "Retrieves from cache by setting this flag",
626+
DefaultText: "current directory",
627+
Destination: &cache,
628+
Value: true,
629+
},
630+
&cli.Int64Flag{
631+
Name: "timeout",
632+
Aliases: []string{"t"},
633+
Category: "OPTIONAL:",
634+
Usage: "Timeout for retrieval operation (seconds)",
635+
DefaultText: "no timeout",
636+
Destination: &timeout,
637+
Value: 0,
638+
},
617639
},
618640
Action: func(cCtx *cli.Context) error {
619641
arg := cCtx.Args().Get(0)
@@ -626,73 +648,9 @@ func newRetrieveCommand() *cli.Command {
626648
return errors.New("CID is invalid")
627649
}
628650

629-
lassie, err := lassie.NewLassie(cCtx.Context)
630-
if err != nil {
631-
return fmt.Errorf("failed to create lassie instance: %s", err)
632-
}
633-
634-
carOpts := []car.Option{
635-
car.WriteAsCarV1(true),
636-
car.StoreIdentityCIDs(false),
637-
car.UseWholeCIDs(false),
638-
}
639-
640-
var carWriter *deferred.DeferredCarWriter
641-
var tmpFile *os.File
642-
643-
if output == "-" {
644-
// Create a temporary file only for writing to stdout case
645-
tmpFile, err = os.CreateTemp("", fmt.Sprintf("%s.car", arg))
646-
if err != nil {
647-
return fmt.Errorf("failed to create temporary file: %s", err)
648-
}
649-
defer func() {
650-
_ = os.Remove(tmpFile.Name())
651-
}()
652-
carWriter = deferred.NewDeferredCarWriterForPath(tmpFile.Name(), []cid.Cid{rootCid}, carOpts...)
653-
} else {
654-
// Write to the provided path or current directory
655-
if output == "" {
656-
output = "." // Default to current directory
657-
}
658-
// Ensure path is a valid directory
659-
info, err := os.Stat(output)
660-
if err != nil {
661-
return fmt.Errorf("failed to access output directory: %s", err)
662-
}
663-
if !info.IsDir() {
664-
return fmt.Errorf("output path is not a directory: %s", output)
665-
}
666-
carPath := path.Join(output, fmt.Sprintf("%s.car", arg))
667-
carWriter = deferred.NewDeferredCarWriterForPath(carPath, []cid.Cid{rootCid}, carOpts...)
668-
}
669-
670-
defer func() {
671-
_ = carWriter.Close()
672-
}()
673-
carStore := storage.NewCachingTempStore(
674-
carWriter.BlockWriteOpener(), storage.NewDeferredStorageCar(os.TempDir(), rootCid),
675-
)
676-
defer func() {
677-
_ = carStore.Close()
678-
}()
679-
680-
request, err := types.NewRequestForPath(carStore, rootCid, "", trustlessutils.DagScopeAll, nil)
681-
if err != nil {
682-
return fmt.Errorf("failed to create request: %s", err)
683-
}
684-
685-
if _, err := lassie.Fetch(cCtx.Context, request, []types.FetchOption{}...); err != nil {
686-
return fmt.Errorf("failed to fetch: %s", err)
687-
}
688-
689-
// Write to stdout only if the output flag is set to '-'
690-
if output == "-" && tmpFile != nil {
691-
_, _ = tmpFile.Seek(0, io.SeekStart)
692-
_, err = io.Copy(os.Stdout, tmpFile)
693-
if err != nil {
694-
return fmt.Errorf("failed to write to stdout: %s", err)
695-
}
651+
retriever := app.NewRetriever(vaultsprovider.New(provider), cache, timeout)
652+
if err := retriever.Retrieve(cCtx.Context, rootCid, output); err != nil {
653+
return fmt.Errorf("failed to retrieve: %s", err)
696654
}
697655

698656
return nil

internal/app/retriever.go

Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
package app
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"io"
7+
"os"
8+
"path"
9+
10+
"github.com/filecoin-project/lassie/pkg/lassie"
11+
"github.com/filecoin-project/lassie/pkg/storage"
12+
"github.com/filecoin-project/lassie/pkg/types"
13+
"github.com/ipfs/go-cid"
14+
"github.com/ipld/go-car/v2"
15+
"github.com/ipld/go-car/v2/storage/deferred"
16+
trustlessutils "github.com/ipld/go-trustless-utils"
17+
)
18+
19+
type retriever interface {
20+
retrieveStdout(context.Context, cid.Cid, int64) error
21+
retrieveFile(context.Context, cid.Cid, string, int64) error
22+
}
23+
24+
// Retriever is responsible for retrieving file from the network.
25+
type Retriever struct {
26+
store retriever
27+
timeout int64
28+
}
29+
30+
// NewRetriever creates a new Retriever.
31+
func NewRetriever(provider VaultsProvider, cache bool, timeout int64) *Retriever {
32+
if cache {
33+
return &Retriever{
34+
store: &cacheStore{
35+
provider: provider,
36+
},
37+
timeout: timeout,
38+
}
39+
}
40+
41+
panic("cold store not implemented yet")
42+
}
43+
44+
// Retrieve retrieves file from the network.
45+
func (r *Retriever) Retrieve(ctx context.Context, c cid.Cid, output string) error {
46+
if output == "-" {
47+
return r.store.retrieveStdout(ctx, c, r.timeout)
48+
}
49+
50+
return r.store.retrieveFile(ctx, c, output, r.timeout)
51+
}
52+
53+
type cacheStore struct {
54+
provider VaultsProvider
55+
}
56+
57+
func (cs *cacheStore) retrieveStdout(ctx context.Context, cid cid.Cid, timeout int64) error {
58+
if _, err := cs.provider.RetrieveEvent(ctx, RetrieveEventParams{
59+
Timeout: timeout,
60+
CID: cid,
61+
}, os.Stdout); err != nil {
62+
return fmt.Errorf("failed to retrieve to file: %s", err)
63+
}
64+
65+
return nil
66+
}
67+
68+
func (cs *cacheStore) retrieveFile(ctx context.Context, cid cid.Cid, output string, timeout int64) error {
69+
// Write to the provided path or current directory
70+
if output == "" {
71+
output = "." // Default to current directory
72+
}
73+
// Ensure path is a valid directory
74+
info, err := os.Stat(output)
75+
if err != nil {
76+
return fmt.Errorf("failed to access output directory: %s", err)
77+
}
78+
if !info.IsDir() {
79+
return fmt.Errorf("output path is not a directory: %s", output)
80+
}
81+
82+
f, err := os.OpenFile(path.Join(output, cid.String()), os.O_RDWR|os.O_CREATE, 0o666)
83+
if err != nil {
84+
return fmt.Errorf("failed to open tmp file: %s", err)
85+
}
86+
_, _ = f.Seek(0, io.SeekStart)
87+
88+
filename, err := cs.provider.RetrieveEvent(ctx, RetrieveEventParams{
89+
Timeout: timeout,
90+
CID: cid,
91+
}, f)
92+
if err != nil {
93+
return fmt.Errorf("failed to retrieve to file: %s", err)
94+
}
95+
96+
if err := os.Rename(f.Name(), path.Join(output, fmt.Sprintf("%s-%s", cid.String(), filename))); err != nil {
97+
return fmt.Errorf("failed renaming the file: %s", err)
98+
}
99+
100+
return nil
101+
}
102+
103+
type coldStore struct{} // nolint
104+
105+
func (cs *coldStore) retrieve(ctx context.Context, c cid.Cid, path string) error { // nolint
106+
lassie, err := lassie.NewLassie(ctx)
107+
if err != nil {
108+
return fmt.Errorf("failed to create lassie instance: %s", err)
109+
}
110+
111+
carOpts := []car.Option{
112+
car.WriteAsCarV1(true),
113+
car.StoreIdentityCIDs(false),
114+
car.UseWholeCIDs(false),
115+
}
116+
117+
carWriter := deferred.NewDeferredCarWriterForPath(path, []cid.Cid{c}, carOpts...)
118+
defer func() {
119+
_ = carWriter.Close()
120+
}()
121+
carStore := storage.NewCachingTempStore(
122+
carWriter.BlockWriteOpener(), storage.NewDeferredStorageCar(os.TempDir(), c),
123+
)
124+
defer func() {
125+
_ = carStore.Close()
126+
}()
127+
128+
request, err := types.NewRequestForPath(carStore, c, "", trustlessutils.DagScopeAll, nil)
129+
if err != nil {
130+
return fmt.Errorf("failed to create request: %s", err)
131+
}
132+
133+
if _, err := lassie.Fetch(ctx, request, []types.FetchOption{}...); err != nil {
134+
return fmt.Errorf("failed to fetch: %s", err)
135+
}
136+
137+
return nil
138+
}

internal/app/retriever_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package app
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"io"
7+
"os"
8+
"path"
9+
"testing"
10+
11+
"github.com/ipfs/go-cid"
12+
"github.com/stretchr/testify/require"
13+
)
14+
15+
func TestRetrieverFileOutput(t *testing.T) {
16+
retriever := NewRetriever(&vaultsProviderMock{}, true, 0)
17+
output := t.TempDir()
18+
cid := cid.Cid{}
19+
err := retriever.Retrieve(context.Background(), cid, output)
20+
require.NoError(t, err)
21+
22+
f, err := os.Open(path.Join(output, fmt.Sprintf("%s-%s", cid.String(), "sample.txt")))
23+
require.NoError(t, err)
24+
25+
data, err := io.ReadAll(f)
26+
require.NoError(t, err)
27+
28+
require.Equal(t, []byte("Hello"), data)
29+
}
30+
31+
func TestRetrieverStdoutOutput(t *testing.T) {
32+
old := os.Stdout
33+
r, w, _ := os.Pipe()
34+
os.Stdout = w // overwrite os.Stdout so we can read from it
35+
36+
retriever := NewRetriever(&vaultsProviderMock{}, true, 0)
37+
38+
err := retriever.Retrieve(context.Background(), cid.Cid{}, "-")
39+
require.NoError(t, err)
40+
41+
_ = w.Close()
42+
data, _ := io.ReadAll(r)
43+
os.Stdout = old
44+
45+
require.Equal(t, []byte("Hello"), data)
46+
}

internal/app/streamer_test.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,3 +282,10 @@ func (bp *vaultsProviderMock) WriteVaultEvent(
282282
close(bp.uploaderInputs)
283283
return nil
284284
}
285+
286+
func (bp *vaultsProviderMock) RetrieveEvent(
287+
_ context.Context, _ RetrieveEventParams, w io.Writer,
288+
) (string, error) {
289+
_, _ = w.Write([]byte("Hello"))
290+
return "sample.txt", nil
291+
}

internal/app/timestamp.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ func ParseTimestamp(ts string) (Timestamp, error) {
4343
if t, err := time.Parse(time.RFC3339, ts); err == nil {
4444
return Timestamp{t.UTC()}, nil
4545
}
46-
fmt.Println(time.Parse(time.RFC3339, ts))
4746

4847
return Timestamp{}, fmt.Errorf("could not parse %s", ts)
4948
}

internal/app/uploader.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"io"
1010
"log"
1111
"os"
12+
"strings"
1213

1314
"github.com/ethereum/go-ethereum/common"
1415
"github.com/ethereum/go-ethereum/crypto"
@@ -53,10 +54,17 @@ func (bu *VaultsUploader) Upload(
5354
return fmt.Errorf("signing the file: %s", err)
5455
}
5556

57+
filename := filepath
58+
if strings.Contains(filepath, "/") {
59+
parts := strings.Split(filepath, "/")
60+
filename = parts[len(parts)-1]
61+
}
62+
5663
params := WriteVaultEventParams{
5764
Vault: Vault(fmt.Sprintf("%s.%s", bu.namespace, bu.relation)),
5865
Timestamp: ts,
5966
Content: f,
67+
Filename: filename,
6068
ProgressBar: progress,
6169
Signature: hex.EncodeToString(signature),
6270
Size: sz,

0 commit comments

Comments
 (0)