/* * 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: sendqueue.cpp 13750 2011-08-03 20:10:43Z george.hazan $ * * Implements a queued, asynchronous sending system for tabSRMM. * */ #include "commonheaders.h" #pragma hdrstop SendQueue *sendQueue = 0; extern const TCHAR *pszIDCSAVE_save, *pszIDCSAVE_close; static char *pss_msg = "/SendMsg"; static char *pss_msgw = "/SendMsgW"; /** * get the service name to send a message * * @param hContact (HANDLE) contact's handle * @param dat _MessageWindowData * @param dwFlags * * @return (char *) name of the service to send a message to this contact */ char *SendQueue::MsgServiceName(const HANDLE hContact = 0, const TWindowData *dat = 0, int dwFlags = 0) { char szServiceName[100]; char *szProto = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM) hContact, 0); if (szProto == NULL) return pss_msg; if(dat) { if (dat->sendMode & SMODE_FORCEANSI || !(dwFlags & PREF_UNICODE)) return pss_msg; } _snprintf(szServiceName, sizeof(szServiceName), "%s%sW", szProto, PSS_MESSAGE); if (ServiceExists(szServiceName)) return pss_msgw; return pss_msg; } /* * searches the queue for a message belonging to the given contact which has been marked * as "failed" by either the ACKRESULT_FAILED or a timeout handler * returns: zero-based queue index or -1 if none was found */ int SendQueue::findNextFailed(const TWindowData *dat) const { if(dat) { int i; for (i = 0; i < NR_SENDJOBS; i++) { if (m_jobs[i].hOwner == dat->hContact && m_jobs[i].iStatus == SQ_ERROR) return i; } return -1; } return -1; } void SendQueue::handleError(TWindowData *dat, const int iEntry) const { if(dat) { TCHAR szErrorMsg[500]; dat->iCurrentQueueError = iEntry; mir_sntprintf(szErrorMsg, 500, _T("%s"), m_jobs[iEntry].szErrorMsg); logError(dat, iEntry, szErrorMsg); recallFailed(dat, iEntry); showErrorControls(dat, TRUE); ::HandleIconFeedback(dat, PluginConfig.g_iconErr); } } /* * add a message to the sending queue. * iLen = required size of the memory block to hold the message */ int SendQueue::addTo(TWindowData *dat, const int iLen, int dwFlags) { int iLength = 0, i; int iFound = NR_SENDJOBS; if (m_currentIndex >= NR_SENDJOBS) { _DebugPopup(dat->hContact, _T("Send queue full")); return 0; } /* * find a free entry in the send queue... */ for (i = 0; i < NR_SENDJOBS; i++) { if (m_jobs[i].hOwner != 0 || m_jobs[i].iStatus != 0) { // this entry is used, check if it's orphaned and can be removed... if (m_jobs[i].hwndOwner && IsWindow(m_jobs[i].hwndOwner)) // window exists, do not reuse it continue; if (time(NULL) - m_jobs[i].dwTime < 120) // non-acked entry, but not old enough, don't re-use it continue; clearJob(i); iFound = i; goto entry_found; } iFound = i; break; } entry_found: if (iFound == NR_SENDJOBS) { _DebugPopup(dat->hContact, _T("Send queue full")); return 0; } iLength = iLen; if (iLength > 0) { if (m_jobs[iFound].sendBuffer == NULL) { if (iLength < HISTORY_INITIAL_ALLOCSIZE) iLength = HISTORY_INITIAL_ALLOCSIZE; m_jobs[iFound].sendBuffer = (char *)malloc(iLength); m_jobs[iFound].dwLen = iLength; } else { if (iLength > m_jobs[iFound].dwLen) { m_jobs[iFound].sendBuffer = (char *)realloc(m_jobs[iFound].sendBuffer, iLength); m_jobs[iFound].dwLen = iLength; } } CopyMemory(m_jobs[iFound].sendBuffer, dat->sendBuffer, iLen); } m_jobs[iFound].dwFlags = dwFlags; m_jobs[iFound].dwTime = time(NULL); HWND hwndDlg = dat->hwnd; dat->cache->saveHistory(0, 0); ::SetDlgItemText(hwndDlg, IDC_MESSAGE, _T("")); ::SetFocus(GetDlgItem(hwndDlg, IDC_MESSAGE)); UpdateSaveAndSendButton(dat); sendQueued(dat, iFound); return 0; } /* * threshold for word-wrapping when sending messages in chunks */ #define SPLIT_WORD_CUTOFF 20 static int SendChunkW(WCHAR *chunk, HANDLE hContact, char *szSvc, DWORD dwFlags) { BYTE *pBuf = NULL; int wLen = lstrlenW(chunk), id; DWORD memRequired = (wLen + 1) * sizeof(WCHAR); DWORD codePage = DBGetContactSettingDword(hContact, SRMSGMOD_T, "ANSIcodepage", CP_ACP); int mbcsSize = WideCharToMultiByte(codePage, 0, chunk, -1, (char *)pBuf, 0, 0, 0); memRequired += mbcsSize; pBuf = (BYTE *)mir_alloc(memRequired); WideCharToMultiByte(codePage, 0, chunk, -1, (char *)pBuf, mbcsSize, 0, 0); CopyMemory(&pBuf[mbcsSize], chunk, (wLen + 1) * sizeof(WCHAR)); id = CallContactService(hContact, szSvc, dwFlags, (LPARAM)pBuf); mir_free(pBuf); return id; } static int SendChunkA(char *chunk, HANDLE hContact, char *szSvc, DWORD dwFlags) { return(CallContactService(hContact, szSvc, dwFlags, (LPARAM)chunk)); } static void DoSplitSendW(LPVOID param) { struct SendJob *job = sendQueue->getJobByIndex((int)param); int id; BOOL fFirstSend = FALSE; WCHAR *wszBegin, *wszTemp, *wszSaved, savedChar; int iLen, iCur = 0, iSavedCur = 0, i; BOOL fSplitting = TRUE; char szServiceName[100], *svcName; HANDLE hContact = job->hOwner; DWORD dwFlags = job->dwFlags; int chunkSize = job->chunkSize / 2; char *szProto = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM) hContact, 0); if (szProto == NULL) svcName = pss_msg; else { _snprintf(szServiceName, sizeof(szServiceName), "%s%sW", szProto, PSS_MESSAGE); if (ServiceExists(szServiceName)) svcName = pss_msgw; else svcName = pss_msg; } iLen = lstrlenA(job->sendBuffer); wszBegin = (WCHAR *) & job->sendBuffer[iLen + 1]; wszTemp = (WCHAR *)mir_alloc(sizeof(WCHAR) * (lstrlenW(wszBegin) + 1)); CopyMemory(wszTemp, wszBegin, sizeof(WCHAR) * (lstrlenW(wszBegin) + 1)); wszBegin = wszTemp; do { iCur += chunkSize; if (iCur > iLen) fSplitting = FALSE; /* * try to "word wrap" the chunks - split on word boundaries (space characters), if possible. * SPLIT_WORD_CUTOFF = max length of unbreakable words, longer words may be split. */ if (fSplitting) { i = 0; wszSaved = &wszBegin[iCur]; iSavedCur = iCur; while (iCur) { if (wszBegin[iCur] == (TCHAR)' ') { wszSaved = &wszBegin[iCur]; break; } if (i == SPLIT_WORD_CUTOFF) { // no space found backwards, restore old split position iCur = iSavedCur; wszSaved = &wszBegin[iCur]; break; } i++; iCur--; } savedChar = *wszSaved; *wszSaved = 0; id = SendChunkW(wszTemp, hContact, svcName, dwFlags); if (!fFirstSend) { job->hSendId = (HANDLE)id; fFirstSend = TRUE; PostMessage(PluginConfig.g_hwndHotkeyHandler, DM_SPLITSENDACK, (WPARAM)param, 0); } *wszSaved = savedChar; wszTemp = wszSaved; if (savedChar == (TCHAR)' ') { wszTemp++; iCur++; } } else { id = SendChunkW(wszTemp, hContact, svcName, dwFlags); if (!fFirstSend) { job->hSendId = (HANDLE)id; fFirstSend = TRUE; PostMessage(PluginConfig.g_hwndHotkeyHandler, DM_SPLITSENDACK, (WPARAM)param, 0); } } Sleep(500L); } while (fSplitting); mir_free(wszBegin); } static void DoSplitSendA(LPVOID param) { struct SendJob *job = sendQueue->getJobByIndex((int)param); int id; BOOL fFirstSend = FALSE; char *szBegin, *szTemp, *szSaved, savedChar; int iLen, iCur = 0, iSavedCur = 0, i; BOOL fSplitting = TRUE; char *svcName; HANDLE hContact = job->hOwner; DWORD dwFlags = job->dwFlags; int chunkSize = job->chunkSize; svcName = pss_msg; iLen = lstrlenA(job->sendBuffer); szTemp = (char *)mir_alloc(iLen + 1); CopyMemory(szTemp, job->sendBuffer, iLen + 1); szBegin = szTemp; do { iCur += chunkSize; if (iCur > iLen) fSplitting = FALSE; if (fSplitting) { i = 0; szSaved = &szBegin[iCur]; iSavedCur = iCur; while (iCur) { if (szBegin[iCur] == ' ') { szSaved = &szBegin[iCur]; break; } if (i == SPLIT_WORD_CUTOFF) { iCur = iSavedCur; szSaved = &szBegin[iCur]; break; } i++; iCur--; } savedChar = *szSaved; *szSaved = 0; id = SendChunkA(szTemp, hContact, PSS_MESSAGE, dwFlags); if (!fFirstSend) { job->hSendId = (HANDLE)id; fFirstSend = TRUE; PostMessage(PluginConfig.g_hwndHotkeyHandler, DM_SPLITSENDACK, (WPARAM)param, 0); } *szSaved = savedChar; szTemp = szSaved; if (savedChar == ' ') { szTemp++; iCur++; } } else { id = SendChunkA(szTemp, hContact, PSS_MESSAGE, dwFlags); if (!fFirstSend) { job->hSendId = (HANDLE)id; fFirstSend = TRUE; PostMessage(PluginConfig.g_hwndHotkeyHandler, DM_SPLITSENDACK, (WPARAM)param, 0); } } Sleep(500L); } while (fSplitting); mir_free(szBegin); } /** * return effective length of the message in bytes (utf-8 encoded) */ int SendQueue::getSendLength(const int iEntry, int sendMode) { if(m_jobs[iEntry].dwFlags & PREF_UNICODE && !(sendMode & SMODE_FORCEANSI)) { int iLen; WCHAR *wszBuf; char *utf8; iLen = lstrlenA(m_jobs[iEntry].sendBuffer); wszBuf = (WCHAR *) & m_jobs[iEntry].sendBuffer[iLen + 1]; utf8 = M->utf8_encodeW(wszBuf); m_jobs[iEntry].iSendLength = lstrlenA(utf8); mir_free(utf8); return(m_jobs[iEntry].iSendLength); } else { m_jobs[iEntry].iSendLength = lstrlenA(m_jobs[iEntry].sendBuffer); return(m_jobs[iEntry].iSendLength); } } int SendQueue::sendQueued(TWindowData *dat, const int iEntry) { HWND hwndDlg = dat->hwnd; if (dat->sendMode & SMODE_MULTIPLE) { HANDLE hContact, hItem; int iJobs = 0; int iMinLength = 0; CContactCache* c = 0; hContact = (HANDLE) CallService(MS_DB_CONTACT_FINDFIRST, 0, 0); m_jobs[iEntry].hOwner = dat->hContact; m_jobs[iEntry].iStatus = SQ_INPROGRESS; m_jobs[iEntry].hwndOwner = hwndDlg; int iSendLength = getSendLength(iEntry, dat->sendMode); do { hItem = (HANDLE) SendDlgItemMessage(hwndDlg, IDC_CLIST, CLM_FINDCONTACT, (WPARAM) hContact, 0); if (hItem && SendDlgItemMessage(hwndDlg, IDC_CLIST, CLM_GETCHECKMARK, (WPARAM) hItem, 0)) { c = CContactCache::getContactCache(hContact); if(c) iMinLength = (iMinLength == 0 ? c->getMaxMessageLength() : min(c->getMaxMessageLength(), iMinLength)); } } while (hContact = (HANDLE) CallService(MS_DB_CONTACT_FINDNEXT, (WPARAM) hContact, 0)); if(iSendLength >= iMinLength) { TCHAR tszError[256]; mir_sntprintf(tszError, 256, CTranslator::get(CTranslator::GEN_SQ_SENDLATER_ERROR_MSG_TOO_LONG), iMinLength); ::SendMessage(dat->hwnd, DM_ACTIVATETOOLTIP, IDC_MESSAGE, reinterpret_cast(tszError)); sendQueue->clearJob(iEntry); return(0); } hContact = (HANDLE) CallService(MS_DB_CONTACT_FINDFIRST, 0, 0); do { hItem = (HANDLE) SendDlgItemMessage(hwndDlg, IDC_CLIST, CLM_FINDCONTACT, (WPARAM) hContact, 0); if (hItem && SendDlgItemMessage(hwndDlg, IDC_CLIST, CLM_GETCHECKMARK, (WPARAM) hItem, 0)) { doSendLater(iEntry, 0, hContact, false); iJobs++; } } while (hContact = (HANDLE) CallService(MS_DB_CONTACT_FINDNEXT, (WPARAM) hContact, 0)); sendQueue->clearJob(iEntry); if(iJobs) sendLater->flushQueue(); // force queue processing return(0); } else { if (dat->hContact == NULL) return 0; //never happens dat->nMax = dat->cache->getMaxMessageLength(); // refresh length info if (dat->sendMode & SMODE_FORCEANSI && M->GetByte(dat->cache->getActiveContact(), dat->cache->getActiveProto(), "UnicodeSend", 1)) M->WriteByte(dat->cache->getActiveContact(), dat->cache->getActiveProto(), "UnicodeSend", 0); else if (!(dat->sendMode & SMODE_FORCEANSI) && !M->GetByte(dat->cache->getActiveContact(), dat->cache->getActiveProto(), "UnicodeSend", 0)) M->WriteByte(dat->cache->getActiveContact(), dat->cache->getActiveProto(), "UnicodeSend", 1); if (M->GetByte("autosplit", 0) && !(dat->sendMode & SMODE_SENDLATER)) { BOOL fSplit = FALSE; DWORD dwOldFlags; /* * determine send buffer length */ if(getSendLength(iEntry, dat->sendMode) >= dat->nMax) fSplit = true; if (!fSplit) goto send_unsplitted; m_jobs[iEntry].hOwner = dat->hContact; m_jobs[iEntry].hwndOwner = hwndDlg; m_jobs[iEntry].iStatus = SQ_INPROGRESS; m_jobs[iEntry].iAcksNeeded = 1; m_jobs[iEntry].chunkSize = dat->nMax; dwOldFlags = m_jobs[iEntry].dwFlags; if (dat->sendMode & SMODE_FORCEANSI) m_jobs[iEntry].dwFlags &= ~PREF_UNICODE; if (!(m_jobs[iEntry].dwFlags & PREF_UNICODE) || dat->sendMode & SMODE_FORCEANSI) mir_forkthread(DoSplitSendA, (LPVOID)iEntry); else mir_forkthread(DoSplitSendW, (LPVOID)iEntry); m_jobs[iEntry].dwFlags = dwOldFlags; } else { send_unsplitted: m_jobs[iEntry].hOwner = dat->hContact; m_jobs[iEntry].hwndOwner = hwndDlg; m_jobs[iEntry].iStatus = SQ_INPROGRESS; m_jobs[iEntry].iAcksNeeded = 1; if(dat->sendMode & SMODE_SENDLATER) { TCHAR tszError[256]; int iSendLength = getSendLength(iEntry, dat->sendMode); if(iSendLength >= dat->nMax) { mir_sntprintf(tszError, 256, CTranslator::get(CTranslator::GEN_SQ_SENDLATER_ERROR_MSG_TOO_LONG), dat->nMax); SendMessage(dat->hwnd, DM_ACTIVATETOOLTIP, IDC_MESSAGE, reinterpret_cast(tszError)); clearJob(iEntry); return(0); } doSendLater(iEntry, dat); clearJob(iEntry); return(0); } m_jobs[iEntry].hSendId = (HANDLE) CallContactService(dat->hContact, MsgServiceName(dat->hContact, dat, m_jobs[iEntry].dwFlags), (dat->sendMode & SMODE_FORCEANSI) ? (m_jobs[iEntry].dwFlags & ~PREF_UNICODE) : m_jobs[iEntry].dwFlags, (LPARAM) m_jobs[iEntry].sendBuffer); if (dat->sendMode & SMODE_NOACK) { // fake the ack if we are not interested in receiving real acks ACKDATA ack = {0}; ack.hContact = dat->hContact; ack.hProcess = m_jobs[iEntry].hSendId; ack.type = ACKTYPE_MESSAGE; ack.result = ACKRESULT_SUCCESS; SendMessage(hwndDlg, HM_EVENTSENT, (WPARAM)MAKELONG(iEntry, 0), (LPARAM)&ack); } else SetTimer(hwndDlg, TIMERID_MSGSEND + iEntry, PluginConfig.m_MsgTimeout, NULL); } } dat->iOpenJobs++; m_currentIndex++; // give icon feedback... if (dat->pContainer->hwndActive == hwndDlg) ::UpdateReadChars(dat); if (!(dat->sendMode & SMODE_NOACK)) ::HandleIconFeedback(dat, PluginConfig.g_IconSend); if (M->GetByte(SRMSGSET_AUTOMIN, SRMSGDEFSET_AUTOMIN)) ::SendMessage(dat->pContainer->hwnd, WM_SYSCOMMAND, SC_MINIMIZE, 0); return 0; } void SendQueue::clearJob(const int iIndex) { m_jobs[iIndex].hOwner = 0; m_jobs[iIndex].hwndOwner = 0; m_jobs[iIndex].iStatus = 0; m_jobs[iIndex].iAcksNeeded = 0; m_jobs[iIndex].dwFlags = 0; m_jobs[iIndex].chunkSize = 0; m_jobs[iIndex].dwTime = 0; m_jobs[iIndex].hSendId = 0; m_jobs[iIndex].iSendLength = 0; } /* * this is called when: * * ) a delivery has completed successfully * ) user decided to cancel a failed send * it removes the completed / canceled send job from the queue and schedules the next job to send (if any) */ void SendQueue::checkQueue(const TWindowData *dat) const { if(dat) { HWND hwndDlg = dat->hwnd; if (dat->iOpenJobs == 0) { ::HandleIconFeedback(const_cast(dat), (HICON) - 1); } else if (!(dat->sendMode & SMODE_NOACK)) ::HandleIconFeedback(const_cast(dat), PluginConfig.g_IconSend); if (dat->pContainer->hwndActive == hwndDlg) ::UpdateReadChars(const_cast(dat)); } } /* * logs an error message to the message window. Optionally, appends the original message * from the given sendJob (queue index) */ void SendQueue::logError(const TWindowData *dat, int iSendJobIndex, const TCHAR *szErrMsg) const { DBEVENTINFO dbei = {0}; int iMsgLen; if(dat == 0) return; dbei.eventType = EVENTTYPE_ERRMSG; dbei.cbSize = sizeof(dbei); if (iSendJobIndex >= 0) { dbei.pBlob = (BYTE *)m_jobs[iSendJobIndex].sendBuffer; iMsgLen = lstrlenA(m_jobs[iSendJobIndex].sendBuffer) + 1; } else { iMsgLen = 0; dbei.pBlob = NULL; } if (m_jobs[iSendJobIndex].dwFlags & PREF_UTF) dbei.flags = DBEF_UTF; if (iSendJobIndex >= 0) { if (m_jobs[iSendJobIndex].dwFlags & PREF_UNICODE) { iMsgLen *= 3; } } dbei.cbBlob = iMsgLen; dbei.timestamp = time(NULL); dbei.szModule = (char *)szErrMsg; StreamInEvents(dat->hwnd, NULL, 1, 1, &dbei); } /* * enable or disable the sending controls in the given window * ) input area * ) multisend contact list instance * ) send button */ void SendQueue::EnableSending(const TWindowData *dat, const int iMode) { if(dat) { HWND hwndDlg = dat->hwnd; ::SendDlgItemMessage(hwndDlg, IDC_MESSAGE, EM_SETREADONLY, (WPARAM) iMode ? FALSE : TRUE, 0); Utils::enableDlgControl(hwndDlg, IDC_CLIST, iMode ? TRUE : FALSE); ::EnableSendButton(dat, iMode); } } /* * show or hide the error control button bar on top of the window */ void SendQueue::showErrorControls(TWindowData *dat, const int showCmd) const { UINT myerrorControls[] = { IDC_STATICERRORICON, IDC_STATICTEXT, IDC_RETRY, IDC_CANCELSEND, IDC_MSGSENDLATER}; int i; HWND hwndDlg = dat->hwnd; if (showCmd) { TCITEM item = {0}; dat->hTabIcon = PluginConfig.g_iconErr; item.mask = TCIF_IMAGE; item.iImage = 0; TabCtrl_SetItem(GetDlgItem(dat->pContainer->hwnd, IDC_MSGTABS), dat->iTabID, &item); dat->dwFlags |= MWF_ERRORSTATE; } else { dat->dwFlags &= ~MWF_ERRORSTATE; dat->hTabIcon = dat->hTabStatusIcon; } for (i = 0; i < 5; i++) { if (IsWindow(GetDlgItem(hwndDlg, myerrorControls[i]))) Utils::showDlgControl(hwndDlg, myerrorControls[i], showCmd ? SW_SHOW : SW_HIDE); } SendMessage(hwndDlg, WM_SIZE, 0, 0); DM_ScrollToBottom(dat, 0, 1); if (m_jobs[0].hOwner != 0) EnableSending(dat, TRUE); } void SendQueue::recallFailed(const TWindowData *dat, int iEntry) const { int iLen = GetWindowTextLengthA(GetDlgItem(dat->hwnd, IDC_MESSAGE)); if(dat) { NotifyDeliveryFailure(dat); if (iLen == 0) { // message area is empty, so we can recall the failed message... SETTEXTEX stx = {ST_DEFAULT, 1200}; if (m_jobs[iEntry].dwFlags & PREF_UNICODE) SendDlgItemMessage(dat->hwnd, IDC_MESSAGE, EM_SETTEXTEX, (WPARAM)&stx, (LPARAM)&m_jobs[iEntry].sendBuffer[lstrlenA(m_jobs[iEntry].sendBuffer) + 1]); else { stx.codepage = (m_jobs[iEntry].dwFlags & PREF_UTF) ? CP_UTF8 : CP_ACP; SendDlgItemMessage(dat->hwnd, IDC_MESSAGE, EM_SETTEXTEX, (WPARAM)&stx, (LPARAM)m_jobs[iEntry].sendBuffer); } UpdateSaveAndSendButton(const_cast(dat)); SendDlgItemMessage(dat->hwnd, IDC_MESSAGE, EM_SETSEL, (WPARAM) - 1, (LPARAM) - 1); } } } void SendQueue::UpdateSaveAndSendButton(TWindowData *dat) { if(dat) { int len; HWND hwndDlg = dat->hwnd; GETTEXTLENGTHEX gtxl = {0}; gtxl.codepage = CP_UTF8; gtxl.flags = GTL_DEFAULT | GTL_PRECISE | GTL_NUMBYTES; len = SendDlgItemMessage(hwndDlg, IDC_MESSAGE, EM_GETTEXTLENGTHEX, (WPARAM) & gtxl, 0); if (len && GetSendButtonState(hwndDlg) == PBS_DISABLED) EnableSendButton(dat, TRUE); else if (len == 0 && GetSendButtonState(hwndDlg) != PBS_DISABLED) EnableSendButton(dat, FALSE); if (len) { // looks complex but avoids flickering on the button while typing. if (!(dat->dwFlags & MWF_SAVEBTN_SAV)) { SendDlgItemMessage(hwndDlg, IDC_SAVE, BM_SETIMAGE, IMAGE_ICON, (LPARAM) PluginConfig.g_buttonBarIcons[ICON_BUTTON_SAVE]); SendDlgItemMessage(hwndDlg, IDC_SAVE, BUTTONADDTOOLTIP, (WPARAM) pszIDCSAVE_save, 0); dat->dwFlags |= MWF_SAVEBTN_SAV; } } else { SendDlgItemMessage(hwndDlg, IDC_SAVE, BM_SETIMAGE, IMAGE_ICON, (LPARAM) PluginConfig.g_buttonBarIcons[ICON_BUTTON_CANCEL]); SendDlgItemMessage(hwndDlg, IDC_SAVE, BUTTONADDTOOLTIP, (WPARAM) pszIDCSAVE_close, 0); dat->dwFlags &= ~MWF_SAVEBTN_SAV; } dat->textLen = len; } } void SendQueue::NotifyDeliveryFailure(const TWindowData *dat) { POPUPDATAT_V2 ppd = {0}; int ibsize = 1023; if(M->GetByte("adv_noErrorPopups", 0)) return; if (CallService(MS_POPUP_QUERY, PUQS_GETSTATUS, 0) == 1) { ZeroMemory((void *)&ppd, sizeof(ppd)); ppd.lchContact = dat->hContact; mir_sntprintf(ppd.lptzContactName, MAX_CONTACTNAME, _T("%s"), dat->cache->getNick()); mir_sntprintf(ppd.lptzText, MAX_SECONDLINE, _T("%s"), CTranslator::get(CTranslator::GEN_SQ_DELIVERYFAILED)); if (!(BOOL)M->GetByte(MODULE, OPT_COLDEFAULT_ERR, TRUE)) { ppd.colorText = (COLORREF)M->GetDword(MODULE, OPT_COLTEXT_ERR, DEFAULT_COLTEXT); ppd.colorBack = (COLORREF)M->GetDword(MODULE, OPT_COLBACK_ERR, DEFAULT_COLBACK); } else ppd.colorText = ppd.colorBack = 0; ppd.PluginWindowProc = reinterpret_cast(Utils::PopupDlgProcError); ppd.lchIcon = PluginConfig.g_iconErr; ppd.PluginData = (void *)dat->hContact; ppd.iSeconds = (int)M->GetDword(MODULE, OPT_DELAY_ERR, (DWORD)DEFAULT_DELAY); CallService(MS_POPUP_ADDPOPUPT, (WPARAM)&ppd, 0); } } /* * searches string for characters typical for RTL text (hebrew and other RTL languages */ int SendQueue::RTL_Detect(const WCHAR *pszwText) { WORD *infoTypeC2; int i, n = 0; int iLen = lstrlenW(pszwText); infoTypeC2 = (WORD *)mir_alloc(sizeof(WORD) * (iLen + 2)); if (infoTypeC2) { ZeroMemory(infoTypeC2, sizeof(WORD) * (iLen + 2)); GetStringTypeW(CT_CTYPE2, pszwText, iLen, infoTypeC2); for (i = 0; i < iLen; i++) { if (infoTypeC2[i] == C2_RIGHTTOLEFT) n++; } mir_free(infoTypeC2); return(n >= 2 ? 1 : 0); } return 0; } int SendQueue::ackMessage(TWindowData *dat, WPARAM wParam, LPARAM lParam) { ACKDATA *ack = (ACKDATA *) lParam; DBEVENTINFO dbei = { 0}; HANDLE hNewEvent; int iFound = SendQueue::NR_SENDJOBS, iNextFailed; TContainerData *m_pContainer = 0; if (dat) m_pContainer = dat->pContainer; iFound = (int)(LOWORD(wParam)); //i = (int)(HIWORD(wParam)); if (m_jobs[iFound].iStatus == SQ_ERROR) { // received ack for a job which is already in error state... if (dat) { // window still open if (dat->iCurrentQueueError == iFound) { dat->iCurrentQueueError = -1; showErrorControls(dat, FALSE); } } /* * we must discard this job, because there is no message window open to handle the * error properly. But we display a tray notification to inform the user about the problem. */ else goto inform_and_discard; } // failed acks are only handled when the window is still open. with no window open, they will be *silently* discarded if (ack->result == ACKRESULT_FAILED) { if (dat) { /* * "hard" errors are handled differently in multisend. There is no option to retry - once failed, they * are discarded and the user is notified with a small log message. */ if (!nen_options.iNoSounds && !(m_pContainer->dwFlags & CNT_NOSOUND)) SkinPlaySound("SendError"); TCHAR *szAckMsg = mir_a2t((char *)ack->lParam); mir_sntprintf(m_jobs[iFound].szErrorMsg, safe_sizeof(m_jobs[iFound].szErrorMsg), CTranslator::get(CTranslator::GEN_MSG_DELIVERYFAILURE), szAckMsg); m_jobs[iFound].iStatus = SQ_ERROR; mir_free(szAckMsg); KillTimer(dat->hwnd, TIMERID_MSGSEND + iFound); if (!(dat->dwFlags & MWF_ERRORSTATE)) handleError(dat, iFound); return 0; } else { inform_and_discard: _DebugPopup(m_jobs[iFound].hOwner, CTranslator::get(CTranslator::GEN_SQ_DELIVERYFAILEDLATE)); clearJob(iFound); return 0; } } dbei.cbSize = sizeof(dbei); dbei.eventType = EVENTTYPE_MESSAGE; dbei.flags = DBEF_SENT; dbei.szModule = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM) m_jobs[iFound].hOwner, 0); dbei.timestamp = time(NULL); dbei.cbBlob = lstrlenA(m_jobs[iFound].sendBuffer) + 1; if (dat) dat->cache->updateStats(TSessionStats::BYTES_SENT, dbei.cbBlob - 1); else { CContactCache *c = CContactCache::getContactCache(m_jobs[iFound].hOwner); if(c) c->updateStats(TSessionStats::BYTES_SENT, dbei.cbBlob - 1); } if (m_jobs[iFound].dwFlags & PREF_UNICODE) dbei.cbBlob *= sizeof(TCHAR) + 1; if (m_jobs[iFound].dwFlags & PREF_RTL) dbei.flags |= DBEF_RTL; if (m_jobs[iFound].dwFlags & PREF_UTF) dbei.flags |= DBEF_UTF; dbei.pBlob = (PBYTE) m_jobs[iFound].sendBuffer; hNewEvent = (HANDLE) CallService(MS_DB_EVENT_ADD, (WPARAM) m_jobs[iFound].hOwner, (LPARAM) & dbei); if (m_pContainer) { if (!nen_options.iNoSounds && !(m_pContainer->dwFlags & CNT_NOSOUND)) SkinPlaySound("SendMsg"); } if (dat && (m_jobs[iFound].hOwner == dat->hContact)) { if (dat->hDbEventFirst == NULL) { dat->hDbEventFirst = hNewEvent; SendMessage(dat->hwnd, DM_REMAKELOG, 0, 0); } } m_jobs[iFound].hSendId = NULL; m_jobs[iFound].iAcksNeeded--; if (m_jobs[iFound].iAcksNeeded == 0) { // everything sent //if (m_jobs[iFound].hOwner != 0 && dat) // EnableSending(dat, TRUE); clearJob(iFound); if (dat) { KillTimer(dat->hwnd, TIMERID_MSGSEND + iFound); dat->iOpenJobs--; } m_currentIndex--; } if (dat) { checkQueue(dat); if ((iNextFailed = findNextFailed(dat)) >= 0 && !(dat->dwFlags & MWF_ERRORSTATE)) handleError(dat, iNextFailed); //MAD: close on send mode else { if (M->GetByte("AutoClose", 0)) { if(M->GetByte("adv_AutoClose_2", 0)) SendMessage(dat->hwnd, WM_CLOSE, 0, 1); else SendMessage(dat->pContainer->hwnd, WM_CLOSE, 0, 0); } } //MAD_ } return 0; } LRESULT SendQueue::WarnPendingJobs(unsigned int uNrMessages) { return(MessageBox(0, CTranslator::get(CTranslator::GEN_SQ_WARNING), CTranslator::get(CTranslator::GEN_SQ_WARNING_TITLE), MB_YESNO | MB_ICONHAND)); } /** * This just adds the message to the database for later delivery and * adds the contact to the list of contacts that have queued messages * * @param iJobIndex int: index of the send job * dat: Message window data * fAddHeader: add the "message was sent delayed" header (default = true) * hContact : contact to which the job should be added (default = hOwner of the send job) * * @return the index on success, -1 on failure */ int SendQueue::doSendLater(int iJobIndex, TWindowData *dat, HANDLE hContact, bool fIsSendLater) { bool fAvail = sendLater->isAvail(); const TCHAR *szNote = 0; if(fIsSendLater && dat) { if(fAvail) szNote = CTranslator::get(CTranslator::GEN_SQ_QUEUED_MESSAGE); else szNote = CTranslator::get(CTranslator::GEN_SQ_QUEUING_NOT_AVAIL); char *utfText = M->utf8_encodeT(szNote); DBEVENTINFO dbei; dbei.cbSize = sizeof(dbei); dbei.eventType = EVENTTYPE_MESSAGE; dbei.flags = DBEF_SENT | DBEF_UTF; dbei.szModule = (char *) CallService(MS_PROTO_GETCONTACTBASEPROTO, (WPARAM) dat->hContact, 0); dbei.timestamp = time(NULL); dbei.cbBlob = lstrlenA(utfText) + 1; dbei.pBlob = (PBYTE) utfText; StreamInEvents(dat->hwnd, 0, 1, 1, &dbei); if (dat->hDbEventFirst == NULL) SendMessage(dat->hwnd, DM_REMAKELOG, 0, 0); dat->cache->saveHistory(0, 0); EnableSendButton(dat, FALSE); if (dat->pContainer->hwndActive == dat->hwnd) UpdateReadChars(dat); SendDlgItemMessage(dat->hwnd, IDC_SAVE, BM_SETIMAGE, IMAGE_ICON, (LPARAM) PluginConfig.g_buttonBarIcons[ICON_BUTTON_CANCEL]); SendDlgItemMessage(dat->hwnd, IDC_SAVE, BUTTONADDTOOLTIP, (WPARAM)pszIDCSAVE_close, 0); dat->dwFlags &= ~MWF_SAVEBTN_SAV; mir_free(utfText); if(!fAvail) return(0); } if(iJobIndex >= 0 && iJobIndex < NR_SENDJOBS) { SendJob* job = &m_jobs[iJobIndex]; char szKeyName[20]; TCHAR tszTimestamp[30], tszHeader[150]; time_t now = time(0); if(fIsSendLater) { TCHAR *formatTime = _T("%Y.%m.%d - %H:%M"); _tcsftime(tszTimestamp, 30, formatTime, _localtime32((__time32_t *)&now)); tszTimestamp[29] = 0; mir_snprintf(szKeyName, 20, "S%d", now); mir_sntprintf(tszHeader, safe_sizeof(tszHeader), CTranslator::get(CTranslator::GEN_SQ_SENDLATER_HEADER), tszTimestamp); } else mir_sntprintf(tszHeader, safe_sizeof(tszHeader), _T("M%d|"), time(0)); if(job->dwFlags & PREF_UTF || !(job->dwFlags & PREF_UNICODE)) { char *utf_header = M->utf8_encodeT(tszHeader); UINT required = lstrlenA(utf_header) + lstrlenA(job->sendBuffer) + 10; char *tszMsg = reinterpret_cast(mir_alloc(required)); if(fIsSendLater) { mir_snprintf(tszMsg, required, "%s%s", job->sendBuffer, utf_header); DBWriteContactSettingString(hContact ? hContact : job->hOwner, "SendLater", szKeyName, tszMsg); } else { mir_snprintf(tszMsg, required, "%s%s", utf_header, job->sendBuffer); sendLater->addJob(tszMsg, (LPARAM)hContact); } mir_free(utf_header); mir_free(tszMsg); } else if(job->dwFlags & PREF_UNICODE) { int iLen = lstrlenA(job->sendBuffer); wchar_t *wszMsg = (wchar_t *)&job->sendBuffer[iLen + 1]; UINT required = sizeof(TCHAR) * (lstrlen(tszHeader) + lstrlenW(wszMsg) + 10); TCHAR *tszMsg = reinterpret_cast(mir_alloc(required)); if(fIsSendLater) mir_sntprintf(tszMsg, required, _T("%s%s"), wszMsg, tszHeader); else mir_sntprintf(tszMsg, required, _T("%s%s"), tszHeader, wszMsg); char *utf = M->utf8_encodeT(tszMsg); if(fIsSendLater) DBWriteContactSettingString(hContact ? hContact : job->hOwner, "SendLater", szKeyName, utf); else sendLater->addJob(utf, (LPARAM)hContact); mir_free(utf); mir_free(tszMsg); } if(fIsSendLater) { int iCount = M->GetDword(hContact ? hContact : job->hOwner, "SendLater", "count", 0); iCount++; M->WriteDword(hContact ? hContact : job->hOwner, "SendLater", "count", iCount); sendLater->addContact(hContact ? hContact : job->hOwner); } return(iJobIndex); } return(-1); }