/* * s e r v e r q . c p p * * Purpose: * Implements IMessageServer wrapper for queuing operations to * IMessageServer object. * * This object knows how to pack stack data for an IMessageServer method * call into a queue so that the call can be reissued when the server is * free. It maintains a small listen window so that is can post async * messages to itself to allow completion of the next task * * Owner: * brettm. * * History: * June 1998: Created * * Copyright (C) Microsoft Corp. 1993, 1994. */ #include "pch.hxx" #include "instance.h" #include "storutil.h" #include "serverq.h" static TCHAR c_szServerQListenWnd[] = "OE ServerQWnd"; #define SQW_NEXTTASK (WM_USER + 1) /* * Notes: * * the queueing is a little odd. For every method, we immediatley throw it in * the queue. If the server is not busy then we post a message to ourselves to * dequeue the task. We do this as if we passed-thru when the server is not busy * then we never get to hook the IStoreCallback to watch for completion, so we * never know when to queue the next task. * */ CServerQ::CServerQ() { m_cRef = 1; m_pServer = NULL; m_pTaskQueue = NULL; m_pLastQueueTask = NULL; m_hwnd = NULL; m_pCurrentCallback = NULL; m_pCurrentTask = NULL; m_cRefConnection = 0; #ifdef DEBUG m_DBG_pArgDataLast = 0; #endif } CServerQ::~CServerQ() { SafeRelease(m_pServer); _Flush(TRUE); SendMessage(m_hwnd, WM_CLOSE, 0, 0); } // IUnknown Members HRESULT CServerQ::QueryInterface(REFIID iid, LPVOID *ppvObject) { HRESULT hr=E_NOINTERFACE; TraceCall("CServerQ::QueryInterface"); if (ppvObject == NULL) return TraceResult(E_INVALIDARG); *ppvObject = NULL; // Find a ptr to the interface if (IID_IUnknown == iid) *ppvObject = (IMessageServer *)this; else if (IID_IMessageServer == iid) *ppvObject = (IMessageServer *)this; else if (IID_IServiceProvider == iid) *ppvObject = (IServiceProvider *)this; else if (IID_IStoreCallback == iid) *ppvObject = (IStoreCallback *)this; if (*ppvObject) { hr = S_OK; AddRef(); } return hr; } ULONG CServerQ::AddRef() { return ++m_cRef; } ULONG CServerQ::Release() { if (--m_cRef == 0) { delete this; return 0; } return m_cRef; } HRESULT CServerQ::Init(IMessageServer *pServerInner) { HRESULT hr=S_OK; WNDCLASS wc; TraceCall("CServerQ::Init"); Assert(m_pServer == NULL); if (!pServerInner) return TraceResult(E_INVALIDARG); if (!GetClassInfo(g_hInst, c_szServerQListenWnd, &wc)) { ZeroMemory(&wc, sizeof(WNDCLASS)); wc.lpfnWndProc = CServerQ::ExtWndProc; wc.hInstance = g_hInst; wc.hCursor = NULL; wc.lpszClassName = c_szServerQListenWnd; wc.hbrBackground = NULL; wc.style = 0; if (!RegisterClass(&wc)) return TraceResult(E_OUTOFMEMORY); } m_hwnd=CreateWindow(c_szServerQListenWnd, NULL, WS_OVERLAPPED, 0, 0, 0, 0, NULL, NULL, g_hInst, (LPVOID)this); if (!m_hwnd) return TraceResult(E_OUTOFMEMORY); #ifdef DEBUG // debug timer SetTimer(m_hwnd, 0, 10000, NULL); #endif ReplaceInterface(m_pServer, pServerInner); return S_OK; } // IMessageServer Methods HRESULT CServerQ::Initialize(IMessageStore *pStore, FOLDERID idStoreRoot, IMessageFolder *pFolder, FOLDERID idFolder) { return m_pServer->Initialize(pStore, idStoreRoot, pFolder, idFolder); } HRESULT CServerQ::ResetFolder(IMessageFolder *pFolder, FOLDERID idFolder) { return m_pServer->ResetFolder(pFolder, idFolder); } HRESULT CServerQ::SetIdleCallback(IStoreCallback *pDefaultCallback) { return m_pServer->SetIdleCallback(pDefaultCallback); } HRESULT CServerQ::SynchronizeFolder(SYNCFOLDERFLAGS dwFlags, DWORD cHeaders, IStoreCallback *pCallback) { ARGUMENT_DATA *pArg; HRESULT hr; hr = _AddToQueue(SOT_SYNC_FOLDER, pCallback, NULL, NULL, NULL, &pArg); if (!FAILED(hr)) { pArg->dwSyncFlags = dwFlags; pArg->cHeaders = cHeaders; return E_PENDING; } return TraceResult(hr); } HRESULT CServerQ::GetMessage(MESSAGEID idMessage, IStoreCallback *pCallback) { ARGUMENT_DATA *pArg, *pTask; HRESULT hr; STOREOPERATIONINFO soi; BOOL fFound=FALSE; ULONG l; // if we have tasks in our queue, looks for a get message task with the same id // if we find one, add this callback to the list and return STORE_S_ALREADYPENDING // to indicate that the pStream passed in will NOT be written to, and the caller // should get the message from the cache when his complete is called // make sure that the current task's next ptr points to the task queue // also make sure there is no task queue if there are no pending tasks Assert (m_pCurrentTask == NULL && m_pTaskQueue==NULL || m_pCurrentTask->pNext == m_pTaskQueue); pTask = m_pCurrentTask; // let's look for a pending request for this message while (pTask) { if (pTask->sot == SOT_GET_MESSAGE && pTask->idMessage == idMessage) { if (pCallback) { // cruise thro' the callback list. If this IStoreCallback is already // registered for this message, then don't add it otherwise it will // get multiple notifications if (pTask->pCallback == pCallback) fFound = TRUE; else for (l = 0; l < pTask->cOtherCallbacks; l++) if (pTask->rgpOtherCallback[l] == pCallback) { fFound = TRUE; break; } if (!fFound) { if (!MemRealloc((LPVOID *)&pTask->rgpOtherCallback, sizeof(IStoreCallback *) * (pTask->cOtherCallbacks+1))) { hr = TraceResult(E_OUTOFMEMORY); goto exit; } pTask->rgpOtherCallback[pTask->cOtherCallbacks++] = pCallback; pCallback->AddRef(); if (pTask == m_pCurrentTask) { // if this task if the current task, then the OnBegin call has already // been called. We fake the OnBegin to provide message id on get message start soi.cbSize = sizeof(STOREOPERATIONINFO); soi.idMessage = idMessage; pCallback->OnBegin(SOT_GET_MESSAGE, &soi, NULL); } } } hr = STORE_S_ALREADYPENDING; goto exit; // found } pTask = pTask->pNext; } // not already queued, let's add it. hr = _AddToQueue(SOT_GET_MESSAGE, pCallback, NULL, NULL, NULL, &pArg); if (!FAILED(hr)) { pArg->idMessage = idMessage; return E_PENDING; } exit: return hr; } HRESULT CServerQ::PutMessage(FOLDERID idFolder, MESSAGEFLAGS dwFlags, LPFILETIME pftReceived, IStream *pStream, IStoreCallback *pCallback) { ARGUMENT_DATA *pArg; HRESULT hr; hr = _AddToQueue(SOT_PUT_MESSAGE, pCallback, NULL, NULL, NULL, &pArg); if (!FAILED(hr)) { pArg->idFolder = idFolder; pArg->dwMsgFlags = dwFlags; if (NULL != pftReceived) { pArg->ftReceived = *pftReceived; pArg->pftReceived = &pArg->ftReceived; } else pArg->pftReceived = NULL; if (NULL != pStream) { pArg->pPutStream = pStream; pStream->AddRef(); } return E_PENDING; } return TraceResult(hr); } HRESULT CServerQ::CopyMessages(IMessageFolder *pDestFldr, COPYMESSAGEFLAGS dwOptions, LPMESSAGEIDLIST pList, LPADJUSTFLAGS pFlags, IStoreCallback *pCallback) { ARGUMENT_DATA *pArg; HRESULT hr; hr = _AddToQueue(SOT_COPYMOVE_MESSAGE, pCallback, pList, pFlags, NULL, &pArg); if (!FAILED(hr)) { if (pArg->pDestFldr = pDestFldr) pDestFldr->AddRef(); pArg->dwCopyOptions = dwOptions; return E_PENDING; } return TraceResult(hr); } HRESULT CServerQ::DeleteMessages(DELETEMESSAGEFLAGS dwOptions, LPMESSAGEIDLIST pList, IStoreCallback *pCallback) { ARGUMENT_DATA *pArg; HRESULT hr; Assert(NULL == pList || pList->cMsgs > 0); hr = _AddToQueue(SOT_DELETING_MESSAGES, pCallback, pList, NULL, NULL, &pArg); if (!FAILED(hr)) { pArg->dwDeleteOptions = dwOptions; return E_PENDING; } return TraceResult(hr); } HRESULT CServerQ::SetMessageFlags(LPMESSAGEIDLIST pList, LPADJUSTFLAGS pFlags, SETMESSAGEFLAGSFLAGS dwFlags, IStoreCallback *pCallback) { ARGUMENT_DATA *pArg=0; HRESULT hr; Assert(NULL == pList || pList->cMsgs > 0); hr = _AddToQueue(SOT_SET_MESSAGEFLAGS, pCallback, pList, pFlags, NULL, &pArg); if (!FAILED(hr)) { pArg->dwSetFlags = dwFlags; return E_PENDING; } return TraceResult(hr); } HRESULT CServerQ::SynchronizeStore(FOLDERID idParent, DWORD dwFlags, IStoreCallback *pCallback) { ARGUMENT_DATA *pArg; HRESULT hr; hr = _AddToQueue(SOT_SYNCING_STORE, pCallback, NULL, NULL, NULL, &pArg); if (!FAILED(hr)) { pArg->idParent = idParent; pArg->dwFlags = dwFlags; return E_PENDING; } return TraceResult(hr); } HRESULT CServerQ::CreateFolder(FOLDERID idParent, SPECIALFOLDER tySpecial, LPCSTR pszName, FLDRFLAGS dwFlags, IStoreCallback *pCallback) { ARGUMENT_DATA *pArg=0; HRESULT hr; hr = _AddToQueue(SOT_CREATE_FOLDER, pCallback, NULL, NULL, pszName, &pArg); if (!FAILED(hr)) { pArg->idParent = idParent; pArg->tySpecial = tySpecial; pArg->dwFldrFlags = dwFlags; return E_PENDING; } return TraceResult(hr); } HRESULT CServerQ::MoveFolder(FOLDERID idFolder, FOLDERID idParentNew, IStoreCallback *pCallback) { ARGUMENT_DATA *pArg; HRESULT hr; hr = _AddToQueue(SOT_MOVE_FOLDER, pCallback, NULL, NULL, NULL, &pArg); if (!FAILED(hr)) { pArg->idFolder = idFolder; pArg->idParentNew = idParentNew; return E_PENDING; } return TraceResult(hr); } HRESULT CServerQ::RenameFolder(FOLDERID idFolder, LPCSTR pszName, IStoreCallback *pCallback) { ARGUMENT_DATA *pArg=0; HRESULT hr; hr = _AddToQueue(SOT_RENAME_FOLDER, pCallback, NULL, NULL, pszName, &pArg); if (!FAILED(hr)) { pArg->idFolder = idFolder; return E_PENDING; } return TraceResult(hr); } HRESULT CServerQ::DeleteFolder(FOLDERID idFolder, DELETEFOLDERFLAGS dwFlags, IStoreCallback *pCallback) { ARGUMENT_DATA *pArg; HRESULT hr; hr = _AddToQueue(SOT_DELETE_FOLDER, pCallback, NULL, NULL, NULL, &pArg); if (!FAILED(hr)) { pArg->idFolder = idFolder; pArg->dwDelFldrFlags = dwFlags; return E_PENDING; } return TraceResult(hr); } HRESULT CServerQ::SubscribeToFolder(FOLDERID idFolder, BOOL fSubscribe, IStoreCallback *pCallback) { ARGUMENT_DATA *pArg; HRESULT hr; hr = _AddToQueue(SOT_SUBSCRIBE_FOLDER, pCallback, NULL, NULL, NULL, &pArg); if (!FAILED(hr)) { pArg->idFolder = idFolder; pArg->fSubscribe = fSubscribe; return E_PENDING; } return TraceResult(hr); } HRESULT CServerQ::GetFolderCounts(FOLDERID idFolder, IStoreCallback *pCallback) { ARGUMENT_DATA *pArg; HRESULT hr; hr = _AddToQueue(SOT_UPDATE_FOLDER, pCallback, NULL, NULL, NULL, &pArg); if (!FAILED(hr)) { pArg->idFolder = idFolder; return E_PENDING; } return TraceResult(hr); } HRESULT CServerQ::GetNewGroups(LPSYSTEMTIME pSysTime, IStoreCallback *pCallback) { ARGUMENT_DATA *pArg; HRESULT hr; Assert(pSysTime != NULL); hr = _AddToQueue(SOT_GET_NEW_GROUPS, pCallback, NULL, NULL, NULL, &pArg); if (!FAILED(hr)) { pArg->sysTime = *pSysTime; return E_PENDING; } return TraceResult(hr); } HRESULT CServerQ::GetWatchedInfo(FOLDERID idFolder, IStoreCallback *pCallback) { ARGUMENT_DATA *pArg; HRESULT hr; hr = _AddToQueue(SOT_GET_WATCH_INFO, pCallback, NULL, NULL, NULL, &pArg); if (!FAILED(hr)) { pArg->idFolder = idFolder; return (E_PENDING); } return TraceResult(hr); } HRESULT CServerQ::Close(DWORD dwFlags) { HRESULT hr=S_OK; if (m_cRefConnection != 0 && dwFlags & MSGSVRF_HANDS_OFF_SERVER) { // some-body else has a connection ref on the server object. // let's ignore the hands-off close return STORE_S_IN_USE; } if (m_pServer) hr = m_pServer->Close(dwFlags); // make sure we flush any pending ops _Flush(FALSE); return hr; } HRESULT CServerQ::OnBegin(STOREOPERATIONTYPE tyOperation, STOREOPERATIONINFO *pOpInfo, IOperationCancel *pCancel) { TraceInfoTag(TAG_SERVERQ, _MSG("CServerQ::OnBegin[sot='%s', pTask->sot='%s']", sotToSz(tyOperation), sotToSz(m_pCurrentTask->sot))); IxpAssert (m_pCurrentTask); if (m_pCurrentTask && m_pCurrentTask->sot == SOT_GET_MESSAGE) { // multiplex for (ULONG ul=0; ul < m_pCurrentTask->cOtherCallbacks; ul++) m_pCurrentTask->rgpOtherCallback[ul]->OnBegin(tyOperation, pOpInfo, pCancel); } return m_pCurrentCallback ? m_pCurrentCallback->OnBegin(tyOperation, pOpInfo, pCancel) : S_OK; } HRESULT CServerQ::OnProgress(STOREOPERATIONTYPE tyOperation, DWORD dwCurrent, DWORD dwMax, LPCSTR pszStatus) { IxpAssert (m_pCurrentTask); if (m_pCurrentTask && m_pCurrentTask->sot == SOT_GET_MESSAGE) { // multiplex for (ULONG ul=0; ul < m_pCurrentTask->cOtherCallbacks; ul++) m_pCurrentTask->rgpOtherCallback[ul]->OnProgress(tyOperation, dwCurrent, dwMax, pszStatus); } return m_pCurrentCallback ? m_pCurrentCallback->OnProgress(tyOperation, dwCurrent, dwMax, pszStatus) : S_OK; } HRESULT CServerQ::OnTimeout(LPINETSERVER pServer, LPDWORD pdwTimeout, IXPTYPE ixpServerType) { return m_pCurrentCallback ? m_pCurrentCallback->OnTimeout(pServer, pdwTimeout, ixpServerType) : S_OK; } HRESULT CServerQ::CanConnect(LPCSTR pszAccountId, DWORD dwFlags) { return m_pCurrentCallback ? m_pCurrentCallback->CanConnect(pszAccountId, dwFlags) : S_OK; } HRESULT CServerQ::OnLogonPrompt(LPINETSERVER pServer, IXPTYPE ixpServerType) { return m_pCurrentCallback ? m_pCurrentCallback->OnLogonPrompt(pServer, ixpServerType) : S_OK; } HRESULT CServerQ::OnComplete(STOREOPERATIONTYPE tyOperation, HRESULT hrComplete, LPSTOREOPERATIONINFO pOpInfo, LPSTOREERROR pErrorInfo) { HRESULT hr; TraceInfoTag(TAG_SERVERQ,_MSG("CServerQ::OnComplete[sot='%s', pTask->sot='%s']", sotToSz(tyOperation), sotToSz(m_pCurrentTask ? m_pCurrentTask->sot : SOT_INVALID))); IxpAssert (m_pCurrentTask); // multiplex if (m_pCurrentTask && m_pCurrentTask->sot == SOT_GET_MESSAGE) { for (ULONG ul=0; ul < m_pCurrentTask->cOtherCallbacks; ul++) m_pCurrentTask->rgpOtherCallback[ul]->OnComplete(tyOperation, hrComplete, pOpInfo, pErrorInfo); } if ((hrComplete == HR_E_OFFLINE) || (hrComplete == HR_E_USER_CANCEL_CONNECT)) { switch (m_pCurrentTask->sot) { case SOT_CREATE_FOLDER: hrComplete = HR_E_OFFLINE_FOLDER_CREATE; break; case SOT_MOVE_FOLDER: hrComplete = HR_E_OFFLINE_FOLDER_MOVE; break; case SOT_DELETE_FOLDER: hrComplete = HR_E_OFFLINE_FOLDER_DELETE; break; case SOT_RENAME_FOLDER: hrComplete = HR_E_OFFLINE_FOLDER_RENAME; break; } } hr = m_pCurrentCallback ? m_pCurrentCallback->OnComplete(tyOperation, hrComplete, pOpInfo, pErrorInfo) : S_OK; // if the operation failed due to a connection error (or user-cancel) then flush the queue // if the sync-folder operation failed, then ALWAYS flush the queue if (FAILED(hrComplete) && ((hrComplete == STORE_E_OPERATION_CANCELED) || (pErrorInfo && pErrorInfo->dwFlags & SE_FLAG_FLUSHALL))) { _Flush(FALSE); } if (m_pCurrentTask && m_pCurrentTask->sot == tyOperation) _StartNextTask(); // operation complete, start the next task return hr; } STDMETHODIMP CServerQ::GetServerMessageFlags(MESSAGEFLAGS *pFlags) { return m_pServer->GetServerMessageFlags(pFlags); } HRESULT CServerQ::OnPrompt(HRESULT hrError, LPCTSTR pszText, LPCTSTR pszCaption, UINT uType, INT *piUserResponse) { return m_pCurrentCallback ? m_pCurrentCallback->OnPrompt(hrError, pszText, pszCaption, uType, piUserResponse) : E_NOTIMPL; } HRESULT CServerQ::GetParentWindow(DWORD dwReserved, HWND *phwndParent) { return m_pCurrentCallback ? m_pCurrentCallback->GetParentWindow(dwReserved, phwndParent) : E_NOTIMPL; } LRESULT CServerQ::ExtWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) { CServerQ *pServer = (CServerQ *)GetWndThisPtr(hwnd); switch (uMsg) { case WM_CREATE: SetWndThisPtrOnCreate(hwnd, lParam); break; #ifdef DEBUG case WM_TIMER: if (pServer->m_DBG_pArgDataLast && pServer->m_DBG_pArgDataLast == pServer->m_pCurrentTask) { // if current-task is the same every 10 seconds, the print a warning message TraceInfo("WARNING: serverq processing same task for > 10 seconds"); pServer->_DBG_DumpQueue(); // beeping here is very hostile to httpmail. httpmail needs // lots and lots of time to download mail headers. this gives // our uses an opportunity to meditate on the many wonders of our new // protocol. //MessageBeep((UINT)-1); } else pServer->m_DBG_pArgDataLast = pServer->m_pCurrentTask; break; #endif case SQW_NEXTTASK: pServer->_OnNextTask(); break; } return DefWindowProc(hwnd, uMsg, wParam, lParam); } /* * Function : _AddToQueue * * Purpose : Adds an element to the tail of the queue and returns the allocated blob * * The only arguments on IMessageServer that need allocation to duplicate are * message list and adjust flags. I roll these arguments into one function AddToQueue * so that there is less code-gen for the error condition case, and if AddToQueue succeeds * there should be no futher failing operations - so noone calling this should need to * clean up the argdata and/or de-queue the failed addition * */ HRESULT CServerQ::_AddToQueue( STOREOPERATIONTYPE sot, IStoreCallback *pCallback, LPMESSAGEIDLIST pList, LPADJUSTFLAGS pFlags, LPCSTR pszName, ARGUMENT_DATA **ppNewArgData) { HRESULT hr; ARGUMENT_DATA *pArgData=0; TraceCall("CServerQ::_AddToQueue"); TraceInfoTag(TAG_SERVERQ,_MSG("CServerQ::_AddToQueue (sot='%s')", sotToSz(sot))); Assert (ppNewArgData); if (!MemAlloc((LPVOID *)&pArgData, sizeof(ARGUMENT_DATA))) { return TraceResult(E_OUTOFMEMORY); } ZeroMemory((LPVOID *)pArgData, sizeof(ARGUMENT_DATA)); if (pList) { hr = CloneMessageIDList(pList, &pArgData->pList); if (FAILED(hr)) { TraceResult(hr); goto error; } } if (pFlags) { hr = CloneAdjustFlags(pFlags, &pArgData->pFlags); if (FAILED(hr)) { TraceResult(hr); goto error; } } if (pszName) { pArgData->pszName = PszDupA(pszName); if (!pArgData->pszName) { hr = TraceResult(E_OUTOFMEMORY); goto error; } } if (pArgData->pCallback = pCallback) pCallback->AddRef(); pArgData->sot = sot; if (m_pLastQueueTask) m_pLastQueueTask->pNext = pArgData; m_pLastQueueTask = pArgData; if (!m_pTaskQueue) { m_pTaskQueue = pArgData; // if there are no pending tasks, then start the next task if (!m_pCurrentTask) _StartNextTask(); } *ppNewArgData = pArgData; return S_OK; error: _FreeArgumentData(pArgData); return hr; } HRESULT CServerQ::_OnNextTask() { HRESULT hr; TraceCall("CServerQ::_OnNextTask"); AssertSz(m_pCurrentTask, "How did we get here without a pending task?"); TraceInfoTag(TAG_SERVERQ,_MSG("CServerQ::_OnNextTask [ sot ='%s']", sotToSz(m_pCurrentTask->sot))); switch (m_pCurrentTask->sot) { case SOT_SYNC_FOLDER: hr = m_pServer->SynchronizeFolder(m_pCurrentTask->dwSyncFlags, m_pCurrentTask->cHeaders, (IStoreCallback *)this); break; case SOT_GET_MESSAGE: // TODO: add getmsg chaining here hr = m_pServer->GetMessage(m_pCurrentTask->idMessage, (IStoreCallback *)this); break; case SOT_PUT_MESSAGE: hr = m_pServer->PutMessage(m_pCurrentTask->idFolder, m_pCurrentTask->dwMsgFlags, m_pCurrentTask->pftReceived, m_pCurrentTask->pPutStream, (IStoreCallback *)this); break; case SOT_COPYMOVE_MESSAGE: hr = m_pServer->CopyMessages(m_pCurrentTask->pDestFldr, m_pCurrentTask->dwCopyOptions, m_pCurrentTask->pList, m_pCurrentTask->pFlags, (IStoreCallback *)this); break; case SOT_DELETING_MESSAGES: hr = m_pServer->DeleteMessages(m_pCurrentTask->dwDeleteOptions, m_pCurrentTask->pList, (IStoreCallback *)this); break; case SOT_SET_MESSAGEFLAGS: hr = m_pServer->SetMessageFlags(m_pCurrentTask->pList, m_pCurrentTask->pFlags, m_pCurrentTask->dwSetFlags, (IStoreCallback *)this); break; case SOT_SYNCING_STORE: hr = m_pServer->SynchronizeStore(m_pCurrentTask->idParent, m_pCurrentTask->dwFlags, (IStoreCallback *)this); break; case SOT_CREATE_FOLDER: hr = m_pServer->CreateFolder(m_pCurrentTask->idParent, m_pCurrentTask->tySpecial, m_pCurrentTask->pszName, m_pCurrentTask->dwFldrFlags, (IStoreCallback *)this); break; case SOT_MOVE_FOLDER: hr = m_pServer->MoveFolder(m_pCurrentTask->idFolder, m_pCurrentTask->idParentNew, (IStoreCallback *)this); break; case SOT_RENAME_FOLDER: hr = m_pServer->RenameFolder(m_pCurrentTask->idFolder, m_pCurrentTask->pszName, (IStoreCallback *)this); break; case SOT_DELETE_FOLDER: hr = m_pServer->DeleteFolder(m_pCurrentTask->idFolder, m_pCurrentTask->dwDelFldrFlags, (IStoreCallback *)this); break; case SOT_SUBSCRIBE_FOLDER: hr = m_pServer->SubscribeToFolder(m_pCurrentTask->idFolder, m_pCurrentTask->fSubscribe, (IStoreCallback *)this); break; case SOT_UPDATE_FOLDER: hr = m_pServer->GetFolderCounts(m_pCurrentTask->idFolder, (IStoreCallback *)this); break; case SOT_GET_NEW_GROUPS: hr = m_pServer->GetNewGroups(&m_pCurrentTask->sysTime, (IStoreCallback *)this); break; case SOT_GET_WATCH_INFO: hr = m_pServer->GetWatchedInfo(m_pCurrentTask->idFolder, (IStoreCallback *)this); break; case SOT_GET_ADURL: hr = m_pServer->GetAdBarUrl((IStoreCallback *)this); break; case SOT_GET_HTTP_MINPOLLINGINTERVAL: hr = m_pServer->GetMinPollingInterval((IStoreCallback*)this); break; default: AssertSz(0, "Bad ArgData type"); } if (hr != E_PENDING) { // if the operation did not return E_PENDING, then it is not completing ASYNC // if this is true, then we need to take care of the callback reporting here are // we have already returned E_PENDING when we put this dude in the queue STOREERROR rError; STOREERROR *pError=0; TCHAR szBuf[CCHMAX_STRINGRES]; TraceInfoTag(TAG_SERVERQ,_MSG("CServerQ::Operation failed to go ASYNC - propagating error. sot='%s', hr=0x%x", sotToSz(m_pCurrentTask->sot), hr)); if (hr != S_OK) { ZeroMemory((LPVOID)&rError, sizeof(STOREERROR)); rError.hrResult = hr; LoadString(g_hLocRes, idsGenericError, szBuf, ARRAYSIZE(szBuf)); rError.pszDetails = szBuf; pError = &rError; } // $TODO: need to add richer error reporting here. if (m_pCurrentCallback) { m_pCurrentCallback->OnBegin(m_pCurrentTask->sot, NULL, NULL); m_pCurrentCallback->OnComplete(m_pCurrentTask->sot, hr, NULL, pError); } if (m_pCurrentTask) _StartNextTask(); // operation complete, start the next task } return S_OK; } HRESULT CServerQ::_FreeArgumentData(ARGUMENT_DATA *pArgData) { ULONG ul; if (!pArgData) return S_OK; switch (pArgData->sot) { case SOT_SYNC_FOLDER: break; case SOT_GET_MESSAGE: for (ul = 0; ul < pArgData->cOtherCallbacks; ul++) ReleaseObj(pArgData->rgpOtherCallback[ul]); SafeMemFree(pArgData->rgpOtherCallback); pArgData->cOtherCallbacks = 0; break; case SOT_PUT_MESSAGE: ReleaseObj(pArgData->pPutStream); break; case SOT_COPYMOVE_MESSAGE: ReleaseObj(pArgData->pDestFldr); SafeMemFree(pArgData->pList); SafeMemFree(pArgData->pFlags); break; case SOT_DELETING_MESSAGES: SafeMemFree(pArgData->pList); break; case SOT_SET_MESSAGEFLAGS: SafeMemFree(pArgData->pList); SafeMemFree(pArgData->pFlags); break; case SOT_SYNCING_STORE: break; case SOT_CREATE_FOLDER: if (pArgData->pszName) MemFree((LPSTR)pArgData->pszName); break; case SOT_MOVE_FOLDER: break; case SOT_RENAME_FOLDER: if (pArgData->pszName) MemFree((LPSTR)pArgData->pszName); break; case SOT_DELETE_FOLDER: break; case SOT_SUBSCRIBE_FOLDER: break; case SOT_UPDATE_FOLDER: break; case SOT_GET_NEW_GROUPS: break; case SOT_GET_WATCH_INFO: break; case SOT_GET_ADURL: case SOT_GET_HTTP_MINPOLLINGINTERVAL: break; default: AssertSz(0, "Bad ArgData type"); } ReleaseObj(pArgData->pCallback); MemFree(pArgData); return S_OK; } HRESULT CreateServerQueue(IMessageServer *pServerInner, IMessageServer **ppServer) { CServerQ *pQueue=0; HRESULT hr; pQueue = new CServerQ(); if (!pQueue) { hr = TraceResult(E_OUTOFMEMORY); goto exit; } hr = pQueue->Init(pServerInner); if (FAILED(hr)) { TraceResult(hr); goto exit; } *ppServer = (IMessageServer *)pQueue; pQueue=0; exit: ReleaseObj(pQueue); return hr; } HRESULT CServerQ::QueryService(REFGUID guidService, REFIID riid, LPVOID *ppvObject) { if (SID_MessageServer == guidService) { HRESULT hrResult; // CServerQ is a IMessageServer, too! Try to satisfy the incoming request ourselves hrResult = QueryInterface(riid, ppvObject); if (SUCCEEDED(hrResult)) return hrResult; // Oh well, we can't provide this interface. Ask our server object. if (m_pServer != NULL) return m_pServer->QueryInterface(riid, ppvObject); } return E_NOINTERFACE; } HRESULT CServerQ::_StartNextTask() { HRESULT hr; TraceCall("CServerQ::_StartNextTask"); // clear the current task and dequeue the next one // we post a message to ourselves to start the operation // so that our stack is clean. _FreeArgumentData(m_pCurrentTask); m_pCurrentTask = NULL; m_pCurrentCallback = NULL; if (!m_pTaskQueue) // no more tasks { TraceInfoTag(TAG_SERVERQ,_MSG("CServerQ::_StartNextTask - no tasks left")); m_pLastQueueTask = NULL; return S_OK; } m_pCurrentTask = m_pTaskQueue; m_pTaskQueue = m_pTaskQueue->pNext; m_pCurrentCallback = m_pCurrentTask->pCallback; PostMessage(m_hwnd, SQW_NEXTTASK, 0, 0); return S_OK; } HRESULT CServerQ::_Flush(BOOL fFlushCurrent) { ARGUMENT_DATA *pArgData, *pArgDataFree; STOREERROR rError; STOREOPERATIONINFO soi; STOREOPERATIONINFO *psoi=NULL; TraceCall("CServerQ::_Flush"); ZeroMemory((LPVOID)&rError, sizeof(STOREERROR)); rError.hrResult = STORE_E_OPERATION_CANCELED; // cancel the current operation, if so requested if (fFlushCurrent && m_pCurrentCallback && m_pCurrentTask) { m_pCurrentCallback->OnComplete(m_pCurrentTask->sot, STORE_E_OPERATION_CANCELED, NULL, &rError); _FreeArgumentData(m_pCurrentTask); m_pCurrentTask = NULL; m_pCurrentCallback = NULL; } m_pLastQueueTask = NULL; // flush any queued ops pArgData = m_pTaskQueue; while (pArgData) { TraceInfoTag(TAG_SERVERQ,_MSG("CServerQ::Flushing Task: '%s'", sotToSz(pArgData->sot))); // send an OnComplete to all of the callbacks in the queue to cancel the operation if (pArgData->sot == SOT_GET_MESSAGE) { // if a GetMessage, besure to pass back the message-id on the flush so that // the view knows which message is getting flushed soi.cbSize = sizeof(STOREOPERATIONINFO); soi.idMessage = pArgData->idMessage; psoi = &soi; for (ULONG ul=0; ul < pArgData->cOtherCallbacks; ul++) { Assert (pArgData->rgpOtherCallback[ul]); pArgData->rgpOtherCallback[ul]->OnBegin(pArgData->sot, psoi, NULL); pArgData->rgpOtherCallback[ul]->OnComplete(pArgData->sot, STORE_E_OPERATION_CANCELED, NULL, &rError); } } if (pArgData->pCallback) { pArgData->pCallback->OnBegin(pArgData->sot, psoi, NULL); pArgData->pCallback->OnComplete(pArgData->sot, STORE_E_OPERATION_CANCELED, NULL, &rError); } pArgDataFree = pArgData; pArgData = pArgData->pNext; _FreeArgumentData(pArgDataFree); } m_pTaskQueue = NULL; return S_OK; } HRESULT CServerQ::ConnectionAddRef() { m_cRefConnection++; return S_OK; } HRESULT CServerQ::ConnectionRelease() { if (m_cRefConnection == 0) return E_UNEXPECTED; m_cRefConnection--; return S_OK; } #ifdef DEBUG HRESULT CServerQ::_DBG_DumpQueue() { ARGUMENT_DATA *pTask; TraceInfo("ServerQ pending tasks:"); pTask = m_pCurrentTask; while (pTask) { TraceInfo(_MSG("\tsot=%s", sotToSz(pTask->sot))); pTask = pTask->pNext; } return S_OK; } #endif HRESULT CServerQ::GetAdBarUrl(IStoreCallback *pCallback) { ARGUMENT_DATA *pArg; HRESULT hr = S_OK; IF_FAILEXIT(hr = _AddToQueue(SOT_GET_ADURL, pCallback, NULL, NULL, NULL, &pArg)); exit: return hr; } HRESULT CServerQ::GetMinPollingInterval(IStoreCallback *pCallback) { ARGUMENT_DATA *pArg; HRESULT hr = S_OK; IF_FAILEXIT(hr = _AddToQueue(SOT_GET_HTTP_MINPOLLINGINTERVAL, pCallback, NULL, NULL, NULL, &pArg)); exit: return hr; }