summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/DotNetOpenAuth/OpenId/RelyingParty/FailedAuthenticationResponse.cs1
-rw-r--r--src/DotNetOpenAuth/OpenId/RelyingParty/NegativeAuthenticationResponse.cs2
-rw-r--r--src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAnonymousResponse.cs2
-rw-r--r--src/DotNetOpenAuth/Reporting.cs201
4 files changed, 197 insertions, 9 deletions
diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/FailedAuthenticationResponse.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/FailedAuthenticationResponse.cs
index f899f03..e4dbc9a 100644
--- a/src/DotNetOpenAuth/OpenId/RelyingParty/FailedAuthenticationResponse.cs
+++ b/src/DotNetOpenAuth/OpenId/RelyingParty/FailedAuthenticationResponse.cs
@@ -31,6 +31,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
Contract.Requires<ArgumentNullException>(exception != null);
this.Exception = exception;
+ Reporting.RecordEventOccurrence(this);
}
#region IAuthenticationResponse Members
diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/NegativeAuthenticationResponse.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/NegativeAuthenticationResponse.cs
index 02c5185..2669634 100644
--- a/src/DotNetOpenAuth/OpenId/RelyingParty/NegativeAuthenticationResponse.cs
+++ b/src/DotNetOpenAuth/OpenId/RelyingParty/NegativeAuthenticationResponse.cs
@@ -30,6 +30,8 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
internal NegativeAuthenticationResponse(NegativeAssertionResponse response) {
Contract.Requires<ArgumentNullException>(response != null);
this.response = response;
+
+ Reporting.RecordEventOccurrence(this);
}
#region IAuthenticationResponse Properties
diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAnonymousResponse.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAnonymousResponse.cs
index 1bc306c..08d0350 100644
--- a/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAnonymousResponse.cs
+++ b/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAnonymousResponse.cs
@@ -39,6 +39,8 @@ namespace DotNetOpenAuth.OpenId.RelyingParty {
if (response.ProviderEndpoint != null && response.Version != null) {
this.provider = new ProviderEndpointDescription(response.ProviderEndpoint, response.Version);
}
+
+ Reporting.RecordEventOccurrence(this);
}
#region IAuthenticationResponse Properties
diff --git a/src/DotNetOpenAuth/Reporting.cs b/src/DotNetOpenAuth/Reporting.cs
index 45ee6db..79451b8 100644
--- a/src/DotNetOpenAuth/Reporting.cs
+++ b/src/DotNetOpenAuth/Reporting.cs
@@ -36,6 +36,15 @@ namespace DotNetOpenAuth {
private static readonly TimeSpan minimumReportingInterval = TimeSpan.FromDays(1);
/// <summary>
+ /// The maximum frequency the set can be flushed to disk.
+ /// </summary>
+#if DEBUG
+ private static readonly TimeSpan minimumFlushInterval = TimeSpan.Zero;
+#else
+ private static readonly TimeSpan minimumFlushInterval = TimeSpan.FromMinutes(15);
+#endif
+
+ /// <summary>
/// The isolated storage to use for collecting data in between published reports.
/// </summary>
private static IsolatedStorageFile file;
@@ -76,6 +85,11 @@ namespace DotNetOpenAuth {
private static List<PersistentHashSet> observations = new List<PersistentHashSet>();
/// <summary>
+ /// The named events that we have counters for.
+ /// </summary>
+ private static Dictionary<string, PersistentCounter> events = new Dictionary<string, PersistentCounter>(StringComparer.OrdinalIgnoreCase);
+
+ /// <summary>
/// The lock acquired while considering whether to publish a report.
/// </summary>
private static object publishingConsiderationLock = new object();
@@ -122,6 +136,32 @@ namespace DotNetOpenAuth {
private static bool Enabled { get; set; }
/// <summary>
+ /// Records an event occurrence.
+ /// </summary>
+ /// <param name="eventName">Name of the event.</param>
+ internal static void RecordEventOccurrence(string eventName) {
+ Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(eventName));
+
+ PersistentCounter counter;
+ lock (events) {
+ if (!events.TryGetValue(eventName, out counter)) {
+ events[eventName] = counter = new PersistentCounter(file, "event-" + SanitizeFileName(eventName) + ".txt");
+ }
+ }
+
+ counter.Increment();
+ }
+
+ /// <summary>
+ /// Records an event occurence.
+ /// </summary>
+ /// <param name="eventNameByObjectType">The object whose type name is the event name to record.</param>
+ internal static void RecordEventOccurrence(object eventNameByObjectType) {
+ Contract.Requires<ArgumentNullException>(eventNameByObjectType != null);
+ RecordEventOccurrence(eventNameByObjectType.GetType().Name);
+ }
+
+ /// <summary>
/// Records the use of a feature by name.
/// </summary>
/// <param name="feature">The feature.</param>
@@ -229,6 +269,7 @@ namespace DotNetOpenAuth {
writer.WriteLine(Util.LibraryVersion);
foreach (var observation in observations) {
+ observation.Flush();
writer.WriteLine("====================================");
writer.WriteLine(observation.FileName);
try {
@@ -241,6 +282,19 @@ namespace DotNetOpenAuth {
}
}
+ foreach (var counter in events.Values) {
+ counter.Flush();
+ }
+
+ foreach (string eventFile in file.GetFileNames("event-*.txt")) {
+ writer.WriteLine("====================================");
+ writer.WriteLine(eventFile);
+ using (var fileStream = new IsolatedStorageFileStream(eventFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, file)) {
+ writer.Flush();
+ fileStream.CopyTo(writer.BaseStream);
+ }
+ }
+
// Make sure the stream is positioned at the beginning.
writer.Flush();
stream.Position = 0;
@@ -321,6 +375,28 @@ namespace DotNetOpenAuth {
}
/// <summary>
+ /// Sanitizes the name of the file so it only includes valid filename characters.
+ /// </summary>
+ /// <param name="fileName">The filename to sanitize.</param>
+ /// <returns>The filename, with any and all invalid filename characters replaced with the hyphen (-) character.</returns>
+ private static string SanitizeFileName(string fileName) {
+ Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(fileName));
+ char[] invalidCharacters = Path.GetInvalidFileNameChars();
+ if (fileName.IndexOfAny(invalidCharacters) < 0) {
+ return fileName; // nothing invalid about this filename.
+ }
+
+ // Use a stringbuilder since we may be replacing several characters
+ // and we don't want to instantiate a new string buffer for each new version.
+ StringBuilder sanitized = new StringBuilder(fileName);
+ foreach (char invalidChar in invalidCharacters) {
+ sanitized.Replace(invalidChar, '-');
+ }
+
+ return sanitized.ToString();
+ }
+
+ /// <summary>
/// A set of values that persist the set to disk.
/// </summary>
private class PersistentHashSet : IDisposable {
@@ -345,15 +421,6 @@ namespace DotNetOpenAuth {
private readonly HashSet<string> memorySet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
/// <summary>
- /// The maximum frequency the set can be flushed to disk.
- /// </summary>
-#if DEBUG
- private static readonly TimeSpan minimumFlushInterval = TimeSpan.Zero;
-#else
- private static readonly TimeSpan minimumFlushInterval = TimeSpan.FromMinutes(15);
-#endif
-
- /// <summary>
/// The maximum number of elements to track before not storing new elements.
/// </summary>
private readonly int maximumElements;
@@ -484,5 +551,121 @@ namespace DotNetOpenAuth {
}
}
}
+
+ /// <summary>
+ /// A feature usage counter.
+ /// </summary>
+ private class PersistentCounter : IDisposable {
+ /// <summary>
+ /// The isolated persistent storage.
+ /// </summary>
+ private readonly FileStream fileStream;
+
+ /// <summary>
+ /// The persistent reader.
+ /// </summary>
+ private readonly StreamReader reader;
+
+ /// <summary>
+ /// The persistent writer.
+ /// </summary>
+ private readonly StreamWriter writer;
+
+ /// <summary>
+ /// The time the last flush occurred.
+ /// </summary>
+ private DateTime lastFlushed;
+
+ /// <summary>
+ /// The in-memory copy of the counter.
+ /// </summary>
+ private int counter;
+
+ /// <summary>
+ /// A flag indicating whether the set has changed since it was last flushed.
+ /// </summary>
+ private bool dirty;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="PersistentCounter"/> class.
+ /// </summary>
+ /// <param name="storage">The storage location.</param>
+ /// <param name="fileName">Name of the file.</param>
+ internal PersistentCounter(IsolatedStorageFile storage, string fileName) {
+ Contract.Requires<ArgumentNullException>(storage != null);
+ Contract.Requires<ArgumentException>(!String.IsNullOrEmpty(fileName));
+ this.FileName = fileName;
+
+ // Load the file into memory.
+ bool fileCreated = storage.GetFileNames(fileName).Length == 0;
+ this.fileStream = new IsolatedStorageFileStream(fileName, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read, storage);
+ this.reader = new StreamReader(this.fileStream, Encoding.UTF8);
+ int.TryParse(this.reader.ReadLine(), out this.counter);
+
+ this.writer = new StreamWriter(this.fileStream, Encoding.UTF8);
+ this.writer.AutoFlush = true;
+ this.lastFlushed = DateTime.Now;
+
+ // Write a unique header to the file so the report collector can match duplicates.
+ if (fileCreated) {
+ this.writer.WriteLine(Guid.NewGuid().ToString("B"));
+ }
+ }
+
+ /// <summary>
+ /// Gets the name of the file.
+ /// </summary>
+ /// <value>The name of the file.</value>
+ internal string FileName { get; private set; }
+
+ #region IDisposable Members
+
+ /// <summary>
+ /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
+ /// </summary>
+ public void Dispose() {
+ this.Dispose(true);
+ }
+
+ #endregion
+
+ /// <summary>
+ /// Increments the counter.
+ /// </summary>
+ internal void Increment() {
+ lock (this) {
+ this.counter++;
+ this.dirty = true;
+ if (this.dirty && DateTime.Now - this.lastFlushed > minimumFlushInterval) {
+ this.Flush();
+ }
+ }
+ }
+
+ /// <summary>
+ /// Flushes any newly added values to disk.
+ /// </summary>
+ internal void Flush() {
+ lock (this) {
+ this.writer.BaseStream.Position = 0;
+ this.writer.BaseStream.SetLength(0); // truncate file
+ this.writer.Write(this.counter);
+ this.dirty = false;
+ this.lastFlushed = DateTime.Now;
+ }
+ }
+
+ /// <summary>
+ /// Releases unmanaged and - optionally - managed resources
+ /// </summary>
+ /// <param name="disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
+ protected virtual void Dispose(bool disposing) {
+ if (disposing) {
+ this.writer.Dispose();
+ this.reader.Dispose();
+ this.fileStream.Dispose();
+ }
+ }
+ }
}
}