#' Restore default library of packages, undoing all changes made by groundhog
#'
#' In a few seconds reverse changes made by groundhog to the default personal
#' library (where R packages are usually installed into with `installed.packages()`).
#' If you are just trying groundhog out for the first time,
#' or you generally rely on base R's `library()` and want to use `groundhog.library()` sporadically
#' then you may want to run `restore.library()` when you are done with your one-time use of groundhog.
#' This will undo any and all changes made by `groundhog` to that library.
#' In most circumstances, restoring a library takes a few seconds, even if the library
#' has had 100s of package modifications.
#'
#'@param days an optional numeric argument used to choose among alternative restore points.
#' When `days` is set, groundhog restores the personal library to the most recent restore point that
#' is at least `days` days old. If `dais` is not set, groundhog restores to the most recent restore point overall.
#' For example, if there are two restore points: one from today, and one from 7 days ago,
#' running `restore.library()` would restore to the former, setting `days=3` would restore to the latter,
#' and setting `days=8` would result in an error. `days = -1` restores to the oldest restore point available.
#' @examples
#' \dontrun{
#' restore.library()
#' restore.library(7)
#' restore.library(-1)
#' }
#'
#' @details
#'When groundhog installs a package, it installs it into groundhog's library
#' (location of that library is obtainable with [get.groundhog.folder()]).
#'Groundhog then immediately moves the installed package(s) (and their dependencies)
#'to the default personal library (location of that library obtainable with: `.libPaths()[1]`).
#'Altering the packages in the local folder is necessary for groundhog to work properly for two main reasons. First,
#'R Studio often loads packages from that library before users run the code in a script, creating
#'version conflicts that cannot be avoided when attempting to load other versions of those
#'packages with `groundhog`. Second, R scripts often run processes in independent R sessions, for
#'example when doing parallel processing. Those background processes will also look for
#'packages in the default personal folder. Because the personal library can only hold
#'one version of a given package, before moving new packages in, groundhog moves
#'any existing other versions of those packages out, to another directory (a local archive).
#'Those files are not deleted, just moved, making it easy and fast to recover.
#'When the first change in the personal folder is made on a given calendar date,
#'groundhog makes a list of all packages available in the personal folder before such change (saving
#'a copy of the results from `installed.packages(.libPaths()[1])`), this saved file is referred to as a
#'a 'restore point'. With `restore.library()` groundhog looks up a restore point, obtain the set of packages
#'that used to be installed, and removes any packages *installed by groundhog* which are in the personal library
#'but were not in that restore point; similarly, it moves back to the local library any packages *removed by groundhog*
#'that were in the restore point but are not currently there. This process take a few seconds even for 100+ packages.
#'Note that there is only one restore point per calendar date, so one effectively restores
#'the personal library to how it was before *any* changes were made to it that day with groundhog.
#'Restore points are saved permanently and can be restored at any point.
#'The set of restore points available is stored in the hidden dataframe `.available.restore.points`.
#'To choose among them use the `days` argument in `restore.library`. The default is to restore
#'based on the most recent restore point, so if a user installs groundhog, tests it, and wants to
#'undo all changes made by groundhog, the default behavior will achieve this goal.
#'Note: restoring can take a few minutes if the groundhog folder is on a different
#'drive from the default personal R library (e.g., two different hard drives) or if it is in Dropbox.
#' @export
#'
restore.library<-function(days=0)
{
#1 Current IP
ip.local <- get.ip('local')
ip.backup <- get.ip('backup')
ip.groundhog <-get.ip('groundhog')
loans <- get.loans()
#2 Chose restore point to use
#2.1 Set the path
restore_dir <- paste0(get.groundhog.folder(),"/restore_points/", get.r.majmin())
dir.create(restore_dir, recursive = TRUE,showWarnings = FALSE)
#2.3 Files available
ip_files <- list.files(restore_dir)
#if none, end
if (length(ip_files)==0) {
message1("No restore points are available.")
return(invisible(FALSE))
}
#2.4 Turn filenames to dates
ip_dates <- as.Date(substr(ip_files, 0,10))
#2.5 Set date for restore based on days
days.since <- Sys.Date() - ip_dates
ip_dates <- ip_dates[days.since >= days]
if (length(ip_dates)==0) {
message("The oldest restore point is from ",max(days.since)," days ago.")
return(invisible(FALSE))
}
datek <- max(ip_dates)
#3 Read restore IP for datek, all packages in install.package for restore point
ip.restore <- readRDS(paste0(restore_dir, "/", datek, ".rds"))
#---------------------------------------------------------------------------
#ADD PACAKGES THAT WERE REMOVED BY GROUNDHOG
#4 Subset of packages in to-be-restored IP, but not in local, so they need to be added
ip.add0 <- ip.restore[! ip.restore$md5 %in% ip.local$md5,] #We used to have these packages, but no longer do, so we add them
#4.1 Keep subset that we know were originally removed by groundhog
ip.add <- ip.add0[ip.add0$md5 %in% ip.backup$md5,] #only keep those that were removed by groundhog
#4.2 If some were removed not by groundhog make a note
non.groundhog_removed <- ip.add0$pkg_vrs[!ip.add0$pkg_vrs %in% ip.add$pkg_vrs]
#4.3 Count how many are being added
n.add <- nrow(ip.add)
#----------------------------------------------------
#5 Set of pkgs being eliminated
ip.purge0 <- ip.local[!ip.local$md5 %in% ip.restore$md5,] #We have these packages but did not use to, so we purge them
#5.1 Do eliminate those we know were originally added by groundhog
ip.purge <- ip.purge0[ip.purge0$md5 %in% loans$md5 | ip.purge0$md5 %in% ip.groundhog$md5,]
#We have these packages but did not use to, so we purge them
#They are either on loan (if using renaming, or in groundhog, if using copying)
#5.2 i some were added not by groundhog make a note
non.groundhog_installed <- ip.purge0$pkg_vrs[!ip.purge0$pkg_vrs %in% ip.purge$pkg_vrs]
#5.3 count number being removed
n.purge <- nrow(ip.purge)
#6 Early return if no changes
if (n.add + n.purge == 0) {
message1("No packages added or removed by groundhog since restore point.")
return(invisible(TRUE))
}
#7 Inform user what would happened if they go forward
#7.1 Package counts
message1("Proceeding entails restoring ", n.add, " packages and uninstalling ", n.purge ," packages.\n",
"This process should take a few seconds.\n")
#7.2 Warnings for non-groundhog changes
#Commenting out for V3.1.0 - seems difficult for users to use this information really
#and not clear this poses additional risk to just switching groundhog<->library
# n1=length(non.groundhog_installed)
# n2=length(non.groundhog_removed)
# if (n1+n2>0) {
# msg <- paste0("-------------------------------------------------------------\n",
# "Warning: Since the creation of the restore point,\n")
# if (n1>0) {
# msg<-paste0(msg, "an additional ", n1 , " packages have been installed but not\n",
# "by groundhog, these packages will *not* be removed:\n",
# pasteN(non.groundhog_installed),"\n")
# }
# if (n2>0) {
# msg<-paste0(msg, "an additional ", n2 , " packages have been removed but not\n",
# "by groundhog, these will *not* be restored:\n",
# pasteN(non.groundhog_removed))
#
# }
# msg<-paste0(msg, ".\nProceeding with restoring only the groundhog produced\n",
# "changes to the local library may lead packages to not\n",
# "work properly.\n",
# "See 'https://groundhogR.com/restore' for more information.\n",
# "-------------------------------------------------------------")
# message(msg)
# }
#7.3. Prompt for 'restore'
message1("To proceed type 'restore', to stop type anything else.")
answer <-readline(prompt=" >")
if (tolower(answer) != 'restore') {
message("You typed '",answer,"', the library was NOT restored.")
return(invisible(FALSE))
}
#-----------------------------
#8 If user says "RESTORE"
if (tolower(answer) == 'restore') {
#8.1 Purge packages (back to groundhog or put to backup)
purge.local (ip.purge , loans) #function 1 in interlibrary.functions.R
#This sends back to groundhog pkgs that came from there (based on MD5)
#and sends to backup those that do no match md5
#8.2 Restore deleted packages #k=1
skip.no_backup=skip.other_pkg=c()
if (n.add > 0)
{
backup.dir <- paste0(get.groundhog.folder(),"/restore_library/",get.r.majmin(),"/")
dir.create(backup.dir, showWarnings = FALSE, recursive = TRUE)
#From/To based on ip.add (recall: ip.add is a subset of ip.restore)
from <-paste0(backup.dir, ip.add$pkg_vrs,"/", ip.add$Package)
to <- paste0(ip.add$LibPath, "/", get.pkg(ip.add$pkg))
#Skip if from does not exist skip
skip.no_backup <- !file.exists(from)
skip.other_pkg <- file.exists(paste0(to,"/", ip.add$pkg))
skip <- skip.no_backup | skip.other_pkg
file.rename.robust2(from,to) #see file.rename.robust2.R
#Delete parent directory
#(so, from = 'pkg_vrs'/'pkg'
unlink(dirname(from),recursive=TRUE)
}
#------------------------------------------------
#Skipped based on FROM
if (sum(skip.no_backup)>0) {
message(sum(skip.no_backup), " packages originally removed by groundhog could not be restored because the backup was missing:")
message(pasteN(from[skip.no_backup]))
}
#Skipped based on TO
if (sum(skip.other_pkg)>0) {
message(sum(skip.other_pkg), " packages originally removed by groundhog could not be restored because another version of the same \n",
"package was installed, not with groundhog, after the restore point was created:")
message(pasteN(from[skip.other_pkg]))
}
#9 Compare obtained with expected
ip.now<-get.ip('local')
n.purge.success <- sum(!ip.purge$md5 %in% ip.now$md5)
n.add.success <- sum(ip.add$md5 %in% ip.now$md5)
#10 Message
msg = paste0("Restore library results:\n",
" - ", n.purge.success, " out of ", n.purge, " intended packages were successfully removed\n",
" - ", n.add.success," out of ", n.add, " intended packages were successfully restored")
prompt.msg = "\nRestart the R session to finalize the restore process."
#Verify
message1(msg)
infinite.prompt(prompt.msg , valid_answers='uncle' , must.restart = TRUE)
} #IF they answered 'restore
} #End of `restore` function
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.