add support for multiple books

git-svn-id: svn+ssh://svn.gnucash.org/repo/gnucash/trunk@6502 57a11ea4-9604-0410-9ed3-97b8803252fd
This commit is contained in:
Linas Vepstas 2002-01-04 18:19:24 +00:00
parent 8ee531499a
commit 63901220a2
3 changed files with 235 additions and 97 deletions

View File

@ -37,20 +37,21 @@
struct gnc_session_struct struct gnc_session_struct
{ {
/* The book is the cache for the underlying gnucash dataset. */ /* A book holds pointers to the various types of datasets used
GNCBook *book; * by GnuCash. A session may have open multiple books. */
GList *books;
/* the requested book id, in the form or a URI, such as /* the requested book id, in the form or a URI, such as
* file:/some/where, or sql:server.host.com:555 * file:/some/where, or sql:server.host.com:555
*/ */
char *book_id; char *book_id;
/* if any book subroutine failed, this records the failure reason /* If any book subroutine failed, this records the failure reason
* (file not found, etc). * (file not found, etc).
* This is a 'stack' that is one deep. * This is a 'stack' that is one deep. (Should be deeper ??)
* FIXME: This is a hack. I'm trying to move us away from static * FIXME: Each backend has its own error stack. The session
* global vars. This may be a temp fix if we decide to integrate * and the backends should all be using (or making it look like)
* FileIO errors into GNCBook errors. * there is only one stack.
*/ */
GNCBackendError last_err; GNCBackendError last_err;
char *error_message; char *error_message;
@ -65,16 +66,17 @@ struct gnc_session_struct
}; };
/*
* gnc_session_set_book() has funny semantics.
* The session stores a list of books. If you call this routine
* with a book that is closed, then its added to the list. If
* you call this routine with a book that is open, then the
* old list is blown away.
*/
void gnc_session_set_book (GNCSession *session, GNCBook *book); void gnc_session_set_book (GNCSession *session, GNCBook *book);
Backend * gnc_session_get_backend (GNCSession *session); Backend * gnc_session_get_backend (GNCSession *session);
/*
* used by backends to mark the notsaved as FALSE just after
* loading. Do not use otherwise!
*/
void gnc_session_push_error (GNCSession *session, GNCBackendError err, void gnc_session_push_error (GNCSession *session, GNCBackendError err,
const char *message); const char *message);

View File

@ -55,14 +55,26 @@
static short module = MOD_IO; static short module = MOD_IO;
/* ---------------------------------------------------------------------- */ /* ====================================================================== */
/* error handling routines */
static void static void
gnc_session_clear_error (GNCSession *session) gnc_session_clear_error (GNCSession *session)
{ {
GNCBackendError err;
session->last_err = ERR_BACKEND_NO_ERR; session->last_err = ERR_BACKEND_NO_ERR;
g_free(session->error_message); g_free(session->error_message);
session->error_message = NULL; session->error_message = NULL;
/* pop the stack on the backend as well. */
if (session->backend)
{
do
{
err = xaccBackendGetError (session->backend);
} while (ERR_BACKEND_NO_ERR != err);
}
} }
void void
@ -80,8 +92,22 @@ gnc_session_push_error (GNCSession *session, GNCBackendError err,
GNCBackendError GNCBackendError
gnc_session_get_error (GNCSession * session) gnc_session_get_error (GNCSession * session)
{ {
GNCBackendError err;
if (!session) return ERR_BACKEND_NO_BACKEND; if (!session) return ERR_BACKEND_NO_BACKEND;
return session->last_err;
/* if we have a local error, return that. */
if (ERR_BACKEND_NO_ERR != session->last_err)
{
return session->last_err;
}
/* maybe we should return a no-backend error ??? */
if (! session->backend) return ERR_BACKEND_NO_ERR;
err = xaccBackendGetError (session->backend);
session->last_err = err;
return err;
} }
static const char * static const char *
@ -106,20 +132,20 @@ gnc_session_pop_error (GNCSession * session)
if (!session) return ERR_BACKEND_NO_BACKEND; if (!session) return ERR_BACKEND_NO_BACKEND;
err = session->last_err; err = gnc_session_get_error(session);
gnc_session_clear_error(session); gnc_session_clear_error(session);
return err; return err;
} }
/* ---------------------------------------------------------------------- */ /* ====================================================================== */
static void static void
gnc_session_init (GNCSession *session) gnc_session_init (GNCSession *session)
{ {
if (!session) return; if (!session) return;
session->book = gnc_book_new (); session->books = g_list_append (NULL, gnc_book_new ());
session->book_id = NULL; session->book_id = NULL;
session->fullpath = NULL; session->fullpath = NULL;
session->logpath = NULL; session->logpath = NULL;
@ -139,25 +165,46 @@ gnc_session_new (void)
GNCBook * GNCBook *
gnc_session_get_book (GNCSession *session) gnc_session_get_book (GNCSession *session)
{ {
GList *node;
if (!session) return NULL; if (!session) return NULL;
return session->book;
for (node=session->books; node; node=node->next)
{
GNCBook *book = node->data;
if ('y' == book->book_open) return book;
}
return NULL;
} }
void void
gnc_session_set_book (GNCSession *session, GNCBook *book) gnc_session_set_book (GNCSession *session, GNCBook *addbook)
{ {
GList *node;
if (!session) return; if (!session) return;
ENTER (" sess=%p book=%p", session, book); ENTER (" sess=%p book=%p", session, addbook);
/* Do not free the old book here unless you also fix
* all the other uses of gnc_session_set_book! */
if (session->book == book) /* See if this book is already there ... */
return; for (node=session->books; node; node=node->next)
{
GNCBook *book = node->data;
if (addbook == book) return;
}
session->book = book; if ('y' == addbook->book_open)
{
/* hack alert -- someone should free all the books in the list,
* but it should probably not be us ... since the books backends
* should be shutdown first, etc */
g_list_free (session->books);
session->books = g_list_append (NULL, addbook);
}
else
{
session->books = g_list_append (session->books, addbook);
}
gnc_book_set_backend (book, session->backend); gnc_book_set_backend (addbook, session->backend);
LEAVE (" "); LEAVE (" ");
} }
@ -182,6 +229,8 @@ gnc_session_get_url (GNCSession *session)
return session->book_id; return session->book_id;
} }
/* ====================================================================== */
static void static void
gnc_session_int_backend_load_error(GNCSession *session, gnc_session_int_backend_load_error(GNCSession *session,
char *message, char *dll_err) char *message, char *dll_err)
@ -222,9 +271,14 @@ gnc_session_load_backend(GNCSession * session, char * backend_name)
if(be_new_func) if(be_new_func)
{ {
GList *node;
session->backend = be_new_func(); session->backend = be_new_func();
gnc_book_set_backend (session->book, session->backend); for (node=session->books; node; node=node->next)
{
GNCBook *book = node->data;
gnc_book_set_backend (book, session->backend);
}
} }
else else
{ {
@ -243,14 +297,16 @@ gnc_session_load_backend(GNCSession * session, char * backend_name)
LEAVE (" "); LEAVE (" ");
} }
/* ====================================================================== */
void void
gnc_session_begin (GNCSession *session, const char * book_id, gnc_session_begin (GNCSession *session, const char * book_id,
gboolean ignore_lock, gboolean create_if_nonexistent) gboolean ignore_lock, gboolean create_if_nonexistent)
{ {
if (!session) return; if (!session) return;
ENTER (" sess=%p book=%p ignore_lock=%d, book-id=%s", ENTER (" sess=%p ignore_lock=%d, book-id=%s",
session, session->book, ignore_lock, session, ignore_lock,
book_id ? book_id : "(null)"); book_id ? book_id : "(null)");
/* clear the error condition of previous errors */ /* clear the error condition of previous errors */
@ -260,25 +316,26 @@ gnc_session_begin (GNCSession *session, const char * book_id,
if (gnc_session_get_url(session)) if (gnc_session_get_url(session))
{ {
gnc_session_push_error (session, ERR_BACKEND_LOCKED, NULL); gnc_session_push_error (session, ERR_BACKEND_LOCKED, NULL);
LEAVE("bad book url"); LEAVE("push error book is already open ");
return; return;
} }
/* seriously invalid */ /* seriously invalid */
if (!book_id) if (!book_id)
{ {
gnc_session_push_error (session, ERR_BACKEND_NO_BACKEND, NULL); gnc_session_push_error (session, ERR_BACKEND_BAD_URL, NULL);
LEAVE("bad book_id"); LEAVE("push error missing book_id");
return; return;
} }
/* Store the sessionid URL */ /* Store the sessionid URL */
session->book_id = g_strdup (book_id); session->book_id = g_strdup (book_id);
/* ResolveURL tries to find the file in the file system. */
session->fullpath = xaccResolveURL(book_id); session->fullpath = xaccResolveURL(book_id);
if (!session->fullpath) if (!session->fullpath)
{ {
gnc_session_push_error (session, ERR_FILEIO_FILE_NOT_FOUND, NULL); gnc_session_push_error (session, ERR_FILEIO_FILE_NOT_FOUND, NULL);
LEAVE("bad fullpath"); LEAVE("push error: can't resolve file path");
return; return;
} }
PINFO ("filepath=%s", session->fullpath ? session->fullpath : "(null)"); PINFO ("filepath=%s", session->fullpath ? session->fullpath : "(null)");
@ -345,17 +402,23 @@ gnc_session_begin (GNCSession *session, const char * book_id,
return; return;
} }
} }
LEAVE (" sess=%p book=%p book-id=%s",
session, session->book, /* No backend was found. That's bad. */
book_id ? book_id : "(null)"); if (NULL == session->backend)
{
gnc_session_push_error (session, ERR_BACKEND_BAD_URL, NULL);
}
LEAVE (" sess=%p book-id=%s",
session, book_id ? book_id : "(null)");
} }
/* ---------------------------------------------------------------------- */ /* ====================================================================== */
void void
gnc_session_load (GNCSession *session) gnc_session_load (GNCSession *session)
{ {
GNCBook *oldbook; GNCBook *newbook;
BookList *oldbooks, *node;
Backend *be; Backend *be;
if (!session) return; if (!session) return;
@ -368,9 +431,10 @@ gnc_session_load (GNCSession *session)
/* At this point, we should are supposed to have a valid book /* At this point, we should are supposed to have a valid book
* id and a lock on the file. */ * id and a lock on the file. */
oldbook = session->book; oldbooks = session->books;
session->book = gnc_book_new (); newbook = gnc_book_new();
PINFO ("new book=%p", session->book); session->books = g_list_append (NULL, newbook);
PINFO ("new book=%p", newbook);
xaccLogSetBaseName(session->logpath); xaccLogSetBaseName(session->logpath);
@ -395,55 +459,77 @@ gnc_session_load (GNCSession *session)
if (be->book_load) if (be->book_load)
{ {
be->book_load (be, session->book); be->book_load (be, newbook);
gnc_session_push_error (session, xaccBackendGetError(be), NULL); gnc_session_push_error (session, xaccBackendGetError(be), NULL);
} }
if (be->price_load) if (be->price_load)
{ {
be->price_load (be, session->book); be->price_load (be, newbook);
gnc_session_push_error(session, xaccBackendGetError(be), NULL); gnc_session_push_error(session, xaccBackendGetError(be), NULL);
} }
gnc_book_set_backend (session->book, be); gnc_book_set_backend (newbook, be);
/* we just got done loading, it can't possibly be dirty !! */ /* we just got done loading, it can't possibly be dirty !! */
gnc_book_mark_saved (session->book); gnc_book_mark_saved (newbook);
xaccLogEnable(); xaccLogEnable();
} }
if (!gnc_book_get_group (session->book)) /* Technically, the following tests can never succeed, because a group
* and pricedb is always allocated when a book is created. So even
* if the load fails, there will be a topgroup and a pricedb. */
if (!gnc_book_get_group (newbook))
{ {
/* ?? should we restore the oldbook here ?? */ /* Something broke, put back the old stuff */
LEAVE("topgroup NULL"); gnc_book_set_backend (newbook, NULL);
gnc_book_destroy (newbook);
g_list_free (session->books);
session->books = oldbooks;
PERR("topgroup NULL");
return; return;
} }
if (!gnc_book_get_pricedb (session->book)) if (!gnc_book_get_pricedb (newbook))
{ {
/* ?? should we restore the oldbook here ?? */ /* Something broke, put back the old stuff */
LEAVE("pricedb NULL"); gnc_book_set_backend (newbook, NULL);
gnc_book_destroy (newbook);
g_list_free (session->books);
session->books = oldbooks;
PERR("pricedb NULL");
return; return;
} }
if (gnc_session_get_error(session) != ERR_BACKEND_NO_ERR) if (gnc_session_get_error(session) != ERR_BACKEND_NO_ERR)
{ {
/* Something broke, put back the old stuff */
gnc_book_set_backend (newbook, NULL);
gnc_book_destroy (newbook);
g_list_free (session->books);
session->books = oldbooks;
LEAVE("error from backend %d", gnc_session_get_error(session)); LEAVE("error from backend %d", gnc_session_get_error(session));
return; return;
} }
xaccLogDisable(); xaccLogDisable();
gnc_book_set_backend (oldbook, NULL); for (node=oldbooks; node; node=node->next)
gnc_book_destroy (oldbook); {
GNCBook *ob = node->data;
gnc_book_set_backend (ob, NULL);
gnc_book_destroy (ob);
}
xaccLogEnable(); xaccLogEnable();
LEAVE ("sess = %p, book_id=%s", session, gnc_session_get_url(session) LEAVE ("sess = %p, book_id=%s", session, gnc_session_get_url(session)
? gnc_session_get_url(session) : "(null)"); ? gnc_session_get_url(session) : "(null)");
} }
/* ====================================================================== */
gboolean gboolean
gnc_session_save_may_clobber_data (GNCSession *session) gnc_session_save_may_clobber_data (GNCSession *session)
{ {
@ -482,48 +568,56 @@ save_error_handler(Backend *be, GNCSession *session)
void void
gnc_session_save (GNCSession *session) gnc_session_save (GNCSession *session)
{ {
GList *node;
Backend *be; Backend *be;
if (!session) return; if (!session) return;
ENTER ("sess=%p book=%p book_id=%s", ENTER ("sess=%p book_id=%s",
session, session->book, session,
gnc_session_get_url(session) gnc_session_get_url(session)
? gnc_session_get_url(session) : "(null)"); ? gnc_session_get_url(session) : "(null)");
/* if there is a backend, and the backend is reachablele /* If there is a backend, and the backend is reachable
* (i.e. we can communicate with it), then synchronize with * (i.e. we can communicate with it), then synchronize with
* the backend. If we cannot contact the backend (e.g. * the backend. If we cannot contact the backend (e.g.
* because we've gone offline, the network has crashed, etc.) * because we've gone offline, the network has crashed, etc.)
* then give the user the option to save to disk. * then give the user the option to save to disk.
*
* hack alert -- FIXME -- XXX the code below no longer
* does what the words above say. This needs fixing.
*/ */
be = session->backend; be = session->backend;
if (be) if (be)
{ {
/* if invoked as SaveAs(), then backend not yet set */ for (node = session->books; node; node=node->next)
gnc_book_set_backend (session->book, be);
if (be->sync_all)
{ {
(be->sync_all)(be, session->book); GNCBook *abook = node->data;
if (save_error_handler(be, session))
return;
}
if (be->sync_group) /* if invoked as SaveAs(), then backend not yet set */
{ gnc_book_set_backend (abook, be);
(be->sync_group)(be, session->book);
if (save_error_handler(be, session)) if (be->sync_all)
return; {
(be->sync_all)(be, abook);
if (save_error_handler(be, session)) return;
}
if (be->sync_group)
{
(be->sync_group)(be, abook);
if (save_error_handler(be, session)) return;
}
if (be->sync_price)
{
(be->sync_price)(be, abook);
if(save_error_handler(be, session)) return;
}
} }
if (be->sync_price) /* If we got to here, then the backend saved everything
{ * just fine, and we are done. So return. */
(be->sync_price)(be, session->book);
if(save_error_handler(be, session))
return;
}
return; return;
} }
@ -540,7 +634,7 @@ gnc_session_save (GNCSession *session)
LEAVE(" "); LEAVE(" ");
} }
/* ---------------------------------------------------------------------- */ /* ====================================================================== */
void void
gnc_session_end (GNCSession *session) gnc_session_end (GNCSession *session)
@ -574,9 +668,10 @@ gnc_session_end (GNCSession *session)
void void
gnc_session_destroy (GNCSession *session) gnc_session_destroy (GNCSession *session)
{ {
GList *node;
if (!session) return; if (!session) return;
ENTER ("sess=%p book=%p book_id=%s", session, session->book, ENTER ("sess=%p book_id=%s", session,
gnc_session_get_url(session) gnc_session_get_url(session)
? gnc_session_get_url(session) : "(null)"); ? gnc_session_get_url(session) : "(null)");
@ -593,10 +688,14 @@ gnc_session_destroy (GNCSession *session)
g_free(session->backend); g_free(session->backend);
} }
gnc_book_set_backend (session->book, NULL); for (node=session->books; node; node=node->next)
{
GNCBook *book = node->data;
gnc_book_set_backend (book, NULL);
gnc_book_destroy (book);
}
gnc_book_destroy (session->book); session->books = NULL;
session->book = NULL;
xaccLogEnable(); xaccLogEnable();
@ -605,27 +704,41 @@ gnc_session_destroy (GNCSession *session)
LEAVE ("sess=%p", session); LEAVE ("sess=%p", session);
} }
/* ====================================================================== */
/* this call is weird. */
void void
gnc_session_swap_data (GNCSession *session_1, GNCSession *session_2) gnc_session_swap_data (GNCSession *session_1, GNCSession *session_2)
{ {
GNCBook *book_1, *book_2; GList *books_1, *books_2, *node;
if (session_1 == session_2) return; if (session_1 == session_2) return;
if (!session_1 || !session_2) return; if (!session_1 || !session_2) return;
ENTER ("sess1=%p sess2=%p", session_1, session_2); ENTER ("sess1=%p sess2=%p", session_1, session_2);
book_1 = session_1->book; books_1 = session_1->books;
book_2 = session_2->book; books_2 = session_2->books;
session_1->book = book_2; session_1->books = books_2;
session_2->book = book_1; session_2->books = books_1;
for (node=books_1; node; node=node->next)
{
GNCBook *book_1 = node->data;
gnc_book_set_backend (book_1, session_2->backend);
}
for (node=books_2; node; node=node->next)
{
GNCBook *book_2 = node->data;
gnc_book_set_backend (book_2, session_1->backend);
}
gnc_book_set_backend (book_1, session_2->backend);
gnc_book_set_backend (book_2, session_1->backend);
LEAVE (" "); LEAVE (" ");
} }
/* ====================================================================== */
gboolean gboolean
gnc_session_events_pending (GNCSession *session) gnc_session_events_pending (GNCSession *session)
{ {
@ -646,7 +759,7 @@ gnc_session_process_events (GNCSession *session)
return session->backend->process_events (session->backend); return session->backend->process_events (session->backend);
} }
/* ---------------------------------------------------------------------- */ /* ====================================================================== */
/* /*
* If $HOME/.gnucash/data directory doesn't exist, then create it. * If $HOME/.gnucash/data directory doesn't exist, then create it.
*/ */
@ -685,7 +798,8 @@ MakeHomeDir (void)
g_free (data); g_free (data);
} }
/* ---------------------------------------------------------------------- */ /* ====================================================================== */
/* XXX hack alert -- we should be yanking this out of some config file */ /* XXX hack alert -- we should be yanking this out of some config file */
static char * searchpaths[] = static char * searchpaths[] =
{ {
@ -771,6 +885,8 @@ xaccUserPathPathGenerator(char *pathbuf, int which)
} }
} }
/* ====================================================================== */
char * char *
xaccResolveFilePath (const char * filefrag) xaccResolveFilePath (const char * filefrag)
{ {
@ -873,7 +989,7 @@ xaccResolveFilePath (const char * filefrag)
return NULL; return NULL;
} }
/* ---------------------------------------------------------------------- */ /* ====================================================================== */
char * char *
xaccResolveURL (const char * pathfrag) xaccResolveURL (const char * pathfrag)
@ -903,7 +1019,7 @@ xaccResolveURL (const char * pathfrag)
return (xaccResolveFilePath (pathfrag)); return (xaccResolveFilePath (pathfrag));
} }
/* ---------------------------------------------------------------------- */ /* ====================================================================== */
/* this should go in a separate binary to create a rpc server */ /* this should go in a separate binary to create a rpc server */
@ -938,3 +1054,5 @@ gnc_run_rpc_server (void)
/* XXX How do we force an exit? */ /* XXX How do we force an exit? */
} }
/* =================== END OF FILE ====================================== */

View File

@ -54,15 +54,33 @@
* they want to keep their data files. * they want to keep their data files.
* *
* 6) In the future, this class is probably a good place to manage * 6) In the future, this class is probably a good place to manage
* a portion of the user authentication porcess, and hold user * a portion of the user authentication process, and hold user
* credentials/cookies/keys/tokens. This is because at the * credentials/cookies/keys/tokens. This is because at the
* coarsest level, authorization can happen at the datastore * coarsest level, authorization can happen at the datastore
* level: i.e. does this user even have the authority to connect * level: i.e. does this user even have the authority to connect
* to and open this datastore? * to and open this datastore?
* *
* A breif note about books & sessions:
* A book encapsulates the datasets manipulated by GnuCash. A book
* holds the actual data. By contrast, the session mediates the
* connection between a book (the thing that lives in virtual memory
* in the local process) and the datastore (the place where book
* data lives permanently, e.g., file, database).
*
* In the current design, a session may hold multiple books. For
* now, exactly what this means is somewhat vague, and code in
* various places makes some implicit assumptions: first, only
* one book is 'current' and open for editing. Next, its assumed
* that all of the books in a session are related in some way.
* i.e. that they are all earlier accounting periods of the
* currently open book. In particular, the backends probably
* make that assumption, in order to store the different accounting
* periods in a clump so that one can be found, given another.
*
*
* HISTORY: * HISTORY:
* Created by Linas Vepstas December 1998 * Created by Linas Vepstas December 1998
* Copyright (c) 1998, 1999, 2001 Linas Vepstas <linas@linas.org> * Copyright (c) 1998, 1999, 2001, 2002 Linas Vepstas <linas@linas.org>
* Copyright (c) 2000 Dave Peticolas * Copyright (c) 2000 Dave Peticolas
*/ */
@ -127,7 +145,7 @@ void gnc_session_begin (GNCSession *session, const char * book_id,
* to use with this URL/datastore. When the URL points at a file, * to use with this URL/datastore. When the URL points at a file,
* then this routine would load the data from the file. With remote * then this routine would load the data from the file. With remote
* backends, e.g. network or SQL, this would load only enough data * backends, e.g. network or SQL, this would load only enough data
* to make teh book actually usable; it would not cause *all* of the * to make the book actually usable; it would not cause *all* of the
* data to be loaded. * data to be loaded.
*/ */
void gnc_session_load (GNCSession *session); void gnc_session_load (GNCSession *session);