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