1 /*
2 * SessionModuleContext.cpp
3 *
4 * Copyright (C) 2021 by RStudio, PBC
5 *
6 * Unless you have received this program directly from RStudio pursuant
7 * to the terms of a commercial license agreement with RStudio, then
8 * this program is licensed to you under the terms of version 3 of the
9 * GNU Affero General Public License. This program is distributed WITHOUT
10 * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
11 * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
12 * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
13 *
14 */
15
16 #include "SessionModuleContextInternal.hpp"
17
18 #include <vector>
19
20 #include <boost/assert.hpp>
21 #include <boost/utility.hpp>
22 #include <boost/format.hpp>
23 #include <boost/numeric/conversion/cast.hpp>
24
25 #include <core/BoostSignals.hpp>
26 #include <core/BoostThread.hpp>
27 #include <shared_core/Error.hpp>
28 #include <shared_core/FilePath.hpp>
29 #include <core/FileInfo.hpp>
30 #include <core/Log.hpp>
31 #include <core/Base64.hpp>
32 #include <shared_core/Hash.hpp>
33 #include <core/Settings.hpp>
34 #include <core/DateTime.hpp>
35 #include <core/FileSerializer.hpp>
36 #include <core/markdown/Markdown.hpp>
37 #include <core/system/FileScanner.hpp>
38 #include <core/IncrementalCommand.hpp>
39 #include <core/PeriodicCommand.hpp>
40 #include <core/collection/Tree.hpp>
41
42 #include <core/http/Util.hpp>
43
44 #include <core/system/Process.hpp>
45 #include <core/system/FileMonitor.hpp>
46 #include <core/system/FileChangeEvent.hpp>
47 #include <core/system/Environment.hpp>
48 #include <core/system/ShellUtils.hpp>
49 #include <core/system/Xdg.hpp>
50
51 #include <core/r_util/RPackageInfo.hpp>
52
53 #include <r/RSexp.hpp>
54 #include <r/RUtil.hpp>
55 #include <r/RExec.hpp>
56 #include <r/RRoutines.hpp>
57 #include <r/RJson.hpp>
58 #include <r/RJsonRpc.hpp>
59 #include <r/RSourceManager.hpp>
60 #include <r/RErrorCategory.hpp>
61 #include <r/RFunctionHook.hpp>
62 #include <r/session/RSession.hpp>
63 #include <r/session/RConsoleActions.hpp>
64
65 #include <session/SessionOptions.hpp>
66 #include <session/SessionPersistentState.hpp>
67 #include <session/SessionClientEvent.hpp>
68 #include <session/SessionClientEventService.hpp>
69
70 #include <session/http/SessionRequest.hpp>
71
72 #include "SessionRpc.hpp"
73 #include "SessionClientEventQueue.hpp"
74 #include "SessionMainProcess.hpp"
75
76 #include <session/projects/SessionProjects.hpp>
77
78 #include <session/SessionConstants.hpp>
79 #include <session/SessionContentUrls.hpp>
80 #include <session/SessionQuarto.hpp>
81
82 #include <session/prefs/UserPrefs.hpp>
83 #include <session/prefs/UserState.hpp>
84
85 #include <shared_core/system/User.hpp>
86
87 #include "modules/SessionBreakpoints.hpp"
88 #include "modules/SessionFiles.hpp"
89 #include "modules/SessionReticulate.hpp"
90 #include "modules/SessionVCS.hpp"
91
92 #include "session-config.h"
93
94 using namespace rstudio::core;
95
96 namespace rstudio {
97 namespace session {
98 namespace module_context {
99
100 namespace {
101
102 // simple service for handling console_input rpc requests
103 class ConsoleInputService : boost::noncopyable
104 {
105 public:
106
ConsoleInputService()107 ConsoleInputService()
108 {
109 core::thread::safeLaunchThread(
110 boost::bind(&ConsoleInputService::run, this),
111 &thread_);
112 }
113
~ConsoleInputService()114 ~ConsoleInputService()
115 {
116 enqueue("!");
117 }
118
enqueue(const std::string & input)119 void enqueue(const std::string& input)
120 {
121 requests_.enque(input);
122 }
123
124 private:
125
run()126 void run()
127 {
128 while (true)
129 {
130 std::string input;
131 while (requests_.deque(&input))
132 {
133 if (input == "!")
134 return;
135
136 core::http::Response response;
137 Error error = session::http::sendSessionRequest(
138 "/rpc/console_input",
139 input,
140 &response);
141 if (error)
142 LOG_ERROR(error);
143 }
144
145 requests_.wait();
146 }
147 }
148
149 boost::thread thread_;
150 core::thread::ThreadsafeQueue<std::string> requests_;
151 };
152
consoleInputService()153 ConsoleInputService& consoleInputService()
154 {
155 static ConsoleInputService instance;
156 return instance;
157 }
158
159 // enqueClientEvent from R
rs_enqueClientEvent(SEXP nameSEXP,SEXP dataSEXP)160 SEXP rs_enqueClientEvent(SEXP nameSEXP, SEXP dataSEXP)
161 {
162 try
163 {
164 // ignore forked sessions
165 if (main_process::wasForked())
166 return R_NilValue;
167
168 // extract name
169 std::string name = r::sexp::asString(nameSEXP);
170
171 // extract json value (for primitive types we only support scalars
172 // since this is the most common type of event data). to return an
173 // array of primitives you need to wrap them in a list/object
174 Error extractError;
175 json::Value data;
176 switch(TYPEOF(dataSEXP))
177 {
178 case NILSXP:
179 {
180 // do nothing, data will be a null json value
181 break;
182 }
183 case VECSXP:
184 {
185 extractError = r::json::jsonValueFromList(dataSEXP, &data);
186 break;
187 }
188 default:
189 {
190 extractError = r::json::jsonValueFromScalar(dataSEXP, &data);
191 break;
192 }
193 }
194
195 // check for error
196 if (extractError)
197 {
198 LOG_ERROR(extractError);
199 throw r::exec::RErrorException(
200 "Couldn't extract json value from event data");
201 }
202
203 // determine the event type from the event name
204 int type = -1;
205 if (name == "package_status_changed")
206 type = session::client_events::kPackageStatusChanged;
207 else if (name == "unhandled_error")
208 type = session::client_events::kUnhandledError;
209 else if (name == "enable_rstudio_connect")
210 type = session::client_events::kEnableRStudioConnect;
211 else if (name == "shiny_gadget_dialog")
212 type = session::client_events::kShinyGadgetDialog;
213 else if (name == "rmd_params_ready")
214 type = session::client_events::kRmdParamsReady;
215 else if (name == "jump_to_function")
216 type = session::client_events::kJumpToFunction;
217 else if (name == "send_to_console")
218 type = session::client_events::kSendToConsole;
219 else if (name == "rprof_started")
220 type = session::client_events::kRprofStarted;
221 else if (name == "rprof_stopped")
222 type = session::client_events::kRprofStopped;
223 else if (name == "rprof_created")
224 type = session::client_events::kRprofCreated;
225 else if (name == "editor_command")
226 type = session::client_events::kEditorCommand;
227 else if (name == "navigate_shiny_frame")
228 type = session::client_events::kNavigateShinyFrame;
229 else if (name == "update_new_connection_dialog")
230 type = session::client_events::kUpdateNewConnectionDialog;
231 else if (name == "terminal_subprocs")
232 type = session::client_events::kTerminalSubprocs;
233 else if (name == "rstudioapi_show_dialog")
234 type = session::client_events::kRStudioAPIShowDialog;
235 else if (name == "object_explorer_event")
236 type = session::client_events::kObjectExplorerEvent;
237 else if (name == "send_to_terminal")
238 type = session::client_events::kSendToTerminal;
239 else if (name == "clear_terminal")
240 type = session::client_events::kClearTerminal;
241 else if (name == "add_terminal")
242 type = session::client_events::kAddTerminal;
243 else if (name == "activate_terminal")
244 type = session::client_events::kActivateTerminal;
245 else if (name == "terminal_cwd")
246 type = session::client_events::kTerminalCwd;
247 else if (name == "remove_terminal")
248 type = session::client_events::kRemoveTerminal;
249 else if (name == "show_page_viewer")
250 type = session::client_events::kShowPageViewerEvent;
251 else if (name == "data_output_completed")
252 type = session::client_events::kDataOutputCompleted;
253 else if (name == "new_document_with_code")
254 type = session::client_events::kNewDocumentWithCode;
255 else if (name == "available_packages_ready")
256 type = session::client_events::kAvailablePackagesReady;
257 else if (name == "compute_theme_colors")
258 type = session::client_events::kComputeThemeColors;
259 else if (name == "tutorial_command")
260 type = session::client_events::kTutorialCommand;
261 else if (name == "tutorial_launch")
262 type = session::client_events::kTutorialLaunch;
263 else if (name == "reticulate_event")
264 type = session::client_events::kReticulateEvent;
265 else if (name == "environment_assigned")
266 type = session::client_events::kEnvironmentAssigned;
267 else if (name == "environment_refresh")
268 type = session::client_events::kEnvironmentRefresh;
269 else if (name == "environment_removed")
270 type = session::client_events::kEnvironmentRemoved;
271 else if (name == "environment_changed")
272 type = session::client_events::kEnvironmentChanged;
273 else if (name == "command_callbacks_changed")
274 type = session::client_events::kCommandCallbacksChanged;
275 else if (name == "console_activate")
276 type = session::client_events::kConsoleActivate;
277
278 if (type != -1)
279 {
280 ClientEvent event(type, data);
281 session::clientEventQueue().add(event);
282 }
283 else
284 {
285 LOG_ERROR_MESSAGE("Unexpected event name from R: " + name);
286 }
287 }
288 catch (r::exec::RErrorException& e)
289 {
290 r::exec::error(e.message());
291 }
292 CATCH_UNEXPECTED_EXCEPTION
293
294 return R_NilValue;
295 }
296
rs_activatePane(SEXP paneSEXP)297 SEXP rs_activatePane(SEXP paneSEXP)
298 {
299 module_context::activatePane(r::sexp::safeAsString(paneSEXP));
300 return R_NilValue;
301 }
302
303 // show error message from R
rs_showErrorMessage(SEXP titleSEXP,SEXP messageSEXP)304 SEXP rs_showErrorMessage(SEXP titleSEXP, SEXP messageSEXP)
305 {
306 std::string title = r::sexp::asString(titleSEXP);
307 std::string message = r::sexp::asString(messageSEXP);
308 module_context::showErrorMessage(title, message);
309 return R_NilValue;
310 }
311
312 // log error message from R
rs_logErrorMessage(SEXP messageSEXP)313 SEXP rs_logErrorMessage(SEXP messageSEXP)
314 {
315 std::string message = r::sexp::asString(messageSEXP);
316 LOG_ERROR_MESSAGE(message);
317 return R_NilValue;
318 }
319
320 // log warning message from R
rs_logWarningMessage(SEXP messageSEXP)321 SEXP rs_logWarningMessage(SEXP messageSEXP)
322 {
323 std::string message = r::sexp::asString(messageSEXP);
324 LOG_WARNING_MESSAGE(message);
325 return R_NilValue;
326 }
327
328 // sleep the main thread (debugging function used to test rpc/abort)
rs_threadSleep(SEXP secondsSEXP)329 SEXP rs_threadSleep(SEXP secondsSEXP)
330 {
331 int seconds = r::sexp::asInteger(secondsSEXP);
332 boost::this_thread::sleep(boost::posix_time::seconds(seconds));
333 return R_NilValue;
334 }
335
336 // get rstudio mode
rs_rstudioProgramMode()337 SEXP rs_rstudioProgramMode()
338 {
339 r::sexp::Protect rProtect;
340 return r::sexp::create(session::options().programMode(), &rProtect);
341 }
342
343 // get rstudio edition
rs_rstudioEdition()344 SEXP rs_rstudioEdition()
345 {
346 return R_NilValue;
347 }
348
349 // get version
rs_rstudioVersion()350 SEXP rs_rstudioVersion()
351 {
352 std::string numericVersion(RSTUDIO_VERSION_MAJOR);
353 numericVersion.append(".")
354 .append(RSTUDIO_VERSION_MINOR).append(".")
355 .append(RSTUDIO_VERSION_PATCH).append(".")
356 .append(boost::regex_replace(
357 std::string(RSTUDIO_VERSION_SUFFIX),
358 boost::regex("[a-zA-Z\\-+]"),
359 ""));
360
361 r::sexp::Protect rProtect;
362 return r::sexp::create(numericVersion, &rProtect);
363 }
364
365 // get long form version
rs_rstudioLongVersion()366 SEXP rs_rstudioLongVersion()
367 {
368 r::sexp::Protect rProtect;
369 return r::sexp::create(std::string(RSTUDIO_VERSION), &rProtect);
370 }
371
372 // get release name
rs_rstudioReleaseName()373 SEXP rs_rstudioReleaseName()
374 {
375 r::sexp::Protect rProtect;
376 return r::sexp::create(std::string(RSTUDIO_RELEASE_NAME), &rProtect);
377 }
378
379 // get citation
rs_rstudioCitation()380 SEXP rs_rstudioCitation()
381 {
382 FilePath resPath = session::options().rResourcesPath();
383 FilePath citationPath = resPath.completeChildPath("CITATION");
384
385 // the citation file may not exist when working in e.g.
386 // development configurations so just ignore if it's missing
387 if (!citationPath.exists())
388 return R_NilValue;
389
390 SEXP citationSEXP;
391 r::sexp::Protect rProtect;
392 Error error = r::exec::RFunction("utils:::readCitationFile",
393 citationPath.getAbsolutePath())
394 .call(&citationSEXP,
395 &rProtect);
396
397 if (error)
398 {
399 LOG_ERROR(error);
400 return R_NilValue;
401 }
402 else
403 {
404 return citationSEXP;
405 }
406 }
407
rs_setUsingMingwGcc49(SEXP usingSEXP)408 SEXP rs_setUsingMingwGcc49(SEXP usingSEXP)
409 {
410 bool usingMingwGcc49 = r::sexp::asLogical(usingSEXP);
411 prefs::userState().setUsingMingwGcc49(usingMingwGcc49);
412 return R_NilValue;
413 }
414
415 // ensure file hidden
rs_ensureFileHidden(SEXP fileSEXP)416 SEXP rs_ensureFileHidden(SEXP fileSEXP)
417 {
418 #ifdef _WIN32
419 std::string file = r::sexp::asString(fileSEXP);
420 if (!file.empty())
421 {
422 FilePath filePath = module_context::resolveAliasedPath(file);
423 Error error = core::system::makeFileHidden(filePath);
424 if (error)
425 LOG_ERROR(error);
426 }
427 #endif
428
429 return R_NilValue;
430 }
431
rs_sourceDiagnostics()432 SEXP rs_sourceDiagnostics()
433 {
434 module_context::sourceDiagnostics();
435 return R_NilValue;
436 }
437
rs_packageLoaded(SEXP pkgnameSEXP)438 SEXP rs_packageLoaded(SEXP pkgnameSEXP)
439 {
440 if (main_process::wasForked())
441 return R_NilValue;
442
443 std::string pkgname = r::sexp::safeAsString(pkgnameSEXP);
444
445 // fire server event
446 events().onPackageLoaded(pkgname);
447
448 // fire client event
449 ClientEvent packageLoadedEvent(
450 client_events::kPackageLoaded,
451 json::Value(pkgname));
452 enqueClientEvent(packageLoadedEvent);
453
454 return R_NilValue;
455 }
456
rs_packageUnloaded(SEXP pkgnameSEXP)457 SEXP rs_packageUnloaded(SEXP pkgnameSEXP)
458 {
459 if (main_process::wasForked())
460 return R_NilValue;
461
462 std::string pkgname = r::sexp::safeAsString(pkgnameSEXP);
463 ClientEvent packageUnloadedEvent(
464 client_events::kPackageUnloaded,
465 json::Value(pkgname));
466 enqueClientEvent(packageUnloadedEvent);
467
468 return R_NilValue;
469 }
470
rs_userPrompt(SEXP typeSEXP,SEXP captionSEXP,SEXP messageSEXP,SEXP yesLabelSEXP,SEXP noLabelSEXP,SEXP includeCancelSEXP,SEXP yesIsDefaultSEXP)471 SEXP rs_userPrompt(SEXP typeSEXP,
472 SEXP captionSEXP,
473 SEXP messageSEXP,
474 SEXP yesLabelSEXP,
475 SEXP noLabelSEXP,
476 SEXP includeCancelSEXP,
477 SEXP yesIsDefaultSEXP)
478 {
479 UserPrompt prompt(r::sexp::asInteger(typeSEXP),
480 r::sexp::safeAsString(captionSEXP),
481 r::sexp::safeAsString(messageSEXP),
482 r::sexp::safeAsString(yesLabelSEXP),
483 r::sexp::safeAsString(noLabelSEXP),
484 r::sexp::asLogical(includeCancelSEXP),
485 r::sexp::asLogical(yesIsDefaultSEXP));
486
487 UserPrompt::Response response = showUserPrompt(prompt);
488
489 r::sexp::Protect rProtect;
490 return r::sexp::create(response, &rProtect);
491 }
492
rs_restartR(SEXP afterRestartSEXP)493 SEXP rs_restartR(SEXP afterRestartSEXP)
494 {
495 std::string afterRestart = r::sexp::safeAsString(afterRestartSEXP);
496 json::Object dataJson;
497 dataJson["after_restart"] = afterRestart;
498 ClientEvent event(client_events::kSuspendAndRestart, dataJson);
499 module_context::enqueClientEvent(event);
500 return R_NilValue;
501 }
502
rs_generateShortUuid()503 SEXP rs_generateShortUuid()
504 {
505 // generate a short uuid -- we make this available in R code so that it's
506 // possible to create random identifiers without perturbing the state of the
507 // RNG that R uses
508 std::string uuid = core::system::generateShortenedUuid();
509 r::sexp::Protect rProtect;
510 return r::sexp::create(uuid, &rProtect);
511 }
512
rs_markdownToHTML(SEXP contentSEXP)513 SEXP rs_markdownToHTML(SEXP contentSEXP)
514 {
515 std::string content = r::sexp::safeAsString(contentSEXP);
516 std::string htmlContent;
517 Error error = markdown::markdownToHTML(content,
518 markdown::Extensions(),
519 markdown::HTMLOptions(),
520 &htmlContent);
521 if (error)
522 {
523 LOG_ERROR(error);
524 htmlContent = content;
525 }
526
527 r::sexp::Protect rProtect;
528 return r::sexp::create(htmlContent, &rProtect);
529 }
530
persistantValueName(SEXP nameSEXP)531 inline std::string persistantValueName(SEXP nameSEXP)
532 {
533 return "rstudioapi_persistent_values_" + r::sexp::safeAsString(nameSEXP);
534 }
535
rs_setPersistentValue(SEXP nameSEXP,SEXP valueSEXP)536 SEXP rs_setPersistentValue(SEXP nameSEXP, SEXP valueSEXP)
537 {
538 std::string name = persistantValueName(nameSEXP);
539 std::string value = r::sexp::safeAsString(valueSEXP);
540 persistentState().settings().set(name, value);
541 return R_NilValue;
542 }
543
rs_getPersistentValue(SEXP nameSEXP)544 SEXP rs_getPersistentValue(SEXP nameSEXP)
545 {
546 std::string name = persistantValueName(nameSEXP);
547 if (persistentState().settings().contains(name))
548 {
549 std::string value = persistentState().settings().get(name);
550 r::sexp::Protect rProtect;
551 return r::sexp::create(value, &rProtect);
552 }
553 else
554 {
555 return R_NilValue;
556 }
557 }
558
rs_setRpcDelay(SEXP delayMsSEXP)559 SEXP rs_setRpcDelay(SEXP delayMsSEXP)
560 {
561 int delayMs = r::sexp::asInteger(delayMsSEXP);
562 rstudio::session::rpc::setRpcDelay(delayMs);
563 return delayMsSEXP;
564 }
565
566 // Force background processing to occur; used during testing since the user is never idle during
567 // headless test execution
rs_performBackgroundProcessing(SEXP isIdle)568 SEXP rs_performBackgroundProcessing(SEXP isIdle)
569 {
570 onBackgroundProcessing(r::sexp::asLogical(isIdle));
571 return R_NilValue;
572 }
573
574 } // anonymous namespace
575
576
577 // register a scratch path which is monitored
578
579 namespace {
580
581 typedef std::map<FilePath,OnFileChange> MonitoredScratchPaths;
582 MonitoredScratchPaths s_monitoredScratchPaths;
583 bool s_monitorByScanning = false;
584
monitoredParentPath()585 FilePath monitoredParentPath()
586 {
587 FilePath monitoredPath = userScratchPath().completePath(kMonitoredPath);
588 Error error = monitoredPath.ensureDirectory();
589 if (error)
590 LOG_ERROR(error);
591 return monitoredPath;
592 }
593
monitoredScratchFilter(const FileInfo & fileInfo)594 bool monitoredScratchFilter(const FileInfo& fileInfo)
595 {
596 return true;
597 }
598
599
onFilesChanged(const std::vector<core::system::FileChangeEvent> & changes)600 void onFilesChanged(const std::vector<core::system::FileChangeEvent>& changes)
601 {
602 for (const core::system::FileChangeEvent& fileChange : changes)
603 {
604 FilePath changedFilePath(fileChange.fileInfo().absolutePath());
605 for (MonitoredScratchPaths::const_iterator
606 it = s_monitoredScratchPaths.begin();
607 it != s_monitoredScratchPaths.end();
608 ++it)
609 {
610 if (changedFilePath.isWithin(it->first))
611 {
612 it->second(fileChange);
613 break;
614 }
615 }
616
617 }
618 }
619
monitoredPathTree()620 boost::shared_ptr<tree<FileInfo> > monitoredPathTree()
621 {
622 boost::shared_ptr<tree<FileInfo> > pMonitoredTree(new tree<FileInfo>());
623 core::system::FileScannerOptions options;
624 options.recursive = true;
625 options.filter = monitoredScratchFilter;
626 Error scanError = scanFiles(FileInfo(monitoredParentPath()),
627 options,
628 pMonitoredTree.get());
629 if (scanError)
630 LOG_ERROR(scanError);
631
632 return pMonitoredTree;
633 }
634
scanForMonitoredPathChanges(boost::shared_ptr<tree<FileInfo>> pPrevTree)635 bool scanForMonitoredPathChanges(boost::shared_ptr<tree<FileInfo> > pPrevTree)
636 {
637 // check for changes
638 std::vector<core::system::FileChangeEvent> changes;
639 boost::shared_ptr<tree<FileInfo> > pCurrentTree = monitoredPathTree();
640 core::system::collectFileChangeEvents(pPrevTree->begin(),
641 pPrevTree->end(),
642 pCurrentTree->begin(),
643 pCurrentTree->end(),
644 &changes);
645
646 // fire events
647 onFilesChanged(changes);
648
649 // reset the tree
650 *pPrevTree = *pCurrentTree;
651
652 // scan again after interval
653 return true;
654 }
655
onMonitoringError(const Error & error)656 void onMonitoringError(const Error& error)
657 {
658 // log the error
659 LOG_ERROR(error);
660
661 // fallback to periodically scanning for changes
662 if (!s_monitorByScanning)
663 {
664 s_monitorByScanning = true;
665 module_context::schedulePeriodicWork(
666 boost::posix_time::seconds(3),
667 boost::bind(scanForMonitoredPathChanges, monitoredPathTree()),
668 true);
669 }
670 }
671
initializeMonitoredUserScratchDir()672 void initializeMonitoredUserScratchDir()
673 {
674 // setup callbacks and register
675 core::system::file_monitor::Callbacks cb;
676 cb.onRegistrationError = onMonitoringError;
677 cb.onMonitoringError = onMonitoringError;
678 cb.onFilesChanged = onFilesChanged;
679 core::system::file_monitor::registerMonitor(
680 monitoredParentPath(),
681 true,
682 monitoredScratchFilter,
683 cb);
684 }
685
686
687 } // anonymous namespace
688
registerMonitoredUserScratchDir(const std::string & dirName,const OnFileChange & onFileChange)689 FilePath registerMonitoredUserScratchDir(const std::string& dirName,
690 const OnFileChange& onFileChange)
691 {
692 // create the subdir
693 FilePath dirPath = monitoredParentPath().completePath(dirName);
694 Error error = dirPath.ensureDirectory();
695 if (error)
696 LOG_ERROR(error);
697
698 // register the path
699 s_monitoredScratchPaths[dirPath] = onFileChange;
700
701 // return it
702 return dirPath;
703 }
704
705
706 namespace {
707
708 // manage signals used for custom save and restore
709 class SuspendHandlers : boost::noncopyable
710 {
711 public:
SuspendHandlers()712 SuspendHandlers() : nextGroup_(0) {}
713
714 public:
add(const SuspendHandler & handler)715 void add(const SuspendHandler& handler)
716 {
717 int group = nextGroup_++;
718 suspendSignal_.connect(group, handler.suspend());
719 resumeSignal_.connect(group, handler.resume());
720 }
721
suspend(const r::session::RSuspendOptions & options,Settings * pSettings)722 void suspend(const r::session::RSuspendOptions& options,
723 Settings* pSettings)
724 {
725 suspendSignal_(options, pSettings);
726 }
727
resume(const Settings & settings)728 void resume(const Settings& settings)
729 {
730 resumeSignal_(settings);
731 }
732
733 private:
734
735 // use groups to ensure signal order. call suspend handlers in order
736 // of subscription and call resume handlers in reverse order of
737 // subscription.
738
739 int nextGroup_;
740
741 RSTUDIO_BOOST_SIGNAL<void(const r::session::RSuspendOptions&,Settings*),
742 RSTUDIO_BOOST_LAST_VALUE<void>,
743 int,
744 std::less<int> > suspendSignal_;
745
746 RSTUDIO_BOOST_SIGNAL<void(const Settings&),
747 RSTUDIO_BOOST_LAST_VALUE<void>,
748 int,
749 std::greater<int> > resumeSignal_;
750 };
751
752 // handlers instance
suspendHandlers()753 SuspendHandlers& suspendHandlers()
754 {
755 static SuspendHandlers instance;
756 return instance;
757 }
758
759 } // anonymous namespace
760
addSuspendHandler(const SuspendHandler & handler)761 void addSuspendHandler(const SuspendHandler& handler)
762 {
763 suspendHandlers().add(handler);
764 }
765
onSuspended(const r::session::RSuspendOptions & options,Settings * pPersistentState)766 void onSuspended(const r::session::RSuspendOptions& options,
767 Settings* pPersistentState)
768 {
769 pPersistentState->beginUpdate();
770 suspendHandlers().suspend(options, pPersistentState);
771 pPersistentState->endUpdate();
772
773 }
774
onResumed(const Settings & persistentState)775 void onResumed(const Settings& persistentState)
776 {
777 suspendHandlers().resume(persistentState);
778 }
779
780 // idle work
781
782 namespace {
783
784 typedef std::vector<boost::shared_ptr<ScheduledCommand> >
785 ScheduledCommands;
786 ScheduledCommands s_scheduledCommands;
787 ScheduledCommands s_idleScheduledCommands;
788
addScheduledCommand(boost::shared_ptr<ScheduledCommand> pCommand,bool idleOnly)789 void addScheduledCommand(boost::shared_ptr<ScheduledCommand> pCommand,
790 bool idleOnly)
791 {
792 if (idleOnly)
793 s_idleScheduledCommands.push_back(pCommand);
794 else
795 s_scheduledCommands.push_back(pCommand);
796 }
797
executeScheduledCommands(ScheduledCommands * pCommands)798 void executeScheduledCommands(ScheduledCommands* pCommands)
799 {
800 // make a copy of scheduled commands before executing them
801 // (this is because a scheduled command could result in
802 // R code executing which in turn could cause the list of
803 // scheduled commands to be mutated and these iterators
804 // invalidated)
805 ScheduledCommands commands = *pCommands;
806
807 // execute all commands
808 std::for_each(commands.begin(),
809 commands.end(),
810 boost::bind(&ScheduledCommand::execute, _1));
811
812 // remove any commands which are finished
813 pCommands->erase(
814 std::remove_if(
815 pCommands->begin(),
816 pCommands->end(),
817 boost::bind(&ScheduledCommand::finished, _1)),
818 pCommands->end());
819 }
820
821
822 } // anonymous namespace
823
scheduleIncrementalWork(const boost::posix_time::time_duration & incrementalDuration,const boost::function<bool ()> & execute,bool idleOnly)824 void scheduleIncrementalWork(
825 const boost::posix_time::time_duration& incrementalDuration,
826 const boost::function<bool()>& execute,
827 bool idleOnly)
828 {
829 addScheduledCommand(boost::shared_ptr<ScheduledCommand>(
830 new IncrementalCommand(incrementalDuration,
831 execute)),
832 idleOnly);
833 }
834
scheduleIncrementalWork(const boost::posix_time::time_duration & initialDuration,const boost::posix_time::time_duration & incrementalDuration,const boost::function<bool ()> & execute,bool idleOnly)835 void scheduleIncrementalWork(
836 const boost::posix_time::time_duration& initialDuration,
837 const boost::posix_time::time_duration& incrementalDuration,
838 const boost::function<bool()>& execute,
839 bool idleOnly)
840 {
841 addScheduledCommand(boost::shared_ptr<ScheduledCommand>(
842 new IncrementalCommand(initialDuration,
843 incrementalDuration,
844 execute)),
845 idleOnly);
846 }
847
848
schedulePeriodicWork(const boost::posix_time::time_duration & period,const boost::function<bool ()> & execute,bool idleOnly,bool immediate)849 void schedulePeriodicWork(const boost::posix_time::time_duration& period,
850 const boost::function<bool()> &execute,
851 bool idleOnly,
852 bool immediate)
853 {
854 addScheduledCommand(boost::shared_ptr<ScheduledCommand>(
855 new PeriodicCommand(period, execute, immediate)),
856 idleOnly);
857 }
858
859
860 namespace {
861
performDelayedWork(const boost::function<void ()> & execute,boost::shared_ptr<bool> pExecuted)862 bool performDelayedWork(const boost::function<void()> &execute,
863 boost::shared_ptr<bool> pExecuted)
864 {
865 if (*pExecuted)
866 return false;
867
868 *pExecuted = true;
869
870 execute();
871
872 return false;
873 }
874
isPackagePosixMakefile(const FilePath & srcPath)875 bool isPackagePosixMakefile(const FilePath& srcPath)
876 {
877 if (!srcPath.exists())
878 return false;
879
880 using namespace projects;
881 ProjectContext& context = session::projects::projectContext();
882 if (!context.hasProject())
883 return false;
884
885 if (context.config().buildType != r_util::kBuildTypePackage)
886 return false;
887
888 FilePath parentDir = srcPath.getParent();
889 if (parentDir.getFilename() != "src")
890 return false;
891
892 FilePath packagePath = context.buildTargetPath();
893 if (parentDir.getParent() != packagePath)
894 return false;
895
896 std::string filename = srcPath.getFilename();
897 return (filename == "Makevars" ||
898 filename == "Makevars.in" ||
899 filename == "Makefile" ||
900 filename == "Makefile.in");
901 }
902
performIdleOnlyAsyncRpcMethod(const core::json::JsonRpcRequest & request,const core::json::JsonRpcFunctionContinuation & continuation,const core::json::JsonRpcAsyncFunction & function)903 void performIdleOnlyAsyncRpcMethod(
904 const core::json::JsonRpcRequest& request,
905 const core::json::JsonRpcFunctionContinuation& continuation,
906 const core::json::JsonRpcAsyncFunction& function)
907 {
908 if (request.isBackgroundConnection)
909 {
910 module_context::scheduleDelayedWork(
911 boost::posix_time::milliseconds(100),
912 boost::bind(function, request, continuation),
913 true);
914 }
915 else
916 {
917 function(request, continuation);
918 }
919 }
920
921 } // anonymous namespeace
922
scheduleDelayedWork(const boost::posix_time::time_duration & period,const boost::function<void ()> & execute,bool idleOnly)923 void scheduleDelayedWork(const boost::posix_time::time_duration& period,
924 const boost::function<void()> &execute,
925 bool idleOnly)
926 {
927 boost::shared_ptr<bool> pExecuted(new bool(false));
928
929 schedulePeriodicWork(period,
930 boost::bind(performDelayedWork, execute, pExecuted),
931 idleOnly,
932 false);
933 }
934
935
onBackgroundProcessing(bool isIdle)936 void onBackgroundProcessing(bool isIdle)
937 {
938 // allow process supervisor to poll for events
939 processSupervisor().poll();
940
941 // check for file monitor changes
942 core::system::file_monitor::checkForChanges();
943
944 // fire event
945 events().onBackgroundProcessing(isIdle);
946
947 // execute incremental commands
948 executeScheduledCommands(&s_scheduledCommands);
949 if (isIdle)
950 executeScheduledCommands(&s_idleScheduledCommands);
951 }
952
953 #ifdef _WIN32
954
955 namespace {
956
consoleCtrlHandler(DWORD type)957 BOOL CALLBACK consoleCtrlHandler(DWORD type)
958 {
959 switch (type)
960 {
961 case CTRL_C_EVENT:
962 case CTRL_BREAK_EVENT:
963 rstudio::r::exec::setInterruptsPending(true);
964 return true;
965 default:
966 return false;
967 }
968 }
969
970 } // end anonymous namespace
971
972 #endif
973
initializeConsoleCtrlHandler()974 void initializeConsoleCtrlHandler()
975 {
976 #ifdef _WIN32
977 // accept Ctrl + C interrupts
978 ::SetConsoleCtrlHandler(nullptr, FALSE);
979
980 // remove an old registration (if any)
981 ::SetConsoleCtrlHandler(consoleCtrlHandler, FALSE);
982
983 // register console control handler
984 ::SetConsoleCtrlHandler(consoleCtrlHandler, TRUE);
985 #endif
986 }
987
registerIdleOnlyAsyncRpcMethod(const std::string & name,const core::json::JsonRpcAsyncFunction & function)988 Error registerIdleOnlyAsyncRpcMethod(
989 const std::string& name,
990 const core::json::JsonRpcAsyncFunction& function)
991 {
992 return registerAsyncRpcMethod(name,
993 boost::bind(performIdleOnlyAsyncRpcMethod,
994 _1, _2, function));
995 }
996
lineEndings(const core::FilePath & srcFile)997 core::string_utils::LineEnding lineEndings(const core::FilePath& srcFile)
998 {
999 // potential special case for Makevars
1000 if (prefs::userPrefs().useNewlinesInMakefiles() && isPackagePosixMakefile(srcFile))
1001 return string_utils::LineEndingPosix;
1002
1003 // get the global default behavior
1004 string_utils::LineEnding lineEndings = prefs::userPrefs().lineEndings();
1005
1006 // use project-level override if available
1007 using namespace session::projects;
1008 ProjectContext& context = projectContext();
1009 if (context.hasProject())
1010 {
1011 if (context.config().lineEndings != r_util::kLineEndingsUseDefault)
1012 lineEndings = (string_utils::LineEnding)context.config().lineEndings;
1013 }
1014
1015 // if we are doing no conversion (passthrough) and there is an existing file
1016 // then we need to peek inside it to see what the existing line endings are
1017 if (lineEndings == string_utils::LineEndingPassthrough)
1018 string_utils::detectLineEndings(srcFile, &lineEndings);
1019
1020 // return computed lineEndings
1021 return lineEndings;
1022 }
1023
readAndDecodeFile(const FilePath & filePath,const std::string & encoding,bool allowSubstChars,std::string * pContents)1024 Error readAndDecodeFile(const FilePath& filePath,
1025 const std::string& encoding,
1026 bool allowSubstChars,
1027 std::string* pContents)
1028 {
1029 // read contents
1030 std::string encodedContents;
1031 Error error = readStringFromFile(filePath, &encodedContents,
1032 options().sourceLineEnding());
1033
1034 if (error)
1035 return error;
1036
1037 // convert to UTF-8
1038 return convertToUtf8(encodedContents,
1039 encoding,
1040 allowSubstChars,
1041 pContents);
1042 }
1043
convertToUtf8(const std::string & encodedContents,const std::string & encoding,bool allowSubstChars,std::string * pContents)1044 Error convertToUtf8(const std::string& encodedContents,
1045 const std::string& encoding,
1046 bool allowSubstChars,
1047 std::string* pContents)
1048 {
1049 Error error;
1050 error = r::util::iconvstr(encodedContents, encoding, "UTF-8",
1051 allowSubstChars, pContents);
1052 if (error)
1053 return error;
1054
1055 stripBOM(pContents);
1056
1057 // Detect invalid UTF-8 sequences and recover
1058 error = string_utils::utf8Clean(pContents->begin(),
1059 pContents->end(),
1060 '?');
1061 return error;
1062 }
1063
userHomePath()1064 FilePath userHomePath()
1065 {
1066 return session::options().userHomePath();
1067 }
1068
createAliasedPath(const FileInfo & fileInfo)1069 std::string createAliasedPath(const FileInfo& fileInfo)
1070 {
1071 return createAliasedPath(FilePath(fileInfo.absolutePath()));
1072 }
1073
createAliasedPath(const FilePath & path)1074 std::string createAliasedPath(const FilePath& path)
1075 {
1076 return FilePath::createAliasedPath(path, userHomePath());
1077 }
1078
resolveAliasedPath(const std::string & aliasedPath)1079 FilePath resolveAliasedPath(const std::string& aliasedPath)
1080 {
1081 return FilePath::resolveAliasedPath(aliasedPath, userHomePath());
1082 }
1083
userScratchPath()1084 FilePath userScratchPath()
1085 {
1086 return session::options().userScratchPath();
1087 }
1088
userUploadedFilesScratchPath()1089 FilePath userUploadedFilesScratchPath()
1090 {
1091 return session::options().userScratchPath().completeChildPath("uploaded-files");
1092 }
1093
scopedScratchPath()1094 FilePath scopedScratchPath()
1095 {
1096 if (projects::projectContext().hasProject())
1097 return projects::projectContext().scratchPath();
1098 else
1099 return userScratchPath();
1100 }
1101
sharedScratchPath()1102 FilePath sharedScratchPath()
1103 {
1104 if (projects::projectContext().hasProject())
1105 return projects::projectContext().sharedScratchPath();
1106 else
1107 return userScratchPath();
1108 }
1109
sharedProjectScratchPath()1110 FilePath sharedProjectScratchPath()
1111 {
1112 if (projects::projectContext().hasProject())
1113 {
1114 return sharedScratchPath();
1115 }
1116 else
1117 {
1118 return FilePath();
1119 }
1120 }
1121
sessionScratchPath()1122 FilePath sessionScratchPath()
1123 {
1124 r_util::ActiveSession& active = activeSession();
1125 if (!active.empty())
1126 return active.scratchPath();
1127 else
1128 return scopedScratchPath();
1129 }
1130
oldScopedScratchPath()1131 FilePath oldScopedScratchPath()
1132 {
1133 if (projects::projectContext().hasProject())
1134 return projects::projectContext().oldScratchPath();
1135 else
1136 return userScratchPath();
1137 }
1138
rLibsUser()1139 std::string rLibsUser()
1140 {
1141 return core::system::getenv("R_LIBS_USER");
1142 }
1143
isVisibleUserFile(const FilePath & filePath)1144 bool isVisibleUserFile(const FilePath& filePath)
1145 {
1146 return (filePath.isWithin(module_context::userHomePath()) &&
1147 !filePath.isWithin(module_context::userScratchPath()));
1148 }
1149
safeCurrentPath()1150 FilePath safeCurrentPath()
1151 {
1152 return FilePath::safeCurrentPath(userHomePath());
1153 }
1154
tempFile(const std::string & prefix,const std::string & extension)1155 FilePath tempFile(const std::string& prefix, const std::string& extension)
1156 {
1157 return r::session::utils::tempFile(prefix, extension);
1158 }
1159
tempDir()1160 FilePath tempDir()
1161 {
1162 return r::session::utils::tempDir();
1163 }
1164
1165
findProgram(const std::string & name)1166 FilePath findProgram(const std::string& name)
1167 {
1168 // Added isMainThread() test here to allow the console to run offline.
1169 // On starting a terminal, it does 'which svn' and adds that dir to the
1170 // path if it find one
1171 // FUTURE: Can we always use findProgramOnPath? It will be a lot faster since R's
1172 // version seems to fork a process to call the shell's 'which'
1173 // Be careful of Windows there are two copies of the environment so
1174 // R's env might be out of sync with rsession's PATH.
1175 if (!r::exec::isMainThread())
1176 {
1177 FilePath resultPath;
1178 Error error = system::findProgramOnPath(name, &resultPath);
1179 if (error)
1180 {
1181 return FilePath();
1182 }
1183 return resultPath;
1184 }
1185 else
1186 {
1187 std::string which;
1188 FilePath resultPath;
1189
1190 // For now, going to check both ways and log warnings if they don't match
1191 Error error = r::exec::RFunction("Sys.which", name).call(&which);
1192 Error dbgError = system::findProgramOnPath(name, &resultPath);
1193 if (error)
1194 {
1195 LOG_ERROR(error);
1196 return FilePath();
1197 }
1198 else
1199 {
1200 if (dbgError && which != "")
1201 {
1202 LOG_WARNING_MESSAGE("findProgramOnPath returns error: " + dbgError.asString() + " Sys.which returns: " + resultPath.getAbsolutePath());
1203 }
1204 else if (which != resultPath.getAbsolutePath())
1205 {
1206 LOG_WARNING_MESSAGE("findProgramOnPath returns wrong result: " + which + " != " + resultPath.getAbsolutePath());
1207 }
1208 return FilePath(which);
1209 }
1210 }
1211 }
1212
addTinytexToPathIfNecessary()1213 bool addTinytexToPathIfNecessary()
1214 {
1215 // avoid some pathological cases where e.g. TinyTeX folder
1216 // exists but doesn't have the pdflatex binary (don't
1217 // attempt to re-add the folder multiple times)
1218 static bool s_added = false;
1219 if (s_added)
1220 return true;
1221
1222 if (!module_context::findProgram("pdflatex").isEmpty())
1223 return false;
1224
1225 SEXP binDirSEXP = R_NilValue;
1226 r::sexp::Protect protect;
1227 Error error = r::exec::RFunction(".rs.tinytexBin").call(&binDirSEXP, &protect);
1228 if (error)
1229 {
1230 LOG_ERROR(error);
1231 return false;
1232 }
1233
1234 if (!r::sexp::isString(binDirSEXP))
1235 return false;
1236
1237 std::string binDir = r::sexp::asString(binDirSEXP);
1238 FilePath binPath = module_context::resolveAliasedPath(binDir);
1239 if (!binPath.exists())
1240 return false;
1241
1242 s_added = true;
1243 core::system::addToSystemPath(binPath);
1244 return true;
1245 }
1246
isPdfLatexInstalled()1247 bool isPdfLatexInstalled()
1248 {
1249 addTinytexToPathIfNecessary();
1250 return !module_context::findProgram("pdflatex").isEmpty();
1251 }
1252
1253 namespace {
1254
hasTextMimeType(const FilePath & filePath)1255 bool hasTextMimeType(const FilePath& filePath)
1256 {
1257 std::string mimeType = filePath.getMimeContentType("");
1258 if (mimeType.empty())
1259 return false;
1260
1261 return boost::algorithm::starts_with(mimeType, "text/") ||
1262 boost::algorithm::ends_with(mimeType, "+xml") ||
1263 boost::algorithm::ends_with(mimeType, "/xml");
1264 }
1265
hasBinaryMimeType(const FilePath & filePath)1266 bool hasBinaryMimeType(const FilePath& filePath)
1267 {
1268 // screen known text types
1269 if (hasTextMimeType(filePath))
1270 return false;
1271
1272 std::string mimeType = filePath.getMimeContentType("");
1273 if (mimeType.empty())
1274 return false;
1275
1276 return boost::algorithm::starts_with(mimeType, "application/") ||
1277 boost::algorithm::starts_with(mimeType, "image/") ||
1278 boost::algorithm::starts_with(mimeType, "audio/") ||
1279 boost::algorithm::starts_with(mimeType, "video/");
1280 }
1281
isJsonFile(const FilePath & filePath)1282 bool isJsonFile(const FilePath& filePath)
1283 {
1284 std::string mimeType = filePath.getMimeContentType();
1285 return boost::algorithm::ends_with(mimeType, "json");
1286 }
1287
1288 } // anonymous namespace
1289
isTextFile(const FilePath & targetPath)1290 bool isTextFile(const FilePath& targetPath)
1291 {
1292 if (hasTextMimeType(targetPath))
1293 return true;
1294
1295 if (isJsonFile(targetPath))
1296 return true;
1297
1298 if (hasBinaryMimeType(targetPath))
1299 return false;
1300
1301 if (targetPath.getSize() == 0)
1302 return true;
1303
1304 #ifndef _WIN32
1305
1306 // the behavior of the 'file' command in the macOS High Sierra beta
1307 // changed such that '--mime' no longer ensured that mime-type strings
1308 // were actually emitted. using '-I' instead appears to work around this.
1309 #ifdef __APPLE__
1310 const char * const kMimeTypeArg = "-I";
1311 #else
1312 const char * const kMimeTypeArg = "--mime";
1313 #endif
1314
1315 core::shell_utils::ShellCommand cmd("file");
1316 cmd << "--dereference";
1317 cmd << kMimeTypeArg;
1318 cmd << "--brief";
1319 cmd << targetPath;
1320 core::system::ProcessResult result;
1321 Error error = core::system::runCommand(cmd,
1322 core::system::ProcessOptions(),
1323 &result);
1324 if (error)
1325 {
1326 LOG_ERROR(error);
1327 return !!error;
1328 }
1329
1330 // strip encoding
1331 std::string fileType = boost::algorithm::trim_copy(result.stdOut);
1332 fileType = fileType.substr(0, fileType.find(';'));
1333
1334 // check value
1335 return boost::algorithm::starts_with(fileType, "text/") ||
1336 boost::algorithm::ends_with(fileType, "+xml") ||
1337 boost::algorithm::ends_with(fileType, "/xml") ||
1338 boost::algorithm::ends_with(fileType, "x-empty") ||
1339 boost::algorithm::equals(fileType, "application/json") ||
1340 boost::algorithm::equals(fileType, "application/postscript");
1341 #else
1342
1343 // read contents of file
1344 std::string contents;
1345 Error error = core::readStringFromFile(targetPath, &contents);
1346 if (error)
1347 {
1348 LOG_ERROR(error);
1349 return !!error;
1350 }
1351
1352 // does it have null bytes?
1353 std::string nullBytes;
1354 nullBytes.push_back('\0');
1355 nullBytes.push_back('\0');
1356 return !boost::algorithm::contains(contents, nullBytes);
1357
1358 #endif
1359
1360 }
1361
editFile(const core::FilePath & filePath,int lineNumber)1362 void editFile(const core::FilePath& filePath, int lineNumber)
1363 {
1364 // construct file system item (also tag with mime type) and position
1365 json::Object fileJson = module_context::createFileSystemItem(filePath);
1366 fileJson["mime_type"] = filePath.getMimeContentType();
1367
1368 json::Value positionJsonValue;
1369 if (lineNumber >= 0)
1370 {
1371 json::Object positionJson;
1372 positionJson["line"] = lineNumber;
1373 positionJson["column"] = 1;
1374 positionJsonValue = positionJson;
1375 }
1376
1377 // fire event
1378 json::Object eventJson;
1379 eventJson["file"] = fileJson;
1380 eventJson["position"] = positionJsonValue;
1381 ClientEvent event(client_events::kFileEdit, eventJson);
1382 module_context::enqueClientEvent(event);
1383 }
1384
rBinDir(core::FilePath * pRBinDirPath)1385 Error rBinDir(core::FilePath* pRBinDirPath)
1386 {
1387 std::string rHomeBin;
1388 r::exec::RFunction rHomeBinFunc("R.home", "bin");
1389 Error error = rHomeBinFunc.call(&rHomeBin);
1390 if (error)
1391 return error;
1392
1393 *pRBinDirPath = FilePath(rHomeBin);
1394 return Success();
1395 }
1396
1397
rScriptPath(FilePath * pRScriptPath)1398 Error rScriptPath(FilePath* pRScriptPath)
1399 {
1400 FilePath rHomeBinPath;
1401 Error error = rBinDir(&rHomeBinPath);
1402 if (error)
1403 return error;
1404
1405 #ifdef _WIN32
1406 *pRScriptPath = rHomeBinPath.completePath("Rterm.exe");
1407 #else
1408 *pRScriptPath = rHomeBinPath.completePath("R");
1409 #endif
1410 return Success();
1411 }
1412
rCmd(const core::FilePath & rBinDir)1413 shell_utils::ShellCommand rCmd(const core::FilePath& rBinDir)
1414 {
1415 #ifdef _WIN32
1416 return shell_utils::ShellCommand(rBinDir.completeChildPath("Rcmd.exe"));
1417 #else
1418 shell_utils::ShellCommand rCmd(rBinDir.completeChildPath("R"));
1419 rCmd << "CMD";
1420 return rCmd;
1421 #endif
1422 }
1423
1424 // get the R local help port
rLocalHelpPort()1425 std::string rLocalHelpPort()
1426 {
1427 std::string port;
1428 Error error = r::exec::RFunction(".rs.httpdPort").call(&port);
1429 if (error)
1430 LOG_ERROR(error);
1431 return port;
1432 }
1433
getLibPaths()1434 std::vector<FilePath> getLibPaths()
1435 {
1436 std::vector<std::string> libPathsString;
1437 r::exec::RFunction rfLibPaths(".libPaths");
1438 Error error = rfLibPaths.call(&libPathsString);
1439 if (error)
1440 LOG_ERROR(error);
1441
1442 std::vector<FilePath> libPaths;
1443 for (const std::string& path : libPathsString)
1444 {
1445 libPaths.push_back(module_context::resolveAliasedPath(path));
1446 }
1447
1448 return libPaths;
1449 }
1450
disablePackages()1451 bool disablePackages()
1452 {
1453 return !core::system::getenv("RSTUDIO_DISABLE_PACKAGES").empty();
1454 }
1455
1456 // check if a package is installed
isPackageInstalled(const std::string & packageName)1457 bool isPackageInstalled(const std::string& packageName)
1458 {
1459 r::session::utils::SuppressOutputInScope suppressOutput;
1460
1461 bool installed = false;
1462 r::exec::RFunction func(".rs.isPackageInstalled", packageName);
1463 Error error = func.call(&installed);
1464 return !error ? installed : false;
1465 }
1466
isPackageVersionInstalled(const std::string & packageName,const std::string & version)1467 bool isPackageVersionInstalled(const std::string& packageName,
1468 const std::string& version)
1469 {
1470 r::session::utils::SuppressOutputInScope suppressOutput;
1471
1472 bool installed = false;
1473 r::exec::RFunction func(".rs.isPackageVersionInstalled",
1474 packageName, version);
1475 Error error = func.call(&installed);
1476 return !error ? installed : false;
1477 }
1478
isMinimumDevtoolsInstalled()1479 bool isMinimumDevtoolsInstalled()
1480 {
1481 return isPackageVersionInstalled("devtools", "1.4.1");
1482 }
1483
isMinimumRoxygenInstalled()1484 bool isMinimumRoxygenInstalled()
1485 {
1486 return isPackageVersionInstalled("roxygen2", "4.0");
1487 }
1488
packageVersion(const std::string & packageName)1489 std::string packageVersion(const std::string& packageName)
1490 {
1491 std::string version;
1492 Error error = r::exec::RFunction(".rs.packageVersionString")
1493 .addParam(packageName)
1494 .call(&version);
1495
1496 if (error)
1497 {
1498 LOG_ERROR(error);
1499 return "(Unknown)";
1500 }
1501 else
1502 {
1503 return version;
1504 }
1505 }
1506
packageVersion(const std::string & packageName,core::Version * pVersion)1507 Error packageVersion(const std::string& packageName,
1508 core::Version* pVersion)
1509 {
1510 std::string version;
1511 Error error = r::exec::RFunction(".rs.packageVersionString")
1512 .addParam(packageName)
1513 .call(&version);
1514
1515 if (error)
1516 return error;
1517
1518 *pVersion = Version(version);
1519 return Success();
1520 }
1521
hasMinimumRVersion(const std::string & version)1522 bool hasMinimumRVersion(const std::string& version)
1523 {
1524 bool hasVersion = false;
1525 boost::format fmt("getRversion() >= '%1%'");
1526 std::string versionTest = boost::str(fmt % version);
1527 Error error = r::exec::evaluateString(versionTest, &hasVersion);
1528 if (error)
1529 LOG_ERROR(error);
1530 return hasVersion;
1531 }
1532
getPackageCompatStatus(const std::string & packageName,const std::string & packageVersion,int protocolVersion)1533 PackageCompatStatus getPackageCompatStatus(
1534 const std::string& packageName,
1535 const std::string& packageVersion,
1536 int protocolVersion)
1537 {
1538 r::session::utils::SuppressOutputInScope suppressOutput;
1539 int compatStatus = COMPAT_UNKNOWN;
1540 r::exec::RFunction func(".rs.getPackageCompatStatus",
1541 packageName, packageVersion, protocolVersion);
1542 Error error = func.call(&compatStatus);
1543 if (error)
1544 {
1545 LOG_ERROR(error);
1546 return COMPAT_UNKNOWN;
1547 }
1548 return static_cast<PackageCompatStatus>(compatStatus);
1549 }
1550
installPackage(const std::string & pkgPath,const std::string & libPath)1551 Error installPackage(const std::string& pkgPath, const std::string& libPath)
1552 {
1553 // get R bin directory
1554 FilePath rBinDir;
1555 Error error = module_context::rBinDir(&rBinDir);
1556 if (error)
1557 return error;
1558
1559 // setup options and command
1560 core::system::ProcessOptions options;
1561 #ifdef _WIN32
1562 shell_utils::ShellCommand installCommand(rBinDir.completeChildPath("R.exe"));
1563 #else
1564 shell_utils::ShellCommand installCommand(rBinDir.completeChildPath("R"));
1565 #endif
1566
1567 installCommand << core::shell_utils::EscapeFilesOnly;
1568
1569 // for packrat projects we execute the profile and set the working
1570 // directory to the project directory; for other contexts we just
1571 // propagate the R_LIBS
1572 if (module_context::packratContext().modeOn)
1573 {
1574 options.workingDir = projects::projectContext().directory();
1575 }
1576 else
1577 {
1578 installCommand << "--vanilla";
1579 core::system::Options env;
1580 core::system::environment(&env);
1581 std::string libPaths = libPathsString();
1582 if (!libPaths.empty())
1583 core::system::setenv(&env, "R_LIBS", libPathsString());
1584 options.environment = env;
1585 }
1586
1587 installCommand << "CMD" << "INSTALL";
1588
1589 // if there is a lib path then provide it
1590 if (!libPath.empty())
1591 {
1592 installCommand << "-l";
1593 installCommand << "\"" + libPath + "\"";
1594 }
1595
1596 // add pakage path
1597 installCommand << "\"" + pkgPath + "\"";
1598 core::system::ProcessResult result;
1599
1600 // run the command
1601 error = core::system::runCommand(installCommand,
1602 options,
1603 &result);
1604
1605 if (error)
1606 return error;
1607
1608 if ((result.exitStatus != EXIT_SUCCESS) && !result.stdErr.empty())
1609 {
1610 return systemError(boost::system::errc::state_not_recoverable,
1611 "Error installing package: " + result.stdErr,
1612 ERROR_LOCATION);
1613 }
1614
1615 return Success();
1616 }
1617
1618
packageNameForSourceFile(const core::FilePath & sourceFilePath)1619 std::string packageNameForSourceFile(const core::FilePath& sourceFilePath)
1620 {
1621 // check whether we are in a package
1622 FilePath sourceDir = sourceFilePath.getParent();
1623 if (sourceDir.getFilename() == "R" &&
1624 r_util::isPackageDirectory(sourceDir.getParent()))
1625 {
1626 r_util::RPackageInfo pkgInfo;
1627 Error error = pkgInfo.read(sourceDir.getParent());
1628 if (error)
1629 {
1630 LOG_ERROR(error);
1631 return std::string();
1632 }
1633
1634 return pkgInfo.name();
1635 }
1636 else
1637 {
1638 return std::string();
1639 }
1640 }
1641
isUnmonitoredPackageSourceFile(const FilePath & filePath)1642 bool isUnmonitoredPackageSourceFile(const FilePath& filePath)
1643 {
1644 // if it's in the current package then it's fine
1645 using namespace projects;
1646 if (projectContext().hasProject() &&
1647 (projectContext().config().buildType == r_util::kBuildTypePackage) &&
1648 filePath.isWithin(projectContext().buildTargetPath()))
1649 {
1650 return false;
1651 }
1652
1653 // ensure we are dealing with a directory
1654 FilePath dir = filePath;
1655 if (!dir.isDirectory())
1656 dir = filePath.getParent();
1657
1658 // see if one the file's parent directories has a DESCRIPTION
1659 while (!dir.isEmpty())
1660 {
1661 FilePath descPath = dir.completeChildPath("DESCRIPTION");
1662 if (descPath.exists())
1663 {
1664 // get path relative to package dir
1665 std::string relative = filePath.getRelativePath(dir);
1666 if (boost::algorithm::starts_with(relative, "R/") ||
1667 boost::algorithm::starts_with(relative, "src/") ||
1668 boost::algorithm::starts_with(relative, "inst/include/"))
1669 {
1670 return true;
1671 }
1672 else
1673 {
1674 return false;
1675 }
1676 }
1677
1678 dir = dir.getParent();
1679 }
1680
1681 return false;
1682 }
1683
1684
rs_packageNameForSourceFile(SEXP sourceFilePathSEXP)1685 SEXP rs_packageNameForSourceFile(SEXP sourceFilePathSEXP)
1686 {
1687 r::sexp::Protect protect;
1688 FilePath sourceFilePath = module_context::resolveAliasedPath(r::sexp::asString(sourceFilePathSEXP));
1689 return r::sexp::create(packageNameForSourceFile(sourceFilePath), &protect);
1690 }
1691
rs_base64encode(SEXP dataSEXP,SEXP binarySEXP)1692 SEXP rs_base64encode(SEXP dataSEXP, SEXP binarySEXP)
1693 {
1694 bool binary = r::sexp::asLogical(binarySEXP);
1695 const char* pData;
1696 std::size_t n;
1697
1698 if (TYPEOF(dataSEXP) == STRSXP)
1699 {
1700 SEXP charSEXP = STRING_ELT(dataSEXP, 0);
1701 pData = CHAR(charSEXP);
1702 n = r::sexp::length(charSEXP);
1703 }
1704 else if (TYPEOF(dataSEXP) == RAWSXP)
1705 {
1706 pData = reinterpret_cast<const char*>(RAW(dataSEXP));
1707 n = r::sexp::length(dataSEXP);
1708 }
1709 else
1710 {
1711 LOG_ERROR_MESSAGE("Unexpected data type");
1712 return R_NilValue;
1713 }
1714
1715 std::string output;
1716 Error error = base64::encode(pData, n, &output);
1717 if (error)
1718 LOG_ERROR(error);
1719
1720 r::sexp::Protect protect;
1721 if (binary)
1722 return r::sexp::createRawVector(output, &protect);
1723 else
1724 return r::sexp::create(output, &protect);
1725 }
1726
rs_base64encodeFile(SEXP pathSEXP)1727 SEXP rs_base64encodeFile(SEXP pathSEXP)
1728 {
1729 std::string path = r::sexp::asString(pathSEXP);
1730 FilePath filePath = module_context::resolveAliasedPath(path);
1731
1732 std::string output;
1733 Error error = base64::encode(filePath, &output);
1734 if (error)
1735 LOG_ERROR(error);
1736
1737 r::sexp::Protect protect;
1738 return r::sexp::create(output, &protect);
1739 }
1740
rs_base64decode(SEXP dataSEXP,SEXP binarySEXP)1741 SEXP rs_base64decode(SEXP dataSEXP, SEXP binarySEXP)
1742 {
1743 bool binary = r::sexp::asLogical(binarySEXP);
1744 const char* pData;
1745 std::size_t n;
1746
1747 if (TYPEOF(dataSEXP) == STRSXP)
1748 {
1749 SEXP charSEXP = STRING_ELT(dataSEXP, 0);
1750 pData = CHAR(charSEXP);
1751 n = r::sexp::length(charSEXP);
1752 }
1753 else if (TYPEOF(dataSEXP) == RAWSXP)
1754 {
1755 pData = reinterpret_cast<const char*>(RAW(dataSEXP));
1756 n = r::sexp::length(dataSEXP);
1757 }
1758 else
1759 {
1760 LOG_ERROR_MESSAGE("Unexpected data type");
1761 return R_NilValue;
1762 }
1763
1764 std::string output;
1765 Error error = base64::decode(pData, n, &output);
1766 if (error)
1767 LOG_ERROR(error);
1768
1769 r::sexp::Protect protect;
1770 if (binary)
1771 return r::sexp::createRawVector(output, &protect);
1772 else
1773 return r::sexp::create(output, &protect);
1774 }
1775
rs_htmlEscape(SEXP textSEXP,SEXP attributeSEXP)1776 SEXP rs_htmlEscape(SEXP textSEXP, SEXP attributeSEXP)
1777 {
1778 std::string escaped = string_utils::htmlEscape(r::sexp::safeAsString(textSEXP),
1779 r::sexp::asLogical(attributeSEXP));
1780 r::sexp::Protect protect;
1781 return r::sexp::create(escaped, &protect);
1782 }
1783
rs_resolveAliasedPath(SEXP pathSEXP)1784 SEXP rs_resolveAliasedPath(SEXP pathSEXP)
1785 {
1786 std::string path = r::sexp::asUtf8String(pathSEXP);
1787 FilePath resolved = module_context::resolveAliasedPath(path);
1788 r::sexp::Protect protect;
1789 return r::sexp::create(resolved.getAbsolutePath(), &protect);
1790 }
1791
rs_sessionModulePath()1792 SEXP rs_sessionModulePath()
1793 {
1794 r::sexp::Protect protect;
1795 return r::sexp::create(
1796 session::options().modulesRSourcePath().getAbsolutePath(), &protect);
1797 }
1798
createFileSystemItem(const FileInfo & fileInfo)1799 json::Object createFileSystemItem(const FileInfo& fileInfo)
1800 {
1801 json::Object entry;
1802
1803 std::string aliasedPath = module_context::createAliasedPath(fileInfo);
1804 std::string rawPath =
1805 module_context::resolveAliasedPath(aliasedPath).getAbsolutePath();
1806
1807 entry["path"] = aliasedPath;
1808 if (aliasedPath != rawPath)
1809 entry["raw_path"] = rawPath;
1810 entry["dir"] = fileInfo.isDirectory();
1811
1812 // length requires cast
1813 try
1814 {
1815 entry["length"] = boost::numeric_cast<boost::uint64_t>(fileInfo.size());
1816 }
1817 catch (const boost::bad_numeric_cast& e)
1818 {
1819 LOG_ERROR_MESSAGE(std::string("Error converting file size: ") +
1820 e.what());
1821 entry["length"] = 0;
1822 }
1823
1824 entry["exists"] = FilePath(fileInfo.absolutePath()).exists();
1825
1826 entry["lastModified"] = date_time::millisecondsSinceEpoch(
1827 fileInfo.lastWriteTime());
1828 return entry;
1829 }
1830
createFileSystemItem(const FilePath & filePath)1831 json::Object createFileSystemItem(const FilePath& filePath)
1832 {
1833 return createFileSystemItem(FileInfo(filePath));
1834 }
1835
rVersion()1836 std::string rVersion()
1837 {
1838 std::string rVersion;
1839 Error error = rstudio::r::exec::RFunction(".rs.rVersionString").call(
1840 &rVersion);
1841 if (error)
1842 LOG_ERROR(error);
1843 return rVersion;
1844 }
1845
rVersionLabel()1846 std::string rVersionLabel()
1847 {
1848 std::string versionLabel = system::getenv("RSTUDIO_R_VERSION_LABEL");
1849 return versionLabel;
1850 }
1851
rHomeDir()1852 std::string rHomeDir()
1853 {
1854 // get the current R home directory
1855 std::string rVersionHome;
1856 Error error = rstudio::r::exec::RFunction("R.home").call(&rVersionHome);
1857 if (error)
1858 LOG_ERROR(error);
1859 return rVersionHome;
1860 }
1861
1862
activeSession()1863 r_util::ActiveSession& activeSession()
1864 {
1865 static boost::shared_ptr<r_util::ActiveSession> pSession;
1866 if (!pSession)
1867 {
1868 std::string id = options().sessionScope().id();
1869 if (!id.empty())
1870 pSession = activeSessions().get(id);
1871 else if (options().programMode() == kSessionProgramModeDesktop)
1872 {
1873 // if no active session, create one and use the launcher token as a
1874 // synthetic session ID
1875 //
1876 // we only do this in desktop mode to preserve backwards compatibility
1877 // with some functionality that depends on session data
1878 // persisting after rstudio has been closed
1879 //
1880 // this entire clause will likely need to be reverted in a future release
1881 // once we ensure that all execution modes have this squared away
1882 pSession = activeSessions().emptySession(options().launcherToken());
1883 }
1884 else
1885 {
1886 // if no scope was specified, we are in singleton session mode
1887 // check to see if there is an existing active session, and use that
1888 std::vector<boost::shared_ptr<r_util::ActiveSession> > sessions =
1889 activeSessions().list(userHomePath(), options().projectSharingEnabled());
1890 if (sessions.size() == 1)
1891 {
1892 // there is only one session, so this must be singleton session mode
1893 // reopen that session
1894 pSession = sessions.front();
1895 }
1896 else
1897 {
1898 // create a new session entry
1899 // this should not really run because in single session mode there will
1900 // only be one session but we'll create one here just in case for safety
1901 std::string sessionId;
1902
1903 // pass ~ as the default working directory
1904 // this is resolved deeper in the code in SessionClientInit to turn into
1905 // the actual user preference or session default directory that it should be
1906 activeSessions().create(options().sessionScope().project(), "~", &sessionId);
1907 pSession = activeSessions().get(sessionId);
1908 }
1909 }
1910 }
1911 return *pSession;
1912 }
1913
1914
activeSessions()1915 r_util::ActiveSessions& activeSessions()
1916 {
1917 static boost::shared_ptr<r_util::ActiveSessions> pSessions;
1918 if (!pSessions)
1919 pSessions.reset(new r_util::ActiveSessions(userScratchPath()));
1920 return *pSessions;
1921 }
1922
1923
1924
libPathsString()1925 std::string libPathsString()
1926 {
1927 // call into R to get the string
1928 std::string libPaths;
1929 Error error = r::exec::RFunction(".rs.libPathsString").call(&libPaths);
1930 if (error)
1931 {
1932 LOG_ERROR(error);
1933 return std::string();
1934 }
1935
1936 // this is presumably system-encoded, so convert this to utf8 before return
1937 return string_utils::systemToUtf8(libPaths);
1938 }
1939
sourceModuleRFile(const std::string & rSourceFile)1940 Error sourceModuleRFile(const std::string& rSourceFile)
1941 {
1942 FilePath modulesPath = session::options().modulesRSourcePath();
1943 FilePath srcPath = modulesPath.completePath(rSourceFile);
1944 return r::sourceManager().sourceTools(srcPath);
1945 }
1946
sourceModuleRFileWithResult(const std::string & rSourceFile,const FilePath & workingDir,core::system::ProcessResult * pResult)1947 Error sourceModuleRFileWithResult(const std::string& rSourceFile,
1948 const FilePath& workingDir,
1949 core::system::ProcessResult* pResult)
1950 {
1951 // R binary
1952 FilePath rProgramPath;
1953 Error error = module_context::rScriptPath(&rProgramPath);
1954 if (error)
1955 return error;
1956 std::string rBin = string_utils::utf8ToSystem(rProgramPath.getAbsolutePath());
1957
1958 // vanilla execution of a single expression
1959 std::vector<std::string> args;
1960 args.push_back("--vanilla");
1961 args.push_back("-s");
1962 args.push_back("-e");
1963
1964 // build source command
1965 boost::format fmt("source('%1%')");
1966 FilePath modulesPath = session::options().modulesRSourcePath();
1967 FilePath srcFilePath = modulesPath.completePath(rSourceFile);
1968 std::string srcPath = core::string_utils::utf8ToSystem(
1969 srcFilePath.getAbsolutePath());
1970 std::string escapedSrcPath = string_utils::jsLiteralEscape(srcPath);
1971 std::string cmd = boost::str(fmt % escapedSrcPath);
1972 args.push_back(cmd);
1973
1974 // options
1975 core::system::ProcessOptions options;
1976 options.terminateChildren = true;
1977 options.workingDir = workingDir;
1978
1979 // allow child process to inherit our R_LIBS
1980 core::system::Options childEnv;
1981 core::system::environment(&childEnv);
1982 std::string libPaths = libPathsString();
1983 if (!libPaths.empty())
1984 core::system::setenv(&childEnv, "R_LIBS", libPaths);
1985 options.environment = childEnv;
1986
1987 // run the child
1988 return core::system::runProgram(rBin, args, "", options, pResult);
1989 }
1990
1991
enqueClientEvent(const ClientEvent & event)1992 void enqueClientEvent(const ClientEvent& event)
1993 {
1994 session::clientEventQueue().add(event);
1995 }
1996
isDirectoryMonitored(const FilePath & directory)1997 bool isDirectoryMonitored(const FilePath& directory)
1998 {
1999 return session::projects::projectContext().isMonitoringDirectory(directory) ||
2000 session::modules::files::isMonitoringDirectory(directory);
2001 }
2002
isRScriptInPackageBuildTarget(const FilePath & filePath)2003 bool isRScriptInPackageBuildTarget(const FilePath &filePath)
2004 {
2005 using namespace session::projects;
2006
2007 if (projectContext().config().buildType == r_util::kBuildTypePackage)
2008 {
2009 FilePath pkgPath = projects::projectContext().buildTargetPath();
2010 std::string pkgRelative = filePath.getRelativePath(pkgPath);
2011 return boost::algorithm::starts_with(pkgRelative, "R/");
2012 }
2013 else
2014 {
2015 return false;
2016 }
2017 }
2018
rs_isRScriptInPackageBuildTarget(SEXP filePathSEXP)2019 SEXP rs_isRScriptInPackageBuildTarget(SEXP filePathSEXP)
2020 {
2021 r::sexp::Protect protect;
2022 FilePath filePath = module_context::resolveAliasedPath(
2023 r::sexp::asUtf8String(filePathSEXP));
2024 return r::sexp::create(isRScriptInPackageBuildTarget(filePath), &protect);
2025 }
2026
fileListingFilter(const core::FileInfo & fileInfo,bool hideObjectFiles)2027 bool fileListingFilter(const core::FileInfo& fileInfo, bool hideObjectFiles)
2028 {
2029 core::FilePath filePath(fileInfo.absolutePath());
2030
2031 // Check to see if this is one of the extensions we always show
2032 std::string ext = filePath.getExtensionLowerCase();
2033 core::json::Array exts = prefs::userPrefs().alwaysShownExtensions();
2034 for (json::Value val: exts)
2035 {
2036 if (json::isType<std::string>(val))
2037 {
2038 if (ext == string_utils::toLower(val.getString()))
2039 {
2040 return true;
2041 }
2042 }
2043 }
2044
2045 // Check to see if this is one of the files we always show
2046 std::string name = filePath.getFilename();
2047 core::json::Array files = prefs::userPrefs().alwaysShownFiles();
2048 for (json::Value val: files)
2049 {
2050 if (json::isType<std::string>(val))
2051 {
2052 if (name == val.getString())
2053 {
2054 return true;
2055 }
2056 }
2057 }
2058
2059 if (hideObjectFiles &&
2060 (ext == ".o" || ext == ".so" || ext == ".dll") &&
2061 filePath.getParent().getFilename() == "src")
2062 {
2063 return false;
2064 }
2065 else
2066 {
2067 return !filePath.isHidden();
2068 }
2069 }
2070
2071 namespace {
2072
2073 // enque file changed event
enqueFileChangedEvent(const core::system::FileChangeEvent & event,boost::shared_ptr<modules::source_control::FileDecorationContext> pCtx)2074 void enqueFileChangedEvent(
2075 const core::system::FileChangeEvent& event,
2076 boost::shared_ptr<modules::source_control::FileDecorationContext> pCtx)
2077 {
2078 // create file change object
2079 json::Object fileChange;
2080 fileChange["type"] = event.type();
2081 json::Object fileSystemItem = createFileSystemItem(event.fileInfo());
2082
2083 if (prefs::userPrefs().vcsAutorefresh())
2084 {
2085 pCtx->decorateFile(
2086 FilePath(event.fileInfo().absolutePath()),
2087 &fileSystemItem);
2088 }
2089
2090 fileChange["file"] = fileSystemItem;
2091
2092 // enque it
2093 ClientEvent clientEvent(client_events::kFileChanged, fileChange);
2094 module_context::enqueClientEvent(clientEvent);
2095 }
2096
2097 } // namespace
2098
enqueFileChangedEvent(const core::system::FileChangeEvent & event)2099 void enqueFileChangedEvent(const core::system::FileChangeEvent &event)
2100 {
2101 FilePath filePath = FilePath(event.fileInfo().absolutePath());
2102
2103 using namespace session::modules::source_control;
2104 auto pCtx = fileDecorationContext(filePath, true);
2105 enqueFileChangedEvent(event, pCtx);
2106 }
2107
enqueFileChangedEvents(const core::FilePath & vcsStatusRoot,const std::vector<core::system::FileChangeEvent> & events)2108 void enqueFileChangedEvents(const core::FilePath& vcsStatusRoot,
2109 const std::vector<core::system::FileChangeEvent>& events)
2110 {
2111 using namespace modules::source_control;
2112
2113 if (events.empty())
2114 return;
2115
2116 // try to find the common parent of the events
2117 FilePath commonParentPath = FilePath(events.front().fileInfo().absolutePath()).getParent();
2118 for (const core::system::FileChangeEvent& event : events)
2119 {
2120 // if not within the common parent then revert to the vcs status root
2121 if (!FilePath(event.fileInfo().absolutePath()).isWithin(commonParentPath))
2122 {
2123 commonParentPath = vcsStatusRoot;
2124 break;
2125 }
2126 }
2127
2128 using namespace session::modules::source_control;
2129 auto pCtx = fileDecorationContext(commonParentPath, true);
2130
2131 // fire client events as necessary
2132 for (const core::system::FileChangeEvent& event : events)
2133 {
2134 enqueFileChangedEvent(event, pCtx);
2135 }
2136 }
2137
enqueueConsoleInput(const std::string & consoleInput)2138 Error enqueueConsoleInput(const std::string& consoleInput)
2139 {
2140 using namespace r::session;
2141
2142 // construct our JSON RPC
2143 json::Array jsonParams = RConsoleInput(consoleInput).toJsonArray();
2144
2145 json::Object jsonRpc;
2146 jsonRpc["method"] = "console_input";
2147 jsonRpc["params"] = jsonParams;
2148 jsonRpc["clientId"] = clientEventService().clientId();
2149
2150 // serialize for transmission
2151 std::ostringstream oss;
2152 jsonRpc.write(oss);
2153
2154 // and fire it off
2155 consoleInputService().enqueue(oss.str());
2156
2157 return Success();
2158 }
2159
2160 // NOTE: we used to call explicitly back into r::session to write output
2161 // and errors however the fact that these functions are called from
2162 // background threads during std stream capture means that they must
2163 // provide thread safety guarantees. since the r::session module generally
2164 // assumes single-threaded operation it is more straightforward to have
2165 // the code here ape the actions of RWriteConsoleEx with a more explicit
2166 // thread safety constraint
2167
consoleWriteOutput(const std::string & output)2168 void consoleWriteOutput(const std::string& output)
2169 {
2170 // NOTE: all actions herein must be threadsafe! (see comment above)
2171
2172 // add console action
2173 r::session::consoleActions().add(kConsoleActionOutput, output);
2174
2175 // enque write output (same as session::rConsoleWrite)
2176 ClientEvent event(client_events::kConsoleWriteOutput, output);
2177 enqueClientEvent(event);
2178
2179 // fire event
2180 module_context::events().onConsoleOutput(
2181 module_context::ConsoleOutputNormal,
2182 output);
2183 }
2184
consoleWriteError(const std::string & message)2185 void consoleWriteError(const std::string& message)
2186 {
2187 // NOTE: all actions herein must be threadsafe! (see comment above)
2188
2189 // add console action
2190 r::session::consoleActions().add(kConsoleActionOutputError, message);
2191
2192 // enque write error (same as session::rConsoleWrite)
2193 ClientEvent event(client_events::kConsoleWriteError, message);
2194 enqueClientEvent(event);
2195
2196 // fire event
2197 module_context::events().onConsoleOutput(
2198 module_context::ConsoleOutputError,
2199 message);
2200 }
2201
showErrorMessage(const std::string & title,const std::string & message)2202 void showErrorMessage(const std::string& title, const std::string& message)
2203 {
2204 session::clientEventQueue().add(showErrorMessageEvent(title, message));
2205 }
2206
showFile(const FilePath & filePath,const std::string & window)2207 void showFile(const FilePath& filePath, const std::string& window)
2208 {
2209 if (session::options().programMode() == kSessionProgramModeDesktop)
2210 {
2211 // for pdfs handle specially for each platform
2212 if (filePath.getExtensionLowerCase() == ".pdf")
2213 {
2214 std::string path = filePath.getAbsolutePath();
2215 Error error = r::exec::RFunction(".rs.shellViewPdf", path).call();
2216 if (error)
2217 LOG_ERROR(error);
2218 }
2219 else
2220 {
2221 ClientEvent event = browseUrlEvent("file:///" + filePath.getAbsolutePath());
2222 module_context::enqueClientEvent(event);
2223 }
2224 }
2225 else if (session::options().programMode() == kSessionProgramModeServer)
2226 {
2227 if (!isPathViewAllowed(filePath))
2228 {
2229 module_context::showErrorMessage(
2230 "File Download Error",
2231 "This system administrator has not granted you permission "
2232 "to view this file.\n");
2233 }
2234 else if (session::options().allowFileDownloads())
2235 {
2236 std::string url = createFileUrl(filePath);
2237 ClientEvent event = browseUrlEvent(url);
2238 module_context::enqueClientEvent(event);
2239 }
2240 else
2241 {
2242 module_context::showErrorMessage(
2243 "File Download Error",
2244 "Unable to show file because file downloads are restricted "
2245 "on this server.\n");
2246 }
2247 }
2248 }
2249
createFileUrl(const core::FilePath & filePath)2250 std::string createFileUrl(const core::FilePath& filePath)
2251 {
2252 // determine url based on whether this is in ~ or not
2253 std::string url;
2254 if (isVisibleUserFile(filePath))
2255 {
2256 std::string relPath = filePath.getRelativePath(
2257 module_context::userHomePath());
2258 url = "files/" + relPath;
2259 }
2260 else
2261 {
2262 url = "file_show?path=" + core::http::util::urlEncode(
2263 filePath.getAbsolutePath(), true);
2264 }
2265 return url;
2266 }
2267
2268
showContent(const std::string & title,const core::FilePath & filePath)2269 void showContent(const std::string& title, const core::FilePath& filePath)
2270 {
2271 // first provision a content url
2272 std::string contentUrl = content_urls::provision(title, filePath);
2273
2274 // fire event
2275 json::Object contentItem;
2276 contentItem["title"] = title;
2277 contentItem["contentUrl"] = contentUrl;
2278 ClientEvent event(client_events::kShowContent, contentItem);
2279 module_context::enqueClientEvent(event);
2280 }
2281
2282
resourceFileAsString(const std::string & fileName)2283 std::string resourceFileAsString(const std::string& fileName)
2284 {
2285 FilePath resPath = session::options().rResourcesPath();
2286 FilePath filePath = resPath.completePath(fileName);
2287 std::string fileContents;
2288 Error error = readStringFromFile(filePath, &fileContents);
2289 if (error)
2290 {
2291 LOG_ERROR(error);
2292 return std::string();
2293 }
2294
2295 return fileContents;
2296 }
2297
2298 // given a pair of paths, return the second in the context of the first
pathRelativeTo(const FilePath & sourcePath,const FilePath & targetPath)2299 std::string pathRelativeTo(const FilePath& sourcePath,
2300 const FilePath& targetPath)
2301 {
2302 std::string relative;
2303 if (targetPath == sourcePath)
2304 {
2305 relative = ".";
2306 }
2307 else if (targetPath.isWithin(sourcePath))
2308 {
2309 relative = targetPath.getRelativePath(sourcePath);
2310 }
2311 else
2312 {
2313 relative = createAliasedPath(targetPath);
2314 }
2315 return relative;
2316 }
2317
activatePane(const std::string & pane)2318 void activatePane(const std::string& pane)
2319 {
2320 ClientEvent event(client_events::kActivatePane, pane);
2321 module_context::enqueClientEvent(event);
2322 }
2323
shellWorkingDirectory()2324 FilePath shellWorkingDirectory()
2325 {
2326 std::string initialDirSetting = prefs::userPrefs().terminalInitialDirectory();
2327 if (initialDirSetting == kTerminalInitialDirectoryProject)
2328 {
2329 if (projects::projectContext().hasProject())
2330 return projects::projectContext().directory();
2331 else
2332 return module_context::safeCurrentPath();
2333 }
2334 else if (initialDirSetting == kTerminalInitialDirectoryCurrent)
2335 return module_context::safeCurrentPath();
2336 else if (initialDirSetting == kTerminalInitialDirectoryHome)
2337 return system::User::getUserHomePath();
2338 else
2339 return module_context::safeCurrentPath();
2340 }
2341
events()2342 Events& events()
2343 {
2344 static Events instance;
2345 return instance;
2346 }
2347
processSupervisor()2348 core::system::ProcessSupervisor& processSupervisor()
2349 {
2350 static core::system::ProcessSupervisor instance;
2351 return instance;
2352 }
2353
sourceDiagnostics()2354 FilePath sourceDiagnostics()
2355 {
2356 FilePath diagnosticsPath =
2357 options().coreRSourcePath().completeChildPath("Diagnostics.R");
2358
2359 Error error = r::exec::RFunction("source")
2360 .addParam(string_utils::utf8ToSystem(diagnosticsPath.getAbsolutePath()))
2361 .addParam("chdir", true)
2362 .call();
2363
2364 if (error)
2365 {
2366 LOG_ERROR(error);
2367 return FilePath();
2368 }
2369 else
2370 {
2371 // note this path is also in Diagnostics.R so changes to the path
2372 // need to be synchronized there
2373 std::string reportPath = core::system::getenv("RSTUDIO_DIAGNOSTICS_REPORT");
2374 if (reportPath.empty())
2375 reportPath = "~/rstudio-diagnostics/diagnostics-report.txt";
2376 return module_context::resolveAliasedPath(reportPath);
2377 }
2378 }
2379
2380 namespace {
2381
beginRpcHandler(json::JsonRpcFunction function,json::JsonRpcRequest request,std::string asyncHandle)2382 void beginRpcHandler(json::JsonRpcFunction function,
2383 json::JsonRpcRequest request,
2384 std::string asyncHandle)
2385 {
2386 try
2387 {
2388 json::JsonRpcResponse response;
2389 Error error = function(request, &response);
2390 BOOST_ASSERT(!response.hasAfterResponse());
2391 if (error)
2392 response.setError(error);
2393
2394 if (!response.hasField(kEventsPending))
2395 response.setField(kEventsPending, "false");
2396
2397 json::Object value;
2398 value["handle"] = asyncHandle;
2399 value["response"] = response.getRawResponse();
2400 ClientEvent evt(client_events::kAsyncCompletion, value);
2401 enqueClientEvent(evt);
2402 }
2403 CATCH_UNEXPECTED_EXCEPTION
2404
2405 }
2406
2407 } // anonymous namespace
2408
executeAsync(const json::JsonRpcFunction & function,const json::JsonRpcRequest & request,json::JsonRpcResponse * pResponse)2409 core::Error executeAsync(const json::JsonRpcFunction& function,
2410 const json::JsonRpcRequest& request,
2411 json::JsonRpcResponse* pResponse)
2412 {
2413 // Immediately return a response to the server with a handle that
2414 // identifies this invocation. In the meantime, kick off the actual
2415 // operation on a new thread.
2416
2417 std::string handle = core::system::generateUuid(true);
2418 core::thread::safeLaunchThread(bind(beginRpcHandler,
2419 function,
2420 request,
2421 handle));
2422 pResponse->setAsyncHandle(handle);
2423 return Success();
2424 }
2425
compileOutputAsJson(const CompileOutput & compileOutput)2426 core::json::Object compileOutputAsJson(const CompileOutput& compileOutput)
2427 {
2428 json::Object compileOutputJson;
2429 compileOutputJson["type"] = compileOutput.type;
2430 compileOutputJson["output"] = compileOutput.output;
2431 return compileOutputJson;
2432 }
2433
CRANReposURL()2434 std::string CRANReposURL()
2435 {
2436 std::string url;
2437 r::exec::evaluateString("getOption('repos')[['CRAN']]", &url);
2438 if (url.empty())
2439 url = prefs::userPrefs().getCRANMirror().url;
2440 return url;
2441 }
2442
rstudioCRANReposURL()2443 std::string rstudioCRANReposURL()
2444 {
2445 std::string protocol = prefs::userPrefs().useSecureDownload() ?
2446 "https" : "http";
2447 return protocol + "://cran.rstudio.com/";
2448 }
2449
rs_rstudioCRANReposUrl()2450 SEXP rs_rstudioCRANReposUrl()
2451 {
2452 r::sexp::Protect rProtect;
2453 return r::sexp::create(rstudioCRANReposURL(), &rProtect);
2454 }
2455
downloadFileMethod(const std::string & defaultMethod)2456 std::string downloadFileMethod(const std::string& defaultMethod)
2457 {
2458 std::string method;
2459 Error error = r::exec::evaluateString(
2460 "getOption('download.file.method', '" +
2461 defaultMethod + "')", &method);
2462 if (error)
2463 LOG_ERROR(error);
2464 return method;
2465 }
2466
CRANDownloadOptions()2467 std::string CRANDownloadOptions()
2468 {
2469 std::string options;
2470 Error error = r::exec::RFunction(".rs.CRANDownloadOptionsString").call(&options);
2471 if (error)
2472 LOG_ERROR(error);
2473 return options;
2474 }
2475
haveSecureDownloadFileMethod()2476 bool haveSecureDownloadFileMethod()
2477 {
2478 bool secure = false;
2479 Error error = r::exec::RFunction(".rs.haveSecureDownloadFileMethod").call(
2480 &secure);
2481 if (error)
2482 LOG_ERROR(error);
2483 return secure;
2484 }
2485
buildRCmd(const core::FilePath & rBinDir)2486 shell_utils::ShellCommand RCommand::buildRCmd(const core::FilePath& rBinDir)
2487 {
2488 #if defined(_WIN32)
2489 shell_utils::ShellCommand rCmd(rBinDir.completeChildPath("Rcmd.exe"));
2490 #else
2491 shell_utils::ShellCommand rCmd(rBinDir.completeChildPath("R"));
2492 rCmd << "CMD";
2493 #endif
2494 return rCmd;
2495 }
2496
recursiveCopyDirectory(const core::FilePath & fromDir,const core::FilePath & toDir)2497 core::Error recursiveCopyDirectory(const core::FilePath& fromDir,
2498 const core::FilePath& toDir)
2499 {
2500 using namespace string_utils;
2501 r::exec::RFunction fileCopy("file.copy");
2502 fileCopy.addParam("from", utf8ToSystem(fromDir.getAbsolutePath()));
2503 fileCopy.addParam("to", utf8ToSystem(toDir.getAbsolutePath()));
2504 fileCopy.addParam("recursive", true);
2505 return fileCopy.call();
2506 }
2507
isSessionTempPath(FilePath filePath)2508 bool isSessionTempPath(FilePath filePath)
2509 {
2510 // get the real path
2511 Error error = core::system::realPath(filePath, &filePath);
2512 if (error)
2513 LOG_ERROR(error);
2514
2515 // get the session temp dir real path; needed since the file path above is
2516 // also a real path--e.g. on OS X, it refers to /private/tmp rather than
2517 // /tmp
2518 FilePath tempDir;
2519 error = core::system::realPath(module_context::tempDir(), &tempDir);
2520 if (error)
2521 LOG_ERROR(error);
2522
2523 return filePath.isWithin(tempDir);
2524 }
2525
2526 namespace {
2527
isUsingRHelpServer()2528 bool isUsingRHelpServer()
2529 {
2530 // don't use R help server in server mode
2531 if (session::options().programMode() == kSessionProgramModeServer)
2532 {
2533 return false;
2534 }
2535
2536 #ifdef _WIN32
2537 // There is a known issue serving content from the session temporary folder
2538 // on R 4.0.0 for Windows:
2539 //
2540 // https://github.com/rstudio/rstudio/issues/6737
2541 //
2542 // so we avoid using the help server in that case.
2543 if (r::util::hasExactVersion("4.0.0"))
2544 {
2545 return false;
2546 }
2547 #endif
2548
2549 // we're running on desktop with a suitable version of R;
2550 // okay to use the help server
2551 return true;
2552 }
2553
2554 } // end anonymous namespace
2555
sessionTempDirUrl(const std::string & sessionTempPath)2556 std::string sessionTempDirUrl(const std::string& sessionTempPath)
2557 {
2558 static bool useRHelpServer = isUsingRHelpServer();
2559
2560 if (useRHelpServer)
2561 {
2562 boost::format fmt("http://localhost:%1%/session/%2%");
2563 return boost::str(fmt % rLocalHelpPort() % sessionTempPath);
2564 }
2565 else
2566 {
2567 boost::format fmt("session/%1%");
2568 return boost::str(fmt % sessionTempPath);
2569 }
2570 }
2571
isPathViewAllowed(const FilePath & filePath)2572 bool isPathViewAllowed(const FilePath& filePath)
2573 {
2574 // Check to see if restrictions are in place
2575 if (!options().restrictDirectoryView())
2576 return true;
2577
2578 // No paths are restricted in desktop mode
2579 if (options().programMode() != kSessionProgramModeServer)
2580 return true;
2581
2582 // Viewing content in the home directory is always allowed
2583 if (filePath.isWithin(userHomePath().getParent()))
2584 return true;
2585
2586 // Viewing content in the session temporary files path is always allowed
2587 if (isSessionTempPath(filePath))
2588 return true;
2589
2590 // Allow users to view the system's configuration
2591 if (filePath.isWithin(core::system::xdg::systemConfigDir()))
2592 return true;
2593
2594 // Viewing content in R libraries is always allowed
2595 std::vector<FilePath> libPaths = getLibPaths();
2596 for (const auto& dir: libPaths)
2597 {
2598 if (filePath.isWithin(dir))
2599 {
2600 return true;
2601 }
2602 }
2603
2604 // Check session option for explicitly allowed directories
2605 std::string allowDirs = session::options().directoryViewAllowList();
2606 if (!allowDirs.empty())
2607 {
2608 std::vector<std::string> dirs = core::algorithm::split(allowDirs, ":");
2609 for (const auto& dir: dirs)
2610 {
2611 if (filePath.isWithin(FilePath(dir)))
2612 {
2613 return true;
2614 }
2615 }
2616 }
2617
2618 // All other paths are implicitly disallowed
2619 return false;
2620 }
2621
2622 namespace {
2623
hasStem(const FilePath & filePath,const std::string & stem)2624 bool hasStem(const FilePath& filePath, const std::string& stem)
2625 {
2626 return filePath.getStem() == stem;
2627 }
2628
2629 } // anonymous namespace
2630
uniqueSaveStem(const core::FilePath & directoryPath,const std::string & base,std::string * pStem)2631 Error uniqueSaveStem(const core::FilePath& directoryPath,
2632 const std::string& base,
2633 std::string* pStem)
2634 {
2635 return uniqueSaveStem(directoryPath, base, "", pStem);
2636 }
2637
uniqueSaveStem(const FilePath & directoryPath,const std::string & base,const std::string & delimiter,std::string * pStem)2638 Error uniqueSaveStem(const FilePath& directoryPath,
2639 const std::string& base,
2640 const std::string& delimiter,
2641 std::string* pStem)
2642 {
2643 // determine unique file name
2644 std::vector<FilePath> children;
2645 Error error = directoryPath.getChildren(children);
2646 if (error)
2647 return error;
2648
2649 // search for unique stem
2650 int i = 0;
2651 *pStem = base;
2652 while(true)
2653 {
2654 // seek stem
2655 std::vector<FilePath>::const_iterator it = std::find_if(
2656 children.begin(),
2657 children.end(),
2658 boost::bind(hasStem, _1, *pStem));
2659 // break if not found
2660 if (it == children.end())
2661 break;
2662
2663 // update stem and search again
2664 boost::format fmt(base + delimiter + "%1%");
2665 *pStem = boost::str(fmt % boost::io::group(std::setfill('0'),
2666 std::setw(2),
2667 ++i));
2668 }
2669
2670 return Success();
2671 }
2672
plotExportFormat(const std::string & name,const std::string & extension)2673 json::Object plotExportFormat(const std::string& name,
2674 const std::string& extension)
2675 {
2676 json::Object formatJson;
2677 formatJson["name"] = name;
2678 formatJson["extension"] = extension;
2679 return formatJson;
2680 }
2681
createSelfContainedHtml(const FilePath & sourceFilePath,const FilePath & targetFilePath)2682 Error createSelfContainedHtml(const FilePath& sourceFilePath,
2683 const FilePath& targetFilePath)
2684 {
2685 r::exec::RFunction func(".rs.pandocSelfContainedHtml");
2686 func.addParam(string_utils::utf8ToSystem(sourceFilePath.getAbsolutePath()));
2687 func.addParam(string_utils::utf8ToSystem(
2688 session::options().rResourcesPath().completePath("pandoc_template.html").getAbsolutePath()));
2689 func.addParam(string_utils::utf8ToSystem(targetFilePath.getAbsolutePath()));
2690 return func.call();
2691 }
2692
isUserFile(const FilePath & filePath)2693 bool isUserFile(const FilePath& filePath)
2694 {
2695 if (projects::projectContext().hasProject())
2696 {
2697 // if we are in a package project then screen our src- files
2698 if (projects::projectContext().config().buildType ==
2699 r_util::kBuildTypePackage)
2700 {
2701 FilePath pkgPath = projects::projectContext().buildTargetPath();
2702 std::string pkgRelative = filePath.getRelativePath(pkgPath);
2703 if (boost::algorithm::starts_with(pkgRelative, "src-"))
2704 return false;
2705 }
2706
2707 // screen our various virtual environment directories + standard content ignores
2708 FilePath projPath = projects::projectContext().directory();
2709 std::vector<std::string> dirs({"packrat/", "renv/"});
2710 const std::vector<FilePath>& ignoreDirs = module_context::ignoreContentDirs();
2711 std::transform(ignoreDirs.begin(), ignoreDirs.end(), std::back_inserter(dirs),
2712 [&projPath](const FilePath& ignorePath) {
2713 return ignorePath.getRelativePath(projPath) + "/";
2714 });
2715 std::string pkgRelative = filePath.getRelativePath(projPath);
2716 for (const auto& dir : dirs) {
2717 if (boost::algorithm::starts_with(pkgRelative, dir))
2718 return false;
2719 }
2720 }
2721
2722 return true;
2723 }
2724
2725 namespace {
2726
2727 // NOTE: sync changes with SessionCompilePdf.cpp logEntryJson
sourceMarkerJson(const SourceMarker & sourceMarker)2728 json::Value sourceMarkerJson(const SourceMarker& sourceMarker)
2729 {
2730 json::Object obj;
2731 obj["type"] = static_cast<int>(sourceMarker.type);
2732 obj["path"] = module_context::createAliasedPath(sourceMarker.path);
2733 obj["line"] = sourceMarker.line;
2734 obj["column"] = sourceMarker.column;
2735 obj["message"] = sourceMarker.message.text();
2736 obj["log_path"] = "";
2737 obj["log_line"] = -1;
2738 obj["show_error_list"] = sourceMarker.showErrorList;
2739 return std::move(obj);
2740 }
2741
2742 } // anonymous namespace
2743
sourceMarkersAsJson(const std::vector<SourceMarker> & markers)2744 json::Array sourceMarkersAsJson(const std::vector<SourceMarker>& markers)
2745 {
2746 json::Array markersJson;
2747 std::transform(markers.begin(),
2748 markers.end(),
2749 std::back_inserter(markersJson),
2750 sourceMarkerJson);
2751 return markersJson;
2752 }
2753
sourceMarkerTypeFromString(const std::string & type)2754 SourceMarker::Type sourceMarkerTypeFromString(const std::string& type)
2755 {
2756 if (type == "error")
2757 return SourceMarker::Error;
2758 else if (type == "warning")
2759 return SourceMarker::Warning;
2760 else if (type == "box")
2761 return SourceMarker::Box;
2762 else if (type == "info")
2763 return SourceMarker::Info;
2764 else if (type == "style")
2765 return SourceMarker::Style;
2766 else if (type == "usage")
2767 return SourceMarker::Usage;
2768 else
2769 return SourceMarker::Error;
2770 }
2771
2772 core::json::Array sourceMarkersAsJson(const std::vector<SourceMarker>& markers);
2773
isLoadBalanced()2774 bool isLoadBalanced()
2775 {
2776 return !core::system::getenv(kRStudioSessionRoute).empty();
2777 }
2778
2779 #ifdef _WIN32
usingMingwGcc49()2780 bool usingMingwGcc49()
2781 {
2782 // return true if the setting is true
2783 bool gcc49 = prefs::userState().usingMingwGcc49();
2784 if (gcc49)
2785 return true;
2786
2787 // otherwise check R version
2788 r::exec::RFunction func(".rs.builtWithRtoolsGcc493");
2789 Error error = func.call(&gcc49);
2790 if (error)
2791 LOG_ERROR(error);
2792 return gcc49;
2793
2794 }
2795 #else
usingMingwGcc49()2796 bool usingMingwGcc49()
2797 {
2798 return false;
2799 }
2800 #endif
2801
2802 namespace {
2803
2804 #ifdef __APPLE__
warnXcodeLicense()2805 void warnXcodeLicense()
2806 {
2807 const char* msg =
2808 R"EOF(Warning: macOS is reporting that you have not yet agreed to the Xcode license.
2809 This can occur if Xcode has been updated or reinstalled (e.g. as part of a macOS update).
2810 Some features (e.g. Git / SVN) may be disabled.
2811
2812 Please run:
2813
2814 sudo xcodebuild -license accept
2815
2816 in a terminal to accept the Xcode license, and then restart RStudio.
2817 )EOF";
2818
2819 std::cerr << msg << std::endl;
2820 }
2821 #endif
2822
2823 } // end anonymous namespace
2824
isMacOS()2825 bool isMacOS()
2826 {
2827 #ifdef __APPLE__
2828 return true;
2829 #else
2830 return false;
2831 #endif
2832 }
2833
hasMacOSDeveloperTools()2834 bool hasMacOSDeveloperTools()
2835 {
2836 if (!isMacOS())
2837 return false;
2838
2839 core::system::ProcessResult result;
2840 Error error = core::system::runCommand(
2841 "/usr/bin/xcrun --find --show-sdk-path",
2842 core::system::ProcessOptions(),
2843 &result);
2844
2845 if (error)
2846 {
2847 LOG_ERROR(error);
2848 return false;
2849 }
2850
2851 if (result.exitStatus == 69)
2852 checkXcodeLicense();
2853
2854 return result.exitStatus == 0;
2855 }
2856
hasMacOSCommandLineTools()2857 bool hasMacOSCommandLineTools()
2858 {
2859 if (!isMacOS())
2860 return false;
2861
2862 return FilePath("/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk").exists();
2863 }
2864
checkXcodeLicense()2865 void checkXcodeLicense()
2866 {
2867 #ifdef __APPLE__
2868
2869 // avoid repeatedly warning the user
2870 static bool s_licenseChecked;
2871 if (s_licenseChecked)
2872 return;
2873
2874 s_licenseChecked = true;
2875
2876 core::system::ProcessResult result;
2877 Error error = core::system::runCommand(
2878 "/usr/bin/xcrun --find --show-sdk-path",
2879 core::system::ProcessOptions(),
2880 &result);
2881
2882 // if an error occurs, log it but avoid otherwise annoying the user
2883 if (error)
2884 {
2885 LOG_ERROR(error);
2886 return;
2887 }
2888
2889 // exit code 69 implies license error
2890 if (result.exitStatus == 69)
2891 warnXcodeLicense();
2892
2893 #endif
2894 }
2895
ignoreContentDirs()2896 std::vector<FilePath> ignoreContentDirs()
2897 {
2898 std::vector<FilePath> ignoreDirs;
2899 if (projects::projectContext().hasProject()) {
2900 // python virtual environments
2901 ignoreDirs = projects::projectContext().pythonEnvs();
2902 quarto::QuartoConfig quartoConf = quarto::quartoConfig();
2903 // quarto site output dir
2904 if (quartoConf.is_project) {
2905 FilePath quartoProjDir = module_context::resolveAliasedPath(quartoConf.project_dir);
2906 ignoreDirs.push_back(quartoProjDir.completeChildPath(quartoConf.project_output_dir));
2907 ignoreDirs.push_back(quartoProjDir.completeChildPath("_freeze"));
2908 }
2909 // rmarkdown site output dir
2910 if (module_context::isWebsiteProject())
2911 {
2912 FilePath buildTargetPath = projects::projectContext().buildTargetPath();
2913 std::string outputDir = module_context::websiteOutputDir();
2914 if (!outputDir.empty())
2915 ignoreDirs.push_back(buildTargetPath.completeChildPath(outputDir));
2916 else
2917 ignoreDirs.push_back(buildTargetPath);
2918 }
2919
2920 }
2921 return ignoreDirs;
2922 }
2923
isIgnoredContent(const core::FilePath & filePath,const std::vector<core::FilePath> & ignoreDirs)2924 bool isIgnoredContent(const core::FilePath& filePath,
2925 const std::vector<core::FilePath>& ignoreDirs)
2926 {
2927 auto it = std::find_if(ignoreDirs.begin(), ignoreDirs.end(), [&filePath](const FilePath& dir) {
2928 return dir.exists() && filePath.isWithin(dir);
2929 });
2930 return it != ignoreDirs.end();
2931 }
2932
2933
getActiveLanguage()2934 std::string getActiveLanguage()
2935 {
2936 if (modules::reticulate::isReplActive())
2937 {
2938 return "Python";
2939 }
2940 else
2941 {
2942 return "R";
2943 }
2944 }
2945
adaptToLanguage(const std::string & language)2946 Error adaptToLanguage(const std::string& language)
2947 {
2948 // check to see what language is active in main console
2949 using namespace r::exec;
2950
2951 // check to see what language is currently active (but default to r)
2952 std::string activeLanguage = getActiveLanguage();
2953
2954 // now, detect if we are transitioning languages
2955 if (language != activeLanguage)
2956 {
2957 // since it may take some time for the console input to be processed,
2958 // we screen out consecutive transition attempts (otherwise we can
2959 // get multiple interleaved attempts to launch the REPL with console
2960 // input)
2961 static RSTUDIO_BOOST_CONNECTION conn;
2962 if (conn.connected())
2963 return Success();
2964
2965 // establish the connection, and then simply disconnect once we
2966 // receive the signal
2967 conn = module_context::events().onConsolePrompt.connect([&](const std::string&) {
2968 conn.disconnect();
2969 });
2970
2971 Error error;
2972
2973 if (activeLanguage == "R" && language == "Python")
2974 {
2975 // r -> python: activate the reticulate REPL
2976 error = module_context::enqueueConsoleInput("reticulate::repl_python()");
2977 }
2978 else if (activeLanguage == "Python" && language == "R")
2979 {
2980 // python -> r: deactivate the reticulate REPL
2981 error = module_context::enqueueConsoleInput("quit");
2982 }
2983
2984 if (error)
2985 LOG_ERROR(error);
2986 }
2987
2988 return Success();
2989 }
2990
initialize()2991 Error initialize()
2992 {
2993 // register .Call methods
2994 RS_REGISTER_CALL_METHOD(rs_activatePane);
2995 RS_REGISTER_CALL_METHOD(rs_base64decode);
2996 RS_REGISTER_CALL_METHOD(rs_base64encode);
2997 RS_REGISTER_CALL_METHOD(rs_base64encodeFile);
2998 RS_REGISTER_CALL_METHOD(rs_htmlEscape);
2999 RS_REGISTER_CALL_METHOD(rs_enqueClientEvent);
3000 RS_REGISTER_CALL_METHOD(rs_ensureFileHidden);
3001 RS_REGISTER_CALL_METHOD(rs_generateShortUuid);
3002 RS_REGISTER_CALL_METHOD(rs_getPersistentValue);
3003 RS_REGISTER_CALL_METHOD(rs_isRScriptInPackageBuildTarget);
3004 RS_REGISTER_CALL_METHOD(rs_logErrorMessage);
3005 RS_REGISTER_CALL_METHOD(rs_logWarningMessage);
3006 RS_REGISTER_CALL_METHOD(rs_markdownToHTML);
3007 RS_REGISTER_CALL_METHOD(rs_packageLoaded);
3008 RS_REGISTER_CALL_METHOD(rs_packageNameForSourceFile);
3009 RS_REGISTER_CALL_METHOD(rs_packageUnloaded);
3010 RS_REGISTER_CALL_METHOD(rs_resolveAliasedPath);
3011 RS_REGISTER_CALL_METHOD(rs_restartR);
3012 RS_REGISTER_CALL_METHOD(rs_rstudioCitation);
3013 RS_REGISTER_CALL_METHOD(rs_rstudioCRANReposUrl);
3014 RS_REGISTER_CALL_METHOD(rs_rstudioEdition);
3015 RS_REGISTER_CALL_METHOD(rs_rstudioProgramMode);
3016 RS_REGISTER_CALL_METHOD(rs_rstudioVersion);
3017 RS_REGISTER_CALL_METHOD(rs_rstudioLongVersion);
3018 RS_REGISTER_CALL_METHOD(rs_rstudioReleaseName);
3019 RS_REGISTER_CALL_METHOD(rs_sessionModulePath);
3020 RS_REGISTER_CALL_METHOD(rs_setPersistentValue);
3021 RS_REGISTER_CALL_METHOD(rs_setUsingMingwGcc49);
3022 RS_REGISTER_CALL_METHOD(rs_showErrorMessage);
3023 RS_REGISTER_CALL_METHOD(rs_sourceDiagnostics);
3024 RS_REGISTER_CALL_METHOD(rs_threadSleep);
3025 RS_REGISTER_CALL_METHOD(rs_userPrompt);
3026 RS_REGISTER_CALL_METHOD(rs_setRpcDelay);
3027 RS_REGISTER_CALL_METHOD(rs_performBackgroundProcessing);
3028
3029 // initialize monitored scratch dir
3030 initializeMonitoredUserScratchDir();
3031
3032 // source the ModuleTools.R file
3033 FilePath modulesPath = session::options().modulesRSourcePath();
3034 return r::sourceManager().sourceTools(modulesPath.completePath("ModuleTools.R"));
3035 }
3036
3037
3038 } // namespace module_context
3039 } // namespace session
3040 } // namespace rstudio
3041