[ Web Proxy ]
URL:
Viewing: https://raw.githubusercontent.com/pearsonca/rstudio/master/src/cpp/session/SessionModuleContext.cpp [Back]  [Original]

/*
 * SessionModuleContext.cpp
 *
 * Copyright (C) 2009-20 by RStudio, PBC
 *
 * Unless you have received this program directly from RStudio pursuant
 * to the terms of a commercial license agreement with RStudio, then
 * this program is licensed to you under the terms of version 3 of the
 * GNU Affero General Public License. This program is distributed WITHOUT
 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
 *
 */

#include "SessionModuleContextInternal.hpp"

#include 

#include 
#include 
#include 
#include 

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include 

#include 
#include 
#include 
#include 
#include 
#include 

#include 

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include 
#include 
#include 
#include 

#include 

#include "SessionClientEventQueue.hpp"

#include 

#include 
#include 

#include 
#include 

#include 

#include "modules/SessionBreakpoints.hpp"
#include "modules/SessionVCS.hpp"
#include "modules/SessionFiles.hpp"

#include "session-config.h"

using namespace rstudio::core ;

namespace rstudio {
namespace session {   
namespace module_context {
      
namespace {

// simple service for handling console_input rpc requests
class ConsoleInputService : boost::noncopyable
{
public:
   
   ConsoleInputService()
   {
      core::thread::safeLaunchThread(
               boost::bind(&ConsoleInputService::run, this),
               &thread_);
   }
   
   ~ConsoleInputService()
   {
      enqueue("!");
   }
   
   void enqueue(const std::string& input)
   {
      requests_.enque(input);
   }
   
private:
   
   void run()
   {
      while (true)
      {
         std::string input;
         while (requests_.deque(&input))
         {
            if (input == "!")
               return;
            
            core::http::Response response;
            Error error = session::http::sendSessionRequest(
                     "/rpc/console_input",
                     input,
                     &response);
            if (error)
               LOG_ERROR(error);
         }
         
         requests_.wait();
      }
   }
   
   boost::thread thread_;
   core::thread::ThreadsafeQueue requests_;
};

ConsoleInputService& consoleInputService()
{
   static ConsoleInputService instance;
   return instance;
}

// enqueClientEvent from R
SEXP rs_enqueClientEvent(SEXP nameSEXP, SEXP dataSEXP)
{
   try
   {
      // extract name
      std::string name = r::sexp::asString(nameSEXP); 
      
      // extract json value (for primitive types we only support scalars
      // since this is the most common type of event data). to return an
      // array of primitives you need to wrap them in a list/object
      Error extractError ;
      json::Value data ;
      switch(TYPEOF(dataSEXP))
      {
         case NILSXP:
         {
            // do nothing, data will be a null json value
            break;
         }   
         case VECSXP:
         {
            extractError = r::json::jsonValueFromList(dataSEXP, &data);
            break;
         }   
         default:
         {
            extractError = r::json::jsonValueFromScalar(dataSEXP, &data);
            break;
         }
      }
      
      // check for error
      if (extractError)
      {
         LOG_ERROR(extractError);
         throw r::exec::RErrorException(
                                        "Couldn't extract json value from event data");
      }
      
      // determine the event type from the event name
      int type = -1 ;
      if (name == "package_status_changed")
         type = session::client_events::kPackageStatusChanged;
      else if (name == "unhandled_error")
         type = session::client_events::kUnhandledError;
      else if (name == "enable_rstudio_connect")
         type = session::client_events::kEnableRStudioConnect;
      else if (name == "shiny_gadget_dialog")
         type = session::client_events::kShinyGadgetDialog;
      else if (name == "rmd_params_ready")
         type = session::client_events::kRmdParamsReady;
      else if (name == "jump_to_function")
         type = session::client_events::kJumpToFunction;
      else if (name == "send_to_console")
         type = session::client_events::kSendToConsole;
      else if (name == "rprof_started")
        type = session::client_events::kRprofStarted;
      else if (name == "rprof_stopped")
        type = session::client_events::kRprofStopped;
      else if (name == "rprof_created")
        type = session::client_events::kRprofCreated;
      else if (name == "editor_command")
         type = session::client_events::kEditorCommand;
      else if (name == "navigate_shiny_frame")
         type = session::client_events::kNavigateShinyFrame;
      else if (name == "update_new_connection_dialog")
         type = session::client_events::kUpdateNewConnectionDialog;
      else if (name == "terminal_subprocs")
         type = session::client_events::kTerminalSubprocs;
      else if (name == "rstudioapi_show_dialog")
         type = session::client_events::kRStudioAPIShowDialog;
      else if (name == "object_explorer_event")
         type = session::client_events::kObjectExplorerEvent;
      else if (name == "send_to_terminal")
         type = session::client_events::kSendToTerminal;
      else if (name == "clear_terminal")
         type = session::client_events::kClearTerminal;
      else if (name == "add_terminal")
         type = session::client_events::kAddTerminal;
      else if (name == "activate_terminal")
         type = session::client_events::kActivateTerminal;
      else if (name == "terminal_cwd")
         type = session::client_events::kTerminalCwd;
      else if (name == "remove_terminal")
         type = session::client_events::kRemoveTerminal;
      else if (name == "show_page_viewer")
         type = session::client_events::kShowPageViewerEvent;
      else if (name == "data_output_completed")
         type = session::client_events::kDataOutputCompleted;
      else if (name == "new_document_with_code")
         type = session::client_events::kNewDocumentWithCode;
      else if (name == "available_packages_ready")
         type = session::client_events::kAvailablePackagesReady;
      else if (name == "compute_theme_colors")
         type = session::client_events::kComputeThemeColors;
      else if (name == "tutorial_command")
         type = session::client_events::kTutorialCommand;
      else if (name == "tutorial_launch")
         type = session::client_events::kTutorialLaunch;

      if (type != -1)
      {
         ClientEvent event(type, data);
         session::clientEventQueue().add(event);
      }
      else
      {
         LOG_ERROR_MESSAGE("Unexpected event name from R: " + name);
      }
   }
   catch(r::exec::RErrorException& e)
   {
      r::exec::error(e.message());
   }
   CATCH_UNEXPECTED_EXCEPTION
   
   return R_NilValue ;
}

SEXP rs_activatePane(SEXP paneSEXP)
{
   module_context::activatePane(r::sexp::safeAsString(paneSEXP));
   return R_NilValue;
}

// show error message from R
SEXP rs_showErrorMessage(SEXP titleSEXP, SEXP messageSEXP)
{
   std::string title = r::sexp::asString(titleSEXP);
   std::string message = r::sexp::asString(messageSEXP);
   module_context::showErrorMessage(title, message);
   return R_NilValue;
}

// log error message from R
SEXP rs_logErrorMessage(SEXP messageSEXP)
{
   std::string message = r::sexp::asString(messageSEXP);
   LOG_ERROR_MESSAGE(message);
   return R_NilValue;
}  

// log warning message from R
SEXP rs_logWarningMessage(SEXP messageSEXP)
{
   std::string message = r::sexp::asString(messageSEXP);
   LOG_WARNING_MESSAGE(message);
   return R_NilValue;
}  
   
// sleep the main thread (debugging function used to test rpc/abort)
SEXP rs_threadSleep(SEXP secondsSEXP)
{
   int seconds = r::sexp::asInteger(secondsSEXP);
   boost::this_thread::sleep(boost::posix_time::seconds(seconds));
   return R_NilValue;
}

// get rstudio mode
SEXP rs_rstudioProgramMode()
{
   r::sexp::Protect rProtect;
   return r::sexp::create(session::options().programMode(), &rProtect);
}

// get rstudio edition
SEXP rs_rstudioEdition()
{
   return R_NilValue;
}

// get version
SEXP rs_rstudioVersion()
{
   r::sexp::Protect rProtect;
   return r::sexp::create(std::string(RSTUDIO_VERSION), &rProtect);
}

// get release name
SEXP rs_rstudioReleaseName()
{
   r::sexp::Protect rProtect;
   return r::sexp::create(std::string(RSTUDIO_RELEASE_NAME), &rProtect);
}

// get citation
SEXP rs_rstudioCitation()
{
   FilePath resPath = session::options().rResourcesPath();
   FilePath citationPath = resPath.completeChildPath("CITATION");

   // the citation file may not exist when working in e.g.
   // development configurations so just ignore if it's missing
   if (!citationPath.exists())
      return R_NilValue;

   SEXP citationSEXP;
   r::sexp::Protect rProtect;
   Error error = r::exec::RFunction("utils:::readCitationFile",
                                    citationPath.getAbsolutePath())
                                                   .call(&citationSEXP,
                                                         &rProtect);

   if (error)
   {
      LOG_ERROR(error);
      return R_NilValue;
   }
   else
   {
      return citationSEXP;
   }
}

SEXP rs_setUsingMingwGcc49(SEXP usingSEXP)
{
   bool usingMingwGcc49 = r::sexp::asLogical(usingSEXP);
   prefs::userState().setUsingMingwGcc49(usingMingwGcc49);
   return R_NilValue;
}

// ensure file hidden
SEXP rs_ensureFileHidden(SEXP fileSEXP)
{
#ifdef _WIN32
   std::string file = r::sexp::asString(fileSEXP);
   if (!file.empty())
   {
      FilePath filePath = module_context::resolveAliasedPath(file);
      Error error = core::system::makeFileHidden(filePath);
      if (error)
         LOG_ERROR(error);
   }
#endif

   return R_NilValue;
}

SEXP rs_sourceDiagnostics()
{
   module_context::sourceDiagnostics();
   return R_NilValue;
}

SEXP rs_packageLoaded(SEXP pkgnameSEXP)
{
   std::string pkgname = r::sexp::safeAsString(pkgnameSEXP);

   // fire server event
   events().onPackageLoaded(pkgname);

   // fire client event
   ClientEvent packageLoadedEvent(
            client_events::kPackageLoaded,
            json::Value(pkgname));
   enqueClientEvent(packageLoadedEvent);

   return R_NilValue;
}

SEXP rs_packageUnloaded(SEXP pkgnameSEXP)
{
   std::string pkgname = r::sexp::safeAsString(pkgnameSEXP);
   ClientEvent packageUnloadedEvent(
            client_events::kPackageUnloaded,
            json::Value(pkgname));
   enqueClientEvent(packageUnloadedEvent);
   return R_NilValue;
}

SEXP rs_userPrompt(SEXP typeSEXP,
                   SEXP captionSEXP,
                   SEXP messageSEXP,
                   SEXP yesLabelSEXP,
                   SEXP noLabelSEXP,
                   SEXP includeCancelSEXP,
                   SEXP yesIsDefaultSEXP)
{
   UserPrompt prompt(r::sexp::asInteger(typeSEXP),
                     r::sexp::safeAsString(captionSEXP),
                     r::sexp::safeAsString(messageSEXP),
                     r::sexp::safeAsString(yesLabelSEXP),
                     r::sexp::safeAsString(noLabelSEXP),
                     r::sexp::asLogical(includeCancelSEXP),
                     r::sexp::asLogical(yesIsDefaultSEXP));

   UserPrompt::Response response = showUserPrompt(prompt);

   r::sexp::Protect rProtect;
   return r::sexp::create(response, &rProtect);
}

SEXP rs_restartR(SEXP afterRestartSEXP)
{
   std::string afterRestart = r::sexp::safeAsString(afterRestartSEXP);
   json::Object dataJson;
   dataJson["after_restart"] = afterRestart;
   ClientEvent event(client_events::kSuspendAndRestart, dataJson);
   module_context::enqueClientEvent(event);
   return R_NilValue;
}

SEXP rs_generateShortUuid()
{
   // generate a short uuid -- we make this available in R code so that it's
   // possible to create random identifiers without perturbing the state of the
   // RNG that R uses
   std::string uuid = core::system::generateShortenedUuid();
   r::sexp::Protect rProtect;
   return r::sexp::create(uuid, &rProtect);
}

SEXP rs_markdownToHTML(SEXP contentSEXP)
{
   std::string content = r::sexp::safeAsString(contentSEXP);
   std::string htmlContent;
   Error error = markdown::markdownToHTML(content,
                                          markdown::Extensions(),
                                          markdown::HTMLOptions(),
                                          &htmlContent);
   if (error)
   {
      LOG_ERROR(error);
      htmlContent = content;
   }

   r::sexp::Protect rProtect;
   return r::sexp::create(htmlContent, &rProtect);
}

inline std::string persistantValueName(SEXP nameSEXP)
{
   return "rstudioapi_persistent_values_" + r::sexp::safeAsString(nameSEXP);
}

SEXP rs_setPersistentValue(SEXP nameSEXP, SEXP valueSEXP)
{
   std::string name = persistantValueName(nameSEXP);
   std::string value = r::sexp::safeAsString(valueSEXP);
   persistentState().settings().set(name, value);
   return R_NilValue;
}

SEXP rs_getPersistentValue(SEXP nameSEXP)
{
   std::string name = persistantValueName(nameSEXP);
   if (persistentState().settings().contains(name))
   {
      std::string value = persistentState().settings().get(name);
      r::sexp::Protect rProtect;
      return r::sexp::create(value, &rProtect);
   }
   else
   {
      return R_NilValue;
   }
}

} // anonymous namespace


// register a scratch path which is monitored

namespace {

typedef std::map MonitoredScratchPaths;
MonitoredScratchPaths s_monitoredScratchPaths;
bool s_monitorByScanning = false;

FilePath monitoredParentPath()
{
   FilePath monitoredPath = userScratchPath().completePath(kMonitoredPath);
   Error error = monitoredPath.ensureDirectory();
   if (error)
      LOG_ERROR(error);
   return monitoredPath;
}

bool monitoredScratchFilter(const FileInfo& fileInfo)
{
   return true;
}


void onFilesChanged(const std::vector& changes)
{
   for (const core::system::FileChangeEvent& fileChange : changes)
   {
      FilePath changedFilePath(fileChange.fileInfo().absolutePath());
      for (MonitoredScratchPaths::const_iterator
               it = s_monitoredScratchPaths.begin();
               it != s_monitoredScratchPaths.end();
               ++it)
      {
         if (changedFilePath.isWithin(it->first))
         {
            it->second(fileChange);
            break;
         }
      }

   }
}

boost::shared_ptr monitoredPathTree()
{
   boost::shared_ptr pMonitoredTree(new tree());
   core::system::FileScannerOptions options;
   options.recursive = true;
   options.filter = monitoredScratchFilter;
   Error scanError = scanFiles(FileInfo(monitoredParentPath()),
                               options,
                               pMonitoredTree.get());
   if (scanError)
      LOG_ERROR(scanError);

   return pMonitoredTree;
}

bool scanForMonitoredPathChanges(boost::shared_ptr pPrevTree)
{
   // check for changes
   std::vector changes;
   boost::shared_ptr pCurrentTree = monitoredPathTree();
   core::system::collectFileChangeEvents(pPrevTree->begin(),
                                         pPrevTree->end(),
                                         pCurrentTree->begin(),
                                         pCurrentTree->end(),
                                         &changes);

   // fire events
   onFilesChanged(changes);

   // reset the tree
   *pPrevTree = *pCurrentTree;

   // scan again after interval
   return true;
}

void onMonitoringError(const Error& error)
{
   // log the error
   LOG_ERROR(error);

   // fallback to periodically scanning for changes
   if (!s_monitorByScanning)
   {
      s_monitorByScanning = true;
      module_context::schedulePeriodicWork(
         boost::posix_time::seconds(3),
         boost::bind(scanForMonitoredPathChanges, monitoredPathTree()),
         true);
   }
}

void initializeMonitoredUserScratchDir()
{
   // setup callbacks and register
   core::system::file_monitor::Callbacks cb;
   cb.onRegistrationError = onMonitoringError;
   cb.onMonitoringError = onMonitoringError;
   cb.onFilesChanged = onFilesChanged;
   core::system::file_monitor::registerMonitor(
                                    monitoredParentPath(),
                                    true,
                                    monitoredScratchFilter,
                                    cb);
}


} // anonymous namespace

FilePath registerMonitoredUserScratchDir(const std::string& dirName,
                                         const OnFileChange& onFileChange)
{
   // create the subdir
   FilePath dirPath = monitoredParentPath().completePath(dirName);
   Error error = dirPath.ensureDirectory();
   if (error)
      LOG_ERROR(error);

   // register the path
   s_monitoredScratchPaths[dirPath] = onFileChange;

   // return it
   return dirPath;
}


namespace {
   
// manage signals used for custom save and restore
class SuspendHandlers : boost::noncopyable 
{
public:
   SuspendHandlers() : nextGroup_(0) {}
   
public:   
   void add(const SuspendHandler& handler)
   {
      int group = nextGroup_++;
      suspendSignal_.connect(group, handler.suspend());
      resumeSignal_.connect(group, handler.resume());
   }
   
   void suspend(const r::session::RSuspendOptions& options,
                Settings* pSettings)
   {
      suspendSignal_(options, pSettings);
   }
   
   void resume(const Settings& settings)
   {
      resumeSignal_(settings);
   }
   
private:
   
   // use groups to ensure signal order. call suspend handlers in order 
   // of subscription and call resume handlers in reverse order of
   // subscription.
   
   int nextGroup_; 
   
   RSTUDIO_BOOST_SIGNAL suspendSignal_;
                  
   RSTUDIO_BOOST_SIGNAL resumeSignal_;
};

// handlers instance
SuspendHandlers& suspendHandlers()
{
   static SuspendHandlers instance;
   return instance;
}
   
} // anonymous namespace
   
void addSuspendHandler(const SuspendHandler& handler)
{
   suspendHandlers().add(handler);
}
   
void onSuspended(const r::session::RSuspendOptions& options,
                 Settings* pPersistentState)
{
   pPersistentState->beginUpdate();
   suspendHandlers().suspend(options, pPersistentState);
   pPersistentState->endUpdate();
   
}

void onResumed(const Settings& persistentState)
{
   suspendHandlers().resume(persistentState);
}

// idle work

namespace {

typedef std::vector
                                                      ScheduledCommands;
ScheduledCommands s_scheduledCommands;
ScheduledCommands s_idleScheduledCommands;

void addScheduledCommand(boost::shared_ptr pCommand,
                         bool idleOnly)
{
   if (idleOnly)
      s_idleScheduledCommands.push_back(pCommand);
   else
      s_scheduledCommands.push_back(pCommand);
}

void executeScheduledCommands(ScheduledCommands* pCommands)
{
   // make a copy of scheduled commands before executing them
   // (this is because a scheduled command could result in
   // R code executing which in turn could cause the list of
   // scheduled commands to be mutated and these iterators
   // invalidated)
   ScheduledCommands commands = *pCommands;

   // execute all commands
   std::for_each(commands.begin(),
                 commands.end(),
                 boost::bind(&ScheduledCommand::execute, _1));

   // remove any commands which are finished
   pCommands->erase(
                 std::remove_if(
                    pCommands->begin(),
                    pCommands->end(),
                    boost::bind(&ScheduledCommand::finished, _1)),
                 pCommands->end());
}


} // anonymous namespace

void scheduleIncrementalWork(
         const boost::posix_time::time_duration& incrementalDuration,
         const boost::function& execute,
         bool idleOnly)
{
   addScheduledCommand(boost::shared_ptr(
                           new IncrementalCommand(incrementalDuration,
                                                  execute)),
                         idleOnly);
}

void scheduleIncrementalWork(
         const boost::posix_time::time_duration& initialDuration,
         const boost::posix_time::time_duration& incrementalDuration,
         const boost::function& execute,
         bool idleOnly)
{
   addScheduledCommand(boost::shared_ptr(
                           new IncrementalCommand(initialDuration,
                                                  incrementalDuration,
                                                  execute)),
                           idleOnly);
}


void schedulePeriodicWork(const boost::posix_time::time_duration& period,
                          const boost::function &execute,
                          bool idleOnly,
                          bool immediate)
{
   addScheduledCommand(boost::shared_ptr(
                           new PeriodicCommand(period, execute, immediate)),
                       idleOnly);
}


namespace {

bool performDelayedWork(const boost::function &execute,
                        boost::shared_ptr pExecuted)
{
   if (*pExecuted)
      return false;

   *pExecuted = true;

   execute();

   return false;
}

bool isPackagePosixMakefile(const FilePath& srcPath)
{
   if (!srcPath.exists())
      return false;

   using namespace projects;
   ProjectContext& context = session::projects::projectContext();
   if (!context.hasProject())
      return false;

   if (context.config().buildType != r_util::kBuildTypePackage)
      return false;

   FilePath parentDir = srcPath.getParent();
   if (parentDir.getFilename() != "src")
      return false;

   FilePath packagePath = context.buildTargetPath();
   if (parentDir.getParent() != packagePath)
      return false;

   std::string filename = srcPath.getFilename();
   return (filename == "Makevars" ||
           filename == "Makevars.in" ||
           filename == "Makefile" ||
           filename == "Makefile.in");
}

void performIdleOnlyAsyncRpcMethod(
      const core::json::JsonRpcRequest& request,
      const core::json::JsonRpcFunctionContinuation& continuation,
      const core::json::JsonRpcAsyncFunction& function)
{
   if (request.isBackgroundConnection)
   {
      module_context::scheduleDelayedWork(
          boost::posix_time::milliseconds(100),
          boost::bind(function, request, continuation),
          true);
   }
   else
   {
      function(request, continuation);
   }
}

} // anonymous namespeace

void scheduleDelayedWork(const boost::posix_time::time_duration& period,
                         const boost::function &execute,
                         bool idleOnly)
{
   boost::shared_ptr pExecuted(new bool(false));

   schedulePeriodicWork(period,
                        boost::bind(performDelayedWork, execute, pExecuted),
                        idleOnly,
                        false);
}


void onBackgroundProcessing(bool isIdle)
{
   // allow process supervisor to poll for events
   processSupervisor().poll();

   // check for file monitor changes
   core::system::file_monitor::checkForChanges();

   // fire event
   events().onBackgroundProcessing(isIdle);

   // execute incremental commands
   executeScheduledCommands(&s_scheduledCommands);
   if (isIdle)
      executeScheduledCommands(&s_idleScheduledCommands);
}

#ifdef _WIN32

namespace {

BOOL CALLBACK consoleCtrlHandler(DWORD type)
{
   switch (type)
   {
   case CTRL_C_EVENT:
   case CTRL_BREAK_EVENT:
      rstudio::r::exec::setInterruptsPending(true);
      return true;
   default:
      return false;
   }
}

} // end anonymous namespace

#endif

void initializeConsoleCtrlHandler()
{
#ifdef _WIN32
   // accept Ctrl + C interrupts
   ::SetConsoleCtrlHandler(nullptr, FALSE);

   // remove an old registration (if any)
   ::SetConsoleCtrlHandler(consoleCtrlHandler, FALSE);

   // register console control handler
   ::SetConsoleCtrlHandler(consoleCtrlHandler, TRUE);
#endif
}

Error registerIdleOnlyAsyncRpcMethod(
                             const std::string& name,
                             const core::json::JsonRpcAsyncFunction& function)
{
   return registerAsyncRpcMethod(name,
                                 boost::bind(performIdleOnlyAsyncRpcMethod,
                                                _1, _2, function));
}

core::string_utils::LineEnding lineEndings(const core::FilePath& srcFile)
{
   // potential special case for Makevars
   if (prefs::userPrefs().useNewlinesInMakefiles() && isPackagePosixMakefile(srcFile))
      return string_utils::LineEndingPosix;

   // get the global default behavior
   string_utils::LineEnding lineEndings = prefs::userPrefs().lineEndings();

   // use project-level override if available
   using namespace session::projects;
   ProjectContext& context = projectContext();
   if (context.hasProject())
   {
      if (context.config().lineEndings != r_util::kLineEndingsUseDefault)
         lineEndings = (string_utils::LineEnding)context.config().lineEndings;
   }

   // if we are doing no conversion (passthrough) and there is an existing file
   // then we need to peek inside it to see what the existing line endings are
   if (lineEndings == string_utils::LineEndingPassthrough)
      string_utils::detectLineEndings(srcFile, &lineEndings);

   // return computed lineEndings
   return lineEndings;
}

Error readAndDecodeFile(const FilePath& filePath,
                        const std::string& encoding,
                        bool allowSubstChars,
                        std::string* pContents)
{
   // read contents
   std::string encodedContents;
   Error error = readStringFromFile(filePath, &encodedContents,
                                    options().sourceLineEnding());

   if (error)
      return error ;

   // convert to UTF-8
   return convertToUtf8(encodedContents,
                        encoding,
                        allowSubstChars,
                        pContents);
}

Error convertToUtf8(const std::string& encodedContents,
                    const std::string& encoding,
                    bool allowSubstChars,
                    std::string* pContents)
{
   Error error;
   error = r::util::iconvstr(encodedContents, encoding, "UTF-8",
                             allowSubstChars, pContents);
   if (error)
      return error;

   stripBOM(pContents);

   // Detect invalid UTF-8 sequences and recover
   error = string_utils::utf8Clean(pContents->begin(),
                                   pContents->end(),
                                   '?');
   return error ;
}

FilePath userHomePath()
{
   return session::options().userHomePath();
}

std::string createAliasedPath(const FileInfo& fileInfo)
{
   return createAliasedPath(FilePath(fileInfo.absolutePath()));
}
   
std::string createAliasedPath(const FilePath& path)
{
   return FilePath::createAliasedPath(path, userHomePath());
}   

FilePath resolveAliasedPath(const std::string& aliasedPath)
{
   return FilePath::resolveAliasedPath(aliasedPath, userHomePath());
}

FilePath userScratchPath()
{
   return session::options().userScratchPath();
}

FilePath userUploadedFilesScratchPath()
{
   return session::options().userScratchPath().completeChildPath("uploaded-files");
}

FilePath scopedScratchPath()
{
   if (projects::projectContext().hasProject())
      return projects::projectContext().scratchPath();
   else
      return userScratchPath();
}

FilePath sharedScratchPath()
{
   if (projects::projectContext().hasProject())
      return projects::projectContext().sharedScratchPath();
   else
      return userScratchPath();
}

FilePath sharedProjectScratchPath()
{
   if (projects::projectContext().hasProject())
   {
      return sharedScratchPath();
   }
   else
   {
      return FilePath();
   }
}

FilePath sessionScratchPath()
{
   r_util::ActiveSession& active = activeSession();
   if (!active.empty())
      return active.scratchPath();
   else
      return scopedScratchPath();
}

FilePath oldScopedScratchPath()
{
   if (projects::projectContext().hasProject())
      return projects::projectContext().oldScratchPath();
   else
      return userScratchPath();
}

std::string rLibsUser()
{
   return core::system::getenv("R_LIBS_USER");
}
   
bool isVisibleUserFile(const FilePath& filePath)
{
   return (filePath.isWithin(module_context::userHomePath()) &&
           !filePath.isWithin(module_context::userScratchPath()));
}

FilePath safeCurrentPath()
{
   return FilePath::safeCurrentPath(userHomePath());
}
   
FilePath tempFile(const std::string& prefix, const std::string& extension)
{
   return r::session::utils::tempFile(prefix, extension);
}

FilePath tempDir()
{
   return r::session::utils::tempDir();
}


FilePath findProgram(const std::string& name)
{
   std::string which;
   Error error = r::exec::RFunction("Sys.which", name).call(&which);
   if (error)
   {
      LOG_ERROR(error);
      return FilePath();
   }
   else
   {
      return FilePath(which);
   }
}

bool addTinytexToPathIfNecessary()
{
   // avoid some pathological cases where e.g. TinyTeX folder
   // exists but doesn't have the pdflatex binary (don't
   // attempt to re-add the folder multiple times)
   static bool s_added = false;
   if (s_added)
      return true;
   
   if (!module_context::findProgram("pdflatex").isEmpty())
      return false;
   
   SEXP binDirSEXP;
   r::sexp::Protect protect;
   Error error = r::exec::RFunction(".rs.tinytexBin").call(&binDirSEXP, &protect);
   if (error)
      LOG_ERROR(error);
   
   if (!r::sexp::isString(binDirSEXP))
      return false;
   
   std::string binDir = r::sexp::asString(binDirSEXP);
   FilePath binPath = module_context::resolveAliasedPath(binDir);
   if (!binPath.exists())
      return false;
   
   s_added = true;
   core::system::addToSystemPath(binPath);
   return true;
}

bool isPdfLatexInstalled()
{
   addTinytexToPathIfNecessary();
   return !module_context::findProgram("pdflatex").isEmpty();
}

namespace {

bool hasTextMimeType(const FilePath& filePath)
{
   std::string mimeType = filePath.getMimeContentType("");
   if (mimeType.empty())
      return false;

   return boost::algorithm::starts_with(mimeType, "text/") ||
          boost::algorithm::ends_with(mimeType, "+xml") ||
          boost::algorithm::ends_with(mimeType, "/xml");
}

bool hasBinaryMimeType(const FilePath& filePath)
{
   // screen known text types
   if (hasTextMimeType(filePath))
      return false;

   std::string mimeType = filePath.getMimeContentType("");
   if (mimeType.empty())
      return false;

   return boost::algorithm::starts_with(mimeType, "application/") ||
          boost::algorithm::starts_with(mimeType, "image/") ||
          boost::algorithm::starts_with(mimeType, "audio/") ||
          boost::algorithm::starts_with(mimeType, "video/");
}

bool isJsonFile(const FilePath& filePath)
{
   std::string mimeType = filePath.getMimeContentType();
   return boost::algorithm::ends_with(mimeType, "json");
}

} // anonymous namespace

bool isTextFile(const FilePath& targetPath)
{
   if (hasTextMimeType(targetPath))
      return true;
   
   if (isJsonFile(targetPath))
      return true;

   if (hasBinaryMimeType(targetPath))
      return false;
   
   if (targetPath.getSize() == 0)
      return true;

#ifndef _WIN32
   
   // the behavior of the 'file' command in the macOS High Sierra beta
   // changed such that '--mime' no longer ensured that mime-type strings
   // were actually emitted. using '-I' instead appears to work around this.
#ifdef __APPLE__
   const char * const kMimeTypeArg = "-I";
#else
   const char * const kMimeTypeArg = "--mime";
#endif
   
   core::shell_utils::ShellCommand cmd("file");
   cmd 

Web Proxy Viewer  |  New URL  |  Original Page