using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using Misuzilla.Net.Irc; namespace Misuzilla.Applications.TwitterIrcGateway { public abstract class SessionBase : MarshalByRefObject, IIrcMessageSendable { private Server _server; private List _connections = new List(); /// /// セッションが終了している途中かどうかを取得します。 /// public Boolean IsClosing { get; private set; } /// /// セッションは接続がなくなっても引き続き保持するかどうかを取得・設定します。 /// public Boolean IsKeepAlive { get; set; } /// /// セッションの固有のIDを取得します。接続してきたユーザを結びつけるために利用されます。 /// public Int32 Id { get; private set; } /// /// 現在のニックネームを取得・設定します。 /// public String CurrentNick { get; set; } /// /// 現在セッションにある接続のコレクションを取得します。 /// public IList Connections { get { return _connections.AsReadOnly(); } } /// /// セッションに接続が開始された際に発生するイベントです。 /// public event EventHandler ConnectionAttached; /// /// セッションから接続が切断された際に発生するイベントです。 /// public event EventHandler ConnectionDetached; /// /// セッションが終了する際に発生するイベントです。 /// public event EventHandler BeforeClosing; /// /// セッションが終了中に発生するイベントです。 /// public event EventHandler Closing; /// /// セッションが終了する処理を終えた際に発生するイベントです。 /// public event EventHandler AfterClosing; public SessionBase(Int32 id, Server server) { Id = id; _server = server; TraceLogger.Server.Information("Session Started: "+Id); } /// /// 接続をセッションに結びつけます。 /// /// public void Attach(ConnectionBase connection) { lock (_connections) lock (_server.Sessions) { _connections.Add(connection); connection.ConnectionEnded += ConnectionEnded; connection.MessageReceived += MessageReceived; SendGatewayServerMessage("Connection Attached: " + connection.ToString()); // ニックネームを合わせる if (String.IsNullOrEmpty(CurrentNick)) { CurrentNick = connection.UserInfo.Nick; } else { connection.SendServer(new NickMessage() {NewNick = CurrentNick}); connection.UserInfo.Nick = CurrentNick; } OnAttached(connection); OnConnectionAttached(new ConnectionAttachEventArgs {Connection = connection}); } } /// /// 接続をセッションから切り離します。キープアライブが有効な場合を除き接続数が0となるとセッションは終了します。 /// /// public void Detach(ConnectionBase connection) { lock (_connections) lock (_server.Sessions) { connection.ConnectionEnded -= ConnectionEnded; connection.MessageReceived -= MessageReceived; _connections.Remove(connection); SendGatewayServerMessage("Connection Detached: " + connection.ToString()); OnDetached(connection); OnConnectionDetached(new ConnectionAttachEventArgs {Connection = connection}); // 接続が0になったらセッション終了 if (_connections.Count == 0 && !IsKeepAlive) { Close(); } } } #region オーバーライドして使うメソッド /// /// 接続が結びつけられたときの処理です。 /// /// protected abstract void OnAttached(ConnectionBase connection); /// /// 接続が切り離されたときの処理です。 /// /// protected abstract void OnDetached(ConnectionBase connection); /// /// クライアントからIRCメッセージを受け取ったときの処理です。 /// /// protected abstract void OnMessageReceivedFromClient(MessageReceivedEventArgs e); #endregion #region イベントハンドラ private void MessageReceived(object sender, MessageReceivedEventArgs e) { // クライアントからきた PRIVMSG/NOTICE は他のクライアントにも投げる if (e.Message is PrivMsgMessage || e.Message is NoticeMessage) { lock (_connections) { foreach (ConnectionBase connection in _connections) { // 送信元には送らない if (connection != sender) connection.SendServer(e.Message); } } } // セッションの方で処理する OnMessageReceivedFromClient(e); } private void ConnectionEnded(object sender, EventArgs e) { Detach((ConnectionBase)sender); } #endregion protected virtual void OnConnectionAttached(ConnectionAttachEventArgs e) { if (ConnectionAttached != null) ConnectionAttached(this, e); } protected virtual void OnConnectionDetached(ConnectionAttachEventArgs e) { if (ConnectionDetached != null) ConnectionDetached(this, e); } /// /// セッションが終了しようとしているときの処理です。 /// protected virtual void OnBeforeClosing() { if (BeforeClosing != null) BeforeClosing(this, EventArgs.Empty); } /// /// セッションが終了中の処理です。 /// protected virtual void OnClosing() { if (Closing != null) Closing(this, EventArgs.Empty); } /// /// セッションが終了処理が完了したあとの処理です。 /// protected virtual void OnAfterClosing() { if (AfterClosing != null) AfterClosing(this, EventArgs.Empty); } /// /// すべての接続を切断してセッションを終了します。 /// public virtual void Close() { // Detachするので二重でここに来ないように。 if (IsClosing) return; OnBeforeClosing(); IsClosing = true; OnClosing(); lock (_connections) { lock (_server.Sessions) { TraceLogger.Server.Information("Session Closing: " + Id); List connections = new List(_connections); foreach (ConnectionBase connection in connections) { Detach(connection); connection.Close(); } _server.Sessions.Remove(Id); } } OnAfterClosing(); } #region IRC メッセージ処理 /// /// IRCメッセージを送信します /// /// public void Send(IRCMessage msg) { if (IsClosing) return; lock (_connections) foreach (ConnectionBase connection in _connections) connection.Send(msg); } /// /// JOIN などクライアントに返すメッセージを送信します /// /// public void SendServer(IRCMessage msg) { if (IsClosing) return; lock (_connections) foreach (ConnectionBase connection in _connections) connection.SendServer(msg); } /// /// IRCサーバからのメッセージを送信します /// /// public void SendServerMessage(IRCMessage msg) { if (IsClosing) return; lock (_connections) foreach (ConnectionBase connection in _connections) connection.SendServerMessage(msg); } /// /// Gatewayからのメッセージを送信します /// /// public void SendGatewayServerMessage(String message) { if (IsClosing) return; lock (_connections) foreach (ConnectionBase connection in _connections) connection.SendGatewayServerMessage(message); } /// /// サーバのエラーメッセージを送信します /// /// public void SendServerErrorMessage(String message) { // if (!_config.IgnoreWatchError) { SendGatewayServerMessage("エラー: " + message); } } /// /// サーバからクライアントにエラーリプライを返します。 /// /// エラーリプライ番号 /// リプライコマンドパラメータ public void SendErrorReply(ErrorReply errorNum, params String[] commandParams) { SendNumericReply((NumericReply)errorNum, commandParams); } /// /// サーバからクライアントにニュメリックリプライを返します。 /// /// リプライ番号 /// リプライコマンドパラメータ public void SendNumericReply(NumericReply numReply, params String[] commandParams) { if (IsClosing) return; lock (_connections) foreach (ConnectionBase connection in _connections) connection.SendNumericReply(numReply, commandParams); } #endregion } }