1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280
|
#' Access shared drives
#'
#' @description
#' A shared drive supports files owned by an organization rather than an
#' individual user. Shared drives follow different sharing and ownership models
#' from a specific user's "My Drive". Shared drives are the successors to the
#' earlier concept of Team Drives.
#' @description How to capture a shared drive or files/folders that live on a
#' shared drive for downstream use:
#' * [shared_drive_find()] and [shared_drive_get()] return a [`dribble`] with
#' metadata on shared drives themselves. You will need this in order to use a
#' shared drive in certain file operations. For example, you can specify a
#' shared drive as the parent folder via the `path` argument for upload, move,
#' copy, etc. In that context, the id of a shared drive functions like the id of
#' its top-level or root folder.
#' * [drive_find()] and [drive_get()] return a [`dribble`] with metadata on
#' files, including folders. Both can be directed to search for files on shared
#' drives using the optional arguments `shared_drive` or `corpus` (documented
#' below).
#'
#' @description Regard the functions mentioned above as the official "port of
#' entry" for working with shared drives. Use these functions to capture your
#' target(s) in a [`dribble`] to pass along to other googledrive functions.
#' The flexibility to refer to files by name or path does not apply as broadly
#' to shared drives. While it's always a good idea to get things into a
#' [`dribble`] early, for shared drives it's often required.
#'
#' @section Specific shared drive:
#' To search one specific shared drive, pass its name, marked id, or
#' [`dribble`] to `shared_drive` somewhere in the call, like so:
#' ```
#' drive_find(..., shared_drive = "i_am_a_shared_drive_name")
#' drive_find(..., shared_drive = as_id("i_am_a_shared_drive_id"))
#' drive_find(..., shared_drive = i_am_a_shared_drive_dribble)
#' ```
#' The value provided to `shared_drive` is pre-processed with
#' [as_shared_drive()].
#' @section Other collections:
#' To search other collections, pass the `corpus` parameter somewhere in the
#' call, like so:
#' ```
#' drive_find(..., corpus = "user")
#' drive_find(..., corpus = "allDrives")
#' drive_find(..., corpus = "domain")
#' ```
#' Possible values of `corpus` and what they mean:
#' * `"user"`: Queries files that the user has accessed, including both shared
#' drive and My Drive files.
#' * `"drive"`: Queries all items in the shared drive specified via
#' `shared_drive`. googledrive automatically fills this in whenever
#' `shared_drive` is not `NULL`.
#' * `"allDrives"`: Queries files that the user has accessed and all shared
#' drives in which they are a member. Note that the response may include
#' `incompleteSearch : true`, indicating that some corpora were not searched
#' for this request (currently, googledrive does not surface this). Prefer
#' `"user"` or `"drive"` to `"allDrives"` for efficiency.
#' * `"domain"`: Queries files that are shared to the domain, including both
#' shared drive and My Drive files.
#' @section Google blogs and docs:
#' Here is some of the best official Google reading about shared drives:
#' * [Team Drives is being renamed to shared drives](https://workspaceupdates.googleblog.com/2019/04/shared-drives.html) from Google Workspace blog
#' * [Upcoming changes to the Google Drive API and Google Picker API](https://cloud.google.com/blog/products/application-development/upcoming-changes-to-the-google-drive-api-and-google-picker-api) from the Google Cloud blog
#' * <https://developers.google.com/drive/api/v3/about-shareddrives>
#' * <https://developers.google.com/drive/api/v3/shared-drives-diffs>
#' * Get started with shared drives: `https://support.google.com/a/users/answer/9310351` from Google Workspace Learning Center
#' * Best practices for shared drives: `https://support.google.com/a/users/answer/9310156` from Google Workspace Learning Center
#' @section API docs:
#' googledrive implements shared drive support as outlined here:
#' * <https://developers.google.com/drive/api/v3/enable-shareddrives>
#'
#' Users shouldn't need to know any of this, but here are details for the
#' curious. The extra information needed to search shared drives consists of the
#' following query parameters:
#' * `corpora`: Where to search? Formed from googledrive's `corpus` argument.
#' * `driveId`: The id of a specific shared drive. Only allowed -- and also
#' absolutely required -- when `corpora = "drive"`. When user specifies a
#' `shared_drive`, googledrive sends its id and also infers that `corpora`
#' should be set to `"drive"`.
#' * `includeItemsFromAllDrives`: Do you want to see shared drive items?
#' Obviously, this should be `TRUE` and googledrive sends this whenever shared
#' drive parameters are detected.
#' * `supportsAllDrives`: Does the sending application (googledrive, in this
#' case) know about shared drive? Obviously, this should be `TRUE` and
#' googledrive sends it for all applicable endpoints, all the time.
#'
#' @name shared_drives
NULL
# Shared drive and "file groupings" support
#
# Internal documentation!
#
# It is intentional that googledrive's two parameters `shared_drive` and
# `corpus` have NO OVERLAP with actual Drive query parameters: this way the
# googledrive high-level wrapping is option A but user could also pass raw
# params through `...` as option B.
#
# @param driveId Character, a shared drive id.
# @template corpus
# @param includeItemsFromAllDrives Logical, googledrive always sets this to
# `TRUE`.
#
# @examples
# \dontrun{
# shared_drive_params(driveId = "123456789")
# shared_drive_params(corpora = "user")
# shared_drive_params(corpora = "allDrives")
# shared_drive_params(corpora = "domain")
#
# # this will error because `corpora = "drive"` also requires the id
# shared_drive_params(corpora = "drive")
#
# # this will error because `corpora = "domain"` forbids inclusion of id
# shared_drive_params(corpora = "domain", driveId = "123456789")
# }
shared_drive_params <- function(driveId = NULL,
corpora = NULL,
includeItemsFromAllDrives = NULL) {
rationalize_corpus(
new_corpus(
driveId, corpora, includeItemsFromAllDrives
)
)
}
new_corpus <- function(driveId = NULL,
corpora = NULL,
includeItemsFromAllDrives = NULL) {
if (!is.null(driveId)) {
# can't use is_string() because object of class drive_id IS acceptable
stopifnot(is.character(driveId), length(driveId) == 1)
}
if (!is.null(corpora)) {
stopifnot(is_string(corpora))
}
if (!is.null(includeItemsFromAllDrives)) {
stopifnot(
is.logical(includeItemsFromAllDrives),
length(includeItemsFromAllDrives) == 1
)
}
structure(
list(
corpora = corpora,
driveId = driveId,
includeItemsFromAllDrives = includeItemsFromAllDrives
),
class = "corpus"
)
}
# this isn't a classic validator, because it fills in some gaps
# there is, however, no magic
# there is user input that is unambiguous but still won't satisfy the API
# 1. if `driveId` is provided and `corpora` is not, we fill in
# `corpora = "drive"`
# 2. we always send `includeItemsFromAllDrives = TRUE`
rationalize_corpus <- function(corpus) {
stopifnot(inherits(corpus, "corpus"))
if (!is.null(corpus[["driveId"]])) {
corpus[["corpora"]] <- corpus[["corpora"]] %||% "drive"
}
validate_corpora(corpus[["corpora"]])
if (corpus[["corpora"]] == "drive" && is.null(corpus[["driveId"]])) {
drive_abort('
When {.code corpus = "drive"}, you must also specify \\
the {.arg shared_drive}.')
}
if (corpus[["corpora"]] != "drive" && !is.null(corpus[["driveId"]])) {
drive_abort('
When {.code corpus != "drive"}, you must not specify \\
a {.arg shared_drive}.')
}
corpus[["includeItemsFromAllDrives"]] <- TRUE
corpus
}
valid_corpora <- c("user", "drive", "allDrives", "domain")
validate_corpora <- function(corpora) {
if (!corpora %in% valid_corpora) {
# yes, I intentionally use `corpus` in this message, even
# though the actual API parameter is `corpora`
# googledrive's user-facing functions have `corpus` in their signature and
# the rationale is explained elsewhere in this file
drive_abort(c(
"Invalid value for {.arg corpus}:",
bulletize(gargle_map_cli(corpora), bullet = "x"),
"These are the only acceptable values:",
bulletize(gargle_map_cli(valid_corpora))
))
}
invisible(corpora)
}
#' Coerce to shared drive
#'
#' @description Converts various representations of a shared drive into a
#' [`dribble`], the object used by googledrive to hold Drive file metadata.
#' Shared drives can be specified via
#' * Name
#' * Shared drive id, marked with [as_id()] to distinguish from name
#' * Data frame or [`dribble`] consisting solely of shared drives
#' * List representing [Drives resource](https://developers.google.com/drive/api/v3/reference/drives#resource-representations)
#' objects (mostly for internal use)
#'
#' @template shared-drive-description
#'
#' @description This is a generic function.
#'
#' @param x A vector of shared drive names, a vector of shared drive ids marked
#' with [as_id()], a list of Drives resource objects, or a suitable data
#' frame.
#' @param ... Other arguments passed down to methods. (Not used.)
#' @export
#' @examples
#' \dontrun{
#' # specify the name
#' as_shared_drive("abc")
#'
#' # specify the id (substitute one of your own!)
#' as_shared_drive(as_id("0AOPK1X2jaNckUk9PVA"))
#' }
as_shared_drive <- function(x, ...) UseMethod("as_shared_drive")
#' @export
as_shared_drive.default <- function(x, ...) {
drive_abort("
Don't know how to coerce an object of class {.cls {class(x)}} into \\
a shared drive {.cls dribble}.")
}
#' @export
as_shared_drive.NULL <- function(x, ...) dribble()
#' @export
as_shared_drive.character <- function(x, ...) shared_drive_get(name = x)
#' @export
as_shared_drive.drive_id <- function(x, ...) shared_drive_get(id = x)
#' @export
as_shared_drive.dribble <- function(x, ...) validate_shared_drive_dribble(x)
#' @export
as_shared_drive.data.frame <- function(x, ...) {
validate_shared_drive_dribble(as_dribble(x))
}
#' @export
as_shared_drive.list <- function(x, ...) {
validate_shared_drive_dribble(as_dribble(x))
}
validate_shared_drive_dribble <- function(x) {
stopifnot(inherits(x, "dribble"))
if (!all(is_shared_drive(x))) {
drive_abort("
All rows of shared drive {.cls dribble} must contain a shared drive.")
}
x
}
|