seafile: fix corrupted uploads after a retried upload error

When an upload failed with a 500 error the upload was retried with a
new upload link but the same input stream. The stream had already been
consumed by the first attempt so the retry uploaded an empty file.

This fixes it by returning a RetryError instead so the caller retries
the upload with a fresh stream, which will fetch a new upload link.
This commit is contained in:
Nick Craig-Wood
2026-09-05 12:14:46 +01:00
parent fa43f10af2
commit 6cdd0ea761
2 changed files with 18 additions and 23 deletions
+15 -21
View File
@@ -88,28 +88,22 @@ func (o *Object) Open(ctx context.Context, options ...fs.OpenOption) (io.ReadClo
// But for unknown-sized objects (indicated by src.Size() == -1), Upload should either // But for unknown-sized objects (indicated by src.Size() == -1), Upload should either
// return an error or update the object properly (rather than e.g. calling panic). // return an error or update the object properly (rather than e.g. calling panic).
func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) error { func (o *Object) Update(ctx context.Context, in io.Reader, src fs.ObjectInfo, options ...fs.OpenOption) error {
// The upload sometimes return a temporary 500 error uploadLink, err := o.fs.getUploadLink(ctx, o.libraryID)
// We cannot use the pacer to retry uploading the file as the upload link is single use only if err != nil {
for retry := 0; retry <= 3; retry++ { return err
uploadLink, err := o.fs.getUploadLink(ctx, o.libraryID)
if err != nil {
return err
}
uploaded, err := o.fs.upload(ctx, in, uploadLink, o.pathInLibrary)
if err == ErrorInternalDuringUpload {
// This is a temporary error, try again with a new upload link
continue
}
if err != nil {
return err
}
// Set the properties from the upload back to the object
o.size = uploaded.Size
return nil
} }
return ErrorInternalDuringUpload
// The upload can't be retried here as the input stream can't be re-read and
// the upload link is single use, so upload returns a retry
// error for the caller to retry with a fresh stream.
uploaded, err := o.fs.upload(ctx, in, uploadLink, o.pathInLibrary)
if err != nil {
return err
}
// Set the properties from the upload back to the object
o.size = uploaded.Size
return nil
} }
// Remove this object // Remove this object
+3 -2
View File
@@ -13,6 +13,7 @@ import (
"github.com/rclone/rclone/backend/seafile/api" "github.com/rclone/rclone/backend/seafile/api"
"github.com/rclone/rclone/fs" "github.com/rclone/rclone/fs"
"github.com/rclone/rclone/fs/fserrors"
"github.com/rclone/rclone/lib/readers" "github.com/rclone/rclone/lib/readers"
"github.com/rclone/rclone/lib/rest" "github.com/rclone/rclone/lib/rest"
) )
@@ -721,8 +722,8 @@ func (f *Fs) upload(ctx context.Context, in io.Reader, uploadLink, filePath stri
return nil, fs.ErrorPermissionDenied return nil, fs.ErrorPermissionDenied
} }
if resp.StatusCode == 500 { if resp.StatusCode == 500 {
// This is a temporary error - we will get a new upload link before retrying // This is a temporary error - the caller will get a new upload link when it retries
return nil, ErrorInternalDuringUpload return nil, fserrors.RetryError(ErrorInternalDuringUpload)
} }
} }
return nil, fmt.Errorf("failed to upload file: %w", err) return nil, fmt.Errorf("failed to upload file: %w", err)