/* Miranda NG: the free IM client for Microsoft* Windows* Copyright (ñ) 2012-15 Miranda NG project (http://miranda-ng.org) 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. */ #include "commonheaders.h" struct DBEventSortingKey { DWORD dwContactId, ts, dwEventId; }; STDMETHODIMP_(LONG) CDbxMdb::GetEventCount(MCONTACT contactID) { mir_cslock lck(m_csDbAccess); if (contactID == NULL) return m_header.eventCount; DBCachedContact *cc = m_cache->GetCachedContact(contactID); return (cc == NULL) ? 0 : cc->dwDriverData; } STDMETHODIMP_(MEVENT) CDbxMdb::AddEvent(MCONTACT contactID, DBEVENTINFO *dbei) { if (dbei == NULL || dbei->cbSize != sizeof(DBEVENTINFO)) return 0; if (dbei->timestamp == 0) return 0; DBEvent dbe; dbe.signature = DBEVENT_SIGNATURE; dbe.contactID = contactID; // store native or subcontact's id dbe.ofsModuleName = GetModuleNameOfs(dbei->szModule); dbe.timestamp = dbei->timestamp; dbe.flags = dbei->flags; dbe.wEventType = dbei->eventType; dbe.cbBlob = dbei->cbBlob; BYTE *pBlob = dbei->pBlob; MCONTACT contactNotifyID = contactID; DBCachedContact *cc = NULL, *ccSub = NULL; if (contactID != 0) { if ((cc = m_cache->GetCachedContact(contactID)) == NULL) return 0; if (cc->IsSub()) { ccSub = cc; // set default sub to the event's source if (!(dbei->flags & DBEF_SENT)) db_mc_setDefault(cc->parentID, contactID, false); contactID = cc->parentID; // and add an event to a metahistory if (db_mc_isEnabled()) contactNotifyID = contactID; } } if (NotifyEventHooks(hEventFilterAddedEvent, contactNotifyID, (LPARAM)dbei)) return NULL; mir_ptr pCryptBlob; if (m_bEncrypted) { size_t len; BYTE *pResult = m_crypto->encodeBuffer(pBlob, dbe.cbBlob, &len); if (pResult != NULL) { pCryptBlob = pBlob = pResult; dbe.cbBlob = (DWORD)len; dbe.flags |= DBEF_ENCRYPTED; } } mir_cslockfull lck(m_csDbAccess); DWORD dwEventId = (contactID == NULL) ? ++m_header.eventCount : ++m_dwMaxEventId; txn_ptr txn(m_pMdbEnv); for (;; Remap()) { MDB_val key = { sizeof(int), &dwEventId }, data = { sizeof(DBEvent) + dbe.cbBlob, NULL }; if (mdb_put(txn, m_dbEvents, &key, &data, MDB_RESERVE) != MDB_SUCCESS) return 0; BYTE *pDest = (BYTE*)data.mv_data; memcpy(pDest, &dbe, sizeof(DBEvent)); memcpy(pDest + sizeof(DBEvent), pBlob, dbe.cbBlob); // add a sorting key DBEventSortingKey key2 = { contactID, dbe.timestamp, dwEventId }; key.mv_size = sizeof(key2); key.mv_data = &key2; data.mv_size = 1; data.mv_data = ""; if (mdb_put(txn, m_dbEventsSort, &key, &data, 0) != MDB_SUCCESS) return 0; if (contactID != NULL) { DBContact dbc = { DBCONTACT_SIGNATURE, ++cc->dwDriverData }; MDB_val keyc = { sizeof(int), &contactID }, datac = { sizeof(DBContact), &dbc }; mdb_put(txn, m_dbContacts, &keyc, &datac, 0); // insert an event into a sub's history too if (ccSub != NULL) { key2.dwContactId = ccSub->contactID; mdb_put(txn, m_dbEventsSort, &key, &data, 0); dbc.eventCount = ++ccSub->dwDriverData; keyc.mv_data = &ccSub->contactID; mdb_put(txn, m_dbContacts, &keyc, &datac, 0); } } else { DWORD keyVal = 1; MDB_val key = { sizeof(DWORD), &keyVal }; data.mv_data = &m_header; data.mv_size = sizeof(m_header); mdb_put(txn, m_dbGlobal, &key, &data, 0); } if (txn.commit()) break; } lck.unlock(); // Notify only in safe mode or on really new events NotifyEventHooks(hEventAddedEvent, contactNotifyID, dwEventId); return dwEventId; } STDMETHODIMP_(BOOL) CDbxMdb::DeleteEvent(MCONTACT contactID, MEVENT hDbEvent) { mir_cslockfull lck(m_csDbAccess); txn_ptr txn(m_pMdbEnv); for (;; Remap()) { MDB_val key = { sizeof(MEVENT), &hDbEvent }, data; if (mdb_get(txn, m_dbEvents, &key, &data) != MDB_SUCCESS) return 1; DBEvent *dbe = (DBEvent*)data.mv_data; DWORD dwSavedContact = dbe->contactID; DBEventSortingKey key2 = { contactID, dbe->timestamp, hDbEvent }; mdb_del(txn, m_dbEvents, &key, &data); // remove a sorting key key.mv_size = sizeof(key2); key.mv_data = &key2; mdb_del(txn, m_dbEventsSort, &key, &data); // remove a sub's history entry too if (contactID != dwSavedContact) { key2.dwContactId = dwSavedContact; mdb_del(txn, m_dbEventsSort, &key, &data); } if (txn.commit()) break; } lck.unlock(); // call notifier while outside mutex NotifyEventHooks(hEventDeletedEvent, contactID, hDbEvent); // get back in lck.lock(); return 0; } STDMETHODIMP_(LONG) CDbxMdb::GetBlobSize(MEVENT hDbEvent) { mir_cslock lck(m_csDbAccess); txn_ptr txn(m_pMdbEnv, true); MDB_val key = { sizeof(MEVENT), &hDbEvent }, data; if (mdb_get(txn, m_dbEvents, &key, &data) != MDB_SUCCESS) return 0; DBEvent *dbe = (DBEvent*)data.mv_data; return (dbe->signature == DBEVENT_SIGNATURE) ? dbe->cbBlob : 0; } STDMETHODIMP_(BOOL) CDbxMdb::GetEvent(MEVENT hDbEvent, DBEVENTINFO *dbei) { if (dbei == NULL || dbei->cbSize != sizeof(DBEVENTINFO)) return 1; if (dbei->cbBlob > 0 && dbei->pBlob == NULL) { dbei->cbBlob = 0; return 1; } mir_cslock lck(m_csDbAccess); txn_ptr txn(m_pMdbEnv, true); MDB_val key = { sizeof(MEVENT), &hDbEvent }, data; if (mdb_get(txn, m_dbEvents, &key, &data) != MDB_SUCCESS) return 1; DBEvent *dbe = (DBEvent*)data.mv_data; if (dbe->signature != DBEVENT_SIGNATURE) return 1; dbei->szModule = GetModuleNameByOfs(dbe->ofsModuleName); dbei->timestamp = dbe->timestamp; dbei->flags = dbe->flags; dbei->eventType = dbe->wEventType; int bytesToCopy = (dbei->cbBlob < dbe->cbBlob) ? dbei->cbBlob : dbe->cbBlob; dbei->cbBlob = dbe->cbBlob; if (bytesToCopy && dbei->pBlob) { BYTE *pSrc = (BYTE*)data.mv_data + sizeof(DBEvent); if (dbe->flags & DBEF_ENCRYPTED) { dbei->flags &= ~DBEF_ENCRYPTED; size_t len; BYTE* pBlob = (BYTE*)m_crypto->decodeBuffer(pSrc, dbe->cbBlob, &len); if (pBlob == NULL) return 1; memcpy(dbei->pBlob, pBlob, bytesToCopy); if (bytesToCopy > (int)len) memset(dbei->pBlob + len, 0, bytesToCopy - len); mir_free(pBlob); } else memcpy(dbei->pBlob, pSrc, bytesToCopy); } return 0; } STDMETHODIMP_(BOOL) CDbxMdb::MarkEventRead(MCONTACT contactID, MEVENT hDbEvent) { DBCachedContact *cc; if (contactID) { if ((cc = m_cache->GetCachedContact(contactID)) == NULL) return -1; if (cc->IsSub()) if ((cc = m_cache->GetCachedContact(cc->parentID)) == NULL) return -1; } else cc = NULL; mir_cslockfull lck(m_csDbAccess); txn_ptr txn(m_pMdbEnv); MDB_val key = { sizeof(MEVENT), &hDbEvent }, data; if (mdb_get(txn, m_dbEvents, &key, &data) != MDB_SUCCESS) return 0; DBEvent *dbe = (DBEvent*)data.mv_data; if (dbe->signature != DBEVENT_SIGNATURE) return -1; if (dbe->markedRead()) return dbe->flags; dbe->flags |= DBEF_READ; mdb_put(txn, m_dbEvents, &key, &data, 0); txn.commit(); lck.unlock(); NotifyEventHooks(hEventMarkedRead, contactID, (LPARAM)hDbEvent); return dbe->flags; } STDMETHODIMP_(MCONTACT) CDbxMdb::GetEventContact(MEVENT hDbEvent) { mir_cslock lck(m_csDbAccess); txn_ptr txn(m_pMdbEnv, true); MDB_val key = { sizeof(MEVENT), &hDbEvent }, data; if (mdb_get(txn, m_dbEvents, &key, &data) != MDB_SUCCESS) return 0; DBEvent *dbe = (DBEvent*)data.mv_data; return (dbe->signature == DBEVENT_SIGNATURE) ? dbe->contactID: INVALID_CONTACT_ID; } STDMETHODIMP_(MEVENT) CDbxMdb::FindFirstEvent(MCONTACT contactID) { DBEventSortingKey keyVal = { contactID, 0, 0 }; MDB_val key = { sizeof(keyVal), &keyVal }, data; mir_cslock lck(m_csDbAccess); txn_ptr txn(m_pMdbEnv, true); cursor_ptr cursor(txn, m_dbEventsSort); if (mdb_cursor_get(cursor, &key, &data, MDB_NEXT) != MDB_SUCCESS) return 0; DBEventSortingKey *pKey = (DBEventSortingKey*)key.mv_data; return (pKey->dwContactId == contactID) ? pKey->dwEventId : 0; } STDMETHODIMP_(MEVENT) CDbxMdb::FindFirstUnreadEvent(MCONTACT contactID) { mir_cslock lck(m_csDbAccess); return NULL; } STDMETHODIMP_(MEVENT) CDbxMdb::FindLastEvent(MCONTACT contactID) { DBEventSortingKey keyVal = { contactID, 0, 0 }; MDB_val key = { sizeof(keyVal), &keyVal }, data; mir_cslock lck(m_csDbAccess); txn_ptr txn(m_pMdbEnv, true); cursor_ptr cursor(txn, m_dbEventsSort); if (mdb_cursor_get(cursor, &key, &data, MDB_PREV) != MDB_SUCCESS) return 0; DBEventSortingKey *pKey = (DBEventSortingKey*)key.mv_data; return (pKey->dwContactId == contactID) ? pKey->dwEventId : 0; } STDMETHODIMP_(MEVENT) CDbxMdb::FindNextEvent(MCONTACT contactID, MEVENT hDbEvent) { MDB_val key = { sizeof(MEVENT), &hDbEvent }, data; mir_cslock lck(m_csDbAccess); txn_ptr txn(m_pMdbEnv, true); if (mdb_get(txn, m_dbEvents, &key, &data) != MDB_SUCCESS) return 0; DBEvent *dbe = (DBEvent*)data.mv_data; DBEventSortingKey keyVal = { contactID, dbe->timestamp, hDbEvent+1 }; key.mv_size = sizeof(keyVal); key.mv_data = &keyVal; cursor_ptr cursor(txn, m_dbEventsSort); if (mdb_cursor_get(cursor, &key, &data, MDB_SET_KEY) != MDB_SUCCESS) if (mdb_cursor_get(cursor, &key, &data, MDB_NEXT) != MDB_SUCCESS) return 0; DBEventSortingKey *pKey = (DBEventSortingKey*)key.mv_data; return (pKey->dwContactId == contactID) ? pKey->dwEventId : 0; } STDMETHODIMP_(MEVENT) CDbxMdb::FindPrevEvent(MCONTACT contactID, MEVENT hDbEvent) { MDB_val key = { sizeof(MEVENT), &hDbEvent }, data; mir_cslock lck(m_csDbAccess); txn_ptr txn(m_pMdbEnv, true); if (mdb_get(txn, m_dbEvents, &key, &data) != MDB_SUCCESS) return 0; DBEvent *dbe = (DBEvent*)data.mv_data; DBEventSortingKey keyVal = { contactID, dbe->timestamp, hDbEvent - 1 }; key.mv_size = sizeof(keyVal); key.mv_data = &keyVal; cursor_ptr cursor(txn, m_dbEventsSort); if (mdb_cursor_get(cursor, &key, &data, MDB_SET_KEY) != MDB_SUCCESS) if (mdb_cursor_get(cursor, &key, &data, MDB_PREV) != MDB_SUCCESS) return 0; DBEventSortingKey *pKey = (DBEventSortingKey*)key.mv_data; return (pKey->dwContactId == contactID) ? pKey->dwEventId : 0; } ///////////////////////////////////////////////////////////////////////////////////////// // low-level history cleaner int CDbxMdb::WipeContactHistory(DBContact *dbc) { // drop subContact's history if any return 0; }