splaisan
splaisan

Reputation: 923

download binary file from shiny server

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

Answers (3)

Japie
Japie

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

splaisan
splaisan

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

HubertL
HubertL

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

Related Questions