diff --git a/src/AddIns/Misc/UsageDataCollector/AnalyticsMonitor.cs b/src/AddIns/Misc/UsageDataCollector/AnalyticsMonitor.cs
index 1a75258a00..b1176c7855 100644
--- a/src/AddIns/Misc/UsageDataCollector/AnalyticsMonitor.cs
+++ b/src/AddIns/Misc/UsageDataCollector/AnalyticsMonitor.cs
@@ -27,6 +27,9 @@ namespace ICSharpCode.UsageDataCollector
}
}
+ ///
+ /// 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);
@@ -52,7 +55,7 @@ namespace ICSharpCode.UsageDataCollector
readonly object lockObj = new object();
string dbFileName;
- AnalyticsSessionWriter session;
+ UsageDataSessionWriter session;
private AnalyticsMonitor()
{
@@ -63,13 +66,17 @@ namespace ICSharpCode.UsageDataCollector
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()
{
bool sessionOpened = false;
lock (lockObj) {
if (session == null) {
try {
- session = new AnalyticsSessionWriter(dbFileName);
+ session = new UsageDataSessionWriter(dbFileName);
session.AddEnvironmentData("appVersion", RevisionClass.FullVersion);
session.AddEnvironmentData("language", ResourceService.Language);
sessionOpened = true;
@@ -79,7 +86,7 @@ namespace ICSharpCode.UsageDataCollector
// upgrade database by deleting the old one
TryDeleteDatabase();
try {
- session = new AnalyticsSessionWriter(dbFileName);
+ session = new UsageDataSessionWriter(dbFileName);
} catch (IncompatibleDatabaseException ex2) {
LoggingService.Warn("AnalyticsMonitor: Could upgrade database: " + ex2.Message);
}
@@ -95,6 +102,11 @@ namespace ICSharpCode.UsageDataCollector
}
}
+ ///
+ /// Retrieves all stored data as XML text.
+ ///
+ /// The database version is not compatible with this
+ /// version of the AnalyticsSessionWriter.
public string GetTextForStoredData()
{
UsageDataUploader uploader = new UsageDataUploader(dbFileName);
diff --git a/src/AddIns/Misc/UsageDataCollector/UsageDataMessage.cs b/src/AddIns/Misc/UsageDataCollector/UsageDataMessage.cs
index a639de2c40..c67520149d 100644
--- a/src/AddIns/Misc/UsageDataCollector/UsageDataMessage.cs
+++ b/src/AddIns/Misc/UsageDataCollector/UsageDataMessage.cs
@@ -11,9 +11,16 @@ using System.Runtime.Serialization;
namespace ICSharpCode.UsageDataCollector
{
+ ///
+ /// Represents a message used to send data to the server.
+ ///
[DataContract]
sealed class UsageDataMessage
{
+ ///
+ /// Unique identifier per user.
+ /// We need this to distinguish between 1 user using a feature 100 times, or 100 users using a feature 1 time.
+ ///
[DataMember]
public Guid UserID;
@@ -33,12 +40,23 @@ namespace ICSharpCode.UsageDataCollector
[DataContract]
sealed class UsageDataSession
{
+ ///
+ /// ID of the session, usually unique per user (unless the user restores a backup of the database).
+ /// Could be used to detect duplicate uploads.
+ ///
[DataMember]
public long SessionID;
+ ///
+ /// Timestamp when the session was started.
+ ///
[DataMember]
public DateTime StartTime;
+ ///
+ /// Timestamp when the session finished.
+ /// Nullable because the end time might not be recorded if the application crashed.
+ ///
[DataMember]
public DateTime? EndTime;
@@ -52,6 +70,11 @@ namespace ICSharpCode.UsageDataCollector
public List Exceptions = new List();
}
+ ///
+ /// A property storing a value about the environment (App version, OS Version etc.).
+ /// These are stored per session because the user might use different versions of an application in parallel,
+ /// or have multiple OS installations pointing to the same database (e.g. when SharpDevelop is used on an USB stick).
+ ///
[DataContract]
sealed class UsageDataEnvironmentProperty
{
@@ -62,31 +85,62 @@ namespace ICSharpCode.UsageDataCollector
public string Value;
}
+ ///
+ /// Represents a feature being used.
+ ///
[DataContract]
sealed class UsageDataFeatureUse
{
+ ///
+ /// The time when the feature was used.
+ ///
[DataMember]
public DateTime Time;
+ ///
+ /// End time of the feature use. Nullable because for some features, the no time span will be recorded.
+ /// Also, for features where the time span usually is recorded, crashes might cause the end time to be missing
+ /// in some cases.
+ ///
[DataMember]
public DateTime? EndTime;
+ ///
+ /// Name of the feature.
+ ///
[DataMember]
public string FeatureName;
+ ///
+ /// How the feature was activated (Menu, Toolbar, Shortcut, etc.)
+ ///
[DataMember]
public string ActivationMethod;
}
+ ///
+ /// Represents an exception that was recorded.
+ /// Only ExceptionType and StackTrace are available because those don't contain personal information.
+ /// The exception message might e.g. contain names of the file the user is working on.
+ ///
[DataContract]
sealed class UsageDataException
{
+ ///
+ /// The time when the exception occurred.
+ ///
[DataMember]
public DateTime Time;
+ ///
+ /// The fully qualified typename of the exception.
+ ///
[DataMember]
public string ExceptionType;
+ ///
+ /// The stack trace of the exception. The 'at'/'in' words are potentially localized.
+ ///
[DataMember]
public string StackTrace;
}
diff --git a/src/AddIns/Misc/UsageDataCollector/UsageDataSessionWriter.cs b/src/AddIns/Misc/UsageDataCollector/UsageDataSessionWriter.cs
index e0d56b4746..6844e59829 100644
--- a/src/AddIns/Misc/UsageDataCollector/UsageDataSessionWriter.cs
+++ b/src/AddIns/Misc/UsageDataCollector/UsageDataSessionWriter.cs
@@ -12,9 +12,13 @@ using System.Runtime.Serialization;
namespace ICSharpCode.UsageDataCollector
{
///
- /// Creates an analytics session.
+ /// Creates a usage data session.
+ ///
+ /// Instance methods on this class are not thread-safe. If you are using it in a multi-threaded application,
+ /// you should consider writing your own wrapper class that takes care of the thread-safety.
+ /// In SharpDevelop, this is done by the AnalyticsMonitor class.
///
- public class AnalyticsSessionWriter : IDisposable
+ public class UsageDataSessionWriter : IDisposable
{
SQLiteConnection connection;
long sessionID;
@@ -22,8 +26,9 @@ namespace ICSharpCode.UsageDataCollector
///
/// Opens/Creates the database and starts writing a new session to it.
///
- ///
- public AnalyticsSessionWriter(string databaseFileName)
+ /// The database version is not compatible with this
+ /// version of the AnalyticsSessionWriter.
+ public UsageDataSessionWriter(string databaseFileName)
{
SQLiteConnectionStringBuilder conn = new SQLiteConnectionStringBuilder();
conn.Add("Data Source", databaseFileName);
@@ -129,6 +134,11 @@ namespace ICSharpCode.UsageDataCollector
}
}
+ ///
+ /// Adds environment data to the current session.
+ ///
+ /// Name of the data entry.
+ /// Value of the data entry.
public void AddEnvironmentData(string name, string value)
{
using (SQLiteCommand cmd = this.connection.CreateCommand()) {
@@ -141,6 +151,12 @@ namespace ICSharpCode.UsageDataCollector
}
}
+ ///
+ /// Adds a feature use to the session.
+ ///
+ /// Name of the feature.
+ /// How the feature was activated (Menu, Toolbar, Shortcut, etc.)
+ /// ID that can be used for
public long AddFeatureUse(string featureName, string activationMethod)
{
long featureRowId;
@@ -160,15 +176,24 @@ namespace ICSharpCode.UsageDataCollector
return featureRowId;
}
- public void WriteEndTimeForFeature(long featureID)
+ ///
+ /// Marks the end time for a feature use.
+ ///
+ /// A value returned from .
+ public void WriteEndTimeForFeature(long featureUseID)
{
using (SQLiteCommand cmd = this.connection.CreateCommand()) {
cmd.CommandText = "UPDATE FeatureUses SET endTime = datetime('now') WHERE id = ?;";
- cmd.Parameters.Add(new SQLiteParameter { Value = featureID });
+ cmd.Parameters.Add(new SQLiteParameter { Value = featureUseID });
cmd.ExecuteNonQuery();
}
}
+ ///
+ /// Adds an exception report to the session.
+ ///
+ /// Full .NET type name of the exception.
+ /// Stacktrace
public void AddException(string exceptionType, string stacktrace)
{
using (SQLiteCommand cmd = this.connection.CreateCommand()) {
@@ -181,10 +206,18 @@ namespace ICSharpCode.UsageDataCollector
}
}
+ bool isDisposed;
+
+ ///
+ /// Closes the session.
+ ///
public void Dispose()
{
- EndSession();
- connection.Close();
+ if (!isDisposed) {
+ isDisposed = true;
+ EndSession();
+ connection.Dispose();
+ }
}
}
diff --git a/src/AddIns/Misc/UsageDataCollector/UsageDataUploader.cs b/src/AddIns/Misc/UsageDataCollector/UsageDataUploader.cs
index 813c344187..887107bec5 100644
--- a/src/AddIns/Misc/UsageDataCollector/UsageDataUploader.cs
+++ b/src/AddIns/Misc/UsageDataCollector/UsageDataUploader.cs
@@ -24,6 +24,9 @@ namespace ICSharpCode.UsageDataCollector
{
string databaseFileName;
+ ///
+ /// Creates a new UsageDataUploader.
+ ///
public UsageDataUploader(string databaseFileName)
{
this.databaseFileName = databaseFileName;
@@ -38,6 +41,11 @@ namespace ICSharpCode.UsageDataCollector
return connection;
}
+ ///
+ /// Retrieves all stored data as XML text.
+ ///
+ /// The database version is not compatible with this
+ /// version of the AnalyticsSessionWriter.
public string GetTextForStoredData()
{
UsageDataMessage message;
@@ -60,6 +68,8 @@ namespace ICSharpCode.UsageDataCollector
///
/// Starts the upload of the usage data.
///
+ /// The database version is not compatible with this
+ /// version of the AnalyticsSessionWriter.
public void StartUpload()
{
UsageDataMessage message;