Reactive monitoring of a file in Shiny - r

I have an app where I need to monitor a file for changes. However I am struggling to do this.
Consider the following example:
library(shiny)
A function that returns the modified date of a file:
file_info <- function(){
if(file.exists("example.txt")){
return(as.character(as.Date(file.info("example.txt")[1,4])))
} else {
return("File not present")
}
}
Shiny part:
ui <- shinyUI(fluidPage(
textOutput("file_status"),
actionButton("create_file", "Create file"),
actionButton("delete_file", "Delete file")
))
server <- shinyServer(function(input, output, session) {
file_st <- reactive(file_info()) #what is the correct approach here?
output$file_status <- renderText({
file_st()
})
observeEvent(input$create_file,{
file.create("example.txt")
})
observeEvent(input$delete_file, {
unlink("example.txt")
})
})
I would like the file_status text field to update each time the example.txt file changes - if possible even if this happens outside of the app.
I have tried various combinations of reactive, observe and reactiveValues without finding the adequate combo.
Thank you

I was able to solve this using reactivePoll which is practically tailored for this type of task
server <- shinyServer(function(input, output, session) {
file_st <- reactivePoll(500, session,
checkFunc = function() {
if (file.exists("example.txt"))
file.info("example.txt")$mtime[1]
else
""},
valueFunc = function(){
if(file.exists("example.txt")){
return(as.character(as.Date(file.info("example.txt")[1,4])))
} else {
return("File not present")
}
}
)
output$file_status <- renderText({
file_st()
})
observeEvent(input$create_file,{
file.create("example.txt")
})
observeEvent(input$delete_file, {
unlink("example.txt")
})
})
ui <- shinyUI(fluidPage(
textOutput("file_status"),
actionButton("create_file", "Create file"),
actionButton("delete_file", "Delete file")
))

Related

How to use callr::r_bg within a downloadHandler in a Shiny App

The scenario I'm emulating with the below minimal example is allowing a user to engage with a Shiny App (click the numericInput control and see server-side events occur) while a long-running download is occurring (simulated with Sys.sleep(10) within downloadHandler).
In a synchronous setting, when the "Download" button is clicked, the user can still interact with UI elements, but other Shiny calculations (in this case, renderText), get put in a queue. I'd like the asynchronous setting, where the download occurs in the background, and users can still interact with the UI elements and get desired output (e.g. renderText).
I'm using callr::r_bg() to achieve asynchronicity within Shiny, but the issue is that my current code of the downloadHandler is incorrect (mtcars should be getting downloaded, but the code is unable to complete the download, 404 error message), I believe it's due to the specific way in which downloadHandler expects the content() function to be written, and the way I've written callr::r_bg() is not playing nicely with that. Any insights would be appreciated!
Reference:
https://www.r-bloggers.com/2020/04/asynchronous-background-execution-in-shiny-using-callr/
Minimal Example:
library(shiny)
ui <- fluidPage(
downloadButton("download", "Download"),
numericInput("count",
NULL,
1,
step = 1),
textOutput("text")
)
server <- function(input, output, session) {
long_download <- function(file) {
Sys.sleep(10)
write.csv(mtcars, file)
}
output$download <- downloadHandler(
filename = "data.csv",
content = function(file) {
x <- callr::r_bg(
func = long_download,
args = list(file)
)
return(x)
}
)
observeEvent(input$count, {
output$text <- renderText({
paste(input$count)
})
})
}
shinyApp(ui, server)
I figured out a solution, and learned the following things:
Because downloadHandler doesn't have a traditional input$X, it can be difficult to include reactivity in the traditional way. The workaround was to present the UI as a hidden downlodButton masked by an actionButton which the user would see. Reactivity was facilitated in the following process: user clicks actionButton -> reactive updates -> when the reactive finishes (reactive()$is_alive() == FALSE), use shinyjs::click to initiate the downloadHandler
Instead of placing the callr function within the downloadHandler, I kept the file within the content arg. There seems to be some difficulties with scoping because the file needs to be available within the content function environment
I'm using a reactive function to track when the background job (the long-running computation) is finished to initiate the download using the syntax: reactive()$is_alive()
The invalidateLater() and toggling of a global variable (download_once) is important to prevent the reactive from constantly activating. Without it, what will happen is your browser will continually download files ad infinitum -- this behavior is scary and will appear virus-like to your Shiny app users!
Note that setting global variables is not a best practice for Shiny apps (will think of a better implementation)
Code Solution:
library(shiny)
library(callr)
library(shinyjs)
ui <- fluidPage(
shinyjs::useShinyjs(),
#creating a hidden download button, since callr requires an input$,
#but downloadButton does not natively have an input$
actionButton("start", "Start Long Download", icon = icon("download")),
downloadButton("download", "Download", style = "visibility:hidden;"),
p("You can still interact with app during computation"),
numericInput("count",
NULL,
1,
step = 1),
textOutput("text"),
textOutput("did_it_work")
)
long_job <- function() {
Sys.sleep(5)
}
server <- function(input, output, session) {
#start async task which waits 5 sec then virtually clicks download
long_run <- eventReactive(input$start, {
#r_bg by default sets env of function to .GlobalEnv
x <- callr::r_bg(
func = long_job,
supervise = TRUE
)
return(x)
})
#desired output = download of mtcars file
output$download <- downloadHandler(filename = "test.csv",
content = function(file) {
write.csv(mtcars, file)
})
#output that's meant to let user know they can still interact with app
output$text <- renderText({
paste(input$count)
})
download_once <- TRUE
#output that tracks progress of background task
check <- reactive({
invalidateLater(millis = 1000, session = session)
if (long_run()$is_alive()) {
x <- "Job running in background"
} else {
x <- "Async job in background completed"
if(isTRUE(download_once)) {
shinyjs::click("download")
download_once <<- FALSE
}
invalidateLater(millis = 1, session = session)
}
return(x)
})
output$did_it_work <- renderText({
check()
})
}
shinyApp(ui, server)
Thanks #latlio for your great answer. I think it cloud be easily improved.
invalidateLater should be used very carefully and only WHEN needed. I use invalidateLater only once and moved it to the logical part where we are waiting for the result. Thus we are NOT invalidating the reactivity infinitely.
library(shiny)
library(callr)
library(shinyjs)
ui <- fluidPage(
shinyjs::useShinyjs(),
#creating a hidden download button, since callr requires an input$,
#but downloadButton does not natively have an input$
actionButton("start", "Start Long Download", icon = icon("download")),
downloadButton("download", "Download", style = "visibility:hidden;"),
p("You can still interact with app during computation"),
numericInput("count",
NULL,
1,
step = 1),
textOutput("text"),
textOutput("did_it_work")
)
long_job <- function() {
Sys.sleep(5)
}
server <- function(input, output, session) {
#start async task which waits 5 sec then virtually clicks download
long_run <- eventReactive(input$start, {
#r_bg by default sets env of function to .GlobalEnv
x <- callr::r_bg(
func = long_job,
supervise = TRUE
)
return(x)
})
#desired output = download of mtcars file
output$download <- downloadHandler(filename = "test.csv",
content = function(file) {
write.csv(mtcars, file)
})
#output that's meant to let user know they can still interact with app
output$text <- renderText({
paste(input$count)
})
#output that tracks progress of background task
check <- reactive({
if (long_run()$is_alive()) {
x <- "Job running in background"
invalidateLater(millis = 1000, session = session)
} else {
x <- "Async job in background completed"
shinyjs::click("download")
}
return(x)
})
output$did_it_work <- renderText({
check()
})
}
shinyApp(ui, server)

Shiny reactive check if file is choose

Good morning. I want to check if file is choose or not in fileInput so I had to create reactive function but this not working.
ui.R
fileInput("file_input","Choose your file in csv")
mainPanel("main panel",textOutput("choose"))
server.R
library(shiny)
isFileChoose<-function(){reactive({
if(is.null(input$file_input))
return (FALSE)
else
return (TRUE)
}) }
server <- function(input, output) {
if(isFileChoose()==FALSE)
{
output$choose<-renderText("Not selected file")
}
}
I do not think you can just use a reactive in a function like that, see here. You could do this:
library(shiny)
ui <- fluidPage(
fileInput("file_input","Choose your file in csv"),
textOutput("choose")
)
server <- function(input, output) {
output$choose <- reactive({
if(is.null(input$file_input))
{
"No input given yet."
}
else
{
"Now we can process the data!"
}
})
}
shinyApp(ui = ui, server = server)
Hope this helps!

R shiny reactive list of files

New to shiny, I want my app to display all files in a directory, and make the list update every time a file is added or removed. I see this is possible with reactivePoll, so I put this in my server:
server <- function(input, output, session) {
has.new.files <- function() {
length(list.files())
}
get.files <- function() {
list.files()
}
output$files <- renderText(reactivePoll(10, session, checkFunc=has.new.files, valueFunc=get.files))
}
However, I don't know how I can access the character vector containing my files within my ui. I also doubt that renderText in my server is the right choice. Here is my ui (non-reactive, just reads file list once):
ui <- fluidPage(
## How to access the files from server function ??
selectInput("file", "Choose file", list.files())
)
Thus, I just don't know hot to access the data, could anyone point me in the right direction?
You could try this:
server <- function(input, output, session) {
has.new.files <- function() {
unique(list.files())
}
get.files <- function() {
list.files()
}
# store as a reactive instead of output
my_files <- reactivePoll(10, session, checkFunc=has.new.files, valueFunc=get.files)
# any time the reactive changes, update the selectInput
observeEvent(my_files(),ignoreInit = T,ignoreNULL = T, {
print(my_files())
updateSelectInput(session, 'file',choices = my_files())
})
}
ui <- fluidPage(
selectInput("file", "Choose file", list.files())
)
shinyApp(ui,server)
It updates the selectInput any time a file is added, deleted or renamed. If you only want it to change if a file is added, you can replace the has.new.files function back with your own. Hope this helps!

renderUI, load data then render a new UI in R Shiny

This might be very simple. But when a reactive value change I would like to render a UI from server.R, then load some data, run some functions and then render a new UI. However I can't seem to get it to work, as it just waits 2 sek and then shows the last renderUI (not showing the loadUI in the meantime).
I know I can use shinyJS (https://github.com/daattali/advanced-shiny/tree/master/loading-screen) for this, however for what I'm doing I would like to show a totally new page, load my data, and then show another new page.
ui <- fluidPage(
actionButton(inputId = "btn_data", label = "Load Data"),
uiOutput("page")
)
server <- function(input, output) {
rv <- reactiveValues(loaddata = FALSE)
observeEvent(input$btn_data, {
rv$loaddata <- TRUE
})
observe({
if(rv$loaddata){
#Page to show, when loading data - loadUI
output$page <- renderUI({
p("Loading data...")
})
#Running functions and loading data
Sys.sleep(2)
#Page to show when data is loaded
output$page <- renderUI({
p("Data is now loaded")
})
}
})
}
shinyApp(ui = ui, server = server)

Possible to show console messages (written with `message`) in a shiny ui?

I don't understand R's message vs cat vs print vs etc. too deeply, but I'm wondering if it's possible to capture messages and show them in a shiny app?
Example: the following app can capture cat statements (and print statements as well) but not message statements
runApp(shinyApp(
ui = fluidPage(
textOutput("test")
),
server = function(input,output, session) {
output$test <- renderPrint({
cat("test cat")
message("test message")
})
}
))
Cross post from the shiny-discuss Google group since I got 0 answers.
Yihui suggested I use withCallingHandlers, and that indeed let me to a solution. I wasn't quite sure how to use that function in a way that would do exactly what I needed because my problem was that I had a function that printed out several messages one at a time and using a naive approach only printed the last message. Here is the my first attempt (which works if you only have one message to show):
foo <- function() {
message("one")
message("two")
}
runApp(shinyApp(
ui = fluidPage(
actionButton("btn","Click me"),
textOutput("text")
),
server = function(input,output, session) {
observeEvent(input$btn, {
withCallingHandlers(
foo(),
message = function(m) output$text <- renderPrint(m$message)
)
})
}
))
Notice how only two\n gets outputted. So my final solution was to use the html function from shinyjs package (disclaimer: I wrote that package), which lets me change or append to the HTML inside an element. It worked perfectly - now both messages got printed out in real-time.
foo <- function() {
message("one")
Sys.sleep(0.5)
message("two")
}
runApp(shinyApp(
ui = fluidPage(
shinyjs::useShinyjs(),
actionButton("btn","Click me"),
textOutput("text")
),
server = function(input,output, session) {
observeEvent(input$btn, {
withCallingHandlers({
shinyjs::html("text", "")
foo()
},
message = function(m) {
shinyjs::html(id = "text", html = m$message, add = TRUE)
})
})
}
))
I know this isn't nearly as elegant, but I worked around a bit similar problem using capture.output; sadly sink doesn't allow simultaneous capture of messages and output though. You don't get them in the original order, but you can extract both streams at least (here turned to HTML):
runApp(shinyApp(
ui = fluidPage(
uiOutput("test")
),
server = function(input,output, session) {
output$test <- renderUI({
HTML(
paste(capture.output(type = "message", expr = {
message(capture.output(type = "output", expr = {
cat("test cat<br>")
message("test message")
cat("test cat2<br>")
message("test message2")
}))
}), collapse="<br>")
)})
})
)
Output:
test message
test message2
test cat
test cat2
Perhaps in the case if user wants to capture both but also separate them, this will provide a handy work-around. (Your shinyjs package seems neat, need to take a look at it!)
This can now be done with the high-level function shinyCatch from the spsComps package.
Basic usage
library(shiny)
library(spsComps)
ui <- fluidPage(
actionButton("msg", "msg"),
actionButton("warn", "warn"),
actionButton("err", "err"),
)
server <- function(input, output, session) {
observeEvent(input$msg, {
shinyCatch({message("a message")}, prefix = '')
})
observeEvent(input$warn, {
shinyCatch({warning("a warning")}, prefix = '')
})
observeEvent(input$err, {
shinyCatch({stop("an error")}, prefix = '')
})
}
shinyApp(ui, server)
Choose blocking level
If exceptions happened, we can choose to continue the code or block downstream code in the reactive context. For example, we want to stop downstream code if an error/warning/message happens:
library(shiny)
library(spsComps)
ui <- fluidPage(
actionButton("err", "code not blocked after error"),
actionButton("err_block", "code blocked after error"),
)
server <- function(input, output, session) {
observeEvent(input$err, {
shinyCatch({stop("an error")}, prefix = '')
print("error does not block ")
})
observeEvent(input$err_block, {
shinyCatch({stop("an error")}, prefix = '', blocking_level = "error")
print("you can't see me if error happens")
})
}
shinyApp(ui, server)
More advanced use
check website and demo
cat and print
There is still no good method to catch real-time info from cat and print. I will come back to update this answer if I found a fix for this.

Resources