Basics of job analytics storage.
[dcpomatic.git] / src / lib / analytics.cc
index 74c21a29c1c40a8f9e232422196c9a4024ff4d5e..cbb8843025e6b62a22cdf41515ac04e5124ae1a0 100644 (file)
 
 #include "analytics.h"
 #include "exceptions.h"
+#include "job.h"
 #include <dcp/raw_convert.h>
+#include <dcp/util.h>
 #include <libcxml/cxml.h>
 #include <libxml++/libxml++.h>
 #include <boost/filesystem.hpp>
 #include <boost/algorithm/string.hpp>
+#include <boost/foreach.hpp>
+#include <iostream>
+
+#include "i18n.h"
 
 using std::string;
+using std::map;
 using dcp::raw_convert;
 using boost::algorithm::trim;
+using boost::shared_ptr;
 
 Analytics* Analytics::_instance;
 int const Analytics::_current_version = 1;
 
+Event::Event ()
+{
+       gettimeofday (&_time, 0);
+}
+
+Event::Event (cxml::ConstNodePtr node)
+{
+       _time.tv_sec = node->number_child<int64_t>("Time");
+       _time.tv_usec = 0;
+       BOOST_FOREACH (cxml::ConstNodePtr i, node->node_children()) {
+               set(i->name(), i->content());
+       }
+}
+
+void
+Event::set (string k, string v)
+{
+       _data[k] = v;
+}
+
+string
+Event::get (string k) const
+{
+       map<string, string>::const_iterator i = _data.find (k);
+       if (i == _data.end()) {
+               return "";
+       }
+       return i->second;
+}
+
+void
+Event::as_xml (xmlpp::Element* parent) const
+{
+       /* It would be nice if this had timezone */
+       parent->add_child("Time")->add_child_text(raw_convert<string>(_time.tv_sec));
+       for (map<string, string>::const_iterator i = _data.begin(); i != _data.end(); ++i) {
+               parent->add_child(i->first)->add_child_text(i->second);
+       }
+}
+
+string
+Event::dump () const
+{
+       string d;
+       d += raw_convert<string>(_time.tv_sec) + "\n";
+       for (map<string, string>::const_iterator i = _data.begin(); i != _data.end(); ++i) {
+               d += i->first + ": " + i->second + "\n";
+       }
+       return d;
+}
+
 Analytics::Analytics ()
-       : _successful_dcp_encodes (0)
+       : _id (dcp::make_uuid())
 {
 
 }
 
+int
+Analytics::successful_dcp_encodes () const
+{
+       boost::mutex::scoped_lock lm (_mutex);
+       int n = 0;
+       BOOST_FOREACH (Event e, _events) {
+               if (e.get("type") == "job_state" && e.get("json_name") == "transcode" && e.get("status") == "finished_ok") {
+                       ++n;
+               }
+       }
+       return n;
+}
+
 void
-Analytics::successful_dcp_encode ()
+Analytics::job_state_changed (shared_ptr<Job> job)
 {
-       ++_successful_dcp_encodes;
+       Event ev;
+       ev.set ("type", "job_state");
+       ev.set ("json_name", job->json_name());
+       ev.set ("sub_name", job->sub_name());
+       ev.set ("error-summary", job->error_summary());
+       ev.set ("error-details", job->error_details());
+       ev.set ("status", job->json_status());
+
+       {
+               boost::mutex::scoped_lock lm (_mutex);
+               _events.push_back (ev);
+       }
+
        write ();
+
+       if (successful_dcp_encodes() == 3) {
+               emit (
+                       boost::bind(
+                               boost::ref(Message),
+                               _("Congratulations!"),
+                               _(
+                                       "<h2>You have made 3 DCPs with DCP-o-matic!</h2>"
+                                       "<img width=\"20%\" src=\"memory:me.jpg\" align=\"center\">"
+                                        "<p>Hello. I'm Carl and I'm the "
+                                       "developer of DCP-o-matic. I work on it in my spare time (with the help "
+                                       "of a fine volunteer team of testers and translators) and I release it "
+                                       "as free software."
+
+                                       "<p>If you find DCP-o-matic useful, please consider a donation to the "
+                                       "project. Financial support will help me to spend more "
+                                       "time developing DCP-o-matic and making it better!"
+
+                                       "<p><ul>"
+                                       "<li><a href=\"https://dcpomatic.com/donate_amount?amount=40\">Go to Paypal to donate £40</a>"
+                                       "<li><a href=\"https://dcpomatic.com/donate_amount?amount=20\">Go to Paypal to donate £20</a>"
+                                       "<li><a href=\"https://dcpomatic.com/donate_amount?amount=10\">Go to Paypal to donate £10</a>"
+                                       "</ul>"
+
+                                       "<p>Thank you!"
+                                       )
+                               )
+                       );
+       }
 }
 
 void
@@ -53,7 +166,11 @@ Analytics::write () const
        xmlpp::Element* root = doc.create_root_node ("Analytics");
 
        root->add_child("Version")->add_child_text(raw_convert<string>(_current_version));
-       root->add_child("SuccessfulDCPEncodes")->add_child_text(raw_convert<string>(_successful_dcp_encodes));
+       boost::mutex::scoped_lock lm (_mutex);
+       root->add_child("Id")->add_child_text(_id);
+       BOOST_FOREACH (Event e, _events) {
+               e.as_xml (root->add_child("Event"));
+       }
 
        try {
                doc.write_to_file_formatted(path("analytics.xml").string());
@@ -70,7 +187,11 @@ try
 {
        cxml::Document f ("Analytics");
        f.read_file (path("analytics.xml"));
-       _successful_dcp_encodes = f.number_child<int>("SuccessfulDCPEncodes");
+       boost::mutex::scoped_lock lm (_mutex);
+       _id = f.string_child("Id");
+       BOOST_FOREACH (cxml::ConstNodePtr i, f.node_children("Event")) {
+               _events.push_back (Event(i));
+       }
 } catch (...) {
        /* Never mind */
 }