1 /*
2  * RSourceManager.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 <r/RSourceManager.hpp>
17 
18 #include <algorithm>
19 
20 #include <boost/algorithm/string/replace.hpp>
21 #include <boost/bind/bind.hpp>
22 
23 #include <shared_core/Error.hpp>
24 #include <shared_core/FilePath.hpp>
25 #include <core/Log.hpp>
26 
27 #include <r/RExec.hpp>
28 
29 using namespace rstudio::core;
30 using namespace boost::placeholders;
31 
32 namespace rstudio {
33 namespace r {
34 
35 
sourceManager()36 SourceManager& sourceManager()
37 {
38    static SourceManager instance;
39    return instance;
40 }
41 
sourceTools(const core::FilePath & filePath)42 Error SourceManager::sourceTools(const core::FilePath& filePath)
43 {
44    if (!filePath.exists())
45       return fileNotFoundError(filePath, ERROR_LOCATION);
46 
47    Error error = sourceLocal(filePath);
48    if (error)
49       return error;
50 
51    toolsFilePaths_.push_back(filePath);
52 
53    return Success();
54 }
55 
56 
sourceLocal(const FilePath & filePath)57 Error SourceManager::sourceLocal(const FilePath& filePath)
58 {
59    return source(filePath, true);
60 }
61 
ensureToolsLoaded()62 void SourceManager::ensureToolsLoaded()
63 {
64    // check whether tools:rstudio is still in the search patch
65    bool toolsRStudioLoaded = true;
66    Error error = r::exec::evaluateString("\"tools:rstudio\" %in% search()",
67                                          &toolsRStudioLoaded);
68    if (error)
69       LOG_ERROR(error);
70 
71    // if not then source all of the tools files back in
72    if (!toolsRStudioLoaded)
73    {
74       std::for_each(toolsFilePaths_.begin(),
75                     toolsFilePaths_.end(),
76                     boost::bind(&SourceManager::reSourceTools, this, _1));
77    }
78 }
79 
reloadIfNecessary()80 void SourceManager::reloadIfNecessary()
81 {
82    if (autoReload_)
83    {
84       std::for_each(sourcedFiles_.begin(),
85                     sourcedFiles_.end(),
86                     boost::bind(&SourceManager::reloadSourceIfNecessary,
87                                 this,
88                                 _1));
89    }
90 }
91 
92 
reSourceTools(const core::FilePath & filePath)93 void SourceManager::reSourceTools(const core::FilePath& filePath)
94 {
95    Error error = source(filePath, true);
96    if (error)
97       LOG_ERROR(error);
98 }
99 
source(const FilePath & filePath,bool local)100 Error SourceManager::source(const FilePath& filePath, bool local)
101 {
102    std::string localPrefix = local ? "local(" : "";
103    std::string localParam = local ? "TRUE" : "FALSE";
104    std::string localSuffix = local ? ")" : "";
105 
106    // do \ escaping (for windows)
107    std::string path = filePath.getAbsolutePath();
108    boost::algorithm::replace_all(path, "\\", "\\\\");
109 
110    // Build the code. If this build is targeted for debugging, keep the source
111    // code around; otherwise, turn it off to conserve memory and expose fewer
112    // internals to the user.
113    std::string rCode = localPrefix + "source(\""
114                            + path + "\", " +
115                            "local=" + localParam + ", " +
116                            "echo=FALSE, " +
117                            "verbose=FALSE, " +
118 #ifdef NDEBUG
119                            "keep.source=FALSE, " +
120 #else
121                            "keep.source=TRUE, " +
122 #endif
123                            "encoding='UTF-8')" + localSuffix;
124 
125    // record that we sourced the file.
126    recordSourcedFile(filePath, local);
127 
128    // source the file
129    return r::exec::executeString(rCode);
130 }
131 
recordSourcedFile(const FilePath & filePath,bool local)132 void SourceManager::recordSourcedFile(const FilePath& filePath, bool local)
133 {
134    SourcedFileInfo fileInfo(filePath.getLastWriteTime(), local);
135    sourcedFiles_[filePath.getAbsolutePath()] = fileInfo;
136 }
137 
reloadSourceIfNecessary(const SourcedFileMap::value_type & value)138 void SourceManager::reloadSourceIfNecessary(
139                                     const SourcedFileMap::value_type& value)
140 {
141    // extract values
142    FilePath sourcedFilePath(value.first);
143    SourcedFileInfo fileInfo = value.second;
144 
145    // compare last write times and source again if necessary
146    double diffTime = std::difftime(sourcedFilePath.getLastWriteTime(),
147                                    fileInfo.lastWriteTime);
148    if (diffTime > 0)
149    {
150       Error error = source(sourcedFilePath, fileInfo.local);
151       if (error)
152          LOG_ERROR(error);
153    }
154 }
155 
156 } // namespace r
157 } // namespace rstudio
158 
159 
160 
161