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.

247 lines
7.3KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE 6 technical preview.
  4. Copyright (c) 2017 - ROLI Ltd.
  5. You may use this code under the terms of the GPL v3
  6. (see www.gnu.org/licenses).
  7. For this technical preview, this file is not subject to commercial licensing.
  8. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  9. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  10. DISCLAIMED.
  11. ==============================================================================
  12. */
  13. #include "jucer_ProjucerAnalytics.h"
  14. //==============================================================================
  15. ProjucerAnalyticsDestination::ProjucerAnalyticsDestination()
  16. : ThreadedAnalyticsDestination ("ProjucerAnalyticsThread")
  17. {
  18. {
  19. MemoryOutputStream mo;
  20. if (Base64::convertFromBase64 (mo, BinaryData::nothingtoseehere_txt))
  21. apiKey = mo.toString();
  22. }
  23. auto dataDir = File::getSpecialLocation (File::userApplicationDataDirectory)
  24. #if JUCE_MAC
  25. .getChildFile ("Application Support")
  26. #endif
  27. .getChildFile ("Projucer")
  28. .getChildFile ("Analytics");
  29. if (! dataDir.exists())
  30. dataDir.createDirectory();
  31. savedEventsFile = dataDir.getChildFile ("analytics_events.xml");
  32. startAnalyticsThread (initialPeriodMs);
  33. }
  34. ProjucerAnalyticsDestination::~ProjucerAnalyticsDestination()
  35. {
  36. Thread::sleep (initialPeriodMs);
  37. stopAnalyticsThread (1000);
  38. }
  39. //==============================================================================
  40. static void setData (const AnalyticsDestination::AnalyticsEvent& event, StringPairArray& data)
  41. {
  42. data.set ("ea", event.name);
  43. if (event.parameters.getAllKeys().contains ("label"))
  44. data.set ("el", event.parameters.getValue ("label", {}));
  45. data.addArray (event.userProperties);
  46. }
  47. bool ProjucerAnalyticsDestination::logBatchedEvents (const Array<AnalyticsEvent>& events)
  48. {
  49. String appData ("v=1&aip=1&tid=" + apiKey);
  50. StringArray postData;
  51. for (auto& event : events)
  52. {
  53. StringPairArray data;
  54. data.set ("t", "event");
  55. data.set ("cid", event.userID);
  56. switch (event.eventType)
  57. {
  58. case ProjucerAnalyticsEvent::appEvent:
  59. {
  60. data.set ("ec", "App");
  61. setData (event, data);
  62. break;
  63. }
  64. case ProjucerAnalyticsEvent::projectEvent:
  65. {
  66. data.set ("ec", "Project");
  67. setData (event, data);
  68. break;
  69. }
  70. case ProjucerAnalyticsEvent::userEvent:
  71. {
  72. data.set ("ec", "User");
  73. setData (event, data);
  74. break;
  75. }
  76. case ProjucerAnalyticsEvent::exampleEvent:
  77. {
  78. data.set ("ec", "Example");
  79. setData (event, data);
  80. break;
  81. }
  82. case ProjucerAnalyticsEvent::startPageEvent:
  83. {
  84. data.set ("ec", "Start Page");
  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. auto xml = parseXMLIfTagMatches (savedEventsFile, "events");
  128. if (xml == nullptr)
  129. xml = std::make_unique<XmlElement> ("events");
  130. for (auto& event : eventsToSave)
  131. {
  132. auto* xmlEvent = new XmlElement ("google_analytics_event");
  133. xmlEvent->setAttribute ("name", event.name);
  134. xmlEvent->setAttribute ("type", event.eventType);
  135. xmlEvent->setAttribute ("timestamp", (int) event.timestamp);
  136. xmlEvent->setAttribute ("user_id", event.userID);
  137. auto* parameters = new XmlElement ("parameters");
  138. for (auto& key : event.parameters.getAllKeys())
  139. parameters->setAttribute (key, event.parameters[key]);
  140. xmlEvent->addChildElement (parameters);
  141. auto* userProperties = new XmlElement ("user_properties");
  142. for (auto& key : event.userProperties.getAllKeys())
  143. userProperties->setAttribute (key, event.userProperties[key]);
  144. xmlEvent->addChildElement (userProperties);
  145. xml->addChildElement (xmlEvent);
  146. }
  147. xml->writeTo (savedEventsFile, {});
  148. }
  149. void ProjucerAnalyticsDestination::restoreUnloggedEvents (std::deque<AnalyticsEvent>& restoredEventQueue)
  150. {
  151. auto xml = parseXMLIfTagMatches (savedEventsFile, "events");
  152. if (xml == nullptr)
  153. return;
  154. auto numEvents = xml->getNumChildElements();
  155. for (int iEvent = 0; iEvent < numEvents; ++iEvent)
  156. {
  157. auto* xmlEvent = xml->getChildElement (iEvent);
  158. StringPairArray parameters;
  159. auto* xmlParameters = xmlEvent->getChildByName ("parameters");
  160. auto numParameters = xmlParameters->getNumAttributes();
  161. for (int iParam = 0; iParam < numParameters; ++iParam)
  162. parameters.set (xmlParameters->getAttributeName (iParam),
  163. xmlParameters->getAttributeValue (iParam));
  164. StringPairArray userProperties;
  165. auto* xmlUserProperties = xmlEvent->getChildByName ("user_properties");
  166. auto numUserProperties = xmlUserProperties->getNumAttributes();
  167. for (int iProp = 0; iProp < numUserProperties; ++iProp)
  168. userProperties.set (xmlUserProperties->getAttributeName (iProp),
  169. xmlUserProperties->getAttributeValue (iProp));
  170. restoredEventQueue.push_back ({
  171. xmlEvent->getStringAttribute ("name"),
  172. xmlEvent->getIntAttribute ("type"),
  173. static_cast<uint32> (xmlEvent->getIntAttribute ("timestamp")),
  174. parameters,
  175. xmlEvent->getStringAttribute ("user_id"),
  176. userProperties
  177. });
  178. }
  179. savedEventsFile.deleteFile();
  180. }