Improve/refactor file upload - when the file draft exists

Add config option - ReplaceExistingDraft
Refactor and add integration tests
Update README and add notes for MIME parsing and progress bug
Improve returned errors
This commit is contained in:
Chun-Hung Tseng
2023-06-29 22:59:17 +02:00
parent fef42081b3
commit 208b70cfd4
11 changed files with 349 additions and 217 deletions

View File

@@ -87,6 +87,10 @@ V2 will bring in optimizations and enhancements, such as optimizing uploading an
- [x] Point to the right proton-go-api branch
- [x] Run `go get github.com/henrybear327/go-proton-api@dev` to update go mod
- [x] Pass in AppVersion as a config option
- [x] Proper error handling by looking at the return code instead of the error string
- [x] Duplicated folder name handling: 422: A file or folder with that name already exists (Code=2500, Status=422)
- [x] Not found: ERROR RESTY 422: File or folder was not found. (Code=2501, Status=422), Attempt 1
- [x] Failed upload: Draft already exists on this revision (Code=2500, Status=409)
### Known limitations
@@ -99,15 +103,13 @@ V2 will bring in optimizations and enhancements, such as optimizing uploading an
## V2
- [ ] Confirm the HMAC algorithm -> if you create a draft using integration test, and then use the web frontend to finish the upload (you will see overwrite pop-up), and then use the web frontend to upload again the same file, but this time you will have 2 files with duplicated names
- [ ] Fix file upload progress -> If the upload failed, please Replace file. If the upload is still in progress, replacing it will cancel the ongoing upload.
- [ ] Improve file searching function to use HMAC instead of just using string comparison
- [ ] Remove e.g. proton.link related exposures in the function signature (this library should abstract them all)
- [ ] Documentation
- [ ] Go through Drive iOS source code and check the logic control flow
- [ ] Figure out the bottleneck by doing some profiling
- [ ] Proper error handling by looking at the return code instead of the error string
- [ ] Duplicated folder name handling: 422: A file or folder with that name already exists (Code=2500, Status=422)
- [ ] Not found: ERROR RESTY 422: File or folder was not found. (Code=2501, Status=422), Attempt 1
- [ ] Failed upload: Draft already exists on this revision (Code=2500, Status=409)
- [ ] File
- [ ] Improve large file handling
- [ ] Handle failed / interrupted upload

View File

@@ -15,6 +15,7 @@ type Config struct {
/* Setting */
DestructiveIntegrationTest bool // CAUTION: the integration test requires a clean proton drive
EmptyTrashAfterIntegrationTest bool // CAUTION: the integration test will clean up all the data in the trash
ReplaceExistingDraft bool // for the file upload replace or keep it as-is option
/* Drive */
DataFolderName string
@@ -53,6 +54,7 @@ func NewConfigWithDefaultValues() *Config {
DestructiveIntegrationTest: false,
EmptyTrashAfterIntegrationTest: false,
ReplaceExistingDraft: false,
DataFolderName: "data",
}
@@ -94,6 +96,7 @@ func NewConfigForIntegrationTests() *Config {
DestructiveIntegrationTest: true,
EmptyTrashAfterIntegrationTest: true,
ReplaceExistingDraft: false,
DataFolderName: "data",
}

View File

@@ -36,7 +36,7 @@ func (protonDrive *ProtonDrive) MoveFolderToTrashByID(ctx context.Context, linkI
return ErrLinkTypeMustToBeFolderType
}
childrenLinks, err := protonDrive.c.ListChildren(ctx, protonDrive.MainShare.ShareID, linkID, false)
childrenLinks, err := protonDrive.c.ListChildren(ctx, protonDrive.MainShare.ShareID, linkID /* false: list only active ones */, false)
if err != nil {
return err
}

View File

@@ -1,62 +1,15 @@
package proton_api_bridge
import (
"context"
"log"
"strings"
"testing"
"github.com/henrybear327/Proton-API-Bridge/common"
"github.com/henrybear327/Proton-API-Bridge/utility"
"github.com/henrybear327/go-proton-api"
)
func setup(t *testing.T) (context.Context, context.CancelFunc, *ProtonDrive) {
utility.SetupLog()
config := common.NewConfigForIntegrationTests()
{
// pre-condition check
if !config.DestructiveIntegrationTest {
t.Fatalf("CAUTION: the integration test requires a clean proton drive")
}
if !config.EmptyTrashAfterIntegrationTest {
t.Fatalf("CAUTION: the integration test requires cleaning up the drive after running the tests")
}
}
ctx, cancel := context.WithCancel(context.Background())
protonDrive, err := NewProtonDrive(ctx, config)
if err != nil {
t.Fatal(err)
}
err = protonDrive.EmptyRootFolder(ctx)
if err != nil {
t.Fatal(err)
}
err = protonDrive.EmptyTrash(ctx)
if err != nil {
t.Fatal(err)
}
return ctx, cancel, protonDrive
}
func tearDown(t *testing.T, ctx context.Context, protonDrive *ProtonDrive) {
if protonDrive.Config.EmptyTrashAfterIntegrationTest {
err := protonDrive.EmptyTrash(ctx)
if err != nil {
t.Fatal(err)
}
}
}
/* Integration Tests */
func TestCreateAndDeleteFolder(t *testing.T) {
ctx, cancel, protonDrive := setup(t)
ctx, cancel, protonDrive := setup(t, false)
t.Cleanup(func() {
defer cancel()
defer tearDown(t, ctx, protonDrive)
@@ -64,63 +17,132 @@ func TestCreateAndDeleteFolder(t *testing.T) {
log.Println("Create a folder tmp at root")
createFolder(t, ctx, protonDrive, "", "tmp")
checkFileListing(t, ctx, protonDrive, []string{"/tmp"})
checkActiveFileListing(t, ctx, protonDrive, []string{"/tmp"})
log.Println("Delet folder tmp")
deleteBySearchingFromRoot(t, ctx, protonDrive, "tmp", true)
checkFileListing(t, ctx, protonDrive, []string{})
log.Println("Delete folder tmp")
deleteBySearchingFromRoot(t, ctx, protonDrive, "tmp", true, false)
checkActiveFileListing(t, ctx, protonDrive, []string{})
}
func TestCreateAndCreateAndDeleteFolder(t *testing.T) {
ctx, cancel, protonDrive := setup(t, false)
t.Cleanup(func() {
defer cancel()
defer tearDown(t, ctx, protonDrive)
})
log.Println("Create a folder tmp at root")
createFolder(t, ctx, protonDrive, "", "tmp")
checkActiveFileListing(t, ctx, protonDrive, []string{"/tmp"})
log.Println("Create a folder tmp at root again")
createFolderExpectError(t, ctx, protonDrive, "", "tmp", proton.ErrFolderNameExist)
checkActiveFileListing(t, ctx, protonDrive, []string{"/tmp"})
log.Println("Delete folder tmp")
deleteBySearchingFromRoot(t, ctx, protonDrive, "tmp", true, false)
}
func TestUploadAndDownloadAndDeleteAFile(t *testing.T) {
ctx, cancel, protonDrive := setup(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")
checkRevisions(protonDrive, ctx, t, "integrationTestImage.png", 1)
checkFileListing(t, ctx, protonDrive, []string{"/integrationTestImage.png"})
uploadFileByFilepath(t, ctx, protonDrive, "", "integrationTestImage.png", "testcase/integrationTestImage.png", false)
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") /* Add a revision */
checkRevisions(protonDrive, ctx, t, "integrationTestImage.png", 2)
uploadFileByFilepath(t, ctx, protonDrive, "", "integrationTestImage.png", "testcase/integrationTestImage2.png", false) /* Add a revision */
checkRevisions(protonDrive, ctx, t, "integrationTestImage.png", 2, 1, 0, 1)
downloadFile(t, ctx, protonDrive, "", "integrationTestImage.png", "testcase/integrationTestImage2.png", "")
checkFileListing(t, ctx, protonDrive, []string{"/integrationTestImage.png"})
log.Println("Delete file integrationTestImage.png")
deleteBySearchingFromRoot(t, ctx, protonDrive, "integrationTestImage.png", false)
checkFileListing(t, ctx, protonDrive, []string{})
deleteBySearchingFromRoot(t, ctx, protonDrive, "integrationTestImage.png", false, false)
checkActiveFileListing(t, ctx, protonDrive, []string{})
}
func TestPartialUploadAndReuploadFailedAndDownloadAndDeleteAFile(t *testing.T) {
ctx, cancel, protonDrive := setup(t, false)
t.Cleanup(func() {
defer cancel()
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)
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)
checkRevisions(protonDrive, ctx, t, "integrationTestImage.png", 1, 0, 1, 0)
checkActiveFileListing(t, ctx, protonDrive, []string{})
// FIXME: delete file with draft revision only
// log.Println("Delete file integrationTestImage.png")
// deleteBySearchingFromRoot(t, ctx, protonDrive, "integrationTestImage.png", false, true)
// checkActiveFileListing(t, ctx, protonDrive, []string{})
}
func TestPartialUploadAndReuploadAndDownloadAndDeleteAFile(t *testing.T) {
ctx, cancel, protonDrive := setup(t, true)
t.Cleanup(func() {
defer cancel()
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)
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)
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 */
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"})
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)
ctx, cancel, protonDrive := setup(t, false)
t.Cleanup(func() {
defer cancel()
defer tearDown(t, ctx, protonDrive)
})
log.Println("Upload empty.txt")
uploadFileByFilepath(t, ctx, protonDrive, "", "empty.txt", "testcase/empty.txt")
checkRevisions(protonDrive, ctx, t, "empty.txt", 1)
checkFileListing(t, ctx, protonDrive, []string{"/empty.txt"})
uploadFileByFilepath(t, ctx, protonDrive, "", "empty.txt", "testcase/empty.txt", false)
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") /* Add a revision */
checkRevisions(protonDrive, ctx, t, "empty.txt", 2)
uploadFileByFilepath(t, ctx, protonDrive, "", "empty.txt", "testcase/empty.txt", false) /* Add a revision */
checkRevisions(protonDrive, ctx, t, "empty.txt", 2, 1, 0, 1)
downloadFile(t, ctx, protonDrive, "", "empty.txt", "testcase/empty.txt", "")
checkFileListing(t, ctx, protonDrive, []string{"/empty.txt"})
checkActiveFileListing(t, ctx, protonDrive, []string{"/empty.txt"})
log.Println("Delete file empty.txt")
deleteBySearchingFromRoot(t, ctx, protonDrive, "empty.txt", false)
checkFileListing(t, ctx, protonDrive, []string{})
deleteBySearchingFromRoot(t, ctx, protonDrive, "empty.txt", false, false)
checkActiveFileListing(t, ctx, protonDrive, []string{})
}
func TestUploadAndDownloadAndDeleteAFileAtAFolderOneLevelFromRoot(t *testing.T) {
ctx, cancel, protonDrive := setup(t)
ctx, cancel, protonDrive := setup(t, false)
t.Cleanup(func() {
defer cancel()
defer tearDown(t, ctx, protonDrive)
@@ -128,26 +150,26 @@ func TestUploadAndDownloadAndDeleteAFileAtAFolderOneLevelFromRoot(t *testing.T)
log.Println("Create folder level1")
createFolder(t, ctx, protonDrive, "", "level1")
checkFileListing(t, ctx, protonDrive, []string{"/level1"})
checkActiveFileListing(t, ctx, protonDrive, []string{"/level1"})
log.Println("Upload integrationTestImage.png to level1")
uploadFileByFilepath(t, ctx, protonDrive, "level1", "integrationTestImage.png", "testcase/integrationTestImage.png")
checkRevisions(protonDrive, ctx, t, "integrationTestImage.png", 1)
checkFileListing(t, ctx, protonDrive, []string{"/level1", "/level1/integrationTestImage.png"})
uploadFileByFilepath(t, ctx, protonDrive, "level1", "integrationTestImage.png", "testcase/integrationTestImage.png", false)
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") /* Add a revision */
checkRevisions(protonDrive, ctx, t, "integrationTestImage.png", 2)
uploadFileByFilepath(t, ctx, protonDrive, "level1", "integrationTestImage.png", "testcase/integrationTestImage2.png", false) /* Add a revision */
checkRevisions(protonDrive, ctx, t, "integrationTestImage.png", 2, 1, 0, 1)
downloadFile(t, ctx, protonDrive, "level1", "integrationTestImage.png", "testcase/integrationTestImage2.png", "")
log.Println("Delete folder level1")
deleteBySearchingFromRoot(t, ctx, protonDrive, "level1", true)
checkFileListing(t, ctx, protonDrive, []string{})
deleteBySearchingFromRoot(t, ctx, protonDrive, "level1", true, false)
checkActiveFileListing(t, ctx, protonDrive, []string{})
}
func TestCreateAndMoveAndDeleteFolder(t *testing.T) {
ctx, cancel, protonDrive := setup(t)
ctx, cancel, protonDrive := setup(t, false)
t.Cleanup(func() {
defer cancel()
defer tearDown(t, ctx, protonDrive)
@@ -155,23 +177,23 @@ func TestCreateAndMoveAndDeleteFolder(t *testing.T) {
log.Println("Create a folder src at root")
createFolder(t, ctx, protonDrive, "", "src")
checkFileListing(t, ctx, protonDrive, []string{"/src"})
checkActiveFileListing(t, ctx, protonDrive, []string{"/src"})
log.Println("Create a folder dst at root")
createFolder(t, ctx, protonDrive, "", "dst")
checkFileListing(t, ctx, protonDrive, []string{"/src", "/dst"})
checkActiveFileListing(t, ctx, protonDrive, []string{"/src", "/dst"})
log.Println("Move folder src to under folder dst")
moveFolder(t, ctx, protonDrive, "src", "dst")
checkFileListing(t, ctx, protonDrive, []string{"/dst", "/dst/src"})
checkActiveFileListing(t, ctx, protonDrive, []string{"/dst", "/dst/src"})
log.Println("Delete folder dst")
deleteBySearchingFromRoot(t, ctx, protonDrive, "dst", true)
checkFileListing(t, ctx, protonDrive, []string{})
deleteBySearchingFromRoot(t, ctx, protonDrive, "dst", true, false)
checkActiveFileListing(t, ctx, protonDrive, []string{})
}
func TestCreateAndMoveAndDeleteFolderWithAFile(t *testing.T) {
ctx, cancel, protonDrive := setup(t)
ctx, cancel, protonDrive := setup(t, false)
t.Cleanup(func() {
defer cancel()
defer tearDown(t, ctx, protonDrive)
@@ -179,35 +201,35 @@ func TestCreateAndMoveAndDeleteFolderWithAFile(t *testing.T) {
log.Println("Create a folder src at root")
createFolder(t, ctx, protonDrive, "", "src")
checkFileListing(t, ctx, protonDrive, []string{"/src"})
checkActiveFileListing(t, ctx, protonDrive, []string{"/src"})
log.Println("Upload integrationTestImage.png to src")
uploadFileByFilepath(t, ctx, protonDrive, "src", "integrationTestImage.png", "testcase/integrationTestImage.png")
checkRevisions(protonDrive, ctx, t, "integrationTestImage.png", 1)
checkFileListing(t, ctx, protonDrive, []string{"/src", "/src/integrationTestImage.png"})
uploadFileByFilepath(t, ctx, protonDrive, "src", "integrationTestImage.png", "testcase/integrationTestImage.png", false)
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", "")
log.Println("Create a folder dst at root")
createFolder(t, ctx, protonDrive, "", "dst")
checkFileListing(t, ctx, protonDrive, []string{"/src", "/src/integrationTestImage.png", "/dst"})
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") /* Add a revision */
checkRevisions(protonDrive, ctx, t, "integrationTestImage.png", 2)
uploadFileByFilepath(t, ctx, protonDrive, "src", "integrationTestImage.png", "testcase/integrationTestImage2.png", false) /* Add a revision */
checkRevisions(protonDrive, ctx, t, "integrationTestImage.png", 2, 1, 0, 1)
downloadFile(t, ctx, protonDrive, "src", "integrationTestImage.png", "testcase/integrationTestImage2.png", "")
checkFileListing(t, ctx, protonDrive, []string{"/src", "/src/integrationTestImage.png", "/dst"})
checkActiveFileListing(t, ctx, protonDrive, []string{"/src", "/src/integrationTestImage.png", "/dst"})
log.Println("Move folder src to under folder dst")
moveFolder(t, ctx, protonDrive, "src", "dst")
checkFileListing(t, ctx, protonDrive, []string{"/dst", "/dst/src", "/dst/src/integrationTestImage.png"})
checkActiveFileListing(t, ctx, protonDrive, []string{"/dst", "/dst/src", "/dst/src/integrationTestImage.png"})
log.Println("Delete folder dst")
deleteBySearchingFromRoot(t, ctx, protonDrive, "dst", true)
checkFileListing(t, ctx, protonDrive, []string{})
deleteBySearchingFromRoot(t, ctx, protonDrive, "dst", true, false)
checkActiveFileListing(t, ctx, protonDrive, []string{})
}
func TestCreateAndMoveAndDeleteAFileOneLevelFromRoot(t *testing.T) {
ctx, cancel, protonDrive := setup(t)
ctx, cancel, protonDrive := setup(t, false)
t.Cleanup(func() {
defer cancel()
defer tearDown(t, ctx, protonDrive)
@@ -215,39 +237,39 @@ func TestCreateAndMoveAndDeleteAFileOneLevelFromRoot(t *testing.T) {
log.Println("Create a folder src at root")
createFolder(t, ctx, protonDrive, "", "src")
checkFileListing(t, ctx, protonDrive, []string{"/src"})
checkActiveFileListing(t, ctx, protonDrive, []string{"/src"})
log.Println("Upload integrationTestImage.png to src")
uploadFileByFilepath(t, ctx, protonDrive, "src", "integrationTestImage.png", "testcase/integrationTestImage.png")
checkRevisions(protonDrive, ctx, t, "integrationTestImage.png", 1)
checkFileListing(t, ctx, protonDrive, []string{"/src", "/src/integrationTestImage.png"})
uploadFileByFilepath(t, ctx, protonDrive, "src", "integrationTestImage.png", "testcase/integrationTestImage.png", false)
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", "")
log.Println("Create a folder dst at root")
createFolder(t, ctx, protonDrive, "", "dst")
checkFileListing(t, ctx, protonDrive, []string{"/src", "/src/integrationTestImage.png", "/dst"})
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") /* Add a revision */
checkRevisions(protonDrive, ctx, t, "integrationTestImage.png", 2)
uploadFileByFilepath(t, ctx, protonDrive, "src", "integrationTestImage.png", "testcase/integrationTestImage2.png", false) /* Add a revision */
checkRevisions(protonDrive, ctx, t, "integrationTestImage.png", 2, 1, 0, 1)
downloadFile(t, ctx, protonDrive, "src", "integrationTestImage.png", "testcase/integrationTestImage2.png", "")
checkFileListing(t, ctx, protonDrive, []string{"/src", "/src/integrationTestImage.png", "/dst"})
checkActiveFileListing(t, ctx, protonDrive, []string{"/src", "/src/integrationTestImage.png", "/dst"})
log.Println("Move file integrationTestImage.png to under folder dst")
moveFile(t, ctx, protonDrive, "integrationTestImage.png", "dst")
checkFileListing(t, ctx, protonDrive, []string{"/src", "/dst", "/dst/integrationTestImage.png"})
checkActiveFileListing(t, ctx, protonDrive, []string{"/src", "/dst", "/dst/integrationTestImage.png"})
log.Println("Delete folder dst")
deleteBySearchingFromRoot(t, ctx, protonDrive, "dst", true)
checkFileListing(t, ctx, protonDrive, []string{"/src"})
deleteBySearchingFromRoot(t, ctx, protonDrive, "dst", true, false)
checkActiveFileListing(t, ctx, protonDrive, []string{"/src"})
log.Println("Delete folder src")
deleteBySearchingFromRoot(t, ctx, protonDrive, "src", true)
checkFileListing(t, ctx, protonDrive, []string{})
deleteBySearchingFromRoot(t, ctx, protonDrive, "src", true, false)
checkActiveFileListing(t, ctx, protonDrive, []string{})
}
func TestUploadLargeNumberOfBlocks(t *testing.T) {
ctx, cancel, protonDrive := setup(t)
ctx, cancel, protonDrive := setup(t, false)
t.Cleanup(func() {
defer cancel()
defer tearDown(t, ctx, protonDrive)
@@ -271,18 +293,18 @@ func TestUploadLargeNumberOfBlocks(t *testing.T) {
file2ContentReader := strings.NewReader(file2Content)
log.Println("Upload fileContent.txt")
uploadFileByReader(t, ctx, protonDrive, "", filename, file1ContentReader)
checkRevisions(protonDrive, ctx, t, filename, 1)
checkFileListing(t, ctx, protonDrive, []string{"/" + filename})
uploadFileByReader(t, ctx, protonDrive, "", filename, file1ContentReader, false)
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)
checkRevisions(protonDrive, ctx, t, filename, 2)
checkFileListing(t, ctx, protonDrive, []string{"/" + filename})
uploadFileByReader(t, ctx, protonDrive, "", filename, file2ContentReader, false)
checkRevisions(protonDrive, ctx, t, filename, 2, 1, 0, 1)
checkActiveFileListing(t, ctx, protonDrive, []string{"/" + filename})
downloadFile(t, ctx, protonDrive, "", filename, "", file2Content)
log.Println("Delete file fileContent.txt")
deleteBySearchingFromRoot(t, ctx, protonDrive, filename, false)
checkFileListing(t, ctx, protonDrive, []string{})
deleteBySearchingFromRoot(t, ctx, protonDrive, filename, false, false)
checkActiveFileListing(t, ctx, protonDrive, []string{})
}

View File

@@ -9,11 +9,58 @@ import (
"testing"
"time"
"github.com/henrybear327/Proton-API-Bridge/common"
"github.com/henrybear327/Proton-API-Bridge/utility"
"github.com/henrybear327/go-proton-api"
mathrand "math/rand"
)
func setup(t *testing.T, replaceExistingDraft bool) (context.Context, context.CancelFunc, *ProtonDrive) {
utility.SetupLog()
config := common.NewConfigForIntegrationTests()
config.ReplaceExistingDraft = replaceExistingDraft
{
// pre-condition check
if !config.DestructiveIntegrationTest {
t.Fatalf("CAUTION: the integration test requires a clean proton drive")
}
if !config.EmptyTrashAfterIntegrationTest {
t.Fatalf("CAUTION: the integration test requires cleaning up the drive after running the tests")
}
}
ctx, cancel := context.WithCancel(context.Background())
protonDrive, err := NewProtonDrive(ctx, config)
if err != nil {
t.Fatal(err)
}
err = protonDrive.EmptyRootFolder(ctx)
if err != nil {
t.Fatal(err)
}
err = protonDrive.EmptyTrash(ctx)
if err != nil {
t.Fatal(err)
}
return ctx, cancel, protonDrive
}
func tearDown(t *testing.T, ctx context.Context, protonDrive *ProtonDrive) {
if protonDrive.Config.EmptyTrashAfterIntegrationTest {
err := protonDrive.EmptyTrash(ctx)
if err != nil {
t.Fatal(err)
}
}
}
// Taken from: https://github.com/rclone/rclone/blob/e43b5ce5e59b5717a9819ff81805dd431f710c10/lib/random/random.go
//
// StringFn create a random string for test purposes using the random
@@ -44,12 +91,10 @@ func RandomString(n int) string {
return StringFn(n, mathrand.Intn)
}
/* Helper functions */
func createFolder(t *testing.T, ctx context.Context, protonDrive *ProtonDrive, parent, name string) {
func createFolderExpectError(t *testing.T, ctx context.Context, protonDrive *ProtonDrive, parent, name string, expectedError error) {
parentLink := protonDrive.RootLink
if parent != "" {
targetFolderLink, err := protonDrive.SearchByNameRecursivelyFromRoot(ctx, parent, true)
targetFolderLink, err := protonDrive.SearchByNameRecursivelyFromRoot(ctx, parent, true, false)
if err != nil {
t.Fatal(err)
}
@@ -63,15 +108,19 @@ func createFolder(t *testing.T, ctx context.Context, protonDrive *ProtonDrive, p
}
_, err := protonDrive.CreateNewFolderByID(ctx, parentLink.LinkID, name)
if err != nil {
if err != expectedError {
t.Fatal(err)
}
}
func uploadFileByReader(t *testing.T, ctx context.Context, protonDrive *ProtonDrive, parent, name string, in io.Reader) {
func createFolder(t *testing.T, ctx context.Context, protonDrive *ProtonDrive, parent, name string) {
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) {
parentLink := protonDrive.RootLink
if parent != "" {
targetFolderLink, err := protonDrive.SearchByNameRecursivelyFromRoot(ctx, parent, true)
targetFolderLink, err := protonDrive.SearchByNameRecursivelyFromRoot(ctx, parent, true, false)
if err != nil {
t.Fatal(err)
}
@@ -84,16 +133,16 @@ 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)
_, _, err := protonDrive.UploadFileByReader(ctx, parentLink.LinkID, name, time.Now(), in, createFileOnly)
if err != nil {
t.Fatal(err)
}
}
func uploadFileByFilepath(t *testing.T, ctx context.Context, protonDrive *ProtonDrive, parent, name string, filepath string) {
func uploadFileByFilepathWithError(t *testing.T, ctx context.Context, protonDrive *ProtonDrive, parent, name string, filepath string, createFileOnly bool, expectedError error) {
parentLink := protonDrive.RootLink
if parent != "" {
targetFolderLink, err := protonDrive.SearchByNameRecursivelyFromRoot(ctx, parent, true)
targetFolderLink, err := protonDrive.SearchByNameRecursivelyFromRoot(ctx, parent, true, false)
if err != nil {
t.Fatal(err)
}
@@ -119,16 +168,20 @@ func uploadFileByFilepath(t *testing.T, ctx context.Context, protonDrive *Proton
in := bufio.NewReader(f)
_, _, err = protonDrive.UploadFileByReader(ctx, parentLink.LinkID, name, info.ModTime(), in)
if err != nil {
_, _, err = protonDrive.UploadFileByReader(ctx, parentLink.LinkID, name, info.ModTime(), in, createFileOnly)
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 downloadFile(t *testing.T, ctx context.Context, protonDrive *ProtonDrive, parent, name string, filepath string, data string) {
parentLink := protonDrive.RootLink
if parent != "" {
targetFolderLink, err := protonDrive.SearchByNameRecursivelyFromRoot(ctx, parent, true)
targetFolderLink, err := protonDrive.SearchByNameRecursivelyFromRoot(ctx, parent, true, false)
if err != nil {
t.Fatal(err)
}
@@ -142,7 +195,7 @@ func downloadFile(t *testing.T, ctx context.Context, protonDrive *ProtonDrive, p
t.Fatalf("parentLink is not of folder type")
}
targetFileLink, err := protonDrive.SearchByNameRecursivelyFromRoot(ctx, name, false)
targetFileLink, err := protonDrive.SearchByNameRecursivelyFromRoot(ctx, name, false, false)
if err != nil {
t.Fatal(err)
}
@@ -182,8 +235,8 @@ func downloadFile(t *testing.T, ctx context.Context, protonDrive *ProtonDrive, p
}
}
func checkRevisions(protonDrive *ProtonDrive, ctx context.Context, t *testing.T, name string, totalRevisions int) {
targetFileLink, err := protonDrive.SearchByNameRecursivelyFromRoot(ctx, name, false)
func checkRevisions(protonDrive *ProtonDrive, ctx context.Context, t *testing.T, name string, totalRevisions, activeRevisions, draftRevisions, obseleteRevisions int) {
targetFileLink, err := protonDrive.SearchByNameRecursivelyFromRoot(ctx, name, false, true)
if err != nil {
t.Fatal(err)
}
@@ -198,12 +251,27 @@ func checkRevisions(protonDrive *ProtonDrive, ctx context.Context, t *testing.T,
if len(revisions) != totalRevisions {
t.Fatalf("Missing revision")
}
for i := range revisions {
if revisions[i].State == proton.RevisionStateActive {
activeRevisions--
}
if revisions[i].State == proton.RevisionStateDraft {
draftRevisions--
}
if revisions[i].State == proton.RevisionStateObsolete {
obseleteRevisions--
}
}
if activeRevisions != 0 || draftRevisions != 0 || obseleteRevisions != 0 {
t.Fatalf("Wrong revision count %v %v %v", activeRevisions, draftRevisions, obseleteRevisions)
}
}
}
// During the integration test, the name much be unique since the link is returned by recursively search for the name from root
func deleteBySearchingFromRoot(t *testing.T, ctx context.Context, protonDrive *ProtonDrive, name string, isFolder bool) {
targetLink, err := protonDrive.SearchByNameRecursivelyFromRoot(ctx, name, isFolder)
func deleteBySearchingFromRoot(t *testing.T, ctx context.Context, protonDrive *ProtonDrive, name string, isFolder bool, listAllActiveOrDraftFiles bool) {
targetLink, err := protonDrive.SearchByNameRecursivelyFromRoot(ctx, name, isFolder, listAllActiveOrDraftFiles)
if err != nil {
t.Fatal(err)
}
@@ -224,7 +292,7 @@ func deleteBySearchingFromRoot(t *testing.T, ctx context.Context, protonDrive *P
}
}
func checkFileListing(t *testing.T, ctx context.Context, protonDrive *ProtonDrive, expectedPaths []string) {
func checkActiveFileListing(t *testing.T, ctx context.Context, protonDrive *ProtonDrive, expectedPaths []string) {
{
paths := make([]string, 0)
err := protonDrive.ListDirectoriesRecursively(ctx, protonDrive.MainShareKR, protonDrive.RootLink, false, -1, 0, true, "", &paths)
@@ -270,11 +338,11 @@ func checkFileListing(t *testing.T, ctx context.Context, protonDrive *ProtonDriv
}
func moveFolder(t *testing.T, ctx context.Context, protonDrive *ProtonDrive, srcFolderName, dstParentFolderName string) {
targetSrcFolderLink, err := protonDrive.SearchByNameRecursivelyFromRoot(ctx, srcFolderName, true)
targetSrcFolderLink, err := protonDrive.SearchByNameRecursivelyFromRoot(ctx, srcFolderName, true, false)
if err != nil {
t.Fatal(err)
}
targetDestFolderLink, err := protonDrive.SearchByNameRecursivelyFromRoot(ctx, dstParentFolderName, true)
targetDestFolderLink, err := protonDrive.SearchByNameRecursivelyFromRoot(ctx, dstParentFolderName, true, false)
if err != nil {
t.Fatal(err)
}
@@ -289,11 +357,11 @@ func moveFolder(t *testing.T, ctx context.Context, protonDrive *ProtonDrive, src
}
func moveFile(t *testing.T, ctx context.Context, protonDrive *ProtonDrive, srcFileName, dstParentFolderName string) {
targetSrcFileLink, err := protonDrive.SearchByNameRecursivelyFromRoot(ctx, srcFileName, false)
targetSrcFileLink, err := protonDrive.SearchByNameRecursivelyFromRoot(ctx, srcFileName, false, false)
if err != nil {
t.Fatal(err)
}
targetDestFolderLink, err := protonDrive.SearchByNameRecursivelyFromRoot(ctx, dstParentFolderName, true)
targetDestFolderLink, err := protonDrive.SearchByNameRecursivelyFromRoot(ctx, dstParentFolderName, true, false)
if err != nil {
t.Fatal(err)
}

View File

@@ -7,10 +7,12 @@ var (
ErrDataFolderNameIsEmpty = errors.New("please supply a DataFolderName to enabling file downloading")
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 becuase it is not empty")
ErrFolderIsNotEmpty = errors.New("folder can't be deleted because it is not empty")
ErrInternalErrorOnFileUpload = errors.New("either link or createFileResp must 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")
)

114
file.go
View File

@@ -8,7 +8,6 @@ import (
"encoding/base64"
"io"
"os"
"strings"
"time"
"github.com/ProtonMail/gopenpgp/v2/crypto"
@@ -31,7 +30,12 @@ func (protonDrive *ProtonDrive) DownloadFileByID(ctx context.Context, linkID str
return protonDrive.DownloadFile(ctx, &link)
}
func (protonDrive *ProtonDrive) GetActiveRevision(ctx context.Context, link *proton.Link) (*proton.Revision, error) {
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
}
revisions, err := protonDrive.c.ListRevisions(ctx, protonDrive.MainShare.ShareID, link.LinkID)
if err != nil {
return nil, err
@@ -39,21 +43,19 @@ func (protonDrive *ProtonDrive) GetActiveRevision(ctx context.Context, link *pro
// log.Printf("revisions %#v", revisions)
// Revisions are only for files, they represent “versions” of files.
// Each file can have 1 active revision and n obsolete revisions.
activeRevision := -1
// Each file can have 1 active/draft revision and n obsolete revisions.
targetRevision := -1
for i := range revisions {
if revisions[i].State == proton.RevisionStateActive {
activeRevision = i
if revisions[i].State == revisionType {
targetRevision = i
break
}
}
revision, err := protonDrive.c.GetRevisionAllBlocks(ctx, protonDrive.MainShare.ShareID, link.LinkID, revisions[activeRevision].ID)
if err != nil {
return nil, err
if targetRevision == -1 { // not found
return nil, nil
}
// log.Println("Total blocks", len(revision.Blocks))
return &revision, nil
return &revisions[targetRevision], nil
}
func (protonDrive *ProtonDrive) GetActiveRevisionWithAttrs(ctx context.Context, link *proton.Link) (*proton.Revision, *FileSystemAttrs, error) {
@@ -61,11 +63,17 @@ func (protonDrive *ProtonDrive) GetActiveRevisionWithAttrs(ctx context.Context,
return nil, nil, ErrLinkMustNotBeNil
}
revision, err := protonDrive.GetActiveRevision(ctx, link)
revisionMetadata, err := protonDrive.GetRevision(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 err != nil {
return nil, nil, err
}
// log.Println("Total blocks", len(revision.Blocks))
nodeKR, err := protonDrive.getNodeKR(ctx, link)
if err != nil {
return nil, nil, err
@@ -81,7 +89,7 @@ func (protonDrive *ProtonDrive) GetActiveRevisionWithAttrs(ctx context.Context,
return nil, nil, err
}
return revision, &FileSystemAttrs{
return &revision, &FileSystemAttrs{
ModificationTime: modificationTime,
Size: revisionXAttrCommon.Size,
}, nil
@@ -133,16 +141,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) (*proton.Link, int64, error) {
func (protonDrive *ProtonDrive) UploadFileByReader(ctx context.Context, parentLinkID string, filename string, modTime time.Time, file io.Reader, createFileOnly bool) (*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)
return protonDrive.uploadFile(ctx, &parentLink, filename, modTime, file, createFileOnly)
}
func (protonDrive *ProtonDrive) UploadFileByPath(ctx context.Context, parentLink *proton.Link, filename string, filePath string) (*proton.Link, int64, error) {
func (protonDrive *ProtonDrive) UploadFileByPath(ctx context.Context, parentLink *proton.Link, filename string, filePath string, createFileOnly bool) (*proton.Link, int64, error) {
f, err := os.Open(filePath)
if err != nil {
return nil, 0, err
@@ -156,7 +164,7 @@ func (protonDrive *ProtonDrive) UploadFileByPath(ctx context.Context, parentLink
in := bufio.NewReader(f)
return protonDrive.uploadFile(ctx, parentLink, filename, info.ModTime(), in)
return protonDrive.uploadFile(ctx, parentLink, filename, info.ModTime(), in, createFileOnly)
}
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) {
@@ -215,17 +223,14 @@ func (protonDrive *ProtonDrive) createFileUploadDraft(ctx context.Context, paren
createFileResp, err := protonDrive.c.CreateFile(ctx, protonDrive.MainShare.ShareID, createFileReq)
if err != nil {
// FIXME: check for duplicated filename by using checkAvailableHashes
// FIXME: better error handling
// 422: A file or folder with that name already exists (Code=2500, Status=422)
if strings.Contains(err.Error(), "(Code=2500, Status=422)") {
// file name conflict, file already exists
link, err := protonDrive.SearchByNameInFolder(ctx, parentLink, filename, true, false)
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
}
return link, nil, nil, nil, nil
}
// other real error caught
return nil, nil, nil, nil, err
}
@@ -327,7 +332,6 @@ func (protonDrive *ProtonDrive) uploadAndCollectBlockData(ctx context.Context, n
}
func (protonDrive *ProtonDrive) commitNewRevision(ctx context.Context, nodeKR *crypto.KeyRing, modificationTime time.Time, size int64, manifestSignatureData []byte, blockTokens []proton.BlockToken, linkID, revisionID string) error {
// TODO: check iOS Drive CommitableRevision
manifestSignature, err := protonDrive.AddrKR.SignDetached(crypto.NewPlainMessage(manifestSignatureData))
if err != nil {
return err
@@ -356,11 +360,13 @@ 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) (*proton.Link, int64, error) {
func (protonDrive *ProtonDrive) uploadFile(ctx context.Context, parentLink *proton.Link, filename string, modTime time.Time, file io.Reader, createFileOnly bool) (*proton.Link, int64, error) {
// FIXME: check iOS: optimize for large files -> enc blocks on the fly
// main issue lies in the mimetype detection, since a full readout is used
// detect MIME type
// FIXME: use https://pkg.go.dev/mime#ExtensionsByType
// FIXME: this will cause the upload progress to display the "fake" progress
fileContent, err := io.ReadAll(file)
if err != nil {
return nil, 0, err
@@ -383,13 +389,33 @@ func (protonDrive *ProtonDrive) uploadFile(ctx context.Context, parentLink *prot
if link != nil {
linkID = link.LinkID
// get a new revision
newRevision, err := protonDrive.c.CreateRevision(ctx, protonDrive.MainShare.ShareID, 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
// FIXME: how do we observe for file upload cancellation
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
revisionID = newRevision.ID
}
// get newSessionKey and newNodeKR
parentNodeKR, err := protonDrive.getNodeKRByID(ctx, link.ParentLinkID)
@@ -408,33 +434,35 @@ func (protonDrive *ProtonDrive) uploadFile(ctx context.Context, parentLink *prot
linkID = createFileResp.ID
revisionID = createFileResp.RevisionID
} else {
// might be the case where the upload failed, since file search will not include file with type draft
// 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 := make([]byte, 0)
blockTokens := make([]proton.BlockToken, 0)
if fileSize == 0 {
/* step 2: upload blocks and collect block data */
// skipped: no block to upload
/* step 3: mark the file as active by updating the revision */
manifestSignature := make([]byte, 0)
blockTokens := make([]proton.BlockToken, 0)
err = protonDrive.commitNewRevision(ctx, newNodeKR, modTime, fileSize, manifestSignature, blockTokens, linkID, revisionID)
if err != nil {
return nil, 0, err
}
} else {
/* step 2: upload blocks and collect block data */
manifestSignatureData, blockTokens, err := protonDrive.uploadAndCollectBlockData(ctx, newSessionKey, newNodeKR, fileContent, linkID, revisionID)
manifestSignature, blockTokens, err = protonDrive.uploadAndCollectBlockData(ctx, newSessionKey, newNodeKR, fileContent, linkID, revisionID)
if err != nil {
return nil, 0, err
}
}
/* step 3: mark the file as active by updating the revision */
err = protonDrive.commitNewRevision(ctx, newNodeKR, modTime, fileSize, manifestSignatureData, blockTokens, linkID, revisionID)
if createFileOnly {
// 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
}
/* step 3: mark the file as active by updating the revision */
err = protonDrive.commitNewRevision(ctx, newNodeKR, modTime, fileSize, manifestSignature, blockTokens, linkID, revisionID)
if err != nil {
return nil, 0, err
}
finalLink, err := protonDrive.c.GetLink(ctx, protonDrive.MainShare.ShareID, linkID)

View File

@@ -167,8 +167,6 @@ func (protonDrive *ProtonDrive) CreateNewFolderByID(ctx context.Context, parentL
}
func (protonDrive *ProtonDrive) CreateNewFolder(ctx context.Context, parentLink *proton.Link, folderName string) (string, error) {
// TODO: check for duplicated folder name
parentNodeKR, err := protonDrive.getNodeKR(ctx, parentLink)
if err != nil {
return "", err
@@ -218,11 +216,11 @@ func (protonDrive *ProtonDrive) CreateNewFolder(ctx context.Context, parentLink
return "", err
}
// if the folder name already exist, this call will return an error
createFolderResp, err := protonDrive.c.CreateFolder(ctx, protonDrive.MainShare.ShareID, createFolderReq)
if err != nil {
return "", err
}
// log.Printf("createFolderResp %#v", createFolderResp)
return createFolderResp.ID, nil

2
go.mod
View File

@@ -5,7 +5,7 @@ go 1.18
require (
github.com/ProtonMail/gopenpgp/v2 v2.7.1
github.com/gabriel-vasile/mimetype v1.4.2
github.com/henrybear327/go-proton-api v0.0.0-20230628220324-22ba21ecb67f
github.com/henrybear327/go-proton-api v0.0.0-20230630092102-8258d2e48d15
github.com/relvacode/iso8601 v1.3.0
)

4
go.sum
View File

@@ -50,8 +50,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-20230628220324-22ba21ecb67f h1:OzLwkcwQZLkFnA0KpPVltWmw59Vq1XeJ9IzDjFvLdi4=
github.com/henrybear327/go-proton-api v0.0.0-20230628220324-22ba21ecb67f/go.mod h1:l42xBSOrCmkAxzWUHcoUsG/cP8m1hMhV72GoChOX3bg=
github.com/henrybear327/go-proton-api v0.0.0-20230630092102-8258d2e48d15 h1:RPH0WPXbBVNjPKru/t+KsYhbm3Sv82XeA3wPcv0+g1M=
github.com/henrybear327/go-proton-api v0.0.0-20230630092102-8258d2e48d15/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=

View File

@@ -11,17 +11,17 @@ import (
Observation: file name is unique, since it's checked (by hash) on the server
*/
func (protonDrive *ProtonDrive) SearchByNameRecursivelyFromRoot(ctx context.Context, targetName string, isFolder bool) (*proton.Link, error) {
func (protonDrive *ProtonDrive) SearchByNameRecursivelyFromRoot(ctx context.Context, targetName string, isFolder bool, listAllActiveOrDraftFiles bool) (*proton.Link, error) {
var linkType proton.LinkType
if isFolder {
linkType = proton.LinkTypeFolder
} else {
linkType = proton.LinkTypeFile
}
return protonDrive.searchByNameRecursively(ctx, protonDrive.MainShareKR, protonDrive.RootLink, targetName, linkType)
return protonDrive.searchByNameRecursively(ctx, protonDrive.MainShareKR, protonDrive.RootLink, targetName, linkType, listAllActiveOrDraftFiles)
}
func (protonDrive *ProtonDrive) SearchByNameRecursivelyByID(ctx context.Context, folderLinkID string, targetName string, isFolder bool) (*proton.Link, error) {
func (protonDrive *ProtonDrive) SearchByNameRecursivelyByID(ctx context.Context, folderLinkID string, targetName string, isFolder bool, listAllActiveOrDraftFiles bool) (*proton.Link, error) {
folderLink, err := protonDrive.c.GetLink(ctx, protonDrive.MainShare.ShareID, folderLinkID)
if err != nil {
return nil, err
@@ -41,10 +41,10 @@ func (protonDrive *ProtonDrive) SearchByNameRecursivelyByID(ctx context.Context,
if err != nil {
return nil, err
}
return protonDrive.searchByNameRecursively(ctx, folderKeyRing, &folderLink, targetName, linkType)
return protonDrive.searchByNameRecursively(ctx, folderKeyRing, &folderLink, targetName, linkType, listAllActiveOrDraftFiles)
}
func (protonDrive *ProtonDrive) SearchByNameRecursively(ctx context.Context, folderLink *proton.Link, targetName string, isFolder bool) (*proton.Link, error) {
func (protonDrive *ProtonDrive) SearchByNameRecursively(ctx context.Context, folderLink *proton.Link, targetName string, isFolder bool, listAllActiveOrDraftFiles bool) (*proton.Link, error) {
var linkType proton.LinkType
if isFolder {
linkType = proton.LinkTypeFolder
@@ -59,7 +59,7 @@ func (protonDrive *ProtonDrive) SearchByNameRecursively(ctx context.Context, fol
if err != nil {
return nil, err
}
return protonDrive.searchByNameRecursively(ctx, folderKeyRing, folderLink, targetName, linkType)
return protonDrive.searchByNameRecursively(ctx, folderKeyRing, folderLink, targetName, linkType, listAllActiveOrDraftFiles)
}
func (protonDrive *ProtonDrive) searchByNameRecursively(
@@ -67,12 +67,13 @@ func (protonDrive *ProtonDrive) searchByNameRecursively(
parentNodeKR *crypto.KeyRing,
link *proton.Link,
targetName string,
linkType proton.LinkType) (*proton.Link, error) {
/*
Assumptions:
- we only care about the active ones
*/
if link.State != proton.LinkStateActive {
linkType proton.LinkType,
listAllActiveOrDraftFiles bool) (*proton.Link, error) {
if listAllActiveOrDraftFiles {
if link.State != proton.LinkStateActive && link.State != proton.LinkStateDraft {
return nil, nil
}
} else if link.State != proton.LinkStateActive {
return nil, nil
}
@@ -100,7 +101,7 @@ func (protonDrive *ProtonDrive) searchByNameRecursively(
defer linkKR.ClearPrivateParams()
for _, childLink := range childrenLinks {
ret, err := protonDrive.searchByNameRecursively(ctx, linkKR, &childLink, targetName, linkType)
ret, err := protonDrive.searchByNameRecursively(ctx, linkKR, &childLink, targetName, linkType, listAllActiveOrDraftFiles)
if err != nil {
return nil, err
}
@@ -118,20 +119,20 @@ func (protonDrive *ProtonDrive) searchByNameRecursively(
func (protonDrive *ProtonDrive) SearchByNameInFolderByID(ctx context.Context,
folderLinkID string,
targetName string,
searchForFile, searchForFolder bool) (*proton.Link, error) {
searchForFile, searchForFolder, listAllActiveOrDraftFiles bool) (*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)
return protonDrive.SearchByNameInFolder(ctx, &folderLink, targetName, searchForFile, searchForFolder, listAllActiveOrDraftFiles)
}
func (protonDrive *ProtonDrive) SearchByNameInFolder(
ctx context.Context,
folderLink *proton.Link,
targetName string,
searchForFile, searchForFolder bool) (*proton.Link, error) {
searchForFile, searchForFolder, listAllActiveOrDraftFiles bool) (*proton.Link, error) {
if !searchForFile && !searchForFolder {
// nothing to search
return nil, nil
@@ -142,6 +143,11 @@ func (protonDrive *ProtonDrive) SearchByNameInFolder(
return nil, ErrLinkTypeMustToBeFolderType
}
if folderLink.State != proton.LinkStateActive {
// we only search in the active folders
return nil, nil
}
parentNodeKR, err := protonDrive.getNodeKRByID(ctx, folderLink.ParentLinkID)
if err != nil {
return nil, err
@@ -159,8 +165,11 @@ func (protonDrive *ProtonDrive) SearchByNameInFolder(
return nil, err
}
for _, childLink := range childrenLinks {
if childLink.State != proton.LinkStateActive {
// we only search in the active folders
if listAllActiveOrDraftFiles {
if childLink.State != proton.LinkStateActive && childLink.State != proton.LinkStateDraft {
continue
}
} else if childLink.State != proton.LinkStateActive {
continue
}