Using www folder: Save and display pdf

I have a shiny app where the user selects an item (there are multiple items to choose from), then a pdf from a private Amazon S3 bucket is saved, and the saved pdf is shown to the user via an iframe.

code within server to get pdf, write it to www, and display it to the user:

output$test_result <- renderUI({
test_result() %>%
          str_replace("https://s3.amazonaws.com", "s3:/") %>%
          get_object() %>%
          writeBin("www/test_result.pdf")
        tags$iframe(style = "height:1400px; width:100%", src = "test_result.pdf")
})

A download button is displayed and the user can download the pdf file if they want

  output$download_test_results <- downloadHandler(
    filename = function() {
      "test_result.pdf"
    },
    content = function(file) {
      file.copy("www/test_result.pdf", file)
    }
  )

I'm confused about how this will work with multiple users. Let's say user 1 selects item 1 and the pdf (test_result.pdf) is written to the www folder. What if another user (user 2) is using the app at the same time and selects item 2, which writes a pdf (the pdf is different but will still have the name "test_result.pdf") to the www folder. Is there a separate www folder for each user session and therefore multiple users can't impact each other's sessions? Or will user 2's selection impact user 1?

Is the www folder the right place to save files? Once the user's session ends, the files can be deleted.

2 Likes

Could you please add code formatting? It just makes the text much more readable, and makes easier for others to help you out! :+1:

Here's our FAQ on how to do so:

Thanks

Whew - I know it's been a while. Not sure if you have solved this problem or not!

Basically, since you are writing to disk, the problem you envision is correct - these users will overwrite one another (or try), and you will experience difficulties / problems.

The solution is writing the files with unique names. Also, and especially since these files do not need to persist, the classic way to do this is with base::tempfile() or fs::file_temp() (I prefer the latter, because the fs package makes OS file-munging stuff way easier).

What this does is it provides you with a unique filename in the R session's temp space (it gets cleaned up after the R session dies). You will also notice that it provides a unique location each time it is fired.

Note that paths will look different depending on your OS, and that you can specify tmpdir or tmp_dir to specify where the filenames are generated.

tempfile("myfile", tmpdir = tempdir(), fileext = ".pdf")
#> [1] "/var/folders/ry/3s4j_27s4snf2kpq08g_sblm0000gn/T//RtmpK8Z3t0/myfile7026575aa3a6.pdf"
tempfile("myfile", tmpdir = tempdir(), fileext = ".pdf")
#> [1] "/var/folders/ry/3s4j_27s4snf2kpq08g_sblm0000gn/T//RtmpK8Z3t0/myfile70267fae1417.pdf"
tempfile("myfile", tmpdir = tempdir(), fileext = ".pdf")
#> [1] "/var/folders/ry/3s4j_27s4snf2kpq08g_sblm0000gn/T//RtmpK8Z3t0/myfile70267da9337d.pdf"
fs::file_temp("myotherfile", tmp_dir = tempdir(), ext = ".pdf")
#> /var/folders/ry/3s4j_27s4snf2kpq08g_sblm0000gn/T/RtmpK8Z3t0/myotherfile70266f7393fe.pdf
fs::file_temp("myotherfile", tmp_dir = tempdir(), ext = ".pdf")
#> /var/folders/ry/3s4j_27s4snf2kpq08g_sblm0000gn/T/RtmpK8Z3t0/myotherfile7026cf941dc.pdf
fs::file_temp("myotherfile", tmp_dir = tempdir(), ext = ".pdf")
#> /var/folders/ry/3s4j_27s4snf2kpq08g_sblm0000gn/T/RtmpK8Z3t0/myotherfile70264052d92b.pdf

Created on 2018-08-06 by the reprex
package
(v0.2.0).

2 Likes

Thanks so much for your response! This is helpful. I did think about doing it this way, however what I can't figure out is how to call the temp file to display it. In the code above, I display the pdf using an iframe. Within that iframe, I have to specify the src, but if I generate a temp file, I don't know what to put in that src reference because I don't know the file name of the temp file. Hopefully that makes sense. Do you know of anyway to get around that problem?

1 Like

I think I understand correctly. Typically, I do something like this:

myfile <- fs::file_temp("myfile", ext = ".csv")
readr::write_csv(iris, myfile)

# use the variable where I need it
print(myfile)
#> /var/folders/ry/3s4j_27s4snf2kpq08g_sblm0000gn/T/RtmpYYi5Cx/myfiled30246c21332.csv

Created on 2018-08-06 by the reprex
package
(v0.2.0).

The only thing you will need to be sure about is that the file can be rendered in the browser (i.e. the temp directory may not be accessible to an iframe). But you can always specify the directory and then still use the function for its temp / unique filename.

1 Like

Ah ok, I think I understand. I'll give that a try and see if it works. If I remember correctly, the only directory I could get to work with the iframe was the www directory, but I can put the temp files there. Thanks for the help!

My pleasure! Please do report back how it went! :smile:

Ok, I got it working!

One last question...when I was testing locally, the files created in the "www" folder remained after the session ended. Do I need to add a few lines of code to clear out any *.pdf files from the "www" folder when the session ends or will they get cleared automatically?

test_result_path <- file_temp("test_result_pdf", tmp_dir = "www", ext = ".pdf")
  
  output$test_results <- renderUI({
        test_result() %>%
          str_replace("https://s3.amazonaws.com", "s3:/") %>%
          get_object() %>%
          writeBin(test_result_path)
        tags$iframe(style = "height:1400px; width:100%", src = str_sub(test_result_path, 5))
 })
2 Likes

Yes, you will probably need to clear out the .pdf files from the www folder when the session ends. You can do this manually for each Shiny session (there are callbacks for when the Shiny session ends. I think advised is onStop) or for the R process as a whole (on.exit).

Glad to hear it is working well, though!

1 Like

Hi @cole and @jallen1006 !

Sorry to revive an old thread but I'm trying to do exactly the same thing, only difference being I'm pulling the files from google drive rather than amazon, but I can't get it to work.

I think the issue might be a shinyapps.io hosting one because everything works fine locally but the app disconnects with the following error message when deployed on shinyapps.io

Warning: Error in curl::curl_fetch_disk: Failed to open file www/report13253a913b.pdf.

The code below is the test app I have deployed along with the .httr-oauth file required to connect to my google drive. I'm thinking it might have something to do with not having write permissions to the www/ folder on shinyapps.io but if you could let me know if you have any other ideas that would be great!

Thanks.

library(shiny)
library(googledrive)
library(stringr)
library(tools)
library(fs)

ui <- fluidPage(
  actionButton("dl", "Download PDF"),
  uiOutput("pdf_viewer")
)

server <- function(input, output, session) {
  
  test_file <- "report.pdf"
  base_filename <- tools::file_path_sans_ext(test_file)
  file_extension <- paste0(".", tools::file_ext(test_file))
  temp_file <- fs::file_temp(base_filename, tmp_dir = "www", file_extension)
  
  observeEvent(input$dl, {

    withProgress({
      as_dribble(test_file) %>%
        drive_download(path = temp_file, overwrite = TRUE)
    })
    
    output$pdf_viewer <- renderUI({
      
      if (file.exists(temp_file)) {
        tags$object(
          data = str_sub(temp_file, 5),
          type = "application/pdf",
          style="height:80vh;",
          width = "100%",
          HTML(
            '<h3>It appears that your browser does not support embedded PDFs.</h3>
             <p>To view this content, try another browser such as Chrome or Firefox</p>'
          )
        )
      }
    })

  })
  
  # This code will be run after the client has disconnected
  session$onSessionEnded(function() {
    unlink(file.path(temp_file), recursive = TRUE)
  })
}

shinyApp(ui, server)

Interesting use case!! It looks to me like your problem is that the www folder does not exist. Are you publishing a www folder alongside your application? If not, you should make sure that you create the folder / ensure its existence at app startup with something like dir.create or fs::dir_create

Thanks for the quick response Cole! I am publishing with a www/ directory.

I just tested it again on a shiny server open source instance I have and I think I've isolated the issue. I initially was getting the same error message but after giving the 'shiny' user write permissions to the www/ directory it's now working.

So is there a way to allow write permissions to this folder on shinyapps.io? I'd rather deploy the app there because we have a professional account and need authentication on the app.

Thanks!

Sorry Cole you were actually right the first time.

I was deploying the app with a www/ but it seems like it wasn't accessible to write to (something to do with shinyapps. io's file storage idiosyncrasies?) but creating the directory inside the server with:

if (!fs::dir_exists("www")) fs::dir_create("www")

Looks to have done the trick.

Thanks!

1 Like