The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

246 lines
7.5KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2017 - ROLI Ltd.
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 5 End-User License
  8. Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
  9. 27th April 2017).
  10. End User License Agreement: www.juce.com/juce-5-licence
  11. Privacy Policy: www.juce.com/juce-5-privacy-policy
  12. Or: You may also use this code under the terms of the GPL v3 (see
  13. www.gnu.org/licenses).
  14. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  15. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  16. DISCLAIMED.
  17. ==============================================================================
  18. */
  19. #include "jucer_ProjucerAnalytics.h"
  20. //==============================================================================
  21. ProjucerAnalyticsDestination::ProjucerAnalyticsDestination()
  22. : ThreadedAnalyticsDestination ("ProjucerAnalyticsThread")
  23. {
  24. {
  25. MemoryOutputStream mo;
  26. if (Base64::convertFromBase64 (mo, BinaryData::nothingtoseehere_txt))
  27. apiKey = mo.toString();
  28. }
  29. auto dataDir = File::getSpecialLocation (File::userApplicationDataDirectory)
  30. .getChildFile ("Projucer")
  31. .getChildFile ("Analytics");
  32. if (! dataDir.exists())
  33. dataDir.createDirectory();
  34. savedEventsFile = dataDir.getChildFile ("analytics_events.xml");
  35. startAnalyticsThread (initialPeriodMs);
  36. }
  37. ProjucerAnalyticsDestination::~ProjucerAnalyticsDestination()
  38. {
  39. Thread::sleep (initialPeriodMs);
  40. stopAnalyticsThread (1000);
  41. }
  42. //==============================================================================
  43. static void setData (const AnalyticsDestination::AnalyticsEvent& event, StringPairArray& data)
  44. {
  45. data.set ("ea", event.name);
  46. if (event.parameters.getAllKeys().contains ("label"))
  47. data.set ("el", event.parameters.getValue ("label", {}));
  48. data.addArray (event.userProperties);
  49. }
  50. bool ProjucerAnalyticsDestination::logBatchedEvents (const Array<AnalyticsEvent>& events)
  51. {
  52. String appData ("v=1&aip=1&tid=" + apiKey);
  53. StringArray postData;
  54. for (auto& event : events)
  55. {
  56. StringPairArray data;
  57. data.set ("t", "event");
  58. data.set ("cid", event.userID);
  59. switch (event.eventType)
  60. {
  61. case ProjucerAnalyticsEvent::appEvent:
  62. {
  63. data.set ("ec", "App");
  64. setData (event, data);
  65. break;
  66. }
  67. case ProjucerAnalyticsEvent::projectEvent:
  68. {
  69. data.set ("ec", "Project");
  70. setData (event, data);
  71. break;
  72. }
  73. case ProjucerAnalyticsEvent::userEvent:
  74. {
  75. data.set ("ec", "User");
  76. setData (event, data);
  77. break;
  78. }
  79. case ProjucerAnalyticsEvent::exampleEvent:
  80. {
  81. data.set ("ec", "Example");
  82. setData (event, data);
  83. break;
  84. }
  85. default:
  86. {
  87. // unknown event type!
  88. jassertfalse;
  89. break;
  90. }
  91. }
  92. StringArray eventData;
  93. for (auto& key : data.getAllKeys())
  94. eventData.add (key + "=" + URL::addEscapeChars (data[key], true));
  95. postData.add (appData + "&" + eventData.joinIntoString ("&"));
  96. }
  97. auto url = URL ("https://www.google-analytics.com/batch")
  98. .withPOSTData (postData.joinIntoString ("\n"));
  99. {
  100. const ScopedLock lock (webStreamCreation);
  101. if (shouldExit)
  102. return false;
  103. webStream.reset (new WebInputStream (url, true));
  104. }
  105. auto success = webStream->connect (nullptr);
  106. // Do an exponential backoff if we failed to connect.
  107. if (success)
  108. periodMs = initialPeriodMs;
  109. else
  110. periodMs *= 2;
  111. setBatchPeriod (periodMs);
  112. return success;
  113. }
  114. void ProjucerAnalyticsDestination::stopLoggingEvents()
  115. {
  116. const ScopedLock lock (webStreamCreation);
  117. shouldExit = true;
  118. if (webStream.get() != nullptr)
  119. webStream->cancel();
  120. }
  121. //==============================================================================
  122. void ProjucerAnalyticsDestination::saveUnloggedEvents (const std::deque<AnalyticsEvent>& eventsToSave)
  123. {
  124. XmlDocument previouslySavedEvents (savedEventsFile);
  125. std::unique_ptr<XmlElement> xml (previouslySavedEvents.getDocumentElement());
  126. if (xml.get() == nullptr || xml->getTagName() != "events")
  127. xml.reset (new XmlElement ("events"));
  128. for (auto& event : eventsToSave)
  129. {
  130. auto* xmlEvent = new XmlElement ("google_analytics_event");
  131. xmlEvent->setAttribute ("name", event.name);
  132. xmlEvent->setAttribute ("type", event.eventType);
  133. xmlEvent->setAttribute ("timestamp", (int) event.timestamp);
  134. xmlEvent->setAttribute ("user_id", event.userID);
  135. auto* parameters = new XmlElement ("parameters");
  136. for (auto& key : event.parameters.getAllKeys())
  137. parameters->setAttribute (key, event.parameters[key]);
  138. xmlEvent->addChildElement (parameters);
  139. auto* userProperties = new XmlElement ("user_properties");
  140. for (auto& key : event.userProperties.getAllKeys())
  141. userProperties->setAttribute (key, event.userProperties[key]);
  142. xmlEvent->addChildElement (userProperties);
  143. xml->addChildElement (xmlEvent);
  144. }
  145. xml->writeToFile (savedEventsFile, {});
  146. }
  147. void ProjucerAnalyticsDestination::restoreUnloggedEvents (std::deque<AnalyticsEvent>& restoredEventQueue)
  148. {
  149. XmlDocument savedEvents (savedEventsFile);
  150. std::unique_ptr<XmlElement> xml (savedEvents.getDocumentElement());
  151. if (xml.get() == nullptr || xml->getTagName() != "events")
  152. return;
  153. auto numEvents = xml->getNumChildElements();
  154. for (int iEvent = 0; iEvent < numEvents; ++iEvent)
  155. {
  156. auto* xmlEvent = xml->getChildElement (iEvent);
  157. StringPairArray parameters;
  158. auto* xmlParameters = xmlEvent->getChildByName ("parameters");
  159. auto numParameters = xmlParameters->getNumAttributes();
  160. for (int iParam = 0; iParam < numParameters; ++iParam)
  161. parameters.set (xmlParameters->getAttributeName (iParam),
  162. xmlParameters->getAttributeValue (iParam));
  163. StringPairArray userProperties;
  164. auto* xmlUserProperties = xmlEvent->getChildByName ("user_properties");
  165. auto numUserProperties = xmlUserProperties->getNumAttributes();
  166. for (int iProp = 0; iProp < numUserProperties; ++iProp)
  167. userProperties.set (xmlUserProperties->getAttributeName (iProp),
  168. xmlUserProperties->getAttributeValue (iProp));
  169. restoredEventQueue.push_back ({
  170. xmlEvent->getStringAttribute ("name"),
  171. xmlEvent->getIntAttribute ("type"),
  172. static_cast<uint32> (xmlEvent->getIntAttribute ("timestamp")),
  173. parameters,
  174. xmlEvent->getStringAttribute ("user_id"),
  175. userProperties
  176. });
  177. }
  178. savedEventsFile.deleteFile();
  179. }