Skip to content

Commit 169e5a9

Browse files
committed
Update multipart upload to use upload_id from server
The server now returns an upload_id from the start endpoint, which must be included in chunk and finalize endpoint paths.
1 parent b0faa6c commit 169e5a9

2 files changed

Lines changed: 37 additions & 22 deletions

File tree

internal/turso/tursoServer.go

Lines changed: 24 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -79,17 +79,17 @@ func (i *TursoServerClient) UploadFileMultipart(filepath string, remoteEncryptio
7979
totalSize := stat.Size()
8080
startTime := time.Now()
8181

82-
chunkSize, err := i.startMultipartUpload(totalSize)
82+
uploadStart, err := i.startMultipartUpload(totalSize)
8383
if err != nil {
8484
return err
8585
}
8686

87-
uploadedBytes, err := i.uploadChunks(chunkSize, file, totalSize, startTime, remoteEncryptionCipher, remoteEncryptionKey, onUploadProgress)
87+
uploadedBytes, err := i.uploadChunks(uploadStart.UploadID, uploadStart.ChunkSize, file, totalSize, startTime, remoteEncryptionCipher, remoteEncryptionKey, onUploadProgress)
8888
if err != nil {
8989
return err
9090
}
9191

92-
if err = i.finalizeUpload(); err != nil {
92+
if err = i.finalizeUpload(uploadStart.UploadID); err != nil {
9393
return err
9494
}
9595

@@ -99,42 +99,51 @@ func (i *TursoServerClient) UploadFileMultipart(filepath string, remoteEncryptio
9999
return nil
100100
}
101101

102-
func (i *TursoServerClient) startMultipartUpload(dbSize int64) (int64, error) {
102+
type multipartUploadStart struct {
103+
ChunkSize int64
104+
UploadID string
105+
}
106+
107+
func (i *TursoServerClient) startMultipartUpload(dbSize int64) (multipartUploadStart, error) {
103108
requestBody := map[string]int64{
104109
"db_size_bytes": dbSize,
105110
}
106111

107112
body, err := marshal(requestBody)
108113
if err != nil {
109-
return 0, fmt.Errorf("failed to marshal multipart upload request: %w", err)
114+
return multipartUploadStart{}, fmt.Errorf("failed to marshal multipart upload request: %w", err)
110115
}
111116

112117
r, err := i.client.Put("/v2/upload/start", body)
113118
if err != nil {
114-
return 0, fmt.Errorf("failed to initiate multipart upload: %w", err)
119+
return multipartUploadStart{}, fmt.Errorf("failed to initiate multipart upload: %w", err)
115120
}
116121
defer r.Body.Close()
117122

118123
if r.StatusCode != http.StatusOK {
119124
body, err := io.ReadAll(r.Body)
120125
if err != nil {
121-
return 0, fmt.Errorf("initiate multipart upload failed with status code %d and error reading response: %v", r.StatusCode, err)
126+
return multipartUploadStart{}, fmt.Errorf("initiate multipart upload failed with status code %d and error reading response: %v", r.StatusCode, err)
122127
}
123-
return 0, fmt.Errorf("initiate multipart upload failed with status code %d: %s", r.StatusCode, string(body))
128+
return multipartUploadStart{}, fmt.Errorf("initiate multipart upload failed with status code %d: %s", r.StatusCode, string(body))
124129
}
125130

126131
type multipartUploadResponse struct {
127-
ChunkSize int64 `json:"chunk_size"`
132+
ChunkSize int64 `json:"chunk_size"`
133+
UploadID string `json:"upload_id"`
128134
}
129135
var uploadResp multipartUploadResponse
130136
if err := json.NewDecoder(r.Body).Decode(&uploadResp); err != nil {
131-
return 0, fmt.Errorf("failed to decode multipart upload response: %w", err)
137+
return multipartUploadStart{}, fmt.Errorf("failed to decode multipart upload response: %w", err)
132138
}
133139

134-
return uploadResp.ChunkSize, nil
140+
return multipartUploadStart{
141+
ChunkSize: uploadResp.ChunkSize,
142+
UploadID: uploadResp.UploadID,
143+
}, nil
135144
}
136145

137-
func (i *TursoServerClient) uploadChunks(chunkSize int64, file io.Reader, totalSize int64, startTime time.Time, remoteEncryptionCipher, remoteEncryptionKey string, onUploadProgress func(progressPct int, uploadedBytes int64, totalBytes int64, elapsedTime time.Duration, done bool)) (int64, error) {
146+
func (i *TursoServerClient) uploadChunks(uploadID string, chunkSize int64, file io.Reader, totalSize int64, startTime time.Time, remoteEncryptionCipher, remoteEncryptionKey string, onUploadProgress func(progressPct int, uploadedBytes int64, totalBytes int64, elapsedTime time.Duration, done bool)) (int64, error) {
138147
var uploadedBytes int64 = 0
139148
chunkID := 0
140149
lastProgressPct := -1
@@ -157,7 +166,7 @@ func (i *TursoServerClient) uploadChunks(chunkSize int64, file io.Reader, totalS
157166
lastUpdate: lastProgressPct,
158167
}
159168

160-
chunkPath := fmt.Sprintf("/v2/upload/chunk/%d", chunkID)
169+
chunkPath := fmt.Sprintf("/v2/upload/%s/chunk/%d", uploadID, chunkID)
161170

162171
var headers = map[string]string{}
163172
if remoteEncryptionCipher != "" && remoteEncryptionKey != "" {
@@ -191,8 +200,8 @@ func (i *TursoServerClient) uploadChunks(chunkSize int64, file io.Reader, totalS
191200
return uploadedBytes, nil
192201
}
193202

194-
func (i *TursoServerClient) finalizeUpload() error {
195-
r, err := i.client.Put("/v2/upload/finalize", nil)
203+
func (i *TursoServerClient) finalizeUpload(uploadID string) error {
204+
r, err := i.client.Put(fmt.Sprintf("/v2/upload/%s/finalize", uploadID), nil)
196205
if err != nil {
197206
return fmt.Errorf("failed to finalize multipart upload: %w", err)
198207
}

internal/turso/tursoServer_test.go

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ type MockTursoServer struct {
2727
receivedHeaders map[string][]string
2828
requestCount int
2929
chunkData map[int][]byte
30+
uploadID string
3031

3132
// Configurable responses
3233
startUploadStatus int
@@ -48,6 +49,7 @@ func NewMockTursoServer() *MockTursoServer {
4849
finalizeStatus: http.StatusOK,
4950
chunkSize: 1024 * 1024, // 1MB default
5051
failAtChunk: -1,
52+
uploadID: "test-upload-id",
5153
}
5254

5355
mock.Server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@@ -63,9 +65,9 @@ func NewMockTursoServer() *MockTursoServer {
6365
switch {
6466
case r.Method == "PUT" && r.URL.Path == "/v2/upload/start":
6567
mock.handleMultipartStart(w, r)
66-
case r.Method == "PUT" && strings.HasPrefix(r.URL.Path, "/v2/upload/chunk/"):
68+
case r.Method == "PUT" && strings.HasPrefix(r.URL.Path, "/v2/upload/"+mock.uploadID+"/chunk/"):
6769
mock.handleChunkUpload(w, r)
68-
case r.Method == "PUT" && r.URL.Path == "/v2/upload/finalize":
70+
case r.Method == "PUT" && r.URL.Path == "/v2/upload/"+mock.uploadID+"/finalize":
6971
mock.handleFinalize(w, r)
7072
default:
7173
w.WriteHeader(http.StatusNotFound)
@@ -83,7 +85,10 @@ func (m *MockTursoServer) handleMultipartStart(w http.ResponseWriter, r *http.Re
8385
}
8486

8587
w.WriteHeader(m.startUploadStatus)
86-
json.NewEncoder(w).Encode(map[string]int64{"chunk_size": m.chunkSize})
88+
json.NewEncoder(w).Encode(map[string]interface{}{
89+
"chunk_size": m.chunkSize,
90+
"upload_id": m.uploadID,
91+
})
8792
}
8893

8994
func (m *MockTursoServer) handleChunkUpload(w http.ResponseWriter, r *http.Request) {
@@ -517,22 +522,23 @@ func TestUploadFileMultipart_ProgressCallbackPerChunk(t *testing.T) {
517522
func TestUploadFileMultipart_ContentLengthHeader(t *testing.T) {
518523
var receivedContentLengths []string
519524
var mu sync.Mutex
525+
uploadID := "test-upload-id"
520526

521527
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
522528
mu.Lock()
523-
if strings.HasPrefix(r.URL.Path, "/v2/upload/chunk/") {
529+
if strings.Contains(r.URL.Path, "/chunk/") {
524530
receivedContentLengths = append(receivedContentLengths, r.Header.Get("Content-Length"))
525531
}
526532
mu.Unlock()
527533

528534
switch {
529535
case r.URL.Path == "/v2/upload/start":
530536
w.WriteHeader(http.StatusOK)
531-
_ = json.NewEncoder(w).Encode(map[string]int64{"chunk_size": 1024})
532-
case strings.HasPrefix(r.URL.Path, "/v2/upload/chunk/"):
537+
_ = json.NewEncoder(w).Encode(map[string]interface{}{"chunk_size": int64(1024), "upload_id": uploadID})
538+
case strings.HasPrefix(r.URL.Path, "/v2/upload/"+uploadID+"/chunk/"):
533539
_, _ = io.ReadAll(r.Body)
534540
w.WriteHeader(http.StatusOK)
535-
case r.URL.Path == "/v2/upload/finalize":
541+
case r.URL.Path == "/v2/upload/"+uploadID+"/finalize":
536542
w.WriteHeader(http.StatusOK)
537543
default:
538544
w.WriteHeader(http.StatusNotFound)

0 commit comments

Comments
 (0)