1#' Copy files, directories or links
2#'
3#' @description
4#' `file_copy()` copies files.
5#'
6#' `link_copy()` creates a new link pointing to the same location as the previous link.
7#'
8#' `dir_copy()` copies the directory recursively at the new location.
9#' @details
10#' The behavior of `dir_copy()` differs slightly than that of `file.copy()` when
11#' `overwrite = TRUE`. The directory will always be copied to `new_path`, even
12#' if the name differs from the basename of `path`.
13#' @param new_path A character vector of paths to the new locations.
14#' @param overwrite Overwrite files if they exist. If this is `FALSE` and the
15#'   file exists an error will be thrown.
16#' @template fs
17#' @return The new path (invisibly).
18#' @name copy
19#' @export
20#' @examples
21#' \dontshow{.old_wd <- setwd(tempdir())}
22#' file_create("foo")
23#' file_copy("foo", "bar")
24#' try(file_copy("foo", "bar"))
25#' file_copy("foo", "bar", overwrite = TRUE)
26#' file_delete(c("foo", "bar"))
27#'
28#' dir_create("foo")
29#' # Create a directory and put a few files in it
30#' files <- file_create(c("foo/bar", "foo/baz"))
31#' file_exists(files)
32#'
33#' # Copy the directory
34#' dir_copy("foo", "foo2")
35#' file_exists(path("foo2", path_file(files)))
36#'
37#' # Create a link to the directory
38#' link_create(path_abs("foo"), "loo")
39#' link_path("loo")
40#' link_copy("loo", "loo2")
41#' link_path("loo2")
42#'
43#' # Cleanup
44#' dir_delete(c("foo", "foo2"))
45#' link_delete(c("loo", "loo2"))
46#' \dontshow{setwd(.old_wd)}
47file_copy <- function(path, new_path, overwrite = FALSE) {
48  # TODO: copy attributes, e.g. cp -p?
49  assert_no_missing(path)
50  assert_no_missing(new_path)
51
52  old <- path_expand(path)
53  new <- path_expand(new_path)
54
55  is_directory <- file_exists(new) & is_dir(new)
56
57  if (length(new) == 1 && is_directory[[1]]) {
58    new <- rep(new, length(path))
59  }
60  assert("Length of `path` must equal length of `new_path`", length(old) == length(new))
61
62  new[is_directory] <- path(new[is_directory], basename(old))
63
64  .Call(fs_copyfile_, old, new, isTRUE(overwrite))
65
66  invisible(path_tidy(new))
67}
68
69#' @rdname copy
70#' @export
71dir_copy <- function(path, new_path, overwrite = FALSE) {
72  assert_no_missing(path)
73  assert_no_missing(new_path)
74  assert("`path` must be a directory", all(is_dir(path)))
75  assert("Length of `path` must equal length of `new_path`", length(path) == length(new_path))
76
77  for (i in seq_along(path)) {
78    if (!isTRUE(overwrite) && isTRUE(unname(is_dir(new_path[[i]])))) {
79      new_path[[i]] <- path(new_path[[i]], path_file(path))
80    }
81    dir_create(new_path[[i]])
82
83    dirs <- dir_ls(path[[i]], type = "directory", recurse = TRUE, all = TRUE)
84    dir_create(path(new_path[[i]], path_rel(dirs, path[[i]])))
85
86    files <- dir_ls(path[[i]], recurse = TRUE,
87      type = c("unknown", "file", "FIFO", "socket", "character_device", "block_device"), all = TRUE)
88    file_copy(files, path(new_path[[i]], path_rel(files, path[[i]])), overwrite = overwrite)
89
90    links <- dir_ls(path[[i]], recurse = TRUE,
91      type = "symlink", all = TRUE)
92    link_copy(links, path(new_path[[i]], path_rel(links, path[[i]])), overwrite = overwrite)
93  }
94
95  invisible(path_tidy(new_path))
96}
97
98#' @rdname copy
99#' @export
100link_copy <- function(path, new_path, overwrite = FALSE) {
101  assert_no_missing(path)
102  assert_no_missing(new_path)
103  stopifnot(all(is_link(path)))
104
105  old <- path_expand(path)
106  new <- path_expand(new_path)
107
108  to_delete <- isTRUE(overwrite) & link_exists(new)
109  if (any(to_delete)) {
110    link_delete(new[to_delete])
111  }
112
113  invisible(link_create(link_path(old), new))
114}
115