From af49c9f19b4cb296e1e2239587443545d838bc13 Mon Sep 17 00:00:00 2001 From: Chun-Hung Tseng Date: Wed, 12 Jul 2023 17:58:47 +0200 Subject: [PATCH] Rewritten file upload logic (esp. on failed upload handling and big-file upload) --- constants.go | 3 +- drive_test.go | 142 +++++++++++++--- drive_test_helper.go | 14 +- error.go | 7 +- file.go | 397 ++++++++++++++++++++++++++----------------- go.mod | 8 +- go.sum | 16 +- search.go | 18 +- 8 files changed, 394 insertions(+), 211 deletions(-) diff --git a/constants.go b/constants.go index e67d2e1..c298f5a 100644 --- a/constants.go +++ b/constants.go @@ -1,5 +1,6 @@ package proton_api_bridge var ( - UPLOAD_BLOCK_SIZE = 4 * 1024 * 1024 + UPLOAD_BLOCK_SIZE = 4 * 1024 * 1024 // 4 MB + UPLOAD_BATCH_BLOCK_SIZE = 8 ) diff --git a/drive_test.go b/drive_test.go index b6fd0f3..cc2b39a 100644 --- a/drive_test.go +++ b/drive_test.go @@ -51,13 +51,31 @@ func TestUploadAndDownloadAndDeleteAFile(t *testing.T) { }) log.Println("Upload integrationTestImage.png") - uploadFileByFilepath(t, ctx, protonDrive, "", "integrationTestImage.png", "testcase/integrationTestImage.png", false) + uploadFileByFilepath(t, ctx, protonDrive, "", "integrationTestImage.png", "testcase/integrationTestImage.png", 0) + checkRevisions(protonDrive, ctx, t, "integrationTestImage.png", 1, 1, 0, 0) + checkActiveFileListing(t, ctx, protonDrive, []string{"/integrationTestImage.png"}) + downloadFile(t, ctx, protonDrive, "", "integrationTestImage.png", "testcase/integrationTestImage.png", "") + + log.Println("Delete file integrationTestImage.png") + deleteBySearchingFromRoot(t, ctx, protonDrive, "integrationTestImage.png", false, false) + checkActiveFileListing(t, ctx, protonDrive, []string{}) +} + +func TestUploadAndUploadAndDownloadAndDeleteAFile(t *testing.T) { + ctx, cancel, protonDrive := setup(t, true) + t.Cleanup(func() { + defer cancel() + defer tearDown(t, ctx, protonDrive) + }) + + log.Println("Upload integrationTestImage.png") + uploadFileByFilepath(t, ctx, protonDrive, "", "integrationTestImage.png", "testcase/integrationTestImage.png", 0) checkRevisions(protonDrive, ctx, t, "integrationTestImage.png", 1, 1, 0, 0) checkActiveFileListing(t, ctx, protonDrive, []string{"/integrationTestImage.png"}) downloadFile(t, ctx, protonDrive, "", "integrationTestImage.png", "testcase/integrationTestImage.png", "") log.Println("Upload a new revision to replace integrationTestImage.png") - uploadFileByFilepath(t, ctx, protonDrive, "", "integrationTestImage.png", "testcase/integrationTestImage2.png", false) /* Add a revision */ + uploadFileByFilepath(t, ctx, protonDrive, "", "integrationTestImage.png", "testcase/integrationTestImage2.png", 0) /* Add a revision */ checkRevisions(protonDrive, ctx, t, "integrationTestImage.png", 2, 1, 0, 1) downloadFile(t, ctx, protonDrive, "", "integrationTestImage.png", "testcase/integrationTestImage2.png", "") @@ -73,13 +91,13 @@ func TestPartialUploadAndReuploadFailedAndDownloadAndDeleteAFile(t *testing.T) { defer tearDown(t, ctx, protonDrive) }) - log.Println("Partial upload a new draft revision of integrationTestImage.png") - uploadFileByFilepath(t, ctx, protonDrive, "", "integrationTestImage.png", "testcase/integrationTestImage.png", true) + log.Println("Create a new draft revision of integrationTestImage.png") + uploadFileByFilepath(t, ctx, protonDrive, "", "integrationTestImage.png", "testcase/integrationTestImage.png", 1) checkRevisions(protonDrive, ctx, t, "integrationTestImage.png", 1, 0, 1, 0) checkActiveFileListing(t, ctx, protonDrive, []string{}) - log.Println("Partial upload a new draft revision of integrationTestImage.png again") - uploadFileByFilepathWithError(t, ctx, protonDrive, "", "integrationTestImage.png", "testcase/integrationTestImage.png", true, ErrDraftExists) + log.Println("Create a new draft revision of integrationTestImage.png again") + uploadFileByFilepathWithError(t, ctx, protonDrive, "", "integrationTestImage.png", "testcase/integrationTestImage.png", 1, ErrDraftExists) checkRevisions(protonDrive, ctx, t, "integrationTestImage.png", 1, 0, 1, 0) checkActiveFileListing(t, ctx, protonDrive, []string{}) @@ -96,18 +114,23 @@ func TestPartialUploadAndReuploadAndDownloadAndDeleteAFile(t *testing.T) { defer tearDown(t, ctx, protonDrive) }) - log.Println("Partial upload a new draft revision of integrationTestImage.png") - uploadFileByFilepath(t, ctx, protonDrive, "", "integrationTestImage.png", "testcase/integrationTestImage.png", true) + log.Println("Create a new draft revision of integrationTestImage.png") + uploadFileByFilepath(t, ctx, protonDrive, "", "integrationTestImage.png", "testcase/integrationTestImage.png", 1) checkRevisions(protonDrive, ctx, t, "integrationTestImage.png", 1, 0, 1, 0) checkActiveFileListing(t, ctx, protonDrive, []string{}) - log.Println("Partial upload a new draft revision of integrationTestImage.png again") - uploadFileByFilepath(t, ctx, protonDrive, "", "integrationTestImage.png", "testcase/integrationTestImage.png", true) + log.Println("Create a new draft revision of integrationTestImage.png again") + uploadFileByFilepath(t, ctx, protonDrive, "", "integrationTestImage.png", "testcase/integrationTestImage.png", 1) checkRevisions(protonDrive, ctx, t, "integrationTestImage.png", 1, 0, 1, 0) checkActiveFileListing(t, ctx, protonDrive, []string{}) - log.Println("Upload a new revision and activates it to replace integrationTestImage.png") - uploadFileByFilepath(t, ctx, protonDrive, "", "integrationTestImage.png", "testcase/integrationTestImage2.png", false) /* Add a revision */ + log.Println("Create a new revision and don't commit integrationTestImage.png") + uploadFileByFilepath(t, ctx, protonDrive, "", "integrationTestImage.png", "testcase/integrationTestImage2.png", 2) + checkRevisions(protonDrive, ctx, t, "integrationTestImage.png", 1, 0, 1, 0) + checkActiveFileListing(t, ctx, protonDrive, []string{}) + + log.Println("Create a new revision and commit it to replace integrationTestImage.png") + uploadFileByFilepath(t, ctx, protonDrive, "", "integrationTestImage.png", "testcase/integrationTestImage2.png", 0) /* Add a revision */ checkRevisions(protonDrive, ctx, t, "integrationTestImage.png", 1, 1, 0, 0) downloadFile(t, ctx, protonDrive, "", "integrationTestImage.png", "testcase/integrationTestImage2.png", "") checkActiveFileListing(t, ctx, protonDrive, []string{"/integrationTestImage.png"}) @@ -117,6 +140,81 @@ func TestPartialUploadAndReuploadAndDownloadAndDeleteAFile(t *testing.T) { checkActiveFileListing(t, ctx, protonDrive, []string{}) } +func TestUploadAndDownloadThreeRevisionsAndDeleteAFile(t *testing.T) { + ctx, cancel, protonDrive := setup(t, true) + t.Cleanup(func() { + defer cancel() + defer tearDown(t, ctx, protonDrive) + }) + + log.Println("Create a new revision and commit it to replace integrationTestImage.png") + uploadFileByFilepath(t, ctx, protonDrive, "", "integrationTestImage.png", "testcase/integrationTestImage.png", 0) /* Add a revision */ + checkRevisions(protonDrive, ctx, t, "integrationTestImage.png", 1, 1, 0, 0) + downloadFile(t, ctx, protonDrive, "", "integrationTestImage.png", "testcase/integrationTestImage.png", "") + checkActiveFileListing(t, ctx, protonDrive, []string{"/integrationTestImage.png"}) + + log.Println("Create a new revision 2 and commit it to replace integrationTestImage.png") + uploadFileByFilepath(t, ctx, protonDrive, "", "integrationTestImage.png", "testcase/empty.txt", 0) /* Add a revision */ + checkRevisions(protonDrive, ctx, t, "integrationTestImage.png", 2, 1, 0, 1) + downloadFile(t, ctx, protonDrive, "", "integrationTestImage.png", "testcase/empty.txt", "") + checkActiveFileListing(t, ctx, protonDrive, []string{"/integrationTestImage.png"}) + + log.Println("Create a new revision 3 and commit it to replace integrationTestImage.png") + uploadFileByFilepath(t, ctx, protonDrive, "", "integrationTestImage.png", "testcase/integrationTestImage2.png", 0) /* Add a revision */ + checkRevisions(protonDrive, ctx, t, "integrationTestImage.png", 3, 1, 0, 2) + downloadFile(t, ctx, protonDrive, "", "integrationTestImage.png", "testcase/integrationTestImage2.png", "") + checkActiveFileListing(t, ctx, protonDrive, []string{"/integrationTestImage.png"}) + + log.Println("Delete file integrationTestImage.png") + deleteBySearchingFromRoot(t, ctx, protonDrive, "integrationTestImage.png", false, false) + checkActiveFileListing(t, ctx, protonDrive, []string{}) +} + +func TestPartialUploadAndReuploadAndDownloadTwiceAndDeleteAFileSmallBig(t *testing.T) { + ctx, cancel, protonDrive := setup(t, true) + t.Cleanup(func() { + defer cancel() + defer tearDown(t, ctx, protonDrive) + }) + + log.Println("Create a new draft revision of integrationTestImage.png") + uploadFileByFilepath(t, ctx, protonDrive, "", "integrationTestImage.png", "testcase/empty.txt", 1) + checkRevisions(protonDrive, ctx, t, "integrationTestImage.png", 1, 0, 1, 0) + checkActiveFileListing(t, ctx, protonDrive, []string{}) + + log.Println("Create a new draft revision of integrationTestImage.png again") + uploadFileByFilepath(t, ctx, protonDrive, "", "integrationTestImage.png", "testcase/empty.txt", 1) + checkRevisions(protonDrive, ctx, t, "integrationTestImage.png", 1, 0, 1, 0) + checkActiveFileListing(t, ctx, protonDrive, []string{}) + + log.Println("Create a new revision and don't commit integrationTestImage.png") + uploadFileByFilepath(t, ctx, protonDrive, "", "integrationTestImage.png", "testcase/empty.txt", 2) + checkRevisions(protonDrive, ctx, t, "integrationTestImage.png", 1, 0, 1, 0) + checkActiveFileListing(t, ctx, protonDrive, []string{}) + + log.Println("Create a new revision and commit it to replace integrationTestImage.png") + uploadFileByFilepath(t, ctx, protonDrive, "", "integrationTestImage.png", "testcase/empty.txt", 0) /* Add a revision */ + checkRevisions(protonDrive, ctx, t, "integrationTestImage.png", 1, 1, 0, 0) + downloadFile(t, ctx, protonDrive, "", "integrationTestImage.png", "testcase/empty.txt", "") + checkActiveFileListing(t, ctx, protonDrive, []string{"/integrationTestImage.png"}) + + log.Println("Create a new revision 2 and don't commit integrationTestImage.png") + uploadFileByFilepath(t, ctx, protonDrive, "", "integrationTestImage.png", "testcase/empty.txt", 2) + checkRevisions(protonDrive, ctx, t, "integrationTestImage.png", 2, 1, 1, 0) + checkActiveFileListing(t, ctx, protonDrive, []string{"/integrationTestImage.png"}) + + // testing the reduce new revision size + log.Println("Create a new revision 2 and commit it to replace integrationTestImage.png") + uploadFileByFilepath(t, ctx, protonDrive, "", "integrationTestImage.png", "testcase/integrationTestImage.png", 0) /* Add a revision */ + checkRevisions(protonDrive, ctx, t, "integrationTestImage.png", 2, 1, 0, 1) + downloadFile(t, ctx, protonDrive, "", "integrationTestImage.png", "testcase/integrationTestImage.png", "") + checkActiveFileListing(t, ctx, protonDrive, []string{"/integrationTestImage.png"}) + + log.Println("Delete file integrationTestImage.png") + deleteBySearchingFromRoot(t, ctx, protonDrive, "integrationTestImage.png", false, false) + checkActiveFileListing(t, ctx, protonDrive, []string{}) +} + func TestUploadAndDeleteAnEmptyFileAtRoot(t *testing.T) { ctx, cancel, protonDrive := setup(t, false) t.Cleanup(func() { @@ -125,13 +223,13 @@ func TestUploadAndDeleteAnEmptyFileAtRoot(t *testing.T) { }) log.Println("Upload empty.txt") - uploadFileByFilepath(t, ctx, protonDrive, "", "empty.txt", "testcase/empty.txt", false) + uploadFileByFilepath(t, ctx, protonDrive, "", "empty.txt", "testcase/empty.txt", 0) checkRevisions(protonDrive, ctx, t, "empty.txt", 1, 1, 0, 0) checkActiveFileListing(t, ctx, protonDrive, []string{"/empty.txt"}) downloadFile(t, ctx, protonDrive, "", "empty.txt", "testcase/empty.txt", "") log.Println("Upload a new revision to replace empty.txt") - uploadFileByFilepath(t, ctx, protonDrive, "", "empty.txt", "testcase/empty.txt", false) /* Add a revision */ + uploadFileByFilepath(t, ctx, protonDrive, "", "empty.txt", "testcase/empty.txt", 0) /* Add a revision */ checkRevisions(protonDrive, ctx, t, "empty.txt", 2, 1, 0, 1) downloadFile(t, ctx, protonDrive, "", "empty.txt", "testcase/empty.txt", "") checkActiveFileListing(t, ctx, protonDrive, []string{"/empty.txt"}) @@ -153,13 +251,13 @@ func TestUploadAndDownloadAndDeleteAFileAtAFolderOneLevelFromRoot(t *testing.T) checkActiveFileListing(t, ctx, protonDrive, []string{"/level1"}) log.Println("Upload integrationTestImage.png to level1") - uploadFileByFilepath(t, ctx, protonDrive, "level1", "integrationTestImage.png", "testcase/integrationTestImage.png", false) + uploadFileByFilepath(t, ctx, protonDrive, "level1", "integrationTestImage.png", "testcase/integrationTestImage.png", 0) checkRevisions(protonDrive, ctx, t, "integrationTestImage.png", 1, 1, 0, 0) checkActiveFileListing(t, ctx, protonDrive, []string{"/level1", "/level1/integrationTestImage.png"}) downloadFile(t, ctx, protonDrive, "level1", "integrationTestImage.png", "testcase/integrationTestImage.png", "") log.Println("Upload a new revision to replace integrationTestImage.png in level1") - uploadFileByFilepath(t, ctx, protonDrive, "level1", "integrationTestImage.png", "testcase/integrationTestImage2.png", false) /* Add a revision */ + uploadFileByFilepath(t, ctx, protonDrive, "level1", "integrationTestImage.png", "testcase/integrationTestImage2.png", 0) /* Add a revision */ checkRevisions(protonDrive, ctx, t, "integrationTestImage.png", 2, 1, 0, 1) downloadFile(t, ctx, protonDrive, "level1", "integrationTestImage.png", "testcase/integrationTestImage2.png", "") @@ -204,7 +302,7 @@ func TestCreateAndMoveAndDeleteFolderWithAFile(t *testing.T) { checkActiveFileListing(t, ctx, protonDrive, []string{"/src"}) log.Println("Upload integrationTestImage.png to src") - uploadFileByFilepath(t, ctx, protonDrive, "src", "integrationTestImage.png", "testcase/integrationTestImage.png", false) + uploadFileByFilepath(t, ctx, protonDrive, "src", "integrationTestImage.png", "testcase/integrationTestImage.png", 0) checkRevisions(protonDrive, ctx, t, "integrationTestImage.png", 1, 1, 0, 0) checkActiveFileListing(t, ctx, protonDrive, []string{"/src", "/src/integrationTestImage.png"}) downloadFile(t, ctx, protonDrive, "src", "integrationTestImage.png", "testcase/integrationTestImage.png", "") @@ -214,7 +312,7 @@ func TestCreateAndMoveAndDeleteFolderWithAFile(t *testing.T) { checkActiveFileListing(t, ctx, protonDrive, []string{"/src", "/src/integrationTestImage.png", "/dst"}) log.Println("Upload a new revision to replace integrationTestImage.png in src") - uploadFileByFilepath(t, ctx, protonDrive, "src", "integrationTestImage.png", "testcase/integrationTestImage2.png", false) /* Add a revision */ + uploadFileByFilepath(t, ctx, protonDrive, "src", "integrationTestImage.png", "testcase/integrationTestImage2.png", 0) /* Add a revision */ checkRevisions(protonDrive, ctx, t, "integrationTestImage.png", 2, 1, 0, 1) downloadFile(t, ctx, protonDrive, "src", "integrationTestImage.png", "testcase/integrationTestImage2.png", "") checkActiveFileListing(t, ctx, protonDrive, []string{"/src", "/src/integrationTestImage.png", "/dst"}) @@ -240,7 +338,7 @@ func TestCreateAndMoveAndDeleteAFileOneLevelFromRoot(t *testing.T) { checkActiveFileListing(t, ctx, protonDrive, []string{"/src"}) log.Println("Upload integrationTestImage.png to src") - uploadFileByFilepath(t, ctx, protonDrive, "src", "integrationTestImage.png", "testcase/integrationTestImage.png", false) + uploadFileByFilepath(t, ctx, protonDrive, "src", "integrationTestImage.png", "testcase/integrationTestImage.png", 0) checkRevisions(protonDrive, ctx, t, "integrationTestImage.png", 1, 1, 0, 0) checkActiveFileListing(t, ctx, protonDrive, []string{"/src", "/src/integrationTestImage.png"}) downloadFile(t, ctx, protonDrive, "src", "integrationTestImage.png", "testcase/integrationTestImage.png", "") @@ -250,7 +348,7 @@ func TestCreateAndMoveAndDeleteAFileOneLevelFromRoot(t *testing.T) { checkActiveFileListing(t, ctx, protonDrive, []string{"/src", "/src/integrationTestImage.png", "/dst"}) log.Println("Upload a new revision to replace integrationTestImage.png in src") - uploadFileByFilepath(t, ctx, protonDrive, "src", "integrationTestImage.png", "testcase/integrationTestImage2.png", false) /* Add a revision */ + uploadFileByFilepath(t, ctx, protonDrive, "src", "integrationTestImage.png", "testcase/integrationTestImage2.png", 0) /* Add a revision */ checkRevisions(protonDrive, ctx, t, "integrationTestImage.png", 2, 1, 0, 1) downloadFile(t, ctx, protonDrive, "src", "integrationTestImage.png", "testcase/integrationTestImage2.png", "") checkActiveFileListing(t, ctx, protonDrive, []string{"/src", "/src/integrationTestImage.png", "/dst"}) @@ -293,13 +391,13 @@ func TestUploadLargeNumberOfBlocks(t *testing.T) { file2ContentReader := strings.NewReader(file2Content) log.Println("Upload fileContent.txt") - uploadFileByReader(t, ctx, protonDrive, "", filename, file1ContentReader, false) + uploadFileByReader(t, ctx, protonDrive, "", filename, file1ContentReader, 0) checkRevisions(protonDrive, ctx, t, filename, 1, 1, 0, 0) checkActiveFileListing(t, ctx, protonDrive, []string{"/" + filename}) downloadFile(t, ctx, protonDrive, "", filename, "", file1Content) log.Println("Upload a new revision to replace fileContent.txt") - uploadFileByReader(t, ctx, protonDrive, "", filename, file2ContentReader, false) + uploadFileByReader(t, ctx, protonDrive, "", filename, file2ContentReader, 0) checkRevisions(protonDrive, ctx, t, filename, 2, 1, 0, 1) checkActiveFileListing(t, ctx, protonDrive, []string{"/" + filename}) downloadFile(t, ctx, protonDrive, "", filename, "", file2Content) diff --git a/drive_test_helper.go b/drive_test_helper.go index e5a2bcc..f0176ef 100644 --- a/drive_test_helper.go +++ b/drive_test_helper.go @@ -126,7 +126,7 @@ func createFolder(t *testing.T, ctx context.Context, protonDrive *ProtonDrive, p createFolderExpectError(t, ctx, protonDrive, parent, name, nil) } -func uploadFileByReader(t *testing.T, ctx context.Context, protonDrive *ProtonDrive, parent, name string, in io.Reader, createFileOnly bool) { +func uploadFileByReader(t *testing.T, ctx context.Context, protonDrive *ProtonDrive, parent, name string, in io.Reader, testParam int) { parentLink := protonDrive.RootLink if parent != "" { targetFolderLink, err := protonDrive.SearchByNameRecursivelyFromRoot(ctx, parent, true, false) @@ -142,13 +142,13 @@ func uploadFileByReader(t *testing.T, ctx context.Context, protonDrive *ProtonDr t.Fatalf("parentLink is not of folder type") } - _, _, err := protonDrive.UploadFileByReader(ctx, parentLink.LinkID, name, time.Now(), in, createFileOnly) + _, _, err := protonDrive.UploadFileByReader(ctx, parentLink.LinkID, name, time.Now(), in, testParam) if err != nil { t.Fatal(err) } } -func uploadFileByFilepathWithError(t *testing.T, ctx context.Context, protonDrive *ProtonDrive, parent, name string, filepath string, createFileOnly bool, expectedError error) { +func uploadFileByFilepathWithError(t *testing.T, ctx context.Context, protonDrive *ProtonDrive, parent, name string, filepath string, testParam int, expectedError error) { parentLink := protonDrive.RootLink if parent != "" { targetFolderLink, err := protonDrive.SearchByNameRecursivelyFromRoot(ctx, parent, true, false) @@ -177,14 +177,14 @@ func uploadFileByFilepathWithError(t *testing.T, ctx context.Context, protonDriv in := bufio.NewReader(f) - _, _, err = protonDrive.UploadFileByReader(ctx, parentLink.LinkID, name, info.ModTime(), in, createFileOnly) + _, _, err = protonDrive.UploadFileByReader(ctx, parentLink.LinkID, name, info.ModTime(), in, testParam) if err != expectedError { t.Fatal(err) } } -func uploadFileByFilepath(t *testing.T, ctx context.Context, protonDrive *ProtonDrive, parent, name string, filepath string, createFileOnly bool) { - uploadFileByFilepathWithError(t, ctx, protonDrive, parent, name, filepath, createFileOnly, nil) +func uploadFileByFilepath(t *testing.T, ctx context.Context, protonDrive *ProtonDrive, parent, name string, filepath string, testParam int) { + uploadFileByFilepathWithError(t, ctx, protonDrive, parent, name, filepath, testParam, nil) } func downloadFile(t *testing.T, ctx context.Context, protonDrive *ProtonDrive, parent, name string, filepath string, data string) { @@ -273,7 +273,7 @@ func checkRevisions(protonDrive *ProtonDrive, ctx context.Context, t *testing.T, } } if activeRevisions != 0 || draftRevisions != 0 || obseleteRevisions != 0 { - t.Fatalf("Wrong revision count %v %v %v", activeRevisions, draftRevisions, obseleteRevisions) + t.Fatalf("Wrong revision count (should be all 0 here) %v %v %v", activeRevisions, draftRevisions, obseleteRevisions) } } } diff --git a/error.go b/error.go index f3b957b..9818db7 100644 --- a/error.go +++ b/error.go @@ -8,11 +8,14 @@ var ( ErrLinkTypeMustToBeFolderType = errors.New("the link type must be of folder type") ErrLinkTypeMustToBeFileType = errors.New("the link type must be of file type") ErrFolderIsNotEmpty = errors.New("folder can't be deleted because it is not empty") - ErrInternalErrorOnFileUpload = errors.New("either link or createFileResp must be not nil") + ErrCantLocateRevision = errors.New("can't create a new file upload request and can't find an active/draft revision") + ErrInternalErrorOnFileUpload = errors.New("either link or file creation request should be not nil") ErrMissingInputUploadAndCollectBlockData = errors.New("missing either session key or key ring") ErrLinkMustNotBeNil = errors.New("missing input proton link") ErrLinkMustBeActive = errors.New("can not operate on link state other than active") ErrDownloadedBlockHashVerificationFailed = errors.New("the hash of the downloaded block doesn't match the original hash") - ErrWrongGetRevisionUsage = errors.New("func GetRevision is used wrongly") ErrDraftExists = errors.New("a draft exist - usually this means a file is being uploaded at another client, or, there was a failed upload attempt") + ErrWrongEOFAssumption = errors.New("we have a problem in the assumption where EOF comes on its own") + ErrCantFindActiveRevision = errors.New("can't find an active revision") + ErrCantFindDraftRevision = errors.New("can't find a draft revision") ) diff --git a/file.go b/file.go index 25667e7..9deb8c9 100644 --- a/file.go +++ b/file.go @@ -7,7 +7,6 @@ import ( "crypto/sha256" "encoding/base64" "io" - "log" "mime" "os" "path/filepath" @@ -32,32 +31,22 @@ func (protonDrive *ProtonDrive) DownloadFileByID(ctx context.Context, linkID str return protonDrive.DownloadFile(ctx, &link) } -func (protonDrive *ProtonDrive) GetRevision(ctx context.Context, link *proton.Link, revisionType proton.RevisionState) (*proton.RevisionMetadata, error) { - if revisionType != proton.RevisionStateActive && revisionType != proton.RevisionStateDraft { - // since we can't return more than 1 revision, we only support active and draft types - return nil, ErrWrongGetRevisionUsage - } - +func (protonDrive *ProtonDrive) GetRevisions(ctx context.Context, link *proton.Link, revisionType proton.RevisionState) ([]*proton.RevisionMetadata, error) { revisions, err := protonDrive.c.ListRevisions(ctx, protonDrive.MainShare.ShareID, link.LinkID) if err != nil { return nil, err } - // log.Printf("revisions %#v", revisions) + ret := make([]*proton.RevisionMetadata, 0) // Revisions are only for files, they represent “versions” of files. // Each file can have 1 active/draft revision and n obsolete revisions. - targetRevision := -1 for i := range revisions { if revisions[i].State == revisionType { - targetRevision = i - break + ret = append(ret, &revisions[i]) } } - if targetRevision == -1 { // not found - return nil, nil - } - return &revisions[targetRevision], nil + return ret, nil } func (protonDrive *ProtonDrive) GetActiveRevisionWithAttrs(ctx context.Context, link *proton.Link) (*proton.Revision, *FileSystemAttrs, error) { @@ -65,16 +54,19 @@ func (protonDrive *ProtonDrive) GetActiveRevisionWithAttrs(ctx context.Context, return nil, nil, ErrLinkMustNotBeNil } - revisionMetadata, err := protonDrive.GetRevision(ctx, link, proton.RevisionStateActive) + revisionsMetadata, err := protonDrive.GetRevisions(ctx, link, proton.RevisionStateActive) if err != nil { return nil, nil, err } - revision, err := protonDrive.c.GetRevisionAllBlocks(ctx, protonDrive.MainShare.ShareID, link.LinkID, revisionMetadata.ID) + if len(revisionsMetadata) != 1 { + return nil, nil, ErrCantFindActiveRevision + } + + revision, err := protonDrive.c.GetRevisionAllBlocks(ctx, protonDrive.MainShare.ShareID, link.LinkID, revisionsMetadata[0].ID) if err != nil { return nil, nil, err } - // log.Println("Total blocks", len(revision.Blocks)) nodeKR, err := protonDrive.getNodeKR(ctx, link) if err != nil { @@ -143,16 +135,16 @@ func (protonDrive *ProtonDrive) DownloadFile(ctx context.Context, link *proton.L return buffer.Bytes(), nil, nil } -func (protonDrive *ProtonDrive) UploadFileByReader(ctx context.Context, parentLinkID string, filename string, modTime time.Time, file io.Reader, createFileOnly bool) (*proton.Link, int64, error) { +func (protonDrive *ProtonDrive) UploadFileByReader(ctx context.Context, parentLinkID string, filename string, modTime time.Time, file io.Reader, testParam int) (*proton.Link, int64, error) { parentLink, err := protonDrive.c.GetLink(ctx, protonDrive.MainShare.ShareID, parentLinkID) if err != nil { return nil, 0, err } - return protonDrive.uploadFile(ctx, &parentLink, filename, modTime, file, createFileOnly) + return protonDrive.uploadFile(ctx, &parentLink, filename, modTime, file, testParam) } -func (protonDrive *ProtonDrive) UploadFileByPath(ctx context.Context, parentLink *proton.Link, filename string, filePath string, createFileOnly bool) (*proton.Link, int64, error) { +func (protonDrive *ProtonDrive) UploadFileByPath(ctx context.Context, parentLink *proton.Link, filename string, filePath string, testParam int) (*proton.Link, int64, error) { f, err := os.Open(filePath) if err != nil { return nil, 0, err @@ -166,18 +158,74 @@ func (protonDrive *ProtonDrive) UploadFileByPath(ctx context.Context, parentLink in := bufio.NewReader(f) - return protonDrive.uploadFile(ctx, parentLink, filename, info.ModTime(), in, createFileOnly) + return protonDrive.uploadFile(ctx, parentLink, filename, info.ModTime(), in, testParam) } -func (protonDrive *ProtonDrive) createFileUploadDraft(ctx context.Context, parentLink *proton.Link, filename string, modTime time.Time, mimeType string) (*proton.Link, *proton.CreateFileRes, *crypto.SessionKey, *crypto.KeyRing, error) { +func (protonDrive *ProtonDrive) handleRevisionConflict(ctx context.Context, link *proton.Link, createFileResp *proton.CreateFileRes) (string, bool, error) { + if link != nil { + linkID := link.LinkID + + draftRevision, err := protonDrive.GetRevisions(ctx, link, proton.RevisionStateDraft) + if err != nil { + return "", false, err + } + + // if we have a draft revision, depending on the user config, we can abort the upload or recreate a draft + // 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) + if len(draftRevision) > 0 { + // TODO: maintain clientUID to mark that this is our own draft (which can indicate failed upload attempt!) + if protonDrive.Config.ReplaceExistingDraft { + // Question: how do we observe for file upload cancellation -> clientUID? + // Random thoughts: if there are concurrent modification to the draft, the server should be able to catch this when commiting the revision + // since the manifestSignature (hash) will fail to match + + // delete the draft revision (will fail if the file only have a draft but no active revisions) + if link.State == proton.LinkStateDraft { + // delete the link (skipping trash, otherwise it won't work) + err = protonDrive.c.DeleteChildren(ctx, protonDrive.MainShare.ShareID, link.ParentLinkID, linkID) + if err != nil { + return "", false, err + } + + return "", true, nil + } + + // delete the draft revision + err = protonDrive.c.DeleteRevision(ctx, protonDrive.MainShare.ShareID, linkID, draftRevision[0].ID) + if err != nil { + return "", false, err + } + } else { + // if there is a draft, based on the web behavior, it will ask if the user wants to replace the failed upload attempt + // current behavior, we report an error to not upload the file (conservative) + return "", false, ErrDraftExists + } + } + + // create a new revision + newRevision, err := protonDrive.c.CreateRevision(ctx, protonDrive.MainShare.ShareID, linkID) + if err != nil { + return "", false, err + } + + return newRevision.ID, false, nil + } else if createFileResp != nil { + return createFileResp.RevisionID, false, nil + } else { + // should not happen anymore, since the file search will include the draft now + return "", false, ErrInternalErrorOnFileUpload + } +} + +func (protonDrive *ProtonDrive) createFileUploadDraft(ctx context.Context, parentLink *proton.Link, filename string, modTime time.Time, mimeType string) (string, string, *crypto.SessionKey, *crypto.KeyRing, error) { parentNodeKR, err := protonDrive.getNodeKR(ctx, parentLink) if err != nil { - return nil, nil, nil, nil, err + return "", "", nil, nil, err } newNodeKey, newNodePassphraseEnc, newNodePassphraseSignature, err := generateNodeKeys(parentNodeKR, protonDrive.AddrKR) if err != nil { - return nil, nil, nil, nil, err + return "", "", nil, nil, err } createFileReq := proton.CreateFileReq{ @@ -200,60 +248,161 @@ func (protonDrive *ProtonDrive) createFileUploadDraft(ctx context.Context, paren /* Name is encrypted using the parent's keyring, and signed with address key */ err = createFileReq.SetName(filename, protonDrive.AddrKR, parentNodeKR) if err != nil { - return nil, nil, nil, nil, err + return "", "", nil, nil, err } parentHashKey, err := parentLink.GetHashKey(parentNodeKR) if err != nil { - return nil, nil, nil, nil, err + return "", "", nil, nil, err } newNodeKR, err := getKeyRing(parentNodeKR, protonDrive.AddrKR, newNodeKey, newNodePassphraseEnc, newNodePassphraseSignature) if err != nil { - return nil, nil, nil, nil, err + return "", "", nil, nil, err } err = createFileReq.SetHash(filename, parentHashKey) if err != nil { - return nil, nil, nil, nil, err + return "", "", nil, nil, err } newSessionKey, err := createFileReq.SetContentKeyPacketAndSignature(newNodeKR, protonDrive.AddrKR) if err != nil { - return nil, nil, nil, nil, err + return "", "", nil, nil, err } - createFileResp, err := protonDrive.c.CreateFile(ctx, protonDrive.MainShare.ShareID, createFileReq) - if err != nil { - if err == proton.ErrFileNameExist { // FIXME: check for duplicated filename by relying on checkAvailableHashes - link, err := protonDrive.SearchByNameInFolder(ctx, parentLink, filename, true, false, true) // we search for everything with the requested name in the folder - if err != nil { - return nil, nil, nil, nil, err + createFileAction := func() (*proton.CreateFileRes, *proton.Link, error) { + createFileResp, err := protonDrive.c.CreateFile(ctx, protonDrive.MainShare.ShareID, createFileReq) + if err != nil { + // FIXME: check for duplicated filename by relying on checkAvailableHashes + // Also saving generating resources such as new nodeKR, etc. + + if err != proton.ErrFileNameExist { + // other real error caught + return nil, nil, err } - return link, nil, nil, nil, nil + + // search for the link within this folder which has an active/draft revision as we have a file creation conflict + link, err := protonDrive.SearchByNameInActiveFolder(ctx, parentLink, filename, true, false, proton.LinkStateActive) + if err != nil { + return nil, nil, err + } + + if link == nil { + link, err = protonDrive.SearchByNameInActiveFolder(ctx, parentLink, filename, true, false, proton.LinkStateDraft) + if err != nil { + return nil, nil, err + } + + if link == nil { + // we have a real problem here (unless the assumption is wrong) + // since we can't create a new file AND we can't locate a file with active/draft revision in it + return nil, nil, ErrCantLocateRevision + } + } + + return nil, link, nil } - // other real error caught - return nil, nil, nil, nil, err + return &createFileResp, nil, nil } - return nil, &createFileResp, newSessionKey, newNodeKR, nil + createFileResp, link, err := createFileAction() + if err != nil { + return "", "", nil, nil, err + } + + revisionID, shouldSubmitCreateFileRequestAgain, err := protonDrive.handleRevisionConflict(ctx, link, createFileResp) + if err != nil { + return "", "", nil, nil, err + } + + if shouldSubmitCreateFileRequestAgain { + // the case where the link has only a draft but no active revision + // we need to delete the link and recreate one + createFileResp, link, err = createFileAction() + if err != nil { + return "", "", nil, nil, err + } + + revisionID, _, err = protonDrive.handleRevisionConflict(ctx, link, createFileResp) + if err != nil { + return "", "", nil, nil, err + } + } + + linkID := "" + if link != nil { + linkID = link.LinkID + + // get original newSessionKey and newNodeKR + parentNodeKR, err = protonDrive.getNodeKRByID(ctx, link.ParentLinkID) + if err != nil { + return "", "", nil, nil, err + } + newNodeKR, err = link.GetKeyRing(parentNodeKR, protonDrive.AddrKR) + if err != nil { + return "", "", nil, nil, err + } + newSessionKey, err = link.GetSessionKey(protonDrive.AddrKR, newNodeKR) + if err != nil { + return "", "", nil, nil, err + } + } else { + linkID = createFileResp.ID + } + + return linkID, revisionID, newSessionKey, newNodeKR, nil } -func (protonDrive *ProtonDrive) uploadAndCollectBlockData(ctx context.Context, newSessionKey *crypto.SessionKey, newNodeKR *crypto.KeyRing, file io.Reader, linkID, revisionID string) ([]byte, []proton.BlockToken, int64, error) { +func (protonDrive *ProtonDrive) uploadAndCollectBlockData(ctx context.Context, newSessionKey *crypto.SessionKey, newNodeKR *crypto.KeyRing, file io.Reader, linkID, revisionID string) ([]byte, int64, error) { type PendingUploadBlocks struct { blockUploadInfo proton.BlockUploadInfo encData []byte } if newSessionKey == nil || newNodeKR == nil { - return nil, nil, 0, ErrMissingInputUploadAndCollectBlockData + return nil, 0, ErrMissingInputUploadAndCollectBlockData } totalFileSize := int64(0) - blocks := make([]PendingUploadBlocks, 0) + pendingUploadBlocks := make([]PendingUploadBlocks, 0) manifestSignatureData := make([]byte, 0) + uploadPendingBlocks := func() error { + if len(pendingUploadBlocks) == 0 { + return nil + } + + blockList := make([]proton.BlockUploadInfo, 0) + for i := range pendingUploadBlocks { + blockList = append(blockList, pendingUploadBlocks[i].blockUploadInfo) + } + blockUploadReq := proton.BlockUploadReq{ + AddressID: protonDrive.MainShare.AddressID, + ShareID: protonDrive.MainShare.ShareID, + LinkID: linkID, + RevisionID: revisionID, + + BlockList: blockList, + } + blockUploadResp, err := protonDrive.c.RequestBlockUpload(ctx, blockUploadReq) + if err != nil { + return err + } + + for i := range blockUploadResp { + err := protonDrive.c.UploadBlock(ctx, blockUploadResp[i].BareURL, blockUploadResp[i].Token, bytes.NewReader(pendingUploadBlocks[i].encData)) + if err != nil { + return err + } + } + + pendingUploadBlocks = pendingUploadBlocks[:0] + + return nil + } + for i := 1; ; i++ { // read at most data of size UPLOAD_BLOCK_SIZE data := make([]byte, UPLOAD_BLOCK_SIZE) // FIXME: get block size from the server config instead of hardcoding it @@ -261,11 +410,11 @@ func (protonDrive *ProtonDrive) uploadAndCollectBlockData(ctx context.Context, n if err != nil { if err == io.EOF { if readBytes > 0 { - log.Fatalln("We have a problem in the assumption") + return nil, 0, ErrWrongEOFAssumption } break } - return nil, nil, 0, err + return nil, 0, err } data = data[:readBytes] totalFileSize += int64(readBytes) @@ -274,16 +423,16 @@ func (protonDrive *ProtonDrive) uploadAndCollectBlockData(ctx context.Context, n dataPlainMessage := crypto.NewPlainMessage(data) encData, err := newSessionKey.Encrypt(dataPlainMessage) if err != nil { - return nil, nil, 0, err + return nil, 0, err } encSignature, err := protonDrive.AddrKR.SignDetachedEncrypted(dataPlainMessage, newNodeKR) if err != nil { - return nil, nil, 0, err + return nil, 0, err } encSignatureStr, err := encSignature.GetArmored() if err != nil { - return nil, nil, 0, err + return nil, 0, err } h := sha256.New() @@ -291,11 +440,11 @@ func (protonDrive *ProtonDrive) uploadAndCollectBlockData(ctx context.Context, n hash := h.Sum(nil) base64Hash := base64.StdEncoding.EncodeToString(hash) if err != nil { - return nil, nil, 0, err + return nil, 0, err } manifestSignatureData = append(manifestSignatureData, hash...) - blocks = append(blocks, PendingUploadBlocks{ + pendingUploadBlocks = append(pendingUploadBlocks, PendingUploadBlocks{ blockUploadInfo: proton.BlockUploadInfo{ Index: i, // iOS drive: BE starts with 1 Size: int64(len(encData)), @@ -304,46 +453,23 @@ func (protonDrive *ProtonDrive) uploadAndCollectBlockData(ctx context.Context, n }, encData: encData, }) - } - blockTokens := make([]proton.BlockToken, 0) - if len(blocks) == 0 { - return manifestSignatureData, blockTokens, 0, nil - } - - blockList := make([]proton.BlockUploadInfo, 0) - for i := range blocks { - blockList = append(blockList, blocks[i].blockUploadInfo) - } - blockUploadReq := proton.BlockUploadReq{ - AddressID: protonDrive.MainShare.AddressID, - ShareID: protonDrive.MainShare.ShareID, - LinkID: linkID, - RevisionID: revisionID, - - BlockList: blockList, - } - blockUploadResp, err := protonDrive.c.RequestBlockUpload(ctx, blockUploadReq) - if err != nil { - return nil, nil, 0, err - } - - for i := range blockUploadResp { - err := protonDrive.c.UploadBlock(ctx, blockUploadResp[i].BareURL, blockUploadResp[i].Token, bytes.NewReader(blocks[i].encData)) - if err != nil { - return nil, nil, 0, err + if (i-1) > 0 && (i-1)%UPLOAD_BATCH_BLOCK_SIZE == 0 { + err = uploadPendingBlocks() + if err != nil { + return nil, 0, err + } } - - blockTokens = append(blockTokens, proton.BlockToken{ - Index: i + 1, - Token: blockUploadResp[i].Token, - }) + } + err := uploadPendingBlocks() + if err != nil { + return nil, 0, err } - return manifestSignatureData, blockTokens, totalFileSize, nil + return manifestSignatureData, totalFileSize, nil } -func (protonDrive *ProtonDrive) commitNewRevision(ctx context.Context, nodeKR *crypto.KeyRing, modificationTime time.Time, size int64, manifestSignatureData []byte, blockTokens []proton.BlockToken, linkID, revisionID string) error { +func (protonDrive *ProtonDrive) commitNewRevision(ctx context.Context, nodeKR *crypto.KeyRing, modificationTime time.Time, size int64, manifestSignatureData []byte, linkID, revisionID string) error { manifestSignature, err := protonDrive.AddrKR.SignDetached(crypto.NewPlainMessage(manifestSignatureData)) if err != nil { return err @@ -353,18 +479,16 @@ func (protonDrive *ProtonDrive) commitNewRevision(ctx context.Context, nodeKR *c return err } - updateRevisionReq := proton.UpdateRevisionReq{ - BlockList: blockTokens, - State: proton.RevisionStateActive, + commitRevisionReq := proton.CommitRevisionReq{ ManifestSignature: manifestSignatureString, SignatureAddress: protonDrive.signatureAddress, } - err = updateRevisionReq.SetEncXAttrString(protonDrive.AddrKR, nodeKR, modificationTime, size) + err = commitRevisionReq.SetEncXAttrString(protonDrive.AddrKR, nodeKR, modificationTime, size) if err != nil { return err } - err = protonDrive.c.UpdateRevision(ctx, protonDrive.MainShare.ShareID, linkID, revisionID, updateRevisionReq) + err = protonDrive.c.CommitRevision(ctx, protonDrive.MainShare.ShareID, linkID, revisionID, commitRevisionReq) if err != nil { return err } @@ -372,7 +496,11 @@ func (protonDrive *ProtonDrive) commitNewRevision(ctx context.Context, nodeKR *c return nil } -func (protonDrive *ProtonDrive) uploadFile(ctx context.Context, parentLink *proton.Link, filename string, modTime time.Time, file io.Reader, createFileOnly bool) (*proton.Link, int64, error) { +// testParam is for integration test only +// 0 = normal mode +// 1 = up to create revision +// 2 = up to block upload +func (protonDrive *ProtonDrive) uploadFile(ctx context.Context, parentLink *proton.Link, filename string, modTime time.Time, file io.Reader, testParam int) (*proton.Link, int64, error) { // TODO: if we should use github.com/gabriel-vasile/mimetype to detect the MIME type from the file content itself // Note: this approach might cause the upload progress to display the "fake" progress, since we read in all the content all-at-once // mimetype.SetLimit(0) @@ -387,84 +515,39 @@ func (protonDrive *ProtonDrive) uploadFile(ctx context.Context, parentLink *prot } /* step 1: create a draft */ - link, createFileResp, newSessionKey, newNodeKR, err := protonDrive.createFileUploadDraft(ctx, parentLink, filename, modTime, mimeType) + linkID, revisionID, newSessionKey, newNodeKR, err := protonDrive.createFileUploadDraft(ctx, parentLink, filename, modTime, mimeType) if err != nil { return nil, 0, err } - linkID := "" - revisionID := "" - - if link != nil { - linkID = link.LinkID - - draftRevision, err := protonDrive.GetRevision(ctx, link, proton.RevisionStateDraft) - if err != nil { - return nil, 0, err - } - if draftRevision != nil { - if protonDrive.Config.ReplaceExistingDraft { - // FIXME: double check if this is the correct way of handling this case - // -> delete the draft revision before progressing since we don't maintain clientUID - // Question: how do we observe for file upload cancellation -> clientUID? - revisionID = draftRevision.ID - } else { - // if there is a draft, based on the web behavior, it will ask if the user wants to replace the failed upload attempt - // current behavior, we report an error to not upload the file (conservative) - return nil, 0, ErrDraftExists - } - } else { - // get a new revision - newRevision, err := protonDrive.c.CreateRevision(ctx, protonDrive.MainShare.ShareID, linkID) - if err != nil { - if err == proton.ErrFileCanNotBeFound { - // Can happen when trying to create a revision on a file without an active revision - return nil, 0, err - } - return nil, 0, err - } - - revisionID = newRevision.ID - } - - // get newSessionKey and newNodeKR - parentNodeKR, err := protonDrive.getNodeKRByID(ctx, link.ParentLinkID) - if err != nil { - return nil, 0, err - } - newNodeKR, err = link.GetKeyRing(parentNodeKR, protonDrive.AddrKR) - if err != nil { - return nil, 0, err - } - newSessionKey, err = link.GetSessionKey(protonDrive.AddrKR, newNodeKR) - if err != nil { - return nil, 0, err - } - } else if createFileResp != nil { - linkID = createFileResp.ID - revisionID = createFileResp.RevisionID - } else { - // should not happen anymore, since the file search will include the draft now - return nil, 0, ErrInternalErrorOnFileUpload - } - - /* step 2: upload blocks and collect block data */ - manifestSignature, blockTokens, fileSize, err := protonDrive.uploadAndCollectBlockData(ctx, newSessionKey, newNodeKR, file, linkID, revisionID) - if err != nil { - return nil, 0, err - } - - if createFileOnly { + if testParam == 1 { + // for integration tests // we try to simulate only draft is created but no upload is performed yet finalLink, err := protonDrive.c.GetLink(ctx, protonDrive.MainShare.ShareID, linkID) if err != nil { return nil, 0, err } - return &finalLink, fileSize, nil + return &finalLink, 0, nil } - /* step 3: mark the file as active by updating the revision */ - err = protonDrive.commitNewRevision(ctx, newNodeKR, modTime, fileSize, manifestSignature, blockTokens, linkID, revisionID) + /* step 2: upload blocks and collect block data */ + manifestSignature, fileSize, err := protonDrive.uploadAndCollectBlockData(ctx, newSessionKey, newNodeKR, file, linkID, revisionID) + if err != nil { + return nil, 0, err + } + + if testParam == 2 { + // for integration tests + // we try to simulate blocks uploaded but not yet commited + finalLink, err := protonDrive.c.GetLink(ctx, protonDrive.MainShare.ShareID, linkID) + if err != nil { + return nil, 0, err + } + return &finalLink, 0, nil + } + + /* step 3: mark the file as active by commiting the revision */ + err = protonDrive.commitNewRevision(ctx, newNodeKR, modTime, fileSize, manifestSignature, linkID, revisionID) if err != nil { return nil, 0, err } diff --git a/go.mod b/go.mod index d656124..e7d8838 100644 --- a/go.mod +++ b/go.mod @@ -4,14 +4,14 @@ go 1.18 require ( github.com/ProtonMail/gopenpgp/v2 v2.7.1 - github.com/henrybear327/go-proton-api v0.0.0-20230706104119-ca374251ebb7 + github.com/henrybear327/go-proton-api v0.0.0-20230712143834-912c2b22f334 github.com/relvacode/iso8601 v1.3.0 ) require ( github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf // indirect - github.com/ProtonMail/gluon v0.16.1-0.20230526091020-fb7689b15ae3 // indirect - github.com/ProtonMail/go-crypto v0.0.0-20230626094100-7e9e0395ebec // indirect + github.com/ProtonMail/gluon v0.17.0 // indirect + github.com/ProtonMail/go-crypto v0.0.0-20230710112148-e01326fd72eb // indirect github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f // indirect github.com/ProtonMail/go-srp v0.0.7 // indirect github.com/PuerkitoBio/goquery v1.8.1 // indirect @@ -27,7 +27,7 @@ require ( github.com/pkg/errors v0.9.1 // indirect github.com/sirupsen/logrus v1.9.3 // indirect golang.org/x/crypto v0.11.0 // indirect - golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect + golang.org/x/exp v0.0.0-20230711153332-06a737ee72cb // indirect golang.org/x/net v0.12.0 // indirect golang.org/x/sync v0.3.0 // indirect golang.org/x/sys v0.10.0 // indirect diff --git a/go.sum b/go.sum index 9a9f83e..c6fd15f 100644 --- a/go.sum +++ b/go.sum @@ -2,11 +2,11 @@ github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7Y github.com/ProtonMail/bcrypt v0.0.0-20210511135022-227b4adcab57/go.mod h1:HecWFHognK8GfRDGnFQbW/LiV7A3MX3gZVs45vk5h8I= github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf h1:yc9daCCYUefEs69zUkSzubzjBbL+cmOXgnmt9Fyd9ug= github.com/ProtonMail/bcrypt v0.0.0-20211005172633-e235017c1baf/go.mod h1:o0ESU9p83twszAU8LBeJKFAAMX14tISa0yk4Oo5TOqo= -github.com/ProtonMail/gluon v0.16.1-0.20230526091020-fb7689b15ae3 h1:HsRC3WKWY2xf3OGfXnVn1S/EhJx/8dKrWX4/JJQIBc8= -github.com/ProtonMail/gluon v0.16.1-0.20230526091020-fb7689b15ae3/go.mod h1:xYLE11dCH40RrNjkuncXZbYjGyuHKeFtdYKT2nkq6M8= +github.com/ProtonMail/gluon v0.17.0 h1:QfMRUcXd47MANHmoerj1ZHXsNzfW9gjsLmF+7Dim5ZU= +github.com/ProtonMail/gluon v0.17.0/go.mod h1:Og5/Dz1MiGpCJn51XujZwxiLG7WzvvjE5PRpZBQmAHo= github.com/ProtonMail/go-crypto v0.0.0-20230321155629-9a39f2531310/go.mod h1:8TI4H3IbrackdNgv+92dI+rhpCaLqM0IfpgCgenFvRE= -github.com/ProtonMail/go-crypto v0.0.0-20230626094100-7e9e0395ebec h1:vV3RryLxt42+ZIVOFbYJCH1jsZNTNmj2NYru5zfx+4E= -github.com/ProtonMail/go-crypto v0.0.0-20230626094100-7e9e0395ebec/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= +github.com/ProtonMail/go-crypto v0.0.0-20230710112148-e01326fd72eb h1:RU+Ff2vE68zFQSoBqlb/LChFztEWWJ9EZ8LU4gA3ubU= +github.com/ProtonMail/go-crypto v0.0.0-20230710112148-e01326fd72eb/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f h1:tCbYj7/299ekTTXpdwKYF8eBlsYsDVoggDAuAjoK66k= github.com/ProtonMail/go-mime v0.0.0-20230322103455-7d82a3887f2f/go.mod h1:gcr0kNtGBqin9zDW9GOHcVntrwnjrK+qdJ06mWYBybw= github.com/ProtonMail/go-srp v0.0.7 h1:Sos3Qk+th4tQR64vsxGIxYpN3rdnG9Wf9K4ZloC1JrI= @@ -49,8 +49,8 @@ github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSM github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/henrybear327/go-proton-api v0.0.0-20230706104119-ca374251ebb7 h1:VS8MOQz8hfWisgJkm+JzBtasjtvIVWWlg6TV0q1Nz2w= -github.com/henrybear327/go-proton-api v0.0.0-20230706104119-ca374251ebb7/go.mod h1:l42xBSOrCmkAxzWUHcoUsG/cP8m1hMhV72GoChOX3bg= +github.com/henrybear327/go-proton-api v0.0.0-20230712143834-912c2b22f334 h1:HwvrHYrakJHEpkLDjE7Sq49altLE3WlKFwQy7mEI+74= +github.com/henrybear327/go-proton-api v0.0.0-20230712143834-912c2b22f334/go.mod h1:l42xBSOrCmkAxzWUHcoUsG/cP8m1hMhV72GoChOX3bg= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= @@ -80,8 +80,8 @@ golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2Uz golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= -golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME= -golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= +golang.org/x/exp v0.0.0-20230711153332-06a737ee72cb h1:xIApU0ow1zwMa2uL1VDNeQlNVFTWMQxZUZCMDy0Q4Us= +golang.org/x/exp v0.0.0-20230711153332-06a737ee72cb/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= diff --git a/search.go b/search.go index e3c8777..31c6629 100644 --- a/search.go +++ b/search.go @@ -116,23 +116,25 @@ func (protonDrive *ProtonDrive) searchByNameRecursively( } // if the target isn't found, nil will be returned for both return values -func (protonDrive *ProtonDrive) SearchByNameInFolderByID(ctx context.Context, +func (protonDrive *ProtonDrive) SearchByNameInActiveFolderByID(ctx context.Context, folderLinkID string, targetName string, - searchForFile, searchForFolder, listAllActiveOrDraftFiles bool) (*proton.Link, error) { + searchForFile, searchForFolder bool, + targetState proton.LinkState) (*proton.Link, error) { folderLink, err := protonDrive.c.GetLink(ctx, protonDrive.MainShare.ShareID, folderLinkID) if err != nil { return nil, err } - return protonDrive.SearchByNameInFolder(ctx, &folderLink, targetName, searchForFile, searchForFolder, listAllActiveOrDraftFiles) + return protonDrive.SearchByNameInActiveFolder(ctx, &folderLink, targetName, searchForFile, searchForFolder, targetState) } -func (protonDrive *ProtonDrive) SearchByNameInFolder( +func (protonDrive *ProtonDrive) SearchByNameInActiveFolder( ctx context.Context, folderLink *proton.Link, targetName string, - searchForFile, searchForFolder, listAllActiveOrDraftFiles bool) (*proton.Link, error) { + searchForFile, searchForFolder bool, + targetState proton.LinkState) (*proton.Link, error) { if !searchForFile && !searchForFolder { // nothing to search return nil, nil @@ -165,11 +167,7 @@ func (protonDrive *ProtonDrive) SearchByNameInFolder( return nil, err } for _, childLink := range childrenLinks { - if listAllActiveOrDraftFiles { - if childLink.State != proton.LinkStateActive && childLink.State != proton.LinkStateDraft { - continue - } - } else if childLink.State != proton.LinkStateActive { + if childLink.State != targetState { continue }