//
//
//
//
// $Revision$
//
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
using ICSharpCode.Core;
using ICSharpCode.Core.Services;
using ICSharpCode.SharpDevelop;
using ICSharpCode.UsageDataCollector.Contracts;
namespace ICSharpCode.UsageDataCollector
{
///
/// Main singleton class of the analytics. This class is thread-safe.
///
public sealed class AnalyticsMonitor : IAnalyticsMonitor
{
const string UploadUrl = "http://usagedatacollector.sharpdevelop.net/upload/UploadUsageData.svc";
public static readonly Uri PrivacyStatementUrl = new Uri("http://www.icsharpcode.net/OpenSource/SD/UsageDataCollector/");
public static readonly AnalyticsMonitor Instance = new AnalyticsMonitor();
public static bool EnabledIsUndecided {
get {
return string.IsNullOrEmpty(PropertyService.Get("ICSharpCode.UsageDataCollector.Enabled"));
}
}
///
/// Allows to enable/disable the usage data monitoring.
///
public static bool Enabled {
get {
return string.Equals(PropertyService.Get("ICSharpCode.UsageDataCollector.Enabled"), bool.TrueString, StringComparison.OrdinalIgnoreCase);
}
set {
PropertyService.Set("ICSharpCode.UsageDataCollector.Enabled", value.ToString());
// Initially opening the session takes some time; which is bad for the startpage
// because the animation would start with a delay. We solve this by calling Open/CloseSession
// on a background thread.
ThreadPool.QueueUserWorkItem(delegate { AsyncEnableDisable(); } );
}
}
static void AsyncEnableDisable()
{
if (Enabled) {
Instance.OpenSession();
} else {
Instance.CloseSession();
Instance.TryDeleteDatabase();
}
}
readonly object lockObj = new object();
string dbFileName;
UsageDataSessionWriter session;
private AnalyticsMonitor()
{
var container = ServiceManager.Instance.GetRequiredService();
container.TryAddService(typeof(IAnalyticsMonitor), this);
dbFileName = Path.Combine(PropertyService.ConfigDirectory, "usageData.dat");
SharpDevelop.Gui.WorkbenchSingleton.WorkbenchUnloaded += delegate { CloseSession(); };
}
///
/// Opens the database connection, updates the database if required.
/// Will start an upload to the server, if required.
///
public void OpenSession()
{
IEnumerable appEnvironmentProperties = GetAppProperties();
bool sessionOpened = false;
lock (lockObj) {
if (session == null) {
try {
session = new UsageDataSessionWriter(dbFileName);
} catch (IncompatibleDatabaseException ex) {
if (ex.ActualVersion < ex.ExpectedVersion) {
LoggingService.Info("AnalyticsMonitor: " + ex.Message + ", removing old database");
// upgrade database by deleting the old one
TryDeleteDatabase();
try {
session = new UsageDataSessionWriter(dbFileName);
} catch (IncompatibleDatabaseException ex2) {
LoggingService.Warn("AnalyticsMonitor: Could upgrade database: " + ex2.Message);
}
} else {
LoggingService.Warn("AnalyticsMonitor: " + ex.Message);
}
}
if (session != null) {
session.OnException = MessageService.ShowException;
session.AddEnvironmentData(appEnvironmentProperties);
sessionOpened = true;
}
}
}
if (sessionOpened) {
UsageDataUploader uploader = new UsageDataUploader(dbFileName);
uploader.EnvironmentDataForDummySession = appEnvironmentProperties;
ThreadPool.QueueUserWorkItem(delegate { uploader.StartUpload(UploadUrl); });
}
}
static IEnumerable GetAppProperties()
{
List properties = new List {
new UsageDataEnvironmentProperty { Name = "appVersion", Value = RevisionClass.FullVersion },
new UsageDataEnvironmentProperty { Name = "language", Value = ResourceService.Language },
new UsageDataEnvironmentProperty { Name = "culture", Value = CultureInfo.CurrentCulture.Name },
new UsageDataEnvironmentProperty { Name = "userAddInCount", Value = AddInTree.AddIns.Where(a => !a.IsPreinstalled).Count().ToString() }
};
string PROCESSOR_ARCHITECTURE = Environment.GetEnvironmentVariable("PROCESSOR_ARCHITEW6432");
if (string.IsNullOrEmpty(PROCESSOR_ARCHITECTURE)) {
PROCESSOR_ARCHITECTURE = Environment.GetEnvironmentVariable("PROCESSOR_ARCHITECTURE");
}
if (!string.IsNullOrEmpty(PROCESSOR_ARCHITECTURE)) {
properties.Add(new UsageDataEnvironmentProperty { Name = "architecture", Value = PROCESSOR_ARCHITECTURE });
}
#if DEBUG
properties.Add(new UsageDataEnvironmentProperty { Name = "debug", Value = "true" });
#endif
return properties;
}
///
/// Retrieves all stored data as XML text.
///
/// The database version is not compatible with this
/// version of the AnalyticsSessionWriter.
public string GetTextForStoredData()
{
lock (lockObj) {
if (session != null)
session.Flush();
}
UsageDataUploader uploader = new UsageDataUploader(dbFileName);
return uploader.GetTextForStoredData();
}
void TryDeleteDatabase()
{
lock (lockObj) {
CloseSession();
try {
File.Delete(dbFileName);
} catch (IOException ex) {
LoggingService.Warn("AnalyticsMonitor: Could delete database: " + ex.Message);
} catch (AccessViolationException ex) {
LoggingService.Warn("AnalyticsMonitor: Could delete database: " + ex.Message);
}
}
}
public void CloseSession()
{
lock (lockObj) {
if (session != null) {
session.Dispose();
session = null;
}
}
}
public void TrackException(Exception exception)
{
lock (lockObj) {
if (session != null) {
session.AddException(exception);
}
}
}
public IAnalyticsMonitorTrackedFeature TrackFeature(string featureName, string activationMethod)
{
TrackedFeature feature = new TrackedFeature();
lock (lockObj) {
if (session != null) {
feature.Feature = session.AddFeatureUse(featureName, activationMethod);
}
}
return feature;
}
sealed class TrackedFeature : IAnalyticsMonitorTrackedFeature
{
internal FeatureUse Feature;
public void EndTracking()
{
if (Feature != null)
Feature.TrackEndTime();
}
}
}
public class AutoStartCommand : AbstractCommand
{
public override void Run()
{
if (AnalyticsMonitor.Enabled)
AnalyticsMonitor.Instance.OpenSession();
}
}
}