1
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
2
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)
5
using System.Collections.Generic;
6
using System.Globalization;
9
using System.Threading;
10
using System.Windows.Media;
11
using ICSharpCode.Core;
12
using ICSharpCode.Core.Services;
13
using ICSharpCode.SharpDevelop;
14
using ICSharpCode.UsageDataCollector.Contracts;
16
namespace ICSharpCode.UsageDataCollector
19
/// Main singleton class of the analytics. This class is thread-safe.
21
public sealed partial class AnalyticsMonitor : IAnalyticsMonitor
23
const string UploadUrl = "http://usagedatacollector.sharpdevelop.net/upload/UploadUsageData.svc";
24
const string ProductName = "sharpdevelop";
25
public static readonly Uri PrivacyStatementUrl = new Uri("http://www.icsharpcode.net/OpenSource/SD/UsageDataCollector/");
27
public static readonly AnalyticsMonitor Instance = new AnalyticsMonitor();
29
public static bool EnabledIsUndecided {
31
return string.IsNullOrEmpty(PropertyService.Get("ICSharpCode.UsageDataCollector.Enabled"));
36
/// Allows to enable/disable the usage data monitoring.
38
public static bool Enabled {
40
return string.Equals(PropertyService.Get("ICSharpCode.UsageDataCollector.Enabled"), bool.TrueString, StringComparison.OrdinalIgnoreCase);
43
PropertyService.Set("ICSharpCode.UsageDataCollector.Enabled", value.ToString());
44
// Initially opening the session takes some time; which is bad for the startpage
45
// because the animation would start with a delay. We solve this by calling Open/CloseSession
46
// on a background thread.
47
ThreadPool.QueueUserWorkItem(delegate { AsyncEnableDisable(); } );
51
static void AsyncEnableDisable()
54
Instance.OpenSession();
56
Instance.CloseSession();
57
Instance.TryDeleteDatabase();
61
readonly object lockObj = new object();
63
UsageDataSessionWriter session;
65
private AnalyticsMonitor()
67
var container = ServiceManager.Instance.GetRequiredService<ThreadSafeServiceContainer>();
68
container.TryAddService(typeof(IAnalyticsMonitor), this);
69
dbFileName = Path.Combine(PropertyService.ConfigDirectory, "usageData.dat");
71
SharpDevelop.Gui.WorkbenchSingleton.WorkbenchUnloaded += delegate { CloseSession(); };
74
static Guid FindUserId()
76
// Ensure we assign only 1 ID to each user; even when he has multiple UDC databases because there
77
// are multiple SharpDevelop versions installed. We do this by reading out the userID GUID from
78
// the existing databases in any neighbor config directory.
79
string[] otherSharpDevelopVersions;
81
otherSharpDevelopVersions = Directory.GetDirectories(Path.Combine(PropertyService.ConfigDirectory, ".."));
82
} catch (IOException) {
83
otherSharpDevelopVersions = new string[0];
84
} catch (UnauthorizedAccessException) {
85
otherSharpDevelopVersions = new string[0];
87
LoggingService.Debug("Looking for existing UDC database in " + otherSharpDevelopVersions.Length + " directories");
88
foreach (string path in otherSharpDevelopVersions) {
89
string dbFileName = Path.Combine(path, "usageData.dat");
90
if (File.Exists(dbFileName)) {
91
LoggingService.Info("Found existing UDC database: " + dbFileName);
92
Guid? guid = UsageDataSessionWriter.RetrieveUserId(dbFileName);
94
LoggingService.Info("Found GUID in existing UDC database: " + guid.Value);
99
LoggingService.Info("Did not find existing UDC database; creating new GUID.");
100
return Guid.NewGuid();
104
/// Opens the database connection, updates the database if required.
105
/// Will start an upload to the server, if required.
107
public void OpenSession()
109
IEnumerable<UsageDataEnvironmentProperty> appEnvironmentProperties = GetAppProperties();
110
bool sessionOpened = false;
112
if (session == null) {
114
session = new UsageDataSessionWriter(dbFileName, FindUserId);
115
} catch (IncompatibleDatabaseException ex) {
116
if (ex.ActualVersion < ex.ExpectedVersion) {
117
LoggingService.Info("AnalyticsMonitor: " + ex.Message + ", removing old database");
118
Guid? oldUserId = UsageDataSessionWriter.RetrieveUserId(dbFileName);
119
// upgrade database by deleting the old one
122
session = new UsageDataSessionWriter(dbFileName, () => (oldUserId ?? FindUserId()));
123
} catch (IncompatibleDatabaseException ex2) {
124
LoggingService.Warn("AnalyticsMonitor: Could not upgrade database: " + ex2.Message);
127
LoggingService.Warn("AnalyticsMonitor: " + ex.Message);
131
if (session != null) {
132
session.OnException = MessageService.ShowException;
133
session.AddEnvironmentData(appEnvironmentProperties);
135
sessionOpened = true;
140
UsageDataUploader uploader = new UsageDataUploader(dbFileName, ProductName);
141
uploader.EnvironmentDataForDummySession = appEnvironmentProperties;
142
ThreadPool.QueueUserWorkItem(delegate { uploader.StartUpload(UploadUrl); });
146
static IEnumerable<UsageDataEnvironmentProperty> GetAppProperties()
148
List<UsageDataEnvironmentProperty> properties = new List<UsageDataEnvironmentProperty> {
149
new UsageDataEnvironmentProperty { Name = "appVersion", Value = RevisionClass.Major + "." + RevisionClass.Minor + "." + RevisionClass.Build + "." + RevisionClass.Revision },
150
new UsageDataEnvironmentProperty { Name = "language", Value = ResourceService.Language },
151
new UsageDataEnvironmentProperty { Name = "culture", Value = CultureInfo.CurrentCulture.Name },
152
new UsageDataEnvironmentProperty { Name = "userAddInCount", Value = AddInTree.AddIns.Where(a => !a.IsPreinstalled).Count().ToString() },
153
new UsageDataEnvironmentProperty { Name = "branch", Value = BranchName },
154
new UsageDataEnvironmentProperty { Name = "commit", Value = CommitHash },
155
new UsageDataEnvironmentProperty { Name = "renderingTier", Value = (RenderCapability.Tier >> 16).ToString() }
157
string PROCESSOR_ARCHITECTURE = Environment.GetEnvironmentVariable("PROCESSOR_ARCHITEW6432");
158
if (string.IsNullOrEmpty(PROCESSOR_ARCHITECTURE)) {
159
PROCESSOR_ARCHITECTURE = Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE");
161
if (!string.IsNullOrEmpty(PROCESSOR_ARCHITECTURE)) {
162
properties.Add(new UsageDataEnvironmentProperty { Name = "architecture", Value = PROCESSOR_ARCHITECTURE });
165
properties.Add(new UsageDataEnvironmentProperty { Name = "debug", Value = "true" });
171
/// Retrieves all stored data as XML text.
173
/// <exception cref="IncompatibleDatabaseException">The database version is not compatible with this
174
/// version of the AnalyticsSessionWriter.</exception>
175
public string GetTextForStoredData()
181
UsageDataUploader uploader = new UsageDataUploader(dbFileName, ProductName);
182
return uploader.GetTextForStoredData();
185
void TryDeleteDatabase()
190
File.Delete(dbFileName);
191
} catch (IOException ex) {
192
LoggingService.Warn("AnalyticsMonitor: Could delete database: " + ex.Message);
193
} catch (AccessViolationException ex) {
194
LoggingService.Warn("AnalyticsMonitor: Could delete database: " + ex.Message);
199
public void CloseSession()
202
if (session != null) {
209
public void TrackException(Exception exception)
212
if (session != null) {
213
session.AddException(exception);
218
public IAnalyticsMonitorTrackedFeature TrackFeature(string featureName, string activationMethod)
220
TrackedFeature feature = new TrackedFeature();
222
if (session != null) {
223
feature.Feature = session.AddFeatureUse(featureName, activationMethod);
229
sealed class TrackedFeature : IAnalyticsMonitorTrackedFeature
231
internal FeatureUse Feature;
233
public void EndTracking()
236
Feature.TrackEndTime();
241
public class AutoStartCommand : AbstractCommand
243
public override void Run()
245
if (AnalyticsMonitor.Enabled)
246
AnalyticsMonitor.Instance.OpenSession();