From d8d35a15a948f6801c9df36adc17be30f4026b17 Mon Sep 17 00:00:00 2001 From: Hong Ooi Date: Sat, 17 Apr 2021 03:26:21 +1000 Subject: [PATCH] Recursive folder delete (#43) closes #21 --- DESCRIPTION | 2 +- NEWS.md | 6 ++++ R/Microsoft365R.R | 3 +- R/ms_drive.R | 6 ++-- R/ms_drive_item.R | 36 ++++++++++++++++++++++- man/ms_drive.Rd | 2 +- man/ms_drive_item.Rd | 2 +- tests/testthat/test01_onedrive.R | 6 ++-- tests/testthat/test02_business_onedrive.R | 8 ++--- 9 files changed, 57 insertions(+), 14 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index c096001..1e3a4a8 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -14,7 +14,7 @@ Depends: R (>= 3.3) Imports: AzureAuth, - AzureGraph (>= 1.2.1), + AzureGraph (>= 1.2.2), utils, curl, httr, diff --git a/NEWS.md b/NEWS.md index 047dca3..89aaa43 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,9 @@ +# Microsoft365R 2.1.0.9000 + +- Add `list_members()` and `get_member()` methods for teams and channels. +- Add support for @mentions in Teams channel messages (#26). +- Add a `by_item` argument to the `delete_item()` method for drives and the `delete()` method for drive items. This is to allow deletion of non-empty folders on SharePoint sites with data protection policies in place. Use with caution (#21). + # Microsoft365R 2.1.0 - Add support for sending and managing emails in Outlook. Use the `get_personal_outlook()` and `get_business_outlook()` client functions to access the emails in your personal account and work or school account, respectively. Functionality supported includes: diff --git a/R/Microsoft365R.R b/R/Microsoft365R.R index b310d1a..595e683 100644 --- a/R/Microsoft365R.R +++ b/R/Microsoft365R.R @@ -49,8 +49,9 @@ utils::globalVariables(c("self", "private")) # CLI for Microsoft 365 app ID .cli_microsoft365_app_id <- "31359c7f-bd7e-475c-86db-fdb8c937548e" -# helper function +# helper functions error_message <- get("error_message", getNamespace("AzureGraph")) +get_confirmation <- get("get_confirmation", getNamespace("AzureGraph")) # dummy mention to keep CRAN happy # we need to ensure that vctrs is loaded so that AzureGraph will use vec_rbind diff --git a/R/ms_drive.R b/R/ms_drive.R index 2d7f032..9a43f2e 100644 --- a/R/ms_drive.R +++ b/R/ms_drive.R @@ -20,7 +20,7 @@ #' - `create_folder(path)`: Create a folder. #' - `open_item(path)`: Open a file or folder. #' - `create_share_link(...)`: Create a shareable link for a file or folder. -#' - `delete_item(path, confirm)`: Delete a file or folder. +#' - `delete_item(path, confirm, by_item)`: Delete a file or folder. By default, ask for confirmation first. For personal OneDrive, deleting a folder will also automatically delete its contents; for business OneDrive or SharePoint document libraries, you may need to set `by_item=TRUE` to delete the contents first depending on your organisation's policies. Note that this can be slow for large folders. #' - `get_item(path)`: Get an item representing a file or folder. #' - `get_item_properties(path)`: Get the properties (metadata) for a file or folder. #' - `set_item_properties(path, ...)`: Set the properties for a file or folder. @@ -134,9 +134,9 @@ public=list( self$get_item(path)$open() }, - delete_item=function(path, confirm=TRUE) + delete_item=function(path, confirm=TRUE, by_item=FALSE) { - self$get_item(path)$delete(confirm=confirm) + self$get_item(path)$delete(confirm=confirm, by_item=by_item) }, get_item=function(path) diff --git a/R/ms_drive_item.R b/R/ms_drive_item.R index 97770f9..182eaee 100644 --- a/R/ms_drive_item.R +++ b/R/ms_drive_item.R @@ -10,7 +10,7 @@ #' - `properties`: The item properties (metadata). #' @section Methods: #' - `new(...)`: Initialize a new object. Do not call this directly; see 'Initialization' below. -#' - `delete(confirm=TRUE)`: Delete this item. By default, ask for confirmation first. For personal OneDrives, deleting a folder will also automatically delete its contents; for business OneDrives or SharePoint document libraries, you must delete the folder contents first before deleting the folder. +#' - `delete(confirm=TRUE, by_item=FALSE)`: Delete this item. By default, ask for confirmation first. For personal OneDrive, deleting a folder will also automatically delete its contents; for business OneDrive or SharePoint document libraries, you may need to set `by_item=TRUE` to delete the contents first depending on your organisation's policies. Note that this can be slow for large folders. #' - `update(...)`: Update the item's properties (metadata) in Microsoft Graph. #' - `do_operation(...)`: Carry out an arbitrary operation on the item. #' - `sync_fields()`: Synchronise the R object with the item metadata in Microsoft Graph. @@ -109,6 +109,40 @@ public=list( super$initialize(token, tenant, properties) }, + delete=function(confirm=TRUE, by_item=FALSE) + { + if(!by_item || !self$is_folder()) + return(super$delete(confirm=confirm)) + + if (confirm && interactive()) + { + msg <- sprintf("Do you really want to delete the %s '%s'?", self$type, self$properties$name) + if (!get_confirmation(msg, FALSE)) + return(invisible(NULL)) + } + + children <- self$list_items() + dirs <- children$isdir + for(d in children$name[dirs]) + self$get_item(d)$delete(confirm=FALSE, by_item=TRUE) + + deletes <- lapply(children$name[!dirs], function(f) + { + path <- private$make_absolute_path(f) + graph_request$new(path, http_verb="DELETE") + }) + # do in batches of 20 + i <- length(deletes) + while(i > 0) + { + batch <- seq(from=max(1, i - 19), to=i) + call_batch_endpoint(self$token, deletes[batch]) + i <- max(1, i - 19) - 1 + } + + super$delete(confirm=FALSE) + }, + is_folder=function() { !is.null(self$properties$folder) diff --git a/man/ms_drive.Rd b/man/ms_drive.Rd index 9c1dec1..104e000 100644 --- a/man/ms_drive.Rd +++ b/man/ms_drive.Rd @@ -34,7 +34,7 @@ Class representing a personal OneDrive or SharePoint document library. \item \code{create_folder(path)}: Create a folder. \item \code{open_item(path)}: Open a file or folder. \item \code{create_share_link(...)}: Create a shareable link for a file or folder. -\item \code{delete_item(path, confirm)}: Delete a file or folder. +\item \code{delete_item(path, confirm, by_item)}: Delete a file or folder. By default, ask for confirmation first. For personal OneDrive, deleting a folder will also automatically delete its contents; for business OneDrive or SharePoint document libraries, you may need to set \code{by_item=TRUE} to delete the contents first depending on your organisation's policies. Note that this can be slow for large folders. \item \code{get_item(path)}: Get an item representing a file or folder. \item \code{get_item_properties(path)}: Get the properties (metadata) for a file or folder. \item \code{set_item_properties(path, ...)}: Set the properties for a file or folder. diff --git a/man/ms_drive_item.Rd b/man/ms_drive_item.Rd index e863f42..aa397b4 100644 --- a/man/ms_drive_item.Rd +++ b/man/ms_drive_item.Rd @@ -24,7 +24,7 @@ Class representing an item (file or folder) in a OneDrive or SharePoint document \itemize{ \item \code{new(...)}: Initialize a new object. Do not call this directly; see 'Initialization' below. -\item \code{delete(confirm=TRUE)}: Delete this item. By default, ask for confirmation first. For personal OneDrives, deleting a folder will also automatically delete its contents; for business OneDrives or SharePoint document libraries, you must delete the folder contents first before deleting the folder. +\item \code{delete(confirm=TRUE, by_item=FALSE)}: Delete this item. By default, ask for confirmation first. For personal OneDrive, deleting a folder will also automatically delete its contents; for business OneDrive or SharePoint document libraries, you may need to set \code{by_item=TRUE} to delete the contents first depending on your organisation's policies. Note that this can be slow for large folders. \item \code{update(...)}: Update the item's properties (metadata) in Microsoft Graph. \item \code{do_operation(...)}: Carry out an arbitrary operation on the item. \item \code{sync_fields()}: Synchronise the R object with the item metadata in Microsoft Graph. diff --git a/tests/testthat/test01_onedrive.R b/tests/testthat/test01_onedrive.R index 8ff5353..076a10a 100644 --- a/tests/testthat/test01_onedrive.R +++ b/tests/testthat/test01_onedrive.R @@ -130,7 +130,7 @@ test_that("Methods work with filenames with special characters", }) -test_that("Nested folder creation works", +test_that("Nested folder creation/deletion works", { od <- get_personal_onedrive() @@ -144,8 +144,10 @@ test_that("Nested folder creation works", it1 <- od$get_item(f1) expect_is(it1, "ms_drive_item") + replicate(30, it1$upload(write_file())) + it123 <- it1$create_folder(file.path(f2, f3)) expect_is(it123, "ms_drive_item") - expect_silent(it1$delete(confirm=FALSE)) + expect_silent(it1$delete(confirm=FALSE, by_item=TRUE)) }) diff --git a/tests/testthat/test02_business_onedrive.R b/tests/testthat/test02_business_onedrive.R index c49de76..f234969 100644 --- a/tests/testthat/test02_business_onedrive.R +++ b/tests/testthat/test02_business_onedrive.R @@ -145,7 +145,7 @@ test_that("Methods work with filenames with special characters", }) -test_that("Nested folder creation works", +test_that("Nested folder creation/deletion works", { od <- get_business_onedrive() @@ -159,10 +159,10 @@ test_that("Nested folder creation works", it1 <- od$get_item(f1) expect_is(it1, "ms_drive_item") + replicate(30, it1$upload(write_file())) + it123 <- it1$create_folder(file.path(f2, f3)) expect_is(it123, "ms_drive_item") - expect_silent(it123$delete(confirm=FALSE)) - expect_silent(it12$delete(confirm=FALSE)) - expect_silent(it1$delete(confirm=FALSE)) + expect_silent(it1$delete(confirm=FALSE, by_item=TRUE)) })