Tempfiles when using shiny - r

I'm developing a shiny application where the user can upload a file and select X parameters, after that he press a button and it generates 5 plots (ggplot2 and barplot) and also a dynamic data table (DT). Also, I want to put my shiny app into a linux server.
I'm using tempfiles() for each file I'm using to create the plots and also the DT.
After that, my question is:
When the user closes the shiny app (close the window), do the tempfiles autodelete?
If not, what can I do to delete the tempfiles?
My tries:
session$onSessionEnded(function() {
if (!is.null(x1)) {
file.remove(x1)
}
if (!is.null(x2)) {
file.remove(x2)
}
if (!is.null(x3)) {
file.remove(x3)
}
if (!is.null(x4)) {
file.remove(x4)
}
if (!is.null(xx)) {
file.remove(xx)
}
})
Or:
session$onSessionEnded(function() {
files <- list.files(tempdir(), full.names = T, pattern = "^file")
file.remove(files)
})
With that code I delete the tempfiles when user presses the button once, and if the user presses the button more than 1 time then the window closes, and it will only delete the last generated files. The second part deletes all files at the temp dir but that affects to the other users?(I think yes so that's why I need another solution).
The .png tempfiles generated by ggplot and barplot doesn't autodelete.
My worry is that if the tempfiles won't autodelete and the linux server will collapse because of a lot of tempfiles.
Hope you can solve my doubts. Att Joan.

You can use the deleteFile=TRUE parameter if you want a render function to automatically delete your temporary files:
shinyServer(function(input, output, clientData) {
output$myImage <- renderImage({
# A temp file to save the output.
# This file will be removed later by renderImage
outfile <- tempfile(fileext='.png')
# Generate the PNG
png(outfile, width=400, height=300)
hist(rnorm(input$obs), main="Generated in renderImage()")
dev.off()
# Return a list containing the filename
list(src = outfile,
contentType = 'image/png',
width = 400,
height = 300,
alt = "This is alternate text")
}, deleteFile = TRUE)
})
A temp file is created to save the output, and that file is later automatically removed because of the deleteFile=TRUE argument.
The default Shiny (shiny.R) also has a built-in mechanism that clear file upload directories if that's your concern. The following code remove the upload directory when the session ends:
registerSessionEndCallbacks = function() {
# This is to be called from the initialization. It registers functions
# that are called when a session ends.
# Clear file upload directories, if present
self$onSessionEnded(private$fileUploadContext$rmUploadDirs)
}
Another point regarding manually deleting your temp files (as what you were attempting): the plot will have to render every time the user switched to another tab or resize his / her browser window, so if you're manually deleting the file, it may be inefficient since it needs to be re-rendered again. The onSessionEnded solution is nicer as it confirms that the session has ended.
session$onSessionEnded(function() {
if (!is.null(input$file1)) {
file.remove(input$file1$datapath)
}
})

Related

Save a ggplot generated scatterplot in Shiny as a PDF file without saving extra files

I had trouble to generate a PDF file from a scatterplot created trough ggplot in a ShinyApp. I was succesful with a similar approach as the one from the answer from user juba to this stackoverflow question, but then the nearPoints() function I was using didn't work and gave an error message saying something about not being able to find the coordinfo. Then I used the ggsave option that Yihui Xie recommended, but with this strategy I'm getting files saved in the folder where my ShinyApp resides. I'm worried that if I try to use this in my ShinyApp hosted in the shinyapps.io site, there would be problems when trying to save these transient and temporal files. I also tried removing the file after the download is done but, anytime the plot is shown the file is created, so the file is created again after the copied file is downloaded. Here is only a sample (some important lines) of the code I used to allow for the download of the plot as a PDF file:
#### User Interface ----
# Show scatterplot with clicking capability
plotOutput(outputId = "scatterplot", click = "plot_click")
# Show data table where data points clicked will be shown
dataTableOutput(outputId = "datatable")
# Download button
downloadButton('dlScatPlot', 'Download plot as PDF')
# Server ----
# Wrap the creation of the scatterplot in a function so the plot can be
# downloaded as PDF
makeScatPlot <- function() {
## some code to generate a ggplot plot
}
# Create the scatterplot object the plotOutput function is expecting
output$scatterplot <- renderPlot({
# The file saved as ggsave originally will be first saved in the server, and
# then in the client side if the Download Button is used
filename <- paste('scatterPlot_', Sys.Date(), '.pdf', sep='')
ggsave(filename, makeScatPlot(), width = 11, height = 4, dpi = 300, units = "in")
makeScatPlot()
})
# Create data table showing points that have been clicked
output$datatable <- DT::renderDataTable({
rows <- nearPoints(df1, input$plot_click) %>%
select(sample_ID, compound, DOI)
DT::datatable(rows, rownames = FALSE)
})
output$dlScatPlot <- downloadHandler(
filename = function() {
paste('scatPlot_', Sys.Date(), '.pdf', sep='')
},
content = function(file) {
file.copy(paste('scatPlot_', Sys.Date(), '.pdf', sep=''), file, overwrite = TRUE)
# To avoid the accumulation of PDFs in the server
file.remove(paste('scatPlot_', Sys.Date(), '.pdf', sep=''))
}
)
I guess it can cause trouble if I upload a ShinyApp script to shinyapps.io that creates one PDF file each time the plot is rendered, right?
Instead of saving files to a specific path, you can save them as temporary file using tempfile(fileext = ".pdf"). Those files will be automatically removed once the session is over. So no need to remove them manually.
I finally came out with an obvious answer. I wasn't doing the straightforward thing that was call the ggsave in the downloadHandler call because I was using the Yihui answer directly. So, finally I just don't create the file inside the renderPlot() function, but in the downloadHandler where it rightfully should be.
# Create the scatterplot object the plotOutput function is expecting
output$scatterplot <- renderPlot({
makeScatPlot()
})
# Create the button to download the scatterplot as PDF
output$dlScatPlot <- downloadHandler(
filename = function() {
paste('scatterPlot_', Sys.Date(), '.pdf', sep='')
},
content = function(file) {
ggsave(file, makeScatPlot(), width = 11, height = 4, dpi = 300, units = "in")
}
)
Using the above code, everything (including the nearPoints call) works now :)

R/Shiny: Download multiple files (zip) from a folder on the server

I would like to create a zip archive (containing several xlsx files) and save it locally. The files are stored in a folder on the server side.
The user selects the files to zip using a checkboxInput.
Here the code for the checkbox:
get.files <- reactive({
list.files("output_file/")
})
obsList <- list()
output$links_list <- renderUI({
lapply(as.list(1:length(get.files())), function(i)
{
btName <- get.files()[i]
# creates an observer only if it doesn't already exists
if (is.null(obsList[[btName]])) {
obsList[[btName]] <<- btName
}
fluidRow(checkboxInput(btName, get.files()[i]) )
})
})
The checkboxes are created dynamically reading the content in the folder ("output_file/"). Near each checkbox there is the name of the file.
The function for the download is:
output$downloadzip<-downloadHandler(
filename = function(){
paste0("Extract.zip")
},
content = function(file){
files <- NULL;
for (i in 1:length(obsList)){
if(input[[obsList[[i]]]])
files <- c(paste("output_file/",obsList[[i]],sep=""),files)
}
#create the zip file
zip(file,files)
},
contentType = "application/zip"
)
The function creates an array of filenames (files) using only the names of files that have been checked.
I have created also a function that allows me to check that only the right files are chosen:
tempText <- eventReactive({input$TempTest},{
l<-c()
for (i in 1:length(obsList)){
if(input[[obsList[[i]]]])
l<-c(l,paste("output_file/",obsList[[i]],sep=""))
}
return(paste(l) )
},
ignoreInit = TRUE)
output$Temp <- renderPrint({ tempText()})
This function renders correctly the strings with the name of the files.
The error that I get when I try to download the zip file is:
sh: : command not found
Can someone help me to fix this?
I have fixed the problem.
The issue is with the zip function that for some reasons doesn't work properly on my server.
The solution is to use directly the system2 function (that is called internally by zip).
Instead of
zip(file,files)
I have to use:
system2("zip", args=(paste(file,files,sep=" ")))

Write.csv using Action buttons

So basically I am trying to make a small setup of sorts and once a certain analysis is done, I would want to export a certain dataset generated to a predefined location and a predefined name (based on the inputs selected earlier). For this purpose, I used the action button which when clicked does this,
observeEvent(input$export_button, {
write.csv(input_dummy_data4ads,paste0("Dummy Files/",unique(input_dummy_data4ads$Dependent_Variable),"_", unique(input_dummy_data4ads$Model_Type),"_", unique(input_dummy_data4ads$AGM),".csv"),row.names = F,na="")
})
The issue here is that if I click the action button once, it generates the desired csv file and at the desired location too. But after pressing it once, it takes the value of 1 (input$export_button) so when I select a new set of inputs using the radio buttons and generate a new plot based on that (by clicking another action button), the app saves a new csv file with a new name (based on the new inputs) at the desired location. What I am trying to do is to reset the value of the action button so that the new csv file is created only when I click it every time.
I tried to understand this but could not incorporate it https://github.com/rstudio/shiny/issues/167
There are specific functions in shiny for this, use downloadButton in your ui and downloadHandler in server.
server.R:
output$export_data <- downloadHandler(
filename = function() {
paste0("Dummy Files/", unique(input_dummy_data4ads$Dependent_Variable), "_", unique(input_dummy_data4ads$Model_Type), "_", unique(input_dummy_data4ads$AGM), ".csv")
},
content = function(con) {
write.csv(input_dummy_data4ads, con, row.names = F, na = "")
}
)
ui.R:
downloadButton("export_data", "Export")

Shiny SelectInput to generate filepath for renderImage

I have a series of ".jpg" files within my www folder of my script. I want to have renderImage to render the image with the filename that is named "dateplatform.jpg" based on the inputs (date, platform) of my UI. When I try the script below within my server.r file the app isn't displaying any image. Any thoughts?
ui (partial)
fluidRow(
column(width=12,
imageOutput("platformimage")
)
)
server (partial)
filename <- reactive ({
paste(input$date, input$platform, sep="")
})
output$platformimage <- reactive({
renderImage({
list(src = filename(),
width = 600,
height = 600)
},deleteFile = FALSE)
})
filename has to have at least the extension attached to it, and it's probably safer to normalizePath it:
filename <- reactive({
normalizePath(file.path(paste0(input$date, input$platform, '.jpg')))
})
If this fail, it's probably because of the server can't find the file. Check the path created by filename(), and fix inside file.path()
Hope this helps.

Re-upload same file shiny R

I am following the simple file upload example from shiny gallery, but with a slight modification. I am required to modify the csv files locally, and see the changes reflected on the UI. However, I believe that this is not possible unless we poll for any changes in the source.
Therefore, I simplify the problem, by allowing re-uploading of the file. But, this is also not happening in Shiny. Once a file "file1.csv" is uploaded, i cannot upload the same file again. I have to upload a different file "file2.csv" and then once again the original file "file1.csv".
This is just time consuming, and I am wondering if anyone has come across such an issue, and possibly found a solution for it.
Adding a jquery to clear the value of the fileInput does the trick.
...
fileInput("csvInput", "Upload CSV file", multiple = TRUE,
accept=c('text/csv',
'text/comma-separated-values,text/plain',
'.csv')),
tags$script('$( "#csvInput" ).on( "click", function() { this.value = null; });'),
...
Actually it is one of the shortcomings of Shiny's fileInput module: it stays still if the same file was selected again, and there is no force-upload option built-in.
To enforce re-uploading, the basic idea is:
After each time uploading, keep the uploaded data in a reactiveValues as storage.
Apply a new fileInput to replace original one
Show a successfully upload message under new fileInput for a better user experience.
In ui.R,using
uiOutput('fileImport')
In server.R:
# reactive storage
v <- reactiveValues()
# fileInput index
v$fileInputIndex <- 1
updateFileInput <- function (name = NULL){
# update output with a new fileInput module
output$fileImport <- renderUI({
index <- isolate(v$fileInputIndex)
result <- div()
result <- tagAppendChild(
result,
fileInput(paste0('file', index), 'Choose CSV File',accept=c('text/csv','text/comma-separated-values,text/plain','.csv'))
)
# show a message of successful uploading
if(!is.null(name)){
result <- tagAppendChild(
result,
div(name," upload complete")
)
}
result
})
}
dataInputRaw <- reactive({
# equals to `input$file1` when initialized
inFile <- input[[paste0('file', v$fileInputIndex)]]
# TICKY PART:
# 1. If initialized, `inFile` and `v$data` are both `NULL`
# 2. After each uploading, new `fileInput` is applied and
# we want to keep previous updated data.
# It also prevent recursive creation of new `fileInput`s.
if (is.null(inFile)){
return(v$data)
}
# load as data frame
v$data <- data.frame(read.csv(inFile$datapath))
# if file successfuly uploaded, increate the index
# then upload `fileInput` with successful message
if (!is.null(v$data)){
v$fileInputIndex <- v$fileInputIndex + 1
updateFileInput(name = inFile$name)
}
# return data
v$data
})
# init
updateFileInput()
I have tested this snippet and it works.

Resources