Skip to content

Commit d85a9cf

Browse files
committed
convert to stream
1 parent c756fd6 commit d85a9cf

7 files changed

Lines changed: 249 additions & 90 deletions

File tree

internal/storage/backend.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package storage
22

33
import (
44
"context"
5+
"io"
56
)
67

78
// Backend defines the interface for our pluggable storage mechanism that acts as the
@@ -11,15 +12,15 @@ type Backend interface {
1112
Login(ctx context.Context) error
1213

1314
// Upload writes a new file to the storage backend.
14-
// filename is typically of the format request-<session>-<seq>-<timestamp>.json
15-
Upload(ctx context.Context, filename string, data []byte) error
15+
// filename is typically of the format request-<session>-<seq>-<timestamp>.bin
16+
Upload(ctx context.Context, filename string, data io.Reader) error
1617

1718
// ListQuery searches the backend for files matching a specific prefix or criteria.
1819
// We use this to discover new request or response payloads.
1920
ListQuery(ctx context.Context, prefix string) ([]string, error)
2021

21-
// Download reads the file content from the backend.
22-
Download(ctx context.Context, filename string) ([]byte, error)
22+
// Download returns an io.ReadCloser for the file content from the backend.
23+
Download(ctx context.Context, filename string) (io.ReadCloser, error)
2324

2425
// Delete removes a file from the backend after it has been read or expired.
2526
Delete(ctx context.Context, filename string) error

internal/storage/google.go

Lines changed: 33 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -217,35 +217,39 @@ func (b *GoogleBackend) getValidToken(ctx context.Context) (string, error) {
217217
return b.token, nil
218218
}
219219

220-
func (b *GoogleBackend) Upload(ctx context.Context, filename string, data []byte) error {
220+
func (b *GoogleBackend) Upload(ctx context.Context, filename string, data io.Reader) error {
221221
tok, err := b.getValidToken(ctx)
222222
if err != nil {
223223
return err
224224
}
225225

226-
var metaBuf bytes.Buffer
227-
metaWriter := multipart.NewWriter(&metaBuf)
226+
pr, pw := io.Pipe()
227+
metaWriter := multipart.NewWriter(pw)
228228

229-
// Part 1: Metadata
230-
h := make(textproto.MIMEHeader)
231-
h.Set("Content-Type", "application/json; charset=UTF-8")
232-
part1, _ := metaWriter.CreatePart(h)
233-
meta := map[string]interface{}{
234-
"name": filename,
235-
}
236-
if b.folderID != "" {
237-
meta["parents"] = []string{b.folderID}
238-
}
239-
json.NewEncoder(part1).Encode(meta)
229+
go func() {
230+
defer pw.Close()
231+
defer metaWriter.Close()
232+
233+
// Part 1: Metadata
234+
h := make(textproto.MIMEHeader)
235+
h.Set("Content-Type", "application/json; charset=UTF-8")
236+
part1, _ := metaWriter.CreatePart(h)
237+
meta := map[string]interface{}{
238+
"name": filename,
239+
}
240+
if b.folderID != "" {
241+
meta["parents"] = []string{b.folderID}
242+
}
243+
json.NewEncoder(part1).Encode(meta)
240244

241-
// Part 2: Content
242-
h = make(textproto.MIMEHeader)
243-
h.Set("Content-Type", "application/octet-stream")
244-
part2, _ := metaWriter.CreatePart(h)
245-
part2.Write(data)
246-
metaWriter.Close()
245+
// Part 2: Content
246+
h = make(textproto.MIMEHeader)
247+
h.Set("Content-Type", "application/octet-stream")
248+
part2, _ := metaWriter.CreatePart(h)
249+
io.Copy(part2, data)
250+
}()
247251

248-
req, err := http.NewRequestWithContext(ctx, "POST", "https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart", &metaBuf)
252+
req, err := http.NewRequestWithContext(ctx, "POST", "https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart", pr)
249253
if err != nil {
250254
return err
251255
}
@@ -310,6 +314,11 @@ func (b *GoogleBackend) ListQuery(ctx context.Context, prefix string) ([]string,
310314
}
311315

312316
b.fileIdsMu.Lock()
317+
// SAFETY: Prevent fileIDs map from infinite growth
318+
if len(b.fileIDs) > 2000 {
319+
b.fileIDs = make(map[string]string)
320+
}
321+
313322
var names []string
314323
for _, f := range resData.Files {
315324
// Only collect exact prefix matches client-side just in case
@@ -323,7 +332,7 @@ func (b *GoogleBackend) ListQuery(ctx context.Context, prefix string) ([]string,
323332
return names, nil
324333
}
325334

326-
func (b *GoogleBackend) Download(ctx context.Context, filename string) ([]byte, error) {
335+
func (b *GoogleBackend) Download(ctx context.Context, filename string) (io.ReadCloser, error) {
327336
b.fileIdsMu.RLock()
328337
fileID, ok := b.fileIDs[filename]
329338
b.fileIdsMu.RUnlock()
@@ -347,14 +356,14 @@ func (b *GoogleBackend) Download(ctx context.Context, filename string) ([]byte,
347356
if err != nil {
348357
return nil, err
349358
}
350-
defer resp.Body.Close()
351359

352360
if resp.StatusCode != http.StatusOK {
353361
body, _ := io.ReadAll(resp.Body)
362+
resp.Body.Close()
354363
return nil, fmt.Errorf("download returned %d: %s", resp.StatusCode, string(body))
355364
}
356365

357-
return io.ReadAll(resp.Body)
366+
return resp.Body, nil
358367
}
359368

360369
func (b *GoogleBackend) Delete(ctx context.Context, filename string) error {

internal/storage/local.go

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,22 @@ func (b *LocalBackend) Login(ctx context.Context) error {
3434
return nil
3535
}
3636

37-
func (b *LocalBackend) Upload(ctx context.Context, filename string, data []byte) error {
37+
func (b *LocalBackend) Upload(ctx context.Context, filename string, data io.Reader) error {
3838
path := filepath.Join(b.baseDir, filename)
3939
// Write to a temporary file first, then rename to avoid partial reads by the polling server
4040
tmpPath := path + ".tmp"
41-
if err := os.WriteFile(tmpPath, data, 0644); err != nil {
42-
return fmt.Errorf("failed to write temp file: %w", err)
41+
f, err := os.Create(tmpPath)
42+
if err != nil {
43+
return fmt.Errorf("failed to create temp file: %w", err)
44+
}
45+
46+
if _, err := io.Copy(f, data); err != nil {
47+
f.Close()
48+
os.Remove(tmpPath)
49+
return fmt.Errorf("failed to write data: %w", err)
4350
}
51+
f.Close()
52+
4453
if err := os.Rename(tmpPath, path); err != nil {
4554
os.Remove(tmpPath)
4655
return fmt.Errorf("failed to rename temp file: %w", err)
@@ -66,7 +75,7 @@ func (b *LocalBackend) ListQuery(ctx context.Context, prefix string) ([]string,
6675
return results, nil
6776
}
6877

69-
func (b *LocalBackend) Download(ctx context.Context, filename string) ([]byte, error) {
78+
func (b *LocalBackend) Download(ctx context.Context, filename string) (io.ReadCloser, error) {
7079
path := filepath.Join(b.baseDir, filename)
7180
file, err := os.Open(path)
7281
if err != nil {
@@ -76,13 +85,7 @@ func (b *LocalBackend) Download(ctx context.Context, filename string) ([]byte, e
7685
}
7786
return nil, fmt.Errorf("failed to open file: %w", err)
7887
}
79-
defer file.Close()
80-
81-
data, err := io.ReadAll(file)
82-
if err != nil {
83-
return nil, fmt.Errorf("failed to read file: %w", err)
84-
}
85-
return data, nil
88+
return file, nil
8689
}
8790

8891
func (b *LocalBackend) Delete(ctx context.Context, filename string) error {

internal/transport/conn.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ func (v *VirtualConn) Write(b []byte) (n int, err error) {
7272
func (v *VirtualConn) Close() error {
7373
v.session.mu.Lock()
7474
v.session.closed = true
75+
v.session.txCond.Broadcast() // Wake up any writers blocked on backpressure
7576
v.session.mu.Unlock()
7677

7778
// A closed connection no longer accepts writes efficiently

0 commit comments

Comments
 (0)