1 2#' @theme assets/extra.css assets/rd.js 3#' @useDynLib zip, .registration = TRUE, .fixes = "c_" 4NULL 5 6#' Compress Files into 'zip' Archives 7#' 8#' `zip()` creates a new zip archive file. 9#' 10#' `zip_append()` appends compressed files to an existing 'zip' file. 11#' 12#' ## Relative paths 13#' 14#' `zip()` and `zip_append()` can run in two different modes: mirror 15#' mode and cherry picking mode. They handle the specified `files` 16#' differently. 17#' 18#' ### Mirror mode 19#' 20#' Mirror mode is for creating the zip archive of a directory structure, 21#' exactly as it is on the disk. The current working directory will 22#' be the root of the archive, and the paths will be fully kept. 23#' zip changes the current directory to `root` before creating the 24#' archive. 25#' 26#' (Absolute paths are also kept. Note that this might result 27#' non-portable archives: some zip tools do not handle zip archives that 28#' contain absolute file names, or file names that start with `../` or 29#' `./`. zip warns you if this should happen.) 30#' 31#' E.g. consider the following directory structure: 32#' 33#' ```{r echo = FALSE, comment = ""} 34#' dir.create(tmp <- tempfile()) 35#' oldwd <- getwd() 36#' setwd(tmp) 37#' dir.create("foo/bar", recursive = TRUE) 38#' dir.create("foo/bar2") 39#' dir.create("foo2") 40#' cat("this is file1", file = "foo/bar/file1") 41#' cat("this is file2", file = "foo/bar/file2") 42#' cat("this is file3", file = "foo2/file3") 43#' out <- processx::run("tree", c("--noreport", "--charset=ascii")) 44#' cat(crayon::strip_style(out$stdout)) 45#' setwd(oldwd) 46#' ``` 47#' 48#' Assuming the current working directory is `foo`, the following zip 49#' entries are created by `zip`: 50#' ```{r, echo = 2:4} 51#' setwd(tmp) 52#' setwd("foo") 53#' zip::zip("../test.zip", c("bar/file1", "bar2", "../foo2")) 54#' zip_list("../test.zip")[, "filename", drop = FALSE] 55#' setwd(oldwd) 56#' ``` 57#' 58#' ### Cherry picking mode 59#' 60#' In cherry picking mode, the selected files and directories 61#' will be at the root of the archive. This mode is handy if you 62#' want to select a subset of files and directories, possibly from 63#' different paths and put all of the in the archive, at the top 64#' level. 65#' 66#' Here is an example with the same directory structure as above: 67#' 68#' ```{r, echo = 3:4} 69#' setwd(tmp) 70#' setwd("foo") 71#' zip::zip( 72#' "../test2.zip", 73#' c("bar/file1", "bar2", "../foo2"), 74#' mode = "cherry-pick" 75#') 76#' zip_list("../test2.zip")[, "filename", drop = FALSE] 77#' setwd(oldwd) 78#' ``` 79#' 80#' ## Permissions: 81#' 82#' `zip()` (and `zip_append()`, etc.) add the permissions of 83#' the archived files and directories to the ZIP archive, on Unix systems. 84#' Most zip and unzip implementations support these, so they will be 85#' recovered after extracting the archive. 86#' 87#' Note, however that the owner and group (uid and gid) are currently 88#' omitted, even on Unix. 89#' 90#' ## `zipr()` and `zipr_append()` 91#' 92#' These function exist for historical reasons. They are identical 93#' to `zip()` and `zipr_append()` with a different default for the 94#' `mode` argument. 95#' 96#' @param zipfile The zip file to create. If the file exists, `zip` 97#' overwrites it, but `zip_append` appends to it. 98#' @param files List of file to add to the archive. See details below 99#' about absolute and relative path names. 100#' @param recurse Whether to add the contents of directories recursively. 101#' @param compression_level A number between 1 and 9. 9 compresses best, 102#' but it also takes the longest. 103#' @param include_directories Whether to explicitly include directories 104#' in the archive. Including directories might confuse MS Office when 105#' reading docx files, so set this to `FALSE` for creating them. 106#' @param root Change to this working directory before creating the 107#' archive. 108#' @param mode Selects how files and directories are stored in 109#' the archice. It can be `"mirror"` or `"cherry-pick"`. 110#' See "Relative Paths" below for details. 111#' @return The name of the created zip file, invisibly. 112#' 113#' @export 114#' @examples 115#' ## Some files to zip up. We will run all this in the R sesion's 116#' ## temporary directory, to avoid messing up the user's workspace. 117#' dir.create(tmp <- tempfile()) 118#' dir.create(file.path(tmp, "mydir")) 119#' cat("first file", file = file.path(tmp, "mydir", "file1")) 120#' cat("second file", file = file.path(tmp, "mydir", "file2")) 121#' 122#' zipfile <- tempfile(fileext = ".zip") 123#' zip::zip(zipfile, "mydir", root = tmp) 124#' 125#' ## List contents 126#' zip_list(zipfile) 127#' 128#' ## Add another file 129#' cat("third file", file = file.path(tmp, "mydir", "file3")) 130#' zip_append(zipfile, file.path("mydir", "file3"), root = tmp) 131#' zip_list(zipfile) 132 133zip <- function(zipfile, files, recurse = TRUE, compression_level = 9, 134 include_directories = TRUE, root = ".", 135 mode = c("mirror", "cherry-pick")) { 136 mode <- match.arg(mode) 137 zip_internal(zipfile, files, recurse, compression_level, append = FALSE, 138 root = root, keep_path = (mode == "mirror"), 139 include_directories = include_directories) 140} 141 142#' @rdname zip 143#' @export 144 145zipr <- function(zipfile, files, recurse = TRUE, compression_level = 9, 146 include_directories = TRUE, root = ".", 147 mode = c("cherry-pick", "mirror")) { 148 mode <- match.arg(mode) 149 zip_internal(zipfile, files, recurse, compression_level, append = FALSE, 150 root = root, keep_path = (mode == "mirror"), 151 include_directories = include_directories) 152} 153 154#' @rdname zip 155#' @export 156 157zip_append <- function(zipfile, files, recurse = TRUE, 158 compression_level = 9, include_directories = TRUE, 159 root = ".", mode = c("mirror", "cherry-pick")) { 160 mode <- match.arg(mode) 161 zip_internal(zipfile, files, recurse, compression_level, append = TRUE, 162 root = root, keep_path = (mode == "mirror"), 163 include_directories = include_directories) 164} 165 166#' @rdname zip 167#' @export 168 169zipr_append <- function(zipfile, files, recurse = TRUE, 170 compression_level = 9, include_directories = TRUE, 171 root = ".", mode = c("cherry-pick", "mirror")) { 172 mode <- match.arg(mode) 173 zip_internal(zipfile, files, recurse, compression_level, append = TRUE, 174 root = root, keep_path = (mode == "mirror"), 175 include_directories = include_directories) 176} 177 178zip_internal <- function(zipfile, files, recurse, compression_level, 179 append, root, keep_path, include_directories) { 180 oldwd <- setwd(root) 181 on.exit(setwd(oldwd), add = TRUE) 182 183 if (any(! file.exists(files))) stop("Some files do not exist") 184 185 data <- get_zip_data(files, recurse, keep_path, include_directories) 186 warn_for_dotdot(data$key) 187 188 .Call(c_R_zip_zip, enc2utf8(zipfile), enc2utf8(data$key), 189 enc2utf8(data$file), data$dir, file.info(data$file)$mtime, 190 as.integer(compression_level), append) 191 192 invisible(zipfile) 193} 194 195#' List Files in a 'zip' Archive 196#' 197#' @details Note that `crc32` is formatted using `as.hexmode()`. `offset` refers 198#' to the start of the local zip header for each entry. Following the approach 199#' of `seek()` it is stored as a `numeric` rather than an `integer` vector and 200#' can therefore represent values up to `2^53-1` (9 PB). 201#' @param zipfile Path to an existing ZIP file. 202#' @return A data frame with columns: `filename`, `compressed_size`, 203#' `uncompressed_size`, `timestamp`, `permissions`, `crc32` and `offset`. 204#' 205#' @family zip/unzip functions 206#' @export 207 208zip_list <- function(zipfile) { 209 zipfile <- enc2utf8(normalizePath(zipfile)) 210 res <- .Call(c_R_zip_list, zipfile) 211 df <- data.frame( 212 stringsAsFactors = FALSE, 213 filename = res[[1]], 214 compressed_size = res[[2]], 215 uncompressed_size = res[[3]], 216 timestamp = as.POSIXct(res[[4]], tz = "UTC", origin = "1970-01-01") 217 ) 218 Encoding(df$filename) <- "UTF-8" 219 df$permissions <- as.octmode(res[[5]]) 220 df$crc32 <- as.hexmode(res[[6]]) 221 df$offset <- res[[7]] 222 df 223} 224 225#' Uncompress 'zip' Archives 226#' 227#' `unzip()` always restores modification times of the extracted files and 228#' directories. 229#' 230#' @section Permissions: 231#' 232#' If the zip archive stores permissions and was created on Unix, 233#' the permissions will be restored. 234#' 235#' @param zipfile Path to the zip file to uncompress. 236#' @param files Character vector of files to extract from the archive. 237#' Files within directories can be specified, but they must use a forward 238#' slash as path separator, as this is what zip files use internally. 239#' If `NULL`, all files will be extracted. 240#' @param overwrite Whether to overwrite existing files. If `FALSE` and 241#' a file already exists, then an error is thrown. 242#' @param junkpaths Whether to ignore all directory paths when creating 243#' files. If `TRUE`, all files will be created in `exdir`. 244#' @param exdir Directory to uncompress the archive to. If it does not 245#' exist, it will be created. 246#' 247#' @export 248#' @examples 249#' ## temporary directory, to avoid messing up the user's workspace. 250#' dir.create(tmp <- tempfile()) 251#' dir.create(file.path(tmp, "mydir")) 252#' cat("first file", file = file.path(tmp, "mydir", "file1")) 253#' cat("second file", file = file.path(tmp, "mydir", "file2")) 254#' 255#' zipfile <- tempfile(fileext = ".zip") 256#' zip::zip(zipfile, "mydir", root = tmp) 257#' 258#' ## List contents 259#' zip_list(zipfile) 260#' 261#' ## Extract 262#' tmp2 <- tempfile() 263#' unzip(zipfile, exdir = tmp2) 264#' dir(tmp2, recursive = TRUE) 265 266unzip <- function(zipfile, files = NULL, overwrite = TRUE, 267 junkpaths = FALSE, exdir = ".") { 268 269 stopifnot( 270 is_string(zipfile), 271 is_character_or_null(files), 272 is_flag(overwrite), 273 is_flag(junkpaths), 274 is_string(exdir)) 275 276 zipfile <- enc2utf8(normalizePath(zipfile)) 277 if (!is.null(files)) files <- enc2utf8(files) 278 mkdirp(exdir) 279 exdir <- enc2utf8(normalizePath(exdir)) 280 281 .Call(c_R_zip_unzip, zipfile, files, overwrite, junkpaths, exdir) 282 283 invisible() 284} 285