反应性地检查文件是否可用

Reactively check if a file is available

我正在开发一个闪亮的应用程序,让您可以导入一些数据、操作和绘制它们。 当您对分析感到满意时,您可以使用 python 脚本生成一个 .xlsx 文件(我正在使用 reticulate 使其在闪亮的应用程序中运行):

server <- function(input, output){
  observeEvent(input$run_py, {
    # pass variable from R to Py
    py$df <- avg.spread()
    py$var <- var.spread()
    py_run_file('py_export_excel.py', local = FALSE, convert = FALSE)
  })
}

.xlsx 文件没有立即准备好,所以我需要一个不同的按钮来让您下载文件。

server <- function(input, output){
  output$downloadData <- downloadHandler(
    filename = "file.xlsx",
    content <- function(file) {
      file.copy("test.xlsx", file)
    }
)}

到目前为止一切正常,但我不喜欢必须按下两个不同的按钮才能只做一件事的想法。 我想要一个反应式处理程序,它会不断检查文件是否存在并自动提示您下载它。有没有办法使以下代码具有反应性?

if(file.exists("test.xlsx")) {
  # download code
  ...
}

您可以使用 invalidateLater 重复(在延迟后)反应块。试试这个:

library(shiny)
library(shinyjs)
.log <- function(...) message(format(Sys.time(), format = "[ %H:%M:%S ]"), " ", ...)
.maxtime <- 60
shinyApp(
  ui = fluidPage(
    useShinyjs(),
    actionButton("doit", "Long process ..."),
    downloadButton("dnld", "Download!")
  ),
  server = function(input, output, session) {
    .log("hello world")

    started <- reactiveVal(Sys.time()[NA])
    shinyjs::disable("dnld")
    # just for this answer
    if (file.exists("61240116.txt")) warning("file exists: '61240116.txt'", call. = FALSE)

    # this is your "long process", here I just delay and touch the
    # file; the important parts to preserve are 'disable' and
    # 'started' (or similar)
    observeEvent(input$doit, {
      shinyjs::disable("dnld")
      .log("starting a long process ...")
      started(Sys.time())
      system("sh -c '{ sleep 5 ; touch 61240116.txt; }'", wait = FALSE)
    })

    # this is the work-horse here: it starts whenever 'started' is
    # triggered, and keeps working as long as it has not been too long
    observe({
      req(started())
      d <- difftime(Sys.time(), started(), units = "secs")
      if (file.exists("61240116.txt")) {
        .log("... done!")
        started(Sys.time()[NA])
        shinyjs::enable("dnld")
      } else if (d > .maxtime) {
        .log("timed out")
        # might need to do some damage-control here
      } else {
        .log("... cycling, ", round(d, 1))
        invalidateLater(1000, session)
      }
    })

    # simple/naive download handler
    output$dnld <- downloadHandler(
      filename = function() "61240116.txt",
      content = function(file) file.copy("61240116.txt", file)
    )
  }
)

# Listening on http://127.0.0.1:5020
# [ 16:56:08 ] hello world
# [ 16:56:17 ] starting a long process ...
# [ 16:56:18 ] ... cycling, 0
# [ 16:56:19 ] ... cycling, 1
# [ 16:56:20 ] ... cycling, 2
# [ 16:56:21 ] ... cycling, 3
# [ 16:56:22 ] ... cycling, 4
# [ 16:56:23 ] ... cycling, 5
# [ 16:56:24 ] ... done!
  1. 启动应用,注意Download!被禁用(第一个shinyjs::disable);
  2. 单击 Long process ... 按钮,注意在控制台上您应该会看到一些状态消息。
  3. 大约 5 秒后,下载按钮应该会启用,您现在可以下载文件了。

本小程序的"engineering":

  • started 有两个方面:开始 1000 毫秒循环失效,以及永远保持此检查来自 运行。 (当然有可能(如果你 "set" started 在多个地方)打破这个结构。)使用 "time" 可能不适合你的情况,也不是完全万无一失的(例如,负载很重的系统可能需要更多时间)。
  • 我出于三个目的将 started 设置并重置为 Sys.time()[NA]:首先,它需要始终是 POSIXt 而不是 "recent";其次,当 difftime 只是一个数字时(例如 0),它会变得不安,因为这需要一个时间起点;第三,req 将导致 observe 块提前终止,如果它是某种形式的 NA(或其他不真实的东西......见 ?req)。我们依赖于这种行为。
  • shinyjs 用于禁用下载按钮,以便 我们 控制何时可以下载某些内容。