rework event handling. Multi-ser events seem to now work; not well tested.

git-svn-id: svn+ssh://svn.gnucash.org/repo/gnucash/trunk@4577 57a11ea4-9604-0410-9ed3-97b8803252fd
This commit is contained in:
Linas Vepstas 2001-06-10 05:20:00 +00:00
parent 715716458e
commit 6f4e784768
4 changed files with 201 additions and 99 deletions

View File

@ -59,6 +59,7 @@
#include "gnc-engine.h" #include "gnc-engine.h"
#include "gnc-engine-util.h" #include "gnc-engine-util.h"
#include "gnc-event.h" #include "gnc-event.h"
#include "gnc-event-p.h"
#include "gnc-pricedb.h" #include "gnc-pricedb.h"
#include "gnc-pricedb-p.h" #include "gnc-pricedb-p.h"
#include "guid.h" #include "guid.h"
@ -239,99 +240,68 @@ static const char *table_drop_str =
/* ============================================================= */ /* ============================================================= */
/* ============================================================= */ /* ============================================================= */
#define NOTIFY_ACCOUNT(be,x) { \ static gboolean
if ((MODE_POLL == be->session_mode) || \ pgendEventsPending (Backend *bend)
(MODE_EVENT == be->session_mode)) \
{ \
SEND_QUERY (be,"NOTIFY gncAccount;", x); \
FINISH_QUERY(be->connection); \
} \
}
#define NOTIFY_CHECKPOINT(be,x) { \
if ((MODE_POLL == be->session_mode) || \
(MODE_EVENT == be->session_mode)) \
{ \
SEND_QUERY (be,"NOTIFY gncCheckpoint;", x);\
FINISH_QUERY(be->connection); \
} \
}
#define NOTIFY_PRICE(be,x) { \
if ((MODE_POLL == be->session_mode) || \
(MODE_EVENT == be->session_mode)) \
{ \
SEND_QUERY (be,"NOTIFY gncPrice;", x); \
FINISH_QUERY(be->connection); \
} \
}
#define NOTIFY_TRANSACTION(be,x) { \
if ((MODE_POLL == be->session_mode) || \
(MODE_EVENT == be->session_mode)) \
{ \
SEND_QUERY (be,"NOTIFY gncTransaction;", x);\
FINISH_QUERY(be->connection); \
} \
}
static void
pgendHandleEvents (PGBackend *be)
{ {
PGBackend *be = (PGBackend *) bend;
PGnotify *note; PGnotify *note;
char *p; char *p;
int do_account=0, do_checkpoint=0, do_price=0; int rc;
int do_session=0, do_transaction=0;
/* no need to handle events in single-modes, since if (!be) return FALSE;
* they shouldn't be generated nor received. */ ENTER ("mypid=%d", be->my_pid);
/* No need to handle events in single-modes */
if ((MODE_SINGLE_UPDATE == be->session_mode) || if ((MODE_SINGLE_UPDATE == be->session_mode) ||
(MODE_SINGLE_FILE == be->session_mode)) (MODE_SINGLE_FILE == be->session_mode))
{ {
note = PQnotifies (be->connection); return FALSE;
while (note)
{
note = PQnotifies (be->connection);
}
return;
} }
/* consolidate multiple event notifications */ /* consolidate multiple event notifications */
rc = PQconsumeInput (be->connection);
if (1 != rc)
{
PERR ("consume input failed: %s", PQerrorMessage(be->connection));
}
note = PQnotifies (be->connection); note = PQnotifies (be->connection);
while (note) while (note)
{ {
/* ignore notifies from myself */ /* ignore notifies from myself */
if (note->be_pid == be->my_pid) if (note->be_pid == be->my_pid)
{ {
PINFO ("this event from myself: %s from pid=%d", note->relname, note->be_pid);
free (note);
note = PQnotifies (be->connection); note = PQnotifies (be->connection);
continue; continue;
} }
PINFO ("%s", note->relname); PINFO ("notify event %s from pid=%d", note->relname, note->be_pid);
if (0 == strcasecmp ("gncTransaction", note->relname)) if (0 == strcasecmp ("gncTransaction", note->relname))
{ {
do_transaction ++; be->do_transaction ++;
} }
else else
if (0 == strcasecmp ("gncCheckpoint", note->relname)) if (0 == strcasecmp ("gncCheckpoint", note->relname))
{ {
do_checkpoint ++; be->do_checkpoint ++;
} }
else else
if (0 == strcasecmp ("gncPrice", note->relname)) if (0 == strcasecmp ("gncPrice", note->relname))
{ {
do_price ++; be->do_price ++;
} }
else else
if (0 == strcasecmp ("gncAccount", note->relname)) if (0 == strcasecmp ("gncAccount", note->relname))
{ {
do_account ++; be->do_account ++;
} }
else else
if (0 == strcasecmp ("gncSession", note->relname)) if (0 == strcasecmp ("gncSession", note->relname))
{ {
do_session ++; be->do_session ++;
} }
else else
{ {
@ -339,31 +309,105 @@ pgendHandleEvents (PGBackend *be)
} }
/* get the next one */ /* get the next one */
free (note);
note = PQnotifies (be->connection); note = PQnotifies (be->connection);
} }
/* handle each event type */ /* for now, we ignore session and checkpoint events */
if (do_account) if (be->do_transaction + be->do_price + be->do_account) return TRUE;
{ return FALSE;
}
if (do_checkpoint)
{
}
if (do_price)
{
}
if (do_session)
{
}
if (do_transaction)
{
}
} }
static gpointer
get_event_cb (PGBackend *be, PGresult *result, int j, gpointer data)
{
GNCEngineEventType type;
GUID guid;
Timespec ts;
Timespec *latest = (Timespec *) data;
char change = (DB_GET_VAL("change",j))[0];
string_to_guid (DB_GET_VAL("guid",j), &guid);
ts = gnc_iso8601_to_timespec_local (DB_GET_VAL("date_changed",j));
if (0 < timespec_cmp(&ts, latest)) *latest = ts;
switch (change)
{
case 'a': type = GNC_EVENT_CREATE; break;
case 'm': type = GNC_EVENT_MODIFY; break;
case 'd': type = GNC_EVENT_DESTROY; break;
default:
PERR ("unknown change type %c", change);
return data;
}
PINFO ("event %c for %s", change, DB_GET_VAL("guid",j));
gnc_engine_generate_event (&guid, type);
return data;
}
#define GET_EVENTS(guid_name,table,timestamp) \
{ \
Timespec latest; \
char *p; \
latest.tv_sec = -2; \
latest.tv_nsec = 0; \
\
p = be->buff; *p = 0; \
p = stpcpy (p, "SELECT change, date_changed, " #guid_name \
" AS guid FROM " #table \
" WHERE sessionGuid <> '"); \
p = stpcpy (p, be->session_guid_str); \
p = stpcpy (p, "' AND date_changed >= '"); \
p = gnc_timespec_to_iso8601_buff (timestamp, p); \
p = stpcpy (p, "';"); \
\
SEND_QUERY (be, be->buff, FALSE); \
pgendGetResults (be, get_event_cb, &latest); \
\
if (0 < timespec_cmp(&latest, &(timestamp))) \
{ \
(timestamp) = latest; \
} \
}
static gboolean
pgendProcessEvents (Backend *bend)
{
PGBackend *be = (PGBackend *) bend;
if (!be) return FALSE;
ENTER (" ");
/* handle each event type */
if (be->do_account)
{
GET_EVENTS (accountGuid, gncAccountTrail, be->last_account);
}
if (be->do_price)
{
GET_EVENTS (priceGuid, gncPriceTrail, be->last_price);
}
if (be->do_transaction)
{
GET_EVENTS (transGuid, gncTransactionTrail, be->last_transaction);
/* gnc_cm_event_handler() doesn't really want to see any split guids */
// GET_EVENTS (entryGuid, gncEntryTrail, be->last_transaction);
}
be->do_account = 0;
be->do_checkpoint = 0;
be->do_price = 0;
be->do_session = 0;
be->do_transaction = 0;
return FALSE;
}
/* ============================================================= */ /* ============================================================= */
/* ============================================================= */ /* ============================================================= */
/* ACCOUNT AND GROUP STUFF */ /* ACCOUNT AND GROUP STUFF */
@ -1081,7 +1125,6 @@ pgendCopyTransactionToEngine (PGBackend *be, const GUID *trans_guid)
guid_to_string (trans_guid)); guid_to_string (trans_guid));
if (jrows != nrows) xaccTransCommitEdit (trans); if (jrows != nrows) xaccTransCommitEdit (trans);
xaccBackendSetError (&be->be, ERR_BACKEND_DATA_CORRUPT); xaccBackendSetError (&be->be, ERR_BACKEND_DATA_CORRUPT);
pgendHandleEvents(be);
pgendEnable(be); pgendEnable(be);
gnc_engine_resume_events(); gnc_engine_resume_events();
return 0; return 0;
@ -1147,7 +1190,6 @@ pgendCopyTransactionToEngine (PGBackend *be, const GUID *trans_guid)
* punt for now ... */ * punt for now ... */
PERR ("no such transaction in the database. This is unexpected ...\n"); PERR ("no such transaction in the database. This is unexpected ...\n");
xaccBackendSetError (&be->be, ERR_SQL_MISSING_DATA); xaccBackendSetError (&be->be, ERR_SQL_MISSING_DATA);
pgendHandleEvents(be);
pgendEnable(be); pgendEnable(be);
gnc_engine_resume_events(); gnc_engine_resume_events();
return 0; return 0;
@ -1156,7 +1198,6 @@ pgendCopyTransactionToEngine (PGBackend *be, const GUID *trans_guid)
/* if engine data was newer, we are done */ /* if engine data was newer, we are done */
if (0 <= engine_data_is_newer) if (0 <= engine_data_is_newer)
{ {
pgendHandleEvents(be);
pgendEnable(be); pgendEnable(be);
gnc_engine_resume_events(); gnc_engine_resume_events();
return engine_data_is_newer; return engine_data_is_newer;
@ -1317,7 +1358,6 @@ pgendCopyTransactionToEngine (PGBackend *be, const GUID *trans_guid)
xaccTransCommitEdit (trans); xaccTransCommitEdit (trans);
/* re-enable events to the backend and GUI */ /* re-enable events to the backend and GUI */
pgendHandleEvents(be);
pgendEnable(be); pgendEnable(be);
gnc_engine_resume_events(); gnc_engine_resume_events();
@ -1383,7 +1423,6 @@ pgendSyncTransaction (PGBackend *be, GUID *trans_guid)
} }
/* re-enable events to the backend and GUI */ /* re-enable events to the backend and GUI */
pgendHandleEvents(be);
pgendEnable(be); pgendEnable(be);
gnc_engine_resume_events(); gnc_engine_resume_events();
@ -1614,7 +1653,6 @@ pgendRunQuery (Backend *bend, Query *q)
* mark it all as having been saved. */ * mark it all as having been saved. */
xaccGroupMarkSaved (be->topgroup); xaccGroupMarkSaved (be->topgroup);
pgendHandleEvents(be);
pgendEnable(be); pgendEnable(be);
gnc_engine_resume_events(); gnc_engine_resume_events();
@ -1651,7 +1689,6 @@ pgendGetAllTransactions (PGBackend *be, AccountGroup *grp)
} }
g_list_free(xaction_list); g_list_free(xaction_list);
pgendHandleEvents(be);
pgendEnable(be); pgendEnable(be);
gnc_engine_resume_events(); gnc_engine_resume_events();
} }
@ -1944,7 +1981,6 @@ pgendPriceLookup (Backend *bend, GNCPriceLookup *look)
gnc_pricedb_mark_clean (look->prdb); gnc_pricedb_mark_clean (look->prdb);
/* re-enable events */ /* re-enable events */
pgendHandleEvents(be);
pgendEnable(be); pgendEnable(be);
gnc_engine_resume_events(); gnc_engine_resume_events();
@ -2369,7 +2405,6 @@ pgendSync (Backend *bend, AccountGroup *grp)
} }
/* re-enable events */ /* re-enable events */
pgendHandleEvents(be);
pgendEnable(be); pgendEnable(be);
gnc_engine_resume_events(); gnc_engine_resume_events();
@ -2448,7 +2483,6 @@ pgendSyncPriceDB (Backend *bend, GNCPriceDB *prdb)
pgendGetAllPrices (be, prdb); pgendGetAllPrices (be, prdb);
/* re-enable events */ /* re-enable events */
pgendHandleEvents(be);
pgendEnable(be); pgendEnable(be);
gnc_engine_resume_events(); gnc_engine_resume_events();
@ -2726,6 +2760,7 @@ pgendSessionValidate (PGBackend *be, int break_lock)
/* make note of the session. */ /* make note of the session. */
be->sessionGuid = xaccGUIDMalloc(); be->sessionGuid = xaccGUIDMalloc();
guid_new (be->sessionGuid); guid_new (be->sessionGuid);
guid_to_string_buff (be->sessionGuid, be->session_guid_str);
pgendStoreOneSessionOnly (be, (void *)-1, SQL_INSERT); pgendStoreOneSessionOnly (be, (void *)-1, SQL_INSERT);
retval = TRUE; retval = TRUE;
} }
@ -2754,7 +2789,7 @@ pgendSessionEnd (PGBackend *be)
p = be->buff; *p=0; p = be->buff; *p=0;
p = stpcpy (p, "UPDATE gncSession SET time_off='NOW' " p = stpcpy (p, "UPDATE gncSession SET time_off='NOW' "
"WHERE sessionGuid='"); "WHERE sessionGuid='");
p = guid_to_string_buff (be->sessionGuid, p); p = stpcpy (p, be->session_guid_str);
p = stpcpy (p, "';\n" p = stpcpy (p, "';\n"
"NOTIFY gncSession;"); "NOTIFY gncSession;");
@ -2762,6 +2797,7 @@ pgendSessionEnd (PGBackend *be)
FINISH_QUERY(be->connection); FINISH_QUERY(be->connection);
xaccGUIDFree (be->sessionGuid); be->sessionGuid = NULL; xaccGUIDFree (be->sessionGuid); be->sessionGuid = NULL;
guid_to_string_buff (&nullguid, be->session_guid_str);
} }
/* ============================================================= */ /* ============================================================= */
@ -2860,7 +2896,6 @@ pgend_book_load_poll (Backend *bend)
pgendGroupGetAllBalances (be, grp, ts); pgendGroupGetAllBalances (be, grp, ts);
/* re-enable events */ /* re-enable events */
pgendHandleEvents(be);
pgendEnable(be); pgendEnable(be);
gnc_engine_resume_events(); gnc_engine_resume_events();
@ -3053,17 +3088,17 @@ pgend_session_begin (GNCBook *sess, const char * sessionid,
if (0 == strcasecmp (start, "single-update")) { if (0 == strcasecmp (start, "single-update")) {
be->session_mode = MODE_SINGLE_UPDATE; be->session_mode = MODE_SINGLE_UPDATE;
} else } else
if (0 == strcasecmp (start, "multi-user")) { if (0 == strcasecmp (start, "multi-user-poll")) {
be->session_mode = MODE_POLL; be->session_mode = MODE_POLL;
} else } else
if (0 == strcasecmp (start, "multi-user-event")) { if (0 == strcasecmp (start, "multi-user")) {
be->session_mode = MODE_EVENT; be->session_mode = MODE_EVENT;
} else } else
{ {
PWARN ("the following message should be shown in a gui"); PWARN ("the following message should be shown in a gui");
PWARN ("unknown mode %s, will use single-update mode", PWARN ("unknown mode %s, will use multi-user mode",
start ? start : "(null)"); start ? start : "(null)");
be->session_mode = MODE_SINGLE_UPDATE; be->session_mode = MODE_EVENT;
} }
} else } else
@ -3344,7 +3379,9 @@ pgend_session_begin (GNCBook *sess, const char * sessionid,
be->be.price_lookup = NULL; be->be.price_lookup = NULL;
be->be.sync = pgendSyncSingleFile; be->be.sync = pgendSyncSingleFile;
be->be.sync_price = pgendSyncPriceDBSingleFile; be->be.sync_price = pgendSyncPriceDBSingleFile;
PWARN ("MODE_SINGLE_FILE is final beta -- \n" be->be.events_pending = NULL;
be->be.process_events = NULL;
PWARN ("mode=single-file is final beta -- \n"
"we've fixed all known bugs but that doesn't mean\n" "we've fixed all known bugs but that doesn't mean\n"
"there aren't any! We think its safe to use.\n"); "there aren't any! We think its safe to use.\n");
break; break;
@ -3364,13 +3401,39 @@ pgend_session_begin (GNCBook *sess, const char * sessionid,
be->be.price_lookup = NULL; be->be.price_lookup = NULL;
be->be.sync = pgendSync; be->be.sync = pgendSync;
be->be.sync_price = pgendSyncPriceDB; be->be.sync_price = pgendSyncPriceDB;
PWARN ("MODE_SINGLE_UPDATE is final beta -- \n" be->be.events_pending = NULL;
be->be.process_events = NULL;
PWARN ("mode=single-update is final beta -- \n"
"we've fixed all known bugs but that doesn't mean\n" "we've fixed all known bugs but that doesn't mean\n"
"there aren't any! We think its safe to use.\n"); "there aren't any! We think its safe to use.\n");
break; break;
case MODE_POLL: case MODE_POLL:
pgendEnable(be); pgendEnable(be);
be->be.book_load = pgend_book_load_poll;
be->be.price_load = pgend_price_load_poll;
be->be.account_begin_edit = NULL;
be->be.account_commit_edit = pgend_account_commit_edit;
be->be.trans_begin_edit = NULL;
be->be.trans_commit_edit = pgend_trans_commit_edit;
be->be.trans_rollback_edit = pgend_trans_rollback_edit;
be->be.price_begin_edit = pgend_price_begin_edit;
be->be.price_commit_edit = pgend_price_commit_edit;
be->be.run_query = pgendRunQuery;
be->be.price_lookup = pgendPriceLookup;
// be->be.sync = pgendSync;
be->be.sync = NULL;
be->be.sync_price = pgendSyncPriceDB;
be->be.events_pending = NULL;
be->be.process_events = NULL;
PWARN ("mode=multi-user-poll is beta -- \n"
"we've fixed all known bugs but that doesn't mean\n"
"there aren't any! If something seems weird, let us know.\n");
break;
case MODE_EVENT:
pgendEnable(be);
pgendSessionGetPid (be); pgendSessionGetPid (be);
pgendSessionSetupNotifies (be); pgendSessionSetupNotifies (be);
@ -3389,13 +3452,13 @@ pgend_session_begin (GNCBook *sess, const char * sessionid,
// be->be.sync = pgendSync; // be->be.sync = pgendSync;
be->be.sync = NULL; be->be.sync = NULL;
be->be.sync_price = pgendSyncPriceDB; be->be.sync_price = pgendSyncPriceDB;
PWARN ("MODE_POLL is beta -- \n" be->be.events_pending = pgendEventsPending;
be->be.process_events = pgendProcessEvents;
PWARN ("mode=multi-user is beta -- \n"
"we've fixed all known bugs but that doesn't mean\n" "we've fixed all known bugs but that doesn't mean\n"
"there aren't any! If something seems weird, let us know.\n"); "there aren't any! If something seems weird, let us know.\n");
break;
case MODE_EVENT:
PERR ("MODE_EVENT is unimplemented");
break; break;
default: default:
@ -3433,6 +3496,8 @@ pgendDisable (PGBackend *be)
be->snr.price_lookup = be->be.price_lookup; be->snr.price_lookup = be->be.price_lookup;
be->snr.sync = be->be.sync; be->snr.sync = be->be.sync;
be->snr.sync_price = be->be.sync_price; be->snr.sync_price = be->be.sync_price;
be->snr.events_pending = be->be.events_pending;
be->snr.process_events = be->be.process_events;
be->be.account_begin_edit = NULL; be->be.account_begin_edit = NULL;
be->be.account_commit_edit = NULL; be->be.account_commit_edit = NULL;
@ -3445,6 +3510,8 @@ pgendDisable (PGBackend *be)
be->be.price_lookup = NULL; be->be.price_lookup = NULL;
be->be.sync = NULL; be->be.sync = NULL;
be->be.sync_price = NULL; be->be.sync_price = NULL;
be->be.events_pending = NULL;
be->be.process_events = NULL;
} }
/* ============================================================= */ /* ============================================================= */
@ -3472,6 +3539,8 @@ pgendEnable (PGBackend *be)
be->be.price_lookup = be->snr.price_lookup; be->be.price_lookup = be->snr.price_lookup;
be->be.sync = be->snr.sync; be->be.sync = be->snr.sync;
be->be.sync_price = be->snr.sync_price; be->be.sync_price = be->snr.sync_price;
be->be.events_pending = be->snr.events_pending;
be->be.process_events = be->snr.process_events;
} }
/* ============================================================= */ /* ============================================================= */
@ -3483,13 +3552,15 @@ static void
pgendInit (PGBackend *be) pgendInit (PGBackend *be)
{ {
int i; int i;
Timespec ts;
/* initialize global variable */ /* initialize global variable */
nullguid = *(xaccGUIDNULL()); nullguid = *(xaccGUIDNULL());
/* access mode */ /* access mode */
be->session_mode = MODE_SINGLE_UPDATE; be->session_mode = MODE_EVENT;
be->sessionGuid = NULL; be->sessionGuid = NULL;
guid_to_string_buff (&nullguid, be->session_guid_str);
/* generic backend handlers */ /* generic backend handlers */
be->be.book_begin = pgend_session_begin; be->be.book_begin = pgend_session_begin;
@ -3524,6 +3595,18 @@ pgendInit (PGBackend *be)
be->connection = NULL; be->connection = NULL;
be->my_pid = 0; be->my_pid = 0;
be->do_account = 0;
be->do_checkpoint = 0;
be->do_price = 0;
be->do_session = 0;
be->do_transaction = 0;
ts.tv_sec = time (0);
ts.tv_nsec = 0;
be->last_account = ts;
be->last_price = ts;
be->last_transaction = ts;
be->builder = sqlBuilder_new(); be->builder = sqlBuilder_new();

View File

@ -59,6 +59,7 @@ struct _pgend {
/* session mode */ /* session mode */
AccessMode session_mode; AccessMode session_mode;
GUID *sessionGuid; GUID *sessionGuid;
char session_guid_str[GUID_ENCODING_LENGTH+1];
/* sql query compiler */ /* sql query compiler */
sqlBuilder *builder; sqlBuilder *builder;
@ -78,6 +79,18 @@ struct _pgend {
/* my postgres backend pid, used for telling apart notifies */ /* my postgres backend pid, used for telling apart notifies */
int my_pid; int my_pid;
/* notify counters */
int do_account;
int do_checkpoint;
int do_price;
int do_session;
int do_transaction;
/* notify dates */
Timespec last_account;
Timespec last_price;
Timespec last_transaction;
/* scratch space for constructing queries */ /* scratch space for constructing queries */
int bufflen; int bufflen;
char *buff; char *buff;

View File

@ -168,7 +168,12 @@ trail.
The audit trails are used internally to resolve certain multi-user The audit trails are used internally to resolve certain multi-user
editing conflicts, for example, when one user attempts to edit a editing conflicts, for example, when one user attempts to edit a
transaction that has been deleteed by another. transaction that has been deleted by another.
They are also used to discover changes that other users have made,
and thus update the local GUI display. This is done in the
pgendProcessEvents() routine and specifically, in the get_event_cb()
callback.
Balances Balances

View File

@ -401,7 +401,8 @@ pgendStoreAudit`'func_name($@)`' (PGBackend *be,
#undef sqlBuild_Where_GUID #undef sqlBuild_Where_GUID
#undef sqlBuild_Where_Int32 #undef sqlBuild_Where_Int32
sqlBuild_Set_Str (be->builder, "date_changed", "NOW"); sqlBuild_Set_Str (be->builder, "date_changed", "NOW");
sqlBuild_Set_GUID (be->builder, "sessionGUID", be->sessionGuid); /* sqlBuild_Set_GUID (be->builder, "sessionGUID", be->sessionGuid); */
sqlBuild_Set_Str (be->builder, "sessionGUID", be->session_guid_str);
sqlBuild_Set_Char (be->builder, "change", update); sqlBuild_Set_Char (be->builder, "change", update);
buf = sqlBuild_Query (be->builder); buf = sqlBuild_Query (be->builder);