summaryrefslogtreecommitdiff
path: root/plugins/Dbx_mdbx/src
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/Dbx_mdbx/src')
-rw-r--r--plugins/Dbx_mdbx/src/dbcontacts.cpp247
-rw-r--r--plugins/Dbx_mdbx/src/dbcrypt.cpp226
-rw-r--r--plugins/Dbx_mdbx/src/dbevents.cpp428
-rw-r--r--plugins/Dbx_mdbx/src/dbintf.cpp279
-rw-r--r--plugins/Dbx_mdbx/src/dbintf.h268
-rw-r--r--plugins/Dbx_mdbx/src/dbmodulechain.cpp72
-rw-r--r--plugins/Dbx_mdbx/src/dbsettings.cpp398
-rw-r--r--plugins/Dbx_mdbx/src/dbutils.cpp47
-rw-r--r--plugins/Dbx_mdbx/src/init.cpp133
m---------plugins/Dbx_mdbx/src/libmdbx0
-rw-r--r--plugins/Dbx_mdbx/src/resource.h35
-rw-r--r--plugins/Dbx_mdbx/src/stdafx.cxx18
-rw-r--r--plugins/Dbx_mdbx/src/stdafx.h169
-rw-r--r--plugins/Dbx_mdbx/src/ui.cpp202
-rw-r--r--plugins/Dbx_mdbx/src/ui.h187
-rw-r--r--plugins/Dbx_mdbx/src/version.h13
16 files changed, 2722 insertions, 0 deletions
diff --git a/plugins/Dbx_mdbx/src/dbcontacts.cpp b/plugins/Dbx_mdbx/src/dbcontacts.cpp
new file mode 100644
index 0000000000..1195c0af6e
--- /dev/null
+++ b/plugins/Dbx_mdbx/src/dbcontacts.cpp
@@ -0,0 +1,247 @@
+/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (c) 2012-18 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"
+
+STDMETHODIMP_(LONG) CDbxMDBX::GetContactCount(void)
+{
+ return m_contactCount;
+}
+
+STDMETHODIMP_(LONG) CDbxMDBX::GetContactSize(void)
+{
+ return sizeof(DBCachedContact);
+}
+
+STDMETHODIMP_(LONG) CDbxMDBX::DeleteContact(MCONTACT contactID)
+{
+ if (contactID == 0) // global contact cannot be removed
+ return 1;
+
+ NotifyEventHooks(hContactDeletedEvent, contactID, 0);
+ {
+ OBJLIST<EventItem> events(50);
+ GatherContactHistory(contactID, events);
+ while (events.getCount()) {
+ DeleteEvent(contactID, events[0].eventId);
+ events.remove(0);
+ }
+ }
+ {
+ MDBX_val key, data;
+ DBSettingKey keyS = { contactID, 0, 0 };
+
+ txn_ptr txn(m_env);
+ cursor_ptr cursor(txn, m_dbSettings);
+
+ key.iov_len = sizeof(keyS); key.iov_base = &keyS;
+
+ for (int res = mdbx_cursor_get(cursor, &key, &data, MDBX_SET_RANGE); res == MDBX_SUCCESS; res = mdbx_cursor_get(cursor, &key, &data, MDBX_NEXT)) {
+ const DBSettingKey *pKey = (const DBSettingKey*)key.iov_base;
+ if (pKey->hContact != contactID)
+ break;
+ mdbx_cursor_del(cursor, 0);
+ }
+
+ txn.commit();
+ }
+
+ MDBX_val key = { &contactID, sizeof(MCONTACT) };
+ for (;; Remap()) {
+ txn_ptr trnlck(m_env);
+ MDBX_CHECK(mdbx_del(trnlck, m_dbContacts, &key, nullptr), 1);
+ if (trnlck.commit() == MDBX_SUCCESS)
+ break;
+ }
+
+ InterlockedDecrement(&m_contactCount);
+
+ return 0;
+}
+
+STDMETHODIMP_(MCONTACT) CDbxMDBX::AddContact()
+{
+ MCONTACT dwContactId = InterlockedIncrement(&m_maxContactId);
+
+ DBCachedContact *cc = m_cache->AddContactToCache(dwContactId);
+
+ MDBX_val key = { &dwContactId, sizeof(MCONTACT) };
+ MDBX_val data = { &cc->dbc, sizeof(cc->dbc) };
+
+ for (;; Remap()) {
+ txn_ptr trnlck(m_env);
+ MDBX_CHECK(mdbx_put(trnlck, m_dbContacts, &key, &data, 0), 0);
+ if (trnlck.commit() == MDBX_SUCCESS)
+ break;
+ }
+
+ InterlockedIncrement(&m_contactCount);
+ NotifyEventHooks(hContactAddedEvent, dwContactId, 0);
+ return dwContactId;
+}
+
+STDMETHODIMP_(BOOL) CDbxMDBX::IsDbContact(MCONTACT contactID)
+{
+ DBCachedContact *cc = m_cache->GetCachedContact(contactID);
+ return (cc != NULL);
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void CDbxMDBX::GatherContactHistory(MCONTACT hContact, LIST<EventItem> &list)
+{
+ DBEventSortingKey keyVal = { 0, 0, hContact };
+ MDBX_val key = { &keyVal, sizeof(keyVal) }, data;
+
+ txn_ptr_ro trnlck(m_txn);
+ cursor_ptr_ro cursor(m_curEventsSort);
+
+ for (int res = mdbx_cursor_get(cursor, &key, &data, MDBX_SET_RANGE); res == MDBX_SUCCESS; res = mdbx_cursor_get(cursor, &key, &data, MDBX_NEXT)) {
+ const DBEventSortingKey *pKey = (const DBEventSortingKey*)key.iov_base;
+ if (pKey->hContact != hContact)
+ return;
+
+ list.insert(new EventItem(pKey->ts, pKey->hEvent));
+ }
+}
+
+BOOL CDbxMDBX::MetaMergeHistory(DBCachedContact *ccMeta, DBCachedContact *ccSub)
+{
+ LIST<EventItem> list(1000);
+ GatherContactHistory(ccSub->contactID, list);
+
+ for (int i = 0; i < list.getCount(); i++) {
+ EventItem *EI = list[i];
+
+ for (;; Remap()) {
+ txn_ptr trnlck(m_env);
+ DBEventSortingKey insVal = { EI->eventId, EI->ts, ccMeta->contactID };
+ MDBX_val key = { &insVal, sizeof(insVal) }, data = { (void*)"", 1 };
+ mdbx_put(trnlck, m_dbEventsSort, &key, &data, 0);
+ if (trnlck.commit() == MDBX_SUCCESS)
+ break;
+ }
+ ccMeta->dbc.dwEventCount++;
+ delete EI;
+ }
+
+ MDBX_val keyc = { &ccMeta->contactID, sizeof(MCONTACT) }, datac = { &ccMeta->dbc, sizeof(ccMeta->dbc) };
+
+ for (;; Remap()) {
+ txn_ptr trnlck(m_env);
+ MDBX_CHECK(mdbx_put(trnlck, m_dbContacts, &keyc, &datac, 0), 1);
+ if (trnlck.commit() == MDBX_SUCCESS)
+ break;
+ }
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+BOOL CDbxMDBX::MetaSplitHistory(DBCachedContact *ccMeta, DBCachedContact *ccSub)
+{
+ LIST<EventItem> list(1000);
+ GatherContactHistory(ccSub->contactID, list);
+
+ for (int i = 0; i < list.getCount(); i++) {
+ EventItem *EI = list[i];
+
+ for (;; Remap()) {
+ txn_ptr trnlck(m_env);
+ DBEventSortingKey insVal = { EI->eventId, EI->ts, ccMeta->contactID };
+ MDBX_val key = { &insVal, sizeof(insVal) }, data = { (void*)"", 1 };
+ mdbx_del(trnlck, m_dbEventsSort, &key, &data);
+ if (trnlck.commit() == MDBX_SUCCESS)
+ break;
+ }
+ ccMeta->dbc.dwEventCount--;
+ delete EI;
+ }
+
+ MDBX_val keyc = { &ccMeta->contactID, sizeof(MCONTACT) }, datac = { &ccMeta->dbc, sizeof(ccMeta->dbc) };
+
+ for (;; Remap()) {
+ txn_ptr trnlck(m_env);
+ MDBX_CHECK(mdbx_put(trnlck, m_dbContacts, &keyc, &datac, 0), 1);
+ if (trnlck.commit() == MDBX_SUCCESS)
+ break;
+ }
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void DBCachedContact::Advance(MEVENT id, DBEvent &dbe)
+{
+ dbc.dwEventCount++;
+
+ if (dbe.flags & (DBEF_READ | DBEF_SENT))
+ return;
+
+ if (dbe.timestamp < dbc.tsFirstUnread || dbc.tsFirstUnread == 0) {
+ dbc.tsFirstUnread = dbe.timestamp;
+ dbc.evFirstUnread = id;
+ }
+}
+
+void DBCachedContact::Snapshot()
+{
+ tmp_dbc = dbc;
+}
+
+void DBCachedContact::Revert()
+{
+ dbc = tmp_dbc;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// initial cycle to fill the contacts' cache
+
+void CDbxMDBX::FillContacts()
+{
+ LIST<DBCachedContact> arContacts(m_contactCount);
+
+ txn_ptr_ro trnlck(m_txn);
+ cursor_ptr_ro cursor(m_curContacts);
+
+ MDBX_val key, data;
+ while (mdbx_cursor_get(cursor, &key, &data, MDBX_NEXT) == MDBX_SUCCESS) {
+ DBCachedContact *cc = m_cache->AddContactToCache(*(MCONTACT*)key.iov_base);
+ cc->dbc = *(DBContact*)data.iov_base;
+
+ CheckProto(cc, "");
+
+ DBVARIANT dbv; dbv.type = DBVT_DWORD;
+ cc->nSubs = (0 != GetContactSetting(cc->contactID, META_PROTO, "NumContacts", &dbv)) ? -1 : dbv.dVal;
+ if (cc->nSubs != -1) {
+ cc->pSubs = (MCONTACT*)mir_alloc(cc->nSubs * sizeof(MCONTACT));
+ for (int k = 0; k < cc->nSubs; k++) {
+ char setting[100];
+ mir_snprintf(setting, _countof(setting), "Handle%d", k);
+ cc->pSubs[k] = (0 != GetContactSetting(cc->contactID, META_PROTO, setting, &dbv)) ? NULL : dbv.dVal;
+ }
+ }
+ cc->nDefault = (0 != GetContactSetting(cc->contactID, META_PROTO, "Default", &dbv)) ? -1 : dbv.dVal;
+ cc->parentID = (0 != GetContactSetting(cc->contactID, META_PROTO, "ParentMeta", &dbv)) ? NULL : dbv.dVal;
+ }
+}
diff --git a/plugins/Dbx_mdbx/src/dbcrypt.cpp b/plugins/Dbx_mdbx/src/dbcrypt.cpp
new file mode 100644
index 0000000000..6142a7b577
--- /dev/null
+++ b/plugins/Dbx_mdbx/src/dbcrypt.cpp
@@ -0,0 +1,226 @@
+/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (c) 2012-18 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"
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+char DBKey_Crypto_Provider[] = "Provider";
+char DBKey_Crypto_Key[] = "Key";
+char DBKey_Crypto_IsEncrypted[] = "EncryptedDB";
+
+CRYPTO_PROVIDER* CDbxMDBX::SelectProvider()
+{
+ CRYPTO_PROVIDER **ppProvs, *pProv;
+ int iNumProvs;
+ Crypto_EnumProviders(&iNumProvs, &ppProvs);
+
+ if (iNumProvs == 0)
+ return nullptr;
+
+ bool bTotalCrypt = false;
+
+ if (iNumProvs > 1) {
+ CSelectCryptoDialog dlg(ppProvs, iNumProvs);
+ dlg.DoModal();
+ pProv = dlg.GetSelected();
+ bTotalCrypt = dlg.TotalSelected();
+ }
+ else pProv = ppProvs[0];
+
+ for (;; Remap()) {
+ txn_ptr txn(m_env);
+
+ MDBX_val key = { DBKey_Crypto_Provider, sizeof(DBKey_Crypto_Provider) }, value = { pProv->pszName, mir_strlen(pProv->pszName) + 1 };
+ MDBX_CHECK(mdbx_put(txn, m_dbCrypto, &key, &value, 0), nullptr);
+
+ key.iov_len = sizeof(DBKey_Crypto_IsEncrypted); key.iov_base = DBKey_Crypto_IsEncrypted; value.iov_len = sizeof(bool); value.iov_base = &bTotalCrypt;
+ MDBX_CHECK(mdbx_put(txn, m_dbCrypto, &key, &value, 0), nullptr);
+
+ if (txn.commit() == MDBX_SUCCESS)
+ break;
+ }
+
+ return pProv;
+}
+
+int CDbxMDBX::InitCrypt()
+{
+ CRYPTO_PROVIDER *pProvider;
+
+ txn_ptr_ro txn(m_txn);
+
+ MDBX_val key = { DBKey_Crypto_Provider, sizeof(DBKey_Crypto_Provider) }, value;
+ if (mdbx_get(txn, m_dbCrypto, &key, &value) == MDBX_SUCCESS) {
+ pProvider = Crypto_GetProvider((const char*)value.iov_base);
+ if (pProvider == nullptr)
+ pProvider = SelectProvider();
+ }
+ else pProvider = SelectProvider();
+
+ if (pProvider == nullptr)
+ return 1;
+
+ if ((m_crypto = pProvider->pFactory()) == nullptr)
+ return 3;
+
+ key.iov_len = sizeof(DBKey_Crypto_Key); key.iov_base = DBKey_Crypto_Key;
+ if (mdbx_get(txn, m_dbCrypto, &key, &value) == MDBX_SUCCESS && (value.iov_len == m_crypto->getKeyLength())) {
+ if (!m_crypto->setKey((const BYTE*)value.iov_base, value.iov_len)) {
+ DlgChangePassParam param = { this };
+ CEnterPasswordDialog dlg(&param);
+ while (true) {
+ if (-128 != dlg.DoModal())
+ return 4;
+ m_crypto->setPassword(pass_ptrA(mir_utf8encodeW(param.newPass)));
+ if (m_crypto->setKey((const BYTE*)value.iov_base, value.iov_len)) {
+ m_bUsesPassword = true;
+ SecureZeroMemory(&param, sizeof(param));
+ break;
+ }
+ param.wrongPass++;
+ }
+ }
+ }
+ else {
+ if (!m_crypto->generateKey())
+ return 6;
+ StoreKey();
+ }
+
+ key.iov_len = sizeof(DBKey_Crypto_IsEncrypted); key.iov_base = DBKey_Crypto_IsEncrypted;
+
+ if (mdbx_get(txn, m_dbCrypto, &key, &value) == MDBX_SUCCESS)
+ m_bEncrypted = *(const bool*)value.iov_base;
+ else
+ m_bEncrypted = false;
+
+ InitDialogs();
+ return 0;
+}
+
+void CDbxMDBX::StoreKey()
+{
+ size_t iKeyLength = m_crypto->getKeyLength();
+ BYTE *pKey = (BYTE*)_alloca(iKeyLength);
+ m_crypto->getKey(pKey, iKeyLength);
+
+ for (;; Remap()) {
+ txn_ptr txn(m_env);
+ MDBX_val key = { DBKey_Crypto_Key, sizeof(DBKey_Crypto_Key) }, value = { pKey, iKeyLength };
+ mdbx_put(txn, m_dbCrypto, &key, &value, 0);
+ if (txn.commit() == MDBX_SUCCESS)
+ break;
+ }
+ SecureZeroMemory(pKey, iKeyLength);
+}
+
+void CDbxMDBX::SetPassword(const wchar_t *ptszPassword)
+{
+ if (ptszPassword == NULL || *ptszPassword == 0) {
+ m_bUsesPassword = false;
+ m_crypto->setPassword(NULL);
+ }
+ else {
+ m_bUsesPassword = true;
+ m_crypto->setPassword(pass_ptrA(mir_utf8encodeW(ptszPassword)));
+ }
+ UpdateMenuItem();
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+int CDbxMDBX::EnableEncryption(bool bEncrypted)
+{
+ if (m_bEncrypted == bEncrypted)
+ return 0;
+
+ {
+ txn_ptr_ro txnro(m_txn);
+
+ MDBX_stat st;
+ mdbx_dbi_stat(txnro, m_dbEvents, &st, sizeof(st));
+
+ std::vector<MEVENT> lstEvents;
+ lstEvents.reserve(st.ms_entries);
+
+ {
+ cursor_ptr_ro cursor(m_curEvents);
+ MDBX_val key, data;
+ while (mdbx_cursor_get(cursor, &key, &data, MDBX_NEXT) == MDBX_SUCCESS) {
+ const MEVENT hDbEvent = *(const MEVENT*)key.iov_base;
+ lstEvents.push_back(hDbEvent);
+ }
+ }
+ for (auto it = lstEvents.begin(); it != lstEvents.end(); ++it) {
+ MEVENT &hDbEvent = *it;
+ MDBX_val key = { &hDbEvent, sizeof(MEVENT) }, data;
+ mdbx_get(txnro, m_dbEvents, &key, &data);
+
+ const DBEvent *dbEvent = (const DBEvent*)data.iov_base;
+ const BYTE *pBlob = (BYTE*)(dbEvent + 1);
+
+ if (((dbEvent->flags & DBEF_ENCRYPTED) != 0) != bEncrypted) {
+ mir_ptr<BYTE> pNewBlob;
+ size_t nNewBlob;
+ uint32_t dwNewFlags;
+
+ if (dbEvent->flags & DBEF_ENCRYPTED) {
+ pNewBlob = (BYTE*)m_crypto->decodeBuffer(pBlob, dbEvent->cbBlob, &nNewBlob);
+ dwNewFlags = dbEvent->flags & (~DBEF_ENCRYPTED);
+ }
+ else {
+ pNewBlob = m_crypto->encodeBuffer(pBlob, dbEvent->cbBlob, &nNewBlob);
+ dwNewFlags = dbEvent->flags | DBEF_ENCRYPTED;
+ }
+
+ for (;; Remap()) {
+ txn_ptr txn(m_env);
+ data.iov_len = sizeof(DBEvent) + nNewBlob;
+ MDBX_CHECK(mdbx_put(txn, m_dbEvents, &key, &data, MDBX_RESERVE), 1);
+
+ DBEvent *pNewDBEvent = (DBEvent *)data.iov_base;
+ *pNewDBEvent = *dbEvent;
+ pNewDBEvent->cbBlob = (uint16_t)nNewBlob;
+ pNewDBEvent->flags = dwNewFlags;
+ memcpy(pNewDBEvent + 1, pNewBlob, nNewBlob);
+
+
+ if (txn.commit() == MDBX_SUCCESS)
+ break;
+ }
+ }
+ }
+ }
+
+ for (;; Remap()) {
+ txn_ptr txn(m_env);
+ MDBX_val key = { DBKey_Crypto_IsEncrypted, sizeof(DBKey_Crypto_IsEncrypted) }, value = { &bEncrypted, sizeof(bool) };
+ MDBX_CHECK(mdbx_put(txn, m_dbCrypto, &key, &value, 0), 1);
+ if (txn.commit() == MDBX_SUCCESS)
+ break;
+ }
+
+ m_bEncrypted = bEncrypted;
+ return 0;
+}
diff --git a/plugins/Dbx_mdbx/src/dbevents.cpp b/plugins/Dbx_mdbx/src/dbevents.cpp
new file mode 100644
index 0000000000..3ad663a5a9
--- /dev/null
+++ b/plugins/Dbx_mdbx/src/dbevents.cpp
@@ -0,0 +1,428 @@
+/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (c) 2012-18 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"
+
+STDMETHODIMP_(LONG) CDbxMDBX::GetEventCount(MCONTACT contactID)
+{
+ DBCachedContact *cc = m_cache->GetCachedContact(contactID);
+ return (cc == NULL) ? 0 : cc->dbc.dwEventCount;
+}
+
+STDMETHODIMP_(MEVENT) CDbxMDBX::AddEvent(MCONTACT contactID, DBEVENTINFO *dbei)
+{
+ if (dbei == NULL) return 0;
+ if (dbei->timestamp == 0) return 0;
+
+ DBEvent dbe;
+ dbe.contactID = contactID; // store native or subcontact's id
+ dbe.iModuleId = GetModuleID(dbei->szModule);
+
+ MCONTACT contactNotifyID = contactID;
+ DBCachedContact *cc, *ccSub = NULL;
+ if ((cc = m_cache->GetCachedContact(contactID)) == NULL)
+ return 0;
+
+ if (cc->IsSub()) {
+ ccSub = cc;
+ if ((cc = m_cache->GetCachedContact(cc->parentID)) == NULL)
+ return 0;
+
+ // set default sub to the event's source
+ if (!(dbei->flags & DBEF_SENT))
+ db_mc_setDefault(cc->contactID, contactID, false);
+ contactID = cc->contactID; // and add an event to a metahistory
+ if (db_mc_isEnabled())
+ contactNotifyID = contactID;
+ }
+
+ if (m_safetyMode)
+ if (NotifyEventHooks(hEventFilterAddedEvent, contactNotifyID, (LPARAM)dbei))
+ return NULL;
+
+ dbe.timestamp = dbei->timestamp;
+ dbe.flags = dbei->flags;
+ dbe.wEventType = dbei->eventType;
+ dbe.cbBlob = dbei->cbBlob;
+ BYTE *pBlob = dbei->pBlob;
+
+ mir_ptr<BYTE> pCryptBlob;
+ if (m_bEncrypted) {
+ size_t len;
+ BYTE *pResult = m_crypto->encodeBuffer(pBlob, dbe.cbBlob, &len);
+ if (pResult != NULL) {
+ pCryptBlob = pBlob = pResult;
+ dbe.cbBlob = (uint16_t)len;
+ dbe.flags |= DBEF_ENCRYPTED;
+ }
+ }
+
+
+ MEVENT dwEventId = InterlockedIncrement(&m_dwMaxEventId);
+
+ const auto Snapshot = [&]() { cc->Snapshot(); if (ccSub) ccSub->Snapshot(); };
+ const auto Revert = [&]() { cc->Revert(); if (ccSub) ccSub->Revert(); };
+
+ for (Snapshot();; Revert(), Remap()) {
+ txn_ptr txn(m_env);
+
+ MDBX_val key = { &dwEventId, sizeof(MEVENT) }, data = { NULL, sizeof(DBEvent) + dbe.cbBlob };
+ MDBX_CHECK(mdbx_put(txn, m_dbEvents, &key, &data, MDBX_RESERVE), 0);
+
+ DBEvent *pNewEvent = (DBEvent*)data.iov_base;
+ *pNewEvent = dbe;
+ memcpy(pNewEvent + 1, pBlob, dbe.cbBlob);
+
+ // add a sorting key
+ DBEventSortingKey key2 = { contactID, dwEventId, dbe.timestamp };
+ key.iov_len = sizeof(key2); key.iov_base = &key2;
+ data.iov_len = 1; data.iov_base = (char*)("");
+ MDBX_CHECK(mdbx_put(txn, m_dbEventsSort, &key, &data, 0), 0);
+
+ cc->Advance(dwEventId, dbe);
+ MDBX_val keyc = { &contactID, sizeof(MCONTACT) }, datac = { &cc->dbc, sizeof(DBContact) };
+ MDBX_CHECK(mdbx_put(txn, m_dbContacts, &keyc, &datac, 0), 0);
+
+ // insert an event into a sub's history too
+ if (ccSub != NULL) {
+ key2.hContact = ccSub->contactID;
+ MDBX_CHECK(mdbx_put(txn, m_dbEventsSort, &key, &data, 0), 0);
+
+ ccSub->Advance(dwEventId, dbe);
+ datac.iov_base = &ccSub->dbc;
+ keyc.iov_base = &ccSub->contactID;
+ MDBX_CHECK(mdbx_put(txn, m_dbContacts, &keyc, &datac, 0), 0);
+ }
+
+ if (txn.commit() == MDBX_SUCCESS)
+ break;
+ }
+
+ // Notify only in safe mode or on really new events
+ if (m_safetyMode)
+ NotifyEventHooks(hEventAddedEvent, contactNotifyID, dwEventId);
+
+ return dwEventId;
+}
+
+STDMETHODIMP_(BOOL) CDbxMDBX::DeleteEvent(MCONTACT contactID, MEVENT hDbEvent)
+{
+ DBCachedContact *cc = m_cache->GetCachedContact(contactID), *cc2 = nullptr;
+ if (cc == NULL || cc->dbc.dwEventCount == 0)
+ return 1;
+
+ DBEvent dbe;
+ {
+ txn_ptr_ro txn(m_txn);
+ MDBX_val key = { &hDbEvent, sizeof(MEVENT) }, data;
+ if (mdbx_get(txn, m_dbEvents, &key, &data) != MDBX_SUCCESS)
+ return 1;
+ dbe = *(DBEvent*)data.iov_base;
+ }
+
+ if (contactID != dbe.contactID) {
+ cc2 = m_cache->GetCachedContact(dbe.contactID);
+ }
+
+ const auto Snapshot = [&]() { cc->Snapshot(); if (cc2) cc2->Snapshot(); };
+ const auto Revert = [&]() { cc->Revert(); if (cc2) cc2->Revert(); };
+
+ for (Snapshot();; Revert(), Remap()) {
+ DBEventSortingKey key2 = { contactID, hDbEvent, dbe.timestamp };
+
+ txn_ptr txn(m_env);
+ MDBX_val key = { &key2, sizeof(key2) }, data;
+
+ MDBX_CHECK(mdbx_del(txn, m_dbEventsSort, &key, &data), 1)
+
+ {
+ key.iov_len = sizeof(MCONTACT); key.iov_base = &contactID;
+ cc->dbc.dwEventCount--;
+ if (cc->dbc.evFirstUnread == hDbEvent)
+ FindNextUnread(txn, cc, key2);
+
+ data.iov_len = sizeof(DBContact); data.iov_base = &cc->dbc;
+ MDBX_CHECK(mdbx_put(txn, m_dbContacts, &key, &data, 0), 1);
+ }
+
+ if (cc2) {
+ key2.hContact = dbe.contactID;
+ MDBX_CHECK(mdbx_del(txn, m_dbEventsSort, &key, &data), 1);
+
+ key.iov_len = sizeof(MCONTACT); key.iov_base = &contactID;
+ cc2->dbc.dwEventCount--;
+ if (cc2->dbc.evFirstUnread == hDbEvent)
+ FindNextUnread(txn, cc2, key2);
+
+ data.iov_len = sizeof(DBContact); data.iov_base = &cc2->dbc;
+ MDBX_CHECK(mdbx_put(txn, m_dbContacts, &key, &data, 0), 1);
+
+ }
+
+ // remove a event
+ key.iov_len = sizeof(MEVENT); key.iov_base = &hDbEvent;
+ MDBX_CHECK(mdbx_del(txn, m_dbEvents, &key, &data), 1);
+
+ if (txn.commit() == MDBX_SUCCESS)
+ break;
+ }
+
+ NotifyEventHooks(hEventDeletedEvent, contactID, hDbEvent);
+
+ return 0;
+}
+
+STDMETHODIMP_(LONG) CDbxMDBX::GetBlobSize(MEVENT hDbEvent)
+{
+ txn_ptr_ro txn(m_txn);
+
+ MDBX_val key = { &hDbEvent, sizeof(MEVENT) }, data;
+ if (mdbx_get(txn, m_dbEvents, &key, &data) != MDBX_SUCCESS)
+ return -1;
+ return ((const DBEvent*)data.iov_base)->cbBlob;
+}
+
+STDMETHODIMP_(BOOL) CDbxMDBX::GetEvent(MEVENT hDbEvent, DBEVENTINFO *dbei)
+{
+ if (dbei == NULL) return 1;
+ if (dbei->cbBlob > 0 && dbei->pBlob == NULL) {
+ dbei->cbBlob = 0;
+ return 1;
+ }
+
+ txn_ptr_ro txn(m_txn);
+
+ MDBX_val key = { &hDbEvent, sizeof(MEVENT) }, data;
+ if (mdbx_get(txn, m_dbEvents, &key, &data) != MDBX_SUCCESS)
+ return 1;
+
+ const DBEvent *dbe = (const DBEvent*)data.iov_base;
+
+ dbei->szModule = GetModuleName(dbe->iModuleId);
+ dbei->timestamp = dbe->timestamp;
+ dbei->flags = dbe->flags;
+ dbei->eventType = dbe->wEventType;
+ size_t bytesToCopy = min(dbei->cbBlob, dbe->cbBlob);
+ dbei->cbBlob = dbe->cbBlob;
+ if (bytesToCopy && dbei->pBlob) {
+ BYTE *pSrc = (BYTE*)data.iov_base + 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 > len)
+ memset(dbei->pBlob + len, 0, bytesToCopy - len);
+ mir_free(pBlob);
+ }
+ else memcpy(dbei->pBlob, pSrc, bytesToCopy);
+ }
+ 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); res == MDBX_SUCCESS; res = mdbx_cursor_get(cursor, &key, &data, MDBX_NEXT)) {
+ const DBEvent *dbe = (const DBEvent*)data.iov_base;
+ if (dbe->contactID != cc->contactID)
+ break;
+ if (!dbe->markedRead()) {
+ cc->dbc.evFirstUnread = key2.hEvent;
+ cc->dbc.tsFirstUnread = key2.ts;
+ return;
+ }
+ }
+
+ cc->dbc.evFirstUnread = cc->dbc.tsFirstUnread = 0;
+}
+
+STDMETHODIMP_(BOOL) CDbxMDBX::MarkEventRead(MCONTACT contactID, MEVENT hDbEvent)
+{
+ if (hDbEvent == 0) return -1;
+
+ DBCachedContact *cc = m_cache->GetCachedContact(contactID);
+ if (cc == NULL)
+ return -1;
+
+ uint32_t wRetVal = -1;
+
+ for (cc->Snapshot();; cc->Revert(), Remap()) {
+ txn_ptr txn(m_env);
+
+ MDBX_val key = { &hDbEvent, sizeof(MEVENT) }, data;
+ MDBX_CHECK(mdbx_get(txn, m_dbEvents, &key, &data), -1);
+
+ const DBEvent *cdbe = (const DBEvent*)data.iov_base;
+
+ if (cdbe->markedRead())
+ return cdbe->flags;
+
+ DBEventSortingKey keyVal = { contactID, hDbEvent, cdbe->timestamp };
+
+ MDBX_CHECK(mdbx_put(txn, m_dbEvents, &key, &data, MDBX_RESERVE), -1);
+
+ DBEvent *pNewEvent = (DBEvent*)data.iov_base;
+ *pNewEvent = *cdbe;
+
+ wRetVal = (pNewEvent->flags |= DBEF_READ);
+
+ FindNextUnread(txn, cc, keyVal);
+ key.iov_len = sizeof(MCONTACT); key.iov_base = &contactID;
+ data.iov_base = &cc->dbc; data.iov_len = sizeof(cc->dbc);
+ MDBX_CHECK(mdbx_put(txn, m_dbContacts, &key, &data, 0), -1);
+
+ if (txn.commit() == MDBX_SUCCESS)
+ break;
+ }
+
+ NotifyEventHooks(hEventMarkedRead, contactID, (LPARAM)hDbEvent);
+ return wRetVal;
+}
+
+STDMETHODIMP_(MCONTACT) CDbxMDBX::GetEventContact(MEVENT hDbEvent)
+{
+ if (hDbEvent == 0)
+ return INVALID_CONTACT_ID;
+
+ txn_ptr_ro txn(m_txn);
+
+ MDBX_val key = { &hDbEvent, sizeof(MEVENT) }, data;
+ if (mdbx_get(txn, m_dbEvents, &key, &data) != MDBX_SUCCESS)
+ return INVALID_CONTACT_ID;
+
+ return ((const DBEvent*)data.iov_base)->contactID;
+}
+
+thread_local uint64_t t_tsLast = 0;
+thread_local MEVENT t_evLast = 0;
+
+STDMETHODIMP_(MEVENT) CDbxMDBX::FindFirstEvent(MCONTACT contactID)
+{
+ DBEventSortingKey keyVal = { contactID, 0, 0 };
+ MDBX_val key = { &keyVal, sizeof(keyVal) }, data;
+
+ txn_ptr_ro txn(m_txn);
+
+ cursor_ptr_ro cursor(m_curEventsSort);
+ if (mdbx_cursor_get(cursor, &key, &data, MDBX_SET_RANGE) != MDBX_SUCCESS)
+ return t_evLast = 0;
+
+ const DBEventSortingKey *pKey = (const DBEventSortingKey*)key.iov_base;
+ t_tsLast = pKey->ts;
+ return t_evLast = (pKey->hContact == contactID) ? pKey->hEvent : 0;
+}
+
+STDMETHODIMP_(MEVENT) CDbxMDBX::FindFirstUnreadEvent(MCONTACT contactID)
+{
+ DBCachedContact *cc = m_cache->GetCachedContact(contactID);
+ return (cc == NULL) ? 0 : cc->dbc.evFirstUnread;
+}
+
+STDMETHODIMP_(MEVENT) CDbxMDBX::FindLastEvent(MCONTACT contactID)
+{
+ DBEventSortingKey keyVal = { contactID, 0xFFFFFFFF, 0xFFFFFFFFFFFFFFFF };
+ MDBX_val key = { &keyVal, sizeof(keyVal) }, data;
+
+ txn_ptr_ro txn(m_txn);
+ cursor_ptr_ro cursor(m_curEventsSort);
+
+ if (mdbx_cursor_get(cursor, &key, &data, MDBX_SET_RANGE) != MDBX_SUCCESS) {
+ if (mdbx_cursor_get(cursor, &key, &data, MDBX_LAST) != MDBX_SUCCESS)
+ return t_evLast = 0;
+ }
+ else {
+ if (mdbx_cursor_get(cursor, &key, &data, MDBX_PREV) != MDBX_SUCCESS)
+ return t_evLast = 0;
+ }
+
+ const DBEventSortingKey *pKey = (const DBEventSortingKey*)key.iov_base;
+ t_tsLast = pKey->ts;
+ return t_evLast = (pKey->hContact == contactID) ? pKey->hEvent : 0;
+}
+
+STDMETHODIMP_(MEVENT) CDbxMDBX::FindNextEvent(MCONTACT contactID, MEVENT hDbEvent)
+{
+ if (hDbEvent == 0)
+ return t_evLast = 0;
+
+ txn_ptr_ro txn(m_txn);
+
+ if (t_evLast != hDbEvent) {
+ MDBX_val key = { &hDbEvent, sizeof(MEVENT) }, data;
+ if (mdbx_get(txn, m_dbEvents, &key, &data) != MDBX_SUCCESS)
+ return 0;
+ t_tsLast = ((DBEvent*)data.iov_base)->timestamp;
+ }
+
+ DBEventSortingKey keyVal = { contactID, hDbEvent, t_tsLast };
+ MDBX_val key = { &keyVal, sizeof(keyVal) }, data;
+
+ cursor_ptr_ro cursor(m_curEventsSort);
+ if (mdbx_cursor_get(cursor, &key, &data, MDBX_SET) != MDBX_SUCCESS)
+ return t_evLast = 0;
+
+ if (mdbx_cursor_get(cursor, &key, &data, MDBX_NEXT) != MDBX_SUCCESS)
+ return t_evLast = 0;
+
+ const DBEventSortingKey *pKey = (const DBEventSortingKey*)key.iov_base;
+ t_tsLast = pKey->ts;
+ return t_evLast = (pKey->hContact == contactID) ? pKey->hEvent : 0;
+}
+
+STDMETHODIMP_(MEVENT) CDbxMDBX::FindPrevEvent(MCONTACT contactID, MEVENT hDbEvent)
+{
+ if (hDbEvent == 0)
+ return t_evLast = 0;
+
+ MDBX_val data;
+
+ txn_ptr_ro txn(m_txn);
+
+ if (t_evLast != hDbEvent) {
+ MDBX_val key = { &hDbEvent, sizeof(MEVENT) };
+ if (mdbx_get(txn, m_dbEvents, &key, &data) != MDBX_SUCCESS)
+ return 0;
+ t_tsLast = ((DBEvent*)data.iov_base)->timestamp;
+ }
+
+ DBEventSortingKey keyVal = { contactID, hDbEvent, t_tsLast };
+ MDBX_val key = { &keyVal, sizeof(keyVal) };
+
+ cursor_ptr_ro cursor(m_curEventsSort);
+ if (mdbx_cursor_get(cursor, &key, &data, MDBX_SET) != MDBX_SUCCESS)
+ return t_evLast = 0;
+
+ if (mdbx_cursor_get(cursor, &key, &data, MDBX_PREV) != MDBX_SUCCESS)
+ return t_evLast = 0;
+
+ const DBEventSortingKey *pKey = (const DBEventSortingKey*)key.iov_base;
+ t_tsLast = pKey->ts;
+ return t_evLast = (pKey->hContact == contactID) ? pKey->hEvent : 0;
+}
diff --git a/plugins/Dbx_mdbx/src/dbintf.cpp b/plugins/Dbx_mdbx/src/dbintf.cpp
new file mode 100644
index 0000000000..09d350b6c9
--- /dev/null
+++ b/plugins/Dbx_mdbx/src/dbintf.cpp
@@ -0,0 +1,279 @@
+/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (c) 2012-18 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"
+
+CDbxMDBX::CDbxMDBX(const TCHAR *tszFileName, int iMode) :
+ m_safetyMode(true),
+ m_bReadOnly((iMode & DBMODE_READONLY) != 0),
+ m_bShared((iMode & DBMODE_SHARED) != 0),
+ m_maxContactId(0)
+{
+ m_tszProfileName = mir_wstrdup(tszFileName);
+ InitDbInstance(this);
+
+ mdbx_env_create(&m_env);
+ mdbx_env_set_maxdbs(m_env, 10);
+ mdbx_env_set_userctx(m_env, this);
+ // mdbx_env_set_assert(m_env, MDBX_FailAssert);
+}
+
+CDbxMDBX::~CDbxMDBX()
+{
+ mdbx_env_close(m_env);
+
+ DestroyServiceFunction(hService);
+ UnhookEvent(hHook);
+
+ if (m_crypto)
+ m_crypto->destroy();
+
+ DestroyHookableEvent(hContactDeletedEvent);
+ DestroyHookableEvent(hContactAddedEvent);
+ DestroyHookableEvent(hSettingChangeEvent);
+ DestroyHookableEvent(hEventMarkedRead);
+
+ DestroyHookableEvent(hEventAddedEvent);
+ DestroyHookableEvent(hEventDeletedEvent);
+ DestroyHookableEvent(hEventFilterAddedEvent);
+
+ DestroyDbInstance(this);
+ mir_free(m_tszProfileName);
+}
+
+int CDbxMDBX::Load(bool bSkipInit)
+{
+ if (Map() != MDBX_SUCCESS)
+ return EGROKPRF_CANTREAD;
+
+ if (!bSkipInit) {
+ txn_ptr trnlck(m_env);
+
+ unsigned int defFlags = MDBX_CREATE;
+
+ mdbx_dbi_open(trnlck, "global", defFlags | MDBX_INTEGERKEY, &m_dbGlobal);
+ mdbx_dbi_open(trnlck, "crypto", defFlags, &m_dbCrypto);
+ mdbx_dbi_open(trnlck, "contacts", defFlags | MDBX_INTEGERKEY, &m_dbContacts);
+ mdbx_dbi_open(trnlck, "modules", defFlags | MDBX_INTEGERKEY, &m_dbModules);
+ mdbx_dbi_open(trnlck, "events", defFlags | MDBX_INTEGERKEY, &m_dbEvents);
+
+ mdbx_dbi_open_ex(trnlck, "eventsrt", defFlags, &m_dbEventsSort, DBEventSortingKey::Compare, nullptr);
+ mdbx_dbi_open_ex(trnlck, "settings", defFlags, &m_dbSettings, DBSettingKey::Compare, nullptr);
+ {
+ uint32_t keyVal = 1;
+ MDBX_val key = { &keyVal, sizeof(keyVal) }, data;
+ if (mdbx_get(trnlck, m_dbGlobal, &key, &data) == MDBX_SUCCESS) {
+ const DBHeader *hdr = (const DBHeader*)data.iov_base;
+ if (hdr->dwSignature != DBHEADER_SIGNATURE)
+ return EGROKPRF_DAMAGED;
+ if (hdr->dwVersion != DBHEADER_VERSION)
+ return EGROKPRF_OBSOLETE;
+
+ m_header = *hdr;
+ }
+ else {
+ m_header.dwSignature = DBHEADER_SIGNATURE;
+ m_header.dwVersion = DBHEADER_VERSION;
+ data.iov_base = &m_header; data.iov_len = sizeof(m_header);
+ mdbx_put(trnlck, m_dbGlobal, &key, &data, 0);
+ }
+ trnlck.commit();
+ }
+ {
+ MDBX_val key, val;
+
+ mdbx_txn_begin(m_env, nullptr, MDBX_RDONLY, &m_txn);
+
+ mdbx_cursor_open(m_txn, m_dbEvents, &m_curEvents);
+ if (mdbx_cursor_get(m_curEvents, &key, &val, MDBX_LAST) == MDBX_SUCCESS)
+ m_dwMaxEventId = *(MEVENT*)key.iov_base;
+
+ mdbx_cursor_open(m_txn, m_dbEventsSort, &m_curEventsSort);
+ mdbx_cursor_open(m_txn, m_dbSettings, &m_curSettings);
+ mdbx_cursor_open(m_txn, m_dbModules, &m_curModules);
+
+ mdbx_cursor_open(m_txn, m_dbContacts, &m_curContacts);
+ if (mdbx_cursor_get(m_curContacts, &key, &val, MDBX_LAST) == MDBX_SUCCESS)
+ m_maxContactId = *(MCONTACT*)key.iov_base;
+
+ MDBX_stat st;
+ mdbx_dbi_stat(m_txn, m_dbContacts, &st, sizeof(st));
+ m_contactCount = st.ms_entries;
+
+ mdbx_txn_reset(m_txn);
+ }
+
+
+ if (InitModules()) return EGROKPRF_DAMAGED;
+ if (InitCrypt()) return EGROKPRF_DAMAGED;
+
+ // everything is ok, go on
+ if (!m_bReadOnly) {
+ // retrieve the event handles
+ hContactDeletedEvent = CreateHookableEvent(ME_DB_CONTACT_DELETED);
+ hContactAddedEvent = CreateHookableEvent(ME_DB_CONTACT_ADDED);
+ hSettingChangeEvent = CreateHookableEvent(ME_DB_CONTACT_SETTINGCHANGED);
+ hEventMarkedRead = CreateHookableEvent(ME_DB_EVENT_MARKED_READ);
+
+ hEventAddedEvent = CreateHookableEvent(ME_DB_EVENT_ADDED);
+ hEventDeletedEvent = CreateHookableEvent(ME_DB_EVENT_DELETED);
+ hEventFilterAddedEvent = CreateHookableEvent(ME_DB_EVENT_FILTER_ADD);
+ }
+
+ FillContacts();
+ }
+
+ return EGROKPRF_NOERROR;
+}
+
+int CDbxMDBX::Create(void)
+{
+ return (Map() == MDBX_SUCCESS) ? 0 : EGROKPRF_CANTREAD;
+}
+
+size_t iDefHeaderOffset = 0;
+BYTE bDefHeader[] = { 0 };
+
+int CDbxMDBX::Check(void)
+{
+ FILE *pFile = _wfopen(m_tszProfileName, L"rb");
+ if (pFile == nullptr)
+ return EGROKPRF_CANTREAD;
+
+ fseek(pFile, (LONG)iDefHeaderOffset, SEEK_SET);
+ BYTE buf[_countof(bDefHeader)];
+ size_t cbRead = fread(buf, 1, _countof(buf), pFile);
+ fclose(pFile);
+ if (cbRead != _countof(buf))
+ return EGROKPRF_DAMAGED;
+
+ return (memcmp(buf, bDefHeader, _countof(bDefHeader))) ? EGROKPRF_UNKHEADER : 0;
+}
+
+int CDbxMDBX::PrepareCheck(int*)
+{
+ InitModules();
+ return InitCrypt();
+}
+
+STDMETHODIMP_(void) CDbxMDBX::SetCacheSafetyMode(BOOL bIsSet)
+{
+ m_safetyMode = bIsSet != 0;
+}
+
+int CDbxMDBX::Map()
+{
+ unsigned int mode = MDBX_NOSUBDIR | MDBX_MAPASYNC | MDBX_WRITEMAP | MDBX_NOSYNC;
+ if (m_bReadOnly)
+ mode |= MDBX_RDONLY;
+ mdbx_env_open(m_env, _T2A(m_tszProfileName), mode, 0664);
+ mdbx_env_set_mapsize(m_env, 0x1000000);
+ return MDBX_SUCCESS;
+}
+
+bool CDbxMDBX::Remap()
+{
+ MDBX_envinfo ei;
+ mdbx_env_info(m_env, &ei, sizeof(ei));
+ return mdbx_env_set_geometry(m_env, -1, -1, ei.mi_mapsize + 0x100000, 0x100000, -1, -1) == MDBX_SUCCESS;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static DWORD DatabaseCorrupted = 0;
+static const TCHAR *msg = NULL;
+static DWORD dwErr = 0;
+static wchar_t tszPanic[] = LPGENW("Miranda has detected corruption in your database. This corruption may be fixed by DbChecker plugin. Please download it from https://miranda-ng.org/p/DbChecker/. Miranda will now shut down.");
+
+EXTERN_C void __cdecl dbpanic(void *)
+{
+ if (msg) {
+ if (dwErr == ERROR_DISK_FULL)
+ msg = TranslateT("Disk is full. Miranda will now shut down.");
+
+ TCHAR err[256];
+ mir_snwprintf(err, msg, TranslateT("Database failure. Miranda will now shut down."), dwErr);
+
+ MessageBox(0, err, TranslateT("Database Error"), MB_SETFOREGROUND | MB_TOPMOST | MB_APPLMODAL | MB_ICONWARNING | MB_OK);
+ }
+ else MessageBox(0, TranslateW(tszPanic), TranslateT("Database Panic"), MB_SETFOREGROUND | MB_TOPMOST | MB_APPLMODAL | MB_ICONWARNING | MB_OK);
+ TerminateProcess(GetCurrentProcess(), 255);
+}
+
+
+EXTERN_C void MDBX_FailAssert(MDBX_env *env, const char *text)
+{
+ ((CDbxMDBX*)mdbx_env_get_userctx(env))->DatabaseCorruption(_A2T(text));
+}
+
+EXTERN_C void MDBX_Log(const char *fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ Netlib_Log(0, CMStringA().FormatV(fmt, args));
+ va_end(args);
+}
+
+void CDbxMDBX::DatabaseCorruption(const TCHAR *text)
+{
+ int kill = 0;
+
+ if (DatabaseCorrupted == 0) {
+ DatabaseCorrupted++;
+ kill++;
+ msg = text;
+ dwErr = GetLastError();
+ }
+ else {
+ /* db is already corrupted, someone else is dealing with it, wait here
+ so that we don't do any more damage */
+ Sleep(INFINITE);
+ return;
+ }
+
+ if (kill) {
+ _beginthread(dbpanic, 0, NULL);
+ Sleep(INFINITE);
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// MIDatabaseChecker
+
+typedef int (CDbxMDBX::*CheckWorker)(int);
+
+int CDbxMDBX::Start(DBCHeckCallback *callback)
+{
+ cb = callback;
+ return ERROR_SUCCESS;
+}
+
+int CDbxMDBX::CheckDb(int, int)
+{
+ return ERROR_OUT_OF_PAPER;
+}
+
+void CDbxMDBX::Destroy()
+{
+ delete this;
+}
diff --git a/plugins/Dbx_mdbx/src/dbintf.h b/plugins/Dbx_mdbx/src/dbintf.h
new file mode 100644
index 0000000000..fa5595dba3
--- /dev/null
+++ b/plugins/Dbx_mdbx/src/dbintf.h
@@ -0,0 +1,268 @@
+/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (c) 2012-18 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.
+*/
+
+#define OWN_CACHED_CONTACT
+
+#include <m_db_int.h>
+
+#pragma warning (disable: 4200)
+
+#define DBMODE_SHARED 0x0001
+#define DBMODE_READONLY 0x0002
+
+#define DBVT_ENCRYPTED 250
+#define DBVT_UNENCRYPTED 251
+
+#define MARKED_READ (DBEF_READ | DBEF_SENT)
+
+#include <pshpack1.h>
+
+#define DBHEADER_VERSION MAKELONG(1, 4)
+
+#define DBHEADER_SIGNATURE 0x40DECADEu
+struct DBHeader
+{
+ uint32_t dwSignature;
+ uint32_t dwVersion; // database format version
+};
+
+struct DBContact
+{
+ uint32_t dwEventCount; // number of events in the chain for this contact
+ MEVENT evFirstUnread;
+ uint64_t tsFirstUnread;
+};
+
+struct DBEvent
+{
+ MCONTACT contactID; // a contact this event belongs to
+ uint32_t iModuleId; // offset to a DBModuleName struct of the name of
+ uint64_t timestamp; // seconds since 00:00:00 01/01/1970
+ uint32_t flags; // see m_database.h, db/event/add
+ uint16_t wEventType; // module-defined event type
+ uint16_t cbBlob; // number of bytes in the blob
+
+ bool __forceinline markedRead() const
+ {
+ return (flags & MARKED_READ) != 0;
+ }
+};
+
+struct DBEventSortingKey
+{
+ MCONTACT hContact;
+ MEVENT hEvent;
+ uint64_t ts;
+
+ static int Compare(const MDBX_val* a, const MDBX_val* b);
+};
+
+struct DBSettingKey
+{
+ MCONTACT hContact;
+ uint32_t dwModuleId;
+ char szSettingName[1];
+
+ static int Compare(const MDBX_val*, const MDBX_val*);
+};
+
+struct DBSettingValue
+{
+ BYTE type;
+ union
+ {
+ BYTE bVal;
+ WORD wVal;
+ DWORD dwVal;
+ char szVal[];
+
+ struct
+ {
+ size_t nLength;
+ BYTE bVal[];
+ } blob;
+ };
+};
+
+#include <poppack.h>
+
+struct DBCachedContact : public DBCachedContactBase
+{
+ void Advance(MEVENT id, DBEvent &dbe);
+ void Snapshot();
+ void Revert();
+ DBContact dbc, tmp_dbc;
+};
+
+struct EventItem
+{
+ __forceinline EventItem(int _ts, MEVENT _id) :
+ ts(_ts), eventId(_id)
+ {}
+
+ uint64_t ts;
+ MEVENT eventId;
+};
+
+struct CDbxMDBX : public MDatabaseCommon, public MIDatabaseChecker, public MZeroedObject
+{
+ friend class MDBXEventCursor;
+
+ CDbxMDBX(const TCHAR *tszFileName, int mode);
+ virtual ~CDbxMDBX();
+
+ int Load(bool bSkipInit);
+ int Create(void);
+ int Check(void);
+
+ void DatabaseCorruption(const TCHAR *ptszText);
+
+ void StoreKey(void);
+ void SetPassword(const wchar_t *ptszPassword);
+ void UpdateMenuItem(void);
+
+ int PrepareCheck(int*);
+
+ __forceinline LPSTR GetMenuTitle() const { return m_bUsesPassword ? (char*)LPGEN("Change/remove password") : (char*)LPGEN("Set password"); }
+
+ __forceinline bool isEncrypted() const { return m_bEncrypted; }
+ __forceinline bool usesPassword() const { return m_bUsesPassword; }
+ int EnableEncryption(bool bEnable);
+public:
+ STDMETHODIMP_(BOOL) IsRelational(void) { return TRUE; }
+ STDMETHODIMP_(void) SetCacheSafetyMode(BOOL);
+
+ STDMETHODIMP_(LONG) GetContactCount(void);
+ STDMETHODIMP_(LONG) DeleteContact(MCONTACT contactID);
+ STDMETHODIMP_(MCONTACT) AddContact(void);
+ STDMETHODIMP_(BOOL) IsDbContact(MCONTACT contactID);
+ STDMETHODIMP_(LONG) GetContactSize(void);
+
+ STDMETHODIMP_(LONG) GetEventCount(MCONTACT contactID);
+ STDMETHODIMP_(MEVENT) AddEvent(MCONTACT contactID, DBEVENTINFO *dbe);
+ STDMETHODIMP_(BOOL) DeleteEvent(MCONTACT contactID, MEVENT hDbEvent);
+ STDMETHODIMP_(LONG) GetBlobSize(MEVENT hDbEvent);
+ STDMETHODIMP_(BOOL) GetEvent(MEVENT hDbEvent, DBEVENTINFO *dbe);
+ STDMETHODIMP_(BOOL) MarkEventRead(MCONTACT contactID, MEVENT hDbEvent);
+ STDMETHODIMP_(MCONTACT) GetEventContact(MEVENT hDbEvent);
+ STDMETHODIMP_(MEVENT) FindFirstEvent(MCONTACT contactID);
+ STDMETHODIMP_(MEVENT) FindFirstUnreadEvent(MCONTACT contactID);
+ STDMETHODIMP_(MEVENT) FindLastEvent(MCONTACT contactID);
+ STDMETHODIMP_(MEVENT) FindNextEvent(MCONTACT contactID, MEVENT hDbEvent);
+ STDMETHODIMP_(MEVENT) FindPrevEvent(MCONTACT contactID, MEVENT hDbEvent);
+
+ STDMETHODIMP_(BOOL) EnumModuleNames(DBMODULEENUMPROC pFunc, void *pParam);
+
+ STDMETHODIMP_(BOOL) GetContactSettingWorker(MCONTACT contactID, LPCSTR szModule, LPCSTR szSetting, DBVARIANT *dbv, int isStatic);
+ STDMETHODIMP_(BOOL) WriteContactSetting(MCONTACT contactID, DBCONTACTWRITESETTING *dbcws);
+ STDMETHODIMP_(BOOL) DeleteContactSetting(MCONTACT contactID, LPCSTR szModule, LPCSTR szSetting);
+ STDMETHODIMP_(BOOL) EnumContactSettings(MCONTACT hContact, DBSETTINGENUMPROC pfnEnumProc, const char *szModule, void *param);
+
+ STDMETHODIMP_(BOOL) MetaMergeHistory(DBCachedContact *ccMeta, DBCachedContact *ccSub);
+ STDMETHODIMP_(BOOL) MetaSplitHistory(DBCachedContact *ccMeta, DBCachedContact *ccSub);
+
+protected:
+ STDMETHODIMP_(BOOL) Start(DBCHeckCallback *callback);
+ STDMETHODIMP_(BOOL) CheckDb(int phase, int firstTime);
+ STDMETHODIMP_(VOID) Destroy();
+
+protected:
+
+ void FillContacts(void);
+
+ int Map();
+ bool Remap();
+
+protected:
+ TCHAR* m_tszProfileName;
+ bool m_safetyMode, m_bReadOnly, m_bShared, m_bEncrypted, m_bUsesPassword;
+
+ ////////////////////////////////////////////////////////////////////////////
+ // database stuff
+public:
+ MICryptoEngine *m_crypto;
+
+protected:
+ MDBX_env *m_env;
+ CMDBX_txn_ro m_txn;
+
+ MDBX_dbi m_dbGlobal;
+ DBHeader m_header;
+
+ HANDLE hSettingChangeEvent, hContactDeletedEvent, hContactAddedEvent, hEventMarkedRead;
+
+ ////////////////////////////////////////////////////////////////////////////
+ // settings
+
+ MDBX_dbi m_dbSettings;
+ MDBX_cursor *m_curSettings;
+
+ HANDLE hService, hHook;
+
+ ////////////////////////////////////////////////////////////////////////////
+ // contacts
+
+ MDBX_dbi m_dbContacts;
+ MDBX_cursor *m_curContacts;
+
+ uint32_t m_contactCount;
+ MCONTACT m_maxContactId;
+
+ void GatherContactHistory(MCONTACT hContact, LIST<EventItem> &items);
+
+ ////////////////////////////////////////////////////////////////////////////
+ // events
+
+ MDBX_dbi m_dbEvents, m_dbEventsSort;
+ MDBX_cursor *m_curEvents, *m_curEventsSort;
+ MEVENT m_dwMaxEventId;
+
+ HANDLE hEventAddedEvent, hEventDeletedEvent, hEventFilterAddedEvent;
+
+ void FindNextUnread(const txn_ptr &_txn, DBCachedContact *cc, DBEventSortingKey &key2);
+
+ ////////////////////////////////////////////////////////////////////////////
+ // modules
+
+ MDBX_dbi m_dbModules;
+ MDBX_cursor *m_curModules;
+
+ std::map<uint32_t, std::string> m_Modules;
+
+ int InitModules();
+
+ uint32_t GetModuleID(const char *szName);
+ char* GetModuleName(uint32_t dwId);
+
+ DBCHeckCallback *cb;
+
+ ////////////////////////////////////////////////////////////////////////////
+ // encryption
+
+ MDBX_dbi m_dbCrypto;
+
+ int InitCrypt(void);
+ CRYPTO_PROVIDER* SelectProvider();
+
+ void InitDialogs();
+}; \ No newline at end of file
diff --git a/plugins/Dbx_mdbx/src/dbmodulechain.cpp b/plugins/Dbx_mdbx/src/dbmodulechain.cpp
new file mode 100644
index 0000000000..b9b29b4c5a
--- /dev/null
+++ b/plugins/Dbx_mdbx/src/dbmodulechain.cpp
@@ -0,0 +1,72 @@
+/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (c) 2012-18 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::InitModules()
+{
+ txn_ptr_ro trnlck(m_txn);
+ cursor_ptr_ro cursor(m_curModules);
+
+ MDBX_val key, data;
+ while (mdbx_cursor_get(cursor, &key, &data, MDBX_NEXT) == MDBX_SUCCESS) {
+ uint32_t iMod = *(uint32_t*)key.iov_base;
+ const char *szMod = (const char*)data.iov_base;
+ m_Modules[iMod] = szMod;
+ }
+ return 0;
+}
+
+// will create the offset if it needs to
+uint32_t CDbxMDBX::GetModuleID(const char *szName)
+{
+ uint32_t iHash = mir_hashstr(szName);
+ if (m_Modules.find(iHash) == m_Modules.end()) {
+ MDBX_val key = { &iHash, sizeof(iHash) }, data = { (void*)szName, strlen(szName) + 1 };
+
+ for (;; Remap()) {
+ txn_ptr txn(m_env);
+ MDBX_CHECK(mdbx_put(txn, m_dbModules, &key, &data, 0), -1);
+ if (txn.commit() == MDBX_SUCCESS)
+ break;
+ }
+ m_Modules[iHash] = szName;
+ }
+
+ return iHash;
+}
+
+char* CDbxMDBX::GetModuleName(uint32_t dwId)
+{
+ auto it = m_Modules.find(dwId);
+ return it != m_Modules.end() ? const_cast<char*>(it->second.c_str()) : nullptr;
+}
+
+STDMETHODIMP_(BOOL) CDbxMDBX::EnumModuleNames(DBMODULEENUMPROC pFunc, void *pParam)
+{
+ for (auto it = m_Modules.begin(); it != m_Modules.end(); ++it)
+ if (int ret = pFunc(it->second.c_str(), pParam))
+ return ret;
+
+ return 0;
+}
diff --git a/plugins/Dbx_mdbx/src/dbsettings.cpp b/plugins/Dbx_mdbx/src/dbsettings.cpp
new file mode 100644
index 0000000000..eacf9d82c4
--- /dev/null
+++ b/plugins/Dbx_mdbx/src/dbsettings.cpp
@@ -0,0 +1,398 @@
+/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (c) 2012-18 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"
+
+#define VLT(n) ((n == DBVT_UTF8 || n == DBVT_ENCRYPTED)?DBVT_ASCIIZ:n)
+
+static bool ValidLookupName(LPCSTR szModule, LPCSTR szSetting)
+{
+ if (!strcmp(szModule, META_PROTO))
+ return strcmp(szSetting, "IsSubcontact") && strcmp(szSetting, "ParentMetaID");
+
+ if (!strcmp(szModule, "Ignore"))
+ return false;
+
+ return true;
+}
+
+int CDbxMDBX::GetContactSettingWorker(MCONTACT contactID, LPCSTR szModule, LPCSTR szSetting, DBVARIANT *dbv, int isStatic)
+{
+ if (szSetting == NULL || szModule == NULL)
+ return 1;
+
+ size_t settingNameLen = strlen(szSetting);
+ size_t moduleNameLen = strlen(szModule);
+
+LBL_Seek:
+ char *szCachedSettingName = m_cache->GetCachedSetting(szModule, szSetting, moduleNameLen, settingNameLen);
+
+ DBVARIANT *pCachedValue = m_cache->GetCachedValuePtr(contactID, szCachedSettingName, 0);
+ if (pCachedValue != NULL) {
+ if (pCachedValue->type == DBVT_ASCIIZ || pCachedValue->type == DBVT_UTF8) {
+ int cbOrigLen = dbv->cchVal;
+ char *cbOrigPtr = dbv->pszVal;
+ memcpy(dbv, pCachedValue, sizeof(DBVARIANT));
+ if (isStatic) {
+ int cbLen = 0;
+ if (pCachedValue->pszVal != NULL)
+ cbLen = (int)strlen(pCachedValue->pszVal);
+
+ cbOrigLen--;
+ dbv->pszVal = cbOrigPtr;
+ if (cbLen < cbOrigLen)
+ cbOrigLen = cbLen;
+ memcpy(dbv->pszVal, pCachedValue->pszVal, cbOrigLen);
+ dbv->pszVal[cbOrigLen] = 0;
+ dbv->cchVal = cbLen;
+ }
+ else {
+ dbv->pszVal = (char*)mir_alloc(strlen(pCachedValue->pszVal) + 1);
+ strcpy(dbv->pszVal, pCachedValue->pszVal);
+ }
+ }
+ else memcpy(dbv, pCachedValue, sizeof(DBVARIANT));
+
+ return (pCachedValue->type == DBVT_DELETED) ? 1 : 0;
+ }
+
+ // never look db for the resident variable
+ if (szCachedSettingName[-1] != 0)
+ return 1;
+
+ DBCachedContact *cc = (contactID) ? m_cache->GetCachedContact(contactID) : NULL;
+
+ txn_ptr_ro trnlck(m_txn);
+
+ DBSettingKey *keyVal = (DBSettingKey *)_alloca(sizeof(DBSettingKey) + settingNameLen);
+ keyVal->hContact = contactID;
+ keyVal->dwModuleId = GetModuleID(szModule);
+ memcpy(&keyVal->szSettingName, szSetting, settingNameLen + 1);
+
+
+ MDBX_val key = { keyVal, sizeof(DBSettingKey) + settingNameLen }, data;
+ if (mdbx_get(trnlck, m_dbSettings, &key, &data) != MDBX_SUCCESS) {
+ // try to get the missing mc setting from the active sub
+ if (cc && cc->IsMeta() && ValidLookupName(szModule, szSetting)) {
+ if (contactID = db_mc_getDefault(contactID)) {
+ if (szModule = GetContactProto(contactID)) {
+ moduleNameLen = strlen(szModule);
+ goto LBL_Seek;
+ }
+ }
+ }
+ return 1;
+ }
+
+ const BYTE *pBlob = (const BYTE*)data.iov_base;
+ if (isStatic && (pBlob[0] & DBVTF_VARIABLELENGTH) && VLT(dbv->type) != VLT(pBlob[0]))
+ return 1;
+
+ int varLen;
+ BYTE iType = dbv->type = pBlob[0]; pBlob++;
+ switch (iType) {
+ case DBVT_DELETED: /* this setting is deleted */
+ dbv->type = DBVT_DELETED;
+ return 2;
+
+ case DBVT_BYTE: dbv->bVal = *pBlob; break;
+ case DBVT_WORD: dbv->wVal = *(WORD*)pBlob; break;
+ case DBVT_DWORD: dbv->dVal = *(DWORD*)pBlob; break;
+
+ case DBVT_UTF8:
+ case DBVT_ASCIIZ:
+ varLen = *(WORD*)pBlob;
+ pBlob += 2;
+ if (isStatic) {
+ dbv->cchVal--;
+ if (varLen < dbv->cchVal)
+ dbv->cchVal = varLen;
+ memcpy(dbv->pszVal, pBlob, dbv->cchVal); // decode
+ dbv->pszVal[dbv->cchVal] = 0;
+ dbv->cchVal = varLen;
+ }
+ else {
+ dbv->pszVal = (char*)mir_alloc(1 + varLen);
+ memcpy(dbv->pszVal, pBlob, varLen);
+ dbv->pszVal[varLen] = 0;
+ }
+ break;
+
+ case DBVT_BLOB:
+ varLen = *(WORD*)pBlob;
+ pBlob += 2;
+ if (isStatic) {
+ if (varLen < dbv->cpbVal)
+ dbv->cpbVal = varLen;
+ memcpy(dbv->pbVal, pBlob, dbv->cpbVal);
+ }
+ else {
+ dbv->pbVal = (BYTE *)mir_alloc(varLen);
+ memcpy(dbv->pbVal, pBlob, varLen);
+ }
+ dbv->cpbVal = varLen;
+ break;
+
+ case DBVT_ENCRYPTED:
+ if (m_crypto == NULL)
+ return 1;
+
+ varLen = *(WORD*)pBlob;
+ pBlob += 2;
+
+ size_t realLen;
+ ptrA decoded(m_crypto->decodeString(pBlob, varLen, &realLen));
+ if (decoded == NULL)
+ return 1;
+
+ varLen = (WORD)realLen;
+ dbv->type = DBVT_UTF8;
+ if (isStatic) {
+ dbv->cchVal--;
+ if (varLen < dbv->cchVal)
+ dbv->cchVal = varLen;
+ memcpy(dbv->pszVal, decoded, dbv->cchVal);
+ dbv->pszVal[dbv->cchVal] = 0;
+ dbv->cchVal = varLen;
+ }
+ else {
+ dbv->pszVal = (char*)mir_alloc(1 + varLen);
+ memcpy(dbv->pszVal, decoded, varLen);
+ dbv->pszVal[varLen] = 0;
+ }
+ break;
+ }
+
+ /**** add to cache **********************/
+ if (iType != DBVT_BLOB && iType != DBVT_ENCRYPTED) {
+ pCachedValue = m_cache->GetCachedValuePtr(contactID, szCachedSettingName, 1);
+ if (pCachedValue != NULL)
+ m_cache->SetCachedVariant(dbv, pCachedValue);
+ }
+
+ return 0;
+}
+
+STDMETHODIMP_(BOOL) CDbxMDBX::WriteContactSetting(MCONTACT contactID, DBCONTACTWRITESETTING *dbcws)
+{
+ if (dbcws == NULL || dbcws->szSetting == NULL || dbcws->szModule == NULL || m_bReadOnly)
+ return 1;
+
+ // the db format can't tolerate more than 255 bytes of space (incl. null) for settings+module name
+ size_t settingNameLen = strlen(dbcws->szSetting);
+ size_t moduleNameLen = strlen(dbcws->szModule);
+
+ // used for notifications
+ DBCONTACTWRITESETTING dbcwNotif = *dbcws;
+ if (dbcwNotif.value.type == DBVT_WCHAR) {
+ if (dbcwNotif.value.pszVal != NULL) {
+ T2Utf val(dbcwNotif.value.pwszVal);
+ if (val == NULL)
+ return 1;
+
+ dbcwNotif.value.pszVal = NEWSTR_ALLOCA(val);
+ dbcwNotif.value.type = DBVT_UTF8;
+ }
+ else return 1;
+ }
+
+ if (dbcwNotif.szModule == NULL || dbcwNotif.szSetting == NULL)
+ return 1;
+
+ DBCONTACTWRITESETTING dbcwWork = dbcwNotif;
+
+ mir_ptr<BYTE> pEncoded(NULL);
+ bool bIsEncrypted = false;
+ switch (dbcwWork.value.type) {
+ case DBVT_BYTE: case DBVT_WORD: case DBVT_DWORD:
+ break;
+
+ case DBVT_ASCIIZ: case DBVT_UTF8:
+ bIsEncrypted = m_bEncrypted || IsSettingEncrypted(dbcws->szModule, dbcws->szSetting);
+LBL_WriteString:
+ if (dbcwWork.value.pszVal == NULL)
+ return 1;
+ dbcwWork.value.cchVal = (WORD)strlen(dbcwWork.value.pszVal);
+ if (bIsEncrypted) {
+ size_t len;
+ BYTE *pResult = m_crypto->encodeString(dbcwWork.value.pszVal, &len);
+ if (pResult != NULL) {
+ pEncoded = dbcwWork.value.pbVal = pResult;
+ dbcwWork.value.cpbVal = (WORD)len;
+ dbcwWork.value.type = DBVT_ENCRYPTED;
+ }
+ }
+ break;
+
+ case DBVT_UNENCRYPTED:
+ dbcwNotif.value.type = dbcwWork.value.type = DBVT_UTF8;
+ goto LBL_WriteString;
+
+ case DBVT_BLOB: case DBVT_ENCRYPTED:
+ if (dbcwWork.value.pbVal == NULL)
+ return 1;
+ break;
+ default:
+ return 1;
+ }
+
+ char *szCachedSettingName = m_cache->GetCachedSetting(dbcwWork.szModule, dbcwWork.szSetting, moduleNameLen, settingNameLen);
+
+ // we don't cache blobs and passwords
+ if (dbcwWork.value.type != DBVT_BLOB && dbcwWork.value.type != DBVT_ENCRYPTED && !bIsEncrypted) {
+ DBVARIANT *pCachedValue = m_cache->GetCachedValuePtr(contactID, szCachedSettingName, 1);
+ if (pCachedValue != NULL) {
+ bool bIsIdentical = false;
+ if (pCachedValue->type == dbcwWork.value.type) {
+ switch (dbcwWork.value.type) {
+ case DBVT_BYTE: bIsIdentical = pCachedValue->bVal == dbcwWork.value.bVal; break;
+ case DBVT_WORD: bIsIdentical = pCachedValue->wVal == dbcwWork.value.wVal; break;
+ case DBVT_DWORD: bIsIdentical = pCachedValue->dVal == dbcwWork.value.dVal; break;
+ case DBVT_UTF8:
+ case DBVT_ASCIIZ: bIsIdentical = strcmp(pCachedValue->pszVal, dbcwWork.value.pszVal) == 0; break;
+ }
+ if (bIsIdentical)
+ return 0;
+ }
+ m_cache->SetCachedVariant(&dbcwWork.value, pCachedValue);
+ }
+ if (szCachedSettingName[-1] != 0) {
+ NotifyEventHooks(hSettingChangeEvent, contactID, (LPARAM)&dbcwWork);
+ return 0;
+ }
+ }
+ else m_cache->GetCachedValuePtr(contactID, szCachedSettingName, -1);
+
+ DBSettingKey *keyVal = (DBSettingKey *)_alloca(sizeof(DBSettingKey) + settingNameLen);
+ keyVal->hContact = contactID;
+ keyVal->dwModuleId = GetModuleID(dbcws->szModule);
+ memcpy(&keyVal->szSettingName, dbcws->szSetting, settingNameLen + 1);
+
+
+ MDBX_val key = { keyVal, sizeof(DBSettingKey) + settingNameLen }, data;
+
+ switch (dbcwWork.value.type) {
+ case DBVT_BYTE: data.iov_len = 2; break;
+ case DBVT_WORD: data.iov_len = 3; break;
+ case DBVT_DWORD: data.iov_len = 5; break;
+
+ case DBVT_ASCIIZ:
+ case DBVT_UTF8:
+ data.iov_len = 3 + dbcwWork.value.cchVal; break;
+
+ case DBVT_BLOB:
+ case DBVT_ENCRYPTED:
+ data.iov_len = 3 + dbcwWork.value.cpbVal; break;
+ }
+
+ for (;; Remap()) {
+ txn_ptr trnlck(m_env);
+ MDBX_CHECK(mdbx_put(trnlck, m_dbSettings, &key, &data, MDBX_RESERVE), 1);
+
+ BYTE *pBlob = (BYTE*)data.iov_base;
+ *pBlob++ = dbcwWork.value.type;
+ switch (dbcwWork.value.type) {
+ case DBVT_BYTE: *pBlob = dbcwWork.value.bVal; break;
+ case DBVT_WORD: *(WORD*)pBlob = dbcwWork.value.wVal; break;
+ case DBVT_DWORD: *(DWORD*)pBlob = dbcwWork.value.dVal; break;
+
+ case DBVT_ASCIIZ:
+ case DBVT_UTF8:
+ data.iov_len = *(WORD*)pBlob = dbcwWork.value.cchVal;
+ pBlob += 2;
+ memcpy(pBlob, dbcwWork.value.pszVal, dbcwWork.value.cchVal);
+ break;
+
+ case DBVT_BLOB:
+ case DBVT_ENCRYPTED:
+ data.iov_len = *(WORD*)pBlob = dbcwWork.value.cpbVal;
+ pBlob += 2;
+ memcpy(pBlob, dbcwWork.value.pbVal, dbcwWork.value.cpbVal);
+ }
+
+ if (trnlck.commit() == MDBX_SUCCESS)
+ break;
+ }
+
+ // notify
+ NotifyEventHooks(hSettingChangeEvent, contactID, (LPARAM)&dbcwNotif);
+ return 0;
+}
+
+STDMETHODIMP_(BOOL) CDbxMDBX::DeleteContactSetting(MCONTACT contactID, LPCSTR szModule, LPCSTR szSetting)
+{
+ if (!szModule || !szSetting)
+ return 1;
+
+ size_t settingNameLen = strlen(szSetting);
+ size_t moduleNameLen = strlen(szModule);
+
+ char *szCachedSettingName = m_cache->GetCachedSetting(szModule, szSetting, moduleNameLen, settingNameLen);
+
+ if (szCachedSettingName[-1] == 0) // it's not a resident variable
+ {
+ DBSettingKey *keyVal = (DBSettingKey*)_alloca(sizeof(DBSettingKey) + settingNameLen);
+ keyVal->hContact = contactID;
+ keyVal->dwModuleId = GetModuleID(szModule);
+ memcpy(&keyVal->szSettingName, szSetting, settingNameLen + 1);
+
+ MDBX_val key = { keyVal, sizeof(DBSettingKey) + settingNameLen };
+
+ for (;; Remap()) {
+ txn_ptr trnlck(m_env);
+ MDBX_CHECK(mdbx_del(trnlck, m_dbSettings, &key, nullptr), 1);
+ if (trnlck.commit() == MDBX_SUCCESS)
+ break;
+ }
+ }
+
+ m_cache->GetCachedValuePtr(contactID, szCachedSettingName, -1);
+
+ // notify
+ DBCONTACTWRITESETTING dbcws = { 0 };
+ dbcws.szModule = szModule;
+ dbcws.szSetting = szSetting;
+ dbcws.value.type = DBVT_DELETED;
+ NotifyEventHooks(hSettingChangeEvent, contactID, (LPARAM)&dbcws);
+ return 0;
+}
+
+STDMETHODIMP_(BOOL) CDbxMDBX::EnumContactSettings(MCONTACT hContact, DBSETTINGENUMPROC pfnEnumProc, const char *szModule, void *param)
+{
+ int result = -1;
+
+ DBSettingKey keyVal = { hContact, GetModuleID(szModule), 0 };
+ txn_ptr_ro txn(m_txn);
+ cursor_ptr_ro cursor(m_curSettings);
+
+ MDBX_val key = { &keyVal, sizeof(keyVal) }, data;
+
+ for (int res = mdbx_cursor_get(cursor, &key, &data, MDBX_SET_RANGE); res == MDBX_SUCCESS; res = mdbx_cursor_get(cursor, &key, &data, MDBX_NEXT)) {
+ const DBSettingKey *pKey = (const DBSettingKey*)key.iov_base;
+ if (pKey->hContact != hContact || pKey->dwModuleId != keyVal.dwModuleId)
+ break;
+ result = pfnEnumProc(pKey->szSettingName, param);
+ }
+
+ return result;
+}
diff --git a/plugins/Dbx_mdbx/src/dbutils.cpp b/plugins/Dbx_mdbx/src/dbutils.cpp
new file mode 100644
index 0000000000..147b43ef30
--- /dev/null
+++ b/plugins/Dbx_mdbx/src/dbutils.cpp
@@ -0,0 +1,47 @@
+/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (c) 2012-18 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"
+
+#define CMP_UINT(x, y) { if ((x) != (y)) return (x) < (y) ? -1 : 1; }
+
+int DBEventSortingKey::Compare(const MDBX_val *ax, const MDBX_val *bx)
+{
+ const DBEventSortingKey *a = (DBEventSortingKey *)ax->iov_base;
+ const DBEventSortingKey *b = (DBEventSortingKey *)bx->iov_base;
+
+ CMP_UINT(a->hContact, b->hContact);
+ CMP_UINT(a->ts, b->ts);
+ CMP_UINT(a->hEvent, b->hEvent);
+ return 0;
+}
+
+int DBSettingKey::Compare(const MDBX_val *ax, const MDBX_val *bx)
+{
+ const DBSettingKey *a = (DBSettingKey *)ax->iov_base;
+ const DBSettingKey *b = (DBSettingKey *)bx->iov_base;
+
+ CMP_UINT(a->hContact, b->hContact);
+ CMP_UINT(a->dwModuleId, b->dwModuleId);
+ return strcmp(a->szSettingName, b->szSettingName);
+}
diff --git a/plugins/Dbx_mdbx/src/init.cpp b/plugins/Dbx_mdbx/src/init.cpp
new file mode 100644
index 0000000000..5ab23c14f8
--- /dev/null
+++ b/plugins/Dbx_mdbx/src/init.cpp
@@ -0,0 +1,133 @@
+/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (c) 2012-18 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 hLangpack;
+
+static PLUGININFOEX pluginInfo =
+{
+ sizeof(PLUGININFOEX),
+ __PLUGIN_NAME,
+ PLUGIN_MAKE_VERSION(__MAJOR_VERSION, __MINOR_VERSION, __RELEASE_NUM, __BUILD_NUM),
+ __DESCRIPTION,
+ __AUTHOR,
+ __COPYRIGHT,
+ __AUTHORWEB,
+ UNICODE_AWARE | STATIC_PLUGIN,
+ // {7C3D0A33-2646-4001-9107-F35EA299D292}
+ { 0x7c3d0a33, 0x2646, 0x4001, { 0x91, 0x7, 0xf3, 0x5e, 0xa2, 0x99, 0xd2, 0x92 } }
+};
+
+HINSTANCE g_hInst = NULL;
+
+LIST<CDbxMDBX> g_Dbs(1, HandleKeySortT);
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+// returns 0 if the profile is created, EMKPRF*
+static int makeDatabase(const TCHAR *profile)
+{
+ std::unique_ptr<CDbxMDBX> db(new CDbxMDBX(profile, 0));
+ return db->Create();
+}
+
+// returns 0 if the given profile has a valid header
+static int grokHeader(const TCHAR *profile)
+{
+ std::unique_ptr<CDbxMDBX> db(new CDbxMDBX(profile, DBMODE_SHARED | DBMODE_READONLY));
+ return db->Check();
+}
+
+// returns 0 if all the APIs are injected otherwise, 1
+static MIDatabase* LoadDatabase(const TCHAR *profile, BOOL bReadOnly)
+{
+ // set the memory, lists & UTF8 manager
+ mir_getLP(&pluginInfo);
+
+ std::unique_ptr<CDbxMDBX> db(new CDbxMDBX(profile, (bReadOnly) ? DBMODE_READONLY : 0));
+ if (db->Load(false) != ERROR_SUCCESS)
+ return NULL;
+
+ g_Dbs.insert(db.get());
+ return db.release();
+}
+
+static int UnloadDatabase(MIDatabase *db)
+{
+ g_Dbs.remove((CDbxMDBX*)db);
+ delete (CDbxMDBX*)db;
+ return 0;
+}
+
+MIDatabaseChecker* CheckDb(const TCHAR *profile, int *error)
+{
+ std::unique_ptr<CDbxMDBX> db(new CDbxMDBX(profile, DBMODE_READONLY));
+ if (db->Load(true) != ERROR_SUCCESS) {
+ *error = ERROR_ACCESS_DENIED;
+ return NULL;
+ }
+
+ if (db->PrepareCheck(error))
+ return NULL;
+
+ return db.release();
+}
+
+static DATABASELINK dblink =
+{
+ sizeof(DATABASELINK),
+ "dbx_mdbx",
+ L"MDBX database driver",
+ makeDatabase,
+ grokHeader,
+ LoadDatabase,
+ UnloadDatabase,
+ CheckDb
+};
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+extern "C" __declspec(dllexport) PLUGININFOEX* MirandaPluginInfoEx(DWORD)
+{
+ return &pluginInfo;
+}
+
+extern "C" __declspec(dllexport) const MUUID MirandaInterfaces[] = { MIID_DATABASE, MIID_LAST };
+
+extern "C" __declspec(dllexport) int Load(void)
+{
+ RegisterDatabasePlugin(&dblink);
+ return 0;
+}
+
+extern "C" __declspec(dllexport) int Unload(void)
+{
+ return 0;
+}
+
+BOOL WINAPI DllMain(HINSTANCE hInstDLL, DWORD, LPVOID)
+{
+ g_hInst = hInstDLL;
+ return TRUE;
+}
diff --git a/plugins/Dbx_mdbx/src/libmdbx b/plugins/Dbx_mdbx/src/libmdbx
new file mode 160000
+Subproject 18432ebfab158ad2ae68a8a993040952f814dd9
diff --git a/plugins/Dbx_mdbx/src/resource.h b/plugins/Dbx_mdbx/src/resource.h
new file mode 100644
index 0000000000..564189306d
--- /dev/null
+++ b/plugins/Dbx_mdbx/src/resource.h
@@ -0,0 +1,35 @@
+//{{NO_DEPENDENCIES}}
+// Включаемый файл, созданный в Microsoft Visual C++.
+// Используется d:\Others\SVN\MirandaNG\trunk\plugins\Dbx_mdb\res\dbx_mdbx.rc
+//
+#define IDREMOVE 3
+#define IDI_ICONPASS 100
+#define IDI_LOGO 101
+#define IDD_LOGIN 102
+#define IDD_NEWPASS 103
+#define IDD_CHANGEPASS 104
+#define IDD_OPTIONS 105
+#define IDD_SELECT_CRYPTOPROVIDER 106
+#define IDC_HEADERBAR 1001
+#define IDC_LANG 1002
+#define IDC_USERPASS 1003
+#define IDC_USERPASS1 1004
+#define IDC_USERPASS2 1005
+#define IDC_OLDPASS 1006
+#define IDC_STANDARD 1007
+#define IDC_TOTAL 1008
+#define IDC_SELECTCRYPT_COMBO 1010
+#define IDC_CRYPTOPROVIDER_DESCR 1011
+#define IDC_CHECK1 1012
+#define IDC_CHECK_TOTALCRYPT 1012
+
+// Next default values for new objects
+//
+#ifdef APSTUDIO_INVOKED
+#ifndef APSTUDIO_READONLY_SYMBOLS
+#define _APS_NEXT_RESOURCE_VALUE 107
+#define _APS_NEXT_COMMAND_VALUE 40001
+#define _APS_NEXT_CONTROL_VALUE 1013
+#define _APS_NEXT_SYMED_VALUE 101
+#endif
+#endif
diff --git a/plugins/Dbx_mdbx/src/stdafx.cxx b/plugins/Dbx_mdbx/src/stdafx.cxx
new file mode 100644
index 0000000000..e579779bcc
--- /dev/null
+++ b/plugins/Dbx_mdbx/src/stdafx.cxx
@@ -0,0 +1,18 @@
+/*
+Copyright (C) 2012-18 Miranda NG team (https://miranda-ng.org)
+
+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 version 2
+of the License.
+
+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, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "stdafx.h" \ No newline at end of file
diff --git a/plugins/Dbx_mdbx/src/stdafx.h b/plugins/Dbx_mdbx/src/stdafx.h
new file mode 100644
index 0000000000..25e91a4001
--- /dev/null
+++ b/plugins/Dbx_mdbx/src/stdafx.h
@@ -0,0 +1,169 @@
+/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (c) 2012-18 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 <windows.h>
+#include <time.h>
+#include <process.h>
+
+#include <memory>
+#include <vector>
+#include <algorithm>
+#include <map>
+
+#include <newpluginapi.h>
+#include <win2k.h>
+#include <m_system_cpp.h>
+#include <m_database.h>
+#include <m_langpack.h>
+#include <m_clist.h>
+#include <m_icolib.h>
+#include <m_options.h>
+#include <m_crypto.h>
+#include <m_metacontacts.h>
+#include <m_protocols.h>
+#include <m_netlib.h>
+#include <m_gui.h>
+
+#include "libmdbx/mdbx.h"
+
+#ifndef thread_local
+# define thread_local __declspec(thread)
+#endif
+
+
+class txn_ptr
+{
+ MDBX_txn *m_txn;
+public:
+ __forceinline txn_ptr(MDBX_env *pEnv)
+ {
+ mdbx_txn_begin(pEnv, NULL, 0, &m_txn);
+ }
+
+ __forceinline ~txn_ptr()
+ {
+ if (m_txn)
+ mdbx_txn_abort(m_txn);
+ }
+
+ __forceinline operator MDBX_txn*() const { return m_txn; }
+
+ __forceinline int commit()
+ {
+ MDBX_txn *tmp = m_txn;
+ m_txn = nullptr;
+ return mdbx_txn_commit(tmp);
+ }
+
+ __forceinline void abort()
+ {
+ mdbx_txn_abort(m_txn);
+ m_txn = NULL;
+ }
+};
+
+struct CMDBX_txn_ro
+{
+ MDBX_txn *m_txn;
+ bool bIsActive;
+ mir_cs cs;
+
+ __forceinline CMDBX_txn_ro() : m_txn(nullptr), bIsActive(false) {}
+
+ __forceinline operator MDBX_txn* () { return m_txn; }
+ __forceinline MDBX_txn** operator &() { return &m_txn; }
+};
+
+class txn_ptr_ro
+{
+ CMDBX_txn_ro &m_txn;
+ bool bNeedReset;
+ mir_cslock lock;
+public:
+ __forceinline txn_ptr_ro(CMDBX_txn_ro &txn) : m_txn(txn), bNeedReset(!txn.bIsActive), lock(m_txn.cs)
+ {
+ if (bNeedReset)
+ {
+ mdbx_txn_renew(m_txn);
+ m_txn.bIsActive = true;
+ }
+ }
+ __forceinline ~txn_ptr_ro()
+ {
+ if (bNeedReset)
+ {
+ mdbx_txn_reset(m_txn);
+ m_txn.bIsActive = false;
+ }
+ }
+ __forceinline operator MDBX_txn*() const { return m_txn; }
+};
+
+class cursor_ptr
+{
+ MDBX_cursor *m_cursor;
+
+public:
+ __forceinline cursor_ptr(MDBX_txn *_txn, MDBX_dbi _dbi)
+ {
+ if (mdbx_cursor_open(_txn, _dbi, &m_cursor) != MDBX_SUCCESS)
+ m_cursor = NULL;
+ }
+
+ __forceinline ~cursor_ptr()
+ {
+ if (m_cursor)
+ mdbx_cursor_close(m_cursor);
+ }
+
+ __forceinline operator MDBX_cursor*() const { return m_cursor; }
+};
+
+class cursor_ptr_ro
+{
+ MDBX_cursor *m_cursor;
+public:
+ __forceinline cursor_ptr_ro(MDBX_cursor *cursor) : m_cursor(cursor)
+ {
+ mdbx_cursor_renew(mdbx_cursor_txn(m_cursor), m_cursor);
+ }
+ __forceinline operator MDBX_cursor*() const { return m_cursor; }
+};
+
+#define MDBX_CHECK(A,B) \
+ switch (A) { \
+ case MDBX_SUCCESS: break; \
+ case MDBX_MAP_FULL: continue; \
+ default: return (B); }
+
+
+
+
+#include "dbintf.h"
+#include "resource.h"
+#include "version.h"
+
+extern HINSTANCE g_hInst;
+extern LIST<CDbxMDBX> g_Dbs;
+
+#include "ui.h"
diff --git a/plugins/Dbx_mdbx/src/ui.cpp b/plugins/Dbx_mdbx/src/ui.cpp
new file mode 100644
index 0000000000..6cf2f6bee8
--- /dev/null
+++ b/plugins/Dbx_mdbx/src/ui.cpp
@@ -0,0 +1,202 @@
+/*
+
+Miranda NG: the free IM client for Microsoft* Windows*
+
+Copyright (c) 2012-18 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"
+
+static HGENMENU hSetPwdMenu;
+
+static UINT oldLangID;
+void LanguageChanged(HWND hwndDlg)
+{
+ UINT_PTR LangID = (UINT_PTR)GetKeyboardLayout(0);
+ char Lang[3] = { 0 };
+ if (LangID != oldLangID) {
+ oldLangID = LangID;
+ GetLocaleInfoA(MAKELCID((LangID & 0xffffffff), SORT_DEFAULT), LOCALE_SABBREVLANGNAME, Lang, 2);
+ Lang[0] = toupper(Lang[0]);
+ Lang[1] = tolower(Lang[1]);
+ SetDlgItemTextA(hwndDlg, IDC_LANG, Lang);
+ }
+}
+
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static bool CheckOldPassword(HWND hwndDlg, CDbxMDBX *db)
+{
+ if (db->usesPassword())
+ {
+ TCHAR buf[100];
+ GetDlgItemText(hwndDlg, IDC_OLDPASS, buf, _countof(buf));
+ pass_ptrA oldPass(mir_utf8encodeW(buf));
+ if (!db->m_crypto->checkPassword(oldPass))
+ {
+ SetDlgItemText(hwndDlg, IDC_HEADERBAR, TranslateT("Wrong old password entered!"));
+ return false;
+ }
+ }
+ return true;
+}
+
+static INT_PTR CALLBACK sttChangePassword(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
+{
+ DlgChangePassParam *param = (DlgChangePassParam*)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
+ TCHAR buf[100];
+
+ switch (uMsg) {
+ case WM_INITDIALOG:
+ TranslateDialogDefault(hwndDlg);
+ SendDlgItemMessage(hwndDlg, IDC_HEADERBAR, WM_SETICON, ICON_SMALL, (LPARAM)IcoLib_GetIconByHandle(iconList[0].hIcolib, true));
+
+ param = (DlgChangePassParam*)lParam;
+ SetWindowLongPtr(hwndDlg, GWLP_USERDATA, lParam);
+
+ oldLangID = 0;
+ SetTimer(hwndDlg, 1, 200, NULL);
+ LanguageChanged(hwndDlg);
+ return TRUE;
+
+ case WM_CTLCOLORSTATIC:
+ if ((HWND)lParam == GetDlgItem(hwndDlg, IDC_LANG)) {
+ SetTextColor((HDC)wParam, GetSysColor(COLOR_HIGHLIGHTTEXT));
+ SetBkMode((HDC)wParam, TRANSPARENT);
+ return (INT_PTR)GetSysColorBrush(COLOR_HIGHLIGHT);
+ }
+ return FALSE;
+
+ case WM_COMMAND:
+ switch (LOWORD(wParam)) {
+ case IDCANCEL:
+ EndDialog(hwndDlg, IDCANCEL);
+ break;
+
+ case IDREMOVE:
+ if (!CheckOldPassword(hwndDlg, param->db)) {
+ LBL_Error:
+ SendDlgItemMessage(hwndDlg, IDC_HEADERBAR, WM_NCPAINT, 0, 0);
+ SetDlgItemTextA(hwndDlg, IDC_USERPASS1, "");
+ SetDlgItemTextA(hwndDlg, IDC_USERPASS2, "");
+ }
+ else {
+ // param->db->WriteSignature(dbSignatureU);
+ param->db->SetPassword(nullptr);
+ param->db->StoreKey();
+ EndDialog(hwndDlg, IDREMOVE);
+ }
+ break;
+
+ case IDOK:
+ TCHAR buf2[100];
+ GetDlgItemText(hwndDlg, IDC_USERPASS1, buf2, _countof(buf2));
+ if (wcslen(buf2) < 3) {
+ SetDlgItemText(hwndDlg, IDC_HEADERBAR, TranslateT("Password is too short!"));
+ goto LBL_Error;
+ }
+
+ GetDlgItemText(hwndDlg, IDC_USERPASS2, buf, _countof(buf));
+ if (wcscmp(buf2, buf)) {
+ SetDlgItemText(hwndDlg, IDC_HEADERBAR, TranslateT("Passwords do not match!"));
+ goto LBL_Error;
+ }
+
+ if (!CheckOldPassword(hwndDlg, param->db))
+ goto LBL_Error;
+
+ // param->db->WriteSignature(dbSignatureE);
+ param->db->SetPassword(buf2);
+ param->db->StoreKey();
+ SecureZeroMemory(buf2, sizeof(buf2));
+ EndDialog(hwndDlg, IDOK);
+ }
+ break;
+
+ case WM_TIMER:
+ LanguageChanged(hwndDlg);
+ return FALSE;
+
+ case WM_DESTROY:
+ KillTimer(hwndDlg, 1);
+ IcoLib_ReleaseIcon((HICON)SendMessage(hwndDlg, WM_GETICON, ICON_SMALL, 0));
+ }
+
+ return FALSE;
+}
+
+static INT_PTR ChangePassword(void* obj, WPARAM, LPARAM)
+{
+ CDbxMDBX *db = (CDbxMDBX*)obj;
+ DlgChangePassParam param = { db };
+ DialogBoxParam(g_hInst, MAKEINTRESOURCE(db->usesPassword() ? IDD_CHANGEPASS : IDD_NEWPASS), 0, sttChangePassword, (LPARAM)&param);
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+
+static int OnOptionsInit(PVOID obj, WPARAM wParam, LPARAM)
+{
+ OPTIONSDIALOGPAGE odp = { sizeof(odp) };
+ odp.position = -790000000;
+ odp.flags = ODPF_BOLDGROUPS;
+ odp.szTitle.a = LPGEN("Database");
+ odp.pDialog = new COptionsDialog((CDbxMDBX*)obj);
+ Options_AddPage(wParam, &odp);
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void CDbxMDBX::UpdateMenuItem()
+{
+ Menu_ModifyItem(hSetPwdMenu, _A2T(GetMenuTitle()), iconList[1].hIcolib);
+}
+
+static int OnModulesLoaded(PVOID obj, WPARAM, LPARAM)
+{
+ CDbxMDBX *db = (CDbxMDBX*)obj;
+
+ Icon_Register(g_hInst, LPGEN("Database"), iconList, _countof(iconList), "mdbx");
+
+ HookEventObj(ME_OPT_INITIALISE, OnOptionsInit, db);
+
+ CMenuItem mi;
+
+ // main menu item
+ mi.root = Menu_CreateRoot(MO_MAIN, LPGENW("Database"), 500000000, iconList[0].hIcolib);
+ Menu_ConfigureItem(mi.root, MCI_OPT_UID, "F7C5567C-D1EE-484B-B4F6-24677A5AAAEF");
+
+ SET_UID(mi, 0x50321866, 0xba1, 0x46dd, 0xb3, 0xa6, 0xc3, 0xcc, 0x55, 0xf2, 0x42, 0x9e);
+ mi.hIcolibItem = iconList[1].hIcolib;
+ mi.name.a = db->GetMenuTitle();
+ mi.pszService = MS_DB_CHANGEPASSWORD;
+ hSetPwdMenu = Menu_AddMainMenuItem(&mi);
+ return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void CDbxMDBX::InitDialogs()
+{
+ hService = CreateServiceFunctionObj(MS_DB_CHANGEPASSWORD, ChangePassword, this);
+ hHook = HookEventObj(ME_SYSTEM_MODULESLOADED, OnModulesLoaded, this);
+}
diff --git a/plugins/Dbx_mdbx/src/ui.h b/plugins/Dbx_mdbx/src/ui.h
new file mode 100644
index 0000000000..704c380cfa
--- /dev/null
+++ b/plugins/Dbx_mdbx/src/ui.h
@@ -0,0 +1,187 @@
+static IconItem iconList[] =
+{
+ { LPGEN("Logo"), "logo", IDI_LOGO },
+ { LPGEN("Password"), "password", IDI_ICONPASS }
+};
+
+#define MS_DB_CHANGEPASSWORD "DB/UI/ChangePassword"
+
+class COptionsDialog : public CDlgBase
+{
+ CCtrlCheck m_chkStandart;
+ CCtrlCheck m_chkTotal;
+ CCtrlButton m_btnChangePass;
+ CDbxMDBX *m_db;
+
+ void OnInitDialog()
+ {
+ m_chkStandart.SetState(!m_db->isEncrypted());
+ m_chkTotal.SetState(m_db->isEncrypted());
+ m_btnChangePass.SetTextA(Translate(m_db->GetMenuTitle()));
+ }
+
+ void OnApply()
+ {
+ m_db->EnableEncryption(m_chkTotal.GetState() != 0);
+ m_chkStandart.SetState(!m_db->isEncrypted());
+ m_chkTotal.SetState(m_db->isEncrypted());
+ }
+
+ void ChangePass(CCtrlButton*)
+ {
+ CallService(MS_DB_CHANGEPASSWORD, 0, 0);
+ }
+
+public:
+ COptionsDialog(CDbxMDBX *db) :
+ CDlgBase(g_hInst, IDD_OPTIONS),
+ m_chkStandart(this, IDC_STANDARD),
+ m_chkTotal(this, IDC_TOTAL),
+ m_btnChangePass(this, IDC_USERPASS),
+ m_db(db)
+ {
+ m_btnChangePass.OnClick = Callback(this, &COptionsDialog::ChangePass);
+ }
+};
+
+class CSelectCryptoDialog : public CDlgBase
+{
+ CCtrlCombo m_combo;
+ CCtrlData m_descr;
+ CCtrlCheck m_chkTotalCrypt;
+ CRYPTO_PROVIDER **m_provs;
+ size_t m_provscount;
+ CRYPTO_PROVIDER *m_selected;
+ bool m_bTotalEncryption;
+
+ void OnInitDialog()
+ {
+ for (size_t i = 0; i < m_provscount; i++)
+ {
+ CRYPTO_PROVIDER *prov = m_provs[i];
+ m_combo.AddStringA(prov->pszName, i);
+ }
+ m_combo.SetCurSel(0);
+ m_descr.SetText(m_provs[0]->ptszDescr);
+ }
+
+ void OnClose()
+ {
+ m_selected = m_provs[ m_combo.GetItemData(m_combo.GetCurSel()) ];
+ m_bTotalEncryption = m_chkTotalCrypt.GetState() != 0;
+ }
+
+ void OnComboChanged(CCtrlCombo*)
+ {
+ m_descr.SetText(m_provs[m_combo.GetItemData(m_combo.GetCurSel())]->ptszDescr);
+ }
+
+public:
+ CSelectCryptoDialog(CRYPTO_PROVIDER **provs, size_t count) :
+ CDlgBase(g_hInst, IDD_SELECT_CRYPTOPROVIDER),
+ m_combo(this, IDC_SELECTCRYPT_COMBO),
+ m_descr(this, IDC_CRYPTOPROVIDER_DESCR),
+ m_chkTotalCrypt(this, IDC_CHECK_TOTALCRYPT),
+ m_provs(provs),
+ m_provscount(count),
+ m_selected(nullptr)
+ {
+ m_combo.OnChange = Callback(this, &CSelectCryptoDialog::OnComboChanged);
+ }
+
+ inline CRYPTO_PROVIDER* GetSelected()
+ {
+ return m_selected;
+ }
+ inline bool TotalSelected()
+ {
+ return m_bTotalEncryption;
+ }
+};
+
+struct DlgChangePassParam
+{
+ CDbxMDBX *db;
+ TCHAR newPass[100];
+ unsigned short wrongPass;
+};
+
+class CEnterPasswordDialog : public CDlgBase
+{
+ CCtrlData m_header;
+ CCtrlData m_language;
+ CCtrlEdit m_passwordEdit;
+ CCtrlButton m_buttonOK;
+
+ DlgChangePassParam *m_param;
+
+ INT_PTR DlgProc(UINT msg, WPARAM wParam, LPARAM lParam)
+ {
+ if (msg == WM_TIMER)
+ {
+ UINT_PTR LangID = (UINT_PTR)GetKeyboardLayout(0);
+ char Lang[3] = { 0 };
+ GetLocaleInfoA(MAKELCID((LangID & 0xffffffff), SORT_DEFAULT), LOCALE_SABBREVLANGNAME, Lang, 2);
+ Lang[0] = toupper(Lang[0]);
+ Lang[1] = tolower(Lang[1]);
+ m_language.SetTextA(Lang);
+ return FALSE;
+ }
+ else if (msg == WM_CTLCOLORSTATIC)
+ {
+ if ((HWND)lParam == m_language.GetHwnd()) {
+ SetTextColor((HDC)wParam, GetSysColor(COLOR_HIGHLIGHTTEXT));
+ SetBkMode((HDC)wParam, TRANSPARENT);
+ return (INT_PTR)GetSysColorBrush(COLOR_HIGHLIGHT);
+ }
+ }
+ return CDlgBase::DlgProc(msg, wParam, lParam);
+ }
+
+ void OnInitDialog()
+ {
+ m_header.SendMsg(WM_SETICON, ICON_SMALL, (LPARAM)LoadIcon(g_hInst, MAKEINTRESOURCE(iconList[0].defIconID)));
+ if (m_param->wrongPass)
+ {
+ if (m_param->wrongPass > 2)
+ {
+ m_passwordEdit.Disable();
+ m_buttonOK.Disable();
+ m_header.SetText(TranslateT("Too many errors!"));
+ }
+ else
+ {
+ m_header.SetText(TranslateT("Password is not correct!"));
+ }
+ }
+ else
+ {
+ m_header.SetText(TranslateT("Please type in your password"));
+ }
+ SetTimer(m_hwnd, 1, 200, NULL);
+ }
+
+ void OnOK(CCtrlButton*)
+ {
+ m_passwordEdit.GetText(m_param->newPass, _countof(m_param->newPass));
+ EndDialog(m_hwnd, -128);
+ }
+
+ void OnDestroy()
+ {
+ KillTimer(m_hwnd, 1);
+ }
+
+public:
+ CEnterPasswordDialog(DlgChangePassParam *param) :
+ CDlgBase(g_hInst, IDD_LOGIN),
+ m_header(this, IDC_HEADERBAR),
+ m_language(this, IDC_LANG),
+ m_passwordEdit(this, IDC_USERPASS),
+ m_buttonOK(this, IDOK),
+ m_param(param)
+ {
+ m_buttonOK.OnClick = Callback(this, &CEnterPasswordDialog::OnOK);
+ }
+
+};
diff --git a/plugins/Dbx_mdbx/src/version.h b/plugins/Dbx_mdbx/src/version.h
new file mode 100644
index 0000000000..0b97e64886
--- /dev/null
+++ b/plugins/Dbx_mdbx/src/version.h
@@ -0,0 +1,13 @@
+#define __MAJOR_VERSION 0
+#define __MINOR_VERSION 95
+#define __RELEASE_NUM 8
+#define __BUILD_NUM 1
+
+#include <stdver.h>
+
+#define __PLUGIN_NAME "Miranda NG MDBX database driver (https://github.com/leo-yuriev/libmdbx)"
+#define __FILENAME "dbx_mdbx.dll"
+#define __DESCRIPTION "Provides Miranda database support: global settings, contacts, history, settings per contact."
+#define __AUTHOR "Miranda-NG project"
+#define __AUTHORWEB "https://miranda-ng.org/p/dbx_mdbx/"
+#define __COPYRIGHT "© 2015-18 Miranda NG team"