rpharma-demo icon indicating copy to clipboard operation
rpharma-demo copied to clipboard

Overriding bookmark storage location

Open rpodcast opened this issue 5 years ago • 0 comments

One of the requirements I have for most of my applications using session management is to customize the location where session files are stored. In my presentation at the conference I showed a simple example where the application will store session specific data in the user's linux home directory. Using the example app in this repo, I found a way to customize the location for saving session bookmarks. See below for my notes on my journey. But the bigger question I have is if Shiny could allow for this customization directly?

Default saving mechanism

shiny has an internal function called saveShinySaveState which controls the actual writing of input values and optionally any values flagged by the user to save. One dynamic piece of this is pulling the value of a specific shiny option called save.interface. The value of this option depends on the execution environment:

  • When a shiny app is executed on shiny server, this option is set to be a custom function as seen in the shiny server source code here. The parameters are id (the unique hash id given to the bookmark) and callback (basically how the function registers itself with a shiny session object R6 method called doBookmark ). The shiny-server code is hardcoding various options for the directories of all bookmarks and app-specific bookmarks.
  • When a shiny app is executed inside RStudio itself (or any general R session), the save.interface option is set to NULL. In that case, a customized version of the saving interface function called saveInterfaceLocal is swapped in dynamically. This function is configured to only save the bookmarks in a sub-directory of the app itself.

Tricking default behavior

In both of the cases above, there is no customization allowed for the location of saving bookmarks. At first I tried hacking around and overriding specific R6 methods and functions to no avail. We needed a way to define our own version of two key options: The aforementioned save.interface and a corresponding load.interface function, each of which need to have the same set of function inputs (id and callback). But how can we pass in custom parameters, such as the user ID of the user executing the app, and a new storage location? The answer is setting custom options via shinyOptions, which is like a shiny-aware version of typical options specifications. Instead of letting shiny-server define the option for us, we can override the options ourselves within the application. We can also set arbitrary options that can be used to pass key information to our custom functions:

  • Set the user ID of the logged-in user as an option. My usual mechanism to dynamically grab the user ID is a custom reactive that is dynamic to the execution environment
# capture the user ID, set to me if not available
  user <- reactive({
    if(!is.null(session$user))  {
      my_user <- session$user
    } else {
      my_user <- Sys.getenv("USER")
    }
    shinyOptions(user = my_user)
    return(my_user)
  })

# force execution of user reactive
  observe({
    user()
  })
  • Set the directory name for saving application settings (can be placed anywhere in server-side processing, no need for a reactive version):
# set file-system options for saving session directory path
shinyOptions(session_dir = ".shinysessions")
  • Define our custom functions for saving and loading interface, and set the appropriate options:
saveInterfaceLocal <- function(id, callback) {
  
  # user ID associated with the app execution
  user <- getShinyOption("user")
  
  #directory name for the app's storage location
  session_dir <- getShinyOption("session_dir")

  stateDir <- file.path("/home", user, session_dir, "shiny_bookmarks", id)
  if (!shiny:::dirExists(stateDir))
    dir.create(stateDir, recursive = TRUE)
  
  callback(stateDir)
}

shinyOptions(save.interface = saveInterfaceLocal)
loadInterfaceLocal <- function(id, callback) {
  # user ID associated with the app execution
  user <- getShinyOption("user")
  
  #directory name for the app's storage location
  session_dir <- getShinyOption("session_dir")
  
  stateDir <- file.path("/home", user, session_dir, "shiny_bookmarks", id)
  callback(stateDir)
}

shinyOptions(load.interface = loadInterfaceLocal)

Once all of these have been set up correctly, everything included in shiny for saving and restoring bookmarks will just work and no other modifications are necessary.

rpodcast avatar Sep 10 '18 21:09 rpodcast