/* Miranda NG: the free IM client for Microsoft* Windows* Copyright (C) 2012-24 Miranda NG team (https://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 "stdafx.h" int CDbxMDBX::GetEventCount(MCONTACT contactID) { if (!contactID) return m_ccDummy.dbc.dwEventCount; DBCachedContact *cc = m_cache->GetCachedContact(contactID); return (cc == nullptr) ? 0 : cc->dbc.dwEventCount; } ///////////////////////////////////////////////////////////////////////////////////////// MEVENT CDbxMDBX::AddEvent(MCONTACT contactID, const DBEVENTINFO *dbei) { if (dbei == nullptr) return 0; if (dbei->timestamp == 0) return 0; MEVENT dwEventId = InterlockedIncrement(&m_dwMaxEventId); if (!EditEvent(contactID, dwEventId, dbei, true)) return 0; return dwEventId; } ///////////////////////////////////////////////////////////////////////////////////////// BOOL CDbxMDBX::DeleteEvent(MEVENT hDbEvent) { DBEvent dbe; char *szId = nullptr; { MDBX_val key = { &hDbEvent, sizeof(MEVENT) }, data; if (mdbx_get(StartTran(), m_dbEvents, &key, &data) != MDBX_SUCCESS) return 1; dbe = *(DBEvent*)data.iov_base; if (dbe.flags & DBEF_HAS_ID) { char *src = (char *)data.iov_base + sizeof(dbe) + dbe.cbBlob + 1; szId = NEWSTR_ALLOCA(src); } } DBCachedContact *cc2, *cc = (dbe.dwContactID != 0) ? m_cache->GetCachedContact(dbe.dwContactID) : &m_ccDummy; if (cc == nullptr || cc->dbc.dwEventCount == 0) return 1; if (!CheckEvent(cc, &dbe, cc2)) return 1; { txn_ptr trnlck(this); DBEventSortingKey key2 = { dbe.dwContactID, hDbEvent, dbe.timestamp }; MDBX_val key = { &key2, sizeof(key2) }, data; if (mdbx_del(trnlck, m_dbEventsSort, &key, nullptr) != MDBX_SUCCESS) return 1; if (dbe.dwContactID != 0) { cc->dbc.dwEventCount--; if (cc->dbc.evFirstUnread == hDbEvent) FindNextUnread(trnlck, cc, key2); MDBX_val keyc = { &dbe.dwContactID, sizeof(MCONTACT) }; data.iov_len = sizeof(DBContact); data.iov_base = &cc->dbc; if (mdbx_put(trnlck, m_dbContacts, &keyc, &data, MDBX_UPSERT) != MDBX_SUCCESS) return 1; } else { m_ccDummy.dbc.dwEventCount--; if (m_ccDummy.dbc.evFirstUnread == hDbEvent) FindNextUnread(trnlck, &m_ccDummy, key2); uint32_t keyVal = 2; MDBX_val keyc = { &keyVal, sizeof(keyVal) }, datac = { &m_ccDummy.dbc, sizeof(m_ccDummy.dbc) }; if (mdbx_put(trnlck, m_dbGlobal, &keyc, &datac, MDBX_UPSERT) != MDBX_SUCCESS) return 0; } if (cc2) { key2.hContact = cc2->contactID; if (mdbx_del(trnlck, m_dbEventsSort, &key, nullptr) != MDBX_SUCCESS) return 1; key.iov_len = sizeof(MCONTACT); key.iov_base = &dbe.dwContactID; cc2->dbc.dwEventCount--; if (cc2->dbc.evFirstUnread == hDbEvent) FindNextUnread(trnlck, cc2, key2); MDBX_val keyc = { &cc2->contactID, sizeof(MCONTACT) }; data.iov_len = sizeof(DBContact); data.iov_base = &cc2->dbc; if (mdbx_put(trnlck, m_dbContacts, &keyc, &data, MDBX_UPSERT) != MDBX_SUCCESS) return 1; } if (szId) { DBEventIdKey keyId; keyId.iModuleId = dbe.iModuleId; strncpy_s(keyId.szEventId, szId, _TRUNCATE); MDBX_val keyid = { &keyId, sizeof(MEVENT) + strlen(keyId.szEventId) + 1 }; mdbx_del(trnlck, m_dbEventIds, &keyid, nullptr); } // remove an event key.iov_len = sizeof(MEVENT); key.iov_base = &hDbEvent; if (mdbx_del(trnlck, m_dbEvents, &key, nullptr) != MDBX_SUCCESS) return 1; } DBFlush(); NotifyEventHooks(g_hevEventDeleted, dbe.dwContactID, hDbEvent); return 0; } ///////////////////////////////////////////////////////////////////////////////////////// BOOL CDbxMDBX::EditEvent(MEVENT hDbEvent, const DBEVENTINFO *dbei) { if (dbei == nullptr) return 1; if (dbei->timestamp == 0) return 1; DBEVENTINFO tmp = *dbei; MDBX_val key = { &hDbEvent, sizeof(MEVENT) }, data; if (mdbx_get(StartTran(), m_dbEvents, &key, &data) != MDBX_SUCCESS) return 1; DBEvent *dbe = (DBEvent*)data.iov_base; tmp.timestamp = dbe->timestamp; return !EditEvent(dbe->dwContactID, hDbEvent, &tmp, false); } bool CDbxMDBX::EditEvent(MCONTACT hContact, MEVENT hDbEvent, const DBEVENTINFO *dbei, bool bNew) { DBEvent dbe; dbe.dwContactID = hContact; // store native or subcontact's id dbe.iModuleId = GetModuleID(dbei->szModule); MCONTACT contactNotifyID = hContact; DBCachedContact *cc, *ccSub = nullptr; if (hContact != 0) { if ((cc = m_cache->GetCachedContact(hContact)) == nullptr) return false; if (cc->IsSub()) { ccSub = cc; if ((cc = m_cache->GetCachedContact(cc->parentID)) == nullptr) return false; // set default sub to the event's source if (!(dbei->flags & DBEF_SENT)) db_mc_setDefault(cc->contactID, hContact, false); hContact = cc->contactID; // and add an event to a metahistory if (db_mc_isEnabled()) contactNotifyID = hContact; } } else cc = &m_ccDummy; if (bNew && m_safetyMode) if (NotifyEventHooks(g_hevEventFiltered, contactNotifyID, (LPARAM)dbei)) return false; dbe.timestamp = dbei->timestamp; dbe.flags = dbei->flags; dbe.wEventType = dbei->eventType; dbe.cbBlob = dbei->cbBlob; char *pBlob = dbei->pBlob; mir_ptr pCryptBlob; if (m_bEncrypted) { size_t len; char *pResult = (char*)m_crypto->encodeBuffer(pBlob, dbe.cbBlob, &len); if (pResult != nullptr) { pCryptBlob = pBlob = pResult; dbe.cbBlob = (uint16_t)len; dbe.flags |= DBEF_ENCRYPTED; } } size_t cbSrvId = mir_strlen(dbei->szId); if (cbSrvId > 0) { cbSrvId++; dbe.flags |= DBEF_HAS_ID; } uint8_t *recBuf = (uint8_t*)_alloca(sizeof(dbe) + dbe.cbBlob + cbSrvId + 2), *p = recBuf; memcpy(p, &dbe, sizeof(dbe)); p += sizeof(dbe); memcpy(p, pBlob, dbe.cbBlob); p += dbe.cbBlob; if (*p != 0) *p++ = 0; if (cbSrvId) { memcpy(p, dbei->szId, cbSrvId); p += cbSrvId; } { txn_ptr trnlck(this); MDBX_val key = { &hDbEvent, sizeof(MEVENT) }, data = { recBuf, size_t(p - recBuf) }; if (mdbx_put(trnlck, m_dbEvents, &key, &data, MDBX_UPSERT) != MDBX_SUCCESS) return false; // add a sorting key DBEventSortingKey key2 = { hContact, hDbEvent, dbe.timestamp }; key.iov_len = sizeof(key2); key.iov_base = &key2; data.iov_len = 1; data.iov_base = (char*)(""); if (mdbx_put(trnlck, m_dbEventsSort, &key, &data, MDBX_UPSERT) != MDBX_SUCCESS) return false; cc->Advance(hDbEvent, dbe); if (hContact != 0) { MDBX_val keyc = { &hContact, sizeof(MCONTACT) }, datac = { &cc->dbc, sizeof(DBContact) }; if (mdbx_put(trnlck, m_dbContacts, &keyc, &datac, MDBX_UPSERT) != MDBX_SUCCESS) return false; // insert an event into a sub's history too if (ccSub != nullptr) { key2.hContact = ccSub->contactID; if (mdbx_put(trnlck, m_dbEventsSort, &key, &data, MDBX_UPSERT) != MDBX_SUCCESS) return false; ccSub->Advance(hDbEvent, dbe); datac.iov_base = &ccSub->dbc; keyc.iov_base = &ccSub->contactID; if (mdbx_put(trnlck, m_dbContacts, &keyc, &datac, MDBX_UPSERT) != MDBX_SUCCESS) return false; } } else { uint32_t keyVal = 2; MDBX_val keyc = { &keyVal, sizeof(keyVal) }, datac = { &m_ccDummy.dbc, sizeof(m_ccDummy.dbc) }; if (mdbx_put(trnlck, m_dbGlobal, &keyc, &datac, MDBX_UPSERT) != MDBX_SUCCESS) return false; } if (dbei->szId) { DBEventIdKey keyId; keyId.iModuleId = dbe.iModuleId; strncpy_s(keyId.szEventId, dbei->szId, _TRUNCATE); MDBX_val keyid = { &keyId, sizeof(MEVENT) + strlen(keyId.szEventId) + 1 }, dataid = { &hDbEvent, sizeof(hDbEvent) }; if (mdbx_put(trnlck, m_dbEventIds, &keyid, &dataid, MDBX_UPSERT) != MDBX_SUCCESS) return false; } } DBFlush(); // Notify only in safe mode or on really new events if (m_safetyMode && !(dbei->flags & DBEF_TEMPORARY)) NotifyEventHooks(bNew ? g_hevEventAdded : g_hevEventEdited, contactNotifyID, hDbEvent); return true; } ///////////////////////////////////////////////////////////////////////////////////////// int CDbxMDBX::GetBlobSize(MEVENT hDbEvent) { MDBX_val key = { &hDbEvent, sizeof(MEVENT) }, data; if (mdbx_get(StartTran(), m_dbEvents, &key, &data) != MDBX_SUCCESS) return -1; return ((const DBEvent*)data.iov_base)->cbBlob; } ///////////////////////////////////////////////////////////////////////////////////////// BOOL CDbxMDBX::GetEvent(MEVENT hDbEvent, DBEVENTINFO *dbei) { if (dbei == nullptr) return 1; if (dbei->cbBlob > 0 && dbei->pBlob == nullptr) { dbei->cbBlob = 0; return 1; } const DBEvent *dbe; { MDBX_val key = { &hDbEvent, sizeof(MEVENT) }, data; if (mdbx_get(StartTran(), m_dbEvents, &key, &data) != MDBX_SUCCESS) return 1; dbe = (const DBEvent*)data.iov_base; } dbei->szModule = GetModuleName(dbe->iModuleId); dbei->timestamp = dbe->timestamp; dbei->flags = dbe->flags; dbei->eventType = dbe->wEventType; int cbBlob = dbe->cbBlob; size_t bytesToCopy = cbBlob; if (dbei->cbBlob == -1) dbei->pBlob = (char *)mir_calloc(cbBlob + 2); else if (dbei->cbBlob < cbBlob) bytesToCopy = dbei->cbBlob; dbei->cbBlob = (uint32_t)cbBlob; if (bytesToCopy && dbei->pBlob) { uint8_t *pSrc = (uint8_t*)dbe + sizeof(DBEvent); if (dbe->flags & DBEF_ENCRYPTED) { dbei->flags &= ~DBEF_ENCRYPTED; size_t len; uint8_t* pBlob = (uint8_t*)m_crypto->decodeBuffer(pSrc, dbe->cbBlob, &len); if (pBlob == nullptr) return 1; memcpy(dbei->pBlob, pBlob, bytesToCopy); if (bytesToCopy > len) memset(dbei->pBlob + len, 0, bytesToCopy - len); mir_free(pBlob); } else memcpy(dbei->pBlob, pSrc, bytesToCopy); if (dbei->flags & DBEF_HAS_ID) dbei->szId = (char *)pSrc + dbei->cbBlob + 1; } return 0; } ///////////////////////////////////////////////////////////////////////////////////////// void CDbxMDBX::FindNextUnread(const txn_ptr &txn, DBCachedContact *cc, DBEventSortingKey &key2) { cursor_ptr cursor(txn, m_dbEventsSort); MDBX_val key = { &key2, sizeof(key2) }, data; for (int res = mdbx_cursor_get(cursor, &key, &data, MDBX_SET_KEY); res == MDBX_SUCCESS; res = mdbx_cursor_get(cursor, &key, &data, MDBX_NEXT)) { const DBEvent *dbe = (const DBEvent*)data.iov_base; if (dbe->dwContactID != cc->contactID) break; if (!dbe->markedRead()) { cc->dbc.evFirstUnread = key2.hEvent; cc->dbc.tsFirstUnread = key2.ts; return; } } cc->dbc.evFirstUnread = cc->dbc.tsFirstUnread = 0; } bool CDbxMDBX::CheckEvent(DBCachedContact *cc, const DBEvent *cdbe, DBCachedContact *&cc2) { // if cc is a sub, cdbe should contain its id if (cc->IsSub()) { if (cc->contactID != cdbe->dwContactID) return false; cc2 = m_cache->GetCachedContact(cc->parentID); return true; } // if cc is a meta, cdbe should be its sub if (cc->IsMeta()) { cc2 = m_cache->GetCachedContact(cdbe->dwContactID); if (cc2 == nullptr) return false; return (cc2->parentID == cc->contactID); } // neither a sub, nor a meta. a usual contact. Contact IDs must match one another cc2 = nullptr; return cc->contactID == cdbe->dwContactID; } ///////////////////////////////////////////////////////////////////////////////////////// BOOL CDbxMDBX::MarkEventRead(MCONTACT contactID, MEVENT hDbEvent) { if (hDbEvent == 0) return -1; DBCachedContact *cc = m_cache->GetCachedContact(contactID), *cc2 = nullptr; if (cc == nullptr) return -1; uint32_t wRetVal = -1; { txn_ptr trnlck(this); MDBX_val key = { &hDbEvent, sizeof(MEVENT) }, data; if (mdbx_get(trnlck, m_dbEvents, &key, &data) != MDBX_SUCCESS) return -1; DBEvent cdbe = *(DBEvent*)data.iov_base; if (cdbe.markedRead()) return cdbe.flags; // check subs & metas if (!CheckEvent(cc, &cdbe, cc2)) return -2; void *recBuf = _alloca(data.iov_len); memcpy(recBuf, data.iov_base, data.iov_len); data.iov_base = recBuf; DBEvent *pNewEvent = (DBEvent*)data.iov_base; wRetVal = (pNewEvent->flags |= DBEF_READ); if (mdbx_put(trnlck, m_dbEvents, &key, &data, MDBX_UPSERT) != MDBX_SUCCESS) return -1; DBEventSortingKey keyVal = { contactID, hDbEvent, cdbe.timestamp }; FindNextUnread(trnlck, cc, keyVal); key.iov_len = sizeof(MCONTACT); key.iov_base = &contactID; data.iov_base = &cc->dbc; data.iov_len = sizeof(cc->dbc); if (mdbx_put(trnlck, m_dbContacts, &key, &data, MDBX_UPSERT) != MDBX_SUCCESS) return -1; if (cc2 != nullptr) { DBEventSortingKey keyVal2 = { cc2->contactID, hDbEvent, cdbe.timestamp }; FindNextUnread(trnlck, cc2, keyVal2); key.iov_len = sizeof(MCONTACT); key.iov_base = &cc2->contactID; data.iov_base = &cc2->dbc; data.iov_len = sizeof(cc2->dbc); if (mdbx_put(trnlck, m_dbContacts, &key, &data, MDBX_UPSERT) != MDBX_SUCCESS) return -1; } } DBFlush(); NotifyEventHooks(g_hevMarkedRead, contactID, (LPARAM)hDbEvent); return wRetVal; } /////////////////////////////////////////////////////////////////////////////////////////////////// MEVENT CDbxMDBX::GetEventById(const char *szModule, const char *szId) { if (szModule == nullptr || szId == nullptr) return 0; DBEventIdKey keyId; keyId.iModuleId = GetModuleID(szModule); strncpy_s(keyId.szEventId, szId, _TRUNCATE); MDBX_val key = { &keyId, sizeof(MEVENT) + strlen(keyId.szEventId) + 1 }, data; if (mdbx_get(StartTran(), m_dbEventIds, &key, &data) != MDBX_SUCCESS) return 0; MEVENT hDbEvent = *(MEVENT *)data.iov_base; MDBX_val key2 = { &hDbEvent, sizeof(MEVENT) }, data2; if (mdbx_get(StartTran(), m_dbEvents, &key2, &data2) != MDBX_SUCCESS) return 0; return hDbEvent; } int CDbxMDBX::UpdateEventId(MEVENT, LPCSTR) { return 1; } ///////////////////////////////////////////////////////////////////////////////////////// MCONTACT CDbxMDBX::GetEventContact(MEVENT hDbEvent) { if (hDbEvent == 0) return INVALID_CONTACT_ID; MDBX_val key = { &hDbEvent, sizeof(MEVENT) }, data; if (mdbx_get(StartTran(), m_dbEvents, &key, &data) != MDBX_SUCCESS) return INVALID_CONTACT_ID; return ((const DBEvent*)data.iov_base)->dwContactID; } ///////////////////////////////////////////////////////////////////////////////////////// MEVENT CDbxMDBX::FindFirstEvent(MCONTACT contactID) { DBCachedContact *cc; if (contactID != 0) { cc = m_cache->GetCachedContact(contactID); if (cc == nullptr) return 0; } else cc = &m_ccDummy; DBEventSortingKey keyVal = { contactID, 0, 0 }; MDBX_val key = { &keyVal, sizeof(keyVal) }, data; mdbx_cursor_bind(StartTran(), m_curEventsSort, m_dbEventsSort); if (mdbx_cursor_get(m_curEventsSort, &key, &data, MDBX_SET_RANGE) != MDBX_SUCCESS) return cc->t_evLast = 0; const DBEventSortingKey *pKey = (const DBEventSortingKey*)key.iov_base; if (pKey->hContact != contactID) return cc->t_evLast = 0; cc->t_tsLast = pKey->ts; return cc->t_evLast = (pKey->hContact == contactID) ? pKey->hEvent : 0; } ///////////////////////////////////////////////////////////////////////////////////////// MEVENT CDbxMDBX::FindFirstUnreadEvent(MCONTACT contactID) { DBCachedContact *cc = m_cache->GetCachedContact(contactID); return (cc == nullptr) ? 0 : cc->dbc.evFirstUnread; } ///////////////////////////////////////////////////////////////////////////////////////// MEVENT CDbxMDBX::FindLastEvent(MCONTACT contactID) { DBCachedContact *cc; if (contactID != 0) { cc = m_cache->GetCachedContact(contactID); if (cc == nullptr) return 0; } else cc = &m_ccDummy; DBEventSortingKey keyVal = { contactID, 0xFFFFFFFF, 0xFFFFFFFFFFFFFFFF }; MDBX_val key = { &keyVal, sizeof(keyVal) }, data; mdbx_cursor_bind(StartTran(), m_curEventsSort, m_dbEventsSort); if (mdbx_cursor_get(m_curEventsSort, &key, &data, MDBX_SET_RANGE) != MDBX_SUCCESS) { if (mdbx_cursor_get(m_curEventsSort, &key, &data, MDBX_LAST) != MDBX_SUCCESS) return cc->t_evLast = 0; } else { if (mdbx_cursor_get(m_curEventsSort, &key, &data, MDBX_PREV) != MDBX_SUCCESS) return cc->t_evLast = 0; } const DBEventSortingKey *pKey = (const DBEventSortingKey*)key.iov_base; if (pKey->hContact != contactID) return cc->t_evLast = 0; cc->t_tsLast = pKey->ts; return cc->t_evLast = (pKey->hContact == contactID) ? pKey->hEvent : 0; } ///////////////////////////////////////////////////////////////////////////////////////// MEVENT CDbxMDBX::FindNextEvent(MCONTACT contactID, MEVENT hDbEvent) { DBCachedContact *cc; if (contactID != 0) { cc = m_cache->GetCachedContact(contactID); if (cc == nullptr) return 0; } else cc = &m_ccDummy; if (hDbEvent == 0) return cc->t_evLast = 0; if (cc->t_evLast != hDbEvent) { MDBX_val key = { &hDbEvent, sizeof(MEVENT) }, data; if (mdbx_get(StartTran(), m_dbEvents, &key, &data) != MDBX_SUCCESS) return 0; cc->t_tsLast = ((DBEvent*)data.iov_base)->timestamp; } DBEventSortingKey keyVal = { contactID, hDbEvent, cc->t_tsLast }; MDBX_val key = { &keyVal, sizeof(keyVal) }, data; mdbx_cursor_bind(StartTran(), m_curEventsSort, m_dbEventsSort); if (mdbx_cursor_get(m_curEventsSort, &key, nullptr, MDBX_SET) != MDBX_SUCCESS) return cc->t_evLast = 0; if (mdbx_cursor_get(m_curEventsSort, &key, &data, MDBX_NEXT) != MDBX_SUCCESS) return cc->t_evLast = 0; const DBEventSortingKey *pKey = (const DBEventSortingKey*)key.iov_base; cc->t_tsLast = pKey->ts; return cc->t_evLast = (pKey->hContact == contactID) ? pKey->hEvent : 0; } ///////////////////////////////////////////////////////////////////////////////////////// MEVENT CDbxMDBX::FindPrevEvent(MCONTACT contactID, MEVENT hDbEvent) { DBCachedContact *cc; if (contactID != 0) { cc = m_cache->GetCachedContact(contactID); if (cc == nullptr) return 0; } else cc = &m_ccDummy; if (hDbEvent == 0) return cc->t_evLast = 0; MDBX_val data; if (cc->t_evLast != hDbEvent) { MDBX_val key = { &hDbEvent, sizeof(MEVENT) }; if (mdbx_get(StartTran(), m_dbEvents, &key, &data) != MDBX_SUCCESS) return 0; cc->t_tsLast = ((DBEvent*)data.iov_base)->timestamp; } DBEventSortingKey keyVal = { contactID, hDbEvent, cc->t_tsLast }; MDBX_val key = { &keyVal, sizeof(keyVal) }; mdbx_cursor_bind(StartTran(), m_curEventsSort, m_dbEventsSort); if (mdbx_cursor_get(m_curEventsSort, &key, nullptr, MDBX_SET) != MDBX_SUCCESS) return cc->t_evLast = 0; if (mdbx_cursor_get(m_curEventsSort, &key, &data, MDBX_PREV) != MDBX_SUCCESS) return cc->t_evLast = 0; const DBEventSortingKey *pKey = (const DBEventSortingKey*)key.iov_base; cc->t_tsLast = pKey->ts; return cc->t_evLast = (pKey->hContact == contactID) ? pKey->hEvent : 0; } ///////////////////////////////////////////////////////////////////////////////////////// // Event cursors class CMdbxEventCursor : public DB::EventCursor { friend class CDbxMDBX; CDbxMDBX *m_pOwner; bool m_bForward, m_bFirst = true; DBCachedContact *m_cc; DBEventSortingKey m_key; public: CMdbxEventCursor(class CDbxMDBX *pDb, DBCachedContact *cc, bool bForward) : EventCursor(cc->contactID), m_pOwner(pDb), m_bForward(bForward), m_cc(cc) { m_key.hContact = hContact; if (bForward) m_key.hEvent = pDb->FindFirstEvent(hContact); else m_key.hEvent = pDb->FindLastEvent(hContact); if (m_key.hEvent != 0) m_key.ts = cc->t_tsLast; } MEVENT FetchNext() override { // if initial seek returned nothing, keep returning 0 forever if (m_bFirst) { if (m_key.hEvent == 0) return 0; m_bFirst = false; return m_key.hEvent; } mdbx_cursor_bind(m_pOwner->StartTran(), m_pOwner->m_curEventsSort, m_pOwner->m_dbEventsSort); // this is precise key position, if it doesn't exist - return MDBX_val key = { &m_key, sizeof(m_key) }, data; if (mdbx_cursor_get(m_pOwner->m_curEventsSort, &key, &data, MDBX_SET) != MDBX_SUCCESS) return 0; // move one record ahead or backward if (mdbx_cursor_get(m_pOwner->m_curEventsSort, &key, &data, (m_bForward) ? MDBX_NEXT : MDBX_PREV) != MDBX_SUCCESS) return 0; // and this record should belong to the same contact DBEventSortingKey dbKey = *(const DBEventSortingKey *)key.iov_base; if (dbKey.hContact != hContact) return 0; m_key = dbKey; return dbKey.hEvent; } }; DB::EventCursor* CDbxMDBX::EventCursor(MCONTACT hContact, MEVENT) { DBCachedContact *cc; if (hContact != 0) { cc = m_cache->GetCachedContact(hContact); if (cc == nullptr) return nullptr; } else cc = &m_ccDummy; return new CMdbxEventCursor(this, cc, true); } DB::EventCursor* CDbxMDBX::EventCursorRev(MCONTACT hContact, MEVENT) { DBCachedContact *cc; if (hContact != 0) { cc = m_cache->GetCachedContact(hContact); if (cc == nullptr) return nullptr; } else cc = &m_ccDummy; return new CMdbxEventCursor(this, cc, false); }