From f90e3b5ca4698dac4885b3ebebe685d65829fef9 Mon Sep 17 00:00:00 2001 From: Can Date: Sun, 24 Dec 2023 00:16:24 +0100 Subject: [PATCH] Fail if any of the exports failed (#13) --- README.md | 3 + documentation/setup.md | 3 +- .../greydev/notionbackup/NotionBackup.java | 64 ++++++++++++++++--- .../greydev/notionbackup/NotionClient.java | 10 ++- .../nextcloud/NextcloudClient.java | 3 +- 5 files changed, 70 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 19dd4a4..4efd7e0 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,9 @@ Create a `.env` file with the following properties ([How do I find all these val PCLOUD_API_HOST= PCLOUD_FOLDER_ID= + # if you don't use the Docker image and want to download the backup to a different folder + # DOWNLOADS_DIRECTORY_PATH= + ### Backup to Cloud With Docker Once you created your `.env` file, you can run the following command to start your backup: diff --git a/documentation/setup.md b/documentation/setup.md index 7e7bb0e..3d7e1e9 100644 --- a/documentation/setup.md +++ b/documentation/setup.md @@ -34,7 +34,8 @@ Do the following steps to set up the refresh token flow. 4. Click on continue and allow the app to access your files 5. Copy the authorization code (referred to as `AUTH_CODE` in the following steps) shown in the following screen -6. Send the following HTTP POST request with the actual values replacing the placeholders +6. Send the following HTTP POST request with the actual values replacing the placeholders. + Note that you need to encode the string `:` as a BASE64 string. ``` curl --request POST \ diff --git a/src/main/java/com/greydev/notionbackup/NotionBackup.java b/src/main/java/com/greydev/notionbackup/NotionBackup.java index 0ecd371..842f105 100644 --- a/src/main/java/com/greydev/notionbackup/NotionBackup.java +++ b/src/main/java/com/greydev/notionbackup/NotionBackup.java @@ -2,8 +2,10 @@ import java.io.File; import java.io.IOException; +import java.rmi.UnexpectedException; import java.util.Optional; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicBoolean; import com.greydev.notionbackup.cloudstorage.pcloud.PCloudApiClientFactory; import com.greydev.notionbackup.cloudstorage.pcloud.PCloudClient; @@ -66,12 +68,42 @@ public static void main(String[] args) { // use a local file to skip the notion export step // final File exportedFile = new File("notion-export-markdown_2022-01-18_23-17-13.zip"); - CompletableFuture futureGoogleDrive = CompletableFuture.runAsync(() -> NotionBackup.startGoogleDriveBackup(exportedFile)); - CompletableFuture futureDropbox = CompletableFuture.runAsync(() -> NotionBackup.startDropboxBackup(exportedFile)); - CompletableFuture futureNextcloud = CompletableFuture.runAsync(() -> NotionBackup.startNextcloudBackup(exportedFile)); - CompletableFuture futurePCloud = CompletableFuture.runAsync(() -> NotionBackup.startPCloudBackup(exportedFile)); + AtomicBoolean hasErrorOccurred = new AtomicBoolean(false); + + CompletableFuture futureGoogleDrive = CompletableFuture + .runAsync(() -> NotionBackup.startGoogleDriveBackup(exportedFile)) + .handle((result, ex) -> { + if (ex != null) hasErrorOccurred.set(true); + return null; + }); + + CompletableFuture futureDropbox = CompletableFuture + .runAsync(() -> NotionBackup.startDropboxBackup(exportedFile)) + .handle((result, ex) -> { + if (ex != null) hasErrorOccurred.set(true); + return null; + }); + + CompletableFuture futureNextcloud = CompletableFuture + .runAsync(() -> NotionBackup.startNextcloudBackup(exportedFile)) + .handle((result, ex) -> { + if (ex != null) hasErrorOccurred.set(true); + return null; + }); + + CompletableFuture futurePCloud = CompletableFuture + .runAsync(() -> NotionBackup.startPCloudBackup(exportedFile)) + .handle((result, ex) -> { + if (ex != null) hasErrorOccurred.set(true); + return null; + }); CompletableFuture.allOf(futureGoogleDrive, futureDropbox, futureNextcloud, futurePCloud).join(); + + if (hasErrorOccurred.get()) { + log.error("Not all backups were completed successfully. See the logs above to get more information about the errors."); + System.exit(1); + } } @@ -92,7 +124,11 @@ public static void startGoogleDriveBackup(File fileToUpload) { return; } GoogleDriveClient GoogleDriveClient = new GoogleDriveClient(googleServiceOptional.get(), googleDriveRootFolderId); - GoogleDriveClient.upload(fileToUpload); + boolean isSuccess = GoogleDriveClient.upload(fileToUpload); + + if (!isSuccess) { + throw new IllegalStateException("Backup was not successful"); + } } @@ -109,7 +145,11 @@ public static void startDropboxBackup(File fileToUpload) { return; } DropboxClient dropboxClient = new DropboxClient(dropboxServiceOptional.get()); - dropboxClient.upload(fileToUpload); + boolean isSuccess = dropboxClient.upload(fileToUpload); + + if (!isSuccess) { + throw new IllegalStateException("Backup was not successful"); + } } private static Optional getDropboxAccessToken() { @@ -153,7 +193,11 @@ public static void startNextcloudBackup(File fileToUpload) { return; } - new NextcloudClient(email, password, webdavUrl).upload(fileToUpload); + boolean isSuccess = new NextcloudClient(email, password, webdavUrl).upload(fileToUpload); + + if (!isSuccess) { + throw new IllegalStateException("Backup was not successful"); + } } public static void startPCloudBackup(File fileToUpload) { @@ -182,7 +226,11 @@ public static void startPCloudBackup(File fileToUpload) { } } PCloudClient pCloudClient = new PCloudClient(pCloudApiClient.get(), pCloudFolderId); - pCloudClient.upload(fileToUpload); + boolean isSuccess = pCloudClient.upload(fileToUpload); + + if (!isSuccess) { + throw new IllegalStateException("Backup was not successful"); + } } private static Optional extractGoogleServiceAccountSecret() { diff --git a/src/main/java/com/greydev/notionbackup/NotionClient.java b/src/main/java/com/greydev/notionbackup/NotionClient.java index 97dfe23..f4f8d38 100644 --- a/src/main/java/com/greydev/notionbackup/NotionClient.java +++ b/src/main/java/com/greydev/notionbackup/NotionClient.java @@ -182,7 +182,11 @@ private Optional triggerExportTask() throws IOException, InterruptedExce } */ if (responseJsonNode.get("taskId") == null) { - log.error("Error name: {}, error message: {}", responseJsonNode.get("name"), responseJsonNode.get("message")); + JsonNode errorName = responseJsonNode.get("name"); + log.error("Error name: {}, error message: {}", errorName, responseJsonNode.get("message")); + if (StringUtils.equalsIgnoreCase(errorName.toString(), "UnauthorizedError")) { + log.error("UnauthorizedError: seems like your token is not valid anymore. Try to log in to Notion again and replace you old token."); + } return Optional.empty(); } @@ -201,10 +205,9 @@ private Optional getDownloadLink(String taskId) throws IOException, Inte .POST(HttpRequest.BodyPublishers.ofString(postBody)) .build(); - for (int i = 0; i < 8000; i++) { + for (int i = 0; i < 800; i++) { HttpResponse response = newClient.send(request, HttpResponse.BodyHandlers.ofString()); - // TODO Need to prepare Jackson Document and see how this is handled. I don't wan't this wrapper "Results" class Results results = objectMapper.readValue(response.body(), Results.class); if (results.getResults().isEmpty()) { @@ -232,6 +235,7 @@ private Optional getDownloadLink(String taskId) throws IOException, Inte log.info("Notion API workspace export 'state': '{}', Pages exported so far: {}", result.getState(), result.getStatus().getPagesExported()); return Optional.of(result.getStatus().getExportUrl()); } + sleep(6000); } log.info("Notion workspace export failed. After waiting 80 minutes, the export status from the Notion API response was still not 'success'"); diff --git a/src/main/java/com/greydev/notionbackup/cloudstorage/nextcloud/NextcloudClient.java b/src/main/java/com/greydev/notionbackup/cloudstorage/nextcloud/NextcloudClient.java index 63d550f..6f10135 100644 --- a/src/main/java/com/greydev/notionbackup/cloudstorage/nextcloud/NextcloudClient.java +++ b/src/main/java/com/greydev/notionbackup/cloudstorage/nextcloud/NextcloudClient.java @@ -40,6 +40,7 @@ public boolean upload(File fileToUpload) { if (response.statusCode() == 201) { log.info("Nextcloud: successfully uploaded '{}'", fileToUpload.getName()); + return true; } else if (response.statusCode() == 204) { log.info("Nextcloud: file upload response code is {}). " + "This probably means a file with the same name already exists and it was overwritten/replaced.", response.statusCode()); @@ -50,7 +51,7 @@ public boolean upload(File fileToUpload) { } } catch (IOException | InterruptedException e) { - log.error("Exception: ", e); + log.error("Nextcloud Exception: ", e); } return false;