// 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.Browser; using TweetDuck.Controls; using TweetDuck.Utils; using TweetLib.Core; using TweetLib.Core.Features.Plugins; using TweetLib.Core.Utils; namespace TweetDuck.Management.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", Lib.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) { message = e.Status switch { WebExceptionStatus.ConnectFailure => "Connection Error", WebExceptionStatus.NameResolutionFailure => "DNS Error", WebExceptionStatus.ProtocolError => "HTTP Error " + (e.Response is HttpWebResponse response ? $"{(int) response.StatusCode} ({response.StatusDescription})" : "(unknown code)"), _ => message }; #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)); } })); } } }