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.

257 lines
7.8KB

  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. case ProjucerAnalyticsEvent::startPageEvent:
  89. {
  90. data.set ("ec", "Start Page");
  91. setData (event, data);
  92. break;
  93. }
  94. default:
  95. {
  96. // unknown event type!
  97. jassertfalse;
  98. break;
  99. }
  100. }
  101. StringArray eventData;
  102. for (auto& key : data.getAllKeys())
  103. eventData.add (key + "=" + URL::addEscapeChars (data[key], true));
  104. postData.add (appData + "&" + eventData.joinIntoString ("&"));
  105. }
  106. auto url = URL ("https://www.google-analytics.com/batch")
  107. .withPOSTData (postData.joinIntoString ("\n"));
  108. {
  109. const ScopedLock lock (webStreamCreation);
  110. if (shouldExit)
  111. return false;
  112. webStream.reset (new WebInputStream (url, true));
  113. }
  114. auto success = webStream->connect (nullptr);
  115. // Do an exponential backoff if we failed to connect.
  116. if (success)
  117. periodMs = initialPeriodMs;
  118. else
  119. periodMs *= 2;
  120. setBatchPeriod (periodMs);
  121. return success;
  122. }
  123. void ProjucerAnalyticsDestination::stopLoggingEvents()
  124. {
  125. const ScopedLock lock (webStreamCreation);
  126. shouldExit = true;
  127. if (webStream.get() != nullptr)
  128. webStream->cancel();
  129. }
  130. //==============================================================================
  131. void ProjucerAnalyticsDestination::saveUnloggedEvents (const std::deque<AnalyticsEvent>& eventsToSave)
  132. {
  133. XmlDocument previouslySavedEvents (savedEventsFile);
  134. std::unique_ptr<XmlElement> xml (previouslySavedEvents.getDocumentElement());
  135. if (xml.get() == nullptr || xml->getTagName() != "events")
  136. xml.reset (new XmlElement ("events"));
  137. for (auto& event : eventsToSave)
  138. {
  139. auto* xmlEvent = new XmlElement ("google_analytics_event");
  140. xmlEvent->setAttribute ("name", event.name);
  141. xmlEvent->setAttribute ("type", event.eventType);
  142. xmlEvent->setAttribute ("timestamp", (int) event.timestamp);
  143. xmlEvent->setAttribute ("user_id", event.userID);
  144. auto* parameters = new XmlElement ("parameters");
  145. for (auto& key : event.parameters.getAllKeys())
  146. parameters->setAttribute (key, event.parameters[key]);
  147. xmlEvent->addChildElement (parameters);
  148. auto* userProperties = new XmlElement ("user_properties");
  149. for (auto& key : event.userProperties.getAllKeys())
  150. userProperties->setAttribute (key, event.userProperties[key]);
  151. xmlEvent->addChildElement (userProperties);
  152. xml->addChildElement (xmlEvent);
  153. }
  154. xml->writeToFile (savedEventsFile, {});
  155. }
  156. void ProjucerAnalyticsDestination::restoreUnloggedEvents (std::deque<AnalyticsEvent>& restoredEventQueue)
  157. {
  158. XmlDocument savedEvents (savedEventsFile);
  159. std::unique_ptr<XmlElement> xml (savedEvents.getDocumentElement());
  160. if (xml.get() == nullptr || xml->getTagName() != "events")
  161. return;
  162. auto numEvents = xml->getNumChildElements();
  163. for (int iEvent = 0; iEvent < numEvents; ++iEvent)
  164. {
  165. auto* xmlEvent = xml->getChildElement (iEvent);
  166. StringPairArray parameters;
  167. auto* xmlParameters = xmlEvent->getChildByName ("parameters");
  168. auto numParameters = xmlParameters->getNumAttributes();
  169. for (int iParam = 0; iParam < numParameters; ++iParam)
  170. parameters.set (xmlParameters->getAttributeName (iParam),
  171. xmlParameters->getAttributeValue (iParam));
  172. StringPairArray userProperties;
  173. auto* xmlUserProperties = xmlEvent->getChildByName ("user_properties");
  174. auto numUserProperties = xmlUserProperties->getNumAttributes();
  175. for (int iProp = 0; iProp < numUserProperties; ++iProp)
  176. userProperties.set (xmlUserProperties->getAttributeName (iProp),
  177. xmlUserProperties->getAttributeValue (iProp));
  178. restoredEventQueue.push_back ({
  179. xmlEvent->getStringAttribute ("name"),
  180. xmlEvent->getIntAttribute ("type"),
  181. static_cast<uint32> (xmlEvent->getIntAttribute ("timestamp")),
  182. parameters,
  183. xmlEvent->getStringAttribute ("user_id"),
  184. userProperties
  185. });
  186. }
  187. savedEventsFile.deleteFile();
  188. }