|
|
@ -26,16 +26,16 @@ type ChunkedUploadResult struct { |
|
|
|
|
|
|
|
|
// ChunkedUploadOption contains options for chunked uploads
|
|
|
// ChunkedUploadOption contains options for chunked uploads
|
|
|
type ChunkedUploadOption struct { |
|
|
type ChunkedUploadOption struct { |
|
|
ChunkSize int32 |
|
|
|
|
|
SmallFileLimit int64 |
|
|
|
|
|
Collection string |
|
|
|
|
|
Replication string |
|
|
|
|
|
DataCenter string |
|
|
|
|
|
SaveSmallInline bool |
|
|
|
|
|
Jwt security.EncodedJwt |
|
|
|
|
|
MimeType string |
|
|
|
|
|
AssignFunc func(ctx context.Context, count int) (*VolumeAssignRequest, *AssignResult, error) |
|
|
|
|
|
UploadFunc func(ctx context.Context, data []byte, option *UploadOption) (*UploadResult, error) // Optional: for testing
|
|
|
|
|
|
|
|
|
ChunkSize int32 |
|
|
|
|
|
SmallFileLimit int64 |
|
|
|
|
|
Collection string |
|
|
|
|
|
Replication string |
|
|
|
|
|
DataCenter string |
|
|
|
|
|
SaveSmallInline bool |
|
|
|
|
|
Jwt security.EncodedJwt |
|
|
|
|
|
MimeType string |
|
|
|
|
|
AssignFunc func(ctx context.Context, count int) (*VolumeAssignRequest, *AssignResult, error) |
|
|
|
|
|
UploadFunc func(ctx context.Context, data []byte, option *UploadOption) (*UploadResult, error) // Optional: for testing
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
var chunkBufferPool = sync.Pool{ |
|
|
var chunkBufferPool = sync.Pool{ |
|
|
@ -121,24 +121,24 @@ uploadLoop: |
|
|
break |
|
|
break |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// For small files at offset 0, store inline instead of uploading
|
|
|
|
|
|
if chunkOffset == 0 && opt.SaveSmallInline && dataSize < opt.SmallFileLimit { |
|
|
|
|
|
smallContent := make([]byte, dataSize) |
|
|
|
|
|
n, readErr := io.ReadFull(bytesBuffer, smallContent) |
|
|
|
|
|
chunkBufferPool.Put(bytesBuffer) |
|
|
|
|
|
<-bytesBufferLimitChan |
|
|
|
|
|
|
|
|
// For small files at offset 0, store inline instead of uploading
|
|
|
|
|
|
if chunkOffset == 0 && opt.SaveSmallInline && dataSize < opt.SmallFileLimit { |
|
|
|
|
|
smallContent := make([]byte, dataSize) |
|
|
|
|
|
n, readErr := io.ReadFull(bytesBuffer, smallContent) |
|
|
|
|
|
chunkBufferPool.Put(bytesBuffer) |
|
|
|
|
|
<-bytesBufferLimitChan |
|
|
|
|
|
|
|
|
if readErr != nil { |
|
|
|
|
|
return nil, fmt.Errorf("failed to read small content: read %d of %d bytes: %w", n, dataSize, readErr) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
if readErr != nil { |
|
|
|
|
|
return nil, fmt.Errorf("failed to read small content: read %d of %d bytes: %w", n, dataSize, readErr) |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
return &ChunkedUploadResult{ |
|
|
|
|
|
FileChunks: nil, |
|
|
|
|
|
Md5Hash: md5Hash, |
|
|
|
|
|
TotalSize: dataSize, |
|
|
|
|
|
SmallContent: smallContent, |
|
|
|
|
|
}, nil |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
return &ChunkedUploadResult{ |
|
|
|
|
|
FileChunks: nil, |
|
|
|
|
|
Md5Hash: md5Hash, |
|
|
|
|
|
TotalSize: dataSize, |
|
|
|
|
|
SmallContent: smallContent, |
|
|
|
|
|
}, nil |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
// Upload chunk in parallel goroutine
|
|
|
// Upload chunk in parallel goroutine
|
|
|
wg.Add(1) |
|
|
wg.Add(1) |
|
|
@ -160,24 +160,24 @@ uploadLoop: |
|
|
return |
|
|
return |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// Upload chunk data
|
|
|
|
|
|
uploadUrl := fmt.Sprintf("http://%s/%s", assignResult.Url, assignResult.Fid) |
|
|
|
|
|
|
|
|
// Upload chunk data
|
|
|
|
|
|
uploadUrl := fmt.Sprintf("http://%s/%s", assignResult.Url, assignResult.Fid) |
|
|
|
|
|
|
|
|
// Use per-assignment JWT if present, otherwise fall back to the original JWT
|
|
|
|
|
|
// This is critical for secured clusters where each volume assignment has its own JWT
|
|
|
|
|
|
jwt := opt.Jwt |
|
|
|
|
|
if assignResult.Auth != "" { |
|
|
|
|
|
jwt = assignResult.Auth |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
// Use per-assignment JWT if present, otherwise fall back to the original JWT
|
|
|
|
|
|
// This is critical for secured clusters where each volume assignment has its own JWT
|
|
|
|
|
|
jwt := opt.Jwt |
|
|
|
|
|
if assignResult.Auth != "" { |
|
|
|
|
|
jwt = assignResult.Auth |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
uploadOption := &UploadOption{ |
|
|
|
|
|
UploadUrl: uploadUrl, |
|
|
|
|
|
Cipher: false, |
|
|
|
|
|
IsInputCompressed: false, |
|
|
|
|
|
MimeType: opt.MimeType, |
|
|
|
|
|
PairMap: nil, |
|
|
|
|
|
Jwt: jwt, |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
uploadOption := &UploadOption{ |
|
|
|
|
|
UploadUrl: uploadUrl, |
|
|
|
|
|
Cipher: false, |
|
|
|
|
|
IsInputCompressed: false, |
|
|
|
|
|
MimeType: opt.MimeType, |
|
|
|
|
|
PairMap: nil, |
|
|
|
|
|
Jwt: jwt, |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
var uploadResult *UploadResult |
|
|
var uploadResult *UploadResult |
|
|
var uploadResultErr error |
|
|
var uploadResultErr error |
|
|
@ -207,20 +207,20 @@ uploadLoop: |
|
|
return |
|
|
return |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// Create chunk entry
|
|
|
|
|
|
// Set ModifiedTsNs to current time (nanoseconds) to track when upload completed
|
|
|
|
|
|
// This is critical for multipart uploads where the same part may be uploaded multiple times
|
|
|
|
|
|
// The part with the latest ModifiedTsNs is selected as the authoritative version
|
|
|
|
|
|
fid, _ := filer_pb.ToFileIdObject(assignResult.Fid) |
|
|
|
|
|
chunk := &filer_pb.FileChunk{ |
|
|
|
|
|
FileId: assignResult.Fid, |
|
|
|
|
|
Offset: offset, |
|
|
|
|
|
Size: uint64(uploadResult.Size), |
|
|
|
|
|
ModifiedTsNs: time.Now().UnixNano(), |
|
|
|
|
|
ETag: uploadResult.ContentMd5, |
|
|
|
|
|
Fid: fid, |
|
|
|
|
|
CipherKey: uploadResult.CipherKey, |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
// Create chunk entry
|
|
|
|
|
|
// Set ModifiedTsNs to current time (nanoseconds) to track when upload completed
|
|
|
|
|
|
// This is critical for multipart uploads where the same part may be uploaded multiple times
|
|
|
|
|
|
// The part with the latest ModifiedTsNs is selected as the authoritative version
|
|
|
|
|
|
fid, _ := filer_pb.ToFileIdObject(assignResult.Fid) |
|
|
|
|
|
chunk := &filer_pb.FileChunk{ |
|
|
|
|
|
FileId: assignResult.Fid, |
|
|
|
|
|
Offset: offset, |
|
|
|
|
|
Size: uint64(uploadResult.Size), |
|
|
|
|
|
ModifiedTsNs: time.Now().UnixNano(), |
|
|
|
|
|
ETag: uploadResult.ContentMd5, |
|
|
|
|
|
Fid: fid, |
|
|
|
|
|
CipherKey: uploadResult.CipherKey, |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
fileChunksLock.Lock() |
|
|
fileChunksLock.Lock() |
|
|
fileChunks = append(fileChunks, chunk) |
|
|
fileChunks = append(fileChunks, chunk) |
|
|
@ -265,4 +265,3 @@ uploadLoop: |
|
|
SmallContent: nil, |
|
|
SmallContent: nil, |
|
|
}, nil |
|
|
}, nil |
|
|
} |
|
|
} |
|
|
|
|
|
|