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