/* dbRW Copyright (c) 2005-2009 Robert Rainwater 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 "dbrw.h" static CRITICAL_SECTION csEventsDb; static HANDLE hEventsThread = 0, hEventsEvent = 0, hHeap; static SortedList sModuleNames; static int events_cmpModuleNames(void *p1, void *p2); static unsigned __stdcall events_timerProcThread(void *arg); typedef struct { char *name; DWORD nameHash; } DBCachedModuleName; enum { SQL_EVT_STMT_COUNT=0, SQL_EVT_STMT_ADD, SQL_EVT_STMT_DELETE, SQL_EVT_STMT_BLOBSIZE, SQL_EVT_STMT_BLOBSIZE_CACHE, SQL_EVT_STMT_GET, SQL_EVT_STMT_GET_CACHE, SQL_EVT_STMT_GETFLAGS, SQL_EVT_STMT_SETFLAGS, SQL_EVT_STMT_GETCONTACT, SQL_EVT_STMT_GETCONTACT_CACHE, SQL_EVT_STMT_FINDFIRST, SQL_EVT_STMT_FINDFIRSTUNREAD, SQL_EVT_STMT_FINDLAST, SQL_EVT_STMT_FINDNEXT, SQL_EVT_STMT_FINDPREV, SQL_EVT_STMT_CREATETEMPTABLE, SQL_EVT_STMT_DROPTEMPTABLE, SQL_EVT_STMT_DELETECONTACT, SQL_EVT_STMT_NUM }; static char *evt_stmts[SQL_EVT_STMT_NUM] = { "SELECT count(*) FROM dbrw_events where contactid = ?;", "INSERT INTO dbrw_events VALUES(NULL,?,?,?,?,?,?,?,?);", "DELETE FROM dbrw_events where id = ? AND contactid = ?;", "SELECT blobsize FROM dbrw_events where id = ? LIMIT 1;", "SELECT blobsize FROM temp_dbrw_events where id = ? LIMIT 1;", "SELECT * FROM dbrw_events where id = ? LIMIT 1;", "SELECT * FROM temp_dbrw_events where id = ? LIMIT 1;", "SELECT flags FROM dbrw_events where id = ? LIMIT 1;", "UPDATE dbrw_events SET flags = ? WHERE id = ?;", "SELECT contactid FROM dbrw_events where id = ? LIMIT 1;", "SELECT contactid FROM temp_dbrw_events where id = ? LIMIT 1;", "SELECT id FROM dbrw_events where contactid = ? ORDER by id;", "SELECT flags,id FROM dbrw_events where contactid = ? ORDER by id;", "SELECT id FROM dbrw_events where contactid = ? ORDER by id DESC;", "SELECT id FROM dbrw_events where contactid = ? AND id > ? ORDER by id LIMIT 1;", "SELECT id FROM dbrw_events where contactid = ? AND id < ? ORDER by id DESC LIMIT 1;", "create temp table temp_dbrw_events (id integer primary key,eventtime integer,flags integer,eventtype integer, blob any, blobsize integer, contactid integer,modulename varchar(255),inserttime integer);" "create temp trigger insert_new_temp_event1 after insert on dbrw_events begin replace into temp_dbrw_events values(new.id,new.eventtime,new.flags,new.eventtype,new.blob,new.blobsize,new.contactid,new.modulename,new.inserttime); end;" "create temp trigger insert_new_temp_event2 after update on dbrw_events begin replace into temp_dbrw_events values(new.id,new.eventtime,new.flags,new.eventtype,new.blob,new.blobsize,new.contactid,new.modulename,new.inserttime); end;" "create temp trigger delete_temp_event after delete on dbrw_events begin delete from temp_dbrw_events where id=old.id and contactid=old.id; end;", "drop trigger insert_new_temp_event1;" "drop trigger insert_new_temp_event2;" "drop trigger delete_temp_event;" "drop table temp_dbrw_events;", "DELETE FROM dbrw_events WHERE contactid = ?;" }; static sqlite3_stmt *evt_stmts_prep[SQL_EVT_STMT_NUM] = {0}; void events_init() { InitializeCriticalSection(&csEventsDb); hHeap = HeapCreate(0, 0, 0); ZeroMemory(&sModuleNames, sizeof(sModuleNames)); sModuleNames.increment = 100; sModuleNames.sortFunc = events_cmpModuleNames; sql_prepare_add(evt_stmts, evt_stmts_prep, SQL_EVT_STMT_NUM); sql_exec(g_sqlite, "BEGIN TRANSACTION;"); sql_exec(g_sqlite, "create temp table temp_dbrw_events (id integer primary key,eventtime integer,flags integer,eventtype integer, blob any, blobsize integer, contactid integer,modulename varchar(255),inserttime integer);" "create temp trigger insert_new_temp_event1 after insert on dbrw_events begin replace into temp_dbrw_events values(new.id,new.eventtime,new.flags,new.eventtype,new.blob,new.blobsize,new.contactid,new.modulename,new.inserttime); end;" "create temp trigger insert_new_temp_event2 after update on dbrw_events begin replace into temp_dbrw_events values(new.id,new.eventtime,new.flags,new.eventtype,new.blob,new.blobsize,new.contactid,new.modulename,new.inserttime); end;" "create temp trigger delete_temp_event after delete on dbrw_events begin delete from temp_dbrw_events where id=old.id and contactid=old.id; end;"); sql_exec(g_sqlite, "COMMIT;"); hEventsEvent = CreateEvent(NULL, FALSE, FALSE, NULL); hEventsThread = (HANDLE)mir_forkthreadex(events_timerProcThread, 0, 0, 0); } void events_destroy() { if (hEventsEvent) { SetEvent(hEventsEvent); WaitForSingleObjectEx(hEventsThread, INFINITE, FALSE); CloseHandle(hEventsThread); } HeapDestroy(hHeap); li.List_Destroy(&sModuleNames); DeleteCriticalSection(&csEventsDb); } static int events_cmpModuleNames(void *p1, void *p2) { DBCachedModuleName *v1 = (DBCachedModuleName*)p1; DBCachedModuleName *v2 = (DBCachedModuleName*)p2; if (v1->nameHash!=v2->nameHash) return v1->nameHash-v2->nameHash; return strcmp(v1->name, v2->name); } static char *events_moduleCacheAdd(char *szModule) { if (!szModule) return 0; { int idx = 0; size_t nameLen; DBCachedModuleName Vtemp, *V; Vtemp.name = szModule; Vtemp.nameHash = utils_hashString(szModule); if (li.List_GetIndex(&sModuleNames, &Vtemp, &idx)) { V = (DBCachedModuleName*)sModuleNames.items[idx]; return V->name; } V = (DBCachedModuleName*)HeapAlloc(hHeap, HEAP_ZERO_MEMORY, sizeof(DBCachedModuleName)); nameLen = strlen(szModule)+1; V->name = (char*)HeapAlloc(hHeap, 0, nameLen); mir_snprintf(V->name, nameLen, "%s", szModule); V->nameHash = utils_hashString(szModule); li.List_Insert(&sModuleNames, V, idx); return V->name; } } static unsigned __stdcall events_timerProcThread(void *arg) { DWORD dwWait; for(;;) { dwWait = WaitForSingleObjectEx(hEventsEvent, DBRW_EVENTS_FLUSHCACHE, TRUE); if (dwWait==WAIT_OBJECT_0) break; else if(dwWait == WAIT_TIMEOUT) { EnterCriticalSection(&csEventsDb); sql_stmt_begin(); sql_step(evt_stmts_prep[SQL_EVT_STMT_DROPTEMPTABLE]); sql_step(evt_stmts_prep[SQL_EVT_STMT_CREATETEMPTABLE]); sql_stmt_end(); sql_reset(evt_stmts_prep[SQL_EVT_STMT_DROPTEMPTABLE]); sql_reset(evt_stmts_prep[SQL_EVT_STMT_CREATETEMPTABLE]); LeaveCriticalSection(&csEventsDb); } else if (dwWait == WAIT_IO_COMPLETION) if (Miranda_Terminated()) break; } CloseHandle(hEventsEvent); hEventsEvent = NULL; return 0; } INT_PTR events_getCount(WPARAM wParam, LPARAM lParam) { int rc = 0; EnterCriticalSection(&csEventsDb); if (!contacts_isContact(wParam, 0)) { LeaveCriticalSection(&csEventsDb); return -1; } sqlite3_bind_int(evt_stmts_prep[SQL_EVT_STMT_COUNT], 1, (int)wParam); if (sql_step(evt_stmts_prep[SQL_EVT_STMT_COUNT])==SQLITE_ROW) rc = sqlite3_column_int(evt_stmts_prep[SQL_EVT_STMT_COUNT], 0); sql_reset(evt_stmts_prep[SQL_EVT_STMT_COUNT]); LeaveCriticalSection(&csEventsDb); return rc; } INT_PTR events_add(WPARAM wParam, LPARAM lParam) { HANDLE hContact = (HANDLE)wParam; DBEVENTINFO *dbei = (DBEVENTINFO*)lParam; int rc = 0; if (dbei==NULL|| dbei->cbSize!=sizeof(DBEVENTINFO)|| dbei->timestamp==0|| NotifyEventHooks(hEventFilterAddedEvent, wParam, lParam)) { log1("Attempt to add invalid event for contact(%d)", (int)hContact); return 0; } EnterCriticalSection(&csEventsDb); sqlite3_bind_int(evt_stmts_prep[SQL_EVT_STMT_ADD], 1, (int)dbei->timestamp); sqlite3_bind_int(evt_stmts_prep[SQL_EVT_STMT_ADD], 2, (int)dbei->flags); sqlite3_bind_int(evt_stmts_prep[SQL_EVT_STMT_ADD], 3, (int)dbei->eventType); sqlite3_bind_blob(evt_stmts_prep[SQL_EVT_STMT_ADD], 4, dbei->pBlob, (int)dbei->cbBlob, SQLITE_STATIC); sqlite3_bind_int(evt_stmts_prep[SQL_EVT_STMT_ADD], 5, (int)dbei->cbBlob); sqlite3_bind_int(evt_stmts_prep[SQL_EVT_STMT_ADD], 6, (int)hContact); sqlite3_bind_text(evt_stmts_prep[SQL_EVT_STMT_ADD], 7, dbei->szModule?dbei->szModule:NULL, -1, SQLITE_STATIC); sqlite3_bind_int(evt_stmts_prep[SQL_EVT_STMT_ADD], 8, time(NULL)); if (sql_step(evt_stmts_prep[SQL_EVT_STMT_ADD])==SQLITE_DONE) { rc = (int)sqlite3_last_insert_rowid(g_sqlite); } else { log2("Error adding event(#%d,%s)", sqlite3_errcode(g_sqlite), sqlite3_errmsg(g_sqlite)); } sql_reset(evt_stmts_prep[SQL_EVT_STMT_ADD]); LeaveCriticalSection(&csEventsDb); if (rc) { NotifyEventHooks(hEventAddedEvent,wParam,(LPARAM)rc); } return rc; } INT_PTR events_delete(WPARAM wParam, LPARAM lParam) { HANDLE hContact = (HANDLE)wParam, hContactFind; HANDLE hDbEvent = (HANDLE)lParam; int rc = 1; hContactFind = (HANDLE)events_getContact((WPARAM)hDbEvent, 0); if ((int)hContactFind==-1||hContact!=hContactFind) return rc; NotifyEventHooks(hEventDeletedEvent, wParam, lParam); EnterCriticalSection(&csEventsDb); sqlite3_bind_int(evt_stmts_prep[SQL_EVT_STMT_DELETE], 1, (int)hDbEvent); sqlite3_bind_int(evt_stmts_prep[SQL_EVT_STMT_DELETE], 2, (int)hContact); if (sql_step(evt_stmts_prep[SQL_EVT_STMT_DELETE])==SQLITE_DONE) { log1("Deleted event(%d)", (int)hDbEvent); rc = 0; } sql_reset(evt_stmts_prep[SQL_EVT_STMT_DELETE]); LeaveCriticalSection(&csEventsDb); return rc; } static int events_getBlobSizeConditional(HANDLE hDbEvent, int cache) { int rc = -1; sqlite3_stmt* stmt; EnterCriticalSection(&csEventsDb); stmt = cache?evt_stmts_prep[SQL_EVT_STMT_BLOBSIZE_CACHE]:evt_stmts_prep[SQL_EVT_STMT_BLOBSIZE]; sqlite3_bind_int(stmt, 1, (int)hDbEvent); if (sql_step(stmt)==SQLITE_ROW) rc = sqlite3_column_int(stmt, 0); sql_reset(stmt); LeaveCriticalSection(&csEventsDb); return rc; } INT_PTR events_getBlobSize(WPARAM wParam, LPARAM lParam) { HANDLE hDbEvent = (HANDLE)wParam; int rc = events_getBlobSizeConditional(hDbEvent, 1); if (rc!=-1) return rc; return events_getBlobSizeConditional(hDbEvent, 0);; } static int events_getConditional(HANDLE hDbEvent, DBEVENTINFO *dbei, int cache) { int rc = 1; sqlite3_stmt* stmt; if(dbei==NULL||dbei->cbSize!=sizeof(DBEVENTINFO)) return 1; if(dbei->cbBlob>0&&dbei->pBlob==NULL) { dbei->cbBlob = 0; return 1; } EnterCriticalSection(&csEventsDb); stmt = cache?evt_stmts_prep[SQL_EVT_STMT_GET_CACHE]:evt_stmts_prep[SQL_EVT_STMT_GET]; sqlite3_bind_int(stmt, 1, (int)hDbEvent); if (sql_step(stmt)==SQLITE_ROW) { unsigned copySize; const void *blob = sqlite3_column_blob(stmt, 4); const unsigned size = sqlite3_column_int(stmt, 5); dbei->timestamp = (DWORD)sqlite3_column_int(stmt, 1); dbei->flags = (DWORD)sqlite3_column_int(stmt, 2); dbei->eventType = (WORD)sqlite3_column_int(stmt, 3); dbei->szModule = events_moduleCacheAdd((char*)sqlite3_column_text(stmt, 7)); copySize = sizecbBlob ? size : dbei->cbBlob; CopyMemory(dbei->pBlob, blob, copySize); dbei->cbBlob = copySize; rc = 0; } sql_reset(stmt); LeaveCriticalSection(&csEventsDb); return rc; } INT_PTR events_get(WPARAM wParam, LPARAM lParam) { HANDLE hDbEvent = (HANDLE)wParam; DBEVENTINFO *dbei = (DBEVENTINFO*)lParam; int rc = events_getConditional(hDbEvent, dbei, 1); if (!rc) return 0; return events_getConditional(hDbEvent, dbei, 0); } INT_PTR events_markRead(WPARAM wParam, LPARAM lParam) { HANDLE hDbEvent = (HANDLE)lParam; int rc = -1; EnterCriticalSection(&csEventsDb); sql_stmt_begin(); sqlite3_bind_int(evt_stmts_prep[SQL_EVT_STMT_GETFLAGS], 1, (int)hDbEvent); if (sql_step(evt_stmts_prep[SQL_EVT_STMT_GETFLAGS])==SQLITE_ROW) { DWORD flags = (DWORD)sqlite3_column_int(evt_stmts_prep[SQL_EVT_STMT_GETFLAGS], 0); sql_reset(evt_stmts_prep[SQL_EVT_STMT_GETFLAGS]); rc = flags; if (!(flags&DBEF_READ)) { flags|=DBEF_READ; sqlite3_bind_int(evt_stmts_prep[SQL_EVT_STMT_SETFLAGS], 1, (int)flags); sqlite3_bind_int(evt_stmts_prep[SQL_EVT_STMT_SETFLAGS], 2, (int)hDbEvent); if (sql_step(evt_stmts_prep[SQL_EVT_STMT_SETFLAGS])!=SQLITE_DONE) { rc = -1; } sql_reset(evt_stmts_prep[SQL_EVT_STMT_SETFLAGS]); } } else sql_reset(evt_stmts_prep[SQL_EVT_STMT_GETFLAGS]); sql_stmt_end(); LeaveCriticalSection(&csEventsDb); return rc; } static int events_getContactConditional(HANDLE hDbEvent, int cache) { int rc = -1; sqlite3_stmt* stmt; EnterCriticalSection(&csEventsDb); stmt = cache?evt_stmts_prep[SQL_EVT_STMT_GETCONTACT_CACHE]:evt_stmts_prep[SQL_EVT_STMT_GETCONTACT]; sqlite3_bind_int(stmt, 1, (int)hDbEvent); if (sql_step(stmt)==SQLITE_ROW) rc = sqlite3_column_int(stmt, 0); sql_reset(stmt); LeaveCriticalSection(&csEventsDb); return rc; } INT_PTR events_getContact(WPARAM wParam, LPARAM lParam) { HANDLE hDbEvent = (HANDLE)wParam; int rc = events_getContactConditional(hDbEvent, 1); if (rc!=-1) return rc; return events_getContactConditional(hDbEvent, 0); } INT_PTR events_findFirst(WPARAM wParam, LPARAM lParam) { HANDLE hContact = (HANDLE)wParam; int rc = 0; EnterCriticalSection(&csEventsDb); sqlite3_bind_int(evt_stmts_prep[SQL_EVT_STMT_FINDFIRST], 1, (int)hContact); if (sql_step(evt_stmts_prep[SQL_EVT_STMT_FINDFIRST])==SQLITE_ROW) { rc = sqlite3_column_int(evt_stmts_prep[SQL_EVT_STMT_FINDFIRST], 0); } sql_reset(evt_stmts_prep[SQL_EVT_STMT_FINDFIRST]); LeaveCriticalSection(&csEventsDb); return rc; } INT_PTR events_findFirstUnread(WPARAM wParam, LPARAM lParam) { HANDLE hContact = (HANDLE)wParam; int rc = 0; DWORD flags = 0; EnterCriticalSection(&csEventsDb); sqlite3_bind_int(evt_stmts_prep[SQL_EVT_STMT_FINDFIRSTUNREAD], 1, (int)hContact); while (sql_step(evt_stmts_prep[SQL_EVT_STMT_FINDFIRSTUNREAD])==SQLITE_ROW) { flags = sqlite3_column_int(evt_stmts_prep[SQL_EVT_STMT_FINDFIRSTUNREAD], 0); if(!(flags&(DBEF_READ|DBEF_SENT))) { rc = sqlite3_column_int(evt_stmts_prep[SQL_EVT_STMT_FINDFIRSTUNREAD], 1); sql_reset(evt_stmts_prep[SQL_EVT_STMT_FINDFIRSTUNREAD]); LeaveCriticalSection(&csEventsDb); return rc; } } sql_reset(evt_stmts_prep[SQL_EVT_STMT_FINDFIRSTUNREAD]); LeaveCriticalSection(&csEventsDb); return rc; } INT_PTR events_findLast(WPARAM wParam, LPARAM lParam) { HANDLE hContact = (HANDLE)wParam; int rc = 0; EnterCriticalSection(&csEventsDb); sqlite3_bind_int(evt_stmts_prep[SQL_EVT_STMT_FINDLAST], 1, (int)hContact); if (sql_step(evt_stmts_prep[SQL_EVT_STMT_FINDLAST])==SQLITE_ROW) rc = sqlite3_column_int(evt_stmts_prep[SQL_EVT_STMT_FINDLAST], 0); sql_reset(evt_stmts_prep[SQL_EVT_STMT_FINDLAST]); LeaveCriticalSection(&csEventsDb); return rc; } INT_PTR events_findNext(WPARAM wParam, LPARAM lParam) { HANDLE hDbEvent = (HANDLE)wParam; int hContact = -1, rc = 0; if (hDbEvent==NULL) { return 0; } hContact = events_getContact(wParam, 0); if (hContact==-1) return 0; EnterCriticalSection(&csEventsDb); sqlite3_bind_int(evt_stmts_prep[SQL_EVT_STMT_FINDNEXT], 1, hContact); sqlite3_bind_int(evt_stmts_prep[SQL_EVT_STMT_FINDNEXT], 2, (int)hDbEvent); if (sql_step(evt_stmts_prep[SQL_EVT_STMT_FINDNEXT])==SQLITE_ROW) rc = sqlite3_column_int(evt_stmts_prep[SQL_EVT_STMT_FINDNEXT], 0); sql_reset(evt_stmts_prep[SQL_EVT_STMT_FINDNEXT]); LeaveCriticalSection(&csEventsDb); return rc; } INT_PTR events_findPrev(WPARAM wParam, LPARAM lParam) { HANDLE hDbEvent = (HANDLE)wParam; int hContact = -1, rc = 0; if (hDbEvent==NULL) { return 0; } hContact = events_getContact(wParam, 0); if (hContact==-1) { return 0; } EnterCriticalSection(&csEventsDb); sqlite3_bind_int(evt_stmts_prep[SQL_EVT_STMT_FINDPREV], 1, hContact); sqlite3_bind_int(evt_stmts_prep[SQL_EVT_STMT_FINDPREV], 2, (int)hDbEvent); rc = sql_step(evt_stmts_prep[SQL_EVT_STMT_FINDPREV]); if (rc==SQLITE_ROW) { rc = sqlite3_column_int(evt_stmts_prep[SQL_EVT_STMT_FINDPREV], 0); } else { rc = 0; } sql_reset(evt_stmts_prep[SQL_EVT_STMT_FINDPREV]); LeaveCriticalSection(&csEventsDb); return rc; } void events_deleteContactData(HANDLE hContact) { EnterCriticalSection(&csEventsDb); sqlite3_bind_int(evt_stmts_prep[SQL_EVT_STMT_DELETECONTACT], 1, (int)hContact); sql_step(evt_stmts_prep[SQL_EVT_STMT_DELETECONTACT]); sql_reset(evt_stmts_prep[SQL_EVT_STMT_DELETECONTACT]); LeaveCriticalSection(&csEventsDb); }