diff --git a/3party/Alohalytics/alohalytics.pro b/3party/Alohalytics/alohalytics.pro index 7a75913eef..5efd666c25 100644 --- a/3party/Alohalytics/alohalytics.pro +++ b/3party/Alohalytics/alohalytics.pro @@ -3,16 +3,19 @@ CONFIG *= console c++11 CONFIG -= app_bundle qt SOURCES += examples/cpp/example.cc \ - src/alohalytics.cc \ + src/cpp/alohalytics.cc \ HEADERS += src/alohalytics.h \ + src/event_base.h \ src/http_client.h \ src/gzip_wrapper.h \ + src/logger.h \ + src/location.h \ QMAKE_LFLAGS *= -lz macx-* { - OBJECTIVE_SOURCES += src/http_client_apple.mm + OBJECTIVE_SOURCES += src/apple/http_client_apple.mm QMAKE_OBJECTIVE_CFLAGS *= -fobjc-arc QMAKE_LFLAGS *= -framework Foundation } diff --git a/3party/Alohalytics/examples/android/build.gradle b/3party/Alohalytics/examples/android/build.gradle index 4a7d595809..6077dd1f4c 100644 --- a/3party/Alohalytics/examples/android/build.gradle +++ b/3party/Alohalytics/examples/android/build.gradle @@ -17,7 +17,7 @@ apply plugin: 'com.android.application' dependencies { // This one is needed to get Google Play advertising ID if Google Play Services are available on the device. - compile 'com.google.android.gms:play-services-location:+' + compile 'com.google.android.gms:play-services-base:+' } android { diff --git a/3party/Alohalytics/examples/android/src/main/AndroidManifest.xml b/3party/Alohalytics/examples/android/src/main/AndroidManifest.xml index 697aec4124..385a880138 100644 --- a/3party/Alohalytics/examples/android/src/main/AndroidManifest.xml +++ b/3party/Alohalytics/examples/android/src/main/AndroidManifest.xml @@ -2,10 +2,14 @@ - - - + + + + + + + - + REGISTERER_##name( \ - std::ref(FLAGS_##name), #name, #type, default_value, description) +#define DEFINE_flag(type, name, default_value, description) \ + type FLAGS_##name = default_value; \ + ::dflags::FlagRegisterer REGISTERER_##name(std::ref(FLAGS_##name), #name, #type, default_value, \ + description) #define DEFINE_int8(name, default_value, description) DEFINE_flag(int8_t, name, default_value, description) #define DEFINE_uint8(name, default_value, description) DEFINE_flag(uint8_t, name, default_value, description) diff --git a/3party/Alohalytics/examples/cpp/example.cc b/3party/Alohalytics/examples/cpp/example.cc index 49e96fe10f..ea51b612a6 100644 --- a/3party/Alohalytics/examples/cpp/example.cc +++ b/3party/Alohalytics/examples/cpp/example.cc @@ -1,7 +1,7 @@ /******************************************************************************* The MIT License (MIT) - Copyright (c) 2014 Alexander Zolotarev from Minsk, Belarus + Copyright (c) 2015 Alexander Zolotarev from Minsk, Belarus Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -44,6 +44,7 @@ DEFINE_bool(debug, true, "Enables debug mode for statistics engine."); DEFINE_bool(upload, false, "If true, triggers event to forcebly upload all statistics to the server."); DEFINE_double(sleep, 1, "The number of seconds to sleep before terminating."); DEFINE_string(id, "0xDEADBABA", "Unique client id."); +DEFINE_bool(location, true, "Simulates event with a location."); using namespace std; using alohalytics::Stats; @@ -103,6 +104,12 @@ int main(int argc, char** argv) { } } + if (FLAGS_location) { + alohalytics::Location location; + location.SetLatLon(123456789L, -14.1234567, 133.1234567, 3.52); + stats.LogEvent("SimulatedLocationEvent", {{"somekey", "somevalue"}}, location); + } + if (FLAGS_upload) { stats.Upload(); } diff --git a/3party/Alohalytics/src/server/demo.cc b/3party/Alohalytics/examples/server/demo.cc similarity index 56% rename from 3party/Alohalytics/src/server/demo.cc rename to 3party/Alohalytics/examples/server/demo.cc index cb0e1d05fb..b2353116b8 100644 --- a/3party/Alohalytics/src/server/demo.cc +++ b/3party/Alohalytics/examples/server/demo.cc @@ -1,7 +1,7 @@ /******************************************************************************* The MIT License (MIT) - Copyright (c) 2014 Alexander Zolotarev from Minsk, Belarus + Copyright (c) 2015 Alexander Zolotarev from Minsk, Belarus Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -26,43 +26,26 @@ // This define is needed to preserve client's timestamps in events. #define ALOHALYTICS_SERVER -#include "../event_base.h" +#include "../../src/event_base.h" -#include "../Bricks/rtti/dispatcher.h" +#include "../../src/Bricks/rtti/dispatcher.h" #include #include #include +// If you use only virtual functions in your events, you don't need any Processor at all. +// Here we process some events with Processor for example only. struct Processor { - void PrintTime(const AlohalyticsBaseEvent & event) { - const time_t timestamp = static_cast(event.timestamp / 1000); - std::cout << std::put_time(std::localtime(×tamp), "%e-%b-%Y %H:%M:%S"); - } - void operator()(const AlohalyticsBaseEvent & event) { - PrintTime(event); - std::cout << "Unhandled event of type " << typeid(event).name() << std::endl; - } - void operator()(const AlohalyticsIdEvent & event) { - PrintTime(event); - std::cout << " ID: " << event.id << std::endl; - } - void operator()(const AlohalyticsKeyEvent & event) { - PrintTime(event); - std::cout << ' ' << event.key << std::endl; - } - void operator()(const AlohalyticsKeyValueEvent & event) { - PrintTime(event); - std::cout << ' ' << event.key << " = " << event.value << std::endl; - } - void operator()(const AlohalyticsKeyPairsEvent & event) { - PrintTime(event); - std::cout << ' ' << event.key << " [ "; - for (const auto & pair : event.pairs) { - std::cout << pair.first << '=' << pair.second << ' '; - } - std::cout << ']' << std::endl; + void operator()(const AlohalyticsBaseEvent &event) { + std::cout << "Unhandled event of type " << typeid(event).name() << " with timestamp " << event.ToString() + << std::endl; } + void operator()(const AlohalyticsIdEvent &event) { std::cout << event.ToString() << std::endl; } + void operator()(const AlohalyticsKeyEvent &event) { std::cout << event.ToString() << std::endl; } + void operator()(const AlohalyticsKeyValueEvent &event) { std::cout << event.ToString() << std::endl; } + void operator()(const AlohalyticsKeyPairsEvent &event) { std::cout << event.ToString() << std::endl; } + void operator()(const AlohalyticsKeyPairsLocationEvent &event) { std::cout << event.ToString() << std::endl; } }; int main(int, char **) { @@ -72,13 +55,11 @@ int main(int, char **) { while (std::cin.good()) { std::unique_ptr ptr; ar(ptr); - bricks::rtti::RuntimeDispatcher::DispatchCall(*ptr, processor); } - } catch (const cereal::Exception & ex) { + } catch (const cereal::Exception &) { } return 0; } diff --git a/3party/Alohalytics/examples/server/gunzip.cc b/3party/Alohalytics/examples/server/gunzip.cc new file mode 100644 index 0000000000..da91432125 --- /dev/null +++ b/3party/Alohalytics/examples/server/gunzip.cc @@ -0,0 +1,60 @@ +/******************************************************************************* + The MIT License (MIT) + + Copyright (c) 2015 Alexander Zolotarev from Minsk, Belarus + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + *******************************************************************************/ + +// Small demo which ungzips and prints cereal stream from file. + +// This define is needed to preserve client's timestamps in events. +#define ALOHALYTICS_SERVER +#include "../../src/event_base.h" +#include "../../src/gzip_wrapper.h" + +#include "../../src/Bricks/file/file.h" + +#include +#include +#include +#include + +int main(int argc, char** argv) { + if (argc < 2) { + std::cout << "Usage: " << argv[0] << " " << std::endl; + return -1; + } + try { + const std::string ungzipped = alohalytics::Gunzip(bricks::ReadFileAsString(argv[1])); + std::istringstream in_stream(ungzipped); + cereal::BinaryInputArchive in_archive(in_stream); + std::unique_ptr ptr; + // Cereal can't detect the end of the stream without our help. + // If tellg returns -1 we will exit safely. + while (ungzipped.size() > static_cast(in_stream.tellg())) { + in_archive(ptr); + std::cout << ptr->ToString() << std::endl; + } + } catch (const std::exception& ex) { + std::cerr << "Exception: " << ex.what() << std::endl; + return -1; + } + return 0; +} diff --git a/3party/Alohalytics/src/FileStorageQueue/fsq.h b/3party/Alohalytics/src/FileStorageQueue/fsq.h index 7e9d3a0e37..a89bc0b0a7 100644 --- a/3party/Alohalytics/src/FileStorageQueue/fsq.h +++ b/3party/Alohalytics/src/FileStorageQueue/fsq.h @@ -413,7 +413,9 @@ class FSQ final : public CONFIG::T_FILE_NAMING_STRATEGY, if (next_file) { // const FileProcessingResult result = processor_.OnFileReady(*next_file.get(), // time_manager_.Now()); - const bool successfully_processed = processor_.OnFileReady(next_file->full_path_name, next_file->size); + // AlexZ: we can't trust next_file->size here. Debugging shows that it can be greater than the real file size. + // TODO: refactor FSQ or capture a bug. + const bool successfully_processed = processor_.OnFileReady(next_file->full_path_name); // Important to clear force_processing_, in a locked way. { std::unique_lock lock(status_mutex_); diff --git a/3party/Alohalytics/src/alohalytics.h b/3party/Alohalytics/src/alohalytics.h index 19048f3f8b..b1eea1c63b 100644 --- a/3party/Alohalytics/src/alohalytics.h +++ b/3party/Alohalytics/src/alohalytics.h @@ -1,7 +1,7 @@ /******************************************************************************* The MIT License (MIT) - Copyright (c) 2014 Alexander Zolotarev from Minsk, Belarus + Copyright (c) 2015 Alexander Zolotarev from Minsk, Belarus Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -27,6 +27,7 @@ #include "message_queue.h" #include "FileStorageQueue/fsq.h" +#include "location.h" #include #include @@ -65,7 +66,7 @@ class Stats final { // Called by file storage engine to upload file with collected data. // Should return true if upload has been successful. // TODO(AlexZ): Refactor FSQ to make this method private. - bool OnFileReady(const std::string& full_path_to_file, uint64_t file_size); + bool OnFileReady(const std::string& full_path_to_file); static Stats& Instance(); @@ -82,26 +83,36 @@ class Stats final { Stats& SetClientId(const std::string& unique_client_id); void LogEvent(std::string const& event_name); + void LogEvent(std::string const& event_name, Location const& location); void LogEvent(std::string const& event_name, std::string const& event_value); + void LogEvent(std::string const& event_name, std::string const& event_value, Location const& location); void LogEvent(std::string const& event_name, TStringMap const& value_pairs); + void LogEvent(std::string const& event_name, TStringMap const& value_pairs, Location const& location); // Forcedly tries to upload all stored data to the server. void Upload(); }; -inline void LogEvent(std::string const& event_name) { - Stats::Instance().LogEvent(event_name); +inline void LogEvent(std::string const& event_name) { Stats::Instance().LogEvent(event_name); } +inline void LogEvent(std::string const& event_name, Location const& location) { + Stats::Instance().LogEvent(event_name, location); } inline void LogEvent(std::string const& event_name, std::string const& event_value) { Stats::Instance().LogEvent(event_name, event_value); } +inline void LogEvent(std::string const& event_name, std::string const& event_value, Location const& location) { + Stats::Instance().LogEvent(event_name, event_value, location); +} inline void LogEvent(std::string const& event_name, TStringMap const& value_pairs) { Stats::Instance().LogEvent(event_name, value_pairs); } +inline void LogEvent(std::string const& event_name, TStringMap const& value_pairs, Location const& location) { + Stats::Instance().LogEvent(event_name, value_pairs, location); +} } // namespace alohalytics diff --git a/3party/Alohalytics/src/android/java/org/alohalytics/Statistics.java b/3party/Alohalytics/src/android/java/org/alohalytics/Statistics.java index db64baceb0..afc603e4f7 100644 --- a/3party/Alohalytics/src/android/java/org/alohalytics/Statistics.java +++ b/3party/Alohalytics/src/android/java/org/alohalytics/Statistics.java @@ -1,7 +1,7 @@ /******************************************************************************* The MIT License (MIT) - Copyright (c) 2014 Alexander Zolotarev from Minsk, Belarus + Copyright (c) 2015 Alexander Zolotarev from Minsk, Belarus Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -26,6 +26,8 @@ package org.alohalytics; import android.content.Context; import android.content.SharedPreferences; +import android.location.Location; +import android.location.LocationManager; import android.util.Pair; import java.io.File; @@ -49,15 +51,7 @@ public class Statistics { return sDebugModeEnabled; } - // Use this setup if you are releasing a new application and/or don't bother about already existing installations - // prior to Alohalytics integration. Alohalytics will check new unique installations by internal logic only if use this function. public static void setup(final String serverUrl, final Context context) { - setup(serverUrl, context, true); - } - - // Set firstAppLaunch to false if you definitely know that your app was previously installed - // (before integrating with Alohalytics) to correctly calculate new unique installations. - public static void setup(final String serverUrl, final Context context, boolean firstAppLaunch) { final String storagePath = context.getFilesDir().getAbsolutePath() + "/Alohalytics/"; // Native code expects valid existing writable dir. (new File(storagePath)).mkdirs(); @@ -78,32 +72,69 @@ public class Statistics { ex.printStackTrace(); } final SharedPreferences prefs = context.getSharedPreferences(PREF_FILE, Context.MODE_PRIVATE); + Location lastKnownLocation = null; + if (SystemInfo.hasPermission("android.permission.ACCESS_FINE_LOCATION", context)) { + // Requires ACCESS_FINE_LOCATION permission. + final LocationManager lm = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); + lastKnownLocation = (lm == null) ? null : lm.getLastKnownLocation(LocationManager.PASSIVE_PROVIDER); + } // Is it a real new install? - if (firstAppLaunch && id.second && installTime == updateTime) { + if (id.second && installTime == updateTime) { logEvent("$install", new String[]{"version", versionName, - "secondsBeforeLaunch", String.valueOf((System.currentTimeMillis() - installTime) / 1000)}); + "secondsBeforeLaunch", String.valueOf((System.currentTimeMillis() - installTime) / 1000)}, + lastKnownLocation); // Collect device info once on start. SystemInfo.getDeviceInfoAsync(context); prefs.edit().putLong(PREF_APP_UPDATE_TIME, updateTime).apply(); } else if (updateTime != installTime && updateTime != prefs.getLong(PREF_APP_UPDATE_TIME, 0)) { logEvent("$update", new String[]{"version", versionName, - "userAgeInSeconds", String.valueOf((System.currentTimeMillis() - installTime) / 1000)}); + "secondsBeforeLaunch", String.valueOf((System.currentTimeMillis() - updateTime) / 1000), + "userAgeInSeconds", String.valueOf((System.currentTimeMillis() - installTime) / 1000)}, + lastKnownLocation); // Also collect device info on update. SystemInfo.getDeviceInfoAsync(context); prefs.edit().putLong(PREF_APP_UPDATE_TIME, updateTime).apply(); + } + logEvent("$launch", SystemInfo.hasPermission("android.permission.ACCESS_NETWORK_STATE", context) + ? SystemInfo.getConnectionInfo(context) : null, lastKnownLocation); + } + + public static native void logEvent(String eventName); + + public static native void logEvent(String eventName, String eventValue); + + // eventDictionary is a key,value,key,value array. + public static native void logEvent(String eventName, String[] eventDictionary); + + private static native void logEvent(String eventName, String[] eventDictionary, boolean hasLatLon, + long timestamp, double lat, double lon, float accuracy, + boolean hasAltitude, double altitude, boolean hasBearing, + float bearing, boolean hasSpeed, float speed, byte source); + + public static void logEvent(String eventName, String[] eventDictionary, Location l) { + if (l == null) { + logEvent(eventName, eventDictionary); } else { - logEvent("$launch"); + // See alohalytics::Location::Source in the location.h for enum constants. + byte source = 0; + switch (l.getProvider()) { + case LocationManager.GPS_PROVIDER: + source = 1; + break; + case LocationManager.NETWORK_PROVIDER: + source = 2; + break; + case LocationManager.PASSIVE_PROVIDER: + source = 3; + break; + } + logEvent(eventName, eventDictionary, l.hasAccuracy(), l.getTime(), l.getLatitude(), l.getLongitude(), + l.getAccuracy(), l.hasAltitude(), l.getAltitude(), l.hasBearing(), l.getBearing(), + l.hasSpeed(), l.getSpeed(), source); } } - native static public void logEvent(String eventName); - - native static public void logEvent(String eventName, String eventValue); - - // eventDictionary is a key,value,key,value array. - native static public void logEvent(String eventName, String[] eventDictionary); - - static public void logEvent(String eventName, Map eventDictionary) { + public static void logEvent(String eventName, Map eventDictionary) { // For faster native processing pass array of strings instead of a map. final String[] array = new String[eventDictionary.size() * 2]; int index = 0; diff --git a/3party/Alohalytics/src/android/java/org/alohalytics/SystemInfo.java b/3party/Alohalytics/src/android/java/org/alohalytics/SystemInfo.java index 1c6abf22ec..5ffb0a2617 100644 --- a/3party/Alohalytics/src/android/java/org/alohalytics/SystemInfo.java +++ b/3party/Alohalytics/src/android/java/org/alohalytics/SystemInfo.java @@ -26,7 +26,10 @@ package org.alohalytics; import android.content.ContentResolver; import android.content.Context; +import android.content.pm.PackageManager; import android.content.res.Configuration; +import android.net.ConnectivityManager; +import android.net.NetworkInfo; import android.os.Build; import android.provider.Settings; import android.telephony.TelephonyManager; @@ -113,16 +116,20 @@ public class SystemInfo { handleException(ex); } - try { - // This code works only if the app has READ_PHONE_STATE permission. - final TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); - ids.put("device_id", tm.getDeviceId()); - ids.put("sim_serial_number", tm.getSimSerialNumber()); - } catch (Exception ex) { - handleException(ex); + if (SystemInfo.hasPermission("android.permission.READ_PHONE_STATE", context)) { + try { + // This code works only if the app has READ_PHONE_STATE permission. + final TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE); + ids.put("device_id", tm.getDeviceId()); + ids.put("sim_serial_number", tm.getSimSerialNumber()); + } catch (Exception ex) { + handleException(ex); + } } Statistics.logEvent("$androidIds", ids.mPairs); + // Force statistics uploading as if user immediately uninstalls the app we won't even know about installation. + Statistics.forceUpload(); } private static void collectDeviceDetails(Context context) { @@ -227,4 +234,38 @@ public class SystemInfo { Statistics.logEvent("$androidDeviceInfo", kvs.mPairs); } + // Requires ACCESS_NETWORK_STATE permission. + public static String[] getConnectionInfo(final Context context) { + final ConnectivityManager cm = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE); + if (cm == null) { + return new String[]{"null", "cm"}; + } + final NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); + if (activeNetwork == null) { + return new String[]{"null", "activeNetwork"}; + } + final boolean isConnected = activeNetwork.isConnected(); + final boolean isRoaming = activeNetwork.isRoaming(); + String type = "unknown"; + switch (activeNetwork.getType()) { + case ConnectivityManager.TYPE_BLUETOOTH: type = "bluetooth"; break; + case ConnectivityManager.TYPE_DUMMY: type = "dummy"; break; + case ConnectivityManager.TYPE_ETHERNET: type = "ethernet"; break; + case ConnectivityManager.TYPE_MOBILE: type = "mobile"; break; + case ConnectivityManager.TYPE_MOBILE_DUN: type = "dun"; break; + case ConnectivityManager.TYPE_MOBILE_HIPRI: type = "hipri"; break; + case ConnectivityManager.TYPE_MOBILE_MMS: type = "mms"; break; + case ConnectivityManager.TYPE_MOBILE_SUPL: type = "supl"; break; + case ConnectivityManager.TYPE_VPN: type = "vpn"; break; + case ConnectivityManager.TYPE_WIFI: type = "wifi"; break; + case ConnectivityManager.TYPE_WIMAX: type = "wimax"; break; + } + return new String[]{"connected", isConnected ? "yes" : "no", + "roaming", isRoaming ? "yes" : "no", + "ctype", type}; + } + + public static boolean hasPermission(final String permission, final Context context) { + return PackageManager.PERMISSION_GRANTED == context.checkCallingOrSelfPermission(permission); + } } diff --git a/3party/Alohalytics/src/android/jni/jni_alohalytics.cc b/3party/Alohalytics/src/android/jni/jni_alohalytics.cc index 786b96ff3d..b12735ae53 100644 --- a/3party/Alohalytics/src/android/jni/jni_alohalytics.cc +++ b/3party/Alohalytics/src/android/jni/jni_alohalytics.cc @@ -1,7 +1,7 @@ /******************************************************************************* The MIT License (MIT) -Copyright (c) 2014 Alexander Zolotarev from Minsk, Belarus +Copyright (c) 2015 Alexander Zolotarev from Minsk, Belarus Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -40,6 +40,8 @@ extern JavaVM* GetJVM(); namespace { +static constexpr double kDefaultAndroidVerticalAccuracy = 0.0; + template unique_ptr MakePointerScopeGuard(POINTER* x, DELETER t) { return unique_ptr(x, t); @@ -81,6 +83,28 @@ string ToStdString(JNIEnv* env, jstring str) { return result; } +// keyValuePairs can be null! +TStringMap FillMapHelper(JNIEnv* env, jobjectArray keyValuePairs) { + TStringMap map; + if (keyValuePairs) { + const jsize count = env->GetArrayLength(keyValuePairs); + string key; + for (jsize i = 0; i < count; ++i) { + const jstring jni_string = static_cast(env->GetObjectArrayElement(keyValuePairs, i)); + if ((i + 1) % 2) { + key = ToStdString(env, jni_string); + map[key] = ""; + } else { + map[key] = ToStdString(env, jni_string); + } + if (jni_string) { + env->DeleteLocalRef(jni_string); + } + } + } + return map; +} + } // namespace extern "C" { @@ -96,20 +120,43 @@ JNIEXPORT void JNICALL Java_org_alohalytics_Statistics_logEvent__Ljava_lang_Stri JNIEXPORT void JNICALL Java_org_alohalytics_Statistics_logEvent__Ljava_lang_String_2_3Ljava_lang_String_2( JNIEnv* env, jclass, jstring eventName, jobjectArray keyValuePairs) { - const jsize count = env->GetArrayLength(keyValuePairs); - TStringMap map; - string key; - for (jsize i = 0; i < count; ++i) { - const jstring jni_string = static_cast(env->GetObjectArrayElement(keyValuePairs, i)); - if ((i + 1) % 2) { - key = ToStdString(env, jni_string); - map[key] = ""; - } else { - map[key] = ToStdString(env, jni_string); - } - if (jni_string) env->DeleteLocalRef(jni_string); + LogEvent(ToStdString(env, eventName), FillMapHelper(env, keyValuePairs)); +} + +JNIEXPORT void JNICALL + Java_org_alohalytics_Statistics_logEvent__Ljava_lang_String_2_3Ljava_lang_String_2ZJDDFZDZFZFB( + JNIEnv* env, + jclass, + jstring eventName, + jobjectArray keyValuePairs, + jboolean hasLatLon, + jlong timestamp, + jdouble lat, + jdouble lon, + jfloat accuracy, + jboolean hasAltitude, + jdouble altitude, + jboolean hasBearing, + jfloat bearing, + jboolean hasSpeed, + jfloat speed, + jbyte source) { + alohalytics::Location l; + if (hasLatLon) { + l.SetLatLon(timestamp, lat, lon, accuracy); + l.SetSource((alohalytics::Location::Source)source); } - LogEvent(ToStdString(env, eventName), map); + if (hasAltitude) { + l.SetAltitude(altitude, kDefaultAndroidVerticalAccuracy); + } + if (hasBearing) { + l.SetBearing(bearing); + } + if (hasSpeed) { + l.SetSpeed(speed); + } + + LogEvent(ToStdString(env, eventName), FillMapHelper(env, keyValuePairs), l); } #define CLEAR_AND_RETURN_FALSE_ON_EXCEPTION \ @@ -134,8 +181,7 @@ JNIEXPORT void JNICALL Java_org_alohalytics_Statistics_setupCPP(JNIEnv* env, g_httpTransportClass = static_cast(env->NewGlobalRef(httpTransportClass)); RETURN_ON_EXCEPTION g_httpTransportClass_run = - env->GetStaticMethodID(g_httpTransportClass, - "run", + env->GetStaticMethodID(g_httpTransportClass, "run", "(Lorg/alohalytics/HttpTransport$Params;)Lorg/alohalytics/HttpTransport$Params;"); RETURN_ON_EXCEPTION g_httpParamsClass = env->FindClass("org/alohalytics/HttpTransport$Params"); @@ -203,8 +249,8 @@ bool HTTPClientPlatformWrapper::RunHTTPRequest() { const auto jniPostData = MakePointerScopeGuard(env->NewByteArray(post_body_.size()), deleteLocalRef); CLEAR_AND_RETURN_FALSE_ON_EXCEPTION - env->SetByteArrayRegion( - jniPostData.get(), 0, post_body_.size(), reinterpret_cast(post_body_.data())); + env->SetByteArrayRegion(jniPostData.get(), 0, post_body_.size(), + reinterpret_cast(post_body_.data())); CLEAR_AND_RETURN_FALSE_ON_EXCEPTION env->SetObjectField(httpParamsObject.get(), dataField, jniPostData.get()); @@ -255,8 +301,7 @@ bool HTTPClientPlatformWrapper::RunHTTPRequest() { CLEAR_AND_RETURN_FALSE_ON_EXCEPTION } - const static jfieldID debugModeField = - env->GetFieldID(g_httpParamsClass, "debugMode", "Z"); + const static jfieldID debugModeField = env->GetFieldID(g_httpParamsClass, "debugMode", "Z"); env->SetBooleanField(httpParamsObject.get(), debugModeField, debug_mode_); CLEAR_AND_RETURN_FALSE_ON_EXCEPTION diff --git a/3party/Alohalytics/src/android/jni/jni_main.cc b/3party/Alohalytics/src/android/jni/jni_main.cc index 897ea1f3aa..c8940d9aba 100644 --- a/3party/Alohalytics/src/android/jni/jni_main.cc +++ b/3party/Alohalytics/src/android/jni/jni_main.cc @@ -1,7 +1,7 @@ /******************************************************************************* The MIT License (MIT) -Copyright (c) 2014 Alexander Zolotarev from Minsk, Belarus +Copyright (c) 2015 Alexander Zolotarev from Minsk, Belarus Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/3party/Alohalytics/src/apple/http_client_apple.mm b/3party/Alohalytics/src/apple/http_client_apple.mm index fe266933b9..17908dc9e9 100644 --- a/3party/Alohalytics/src/apple/http_client_apple.mm +++ b/3party/Alohalytics/src/apple/http_client_apple.mm @@ -1,7 +1,7 @@ /******************************************************************************* The MIT License (MIT) -Copyright (c) 2014 Alexander Zolotarev from Minsk, Belarus +Copyright (c) 2015 Alexander Zolotarev from Minsk, Belarus Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/3party/Alohalytics/src/cpp/alohalytics.cc b/3party/Alohalytics/src/cpp/alohalytics.cc index a585afe2cf..3185380927 100644 --- a/3party/Alohalytics/src/cpp/alohalytics.cc +++ b/3party/Alohalytics/src/cpp/alohalytics.cc @@ -1,7 +1,7 @@ /******************************************************************************* The MIT License (MIT) - Copyright (c) 2014 Alexander Zolotarev from Minsk, Belarus + Copyright (c) 2015 Alexander Zolotarev from Minsk, Belarus Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -36,7 +36,10 @@ #include "../cereal/include/types/string.hpp" #include "../cereal/include/types/map.hpp" -#define LOG_IF_DEBUG(...) if (debug_mode_) { alohalytics::Logger().Log(__VA_ARGS__); } +#define LOG_IF_DEBUG(...) \ + if (debug_mode_) { \ + alohalytics::Logger().Log(__VA_ARGS__); \ + } namespace alohalytics { @@ -46,7 +49,7 @@ struct NoOpDeleter { void operator()(T*) {} }; - // Use alohalytics::Stats::Instance() to access statistics engine. +// Use alohalytics::Stats::Instance() to access statistics engine. Stats::Stats() : message_queue_(*this) {} bool Stats::UploadBuffer(const std::string& url, std::string&& buffer, bool debug_mode) { @@ -55,7 +58,7 @@ bool Stats::UploadBuffer(const std::string& url, std::string&& buffer, bool debu request.set_post_body(std::move(buffer), "application/alohalytics-binary-blob"); try { - return request.RunHTTPRequest() && 200 == request.error_code(); + return request.RunHTTPRequest() && 200 == request.error_code() && !request.was_redirected(); } catch (const std::exception& ex) { if (debug_mode) { ALOG("Exception while trying to upload file", ex.what()); @@ -85,15 +88,21 @@ void Stats::OnMessage(const std::string& message, size_t dropped_events) { // Called by file storage engine to upload file with collected data. // Should return true if upload has been successful. // TODO(AlexZ): Refactor FSQ to make this method private. -bool Stats::OnFileReady(const std::string& full_path_to_file, uint64_t file_size) { +bool Stats::OnFileReady(const std::string& full_path_to_file) { if (upload_url_.empty()) { - LOG_IF_DEBUG("Warning: upload server url was not set and file of", file_size, "bytes was not uploaded."); + LOG_IF_DEBUG("Warning: upload server url was not set and file", full_path_to_file, "was not uploaded."); return false; } if (unique_client_id_event_.empty()) { LOG_IF_DEBUG("Warning: unique client ID is not set so statistics was not uploaded."); return false; } + // TODO(AlexZ): Refactor to use crossplatform and safe/non-throwing version. + const uint64_t file_size = bricks::FileSystem::GetFileSize(full_path_to_file); + if (0 == file_size) { + LOG_IF_DEBUG("ERROR: Trying to upload file of size 0?", full_path_to_file); + return false; + } LOG_IF_DEBUG("Trying to upload statistics file", full_path_to_file, "to", upload_url_); @@ -145,8 +154,8 @@ Stats& Stats::SetStoragePath(const std::string& full_path_to_storage_with_a_slas LOG_IF_DEBUG("Active file size:", status.appended_file_size); const size_t count = status.finalized.queue.size(); if (count) { - LOG_IF_DEBUG( - count, "files with total size of", status.finalized.total_size, "bytes are waiting for upload."); + LOG_IF_DEBUG(count, "files with total size of", status.finalized.total_size, + "bytes are waiting for upload."); } } const size_t memory_events_count = memory_storage_.size(); @@ -176,16 +185,28 @@ Stats& Stats::SetClientId(const std::string& unique_client_id) { return *this; } +static inline void LogEventImpl(AlohalyticsBaseEvent const& event, MessageQueue& msq) { + std::ostringstream sstream; + { + // unique_ptr is used to correctly serialize polymorphic types. + cereal::BinaryOutputArchive(sstream) << std::unique_ptr(&event); + } + msq.PushMessage(std::move(sstream.str())); +} + void Stats::LogEvent(std::string const& event_name) { LOG_IF_DEBUG("LogEvent:", event_name); AlohalyticsKeyEvent event; event.key = event_name; - std::ostringstream sstream; - { - // unique_ptr is used to correctly serialize polymorphic types. - cereal::BinaryOutputArchive(sstream) << std::unique_ptr(&event); - } - message_queue_.PushMessage(std::move(sstream.str())); + LogEventImpl(event, message_queue_); +} + +void Stats::LogEvent(std::string const& event_name, Location const& location) { + LOG_IF_DEBUG("LogEvent:", event_name, location.ToDebugString()); + AlohalyticsKeyLocationEvent event; + event.key = event_name; + event.location = location; + LogEventImpl(event, message_queue_); } void Stats::LogEvent(std::string const& event_name, std::string const& event_value) { @@ -193,9 +214,16 @@ void Stats::LogEvent(std::string const& event_name, std::string const& event_val AlohalyticsKeyValueEvent event; event.key = event_name; event.value = event_value; - std::ostringstream sstream; - { cereal::BinaryOutputArchive(sstream) << std::unique_ptr(&event); } - message_queue_.PushMessage(std::move(sstream.str())); + LogEventImpl(event, message_queue_); +} + +void Stats::LogEvent(std::string const& event_name, std::string const& event_value, Location const& location) { + LOG_IF_DEBUG("LogEvent:", event_name, "=", event_value, location.ToDebugString()); + AlohalyticsKeyValueLocationEvent event; + event.key = event_name; + event.value = event_value; + event.location = location; + LogEventImpl(event, message_queue_); } void Stats::LogEvent(std::string const& event_name, TStringMap const& value_pairs) { @@ -203,9 +231,16 @@ void Stats::LogEvent(std::string const& event_name, TStringMap const& value_pair AlohalyticsKeyPairsEvent event; event.key = event_name; event.pairs = value_pairs; - std::ostringstream sstream; - { cereal::BinaryOutputArchive(sstream) << std::unique_ptr(&event); } - message_queue_.PushMessage(std::move(sstream.str())); + LogEventImpl(event, message_queue_); +} + +void Stats::LogEvent(std::string const& event_name, TStringMap const& value_pairs, Location const& location) { + LOG_IF_DEBUG("LogEvent:", event_name, "=", value_pairs, location.ToDebugString()); + AlohalyticsKeyPairsLocationEvent event; + event.key = event_name; + event.pairs = value_pairs; + event.location = location; + LogEventImpl(event, message_queue_); } // Forcedly tries to upload all stored data to the server. diff --git a/3party/Alohalytics/src/event_base.h b/3party/Alohalytics/src/event_base.h index 45dfc429a6..176420c0d5 100644 --- a/3party/Alohalytics/src/event_base.h +++ b/3party/Alohalytics/src/event_base.h @@ -1,7 +1,7 @@ /******************************************************************************* The MIT License (MIT) - Copyright (c) 2014 Alexander Zolotarev from Minsk, Belarus + Copyright (c) 2015 Alexander Zolotarev from Minsk, Belarus Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -27,8 +27,8 @@ // Define ALOHALYTICS_SERVER when using this header on a server-side. -#include #include +#include #include "cereal/include/cereal.hpp" #include "cereal/include/types/base_class.hpp" @@ -37,10 +37,22 @@ #include "cereal/include/types/string.hpp" #include "cereal/include/types/map.hpp" +#include "location.h" + // For easier processing on a server side, every statistics event should derive from this base class. struct AlohalyticsBaseEvent { uint64_t timestamp; + virtual std::string ToString() const { + const time_t timet = static_cast(timestamp / 1000); + char buf[100]; + if (::strftime(buf, 100, "%e-%b-%Y %H:%M:%S", ::gmtime(&timet))) { + return buf; + } else { + return "INVALID_TIME"; + } + } + static uint64_t CurrentTimestamp() { return std::chrono::duration_cast( std::chrono::system_clock::now().time_since_epoch()).count(); @@ -59,55 +71,119 @@ struct AlohalyticsBaseEvent { // To use polymorphism on a server side. virtual ~AlohalyticsBaseEvent() = default; }; -CEREAL_REGISTER_TYPE_WITH_NAME(AlohalyticsBaseEvent, "b"); +CEREAL_REGISTER_TYPE_WITH_NAME(AlohalyticsBaseEvent, "b") // Special event in the beginning of each block (file) sent to stats server. // Designed to mark all events in this data block as belonging to one user with specified id. struct AlohalyticsIdEvent : public AlohalyticsBaseEvent { std::string id; + virtual std::string ToString() const { return AlohalyticsBaseEvent::ToString() + " ID: " + id; } + template void serialize(Archive& ar) { AlohalyticsBaseEvent::serialize(ar); ar(CEREAL_NVP(id)); } }; -CEREAL_REGISTER_TYPE_WITH_NAME(AlohalyticsIdEvent, "i"); +CEREAL_REGISTER_TYPE_WITH_NAME(AlohalyticsIdEvent, "i") // Simple event with a string name (key) only. struct AlohalyticsKeyEvent : public AlohalyticsBaseEvent { std::string key; + virtual std::string ToString() const { return AlohalyticsBaseEvent::ToString() + " " + key; } + template void serialize(Archive& ar) { AlohalyticsBaseEvent::serialize(ar); ar(CEREAL_NVP(key)); } }; -CEREAL_REGISTER_TYPE_WITH_NAME(AlohalyticsKeyEvent, "k"); +CEREAL_REGISTER_TYPE_WITH_NAME(AlohalyticsKeyEvent, "k") // Simple event with a string key/value pair. struct AlohalyticsKeyValueEvent : public AlohalyticsKeyEvent { std::string value; + virtual std::string ToString() const { return AlohalyticsKeyEvent::ToString() + " = " + value; } + template void serialize(Archive& ar) { AlohalyticsKeyEvent::serialize(ar); ar(CEREAL_NVP(value)); } }; -CEREAL_REGISTER_TYPE_WITH_NAME(AlohalyticsKeyValueEvent, "v"); +CEREAL_REGISTER_TYPE_WITH_NAME(AlohalyticsKeyValueEvent, "v") // Simple event with a string key and map value. struct AlohalyticsKeyPairsEvent : public AlohalyticsKeyEvent { std::map pairs; + virtual std::string ToString() const { + std::ostringstream stream; + stream << AlohalyticsKeyEvent::ToString() << " [ "; + for (const auto& pair : pairs) { + stream << pair.first << '=' << pair.second << ' '; + } + stream << ']'; + return stream.str(); + } + template void serialize(Archive& ar) { AlohalyticsKeyEvent::serialize(ar); ar(CEREAL_NVP(pairs)); } }; -CEREAL_REGISTER_TYPE_WITH_NAME(AlohalyticsKeyPairsEvent, "p"); +CEREAL_REGISTER_TYPE_WITH_NAME(AlohalyticsKeyPairsEvent, "p") + +// Key + location. +struct AlohalyticsKeyLocationEvent : public AlohalyticsKeyEvent { + alohalytics::Location location; + + virtual std::string ToString() const { + return AlohalyticsKeyEvent::ToString() + ' ' + location.ToDebugString(); + } + + template + void serialize(Archive& ar) { + AlohalyticsKeyEvent::serialize(ar); + ar(CEREAL_NVP(location)); + } +}; +CEREAL_REGISTER_TYPE_WITH_NAME(AlohalyticsKeyLocationEvent, "kl") + +// Key + value + location. +struct AlohalyticsKeyValueLocationEvent : public AlohalyticsKeyValueEvent { + alohalytics::Location location; + + virtual std::string ToString() const { + return AlohalyticsKeyValueEvent::ToString() + ' ' + location.ToDebugString(); + } + + template + void serialize(Archive& ar) { + AlohalyticsKeyValueEvent::serialize(ar); + ar(CEREAL_NVP(location)); + } +}; +CEREAL_REGISTER_TYPE_WITH_NAME(AlohalyticsKeyValueLocationEvent, "vl") + +// Key + pairs of key/value + location. +struct AlohalyticsKeyPairsLocationEvent : public AlohalyticsKeyPairsEvent { + alohalytics::Location location; + + virtual std::string ToString() const { + return AlohalyticsKeyPairsEvent::ToString() + ' ' + location.ToDebugString(); + } + + template + void serialize(Archive& ar) { + AlohalyticsKeyPairsEvent::serialize(ar); + ar(CEREAL_NVP(location)); + } +}; +CEREAL_REGISTER_TYPE_WITH_NAME(AlohalyticsKeyPairsLocationEvent, "pl") #endif // EVENT_BASE_H diff --git a/3party/Alohalytics/src/gzip_wrapper.h b/3party/Alohalytics/src/gzip_wrapper.h index be29c118ff..feea516c9c 100644 --- a/3party/Alohalytics/src/gzip_wrapper.h +++ b/3party/Alohalytics/src/gzip_wrapper.h @@ -1,7 +1,7 @@ /******************************************************************************* The MIT License (MIT) -Copyright (c) 2014 Alexander Zolotarev from Minsk, Belarus +Copyright (c) 2015 Alexander Zolotarev from Minsk, Belarus Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -22,28 +22,48 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. *******************************************************************************/ #include +#include #include #include "logger.h" namespace alohalytics { -// Returns empty string on error. -inline std::string Gzip(const std::string& data_to_compress) { +static constexpr size_t kGzipBufferSize = 32768; + +struct GzipErrorException : public std::exception { + int err_; + std::string msg_; + GzipErrorException(int err, const char* msg) : err_(err), msg_(msg ? msg : "") {} + virtual char const* what() const noexcept { + return ("ERROR " + std::to_string(err_) + " while gzipping with zlib. " + msg_).c_str(); + } +}; + +struct GunzipErrorException : public std::exception { + int err_; + std::string msg_; + GunzipErrorException(int err, const char* msg) : err_(err), msg_(msg ? msg : "") {} + virtual char const* what() const noexcept { + return ("ERROR " + std::to_string(err_) + " while gunzipping with zlib. " + msg_).c_str(); + } +}; + +inline std::string Gzip(const std::string& data_to_compress) throw(GzipErrorException) { z_stream z = {}; int res = ::deflateInit2(&z, Z_BEST_COMPRESSION, Z_DEFLATED, 15 + 16, 8, Z_DEFAULT_STRATEGY); if (Z_OK == res) { z.next_in = const_cast(reinterpret_cast(data_to_compress.data())); z.avail_in = data_to_compress.size(); std::string compressed; - static const size_t kGzipBufferSize = 32768; - char buffer[std::min(data_to_compress.size(), kGzipBufferSize)]; + std::vector buffer; + buffer.resize(std::min(data_to_compress.size(), kGzipBufferSize)); do { - z.next_out = reinterpret_cast(buffer); - z.avail_out = sizeof(buffer)/sizeof(buffer[0]); + z.next_out = buffer.data(); + z.avail_out = buffer.size(); res = ::deflate(&z, Z_FINISH); if (compressed.size() < z.total_out) { - compressed.append(buffer, z.total_out - compressed.size()); + compressed.append(reinterpret_cast(buffer.data()), z.total_out - compressed.size()); } } while (Z_OK == res); ::deflateEnd(&z); @@ -51,8 +71,32 @@ inline std::string Gzip(const std::string& data_to_compress) { return compressed; } } - ALOG("ERROR", res, "while gzipping with zlib.", z.msg ? z.msg : ""); - return std::string(); + throw GzipErrorException(res, z.msg); } -} // namespace alohalytics +inline std::string Gunzip(const std::string& data_to_decompress) throw(GzipErrorException) { + z_stream z = {}; + int res = ::inflateInit2(&z, 16 + MAX_WBITS); + if (Z_OK == res) { + z.next_in = const_cast(reinterpret_cast(data_to_decompress.data())); + z.avail_in = data_to_decompress.size(); + std::string decompressed; + std::vector buffer; + buffer.resize(std::min(data_to_decompress.size(), kGzipBufferSize)); + do { + z.next_out = buffer.data(); + z.avail_out = buffer.size(); + res = ::inflate(&z, Z_NO_FLUSH); + if (decompressed.size() < z.total_out) { + decompressed.append(reinterpret_cast(buffer.data()), z.total_out - decompressed.size()); + } + } while (Z_OK == res); + ::inflateEnd(&z); + if (Z_STREAM_END == res) { + return decompressed; + } + } + throw GunzipErrorException(res, z.msg); +} + +} // namespace alohalytics diff --git a/3party/Alohalytics/src/http_client.h b/3party/Alohalytics/src/http_client.h index 855c1496e7..6cf5ddd57c 100644 --- a/3party/Alohalytics/src/http_client.h +++ b/3party/Alohalytics/src/http_client.h @@ -1,7 +1,7 @@ /******************************************************************************* The MIT License (MIT) -Copyright (c) 2014 Alexander Zolotarev from Minsk, Belarus +Copyright (c) 2015 Alexander Zolotarev from Minsk, Belarus Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/3party/Alohalytics/src/location.h b/3party/Alohalytics/src/location.h new file mode 100644 index 0000000000..5c4735ed50 --- /dev/null +++ b/3party/Alohalytics/src/location.h @@ -0,0 +1,277 @@ +/******************************************************************************* + The MIT License (MIT) + + Copyright (c) 2015 Alexander Zolotarev from Minsk, Belarus + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + *******************************************************************************/ + +#ifndef LOCATION_H +#define LOCATION_H + +#if defined(__BYTE_ORDER__) && (__BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__) +#error "This code does not support serialization on Big Endian archs (see TODOs below)." +#endif + +#include +#include +#include +#include // For ToDebugString() +#include + +#include + +namespace alohalytics { + +class Location { + enum Mask : uint8_t { + NOT_INITIALIZED = 0, + HAS_LATLON = 1 << 0, + HAS_ALTITUDE = 1 << 1, + HAS_BEARING = 1 << 2, + HAS_SPEED = 1 << 3, + HAS_SOURCE = 1 << 4 + } valid_values_mask_ = NOT_INITIALIZED; + + public: + class LocationDecodeException : public std::exception {}; + + // Milliseconds from January 1, 1970. + uint64_t timestamp_ms_; + double latitude_deg_; + double longitude_deg_; + double horizontal_accuracy_m_; + double altitude_m_; + double vertical_accuracy_m_; + // Positive degrees from the true North. + double bearing_deg_; + // Meters per second. + double speed_mps_; + + // We use degrees with precision of 7 decimal places - it's approx 2cm precision on the Earth. + static constexpr double TEN_MILLION = 10000000.; + // Some params below can be stored with 2 decimal places precision. + static constexpr double ONE_HUNDRED = 100.; + + // Compacts location into the byte representation. + // TODO(AlexZ): We don't care about endiannes for now. + std::string Encode() const { + std::string s; + s.push_back(valid_values_mask_); + if (valid_values_mask_ & HAS_LATLON) { + static_assert(sizeof(timestamp_ms_) == 8, "We cut off timestamp from 8 bytes to 6 to save space."); + AppendToStringAsBinary(s, timestamp_ms_, sizeof(timestamp_ms_) - 2); + const int32_t lat10mil = latitude_deg_ * TEN_MILLION; + AppendToStringAsBinary(s, lat10mil); + const int32_t lon10mil = longitude_deg_ * TEN_MILLION; + AppendToStringAsBinary(s, lon10mil); + const uint32_t horizontal_accuracy_cm = horizontal_accuracy_m_ * ONE_HUNDRED; + AppendToStringAsBinary(s, horizontal_accuracy_cm); + if (valid_values_mask_ & HAS_SOURCE) { + s.push_back(source_); + } + } + if (valid_values_mask_ & HAS_ALTITUDE) { + const int32_t altitude_cm = altitude_m_ * ONE_HUNDRED; + AppendToStringAsBinary(s, altitude_cm); + const uint16_t vertical_accuracy_cm = vertical_accuracy_m_ * ONE_HUNDRED; + AppendToStringAsBinary(s, vertical_accuracy_cm); + } + if (valid_values_mask_ & HAS_BEARING) { + const uint32_t bearing10mil = bearing_deg_ * TEN_MILLION; + AppendToStringAsBinary(s, bearing10mil); + } + if (valid_values_mask_ & HAS_SPEED) { + const uint16_t speedx100mps = speed_mps_ * ONE_HUNDRED; + AppendToStringAsBinary(s, speedx100mps); + } + return s; + } + + // Initializes location from serialized byte array created by ToString() method. + // TODO(AlexZ): we don't care about endianness right now. + explicit Location(const std::string &encoded) { Decode(encoded); } + + void Decode(const std::string &encoded) { + if (encoded.empty()) { + throw LocationDecodeException(); + } + std::string::size_type i = 0; + const std::string::size_type size = encoded.size(); + valid_values_mask_ = static_cast(encoded[i++]); + if (valid_values_mask_ & HAS_LATLON) { + if ((size - i) < 18) { + throw LocationDecodeException(); + } + timestamp_ms_ = *reinterpret_cast(&encoded[i]) | + (static_cast(*reinterpret_cast(&encoded[i + 4])) << 32); + i += sizeof(uint64_t) - 2; // We use 6 bytes to store timestamps. + latitude_deg_ = *reinterpret_cast(&encoded[i]) / TEN_MILLION; + i += sizeof(int32_t); + longitude_deg_ = *reinterpret_cast(&encoded[i]) / TEN_MILLION; + i += sizeof(int32_t); + horizontal_accuracy_m_ = *reinterpret_cast(&encoded[i]) / ONE_HUNDRED; + i += sizeof(uint32_t); + if (valid_values_mask_ & HAS_SOURCE) { + if ((size - i) < 1) { + throw LocationDecodeException(); + } + source_ = static_cast(encoded[i++]); + } + } + if (valid_values_mask_ & HAS_ALTITUDE) { + if ((size - i) < 6) { + throw LocationDecodeException(); + } + altitude_m_ = *reinterpret_cast(&encoded[i]) / ONE_HUNDRED; + i += sizeof(int32_t); + vertical_accuracy_m_ = *reinterpret_cast(&encoded[i]) / ONE_HUNDRED; + i += sizeof(uint16_t); + } + if (valid_values_mask_ & HAS_BEARING) { + if ((size - i) < 4) { + throw LocationDecodeException(); + } + bearing_deg_ = *reinterpret_cast(&encoded[i]) / TEN_MILLION; + i += sizeof(uint32_t); + } + if (valid_values_mask_ & HAS_SPEED) { + if ((size - i) < 2) { + throw LocationDecodeException(); + } + speed_mps_ = *reinterpret_cast(&encoded[i]) / ONE_HUNDRED; + i += sizeof(uint16_t); + } + } + + Location() = default; + + enum Source : std::uint8_t { UNKNOWN = 0, GPS = 1, NETWORK = 2, PASSIVE = 3 } source_; + + bool HasLatLon() const { return valid_values_mask_ & HAS_LATLON; } + Location &SetLatLon(uint64_t timestamp_ms, + double latitude_deg, + double longitude_deg, + double horizontal_accuracy_m) { + // We do not support values without known horizontal accuracy. + if (horizontal_accuracy_m > 0.0) { + timestamp_ms_ = timestamp_ms; + latitude_deg_ = latitude_deg; + longitude_deg_ = longitude_deg; + horizontal_accuracy_m_ = horizontal_accuracy_m; + valid_values_mask_ = static_cast(valid_values_mask_ | HAS_LATLON); + } + return *this; + } + + bool HasSource() const { return valid_values_mask_ & HAS_SOURCE; } + Location &SetSource(Source source) { + source_ = source; + valid_values_mask_ = static_cast(valid_values_mask_ | HAS_SOURCE); + return *this; + } + + bool HasAltitude() const { return valid_values_mask_ & HAS_ALTITUDE; } + Location &SetAltitude(double altitude_m, double vertical_accuracy_m) { + if (vertical_accuracy_m > 0.0) { + altitude_m_ = altitude_m; + vertical_accuracy_m_ = vertical_accuracy_m; + valid_values_mask_ = static_cast(valid_values_mask_ | HAS_ALTITUDE); + } + return *this; + } + + bool HasBearing() const { return valid_values_mask_ & HAS_BEARING; } + Location &SetBearing(double bearing_deg) { + if (bearing_deg >= 0.0) { + bearing_deg_ = bearing_deg; + valid_values_mask_ = static_cast(valid_values_mask_ | HAS_BEARING); + } + return *this; + } + + bool HasSpeed() const { return valid_values_mask_ & HAS_SPEED; } + Location &SetSpeed(double speed_mps) { + if (speed_mps >= 0.0) { + speed_mps_ = speed_mps; + valid_values_mask_ = static_cast(valid_values_mask_ | HAS_SPEED); + } + return *this; + } + + template + void save(Archive &ar) const { + ar(Encode()); + } + + template + void load(Archive &ar) { + std::string encoded_location; + ar(encoded_location); + Decode(encoded_location); + } + + std::string ToDebugString() const { + std::ostringstream stream; + stream << '<' << std::fixed; + if (valid_values_mask_ & HAS_LATLON) { + stream << "utc=" << timestamp_ms_ << ",lat=" << std::setprecision(7) << latitude_deg_ + << ",lon=" << std::setprecision(7) << longitude_deg_ << ",acc=" << std::setprecision(2) + << horizontal_accuracy_m_; + } + if (valid_values_mask_ & HAS_ALTITUDE) { + stream << ",alt=" << std::setprecision(2) << altitude_m_ << ",vac=" << std::setprecision(2) + << vertical_accuracy_m_; + } + if (valid_values_mask_ & HAS_BEARING) { + stream << ",bea=" << std::setprecision(7) << bearing_deg_; + } + if (valid_values_mask_ & HAS_SPEED) { + stream << ",spd=" << std::setprecision(2) << speed_mps_; + } + if (valid_values_mask_ & HAS_SOURCE) { + stream << ",src="; + switch (source_) { + case Source::GPS: + stream << "GPS"; + break; + case Source::NETWORK: + stream << "Net"; + break; + case Source::PASSIVE: + stream << "Psv"; + break; + default: + stream << "Unk"; + break; + } + } + stream << '>'; + return stream.str(); + } + + private: + template + static inline void AppendToStringAsBinary(std::string &str, const T &value, size_t bytes = sizeof(T)) { + str.append(reinterpret_cast(&value), bytes); + } +}; +} // namespace alohalytics + +#endif // LOCATION_H diff --git a/3party/Alohalytics/src/logger.h b/3party/Alohalytics/src/logger.h index a4bccb2150..19a9460594 100644 --- a/3party/Alohalytics/src/logger.h +++ b/3party/Alohalytics/src/logger.h @@ -1,7 +1,7 @@ /******************************************************************************* The MIT License (MIT) - Copyright (c) 2014 Alexander Zolotarev from Minsk, Belarus + Copyright (c) 2015 Alexander Zolotarev from Minsk, Belarus Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -53,7 +53,6 @@ class Logger { Logger(const char* file, int line) { out_ << file << ':' << line << ": "; } ~Logger() { -// out_ << std::endl; #if defined(__OBJC__) NSLog(@"Alohalytics: %s", out_.str().c_str()); #elif defined(ANDROID) @@ -65,7 +64,8 @@ class Logger { template void Log(const T& arg1, const ARGS&... others) { - out_ << arg1 << ' '; + Log(arg1); + out_ << ' '; Log(others...); } diff --git a/3party/Alohalytics/src/posix/http_client_curl.cc b/3party/Alohalytics/src/posix/http_client_curl.cc index b6b3b08440..8a914c1f76 100644 --- a/3party/Alohalytics/src/posix/http_client_curl.cc +++ b/3party/Alohalytics/src/posix/http_client_curl.cc @@ -26,7 +26,7 @@ #include // popen #include -#include // std::cerr +#include // std::cerr #ifdef _MSC_VER #define popen _popen diff --git a/3party/Alohalytics/tests/test_gzip.cc b/3party/Alohalytics/tests/test_gzip.cc new file mode 100644 index 0000000000..cfe80d540b --- /dev/null +++ b/3party/Alohalytics/tests/test_gzip.cc @@ -0,0 +1,18 @@ +#include "../src/gzip_wrapper.h" + +#include + +int main(int, char **) { + std::string data; + for (int i = 0; i < 10000 + rand(); ++i) { + data.push_back(rand()); + } + const std::string gzipped = alohalytics::Gzip(data); + const std::string ungzipped = alohalytics::Gunzip(gzipped); + if (ungzipped != data) { + std::cerr << "Gzip/gunzip test has failed" << std::endl; + return -1; + } + std::cout << "Test has passed" << std::endl; + return 0; +} diff --git a/3party/Alohalytics/tests/test_location.cc b/3party/Alohalytics/tests/test_location.cc new file mode 100644 index 0000000000..3689e6f1fa --- /dev/null +++ b/3party/Alohalytics/tests/test_location.cc @@ -0,0 +1,88 @@ +/******************************************************************************* + The MIT License (MIT) + + Copyright (c) 2015 Alexander Zolotarev from Minsk, Belarus + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + *******************************************************************************/ +#include "../src/location.h" + +#include +#include +#include +#include + +using alohalytics::Location; +using std::cerr; +using std::endl; +using std::string; + +#define ASSERT_EQUAL(x, y) \ + if ((x) != (y)) { \ + cerr << "Test failed: " << #x << " != " << #y << " (" << (x) << " != " << (y) << ")" << endl; \ + return -1; \ + } +#define ASSERT_ALMOST_EQUAL(x, y, epsilon) \ + if (fabs((x) - (y)) > epsilon) { \ + cerr << "Test failed: " << #x << " ~!= " << #y << " (" << (x) << " ~!= " << (y) << ")" << endl; \ + return -1; \ + } + +int main(int, char **) { + cerr.precision(20); + cerr << std::fixed; + + const uint64_t timestamp = 1423169916587UL; + const double lat = -84.123456789; + const double lon = 177.123456789; + const double horizontal_accuracy = 0.345678912345; + const double alt = -11.123456789; + const double vertical_accuracy = 0.987654321; + const double speed = 27.123456789; + const double bearing = 0.123456789; + const Location::Source source = alohalytics::Location::Source::NETWORK; + + Location l0; + const std::string serialized = l0.SetLatLon(timestamp, lat, lon, horizontal_accuracy) + .SetAltitude(alt, vertical_accuracy) + .SetSpeed(speed) + .SetBearing(bearing) + .SetSource(source) + .Encode(); + ASSERT_EQUAL(serialized.size(), 32); + + const alohalytics::Location l(serialized); + ASSERT_EQUAL(l.HasLatLon(), true); + ASSERT_EQUAL(l.timestamp_ms_, timestamp); + ASSERT_ALMOST_EQUAL(l.latitude_deg_, lat, 1e-7); + ASSERT_ALMOST_EQUAL(l.longitude_deg_, lon, 1e-7); + ASSERT_ALMOST_EQUAL(l.horizontal_accuracy_m_, horizontal_accuracy, 1e-2); + ASSERT_EQUAL(l.HasAltitude(), true); + ASSERT_ALMOST_EQUAL(l.altitude_m_, alt, 1e-2); + ASSERT_ALMOST_EQUAL(l.vertical_accuracy_m_, vertical_accuracy, 1e-2); + ASSERT_EQUAL(l.HasBearing(), true); + ASSERT_ALMOST_EQUAL(l.bearing_deg_, bearing, 1e-7); + ASSERT_EQUAL(l.HasSpeed(), true); + ASSERT_ALMOST_EQUAL(l.speed_mps_, speed, 1e-2); + ASSERT_EQUAL(l.HasSource(), true); + ASSERT_EQUAL(l.source_, source); + + std::cout << "All tests have passed." << endl; + return 0; +}