Implement Feature Request: Support Permanent Delete on OneDrive (#2999)

* Implement Feature Request: Support Permanent Delete on OneDrive (#2803)
This commit is contained in:
abraunegg 2024-11-26 19:30:41 +11:00 committed by GitHub
commit 228e7db188
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 127 additions and 8 deletions

View file

@ -36,6 +36,7 @@ Before reading this document, please ensure you are running application version
- [no_remote_delete](#no_remote_delete)
- [notify_file_actions](#notify_file_actions)
- [operation_timeout](#operation_timeout)
- [permanent_delete](#permanent_delete)
- [rate_limit](#rate_limit)
- [read_only_auth_scope](#read_only_auth_scope)
- [remove_source_files](#remove_source_files)
@ -505,6 +506,38 @@ _**Default Value:**_ 3600
_**Config Example:**_ `operation_timeout = "3600"`
### permanent_delete
_**Description:**_ Permanently delete an item online when it is removed locally. When using this method, they're permanently removed and aren't sent to the Microsoft OneDrive Recycle Bin. Therefore, permanently deleted drive items can't be restored afterward. Online data loss MAY occur in this scenario.
_**Value Type:**_ Boolean
_**Default Value:**_ False
_**Config Example:**_ `permanent_delete = "true"`
> [!IMPORTANT]
> The Microsoft OneDrive API for this capability is also very narrow:
> | Account Type | Config Option is Supported |
> |--------------|------------------|
> | Personal | ❌ |
> | Business | ✔ |
> | SharePoint | ✔ |
> | Microsoft Cloud Germany | ✔ |
> | Microsoft Cloud for US Government | ❌ |
> | Azure and Office365 operated by VNET in China | ❌ |
>
> When using this config option against an unsupported Personal Accounts the following message will be generated:
> ```
> WARNING: The application is configured to permanently delete files online; however, this action is not supported by Microsoft OneDrive Personal Accounts.
> ```
>
> When using this config option against a supported account the following message will be generated:
> ```
> WARNING: Application has been configured to permanently remove files online rather than send to the recycle bin. Permanently deleted items can't be restored.
> WARNING: Online data loss MAY occur in this scenario.
> ```
>
### rate_limit
_**Description:**_ This configuration option controls the bandwidth used by the application, per thread, when interacting with Microsoft OneDrive.

View file

@ -332,6 +332,8 @@ class ApplicationConfig {
boolValues["read_only_auth_scope"] = false;
// - Flag to cleanup local files when using --download-only
boolValues["cleanup_local_files"] = false;
// - Perform a permanentDelete on deletion activities
boolValues["permanent_delete"] = false;
// Webhook Feature Options
boolValues["webhook_enabled"] = false;
@ -1411,6 +1413,7 @@ class ApplicationConfig {
addLogEntry("Config option 'sync_dir_permissions' = " ~ to!string(getValueLong("sync_dir_permissions")));
addLogEntry("Config option 'sync_file_permissions' = " ~ to!string(getValueLong("sync_file_permissions")));
addLogEntry("Config option 'space_reservation' = " ~ to!string(getValueLong("space_reservation")));
addLogEntry("Config option 'permanent_delete' = " ~ to!string(getValueBool("permanent_delete")));
// curl operations
addLogEntry("Config option 'application_id' = " ~ getValueString("application_id"));

View file

@ -630,6 +630,16 @@ class OneDriveApi {
performDelete(url);
}
// https://learn.microsoft.com/en-us/graph/api/driveitem-permanentdelete?view=graph-rest-1.0
void permanentDeleteById(const(char)[] driveId, const(char)[] id, const(char)[] eTag = null) {
// string[string] requestHeaders;
const(char)[] url = driveByIdUrl ~ driveId ~ "/items/" ~ id ~ "/permanentDelete";
//TODO: investigate why this always fail with 412 (Precondition Failed)
// if (eTag) requestHeaders["If-Match"] = eTag;
// as per documentation, a permanentDelete needs to be a HTTP POST
performPermanentDelete(url);
}
// https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_post_children
JSONValue createById(string parentDriveId, string parentId, JSONValue item) {
string url = driveByIdUrl ~ parentDriveId ~ "/items/" ~ parentId ~ "/children";
@ -962,6 +972,14 @@ class OneDriveApi {
}, validateJSONResponse, callingFunction, lineno);
}
private void performPermanentDelete(const(char)[] url, string[string] requestHeaders=null, string callingFunction=__FUNCTION__, int lineno=__LINE__) {
bool validateJSONResponse = false;
oneDriveErrorHandlerWrapper((CurlResponse response) {
connect(HTTP.Method.post, url, false, response, requestHeaders);
return curlEngine.execute();
}, validateJSONResponse, callingFunction, lineno);
}
private void downloadFile(const(char)[] url, string filename, long fileSize, string callingFunction=__FUNCTION__, int lineno=__LINE__) {
// Threshold for displaying download bar
long thresholdFileSize = 4 * 2^^20; // 4 MiB

View file

@ -170,6 +170,8 @@ class SyncEngine {
// Is bypass_data_preservation set via config file
// Local data loss MAY occur in this scenario
bool bypassDataPreservation = false;
// Has the user configured to permanently delete files online rather than send to online recycle bin
bool permanentDelete = false;
// Maximum file size upload
// https://support.microsoft.com/en-us/office/invalid-file-names-and-file-types-in-onedrive-and-sharepoint-64883a5d-228e-48f5-b3d2-eb39e07630fa?ui=en-us&rs=en-us&ad=us
// July 2020, maximum file size for all accounts is 100GB
@ -307,8 +309,10 @@ class SyncEngine {
// Are we forcing the client to bypass any data preservation techniques to NOT rename any local files if there is a conflict?
// The enabling of this function could lead to data loss
if (appConfig.getValueBool("bypass_data_preservation")) {
addLogEntry();
addLogEntry("WARNING: Application has been configured to bypass local data preservation in the event of file conflict.");
addLogEntry("WARNING: Local data loss MAY occur in this scenario.");
addLogEntry();
this.bypassDataPreservation = true;
}
@ -408,6 +412,46 @@ class SyncEngine {
forceExit();
}
// Has the client been configured to permanently delete files online rather than send these to the online recycle bin?
if (appConfig.getValueBool("permanent_delete")) {
// This can only be set if not using:
// - US Government L4
// - US Government L5 (DOD)
// - Azure and Office365 operated by VNET in China
//
// Additionally, this is not supported by OneDrive Personal accounts:
//
// This is a doc bug. In fact, OneDrive personal accounts do not support the permanentDelete API, it only applies to OneDrive for Business and SharePoint document libraries.
//
// Reference: https://learn.microsoft.com/en-us/answers/questions/1501170/onedrive-permanently-delete-a-file
string azureConfigValue = appConfig.getValueString("azure_ad_endpoint");
// Now that we know the 'accountType' we can configure this correctly
if ((appConfig.accountType != "personal") && (azureConfigValue.empty || azureConfigValue == "DE")) {
// Only supported for Global Service and DE based on https://learn.microsoft.com/en-us/graph/api/driveitem-permanentdelete?view=graph-rest-1.0
addLogEntry();
addLogEntry("WARNING: Application has been configured to permanently remove files online rather than send to the recycle bin. Permanently deleted items can't be restored.");
addLogEntry("WARNING: Online data loss MAY occur in this scenario.");
addLogEntry();
this.permanentDelete = true;
} else {
// what error message do we present
if (appConfig.accountType == "personal") {
// personal account type - API not supported
addLogEntry();
addLogEntry("WARNING: The application is configured to permanently delete files online; however, this action is not supported by Microsoft OneDrive Personal Accounts.");
addLogEntry();
} else {
// Not a personal account
addLogEntry();
addLogEntry("WARNING: The application is configured to permanently delete files online; however, this action is not supported by the National Cloud Deployment in use.");
addLogEntry();
}
// ensure this is false regardless
this.permanentDelete = false;
}
}
// API was initialised
if (verboseLogging) {addLogEntry("Sync Engine Initialised with new Onedrive API instance", ["verbose"]);}
return true;
@ -6668,8 +6712,13 @@ class SyncEngine {
uploadDeletedItemOneDriveApiInstance = new OneDriveApi(appConfig);
uploadDeletedItemOneDriveApiInstance.initialise();
// Perform the delete via the default OneDrive API instance
uploadDeletedItemOneDriveApiInstance.deleteById(actualItemToDelete.driveId, actualItemToDelete.id);
if (!permanentDelete) {
// Perform the delete via the default OneDrive API instance
uploadDeletedItemOneDriveApiInstance.deleteById(actualItemToDelete.driveId, actualItemToDelete.id);
} else {
// Perform the permanent delete via the default OneDrive API instance
uploadDeletedItemOneDriveApiInstance.permanentDeleteById(actualItemToDelete.driveId, actualItemToDelete.id);
}
// OneDrive API Instance Cleanup - Shutdown API, free curl object and memory
uploadDeletedItemOneDriveApiInstance.releaseCurlEngine();
@ -6736,16 +6785,27 @@ class SyncEngine {
// Log the action
if (debugLogging) {addLogEntry("Attempting to delete this child item id: " ~ child.id ~ " from drive: " ~ child.driveId, ["debug"]);}
// perform the delete via the default OneDrive API instance
performReverseDeletionOneDriveApiInstance.deleteById(child.driveId, child.id, child.eTag);
if (!permanentDelete) {
// Perform the delete via the default OneDrive API instance
performReverseDeletionOneDriveApiInstance.deleteById(child.driveId, child.id, child.eTag);
} else {
// Perform the permanent delete via the default OneDrive API instance
performReverseDeletionOneDriveApiInstance.permanentDeleteById(child.driveId, child.id, child.eTag);
}
// delete the child reference in the local database
itemDB.deleteById(child.driveId, child.id);
}
// Log the action
if (debugLogging) {addLogEntry("Attempting to delete this parent item id: " ~ itemToDelete.id ~ " from drive: " ~ itemToDelete.driveId, ["debug"]);}
// Perform the delete via the default OneDrive API instance
performReverseDeletionOneDriveApiInstance.deleteById(itemToDelete.driveId, itemToDelete.id, itemToDelete.eTag);
if (!permanentDelete) {
// Perform the delete via the default OneDrive API instance
performReverseDeletionOneDriveApiInstance.deleteById(itemToDelete.driveId, itemToDelete.id, itemToDelete.eTag);
} else {
// Perform the permanent delete via the default OneDrive API instance
performReverseDeletionOneDriveApiInstance.permanentDeleteById(itemToDelete.driveId, itemToDelete.id, itemToDelete.eTag);
}
// OneDrive API Instance Cleanup - Shutdown API, free curl object and memory
performReverseDeletionOneDriveApiInstance.releaseCurlEngine();
@ -7734,8 +7794,13 @@ class SyncEngine {
// Try the online deletion
try {
// Perform the delete via the default OneDrive API instance
deleteByPathNoSyncAPIInstance.deleteById(deletionItem.driveId, deletionItem.id);
if (!permanentDelete) {
// Perform the delete via the default OneDrive API instance
deleteByPathNoSyncAPIInstance.deleteById(deletionItem.driveId, deletionItem.id);
} else {
// Perform the permanent delete via the default OneDrive API instance
deleteByPathNoSyncAPIInstance.permanentDeleteById(deletionItem.driveId, deletionItem.id);
}
// If we get here without error, directory was deleted
addLogEntry("The requested directory to delete online has been deleted");
} catch (OneDriveException exception) {