|
- /*
- ==============================================================================
-
- This file is part of the JUCE library.
- Copyright (c) 2017 - ROLI Ltd.
-
- JUCE is an open source library subject to commercial or open-source
- licensing.
-
- By using JUCE, you agree to the terms of both the JUCE 5 End-User License
- Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
- 27th April 2017).
-
- End User License Agreement: www.juce.com/juce-5-licence
- Privacy Policy: www.juce.com/juce-5-privacy-policy
-
- Or: You may also use this code under the terms of the GPL v3 (see
- www.gnu.org/licenses).
-
- JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
- EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
- DISCLAIMED.
-
- ==============================================================================
- */
-
- #include "jucer_ProjucerAnalytics.h"
-
- //==============================================================================
- ProjucerAnalyticsDestination::ProjucerAnalyticsDestination()
- : ThreadedAnalyticsDestination ("ProjucerAnalyticsThread")
- {
- {
- MemoryOutputStream mo;
- if (Base64::convertFromBase64 (mo, BinaryData::nothingtoseehere_txt))
- apiKey = mo.toString();
- }
-
- auto dataDir = File::getSpecialLocation (File::userApplicationDataDirectory)
- #if JUCE_MAC
- .getChildFile ("Application Support")
- #endif
- .getChildFile ("Projucer")
- .getChildFile ("Analytics");
-
- if (! dataDir.exists())
- dataDir.createDirectory();
-
- savedEventsFile = dataDir.getChildFile ("analytics_events.xml");
-
- startAnalyticsThread (initialPeriodMs);
- }
-
- ProjucerAnalyticsDestination::~ProjucerAnalyticsDestination()
- {
- Thread::sleep (initialPeriodMs);
-
- stopAnalyticsThread (1000);
- }
-
- //==============================================================================
- static void setData (const AnalyticsDestination::AnalyticsEvent& event, StringPairArray& data)
- {
- data.set ("ea", event.name);
-
- if (event.parameters.getAllKeys().contains ("label"))
- data.set ("el", event.parameters.getValue ("label", {}));
-
- data.addArray (event.userProperties);
- }
-
- bool ProjucerAnalyticsDestination::logBatchedEvents (const Array<AnalyticsEvent>& events)
- {
- String appData ("v=1&aip=1&tid=" + apiKey);
-
- StringArray postData;
-
- for (auto& event : events)
- {
- StringPairArray data;
-
- data.set ("t", "event");
- data.set ("cid", event.userID);
-
- switch (event.eventType)
- {
- case ProjucerAnalyticsEvent::appEvent:
- {
- data.set ("ec", "App");
- setData (event, data);
-
- break;
- }
-
- case ProjucerAnalyticsEvent::projectEvent:
- {
- data.set ("ec", "Project");
- setData (event, data);
-
- break;
- }
-
- case ProjucerAnalyticsEvent::userEvent:
- {
- data.set ("ec", "User");
- setData (event, data);
-
- break;
- }
-
- case ProjucerAnalyticsEvent::exampleEvent:
- {
- data.set ("ec", "Example");
- setData (event, data);
-
- break;
- }
-
- case ProjucerAnalyticsEvent::startPageEvent:
- {
- data.set ("ec", "Start Page");
- setData (event, data);
-
- break;
- }
-
- default:
- {
- // unknown event type!
- jassertfalse;
- break;
- }
- }
-
- StringArray eventData;
-
- for (auto& key : data.getAllKeys())
- eventData.add (key + "=" + URL::addEscapeChars (data[key], true));
-
- postData.add (appData + "&" + eventData.joinIntoString ("&"));
- }
-
- auto url = URL ("https://www.google-analytics.com/batch")
- .withPOSTData (postData.joinIntoString ("\n"));
-
- {
- const ScopedLock lock (webStreamCreation);
-
- if (shouldExit)
- return false;
-
- webStream.reset (new WebInputStream (url, true));
- }
-
- auto success = webStream->connect (nullptr);
-
- // Do an exponential backoff if we failed to connect.
- if (success)
- periodMs = initialPeriodMs;
- else
- periodMs *= 2;
-
- setBatchPeriod (periodMs);
-
- return success;
- }
-
- void ProjucerAnalyticsDestination::stopLoggingEvents()
- {
- const ScopedLock lock (webStreamCreation);
-
- shouldExit = true;
-
- if (webStream.get() != nullptr)
- webStream->cancel();
- }
-
- //==============================================================================
- void ProjucerAnalyticsDestination::saveUnloggedEvents (const std::deque<AnalyticsEvent>& eventsToSave)
- {
- auto xml = parseXMLIfTagMatches (savedEventsFile, "events");
-
- if (xml == nullptr)
- xml = std::make_unique<XmlElement> ("events");
-
- for (auto& event : eventsToSave)
- {
- auto* xmlEvent = new XmlElement ("google_analytics_event");
- xmlEvent->setAttribute ("name", event.name);
- xmlEvent->setAttribute ("type", event.eventType);
- xmlEvent->setAttribute ("timestamp", (int) event.timestamp);
- xmlEvent->setAttribute ("user_id", event.userID);
-
- auto* parameters = new XmlElement ("parameters");
-
- for (auto& key : event.parameters.getAllKeys())
- parameters->setAttribute (key, event.parameters[key]);
-
- xmlEvent->addChildElement (parameters);
-
- auto* userProperties = new XmlElement ("user_properties");
-
- for (auto& key : event.userProperties.getAllKeys())
- userProperties->setAttribute (key, event.userProperties[key]);
-
- xmlEvent->addChildElement (userProperties);
-
- xml->addChildElement (xmlEvent);
- }
-
- xml->writeTo (savedEventsFile, {});
- }
-
- void ProjucerAnalyticsDestination::restoreUnloggedEvents (std::deque<AnalyticsEvent>& restoredEventQueue)
- {
- auto xml = parseXMLIfTagMatches (savedEventsFile, "events");
-
- if (xml == nullptr)
- return;
-
- auto numEvents = xml->getNumChildElements();
-
- for (int iEvent = 0; iEvent < numEvents; ++iEvent)
- {
- auto* xmlEvent = xml->getChildElement (iEvent);
-
- StringPairArray parameters;
- auto* xmlParameters = xmlEvent->getChildByName ("parameters");
- auto numParameters = xmlParameters->getNumAttributes();
-
- for (int iParam = 0; iParam < numParameters; ++iParam)
- parameters.set (xmlParameters->getAttributeName (iParam),
- xmlParameters->getAttributeValue (iParam));
-
- StringPairArray userProperties;
- auto* xmlUserProperties = xmlEvent->getChildByName ("user_properties");
- auto numUserProperties = xmlUserProperties->getNumAttributes();
-
- for (int iProp = 0; iProp < numUserProperties; ++iProp)
- userProperties.set (xmlUserProperties->getAttributeName (iProp),
- xmlUserProperties->getAttributeValue (iProp));
-
- restoredEventQueue.push_back ({
- xmlEvent->getStringAttribute ("name"),
- xmlEvent->getIntAttribute ("type"),
- static_cast<uint32> (xmlEvent->getIntAttribute ("timestamp")),
- parameters,
- xmlEvent->getStringAttribute ("user_id"),
- userProperties
- });
- }
-
- savedEventsFile.deleteFile();
- }
|