2 Copyright (C) 2018 Carl Hetherington <cth@carlh.net>
4 This file is part of DCP-o-matic.
6 DCP-o-matic is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
11 DCP-o-matic is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
16 You should have received a copy of the GNU General Public License
17 along with DCP-o-matic. If not, see <http://www.gnu.org/licenses/>.
21 #include "analytics.h"
22 #include "exceptions.h"
25 #include <dcp/raw_convert.h>
27 #include <libcxml/cxml.h>
28 #include <curl/curl.h>
29 #include <libxml++/libxml++.h>
30 #include <boost/filesystem.hpp>
31 #include <boost/algorithm/string.hpp>
32 #include <boost/foreach.hpp>
39 using dcp::raw_convert;
40 using boost::algorithm::trim;
41 using boost::shared_ptr;
43 Analytics* Analytics::_instance;
44 int const Analytics::_current_version = 1;
48 gettimeofday (&_time, 0);
51 Event::Event (cxml::ConstNodePtr node)
53 _time.tv_sec = node->number_child<int64_t>("Time");
55 BOOST_FOREACH (cxml::ConstNodePtr i, node->node_children()) {
56 set(i->name(), i->content());
61 Event::set (string k, string v)
67 Event::get (string k) const
69 map<string, string>::const_iterator i = _data.find (k);
70 if (i == _data.end()) {
77 Event::as_xml (xmlpp::Element* parent) const
79 /* It would be nice if this had timezone */
80 parent->add_child("Time")->add_child_text(raw_convert<string>(_time.tv_sec));
81 for (map<string, string>::const_iterator i = _data.begin(); i != _data.end(); ++i) {
82 parent->add_child(i->first)->add_child_text(i->second);
90 d += raw_convert<string>(_time.tv_sec) + "\n";
91 for (map<string, string>::const_iterator i = _data.begin(); i != _data.end(); ++i) {
92 d += i->first + ": " + i->second + "\n";
97 Analytics::Analytics ()
98 : _id (dcp::make_uuid())
104 Analytics::~Analytics ()
110 _thread->interrupt();
111 if (_thread->joinable()) {
115 /* Too late to do anything about this */
125 _thread = new boost::thread (boost::bind(&Analytics::thread, this));
126 #ifdef DCPOMATIC_LINUX
127 pthread_setname_np (_thread->native_handle(), "update-checker");
138 boost::mutex::scoped_lock lm (_mutex);
139 if (_events.empty ()) {
143 CURL* curl = curl_easy_init ();
148 curl_easy_setopt (curl, CURLOPT_URL, "https://dcpomatic.com/analytics");
150 xmlpp_document (doc);
151 curl_easy_setopt (curl, CURLOPT_POST, 1);
152 curl_easy_setopt (curl, CURLOPT_COPYPOSTFIELDS, doc.write_to_string().c_str());
153 CURLcode res = curl_easy_perform (curl);
154 if (res == CURLE_OK) {
157 curl_easy_cleanup (curl);
162 dcpomatic_sleep (60);
170 Analytics::successful_dcp_encodes () const
172 boost::mutex::scoped_lock lm (_mutex);
174 BOOST_FOREACH (Event e, _events) {
175 if (e.get("type") == "job_state" && e.get("json_name") == "transcode" && e.get("status") == "finished_ok") {
183 Analytics::job_state_changed (shared_ptr<Job> job)
186 ev.set ("type", "job_state");
187 ev.set ("json_name", job->json_name());
188 ev.set ("sub_name", job->sub_name());
189 ev.set ("error-summary", job->error_summary());
190 ev.set ("error-details", job->error_details());
191 ev.set ("status", job->json_status());
194 boost::mutex::scoped_lock lm (_mutex);
195 _events.push_back (ev);
199 if (successful_dcp_encodes() == 3) {
203 _("Congratulations!"),
205 "<h2>You have made 3 DCPs with DCP-o-matic!</h2>"
206 "<img width=\"20%\" src=\"memory:me.jpg\" align=\"center\">"
207 "<p>Hello. I'm Carl and I'm the "
208 "developer of DCP-o-matic. I work on it in my spare time (with the help "
209 "of a fine volunteer team of testers and translators) and I release it "
212 "<p>If you find DCP-o-matic useful, please consider a donation to the "
213 "project. Financial support will help me to spend more "
214 "time developing DCP-o-matic and making it better!"
217 "<li><a href=\"https://dcpomatic.com/donate_amount?amount=40\">Go to Paypal to donate £40</a>"
218 "<li><a href=\"https://dcpomatic.com/donate_amount?amount=20\">Go to Paypal to donate £20</a>"
219 "<li><a href=\"https://dcpomatic.com/donate_amount?amount=10\">Go to Paypal to donate £10</a>"
229 /** Must be called with a lock held on _mutex*/
231 Analytics::xmlpp_document (xmlpp::Document& doc) const
233 xmlpp::Element* root = doc.create_root_node ("Analytics");
235 root->add_child("Version")->add_child_text(raw_convert<string>(_current_version));
236 root->add_child("Id")->add_child_text(_id);
237 BOOST_FOREACH (Event e, _events) {
238 e.as_xml (root->add_child("Event"));
242 /** Must be called with a lock held on _mutex */
244 Analytics::write () const
248 xmlpp_document (doc);
249 doc.write_to_file_formatted(path("analytics.xml").string());
250 } catch (xmlpp::exception& e) {
251 string s = e.what ();
253 throw FileError (s, path("analytics.xml"));
261 cxml::Document f ("Analytics");
262 f.read_file (path("analytics.xml"));
263 boost::mutex::scoped_lock lm (_mutex);
264 _id = f.string_child("Id");
265 BOOST_FOREACH (cxml::ConstNodePtr i, f.node_children("Event")) {
266 _events.push_back (Event(i));
273 Analytics::instance ()
276 _instance = new Analytics();