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.

255 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. 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. auto xml = parseXMLIfTagMatches (savedEventsFile, "events");
  134. if (xml == nullptr)
  135. xml = std::make_unique<XmlElement> ("events");
  136. for (auto& event : eventsToSave)
  137. {
  138. auto* xmlEvent = new XmlElement ("google_analytics_event");
  139. xmlEvent->setAttribute ("name", event.name);
  140. xmlEvent->setAttribute ("type", event.eventType);
  141. xmlEvent->setAttribute ("timestamp", (int) event.timestamp);
  142. xmlEvent->setAttribute ("user_id", event.userID);
  143. auto* parameters = new XmlElement ("parameters");
  144. for (auto& key : event.parameters.getAllKeys())
  145. parameters->setAttribute (key, event.parameters[key]);
  146. xmlEvent->addChildElement (parameters);
  147. auto* userProperties = new XmlElement ("user_properties");
  148. for (auto& key : event.userProperties.getAllKeys())
  149. userProperties->setAttribute (key, event.userProperties[key]);
  150. xmlEvent->addChildElement (userProperties);
  151. xml->addChildElement (xmlEvent);
  152. }
  153. xml->writeTo (savedEventsFile, {});
  154. }
  155. void ProjucerAnalyticsDestination::restoreUnloggedEvents (std::deque<AnalyticsEvent>& restoredEventQueue)
  156. {
  157. auto xml = parseXMLIfTagMatches (savedEventsFile, "events");
  158. if (xml == nullptr)
  159. return;
  160. auto numEvents = xml->getNumChildElements();
  161. for (int iEvent = 0; iEvent < numEvents; ++iEvent)
  162. {
  163. auto* xmlEvent = xml->getChildElement (iEvent);
  164. StringPairArray parameters;
  165. auto* xmlParameters = xmlEvent->getChildByName ("parameters");
  166. auto numParameters = xmlParameters->getNumAttributes();
  167. for (int iParam = 0; iParam < numParameters; ++iParam)
  168. parameters.set (xmlParameters->getAttributeName (iParam),
  169. xmlParameters->getAttributeValue (iParam));
  170. StringPairArray userProperties;
  171. auto* xmlUserProperties = xmlEvent->getChildByName ("user_properties");
  172. auto numUserProperties = xmlUserProperties->getNumAttributes();
  173. for (int iProp = 0; iProp < numUserProperties; ++iProp)
  174. userProperties.set (xmlUserProperties->getAttributeName (iProp),
  175. xmlUserProperties->getAttributeValue (iProp));
  176. restoredEventQueue.push_back ({
  177. xmlEvent->getStringAttribute ("name"),
  178. xmlEvent->getIntAttribute ("type"),
  179. static_cast<uint32> (xmlEvent->getIntAttribute ("timestamp")),
  180. parameters,
  181. xmlEvent->getStringAttribute ("user_id"),
  182. userProperties
  183. });
  184. }
  185. savedEventsFile.deleteFile();
  186. }