Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge branch 'feature/presentation-log'

  • Loading branch information...
commit dfd311bbe7437693b384fb6f93c88b413d721a3c 2 parents 2adabc8 + 64c2b5a
@jjallaire jjallaire authored
View
1  src/cpp/session/CMakeLists.txt
@@ -80,6 +80,7 @@ set (SESSION_SOURCE_FILES
modules/build/SessionBuildUtils.cpp
modules/build/SessionSourceCpp.cpp
modules/presentation/SessionPresentation.cpp
+ modules/presentation/PresentationLog.cpp
modules/presentation/PresentationState.cpp
modules/presentation/SlideMediaRenderer.cpp
modules/presentation/SlideNavigationList.cpp
View
8 src/cpp/session/SessionMain.cpp
@@ -1530,6 +1530,9 @@ void consolePrompt(const std::string& prompt, bool addToHistory)
// allow modules to detect changes after execution of previous REPL
detectChanges(module_context::ChangeSourceREPL);
+
+ // call prompt hook
+ module_context::events().onConsolePrompt(prompt);
}
void reissueLastConsolePrompt()
@@ -1590,9 +1593,12 @@ bool rConsoleRead(const std::string& prompt,
s_consoleInputBuffer.pop();
}
- // fire onBeforeExecute event if this isn't a cancel
+ // fire onBeforeExecute and onConsoleInput events if this isn't a cancel
if (!pConsoleInput->cancel)
+ {
module_context::events().onBeforeExecute();
+ module_context::events().onConsoleInput(pConsoleInput->text);
+ }
// we are about to return input to r so set the flag indicating that state
s_rProcessingInput = true;
View
2  src/cpp/session/include/session/SessionModuleContext.hpp
@@ -209,6 +209,8 @@ struct Events : boost::noncopyable
{
boost::signal<void ()> onClientInit;
boost::signal<void ()> onBeforeExecute;
+ boost::signal<void(const std::string&)> onConsolePrompt;
+ boost::signal<void(const std::string&)> onConsoleInput;
boost::signal<void (ConsoleOutputType, const std::string&)>
onConsoleOutput;
boost::signal<void (ChangeSource)> onDetectChanges;
View
25 src/cpp/session/modules/SessionPresentation.R
@@ -35,3 +35,28 @@
doc))
})
+.rs.addFunction( "logPresentationEvent", function(file,
+ type,
+ time,
+ presentation,
+ slide,
+ input,
+ errors) {
+
+ entry <- data.frame(type = type,
+ time = time,
+ presentation = presentation,
+ slide = slide,
+ input = input,
+ errors = errors)
+
+ exists <- file.exists(file)
+ write.table(entry,
+ file,
+ append=exists,
+ sep = "\n",
+ row.names = FALSE,
+ col.names = !exists,
+ qmethod = "double")
+})
+
View
222 src/cpp/session/modules/presentation/PresentationLog.cpp
@@ -0,0 +1,222 @@
+/*
+ * PresentationLog.cpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+
+#include "PresentationLog.hpp"
+
+#include <boost/bind.hpp>
+#include <boost/foreach.hpp>
+#include <boost/algorithm/string/join.hpp>
+#include <boost/algorithm/string/trim.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
+
+#include <core/Error.hpp>
+#include <core/DateTime.hpp>
+#include <core/StringUtils.hpp>
+#include <core/SafeConvert.hpp>
+#include <core/FileSerializer.hpp>
+
+#include <r/RSexp.hpp>
+#include <r/RExec.hpp>
+
+#include "PresentationState.hpp"
+
+using namespace core;
+
+namespace session {
+namespace modules {
+namespace presentation {
+
+Log& log()
+{
+ static Log instance;
+ return instance;
+}
+
+
+Error Log::initialize()
+{
+
+ // connect to console events
+ using namespace boost;
+ using namespace session::module_context;
+
+ events().onConsolePrompt.connect(boost::bind(&Log::onConsolePrompt,
+ this, _1));
+ events().onConsoleInput.connect(boost::bind(&Log::onConsoleInput,
+ this, _1));
+ events().onConsoleOutput.connect(boost::bind(&Log::onConsoleOutput,
+ this, _1, _2));
+
+ return Success();
+}
+
+void Log::onSlideDeckChanged(const SlideDeck& slideDeck)
+{
+ slideDeckInputCommands_.clear();
+
+ const std::vector<Slide>& slides = slideDeck.slides();
+ for (std::size_t i = 0; i<slides.size(); i++)
+ {
+ const std::vector<Command>& commands = slides[i].commands();
+ BOOST_FOREACH(const Command& command, commands)
+ {
+ if (command.name() == "console-input")
+ {
+ std::string params = boost::algorithm::trim_copy(command.params());
+ slideDeckInputCommands_[i].insert(params);
+ }
+ }
+
+ const std::vector<AtCommand>& atCommands = slides[i].atCommands();
+ BOOST_FOREACH(const AtCommand& atCommand, atCommands)
+ {
+ if (atCommand.command().name() == "console-input")
+ {
+ std::string params = boost::algorithm::trim_copy(
+ atCommand.command().params());
+ slideDeckInputCommands_[i].insert(params);
+ }
+ }
+ }
+}
+
+void Log::onSlideIndexChanged(int index)
+{
+ currentSlideIndex_ = index;
+
+ append(NavigationEntry,
+ presentation::state::directory(),
+ currentSlideIndex_,
+ "",
+ "");
+}
+
+void Log::onConsolePrompt(const std::string& prompt)
+{
+ if (!presentation::state::isActive())
+ return;
+
+ if (!consoleInputBuffer_.empty())
+ {
+ std::string input = boost::algorithm::trim_copy(
+ boost::algorithm::join(consoleInputBuffer_, "\n"));
+ std::string errors = boost::algorithm::trim_copy(
+ boost::algorithm::join(errorOutputBuffer_, "\n"));
+
+ // check to see if this command was one of the ones instrumented
+ // by the current slide
+ if (slideDeckInputCommands_[currentSlideIndex_].count(input) == 0)
+ {
+ append(InputEntry,
+ presentation::state::directory(),
+ currentSlideIndex_,
+ input,
+ errors);
+ }
+ }
+
+ consoleInputBuffer_.clear();
+ errorOutputBuffer_.clear();
+}
+
+
+void Log::onConsoleInput(const std::string& text)
+{
+ if (!presentation::state::isActive())
+ return;
+
+ consoleInputBuffer_.push_back(text);
+
+ errorOutputBuffer_.clear();
+}
+
+
+void Log::onConsoleOutput(module_context::ConsoleOutputType type,
+ const std::string& output)
+{
+ if (!presentation::state::isActive())
+ return;
+
+ if (type == module_context::ConsoleOutputError)
+ errorOutputBuffer_.push_back(output);
+
+}
+
+namespace {
+
+std::string csvString(std::string str)
+{
+ boost::algorithm::replace_all(str, "\n", "\\n");
+ boost::algorithm::replace_all(str, "\"", "\\\"");
+ return "\"" + str + "\"";
+}
+
+} // anonymous namespace
+
+void Log::append(EntryType type,
+ const FilePath& presPath,
+ int slideIndex,
+ const std::string& input,
+ const std::string& errors)
+{
+ // determine log file path and ensure it exists
+ using namespace module_context;
+ FilePath presDir = userScratchPath().childPath("presentation");
+ Error error = presDir.ensureDirectory();
+ if (error)
+ {
+ LOG_ERROR(error);
+ return;
+ }
+
+ FilePath logFilePath = presDir.childPath("presentation-log.csv");
+ if (!logFilePath.exists())
+ {
+ Error error = core::writeStringToFile(logFilePath,
+ "type, timestamp, presentation, slide, input, errors\n");
+ if (error)
+ {
+ LOG_ERROR(error);
+ return;
+ }
+ }
+
+ // generate timestamp
+ using namespace boost::posix_time;
+ ptime time = microsec_clock::universal_time();
+ std::string dateTime = date_time::format(time, "%Y-%m-%dT%H:%M:%SZ");
+
+ // generate entry
+ std::vector<std::string> fields;
+ fields.push_back((type == NavigationEntry) ? "Navigation" : "Input");
+ fields.push_back(dateTime);
+ fields.push_back(csvString(module_context::createAliasedPath(presPath)));
+ fields.push_back(safe_convert::numberToString(slideIndex));
+ fields.push_back(csvString(input));
+ fields.push_back(csvString(errors));
+ std::string entry = boost::algorithm::join(fields, ",");
+
+ // append entry
+ error = core::appendToFile(logFilePath, entry + "\n");
+ if (error)
+ LOG_ERROR(error);
+}
+
+
+} // namespace presentation
+} // namespace modules
+} // namesapce session
+
View
82 src/cpp/session/modules/presentation/PresentationLog.hpp
@@ -0,0 +1,82 @@
+/*
+ * PresentationLog.hpp
+ *
+ * Copyright (C) 2009-12 by RStudio, Inc.
+ *
+ * Unless you have received this program directly from RStudio pursuant
+ * to the terms of a commercial license agreement with RStudio, then
+ * this program is licensed to you under the terms of version 3 of the
+ * GNU Affero General Public License. This program is distributed WITHOUT
+ * ANY EXPRESS OR IMPLIED WARRANTY, INCLUDING THOSE OF NON-INFRINGEMENT,
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Please refer to the
+ * AGPL (http://www.gnu.org/licenses/agpl-3.0.txt) for more details.
+ *
+ */
+
+#ifndef SESSION_PRESENTATION_LOG_HPP
+#define SESSION_PRESENTATION_LOG_HPP
+
+#include <string>
+#include <set>
+#include <vector>
+
+#include <boost/utility.hpp>
+
+#include <session/SessionModuleContext.hpp>
+
+#include "SlideParser.hpp"
+
+namespace core {
+ class Error;
+ class FilePath;
+}
+
+namespace session {
+namespace modules {
+namespace presentation {
+
+class Log;
+Log& log();
+
+class Log : boost::noncopyable
+{
+private:
+ Log() : currentSlideIndex_(0) {}
+ friend Log& log();
+
+public:
+ core::Error initialize();
+
+ void onSlideDeckChanged(const SlideDeck& slideDeck);
+ void onSlideIndexChanged(int index);
+
+private:
+ void onConsolePrompt(const std::string& prompt);
+ void onConsoleInput(const std::string& text);
+ void onConsoleOutput(module_context::ConsoleOutputType type,
+ const std::string& output);
+
+ enum EntryType { NavigationEntry, InputEntry };
+
+ static void append(EntryType type,
+ const core::FilePath& presPath,
+ int slideIndex,
+ const std::string& input,
+ const std::string& errors);
+
+
+private:
+ int currentSlideIndex_;
+ std::map<int, std::set<std::string> > slideDeckInputCommands_;
+
+ std::vector<std::string> consoleInputBuffer_;
+ std::vector<std::string> errorOutputBuffer_;
+
+
+};
+
+} // namespace presentation
+} // namespace modules
+} // namesapce session
+
+#endif // SESSION_PRESENTATION_LOG_HPP
View
10 src/cpp/session/modules/presentation/SessionPresentation.cpp
@@ -32,6 +32,7 @@
#include "../SessionRPubs.hpp"
+#include "PresentationLog.hpp"
#include "PresentationState.hpp"
#include "SlideRequestHandler.hpp"
@@ -118,6 +119,8 @@ Error setPresentationSlideIndex(const json::JsonRpcRequest& request,
presentation::state::setSlideIndex(index);
+ presentation::log().onSlideIndexChanged(index);
+
return Success();
}
@@ -219,8 +222,6 @@ Error createPresentationRpubsSource(const json::JsonRpcRequest& request,
return Success();
}
-
-
} // anonymous namespace
@@ -245,6 +246,11 @@ Error initialize()
methodDefShowHelpDoc.numArgs = 1;
r::routines::addCallMethod(methodDefShowHelpDoc);
+ // initialize presentation log
+ Error error = log().initialize();
+ if (error)
+ return error;
+
using boost::bind;
using namespace session::module_context;
ExecBlock initBlock ;
View
4 src/cpp/session/modules/presentation/SlideRequestHandler.cpp
@@ -38,6 +38,7 @@
#include <session/projects/SessionProjects.hpp>
#include "PresentationState.hpp"
+#include "PresentationLog.hpp"
#include "SlideParser.hpp"
#include "SlideRenderer.hpp"
@@ -623,6 +624,9 @@ void handlePresentationRootRequest(const std::string& path,
return;
}
+ // notify slide deck changed
+ log().onSlideDeckChanged(slideDeck);
+
// set preload to none for media
vars["slides"] = boost::algorithm::replace_all_copy(
vars["slides"],
Please sign in to comment.
Something went wrong with that request. Please try again.