From 488f756ecdb80efe267579cafbe4ca65cb0524b9 Mon Sep 17 00:00:00 2001 From: abraunegg Date: Sat, 24 Aug 2019 15:54:48 +1000 Subject: [PATCH] Fix unhandled error exceptions that cause application to exit / crash when uploading files (Issue #624) (#625) * Remove 'else throw e' which causes the application to exit / crash and replace with what the actual error message is from OneDrive * Make logging of unhandled errors consistent * Add extra error handling when uploading files for errors received from OneDrive or file system errors * Flag that the upload failed if there is an exception error raised * Update logging output for when an error occurs to indicate that the file was skipped * Log the OneDrive response as log.vlog rather than log.error as it is now being handled correctly * Throw an exception for 50x errors so that it gets caught correctly * Clean up logging when exception is generated --- src/onedrive.d | 8 +- src/sync.d | 501 +++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 406 insertions(+), 103 deletions(-) diff --git a/src/onedrive.d b/src/onedrive.d index d03c3a7b..063d156e 100644 --- a/src/onedrive.d +++ b/src/onedrive.d @@ -802,22 +802,22 @@ final class OneDriveApi case 500: // No actions log.vlog("OneDrive returned a 'HTTP 500 Internal Server Error' - gracefully handling error"); - break; + throw new OneDriveException(http.statusLine.code, http.statusLine.reason, response); case 502: // No actions log.vlog("OneDrive returned a 'HTTP 502 Bad Gateway Error' - gracefully handling error"); - break; + throw new OneDriveException(http.statusLine.code, http.statusLine.reason, response); case 503: // No actions log.vlog("OneDrive returned a 'HTTP 503 Service Unavailable Error' - gracefully handling error"); - break; + throw new OneDriveException(http.statusLine.code, http.statusLine.reason, response); case 504: // No actions log.vlog("OneDrive returned a 'HTTP 504 Gateway Timeout Error' - gracefully handling error"); - break; + throw new OneDriveException(http.statusLine.code, http.statusLine.reason, response); // Default - all other errors that are not a 2xx or a 302 default: diff --git a/src/sync.d b/src/sync.d index 459b7a8e..da61e17d 100644 --- a/src/sync.d +++ b/src/sync.d @@ -221,6 +221,8 @@ final class SyncEngine private bool malwareDetected = false; // download filesystem issue flag private bool downloadFailed = false; + // upload failure - OneDrive or filesystem issue (reading data) + private bool uploadFailed = false; // initialization has been done private bool initDone = false; // sync engine dryRun flag @@ -397,7 +399,6 @@ final class SyncEngine log.vdebug("documentLibrary account type - flagging to disable upload validation checks due to Microsoft SharePoint file modification enrichments"); } - // download all new changes from OneDrive void applyDifferences() { @@ -674,16 +675,10 @@ final class SyncEngine // Retry by calling applyDifferences() again log.vlog("OneDrive returned a 'HTTP 504 - Gateway Timeout' - gracefully handling error"); applyDifferences(driveId, idToQuery); - } - - else { + } else { // Default operation if not 404, 410, 500, 504 errors - log.log("\n\nOneDrive returned an error with the following message:\n"); - auto errorArray = splitLines(e.msg); - log.log("Error Message: ", errorArray[0]); - // extract 'message' as the reason - JSONValue errorMessage = parseJSON(replace(e.msg, errorArray[0], "")); - log.log("Error Reason: ", errorMessage["error"]["message"].str); + // display what the error is + displayOneDriveErrorMessage(e.msg); log.log("\nRemove your '", cfg.databaseFilePath, "' file and try to sync again\n"); return; } @@ -1226,7 +1221,8 @@ final class SyncEngine } } catch (std.exception.ErrnoException e) { // There was a file system error - log.error("ERROR: ", e.msg); + // display the error message + displayFileSystemErrorMessage(e.msg); downloadFailed = true; return; } @@ -1353,7 +1349,8 @@ final class SyncEngine // Remove the path now that it is empty of children rmdirRecurse(path); } catch (FileException e) { - log.log(e.msg); + // display the error message + displayFileSystemErrorMessage(e.msg); } } } @@ -1534,8 +1531,12 @@ final class SyncEngine } } + // upload local file system differences to OneDrive private void uploadFileDifferences(Item item, string path) { + // Reset upload failure - OneDrive or filesystem issue (reading data) + uploadFailed = false; + assert(item.type == ItemType.file); if (exists(path)) { if (isFile(path)) { @@ -1562,58 +1563,101 @@ final class SyncEngine try { response = onedrive.simpleUploadReplace(path, item.driveId, item.id, item.eTag); } catch (OneDriveException e) { + if (e.httpStatusCode == 401) { + // OneDrive returned a 'HTTP/1.1 401 Unauthorized Error' - file failed to be uploaded + writeln("skipped."); + log.vlog("OneDrive returned a 'HTTP 401 - Unauthorized' - gracefully handling error"); + uploadFailed = true; + return; + } if (e.httpStatusCode == 404) { // HTTP request returned status code 404 - the eTag provided does not exist // Delete record from the local database - file will be uploaded as a new file + writeln("skipped."); log.vlog("OneDrive returned a 'HTTP 404 - eTag Issue' - gracefully handling error"); itemdb.deleteById(item.driveId, item.id); + uploadFailed = true; return; } - // Resolve https://github.com/abraunegg/onedrive/issues/36 if ((e.httpStatusCode == 409) || (e.httpStatusCode == 423)) { // The file is currently checked out or locked for editing by another user // We cant upload this file at this time - writeln(" skipped."); + writeln("skipped."); log.fileOnly("Uploading modified file ", path, " ... skipped."); write("", path, " is currently checked out or locked for editing by another user."); log.fileOnly(path, " is currently checked out or locked for editing by another user."); + uploadFailed = true; return; } - if (e.httpStatusCode == 412) { // HTTP request returned status code 412 - ETag does not match current item's value // Delete record from the local database - file will be uploaded as a new file + writeln("skipped."); log.vdebug("Simple Upload Replace Failed - OneDrive eTag / cTag match issue"); log.vlog("OneDrive returned a 'HTTP 412 - Precondition Failed' - gracefully handling error. Will upload as new file."); itemdb.deleteById(item.driveId, item.id); + uploadFailed = true; return; } - if (e.httpStatusCode == 504) { // HTTP request returned status code 504 (Gateway Timeout) // Try upload as a session response = session.upload(path, item.driveId, item.parentId, baseName(path), item.eTag); + } else { + // display what the error is + writeln("skipped."); + displayOneDriveErrorMessage(e.msg); + uploadFailed = true; + return; } - else throw e; + } catch (FileException e) { + // display the error message + writeln("skipped."); + displayFileSystemErrorMessage(e.msg); + uploadFailed = true; + return; } + // upload done without error writeln("done."); } else { writeln(""); try { response = session.upload(path, item.driveId, item.parentId, baseName(path), item.eTag); - } catch (OneDriveException e) { + } catch (OneDriveException e) { + if (e.httpStatusCode == 401) { + // OneDrive returned a 'HTTP/1.1 401 Unauthorized Error' - file failed to be uploaded + writeln("skipped."); + log.vlog("OneDrive returned a 'HTTP 401 - Unauthorized' - gracefully handling error"); + uploadFailed = true; + return; + } if (e.httpStatusCode == 412) { // HTTP request returned status code 412 - ETag does not match current item's value // Delete record from the local database - file will be uploaded as a new file + writeln("skipped."); log.vdebug("Simple Upload Replace Failed - OneDrive eTag / cTag match issue"); log.vlog("OneDrive returned a 'HTTP 412 - Precondition Failed' - gracefully handling error. Will upload as new file."); itemdb.deleteById(item.driveId, item.id); + uploadFailed = true; + return; + } else { + // display what the error is + writeln("skipped."); + displayOneDriveErrorMessage(e.msg); + uploadFailed = true; return; } + } catch (FileException e) { + // display the error message + writeln("skipped."); + displayFileSystemErrorMessage(e.msg); + uploadFailed = true; + return; } + // upload done without error writeln("done."); - } + } } else { // OneDrive Business Account // We need to always use a session to upload, but handle the changed file correctly @@ -1623,19 +1667,39 @@ final class SyncEngine try { response = session.upload(path, item.driveId, item.parentId, baseName(path), item.eTag); } catch (OneDriveException e) { + if (e.httpStatusCode == 401) { + // OneDrive returned a 'HTTP/1.1 401 Unauthorized Error' - file failed to be uploaded + writeln("skipped."); + log.vlog("OneDrive returned a 'HTTP 401 - Unauthorized' - gracefully handling error"); + uploadFailed = true; + return; + } // Resolve https://github.com/abraunegg/onedrive/issues/36 if ((e.httpStatusCode == 409) || (e.httpStatusCode == 423)) { // The file is currently checked out or locked for editing by another user // We cant upload this file at this time - writeln(" skipped."); + writeln("skipped."); log.fileOnly("Uploading modified file ", path, " ... skipped."); writeln("", path, " is currently checked out or locked for editing by another user."); log.fileOnly(path, " is currently checked out or locked for editing by another user."); + uploadFailed = true; + return; + } else { + // display what the error is + writeln("skipped."); + displayOneDriveErrorMessage(e.msg); + uploadFailed = true; return; } - // what is this error????? - else throw e; + } catch (FileException e) { + // display the error message + writeln("skipped."); + displayFileSystemErrorMessage(e.msg); + uploadFailed = true; + return; } + // upload done without error + writeln("done."); // As the session.upload includes the last modified time, save the response saveItem(response); } @@ -1649,22 +1713,41 @@ final class SyncEngine try { response = session.upload(path, item.driveId, item.parentId, baseName(path), item.eTag); } catch (OneDriveException e) { + if (e.httpStatusCode == 401) { + // OneDrive returned a 'HTTP/1.1 401 Unauthorized Error' - file failed to be uploaded + writeln("skipped."); + log.vlog("OneDrive returned a 'HTTP 401 - Unauthorized' - gracefully handling error"); + uploadFailed = true; + return; + } // Resolve https://github.com/abraunegg/onedrive/issues/36 if ((e.httpStatusCode == 409) || (e.httpStatusCode == 423)) { // The file is currently checked out or locked for editing by another user // We cant upload this file at this time - writeln(" skipped."); + writeln("skipped."); log.fileOnly("Uploading modified file ", path, " ... skipped."); writeln("", path, " is currently checked out or locked for editing by another user."); log.fileOnly(path, " is currently checked out or locked for editing by another user."); + uploadFailed = true; + return; + } else { + // display what the error is + writeln("skipped."); + displayOneDriveErrorMessage(e.msg); + uploadFailed = true; return; } - // what is this error????? - else throw e; + } catch (FileException e) { + // display the error message + writeln("skipped."); + displayFileSystemErrorMessage(e.msg); + uploadFailed = true; + return; } + // upload done without error + writeln("done."); // As the session.upload includes the last modified time, save the response saveItem(response); - } else { // Due to https://github.com/OneDrive/onedrive-api-docs/issues/935 Microsoft modifies all PDF, MS Office & HTML files with added XML content. It is a 'feature' of SharePoint. // This means, as a session upload, on 'completion' the file is 'moved' and generates a 404 ...... @@ -1677,9 +1760,6 @@ final class SyncEngine return; } } - - // log line completion - writeln("done."); } log.fileOnly("Uploading modified file ", path, " ... done."); if ("cTag" in response) { @@ -1761,6 +1841,7 @@ final class SyncEngine } } + // upload new items to OneDrive private void uploadNewItems(string path) { // https://support.microsoft.com/en-us/help/3125202/restrictions-and-limitations-when-you-sync-files-and-folders @@ -1895,8 +1976,9 @@ final class SyncEngine foreach (DirEntry entry; entries) { uploadNewItems(entry.name); } - } catch (std.file.FileException e) { - log.error("ERROR: ", e.msg); + } catch (FileException e) { + // display the error message + displayFileSystemErrorMessage(e.msg); return; } } else { @@ -1910,9 +1992,13 @@ final class SyncEngine } Item item; if (!itemdb.selectByPath(path, defaultDriveId, item)) { + // item is not in the database, upload new file uploadNewFile(path); - remainingFreeSpace = (remainingFreeSpace - fileSize); - log.vlog("Remaining free space: ", remainingFreeSpace); + if (!uploadFailed) { + // upload did not fail + remainingFreeSpace = (remainingFreeSpace - fileSize); + log.vlog("Remaining free space: ", remainingFreeSpace); + } } } else { // Not enough free space @@ -1925,6 +2011,7 @@ final class SyncEngine } } + // create new directory on OneDrive private void uploadCreateDir(const(string) path) { log.vlog("OneDrive Client requested to create remote path: ", path); @@ -2058,8 +2145,12 @@ final class SyncEngine } } + // upload a new file to OneDrive private void uploadNewFile(string path) { + // Reset upload failure - OneDrive or filesystem issue (reading data) + uploadFailed = false; + Item parent; // Check the database for the parent //enforce(itemdb.selectByPath(dirName(path), defaultDriveId, parent), "The parent item is not in the local database"); @@ -2089,8 +2180,9 @@ final class SyncEngine fileDetailsFromOneDrive = onedrive.getPathDetails(path); } catch (OneDriveException e) { if (e.httpStatusCode == 401) { - // OneDrive returned a 'HTTP/1.1 401 Unauthorized Error' - no error message logged - log.error("ERROR: OneDrive returned a 'HTTP 401 - Unauthorized' - gracefully handling error"); + // OneDrive returned a 'HTTP/1.1 401 Unauthorized Error' + writeln("Skipping item - OneDrive returned a 'HTTP 401 - Unauthorized' when attempting to query if file exists"); + log.vlog("OneDrive returned a 'HTTP 401 - Unauthorized' - gracefully handling error"); return; } @@ -2101,7 +2193,7 @@ final class SyncEngine writeln("Skipping item - excluded by skip_size config: ", path, " (", thisFileSize/2^^20," MB)"); return; } - write("Uploading new file ", path, " ..."); + write("Uploading new file ", path, " ... "); JSONValue response; if (!dryRun) { @@ -2113,6 +2205,16 @@ final class SyncEngine response = onedrive.simpleUpload(path, parent.driveId, parent.id, baseName(path)); } catch (OneDriveException e) { // error uploading file + // display what the error is + writeln("skipped."); + displayOneDriveErrorMessage(e.msg); + uploadFailed = true; + return; + } catch (FileException e) { + // display the error message + writeln("skipped."); + displayFileSystemErrorMessage(e.msg); + uploadFailed = true; return; } } else { @@ -2126,6 +2228,13 @@ final class SyncEngine try { response = onedrive.simpleUpload(path, parent.driveId, parent.id, baseName(path)); } catch (OneDriveException e) { + if (e.httpStatusCode == 401) { + // OneDrive returned a 'HTTP/1.1 401 Unauthorized Error' - file failed to be uploaded + writeln("skipped."); + log.vlog("OneDrive returned a 'HTTP 401 - Unauthorized' - gracefully handling error"); + uploadFailed = true; + return; + } if (e.httpStatusCode == 504) { // HTTP request returned status code 504 (Gateway Timeout) // Try upload as a session @@ -2133,10 +2242,25 @@ final class SyncEngine response = session.upload(path, parent.driveId, parent.id, baseName(path)); } catch (OneDriveException e) { // error uploading file + // display what the error is + writeln("skipped."); + displayOneDriveErrorMessage(e.msg); + uploadFailed = true; return; } + } else { + // display what the error is + writeln("skipped."); + displayOneDriveErrorMessage(e.msg); + uploadFailed = true; + return; } - else throw e; + } catch (FileException e) { + // display the error message + writeln("skipped."); + displayFileSystemErrorMessage(e.msg); + uploadFailed = true; + return; } } else { // File larger than threshold - use a session to upload @@ -2144,11 +2268,24 @@ final class SyncEngine try { response = session.upload(path, parent.driveId, parent.id, baseName(path)); } catch (OneDriveException e) { - // error uploading file - log.vlog("Upload failed with OneDriveException: ", e.msg); - return; + if (e.httpStatusCode == 401) { + // OneDrive returned a 'HTTP/1.1 401 Unauthorized Error' - file failed to be uploaded + writeln("skipped."); + log.vlog("OneDrive returned a 'HTTP 401 - Unauthorized' - gracefully handling error"); + uploadFailed = true; + return; + } else { + // display what the error is + writeln("skipped."); + displayOneDriveErrorMessage(e.msg); + uploadFailed = true; + return; + } } catch (FileException e) { - log.vlog("Upload failed with File Exception: ", e.msg); + // display the error message + writeln("skipped."); + displayFileSystemErrorMessage(e.msg); + uploadFailed = true; return; } } @@ -2158,11 +2295,24 @@ final class SyncEngine try { response = session.upload(path, parent.driveId, parent.id, baseName(path)); } catch (OneDriveException e) { - // error uploading file - log.vlog("Upload failed with OneDriveException: ", e.msg); - return; + if (e.httpStatusCode == 401) { + // OneDrive returned a 'HTTP/1.1 401 Unauthorized Error' - file failed to be uploaded + writeln("skipped."); + log.vlog("OneDrive returned a 'HTTP 401 - Unauthorized' - gracefully handling error"); + uploadFailed = true; + return; + } else { + // display what the error is + writeln("skipped."); + displayOneDriveErrorMessage(e.msg); + uploadFailed = true; + return; + } } catch (FileException e) { - log.vlog("Upload failed with File Exception: ", e.msg); + // display the error message + writeln("skipped."); + displayFileSystemErrorMessage(e.msg); + uploadFailed = true; return; } } @@ -2170,9 +2320,10 @@ final class SyncEngine // response from OneDrive has to be a valid JSON object if (response.type() == JSONType.object){ + // upload done without error + writeln("done."); // Log action to log file log.fileOnly("Uploading new file ", path, " ... done."); - writeln(" done."); // The file was uploaded, or a 4xx / 5xx error was generated if ("size" in response){ // The response JSON contains size, high likelihood valid response returned @@ -2241,7 +2392,7 @@ final class SyncEngine } } else { // we are --dry-run - simulate the file upload - writeln(" done."); + writeln("done."); response = createFakeResponse(path); // Log action to log file log.fileOnly("Uploading new file ", path, " ... done."); @@ -2277,47 +2428,140 @@ final class SyncEngine if (localFileModifiedTime > remoteFileModifiedTime){ // local file is newer log.vlog("Requested file to upload is newer than existing file on OneDrive"); - write("Uploading modified file ", path, " ..."); + write("Uploading modified file ", path, " ... "); JSONValue response; if (!dryRun) { if (accountType == "personal"){ // OneDrive Personal account upload handling - if (getSize(path) <= thresholdFileSize) { - response = onedrive.simpleUpload(path, parent.driveId, parent.id, baseName(path)); - writeln(" done."); + if (thisFileSize <= thresholdFileSize) { + try { + response = onedrive.simpleUpload(path, parent.driveId, parent.id, baseName(path)); + writeln("done."); + } catch (OneDriveException e) { + if (e.httpStatusCode == 401) { + // OneDrive returned a 'HTTP/1.1 401 Unauthorized Error' - file failed to be uploaded + writeln("skipped."); + log.vlog("OneDrive returned a 'HTTP 401 - Unauthorized' - gracefully handling error"); + uploadFailed = true; + return; + } + if (e.httpStatusCode == 504) { + // HTTP request returned status code 504 (Gateway Timeout) + // Try upload as a session + try { + response = session.upload(path, parent.driveId, parent.id, baseName(path)); + writeln("done."); + } catch (OneDriveException e) { + // error uploading file + // display what the error is + writeln("skipped."); + displayOneDriveErrorMessage(e.msg); + uploadFailed = true; + return; + } + } else { + // display what the error is + writeln("skipped."); + displayOneDriveErrorMessage(e.msg); + uploadFailed = true; + return; + } + } catch (FileException e) { + // display the error message + writeln("skipped."); + displayFileSystemErrorMessage(e.msg); + uploadFailed = true; + return; + } } else { + // File larger than threshold - use a session to upload writeln(""); - response = session.upload(path, parent.driveId, parent.id, baseName(path)); - writeln(" done."); - } - string id = response["id"].str; - string cTag; - - // Is there a valid cTag in the response? - if ("cTag" in response) { - // use the cTag instead of the eTag because Onedrive may update the metadata of files AFTER they have been uploaded - cTag = response["cTag"].str; - } else { - // Is there an eTag in the response? - if ("eTag" in response) { - // use the eTag from the response as there was no cTag - cTag = response["eTag"].str; - } else { - // no tag available - set to nothing - cTag = ""; + try { + response = session.upload(path, parent.driveId, parent.id, baseName(path)); + writeln("done."); + } catch (OneDriveException e) { + if (e.httpStatusCode == 401) { + // OneDrive returned a 'HTTP/1.1 401 Unauthorized Error' - file failed to be uploaded + writeln("skipped."); + log.vlog("OneDrive returned a 'HTTP 401 - Unauthorized' - gracefully handling error"); + uploadFailed = true; + return; + } else { + // display what the error is + writeln("skipped."); + displayOneDriveErrorMessage(e.msg); + uploadFailed = true; + return; + } + } catch (FileException e) { + // display the error message + writeln("skipped."); + displayFileSystemErrorMessage(e.msg); + uploadFailed = true; + return; } } - SysTime mtime = timeLastModified(path).toUTC(); - uploadLastModifiedTime(parent.driveId, id, cTag, mtime); + // response from OneDrive has to be a valid JSON object + if (response.type() == JSONType.object){ + // response is a valid JSON object + string id = response["id"].str; + string cTag; + + // Is there a valid cTag in the response? + if ("cTag" in response) { + // use the cTag instead of the eTag because Onedrive may update the metadata of files AFTER they have been uploaded + cTag = response["cTag"].str; + } else { + // Is there an eTag in the response? + if ("eTag" in response) { + // use the eTag from the response as there was no cTag + cTag = response["eTag"].str; + } else { + // no tag available - set to nothing + cTag = ""; + } + } + + if (exists(path)) { + SysTime mtime = timeLastModified(path).toUTC(); + uploadLastModifiedTime(parent.driveId, id, cTag, mtime); + } else { + // will be removed in different event! + log.log("File disappeared after upload: ", path); + } + } } else { // OneDrive Business account modified file upload handling if (accountType == "business"){ + // OneDrive Business Account - always use a session to upload writeln(""); - // session upload - response = session.upload(path, parent.driveId, parent.id, baseName(path), fileDetailsFromOneDrive["eTag"].str); - writeln(" done."); + try { + response = session.upload(path, parent.driveId, parent.id, baseName(path), fileDetailsFromOneDrive["eTag"].str); + } catch (OneDriveException e) { + if (e.httpStatusCode == 401) { + // OneDrive returned a 'HTTP/1.1 401 Unauthorized Error' - file failed to be uploaded + writeln("skipped."); + log.vlog("OneDrive returned a 'HTTP 401 - Unauthorized' - gracefully handling error"); + uploadFailed = true; + return; + } else { + // display what the error is + writeln("skipped."); + displayOneDriveErrorMessage(e.msg); + uploadFailed = true; + return; + } + } catch (FileException e) { + // display the error message + writeln("skipped."); + displayFileSystemErrorMessage(e.msg); + uploadFailed = true; + return; + } + // upload complete + writeln("done."); saveItem(response); } @@ -2329,17 +2573,61 @@ final class SyncEngine // We also cant use a session to upload the file, we have to use simpleUploadReplace if (getSize(path) <= thresholdFileSize) { - // Upload file via simpleUploadReplace as below threshhold size - response = onedrive.simpleUploadReplace(path, fileDetailsFromOneDrive["parentReference"]["driveId"].str, fileDetailsFromOneDrive["id"].str, fileDetailsFromOneDrive["eTag"].str); + // Upload file via simpleUploadReplace as below threshold size + try { + response = onedrive.simpleUploadReplace(path, fileDetailsFromOneDrive["parentReference"]["driveId"].str, fileDetailsFromOneDrive["id"].str, fileDetailsFromOneDrive["eTag"].str); + } catch (OneDriveException e) { + if (e.httpStatusCode == 401) { + // OneDrive returned a 'HTTP/1.1 401 Unauthorized Error' - file failed to be uploaded + writeln("skipped."); + log.vlog("OneDrive returned a 'HTTP 401 - Unauthorized' - gracefully handling error"); + uploadFailed = true; + return; + } else { + // display what the error is + writeln("skipped."); + displayOneDriveErrorMessage(e.msg); + uploadFailed = true; + return; + } + } catch (FileException e) { + // display the error message + writeln("skipped."); + displayFileSystemErrorMessage(e.msg); + uploadFailed = true; + return; + } } else { // Have to upload via a session, however we have to delete the file first otherwise this will generate a 404 error post session upload // Remove the existing file onedrive.deleteById(fileDetailsFromOneDrive["parentReference"]["driveId"].str, fileDetailsFromOneDrive["id"].str, fileDetailsFromOneDrive["eTag"].str); // Upload as a session, as a new file writeln(""); - response = session.upload(path, parent.driveId, parent.id, baseName(path)); + try { + response = session.upload(path, parent.driveId, parent.id, baseName(path)); + } catch (OneDriveException e) { + if (e.httpStatusCode == 401) { + // OneDrive returned a 'HTTP/1.1 401 Unauthorized Error' - file failed to be uploaded + writeln("skipped."); + log.vlog("OneDrive returned a 'HTTP 401 - Unauthorized' - gracefully handling error"); + uploadFailed = true; + return; + } else { + // display what the error is + writeln("skipped."); + displayOneDriveErrorMessage(e.msg); + uploadFailed = true; + return; + } + } catch (FileException e) { + // display the error message + writeln("skipped."); + displayFileSystemErrorMessage(e.msg); + uploadFailed = true; + return; + } } - writeln(" done."); + writeln("done."); saveItem(response); // Due to https://github.com/OneDrive/onedrive-api-docs/issues/935 Microsoft modifies all PDF, MS Office & HTML files with added XML content. It is a 'feature' of SharePoint. // So - now the 'local' and 'remote' file is technically DIFFERENT ... thanks Microsoft .. NO way to disable this stupidity @@ -2358,7 +2646,7 @@ final class SyncEngine } } else { // we are --dry-run - simulate the file upload - writeln(" done."); + writeln("done."); response = createFakeResponse(path); // Log action to log file log.fileOnly("Uploading modified file ", path, " ... done."); @@ -2368,7 +2656,6 @@ final class SyncEngine // Log action to log file log.fileOnly("Uploading modified file ", path, " ... done."); - } else { // Save the details of the file that we got from OneDrive // --dry-run safe @@ -2397,6 +2684,7 @@ final class SyncEngine } } + // delete an item on OneDrive private void uploadDeleteItem(Item item, string path) { log.log("Deleting item from OneDrive: ", path); @@ -2436,12 +2724,8 @@ final class SyncEngine } } else { // Not a 403 response & OneDrive Business Account / O365 Shared Folder / Library - log.log("\n\nOneDrive returned an error with the following message:\n"); - auto errorArray = splitLines(e.msg); - log.log("Error Message: ", errorArray[0]); - // extract 'message' as the reason - JSONValue errorMessage = parseJSON(replace(e.msg, errorArray[0], "")); - log.log("Error Reason: ", errorMessage["error"]["message"].str); + // display what the error is + displayOneDriveErrorMessage(e.msg); return; } } @@ -2456,6 +2740,7 @@ final class SyncEngine } } + // update the item's last modified time private void uploadLastModifiedTime(const(char)[] driveId, const(char)[] id, const(char)[] eTag, SysTime mtime) { JSONValue data = [ @@ -2481,6 +2766,7 @@ final class SyncEngine saveItem(response); } + // save item details into database private void saveItem(JSONValue jsonItem) { // jsonItem has to be a valid object @@ -2504,6 +2790,23 @@ final class SyncEngine } } + // Parse and display error message received from OneDrive + private void displayOneDriveErrorMessage(string message) { + log.error("ERROR: OneDrive returned an error with the following message:"); + auto errorArray = splitLines(message); + log.error(" Error Message: ", errorArray[0]); + // extract 'message' as the reason + JSONValue errorMessage = parseJSON(replace(message, errorArray[0], "")); + log.error(" Error Reason: ", errorMessage["error"]["message"].str); + } + + // Parse and display error message received from the local file system + private void displayFileSystemErrorMessage(string message) { + log.error("ERROR: The local file system returned an error with the following message:"); + auto errorArray = splitLines(message); + log.error(" Error Message: ", errorArray[0]); + } + // https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_move // This function is only called in monitor mode when an move event is coming from // inotify and we try to move the item. @@ -2551,6 +2854,7 @@ final class SyncEngine } } + // delete an item by it's path void deleteByPath(string path) { Item item; @@ -2564,8 +2868,12 @@ final class SyncEngine try { uploadDeleteItem(item, path); } catch (OneDriveException e) { - if (e.httpStatusCode == 404) log.log(e.msg); - else throw e; + if (e.httpStatusCode == 404) { + log.log(e.msg); + } else { + // display what the error is + displayOneDriveErrorMessage(e.msg); + } } } @@ -2622,11 +2930,7 @@ final class SyncEngine return; } else { // display what the error is - auto errorArray = splitLines(e.msg); - log.error("Error Message: ", errorArray[0]); - // extract 'message' as the reason - JSONValue errorMessage = parseJSON(replace(e.msg, errorArray[0], "")); - log.error("Error Reason: ", errorMessage["error"]["message"].str); + displayOneDriveErrorMessage(e.msg); return; } } @@ -2647,11 +2951,8 @@ final class SyncEngine siteDriveQuery = onedrive.o365SiteDrives(site_id); } catch (OneDriveException e) { log.error("ERROR: Query of OneDrive for Office Site ID failed"); - auto errorArray = splitLines(e.msg); - log.error("Error Message: ", errorArray[0]); - // extract 'message' as the reason - JSONValue errorMessage = parseJSON(replace(e.msg, errorArray[0], "")); - log.error("Error Reason: ", errorMessage["error"]["message"].str); + // display what the error is + displayOneDriveErrorMessage(e.msg); return; } @@ -2700,7 +3001,9 @@ final class SyncEngine try { fileDetails = onedrive.getFileDetails(item.driveId, item.id); } catch (OneDriveException e) { - log.error("ERROR: Query of OneDrive for file details failed"); + // display what the error is + displayOneDriveErrorMessage(e.msg); + return; } if ((fileDetails.type() == JSONType.object) && ("webUrl" in fileDetails)) {