Using www folder: Save and display pdf

aws

#1

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

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


#3

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).


#4

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?


#5

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.


#6

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!


#7

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


#8

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))
 })

#9

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!