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