/* Copyright (C) 2018 Carl Hetherington This file is part of DCP-o-matic. DCP-o-matic is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. DCP-o-matic is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with DCP-o-matic. If not, see . */ #include "analytics.h" #include "exceptions.h" #include "job.h" #include "cross.h" #include #include #include #include #include #include #include #include #include #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("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::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(_time.tv_sec)); for (map::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(_time.tv_sec) + "\n"; for (map::const_iterator i = _data.begin(); i != _data.end(); ++i) { d += i->first + ": " + i->second + "\n"; } return d; } Analytics::Analytics () : _id (dcp::make_uuid()) , _thread (0) { } Analytics::~Analytics () { if (!_thread) { return; } _thread->interrupt(); if (_thread->joinable()) { try { _thread->join(); } catch (...) { /* Too late to do anything about this */ } } delete _thread; } void Analytics::start () { _thread = new boost::thread (boost::bind(&Analytics::thread, this)); #ifdef DCPOMATIC_LINUX pthread_setname_np (_thread->native_handle(), "update-checker"); #endif } void Analytics::thread () try { while (true) { { boost::mutex::scoped_lock lm (_mutex); if (_events.empty ()) { continue; } CURL* curl = curl_easy_init (); if (!curl) { continue; } curl_easy_setopt (curl, CURLOPT_URL, "https://dcpomatic.com/analytics"); xmlpp::Document doc; xmlpp_document (doc); curl_easy_setopt (curl, CURLOPT_POST, 1); curl_easy_setopt (curl, CURLOPT_COPYPOSTFIELDS, doc.write_to_string().c_str()); CURLcode res = curl_easy_perform (curl); if (res == CURLE_OK) { _events.clear (); } curl_easy_cleanup (curl); write (); } dcpomatic_sleep (60); } } catch (...) { /* Never mind */ } 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::job_state_changed (shared_ptr job) { 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!"), _( "

You have made 3 DCPs with DCP-o-matic!

" "" "

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." "

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!" "

" "

Thank you!" ) ) ); } } /** Must be called with a lock held on _mutex*/ void Analytics::xmlpp_document (xmlpp::Document& doc) const { xmlpp::Element* root = doc.create_root_node ("Analytics"); root->add_child("Version")->add_child_text(raw_convert(_current_version)); root->add_child("Id")->add_child_text(_id); BOOST_FOREACH (Event e, _events) { e.as_xml (root->add_child("Event")); } } /** Must be called with a lock held on _mutex */ void Analytics::write () const { try { xmlpp::Document doc; xmlpp_document (doc); doc.write_to_file_formatted(path("analytics.xml").string()); } catch (xmlpp::exception& e) { string s = e.what (); trim (s); throw FileError (s, path("analytics.xml")); } } void Analytics::read () try { cxml::Document f ("Analytics"); f.read_file (path("analytics.xml")); 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 */ } Analytics* Analytics::instance () { if (!_instance) { _instance = new Analytics(); _instance->read(); } return _instance; }