Logo Search packages:      
Sourcecode: galaxium version File versions  Download package

MsnConversation.cs

/*
 * Galaxium Messenger
 * Copyright (C) 2005-2007 Philippe Durand <draekz@gmail.com>
 * Copyright (C) 2005-2007 Ben Motmans <ben.motmans@gmail.com>
 * Copyright (C) 2008 Paul Burton <paulburton89@gmail.com>
 * 
 * License: GNU General Public License (GPL)
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Web.Services.Protocols;
using System.Xml;

using Anculus.Core;

using Galaxium.Client;
using Galaxium.Core;
using Galaxium.Gui;
using Galaxium.Protocol.Msn.Soap;

namespace Galaxium.Protocol.Msn
{
      public class MsnConversation : AbstractConversation, IComparable<MsnConversation>
      {
            enum ConversationTransport { Switchboard, Notification, OfflineSwitchboard, Offline };
            
            // Milliseconds to wait for an ack, any longer than this and we'll assume the send failed
            const int _ackTimeout = 30000;
            
            public event EventHandler<ActionMessageEventArgs> ActionMessageReceived;
            public event EventHandler<InviteReceivedEventArgs> ActivityInviteReceived;
            public event EventHandler<InkEventArgs> InkReceived;
            //public event TextMessageEventHandler MessageReceived;
            public event EventHandler<ContactEventArgs> NudgeReceived;
            public event EventHandler<ContactEventArgs> TypingReceived;
            public event EventHandler<WinkEventArgs> WinkReceived;
            public event EventHandler<VoiceClipEventArgs> VoiceClipReceived;
            public event EventHandler<InviteReceivedEventArgs> WebcamInviteReceived;
            
            public event EventHandler<ContactEventArgs> InviteContactFailed;
            public event EventHandler<InkEventArgs> InkSendFailed;
            public event EventHandler<TextMessageEventArgs> MessageSendFailed;
            public event EventHandler<ContactEventArgs> NudgeSendFailed;
            public event EventHandler<WinkEventArgs> WinkSendFailed;
            public event EventHandler<VoiceClipEventArgs> VoiceClipSendFailed;
            
            public event EventHandler CapabilitiesChanged;
            
            delegate void GotLockKeyCallback ();
            
            string _id;
            bool _listenerReady = false;
            Queue<IMsnContent> _inQueue = new Queue<IMsnContent> ();
            SBConnection _switchboard = null;
            ConversationTransport _transport = ConversationTransport.Switchboard;
            List<IEmoticon> _lastEmots;
            
            Dictionary<ContentCommand, DateTime> _sentWaitingAck = new Dictionary<ContentCommand, DateTime> ();
            ContentCommand _ackNextCmd;
            uint _ackTimerHandle;
            
            Guid _offlineRunID = Guid.NewGuid ();
            int _offlineNum;
            
            uint _typingTimerId;
            
            public override bool Active
            {
                  get { return _switchboard != null; }
                  set { base.Active = value; }
            }
            
            public bool CanInvite
            {
                  get { return _transport == ConversationTransport.Switchboard; }
            }
            
            public bool CanSendActionMessage
            {
                  get { return true; }
            }
            
            public bool CanSendEmoticons
            {
                  get { return _transport == ConversationTransport.Switchboard; }
            }
            
            public bool CanSendMessage
            {
                  get
                  {
                        if ((_transport == ConversationTransport.Notification) && (!ContactsOnline))
                              return false;
                        
                        return true;
                  }
            }
            
            public bool CanSendNudge
            {
                  get
                  {
                        if ((_transport == ConversationTransport.Notification) && (!ContactsOnline))
                              return false;
                        
                        return (_transport != ConversationTransport.Offline) && (_transport != ConversationTransport.OfflineSwitchboard);
                  }
            }
            
            public bool CanSendTyping
            {
                  get { return (_transport != ConversationTransport.Offline) && (_transport != ConversationTransport.OfflineSwitchboard); }
            }
            
            public bool CanSendWink
            {
                  get { return _transport == ConversationTransport.Switchboard; }
            }
            
            public bool CanSendVoiceClip
            {
                  get { return _transport == ConversationTransport.Switchboard; }
            }
            
            public bool CanStartActivity
            {
                  get { return _transport == ConversationTransport.Switchboard; }
            }
            
            public string ID
            {
                  get { return _id; }
            }
            
            public bool ListenerReady
            {
                  get { return _listenerReady; }
                  set
                  {
                        //Anculus.Core.Log.Debug ("ListenerReady now {0}", value);
                        
                        _listenerReady = value;
                        ProcessInQueue ();
                  }
            }
            
            public string ActivityPageUrl
            {
                  get
                  {
                        (Session as MsnSession).ClientConfig.EnsureConfig (null);
                        
                        if ((Session as MsnSession).ClientConfig.ConfigRoot == null)
                        {
                              Log.Warn ("Unable to retrieve client config");
                              return string.Empty;
                        }
                        
                        XmlElement xml = MsnXmlUtility.FindElement ((Session as MsnSession).ClientConfig.ConfigRoot, "LocalizedConfig/AppDirConfig/AppDirPageURL");
                        
                        if (xml == null)
                        {
                              Log.Warn ("Unable to find application directory page config");
                              return string.Empty;
                        }
                        
                        return xml.InnerText;
                  }
            }
            
            internal SBConnection Switchboard
            {
                  get { return _switchboard; }
            }
            
            bool ContactsOnline
            {
                  get
                  {
                        bool ret = true;
                        
                        foreach (MsnContact contact in _contacts)
                              ret &= contact.Presence != MsnPresence.Offline;
                        
                        return ret;
                  }
            }
            
            public bool Typing
            {
                  get { return _typingTimerId != 0; }
                  set
                  {
                        if ((!value) && (_typingTimerId != 0))
                        {
                              // Stop the timer
                              TimerUtility.RemoveCallback (_typingTimerId);
                              _typingTimerId = 0;
                        }
                        else if (value && (_typingTimerId == 0))
                        {
                              SendTyping ();
                              _typingTimerId = TimerUtility.RequestInfiniteCallback (TypingTimerCallback, 2000);
                        }
                  }
            }
            
            public MsnConversation (params MsnContact[] contacts)
                  : base (contacts[0], contacts[0].Session)
            {
                  _id = Guid.NewGuid ().ToString ();
                  
                  lock (_contacts)
                  {
                        foreach (MsnContact contact in contacts)
                        {
                              _contacts.Add (contact);
                              AddContactEventHandlers (contact);
                        }
                  }
                  
                  SelectTransport ();
            }
            
            // Constructor called when a switchboard receives content
            public MsnConversation (SBConnection sb)
                  : base (sb.Contacts[0], sb.Session)
            {
                  Activate (sb);
            }
            
            // Constructor called when an OIM is received
            public MsnConversation (MsnContact contact, string id)
                  : base (contact, contact.Session)
            {
                  _id = id;
                  SelectTransport ();
            }
            
            internal void Activate (SBConnection sb)
            {
                  _switchboard = sb;
                  _id = sb.ID;
                  SetTransport (ConversationTransport.Switchboard);
                  
                  foreach (MsnContact contact in sb.Contacts)
                  {
                        // Contact could already be present if reactivating an inactive conversation
                        
                        if (!_contacts.Contains (contact))
                              _contacts.Add (contact);
                  }
                                    
                  SetupSwitchboard ();
            }
            
            // Select the way in which we'll send content to contacts
            void SelectTransport ()
            {
                  bool _contactWindowsLive = true;
                  lock (ContactCollection)
                  {
                        foreach (MsnContact contact in ContactCollection)
                              _contactWindowsLive &= contact.Network == Network.WindowsLive;
                  }
                  
                  if (_contactWindowsLive)
                  {
                        // If the contact is offline, WLM always tries a switchboard
                        // and falls back to sending via OIMs if the invite fails.
                        // We'll do the same with OfflineSwitchboard
                        
                        if (ContactsOnline)
                              SetTransport (ConversationTransport.Switchboard);
                        else
                              SetTransport (ConversationTransport.OfflineSwitchboard);
                  }
                  else
                        SetTransport (ConversationTransport.Notification);
            }
            
            void SetTransport (ConversationTransport newTransport)
            {
                  ThreadUtility.Check ();
                  
                  if (_transport == newTransport)
                        return;
                  
                  Log.Debug ("Conversation {0} switching to {1} transport", _id, newTransport);
                  
                  _transport = newTransport;
                  OnCapabilitiesChanged ();
            }
            
            // We need a switchboard, so ensure one is setup
            void NeedSwitchboard ()
            {
                  ThreadUtility.Check ();
                  
                  ThrowUtility.ThrowIfFalse ("NeedSwitchboard called when not using a switchboard???", (_transport == ConversationTransport.Switchboard) || (_transport == ConversationTransport.OfflineSwitchboard));
                  
                  if (_switchboard != null)
                        return;
                  
                  // We don't already have a switchboard, so create one
                  
                  // If everyone has left, we invite the primary contact again
                  lock (_contacts)
                  {
                        if (ContactCollection.Count == 0)
                              _contacts.Add (PrimaryContact);
                        
                        MsnContact[] contacts = new MsnContact[ContactCollection.Count];
                        ContactCollection.CopyTo (contacts, 0);
                  
                        //Log.Debug ("Calling GETSWITCHBOARD from the MSNConversation");
                        _switchboard = (Session as MsnSession).GetSwitchboard (true, contacts);
                        
                        SetupSwitchboard ();
                  }
            }
            
            // Setup event handlers we need
            void SetupSwitchboard ()
            {
                  _switchboard.Established += OnSBEstablished;
                  _switchboard.Closed += OnSBClosed;
                  _switchboard.ContactJoined += OnSBContactJoined;
                  _switchboard.ContactLeft += OnSBContactLeft;
                  _switchboard.InviteContactFailed += OnSBInviteContactFailed;
                  
                  _switchboard.AddContentHandler (typeof (ControlContent), new ContentHandler (ProcessContent));
                  _switchboard.AddContentHandler (typeof (DataCastContent), new ContentHandler (ProcessContent));
                  _switchboard.AddContentHandler (typeof (EmoticonContent), new ContentHandler (ProcessContent));
                  _switchboard.AddContentHandler (typeof (PlainTextContent), new ContentHandler (ProcessContent));
            }
            
            void OnSBEstablished (object sender, EventArgs args)
            {
                  base.OnEstablished (new ConversationEventArgs (this));
            }
            
            void OnSBClosed (object sender, EventArgs args)
            {
                  if (!_switchboard.Reconnecting)
                  {
                        Anculus.Core.Log.Debug ("Switchboard closed, cleaning up");
                        
                        _switchboard.Established -= OnSBEstablished;
                        _switchboard.Closed -= OnSBClosed;
                        _switchboard.ContactJoined -= OnSBContactJoined;
                        _switchboard.ContactLeft -= OnSBContactLeft;
                        _switchboard.InviteContactFailed -= OnSBInviteContactFailed;
                        
                        _switchboard.RemoveContentHandler (typeof (ControlContent), new ContentHandler (ProcessContent));
                        _switchboard.RemoveContentHandler (typeof (DataCastContent), new ContentHandler (ProcessContent));
                        _switchboard.RemoveContentHandler (typeof (EmoticonContent), new ContentHandler (ProcessContent));
                        _switchboard.RemoveContentHandler (typeof (PlainTextContent), new ContentHandler (ProcessContent));
                        
                        lock (_sentWaitingAck)
                        {
                              while (_switchboard.OutQueue.Count > 0)
                              {
                                    IMsnCommand cmd = _switchboard.OutQueue[0];
                                    _switchboard.OutQueue.RemoveAt (0);
                                    
                                    if (cmd is ContentCommand)
                                    {
                                          if (_sentWaitingAck.ContainsKey (cmd as ContentCommand))
                                                _sentWaitingAck.Remove (cmd as ContentCommand);
                                          
                                          OnSendFailed (cmd as ContentCommand);
                                    }
                              }
                        }
                        
                        _switchboard = null;
                        
                        // If we've fallen back to OIMs, a closed switchboard shouldn't change that
                        if (_transport != ConversationTransport.Offline)
                              SelectTransport ();
                        
                        base.OnClosed (new ConversationEventArgs (this));
                  }
                  else
                        Log.Debug ("Switchboard reconnecting");
            }
            
            void OnSBInviteContactFailed (object sender, ContactEventArgs args)
            {
                  Log.Debug ("Failed to invite {0}", args.Contact.UniqueIdentifier);
                  
                  OnInviteContactFailed (args);
                  
                  if (_contacts.Contains (args.Contact))
                  {
                        // This was one of the contacts present before the switchboard was created
                        
                        // Remove the contact
                        _contacts.Remove (args.Contact);
                        RemoveContactEventHandlers (args.Contact);
                        
                        if (_contacts.Count == 0)
                        {
                              // This was the only contact in the switchboard/conversation
                              
                              if (args.Contact.Presence == MsnPresence.Offline)
                              {
                                    // Fallback to sending OIMs
                                    
                                    SetTransport (ConversationTransport.Offline);
                                    
                                    // We still need the contact
                                    // Event handlers should still be present because this contact must be primary
                                    _contacts.Add (args.Contact);
                                    
                                    List<IMsnCommand> local = new List<IMsnCommand> (_switchboard.OutQueue);
                                    foreach (IMsnCommand cmd in local)
                                    {
                                          if (!(cmd is ContentCommand))
                                                continue;
                                          
                                          _switchboard.OutQueue.Remove (cmd);
                                          
                                          if (_sentWaitingAck.ContainsKey (cmd as ContentCommand))
                                                _sentWaitingAck.Remove (cmd as ContentCommand);
                                          
                                          IMsnContent content = (cmd as ContentCommand).Content;
                                          Send (content);
                                    }
                              }
                              
                              // We don't need the switchboard anymore
                              _switchboard.Disconnect ();
                        }
                  }
            }
            
            void OnSBContactJoined (object sender, ContactEventArgs args)
            {
                  lock (ContactCollection)
                  {
                        // This can happen if we've just opened the switchboard
                        if (ContactCollection.Contains (args.Contact))
                              return;
                        
                        ContactCollection.Add (args.Contact);
                  }
                  
                  AddContactEventHandlers (args.Contact);

                  OnContactJoined (new ContactActionEventArgs (Session, this, args.Contact));
                  OnCapabilitiesChanged ();
            }
            
            void OnSBContactLeft (object sender, ContactEventArgs args)
            {
                  if (args.Contact == PrimaryContact)
                  {
                        if (ContactCollection.Count > 1)
                        {
                              // The primary contact left, switch the primary contact to someone else
                              // This also makes sure if this conversation opens a new switchboard we
                              // invite the last contact, rather than the original primary who left
                              
                              foreach (MsnContact contact in ContactCollection)
                              {
                                    _primaryContact = contact;
                                    break;
                              }
                        }
                        else
                        {
                              // Don't remove if this is the primary contact and will remain so
                              
                              SelectTransport ();
                              OnCapabilitiesChanged ();
                              return;
                        }
                  }
                  

                  lock (ContactCollection)
                        ContactCollection.Remove (args.Contact);
                  
                  RemoveContactEventHandlers (args.Contact);
                  
                  OnContactLeft (new ContactActionEventArgs (Session, this, args.Contact));
                  SelectTransport ();
                  OnCapabilitiesChanged ();
                  
                  if (ContactCollection.Count == 0)
                        OnAllContactsLeft (new ConversationEventArgs (this));
            }
            
            void AddContactEventHandlers (IContact contact)
            {
                  contact.PresenceChange += ContactPresenceChange;
            }
            
            void RemoveContactEventHandlers (IContact contact)
            {
                  if (contact != _primaryContact)
                        contact.PresenceChange -= ContactPresenceChange;
            }
            
            void ContactPresenceChange (object sender, EntityChangeEventArgs<IPresence> args)
            {
                  SelectTransport ();
                  OnCapabilitiesChanged ();
            }
            
            public override void InviteContact (IContact contact)
            {
                  if (!CanInvite)
                        return;
                  
                  if (_contacts.Contains (contact))
                  {
                        Log.Warn ("Attempted to invite a contact already in the conversation {0}", contact.UniqueIdentifier);
                        return;
                  }
                  
                  NeedSwitchboard ();
                  _switchboard.Invite (contact as MsnContact);
            }
            
            public override void Close ()
            {
                  if (_ackTimerHandle != 0)
                  {
                        TimerUtility.RemoveCallback (_ackTimerHandle);
                        _ackTimerHandle = 0;
                  }
                  
                  // Don't close the switchboard unless it was a multi-person conversation!
                  // It might be in use elsewhere (eg. P2P)
                  
                  if ((_contacts.Count > 1) && (_switchboard != null))
                        _switchboard.Disconnect ();
            }
            
#region Send Methods
            public void Send (IMsnContent content)
            {
                  if (_transport == ConversationTransport.Notification)
                  {
                        // Send message via notification server using UUM
                        // eg. We're sending to a yahoo contact
                        
                        lock (ContactCollection)
                        {
                              foreach (MsnContact contact in ContactCollection)
                              {
                                    UUMCommand cmd = content.ToCommand<UUMCommand> ();
                                    cmd.Destination = contact;
                                    
                                    //TODO: Can we set the UUM type some other way?
                                    // I don't like it but can't think of anything better
                                    if (content is PlainTextContent)
                                          cmd.Type = UUMType.TextMessage;
                                    else if (content is ControlContent)
                                          cmd.Type = UUMType.TypingUser;
                                    else
                                          cmd.Type = UUMType.Nudge;
                                    
                                    (Session as MsnSession).Connection.Send (cmd, delegate (IMsnCommand response)
                                    {
                                          if (response is NAKCommand)
                                                OnSendFailed (cmd);
                                    });
                              }
                        }
                  }
                  else if (_transport == ConversationTransport.Offline)
                  {
                        // Contacts are not online, we need to send using Offline IM

                        SendOIM (content);
                  }
                  else
                  {
                        // Contacts are all capable of using a switchboard
                        // and all are online, so send over switchboard using MSG
                        
                        NeedSwitchboard ();
                        
                        // Don't bother sending control content (typing notification) if
                        // the switchboard isn't ready yet
                        if ((content is ControlContent) && (!_switchboard.CanSend))
                              return;
                        
                        MSGCommand cmd = content.ToCommand<MSGCommand> ();
                        
                        if (cmd.AckType == MSGAckType.Always)
                        {
                              lock (_sentWaitingAck)
                              {
                                    //Log.Debug ("Sending message we excpect an ACK for");
                                    
                                    _sentWaitingAck.Add (cmd, DateTime.Now.AddMilliseconds (_ackTimeout));
                                    UpdateAckTimer ();
                              }
                        }
                        
                        _switchboard.Send (cmd, delegate (IMsnCommand response)
                        {
                              lock (_sentWaitingAck)
                              {
                                    //Log.Debug ("Received response, removing command from _sentWaitingAck list");
                                    
                                    if (_sentWaitingAck.ContainsKey (cmd))
                                          _sentWaitingAck.Remove (cmd);
                              }

                              UpdateAckTimer ();
                              
                              if (response is NAKCommand)
                                    OnSendFailed (cmd);
                        });
                  }
            }
            
            public void SendActionMessage (string msg)
            {
                  if (!CanSendActionMessage)
                        return;
                  
                  DataCastContent content = new DataCastContent (Session as MsnSession, 4);
                  content.MIMEBody["Data"] = msg;
                  
                  Send (content);
            }
            
            void SendEmoticons (MsnTextMessage msg)
            {
                  if (!CanSendEmoticons)
                        return;
                  
                  List<MsnEmoticon> emoticons = new List<MsnEmoticon> ();
                  
                  foreach (ITextChunk chunk in msg.Chunks)
                  {
                        if (chunk.Type != TextChunkType.Emoticon)
                              continue;
                        
                        IEmoticon emot = ((EmoticonTextChunk)chunk).Emoticon;
                        
                        if (EmoticonUtility.IsStandard (emot))
                              continue;
                        
                        if (emot is MsnEmoticon)
                              emoticons.Add (emot as MsnEmoticon);
                        else
                              emoticons.Add (new MsnEmoticon (Session as MsnSession, emot));
                  }
                  
                  if (emoticons.Count > 0)
                  {
                        EmoticonContent content = new EmoticonContent (Session as MsnSession);
                        content.Emoticons = emoticons;
                        
                        Send (content);
                  }
            }
            
            public void SendMessage (MsnTextMessage msg)
            {
                  if (!CanSendMessage)
                        return;
                  
                  SendEmoticons (msg);
                  
                  PlainTextContent content = new PlainTextContent (Session as MsnSession);
                  content.Message = msg;
                  
                  Send (content);
                  
                  LogMessage (msg);
            }
            
            public void SendNudge ()
            {
                  if (!CanSendNudge)
                        return;
                  
                  DataCastContent content = new DataCastContent (Session as MsnSession, 1);
                  
                  Send (content);
            }
            
            public void SendTyping ()
            {
                  if (!CanSendTyping)
                        return;
                  
                  ControlContent content = new ControlContent (Session as MsnSession);
                  
                  Send (content);
            }
            
            public void SendWink (MsnWink wink)
            {
                  if (!CanSendWink)
                        return;
                  
                  DataCastContent content = new DataCastContent (Session as MsnSession, 2);
                  content.MIMEBody["Data"] = wink.Context;
                  
                  Send (content);
            }
#endregion
            
            internal void ProcessContent (IMsnContent content)
            {
                  //Anculus.Core.Log.Debug ("Process {0}, {1}", content.GetType ().Name, _listenerReady ? "ready" : "not ready");
                  
                  if (content is PlainTextContent)
                  {
                        ReceivedMessageActivity activity = new ReceivedMessageActivity (this, (content as PlainTextContent).Message);
                        ActivityUtility.EmitActivity (this, activity);
                        
                        // If an activity handler sets this to true, we don't display the message
                        // In all likelihood, this wasn't actually a regular message, rather
                        // something like a Messenger Plus! sound which has been handled elsewhere
                        
                        //Anculus.Core.Log.Debug ("Activity Handled? {0}", activity.Handled);
                        
                        if (activity.Handled)
                        {
                              Log.Debug ("Activity handled, dropping message");
                              return;
                        }
                  }
                  else if (content is DataCastContent)
                  {
                        DataCastContent cast = content as DataCastContent;
                        
                        if (cast.ID == 1)
                        {
                              ActivityUtility.EmitActivity (this, new ReceivedNudgeActivity (this));
                        }
                        else if (cast.ID == 2)
                        {
                              MsnWink wink = MsnObject.Load (Session as MsnSession, cast.MIMEBody["Data"]) as MsnWink;
                              ActivityUtility.EmitActivity (this, new ReceivedWinkActivity (this, wink));
                        }
                        else if (cast.ID == 3)
                        {
                              MsnVoiceClip clip = MsnObject.Load (Session as MsnSession, cast.MIMEBody["Data"]) as MsnVoiceClip;
                              ActivityUtility.EmitActivity (this, new ReceivedVoiceClipActivity (this, clip));
                        }
                  }
                  
                  _inQueue.Enqueue (content);
                  ProcessInQueue ();
            }
            
            void ProcessInQueue ()
            {
                  if (!_listenerReady)
                        return;
                  
                  while (_inQueue.Count > 0)
                  {
                        IMsnContent content = _inQueue.Dequeue ();
                        
                        //Anculus.Core.Log.Debug ("Processing {0} from queue", content);
                        
                        if (content is PlainTextContent)
                              OnPlainTextContentReceived (content as PlainTextContent);
                        else if (content is EmoticonContent)
                              OnEmoticonContentReceived (content as EmoticonContent);
                        else if (content is ControlContent)
                              OnControlContentReceived (content as ControlContent);
                        else if (content is DataCastContent)
                              OnDataCastContentReceived (content as DataCastContent);
                  }
            }
            
#region Content Handlers
            protected void OnPlainTextContentReceived (PlainTextContent content)
            {
                  if (_lastEmots != null)
                  {
                        (content.Message as MsnTextMessage).CustomEmoticons = _lastEmots;
                        _lastEmots = null;
                  }
                  
                  ITextMessage msg = content.Message;
                  OnMessageReceived (new TextMessageEventArgs (msg));
                  LogMessage (msg);
            }
            
            protected void OnEmoticonContentReceived (EmoticonContent content)
            {
                  _lastEmots = new List<IEmoticon> (content.Emoticons.ToArray ());
                  
                  foreach (MsnEmoticon emot in _lastEmots)
                        emot.Request ();
            }
            
            protected void OnControlContentReceived (ControlContent content)
            {
                  OnTypingReceived (new ContactEventArgs (content.Source as MsnContact));
            }
            
            protected void OnDataCastContentReceived (DataCastContent content)
            {
                  if (content.ID == 1)
                  {
                        // Nudge
                        
                        OnNudgeReceived (new ContactEventArgs (content.Source as MsnContact));
                  }
                  else if (content.ID == 2)
                  {
                        // Wink
                        
                        MsnWink wink = MsnObject.Load (Session as MsnSession, content.MIMEBody["Data"]) as MsnWink;
                        
                        if (wink == null)
                        {
                              Log.Warn ("Received wink but unable to parse context");
                              return;
                        }
                        
                        OnWinkReceived (new WinkEventArgs (wink));
                  }
                  else if (content.ID == 3)
                  {
                        // Voice clip
                        
                        MsnVoiceClip clip = MsnObject.Load (Session as MsnSession, content.MIMEBody["Data"]) as MsnVoiceClip;
                        
                        if (clip == null)
                        {
                              Log.Warn ("Received voice clip but unable to parse context");
                              return;
                        }
                        
                        OnVoiceClipReceived (new VoiceClipEventArgs (clip));
                  }
                  else if (content.ID == 4)
                  {
                        // Action Message
                        
                        OnActionMessageReceived (new ActionMessageEventArgs (content.MIMEBody["Data"].Value));
                  }
                  else
                        Log.Warn ("Unknown DataCast Received: {0}", content.ID);
            }
#endregion
            
#region Event Raisers
            protected void OnInviteContactFailed (ContactEventArgs args)
            {
                  if (InviteContactFailed != null)
                        InviteContactFailed (this, args);
            }
            
            protected void OnCapabilitiesChanged ()
            {
                  if (CapabilitiesChanged != null)
                        CapabilitiesChanged (this, EventArgs.Empty);
            }
            
            protected void OnActionMessageReceived (ActionMessageEventArgs args)
            {
                  LogEvent (DateTime.Now, args.Message);
                  
                  if (ActionMessageReceived != null)
                        ActionMessageReceived (this, args);
            }
            
            protected void OnActivityInviteReceived (InviteReceivedEventArgs args)
            {
                  if (ActivityInviteReceived != null)
                        ActivityInviteReceived (this, args);
            }
            
            protected void OnInkReceived (InkEventArgs args)
            {
                  if (InkReceived != null)
                        InkReceived (this, args);
            }
            
            protected void OnNudgeReceived (ContactEventArgs args)
            {
                  if (NudgeReceived != null)
                        NudgeReceived (this, args);
            }
            
            protected void OnSeeWebcamInviteReceived (InviteReceivedEventArgs args)
            {
                  if (WebcamInviteReceived != null)
                        WebcamInviteReceived (this, args);
            }
            
            protected void OnTypingReceived (ContactEventArgs args)
            {
                  if (TypingReceived != null)
                        TypingReceived (this, args);
            }
            
            protected void OnWinkReceived (WinkEventArgs args)
            {
                  if (WinkReceived != null)
                        WinkReceived (this, args);
            }
            
            protected void OnVoiceClipReceived (VoiceClipEventArgs args)
            {
                  if (VoiceClipReceived != null)
                        VoiceClipReceived (this, args);
            }
            
            protected void OnInkSendFailed (InkEventArgs args)
            {
                  if (InkSendFailed != null)
                        InkSendFailed (this, args);
            }
            
            protected void OnMessageSendFailed (TextMessageEventArgs args)
            {
                  if (MessageSendFailed != null)
                        MessageSendFailed (this, args);
            }
            
            protected void OnNudgeSendFailed (ContactEventArgs args)
            {
                  if (NudgeSendFailed != null)
                        NudgeSendFailed (this, args);
            }
            
            protected void OnWinkSendFailed (WinkEventArgs args)
            {
                  if (WinkSendFailed != null)
                        WinkSendFailed (this, args);
            }
            
            protected void OnVoiceClipSendFailed (VoiceClipEventArgs args)
            {
                  if (VoiceClipSendFailed != null)
                        VoiceClipSendFailed (this, args);
            }
#endregion
            
            void OnSendFailed (ContentCommand cmd)
            {
                  IMsnContent content = cmd.Content;
                  
                  if (content == null)
                  {
                        Anculus.Core.Log.Warn ("Failed to send ContentCommand, but content is null");
                        return;
                  }
                  
                  Anculus.Core.Log.Debug ("Failed to send {0}", content);
                  
                  if (content is PlainTextContent)
                        OnMessageSendFailed (new TextMessageEventArgs ((content as PlainTextContent).Message, PrimaryContact));
                  else if (content is DataCastContent)
                  {
                        int id = (content as DataCastContent).ID;
                        
                        if (id == 1)
                              OnNudgeSendFailed (new ContactEventArgs (PrimaryContact));
                        else if (id == 2)
                              OnWinkSendFailed (new WinkEventArgs (MsnObject.Load (Session as MsnSession, (content as DataCastContent).DataString) as MsnWink));
                        else if (id == 3)
                              OnVoiceClipSendFailed (new VoiceClipEventArgs (MsnObject.Load (Session as MsnSession, (content as DataCastContent).DataString) as MsnVoiceClip));
                  }
            }
            
            internal void EmitActivityInvite (P2PActivity activity)
            {
                  Anculus.Core.Log.Debug ("Invited to start '{0}' (AppID {1})", activity.Name, activity.AppID);
                  
                  OnActivityInviteReceived (new InviteReceivedEventArgs (activity));
            }
            
            internal void EmitSeeWebcamInvite (P2PViewWebcam seecam)
            {
                  Anculus.Core.Log.Debug ("Invited to see webcam");
                  
                  OnSeeWebcamInviteReceived (new InviteReceivedEventArgs (seecam));
            }
            
            public void EmitActionMessage (string msg)
            {
                  // Create the content & process it so it gets queued instead of firing off the event
                  // immediately
                  
                  //TODO: I don't like this, it's a bit hackish. The queue for incoming content
                  // should be modified to allow queueing without all this mess
                  
                  DataCastContent content = new DataCastContent (Session as MsnSession, 4);
                  content.MIMEBody["Data"] = msg;
                  
                  ProcessContent (content);
            }
            
#region Ack Timer
            void UpdateAckTimer ()
            {
                  lock (_sentWaitingAck)
                  {
                        if (_sentWaitingAck.Count == 0)
                        {
                              // Not waiting for any acks
                              
                              //Log.Debug ("Not expecting any ACKs");
                              
                              if (_ackTimerHandle != 0)
                              {
                                    TimerUtility.RemoveCallback (_ackTimerHandle);
                                    _ackTimerHandle = 0;
                              }
                              
                              return;
                        }
                        
                        ContentCommand nextCmd = null;
                        
                        foreach (ContentCommand cmd in _sentWaitingAck.Keys)
                        {
                              if ((nextCmd == null) || (_sentWaitingAck[cmd] < _sentWaitingAck[nextCmd]))
                                    nextCmd = cmd;
                        }
                        
                        if (nextCmd == _ackNextCmd)
                        {
                              // No change since the last update
                              return;
                        }
                        
                        _ackNextCmd = nextCmd;
                        
                        if (_ackTimerHandle != 0)
                        {
                              TimerUtility.RemoveCallback (_ackTimerHandle);
                              _ackTimerHandle = 0;
                        }
                        
                        int interval = (int)_sentWaitingAck[nextCmd].Subtract (DateTime.Now).TotalMilliseconds;
                        //Log.Debug ("Expecting next ACK within {0}ms", interval);
                        
                        // interval can be negative if it took longer for AckTimerElapsed to process
                        // the failures than was left before the next command timed out
                        
                        if (interval <= 500)
                              AckTimerElapsed ();
                        else
                              _ackTimerHandle = TimerUtility.RequestCallback (AckTimerElapsed, interval);
                  }
            }
            
            void AckTimerElapsed ()
            {
                  lock (_sentWaitingAck)
                  {
                        // We can't loop through sentWaitingAck.Keys directly because we'll be modifying it
                        List<ContentCommand> local = new List<ContentCommand> (_sentWaitingAck.Keys);
                        
                        foreach (ContentCommand cmd in local)
                        {
                              if (_sentWaitingAck[cmd] < DateTime.Now)
                              {
                                    _sentWaitingAck.Remove (cmd);
                                    
                                    if ((_switchboard != null) && (_switchboard.OutQueue.Contains (cmd)))
                                          _switchboard.OutQueue.Remove (cmd);
                                    
                                    OnSendFailed (cmd);
                              }
                        }
                  }
                  
                  //Log.Debug ("Done reporting send failures");
                  UpdateAckTimer ();
            }
#endregion
            
#region Offline Messaging
            void SendOIM (IMsnContent content)
            {
                  if (!(content is PlainTextContent))
                  {
                        //TODO: can we send anything other than text?
                        OnSendFailed (content.ToCommand<MSGCommand> ());
                        return;
                  }
                  
                  (Session as MsnSession).RequireSecurityTokens (new ExceptionDelegate (delegate
                  {
                        SendOIM (PrimaryContact as MsnContact, content, ++_offlineNum);
                        
                  }), SecurityToken.Messenger);
            }
            
            void SendOIM (MsnContact contact, IMsnContent content, int num)
            {
                  MIMECollection mime = new MIMECollection ();
                  mime["MIME-Version"] = "1.0";
                  mime["Content-Type"] = "text/plain";
                  mime["Content-Type"][" charset"] = "UTF-8";
                  mime["Content-Transfer-Encoding"] = "base64";
                  mime["X-OIM-Message-Type"] = "OfflineMessage";
                  mime["X-OIM-Run-Id"] = _offlineRunID.ToString ("B").ToUpper ();
                  mime["X-OIM-Sequence-Num"] = num.ToString ();
                  
                  string contentStr = mime.ToString () + "\r\n" + EncodingUtility.Base64Encode ((content as PlainTextContent).Message.GetText (), Encoding.UTF8);
                  
                  (Session as MsnSession).OIMStoreService.toHeader = new Soap.Headers.ToHeader (contact);
                  (Session as MsnSession).OIMStoreService.sequenceHeader.MessageNumber = num;
                  
                  (Session as MsnSession).OIMStoreService.BeginStore2 ("text", contentStr, delegate (IAsyncResult asyncResult)
                  {
                        try
                        {
                              (Session as MsnSession).OIMStoreService.EndStore2 (asyncResult);
                              
                              Anculus.Core.Log.Debug ("Successfully stored OIM");
                        }
                        catch (SoapException ex)
                        {
                              if (ex.Code.Name == "AuthenticationFailed")
                              {
                                    if (ex.Detail == null)
                                    {
                                          Anculus.Core.Log.Warn ("Unable to find SOAP fault detail, if you're using mono 1.2.4 or older please update");
                                          OnSendFailed (content.ToCommand<MSGCommand> ());
                                          return;
                                    }
                                    
                                    string authPolicy = MsnXmlUtility.FindText (ex.Detail as XmlElement, "RequiredAuthPolicy");
                                    string lockKeyChallenge = MsnXmlUtility.FindText (ex.Detail as XmlElement, "LockKeyChallenge");
                                    
                                    if (string.IsNullOrEmpty (lockKeyChallenge))
                                    {
                                          Anculus.Core.Log.Error (ex, "Error storing OIM");
                                          OnSendFailed (content.ToCommand<MSGCommand> ());
                                          return;
                                    }
                                    
                                    Anculus.Core.Log.Debug ("Got LockKeyChallenge: {0} ({1} auth)", lockKeyChallenge, authPolicy);
                                    
                                    (Session as MsnSession).OIMStoreService.ticketHeader.LockKey = new Challenge (MsnConstants.ProductID, MsnConstants.ProductKey).GetChallengeResponse (lockKeyChallenge);
                                    
                                    Anculus.Core.Log.Debug ("Calculated LockKey: {0}", (Session as MsnSession).OIMStoreService.ticketHeader.LockKey);
                                    
                                    //Send this again
                                    SendOIM (contact, content, num);
                              }
                              else
                              {
                                    Anculus.Core.Log.Error ("Error storing OIM: {0}", ex.Code.Name);
                                    OnSendFailed (content.ToCommand<MSGCommand> ());
                              }
                        }
                        catch (Exception ex)
                        {
                              Anculus.Core.Log.Error (ex, "Error storing OIM");
                              OnSendFailed (content.ToCommand<MSGCommand> ());
                        }
                  }, null);
            }
#endregion
            
            void TypingTimerCallback ()
            {
                  SendTyping ();
            }
            
            public int CompareTo (MsnConversation x)
            {
                  return _id.CompareTo (x._id);
            }
      }
}

Generated by  Doxygen 1.6.0   Back to index