| 
							- /*
 -   ==============================================================================
 - 
 -    This file is part of the JUCE examples.
 -    Copyright (c) 2020 - Raw Material Software Limited
 - 
 -    The code included in this file is provided under the terms of the ISC license
 -    http://www.isc.org/downloads/software-support-policy/isc-license. Permission
 -    To use, copy, modify, and/or distribute this software for any purpose with or
 -    without fee is hereby granted provided that the above copyright notice and
 -    this permission notice appear in all copies.
 - 
 -    THE SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES,
 -    WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR
 -    PURPOSE, ARE DISCLAIMED.
 - 
 -   ==============================================================================
 - */
 - 
 - /*******************************************************************************
 -  The block below describes the properties of this PIP. A PIP is a short snippet
 -  of code that can be read by the Projucer and used to generate a JUCE project.
 - 
 -  BEGIN_JUCE_PIP_METADATA
 - 
 -  name:             AnalyticsCollectionDemo
 -  version:          1.0.0
 -  vendor:           JUCE
 -  website:          http://juce.com
 -  description:      Collects analytics data.
 - 
 -  dependencies:     juce_analytics, juce_core, juce_data_structures, juce_events,
 -                    juce_graphics, juce_gui_basics
 -  exporters:        xcode_mac, vs2019, linux_make, xcode_iphone, androidstudio
 - 
 -  moduleFlags:      JUCE_STRICT_REFCOUNTEDPOINTER=1
 - 
 -  type:             Component
 -  mainClass:        AnalyticsCollectionDemo
 - 
 -  useLocalCopy:     1
 - 
 -  END_JUCE_PIP_METADATA
 - 
 - *******************************************************************************/
 - 
 - #pragma once
 - 
 - 
 - //==============================================================================
 - enum DemoAnalyticsEventTypes
 - {
 -     event,
 -     sessionStart,
 -     sessionEnd,
 -     screenView,
 -     exception
 - };
 - 
 - //==============================================================================
 - class GoogleAnalyticsDestination  : public ThreadedAnalyticsDestination
 - {
 - public:
 -     GoogleAnalyticsDestination()
 -         : ThreadedAnalyticsDestination ("GoogleAnalyticsThread")
 -     {
 -         {
 -             // Choose where to save any unsent events.
 - 
 -             auto appDataDir = File::getSpecialLocation (File::userApplicationDataDirectory)
 -                                    .getChildFile (JUCEApplication::getInstance()->getApplicationName());
 - 
 -             if (! appDataDir.exists())
 -                 appDataDir.createDirectory();
 - 
 -             savedEventsFile = appDataDir.getChildFile ("analytics_events.xml");
 -         }
 - 
 -         {
 -             // It's often a good idea to construct any analytics service API keys
 -             // at runtime, so they're not searchable in the binary distribution of
 -             // your application (but we've not done this here). You should replace
 -             // the following key with your own to get this example application
 -             // fully working.
 - 
 -             apiKey = "UA-XXXXXXXXX-1";
 -         }
 - 
 -         startAnalyticsThread (initialPeriodMs);
 -     }
 - 
 -     ~GoogleAnalyticsDestination() override
 -     {
 -         // Here we sleep so that our background thread has a chance to send the
 -         // last lot of batched events. Be careful - if your app takes too long to
 -         // shut down then some operating systems will kill it forcibly!
 -         Thread::sleep (initialPeriodMs);
 - 
 -         stopAnalyticsThread (1000);
 -     }
 - 
 -     int getMaximumBatchSize() override   { return 20; }
 - 
 -     bool logBatchedEvents (const Array<AnalyticsEvent>& events) override
 -     {
 -         // Send events to Google Analytics.
 - 
 -         String appData ("v=1&aip=1&tid=" + apiKey);
 - 
 -         StringArray postData;
 - 
 -         for (auto& event : events)
 -         {
 -             StringPairArray data;
 - 
 -             switch (event.eventType)
 -             {
 -                 case (DemoAnalyticsEventTypes::event):
 -                 {
 -                     data.set ("t", "event");
 - 
 -                     if (event.name == "startup")
 -                     {
 -                         data.set ("ec",  "info");
 -                         data.set ("ea",  "appStarted");
 -                     }
 -                     else if (event.name == "shutdown")
 -                     {
 -                         data.set ("ec",  "info");
 -                         data.set ("ea",  "appStopped");
 -                     }
 -                     else if (event.name == "button_press")
 -                     {
 -                         data.set ("ec",  "button_press");
 -                         data.set ("ea",  event.parameters["id"]);
 -                     }
 -                     else if (event.name == "crash")
 -                     {
 -                         data.set ("ec",  "crash");
 -                         data.set ("ea",  "crash");
 -                     }
 -                     else
 -                     {
 -                         jassertfalse;
 -                         continue;
 -                     }
 - 
 -                     break;
 -                 }
 - 
 -                 default:
 -                 {
 -                     // Unknown event type! In this demo app we're just using a
 -                     // single event type, but in a real app you probably want to
 -                     // handle multiple ones.
 -                     jassertfalse;
 -                     break;
 -                 }
 -             }
 - 
 -             data.set ("cid", event.userID);
 - 
 -             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 stopLoggingEvents() override
 -     {
 -         const ScopedLock lock (webStreamCreation);
 - 
 -         shouldExit = true;
 - 
 -         if (webStream.get() != nullptr)
 -             webStream->cancel();
 -     }
 - 
 - private:
 -     void saveUnloggedEvents (const std::deque<AnalyticsEvent>& eventsToSave) override
 -     {
 -         // Save unsent events to disk. Here we use XML as a serialisation format, but
 -         // you can use anything else as long as the restoreUnloggedEvents method can
 -         // restore events from disk. If you're saving very large numbers of events then
 -         // a binary format may be more suitable if it is faster - remember that this
 -         // method is called on app shutdown so it needs to complete quickly!
 - 
 -         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 restoreUnloggedEvents (std::deque<AnalyticsEvent>& restoredEventQueue) override
 -     {
 -         if (auto xml = parseXMLIfTagMatches (savedEventsFile, "events"))
 -         {
 -             auto numEvents = xml->getNumChildElements();
 - 
 -             for (auto iEvent = 0; iEvent < numEvents; ++iEvent)
 -             {
 -                 auto* xmlEvent = xml->getChildElement (iEvent);
 - 
 -                 StringPairArray parameters;
 -                 auto* xmlParameters = xmlEvent->getChildByName ("parameters");
 -                 auto numParameters = xmlParameters->getNumAttributes();
 - 
 -                 for (auto 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 (auto 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();
 -         }
 -     }
 - 
 -     const int initialPeriodMs = 1000;
 -     int periodMs = initialPeriodMs;
 - 
 -     CriticalSection webStreamCreation;
 -     bool shouldExit = false;
 -     std::unique_ptr<WebInputStream> webStream;
 - 
 -     String apiKey;
 - 
 -     File savedEventsFile;
 - };
 - 
 - //==============================================================================
 - class AnalyticsCollectionDemo   : public Component
 - {
 - public:
 -     //==============================================================================
 -     AnalyticsCollectionDemo()
 -     {
 -         // Add an analytics identifier for the user. Make sure you don't accidentally
 -         // collect identifiable information if you haven't asked for permission!
 -         Analytics::getInstance()->setUserId ("AnonUser1234");
 - 
 -         // Add any other constant user information.
 -         StringPairArray userData;
 -         userData.set ("group", "beta");
 -         Analytics::getInstance()->setUserProperties (userData);
 - 
 -         // Add any analytics destinations we want to use to the Analytics singleton.
 -         Analytics::getInstance()->addDestination (new GoogleAnalyticsDestination());
 - 
 -         // The event type here should probably be DemoAnalyticsEventTypes::sessionStart
 -         // in a more advanced app.
 -         Analytics::getInstance()->logEvent ("startup", {}, DemoAnalyticsEventTypes::event);
 - 
 -         crashButton.onClick = [this] { sendCrash(); };
 - 
 -         addAndMakeVisible (eventButton);
 -         addAndMakeVisible (crashButton);
 - 
 -         setSize (300, 200);
 - 
 -         StringPairArray logButtonPressParameters;
 -         logButtonPressParameters.set ("id", "a");
 -         logEventButtonPress.reset (new ButtonTracker (eventButton, "button_press", logButtonPressParameters));
 -     }
 - 
 -     ~AnalyticsCollectionDemo() override
 -     {
 -         // The event type here should probably be DemoAnalyticsEventTypes::sessionEnd
 -         // in a more advanced app.
 -         Analytics::getInstance()->logEvent ("shutdown", {}, DemoAnalyticsEventTypes::event);
 -     }
 - 
 -     void paint (Graphics& g) override
 -     {
 -         g.fillAll (getLookAndFeel().findColour (ResizableWindow::backgroundColourId));
 -     }
 - 
 -     void resized() override
 -     {
 -         eventButton.centreWithSize (100, 40);
 -         eventButton.setBounds (eventButton.getBounds().translated (0, 25));
 -         crashButton.setBounds (eventButton.getBounds().translated (0, -50));
 -     }
 - 
 - private:
 -     //==============================================================================
 -     void sendCrash()
 -     {
 -         // In a more advanced application you would probably use a different event
 -         // type here.
 -         Analytics::getInstance()->logEvent ("crash", {}, DemoAnalyticsEventTypes::event);
 -         Analytics::getInstance()->getDestinations().clear();
 -         JUCEApplication::getInstance()->shutdown();
 -     }
 - 
 -     TextButton eventButton { "Press me!" }, crashButton { "Simulate crash!" };
 -     std::unique_ptr<ButtonTracker> logEventButtonPress;
 - 
 -     JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AnalyticsCollectionDemo)
 - };
 
 
  |