summaryrefslogtreecommitdiff
path: root/plugins/TabSRMM/src/sendqueue.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/TabSRMM/src/sendqueue.cpp')
-rw-r--r--plugins/TabSRMM/src/sendqueue.cpp989
1 files changed, 989 insertions, 0 deletions
diff --git a/plugins/TabSRMM/src/sendqueue.cpp b/plugins/TabSRMM/src/sendqueue.cpp
new file mode 100644
index 0000000000..b4e90359e9
--- /dev/null
+++ b/plugins/TabSRMM/src/sendqueue.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: 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<LPARAM>(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<LPARAM>(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<TWindowData *>(dat), (HICON) - 1);
+ }
+ else if (!(dat->sendMode & SMODE_NOACK))
+ ::HandleIconFeedback(const_cast<TWindowData *>(dat), PluginConfig.g_IconSend);
+
+ if (dat->pContainer->hwndActive == hwndDlg)
+ ::UpdateReadChars(const_cast<TWindowData *>(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<TWindowData *>(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<WNDPROC>(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<char *>(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<TCHAR *>(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);
+}