Browse Source
git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/trunk@4919 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61shortcuts
9 changed files with 530 additions and 56 deletions
@ -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> |
||||||
@ -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(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -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; |
||||||
|
} |
||||||
|
} |
||||||
@ -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…
Reference in new issue