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.

249 lines
7.6KB

  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. #if JUCE_MAC
  31. .getChildFile ("Application Support")
  32. #endif
  33. .getChildFile ("Projucer")
  34. .getChildFile ("Analytics");
  35. if (! dataDir.exists())
  36. dataDir.createDirectory();
  37. savedEventsFile = dataDir.getChildFile ("analytics_events.xml");
  38. startAnalyticsThread (initialPeriodMs);
  39. }
  40. ProjucerAnalyticsDestination::~ProjucerAnalyticsDestination()
  41. {
  42. Thread::sleep (initialPeriodMs);
  43. stopAnalyticsThread (1000);
  44. }
  45. //==============================================================================
  46. static void setData (const AnalyticsDestination::AnalyticsEvent& event, StringPairArray& data)
  47. {
  48. data.set ("ea", event.name);
  49. if (event.parameters.getAllKeys().contains ("label"))
  50. data.set ("el", event.parameters.getValue ("label", {}));
  51. data.addArray (event.userProperties);
  52. }
  53. bool ProjucerAnalyticsDestination::logBatchedEvents (const Array<AnalyticsEvent>& events)
  54. {
  55. String appData ("v=1&aip=1&tid=" + apiKey);
  56. StringArray postData;
  57. for (auto& event : events)
  58. {
  59. StringPairArray data;
  60. data.set ("t", "event");
  61. data.set ("cid", event.userID);
  62. switch (event.eventType)
  63. {
  64. case ProjucerAnalyticsEvent::appEvent:
  65. {
  66. data.set ("ec", "App");
  67. setData (event, data);
  68. break;
  69. }
  70. case ProjucerAnalyticsEvent::projectEvent:
  71. {
  72. data.set ("ec", "Project");
  73. setData (event, data);
  74. break;
  75. }
  76. case ProjucerAnalyticsEvent::userEvent:
  77. {
  78. data.set ("ec", "User");
  79. setData (event, data);
  80. break;
  81. }
  82. case ProjucerAnalyticsEvent::exampleEvent:
  83. {
  84. data.set ("ec", "Example");
  85. setData (event, data);
  86. break;
  87. }
  88. default:
  89. {
  90. // unknown event type!
  91. jassertfalse;
  92. break;
  93. }
  94. }
  95. StringArray eventData;
  96. for (auto& key : data.getAllKeys())
  97. eventData.add (key + "=" + URL::addEscapeChars (data[key], true));
  98. postData.add (appData + "&" + eventData.joinIntoString ("&"));
  99. }
  100. auto url = URL ("https://www.google-analytics.com/batch")
  101. .withPOSTData (postData.joinIntoString ("\n"));
  102. {
  103. const ScopedLock lock (webStreamCreation);
  104. if (shouldExit)
  105. return false;
  106. webStream.reset (new WebInputStream (url, true));
  107. }
  108. auto success = webStream->connect (nullptr);
  109. // Do an exponential backoff if we failed to connect.
  110. if (success)
  111. periodMs = initialPeriodMs;
  112. else
  113. periodMs *= 2;
  114. setBatchPeriod (periodMs);
  115. return success;
  116. }
  117. void ProjucerAnalyticsDestination::stopLoggingEvents()
  118. {
  119. const ScopedLock lock (webStreamCreation);
  120. shouldExit = true;
  121. if (webStream.get() != nullptr)
  122. webStream->cancel();
  123. }
  124. //==============================================================================
  125. void ProjucerAnalyticsDestination::saveUnloggedEvents (const std::deque<AnalyticsEvent>& eventsToSave)
  126. {
  127. XmlDocument previouslySavedEvents (savedEventsFile);
  128. std::unique_ptr<XmlElement> xml (previouslySavedEvents.getDocumentElement());
  129. if (xml.get() == nullptr || xml->getTagName() != "events")
  130. xml.reset (new XmlElement ("events"));
  131. for (auto& event : eventsToSave)
  132. {
  133. auto* xmlEvent = new XmlElement ("google_analytics_event");
  134. xmlEvent->setAttribute ("name", event.name);
  135. xmlEvent->setAttribute ("type", event.eventType);
  136. xmlEvent->setAttribute ("timestamp", (int) event.timestamp);
  137. xmlEvent->setAttribute ("user_id", event.userID);
  138. auto* parameters = new XmlElement ("parameters");
  139. for (auto& key : event.parameters.getAllKeys())
  140. parameters->setAttribute (key, event.parameters[key]);
  141. xmlEvent->addChildElement (parameters);
  142. auto* userProperties = new XmlElement ("user_properties");
  143. for (auto& key : event.userProperties.getAllKeys())
  144. userProperties->setAttribute (key, event.userProperties[key]);
  145. xmlEvent->addChildElement (userProperties);
  146. xml->addChildElement (xmlEvent);
  147. }
  148. xml->writeToFile (savedEventsFile, {});
  149. }
  150. void ProjucerAnalyticsDestination::restoreUnloggedEvents (std::deque<AnalyticsEvent>& restoredEventQueue)
  151. {
  152. XmlDocument savedEvents (savedEventsFile);
  153. std::unique_ptr<XmlElement> xml (savedEvents.getDocumentElement());
  154. if (xml.get() == nullptr || xml->getTagName() != "events")
  155. return;
  156. auto numEvents = xml->getNumChildElements();
  157. for (int iEvent = 0; iEvent < numEvents; ++iEvent)
  158. {
  159. auto* xmlEvent = xml->getChildElement (iEvent);
  160. StringPairArray parameters;
  161. auto* xmlParameters = xmlEvent->getChildByName ("parameters");
  162. auto numParameters = xmlParameters->getNumAttributes();
  163. for (int iParam = 0; iParam < numParameters; ++iParam)
  164. parameters.set (xmlParameters->getAttributeName (iParam),
  165. xmlParameters->getAttributeValue (iParam));
  166. StringPairArray userProperties;
  167. auto* xmlUserProperties = xmlEvent->getChildByName ("user_properties");
  168. auto numUserProperties = xmlUserProperties->getNumAttributes();
  169. for (int iProp = 0; iProp < numUserProperties; ++iProp)
  170. userProperties.set (xmlUserProperties->getAttributeName (iProp),
  171. xmlUserProperties->getAttributeValue (iProp));
  172. restoredEventQueue.push_back ({
  173. xmlEvent->getStringAttribute ("name"),
  174. xmlEvent->getIntAttribute ("type"),
  175. static_cast<uint32> (xmlEvent->getIntAttribute ("timestamp")),
  176. parameters,
  177. xmlEvent->getStringAttribute ("user_id"),
  178. userProperties
  179. });
  180. }
  181. savedEventsFile.deleteFile();
  182. }