// Uncomment to debug reports locally // #define ANALYTICS_LOCALHOST // #define ANALYTICS_INSTANT using System; using System.Net; using System.Threading.Tasks; using System.Timers; using TweetDuck.Core.Controls; using TweetDuck.Core.Utils; using TweetDuck.Plugins; using TweetLib.Core.Utils; namespace TweetDuck.Core.Other.Analytics{ sealed class AnalyticsManager : IDisposable{ private static readonly TimeSpan CollectionInterval = TimeSpan.FromDays(14); private static readonly Uri CollectionUrl = new Uri( #if (DEBUG && ANALYTICS_LOCALHOST) "http://localhost/newhome/tweetduck/~breadcrumb/request.php?type=report" #else "https://tweetduck.chylex.com/breadcrumb/report" #endif ); public AnalyticsFile File { get; } private readonly FormBrowser browser; private readonly PluginManager plugins; private readonly Timer currentTimer, saveTimer; public AnalyticsManager(FormBrowser browser, PluginManager plugins, string file){ this.browser = browser; this.plugins = plugins; this.File = AnalyticsFile.Load(file); this.File.PropertyChanged += File_PropertyChanged; this.currentTimer = new Timer{ SynchronizingObject = browser }; this.currentTimer.Elapsed += currentTimer_Elapsed; this.saveTimer = new Timer{ SynchronizingObject = browser, Interval = 60000 }; this.saveTimer.Elapsed += saveTimer_Elapsed; if (this.File.LastCollectionVersion != Program.VersionTag){ ScheduleReportIn(TimeSpan.FromHours(8), string.Empty); } else{ RestartTimer(); } #if (DEBUG && ANALYTICS_INSTANT) SendReport(); #endif } public void Dispose(){ File.PropertyChanged -= File_PropertyChanged; if (saveTimer.Enabled){ File.Save(); } currentTimer.Dispose(); saveTimer.Dispose(); } private void File_PropertyChanged(object sender, EventArgs e){ saveTimer.Enabled = true; } private void saveTimer_Elapsed(object sender, ElapsedEventArgs e){ saveTimer.Stop(); File.Save(); } private void ScheduleReportIn(TimeSpan delay, string message = null){ SetLastDataCollectionTime(DateTime.Now.Subtract(CollectionInterval).Add(delay), message); } private void SetLastDataCollectionTime(DateTime dt, string message = null){ File.LastDataCollection = new DateTime(dt.Year, dt.Month, dt.Day, dt.Hour, dt.Minute, 0, dt.Kind); File.LastCollectionVersion = Program.VersionTag; File.LastCollectionMessage = message ?? dt.ToString("g", Program.Culture); File.Save(); RestartTimer(); } private void RestartTimer(){ TimeSpan diff = DateTime.Now.Subtract(File.LastDataCollection); int minutesTillNext = (int)(CollectionInterval.TotalMinutes-Math.Floor(diff.TotalMinutes)); currentTimer.Interval = Math.Max(minutesTillNext, 2)*60000; currentTimer.Start(); } private void currentTimer_Elapsed(object sender, ElapsedEventArgs e){ currentTimer.Stop(); TimeSpan diff = DateTime.Now.Subtract(File.LastDataCollection); if (Math.Floor(diff.TotalMinutes) >= CollectionInterval.TotalMinutes){ SendReport(); } else{ RestartTimer(); } } private void SendReport(){ AnalyticsReportGenerator.ExternalInfo info = AnalyticsReportGenerator.ExternalInfo.From(browser); Task.Factory.StartNew(() => { AnalyticsReport report = AnalyticsReportGenerator.Create(File, info, plugins); #if (DEBUG && !ANALYTICS_INSTANT) System.Diagnostics.Debugger.Break(); #endif WebUtils.NewClient(BrowserUtils.UserAgentVanilla).UploadValues(CollectionUrl, "POST", report.ToNameValueCollection()); }).ContinueWith(task => browser.InvokeAsyncSafe(() => { if (task.Status == TaskStatus.RanToCompletion){ SetLastDataCollectionTime(DateTime.Now); } else if (task.Exception != null){ string message = null; if (task.Exception.InnerException is WebException e){ switch(e.Status){ case WebExceptionStatus.ConnectFailure: message = "Connection Error"; break; case WebExceptionStatus.NameResolutionFailure: message = "DNS Error"; break; case WebExceptionStatus.ProtocolError: HttpWebResponse response = e.Response as HttpWebResponse; message = "HTTP Error "+(response != null ? $"{(int)response.StatusCode} ({response.StatusDescription})" : "(unknown code)"); break; } #if DEBUG System.IO.Stream responseStream = e.Response.GetResponseStream(); if (responseStream != null){ System.Diagnostics.Debug.WriteLine(new System.IO.StreamReader(responseStream).ReadToEnd()); } #endif } ScheduleReportIn(TimeSpan.FromHours(4), message ?? "Error: "+(task.Exception.InnerException?.Message ?? task.Exception.Message)); } })); } } }