#include "pch.hxx" #include "store.h" #include "instance.h" #include "storutil.h" #include #include #include #include "enumsync.h" #include "playback.h" #include "sync.h" COfflineSync *g_pSync = NULL; //-------------------------------------------------------------------------- // COfflineSync::COfflineSync //-------------------------------------------------------------------------- COfflineSync::COfflineSync(void) { m_cRef = 1; m_pDB = NULL; } //-------------------------------------------------------------------------- // CFolderSync::~CFolderSync //-------------------------------------------------------------------------- COfflineSync::~COfflineSync(void) { if (m_pDB != NULL) m_pDB->Release(); } //-------------------------------------------------------------------------- // CFolderSync::QueryInterface //-------------------------------------------------------------------------- STDMETHODIMP COfflineSync::QueryInterface(REFIID riid, LPVOID *ppv) { if (IID_IUnknown == riid) *ppv = (IUnknown *)this; { *ppv = NULL; return(E_NOINTERFACE); } // AddRef It ((IUnknown *)*ppv)->AddRef(); return(S_OK); } //-------------------------------------------------------------------------- // COfflineSync::AddRef //-------------------------------------------------------------------------- STDMETHODIMP_(ULONG) COfflineSync::AddRef(void) { return InterlockedIncrement(&m_cRef); } //-------------------------------------------------------------------------- // COfflineSync::Release //-------------------------------------------------------------------------- STDMETHODIMP_(ULONG) COfflineSync::Release(void) { LONG cRef = InterlockedDecrement(&m_cRef); if (0 == cRef) delete this; return (ULONG)cRef; } HRESULT COfflineSync::_SetMessageFlags(IMessageFolder *pFolder, FOLDERID idServer, FOLDERID idFolder, MESSAGEID idMessage, MESSAGEFLAGS dwFlags, LPADJUSTFLAGS pFlags) { MESSAGEFLAGS dwAdd, dwRemove; SYNCOPINFO info; MESSAGEIDLIST list; HRESULT hr; Assert(pFolder != NULL); Assert(pFlags != NULL); Assert(0 != pFlags->dwAdd || 0 != pFlags->dwRemove); // if no new flags are being added or only unset flags are being removed, // we don't need to do anything if ((dwFlags | pFlags->dwAdd) == dwFlags && (dwFlags & pFlags->dwRemove) == 0) return(S_OK); hr = _FindExistingOperation(idServer, idFolder, idMessage, SYNC_CREATE_MSG | SYNC_SETPROP_MSG, SYNC_COPY_MSG | SYNC_MOVE_MSG, &info); if (FAILED(hr)) return(hr); if (hr == S_OK) { if (info.tyOperation == SYNC_CREATE_MSG) { // this message has been created offline and will be uploaded to the // server, so we can just set the flags in the message and they will // get uploaded with the message } else { dwAdd = pFlags->dwAdd; dwRemove = pFlags->dwRemove; dwFlags = info.dwAdd & dwRemove; if (dwFlags != 0) { FLAGCLEAR(info.dwAdd, dwFlags); FLAGCLEAR(dwRemove, dwFlags); } dwFlags = info.dwRemove & dwAdd; if (dwFlags != 0) { FLAGCLEAR(info.dwRemove, dwFlags); FLAGCLEAR(dwAdd, dwFlags); } FLAGSET(info.dwAdd, dwAdd); FLAGSET(info.dwRemove, dwRemove); if (info.dwAdd == 0 && info.dwRemove == 0 && info.tyOperation == SYNC_SETPROP_MSG) { // no flags are being changed so we can get rid of this operation hr = m_pDB->DeleteRecord(&info); } else { hr = m_pDB->UpdateRecord(&info); } } m_pDB->FreeRecord(&info); if (FAILED(hr)) return(hr); } else { // no create or set prop operations exist for this message yet, // so create a new one ZeroMemory(&info, sizeof(SYNCOPINFO)); hr = m_pDB->GenerateId((LPDWORD)&info.idOperation); if (FAILED(hr)) return(hr); info.idServer = idServer; info.idFolder = idFolder; info.idMessage = idMessage; info.tyOperation = SYNC_SETPROP_MSG; // info.dwFlags info.dwAdd = (~dwFlags & pFlags->dwAdd); info.dwRemove = (dwFlags & pFlags->dwRemove); hr = m_pDB->InsertRecord(&info); if (FAILED(hr)) return(hr); } list.cAllocated = 0; list.cMsgs = 1; list.prgidMsg = &idMessage; hr = pFolder->SetMessageFlags(&list, pFlags, NULL, NULL); return(hr); } HRESULT COfflineSync::SetMessageFlags(IMessageFolder *pFolder, LPMESSAGEIDLIST pList, LPADJUSTFLAGS pFlags) { DWORD i; FOLDERID idFolder, idServer; MESSAGEINFO Message; HRESULT hr; HROWSET hRowset = NULL; Assert(pFolder != NULL); Assert(pFlags != NULL); Assert(0 != pFlags->dwAdd || 0 != pFlags->dwRemove); hr = pFolder->GetFolderId(&idFolder); if (FAILED(hr)) return(hr); hr = GetFolderServerId(idFolder, &idServer); if (FAILED(hr)) return(hr); if (NULL == pList) { hr = pFolder->CreateRowset(IINDEX_PRIMARY, NOFLAGS, &hRowset); if (FAILED(hr)) return(hr); } for (i = 0; ; i++) { if (pList != NULL) { if (i >= pList->cMsgs) break; Message.idMessage = pList->prgidMsg[i]; hr = pFolder->FindRecord(IINDEX_PRIMARY, COLUMNS_ALL, &Message, NULL); if (FAILED(hr)) break; else if (hr != DB_S_FOUND) continue; } else { hr = pFolder->QueryRowset(hRowset, 1, (LPVOID *)&Message, NULL); if (S_FALSE == hr) { hr = S_OK; break; } else if (FAILED(hr)) { break; } } hr = _SetMessageFlags(pFolder, idServer, idFolder, Message.idMessage, Message.dwFlags, pFlags); pFolder->FreeRecord(&Message); if (FAILED(hr)) break; } if (hRowset != NULL) pFolder->CloseRowset(&hRowset); return(hr); } HRESULT COfflineSync::DeleteMessages(IMessageFolder *pFolder, DELETEMESSAGEFLAGS dwFlags, LPMESSAGEIDLIST pList) { DWORD i; ADJUSTFLAGS afFlags; BOOL fNoOp, fImap; FOLDERINFO Server; FOLDERID idFolder, idServer; MESSAGEID idMessage; SYNCOPINFO info; MESSAGEIDLIST list; MESSAGEINFO Message; HRESULT hr; HROWSET hRowset = NULL; // TODO: what about trashcan deletes???? Assert(pFolder != NULL); hr = pFolder->GetFolderId(&idFolder); if (FAILED(hr)) return(hr); hr = GetFolderServer(idFolder, &Server); if (FAILED(hr)) return(hr); idServer = Server.idFolder; fImap = (Server.tyFolder == FOLDER_IMAP); g_pStore->FreeRecord(&Server); if (fImap) { if (!!(dwFlags & DELETE_MESSAGE_UNDELETE)) { afFlags.dwAdd = 0; afFlags.dwRemove = ARF_ENDANGERED; } else { afFlags.dwAdd = ARF_ENDANGERED; afFlags.dwRemove = 0; } return(SetMessageFlags(pFolder, pList, &afFlags)); } Assert(0 == (dwFlags & DELETE_MESSAGE_UNDELETE)); if (NULL == pList) { hr = pFolder->CreateRowset(IINDEX_PRIMARY, NOFLAGS, &hRowset); if (FAILED(hr)) return(hr); } list.cAllocated = 0; list.cMsgs = 1; list.prgidMsg = &idMessage; afFlags.dwAdd = ARF_DELETED_OFFLINE; afFlags.dwRemove = 0; for (i = 0; ; i++) { if (pList != NULL) { if (i >= pList->cMsgs) break; idMessage = pList->prgidMsg[i]; } else { hr = pFolder->QueryRowset(hRowset, 1, (LPVOID *)&Message, NULL); if (S_FALSE == hr) { hr = S_OK; break; } else if (FAILED(hr)) { break; } idMessage = Message.idMessage; pFolder->FreeRecord(&Message); } hr = _FindExistingOperation(idServer, idFolder, idMessage, SYNC_CREATE_MSG | SYNC_SETPROP_MSG, SYNC_COPY_MSG | SYNC_MOVE_MSG, &info); if (FAILED(hr)) break; if (hr == S_OK) { fNoOp = TRUE; if (info.tyOperation == SYNC_CREATE_MSG || info.tyOperation == SYNC_COPY_MSG) { // we don't need to do this create or copy anymore because the message is being // deleted. we don't need to do the delete either because the message // has never existed on the server hr = m_pDB->DeleteRecord(&info); } else if (info.tyOperation == SYNC_SETPROP_MSG) { // if it is a set prop operation, we don't need to do it anymore because the message // is just getting deleted anyway fNoOp = FALSE; hr = m_pDB->DeleteRecord(&info); } else { Assert(info.tyOperation == SYNC_MOVE_MSG); // convert it to a delete operation which is the same as moving it then deleting it info.idFolderDest = 0; info.idMessageDest = 0; info.tyOperation = SYNC_DELETE_MSG; info.dwAdd = 0; info.dwRemove = 0; info.dwFlags = dwFlags; hr = m_pDB->UpdateRecord(&info); } m_pDB->FreeRecord(&info); if (FAILED(hr)) { break; } else if (fNoOp) { hr = pFolder->DeleteMessages(dwFlags, &list, NULL, NULL); if (FAILED(hr)) break; continue; } } // create the delete operation ZeroMemory(&info, sizeof(SYNCOPINFO)); hr = m_pDB->GenerateId((LPDWORD)&info.idOperation); if (FAILED(hr)) break; info.idServer = idServer; info.idFolder = idFolder; info.idMessage = idMessage; info.tyOperation = SYNC_DELETE_MSG; info.dwFlags = dwFlags; // info.dwAdd // info.dwRemove hr = m_pDB->InsertRecord(&info); if (FAILED(hr)) break; hr = pFolder->SetMessageFlags(&list, &afFlags, NULL, NULL); if (FAILED(hr)) break; } if (hRowset != NULL) m_pDB->CloseRowset(&hRowset); return(hr); } HRESULT COfflineSync::CreateMessage(IMessageFolder *pFolder, LPMESSAGEID pidMessage, SAVEMESSAGEFLAGS dwOptions, MESSAGEFLAGS dwFlags, IStream *pStream, IMimeMessage *pMessage) { MESSAGEID idMessage; HRESULT hr; SYNCOPINFO info; FOLDERID idFolder, idServer; Assert(pFolder != NULL); Assert(pMessage != NULL); Assert(!!(dwOptions & SAVE_MESSAGE_GENID)); hr = pFolder->GetFolderId(&idFolder); if (FAILED(hr)) return(hr); hr = GetFolderServerId(idFolder, &idServer); if (FAILED(hr)) return(hr); ZeroMemory(&info, sizeof(SYNCOPINFO)); hr = m_pDB->GenerateId((LPDWORD)&info.idOperation); if (FAILED(hr)) return(hr); hr = pFolder->SaveMessage(&idMessage, SAVE_MESSAGE_GENID, dwFlags, pStream, pMessage, NULL); if (FAILED(hr)) return(hr); info.idServer = idServer; info.idFolder = idFolder; info.idMessage = idMessage; info.tyOperation = SYNC_CREATE_MSG; info.dwFlags = dwOptions; // info.dwAdd // info.dwRemove hr = m_pDB->InsertRecord(&info); return(hr); } HRESULT COfflineSync::CopyMessages(IMessageFolder *pFolder, IMessageFolder *pFolderDest, COPYMESSAGEFLAGS dwCopyFlags, LPMESSAGEIDLIST pList, LPADJUSTFLAGS pFlags) { DWORD i; BOOL fMove, fImap, fCopyToMove; FOLDERINFO Server; MESSAGEFLAGS dwFlags; ADJUSTFLAGS afFlags; FOLDERID idFolder, idServer, idFolderDest; MESSAGEID idMessage; SYNCOPINFO info, infoT; MESSAGEIDLIST list; HRESULT hr; IMimeMessage *pMsg; HROWSET hRowset = NULL; MESSAGEINFO Message = { 0 }; Assert(pFolder != NULL); Assert(pFolderDest != NULL); hr = pFolder->GetFolderId(&idFolder); if (FAILED(hr)) return(hr); hr = pFolderDest->GetFolderId(&idFolderDest); if (FAILED(hr)) return(hr); hr = GetFolderServer(idFolder, &Server); if (FAILED(hr)) return(hr); idServer = Server.idFolder; fImap = (Server.tyFolder == FOLDER_IMAP); g_pStore->FreeRecord(&Server); #ifdef DEBUG FOLDERID idServerDest; Assert(SUCCEEDED(GetFolderServerId(idFolderDest, &idServerDest))); Assert(SUCCEEDED(hr)); Assert(idServer == idServerDest); #endif // DEBUG if (NULL == pList) { hr = pFolder->CreateRowset(IINDEX_PRIMARY, NOFLAGS, &hRowset); if (FAILED(hr)) return(hr); } fMove = !!(dwCopyFlags & COPY_MESSAGE_MOVE); if (fMove) { list.cAllocated = 0; list.cMsgs = 1; list.prgidMsg = &idMessage; if (fImap) { afFlags.dwAdd = ARF_ENDANGERED; afFlags.dwRemove = 0; } else { afFlags.dwAdd = ARF_DELETED_OFFLINE; afFlags.dwRemove = 0; } } for (i = 0; ; i++) { if (pList != NULL) { if (i >= pList->cMsgs) break; Message.idMessage = pList->prgidMsg[i]; hr = pFolder->FindRecord(IINDEX_PRIMARY, COLUMNS_ALL, &Message, NULL); if (FAILED(hr)) break; else if (hr != DB_S_FOUND) continue; } else { hr = pFolder->QueryRowset(hRowset, 1, (LPVOID *)&Message, NULL); if (S_FALSE == hr) { hr = S_OK; break; } else if (FAILED(hr)) { break; } } idMessage = Message.idMessage; dwFlags = Message.dwFlags; if (pFlags != NULL) { if (pFlags->dwRemove != 0) Message.dwFlags &= ~pFlags->dwRemove; if (pFlags->dwAdd != 0) Message.dwFlags |= pFlags->dwAdd; } pMsg = NULL; hr = pFolder->OpenMessage(idMessage, NOFLAGS, &pMsg, NULL); if (hr == STORE_E_NOBODY) { // just create the header in the destination folder Assert(pMsg == NULL); hr = pFolderDest->GenerateId((LPDWORD)&Message.idMessage); if (SUCCEEDED(hr)) hr = pFolderDest->InsertRecord(&Message); } else if (SUCCEEDED(hr)) { // create the whole message in the destination folder Assert(pMsg != NULL); hr = pFolderDest->SaveMessage(&Message.idMessage, SAVE_MESSAGE_GENID, Message.dwFlags, 0, pMsg, NULL); pMsg->Release(); } else { Assert(pMsg == NULL); } if (FAILED(hr)) break; hr = _FindExistingOperation(idServer, idFolder, idMessage, SYNC_CREATE_MSG, SYNC_COPY_MSG | SYNC_MOVE_MSG, &info); if (FAILED(hr)) break; if (hr == S_OK) { if (fMove) { // delete source msg because we're moving a msg which doesn't exist on the server // and then move the previous operation to the destination folder hr = pFolder->DeleteMessages(DELETE_MESSAGE_NOTRASHCAN | DELETE_MESSAGE_NOPROMPT, &list, NULL, NULL); if (SUCCEEDED(hr)) { if (info.tyOperation == SYNC_CREATE_MSG) { info.idFolder = idFolderDest; info.idMessage = Message.idMessage; } else { info.idFolderDest = idFolderDest; info.idMessageDest = Message.idMessage; } hr = m_pDB->UpdateRecord(&info); // foo } } else { if (info.tyOperation == SYNC_CREATE_MSG) { // we can't copy this message because it doesn't exist on the server, // so we'll add another create operation for the destination message ZeroMemory(&infoT, sizeof(SYNCOPINFO)); hr = m_pDB->GenerateId((LPDWORD)&infoT.idOperation); if (SUCCEEDED(hr)) { infoT.idServer = idServer; infoT.idFolder = idFolderDest; infoT.idMessage = Message.idMessage; infoT.tyOperation = SYNC_CREATE_MSG; // infoT.dwFlags // info.dwAdd // info.dwRemove hr = m_pDB->InsertRecord(&infoT); } } else if (info.tyOperation == SYNC_COPY_MSG) { fCopyToMove = FALSE; // if there is an earlier operation that will result in the source msg // being deleted, then we'll find that and remove it (delete) or change // it (move), and then we'll do a move instead of a copy hr = _FindExistingOperation(info.idServer, info.idFolder, info.idMessage, SYNC_DELETE_MSG | SYNC_MOVE_MSG, 0, &infoT); if (hr == S_OK) { if (infoT.tyOperation == SYNC_DELETE_MSG) { hr = m_pDB->DeleteRecord(&infoT); } else { Assert(infoT.tyOperation == SYNC_MOVE_MSG); infoT.tyOperation = SYNC_COPY_MSG; hr = m_pDB->UpdateRecord(&infoT); } fCopyToMove = TRUE; m_pDB->FreeRecord(&infoT); } if (SUCCEEDED(hr)) { hr = m_pDB->GenerateId((LPDWORD)&info.idOperation); if (SUCCEEDED(hr)) { info.idFolderDest = idFolderDest; info.idMessageDest = Message.idMessage; if (fCopyToMove) info.tyOperation = SYNC_MOVE_MSG; hr = m_pDB->InsertRecord(&info); } } } else { Assert(info.tyOperation == SYNC_MOVE_MSG); // instead of doing a move then a copy which wouldn't work so good, // we'll have the current copy become a move and the earlier move will become a copy infoT = info; hr = m_pDB->GenerateId((LPDWORD)&infoT.idOperation); if (SUCCEEDED(hr)) { infoT.idFolderDest = idFolderDest; infoT.idMessageDest = Message.idMessage; hr = m_pDB->InsertRecord(&infoT); if (SUCCEEDED(hr)) { info.tyOperation = SYNC_COPY_MSG; hr = m_pDB->UpdateRecord(&info); } } } } m_pDB->FreeRecord(&info); if (FAILED(hr)) { break; } else { pFolder->FreeRecord(&Message); continue; } } ZeroMemory(&info, sizeof(SYNCOPINFO)); hr = m_pDB->GenerateId((LPDWORD)&info.idOperation); if (FAILED(hr)) break; info.idServer = idServer; info.idFolder = idFolder; info.idMessage = idMessage; info.tyOperation = (fMove && !fImap) ? SYNC_MOVE_MSG : SYNC_COPY_MSG; // info.dwFlags if (pFlags != NULL) { info.dwAdd = (~dwFlags & pFlags->dwAdd); info.dwRemove = (dwFlags & pFlags->dwRemove); } info.idFolderDest = idFolderDest; info.idMessageDest = Message.idMessage; hr = m_pDB->InsertRecord(&info); // TODO: if this fails, we should probably blow away the new msg we just created... if (FAILED(hr)) break; if (fMove) { if (fImap) { // put this msg in the endangered species list hr = _SetMessageFlags(pFolder, idServer, idFolder, idMessage, dwFlags, &afFlags); if (FAILED(hr)) break; } else { // hide this msg because it is now deleted pFolder->SetMessageFlags(&list, &afFlags, NULL, NULL); } } pFolder->FreeRecord(&Message); } pFolder->FreeRecord(&Message); if (hRowset != NULL) pFolder->CloseRowset(&hRowset); return(hr); } HRESULT COfflineSync::Initialize() { HRESULT hr; BOOL fReset; TABLEINDEX Index; CHAR szDirectory[MAX_PATH]; CHAR szFilePath[MAX_PATH]; SYNCOPUSERDATA UserData={0}; Assert(g_pStore != NULL); hr = g_pStore->GetDirectory(szDirectory, ARRAYSIZE(szDirectory)); if (FAILED(hr)) return(hr); hr = MakeFilePath(szDirectory, c_szOfflineFile, c_szEmpty, szFilePath, ARRAYSIZE(szFilePath)); if (FAILED(hr)) return(hr); hr = g_pDBSession->OpenDatabase(szFilePath, NOFLAGS, &g_SyncOpTableSchema, NULL, &m_pDB); if (FAILED(hr)) return(hr); fReset = FALSE; // Create the idServer / idFolder Index if (FAILED(m_pDB->GetIndexInfo(IINDEX_ALL, NULL, &Index))) fReset = TRUE; // If still noreset, see of indexes are the same else if (S_FALSE == CompareTableIndexes(&Index, &g_OpFolderIdIndex)) fReset = TRUE; // Change the Index if (fReset) { // Create the idParent / FolderName Index hr = m_pDB->ModifyIndex(IINDEX_ALL, NULL, &g_OpFolderIdIndex); if (FAILED(hr)) return(hr); } hr = m_pDB->GetUserData(&UserData, sizeof(SYNCOPUSERDATA)); if (SUCCEEDED(hr)) { if (!UserData.fInitialized) { UserData.fInitialized = TRUE; hr = m_pDB->SetUserData(&UserData, sizeof(SYNCOPUSERDATA)); } } return(hr); } HRESULT COfflineSync::DoPlayback(HWND hwnd, FOLDERID *pId, DWORD cId, FOLDERID idFolderSel) { HRESULT hr; DWORD cOps; COfflinePlayback *pPlayback; Assert(pId != NULL); Assert(cId > 0); hr = m_pDB->GetRecordCount(IINDEX_PRIMARY, &cOps); if (SUCCEEDED(hr) && cOps > 0) { pPlayback = new COfflinePlayback; if (pPlayback == NULL) return(E_OUTOFMEMORY); hr = pPlayback->DoPlayback(hwnd, m_pDB, pId, cId, idFolderSel); pPlayback->Release(); } return(hr); } HRESULT COfflineSync::_FindExistingOperation(FOLDERID idServer, FOLDERID idFolder, MESSAGEID idMessage, DWORD dwFlagsSrc, DWORD dwFlagsDest, LPSYNCOPINFO pInfo) { ROWORDINAL iRow; HRESULT hr; HROWSET hRowset = NULL; Assert(pInfo != NULL); Assert(dwFlagsSrc != 0 || dwFlagsDest != 0); ZeroMemory(pInfo, sizeof(SYNCOPINFO)); pInfo->idServer = idServer; hr = m_pDB->FindRecord(IINDEX_ALL, 1, pInfo, &iRow); if (hr != DB_S_FOUND) return(S_FALSE); m_pDB->FreeRecord(pInfo); hr = m_pDB->CreateRowset(IINDEX_ALL, NOFLAGS, &hRowset); if (FAILED(hr)) return(hr); hr = m_pDB->SeekRowset(hRowset, SEEK_ROWSET_BEGIN, iRow - 1, NULL); if (SUCCEEDED(hr)) { while (TRUE) { hr = m_pDB->QueryRowset(hRowset, 1, (LPVOID *)pInfo, NULL); if (S_FALSE == hr) { break; } else if (FAILED(hr)) { break; } if (pInfo->idServer != idServer) { hr = S_FALSE; m_pDB->FreeRecord(pInfo); break; } if (dwFlagsSrc != 0) { if (pInfo->idFolder == idFolder && pInfo->idMessage == idMessage) { if (!!(dwFlagsSrc & pInfo->tyOperation)) break; } } if (dwFlagsDest != 0) { if (pInfo->idFolderDest == idFolder && pInfo->idMessageDest == idMessage) { if (!!(dwFlagsDest & pInfo->tyOperation)) break; } } m_pDB->FreeRecord(pInfo); } } m_pDB->CloseRowset(&hRowset); return(hr); }