Skip to content

Commit c1231c7

Browse files
committed
Fix upload conflict failing with 2501 on a leftover draft link
When a file upload fails or is interrupted, a draft link is left behind. The next upload of the same file hits a name conflict, and handleRevisionConflict located that draft link and called ListRevisions on it to look for a draft revision to replace. Proton's API now answers GET /files/<linkID>/revisions with "File or folder not found" (Code=2501) for a draft link (one with no active revision), so the upload failed instead of replacing the draft. This surfaced as a failure of the integration test FsPutFiles (the preceding FsPutError leaves such a draft). Handle a draft-state link directly: delete it and resubmit the file creation when replace_existing_draft is set, otherwise return ErrDraftExists - without listing its revisions. The active-link path (delete a stale draft revision, then create a new revision) is unchanged.
1 parent 2c48ccf commit c1231c7

1 file changed

Lines changed: 36 additions & 27 deletions

File tree

file_upload.go

Lines changed: 36 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -23,41 +23,50 @@ func (protonDrive *ProtonDrive) handleRevisionConflict(ctx context.Context, link
2323
if link != nil {
2424
linkID := link.LinkID
2525

26+
// A draft link (no active revision) is a leftover from a failed or
27+
// incomplete upload. Listing the revisions of such a link returns a
28+
// "File or folder not found" (2501) error, so don't: handle it
29+
// directly by deleting the link and resubmitting the file creation.
30+
// There is a restriction of one draft revision per file.
31+
if link.State == proton.LinkStateDraft {
32+
// TODO: maintain clientUID to mark that this is our own draft (which can indicate failed upload attempt!)
33+
if !protonDrive.Config.ReplaceExistingDraft {
34+
// based on the web behavior, it will ask if the user wants to replace the failed upload attempt
35+
// current behavior, we report an error to not upload the file (conservative)
36+
return "", false, ErrDraftExists
37+
}
38+
39+
// delete the link (skipping trash, otherwise it won't work) and
40+
// signal the caller to resubmit the file creation request
41+
err := protonDrive.c.DeleteChildren(ctx, protonDrive.MainShare.ShareID, link.ParentLinkID, linkID)
42+
if err != nil {
43+
return "", false, err
44+
}
45+
46+
return "", true, nil
47+
}
48+
49+
// The link has an active revision. A concurrent/failed upload may also
50+
// have left a draft revision on it; depending on the user config, we
51+
// can abort the upload or delete that draft revision before creating a
52+
// new one.
2653
draftRevision, err := protonDrive.GetRevisions(ctx, link, proton.RevisionStateDraft)
2754
if err != nil {
2855
return "", false, err
2956
}
3057

31-
// if we have a draft revision, depending on the user config, we can abort the upload or recreate a draft
32-
// if we have no draft revision, then we can create a new draft revision directly (there is a restriction of 1 draft revision per file)
3358
if len(draftRevision) > 0 {
34-
// TODO: maintain clientUID to mark that this is our own draft (which can indicate failed upload attempt!)
35-
if protonDrive.Config.ReplaceExistingDraft {
36-
// Question: how do we observe for file upload cancellation -> clientUID?
37-
// Random thoughts: if there are concurrent modification to the draft, the server should be able to catch this when commiting the revision
38-
// since the manifestSignature (hash) will fail to match
39-
40-
// delete the draft revision (will fail if the file only have a draft but no active revisions)
41-
if link.State == proton.LinkStateDraft {
42-
// delete the link (skipping trash, otherwise it won't work)
43-
err = protonDrive.c.DeleteChildren(ctx, protonDrive.MainShare.ShareID, link.ParentLinkID, linkID)
44-
if err != nil {
45-
return "", false, err
46-
}
47-
48-
return "", true, nil
49-
}
50-
51-
// delete the draft revision
52-
err = protonDrive.c.DeleteRevision(ctx, protonDrive.MainShare.ShareID, linkID, draftRevision[0].ID)
53-
if err != nil {
54-
return "", false, err
55-
}
56-
} else {
57-
// if there is a draft, based on the web behavior, it will ask if the user wants to replace the failed upload attempt
58-
// current behavior, we report an error to not upload the file (conservative)
59+
if !protonDrive.Config.ReplaceExistingDraft {
5960
return "", false, ErrDraftExists
6061
}
62+
63+
// Question: how do we observe for file upload cancellation -> clientUID?
64+
// Random thoughts: if there are concurrent modification to the draft, the server should be able to catch this when commiting the revision
65+
// since the manifestSignature (hash) will fail to match
66+
err = protonDrive.c.DeleteRevision(ctx, protonDrive.MainShare.ShareID, linkID, draftRevision[0].ID)
67+
if err != nil {
68+
return "", false, err
69+
}
6170
}
6271

6372
// create a new revision

0 commit comments

Comments
 (0)