1 /*
2 * NotebookWorkingDir.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 "SessionRmdNotebook.hpp"
17 #include "NotebookWorkingDir.hpp"
18
19 #include <r/RExec.hpp>
20
21 #include <shared_core/FilePath.hpp>
22
23 #include <session/SessionSourceDatabase.hpp>
24 #include <session/SessionModuleContext.hpp>
25
26 using namespace rstudio::core;
27
28 namespace rstudio {
29 namespace session {
30 namespace modules {
31 namespace rmarkdown {
32 namespace notebook {
33
DirCapture()34 DirCapture::DirCapture():
35 warned_(false)
36 {
37 }
38
39
~DirCapture()40 DirCapture::~DirCapture()
41 {
42 }
43
connectDir(const std::string & docId,const core::FilePath & workingDir)44 Error DirCapture::connectDir(const std::string& docId,
45 const core::FilePath& workingDir)
46 {
47 if (workingDir.exists())
48 {
49 // prefer manually specified working directory
50 workingDir_ = workingDir;
51 }
52 else
53 {
54 // no manually specified dir; use working directory to doc path, if it
55 // has one
56 std::string docPath;
57 source_database::getPath(docId, &docPath);
58 if (!docPath.empty())
59 {
60 workingDir_ = module_context::resolveAliasedPath(docPath).getParent();
61 }
62 }
63
64 if (!workingDir_.isEmpty())
65 {
66 // if we have a working directory, switch to it, and save directory we're
67 // changing from (so we can detect changes)
68 FilePath currentDir = FilePath::safeCurrentPath(workingDir_);
69 if (currentDir != workingDir_)
70 {
71 Error error = FilePath::makeCurrent(workingDir_.getAbsolutePath());
72 if (error)
73 return error;
74 }
75 prevWorkingDir_ = currentDir.getAbsolutePath();
76 }
77
78 NotebookCapture::connect();
79
80 return Success();
81 }
82
disconnect()83 void DirCapture::disconnect()
84 {
85 // restore working directory, if we saved one
86 if (connected() && !prevWorkingDir_.empty())
87 {
88 Error error = FilePath::makeCurrent(prevWorkingDir_);
89 if (error)
90 LOG_ERROR(error);
91 }
92 NotebookCapture::disconnect();
93 }
94
onExprComplete()95 void DirCapture::onExprComplete()
96 {
97 if (!warned_ && !workingDir_.isEmpty())
98 {
99 // raise a warning when changing a working directory inside the notebook
100 // (this leads to unexpected behavior)
101 FilePath currentDir = FilePath::safeCurrentPath(workingDir_);
102 if (!currentDir.isEquivalentTo(workingDir_))
103 {
104 r::exec::warning("The working directory was changed to " +
105 currentDir.getAbsolutePath() + " inside a notebook chunk. The "
106 "working directory will be reset when the chunk is finished "
107 "running. Use the knitr root.dir option in the setup chunk "
108 "to change the working directory for notebook chunks.\n");
109
110 // don't show warning more than once per chunk
111 warned_ = true;
112 }
113 }
114 }
115
116 } // namespace notebook
117 } // namespace rmarkdown
118 } // namespace modules
119 } // namespace session
120 } // namespace rstudio
121
122
123