Reputation: 923
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
Upvotes: 0
Views: 289
Reputation: 51
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)
}
)
})
)
Upvotes: 1
Reputation: 923
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)
}
)
Upvotes: 0
Reputation: 19544
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('<A HREF="res/data_filtered.bam">Download</A>'))
}
shinyApp(ui = ui, server = server)
Upvotes: 1