Rewritten file upload logic (esp. on failed upload handling and big-file upload)

This commit is contained in:
Chun-Hung Tseng
2023-07-12 17:58:47 +02:00
parent e294306c44
commit af49c9f19b
8 changed files with 394 additions and 211 deletions

View File

@@ -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
)

View File

@@ -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)

View File

@@ -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)
}
}
}

View File

@@ -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")
)

397
file.go
View File

@@ -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
}

8
go.mod
View File

@@ -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

16
go.sum
View File

@@ -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=

View File

@@ -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
}