[smuxi/experiments/new_chat_sync_manager: 1/5] Frontend(-GNOME): ChatViewSyncManager's internals refactored into a state-machine
- From: Mirco M. M. Bauer <mmmbauer src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [smuxi/experiments/new_chat_sync_manager: 1/5] Frontend(-GNOME): ChatViewSyncManager's internals refactored into a state-machine
- Date: Sun, 20 Jul 2014 19:29:33 +0000 (UTC)
commit d5914d8942878444150eda0b5be11c2e4f60f6d7
Author: Oliver Schneider <mail oli-obk de>
Date: Wed Jul 16 21:37:03 2014 +0200
Frontend(-GNOME): ChatViewSyncManager's internals refactored into a state-machine
src/Frontend-GNOME/ChatViewManager.cs | 36 ++-
src/Frontend/ChatViewSyncManager.cs | 579 ++++++++++++++++++++++++++-------
2 files changed, 476 insertions(+), 139 deletions(-)
---
diff --git a/src/Frontend-GNOME/ChatViewManager.cs b/src/Frontend-GNOME/ChatViewManager.cs
index afae62a..0551d59 100644
--- a/src/Frontend-GNOME/ChatViewManager.cs
+++ b/src/Frontend-GNOME/ChatViewManager.cs
@@ -114,6 +114,7 @@ namespace Smuxi.Frontend.Gnome
SyncManager = new ChatViewSyncManager();
SyncManager.ChatAdded += OnChatAdded;
SyncManager.ChatSynced += OnChatSynced;
+ SyncManager.ChatRemoved += OnChatRemoved;
SyncManager.WorkerException += OnWorkerException;
}
@@ -131,25 +132,33 @@ namespace Smuxi.Frontend.Gnome
public override void RemoveChat(ChatModel chat)
{
- ChatView chatView = GetChat(chat);
- if (chatView == null) {
- #if LOG4NET
+ if (chat == null) {
+#if LOG4NET
f_Logger.Warn("RemoveChat(): chatView is null!");
#endif
return;
}
+ SyncManager.QueueRemove(chat);
+ }
- f_Notebook.RemovePage(f_Notebook.PageNum(chatView));
- TreeView.Remove(chatView);
- f_Chats.Remove(chatView);
- SyncManager.Remove(chat);
- SyncedChats.Remove(chatView);
+ void OnChatRemoved(object sender, ChatViewRemovedEventArgs e)
+ {
+ var chatView = (ChatView)e.ChatView;
+ GLib.Idle.Add(delegate {
+ f_Notebook.RemovePage(f_Notebook.PageNum(chatView));
+ TreeView.Remove(chatView);
+ f_Chats.Remove(chatView);
+ SyncedChats.Remove(chatView);
- if (ChatRemoved != null) {
- ChatRemoved(this, new ChatViewManagerChatRemovedEventArgs(chatView));
- }
+ if (ChatRemoved != null) {
+ ChatRemoved(this, new ChatViewManagerChatRemovedEventArgs(chatView));
+ }
- chatView.Dispose();
+ chatView.Dispose();
+
+ SyncManager.QueueRemoveFinished(chatView.ChatModel);
+ return false;
+ });
}
public override void EnableChat(ChatModel chat)
@@ -293,7 +302,7 @@ namespace Smuxi.Frontend.Gnome
TreeView.Append(chatView);
// notify the sync manager that the ChatView is ready to be synced
- SyncManager.ReleaseSync(chatView);
+ SyncManager.QueueReadyToSync(chatView);
#if GTK_SHARP_2_10
f_Notebook.SetTabReorderable(chatView, true);
@@ -364,6 +373,7 @@ namespace Smuxi.Frontend.Gnome
if (ChatSynced != null) {
ChatSynced(this, new ChatViewManagerChatSyncedEventArgs(chatView));
}
+ SyncManager.QueueSyncFinished(chatView);
return false;
});
}
diff --git a/src/Frontend/ChatViewSyncManager.cs b/src/Frontend/ChatViewSyncManager.cs
index 7a726b4..ba77aa5 100644
--- a/src/Frontend/ChatViewSyncManager.cs
+++ b/src/Frontend/ChatViewSyncManager.cs
@@ -32,12 +32,294 @@ namespace Smuxi.Frontend
#if LOG4NET
private static readonly log4net.ILog Logger =
log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
#endif
+
+ /*
+ * InitialState ---Add---> AddedState
+ * AddedState ---Sync---> SyncQueuedState
+ * ---ReadyToSync---> WaitingForSyncState
+ * SyncQueuedState ---ReadyToSync---> SyncingState
+ * WaitingForSyncState ---Sync---> SyncingState
+ * SyncingState ---SyncFinished---> SyncState
+ *
+ * AddedState ---Remove---> RemovingState
+ * SyncQueuedState ---Remove---> RemovingState
+ * WaitingForSyncState ---Remove---> RemovingState
+ * SyncingState ---Remove---> RemovingState
+ * SyncState ---Remove---> RemovingState
+ * RemovingState ---RemoveFinished---> KILL IT WITH FIRE
+ */
+
+ abstract class State
+ {
+ protected SyncInfo Chat { get; private set; }
+
+ protected State(SyncInfo chat)
+ {
+ if (chat == null) {
+ throw new ArgumentNullException("chat");
+ }
+ if (Chat != null) {
+ throw new Exception("State already initialized");
+ }
+ Chat = chat;
+ }
+
+ public virtual void ExecuteAdd()
+ {
+ throw new InvalidStateException("could not add");
+ }
+ public virtual void ExecuteRemove()
+ {
+ throw new InvalidStateException("could not remove");
+ }
+ public virtual void ExecuteRemoveFinished()
+ {
+ throw new InvalidStateException("could not remove");
+ }
+ public virtual void ExecuteSync()
+ {
+ throw new InvalidStateException("could not sync");
+ }
+ public virtual void ExecuteReadyToSync()
+ {
+ throw new InvalidStateException("could not be ready to sync");
+ }
+ public virtual void ExecuteSyncFinished()
+ {
+ throw new InvalidStateException("could not finish sync");
+ }
+ }
+
+ class InitialState : State
+ {
+ public InitialState(SyncInfo chat)
+ :base(chat)
+ {
+ }
+
+ public override void ExecuteAdd()
+ {
+ Trace.Call(Chat.ChatModel);
+ Chat.SetState<AddedState>();
+ }
+ }
+
+ class AddedState : State
+ {
+ public AddedState(SyncInfo chat)
+ :base(chat)
+ {
+#if LOG4NET
+ DateTime start = DateTime.UtcNow;
+#endif
+ // REMOTING CALL 1
+ var chatId = Chat.ChatModel.ID;
+ // REMOTING CALL 2
+ var chatType = Chat.ChatModel.ChatType;
+ // REMOTING CALL 3
+ var chatPosition = Chat.ChatModel.Position;
+ // REMOTING CALL 4
+ IProtocolManager protocolManager = Chat.ChatModel.ProtocolManager;
+ Type protocolManagerType = null;
+ if (protocolManager != null) {
+ protocolManagerType = protocolManager.GetType();
+ }
+#if LOG4NET
+ DateTime stop = DateTime.UtcNow;
+ double duration = stop.Subtract(start).TotalMilliseconds;
+ Logger.Debug("Add() done, syncing took: " +
+ Math.Round(duration) + " ms");
+#endif
+ Chat.Manager.OnChatAdded(Chat.ChatModel,
+ chatId,
+ chatType,
+ chatPosition,
+ protocolManager,protocolManagerType);
+ }
+
+ public override void ExecuteReadyToSync()
+ {
+ Trace.Call(Chat.ChatModel);
+ Chat.SetState<WaitingForSyncState>();
+ }
+
+ public override void ExecuteSync()
+ {
+ Trace.Call(Chat.ChatModel);
+ Chat.SetState<SyncQueuedState>();
+ }
+
+ public override void ExecuteRemove()
+ {
+ Trace.Call(Chat.ChatModel);
+ Chat.SetState<RemovingState>();
+ }
+ }
+
+ class SyncQueuedState : State
+ {
+ public SyncQueuedState(SyncInfo chat)
+ :base(chat)
+ {
+ }
+
+ public override void ExecuteReadyToSync()
+ {
+ Trace.Call(Chat.ChatModel);
+ Chat.SetState<SyncingState>();
+ }
+ }
+
+ class WaitingForSyncState : State
+ {
+ public WaitingForSyncState(SyncInfo chat)
+ :base(chat)
+ {
+ }
+
+ public override void ExecuteSync()
+ {
+ Trace.Call(Chat.ChatModel);
+ Chat.SetState<SyncingState>();
+ }
+
+ public override void ExecuteRemove()
+ {
+ Trace.Call(Chat.ChatModel);
+ Chat.SetState<RemovingState>();
+ }
+ }
+
+ class SyncingState : State
+ {
+ public SyncingState(SyncInfo chat)
+ :base(chat)
+ {
+#if LOG4NET
+ DateTime start = DateTime.UtcNow;
+#endif
+ Chat.ChatView.Sync();
+#if LOG4NET
+ DateTime stop = DateTime.UtcNow;
+ double duration = stop.Subtract(start).TotalMilliseconds;
+ Logger.Debug("Sync() <" + Chat.ChatView.ID + ">.Sync() done, " +
+ " syncing took: " + Math.Round(duration) + " ms");
+#endif
+ Chat.Manager.OnChatSynced(Chat.ChatView);
+ }
+
+ public override void ExecuteSyncFinished()
+ {
+ Trace.Call(Chat.ChatModel);
+ Chat.SetState<SyncState>();
+ }
+ }
+
+ class SyncState : State
+ {
+ public SyncState(SyncInfo chat)
+ :base(chat)
+ {
+ }
+
+ public override void ExecuteRemove()
+ {
+ Trace.Call(Chat.ChatModel);
+ Chat.SetState<RemovingState>();
+ }
+ }
+
+ class RemovingState : State
+ {
+ public RemovingState(SyncInfo chat)
+ :base(chat)
+ {
+ Chat.Manager.OnChatRemoved(Chat.ChatView);
+ }
+
+ public override void ExecuteRemoveFinished()
+ {
+ Trace.Call(Chat.ChatModel);
+ Chat.Manager.Remove(Chat.ChatModel);
+ }
+
+ public override void ExecuteReadyToSync()
+ {
+ // no-op
+ // this can happen when you add and remove very fast after each other.
+ // the add callback might be in a different thread and therefore be delayed
+ }
+ }
+
+ class SyncInfo
+ {
+ internal readonly ChatViewSyncManager Manager;
+ internal State State { private set; get; }
+ internal readonly ChatModel ChatModel;
+ internal IChatView ChatView { get; set; }
+ readonly object syncRoot = new object();
+
+ public SyncInfo(ChatViewSyncManager manager, ChatModel chatModel)
+ {
+ Manager = manager;
+ ChatModel = chatModel;
+ SetState<InitialState>();
+ }
+
+ public void SetState<T>() where T : State
+ {
+ State = (T)Activator.CreateInstance(typeof(T), this);
+ }
+
+ public void ExecuteAdd()
+ {
+ lock (syncRoot) {
+ State.ExecuteAdd();
+ }
+ }
+
+ public void ExecuteRemove()
+ {
+ lock (syncRoot) {
+ State.ExecuteRemove();
+ }
+ }
+
+ public void ExecuteRemoveFinished()
+ {
+ lock (syncRoot) {
+ State.ExecuteRemoveFinished();
+ }
+ }
+
+ public void ExecuteSync()
+ {
+ lock (syncRoot) {
+ State.ExecuteSync();
+ }
+ }
+
+ public void ExecuteReadyToSync()
+ {
+ lock (syncRoot) {
+ State.ExecuteReadyToSync();
+ }
+ }
+
+ public void ExecuteSyncFinished()
+ {
+ lock (syncRoot) {
+ State.ExecuteSyncFinished();
+ }
+ }
+ }
+
ThreadPoolQueue WorkerQueue { set; get; }
- Dictionary<object, AutoResetEvent> SyncWaitQueue { set; get; }
- Dictionary<object, IChatView> SyncReleaseQueue { set; get; }
+ Dictionary<object, SyncInfo> Chats { set; get; }
public event EventHandler<ChatViewAddedEventArgs> ChatAdded;
public event EventHandler<ChatViewSyncedEventArgs> ChatSynced;
+ public event EventHandler<ChatViewRemovedEventArgs> ChatRemoved;
public event EventHandler<WorkerExceptionEventArgs> WorkerException;
public ChatViewSyncManager()
@@ -45,11 +327,13 @@ namespace Smuxi.Frontend
WorkerQueue = new ThreadPoolQueue() {
MaxWorkers = 4
};
- SyncWaitQueue = new Dictionary<object, AutoResetEvent>();
- SyncReleaseQueue = new Dictionary<object, IChatView>();
+ Chats = new Dictionary<object, SyncInfo>();
}
- public void Add(ChatModel chatModel)
+ /// <remarks>
+ /// This method is thread safe.
+ /// </remarks>
+ void Remove(ChatModel chatModel)
{
Trace.Call(chatModel);
@@ -57,36 +341,20 @@ namespace Smuxi.Frontend
throw new ArgumentNullException("chatModel");
}
+ var chatKey = GetChatKey(chatModel);
#if LOG4NET
- DateTime start = DateTime.UtcNow;
+ Logger.DebugFormat("Remove() <{0}> removing from release queue",
+ chatKey);
#endif
- // REMOTING CALL 1
- var chatId = chatModel.ID;
- // REMOTING CALL 2
- var chatType = chatModel.ChatType;
- // REMOTING CALL 3
- var chatPosition = chatModel.Position;
- // REMOTING CALL 4
- IProtocolManager protocolManager = chatModel.ProtocolManager;
- Type protocolManagerType = null;
- if (protocolManager != null) {
- protocolManagerType = protocolManager.GetType();
+ lock (Chats) {
+ Chats.Remove(chatKey);
}
-#if LOG4NET
- DateTime stop = DateTime.UtcNow;
- double duration = stop.Subtract(start).TotalMilliseconds;
- Logger.Debug("Add() done, syncing took: " +
- Math.Round(duration) + " ms");
-#endif
-
- OnChatAdded(chatModel, chatId, chatType, chatPosition,
- protocolManager, protocolManagerType);
}
/// <remarks>
/// This method is thread safe.
/// </remarks>
- public void Remove(ChatModel chatModel)
+ public void QueueAdd(ChatModel chatModel)
{
Trace.Call(chatModel);
@@ -94,42 +362,34 @@ namespace Smuxi.Frontend
throw new ArgumentNullException("chatModel");
}
- var chatKey = GetChatKey(chatModel);
-#if LOG4NET
- Logger.DebugFormat("Remove() <{0}> removing from release queue",
- chatKey);
-#endif
- lock (SyncReleaseQueue) {
- SyncReleaseQueue.Remove(chatKey);
- }
+ var chat = GetOrCreateChat(chatModel);
+ WorkerQueue.Enqueue(delegate {
+ AddWorker(chat);
+ });
}
- public void Sync(IChatView chatView)
+ public void QueueRemove(ChatModel chatModel)
{
- Trace.Call(chatView);
+ Trace.Call(chatModel);
- if (chatView == null) {
- throw new ArgumentNullException("chatView");
+ if (chatModel == null) {
+ throw new ArgumentNullException("chatModel");
}
+ SyncInfo chat;
+ if (!TryGetChat(chatModel, out chat)) {
#if LOG4NET
- DateTime start = DateTime.UtcNow;
-#endif
- chatView.Sync();
-#if LOG4NET
- DateTime stop = DateTime.UtcNow;
- double duration = stop.Subtract(start).TotalMilliseconds;
- Logger.Debug("Sync() <" + chatView.ID + ">.Sync() done, " +
- " syncing took: " + Math.Round(duration) + " ms");
+ Logger.WarnFormat("QueueRemove() <{0}> already removed or never existed",
+ chatModel);
#endif
-
- OnChatSynced(chatView);
+ return;
+ }
+ WorkerQueue.Enqueue(delegate {
+ RemoveWorker(chat);
+ });
}
- /// <remarks>
- /// This method is thread safe.
- /// </remarks>
- public void QueueAdd(ChatModel chatModel)
+ public void QueueRemoveFinished(ChatModel chatModel)
{
Trace.Call(chatModel);
@@ -137,18 +397,41 @@ namespace Smuxi.Frontend
throw new ArgumentNullException("chatModel");
}
- var chatKey = GetChatKey(chatModel);
- lock (SyncWaitQueue) {
- SyncWaitQueue.Add(chatKey, new AutoResetEvent(false));
+ SyncInfo chat;
+ if (!TryGetChat(chatModel, out chat)) {
#if LOG4NET
- Logger.Debug("QueueAdd() <" + chatKey + "> created sync lock");
+ Logger.WarnFormat("QueueRemoveFinished() <{0}> already removed or never existed",
+ chatModel);
#endif
+ return;
}
+
WorkerQueue.Enqueue(delegate {
- AddWorker(chatModel);
+ RemoveFinishedWorker(chat);
});
}
+ bool TryGetChat(ChatModel chatModel, out SyncInfo chat)
+ {
+ var key = GetChatKey(chatModel);
+ lock (Chats) {
+ return Chats.TryGetValue(key, out chat);
+ }
+ }
+
+ SyncInfo GetOrCreateChat(ChatModel chatModel)
+ {
+ var key = GetChatKey(chatModel);
+ lock (Chats) {
+ SyncInfo chat;
+ if (!Chats.TryGetValue(key, out chat)) {
+ chat = new SyncInfo(this, chatModel);
+ Chats.Add(key, chat);
+ }
+ return chat;
+ }
+ }
+
/// <remarks>
/// This method is thread safe.
/// </remarks>
@@ -160,15 +443,24 @@ namespace Smuxi.Frontend
throw new ArgumentNullException("chatModel");
}
+ SyncInfo chat;
+ if (!TryGetChat(chatModel, out chat)) {
+#if LOG4NET
+ Logger.WarnFormat("QueueSync() <{0}> unknow chat, cannot sync",
+ chatModel);
+#endif
+ return;
+ }
+
WorkerQueue.Enqueue(delegate {
- SyncWorker(chatModel);
+ SyncWorker(chat);
});
}
/// <remarks>
/// This method is thread safe.
/// </remarks>
- public void ReleaseSync(IChatView chatView)
+ public void QueueReadyToSync(IChatView chatView)
{
Trace.Call(chatView);
@@ -176,38 +468,46 @@ namespace Smuxi.Frontend
throw new ArgumentNullException("chatView");
}
- var chatKey = GetChatKey(chatView.ChatModel);
+ SyncInfo chat;
+ if (!TryGetChat(chatView.ChatModel, out chat)) {
#if LOG4NET
- Logger.Debug("ReleaseSync() <" + chatKey + "> releasing " +
- "<" + chatView.ID + ">");
+ Logger.WarnFormat("QueueReadyToSync() <{0}> unknow chat, something is wrong",
+ chatView.ChatModel);
#endif
- lock (SyncReleaseQueue) {
- SyncReleaseQueue.Add(chatKey, chatView);
+ return;
}
- AutoResetEvent syncWait = null;
- lock (SyncWaitQueue) {
- SyncWaitQueue.TryGetValue(chatKey, out syncWait);
+ chat.ChatView = chatView;
+
+ WorkerQueue.Enqueue(delegate {
+ ReadyToSyncWorker(chat);
+ });
+ }
+
+ public void QueueSyncFinished(IChatView chatView)
+ {
+ Trace.Call(chatView);
+
+ if (chatView == null) {
+ throw new ArgumentNullException("chatView");
}
- if (syncWait == null) {
+
+ SyncInfo chat;
+ if (!TryGetChat(chatView.ChatModel, out chat)) {
#if LOG4NET
- Logger.Error("ReleaseSync(<" + chatView.ID + ">): failed to release " +
- "<" + chatKey + "> as syncWait is null!");
+ Logger.WarnFormat("QueueSyncFinished() <{0}> unknow chat, something is wrong",
+ chatView.ChatModel);
#endif
return;
}
- // release the sync worker
- syncWait.Set();
+
+ WorkerQueue.Enqueue(delegate {
+ SyncFinishedWorker(chat);
+ });
}
public void Clear()
{
Trace.Call();
-
- lock (SyncWaitQueue)
- lock (SyncReleaseQueue) {
- SyncWaitQueue.Clear();
- SyncReleaseQueue.Clear();
- }
}
object GetChatKey(ChatModel chatModel)
@@ -220,73 +520,75 @@ namespace Smuxi.Frontend
return chatModel;
}
- void AddWorker(ChatModel chatModel)
+ void AddWorker(SyncInfo chat)
{
try {
- Add(chatModel);
- } catch (Exception ex) {
+ chat.ExecuteAdd();
+ } catch(InvalidStateException ex) {
#if LOG4NET
- Logger.Error("AddWorker(): Add() threw exception!" , ex);
+ Logger.Error("AddWorker(): ExecuteAdd() threw exception!" , ex);
#endif
- if (WorkerException != null) {
- WorkerException(
- this,
- new WorkerExceptionEventArgs(chatModel, ex)
- );
- }
- OnWorkerException(chatModel, ex);
+ OnWorkerException(chat.ChatModel, ex);
}
}
- void SyncWorker(ChatModel chatModel)
+ void SyncWorker(SyncInfo chat)
{
try {
- var chatKey = GetChatKey(chatModel);
- AutoResetEvent syncWait = null;
- lock (SyncWaitQueue) {
- SyncWaitQueue.TryGetValue(chatKey, out syncWait);
- }
- if (syncWait != null) {
+ chat.ExecuteSync();
+ } catch(InvalidStateException ex) {
#if LOG4NET
- Logger.Debug("SyncWorker() <" + chatKey + "> waiting for " +
- "sync lock release...");
+ Logger.Error("SyncWorker(): ExecuteSync() threw exception!" , ex);
#endif
- // This chat was queued by QueueAdd() thus we need to wait
- // till the ChatView is created and ready to be synced
- syncWait.WaitOne();
+ OnWorkerException(chat.ChatModel, ex);
+ }
+ }
+
+ void ReadyToSyncWorker(SyncInfo chat)
+ {
+ try {
+ chat.ExecuteReadyToSync();
+ } catch(InvalidStateException ex) {
#if LOG4NET
- Logger.Debug("SyncWorker() <" + chatKey + "> " +
- "sync lock released");
+ Logger.Error("ReadyToSyncWorker(): ExecuteReadyToSync() threw exception!" , ex);
#endif
+ OnWorkerException(chat.ChatModel, ex);
+ }
+ }
- // no longer need the sync lock
- lock (SyncWaitQueue) {
- SyncWaitQueue.Remove(chatKey);
- }
- }
+ void SyncFinishedWorker(SyncInfo chat)
+ {
+ try {
+ chat.ExecuteSyncFinished();
+ } catch(InvalidStateException ex) {
+#if LOG4NET
+ Logger.Error("SyncFinishedWorker(): ExecuteSyncFinished() threw exception!" , ex);
+#endif
+ OnWorkerException(chat.ChatModel, ex);
+ }
+ }
- IChatView chatView = null;
- lock (SyncReleaseQueue) {
- if (!SyncReleaseQueue.TryGetValue(chatKey, out chatView)) {
+ void RemoveWorker(SyncInfo chat)
+ {
+ try {
+ chat.ExecuteRemove();
+ } catch(InvalidStateException ex) {
#if LOG4NET
- Logger.Warn("SyncWorker(): chatView is null! " +
- "probably a reconnect, bailing out...");
+ Logger.Error("RemoveWorker(): ExecuteRemove() threw exception!" , ex);
#endif
- return;
- }
- // no longer need the release slot
- // BUG: this breaks re-syncing an existing chat! For that
- // reason the frontend _must_ notify us via Remove() if the
- // chat sync state is no longer needed
- //SyncReleaseQueue.Remove(chatKey);
- }
+ OnWorkerException(chat.ChatModel, ex);
+ }
+ }
- Sync(chatView);
- } catch (Exception ex) {
+ void RemoveFinishedWorker(SyncInfo chat)
+ {
+ try {
+ chat.ExecuteRemoveFinished();
+ } catch(InvalidStateException ex) {
#if LOG4NET
- Logger.Error("SyncWorker(): Exception!", ex);
+ Logger.Error("RemoveWorker(): ExecuteRemove() threw exception!" , ex);
#endif
- OnWorkerException(chatModel, ex);
+ OnWorkerException(chat.ChatModel, ex);
}
}
@@ -311,6 +613,13 @@ namespace Smuxi.Frontend
}
}
+ void OnChatRemoved(IChatView chatView)
+ {
+ if (ChatRemoved != null) {
+ ChatRemoved(this, new ChatViewRemovedEventArgs(chatView));
+ }
+ }
+
void OnWorkerException(ChatModel chatModel, Exception ex)
{
if (WorkerException != null) {
@@ -355,6 +664,16 @@ namespace Smuxi.Frontend
}
}
+ public class ChatViewRemovedEventArgs : EventArgs
+ {
+ public IChatView ChatView { get; private set; }
+
+ public ChatViewRemovedEventArgs(IChatView chatView)
+ {
+ ChatView = chatView;
+ }
+ }
+
public class WorkerExceptionEventArgs : EventArgs
{
public ChatModel ChatModel { get; private set; }
@@ -366,4 +685,12 @@ namespace Smuxi.Frontend
Exception = ex;
}
}
+
+ public class InvalidStateException : Exception
+ {
+ public InvalidStateException(string msg)
+ :base(msg)
+ {
+ }
+ }
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]