1""" 2 @file 3 @brief This file sends anonymous application metrics and errors over HTTP 4 @author Jonathan Thomas <jonathan@openshot.org> 5 6 @section LICENSE 7 8 Copyright (c) 2008-2018 OpenShot Studios, LLC 9 (http://www.openshotstudios.com). This file is part of 10 OpenShot Video Editor (http://www.openshot.org), an open-source project 11 dedicated to delivering high quality video editing and animation solutions 12 to the world. 13 14 OpenShot Video Editor is free software: you can redistribute it and/or modify 15 it under the terms of the GNU General Public License as published by 16 the Free Software Foundation, either version 3 of the License, or 17 (at your option) any later version. 18 19 OpenShot Video Editor is distributed in the hope that it will be useful, 20 but WITHOUT ANY WARRANTY; without even the implied warranty of 21 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 GNU General Public License for more details. 23 24 You should have received a copy of the GNU General Public License 25 along with OpenShot Library. If not, see <http://www.gnu.org/licenses/>. 26 """ 27 28# idna encoding import required to prevent bug (unknown encoding: idna) 29import encodings.idna 30import requests 31import platform 32import threading 33import time 34import urllib.parse 35from copy import deepcopy 36 37from classes import info 38from classes import language 39from classes.app import get_app 40from classes.logger import log 41 42import openshot 43 44from PyQt5.QtCore import QT_VERSION_STR, PYQT_VERSION_STR 45 46try: 47 import distro 48except ModuleNotFoundError: 49 distro = None 50 51# Get settings 52s = get_app().get_settings() 53 54# Determine OS version 55os_version = "X11; Linux %s" % platform.machine() 56os_distro = "None" 57try: 58 if platform.system() == "Darwin": 59 v = platform.mac_ver() 60 os_version = "Macintosh; Intel Mac OS X %s" % v[0].replace(".", "_") 61 os_distro = "OS X %s" % v[0] 62 63 elif 1 and platform.system() == "FreeBSD": 64 v = platform.version().split(" ") 65 os_version = "X11; %s %s" % (v[0], platform.machine()) 66 os_distro = " ".join([v[0], v[1]]) 67 68 elif platform.system() == "Windows": 69 v = platform.win32_ver() 70 os_version = "Windows NT %s; %s" % (v[0], v[1]) 71 os_distro = "Windows %s" % "-".join(v) 72 73 elif platform.system() == "Linux": 74 # Get the distro name and version (if any) 75 if distro: 76 os_distro = "-".join(distro.linux_distribution()[0:2]) 77 else: 78 os_distro = "Linux" 79 80except Exception: 81 log.debug("Error determining OS version", exc_info=1) 82 83# Build user-agent 84user_agent = "Mozilla/5.0 (%s) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.120 Safari/537.36" % os_version 85 86params = { 87 "cid": s.get("unique_install_id"), # Unique install ID 88 "v": 1, # Google Measurement API version 89 "tid": "UA-4381101-5", # Google Analytic Tracking ID 90 "an": info.PRODUCT_NAME, # App Name 91 "aip": 1, # Anonymize IP 92 "aid": "org.openshot.%s" % info.NAME, # App ID 93 "av": info.VERSION, # App Version 94 "ul": language.get_current_locale().replace('_', '-').lower(), # Current Locale 95 "ua": user_agent, # Custom User Agent (for OS, Processor, and OS version) 96 "cd1": openshot.OPENSHOT_VERSION_FULL, # Dimension 1: libopenshot version 97 "cd2": platform.python_version(), # Dimension 2: python version (i.e. 3.4.3) 98 "cd3": QT_VERSION_STR, # Dimension 3: qt5 version (i.e. 5.2.1) 99 "cd4": PYQT_VERSION_STR, # Dimension 4: pyqt5 version (i.e. 5.2.1) 100 "cd5": os_distro 101} 102 103# Queue for metrics (incase things are disabled... just queue it up 104# incase the user enables metrics later 105metric_queue = [] 106 107 108def track_metric_screen(screen_name): 109 """Track a GUI screen being shown""" 110 metric_params = deepcopy(params) 111 metric_params["t"] = "screenview" 112 metric_params["cd"] = screen_name 113 metric_params["cid"] = s.get("unique_install_id") 114 115 t = threading.Thread(target=send_metric, args=[metric_params]) 116 t.daemon = True 117 t.start() 118 119 120def track_metric_event(event_action, event_label, event_category="General", event_value=0): 121 """Track a GUI screen being shown""" 122 metric_params = deepcopy(params) 123 metric_params["t"] = "event" 124 metric_params["ec"] = event_category 125 metric_params["ea"] = event_action 126 metric_params["el"] = event_label 127 metric_params["ev"] = event_value 128 metric_params["cid"] = s.get("unique_install_id") 129 130 t = threading.Thread(target=send_metric, args=[metric_params]) 131 t.daemon = True 132 t.start() 133 134 135def track_metric_error(error_name, is_fatal=False): 136 """Track an error has occurred""" 137 metric_params = deepcopy(params) 138 metric_params["t"] = "exception" 139 metric_params["exd"] = error_name 140 metric_params["exf"] = 0 141 if is_fatal: 142 metric_params["exf"] = 1 143 144 t = threading.Thread(target=send_metric, args=[metric_params]) 145 t.daemon = True 146 t.start() 147 148 149def track_metric_session(is_start=True): 150 """Track a GUI screen being shown""" 151 metric_params = deepcopy(params) 152 metric_params["t"] = "screenview" 153 metric_params["sc"] = "start" 154 metric_params["cd"] = "launch-app" 155 metric_params["cid"] = s.get("unique_install_id") 156 if not is_start: 157 metric_params["sc"] = "end" 158 metric_params["cd"] = "close-app" 159 160 t = threading.Thread(target=send_metric, args=[metric_params]) 161 t.daemon = True 162 t.start() 163 164 165def send_metric(params): 166 """Send anonymous metric over HTTP for tracking""" 167 168 # Add to queue and *maybe* send if the user allows it 169 metric_queue.append(params) 170 171 # Check if the user wants to send metrics and errors 172 if s.get("send_metrics"): 173 174 for metric_params in metric_queue: 175 url_params = urllib.parse.urlencode(metric_params) 176 url = "https://www.google-analytics.com/collect?%s" % url_params 177 178 # Send metric HTTP data 179 try: 180 r = requests.get(url, headers={"user-agent": user_agent}) 181 except Exception: 182 log.warning("Failed to track metric", exc_info=1) 183 184 # Wait a moment, so we don't spam the requests 185 time.sleep(0.25) 186 187 # All metrics have been sent (or attempted to send) 188 # Clear the queue 189 metric_queue.clear() 190