-
Notifications
You must be signed in to change notification settings - Fork 62
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement Onedrive/Sharepoint board (#513)
Fixes #498
- Loading branch information
Showing
12 changed files
with
341 additions
and
18 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,226 @@ | ||
#' Use a OneDrive or Sharepoint document library as a board | ||
#' | ||
#' Pin data to a folder in Onedrive or a SharePoint Online document library | ||
#' using the Microsoft365R package. | ||
#' | ||
#' @inheritParams new_board | ||
#' @param drive A OneDrive or SharePoint document library object, of class | ||
#' [`Microsoft365R::ms_drive`]. | ||
#' @param path Path to directory to store pins. This can be either a string | ||
#' containing the pathname like `"path/to/board"`, or a | ||
#' [`Microsoft365R::ms_drive_item`] object pointing to the board path. | ||
#' @param delete_by_item Whether to handle folder deletions on an item-by-item | ||
#' basis, rather than deleting the entire folder at once. You may need to set | ||
#' this to `TRUE` for a board in SharePoint Online or OneDrive for Business, | ||
#' due to document protection policies that prohibit deleting non-empty | ||
#' folders. | ||
#' @details | ||
#' Sharing a board in OneDrive (personal or business) is a bit complicated, as OneDrive normally allows only the person who owns the drive to access files and folders. First, the drive owner has to set the board folder as shared with other users, using either the OneDrive web interface or Microsoft365R's `ms_drive_item$create_share_link()` method. The other users then call `board_ms365` with a _drive item object_ in the `path` argument, pointing to the shared folder. See the examples below. | ||
#' | ||
#' Sharing a board in SharePoint Online is much more straightforward, assuming all users have access to the document library: in this case, everyone can use the same call `board_ms365(doclib, "path/to/board")`. If you want to share a board with users outside your team, follow the same steps for sharing a board in OneDrive. | ||
#' @export | ||
#' @examples | ||
#' \dontrun{ | ||
#' # A board in your personal OneDrive | ||
#' od <- Microsoft365R::get_personal_onedrive() | ||
#' board <- board_ms365(od, "myboard") | ||
#' board %>% pin_write(iris) | ||
#' | ||
#' # A board in OneDrive for Business | ||
#' odb <- Microsoft365R::get_business_onedrive(tenant = "mytenant") | ||
#' board <- board_ms365(odb, "myproject/board") | ||
#' | ||
#' # A board in a SharePoint Online document library | ||
#' sp <- Microsoft365R::get_sharepoint_site("my site", tenant = "mytenant") | ||
#' doclib <- sp$get_drive() | ||
#' board <- board_ms365(doclib, "general/project1/board") | ||
#' | ||
#' | ||
#' ## Sharing a board in OneDrive: | ||
#' # First, create the board on the drive owner's side | ||
#' board <- board_ms365(od, "myboard") | ||
#' | ||
#' # Next, let other users write to the folder | ||
#' # - set the expiry to NULL if you want the folder to be permanently available | ||
#' od$get_item("myboard")$create_share_link("edit", expiry="30 days") | ||
#' | ||
#' # On the recipient's side: find the shared folder item, then pass it to board_ms365 | ||
#' shared_items <- od$list_shared_items() | ||
#' board_folder <- shared_items$remoteItem[[which(shared_items$name == "myboard")]] | ||
#' board <- board_ms365(od, board_folder) | ||
#' } | ||
board_ms365 <- function(drive, path, versioned = TRUE, cache = NULL, delete_by_item = FALSE) { | ||
check_installed("Microsoft365R") | ||
|
||
if (!inherits(drive, "ms_drive")) { | ||
abort("`drive` must be a OneDrive or SharePoint document library object") | ||
} | ||
if (!inherits(path, c("character", "ms_drive_item"))) { | ||
abort("`path` must be either a string or a drive item object") | ||
} | ||
|
||
if (!inherits(path, "ms_drive_item")) { | ||
# try to create the board folder: ignore error if folder already exists | ||
try(drive$create_folder(path), silent = TRUE) | ||
folder <- drive$get_item(path) | ||
} | ||
else { | ||
folder <- path | ||
# ensure we have the correct properties for a shared item in OneDrive | ||
folder$sync_fields() | ||
path <- NULL | ||
} | ||
|
||
if (!folder$is_folder()) { | ||
abort("Invalid path specified") | ||
} | ||
|
||
cache <- cache %||% board_cache_path(paste0("ms365-", hash(folder$properties$id))) | ||
new_board_v1("pins_board_ms365", | ||
folder = folder, | ||
path = path, | ||
cache = cache, | ||
versioned = versioned, | ||
delete_by_item = delete_by_item | ||
) | ||
} | ||
|
||
board_ms365_test_charpath <- function(...) { | ||
if (identical(Sys.getenv("PINS_MS365_USE_PERSONAL"), "true")) { | ||
drv <- Microsoft365R::get_personal_onedrive() | ||
} else { | ||
skip_if_missing_envvars("board_ms365()", "PINS_MS365_TEST_DRIVE") | ||
drv <- readRDS(Sys.getenv("PINS_MS365_TEST_DRIVE")) | ||
} | ||
board_ms365(drv, path = "pin_testing", cache = tempfile(), ...) | ||
} | ||
|
||
board_ms365_test_driveitem <- function(...) { | ||
if (identical(Sys.getenv("PINS_MS365_USE_PERSONAL"), "true")) { | ||
drv <- Microsoft365R::get_personal_onedrive() | ||
} else { | ||
skip_if_missing_envvars("board_ms365()", "PINS_MS365_TEST_DRIVE") | ||
drv <- readRDS(Sys.getenv("PINS_MS365_TEST_DRIVE")) | ||
} | ||
folder <- try(drv$create_folder("pin_testing_2"), silent = TRUE) | ||
board_ms365(drv, path = folder, cache = tempfile(), ...) | ||
} | ||
|
||
#' @export | ||
pin_list.pins_board_ms365 <- function(board, ...) { | ||
ms365_list_dirs(board) | ||
} | ||
|
||
#' @export | ||
pin_exists.pins_board_ms365 <- function(board, name, ...) { | ||
name %in% ms365_list_dirs(board) | ||
} | ||
|
||
#' @export | ||
pin_delete.pins_board_ms365 <- function(board, names, ...) { | ||
for (name in names) { | ||
check_pin_exists(board, name) | ||
ms365_delete_dir(board, name) | ||
} | ||
invisible(board) | ||
} | ||
|
||
#' @export | ||
pin_versions.pins_board_ms365 <- function(board, name, ...) { | ||
check_pin_exists(board, name) | ||
version_from_path(ms365_list_dirs(board, name)) | ||
} | ||
|
||
#' @export | ||
pin_version_delete.pins_board_ms365 <- function(board, name, version, ...) { | ||
ms365_delete_dir(board, fs::path(name, version)) | ||
} | ||
|
||
#' @export | ||
pin_meta.pins_board_ms365 <- function(board, name, version = NULL, ...) { | ||
check_pin_exists(board, name) | ||
version <- check_pin_version(board, name, version) | ||
metadata_key <- fs::path(name, version, "data.txt") | ||
|
||
if (!ms365_file_exists(board, metadata_key)) { | ||
abort_pin_version_missing(version) | ||
} | ||
|
||
path_version <- fs::path(board$cache, name, version) | ||
fs::dir_create(path_version) | ||
|
||
ms365_download(board, metadata_key) | ||
local_meta( | ||
read_meta(fs::path(board$cache, name, version)), | ||
dir = path_version, | ||
version = version | ||
) | ||
} | ||
|
||
#' @export | ||
pin_fetch.pins_board_ms365 <- function(board, name, version = NULL, ...) { | ||
meta <- pin_meta(board, name, version = version) | ||
cache_touch(board, meta) | ||
|
||
for (file in meta$file) { | ||
key <- fs::path(name, meta$local$version, file) | ||
ms365_download(board, key) | ||
} | ||
|
||
meta | ||
} | ||
|
||
#' @export | ||
pin_store.pins_board_ms365 <- function(board, name, paths, metadata, | ||
versioned = NULL, ...) { | ||
check_name(name) | ||
version <- version_setup(board, name, version_name(metadata), versioned = versioned) | ||
|
||
version_dir <- fs::path(name, version) | ||
|
||
# Upload metadata | ||
meta_tmpfile <- tempfile(fileext = ".yml") | ||
on.exit(unlink(meta_tmpfile)) | ||
yaml::write_yaml(metadata, meta_tmpfile) | ||
board$folder$upload(meta_tmpfile, fs::path(version_dir, "data.txt")) | ||
|
||
# Upload files | ||
for (path in paths) { | ||
board$folder$upload(path, fs::path(version_dir, fs::path_file(path))) | ||
} | ||
|
||
name | ||
} | ||
|
||
|
||
# helpers | ||
|
||
# list all the directories inside 'path', which is assumed to live in the board folder | ||
ms365_list_dirs <- function(board, path = "") { | ||
conts <- board$folder$list_files(path) | ||
conts$name[conts$isdir] | ||
} | ||
|
||
# delete directory 'path', which is assumed to live in the board folder | ||
ms365_delete_dir <- function(board, path = "") { | ||
child <- board$folder$get_item(path) | ||
child$delete(confirm = FALSE, by_item = board$delete_by_item) | ||
} | ||
|
||
# check if a file exists and is not a directory | ||
ms365_file_exists <- function(board, key) { | ||
item <- try(board$folder$get_item(key), silent = TRUE) | ||
inherits(item, "ms_drive_item") && !item$is_folder() | ||
} | ||
|
||
# download a specific file from the board, as given by the 'key' path | ||
ms365_download <- function(board, key) { | ||
path <- fs::path(board$cache, key) | ||
|
||
if (!fs::file_exists(path)) { | ||
board$folder$get_item(key)$download(path) | ||
fs::file_chmod(path, "u=r") | ||
} | ||
|
||
path | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.