Browse Source

Worked on UsageDataCollector.

git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@4919 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61
shortcuts
Daniel Grunwald 17 years ago
parent
commit
49178eb1e5
  1. 38
      src/AddIns/Misc/UsageDataCollector/AnalyticsMonitor.cs
  2. 21
      src/AddIns/Misc/UsageDataCollector/CollectedDataView.xaml
  3. 41
      src/AddIns/Misc/UsageDataCollector/CollectedDataView.xaml.cs
  4. 3
      src/AddIns/Misc/UsageDataCollector/OptionPage.xaml
  5. 23
      src/AddIns/Misc/UsageDataCollector/OptionPage.xaml.cs
  6. 20
      src/AddIns/Misc/UsageDataCollector/UsageDataCollector.csproj
  7. 93
      src/AddIns/Misc/UsageDataCollector/UsageDataMessage.cs
  8. 139
      src/AddIns/Misc/UsageDataCollector/UsageDataSessionWriter.cs
  9. 208
      src/AddIns/Misc/UsageDataCollector/UsageDataUploader.cs

38
src/AddIns/Misc/UsageDataCollector/AnalyticsMonitor.cs

@ -6,10 +6,11 @@ @@ -6,10 +6,11 @@
// </file>
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 @@ -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 @@ -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);
}
}
}

21
src/AddIns/Misc/UsageDataCollector/CollectedDataView.xaml

@ -0,0 +1,21 @@ @@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<Window
x:Class="ICSharpCode.UsageDataCollector.CollectedDataView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:core="http://icsharpcode.net/sharpdevelop/core" xmlns:avalonEdit="http://icsharpcode.net/sharpdevelop/avalonedit"
Title="Collected Data"
Width="649"
Height="418">
<DockPanel>
<TextBlock
DockPanel.Dock="Top">This window shows the data that was collected but not yet uploaded.</TextBlock>
<Button
DockPanel.Dock="Bottom"
Width="75"
Height="23"
Content="{core:Localize Global.CloseButtonText}"
Margin="16,8,16,8"
Click="Button_Click" />
<avalonEdit:TextEditor
Name="textEditor"
IsReadOnly="True" />
</DockPanel>
</Window>

41
src/AddIns/Misc/UsageDataCollector/CollectedDataView.xaml.cs

@ -0,0 +1,41 @@ @@ -0,0 +1,41 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Daniel Grunwald"/>
// <version>$Revision$</version>
// </file>
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
{
/// <summary>
/// Interaction logic for CollectedDataView.xaml
/// </summary>
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();
}
}
}

3
src/AddIns/Misc/UsageDataCollector/OptionPage.xaml

@ -6,6 +6,9 @@ @@ -6,6 +6,9 @@
</TextBlock>
<RadioButton Name="acceptRadio">I would like to participate - collect and upload usage data</RadioButton>
<RadioButton Name="declineRadio">I do not want to participate</RadioButton>
<Button Name="showCollectedDataButton" Click="ShowCollectedDataButton_Click" Margin="4" HorizontalAlignment="Left">
Show collected data
</Button>
<TextBlock HorizontalAlignment="Left">
<Hyperlink NavigateUri="http://www.example.com/UsageData">Privacy Statement</Hyperlink>
</TextBlock>

23
src/AddIns/Misc/UsageDataCollector/OptionPage.xaml.cs

@ -6,6 +6,11 @@ @@ -6,6 +6,11 @@
// </file>
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 @@ -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 @@ -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 @@ -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();
}
}
}

20
src/AddIns/Misc/UsageDataCollector/UsageDataCollector.csproj

@ -43,12 +43,18 @@ @@ -43,12 +43,18 @@
<RequiredTargetFramework>3.0</RequiredTargetFramework>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core">
<RequiredTargetFramework>3.5</RequiredTargetFramework>
</Reference>
<Reference Include="System.Data" />
<Reference Include="System.Data.SQLite">
<HintPath>..\..\..\Libraries\SQLite\System.Data.SQLite.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Drawing" />
<Reference Include="System.Runtime.Serialization">
<RequiredTargetFramework>3.0</RequiredTargetFramework>
</Reference>
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml" />
<Reference Include="WindowsBase">
@ -65,7 +71,12 @@ @@ -65,7 +71,12 @@
<Link>Configuration\GlobalAssemblyInfo.cs</Link>
</Compile>
<Compile Include="AnalyticsMonitor.cs" />
<Compile Include="AnalyticsSessionWriter.cs" />
<Compile Include="CollectedDataView.xaml.cs">
<DependentUpon>CollectedDataView.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="UsageDataMessage.cs" />
<Compile Include="UsageDataSessionWriter.cs" />
<Compile Include="Configuration\AssemblyInfo.cs" />
<Compile Include="OptionPage.xaml.cs">
<DependentUpon>OptionPage.xaml</DependentUpon>
@ -75,12 +86,19 @@ @@ -75,12 +86,19 @@
<DependentUpon>StartPageMessage.xaml</DependentUpon>
<SubType>Code</SubType>
</Compile>
<Compile Include="UsageDataUploader.cs" />
</ItemGroup>
<ItemGroup>
<Page Include="CollectedDataView.xaml" />
<Page Include="OptionPage.xaml" />
<Page Include="StartPageMessage.xaml" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\Libraries\AvalonEdit\ICSharpCode.AvalonEdit\ICSharpCode.AvalonEdit.csproj">
<Project>{6C55B776-26D4-4DB3-A6AB-87E783B2F3D1}</Project>
<Name>ICSharpCode.AvalonEdit</Name>
<Private>False</Private>
</ProjectReference>
<ProjectReference Include="..\..\..\Main\Base\Project\ICSharpCode.SharpDevelop.csproj">
<Project>{2748AD25-9C63-4E12-877B-4DCE96FBED54}</Project>
<Name>ICSharpCode.SharpDevelop</Name>

93
src/AddIns/Misc/UsageDataCollector/UsageDataMessage.cs

@ -0,0 +1,93 @@ @@ -0,0 +1,93 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Daniel Grunwald"/>
// <version>$Revision$</version>
// </file>
using System;
using System.Collections.Generic;
using System.Runtime.Serialization;
namespace ICSharpCode.UsageDataCollector
{
[DataContract]
sealed class UsageDataMessage
{
[DataMember]
public Guid UserID;
[DataMember]
public List<UsageDataSession> Sessions = new List<UsageDataSession>();
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<UsageDataEnvironmentProperty> EnvironmentProperties = new List<UsageDataEnvironmentProperty>();
[DataMember]
public List<UsageDataFeatureUse> FeatureUses = new List<UsageDataFeatureUse>();
[DataMember]
public List<UsageDataException> Exceptions = new List<UsageDataException>();
}
[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;
}
}

139
src/AddIns/Misc/UsageDataCollector/AnalyticsSessionWriter.cs → src/AddIns/Misc/UsageDataCollector/UsageDataSessionWriter.cs

@ -5,7 +5,6 @@ @@ -5,7 +5,6 @@
// <version>$Revision$</version>
// </file>
using ICSharpCode.Core;
using System;
using System.Data.SQLite;
using System.Runtime.Serialization;
@ -20,6 +19,10 @@ namespace ICSharpCode.UsageDataCollector @@ -20,6 +19,10 @@ namespace ICSharpCode.UsageDataCollector
SQLiteConnection connection;
long sessionID;
/// <summary>
/// Opens/Creates the database and starts writing a new session to it.
/// </summary>
/// <exception cref="IncompatibleDatabaseException"></exception>
public AnalyticsSessionWriter(string databaseFileName)
{
SQLiteConnectionStringBuilder conn = new SQLiteConnectionStringBuilder();
@ -27,11 +30,19 @@ namespace ICSharpCode.UsageDataCollector @@ -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);
/// <summary>
/// Creates or upgrades the database
/// </summary>
@ -44,8 +55,7 @@ namespace ICSharpCode.UsageDataCollector @@ -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 @@ -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 @@ -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 @@ -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 @@ -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));
}
}
}
}

208
src/AddIns/Misc/UsageDataCollector/UsageDataUploader.cs

@ -0,0 +1,208 @@ @@ -0,0 +1,208 @@
// <file>
// <copyright see="prj:///doc/copyright.txt"/>
// <license see="prj:///doc/license.txt"/>
// <owner name="Daniel Grunwald"/>
// <version>$Revision$</version>
// </file>
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
{
/// <summary>
/// Allows uploading collected data.
/// </summary>
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;
}
/// <summary>
/// Starts the upload of the usage data.
/// </summary>
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<UsageDataSession> sessions)
{
return string.Join(
",",
sessions.Select(s => s.SessionID.ToString(CultureInfo.InvariantCulture)).ToArray());
}
/// <summary>
/// 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).
/// </summary>
sealed class StringInterner
{
Dictionary<string, string> cache = new Dictionary<string, string>();
public string Intern(string input)
{
if (input != null) {
string result;
if (cache.TryGetValue(input, out result))
return result;
cache.Add(input, input);
}
return input;
}
}
}
}
Loading…
Cancel
Save