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