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