Skip to content

Commit 0da1181

Browse files
authored
Add Google Drive board (#749)
* Start work on Google Drive board * Fix examples * Start outline of new board vignette * Start tests * Update NEWS * Extra quietness during tests * Make reading and writing quiet * Fix versions * Do vignette in a separate PR * Add snapshots
1 parent ec05d3e commit 0da1181

10 files changed

+284
-1
lines changed

DESCRIPTION

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ Suggests:
4848
filelock,
4949
gitcreds,
5050
googleCloudStorageR,
51+
googledrive,
5152
ids,
5253
knitr,
5354
Microsoft365R,

NAMESPACE

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ S3method(pin_delete,pins_board_azure)
5757
S3method(pin_delete,pins_board_connect)
5858
S3method(pin_delete,pins_board_folder)
5959
S3method(pin_delete,pins_board_gcs)
60+
S3method(pin_delete,pins_board_gdrive)
6061
S3method(pin_delete,pins_board_kaggle_competition)
6162
S3method(pin_delete,pins_board_kaggle_dataset)
6263
S3method(pin_delete,pins_board_ms365)
@@ -66,6 +67,7 @@ S3method(pin_exists,pins_board_azure)
6667
S3method(pin_exists,pins_board_connect)
6768
S3method(pin_exists,pins_board_folder)
6869
S3method(pin_exists,pins_board_gcs)
70+
S3method(pin_exists,pins_board_gdrive)
6971
S3method(pin_exists,pins_board_kaggle_competition)
7072
S3method(pin_exists,pins_board_kaggle_dataset)
7173
S3method(pin_exists,pins_board_ms365)
@@ -75,6 +77,7 @@ S3method(pin_fetch,pins_board_azure)
7577
S3method(pin_fetch,pins_board_connect)
7678
S3method(pin_fetch,pins_board_folder)
7779
S3method(pin_fetch,pins_board_gcs)
80+
S3method(pin_fetch,pins_board_gdrive)
7881
S3method(pin_fetch,pins_board_kaggle_competition)
7982
S3method(pin_fetch,pins_board_kaggle_dataset)
8083
S3method(pin_fetch,pins_board_ms365)
@@ -84,6 +87,7 @@ S3method(pin_list,pins_board_azure)
8487
S3method(pin_list,pins_board_connect)
8588
S3method(pin_list,pins_board_folder)
8689
S3method(pin_list,pins_board_gcs)
90+
S3method(pin_list,pins_board_gdrive)
8791
S3method(pin_list,pins_board_kaggle_competition)
8892
S3method(pin_list,pins_board_kaggle_dataset)
8993
S3method(pin_list,pins_board_local)
@@ -98,6 +102,7 @@ S3method(pin_meta,pins_board_azure)
98102
S3method(pin_meta,pins_board_connect)
99103
S3method(pin_meta,pins_board_folder)
100104
S3method(pin_meta,pins_board_gcs)
105+
S3method(pin_meta,pins_board_gdrive)
101106
S3method(pin_meta,pins_board_kaggle_competition)
102107
S3method(pin_meta,pins_board_kaggle_dataset)
103108
S3method(pin_meta,pins_board_ms365)
@@ -111,6 +116,7 @@ S3method(pin_store,pins_board_azure)
111116
S3method(pin_store,pins_board_connect)
112117
S3method(pin_store,pins_board_folder)
113118
S3method(pin_store,pins_board_gcs)
119+
S3method(pin_store,pins_board_gdrive)
114120
S3method(pin_store,pins_board_kaggle_competition)
115121
S3method(pin_store,pins_board_kaggle_dataset)
116122
S3method(pin_store,pins_board_ms365)
@@ -121,6 +127,7 @@ S3method(pin_version_delete,pins_board_azure)
121127
S3method(pin_version_delete,pins_board_connect)
122128
S3method(pin_version_delete,pins_board_folder)
123129
S3method(pin_version_delete,pins_board_gcs)
130+
S3method(pin_version_delete,pins_board_gdrive)
124131
S3method(pin_version_delete,pins_board_ms365)
125132
S3method(pin_version_delete,pins_board_s3)
126133
S3method(pin_version_delete,pins_board_url)
@@ -129,6 +136,7 @@ S3method(pin_versions,pins_board_azure)
129136
S3method(pin_versions,pins_board_connect)
130137
S3method(pin_versions,pins_board_folder)
131138
S3method(pin_versions,pins_board_gcs)
139+
S3method(pin_versions,pins_board_gdrive)
132140
S3method(pin_versions,pins_board_kaggle_dataset)
133141
S3method(pin_versions,pins_board_ms365)
134142
S3method(pin_versions,pins_board_s3)
@@ -141,6 +149,7 @@ S3method(required_pkgs,pins_board)
141149
S3method(required_pkgs,pins_board_azure)
142150
S3method(required_pkgs,pins_board_connect)
143151
S3method(required_pkgs,pins_board_gcs)
152+
S3method(required_pkgs,pins_board_gdrive)
144153
S3method(required_pkgs,pins_board_ms365)
145154
S3method(required_pkgs,pins_board_s3)
146155
S3method(str,pins_hidden)
@@ -162,6 +171,7 @@ export(board_deregister)
162171
export(board_desc)
163172
export(board_folder)
164173
export(board_gcs)
174+
export(board_gdrive)
165175
export(board_get)
166176
export(board_initialize)
167177
export(board_kaggle_competitions)

NEWS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
* New environment variable `PINS_CACHE_DIR` controls the location of the
44
default cache path (#748).
5+
6+
* Added new board for Google Drive `board_gdrive()` (#749).
57

68
# pins 1.2.0
79

R/board_gdrive.R

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
#' Use a Google Drive folder as a board
2+
#'
3+
#' Pin data to a folder in Google Drive using the googledrive package.
4+
#'
5+
#' @inheritParams new_board
6+
#' @param path Path to existing directory on Google Drive to store pins. Can be
7+
#' given as an actual path like `"path/to/folder"` (character), a file id or
8+
#' URL marked with [googledrive::as_id()], or a [googledrive::dribble].
9+
#'
10+
#' @details
11+
#' * The functions in pins do not create a new Google Drive folder. You can
12+
#' create a new folder from R with [googledrive::drive_mkdir()], and then set
13+
#' the sharing for your folder with [googledrive::drive_share()].
14+
#' * If you have problems with authentication to Google Drive, learn more at
15+
#' [googledrive::drive_auth()].
16+
#' * `board_gdrive()` is powered by the googledrive package, which is a
17+
#' suggested dependency of pins (not required for pins in general). If
18+
#' you run into errors when deploying content to a server like
19+
#' <https://www.shinyapps.io> or [Connect](https://posit.co/products/enterprise/connect/),
20+
#' add `requireNamespace("googledrive")` to your app or document for [automatic
21+
#' dependency discovery](https://docs.posit.co/connect/user/troubleshooting/#render-missing-r-package).
22+
#'
23+
#' @export
24+
#'
25+
#' @examples
26+
#' \dontrun{
27+
#' board <- board_gdrive("folder-for-my-pins")
28+
#' board %>% pin_write(1:10, "great-integers", type = "json")
29+
#' board %>% pin_read("great-integers")
30+
#' }
31+
board_gdrive <- function(path,
32+
versioned = TRUE,
33+
cache = NULL) {
34+
check_installed("googledrive")
35+
dribble <- googledrive::as_dribble(path)
36+
37+
if (!googledrive::single_file(dribble) || !googledrive::is_folder(dribble)) {
38+
cli::cli_abort(c(
39+
"{.arg path} must resolve to a single existing Drive folder",
40+
i = "Consider creating your pin board with {.fun googledrive::drive_mkdir}"
41+
))
42+
}
43+
44+
cache <- cache %||% board_cache_path(paste0("gdrive-", hash(dribble$id)))
45+
new_board_v1(
46+
"pins_board_gdrive",
47+
dribble = dribble,
48+
cache = cache,
49+
versioned = versioned
50+
)
51+
}
52+
53+
board_gdrive_test <- function(...) {
54+
skip_if_missing_envvars(
55+
tests = "board_gdrive()",
56+
envvars = c("PINS_GDRIVE_USE_PERSONAL")
57+
)
58+
59+
board_gdrive("pins-testing", cache = tempfile())
60+
}
61+
62+
#' @export
63+
pin_list.pins_board_gdrive <- function(board, ...) {
64+
googledrive::drive_ls(board$dribble)$name
65+
}
66+
67+
#' @export
68+
pin_exists.pins_board_gdrive <- function(board, name, ...) {
69+
all_names <- googledrive::drive_ls(board$dribble$name)$name
70+
name %in% all_names
71+
}
72+
73+
#' @export
74+
pin_delete.pins_board_gdrive <- function(board, names, ...) {
75+
for (name in names) {
76+
check_pin_exists(board, name)
77+
gdrive_delete_dir(board, name)
78+
}
79+
invisible(board)
80+
}
81+
82+
#' @export
83+
pin_version_delete.pins_board_gdrive <- function(board, name, version, ...) {
84+
gdrive_delete_dir(board, fs::path(name, version))
85+
}
86+
87+
#' @export
88+
pin_versions.pins_board_gdrive <- function(board, name, ...) {
89+
check_pin_exists(board, name)
90+
path <- fs::path(board$dribble$path, name)
91+
version_from_path(sort(googledrive::drive_ls(path)$name))
92+
}
93+
94+
95+
#' @export
96+
pin_meta.pins_board_gdrive <- function(board, name, version = NULL, ...) {
97+
googledrive::local_drive_quiet()
98+
check_pin_exists(board, name)
99+
version <- check_pin_version(board, name, version)
100+
metadata_key <- fs::path(name, version, "data.txt")
101+
102+
if (!gdrive_file_exists(board, metadata_key)) {
103+
abort_pin_version_missing(version)
104+
}
105+
106+
path_version <- fs::path(board$cache, name, version)
107+
fs::dir_create(path_version)
108+
109+
gdrive_download(board, metadata_key)
110+
local_meta(
111+
read_meta(fs::path(board$cache, name, version)),
112+
name = name,
113+
dir = path_version,
114+
version = version
115+
)
116+
}
117+
118+
#' @export
119+
pin_fetch.pins_board_gdrive <- function(board, name, version = NULL, ...) {
120+
googledrive::local_drive_quiet()
121+
meta <- pin_meta(board, name, version = version)
122+
cache_touch(board, meta)
123+
124+
for (file in meta$file) {
125+
key <- fs::path(name, meta$local$version, file)
126+
gdrive_download(board, key)
127+
}
128+
129+
meta
130+
}
131+
132+
#' @export
133+
pin_store.pins_board_gdrive <- function(board, name, paths, metadata,
134+
versioned = NULL, ...) {
135+
googledrive::local_drive_quiet()
136+
check_pin_name(name)
137+
version <- version_setup(board, name, version_name(metadata), versioned = versioned)
138+
139+
gdrive_mkdir(board$dribble$name, name)
140+
gdrive_mkdir(fs::path(board$dribble$name, name), version)
141+
142+
version_dir <- fs::path(name, version)
143+
144+
# Upload metadata
145+
temp_file <- withr::local_tempfile()
146+
yaml::write_yaml(metadata, file = temp_file)
147+
googledrive::drive_upload(
148+
temp_file,
149+
fs::path(board$dribble$path, version_dir, "data.txt")
150+
)
151+
152+
# Upload files
153+
for (path in paths) {
154+
googledrive::drive_upload(
155+
path,
156+
fs::path(board$dribble$path, version_dir, fs::path_file(path))
157+
)
158+
}
159+
160+
name
161+
}
162+
163+
164+
#' @rdname required_pkgs.pins_board
165+
#' @export
166+
required_pkgs.pins_board_gdrive <- function(x, ...) {
167+
ellipsis::check_dots_empty()
168+
"googledrive"
169+
}
170+
171+
172+
# Helpers -----------------------------------------------------------------
173+
174+
gdrive_file_exists <- function(board, name) {
175+
path <- fs::path(board$dribble$name, fs::path_dir(name))
176+
name <- fs::path_file(name)
177+
possibly_drive_ls <- purrr::possibly(googledrive::drive_ls)
178+
all_names <- possibly_drive_ls(path)
179+
name %in% all_names$name
180+
}
181+
182+
gdrive_delete_dir <- function(board, dir) {
183+
path <- fs::path(board$dribble$path, dir)
184+
googledrive::drive_trash(path)
185+
invisible()
186+
}
187+
188+
gdrive_download <- function(board, key) {
189+
path <- fs::path(board$cache, key)
190+
if (!fs::file_exists(path)) {
191+
googledrive::drive_download(key, path)
192+
fs::file_chmod(path, "u=r")
193+
}
194+
path
195+
}
196+
197+
gdrive_mkdir <- function(dir, name) {
198+
dribble <- googledrive::as_dribble(fs::path(dir, name))
199+
if (googledrive::no_file(dribble) || !googledrive::is_folder(dribble)) {
200+
googledrive::drive_mkdir(name, dir, overwrite = FALSE)
201+
}
202+
invisible()
203+
}

_pkgdown.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ reference:
3939
- board_connect
4040
- board_connect_url
4141
- board_gcs
42+
- board_gdrive
4243
- board_local
4344
- board_ms365
4445
- board_s3

man/board_gdrive.Rd

Lines changed: 43 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/required_pkgs.pins_board.Rd

Lines changed: 5 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/testthat/_snaps/board_gdrive.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# can find board required pkgs
2+
3+
Code
4+
required_pkgs(board)
5+
Output
6+
[1] "googledrive"
7+
8+
# metadata checking functions give correct errors
9+
10+
`tags` must be a character vector or `NULL`, not a list.
11+
12+
---
13+
14+
`metadata` must be a list or `NULL`, not a character vector.
15+

tests/testthat/setup.R

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
options(pins.verbose = FALSE)
22
options(pins.quiet = TRUE)
3+
options(googledrive_quiet = TRUE)

tests/testthat/test-board_gdrive.R

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
test_api_basic(board_gdrive_test())
2+
test_api_versioning(board_gdrive_test())
3+
test_api_meta(board_gdrive_test())

0 commit comments

Comments
 (0)