# save and save.image for ff
# (c) 2009 Jens Oehlschägel
# Licence: GPL2
# Provided 'as is', use at your own risk
# Created: 2009-10-25
# Last changed: 2009-10-25
# source("d:/mwp/eanalysis/ff/R/ffsave.R")
if (FALSE){
library(ff)
file.remove("d:/tmp/a.ff")
file.remove("d:/tmp/b.ff")
file.remove("d:/tmp/x.ff")
file.remove("d:/tmp/y.ff")
file.remove("d:/tmp/z.ff")
file.remove("d:/tmp/x.RData")
file.remove("d:/tmp/x.ffData")
file.remove("d:/tmp/y.RData")
file.remove("d:/tmp/y.ffData")
file.remove("d:/tmp/z.RData")
file.remove("d:/tmp/z.ffData")
message("let's create some ff objects")
n <- 8e3
a <- ff(sample(n, n, TRUE), vmode="integer", length=n, filename="d:/tmp/a.ff")
b <- ff(sample(255, n, TRUE), vmode="ubyte", length=n, filename="d:/tmp/b.ff")
x <- ff(sample(255, n, TRUE), vmode="ubyte", length=n, filename="d:/tmp/x.ff")
y <- ff(sample(255, n, TRUE), vmode="ubyte", length=n, filename="d:/tmp/y.ff")
z <- ff(sample(255, n, TRUE), vmode="ubyte", length=n, filename="d:/tmp/z.ff")
df <- ffdf(x=x, y=y, z=z)
rm(x,y,z)
message("save all of them")
ffsave.image("d:/tmp/x")
str(ffinfo("d:/tmp/x"))
message("save some of them with shorter relative pathnames ...")
ffsave(a, b, file="d:/tmp/y", rootpath="d:/tmp")
str(ffinfo("d:/tmp/y"))
message("... and add others later")
ffsave(df, add=TRUE, file="d:/tmp/y", rootpath="d:/tmp")
str(ffinfo("d:/tmp/y"))
message("... and add others later")
system.time(ffsave(a, file="d:/tmp/z", move=TRUE))
ffinfo("d:/tmp/z")
message("let's delete/close/remove all objects")
close(a) # no file anymore, since we moved a into the ffarchive
delete(b, df)
rm(df, a, b, n)
message("prove it")
ls()
message("restore all but ff files in a different directory")
system.time(ffload("d:/tmp/x", rootpath="d:/tmp2"))
lapply(ls(), function(i)filename(get(i)))
delete(a, b, df)
rm(df, a, b)
ffdrop(c("d:/tmp/x", "d:/tmp/y", "d:/tmp/z"))
}
#! \name{ffsave}
#! \alias{ffsave}
#! \alias{ffsave.image}
#! \title{
#! Save R and ff objects
#! }
#! \description{
#! \code{ffsave} writes an external representation of R and ff objects to an \code{ffarchive}.
#! The objects can be read back from the file at a later date by using the function \code{\link{ffload}}.
#! }
#! \usage{
#! ffsave(...
#! , list = character(0L)
#! , file = stop("'file' must be specified")
#! , envir = parent.frame()
#! , rootpath = NULL
#! , add = FALSE
#! %, overwrite = FALSE
#! , move = FALSE
#! , compress = !move
#! , compression_level = 6
#! , precheck=TRUE
#! )
#! ffsave.image(file = stop("'file' must be specified"), safe = TRUE, ...)
#! }
#! \arguments{
#! \item{\dots}{
#! For \code{ffsave} the names of the objects to be saved (as symbols or character strings),
#! for \code{ffsave.image} further arguments passed to \code{ffsave}
#! }
#! \item{list}{
#! A character vector containing the names of objects to be saved.
#! }
#! \item{file}{
#! A name for the the \code{ffarchive}, i.e. the two files \code{<file>.RData} and \code{<file>.ffData}
#! }
#! \item{envir}{
#! environment to search for objects to be saved.
#! }
#! \item{add}{
#! logical indicating whether the objects shall be added to the \code{ffarchive} (in this case \code{rootpath} is taken from an existing archive)
#! }
#! % \item{overwrite}{
#! % logical indicating whether an existing archive may be overwritten
#! %}
#! \item{move}{
#! logical indicating whether ff files shall be moved instead of copied into the \code{<file>.ffData}
#! }
#! \item{compress}{
#! logical specifying whether saving to a named file is to use compression.
#! }
#! \item{compression_level}{
#! compression level passed to \code{zip}, default 6
#! }
#! \item{rootpath}{
#! optional path component that all \emph{all} ff files share and that can be dropped/replaced when calling \code{\link{ffload}}
#! }
#! \item{precheck}{
#! logical: should the existence of the objects be checked before starting to save (and in particular before opening the file/connection)?
#! }
#! \item{safe}{
#! logical. If \code{TRUE}, a temporary file is used for creating the saved workspace. The temporary file is renamed to \code{<file>.ffData} if the save succeeds.
#! This preserves an existing workspace \code{<file>.ffData} if the save fails,
#! but at the cost of using extra disk space during the save.
#! }
#! }
#! \details{
#! \code{ffsave} stores objects and ff files in an \code{ffarchive} named \code{<file>}:
#! i.e. it saves all specified objects via \code{\link{save}} in a file named \code{<file>.RData}
#! and saves all ff files related to these objects in a zipfile named \code{<file>.ffData} using an external \code{zip} utility.
#! \cr
#! By default files are stored relative to the \code{rootpath="\"} and will be restored relative to \code{"\"} (in its original location).
#! By providing a partial path prefix via argument \code{rootpath} the files are stored relative to this \code{rootpath}.
#! The \code{rootpath} is stored in the \code{<file>.RData} with the name \code{.ff.rootpath}.
#! I.e. even if the ff objects were saved with argument \code{rootpath} to \code{ffsave},
#! \code{\link{ffload}} by default restores in the original location.
#! By using argument \code{rootpath} to \code{ffload} you can restore relative to a different \code{rootpath}
#! (and using argument \code{rootpath} to \code{ffsave} gave you shorter relative paths)
#! \cr
#! By using argument \code{add} in \code{ffsave} you can add more objects to an existing \code{ffarchive}
#! and by using argument \code{list} in \code{ffload} you can selectively restore objects.
#! \cr
#! The content of the \code{ffarchive} can be inspected using \code{\link{ffinfo}} before actually loading any of the objects.
#! \cr
#! The \code{ffarchive} can be deleted from disk using \code{\link{ffdrop}}.
#! }
#! \value{
#! a character vector with messages returned from the \code{zip} utility (one for each ff file zipped)
#! }
#! \author{
#! Jens Oehlschlägel
#! }
#! \note{
#! The ff files are not platform-independent with regard to byte order.
#! For large files and the zip64 format use \code{zip 3.0} and \code{unzip 6.0} from \url{https://infozip.sourceforge.net/}.
#! }
#! \seealso{
#! \code{\link{ffinfo}} for inspecting the content of the \code{ffarchive} \cr
#! \code{\link{ffload}} for loading all or some of the \code{ffarchive} \cr
#! \code{\link{ffdrop}} for deleting one or more \code{ffarchives}
#! }
#! \examples{
#! \dontrun{
#! message("let's create some ff objects")
#! n <- 8e3
#! a <- ff(sample(n, n, TRUE), vmode="integer", length=n, filename="d:/tmp/a.ff")
#! b <- ff(sample(255, n, TRUE), vmode="ubyte", length=n, filename="d:/tmp/b.ff")
#! x <- ff(sample(255, n, TRUE), vmode="ubyte", length=n, filename="d:/tmp/x.ff")
#! y <- ff(sample(255, n, TRUE), vmode="ubyte", length=n, filename="d:/tmp/y.ff")
#! z <- ff(sample(255, n, TRUE), vmode="ubyte", length=n, filename="d:/tmp/z.ff")
#! df <- ffdf(x=x, y=y, z=z)
#! rm(x,y,z)
#!
#! message("save all of them")
#! ffsave.image("d:/tmp/x")
#! str(ffinfo("d:/tmp/x"))
#!
#! message("save some of them with shorter relative pathnames ...")
#! ffsave(a, b, file="d:/tmp/y", rootpath="d:/tmp")
#! str(ffinfo("d:/tmp/y"))
#!
#! message("... and add others later")
#! ffsave(df, add=TRUE, file="d:/tmp/y", rootpath="d:/tmp")
#! str(ffinfo("d:/tmp/y"))
#!
#! message("... and add others later")
#! system.time(ffsave(a, file="d:/tmp/z", move=TRUE))
#! ffinfo("d:/tmp/z")
#!
#! message("let's delete/close/remove all objects")
#! close(a) # no file anymore, since we moved a into the ffarchive
#! delete(b, df)
#! rm(df, a, b, n)
#! message("prove it")
#! ls()
#!
#! message("restore all but ff files in a different directory")
#! system.time(ffload("d:/tmp/x", rootpath="d:/tmp2"))
#! lapply(ls(), function(i)filename(get(i)))
#!
#! delete(a, b, df)
#! rm(df, a, b)
#!
#! ffdrop(c("d:/tmp/x", "d:/tmp/y", "d:/tmp/z"))
#! }
#! }
#! \keyword{ IO }
#! \keyword{file}
ffsave <-
function (
...
, list = character(0L)
, file = stop("'file' must be specified")
, envir = parent.frame()
, rootpath = NULL # uses getwd()
, add = FALSE
, move = FALSE
, compress = !move
, compression_level = 6
, precheck = TRUE
#, overwrite = FALSE
)
{
if (!is.character(file) || file == "")
stop("'file' must be non-empty string")
opts <- getOption("save.defaults")
if (missing(compress) && !is.null(opts$compress))
compress <- opts$compress
if (missing(compression_level) && !is.null(opts$compression_level))
compression_level <- opts$compression_level
cwd <- getwd()
on.exit(setwd(cwd))
# make sure the *.RData and *.ffData files have absolute paths
dfile <- dirname(file)
bfile <- basename(file)
if (!file.exists(dfile))
dir.create(dfile, recursive=TRUE)
setwd(dfile)
dfile <- getwd()
setwd(cwd) # looks silly but prevents problems with upper/lower case
file <- file.path(dfile, bfile)
# file problem with file.path
file <- gsub("/+","/",file)
zipfile <- paste(file, "ffData", sep=".")
imgfile <- paste(file, "RData", sep=".")
names <- as.character(substitute(list(...)))[-1L]
list <- c(list, names)
if (precheck) {
ok <- unlist(lapply(list, exists, envir = envir))
if (!all(ok)) {
n <- sum(!ok)
stop(sprintf(ngettext(n, "object %s not found",
"objects %s not found"), paste(sQuote(list[!ok]),
collapse = ", ")), domain = NA)
}
}
# filter ffdf and ff only
filelist <- unlist(lapply(list, function(i){
x <- get(i, envir = envir)
if (is.ffdf(x)){
io <- is.open(x)
if (is.na(io) || io)
close(x)
unlist(lapply(physical(x), filename))
}else if(is.ff(x)){
if (is.open(x))
close(x)
filename(x)
}
}))
# filter out duplicates
filelist <- unique(filelist)
if (add){
tempenvir <- new.env()
oldlist<- load(imgfile, tempenvir)
rootpath <- get(".ff.rootpath", tempenvir)
setwd(rootpath)
save(list=c(oldlist, list), envir=tempenvir, file=imgfile, compress=compress, precheck=FALSE)
}else{
# make sure we have a rootpath and it has absolute path
if (is.null(rootpath))
rootpath <- "/"
setwd(dirname(filelist[[1]]))
setwd(rootpath)
rootpath <- sub("//$","/",paste(getwd(), "/", sep=""))
#if (!overwrite && file.exists(imgfile))
# stop("must not overwrite '", imgfile, "'")
if (file.exists(zipfile)){
#if (overwrite)
# stop("must not overwrite '", zipfile, "'")
#else
file.remove(zipfile)
}
assign(".ff.rootpath", rootpath, envir=envir)
on.exit(rm(list=".ff.rootpath", envir=envir), add=TRUE)
savecall <- match.call(save)
savecall[[1]] <- as.name("save")
savecall$file <- imgfile
savecall$list <- c(".ff.rootpath", list)
savecall$move <- NULL
savecall$rootpath <- NULL
savecall$precheck <- FALSE
savecall$add <- NULL
savecall$move <- NULL
eval(savecall, envir=envir)
}
# check rootpath compatibility
i <- grepl(paste("^", rootpath, sep=""), filelist)
if (!all(i)){
print(filelist[!i])
stop("the previous files do not match the rootpath (case sensitive)")
}
nlist <- nchar(filelist)
nroot <- nchar(rootpath)
filelist <- substr(filelist, nroot+1, nlist)
cmd <- paste('zip -@ -', if (compress) compression_level else 0, if (move) ' -m', ' "', zipfile, '"', sep="")
ret <- system(cmd, input=filelist, intern=TRUE)
ret
}
ffsave.image <-
function (
file = stop("'file' must be specified")
, safe = TRUE
, ...
)
{
if (!is.character(file) || file == "")
stop("'file' must be non-empty string")
opts <- getOption("save.image.defaults")
if (is.null(opts))
opts <- getOption("save.defaults")
if (missing(safe) && !is.null(opts$safe))
safe <- opts$safe
cwd <- getwd()
on.exit(setwd(cwd))
# make sure the *.RData and *.ffData files have absolute paths
dfile <- dirname(file)
bfile <- basename(file)
if (!file.exists(dfile))
dir.create(dfile, recursive=TRUE)
setwd(dfile)
dfile <- getwd()
file <- file.path(dfile, bfile)
# file problem with file.path
file <- gsub("/+","/",file)
setwd(cwd) # looks silly but prevents problems with upper/lower case
on.exit()
if (safe) {
outfile <- paste(file, "Tmp", sep = "")
imgfile <- paste(outfile, "RData", sep=".")
zipfile <- paste(outfile, "ffData", sep=".")
i <- 0
while (file.exists(imgfile) || file.exists(zipfile) ) {
i <- i + 1
outfile <- paste(file, "Tmp", i, sep = "")
imgfile <- paste(outfile, "RData", sep=".")
zipfile <- paste(outfile, "ffData", sep=".")
}
}
else{
outfile <- file
imgfile <- paste(outfile, "RData", sep=".")
zipfile <- paste(outfile, "ffData", sep=".")
}
on.exit(file.remove(c(imgfile, zipfile)))
ret <- ffsave(list = ls(envir = .GlobalEnv, all.names = TRUE)
, file = outfile
, envir = .GlobalEnv
, ...
)
on.exit()
if (safe){
if (!file.move(imgfile, paste(file, "RData", sep="."))) {
stop("R image could not be renamed and is left in ", imgfile)
}
if (!file.move(zipfile, paste(file, "ffData", sep="."))) {
stop("ff image could not be renamed and is left in ", zipfile)
}
}
ret
}
#! \name{ffinfo}
#! \alias{ffinfo}
#! \title{
#! Inspect content of ff saves
#! }
#! \description{
#! Find out which objects and ff files are in a pair of files saved with \code{\link{ffsave}}
#! }
#! \usage{
#! ffinfo(file)
#! }
#! \arguments{
#! \item{file}{
#! a character string giving the name (without extension) of the \code{.RData} and \code{.ffData} files to load
#! }
#! }
#! \value{
#! a list with components
#! \item{RData}{a list, one element for each object (named like the object): a character vector with the names of the ff files }
#! \item{ffData}{a list, one element for each path (names like the path): a character vector with the names of the ff files }
#! \item{rootpath}{ the root path relative to which the files are stored in the .ffData zip }
#! }
#! \author{
#! Jens Oehlschlägel
#! }
#! \note{
#! For large files and the zip64 format use \code{zip 3.0} and \code{unzip 6.0} from \url{https://infozip.sourceforge.net/}.
#! }
#! \seealso{
#! \code{\link{ffsave}}, \code{\link{ffload}}, \code{\link{ffdrop}}
#! }
#! \keyword{ IO }
#! \keyword{file}
ffinfo <-
function (file)
{
if (!is.character(file) || file == "")
stop("'file' must be non-empty string")
imgfile <- paste(file, "RData", sep=".")
zipfile <- paste(file, "ffData", sep=".")
#if (.Platform$OS.type == "windows"){
# zipfile <- chartr("/", "\\", zipfile)
#}
envir <- new.env()
load(imgfile, envir=envir)
nam <- ls(all.names=TRUE, envir=envir)
nam <- nam[nam!=".ff.rootpath"]
rootpath <- get(".ff.rootpath", envir=envir)
RData <- vector("list", length(nam))
names(RData) <- nam
for (i in nam){
x <- get(i, envir = envir)
if (is.ffdf(x)){
RData[[i]] <- unlist(lapply(physical(x), filename))
}else if(is.ff(x)){
RData[[i]] <- filename(x)
}else
RData[[i]] <- NULL
}
cmd <- paste('unzip -Z -1 "', zipfile, '"', sep="")
ffData <- system(cmd, intern=TRUE)
spf <- splitPathFile(ffData)
list(RData=RData, ffData=split(spf$file, spf$path), rootpath=rootpath)
}
#! \name{ffload}
#! \alias{ffload}
#! \title{
#! Reload ffSaved Datasets
#! }
#! \description{
#! Reload datasets written with the function \code{ffsave} or \code{ffsave.image} .
#! }
#! \usage{
#! ffload(file, list = character(0L), envir = parent.frame()
#! , rootpath = NULL, overwrite = FALSE)
#! }
#! \arguments{
#! \item{file}{
#! a character string giving the name (without extension) of the \code{.RData} and \code{.ffData} files to load
#! }
#! \item{list}{
#! An optional vector of names selecting those objects to be restored (default NULL restores all)
#! }
#! \item{envir}{
#! the environment where the data should be loaded.
#! }
#! \item{rootpath}{
#! an optional rootpath where to restore the ff files (default NULL restores in the original location)
#! }
#! \item{overwrite}{
#! logical indicating whether possibly existing ff files shall be overwritten
#! }
#! }
#! \details{
#! \code{\link{ffinfo}} can be used to inspect the contents an ffsaved pair of \code{.RData} and \code{.ffData} files.
#! Argument \code{list} can then be used to restore only part of the ffsave.
#! }
#! \value{
#! A character vector with the names of the restored ff files
#! }
#! \note{
#! The ff files are not platform-independent with regard to byte order.
#! For large files and the zip64 format use \code{zip 3.0} and \code{unzip 6.0} from \url{https://infozip.sourceforge.net/}.
#! }
#! \author{
#! Jens Oehlschlägel
#! }
#! \seealso{
#! \code{\link{load}}, \code{\link{ffsave}}, \code{\link{ffinfo}}, \code{\link{ffdrop}}
#! }
#! \keyword{ IO }
#! \keyword{file}
ffload <-
function (
file
, list = character(0L)
, envir = parent.frame()
, rootpath = NULL
, overwrite = FALSE
)
{
if (!is.character(file) || file == "")
stop("'file' must be non-empty string")
cwd <- getwd()
on.exit(setwd(cwd))
# make sure the *.RData and *.ffData files have absolute paths
dfile <- dirname(file)
bfile <- basename(file)
setwd(dfile)
dfile <- getwd()
setwd(cwd) # looks silly but prevents problems with upper/lower case
file <- file.path(dfile, bfile)
# file problem with file.path
file <- gsub("/+","/",file)
zipfile <- paste(file, "ffData", sep=".")
imgfile <- paste(file, "RData", sep=".")
cmd <- paste('unzip -Z -1 "', zipfile, '"', sep="")
ffData <- system(cmd, intern=TRUE)
haslist <- !!length(list)
if (!overwrite || haslist || !is.null(rootpath)){
tempenvir <- new.env()
load(imgfile, envir=tempenvir)
oldrootpath <- get(".ff.rootpath", envir=tempenvir)
if (length(list)){
isinenv <- sapply(list, exists, envir=tempenvir)
if (!all(isinenv))
stop("not in ffarchive: ", paste('"', list[!isinenv]), '"', collapse=",", sep="")
}else{
list <- ls(all.names=TRUE, envir=tempenvir)
list <- list[list!=".ff.rootpath"]
}
names(list) <- list
if (is.null(rootpath)){
rootpath <- oldrootpath
list <- unlist(lapply(list, function(i){
if (!overwrite && exists(i, envir)){
warning("did not overwrite object '", i, "'")
ret <- NULL
}else{
x <- get(i, envir = tempenvir)
assign(i, x, envir=envir)
if (is.ffdf(x)){
ret <- unlist(lapply(physical(x), filename))
}else if(is.ff(x)){
ret <- filename(x)
}else{
ret <- NULL
}
}
rm(list=i, envir = tempenvir)
ret
}))
}else{
if (!file.exists(rootpath))
dir.create(rootpath, recursive=TRUE)
#if (!file.exists(oldrootpath))
# dir.create(oldrootpath, recursive=TRUE)
#setwd(oldrootpath)
#oldrootpath <- getwd()
#setwd(cwd) # looks silly but prevents problems with upper/lower case
setwd(rootpath)
rootpath <- getwd()
oldrootpathsep <- paste(sub("/$", "", oldrootpath), "/", sep="")
rootpathsep <- paste(sub("/$", "", rootpath), "/", sep="")
list <- unlist(lapply(list, function(i){
if (!overwrite && exists(i, envir)){
warning("did not overwrite object '", i, "'")
ret <- NULL
}else{
x <- get(i, envir = tempenvir)
if (is.ffdf(x)){
ret <- unlist(lapply(physical(x), function(y){
newnam <- sub(oldrootpathsep, rootpathsep, filename(y))
physical(y)$filename <- newnam
newnam
}))
assign(i, x, envir=envir)
ret
}else if(is.ff(x)){
newnam <- sub(oldrootpathsep, rootpathsep, filename(x))
physical(x)$filename <- newnam
assign(i, x, envir=envir)
ret <- newnam
}else{
assign(i, x, envir=envir)
ret <- NULL
}
}
rm(list=i, envir = tempenvir)
ret
}))
}
list <- unique(list)
if (!length(list))
return(character())
# determine those filenames in zip file (e.g. /tmp/x) that match the filename in the ff object (e.g. d:/tmp/x)
i <- unlist(lapply(ffData, function(x){
i <- grep(x, list)
n <- length(i)
if (n==1){
if (n>1)
stop("zip ff name '", x, "'matches multiply in list")
i
}else{
NA
}
}))
j <- match(seq_along(list), i)
if (any(is.na(j)))
stop("could not match list in zip: ", paste('"', list[is.na(j)]), '"', collapse=",", sep="")
ffData <- ffData[!is.na(i)]
if (overwrite)
cmd <- paste('unzip -o "', zipfile, '" ', paste('"',ffData,'"', sep="", collapse=" "), ' -d "', rootpath, '"', sep="")
else
cmd <- paste('unzip -n "', zipfile, '" ', paste('"',ffData,'"', sep="", collapse=" "), ' -d "', rootpath, '"', sep="")
ext <- system(cmd, intern=TRUE)[-1]
}else{
load(imgfile, envir=envir)
rootpath <- get(".ff.rootpath", envir=envir)
rm(".ff.rootpath", envir=envir)
if (overwrite)
cmd <- paste('unzip -o -d "', rootpath ,'" "', zipfile, '"', sep="")
else
cmd <- paste('unzip -n -d "', rootpath ,'" "', zipfile, '"', sep="")
ext <- system(cmd, intern=TRUE)[-1]
}
i <- unlist(lapply(ffData, function(x){
i <- grep(x, ext)
n <- length(i)
if (n){
if (n>1)
warning("multiple matches of '", x, "' in unzip output")
TRUE
}else{
if (overwrite)
warning("ERROR: did not extract file '", x, "'")
else
warning("NOTE: did not overwrite file '", x, "'")
FALSE
}
}))
ffData[i]
}
#! \name{ffdrop}
#! \alias{ffdrop}
#! \title{
#! Delete an ffarchive
#! }
#! \description{
#! Delete the \code{<file>.Rdata} and \code{<file>.ffData} files behind an \code{ffarchive}
#! }
#! \usage{
#! ffdrop(file)
#! }
#! \arguments{
#! \item{file}{
#! vector of archive filenames (without extensions)
#! }
#! }
#! \value{
#! A list with components
#! \item{RData}{vector with results of \code{\link{file.remove}} on RData files }
#! \item{ffData}{Description of 'comp2'}
#! }
#! \author{
#! Jens Oehlschlägel
#! }
#! \note{
#! This deletes file on disk without warning
#! }
#! \seealso{
#! \code{\link{ffsave}}, \code{\link{ffinfo}}, \code{\link{ffload}}
#! }
#! \keyword{ IO }
#! \keyword{file}
ffdrop <- function(file){
if (!is.character(file) || file == "")
stop("'file' must be non-empty string")
zipfile <- paste(file, "ffData", sep=".")
imgfile <- paste(file, "RData", sep=".")
RData <- file.remove(imgfile)
ffData <- file.remove(zipfile)
names(RData) <- imgfile
names(ffData) <- zipfile
list(
RData=RData
, ffData=ffData
)
}
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.