Click here to Skip to main content
15,891,777 members
Articles / Programming Languages / C#

An MSN Messenger Log Listener

Rate me:
Please Sign up or sign in to vote.
4.66/5 (19 votes)
6 Feb 20066 min read 131.4K   1.5K   71  
A custom log listener for the Microsoft Enterprise Library Logging framework.
/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is WCPierce MSN Messenger Log Listener
 *
 * The Initial Developer of the Original Code is William C. Pierce.
 * Portions created by the Initial Developer are Copyright (C) 2005
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */
using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Threading;

using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
using Microsoft.Practices.EnterpriseLibrary.Logging;
using Microsoft.Practices.EnterpriseLibrary.Logging.Formatters;
using Microsoft.Practices.EnterpriseLibrary.Logging.TraceListeners;

using WCPierce.Practices.EnterpriseLibrary.Logging.MsnMessenger.Configuration;

using XihSolutions.DotMSN;
using XihSolutions.DotMSN.Core;

namespace WCPierce.Practices.EnterpriseLibrary.Logging.MsnMessenger
{
  /// <summary>
  /// This class sends Logging messages to an MSN Messenger account.  This class
  /// does not guarentee delivery of Log messages so it is recommend to use this
  /// Log Listener in conjuction with another Listener e.g. Flat File, Email,
  /// Database, etc.  The MSN Messenger Listener queues all messages recevied 
  /// until a connection is established with the MSN server and a conversation is
  /// created with one or more recipients.
  /// </summary>
  [ConfigurationElementType(typeof(FormattedMsnMessengerTraceListenerData))]
  public class FormattedMsnMessengerTraceListener : FormattedTraceListenerBase
  {
    #region Private Fields
    
    // Values used for custom MSN Messenger clients
    static readonly string CLIENT_ID   = "msmsgs@msnmsgr.com";
    static readonly string CLIENT_CODE = "Q1P7W2E4J9R8U3S5";

    // Holds the list of Log recipients
    string[] _sReceiverAccounts = null;

    // The actual Messenger object
    Messenger _messenger = null;

    // The Conversation used to send Log Entries
    Conversation _conversation = null;

    // A Queue container for First-In First-Out messages
    Queue<string> _messageQueue = null;
    int _iMaxMessageQueue = 0;

    // Holds any exceptions thrown by the Messenger
    Exception _thrownException = null;
    object _oExceptionLock = new object();

    // True if we are in the process of connecting and signing in to the MSN network
    bool _bConnecting = false;
    object _oConnectingLock = new object();

    // True if we are in the process of inviting recipients to the Conversation
    bool _bInviting = false;
    object _oInvitingLock = new object();
    
    // The Date and Time of the last Connection attempt
    DateTime _dtLastConnectionAttempt = DateTime.MinValue;

    // The Date and Time of the last Invitation attempt
    DateTime _dtLastInvitationAttempt = DateTime.MinValue;

    // The time in Minutes between Connection attempts
    TimeSpan _tsConnectionRetryInterval = TimeSpan.MaxValue;

    // The time in Minute between Invitation attempts
    TimeSpan _tsInvitationRetryInterval = TimeSpan.MaxValue;

    #endregion // Private Fields

    #region Constructor

    /// <summary>
    /// Initializes the Listener and begins the process to establish a connection
    /// with the MSN server.
    /// </summary>
    /// <param name="sSenderAccount">The account used to send Log messages</param>
    /// <param name="sSenderPassword">The password for sSenderAccount</param>
    /// <param name="sReceiverAccounts">A semi-colon (;) delimited list of accounts
    /// that will receive Log messages</param>
    /// <param name="iConnectionRetryInterval">The time in Minutes between Connection attempts</param>
    /// <param name="iInvitationRetryInterval">The time in Minutes between Invitation attempts</param>
    /// <param name="iMaxMessageQueue">The maximum number of messages to queue for 
    /// deliver.  If this value is exceeded, the oldest message is removed from the 
    /// queue before adding the latest message</param>
    /// <param name="Formatter"></param>
    public FormattedMsnMessengerTraceListener(string sSenderAccount, string sSenderPassword, string sReceiverAccounts, int iConnectionRetryInterval, int iInvitationRetryInterval, int iMaxMessageQueue, ILogFormatter Formatter)
      : base(Formatter)
    {
      // Initialize Private Fields
      _sReceiverAccounts = sReceiverAccounts.Split(';');
      _tsConnectionRetryInterval = TimeSpan.FromMinutes(iConnectionRetryInterval);
      _tsInvitationRetryInterval = TimeSpan.FromMinutes(iInvitationRetryInterval);

      // Initialize the Message queue
      _iMaxMessageQueue = iMaxMessageQueue;
      _messageQueue = new Queue<string>(_iMaxMessageQueue);

      // Initialize the Messenger
      _messenger = new Messenger();
      _messenger.Credentials.ClientID = CLIENT_ID;
      _messenger.Credentials.ClientCode = CLIENT_CODE;
      _messenger.Credentials.Account = sSenderAccount;
      _messenger.Credentials.Password = sSenderPassword;

      // Attach events
      _messenger.Nameserver.SignedIn += new EventHandler(Nameserver_SignedIn);
      _messenger.Nameserver.ExceptionOccurred += new HandlerExceptionEventHandler(Nameserver_ExceptionOccurred);
      _messenger.NameserverProcessor.ConnectionException += new ProcessorExceptionEventHandler(NameserverProcessor_ConnectionException);
      _messenger.NameserverProcessor.ConnectingException += new ProcessorExceptionEventHandler(NameserverProcessor_ConnectingException);

      // Start the first Connection
      _bConnecting = true;
      _dtLastConnectionAttempt = DateTime.Now;
      _messenger.Connect();
    }

    #endregion  // Constructor

    #region Overrides

    /// <summary>
    /// Queues the Message then attempts to send it if a connection is 
    /// established and a conversation has been created and joined.
    /// </summary>
    /// <param name="sMessage">The Log Message to send</param>
    public override void Write(string sMessage)
    {
      // Queue the message
      if (String.Empty != sMessage)
      {
        // If we have filled the queue, remove the oldest message before adding
        // the new message
        if (_iMaxMessageQueue <= _messageQueue.Count)
        {
          _messageQueue.Dequeue();
        }
        _messageQueue.Enqueue(sMessage);
      }

      // If an Exception was raised as the result of an event, throw it now
      if (null != _thrownException)
      {
        lock (_oExceptionLock)
        {
          if (null != _thrownException)
          {
            try
            {
              throw _thrownException;
            }
            finally
            {
              _thrownException = null;
            }
          }
        }
      }
      
      // Make sure we have a Connection and a Conversation before sending messages
      if (true == _EnsureConnection() && true == _EnsureConversation())
      {
        // Send any messages that may have been queued while a connection was being established
        while (0 < _messageQueue.Count)
        {
          TextMessage message = new TextMessage(_messageQueue.Dequeue());
          _conversation.Switchboard.SendTextMessage(message);
        }
      }
    }

    /// <summary>
    /// Write and WriteLine are the same to our Listener
    /// </summary>
    /// <param name="sMessage"></param>
    public override void WriteLine(string sMessage)
    {
      Write(sMessage);
    }

    /// <summary>
    /// Perform instrumentation before Writing a LogEntry
    /// </summary>
    /// <param name="EventCache"></param>
    /// <param name="sSource"></param>
    /// <param name="EventType"></param>
    /// <param name="iId"></param>
    /// <param name="oData"></param>
    public override void TraceData(TraceEventCache EventCache, string sSource, TraceEventType EventType, int iId, object oData)
    {
      if (oData is LogEntry)
      {
        LogEntry le = oData as LogEntry;
        Write(Formatter.Format(le));
        InstrumentationProvider.FireTraceListenerEntryWrittenEvent();
      }
      else if (oData is string)
      {
        Write(oData as string);
      }
      else
      {
        base.TraceData(EventCache, sSource, EventType, iId, oData);
      }
    }

    /// <summary>
    /// Valid configuration keys
    /// </summary>
    /// <returns></returns>
    protected override string[] GetSupportedAttributes()
    {
      return new string[7] { "formatter", "senderAccount", "senderPassword", "receiverAccounts", "connectionRetryInterval", "invitationRetryInterval", "maxMessageQueue" };
    }

    /// <summary>
    /// Not sure if this is really necessary but null all objects and detach all events
    /// </summary>
    public override void Close()
    {
      _messageQueue = null;

      // Kill the Conversation
      if (null != _conversation)
      {
        if (null != _conversation.Switchboard)
        {
          _conversation.Switchboard.ContactJoined -= new ContactChangedEventHandler(Switchboard_ContactJoined);
        }
        _conversation = null;
      }

      // Kill the Messenger
      if (null != _messenger)
      {
        if (true == _messenger.Connected)
        {
          _messenger.Disconnect();
        }

        _messenger.Nameserver.SignedIn -= new EventHandler(Nameserver_SignedIn);
        _messenger.Nameserver.ExceptionOccurred -= new HandlerExceptionEventHandler(Nameserver_ExceptionOccurred);
        _messenger.NameserverProcessor.ConnectionException -= new ProcessorExceptionEventHandler(NameserverProcessor_ConnectionException);
        _messenger.NameserverProcessor.ConnectingException -= new ProcessorExceptionEventHandler(NameserverProcessor_ConnectingException);
        _messenger = null;
      }
      base.Close();
    }

    /// <summary>
    /// If we are really disposing then call Close
    /// </summary>
    /// <param name="bDisposing"></param>
    protected override void Dispose(bool bDisposing)
    {
      if (bDisposing)
      {
        this.Close();
      }
      base.Dispose(bDisposing);
    }

    #endregion // Overrides

    #region Private Methods

    /// <summary>
    /// Synchronized error handling
    /// </summary>
    /// <param name="Ex"></param>
    void _HandleException(Exception Ex)
    {
      if (null == _thrownException)
      {
        lock (_oExceptionLock)
        {
          if (null == _thrownException)
          {
            _thrownException = Ex;
          }
        }
      }
    }

    /// <summary>
    /// Returns true if we have a valid Connection.  If we do not have a valid 
    /// connection and we are not in the middle of trying to establish a 
    /// connection, we begin establishing a connection.  If it has been a while 
    /// since our last connection attempt, we try again.  The allows the Listener 
    /// to handle situations where receipients go offline then come back online 
    /// at a later date/time.
    /// </summary>
    /// <returns></returns>
    bool _EnsureConnection()
    {
      // If we are Connected and SignedIn we are good to go
      if (true == _messenger.Connected && true == _messenger.Nameserver.IsSignedIn )
      {
        return true;
      }

      // If we aren't currently attempting to connect
      if (false == _bConnecting)
      {
        lock (_oConnectingLock)
        {
          if (false == _bConnecting)
          {
            // Initialize a connection
            _messenger.Disconnect();
            _bConnecting = true;
            _dtLastConnectionAttempt = DateTime.Now;
            _messenger.Connect();
          }
        }
      }
      // If we are trying to connect but haven't had any luck for a while we try again
      else if( _tsConnectionRetryInterval <= (DateTime.Now - _dtLastConnectionAttempt) )
      {
        lock (_oConnectingLock)
        {
          _bConnecting = false;
        }
      }

      return false;
    }

    /// <summary>
    /// Returns true if we have a valid Conversation.  If we do not have a valid
    /// conversation and we are not in the middle of trying to establish a
    /// conversation, we begin establishing a conversation.  If it has been a 
    /// while since our last conversation attempt, we try again.  This allows
    /// the Listener to handle situations where receipients go offline then come back
    /// online at a later date/time.
    /// </summary>
    /// <returns></returns>
    bool _EnsureConversation()
    {
      if (null != _conversation && 0 < _conversation.Switchboard.Contacts.Count)
      {
        return true;
      }

      if (false == _bInviting)
      {
        lock (_oInvitingLock)
        {
          if (false == _bInviting)
          {
            _bInviting = true;
            _dtLastInvitationAttempt = DateTime.Now;
            foreach (string receiver in _sReceiverAccounts)
            {
              _conversation.Invite(receiver);
            }
          }
        }
      }
      // If we are trying to converse but haven't had any luck for a while we try again
      else if ( _tsInvitationRetryInterval <= (DateTime.Now - _dtLastInvitationAttempt) )
      {
        lock (_oInvitingLock)
        {
          _bInviting = false;
        }
      }

      return false;
    }

    #endregion // Private Methods

    #region Messenger Events

    /// <summary>
    /// Takes any exeception and stores it until it can be raised to the Logging framework
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    void NameserverProcessor_ConnectingException(object sender, ExceptionEventArgs e)
    {
      _HandleException(e.Exception);
    }

    /// <summary>
    /// Takes any exeception and stores it until it can be raised to the Logging framework
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    void NameserverProcessor_ConnectionException(object sender, ExceptionEventArgs e)
    {
      _HandleException(e.Exception);
    }

    /// <summary>
    /// Takes any exeception and stores it until it can be raised to the Logging framework
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    void Nameserver_ExceptionOccurred(object sender, ExceptionEventArgs e)
    {
      _HandleException(e.Exception);
    }

    /// <summary>
    /// After a Connection is established, we must SignIn.  When we SignIn successfully
    /// we proceed to create a conversation and wait for receipients to join
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    void Nameserver_SignedIn(object sender, EventArgs e)
    {
      // Show use as online
      _messenger.Owner.Status = PresenceStatus.Online;

      if (null == _conversation)
      {
        // Create a new conversation
        _conversation = _messenger.CreateConversation();

        // We can't send a message until at least one Contact has joined
        _conversation.Switchboard.ContactJoined += new ContactChangedEventHandler(Switchboard_ContactJoined);
      }

      // Signal that we are no longer attempting to connect
      if (true == _bConnecting)
      {
        lock (_oConnectingLock)
        {
          if (true == _bConnecting)
          {
            _bConnecting = false;
          }
        }
      }
    }

    /// <summary>
    /// When a recipient joins, we signal that we are no longer in the process of
    /// inviting recipients
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    void Switchboard_ContactJoined(object sender, ContactEventArgs e)
    {
      if (true == _bInviting)
      {
        lock (_oInvitingLock)
        {
          if (true == _bInviting)
          {
            _bInviting = false;
          }
        }
      }
    }

    #endregion // Messenger Events
  }
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Software Developer (Senior)
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions