Skip to content

Commit

Permalink
Implement Feature Request #2803: Support Permanent Delete on OneDrive
Browse files Browse the repository at this point in the history
* Initial work on developing feature request
  • Loading branch information
abraunegg committed Nov 23, 2024
1 parent e7dbf8e commit cb3c2b6
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 8 deletions.
3 changes: 3 additions & 0 deletions src/config.d
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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"));
Expand Down
18 changes: 18 additions & 0 deletions src/onedrive.d
Original file line number Diff line number Diff line change
Expand Up @@ -639,6 +639,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";
Expand Down Expand Up @@ -971,6 +981,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
Expand Down
81 changes: 73 additions & 8 deletions src/sync.d
Original file line number Diff line number Diff line change
Expand Up @@ -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 bind
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
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -405,6 +409,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)
// - China operated by 21Vianet

Check failure

Code scanning / check-spelling

Unrecognized Spelling Error

Vianet is not a recognized word. (unrecognized-spelling)
//
// 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;
Expand Down Expand Up @@ -6596,8 +6640,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();
Expand Down Expand Up @@ -6664,16 +6713,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();
Expand Down Expand Up @@ -7662,8 +7722,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) {
Expand Down

0 comments on commit cb3c2b6

Please sign in to comment.