From 11a1c4271bcde67674e5ac1dd1b853a647f9f57c Mon Sep 17 00:00:00 2001
From: George Hazan <ghazan@miranda.im>
Date: Sun, 10 Jan 2021 15:32:17 +0300
Subject: CSendLater: fake class removed

---
 plugins/TabSRMM/src/generic_msghandlers.cpp |   4 +-
 plugins/TabSRMM/src/globals.cpp             |   4 +-
 plugins/TabSRMM/src/hotkeyhandler.cpp       |  16 +-
 plugins/TabSRMM/src/mim.cpp                 |   2 +-
 plugins/TabSRMM/src/msgdialog.cpp           |   2 +-
 plugins/TabSRMM/src/msgs.cpp                |   9 +-
 plugins/TabSRMM/src/sendlater.cpp           | 720 +++++++++++++++-------------
 plugins/TabSRMM/src/sendlater.h             |  94 +---
 plugins/TabSRMM/src/sendqueue.cpp           |  12 +-
 plugins/TabSRMM/src/srmm.cpp                |   2 +-
 10 files changed, 417 insertions(+), 448 deletions(-)

(limited to 'plugins')

diff --git a/plugins/TabSRMM/src/generic_msghandlers.cpp b/plugins/TabSRMM/src/generic_msghandlers.cpp
index 5175a533d3..6614834eaf 100644
--- a/plugins/TabSRMM/src/generic_msghandlers.cpp
+++ b/plugins/TabSRMM/src/generic_msghandlers.cpp
@@ -363,7 +363,7 @@ LRESULT CMsgDialog::DM_MsgWindowCmdHandler(UINT cmd, WPARAM wParam, LPARAM lPara
 			RedrawWindow(m_hwnd, nullptr, nullptr, RDW_ERASENOW | RDW_UPDATENOW);
 			break;
 		case ID_SENDMENU_SENDLATER:
-			if (sendLater->isAvail())
+			if (SendLater::Avail)
 				m_sendMode ^= SMODE_SENDLATER;
 			else
 				CWarning::show(CWarning::WARN_NO_SENDLATER, MB_OK | MB_ICONINFORMATION);
@@ -689,7 +689,7 @@ void CMsgDialog::DM_UpdateLastMessage() const
 
 HWND CMsgDialog::DM_CreateClist()
 {
-	if (!sendLater->isAvail()) {
+	if (!SendLater::Avail) {
 		CWarning::show(CWarning::WARN_NO_SENDLATER, MB_OK | MB_ICONINFORMATION);
 		m_sendMode &= ~SMODE_MULTIPLE;
 		return nullptr;
diff --git a/plugins/TabSRMM/src/globals.cpp b/plugins/TabSRMM/src/globals.cpp
index 04dc12876e..e021ed9ffa 100644
--- a/plugins/TabSRMM/src/globals.cpp
+++ b/plugins/TabSRMM/src/globals.cpp
@@ -265,7 +265,7 @@ int CGlobals::ModulesLoaded(WPARAM, LPARAM)
 	mi.pszService = MS_TABMSG_SETUSERPREFS;
 	PluginConfig.m_UserMenuItem = Menu_AddContactMenuItem(&mi);
 
-	if (sendLater->isAvail()) {
+	if (SendLater::Avail) {
 		SET_UID(mi, 0x8f32b04e, 0x314e, 0x42eb, 0x89, 0xc6, 0x56, 0x21, 0xf5, 0x1a, 0x2f, 0x22);
 		mi.position = -500050006;
 		mi.hIcolibItem = nullptr;
@@ -461,7 +461,7 @@ void CGlobals::RestoreUnreadMessageAlerts(void)
 
 	for (auto &hContact : Contacts()) {
 		if (db_get_dw(hContact, "SendLater", "count", 0))
-			sendLater->addContact(hContact);
+			SendLater::addContact(hContact);
 
 		for (MEVENT hDbEvent = db_event_firstUnread(hContact); hDbEvent; hDbEvent = db_event_next(hContact, hDbEvent)) {
 			DBEVENTINFO dbei = {};
diff --git a/plugins/TabSRMM/src/hotkeyhandler.cpp b/plugins/TabSRMM/src/hotkeyhandler.cpp
index d8e6a59db1..4e11a2f606 100644
--- a/plugins/TabSRMM/src/hotkeyhandler.cpp
+++ b/plugins/TabSRMM/src/hotkeyhandler.cpp
@@ -393,17 +393,17 @@ LONG_PTR CALLBACK HotkeyHandlerDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LP
 				SendMessage(pCont->m_hwnd, WM_TIMER, TIMERID_HEARTBEAT, 0);
 
 			// process send later contacts and jobs, if enough time has elapsed
-			if (sendLater->isAvail() && !sendLater->isInteractive() && (time(0) - sendLater->lastProcessed()) > CSendLater::SENDLATER_PROCESS_INTERVAL) {
-				sendLater->setLastProcessed(time(0));
+			if (SendLater::Avail && !SendLater::isInteractive() && (time(0) - SendLater::lastProcessed()) > SENDLATER_PROCESS_INTERVAL) {
+				SendLater::setLastProcessed(time(0));
 
 				// check the list of contacts that may have new send later jobs
 				// (added on user's request)
-				sendLater->processContacts();
+				SendLater::processContacts();
 
 				// start processing the job list
-				if (!sendLater->isJobListEmpty()) {
+				if (!SendLater::isJobListEmpty()) {
 					KillTimer(hwndDlg, wParam);
-					sendLater->startJobListProcess();
+					SendLater::startJobListProcess();
 					SetTimer(hwndDlg, TIMERID_SENDLATER_TICK, TIMEOUT_SENDLATER_TICK, nullptr);
 				}
 			}
@@ -413,12 +413,12 @@ LONG_PTR CALLBACK HotkeyHandlerDlgProc(HWND hwndDlg, UINT msg, WPARAM wParam, LP
 		// TODO better timings, possibly slow down when many jobs are in the
 		// queue.
 		else if (wParam == TIMERID_SENDLATER_TICK) {
-			if (!sendLater->haveJobs()) {
+			if (!SendLater::haveJobs()) {
 				KillTimer(hwndDlg, wParam);
 				SetTimer(hwndDlg, TIMERID_SENDLATER, TIMEOUT_SENDLATER, nullptr);
-				sendLater->qMgrUpdate(true);
+				SendLater::qMgrUpdate(true);
 			}
-			else sendLater->processCurrentJob();
+			else SendLater::processCurrentJob();
 		}
 		break;
 
diff --git a/plugins/TabSRMM/src/mim.cpp b/plugins/TabSRMM/src/mim.cpp
index 0c5708fe4e..5e3476f300 100644
--- a/plugins/TabSRMM/src/mim.cpp
+++ b/plugins/TabSRMM/src/mim.cpp
@@ -335,7 +335,7 @@ int CMimAPI::ProtoAck(WPARAM, LPARAM lParam)
 			}
 		}
 		if (iFound == SendQueue::NR_SENDJOBS)     // no matching send info found in the queue
-			sendLater->processAck(pAck);
+			SendLater::processAck(pAck);
 		else                                      // try to find the process handle in the list of open send later jobs
 			SendMessage(jobs[iFound].hOwnerWnd, HM_EVENTSENT, (WPARAM)MAKELONG(iFound, i), lParam);
 	}
diff --git a/plugins/TabSRMM/src/msgdialog.cpp b/plugins/TabSRMM/src/msgdialog.cpp
index 6dcc92760c..16ca4dfc45 100644
--- a/plugins/TabSRMM/src/msgdialog.cpp
+++ b/plugins/TabSRMM/src/msgdialog.cpp
@@ -1423,7 +1423,7 @@ int CMsgDialog::OnFilter(MSGFILTER *pFilter)
 			PostMessage(m_hwnd, WM_COMMAND, MAKELONG(IDC_PIC, BN_CLICKED), 0);
 			return _dlgReturn(m_hwnd, 1);
 		case TABSRMM_HK_TOGGLESENDLATER:
-			if (sendLater->isAvail()) {
+			if (SendLater::Avail) {
 				m_sendMode ^= SMODE_SENDLATER;
 				SetWindowPos(m_message.GetHwnd(), nullptr, 0, 0, 0, 0, SWP_DRAWFRAME | SWP_FRAMECHANGED | SWP_NOZORDER |
 					SWP_NOMOVE | SWP_NOSIZE | SWP_NOCOPYBITS);
diff --git a/plugins/TabSRMM/src/msgs.cpp b/plugins/TabSRMM/src/msgs.cpp
index 2f106f009b..718a80faa2 100644
--- a/plugins/TabSRMM/src/msgs.cpp
+++ b/plugins/TabSRMM/src/msgs.cpp
@@ -650,6 +650,12 @@ static INT_PTR ReloadSettings(WPARAM, LPARAM lParam)
 	return 0;
 }
 
+static INT_PTR svcQMgr(WPARAM, LPARAM)
+{
+	SendLater::invokeQueueMgrDlg();
+	return 0;
+}
+
 /////////////////////////////////////////////////////////////////////////////////////////
 // initialises the internal API, services, events etc...
 
@@ -664,7 +670,7 @@ static void TSAPI InitAPI()
 	CreateServiceFunction("TabSRMsg/ReloadSettings", ReloadSettings);
 
 	CreateServiceFunction(MS_TABMSG_SETUSERPREFS, SetUserPrefs);
-	CreateServiceFunction(MS_TABMSG_SLQMGR, CSendLater::svcQMgr);
+	CreateServiceFunction(MS_TABMSG_SLQMGR, svcQMgr);
 
 	SI_InitStatusIcons();
 }
@@ -685,7 +691,6 @@ int LoadSendRecvMessageModule(void)
 	PluginConfig.hUserPrefsWindowList = WindowList_Create();
 	sendQueue = new SendQueue;
 	Skin = new CSkin;
-	sendLater = new CSendLater;
 
 	HookEvent(ME_OPT_INITIALISE, OptInitialise);
 
diff --git a/plugins/TabSRMM/src/sendlater.cpp b/plugins/TabSRMM/src/sendlater.cpp
index b91fa27ee5..3d7af976c5 100644
--- a/plugins/TabSRMM/src/sendlater.cpp
+++ b/plugins/TabSRMM/src/sendlater.cpp
@@ -28,128 +28,219 @@
 
 #include "stdafx.h"
 
-CSendLater *sendLater = nullptr;
+/////////////////////////////////////////////////////////////////////////////////////////
+// CSendLaterJob class implementation
 
-// implementation of the CSendLaterJob class
-CSendLaterJob::CSendLaterJob()
+struct CSendLaterJob : public MZeroedObject
 {
-	memset(this, 0, sizeof(CSendLaterJob));
-	fSuccess = false;
-}
+	// job status/error codes
+	enum {
+		INVALID_CONTACT = 'I',
+		JOB_DEFERRED = 'D',
+		JOB_AGE = 'O',
+		JOB_MYSTATUS = 'M',
+		JOB_STATUS = 'S',
+		JOB_WAITACK = 'A',
+		JOB_REMOVABLE = 'R',
+		JOB_HOLD = 'H',
+	};
+
+	// internal flags
+	enum {
+		SLF_SUSPEND = 1,
+		SLF_INVALID = 2
+	};
+
+	char     szId[20];            // database key name (time stamp of original send)
+	MCONTACT hContact;            // original contact where the message has been assigned
+	MCONTACT hTargetContact;      // *real* contact (can be different for metacontacts, e.g).
+	HANDLE   hProcess;            // returned from the protocols sending service. needed to find it in the ACK handler
+	time_t   created;             // job was created at this time (important to kill jobs, that are too old)
+	time_t   lastSent;            // time at which the delivery was initiated. used to handle timeouts
+	char    *sendBuffer;          // utf-8 send buffer
+	PBYTE    pBuf;                // conventional send buffer (for non-utf8 protocols)
+	DWORD    dwFlags;
+	int      iSendCount;          // # of times we tried to send it...
+	bool     fSuccess, fFailed;
+	BYTE     bCode;               // error/progress code (for the UI)
+
+	// returns true if this job is persistent (saved to the database).
+	// such a job will survive a restart of Miranda
+	__inline bool isPersistentJob() const {
+		return(szId[0] == 'S' ? true : false);
+	}
 
-// return true if this job is persistent (saved to the database).
-// such a job will survive a restart of Miranda
-bool CSendLaterJob::isPersistentJob()
-{
-	return(szId[0] == 'S' ? true : false);
-}
+	// try to send an open job from the job list
+	// this is ONLY called from the WM_TIMER handler and should never be executed directly.
 
-// check conditions for deletion
-bool CSendLaterJob::mustDelete()
-{
-	if (fSuccess)
-		return true;
+	int sendIt()
+	{
+		time_t now = time(0);
+		if (bCode == JOB_HOLD || bCode == JOB_DEFERRED || fSuccess || fFailed || lastSent > now)
+			return 0;											// this one is frozen or done (will be removed soon), don't process it now.
 
-	if (fFailed && bCode == JOB_REMOVABLE)
-		return true;
+		if (now - created > SENDLATER_AGE_THRESHOLD) {		// too old, this will be discarded and user informed by popup
+			fFailed = true;
+			bCode = JOB_AGE;
+			return 0;
+		}
 
-	return false;
-}
+		// mark job as deferred (5 unsuccessful sends). Job will not be removed, but
+		// the user must manually reset it in order to trigger a new send attempt.
+		if (iSendCount == 5) {
+			bCode = JOB_DEFERRED;
+			return 0;
+		}
 
-// clean database entries for a persistent job (currently: manual send later jobs)
-void CSendLaterJob::cleanDB()
-{
-	if (isPersistentJob()) {
-		char szKey[100];
-
-		db_unset(hContact, "SendLater", szId);
-		int iCount = db_get_dw(hContact, "SendLater", "count", 0);
-		if (iCount)
-			iCount--;
-		db_set_dw(hContact, "SendLater", "count", iCount);
-
-		// delete flags
-		mir_snprintf(szKey, "$%s", szId);
-		db_unset(hContact, "SendLater", szKey);
+		if (iSendCount > 0 && (now - lastSent < SENDLATER_RESEND_THRESHOLD))
+			return 0;											// this one was sent, but probably failed. Resend it after a while
+
+		CContactCache *c = CContactCache::getContactCache(hContact);
+		if (!c->isValid()) {
+			fFailed = true;
+			bCode = INVALID_CONTACT;
+			return 0;						// can happen (contact has been deleted). mark the job as failed
+		}
+
+		MCONTACT cc = c->getActiveContact();
+		const char *szProto = c->getActiveProto();
+		if (!cc || szProto == nullptr)
+			return 0;
+
+		int wMyStatus = Proto_GetStatus(szProto);
+
+		// status mode checks
+		if (wMyStatus == ID_STATUS_OFFLINE) {
+			bCode = JOB_MYSTATUS;
+			return 0;
+		}
+		if (szId[0] == 'S') {
+			if (wMyStatus != ID_STATUS_ONLINE || wMyStatus != ID_STATUS_FREECHAT) {
+				bCode = JOB_MYSTATUS;
+				return 0;
+			}
+		}
+
+		lastSent = now;
+		iSendCount++;
+		hTargetContact = cc;
+		bCode = JOB_WAITACK;
+		hProcess = (HANDLE)ProtoChainSend(cc, PSS_MESSAGE, 0, (LPARAM)sendBuffer);
+		return 0;
 	}
-}
 
-// read flags for a persistent jobs from the db
-// flag key name is the job id with a "$" prefix.
-void CSendLaterJob::readFlags()
-{
-	if (isPersistentJob()) {
-		char szKey[100];
-		DWORD localFlags;
+	// reads flags for a persistent jobs from the db
+	// flag key name is the job id with a "$" prefix.
+	void readFlags()
+	{
+		if (isPersistentJob()) {
+			char szKey[100];
+			DWORD localFlags;
 
-		mir_snprintf(szKey, "$%s", szId);
-		localFlags = db_get_dw(hContact, "SendLater", szKey, 0);
+			mir_snprintf(szKey, "$%s", szId);
+			localFlags = db_get_dw(hContact, "SendLater", szKey, 0);
 
-		if (localFlags & SLF_SUSPEND)
-			bCode = JOB_HOLD;
+			if (localFlags & SLF_SUSPEND)
+				bCode = JOB_HOLD;
+		}
 	}
-}
 
-// write flags for a persistent jobs from the db
-// flag key name is the job id with a "$" prefix.
-void CSendLaterJob::writeFlags()
-{
-	if (isPersistentJob()) {
-		DWORD localFlags = (bCode == JOB_HOLD ? SLF_SUSPEND : 0);
-		char szKey[100];
+	// writes flags for a persistent jobs from the db
+	// flag key name is the job id with a "$" prefix.
+	void writeFlags()
+	{
+		if (isPersistentJob()) {
+			DWORD localFlags = (bCode == JOB_HOLD ? SLF_SUSPEND : 0);
+			char szKey[100];
 
-		mir_snprintf(szKey, "$%s", szId);
-		db_set_dw(hContact, "SendLater", szKey, localFlags);
+			mir_snprintf(szKey, "$%s", szId);
+			db_set_dw(hContact, "SendLater", szKey, localFlags);
+		}
 	}
-}
 
-// delete a send later job
-CSendLaterJob::~CSendLaterJob()
-{
-	if (fSuccess || fFailed) {
-		if ((sendLater->haveErrorPopups() && fFailed) || (sendLater->haveSuccessPopups() && fSuccess)) {
-			bool fShowPopup = true;
-
-			if (fFailed && bCode == JOB_REMOVABLE)	// no popups for jobs removed on user's request
-				fShowPopup = false;
-			/*
-			 * show a popup notification, unless they are disabled
-			 */
-			if (fShowPopup) {
-				wchar_t	*tszName = Clist_GetContactDisplayName(hContact);
-
-				POPUPDATAW ppd;
-				ppd.lchContact = hContact;
-				wcsncpy_s(ppd.lpwzContactName, (tszName ? tszName : TranslateT("'(Unknown contact)'")), _TRUNCATE);
-				wchar_t *msgPreview = Utils::GetPreviewWithEllipsis(reinterpret_cast<wchar_t *>(&pBuf[mir_strlen((char *)pBuf) + 1]), 100);
-				if (fSuccess) {
-					mir_snwprintf(ppd.lpwzText, TranslateT("A send later job completed successfully.\nThe original message: %s"),
-						msgPreview);
-					mir_free(msgPreview);
-				}
-				else if (fFailed) {
-					mir_snwprintf(ppd.lpwzText, TranslateT("A send later job failed to complete.\nThe original message: %s"),
-						msgPreview);
-					mir_free(msgPreview);
-				}
+	// cleans database entries for a persistent job (currently: manual send later jobs)
+	void cleanDB()
+	{
+		if (isPersistentJob()) {
+			char szKey[100];
+
+			db_unset(hContact, "SendLater", szId);
+			int iCount = db_get_dw(hContact, "SendLater", "count", 0);
+			if (iCount)
+				iCount--;
+			db_set_dw(hContact, "SendLater", "count", iCount);
+
+			// delete flags
+			mir_snprintf(szKey, "$%s", szId);
+			db_unset(hContact, "SendLater", szKey);
+		}
+	}
+
+	// checks conditions for deletion
+	bool mustDelete()
+	{
+		if (fSuccess)
+			return true;
+
+		if (fFailed && bCode == JOB_REMOVABLE)
+			return true;
+
+		return false;
+	}
+
+	~CSendLaterJob()
+	{
+		if (fSuccess || fFailed) {
+			if ((SendLater::ErrorPopups && fFailed) || (SendLater::SuccessPopups && fSuccess)) {
+				bool fShowPopup = true;
+
+				if (fFailed && bCode == JOB_REMOVABLE)	// no popups for jobs removed on user's request
+					fShowPopup = false;
 				/*
-				 * use message settings (timeout/colors) for success popups
-				 */
-				ppd.colorText = fFailed ? RGB(255, 245, 225) : nen_options.colTextMsg;
-				ppd.colorBack = fFailed ? RGB(191, 0, 0) : nen_options.colBackMsg;
-				ppd.PluginWindowProc = Utils::PopupDlgProcError;
-				ppd.lchIcon = fFailed ? PluginConfig.g_iconErr : PluginConfig.g_IconMsgEvent;
-				ppd.PluginData = nullptr;
-				ppd.iSeconds = fFailed ? -1 : nen_options.iDelayMsg;
-				PUAddPopupW(&ppd);
+				* show a popup notification, unless they are disabled
+				*/
+				if (fShowPopup) {
+					wchar_t *tszName = Clist_GetContactDisplayName(hContact);
+
+					POPUPDATAW ppd;
+					ppd.lchContact = hContact;
+					wcsncpy_s(ppd.lpwzContactName, (tszName ? tszName : TranslateT("'(Unknown contact)'")), _TRUNCATE);
+					ptrW msgPreview(Utils::GetPreviewWithEllipsis(reinterpret_cast<wchar_t *>(&pBuf[mir_strlen((char *)pBuf) + 1]), 100));
+					if (fSuccess)
+						mir_snwprintf(ppd.lpwzText, TranslateT("A send later job completed successfully.\nThe original message: %s"), msgPreview);
+					else if (fFailed)
+						mir_snwprintf(ppd.lpwzText, TranslateT("A send later job failed to complete.\nThe original message: %s"), msgPreview);
+
+					/*
+					* use message settings (timeout/colors) for success popups
+					*/
+					ppd.colorText = fFailed ? RGB(255, 245, 225) : nen_options.colTextMsg;
+					ppd.colorBack = fFailed ? RGB(191, 0, 0) : nen_options.colBackMsg;
+					ppd.PluginWindowProc = Utils::PopupDlgProcError;
+					ppd.lchIcon = fFailed ? PluginConfig.g_iconErr : PluginConfig.g_IconMsgEvent;
+					ppd.PluginData = nullptr;
+					ppd.iSeconds = fFailed ? -1 : nen_options.iDelayMsg;
+					PUAddPopupW(&ppd);
+				}
 			}
+			if (fFailed && (bCode == JOB_AGE || bCode == JOB_REMOVABLE) && szId[0] == 'S')
+				cleanDB();
 		}
-		if (fFailed && (bCode == JOB_AGE || bCode == JOB_REMOVABLE) && szId[0] == 'S')
-			cleanDB();
+	
 		mir_free(sendBuffer);
 		mir_free(pBuf);
 	}
-}
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Module data
+
+LIST<void>     g_sendLaterContactList(5, PtrKeySortT);
+LIST<CSendLaterJob> g_sendLaterJobList(5);
+
+bool    g_bIsInteractive = false;
+time_t  g_last_sendlater_processed = time(0);
+int     g_currJob = -1;
 
 /////////////////////////////////////////////////////////////////////////////////////////
 // Send Later dialog
@@ -163,9 +254,6 @@ static class CSendLaterDlg *pDialog;
 
 class CSendLaterDlg : public CDlgBase
 {
-	friend class CSendLater;
-
-	CSendLater *m_later;
 	MCONTACT m_hFilter = 0;  // contact handle to filter the qmgr list (0 = no filter, show all)
 	int      m_sel = -1;     // index of the combo box entry corresponding to the contact filter;
 
@@ -180,105 +268,6 @@ class CSendLaterDlg : public CDlgBase
 		return m_sel;
 	}
 
-	// fills the list of jobs with current contents of the job queue
-	// filters by m_hFilter (contact handle)
-	void FillList()
-	{
-		wchar_t *formatTime = L"%Y.%m.%d - %H:%M";
-
-		m_sel = 0;
-		m_filter.InsertString(TranslateT("<All contacts>"), -1, 0);
-
-		LVITEM lvItem = { 0 };
-
-		BYTE bCode = '-';
-		unsigned uIndex = 0;
-		for (auto &p : m_later->m_sendLaterJobList) {
-			CContactCache *c = CContactCache::getContactCache(p->hContact);
-
-			const wchar_t *tszNick = c->getNick();
-			if (m_hFilter && m_hFilter != p->hContact) {
-				AddFilter(c->getContact(), tszNick);
-				continue;
-			}
-
-			lvItem.mask = LVIF_TEXT | LVIF_PARAM;
-			wchar_t tszBuf[255];
-			mir_snwprintf(tszBuf, L"%s [%s]", tszNick, c->getRealAccount());
-			lvItem.pszText = tszBuf;
-			lvItem.cchTextMax = _countof(tszBuf);
-			lvItem.iItem = uIndex++;
-			lvItem.iSubItem = 0;
-			lvItem.lParam = LPARAM(p);
-			m_list.InsertItem(&lvItem);
-			AddFilter(c->getContact(), tszNick);
-
-			lvItem.mask = LVIF_TEXT;
-			wchar_t tszTimestamp[30];
-			wcsftime(tszTimestamp, 30, formatTime, _localtime32((__time32_t *)&p->created));
-			tszTimestamp[29] = 0;
-			lvItem.pszText = tszTimestamp;
-			lvItem.iSubItem = 1;
-			m_list.SetItem(&lvItem);
-
-			wchar_t *msg = mir_utf8decodeW(p->sendBuffer);
-			wchar_t *preview = Utils::GetPreviewWithEllipsis(msg, 255);
-			lvItem.pszText = preview;
-			lvItem.iSubItem = 2;
-			m_list.SetItem(&lvItem);
-			mir_free(preview);
-			mir_free(msg);
-
-			const wchar_t *tszStatusText = nullptr;
-			if (p->fFailed) {
-				tszStatusText = p->bCode == CSendLaterJob::JOB_REMOVABLE ?
-					TranslateT("Removed") : TranslateT("Failed");
-			}
-			else if (p->fSuccess)
-				tszStatusText = TranslateT("Sent OK");
-			else {
-				switch (p->bCode) {
-				case CSendLaterJob::JOB_DEFERRED:
-					tszStatusText = TranslateT("Deferred");
-					break;
-				case CSendLaterJob::JOB_AGE:
-					tszStatusText = TranslateT("Failed");
-					break;
-				case CSendLaterJob::JOB_HOLD:
-					tszStatusText = TranslateT("Suspended");
-					break;
-				default:
-					tszStatusText = TranslateT("Pending");
-					break;
-				}
-			}
-			if (p->bCode)
-				bCode = p->bCode;
-
-			wchar_t tszStatus[20];
-			mir_snwprintf(tszStatus, L"X/%s[%c] (%d)", tszStatusText, bCode, p->iSendCount);
-			tszStatus[0] = p->szId[0];
-			lvItem.pszText = tszStatus;
-			lvItem.iSubItem = 3;
-			m_list.SetItem(&lvItem);
-
-			if (p->lastSent == 0)
-				wcsncpy_s(tszTimestamp, L"Never", _TRUNCATE);
-			else {
-				wcsftime(tszTimestamp, 30, formatTime, _localtime32((__time32_t *)&p->lastSent));
-				tszTimestamp[29] = 0;
-			}
-			lvItem.pszText = tszTimestamp;
-			lvItem.iSubItem = 4;
-			m_list.SetItem(&lvItem);
-		}
-
-		if (m_hFilter == 0)
-			m_filter.SetCurSel(0);
-		else
-			m_filter.SetCurSel(m_sel);
-	}
-
 	// set the column headers
 	void SetupColumns()
 	{
@@ -314,14 +303,15 @@ class CSendLaterDlg : public CDlgBase
 	}
 
 	CCtrlCheck chkSuccess, chkError;
+	CCtrlHyperlink m_link;
 	CCtrlCombo m_filter;
+
+public:
 	CCtrlListView m_list;
-	CCtrlHyperlink m_link;
 
 public:
-	CSendLaterDlg(CSendLater *pLater) :
+	CSendLaterDlg() :
 		CDlgBase(g_plugin, IDD_SENDLATER_QMGR),
-		m_later(pLater),
 		m_list(this, IDC_QMGR_LIST),
 		m_link(this, IDC_QMGR_HELP, "https://wiki.miranda-ng.org/index.php?title=Plugin:TabSRMM/en/Send_later"),
 		m_filter(this, IDC_QMGR_FILTER),
@@ -346,8 +336,8 @@ public:
 		SetupColumns();
 		FillList();
 
-		chkSuccess.SetState(m_later->m_fSuccessPopups);
-		chkError.SetState(m_later->m_fErrorPopups);
+		chkSuccess.SetState(SendLater::SuccessPopups);
+		chkError.SetState(SendLater::ErrorPopups);
 		Show();
 		return true;
 	}
@@ -390,10 +380,10 @@ public:
 		if (m_list.GetSelectedCount() == 1)
 			::EnableMenuItem(hSubMenu, ID_QUEUEMANAGER_COPYMESSAGETOCLIPBOARD, MF_ENABLED);
 
-		m_later->m_fIsInteractive = true;
+		g_bIsInteractive = true;
 		int selection = ::TrackPopupMenu(hSubMenu, TPM_RETURNCMD, pt.x, pt.y, 0, m_hwnd, nullptr);
 		if (selection == ID_QUEUEMANAGER_CANCELALLMULTISENDJOBS) {
-			for (auto &p : m_later->m_sendLaterJobList) {
+			for (auto &p : g_sendLaterJobList) {
 				if (p->szId[0] == 'M') {
 					p->fFailed = true;
 					p->bCode = CSendLaterJob::JOB_REMOVABLE;
@@ -402,21 +392,19 @@ public:
 		}
 		else if (selection != 0) {
 			HandleMenuClick(selection);
-			m_later->m_last_sendlater_processed = 0;			// force a queue check
+			g_last_sendlater_processed = 0;			// force a queue check
 		}
-		m_later->m_fIsInteractive = false;
+		g_bIsInteractive = false;
 	}
 
 	void onChange_Success(CCtrlCheck *)
 	{
-		m_later->m_fSuccessPopups = chkSuccess.GetState();
-		db_set_b(0, SRMSGMOD_T, "qmgrSuccessPopups", m_later->m_fSuccessPopups);
+		SendLater::SuccessPopups = chkSuccess.GetState();
 	}
 
 	void onChange_Error(CCtrlCheck *)
 	{
-		m_later->m_fErrorPopups = chkError.GetState();
-		db_set_b(0, SRMSGMOD_T, "qmgrErrorPopups", m_later->m_fErrorPopups);
+		SendLater::ErrorPopups = chkError.GetState();
 	}
 
 	// this handles all commands sent by the context menu
@@ -477,30 +465,135 @@ public:
 		}
 		FillList();
 	}
+
+	// fills the list of jobs with current contents of the job queue
+	// filters by m_hFilter (contact handle)
+	void FillList()
+	{
+		wchar_t *formatTime = L"%Y.%m.%d - %H:%M";
+
+		m_sel = 0;
+		m_filter.InsertString(TranslateT("<All contacts>"), -1, 0);
+
+		LVITEM lvItem = { 0 };
+
+		BYTE bCode = '-';
+		unsigned uIndex = 0;
+		for (auto &p : g_sendLaterJobList) {
+			CContactCache *c = CContactCache::getContactCache(p->hContact);
+
+			const wchar_t *tszNick = c->getNick();
+			if (m_hFilter && m_hFilter != p->hContact) {
+				AddFilter(c->getContact(), tszNick);
+				continue;
+			}
+
+			lvItem.mask = LVIF_TEXT | LVIF_PARAM;
+			wchar_t tszBuf[255];
+			mir_snwprintf(tszBuf, L"%s [%s]", tszNick, c->getRealAccount());
+			lvItem.pszText = tszBuf;
+			lvItem.cchTextMax = _countof(tszBuf);
+			lvItem.iItem = uIndex++;
+			lvItem.iSubItem = 0;
+			lvItem.lParam = LPARAM(p);
+			m_list.InsertItem(&lvItem);
+			AddFilter(c->getContact(), tszNick);
+
+			lvItem.mask = LVIF_TEXT;
+			wchar_t tszTimestamp[30];
+			wcsftime(tszTimestamp, 30, formatTime, _localtime32((__time32_t *)&p->created));
+			tszTimestamp[29] = 0;
+			lvItem.pszText = tszTimestamp;
+			lvItem.iSubItem = 1;
+			m_list.SetItem(&lvItem);
+
+			wchar_t *msg = mir_utf8decodeW(p->sendBuffer);
+			wchar_t *preview = Utils::GetPreviewWithEllipsis(msg, 255);
+			lvItem.pszText = preview;
+			lvItem.iSubItem = 2;
+			m_list.SetItem(&lvItem);
+			mir_free(preview);
+			mir_free(msg);
+
+			const wchar_t *tszStatusText = nullptr;
+			if (p->fFailed) {
+				tszStatusText = p->bCode == CSendLaterJob::JOB_REMOVABLE ?
+					TranslateT("Removed") : TranslateT("Failed");
+			}
+			else if (p->fSuccess)
+				tszStatusText = TranslateT("Sent OK");
+			else {
+				switch (p->bCode) {
+				case CSendLaterJob::JOB_DEFERRED:
+					tszStatusText = TranslateT("Deferred");
+					break;
+				case CSendLaterJob::JOB_AGE:
+					tszStatusText = TranslateT("Failed");
+					break;
+				case CSendLaterJob::JOB_HOLD:
+					tszStatusText = TranslateT("Suspended");
+					break;
+				default:
+					tszStatusText = TranslateT("Pending");
+					break;
+				}
+			}
+			if (p->bCode)
+				bCode = p->bCode;
+
+			wchar_t tszStatus[20];
+			mir_snwprintf(tszStatus, L"X/%s[%c] (%d)", tszStatusText, bCode, p->iSendCount);
+			tszStatus[0] = p->szId[0];
+			lvItem.pszText = tszStatus;
+			lvItem.iSubItem = 3;
+			m_list.SetItem(&lvItem);
+
+			if (p->lastSent == 0)
+				wcsncpy_s(tszTimestamp, L"Never", _TRUNCATE);
+			else {
+				wcsftime(tszTimestamp, 30, formatTime, _localtime32((__time32_t *)&p->lastSent));
+				tszTimestamp[29] = 0;
+			}
+			lvItem.pszText = tszTimestamp;
+			lvItem.iSubItem = 4;
+			m_list.SetItem(&lvItem);
+		}
+
+		if (m_hFilter == 0)
+			m_filter.SetCurSel(0);
+		else
+			m_filter.SetCurSel(m_sel);
+	}
 };
 
-CSendLater::CSendLater() :
-	m_sendLaterContactList(5, PtrKeySortT),
-	m_sendLaterJobList(5),
-	m_fAvail(SRMSGMOD_T, "sendLaterAvail", false),
-	m_fErrorPopups(SRMSGMOD_T, "qmgrErrorPopups", false),
-	m_fSuccessPopups(SRMSGMOD_T, "qmgrSuccessPopups", false)
-{
-	m_last_sendlater_processed = time(0);
-}
+/////////////////////////////////////////////////////////////////////////////////////////
+// SendLater class
+
+CMOption<bool> SendLater::Avail(SRMSGMOD_T, "sendLaterAvail", false);
+CMOption<bool> SendLater::ErrorPopups(SRMSGMOD_T, "qmgrErrorPopups", false);
+CMOption<bool> SendLater::SuccessPopups(SRMSGMOD_T, "qmgrSuccessPopups", false);
 
+bool   SendLater::isInteractive() { return g_bIsInteractive; }
+bool   SendLater::isJobListEmpty() { return g_sendLaterJobList.getCount() == 0; }
+time_t SendLater::lastProcessed() { return g_last_sendlater_processed; }
+void   SendLater::setLastProcessed(const time_t _t) { g_last_sendlater_processed = _t; }
+void   SendLater::flushQueue() { g_last_sendlater_processed = 0; }
+bool   SendLater::haveJobs() { return (g_sendLaterJobList.getCount() != 0 && g_currJob != -1); }
+
+/////////////////////////////////////////////////////////////////////////////////////////
 // clear all open send jobs. Only called on system shutdown to remove
 // the jobs from memory. Must _NOT_ delete any sendlater related stuff from
 // the database (only successful sends may do this).
-CSendLater::~CSendLater()
+
+void SendLater::shutDown()
 {
 	if (pDialog)
 		pDialog->Close();
 
-	if (m_sendLaterJobList.getCount() == 0)
+	if (g_sendLaterJobList.getCount() == 0)
 		return;
 
-	for (auto &p : m_sendLaterJobList) {
+	for (auto &p : g_sendLaterJobList) {
 		mir_free(p->sendBuffer);
 		mir_free(p->pBuf);
 		p->fSuccess = false;					// avoid clearing jobs from the database
@@ -508,14 +601,15 @@ CSendLater::~CSendLater()
 	}
 }
 
-void CSendLater::startJobListProcess()
+void SendLater::startJobListProcess()
 {
-	m_currJob = 0;
+	g_currJob = 0;
 
 	if (pDialog)
 		pDialog->m_list.Disable();
 }
 
+/////////////////////////////////////////////////////////////////////////////////////////
 // checks if the current job in the timer-based process queue is subject
 // for deletion (that is, it has failed or succeeded)
 //
@@ -525,65 +619,62 @@ void CSendLater::startJobListProcess()
 // hotkeyhandler.cpp.
 //
 // returns true if more jobs are awaiting processing, false otherwise.
-bool CSendLater::processCurrentJob()
+
+bool SendLater::processCurrentJob()
 {
-	if (!m_sendLaterJobList.getCount() || m_currJob == -1)
+	if (!g_sendLaterJobList.getCount() || g_currJob == -1)
 		return false;
 
-	if (m_currJob >= m_sendLaterJobList.getCount()) {
-		m_currJob = -1;
+	if (g_currJob >= g_sendLaterJobList.getCount()) {
+		g_currJob = -1;
 		return false;
 	}
 
-	CSendLaterJob *p = m_sendLaterJobList[m_currJob];
+	CSendLaterJob *p = g_sendLaterJobList[g_currJob];
 	if (p->fSuccess || p->fFailed) {
 		if (p->mustDelete()) {
-			m_sendLaterJobList.remove(m_currJob);
+			g_sendLaterJobList.remove(g_currJob);
 			delete p;
 		}
-		else m_currJob++;
+		else g_currJob++;
 	}
-	else sendIt(m_sendLaterJobList[m_currJob++]);
+	else g_sendLaterJobList[g_currJob++]->sendIt();
 
-	if (m_currJob >= m_sendLaterJobList.getCount()) {
-		m_currJob = -1;
+	if (g_currJob >= g_sendLaterJobList.getCount()) {
+		g_currJob = -1;
 		return false;
 	}
 
 	return true;
 }
 
-// stub used as enum proc for the database enumeration, collecting
-// all entries in the SendLater module
-// (static function)
-int _cdecl CSendLater::addStub(const char *szSetting, void *lParam)
+/////////////////////////////////////////////////////////////////////////////////////////
+// called periodically from a timer, check if new contacts were added
+// and process them
+
+static int _cdecl addStub(const char *szSetting, void *lParam)
 {
-	return(sendLater->addJob(szSetting, lParam));
+	return(SendLater::addJob(szSetting, lParam));
 }
 
-// Process a single contact from the list of contacts with open send later jobs
-// enum the "SendLater" module and add all jobs to the list of open jobs.
-// addJob() will deal with possible duplicates
-// @param hContact HANDLE: contact's handle
-void CSendLater::processSingleContact(const MCONTACT hContact)
+static void processSingleContact(const MCONTACT hContact)
 {
 	int iCount = db_get_dw(hContact, "SendLater", "count", 0);
 	if (iCount)
-		db_enum_settings(hContact, CSendLater::addStub, "SendLater", (void*)hContact);
+		db_enum_settings(hContact, addStub, "SendLater", (void*)hContact);
 }
 
-// called periodically from a timer, check if new contacts were added
-// and process them
-void CSendLater::processContacts()
+void SendLater::processContacts()
 {
-	if (m_fAvail) {
-		for (auto &it : m_sendLaterContactList)
+	if (SendLater::Avail) {
+		for (auto &it : g_sendLaterContactList)
 			processSingleContact((UINT_PTR)it);
 
-		m_sendLaterContactList.destroy();
+		g_sendLaterContactList.destroy();
 	}
 }
 
+/////////////////////////////////////////////////////////////////////////////////////////
 // This function adds a new job to the list of messages to send unattended
 // used by the send later feature and multisend
 //
@@ -593,20 +684,21 @@ void CSendLater::processContacts()
 //
 // @param 	lParam: a contact handle for which the job should be scheduled
 // @return 	0 on failure, 1 otherwise
-int CSendLater::addJob(const char *szSetting, void *lParam)
+
+int SendLater::addJob(const char *szSetting, void *lParam)
 {
 	MCONTACT	hContact = (UINT_PTR)lParam;
 	DBVARIANT dbv = { 0 };
 	char *szOrig_Utf = nullptr;
 
-	if (!m_fAvail || !szSetting || !mir_strcmp(szSetting, "count") || mir_strlen(szSetting) < 8)
+	if (!SendLater::Avail || !szSetting || !mir_strcmp(szSetting, "count") || mir_strlen(szSetting) < 8)
 		return 0;
 
 	if (szSetting[0] != 'S' && szSetting[0] != 'M')
 		return 0;
 
 	// check for possible dupes
-	for (auto &p : m_sendLaterJobList)
+	for (auto &p : g_sendLaterJobList)
 		if (p->hContact == hContact && !mir_strcmp(p->szId, szSetting))
 			return 0;
 
@@ -656,105 +748,48 @@ int CSendLater::addJob(const char *szSetting, void *lParam)
 
 	mir_free(szWchar);
 	job->readFlags();
-	m_sendLaterJobList.insert(job);
+	g_sendLaterJobList.insert(job);
 	qMgrUpdate();
 	return 1;
 }
 
-// Try to send an open job from the job list
-// this is ONLY called from the WM_TIMER handler and should never be executed directly.
-int CSendLater::sendIt(CSendLaterJob *job)
-{
-	time_t now = time(0);
-	if (job->bCode == CSendLaterJob::JOB_HOLD || job->bCode == CSendLaterJob::JOB_DEFERRED || job->fSuccess || job->fFailed || job->lastSent > now)
-		return 0;											// this one is frozen or done (will be removed soon), don't process it now.
-
-	if (now - job->created > SENDLATER_AGE_THRESHOLD) {		// too old, this will be discarded and user informed by popup
-		job->fFailed = true;
-		job->bCode = CSendLaterJob::JOB_AGE;
-		return 0;
-	}
-
-	// mark job as deferred (5 unsuccessful sends). Job will not be removed, but
-	// the user must manually reset it in order to trigger a new send attempt.
-	if (job->iSendCount == 5) {
-		job->bCode = CSendLaterJob::JOB_DEFERRED;
-		return 0;
-	}
-
-	if (job->iSendCount > 0 && (now - job->lastSent < SENDLATER_RESEND_THRESHOLD))
-		return 0;											// this one was sent, but probably failed. Resend it after a while
-
-	CContactCache *c = CContactCache::getContactCache(job->hContact);
-	if (!c->isValid()) {
-		job->fFailed = true;
-		job->bCode = CSendLaterJob::INVALID_CONTACT;
-		return 0;						// can happen (contact has been deleted). mark the job as failed
-	}
-
-	MCONTACT hContact = c->getActiveContact();
-	const char *szProto = c->getActiveProto();
-	if (!hContact || szProto == nullptr)
-		return 0;
-
-	int wMyStatus = Proto_GetStatus(szProto);
-
-	// status mode checks
-	if (wMyStatus == ID_STATUS_OFFLINE) {
-		job->bCode = CSendLaterJob::JOB_MYSTATUS;
-		return 0;
-	}
-	if (job->szId[0] == 'S') {
-		if (wMyStatus != ID_STATUS_ONLINE || wMyStatus != ID_STATUS_FREECHAT) {
-			job->bCode = CSendLaterJob::JOB_MYSTATUS;
-			return 0;
-		}
-	}
-
-	job->lastSent = now;
-	job->iSendCount++;
-	job->hTargetContact = hContact;
-	job->bCode = CSendLaterJob::JOB_WAITACK;
-	job->hProcess = (HANDLE)ProtoChainSend(hContact, PSS_MESSAGE, 0, (LPARAM)job->sendBuffer);
-	return 0;
-}
-
+/////////////////////////////////////////////////////////////////////////////////////////
 // add a contact to the list of contacts having open send later jobs.
 // This is is periodically checked for new additions (processContacts())
 // and new jobs are created.
-void CSendLater::addContact(const MCONTACT hContact)
+
+void SendLater::addContact(const MCONTACT hContact)
 {
-	if (!m_fAvail)
+	if (!SendLater::Avail)
 		return;
 
-	if (m_sendLaterContactList.getCount() == 0) {
-		m_sendLaterContactList.insert((HANDLE)hContact);
-		m_last_sendlater_processed = 0; // force processing at next tick
+	if (g_sendLaterContactList.getCount() == 0) {
+		g_sendLaterContactList.insert((HANDLE)hContact);
+		g_last_sendlater_processed = 0; // force processing at next tick
 		return;
 	}
 
-	/*
-	* this list should not have duplicate entries
-	*/
-
-	if (m_sendLaterContactList.find((HANDLE)hContact))
+	// this list should not have duplicate entries
+	if (g_sendLaterContactList.find((HANDLE)hContact))
 		return;
 
-	m_sendLaterContactList.insert((HANDLE)hContact);
-	m_last_sendlater_processed = 0; // force processing at next tick
+	g_sendLaterContactList.insert((HANDLE)hContact);
+	g_last_sendlater_processed = 0; // force processing at next tick
 }
 
+/////////////////////////////////////////////////////////////////////////////////////////
 // process ACK messages for the send later job list. Called from the proto ack
 // handler when it does not find a match in the normal send queue
 //
 // Add the message to the database and mark it as successful. The job will be
 // removed later by the job list processing code.
-HANDLE CSendLater::processAck(const ACKDATA *ack)
+
+HANDLE SendLater::processAck(const ACKDATA *ack)
 {
-	if (m_sendLaterJobList.getCount() == 0 || !m_fAvail)
+	if (g_sendLaterJobList.getCount() == 0 || !SendLater::Avail)
 		return nullptr;
 
-	for (auto &p : m_sendLaterJobList)
+	for (auto &p : g_sendLaterJobList)
 		if (p->hProcess == ack->hProcess && p->hTargetContact == ack->hContact && !(p->fSuccess || p->fFailed)) {
 			if (!p->fSuccess) {
 				DBEVENTINFO dbei = {};
@@ -778,26 +813,23 @@ HANDLE CSendLater::processAck(const ACKDATA *ack)
 	return nullptr;
 }
 
+/////////////////////////////////////////////////////////////////////////////////////////
 // UI stuff (dialog procedures for the queue manager dialog
-void CSendLater::qMgrUpdate(bool fReEnable)
+
+void SendLater::qMgrUpdate(bool fReEnable)
 {
 	if (pDialog) {
 		if (fReEnable)
-			pDialog->m_list.Enable(true);
+			pDialog->m_list.Enable();
 		pDialog->FillList();
 	}
 }
 
+/////////////////////////////////////////////////////////////////////////////////////////
 // invoke queue manager dialog - do nothing if this dialog is already open
-void CSendLater::invokeQueueMgrDlg()
-{
-	if (pDialog == nullptr)
-		(new CSendLaterDlg(this))->Create();
-}
 
-// service function to invoke the queue manager
-INT_PTR CSendLater::svcQMgr(WPARAM, LPARAM)
+void SendLater::invokeQueueMgrDlg()
 {
-	sendLater->invokeQueueMgrDlg();
-	return 0;
+	if (pDialog == nullptr)
+		(new CSendLaterDlg())->Create();
 }
diff --git a/plugins/TabSRMM/src/sendlater.h b/plugins/TabSRMM/src/sendlater.h
index 18e3d318f8..72d87cbd9b 100644
--- a/plugins/TabSRMM/src/sendlater.h
+++ b/plugins/TabSRMM/src/sendlater.h
@@ -35,87 +35,22 @@
 #define TIMEOUT_SENDLATER 10000
 #define TIMEOUT_SENDLATER_TICK 200
 
-struct CSendLaterJob
-{
-	// job status/error codes
-	enum {
-		INVALID_CONTACT = 'I',
-		JOB_DEFERRED = 'D',
-		JOB_AGE = 'O',
-		JOB_MYSTATUS = 'M',
-		JOB_STATUS = 'S',
-		JOB_WAITACK = 'A',
-		JOB_REMOVABLE = 'R',
-		JOB_HOLD = 'H',
-	};
-
-	// internal flags
-	enum {
-		SLF_SUSPEND = 1,
-		SLF_INVALID = 2
-	};
-
-	void	readFlags();
-	void	writeFlags();
-	void	cleanDB();
-	bool	isPersistentJob();
-	bool	mustDelete();
-
-	CSendLaterJob();
-	~CSendLaterJob();
-
-	char     szId[20];            // database key name (time stamp of original send)
-	MCONTACT hContact;            // original contact where the message has been assigned
-	MCONTACT hTargetContact;      // *real* contact (can be different for metacontacts, e.g).
-	HANDLE   hProcess;            // returned from the protocols sending service. needed to find it in the ACK handler
-	time_t   created;             // job was created at this time (important to kill jobs, that are too old)
-	time_t   lastSent;            // time at which the delivery was initiated. used to handle timeouts
-	char    *sendBuffer;          // utf-8 send buffer
-	PBYTE    pBuf;                // conventional send buffer (for non-utf8 protocols)
-	DWORD    dwFlags;
-	int      iSendCount;          // # of times we tried to send it...
-	bool     fSuccess, fFailed;
-	BYTE     bCode;               // error/progress code (for the UI)
+enum {
+	SENDLATER_AGE_THRESHOLD = (86400 * 3),				// 3 days, older messages will be removed from the db.
+	SENDLATER_RESEND_THRESHOLD = 180,					// timeouted messages should be resent after that many seconds
+	SENDLATER_PROCESS_INTERVAL = 50						// process the list of waiting job every this many seconds
 };
 
-class CSendLater
+namespace SendLater
 {
-	friend class CSendLaterDlg;
-
-	void    processSingleContact(const MCONTACT hContact);
-	int     sendIt(CSendLaterJob *job);
-
-	LIST<void> m_sendLaterContactList;
-	LIST<CSendLaterJob> m_sendLaterJobList;
+	void   shutDown();
 
-	CMOption<bool> m_fAvail, m_fErrorPopups, m_fSuccessPopups;
-
-	bool     m_fIsInteractive = false;
-	time_t   m_last_sendlater_processed;
-	int      m_currJob = -1;
-
-public:
-	enum {
-		SENDLATER_AGE_THRESHOLD = (86400 * 3),				// 3 days, older messages will be removed from the db.
-		SENDLATER_RESEND_THRESHOLD = 180,					// timeouted messages should be resent after that many seconds
-		SENDLATER_PROCESS_INTERVAL = 50						// process the list of waiting job every this many seconds
-	};
-
-	CSendLater();
-	~CSendLater();
-	
-	__inline bool   isAvail() { return m_fAvail; }
-	__inline bool   haveErrorPopups() { return m_fErrorPopups; }
-	__inline bool   haveSuccessPopups() { return m_fSuccessPopups; }
-
-	__inline bool   isInteractive() const { return m_fIsInteractive; }
-	__inline bool   isJobListEmpty() const { return m_sendLaterJobList.getCount() == 0; }
-	__inline time_t lastProcessed() const { return m_last_sendlater_processed; }
-	__inline void   setLastProcessed(const time_t _t) { m_last_sendlater_processed = _t; }
-	__inline void   flushQueue() { m_last_sendlater_processed = 0; }
-	__inline bool   haveJobs() const { return (m_sendLaterJobList.getCount() != 0 && m_currJob != -1); }
-
-	static int _cdecl addStub(const char *szSetting, void *lParam);
+	bool   isInteractive();
+	bool   isJobListEmpty();
+	time_t lastProcessed();
+	void   setLastProcessed(const time_t _t);
+	void   flushQueue();
+	bool   haveJobs();
 
 	bool   processCurrentJob();
 	void   processContacts();
@@ -126,9 +61,8 @@ public:
 
 	void   invokeQueueMgrDlg();
 	void   qMgrUpdate(bool fReEnable = false);
-	static INT_PTR svcQMgr(WPARAM wParam, LPARAM lParam);
-};
 
-extern CSendLater *sendLater;
+	extern CMOption<bool> Avail, ErrorPopups, SuccessPopups;
+};
 
 #endif /* __SENDLATER_H */
diff --git a/plugins/TabSRMM/src/sendqueue.cpp b/plugins/TabSRMM/src/sendqueue.cpp
index bf444489fd..51a0904844 100644
--- a/plugins/TabSRMM/src/sendqueue.cpp
+++ b/plugins/TabSRMM/src/sendqueue.cpp
@@ -231,7 +231,7 @@ int SendQueue::sendQueued(CMsgDialog *dat, const int iEntry)
 
 		sendQueue->clearJob(iEntry);
 		if (iJobs)
-			sendLater->flushQueue(); // force queue processing
+			SendLater::flushQueue(); // force queue processing
 		return 0;
 	}
 
@@ -535,12 +535,10 @@ LRESULT SendQueue::WarnPendingJobs(unsigned int)
 
 int SendQueue::doSendLater(int iJobIndex, CMsgDialog *dat, MCONTACT hContact, bool fIsSendLater)
 {
-	bool  fAvail = sendLater->isAvail();
-
 	const wchar_t *szNote = nullptr;
 
 	if (fIsSendLater && dat) {
-		if (fAvail)
+		if (SendLater::Avail)
 			szNote = TranslateT("Message successfully queued for later delivery.\nIt will be sent as soon as possible and a popup will inform you about the result.");
 		else
 			szNote = TranslateT("The send later feature is not available on this protocol.");
@@ -565,7 +563,7 @@ int SendQueue::doSendLater(int iJobIndex, CMsgDialog *dat, MCONTACT hContact, bo
 		SendDlgItemMessage(dat->GetHwnd(), IDC_CLOSE, BUTTONADDTOOLTIP, (WPARAM)TranslateT("Close session"), BATF_UNICODE);
 		dat->m_bSaveBtn = false;
 
-		if (!fAvail)
+		if (!SendLater::Avail)
 			return 0;
 	}
 
@@ -595,7 +593,7 @@ int SendQueue::doSendLater(int iJobIndex, CMsgDialog *dat, MCONTACT hContact, bo
 	}
 	else {
 		mir_snprintf(tszMsg, required, "%s%s", utf_header.get(), job->szSendBuffer);
-		sendLater->addJob(tszMsg, (void*)hContact);
+		SendLater::addJob(tszMsg, (void*)hContact);
 	}
 	mir_free(tszMsg);
 
@@ -603,7 +601,7 @@ int SendQueue::doSendLater(int iJobIndex, CMsgDialog *dat, MCONTACT hContact, bo
 		int iCount = db_get_dw(hContact ? hContact : job->hContact, "SendLater", "count", 0);
 		iCount++;
 		db_set_dw(hContact ? hContact : job->hContact, "SendLater", "count", iCount);
-		sendLater->addContact(hContact ? hContact : job->hContact);
+		SendLater::addContact(hContact ? hContact : job->hContact);
 	}
 	return iJobIndex;
 }
diff --git a/plugins/TabSRMM/src/srmm.cpp b/plugins/TabSRMM/src/srmm.cpp
index ea15645329..f2e8da9603 100644
--- a/plugins/TabSRMM/src/srmm.cpp
+++ b/plugins/TabSRMM/src/srmm.cpp
@@ -88,8 +88,8 @@ int CMPlugin::Unload()
 	Skin->setupTabCloseBitmap(true);
 	Skin->UnloadAeroTabs();
 	CleanTempFiles();
+	SendLater::shutDown();
 	delete Skin;
-	delete sendLater;
 	delete sendQueue;
 	return iRet;
 }
-- 
cgit v1.2.3