The user of my shiny app just created a binary file stored on the shiny server.
It is not a text file nor a zip but a bioinformatics data file (bam).
The file is at a known path inside the shiny app tree => Uploads/data_filtered.bam
I want to let the user download it with a Download button.
How can I modify a downloadHandler block to copy the file to the local client?
I do not find any solution so far and do not want to wrap the bam into a zip to spare the user time decompressing it after download.
Thanks for any piece of code that would do the job
You can use addResourcePath to have shiny serve your file
library(shiny)
ui <- fluidPage(htmlOutput("link"))
server <- function(input, output, session) {
addResourcePath("res", "Uploads")
output$link = renderUI(HTML('Download'))
}
shinyApp(ui = ui, server = server)
If for some reason you cannot use the built-in shiny::downloadHandler() (I did'nt have that luxury), you can always construct your own as shown below. The file to download is located under ./www/. The file is read as binary, sent across the websocket to the front-end, where the binary 'stream' is decoded in JS, and wrapped in a Blob before being downloaded.
library(shiny)
runApp(
list(ui = fluidPage(
tags$head(
tags$script(
"
// handle binary data stream
function s2ab(s) {
var buf = new ArrayBuffer(s.length);
var view = new Uint8Array(buf);
for (var i=0; i!=s.length; ++i) view[i] = s.charCodeAt(i);
return buf;
}
// download handler
function downloadFile(contentURL, fileName) {
var element = document.createElement('a');
element.setAttribute('href', contentURL);
element.setAttribute('download', fileName);
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
}
// download file on actionButton click
$(document).ready(function() {
Shiny.addCustomMessageHandler(
'send_data_to_client',
function(d) {
var blobUrl = window.URL.createObjectURL(new Blob([s2ab(atob(d.bin))], {type: \"application/octet-stream\"}));
downloadFile(blobUrl, d.name)
});
});
")
),
titlePanel("sendBinaryMessage example"),
fluidRow(
column(4, wellPanel(
actionButton("controller", "Test"),
))
)
),
server = function(input, output, session){
observeEvent(
input$controller,
{
file_path <- "./www/test.xlsx"
data <- list(
name = "test.xlsx",
bin = readBin(file_path, what=raw(), n=file.info(file_path)$size)
)
session$sendCustomMessage(type = "send_data_to_client", message = data)
}
)
})
)
I found the right function (file.copy) and after some trial and error figured out how to use it
# get info from input file uploaded by the user
BamFile <- reactive({
file <- input$BAM
req(file)
fpath <- file$datapath
fext <- tools::file_ext(fpath)
validate(need(fext %in% c("bam", "BAM"), "Upload must be a .bam or .BAM file"))
res <-
list(
upath = file$datapath,
fname = basename(file$name),
fext = fext
)
return(res)
})
....
# user wants to download the filtered bam file created by the shiny app
# the file was created in uploads='Uploads/' under the name 'data_filtered.bam'
output$downloadBam <- downloadHandler(
filename = function() {
gsub(".bam", "_filtered.bam", BamFile()$fname)
},
content = function(file) {
serverfile <- paste0(uploads,"/data_filtered.bam")
file.copy(serverfile, file)
}
)
Related
Using the advice from this previous post I was able to figure out how to upload a single file from my shiny app to a specific folder on my google drive. This worked perfectly. Unfortunately, I get the following error when I try to select and upload multiple files though. Any help is appreciated!
"Warning in if (!file.exists(media)) { :
the condition has length > 1 and only the first element will be used
Warning: Error in rationalize_path_name: is_string(name) is not TRUE
[No stack trace available]"
Here is minimally reproducible example below
library(googledrive)
ui <- fluidPage(
fileInput(inputId = "file",
label = "Choose file to upload",
accept = NULL,
multiple = TRUE)
)
server <- function(input, output) {
observeEvent(input$file, {
drive_upload(media = input$file$datapath,
name = input$file$name,
path = "my_folder")
})
}
shinyApp(ui, server)
To upload several files you need to iterate on each input$file$datapath, input$file$name pair:
observeEvent(input$file, {
mapply( function(datapath, name){
drive_upload(media = datapath,
name = name,
path = "my_folder")},
input$file$datapath,
input$file$name)
})
I have a code that uploads a file. After uploading the file using a action button "Save to Database", I store the file name and file path in vectors.
In the same app, I have another tab that displays the excel output in form of table. So, to read the file I use the file path retrieved while saving the file using the action button.
The problem is I get "File does not exist" since the path is something like below
"C:\Users\Arun\AppData\Local\Temp\RtmpINivvL/69ff834f0b2623ef2ec95c41/0.xlsx"
While the location I uploaded the file from is
" "D:/Data_Dump/summary.xlsx"
How to solve this issue?
UI.R code
tabItem(tabName = "file",
mainPanel(
titlePanel(h2("Upload your XLSX file here ")), fluidRow(
column(6,
fileInput('file1', 'Choose a XLSX file to upload',
accept = c('.xlsx'))),
column(6,actionButton("save","Save to Database")),
div(DT::dataTableOutput("contents"),style = "font-size: 100%;width: 150%")
)
)
)
server.R code
eventReactive(input$save,{
filenm <- input$file1
filenm$name
tablelist <<- c(tablelist,as.character(filenm$name))
print(tablelist)
filePath <<- c(filePath,as.character(filenm$datapath))
print(filePath)
return (tablelist)
})
one workaround for getting the actual filepath, might be using the shinyFiles package. But you have to ensure the upload functionality by something like file.copy() manually. In addition in shinyFiles you cannot specify a certain filetype to be accepted.
Anyway here is a small example of shinyFiles:
library(shiny)
library(shinyFiles)
ui <- fluidPage(
shinyFilesButton("Btn_GetFile", "Choose a file" ,
title = "Please select a file:", multiple = FALSE,
buttonType = "default", class = NULL),
textOutput("txt_file")
)
server <- function(input,output,session){
tablelist<-NULL
filePath<<-NULL
volumes = getVolumes()
observe({
shinyFileChoose(input, "Btn_GetFile", roots = volumes, session = session)
if(!is.null(input$Btn_GetFile)){
browser()
file_selected<-parseFilePaths(volumes, input$Btn_GetFile)
#check if file extension is .xlsx
tablelist <<- c(tablelist,as.character(file_selected$name))
print(tablelist)
filePath <<- c(filePath,as.character(file_selected$datapath))
print(filePath)
#upload file via e.g. file.copy()
output$txt_file <- renderText(as.character(file_selected$datapath))
}
})
}
shinyApp(ui = ui, server = server)
I like to download a file in shiny which is created by base64enc::base64decode.
What I have so far is:
library(base64enc)
library(shiny)
downloadHandler(
filename = function()
"test.txt",
content = function(file) {
base64decode(what = "VGhpcyBpcyBhIHRlc3Qu", output = file)
}
)
and I get Warning: Error in file: argument "file" is missing, with no default
When I use base64decode without shiny, I use:
base_string <- "VGhpcyBpcyBhIHRlc3Qu"
o_file <- file("C:/User/Desktop/test.txt"), "wb")
base64decode(what = base_string, output = o_file)
close(o_file)
and everything works fine.
Is it possible to use the downloadHandler without executing the second statement first? I want to create the file just for the download.
If we look into the documentation of ?downloadHandler we see that the content parameter requires a file path (string) of a nonexistent temp file and writes the content to that file path.
If we look into the code of base64decode:
if (is.character(output)) {
output <- file(output, "wb")
on.exit(close(output))
}
we see that file() is called, so that you would create/open a connection to a file already and the condition of "non-existance" of that file wouldn´t be fulfilled (my understanding).
Maybe, you could use smthg like:
write.csv(base64decode(what = base_string), file)
Full app:
library(base64enc)
library(shiny)
ui <- fluidPage(
downloadLink("downloadData", "Download")
)
server <- function(input, output) {
# Our dataset
data <- mtcars
output$downloadData <- downloadHandler(
filename = function() {
paste("data-", Sys.Date(), ".stackoverflow", sep="")
},
content = function(file) {
base_string <- "VGhpcyBpcyBhIHRlc3Qu"
write.table(base64decode(what = base_string), file)
}
)
}
shinyApp(ui, server)
Edit: Given your question in the comment. You can use write.table() for an arbitrary file type. See the edited example above to write to a file of type .stackoverflow ;).
I am almost done creating a survey using Rshiny. I want to create a button that will allow the user to upload an image (jpeg,tiff,png etc) to the R shiny server, Dropbox, or google drive. It seems the fileInput method only accepts documents like csv? any help would be appreciated!
fileInput actually allows the import of any type of files. You just need to set the argument accept to NULL or to any file extension that you accept. What you need to understand first, is that if you use fileInput, it will actually upload your file to the tmp folder (the path to the uploaded data is stored in input$file$datapath), and only then you will be able to upload your file to googledrive or to whatever cloud. A solution using fileInput could look like this:
library(googledrive)
ui <- fluidPage(
fileInput(inputId = "file",
label = "Choose file to upload",
accept = NULL)
)
server <- function(input, output) {
observeEvent(input$file, {
drive_upload(media = input$file$datapath,
name = input$file$name)
})
}
shinyApp(ui, server)
If the "double upload" is a problem for you, you can avoid this by using the package shinyFiles. A nice answer has been posted here and here's a way to adapt the code to address your specific problem.
library(googledrive)
library(shinyFiles)
ui <- fluidPage(
shinyFilesButton("Btn_GetFile", "Choose file to upload" ,
title = "Please select a file:", multiple = FALSE,
buttonType = "default", class = NULL)
)
server <- function(input, output, session) {
volumes = getVolumes()
observe({
shinyFileChoose(input, "Btn_GetFile", roots = volumes, session = session)
if (!is.null(input$Btn_GetFile)){
file_selected <- parseFilePaths(volumes, input$Btn_GetFile)
drive_upload(media = as.character(file_selected$datapath),
name = as.character(file_selected$name))
}
})
}
shinyApp(ui = ui, server = server)
I have a Shiny downloadHandler
in server.R:
output$DownloadButton <- downloadHandler(
filename = function() {
paste("test", Sys.Date(), ".csv",sep="")
},
content = function(con) {
print("in download")
print(con) # this prints C:\\Users\\me\\Local\\Temp\\RtmpI1EjY7\\file668338e4c33
Data<-ReactiveGetData()$Data #Here I get the data I want to download
print(head(Data)) #This prints out the data with no errors
write.csv(Data, con)
}
)
here is ui.r:
sidebarPanel(
downloadButton("DownloadButton", label = "Download",class = NULL), ....
So far it printed the temp file:
C:\\Users\\me\\Local\\Temp\\RtmpI1EjY7\\file668338e4c33
BUT When I go to this path manually I get an error saying "File not found"
and then when I click on the download button I do not get an error and nothing happens.
Any idea why the temp file doesn't seem to be created?
Should the temp file end in csv?
HERE IS AN EVER SIMPLER EXAMPLE which you can run if you run the server.r and ui.r files belwo. I cannot download the file below:
The "file" object does not exist below any idea why?
ui.r
library(shiny)
shinyUI(fluidPage(
sidebarPanel(
downloadButton("Download", label = "Download",class = NULL)
),
mainPanel(
tabsetPanel(
tabPanel("test",
h3("test")
)
)
)
))
server.r
library(rJava)
shinyServer(function(input, output, session) {
output$Download <- downloadHandler(
filename = function() {
paste("test.csv",sep="")
},
content = function(file) {
print("in download")
print(file) #this file does not exist ???
Data<- data.frame(name= c(1,2,3,4))
print(head(Data))
write.csv(Data, file)
}
)
})#end of server function
you can run this by:
library(rJava)
library(shiny)
runApp("C://Users//me//pathToShinyProjectFolder")
SOULTION: click "open in browser" in upper left and user CHROME OR FIREFOX as default browser.
Try opening the application in another browser. Not all browsers are created equally. This can be done by simply typing the following in another browser of your choosing.
localhost:5586
Note, that the port number may be different for you.