From 63901220a26bc4bc9b9ac9b8f74d090f4734e7f3 Mon Sep 17 00:00:00 2001 From: Linas Vepstas Date: Fri, 4 Jan 2002 18:19:24 +0000 Subject: [PATCH] add support for multiple books git-svn-id: svn+ssh://svn.gnucash.org/repo/gnucash/trunk@6502 57a11ea4-9604-0410-9ed3-97b8803252fd --- src/engine/gnc-session-p.h | 28 ++-- src/engine/gnc-session.c | 280 ++++++++++++++++++++++++++----------- src/engine/gnc-session.h | 24 +++- 3 files changed, 235 insertions(+), 97 deletions(-) diff --git a/src/engine/gnc-session-p.h b/src/engine/gnc-session-p.h index 6d7d6791af..1234c3530d 100644 --- a/src/engine/gnc-session-p.h +++ b/src/engine/gnc-session-p.h @@ -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); diff --git a/src/engine/gnc-session.c b/src/engine/gnc-session.c index e3a24271c9..cb45bc317d 100644 --- a/src/engine/gnc-session.c +++ b/src/engine/gnc-session.c @@ -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 ====================================== */ diff --git a/src/engine/gnc-session.h b/src/engine/gnc-session.h index 225a2d8f59..4a0004804b 100644 --- a/src/engine/gnc-session.h +++ b/src/engine/gnc-session.h @@ -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 + * Copyright (c) 1998, 1999, 2001, 2002 Linas Vepstas * 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);