/*
* (standard BSD license)
*
* Copyright (c) 2002, Chad Myers, et al.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer. Redistributions in binary form
* must reproduce the above copyright notice, this list of conditions and the
* following disclaimer in the documentation and/or other materials provided with
* the distribution. Neither the name of SourceForge, nor Microsoft nor the names
* of its contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
using System;
using System.Collections;
using System.Threading;
namespace MSNP{
/// <summary>
/// Represents a session with a user or group of users.
/// </summary>
public class Session
{
/**********************************************/
// Fields
/**********************************************/
private SwitchboardConnectionHandler m_Conn; // Reference to the SBCH that spawned this class
private ISessionHandler m_Handler; // Reference to the implementer handler for session events
private Thread m_Thread; // The thread for this session
private Queue m_ResponseQueue = Queue.Synchronized(new Queue(25)); // Incoming responses to be processed
private Hashtable m_Roster = Hashtable.Synchronized(new Hashtable(10)); // Current users in this session
private bool m_Running = false; // Determines if the session should continue processing
private object m_SessionIdentifier; // Implementer specified identifier for this session
/**********************************************/
// Constructors
/**********************************************/
/// <summary>
/// Main constructor.
/// </summary>
/// <param name="conn">The SBCH that created this session</param>
/// <param name="handler">The implementer provided session event handler</param>
/// <param name="sessionIdentifier">The implementer specified identifier for this session</param>
internal Session(SwitchboardConnectionHandler conn, ISessionHandler handler, object sessionIdentifier)
{
m_Conn = conn;
m_Handler = handler;
m_SessionIdentifier = sessionIdentifier;
}
/**********************************************/
// Properties
/**********************************************/
/// <summary>The MSN assigned ID for this session.</summary>
/// <remarks>
/// It is not safe to use this value as a unique identifier for this object. Sessions which have been
/// started as a result of the local user's request will not immediately have its ID set and will be
/// null. MSNP does not immediately return the session ID of an newly created outgoing session until the first
/// CAL has been place. Until then, the ID will be null.
/// </remarks>
public String SessionID{ get{ return m_Conn.SessionID; } }
/// <summary>The caller-designated session identifier.</summary>
/// <remarks>
/// The MSNPHelper will not modify this value. This is merely a caller-changable field to allow the
/// caller to identify this session from any others. This value defaults to null if the session is
/// incoming, otherwise it is whatever value the caller has set, or passed in to the <see cref="MSNPHelper.RequestSession"/>
/// method.
/// </remarks>
public object SessionIdentifier{ get{ return m_SessionIdentifier; } set{ m_SessionIdentifier = value; } }
/**********************************************/
// Internal Methods
/**********************************************/
/// <summary>
/// Starts this session's thread. Sessions run in a seperate thread from the Switchboard handler because
/// the processing involved with session events may desync the SBCH.
/// </summary>
internal void Start()
{
if( m_Thread == null || m_Thread.ThreadState == ThreadState.Stopped )
{
ThreadStart start = new ThreadStart(handleSession);
m_Thread = new Thread(start);
m_Thread.Name = "Session[" + m_Conn.SessionID + "]";
m_Running = true;
m_Thread.Start();
}
else
{
throw new Exception("Session handler already started");
}
}
/// <summary>
/// Adds a user to the roster. Called by the SBCH when a user joins the session
/// </summary>
/// <param name="handle">The user handle (email address) of the user joining</param>
/// <param name="friendlyName">The friendly name of the user joining</param>
internal void AddRosterUser(String handle, String friendlyName)
{
m_Roster.Add(handle, friendlyName);
}
/// <summary>
/// Called by the SBCH when there is a new response or message for this session to handle
/// </summary>
/// <param name="response">The response to add to the queue</param>
internal void AddResponseToQueue(Response response)
{
m_ResponseQueue.Enqueue(response);
}
/// <summary>
/// Signals that this session should terminate.
/// </summary>
internal void Stop()
{
lock(this)
{
m_Running = false;
}
}
/**********************************************/
// Private Methods
/**********************************************/
/// <summary>
/// Main thread function. Enters the main thread loop.
/// </summary>
private void handleSession()
{
m_Handler.SessionStarted(this);
while(m_Running)
{
processSession();
Thread.Sleep(200);
}
m_Handler.SessionEnded(this);
}
/// <summary>
/// Called every cycle to handle incoming responses and send out requests
/// </summary>
private void processSession()
{
ICollection responses = GetQueuedResponses();
foreach( Response response in responses )
{
if( response.Command == ConnectionHandler.MSG )
{
MimeMessage msg = new MimeMessage(response.Headers, response.Message, response.Parameters[0], response.Parameters[1]);
m_Handler.MessageReceived(this, msg);
}
else if( response.Command == ConnectionHandler.JOI )
{
m_Roster.Add(response.Parameters[0], response.Parameters[1]);
m_Handler.UserJoined(this, response.Parameters[0], response.Parameters[1]);
}
else if( response.Command == ConnectionHandler.BYE )
{
m_Roster.Remove(response.Parameters[0]);
m_Handler.UserDeparted(this, response.Parameters[0]);
}
else if( response.IsError )
{
m_Handler.ErrorReceived(this, m_Conn.GetErrorDescriptionForCommand(response.Command));
}
}
}
/// <summary>
/// Retrieves all the currently queue responses for processing
/// </summary>
/// <returns>A collection of responses to process</returns>
private ICollection GetQueuedResponses()
{
ICollection responses;
lock(m_ResponseQueue.SyncRoot)
{
responses = (ICollection) m_ResponseQueue.Clone();
m_ResponseQueue.Clear();
}
return responses;
}
/**********************************************/
// Public Methods
/**********************************************/
/// <summary>
/// Returns the list of users currently in the session
/// </summary>
/// <remarks>
/// This method is thread-safe. The dictionary returned is a clone of the actual
/// roster, so modifications will not be recognized inside the session.
/// </remarks>
/// <returns>A dictionary of friendly names keyed by their user handles.</returns>
public IDictionary GetRoster()
{
return (IDictionary)m_Roster.Clone();
}
/// <summary>
/// Sends a message to the session.
/// </summary>
/// <remarks>
/// <para>Any user joined in the session will receive the message. If this message is intended
/// private, the caller should use the <see cref="GetRoster"/> method to ensure that there
/// is only the intended recipient.</para>
/// <para>Message delivery is not garaunteed and may fail at any time. Sending messages after
/// the session has ended will do nothing and will not cause an error. The caller should ensure
/// that the session is open before sending a message, otherwise it will be lost.</para>
/// <para>This method is thread-safe, however, users could possibly join during the time when
/// the message is being sent.</para>
/// </remarks>
/// <param name="message">The message body text to send</param>
public void SendMessage(String message)
{
m_Conn.SendMessage(message);
}
/// <summary>
/// Sends a message to the session
/// </summary>
/// <remarks>
/// <para>The caller of this method must be careful to set all the headers properly.
/// You must at least have the MIME-Version and Content-Type header. Usually,
/// MIME-Version is always "1.0" and Content-Type is text/plain.
/// </para>
/// <para>Any user joined in the session will receive the message. If this message is intended
/// private, the caller should use the <see cref="GetRoster"/> method to ensure that there
/// is only the intended recipient.</para>
/// <para>Message delivery is not garaunteed and may fail at any time. Sending messages after
/// the session has ended will do nothing and will not cause an error. The caller should ensure
/// that the session is open before sending a message, otherwise it will be lost.</para>
/// <para>This method is thread-safe, however, users could possibly join during the time when
/// the message is being sent.</para>
/// </remarks>
/// <param name="message"></param>
public void SendMessage(MimeMessage message)
{
m_Conn.SendMessage(message);
}
/// <summary>
/// Requests that this session end
/// </summary>
/// <remarks>
/// This will send the BYE command to the switchboard and close the connection.
/// </remarks>
public void EndSession()
{
m_Conn.Stop();
}
/// <summary>Returns a string representation of this session. This method is intended for debugging purposes only.</summary>
public override string ToString(){ return "Session[" + SessionID + "]"; }
}
}
|