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
{
/* The book is the cache for the underlying gnucash dataset. */
GNCBook *book;
/* A book holds pointers to the various types of datasets used
* by GnuCash. A session may have open multiple books. */
GList *books;
/* the requested book id, in the form or a URI, such as
* file:/some/where, or sql:server.host.com:555
*/
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).
* This is a 'stack' that is one deep.
* FIXME: This is a hack. I'm trying to move us away from static
* global vars. This may be a temp fix if we decide to integrate
* FileIO errors into GNCBook errors.
* This is a 'stack' that is one deep. (Should be deeper ??)
* FIXME: Each backend has its own error stack. The session
* and the backends should all be using (or making it look like)
* there is only one stack.
*/
GNCBackendError last_err;
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);
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,
const char *message);

View File

@ -55,14 +55,26 @@
static short module = MOD_IO;
/* ---------------------------------------------------------------------- */
/* ====================================================================== */
/* error handling routines */
static void
gnc_session_clear_error (GNCSession *session)
{
GNCBackendError err;
session->last_err = ERR_BACKEND_NO_ERR;
g_free(session->error_message);
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
@ -80,8 +92,22 @@ gnc_session_push_error (GNCSession *session, GNCBackendError err,
GNCBackendError
gnc_session_get_error (GNCSession * session)
{
GNCBackendError err;
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 *
@ -106,20 +132,20 @@ gnc_session_pop_error (GNCSession * session)
if (!session) return ERR_BACKEND_NO_BACKEND;
err = session->last_err;
err = gnc_session_get_error(session);
gnc_session_clear_error(session);
return err;
}
/* ---------------------------------------------------------------------- */
/* ====================================================================== */
static void
gnc_session_init (GNCSession *session)
{
if (!session) return;
session->book = gnc_book_new ();
session->books = g_list_append (NULL, gnc_book_new ());
session->book_id = NULL;
session->fullpath = NULL;
session->logpath = NULL;
@ -139,25 +165,46 @@ gnc_session_new (void)
GNCBook *
gnc_session_get_book (GNCSession *session)
{
GList *node;
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
gnc_session_set_book (GNCSession *session, GNCBook *book)
gnc_session_set_book (GNCSession *session, GNCBook *addbook)
{
GList *node;
if (!session) return;
ENTER (" sess=%p book=%p", session, book);
/* Do not free the old book here unless you also fix
* all the other uses of gnc_session_set_book! */
ENTER (" sess=%p book=%p", session, addbook);
if (session->book == book)
return;
/* See if this book is already there ... */
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 (" ");
}
@ -182,6 +229,8 @@ gnc_session_get_url (GNCSession *session)
return session->book_id;
}
/* ====================================================================== */
static void
gnc_session_int_backend_load_error(GNCSession *session,
char *message, char *dll_err)
@ -222,9 +271,14 @@ gnc_session_load_backend(GNCSession * session, char * backend_name)
if(be_new_func)
{
GList *node;
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
{
@ -243,14 +297,16 @@ gnc_session_load_backend(GNCSession * session, char * backend_name)
LEAVE (" ");
}
/* ====================================================================== */
void
gnc_session_begin (GNCSession *session, const char * book_id,
gboolean ignore_lock, gboolean create_if_nonexistent)
{
if (!session) return;
ENTER (" sess=%p book=%p ignore_lock=%d, book-id=%s",
session, session->book, ignore_lock,
ENTER (" sess=%p ignore_lock=%d, book-id=%s",
session, ignore_lock,
book_id ? book_id : "(null)");
/* 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))
{
gnc_session_push_error (session, ERR_BACKEND_LOCKED, NULL);
LEAVE("bad book url");
LEAVE("push error book is already open ");
return;
}
/* seriously invalid */
if (!book_id)
{
gnc_session_push_error (session, ERR_BACKEND_NO_BACKEND, NULL);
LEAVE("bad book_id");
gnc_session_push_error (session, ERR_BACKEND_BAD_URL, NULL);
LEAVE("push error missing book_id");
return;
}
/* Store the sessionid URL */
session->book_id = g_strdup (book_id);
/* ResolveURL tries to find the file in the file system. */
session->fullpath = xaccResolveURL(book_id);
if (!session->fullpath)
{
gnc_session_push_error (session, ERR_FILEIO_FILE_NOT_FOUND, NULL);
LEAVE("bad fullpath");
LEAVE("push error: can't resolve file path");
return;
}
PINFO ("filepath=%s", session->fullpath ? session->fullpath : "(null)");
@ -345,17 +402,23 @@ gnc_session_begin (GNCSession *session, const char * book_id,
return;
}
}
LEAVE (" sess=%p book=%p book-id=%s",
session, session->book,
book_id ? book_id : "(null)");
/* No backend was found. That's bad. */
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
gnc_session_load (GNCSession *session)
{
GNCBook *oldbook;
GNCBook *newbook;
BookList *oldbooks, *node;
Backend *be;
if (!session) return;
@ -368,9 +431,10 @@ gnc_session_load (GNCSession *session)
/* At this point, we should are supposed to have a valid book
* id and a lock on the file. */
oldbook = session->book;
session->book = gnc_book_new ();
PINFO ("new book=%p", session->book);
oldbooks = session->books;
newbook = gnc_book_new();
session->books = g_list_append (NULL, newbook);
PINFO ("new book=%p", newbook);
xaccLogSetBaseName(session->logpath);
@ -395,55 +459,77 @@ gnc_session_load (GNCSession *session)
if (be->book_load)
{
be->book_load (be, session->book);
be->book_load (be, newbook);
gnc_session_push_error (session, xaccBackendGetError(be), NULL);
}
if (be->price_load)
{
be->price_load (be, session->book);
be->price_load (be, newbook);
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 !! */
gnc_book_mark_saved (session->book);
gnc_book_mark_saved (newbook);
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 ?? */
LEAVE("topgroup NULL");
/* 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;
PERR("topgroup NULL");
return;
}
if (!gnc_book_get_pricedb (session->book))
if (!gnc_book_get_pricedb (newbook))
{
/* ?? should we restore the oldbook here ?? */
LEAVE("pricedb NULL");
/* 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;
PERR("pricedb NULL");
return;
}
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));
return;
}
xaccLogDisable();
gnc_book_set_backend (oldbook, NULL);
gnc_book_destroy (oldbook);
for (node=oldbooks; node; node=node->next)
{
GNCBook *ob = node->data;
gnc_book_set_backend (ob, NULL);
gnc_book_destroy (ob);
}
xaccLogEnable();
LEAVE ("sess = %p, book_id=%s", session, gnc_session_get_url(session)
? gnc_session_get_url(session) : "(null)");
}
/* ====================================================================== */
gboolean
gnc_session_save_may_clobber_data (GNCSession *session)
{
@ -482,48 +568,56 @@ save_error_handler(Backend *be, GNCSession *session)
void
gnc_session_save (GNCSession *session)
{
GList *node;
Backend *be;
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) : "(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
* the backend. If we cannot contact the backend (e.g.
* because we've gone offline, the network has crashed, etc.)
* 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;
if (be)
{
/* if invoked as SaveAs(), then backend not yet set */
gnc_book_set_backend (session->book, be);
if (be->sync_all)
for (node = session->books; node; node=node->next)
{
(be->sync_all)(be, session->book);
if (save_error_handler(be, session))
return;
}
GNCBook *abook = node->data;
if (be->sync_group)
{
(be->sync_group)(be, session->book);
if (save_error_handler(be, session))
return;
/* if invoked as SaveAs(), then backend not yet set */
gnc_book_set_backend (abook, be);
if (be->sync_all)
{
(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)
{
(be->sync_price)(be, session->book);
if(save_error_handler(be, session))
return;
}
/* If we got to here, then the backend saved everything
* just fine, and we are done. So return. */
return;
}
@ -540,7 +634,7 @@ gnc_session_save (GNCSession *session)
LEAVE(" ");
}
/* ---------------------------------------------------------------------- */
/* ====================================================================== */
void
gnc_session_end (GNCSession *session)
@ -574,9 +668,10 @@ gnc_session_end (GNCSession *session)
void
gnc_session_destroy (GNCSession *session)
{
GList *node;
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) : "(null)");
@ -593,10 +688,14 @@ gnc_session_destroy (GNCSession *session)
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->book = NULL;
session->books = NULL;
xaccLogEnable();
@ -605,27 +704,41 @@ gnc_session_destroy (GNCSession *session)
LEAVE ("sess=%p", session);
}
/* ====================================================================== */
/* this call is weird. */
void
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;
ENTER ("sess1=%p sess2=%p", session_1, session_2);
book_1 = session_1->book;
book_2 = session_2->book;
books_1 = session_1->books;
books_2 = session_2->books;
session_1->book = book_2;
session_2->book = book_1;
session_1->books = books_2;
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 (" ");
}
/* ====================================================================== */
gboolean
gnc_session_events_pending (GNCSession *session)
{
@ -646,7 +759,7 @@ gnc_session_process_events (GNCSession *session)
return session->backend->process_events (session->backend);
}
/* ---------------------------------------------------------------------- */
/* ====================================================================== */
/*
* If $HOME/.gnucash/data directory doesn't exist, then create it.
*/
@ -685,7 +798,8 @@ MakeHomeDir (void)
g_free (data);
}
/* ---------------------------------------------------------------------- */
/* ====================================================================== */
/* XXX hack alert -- we should be yanking this out of some config file */
static char * searchpaths[] =
{
@ -771,6 +885,8 @@ xaccUserPathPathGenerator(char *pathbuf, int which)
}
}
/* ====================================================================== */
char *
xaccResolveFilePath (const char * filefrag)
{
@ -873,7 +989,7 @@ xaccResolveFilePath (const char * filefrag)
return NULL;
}
/* ---------------------------------------------------------------------- */
/* ====================================================================== */
char *
xaccResolveURL (const char * pathfrag)
@ -903,7 +1019,7 @@ xaccResolveURL (const char * pathfrag)
return (xaccResolveFilePath (pathfrag));
}
/* ---------------------------------------------------------------------- */
/* ====================================================================== */
/* 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? */
}
/* =================== END OF FILE ====================================== */

View File

@ -54,15 +54,33 @@
* they want to keep their data files.
*
* 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
* coarsest level, authorization can happen at the datastore
* level: i.e. does this user even have the authority to connect
* 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:
* 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
*/
@ -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,
* then this routine would load the data from the file. With remote
* 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.
*/
void gnc_session_load (GNCSession *session);