/// <summary> /// Performs an UI manipulations required for GUI depicted status. /// </summary> private void UpdateStatus() { // Simple updates labelCounts.Text = String.Join( Resources.Stats_Join, FormatCount( intelReporter.IntelSent, Resources.IntelCount_Zero, Resources.IntelCount_One, Resources.IntelCount_Many), FormatCount( intelReporter.IntelDropped, Resources.DropCount_Zero, Resources.DropCount_One, Resources.DropCount_Many), FormatCount( intelReporter.Users, Resources.UserCount_Zero, Resources.UserCount_One, Resources.UserCount_Many), FormatCount( this.noveltyCount, Resources.NoveltyCount_Zero, Resources.NoveltyCount_One, Resources.NoveltyCount_Many) ); // Authentication errors are handled specially if (this.configError && !panelAuthentication.Visible) { this.ShowAuthWindow(); } // Update the status string var status = intelReporter.Status; var statusString = Resources.ResourceManager.GetString( String.Format( CultureInfo.InvariantCulture, "StatusString_{0}", status)) ?? Resources.StatusString_Unknown; this.labelStatusString.Text = String.Format( CultureInfo.CurrentCulture, statusString, status, intelReporter.Path); // Status changes switch (status) { case IntelStatus.Starting: // Starting up... break; case IntelStatus.Active: // Normal operation this.ShowChannelList(); if (this.updateEvent != null) { this.notifyIcon.Text = Resources.NotifyIcon_Upgrade; } else { this.notifyIcon.Text = Resources.NotifyIcon_Active; } break; case IntelStatus.Waiting: // Normal operation if (this.updateEvent != null) { this.ShowChannelList(); this.notifyIcon.Text = Resources.NotifyIcon_Upgrade; } else { this.ShowStatus( Resources.IntelStatus_IdleTitle, Resources.IntelStatus_Idle); this.notifyIcon.Text = Resources.NotifyIcon_Inactive; } break; case IntelStatus.AuthenticationError: // Doesn't like our password if (!this.configError) { this.ShowAuthError( Resources.IntelStatus_AuthTitle, Resources.IntelStatus_Auth); } if (!this.lastAuthBalloon.HasValue || (this.lastAuthBalloon + new TimeSpan(TimeSpan.TicksPerDay) < DateTime.Now)) { this.notifyIcon.ShowBalloonTip(10000, Resources.Auth_NotificationTitle, Resources.Auth_NotificationText, ToolTipIcon.Error); this.lastAuthBalloon = DateTime.Now; } this.notifyIcon.Text = Resources.NotifyIcon_AuthError; break; case IntelStatus.InvalidPath: // Couldn't find the log directory this.ShowStatus( Resources.IntelStatus_MissingTitle, Resources.IntelStatus_Missing); this.notifyIcon.Text = Resources.NotifyIcon_Error; break; case IntelStatus.NetworkError: // Unable to contact the network server this.ShowStatus( Resources.IntelStatus_ErrorTitle, Resources.IntelStatus_Error); this.notifyIcon.Text = Resources.NotifyIcon_Error; break; default: // This represents a pretty critical failure of the // system, so it gets priority over everything, including // user entry. panelAuthentication.Visible = false; this.ShowStatus( Resources.IntelStatus_FatalTitle, Resources.IntelStatus_Fatal); this.notifyIcon.Text = Resources.NotifyIcon_Error; break; } this.oldStatus = status; }
/// <summary> /// Releases the unmanaged resources used by the /// <see cref="IntelChannelContainer"/> and optionally releases /// the managed resources. /// </summary> /// <param name="disposing"> /// <see langword="true"/> to release both managed and unmanaged /// resources; <see langword="false"/> to release only unmanaged /// resources. /// </param> protected virtual void Dispose(bool disposing) { Contract.Ensures(Status == IntelStatus.Disposed); Contract.Ensures(!IsRunning); try { if (disposing) { lock (this.syncRoot) { if (this.status != IntelStatus.Disposed) { this.Status = IntelStatus.Disposing; this.updateTimer.Dispose(); channels.ForEach(x => x.Dispose()); } } this.IntelReported = null; this.PropertyChanged = null; } } finally { this.status = IntelStatus.Disposed; } }
/// <inheritdoc/> protected override void Dispose(bool disposing) { try { if (disposing) { // Clean up lock (this.syncRoot) { if (this.Status != IntelStatus.Disposed) { this.Status = IntelStatus.Disposing; if (this.session != null) { this.session.Dispose(); this.session = null; } this.timerSession.Dispose(); this.channels.Dispose(); } } } else { // Cannot safely clean up } } finally { this.status = IntelStatus.Disposed; base.Dispose(disposing); } }
/// <summary> /// Called after <see cref="Start" /> has been called. /// </summary> /// <remarks> /// <see cref="OnStart" /> will be called from within a synchronized /// context so derived classes should not attempt to perform any /// additional synchronization themselves. /// </remarks> protected virtual void OnStart() { Contract.Requires<InvalidOperationException>( Status == IntelStatus.Starting); Contract.Ensures((Status == IntelStatus.Active) || (Status == IntelStatus.Waiting) || (Status == IntelStatus.InvalidPath)); // Create the file system watcher object try { this.watcher = this.CreateFileSystemWatcher(); } catch(ArgumentException) { this.Status = IntelStatus.InvalidPath; } // Open the log file with the latest timestamp in its filename this.ScanFiles(); }
/// <summary> /// Called every couple of seconds to sweep the log file for /// new entries. /// </summary> /// <remarks> /// <see cref="OnTick" /> will be called from within a synchronized /// context so derived classes should not attempt to perform any /// additional synchronization themselves. /// </remarks> protected virtual void OnTick() { if (this.watcher == null) { // Try (again) to create the watcher object try { this.watcher = this.CreateFileSystemWatcher(); this.Status = IntelStatus.Waiting; this.ScanFiles(); } catch (ArgumentException) { // Still doesn't seem to exist } } if (this.reader != null) { // Read new log entries from the current log try { string line; while ((line = reader.ReadLine()) != null) { Trace.WriteLine("R " + line, IntelExtensions.WebTraceCategory); var match = Parser.Match(line); if (match.Success && !match.Groups[7].Value.StartsWith(MotdPrefix)) { var e = new IntelEventArgs( this.Name, new DateTime( match.Groups[1].ToInt32(), match.Groups[2].ToInt32(), match.Groups[3].ToInt32(), match.Groups[4].ToInt32(), match.Groups[5].ToInt32(), match.Groups[6].ToInt32(), DateTimeKind.Utc), match.Groups[7].Value); this.OnIntelReported(e); } } } catch (IOException) { this.CloseFile(); } // Close the log if it has been idle for too long if (this.lastEntry + this.expireLog < DateTime.UtcNow) { this.CloseFile(); } } }
/// <summary> /// Releases the unmanaged resources used by the <see cref="IntelChannel"/> /// and optionally releases the managed resources. /// </summary> /// <param name="disposing"> /// <see langword="true"/> to release both managed and unmanaged /// resources; <see langword="false"/> to release only unmanaged /// resources. /// </param> protected override void Dispose(bool disposing) { Contract.Ensures(Status == IntelStatus.Disposed); try { if (disposing) { lock (this.syncRoot) { if (this.status != IntelStatus.Disposed) { // Normal shutdown var isRunning = this.IsRunning; this.Status = IntelStatus.Disposing; if (isRunning) { this.OnStop(); } // Dispose child objects this.logTimer.Dispose(); // Clear any lingering object references this.IntelReported = null; this.PropertyChanged = null; } } } } finally { this.status = IntelStatus.Disposed; } base.Dispose(disposing); }
/// <summary> /// Closes the log file we are currently monitoring (if any). /// </summary> protected void CloseFile() { Contract.Ensures((Status == IntelStatus.Waiting) || (Status == IntelStatus.InvalidPath)); if (this.reader != null) { try { this.reader.Close(); } catch (IOException) { } finally { this.reader = null; } } this.LogFile = null; this.Status = (this.watcher != null) ? IntelStatus.Waiting : IntelStatus.InvalidPath; }
/// <summary> /// Closes the existing log file and opens a new log file. /// </summary> /// <param name="fileInfo">The new log file to track.</param> /// <returns> /// <see langword="true" /> if we were able to open the file; /// otherwise, <see langword="false" />. /// </returns> protected internal bool OpenFile(FileInfo fileInfo) { Contract.Requires<ArgumentNullException>(fileInfo != null, "fileInfo"); // Defer raising PropertyChanged until final decision is made var status = this.status; FileInfo logFile = null; try { // Close the existing file (if any) if (this.reader != null) { try { this.reader.Close(); } catch (IOException) { } finally { this.reader = null; } } // Clear the status status = (this.watcher != null) ? IntelStatus.Waiting : IntelStatus.InvalidPath; // Try to open the file stream FileStream stream = null; try { stream = fileInfo.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite); stream.Seek(0, SeekOrigin.End); // XXX: We rely upon StreamReader's BOM detection. EVE seems // to generate Little Endian UTF-16 log files. We could hard // code that, but we don't know if that would cause other // problems. this.reader = new StreamReader(stream, true); status = IntelStatus.Active; logFile = fileInfo; this.lastEntry = DateTime.UtcNow; } catch (IOException) { // Don't leak FileStream references if (stream != null) { try { stream.Close(); } catch (IOException) { } } } } catch { // Unexpected failure status = IntelStatus.FatalError; throw; } finally { // Raise any deferred PropertyChanged events this.Status = status; this.LogFile = logFile; } // Success if we opened a reader return (this.reader != null); }
/// <summary> /// Stops the <see cref="IntelChannel"/> from monitoring for new log /// entries. /// </summary> /// <seealso cref="Start"/> public virtual void Stop() { Contract.Ensures((Status == IntelStatus.Stopped) || (Status == IntelStatus.Disposed)); Contract.Ensures(!IsRunning); lock (this.syncRoot) { if ((this.status != IntelStatus.Stopped) && (this.status != IntelStatus.Disposed)) { try { this.Status = IntelStatus.Stopping; this.OnStop(); this.Status = IntelStatus.Stopped; } catch { this.status = IntelStatus.FatalError; throw; } } } }
/// <summary> /// Initiate the acquisition of log entries from the EVE chat logs. This /// method enables <see cref="IntelReported"/> events. /// </summary> /// <seealso cref="Stop"/> public virtual void Start() { Contract.Requires<ObjectDisposedException>( Status != IntelStatus.Disposed, null); Contract.Requires<InvalidOperationException>( !String.IsNullOrEmpty(Name)); Contract.Ensures(Status != IntelStatus.Stopped); Contract.Ensures(IsRunning); lock (this.syncRoot) { try { if (this.status == IntelStatus.Stopped) { this.Status = IntelStatus.Starting; this.channelFileName = this.Name; this.OnStart(); if (this.status == IntelStatus.Starting) { this.Status = IntelStatus.Waiting; } } } catch { this.Status = IntelStatus.FatalError; throw; } } }