From f920ef497f3299ae24fe783ce03bdd93b419f764 Mon Sep 17 00:00:00 2001 From: Kirill Volinsky Date: Fri, 18 May 2012 22:02:50 +0000 Subject: plugins folders renaming git-svn-id: http://svn.miranda-ng.org/main/trunk@60 1316c22d-e87f-b044-9b9b-93d7a3e3ba9c --- plugins/Tabsrmm/src/sendlater.cpp | 989 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 989 insertions(+) create mode 100644 plugins/Tabsrmm/src/sendlater.cpp (limited to 'plugins/Tabsrmm/src/sendlater.cpp') diff --git a/plugins/Tabsrmm/src/sendlater.cpp b/plugins/Tabsrmm/src/sendlater.cpp new file mode 100644 index 0000000000..2cbf0b7237 --- /dev/null +++ b/plugins/Tabsrmm/src/sendlater.cpp @@ -0,0 +1,989 @@ +/* + * astyle --force-indent=tab=4 --brackets=linux --indent-switches + * --pad=oper --one-line=keep-blocks --unpad=paren + * + * Miranda IM: the free IM client for Microsoft* Windows* + * + * Copyright 2000-2009 Miranda ICQ/IM project, + * all portions of this codebase are copyrighted to the people + * listed in contributors.txt. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * you should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * part of tabSRMM messaging plugin for Miranda. + * + * (C) 2005-2010 by silvercircle _at_ gmail _dot_ com and contributors + * + * $Id: sendlater.cpp 14225 2012-05-08 19:49:18Z george.hazan $ + * + * the sendlater class implementation + */ + +#include "commonheaders.h" +#pragma hdrstop + +#define U_PREF_UNICODE PREF_UNICODE +/* + * implementation of the CSendLaterJob class + */ + +CSendLaterJob::CSendLaterJob() +{ + ZeroMemory(this, sizeof(CSendLaterJob)); + fSuccess = 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); +} + +/** + * check conditions for deletion + */ +bool CSendLaterJob::mustDelete() +{ + if(fSuccess) + return(true); + else if(fFailed) { + if(bCode == JOB_REMOVABLE) + return(true); + } + return(false); +} + +/** + * clean database entries for a persistent job (currently: manual send later jobs) + */ +void CSendLaterJob::cleanDB() +{ + if(isPersistentJob()) { + char szKey[100]; + + DBDeleteContactSetting(hContact, "SendLater", szId); + int iCount = M->GetDword(hContact, "SendLater", "count", 0); + if(iCount) + iCount--; + M->WriteDword(hContact, "SendLater", "count", iCount); + /* + * delete flags + */ + mir_snprintf(szKey, 100, "$%s", szId); + DBDeleteContactSetting(hContact, "SendLater", szKey); + } +} + +/** + * 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; + + mir_snprintf(szKey, 100, "$%s", szId); + localFlags = M->GetDword(hContact, "SendLater", szKey, 0); + + 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]; + + mir_snprintf(szKey, 100, "$%s", szId); + M->WriteDword(hContact, "SendLater", szKey, localFlags); + } +} + +/** + * delete a send later job + */ +CSendLaterJob::~CSendLaterJob() +{ + if(fSuccess || fFailed) { + POPUPDATAT_V2 ppd = {0}; + + 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 (PluginConfig.g_PopupAvail && fShowPopup) { + TCHAR *tszName = (TCHAR *)CallService(MS_CLIST_GETCONTACTDISPLAYNAME, (WPARAM)hContact, GCDNF_TCHAR); + + ZeroMemory((void *)&ppd, sizeof(ppd)); + ppd.lchContact = hContact; + ppd.cbSize = sizeof(ppd); + mir_sntprintf(ppd.lptzContactName, MAX_CONTACTNAME, _T("%s"), tszName ? tszName : CTranslator::get(CTranslator::GEN_UNKNOWN_CONTACT)); + TCHAR *msgPreview = Utils::GetPreviewWithEllipsis(reinterpret_cast(&pBuf[lstrlenA((char *)pBuf) + 1]), 100); + if(fSuccess) { + mir_sntprintf(ppd.lptzText, MAX_SECONDLINE, CTranslator::get(CTranslator::GEN_SQ_SENDLATER_SUCCESS_POPUP), + msgPreview); + mir_free(msgPreview); + } + else if(fFailed) { + mir_sntprintf(ppd.lptzText, MAX_SECONDLINE, CTranslator::get(CTranslator::GEN_SQ_SENDLATER_FAILED_POPUP), + msgPreview); + mir_free(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 = reinterpret_cast(Utils::PopupDlgProcError); + ppd.lchIcon = fFailed ? PluginConfig.g_iconErr : PluginConfig.g_IconMsgEvent; + ppd.PluginData = (void *)hContact; + ppd.iSeconds = fFailed ? -1 : nen_options.iDelayMsg; + CallService(MS_POPUP_ADDPOPUPW, (WPARAM)&ppd, 0); + } + } + if(fFailed && (bCode == JOB_AGE || bCode == JOB_REMOVABLE) && szId[0] == 'S') + cleanDB(); + mir_free(sendBuffer); + mir_free(pBuf); + } +} + +CSendLater::CSendLater() +{ + m_sendLaterContactList.clear(); + m_sendLaterJobList.clear(); + m_fAvail = M->GetByte("sendLaterAvail", 0) ? true : false; + m_last_sendlater_processed = time(0); + m_hwndDlg = 0; + m_fIsInteractive = false; + m_fErrorPopups = M->GetByte(0, SRMSGMOD_T, "qmgrErrorPopups", 0) ? true : false; + m_fSuccessPopups = M->GetByte(0, SRMSGMOD_T, "qmgrSuccessPopups", 0) ? true : false; +} + +/** + * 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() +{ + if(m_hwndDlg) + ::DestroyWindow(m_hwndDlg); + + if(m_sendLaterJobList.empty()) + return; + + SendLaterJobIterator it = m_sendLaterJobList.begin(); + + while(it != m_sendLaterJobList.end()) { + mir_free((*it)->sendBuffer); + mir_free((*it)->pBuf); + (*it)->fSuccess = false; // avoid clearing jobs from the database + delete *it; + it++; + } +} + +void CSendLater::startJobListProcess() +{ + m_jobIterator = m_sendLaterJobList.begin(); + + if (m_hwndDlg) + Utils::enableDlgControl(m_hwndDlg, IDC_QMGR_LIST, false); +} + +/** + * checks if the current job in the timer-based process queue is subject + * for deletion (that is, it has failed or succeeded) + * + * if not, it will send the job and increment the list iterator. + * + * this method is called once per tick from the timer based scheduler in + * hotkeyhandler.cpp. + * + * returns true if more jobs are awaiting processing, false otherwise. + */ +bool CSendLater::processCurrentJob() +{ + if(m_sendLaterJobList.empty() || m_jobIterator == m_sendLaterJobList.end()) + return(false); + + if((*m_jobIterator)->fSuccess || (*m_jobIterator)->fFailed) { + if((*m_jobIterator)->mustDelete()) { + delete *m_jobIterator; + m_jobIterator = m_sendLaterJobList.erase(m_jobIterator); + } + else + m_jobIterator++; + return(m_jobIterator == m_sendLaterJobList.end() ? false : true); + } + sendIt(*m_jobIterator); + m_jobIterator++; + return(m_jobIterator == m_sendLaterJobList.end() ? false : 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, LPARAM 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 HANDLE hContact) +{ + int iCount = M->GetDword(hContact, "SendLater", "count", 0); + + if(iCount) { + DBCONTACTENUMSETTINGS ces = {0}; + + ces.pfnEnumProc = CSendLater::addStub; + ces.szModule = "SendLater"; + ces.lParam = (LPARAM)hContact; + + CallService(MS_DB_CONTACT_ENUMSETTINGS, (WPARAM)hContact, (LPARAM)&ces); + } +} + +/** + * called periodically from a timer, check if new contacts were added + * and process them + */ +void CSendLater::processContacts() +{ + if(m_fAvail && !m_sendLaterContactList.empty()) { + std::vector::iterator it = m_sendLaterContactList.begin(); + while(it != m_sendLaterContactList.end()) { + processSingleContact(*it); + it++; + } + m_sendLaterContactList.clear(); + } +} + +/** + * This function adds a new job to the list of messages to send unattended + * used by the send later feature and multisend + * + * @param szSetting is either the name of the database key for a send later + * job OR the utf-8 encoded message for a multisend job prefixed with + * a 'M+timestamp'. Send later job ids start with "S". + * + * @param lParam: a contact handle for which the job should be scheduled + * @return 0 on failure, 1 otherwise + */ +int CSendLater::addJob(const char *szSetting, LPARAM lParam) +{ + HANDLE hContact = (HANDLE)lParam; + DBVARIANT dbv = {0}; + char *szOrig_Utf = 0; + + if(!m_fAvail || !szSetting || !strcmp(szSetting, "count") || lstrlenA(szSetting) < 8) + return(0); + + if(szSetting[0] != 'S' && szSetting[0] != 'M') + return(0); + + SendLaterJobIterator it = m_sendLaterJobList.begin(); + + /* + * check for possible dupes + */ + while(it != m_sendLaterJobList.end()) { + if((*it)->hContact == hContact && !strcmp((*it)->szId, szSetting)) { + return(0); + } + it++; + } + + if(szSetting[0] == 'S') { + if(0 == DBGetContactSettingString(hContact, "SendLater", szSetting, &dbv)) + szOrig_Utf = dbv.pszVal; + else + return(0); + } + else if(szSetting[0] == 'M') { + char *szSep = strchr(const_cast(szSetting), '|'); + if(!szSep) + return(0); + *szSep = 0; + szOrig_Utf = szSep + 1; + } + else + return(0); + + CSendLaterJob *job = new CSendLaterJob; + + strncpy(job->szId, szSetting, 20); + job->szId[19] = 0; + job->hContact = hContact; + job->created = atol(&szSetting[1]); + + char *szAnsi = 0; + wchar_t *szWchar = 0; + UINT required = 0; + + int iLen = lstrlenA(szOrig_Utf); + job->sendBuffer = reinterpret_cast(mir_alloc(iLen + 1)); + strncpy(job->sendBuffer, szOrig_Utf, iLen); + job->sendBuffer[iLen] = 0; + + /* + * construct conventional send buffer + */ + + szAnsi = M->utf8_decodecp(szOrig_Utf, CP_ACP, &szWchar); + iLen = lstrlenA(szAnsi); + if(szWchar) + required = iLen + 1 + ((lstrlenW(szWchar) + 1) * sizeof(wchar_t)); + else + required = iLen + 1; + + job->pBuf = (PBYTE)mir_calloc(required); + + strncpy((char *)job->pBuf, szAnsi, iLen); + job->pBuf[iLen] = 0; + if(szWchar) + wcsncpy((wchar_t *)&job->pBuf[iLen + 1], szWchar, lstrlenW(szWchar)); + + if(szSetting[0] == 'S') + DBFreeVariant(&dbv); + + mir_free(szWchar); + job->readFlags(); + m_sendLaterJobList.push_back(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) +{ + HANDLE hContact = job->hContact; + time_t now = time(0); + DWORD dwFlags = 0; + DBVARIANT dbv = {0}; + const char* szProto = 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)) { + //_DebugTraceA("Send it: message %s for %s RESEND but not old enough", job->szId, (char *)CallService(MS_CLIST_GETCONTACTDISPLAYNAME, (WPARAM)hContact, 0)); + return(0); // this one was sent, but probably failed. Resend it after a while + } + + CContactCache *c = CContactCache::getContactCache(hContact); + if(!c) + return(0); // should not happen + + if(!c->isValid()) { + job->fFailed = true; + job->bCode = CSendLaterJob::INVALID_CONTACT; + return(0); // can happen (contact has been deleted). mark the job as failed + } + + hContact = c->getActiveContact(); + szProto = c->getActiveProto(); + + if(!hContact || szProto == 0) + return(0); + + WORD wMyStatus = (WORD)CallProtoService(szProto, PS_GETSTATUS, 0, 0); + WORD wContactStatus = c->getActiveStatus(); + + /* + * 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); + } + } + if(wContactStatus == ID_STATUS_OFFLINE) { + job->bCode = CSendLaterJob::JOB_STATUS; + return(0); + } + + dwFlags = IsUtfSendAvailable(hContact) ? PREF_UTF : U_PREF_UNICODE; + + char *svcName = SendQueue::MsgServiceName(hContact, 0, dwFlags); + + job->lastSent = now; + job->iSendCount++; + job->hTargetContact = hContact; + job->bCode = CSendLaterJob::JOB_WAITACK; + + if(dwFlags & PREF_UTF) + job->hProcess = (HANDLE)CallContactService(hContact, svcName, dwFlags, (LPARAM)job->sendBuffer); + else + job->hProcess = (HANDLE)CallContactService(hContact, svcName, dwFlags, (LPARAM)job->pBuf); + return(0); +} + +/* + * add a contact to the list of contacts having open send later jobs. + * This ist is periodically checked for new additions (processContacts()) + * and new jobs are created. + */ + +void CSendLater::addContact(const HANDLE hContact) +{ + if(!m_fAvail) + return; + + std::vector::iterator it = m_sendLaterContactList.begin(); + + if(m_sendLaterContactList.empty()) { + m_sendLaterContactList.push_back(hContact); + m_last_sendlater_processed = 0; // force processing at next tick + return; + } + + /* + * this list should not have duplicate entries + */ + + while(it != m_sendLaterContactList.end()) { + if(*it == hContact) + return; + it++; + } + m_sendLaterContactList.push_back(hContact); + m_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) +{ + if(m_sendLaterJobList.empty() || !m_fAvail) + return(0); + + SendLaterJobIterator it = m_sendLaterJobList.begin(); + + while(it != m_sendLaterJobList.end()) { + if((*it)->hProcess == ack->hProcess && (*it)->hTargetContact == ack->hContact && !((*it)->fSuccess || (*it)->fFailed)) { + DBEVENTINFO dbei = {0}; + + if(!(*it)->fSuccess) { + dbei.cbSize = sizeof(dbei); + dbei.eventType = EVENTTYPE_MESSAGE; + dbei.flags = DBEF_SENT; + dbei.szModule = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM)((*it)->hContact), 0); + dbei.timestamp = time(NULL); + dbei.cbBlob = lstrlenA((*it)->sendBuffer) + 1; + dbei.flags |= DBEF_UTF; + dbei.pBlob = (PBYTE)((*it)->sendBuffer); + HANDLE hNewEvent = (HANDLE) CallService(MS_DB_EVENT_ADD, (WPARAM)((*it)->hContact), (LPARAM)&dbei); + + (*it)->cleanDB(); + } + (*it)->fSuccess = true; // mark as successful, job list processing code will remove it later + (*it)->hProcess = (HANDLE)-1; + (*it)->bCode = '-'; + qMgrUpdate(); + return(0); + } + it++; + } + return(0); +} + +/* + * UI stuff (dialog procedures for the queue manager dialog + */ + +void CSendLater::qMgrUpdate(bool fReEnable) +{ + if(m_hwndDlg) { + if(fReEnable) + Utils::enableDlgControl(m_hwndDlg, IDC_QMGR_LIST, true); + ::SendMessage(m_hwndDlg, WM_USER + 100, 0, 0); // if qmgr is open, tell it to update + } +} + +LRESULT CSendLater::qMgrAddFilter(const HANDLE hContact, const TCHAR* tszNick) +{ + LRESULT lr; + + lr = ::SendMessage(m_hwndFilter, CB_FINDSTRING, 0, reinterpret_cast(tszNick)); + if(lr == CB_ERR) { + lr = ::SendMessage(m_hwndFilter, CB_INSERTSTRING, -1, reinterpret_cast(tszNick)); + ::SendMessage(m_hwndFilter, CB_SETITEMDATA, lr, reinterpret_cast(hContact)); + if(hContact == m_hFilter) + m_sel = lr; + } + return(m_sel); +} + +/** + * fills the list of jobs with current contents of the job queue + * filters by m_hFilter (contact handle) + * + */ +void CSendLater::qMgrFillList(bool fClear) +{ + LVITEM lvItem = {0}; + unsigned uIndex = 0; + CContactCache* c = 0; + TCHAR* formatTime = _T("%Y.%m.%d - %H:%M"); + TCHAR tszTimestamp[30]; + TCHAR tszStatus[20]; + const TCHAR* tszStatusText = 0; + BYTE bCode = '-'; + + if(fClear) { + ::SendMessage(m_hwndList, LVM_DELETEALLITEMS, 0, 0); + ::SendMessage(m_hwndFilter, CB_RESETCONTENT, 0, 0); + } + + m_sel = 0; + ::SendMessage(m_hwndFilter, CB_INSERTSTRING, -1, + reinterpret_cast(CTranslator::get(CTranslator::QMGR_FILTER_ALLCONTACTS))); + ::SendMessage(m_hwndFilter, CB_SETITEMDATA, 0, 0); + + lvItem.cchTextMax = 255; + + SendLaterJobIterator it = m_sendLaterJobList.begin(); + + while(it != m_sendLaterJobList.end()) { + + c = CContactCache::getContactCache((*it)->hContact); + if(c) { + const TCHAR* tszNick = c->getNick(); + TCHAR tszBuf[255]; + + if(m_hFilter && m_hFilter != (*it)->hContact) { + qMgrAddFilter(c->getContact(), tszNick); + it++; + continue; + } + lvItem.mask = LVIF_TEXT|LVIF_PARAM; + mir_sntprintf(tszBuf, 255, _T("%s [%s]"), tszNick, c->getRealAccount()); + lvItem.pszText = tszBuf; + lvItem.iItem = uIndex++; + lvItem.iSubItem = 0; + lvItem.lParam = reinterpret_cast(*it); + ::SendMessage(m_hwndList, LVM_INSERTITEM, 0, reinterpret_cast(&lvItem)); + qMgrAddFilter(c->getContact(), tszNick); + + lvItem.mask = LVIF_TEXT; + _tcsftime(tszTimestamp, 30, formatTime, _localtime32((__time32_t *)&(*it)->created)); + tszTimestamp[29] = 0; + lvItem.pszText = tszTimestamp; + lvItem.iSubItem = 1; + ::SendMessage(m_hwndList, LVM_SETITEM, 0, reinterpret_cast(&lvItem)); + + TCHAR* msg = M->utf8_decodeT((*it)->sendBuffer); + TCHAR* preview = Utils::GetPreviewWithEllipsis(msg, 255); + lvItem.pszText = preview; + lvItem.iSubItem = 2; + ::SendMessage(m_hwndList, LVM_SETITEM, 0, reinterpret_cast(&lvItem)); + mir_free(preview); + mir_free(msg); + + if((*it)->fFailed) { + tszStatusText = (*it)->bCode == CSendLaterJob::JOB_REMOVABLE ? + CTranslator::get(CTranslator::QMGR_STATUS_REMOVED) : CTranslator::get(CTranslator::QMGR_STATUS_FAILED); + } + else if((*it)->fSuccess) + tszStatusText = CTranslator::get(CTranslator::QMGR_STATUS_SENTOK); + else { + switch((*it)->bCode) { + case CSendLaterJob::JOB_DEFERRED: + tszStatusText = CTranslator::get(CTranslator::QMGR_STATUS_DEFERRED); + break; + case CSendLaterJob::JOB_AGE: + tszStatusText = CTranslator::get(CTranslator::QMGR_STATUS_FAILED); + break; + case CSendLaterJob::JOB_HOLD: + tszStatusText = CTranslator::get(CTranslator::QMGR_STATUS_HOLD); + break; + default: + tszStatusText = CTranslator::get(CTranslator::QMGR_STATUS_PENDING); + break; + } + } + if((*it)->bCode) + bCode = (*it)->bCode; + mir_sntprintf(tszStatus, 20, _T("X/%s[%c] (%d)"), tszStatusText, bCode, (*it)->iSendCount); + tszStatus[0] = static_cast((*it)->szId[0]); + lvItem.pszText = tszStatus; + lvItem.iSubItem = 3; + ::SendMessage(m_hwndList, LVM_SETITEM, 0, reinterpret_cast(&lvItem)); + + if((*it)->lastSent == 0) + mir_sntprintf(tszTimestamp, 30, _T("%s"), _T("Never")); + else { + _tcsftime(tszTimestamp, 30, formatTime, _localtime32((__time32_t *)&(*it)->lastSent)); + tszTimestamp[29] = 0; + } + lvItem.pszText = tszTimestamp; + lvItem.iSubItem = 4; + ::SendMessage(m_hwndList, LVM_SETITEM, 0, reinterpret_cast(&lvItem)); + } + it++; + } + if(m_hFilter == 0) + ::SendMessage(m_hwndFilter, CB_SETCURSEL, 0, 0); + else + ::SendMessage(m_hwndFilter, CB_SETCURSEL, m_sel, 0); +} + +/* + * set the column headers + */ +#define QMGR_LIST_NRCOLUMNS 5 + +static char* szColFormat = "%d;%d;%d;%d;%d"; +static char* szColDefault = "100;120;80;120;120"; + +void CSendLater::qMgrSetupColumns() +{ + LVCOLUMN col = {0}; + int nWidths[QMGR_LIST_NRCOLUMNS]; + DBVARIANT dbv = {0}; + RECT rcList; + LONG cxList; + + ::GetWindowRect(m_hwndList, &rcList); + cxList = rcList.right - rcList.left; + + if(0 == M->GetString(0, SRMSGMOD_T, "qmgrListColumns", &dbv)) { + sscanf(dbv.pszVal, szColFormat, &nWidths[0], &nWidths[1], &nWidths[2], &nWidths[3], &nWidths[4]); + DBFreeVariant(&dbv); + } + else + sscanf(szColDefault, szColFormat, &nWidths[0], &nWidths[1], &nWidths[2], &nWidths[3], &nWidths[4]); + + col.mask = LVCF_TEXT|LVCF_WIDTH|LVCF_SUBITEM; + col.cx = max(nWidths[0], 10); + col.pszText = const_cast(CTranslator::get(CTranslator::GEN_CONTACT)); + + ::SendMessage(m_hwndList, LVM_INSERTCOLUMN, 0, reinterpret_cast(&col)); + + col.pszText = const_cast(CTranslator::get(CTranslator::QMGR_COL_ODATE)); + col.cx = max(nWidths[1], 10); + ::SendMessage(m_hwndList, LVM_INSERTCOLUMN, 1, reinterpret_cast(&col)); + + col.pszText = const_cast(CTranslator::get(CTranslator::QMGR_COL_MESSAGETEXT)); + col.cx = max((cxList - nWidths[0] - nWidths[1] - nWidths[3] - nWidths[4] - 10), 10); + ::SendMessage(m_hwndList, LVM_INSERTCOLUMN, 2, reinterpret_cast(&col)); + + col.pszText = const_cast(CTranslator::get(CTranslator::QMGR_COL_STATUS)); + col.cx = max(nWidths[3], 10); + ::SendMessage(m_hwndList, LVM_INSERTCOLUMN, 3, reinterpret_cast(&col)); + + col.pszText = const_cast(CTranslator::get(CTranslator::QMGR_COL_LASTSENDINFO)); + col.cx = max(nWidths[4], 10); + ::SendMessage(m_hwndList, LVM_INSERTCOLUMN, 4, reinterpret_cast(&col)); + +} + +/** + * save user defined column widths to the database + */ +void CSendLater::qMgrSaveColumns() +{ + char szColFormatNew[100]; + int nWidths[QMGR_LIST_NRCOLUMNS], i; + LVCOLUMN col = {0}; + + col.mask = LVCF_WIDTH; + for(i = 0; i < QMGR_LIST_NRCOLUMNS; i++) { + ::SendMessage(m_hwndList, LVM_GETCOLUMN, i, reinterpret_cast(&col)); + nWidths[i] = max(col.cx, 10); + } + mir_snprintf(szColFormatNew, 100, "%d;%d;%d;%d;%d", nWidths[0], nWidths[1], nWidths[2], nWidths[3], nWidths[4]); + ::DBWriteContactSettingString(0, SRMSGMOD_T, "qmgrListColumns", szColFormatNew); +} + +INT_PTR CALLBACK CSendLater::DlgProcStub(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + CSendLater *s = reinterpret_cast(::GetWindowLongPtr(hwnd, GWLP_USERDATA)); + + if(s) + return(s->DlgProc(hwnd, msg, wParam, lParam)); + + switch(msg) { + case WM_INITDIALOG: { + ::SetWindowLongPtr(hwnd, GWLP_USERDATA, lParam); + s = reinterpret_cast(lParam); + return(s->DlgProc(hwnd, msg, wParam, lParam)); + } + default: + break; + } + return(FALSE); +} + +INT_PTR CALLBACK CSendLater::DlgProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) +{ + switch(msg) { + case WM_INITDIALOG: + m_hwndDlg = hwnd; + TranslateDialogDefault(hwnd); + m_hwndList = ::GetDlgItem(m_hwndDlg, IDC_QMGR_LIST); + m_hwndFilter = ::GetDlgItem(m_hwndDlg, IDC_QMGR_FILTER); + m_hFilter = reinterpret_cast(M->GetDword(0, SRMSGMOD_T, "qmgrFilterContact", 0)); + + ::SetWindowLongPtr(m_hwndList, GWL_STYLE, ::GetWindowLongPtr(m_hwndList, GWL_STYLE) | LVS_SHOWSELALWAYS); + ::SendMessage(m_hwndList, LVM_SETEXTENDEDLISTVIEWSTYLE, 0, LVS_EX_FULLROWSELECT|LVS_EX_GRIDLINES|LVS_EX_LABELTIP|LVS_EX_DOUBLEBUFFER); + qMgrSetupColumns(); + qMgrFillList(); + if(PluginConfig.g_PopupAvail) { + ::CheckDlgButton(m_hwndDlg, IDC_QMGR_SUCCESSPOPUPS, m_fSuccessPopups ? BST_CHECKED : BST_UNCHECKED); + ::CheckDlgButton(m_hwndDlg, IDC_QMGR_ERRORPOPUPS, m_fErrorPopups ? BST_CHECKED : BST_UNCHECKED); + } + else { + Utils::showDlgControl(m_hwndDlg, IDC_QMGR_ERRORPOPUPS, SW_HIDE); + Utils::showDlgControl(m_hwndDlg, IDC_QMGR_SUCCESSPOPUPS, SW_HIDE); + } + ::ShowWindow(hwnd, SW_NORMAL); + return(FALSE); + + case WM_NOTIFY: { + NMHDR* pNMHDR = reinterpret_cast(lParam); + + if(pNMHDR->hwndFrom == m_hwndList) { + switch(pNMHDR->code) { + case NM_RCLICK: { + HMENU hMenu = ::LoadMenu(g_hInst, MAKEINTRESOURCE(IDR_TABCONTEXT)); + HMENU hSubMenu = ::GetSubMenu(hMenu, 13); + ::TranslateMenu(hSubMenu); + + POINT pt; + ::GetCursorPos(&pt); + /* + * copy to clipboard only allowed with a single selection + */ + if(::SendMessage(m_hwndList, LVM_GETSELECTEDCOUNT, 0, 0) == 1) + ::EnableMenuItem(hSubMenu, ID_QUEUEMANAGER_COPYMESSAGETOCLIPBOARD, MF_ENABLED); + + m_fIsInteractive = true; + int selection = ::TrackPopupMenu(hSubMenu, TPM_RETURNCMD, pt.x, pt.y, 0, m_hwndDlg, NULL); + if(selection == ID_QUEUEMANAGER_CANCELALLMULTISENDJOBS) { + SendLaterJobIterator it = m_sendLaterJobList.begin(); + while(it != m_sendLaterJobList.end()) { + if((*it)->szId[0] == 'M') { + (*it)->fFailed = true; + (*it)->bCode = CSendLaterJob::JOB_REMOVABLE; + } + it++; + } + } + else if(selection != 0) { + ::SendMessage(hwnd, WM_COMMAND, MAKEWPARAM(IDC_QMGR_REMOVE, LOWORD(selection)), 0); + m_last_sendlater_processed = 0; // force a queue check + } + ::DestroyMenu(hMenu); + m_fIsInteractive = false; + break; + } + default: + break; + } + } + break; + } + + case WM_COMMAND: + if(HIWORD(wParam) == CBN_SELCHANGE && reinterpret_cast(lParam) == m_hwndFilter) { + LRESULT lr = ::SendMessage(m_hwndFilter, CB_GETCURSEL, 0, 0); + if(lr != CB_ERR) { + m_hFilter = reinterpret_cast(::SendMessage(m_hwndFilter, CB_GETITEMDATA, lr, 0)); + qMgrFillList(); + } + break; + } + switch(LOWORD(wParam)) { + case IDOK: + case IDCANCEL: + qMgrSaveColumns(); + ::DestroyWindow(hwnd); + break; + + case IDC_QMGR_SUCCESSPOPUPS: + m_fSuccessPopups = ::IsDlgButtonChecked(m_hwndDlg, IDC_QMGR_SUCCESSPOPUPS) ? true : false; + M->WriteByte(0, SRMSGMOD_T, "qmgrSuccessPopups", m_fSuccessPopups ? 1 : 0); + break; + + case IDC_QMGR_ERRORPOPUPS: + m_fErrorPopups = ::IsDlgButtonChecked(m_hwndDlg, IDC_QMGR_ERRORPOPUPS) ? true : false; + M->WriteByte(0, SRMSGMOD_T, "qmgrErrorPopups", m_fErrorPopups ? 1 : 0); + break; + + case IDC_QMGR_HELP: + CallService(MS_UTILS_OPENURL, 0, reinterpret_cast("http://wiki.miranda.or.at/TabSRMM/SendLater")); + break; + /* + * this handles all commands sent by the context menu + * mark jobs for removal/reset/hold/unhold + * exception: kill all open multisend jobs is directly handled from the context menu + */ + case IDC_QMGR_REMOVE: { + if(::SendMessage(m_hwndList, LVM_GETSELECTEDCOUNT, 0, 0) != 0) { + LVITEM item = {0}; + LRESULT items = ::SendMessage(m_hwndList, LVM_GETITEMCOUNT, 0, 0); + item.mask = LVIF_STATE|LVIF_PARAM; + item.stateMask = LVIS_SELECTED; + + if(HIWORD(wParam) != ID_QUEUEMANAGER_COPYMESSAGETOCLIPBOARD) { + if(MessageBox(0, CTranslator::get(CTranslator::QMGR_WARNING_REMOVAL), CTranslator::get(CTranslator::QMGR_TITLE), + MB_ICONQUESTION | MB_OKCANCEL) == IDCANCEL) + break; + } + for(LRESULT i = 0; i < items; i++) { + item.iItem = i; + ::SendMessage(m_hwndList, LVM_GETITEM, 0, reinterpret_cast(&item)); + if(item.state & LVIS_SELECTED) { + CSendLaterJob* job = reinterpret_cast(item.lParam); + if(job) { + switch(HIWORD(wParam)) { + case ID_QUEUEMANAGER_MARKSELECTEDFORREMOVAL: + job->bCode = CSendLaterJob::JOB_REMOVABLE; + job->fFailed = true; + break; + case ID_QUEUEMANAGER_HOLDSELECTED: + job->bCode = CSendLaterJob::JOB_HOLD; + job->writeFlags(); + break; + case ID_QUEUEMANAGER_RESUMESELECTED: + job->bCode = 0; + job->writeFlags(); + break; + case ID_QUEUEMANAGER_COPYMESSAGETOCLIPBOARD: { + TCHAR *msg = M->utf8_decodeT(job->sendBuffer); + Utils::CopyToClipBoard(msg, m_hwndDlg); + mir_free(msg); + break; + } + /* + * reset deferred and aged jobs + * so they can be resent at next processing + */ + case ID_QUEUEMANAGER_RESETSELECTED: { + if(job->bCode == CSendLaterJob::JOB_DEFERRED) { + job->iSendCount = 0; + job->bCode = '-'; + } + else if(job->bCode == CSendLaterJob::JOB_AGE) { + job->fFailed = false; + job->bCode = '-'; + job->created = time(0); + } + break; + } + default: + break; + } + } + } + } + qMgrFillList(); + } + break; + } + } + break; + + case WM_USER + 100: + qMgrFillList(); + break; + case WM_NCDESTROY: { + m_hwndDlg = 0; + M->WriteDword(0, SRMSGMOD_T, "qmgrFilterContact", reinterpret_cast(m_hFilter)); + break; + } + } + return(FALSE); +} + +/** + * invoke queue manager dialog - do nothing if this dialog is already open + */ +void CSendLater::invokeQueueMgrDlg() +{ + if(m_hwndDlg == 0) + m_hwndDlg = ::CreateDialogParam(g_hInst, MAKEINTRESOURCE(IDD_SENDLATER_QMGR), 0, CSendLater::DlgProcStub, + reinterpret_cast(this)); +} + +/* + * service function to invoke the queue manager + */ + +INT_PTR CSendLater::svcQMgr(WPARAM wParam, LPARAM lParam) +{ + sendLater->invokeQueueMgrDlg(); + return(0); +} + + +CSendLater* sendLater = 0; -- cgit v1.2.3