diff --git a/src/AddIns/Misc/UsageDataCollector/AnalyticsMonitor.cs b/src/AddIns/Misc/UsageDataCollector/AnalyticsMonitor.cs
index 24ff5460e0..a885f5d8ca 100644
--- a/src/AddIns/Misc/UsageDataCollector/AnalyticsMonitor.cs
+++ b/src/AddIns/Misc/UsageDataCollector/AnalyticsMonitor.cs
@@ -6,10 +6,11 @@
//
using System;
+using System.IO;
using ICSharpCode.Core;
using ICSharpCode.Core.Services;
using ICSharpCode.SharpDevelop;
-using System.IO;
+using System.Threading;
namespace ICSharpCode.UsageDataCollector
{
@@ -56,15 +57,40 @@ namespace ICSharpCode.UsageDataCollector
public void OpenSession()
{
+ bool sessionOpened = false;
lock (lockObj) {
if (session == null) {
try {
session = new AnalyticsSessionWriter(dbFileName);
- } catch (DatabaseTooNewException) {
- LoggingService.Warn("Could not use AnalyticsMonitor: too new version of database");
+ session.AddEnvironmentData("appVersion", RevisionClass.FullVersion);
+ session.AddEnvironmentData("language", ResourceService.Language);
+ sessionOpened = true;
+ } 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 AnalyticsSessionWriter(dbFileName);
+ } catch (IncompatibleDatabaseException ex2) {
+ LoggingService.Warn("AnalyticsMonitor: Could upgrade database: " + ex2.Message);
+ }
+ } else {
+ LoggingService.Warn("AnalyticsMonitor: " + ex.Message);
+ }
}
}
}
+ if (sessionOpened) {
+ UsageDataUploader uploader = new UsageDataUploader(dbFileName);
+ ThreadPool.QueueUserWorkItem(delegate { uploader.StartUpload(); });
+ }
+ }
+
+ public string GetTextForStoredData()
+ {
+ UsageDataUploader uploader = new UsageDataUploader(dbFileName);
+ return uploader.GetTextForStoredData();
}
void TryDeleteDatabase()
@@ -73,8 +99,10 @@ namespace ICSharpCode.UsageDataCollector
CloseSession();
try {
File.Delete(dbFileName);
- } catch (IOException) {
- } catch (AccessViolationException) {
+ } catch (IOException ex) {
+ LoggingService.Warn("AnalyticsMonitor: Could delete database: " + ex.Message);
+ } catch (AccessViolationException ex) {
+ LoggingService.Warn("AnalyticsMonitor: Could delete database: " + ex.Message);
}
}
}
diff --git a/src/AddIns/Misc/UsageDataCollector/CollectedDataView.xaml b/src/AddIns/Misc/UsageDataCollector/CollectedDataView.xaml
new file mode 100644
index 0000000000..82e95d603f
--- /dev/null
+++ b/src/AddIns/Misc/UsageDataCollector/CollectedDataView.xaml
@@ -0,0 +1,21 @@
+
+
+
+ This window shows the data that was collected but not yet uploaded.
+
+
+
+
\ No newline at end of file
diff --git a/src/AddIns/Misc/UsageDataCollector/CollectedDataView.xaml.cs b/src/AddIns/Misc/UsageDataCollector/CollectedDataView.xaml.cs
new file mode 100644
index 0000000000..b6a53045de
--- /dev/null
+++ b/src/AddIns/Misc/UsageDataCollector/CollectedDataView.xaml.cs
@@ -0,0 +1,41 @@
+//
+//
+//
+//
+// $Revision$
+//
+
+using ICSharpCode.AvalonEdit.Folding;
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using ICSharpCode.AvalonEdit.Highlighting;
+
+namespace ICSharpCode.UsageDataCollector
+{
+ ///
+ /// Interaction logic for CollectedDataView.xaml
+ ///
+ public partial class CollectedDataView : Window
+ {
+ public CollectedDataView(string collectedXml)
+ {
+ InitializeComponent();
+ textEditor.Text = collectedXml;
+ textEditor.SyntaxHighlighting = HighlightingManager.Instance.GetDefinition("XML");
+ FoldingManager foldingManager = FoldingManager.Install(textEditor.TextArea);
+ new XmlFoldingStrategy().UpdateFoldings(foldingManager, textEditor.Document);
+ }
+
+ void Button_Click(object sender, RoutedEventArgs e)
+ {
+ Close();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/AddIns/Misc/UsageDataCollector/OptionPage.xaml b/src/AddIns/Misc/UsageDataCollector/OptionPage.xaml
index c97dd3bae2..539dff3c4b 100644
--- a/src/AddIns/Misc/UsageDataCollector/OptionPage.xaml
+++ b/src/AddIns/Misc/UsageDataCollector/OptionPage.xaml
@@ -6,6 +6,9 @@
I would like to participate - collect and upload usage data
I do not want to participate
+
Privacy Statement
diff --git a/src/AddIns/Misc/UsageDataCollector/OptionPage.xaml.cs b/src/AddIns/Misc/UsageDataCollector/OptionPage.xaml.cs
index af1d3a8950..ae45e52bef 100644
--- a/src/AddIns/Misc/UsageDataCollector/OptionPage.xaml.cs
+++ b/src/AddIns/Misc/UsageDataCollector/OptionPage.xaml.cs
@@ -6,6 +6,11 @@
//
using System;
+using System.Diagnostics;
+using System.Windows;
+using System.Windows.Documents;
+using System.Windows.Navigation;
+
using ICSharpCode.SharpDevelop.Gui;
namespace ICSharpCode.UsageDataCollector
@@ -18,6 +23,7 @@ namespace ICSharpCode.UsageDataCollector
public OptionPage()
{
InitializeComponent();
+ AddHandler(Hyperlink.RequestNavigateEvent, new RequestNavigateEventHandler(OnRequestNavigate));
}
public override void LoadOptions()
@@ -29,6 +35,7 @@ namespace ICSharpCode.UsageDataCollector
else
declineRadio.IsChecked = true;
}
+ showCollectedDataButton.IsEnabled = acceptRadio.IsChecked ?? false;
}
public override bool SaveOptions()
@@ -39,5 +46,21 @@ namespace ICSharpCode.UsageDataCollector
AnalyticsMonitor.Enabled = false;
return base.SaveOptions();
}
+
+ void OnRequestNavigate(object sender, RequestNavigateEventArgs e)
+ {
+ e.Handled = true;
+ try {
+ Process.Start(e.Uri.ToString());
+ } catch {
+ // catch exceptions - e.g. incorrectly installed web browser
+ }
+ }
+
+ void ShowCollectedDataButton_Click(object sender, RoutedEventArgs e)
+ {
+ string data = AnalyticsMonitor.Instance.GetTextForStoredData();
+ (new CollectedDataView(data) { Owner = Window.GetWindow(this), ShowInTaskbar = false }).ShowDialog();
+ }
}
}
\ No newline at end of file
diff --git a/src/AddIns/Misc/UsageDataCollector/UsageDataCollector.csproj b/src/AddIns/Misc/UsageDataCollector/UsageDataCollector.csproj
index 66d47ae583..21277c2567 100644
--- a/src/AddIns/Misc/UsageDataCollector/UsageDataCollector.csproj
+++ b/src/AddIns/Misc/UsageDataCollector/UsageDataCollector.csproj
@@ -43,12 +43,18 @@
3.0
+
+ 3.5
+
..\..\..\Libraries\SQLite\System.Data.SQLite.dll
True
+
+ 3.0
+
@@ -65,7 +71,12 @@
Configuration\GlobalAssemblyInfo.cs
-
+
+ CollectedDataView.xaml
+ Code
+
+
+
OptionPage.xaml
@@ -75,12 +86,19 @@
StartPageMessage.xaml
Code
+
+
+
+ {6C55B776-26D4-4DB3-A6AB-87E783B2F3D1}
+ ICSharpCode.AvalonEdit
+ False
+
{2748AD25-9C63-4E12-877B-4DCE96FBED54}
ICSharpCode.SharpDevelop
diff --git a/src/AddIns/Misc/UsageDataCollector/UsageDataMessage.cs b/src/AddIns/Misc/UsageDataCollector/UsageDataMessage.cs
new file mode 100644
index 0000000000..a639de2c40
--- /dev/null
+++ b/src/AddIns/Misc/UsageDataCollector/UsageDataMessage.cs
@@ -0,0 +1,93 @@
+//
+//
+//
+//
+// $Revision$
+//
+
+using System;
+using System.Collections.Generic;
+using System.Runtime.Serialization;
+
+namespace ICSharpCode.UsageDataCollector
+{
+ [DataContract]
+ sealed class UsageDataMessage
+ {
+ [DataMember]
+ public Guid UserID;
+
+ [DataMember]
+ public List Sessions = new List();
+
+ public UsageDataSession FindSession(long sessionID)
+ {
+ foreach (UsageDataSession s in Sessions) {
+ if (s.SessionID == sessionID)
+ return s;
+ }
+ throw new ArgumentException("Session not found.");
+ }
+ }
+
+ [DataContract]
+ sealed class UsageDataSession
+ {
+ [DataMember]
+ public long SessionID;
+
+ [DataMember]
+ public DateTime StartTime;
+
+ [DataMember]
+ public DateTime? EndTime;
+
+ [DataMember]
+ public List EnvironmentProperties = new List();
+
+ [DataMember]
+ public List FeatureUses = new List();
+
+ [DataMember]
+ public List Exceptions = new List();
+ }
+
+ [DataContract]
+ sealed class UsageDataEnvironmentProperty
+ {
+ [DataMember]
+ public string Name;
+
+ [DataMember]
+ public string Value;
+ }
+
+ [DataContract]
+ sealed class UsageDataFeatureUse
+ {
+ [DataMember]
+ public DateTime Time;
+
+ [DataMember]
+ public DateTime? EndTime;
+
+ [DataMember]
+ public string FeatureName;
+
+ [DataMember]
+ public string ActivationMethod;
+ }
+
+ [DataContract]
+ sealed class UsageDataException
+ {
+ [DataMember]
+ public DateTime Time;
+
+ [DataMember]
+ public string ExceptionType;
+
+ [DataMember]
+ public string StackTrace;
+ }
+}
diff --git a/src/AddIns/Misc/UsageDataCollector/AnalyticsSessionWriter.cs b/src/AddIns/Misc/UsageDataCollector/UsageDataSessionWriter.cs
similarity index 51%
rename from src/AddIns/Misc/UsageDataCollector/AnalyticsSessionWriter.cs
rename to src/AddIns/Misc/UsageDataCollector/UsageDataSessionWriter.cs
index 3e93cd1e8d..1a9fd45d94 100644
--- a/src/AddIns/Misc/UsageDataCollector/AnalyticsSessionWriter.cs
+++ b/src/AddIns/Misc/UsageDataCollector/UsageDataSessionWriter.cs
@@ -5,7 +5,6 @@
// $Revision$
//
-using ICSharpCode.Core;
using System;
using System.Data.SQLite;
using System.Runtime.Serialization;
@@ -20,6 +19,10 @@ namespace ICSharpCode.UsageDataCollector
SQLiteConnection connection;
long sessionID;
+ ///
+ /// Opens/Creates the database and starts writing a new session to it.
+ ///
+ ///
public AnalyticsSessionWriter(string databaseFileName)
{
SQLiteConnectionStringBuilder conn = new SQLiteConnectionStringBuilder();
@@ -27,11 +30,19 @@ namespace ICSharpCode.UsageDataCollector
connection = new SQLiteConnection(conn.ConnectionString);
connection.Open();
- InitializeTables();
-
- StartSession();
+ try {
+ InitializeTables();
+
+ StartSession();
+ } catch {
+ connection.Close();
+ throw;
+ }
}
+
+ static readonly Version expectedDBVersion = new Version(1, 0, 1);
+
///
/// Creates or upgrades the database
///
@@ -44,8 +55,7 @@ namespace ICSharpCode.UsageDataCollector
name TEXT NOT NULL PRIMARY KEY,
value TEXT NOT NULL
);
- INSERT OR IGNORE INTO Properties (name, value) VALUES ('dbVersion', '1.0');
- INSERT OR IGNORE INTO Properties (name, value) VALUES ('userID', '" + Guid.NewGuid().ToString() + @"');
+ INSERT OR IGNORE INTO Properties (name, value) VALUES ('dbVersion', '" + expectedDBVersion.ToString() + @"');
";
cmd.ExecuteNonQuery();
}
@@ -54,24 +64,27 @@ namespace ICSharpCode.UsageDataCollector
string version = (string)cmd.ExecuteScalar();
if (version == null)
throw new InvalidOperationException("Error retrieving database version");
- if (version != "1.0") {
- throw new DatabaseTooNewException();
+ Version actualDBVersion = new Version(version);
+ if (actualDBVersion != expectedDBVersion) {
+ throw new IncompatibleDatabaseException(expectedDBVersion, actualDBVersion);
}
}
using (SQLiteCommand cmd = this.connection.CreateCommand()) {
cmd.CommandText = @"
+ INSERT OR IGNORE INTO Properties (name, value) VALUES ('userID', '" + Guid.NewGuid().ToString() + @"');
+
CREATE TABLE IF NOT EXISTS Sessions (
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
startTime TEXT NOT NULL,
- endTime TEXT,
- appVersion TEXT,
- platform TEXT,
- osVersion TEXT,
- processorCount INTEGER,
- dotnetRuntime TEXT,
- language TEXT
+ endTime TEXT
+ );
+ CREATE TABLE IF NOT EXISTS Environment (
+ session INTEGER NOT NULL,
+ name TEXT NOT NULL,
+ value TEXT
);
CREATE TABLE IF NOT EXISTS FeatureUses (
+ id INTEGER NOT NULL PRIMARY KEY,
session INTEGER NOT NULL,
time TEXT NOT NULL,
endTime TEXT,
@@ -95,33 +108,39 @@ namespace ICSharpCode.UsageDataCollector
{
using (SQLiteTransaction transaction = this.connection.BeginTransaction()) {
using (SQLiteCommand cmd = this.connection.CreateCommand()) {
- cmd.CommandText = "INSERT INTO Sessions (startTime, appVersion, platform, osVersion, processorCount, dotnetRuntime, language)" +
- " VALUES (datetime(), ?, ?, ?, ?, ?, ?);";
- cmd.Parameters.Add(new SQLiteParameter { Value = RevisionClass.FullVersion });
- cmd.Parameters.Add(new SQLiteParameter { Value = Environment.OSVersion.Platform.ToString() });
- cmd.Parameters.Add(new SQLiteParameter { Value = Environment.OSVersion.Version.ToString() });
- cmd.Parameters.Add(new SQLiteParameter { Value = Environment.ProcessorCount });
- cmd.Parameters.Add(new SQLiteParameter { Value = Environment.Version.ToString() });
- cmd.Parameters.Add(new SQLiteParameter { Value = ICSharpCode.Core.ResourceService.Language });
+ cmd.CommandText = "INSERT INTO Sessions (startTime) VALUES (datetime());";
cmd.ExecuteNonQuery();
}
using (SQLiteCommand cmd = this.connection.CreateCommand()) {
cmd.CommandText = "SELECT last_insert_rowid();";
sessionID = (long)cmd.ExecuteScalar();
}
+ AddEnvironmentData("platform", Environment.OSVersion.Platform.ToString());
+ AddEnvironmentData("osVersion", Environment.OSVersion.Version.ToString());
+ AddEnvironmentData("processorCount", Environment.ProcessorCount.ToString());
+ AddEnvironmentData("dotnetRuntime", Environment.Version.ToString());
transaction.Commit();
}
}
void EndSession()
{
- using (SQLiteTransaction transaction = this.connection.BeginTransaction()) {
- using (SQLiteCommand cmd = this.connection.CreateCommand()) {
- cmd.CommandText = "UPDATE Sessions SET endTime = datetime() WHERE id = ?;";
- cmd.Parameters.Add(new SQLiteParameter { Value = sessionID });
- cmd.ExecuteNonQuery();
- }
- transaction.Commit();
+ using (SQLiteCommand cmd = this.connection.CreateCommand()) {
+ cmd.CommandText = "UPDATE Sessions SET endTime = datetime() WHERE id = ?;";
+ cmd.Parameters.Add(new SQLiteParameter { Value = sessionID });
+ cmd.ExecuteNonQuery();
+ }
+ }
+
+ public void AddEnvironmentData(string name, string value)
+ {
+ using (SQLiteCommand cmd = this.connection.CreateCommand()) {
+ cmd.CommandText = "INSERT INTO Environment (session, name, value)" +
+ " VALUES (?, ?, ?);";
+ cmd.Parameters.Add(new SQLiteParameter { Value = sessionID });
+ cmd.Parameters.Add(new SQLiteParameter { Value = name });
+ cmd.Parameters.Add(new SQLiteParameter { Value = value });
+ cmd.ExecuteNonQuery();
}
}
@@ -148,28 +167,22 @@ namespace ICSharpCode.UsageDataCollector
public void WriteEndTimeForFeature(long featureID)
{
- using (SQLiteTransaction transaction = this.connection.BeginTransaction()) {
- using (SQLiteCommand cmd = this.connection.CreateCommand()) {
- cmd.CommandText = "UPDATE FeatureUses SET endTime = datetime() WHERE ROWID = ?;";
- cmd.Parameters.Add(new SQLiteParameter { Value = featureID });
- cmd.ExecuteNonQuery();
- }
- transaction.Commit();
+ using (SQLiteCommand cmd = this.connection.CreateCommand()) {
+ cmd.CommandText = "UPDATE FeatureUses SET endTime = datetime() WHERE id = ?;";
+ cmd.Parameters.Add(new SQLiteParameter { Value = featureID });
+ cmd.ExecuteNonQuery();
}
}
public void AddException(string exceptionType, string stacktrace)
{
- using (SQLiteTransaction transaction = this.connection.BeginTransaction()) {
- using (SQLiteCommand cmd = this.connection.CreateCommand()) {
- cmd.CommandText = "INSERT INTO Exceptions (session, time, type, stackTrace)" +
- " VALUES (?, datetime(), ?, ?);";
- cmd.Parameters.Add(new SQLiteParameter { Value = sessionID });
- cmd.Parameters.Add(new SQLiteParameter { Value = exceptionType });
- cmd.Parameters.Add(new SQLiteParameter { Value = stacktrace });
- cmd.ExecuteNonQuery();
- }
- transaction.Commit();
+ using (SQLiteCommand cmd = this.connection.CreateCommand()) {
+ cmd.CommandText = "INSERT INTO Exceptions (session, time, type, stackTrace)" +
+ " VALUES (?, datetime(), ?, ?);";
+ cmd.Parameters.Add(new SQLiteParameter { Value = sessionID });
+ cmd.Parameters.Add(new SQLiteParameter { Value = exceptionType });
+ cmd.Parameters.Add(new SQLiteParameter { Value = stacktrace });
+ cmd.ExecuteNonQuery();
}
}
@@ -181,9 +194,35 @@ namespace ICSharpCode.UsageDataCollector
}
[Serializable]
- public class DatabaseTooNewException : Exception
+ public class IncompatibleDatabaseException : Exception
{
- public DatabaseTooNewException() {}
- protected DatabaseTooNewException(SerializationInfo info, StreamingContext context) : base(info, context) {}
+ public Version ExpectedVersion { get; set; }
+ public Version ActualVersion { get; set; }
+
+ public IncompatibleDatabaseException() {}
+
+ public IncompatibleDatabaseException(Version expectedVersion, Version actualVersion)
+ : base("Expected DB version " + expectedVersion + " but found " + actualVersion)
+ {
+ this.ExpectedVersion = expectedVersion;
+ this.ActualVersion = actualVersion;
+ }
+
+ protected IncompatibleDatabaseException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ if (info != null) {
+ this.ExpectedVersion = (Version)info.GetValue("ExpectedVersion", typeof(Version));
+ this.ActualVersion = (Version)info.GetValue("ActualVersion", typeof(Version));
+ }
+ }
+
+ public override void GetObjectData(SerializationInfo info, StreamingContext context)
+ {
+ base.GetObjectData(info, context);
+ if (info != null) {
+ info.AddValue("ExpectedVersion", this.ExpectedVersion, typeof(Version));
+ info.AddValue("ActualVersion", this.ActualVersion, typeof(Version));
+ }
+ }
}
}
\ No newline at end of file
diff --git a/src/AddIns/Misc/UsageDataCollector/UsageDataUploader.cs b/src/AddIns/Misc/UsageDataCollector/UsageDataUploader.cs
new file mode 100644
index 0000000000..233c47bad6
--- /dev/null
+++ b/src/AddIns/Misc/UsageDataCollector/UsageDataUploader.cs
@@ -0,0 +1,208 @@
+//
+//
+//
+//
+// $Revision$
+//
+
+using System;
+using System.Collections.Generic;
+using System.Data.SQLite;
+using System.Globalization;
+using System.IO;
+using System.IO.Compression;
+using System.Linq;
+using System.Runtime.Serialization;
+using System.Xml;
+
+namespace ICSharpCode.UsageDataCollector
+{
+ ///
+ /// Allows uploading collected data.
+ ///
+ public class UsageDataUploader
+ {
+ string databaseFileName;
+
+ public UsageDataUploader(string databaseFileName)
+ {
+ this.databaseFileName = databaseFileName;
+ }
+
+ SQLiteConnection OpenConnection()
+ {
+ SQLiteConnectionStringBuilder conn = new SQLiteConnectionStringBuilder();
+ conn.Add("Data Source", databaseFileName);
+ SQLiteConnection connection = new SQLiteConnection(conn.ConnectionString);
+ connection.Open();
+ return connection;
+ }
+
+ ///
+ /// Starts the upload of the usage data.
+ ///
+ public void StartUpload()
+ {
+ UsageDataMessage message = GetDataToBeTransmitted(false);
+ DataContractSerializer serializer = new DataContractSerializer(typeof(UsageDataMessage));
+ using (FileStream fs = new FileStream(Path.Combine(Path.GetTempPath(), "SharpDevelopUsageData.xml.gz"), FileMode.Create, FileAccess.Write)) {
+ using (GZipStream zip = new GZipStream(fs, CompressionMode.Compress)) {
+ serializer.WriteObject(zip, message);
+ }
+ }
+ }
+
+ internal UsageDataMessage GetDataToBeTransmitted(bool fetchIncompleteSessions)
+ {
+ using (SQLiteConnection connection = OpenConnection()) {
+ using (SQLiteTransaction transaction = connection.BeginTransaction()) {
+ return FetchDataForUpload(connection, fetchIncompleteSessions);
+ }
+ }
+ }
+
+ public string GetTextForStoredData()
+ {
+ UsageDataMessage message = GetDataToBeTransmitted(true);
+ using (StringWriter w = new StringWriter()) {
+ using (XmlTextWriter xmlWriter = new XmlTextWriter(w)) {
+ xmlWriter.Formatting = Formatting.Indented;
+ DataContractSerializer serializer = new DataContractSerializer(typeof(UsageDataMessage));
+ serializer.WriteObject(xmlWriter, message);
+ }
+ return w.ToString();
+ }
+ }
+
+ #region FetchDataForUpload
+ Version expectedDBVersion = new Version(1, 0, 1);
+
+ UsageDataMessage FetchDataForUpload(SQLiteConnection connection, bool fetchIncompleteSessions)
+ {
+ // Check the database version
+ using (SQLiteCommand cmd = connection.CreateCommand()) {
+ cmd.CommandText = "SELECT value FROM Properties WHERE name = 'dbVersion';";
+ string version = (string)cmd.ExecuteScalar();
+ if (version == null)
+ throw new InvalidOperationException("Error retrieving database version");
+ Version actualDBVersion = new Version(version);
+ if (actualDBVersion != expectedDBVersion) {
+ throw new IncompatibleDatabaseException(expectedDBVersion, actualDBVersion);
+ }
+ }
+
+ UsageDataMessage message = new UsageDataMessage();
+ // Retrieve the User ID
+ using (SQLiteCommand cmd = connection.CreateCommand()) {
+ cmd.CommandText = "SELECT value FROM Properties WHERE name = 'userID';";
+ string userID = (string)cmd.ExecuteScalar();
+ message.UserID = new Guid(userID);
+ }
+
+ // Retrieve the list of sessions
+ using (SQLiteCommand cmd = connection.CreateCommand()) {
+ if (fetchIncompleteSessions) {
+ cmd.CommandText = @"SELECT id, startTime, endTime FROM Sessions;";
+ } else {
+ // Fetch all sessions which are either closed or inactive for more than 24 hours
+ cmd.CommandText = @"SELECT id, startTime, endTime FROM Sessions
+ WHERE (endTime IS NOT NULL)
+ OR (ifnull((SELECT max(time) FROM FeatureUses WHERE FeatureUses.session = Sessions.id), Sessions.startTime)
+ < datetime('now','-1 day'));";
+ }
+ using (SQLiteDataReader reader = cmd.ExecuteReader()) {
+ while (reader.Read()) {
+ UsageDataSession session = new UsageDataSession();
+ session.SessionID = reader.GetInt64(0);
+ session.StartTime = reader.GetDateTime(1);
+ if (!reader.IsDBNull(2))
+ session.EndTime = reader.GetDateTime(2);
+ message.Sessions.Add(session);
+ }
+ }
+ }
+ string commaSeparatedSessionIDList = GetCommaSeparatedIDList(message.Sessions);
+
+ StringInterner stringInterning = new StringInterner();
+ // Retrieve the environment
+ using (SQLiteCommand cmd = connection.CreateCommand()) {
+ cmd.CommandText = "SELECT session, name, value FROM Environment WHERE session IN (" + commaSeparatedSessionIDList + ");";
+ using (SQLiteDataReader reader = cmd.ExecuteReader()) {
+ while (reader.Read()) {
+ long sessionID = reader.GetInt64(0);
+ UsageDataSession session = message.FindSession(sessionID);
+ session.EnvironmentProperties.Add(
+ new UsageDataEnvironmentProperty {
+ Name = stringInterning.Intern(reader.GetString(1)),
+ Value = stringInterning.Intern(reader.GetString(2))
+ });
+ }
+ }
+ }
+
+ // Retrieve the feature uses
+ using (SQLiteCommand cmd = connection.CreateCommand()) {
+ cmd.CommandText = "SELECT session, time, endTime, feature, activationMethod FROM FeatureUses WHERE session IN (" + commaSeparatedSessionIDList + ");";
+ using (SQLiteDataReader reader = cmd.ExecuteReader()) {
+ while (reader.Read()) {
+ long sessionID = reader.GetInt64(0);
+ UsageDataSession session = message.FindSession(sessionID);
+ UsageDataFeatureUse featureUse = new UsageDataFeatureUse();
+ featureUse.Time = reader.GetDateTime(1);
+ if (!reader.IsDBNull(2))
+ featureUse.EndTime = reader.GetDateTime(2);
+ featureUse.FeatureName = stringInterning.Intern(reader.GetString(3));
+ featureUse.ActivationMethod = stringInterning.Intern(reader.GetString(4));
+ session.FeatureUses.Add(featureUse);
+ }
+ }
+ }
+
+ // Retrieve the exceptions
+ using (SQLiteCommand cmd = connection.CreateCommand()) {
+ cmd.CommandText = "SELECT session, time, type, stackTrace FROM Exceptions WHERE session IN (" + commaSeparatedSessionIDList + ");";
+ using (SQLiteDataReader reader = cmd.ExecuteReader()) {
+ while (reader.Read()) {
+ long sessionID = reader.GetInt64(0);
+ UsageDataSession session = message.FindSession(sessionID);
+ UsageDataException exception = new UsageDataException();
+ exception.Time = reader.GetDateTime(1);
+ exception.ExceptionType = stringInterning.Intern(reader.GetString(2));
+ exception.StackTrace = stringInterning.Intern(reader.GetString(3));
+ session.Exceptions.Add(exception);
+ }
+ }
+ }
+
+ return message;
+ }
+ #endregion
+
+ string GetCommaSeparatedIDList(IEnumerable sessions)
+ {
+ return string.Join(
+ ",",
+ sessions.Select(s => s.SessionID.ToString(CultureInfo.InvariantCulture)).ToArray());
+ }
+
+ ///
+ /// Helps keep the memory usage during data preparation down (there are lots of duplicate strings, and we don't
+ /// want to keep them in RAM repeatedly).
+ ///
+ sealed class StringInterner
+ {
+ Dictionary cache = new Dictionary();
+
+ public string Intern(string input)
+ {
+ if (input != null) {
+ string result;
+ if (cache.TryGetValue(input, out result))
+ return result;
+ cache.Add(input, input);
+ }
+ return input;
+ }
+ }
+ }
+}