1 /*
2 * RConsoleActions.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/session/RConsoleActions.hpp>
17
18 #include <algorithm>
19 #include <gsl/gsl>
20
21 #include <boost/algorithm/string/split.hpp>
22
23 #include <core/Log.hpp>
24 #include <shared_core/Error.hpp>
25 #include <shared_core/FilePath.hpp>
26 #include <core/FileSerializer.hpp>
27 #include <core/Thread.hpp>
28
29 #include <r/ROptions.hpp>
30
31 using namespace rstudio::core;
32
33 namespace rstudio {
34 namespace r {
35 namespace session {
36
37 namespace {
38 const char * const kActionType = "type";
39 const char * const kActionData = "data";
40 }
41
consoleActions()42 ConsoleActions& consoleActions()
43 {
44 static ConsoleActions instance;
45 return instance;
46 }
47
ConsoleActions()48 ConsoleActions::ConsoleActions()
49 {
50 setCapacity(1000);
51 }
52
capacity() const53 int ConsoleActions::capacity() const
54 {
55 LOCK_MUTEX(mutex_)
56 {
57 return gsl::narrow_cast<int>(actionsType_.capacity());
58 }
59 END_LOCK_MUTEX
60
61 // keep compiler happy
62 return 0;
63 }
64
setCapacity(int capacity)65 void ConsoleActions::setCapacity(int capacity)
66 {
67 LOCK_MUTEX(mutex_)
68 {
69 actionsType_.set_capacity(capacity);
70 actionsData_.set_capacity(capacity);
71 }
72 END_LOCK_MUTEX
73 }
74
add(int type,const std::string & data)75 void ConsoleActions::add(int type, const std::string& data)
76 {
77 LOCK_MUTEX(mutex_)
78 {
79 // manage pending input buffer
80 if (type == kConsoleActionPrompt &&
81 data == r::options::getOption<std::string>("prompt"))
82 {
83 pendingInput_.clear();
84 }
85 else if (type == kConsoleActionInput)
86 {
87 std::vector<std::string> input;
88 boost::algorithm::split(input,
89 data,
90 boost::algorithm::is_any_of("\n"));
91 pendingInput_.insert(pendingInput_.end(), input.begin(), input.end());
92 }
93
94
95 // automatically combine consecutive output actions (up to 512 bytes)
96 // we enforce a limit so that the limit defined for our circular buffer
97 // (see setCapacity above) implies a content size limit as well (if we
98 // didn't cap the size of combined output then the output actions could
99 // grow to arbitrary size)
100 if (type == kConsoleActionOutput &&
101 actionsType_.size() > 0 &&
102 actionsType_.back().getInt() == kConsoleActionOutput &&
103 actionsData_.back().getString().size() < 512)
104 {
105 actionsData_.back() = actionsData_.back().getString() + data;
106 }
107 else
108 {
109 actionsType_.push_back(json::Value(type));
110 actionsData_.push_back(json::Value(data));
111 }
112 }
113 END_LOCK_MUTEX
114 }
115
notifyInterrupt()116 void ConsoleActions::notifyInterrupt()
117 {
118 LOCK_MUTEX(mutex_)
119 {
120 pendingInput_.clear();
121 }
122 END_LOCK_MUTEX
123 }
124
reset()125 void ConsoleActions::reset()
126 {
127 LOCK_MUTEX(mutex_)
128 {
129 // clear the existing actions
130 actionsType_.clear();
131 actionsData_.clear();
132 }
133 END_LOCK_MUTEX
134 }
135
asJson(json::Object * pActions) const136 void ConsoleActions::asJson(json::Object* pActions) const
137 {
138 LOCK_MUTEX(mutex_)
139 {
140 // clear inbound
141 pActions->clear();
142
143 // copy actions and insert into destination
144 json::Array actionsType;
145 std::copy(actionsType_.begin(),
146 actionsType_.end(),
147 std::back_inserter(actionsType));
148 pActions->operator[](kActionType) = actionsType;
149
150 // copy data and insert into destination
151 json::Array actionsData;
152 std::copy(actionsData_.begin(),
153 actionsData_.end(),
154 std::back_inserter(actionsData));
155 pActions->operator[](kActionData) = actionsData;
156 }
157 END_LOCK_MUTEX
158 }
159
loadFromFile(const FilePath & filePath)160 Error ConsoleActions::loadFromFile(const FilePath& filePath)
161 {
162 LOCK_MUTEX(mutex_)
163 {
164 actionsType_.clear();
165 actionsData_.clear();
166
167 if (filePath.exists())
168 {
169 // read from file
170 std::string actionsJson;
171 Error error = readStringFromFile(filePath, &actionsJson);
172 if (error)
173 return error;
174
175 // parse json and confirm it contains an object
176 json::Value value;
177 if (
178 !value.parse(actionsJson) && value.isObject() )
179 {
180 json::Object actions = value.getObject();
181
182 json::Value typeValue = actions[kActionType];
183 if (typeValue.getType() == json::Type::ARRAY)
184 {
185 const json::Array& actionsType = typeValue.getArray();
186 std::copy(actionsType.begin(),
187 actionsType.end(),
188 std::back_inserter(actionsType_));
189 }
190 else
191 {
192 LOG_WARNING_MESSAGE("unexpected json type in: " + actionsJson);
193 }
194
195 json::Value dataValue = actions[kActionData];
196 if ( dataValue.getType() == json::Type::ARRAY )
197 {
198 const json::Array& actionsData = dataValue.getArray();
199 std::copy(actionsData.begin(),
200 actionsData.end(),
201 std::back_inserter(actionsData_));
202 }
203 else
204 {
205 LOG_WARNING_MESSAGE("unexpected json type in: " + actionsJson);
206 }
207 }
208 else
209 {
210 LOG_WARNING_MESSAGE("unexpected json type in: " + actionsJson);
211 }
212 }
213 }
214 END_LOCK_MUTEX
215
216 return Success();
217 }
218
saveToFile(const core::FilePath & filePath) const219 Error ConsoleActions::saveToFile(const core::FilePath& filePath) const
220 {
221 // write actions
222 json::Object actionsObject;
223 asJson(&actionsObject);
224 std::ostringstream ostr;
225 actionsObject.writeFormatted(ostr);
226
227 // write to file
228 return writeStringToFile(filePath, ostr.str());
229 }
230
231 } // namespace session
232 } // namespace r
233 } // namespace rstudio
234
235
236
237