Change Sessions to GNCBooks, which will represent a set of gnucash

data, including account hierarchy, budgets, recurring transactions,
etc.


git-svn-id: svn+ssh://svn.gnucash.org/repo/gnucash/trunk@3154 57a11ea4-9604-0410-9ed3-97b8803252fd
zzzoldreleases/1.6
Dave Peticolas 26 years ago
parent a639f7e1f0
commit 29b425a9b3

@ -21,28 +21,26 @@
#include "config.h"
#include <string.h>
#include <glib.h>
#include <guile/gh.h>
#include <string.h>
#include "Destroy.h"
#include "FileBox.h"
#include "FileDialog.h"
#include "FileIO.h"
#include "Group.h"
#include "messages.h"
#include "Session.h"
#include "TransLog.h"
#include "Destroy.h"
#include "file-history.h"
#include "gnc-engine-util.h"
#include "gnc-ui.h"
#include "file-history.h"
#include "messages.h"
/* This static indicates the debugging module that this .o belongs to. */
static short module = MOD_GUI;
/** GLOBALS *********************************************************/
static Session *current_session = NULL;
static AccountGroup *topgroup = NULL; /* the current top of the hierarchy */
static GNCBook *current_book = NULL;
/* ======================================================== */
@ -55,7 +53,7 @@ file_not_found_msg (void)
/* ======================================================== */
static gboolean
show_file_error (GNCFileIOError io_error, char *newfile)
show_file_error (GNCFileIOError io_error, const char *newfile)
{
gboolean uh_oh = FALSE;
char *buf = NULL;
@ -113,9 +111,9 @@ show_file_error (GNCFileIOError io_error, char *newfile)
/* ======================================================== */
static gboolean
show_session_error(Session *session, char *newfile)
show_book_error(GNCBook *book, const char *newfile)
{
int norr = xaccSessionGetError (session);
int norr = gnc_book_get_error (book);
gboolean uh_oh = FALSE;
char *buf = NULL;
@ -147,38 +145,30 @@ show_session_error(Session *session, char *newfile)
void
gncFileNew (void)
{
Session *sess;
AccountGroup *grp;
GNCBook *book;
AccountGroup *group;
/* If user attempts to start a new session before saving
* results of the last one, prompt them to clean up their
* act. */
/* If user attempts to start a new session before saving results of
* the last one, prompt them to clean up their act. */
if (!gncFileQuerySave ())
return;
sess = current_session;
grp = xaccSessionGetGroup (sess);
/* if session not yet started ... */
if (!grp) grp = topgroup;
book = gncGetCurrentBook ();
group = gnc_book_get_group (book);
/* when starting new everything, destroy old stuff first */
/* destroy open windows first, before destroying the group itself */
xaccGroupWindowDestroy (grp);
/* close any ongoing file sessions, if any */
xaccSessionEnd (sess);
xaccSessionDestroy (sess);
current_session = NULL;
topgroup = NULL;
xaccGroupWindowDestroy (group);
/* disable logging while we move over to the new set of accounts to
* edit; the mass deletion of accounts and transactions during
* switchover is not something we want to keep in a journal. */
/* close any ongoing file sessions, and free the accounts.
* disable logging so we don't get all that junk. */
xaccLogDisable();
xaccFreeAccountGroup (grp);
gnc_book_destroy (book);
current_book = NULL;
xaccLogEnable();
grp = xaccMallocAccountGroup();
topgroup = grp;
/* start a new book */
gncGetCurrentBook ();
}
/* ======================================================== */
@ -186,16 +176,14 @@ gncFileNew (void)
gboolean
gncFileQuerySave (void)
{
Session *sess;
AccountGroup *grp;
GNCBook *book;
AccountGroup *group;
gncUIWidget app;
sess = current_session;
grp = xaccSessionGetGroup (sess);
/* if session not yet started ... */
if (!grp) grp = topgroup;
book = gncGetCurrentBook ();
group = gnc_book_get_group (book);
app = gnc_get_ui_data();
app = gnc_get_ui_data ();
/* If user wants to mess around before finishing business with
* the old file, give em a chance to figure out what's up.
@ -203,7 +191,7 @@ gncFileQuerySave (void)
* up the file-selection dialog, we don't blow em out of the water;
* instead, give them another chance to say "no" to the verify box.
*/
while ( xaccGroupNotSaved (grp) )
while (xaccGroupNotSaved (group))
{
GNCVerifyResult result;
const char *message = _("Changes have been made since the last "
@ -252,15 +240,17 @@ gncLockFailHandler (const char *file)
static void
gncPostFileOpen (const char * filename)
{
Session *newsess;
AccountGroup *oldgrp;
GNCBook *new_book;
AccountGroup *old_group;
gboolean uh_oh = FALSE;
AccountGroup *newgrp;
AccountGroup *new_group;
char * newfile;
if (!filename) return;
newfile = xaccResolveFilePath (filename);
if (!newfile) {
if (!newfile)
{
char *buf = g_strdup_printf (file_not_found_msg(), filename);
gnc_error_dialog (buf);
g_free(buf);
@ -269,47 +259,43 @@ gncPostFileOpen (const char * filename)
/* -------------- BEGIN CORE SESSION CODE ------------- */
/* -- this code is almost identical in FileOpen and FileSaveAs -- */
oldgrp = xaccSessionGetGroup (current_session);
/* if session not yet started ... */
if (!oldgrp) oldgrp = topgroup;
old_group = gnc_book_get_group (gncGetCurrentBook ());
/* load the accounts from the users datafile */
/* but first, check to make sure we've got a session going ... */
newsess = xaccMallocSession ();
/* but first, check to make sure we've got a book going. */
new_book = gnc_book_new ();
/* disable logging while we move over to the new set of accounts to
* edit; the mass deletetion of accounts and transactions during
* switchover is not something we want to keep in a journal. */
gnc_set_busy_cursor(NULL);
xaccLogDisable();
newgrp = NULL;
if(xaccSessionBeginFile(newsess, newfile, gncLockFailHandler)) {
if(xaccSessionLoad(newsess)) {
newgrp = xaccSessionGetGroup(newsess);
}
gnc_set_busy_cursor (NULL);
xaccLogDisable ();
new_group = NULL;
if (gnc_book_begin_file (new_book, newfile, gncLockFailHandler))
{
if (gnc_book_load (new_book))
new_group = gnc_book_get_group (new_book);
}
xaccLogEnable();
gnc_unset_busy_cursor(NULL);
xaccLogEnable ();
gnc_unset_busy_cursor (NULL);
/* check for session errors, put up appropriate dialog */
uh_oh = show_session_error (newsess, newfile);
/* check for book errors, put up appropriate dialog */
uh_oh = show_book_error (new_book, newfile);
if (!uh_oh)
{
GNCFileIOError io_err = xaccSessionGetFileError(newsess);
GNCFileIOError io_err = gnc_book_get_file_error (new_book);
/* check for i/o error, put up appropriate error message */
uh_oh = show_file_error(io_err, newfile);
if (uh_oh)
{
xaccFreeAccountGroup (newgrp);
newgrp = NULL;
}
new_group = NULL;
/* Umm, came up empty-handed, i.e. the file was not found. */
/* This is almost certainly not what the user wanted. */
if (!uh_oh && !newgrp && (io_err == ERR_FILEIO_NONE))
if (!uh_oh && !new_group && (io_err == ERR_FILEIO_NONE))
{
char *buf = g_strdup_printf (file_not_found_msg(), newfile);
char *buf = g_strdup_printf (file_not_found_msg(), newfile);
gnc_error_dialog (buf);
g_free (buf);
uh_oh = TRUE;
@ -319,50 +305,46 @@ gncPostFileOpen (const char * filename)
/* going down -- abandon ship */
if (uh_oh)
{
xaccSessionEnd (newsess);
xaccSessionDestroy (newsess);
/* well, no matter what, I think its a good idea to have
* a topgroup around. For example, early in the gnucash startup
* sequence, the user opens a file; if this open fails for any
* reason, we don't want to leave them high & dry without a topgroup,
* because if the user continues, then bad things will happen.
*/
if (NULL == topgroup)
{
topgroup = xaccMallocAccountGroup();
}
gnc_book_destroy (new_book);
/* well, no matter what, I think its a good idea to have a
* topgroup around. For example, early in the gnucash startup
* sequence, the user opens a file; if this open fails for any
* reason, we don't want to leave them high & dry without a
* topgroup, because if the user continues, then bad things will
* happen. */
gncGetCurrentBook ();
free (newfile);
return;
}
/* if we got to here, then we've successfully gotten a new session */
/* close up the old file session (if any) */
xaccSessionEnd (current_session);
xaccSessionDestroy (current_session);
current_session = newsess;
/* --------------- END CORE SESSION CODE -------------- */
/* clean up old stuff, and then we're outta here. */
xaccLogDisable();
xaccLogSetBaseName (newfile);
gnc_history_add_file (newfile);
/* destroy open windows first, before destroying the group itself */
xaccGroupWindowDestroy (oldgrp);
xaccFreeAccountGroup (oldgrp);
topgroup = newgrp;
xaccGroupWindowDestroy (old_group);
gnc_book_destroy (current_book);
current_book = new_book;
xaccLogEnable();
/* --------------- END CORE SESSION CODE -------------- */
/* clean up old stuff, and then we're outta here. */
gnc_history_add_file (newfile);
free (newfile);
/* run a file-opened hook. For now, the main thing it will do
/* run a file-opened hook. For now, the main thing it will do
* is notice if legacy currencies are being imported. */
{
SCM run_danglers = gh_eval_str("gnc:hook-run-danglers");
SCM hook = gh_eval_str("gnc:*file-opened-hook*");
SCM filename;
if(newfile) {
if (newfile)
{
filename = gh_str02scm(newfile);
gh_call2(run_danglers, hook, filename);
}
@ -386,8 +368,7 @@ gncFileOpen (void)
* user fails to pick a file (by e.g. hitting the cancel button), we
* might be left with a null topgroup, which leads to nastiness when
* user goes to create their very first account. So create one. */
if (topgroup == NULL)
topgroup = xaccMallocAccountGroup();
gncGetCurrentBook ();
}
void
@ -402,22 +383,23 @@ gncFileOpenFile (const char * newfile)
}
/* ======================================================== */
static int been_here_before = 0;
static gboolean been_here_before = FALSE;
void
gncFileSave (void)
{
AccountGroup *newgrp = NULL;
char * newfile;
GNCFileIOError io_err;
int norr, uh_oh = 0;
const char * newfile;
GNCBook *book;
int uh_oh = 0;
int norr;
/* hack alert -- Somehow make sure all in-progress edits get committed! */
/* if no session exists, then we don't have a filename/path
* to save to. Get one now. */
if ((NULL == current_session) ||
(NULL == xaccSessionGetGroup (current_session)))
/* If we don't have a filename/path to save to get one. */
book = gncGetCurrentBook ();
if (!gnc_book_get_file_path (book))
{
gncFileSaveAs();
return;
@ -425,19 +407,19 @@ gncFileSave (void)
/* use the current session to save to file */
gnc_set_busy_cursor(NULL);
xaccSessionSave (current_session);
gnc_book_save (book);
gnc_unset_busy_cursor(NULL);
/* Make sure everything's OK - disk could be full, file could have
become read-only etc. */
norr = xaccSessionGetError (current_session);
norr = gnc_book_get_error (book);
if (norr)
{
const char *format = _("There was an error writing the file\n %s"
"\n\n%s");
char *message;
newfile = xaccSessionGetFilePath(current_session);
newfile = gnc_book_get_file_path (book);
if (newfile == NULL)
newfile = "";
@ -447,9 +429,9 @@ gncFileSave (void)
if (been_here_before) return;
been_here_before = 1;
been_here_before = TRUE;
gncFileSaveAs(); /* been_here prevents infinite recuirsion */
been_here_before = 0;
been_here_before = FALSE;
return;
}
@ -457,21 +439,16 @@ gncFileSave (void)
/* check for i/o error, put up appropriate error message.
* NOTE: the file-writing routines never set the file io
* error code, so this seems to be unneccesary. */
io_err = xaccSessionGetFileError(current_session);
newfile = xaccSessionGetFilePath(current_session);
gnc_history_add_file(newfile);
io_err = gnc_book_get_file_error (book);
newfile = gnc_book_get_file_path (book);
gnc_history_add_file (newfile);
uh_oh = show_file_error (io_err, newfile);
if (uh_oh)
{
xaccFreeAccountGroup (newgrp);
newgrp = NULL;
}
/* going down -- abandon ship */
if (uh_oh) return;
xaccGroupMarkSaved (topgroup);
xaccGroupMarkSaved (gnc_book_get_group (book));
}
/* ======================================================== */
@ -479,81 +456,80 @@ gncFileSave (void)
void
gncFileSaveAs (void)
{
Session *newsess;
AccountGroup *oldgrp;
AccountGroup *group;
GNCBook *new_book;
GNCBook *book;
const char *filename;
char *newfile;
char * oldfile;
const char *oldfile;
gboolean uh_oh = FALSE;
filename = fileBox(_("Save"), "*.gnc");
if (!filename) return;
/* check to see if the user did something silly,
* like specifying the same file as the current file ...
* if so, then just do that, instead of the below,
* which assumes a truly new name was given.
*/
/* Check to see if the user specified the same file as the current
* file. If so, then just do that, instead of the below, which
* assumes a truly new name was given. */
newfile = xaccResolveFilePath (filename);
if (!newfile) {
if (!newfile)
{
char *buf = g_strdup_printf (file_not_found_msg(), filename);
gnc_error_dialog (buf);
g_free (buf);
return;
}
oldfile = xaccSessionGetFilePath (current_session);
if (oldfile && (strcmp(oldfile, newfile) == 0)) {
book = gncGetCurrentBook ();
oldfile = gnc_book_get_file_path (book);
if (oldfile && (strcmp(oldfile, newfile) == 0))
{
free (newfile);
gncFileSave ();
return;
}
/* -- this session code is NOT identical in FileOpen and FileSaveAs -- */
oldgrp = xaccSessionGetGroup (current_session);
/* if session not yet started ... */
if (!oldgrp) oldgrp = topgroup;
/* create a new session ... */
newsess = xaccMallocSession ();
group = gnc_book_get_group (book);
/* disable logging while we move over to the new set of accounts to
* edit; the mass deletetion of accounts and transactions during
* switchover is not something we want to keep in a journal. */
xaccLogDisable();
xaccSessionBeginFile(newsess, newfile, gncLockFailHandler);
xaccLogEnable();
* switchover is not something we want to keep in a journal. */
xaccLogDisable ();
new_book = gnc_book_new ();
gnc_book_begin_file (new_book, newfile, gncLockFailHandler);
xaccLogEnable ();
/* check for session errors (e.g. file locked by another user) */
uh_oh = show_session_error (newsess, newfile);
uh_oh = show_book_error (new_book, newfile);
/* No check for file errors since we didn't read a file... */
/* going down -- abandon ship */
if (uh_oh) {
xaccSessionDestroy (newsess);
/* well, no matter what, I think its a good idea to have
* a topgroup around. For example, early in the gnucash startup
* sequence, the user opens a file ... if this open fails for any
* reason, we don't want to leave them high & dry without a topgroup,
* because if user continues, then bad things will happen ...
*/
if(NULL == topgroup) {
topgroup = xaccMallocAccountGroup();
}
if (uh_oh)
{
xaccLogDisable ();
gnc_book_destroy (new_book);
xaccLogEnable ();
free (newfile);
gnc_refresh_main_window();
return;
}
/* if we got to here, then we've successfully gotten a new session */
/* close up the old file session (if any) */
xaccSessionDestroy (current_session);
current_session = newsess;
xaccLogDisable ();
gnc_book_set_group (book, NULL);
gnc_book_destroy (book);
current_book = new_book;
xaccLogEnable ();
/* --------------- END CORE SESSION CODE -------------- */
/* oops ... file already exists ... ask user what to do... */
if(xaccSessionSaveMayClobberData(newsess)) {
if (gnc_book_save_may_clobber_data (new_book))
{
const char *format = _("The file \n %s\n already exists.\n"
"Are you sure you want to overwrite it?");
char *tmpmsg;
@ -567,20 +543,17 @@ gncFileSaveAs (void)
if (!result)
return;
/* Whoa-ok. Blow away the previous file.
* Do not disable logging ... we want to capture the
* old file in the log, just in case the user later
* decides it was all a big mistake. */
xaccSessionSetGroup (newsess, NULL);
/* xaccLogDisable(); no don't disable, keep logging on */
/* Whoa-ok. Blow away the previous file. Do not disable
* logging. We want to capture the old file in the log, just in
* case the user later decides it was all a big mistake. */
}
/* OK, save the data to the file ... */
xaccLogSetBaseName (newfile);
xaccSessionSetGroup (newsess, oldgrp);
gnc_book_set_group (new_book, group);
gncFileSave ();
free (newfile);
free (newfile);
gnc_refresh_main_window();
}
@ -589,21 +562,16 @@ gncFileSaveAs (void)
void
gncFileQuit (void)
{
AccountGroup *grp;
GNCBook *book;
grp = xaccSessionGetGroup (current_session);
/* if session not yet started ... */
if (!grp) grp = topgroup;
book = gncGetCurrentBook ();
/* disable logging; the mass deletetion of accounts and transactions
* during shutdown is not something we want to keep in a journal. */
xaccLogDisable();
xaccSessionEnd (current_session);
xaccSessionDestroy (current_session);
current_session = NULL;
xaccFreeAccountGroup (grp);
topgroup = NULL;
gnc_book_destroy (book);
current_book = NULL;
}
/* ======================================================== */
@ -611,27 +579,22 @@ gncFileQuit (void)
AccountGroup *
gncGetCurrentGroup (void)
{
AccountGroup *grp;
grp = xaccSessionGetGroup (current_session);
if (grp) return grp;
/* If we are here, then no session has yet been started ... */
grp = topgroup;
if (grp) return grp;
GNCBook *book;
/* if we are here, then topgroup not yet initialized ... */
xaccLogEnable();
topgroup = xaccMallocAccountGroup();
book = gncGetCurrentBook ();
return topgroup;
return gnc_book_get_group (book);
}
/* ======================================================== */
Session *
gncGetCurrentSession (void)
GNCBook *
gncGetCurrentBook (void)
{
return (current_session);
if (!current_book)
current_book = gnc_book_new ();
return current_book;
}
/********************* END OF FILE **********************************/

@ -122,7 +122,7 @@
#include "config.h"
#include "Group.h"
#include "Session.h"
#include "gnc-book.h"
void gncFileNew (void);
void gncFileOpen (void);
@ -138,6 +138,6 @@ void gncFileQuit (void);
AccountGroup *gncGetCurrentGroup (void);
Session *gncGetCurrentSession (void);
GNCBook *gncGetCurrentBook (void);
#endif /* __GNC_FILE_DIALOG_H__ */

@ -5,7 +5,7 @@
* back-ends (which will probably be sql databases).
*
* The callbacks will be called at the appropriate times during
* a session to allow the backend to store the data as needed.
* a book session to allow the backend to store the data as needed.
*
*/
@ -13,10 +13,11 @@
#define __XACC_BACKEND_P_H__
#include "config.h"
#include "Account.h"
#include "Group.h"
#include "Session.h"
#include "Transaction.h"
#include "gnc-book.h"
typedef struct _backend Backend;
@ -31,8 +32,8 @@ typedef struct _backend Backend;
struct _backend
{
AccountGroup * (*session_begin) (Session *, const char * sessionid);
int (*session_end) (Session *);
AccountGroup * (*book_begin) (GNCBook *, const char * book_id);
int (*book_end) (GNCBook *);
int (*account_begin_edit) (Backend *, Account *, int defer);
int (*account_commit_edit) (Backend *, Account *);
int (*trans_begin_edit) (Backend *, Transaction *, int defer);
@ -60,7 +61,7 @@ Backend * xaccTransactionGetBackend (Transaction *);
*/
void xaccGroupSetBackend (AccountGroup *, Backend *);
Backend * xaccGroupGetBackend (AccountGroup *);
Backend * xaccSessionGetBackend (Session *);
Backend * xaccGNCBookGetBackend (GNCBook *book);
#endif /* __XACC_BACKEND_P_H__ */

@ -144,8 +144,8 @@ xaccFreeAccountGroup( AccountGroup *grp )
xaccAccountGroupBeginEdit (grp, 1);
for( i=0; i<grp->numAcc; i++ )
xaccFreeAccount( grp->account[i] );
for(i = 0; i < grp->numAcc; i++ )
xaccFreeAccount(grp->account[i]);
g_free(grp->account);

@ -14,7 +14,6 @@ libgncengine_la_SOURCES = \
Group.c \
Query.c \
Scrub.c \
Session.c \
Transaction.c \
TransLog.c \
date.c \
@ -25,7 +24,8 @@ libgncengine_la_SOURCES = \
io-gncxml-w.c \
md5.c \
kvp_frame.c \
gnc-commodity.c\
gnc-book.c \
gnc-commodity.c \
gnc-numeric.c \
gnc-engine.c \
gnc-engine-util.c
@ -45,7 +45,6 @@ noinst_HEADERS = \
GroupP.h \
Query.h \
Scrub.h \
Session.h \
TransLog.h \
Transaction.h \
TransactionP.h \
@ -56,6 +55,7 @@ noinst_HEADERS = \
io-gncxml.h \
md5.h \
kvp_frame.h \
gnc-book.h \
gnc-commodity.h \
gnc-numeric.h \
gnc-engine.h \

@ -1,639 +0,0 @@
/********************************************************************\
* 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, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
* Boston, MA 02111-1307, USA gnu@gnu.org *
\********************************************************************/
/*
* FILE:
* Session.c
*
* FUNCTION:
* Provide wrappers for initiating/concluding a file-editing session.
*
* HISTORY:
* Created by Linas Vepstas December 1998
* Copyright (c) 1998-2000 Linas Vepstas
*/
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "BackendP.h"
#include "FileIO.h"
#include "Group.h"
#include "Session.h"
#include "gnc-engine-util.h"
struct _session {
AccountGroup *topgroup;
/* the requested session id, in the form or a URI, such as
* file:/some/where, or sql:server.host.com:555
*/
char *sessionid;
/* if session failed, this records the failure reason
* (file not found, etc).
* the standard errno values are used.
*/
int errtype;
/* 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 Session errors... */
GNCFileIOError last_file_err;
/* ---------------------------------------------------- */
/* teh following struct members apply only for file-io */
/* the fully-resolved path to the file */
char *fullpath;
/* name of lockfile, and filedescr */
char * lockfile;
char * linkfile;
int lockfd;
/* ---------------------------------------------------- */
/* this struct member applies only for SQL i/o */
Backend *backend;
};
/* ============================================================== */
Session *
xaccMallocSession (void)
{
Session *sess;
sess = g_new(Session, 1);
xaccInitSession (sess);
return sess;
}
void
xaccInitSession (Session *sess)
{
if (!sess) return;
sess->topgroup = NULL;
sess->errtype = 0;
sess->last_file_err = ERR_FILEIO_NONE;
sess->sessionid = NULL;
sess->fullpath = NULL;
sess->lockfile = NULL;
sess->linkfile = NULL;
sess->lockfd = -1;
sess->backend = NULL;
};
/* ============================================================== */
int
xaccSessionGetError (Session * sess)
{
int retval;
if (!sess) return EINVAL;
retval = sess->errtype;
sess->errtype = 0;
return (retval);
}
/* ============================================================== */
GNCFileIOError
xaccSessionGetFileError (Session * sess) {
if (!sess) return ERR_FILEIO_MISC;
return sess->last_file_err;
}
/* ============================================================== */
AccountGroup *
xaccSessionGetGroup (Session *sess)
{
if (!sess) return NULL;
return (sess->topgroup);
}
void
xaccSessionSetGroup (Session *sess, AccountGroup *grp)
{
if (!sess) return;
sess->topgroup = grp;
}
/* ============================================================== */
Backend *
xaccSessionGetBackend (Session *sess)
{
if (!sess) return NULL;
return (sess->backend);
}
/* ============================================================== */
char *
xaccSessionGetFilePath (Session *sess)
{
if (!sess) return NULL;
return (sess->fullpath);
}
/* ============================================================== */
gboolean
xaccSessionBegin (Session *sess, const char * sid) {
if(!sess) return FALSE;
if(!sid) return FALSE;
/* clear the error condition of previous errors */
sess->errtype = 0;
sess->last_file_err = ERR_FILEIO_NONE;
/* check to see if this session is already open */
if (sess->sessionid) {
sess->errtype = ETXTBSY;
return FALSE;
}
/* seriously invalid */
if (!sid) {
sess->errtype = EINVAL;
return FALSE;
}
/* check to see if this is a type we know how to handle */
if(strncmp(sid, "file:", 5) != 0) {
sess->errtype = ENOSYS;
return FALSE;
}
/* add 5 to space past 'file:' */
return xaccSessionBeginFile(sess, sid+5, NULL);
}
/* ============================================================== */
#if 0
static AccountGroup *
xaccSessionBeginSQL (Session *sess, const char * dbname)
{
Backend *be = NULL;
AccountGroup *grp = NULL;
if (!sess) return NULL;
/* #define SQLHACK */
#ifdef SQLHACK
{
/* for testing the sql, just a hack, remove later ... */
extern Backend * pgendNew (void);
be = pgendNew ();
}
#endif
sess->backend = be;
if (be && be->session_begin) {
grp = (be->session_begin) (sess, dbname);
}
/* comment out until testing done, else clobber file ...*/
/* sess->topgroup = grp; */
xaccGroupSetBackend (sess->topgroup, be);
return (sess->topgroup);
}
#endif
/* ============================================================== */
static gboolean
xaccSessionGetFileLock (Session *sess)
{
struct stat statbuf;
char pathbuf[PATH_MAX];
char *path = NULL;
int rc;
rc = stat (sess->lockfile, &statbuf);
if (!rc) {
/* oops .. file is all locked up .. */
sess->errtype = ETXTBSY;
return FALSE;
}
sess->lockfd = open (sess->lockfile, O_RDWR | O_CREAT | O_EXCL , 0);
if (0 > sess->lockfd) {
/* oops .. file is all locked up .. */
sess->errtype = ETXTBSY;
return FALSE;
}
/* OK, now work around some NFS atomic lock race condition
* mumbo-jumbo. We do this by linking a unique file, and
* then examing the link count. At least that's what the
* NFS programmers guide suggests.
* Note: the "unique filename" must be unique for the
* triplet filename-host-process, otherwise accidental
* aliases can occur.
*/
/* apparently, even this code may not work for some NFS
* implementations. In the long run, I am told that
* ftp.debian.org
* /pub/debian/dists/unstable/main/source/libs/liblockfile_0.1-6.tar.gz
* provides a better long-term solution.
*/
strcpy (pathbuf, sess->lockfile);
path = strrchr (pathbuf, '.');
sprintf (path, ".%lx.%d.LNK", gethostid(), getpid());
link (sess->lockfile, pathbuf);
rc = stat (sess->lockfile, &statbuf);
if (rc) {
/* oops .. stat failed! This can't happen! */
sess->errtype = ETXTBSY;
unlink (pathbuf);
close (sess->lockfd);
unlink (sess->lockfile);
return FALSE;
}
if (2 != statbuf.st_nlink) {
/* oops .. stat failed! This can't happen! */
sess->errtype = ETXTBSY;
unlink (pathbuf);
close (sess->lockfd);
unlink (sess->lockfile);
return FALSE;
}
sess->linkfile = g_strdup (pathbuf);
return TRUE;
}
/* ============================================================== */
gboolean
xaccSessionBeginFile (Session *sess, const char * filefrag,
SessionLockFailHandler handler) {
if(!sess) return FALSE;
if(!filefrag) return FALSE;
/* clear the error condition of previous errors */
sess->errtype = 0;
sess->last_file_err = ERR_FILEIO_NONE;
/* check to see if this session is already open */
if (sess->sessionid) {
sess->errtype = ETXTBSY;
return FALSE;
}
/* seriously invalid */
if (!filefrag) {
sess->errtype = EINVAL;
return FALSE;
}
/* ---------------------------------------------------- */
/* OK, now we try to find or build an absolute file path */
sess->fullpath = xaccResolveFilePath (filefrag);
if (! (sess->fullpath)) {
sess->errtype = ERANGE;
return FALSE; /* ouch */
}
/* Store the sessionid URL also ... */
sess->sessionid = g_strconcat ("file:", filefrag, NULL);
/* ---------------------------------------------------- */
/* We should now have a fully resolved path name.
* Lets see if we can get a lock on it. */
sess->lockfile = g_strconcat(sess->fullpath, ".LCK", NULL);
if (!xaccSessionGetFileLock (sess)) {
if (!handler || !handler (sess->fullpath)) {
g_free (sess->sessionid); sess->sessionid = NULL;
g_free (sess->fullpath); sess->fullpath = NULL;
g_free (sess->lockfile); sess->lockfile = NULL;
return FALSE;
}
}
return TRUE;
}
#ifdef SQLHACK
/* for testing the sql, just a hack, remove later ... */
/* this should never ever appear here ... */
xaccSessionBeginSQL (sess, "postgres://localhost/gnc_bogus");
#endif
/* ============================================================== */
gboolean
xaccSessionLoad(Session *sess) {
if(!sess) return FALSE;
if(!sess->sessionid) return FALSE;
if(strncmp(sess->sessionid, "file:", 5) == 0) {
/* file: */
if(!sess->lockfile) {
sess->errtype = ENOLCK;
return FALSE;
}
/* At this point, we should have a valid session id and a lock on
the file. */
sess->errtype = 0;
sess->last_file_err = ERR_FILEIO_NONE;
sess->topgroup = xaccReadAccountGroupFile (sess->fullpath,
&(sess->last_file_err));
if(!sess->topgroup || (sess->last_file_err != ERR_FILEIO_NONE)) {
sess->errtype = EIO;
return FALSE;
}
return TRUE;
} else {
sess->errtype = ENOSYS;
return FALSE;
}
}
/* ============================================================== */
gboolean
xaccSessionSaveMayClobberData(Session *s) {
/* FIXME: Make sure this doesn't need more sophisticated semantics
in the face of special file, devices, pipes, symlinks, etc... */
struct stat statbuf;
if(!s) return FALSE;
if(!s->fullpath) return FALSE;
if(stat(s->fullpath, &statbuf) == 0) return TRUE;
return FALSE;
#ifdef SQLHACK
/* for testing the sql, just a hack, remove later ... */
/* this should never ever appear here ... */
xaccSessionBeginSQL (sess, "postgres://localhost/gnc_bogus");
#endif
}
/* ============================================================== */
void
xaccSessionSave (Session *sess)
{
if (!sess) return;
/* if the fullpath doesn't exist, either the user failed to initialize,
* or the lockfile was never obtained ... either way, we can't write. */
sess->errtype = 0;
sess->last_file_err = ERR_FILEIO_NONE;
if (!(sess->fullpath)) {
sess->errtype = ENOLCK;
return;
}
if (sess->topgroup) {
gboolean write_ok = xaccWriteAccountGroupFile (sess->fullpath,
sess->topgroup,
TRUE,
&(sess->last_file_err));
if (!write_ok) sess->errtype = errno;
}
}
/* ============================================================== */
void
xaccSessionEnd (Session *sess)
{
if (!sess) return;
sess->errtype = 0;
sess->last_file_err = ERR_FILEIO_NONE;
if (sess->linkfile) unlink (sess->linkfile);
if (0 < sess->lockfd) close (sess->lockfd);
if (sess->lockfile) unlink (sess->lockfile);
g_free (sess->sessionid);
sess->sessionid = NULL;
g_free (sess->fullpath);
sess->fullpath = NULL;
g_free (sess->lockfile);
sess->lockfile = NULL;
g_free (sess->linkfile);
sess->linkfile = NULL;
sess->topgroup = NULL;
}
void
xaccSessionDestroy (Session *sess)
{
if (!sess) return;
xaccSessionEnd (sess);
g_free (sess);
}
/* ============================================================== */
/*
* If $HOME/.gnucash/data directory doesn't exist, then create it.
*/
static void
MakeHomeDir (void)
{
int rc;
struct stat statbuf;
char *home;
char *path;
char *data;
/* Punt. Can't figure out where home is. */
home = getenv ("HOME");
if (!home) return;
path = g_strconcat(home, "/.gnucash", NULL);
rc = stat (path, &statbuf);
if (rc) {
/* assume that the stat failed only because the dir is absent,
* and not because its read-protected or other error.
* Go ahead and make it. Don't bother much with checking mkdir
* for errors; seems pointless. */
mkdir (path, S_IRWXU); /* perms = S_IRWXU = 0700 */
}
data = g_strconcat (path, "/data", NULL);
rc = stat (data, &statbuf);
if (rc)
mkdir (data, S_IRWXU);
g_free (path);
g_free (data);
}
/* ============================================================== */
/* XXX hack alert -- we should be yanking this out of
* some config file
*/
static char * searchpaths[] = {
"/usr/share/gnucash/data/",
NULL,
};
char *
xaccResolveFilePath (const char * filefrag)
{
struct stat statbuf;
char pathbuf[PATH_MAX];
char *path = NULL;
int namelen, len;
int i, rc;
/* seriously invalid */
if (!filefrag) return NULL;
/* ---------------------------------------------------- */
/* OK, now we try to find or build an absolute file path */
/* check for an absolute file path */
if ('/' == *filefrag)
return strdup (filefrag);
/* get conservative on the length so that sprintf(getpid()) works ... */
/* strlen ("/.LCK") + sprintf (%x%d) */
namelen = strlen (filefrag) + 25;
for (i=-2; 1 ; i++)
{
switch (i) {
case -2: {
/* try to find a file by this name in the cwd ... */
path = getcwd (pathbuf, PATH_MAX);
if (!path) continue;
len = strlen (path) + namelen;
if (PATH_MAX <= len) continue;
strcat (path, "/");
break;
}
case -1: {
/* look for something in $HOME/.gnucash/data */
path = getenv ("HOME");
if (!path) continue;
len = strlen (path) + namelen + 20;
if (PATH_MAX <= len) continue;
strcpy (pathbuf, path);
strcat (pathbuf, "/.gnucash/data/");
path = pathbuf;
break;
}
default: {
/* OK, check the user-configured paths */
path = searchpaths[i];
if (path) {
len = strlen (path) + namelen;
if (PATH_MAX <= len) continue;
strcpy (pathbuf, path);
path = pathbuf;
}
}
}
if (!path) break;
/* lets see if we found the file here ... */
/* haral: if !S_ISREG: there is something with that name
* but it's not a regular file */
strcat (path, filefrag);
rc = stat (path, &statbuf);
if ((!rc) && (S_ISREG(statbuf.st_mode))) {
return (strdup (path));
}
}
/* make sure that the gnucash home dir exists. */
MakeHomeDir();
/* OK, we didn't find the file. */
/* If the user specified a simple filename (i.e. no slashes in it)
* then create the file. But if it has slashes in it, then creating
* a bunch of directories seems like a bad idea; more likely, the user
* specified a bad filename. So return with error. */
if (strchr (filefrag, '/')) {
return NULL;
}
/* Lets try creating a new file in $HOME/.gnucash/data */
path = getenv ("HOME");
if (path) {
len = strlen (path) + namelen + 50;
if (PATH_MAX > len) {
strcpy (pathbuf, path);
strcat (pathbuf, "/.gnucash/data/");
strcat (pathbuf, filefrag);
return (strdup (pathbuf));
}
}
/* OK, we still didn't find the file */
/* Lets try creating a new file in the cwd */
path = getcwd (pathbuf, PATH_MAX);
if (path) {
len = strlen (path) + namelen;
if (PATH_MAX > len) {
strcat (path, "/");
strcat (path, filefrag);
return (strdup (path));
}
}
return NULL;
}
/* ==================== END OF FILE ================== */

@ -1,205 +0,0 @@
/********************************************************************\
* 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, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
* Boston, MA 02111-1307, USA gnu@gnu.org *
* *
\********************************************************************/
/*
* FILE:
* Session.h
*
* FUNCTION:
* Provide wrappers for initiating/concluding a file-editing session.
* This class provides several important services:
*
* 1) Prevents multiple users from editing the same file at the same
* time, thus avoiding lost data due to race conditions. Thus
* an open session implies that the associated file is locked.
*
* 2) Provides a search path for the file to be edited. This should
* simplify install & maintenance problems for naive users who
* may not have a good grasp on what a file ssytem is, or where
* they want to keep their data files.
*
* The current implementations assumes the use of files and file
* locks; however, the API was designed to be general enough to
* allow the use of generic URL's, and/or implementation on top
* of SQL or other database/persistant object technology.
*
* HISTORY:
* Created by Linas Vepstas December 1998
* Copyright (c) 1998, 1999 Linas Vepstas
*/
#ifndef __XACC_SESSION_H__
#define __XACC_SESSION_H__
#include "Group.h"
#include "FileIO.h"
/** STRUCTS *********************************************************/
typedef struct _session Session;
/** PROTOTYPES ******************************************************/
/*
* The xaccMallocSession() routine simply mallocs memory for a session object.
* The xaccInitSession() routine initializes memory for a session object.
* The xaccSessionDestroy() routine frees the associated memory.
* Note that this routine does *not* free the account group!
*/
Session * xaccMallocSession (void);
void xaccInitSession (Session *);
void xaccSessionDestroy (Session *);
/*
* The xaccSessionBegin() method begins a new session. It takes as an argument
* the sessionid. The session id must be a string in the form of a URI/URL.
* In the current implementation, only one type of URI is supported, and
* that is the file URI: anything of the form
* "file:/home/somewhere/somedir/file.xac"
* The path part must be a valid path. The file-part must be a valid
* xacc/gnucash-format file. Paths may be relativ or absolute. If the
* path is relative; that is, if the argument is
* "file:somefile.xac"
* then a sequence of search paths are checked for a file of this name.
*
* If the file exists, can be opened and read, then the AccountGroup
* will be returned. Otherwise, NULL is returned, and the reason for
* the NULL can be gotten with xaccSessionGetError(). Note that NULL
* does not always indicate an error ... e.g. if a new file is created,
* NULL is returned, and xaccSessionGetError returns 0. That's OK.
*
* If the file is succesfully opened and read, then a lock will have been
* obtained, and all other access to the file will be denied. This feature
* is intended to be a brute-force global lock to avoid multiple writers.
*
* The xaccSessionBeginFile() routine is identical to the xaccSessionBegin()
* routine, except that the argument is a filename (i.e. the five
* letters "file:" should not be prepended) and there is an additional
* function argument. This function is called if xaccSessionBeginFile
* fails to obtain a lock for the file. If it returns true, the file
* is loaded anyway. If it returns false, or the handler is NULL, a
* failed lock attempt will abort the load. The lock fail handler is
* passed the filename of the data file being loaded.
*
* The xaccSessionGetFilePath() routine returns the fully-qualified file
* path for the session. That is, if a relative or partial filename
* was for the session, then it had to have been fully resolved to
* open the session. This routine returns the result of this resolution.
*
* The xaccSessionGetError() routine can be used to obtain the reason for
* any failure. Standard errno values are used. Calling this routine resets
* the error value. This routine allows an implementation of multiple
* error values, e.g. in a stack, where this routine pops the top value.
* The current implementation has a stack that is one-deep.
*
* Some important error values:
* EINVAL -- bad argument
* ETXTBSY -- session id is in use; its locked by someone else.
* ENOSYS -- unsupported URI type.
* ERANGE -- file path too long
* ENOLCK -- session not open when SessionSave() was called.
*
*
* The xaccSessionGetGroup() method will return the current top-level
* account group.
*
* The xaccSessionSetGroup() method will set the topgroup to a new value.
*
* The xaccSessionSave() method will commit all changes that have been
* made to the top-level account group. In the current
* implementation, this is nothing more than a write to the file of
* the current AccountGroup of the session.
*
* The xaccSessionEnd() method will release the session lock. It will *not*
* save the account group to a file. Thus, this method acts as an "abort"
* or "rollback" primitive.
*
* The xaccResolveFilePath() routine is a utility that will accept a
* fragmentary filename as input, and resolve it into a fully-qualified path
* in the file system, i.e. a path that begins with a leading slash.
* First, the current working directory is searched for the file.
* Next, the directory $HOME/.gnucash/data, and finally, a list of other
* (configurable) paths. If the file is not found, then the path
* $HOME/.gnucash/data is used. If $HOME is not defined, then the current
* working directory is used.
*
* EXAMPLE USAGE:
* To read, modify and save an existing file:
* ------------------------------------------
* Session *sess = xaccMallocSession ();
* xaccSessionBegin (sess, "file:/some/file.xac");
* AccountGroup *grp = xaccSessionGetGroup (sess);
* if (grp) {
* ... edit the session ...
* xaccSessionSave (sess);
* } else {
* int err = xaccSessionGetError (sess);
* printf ("%s\n", strerror (err));
* }
* xaccSessionEnd (sess);
* xaccSessionDestroy (sess);
*
* To save an existing session to a file:
* --------------------------------------
* AccountGroup * grp = ...
* ... edit accounts, transactions, etc ...
* Session *sess = xaccMallocSession ();
* xaccSessionBegin (sess, "file:/some/file.xac");
* xaccSessionSetGroup (sess, grp);
* int err = xaccSessionGetError (sess);
* if (0 == err) {
* xaccSessionSave (sess);
* } else {
* printf ("%s\n", strerror (err));
* }
* xaccSessionEnd (sess);
* xaccSessionDestroy (sess);
* */
typedef gboolean (*SessionLockFailHandler)(const char *file);
/* These do not load anything. */
gboolean xaccSessionBegin(Session *, const char * sessionid);
gboolean xaccSessionBeginFile(Session *, const char * filename,
SessionLockFailHandler handler);
/* Loads the account group indicated by SessionBegin. */
gboolean xaccSessionLoad(Session *);
int xaccSessionGetError (Session *);
/* 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 Session errors... This just returns the last
FileIO error, but it doesn't clear it. */
GNCFileIOError xaccSessionGetFileError (Session *);
AccountGroup * xaccSessionGetGroup (Session *);
void xaccSessionSetGroup (Session *, AccountGroup *topgroup);
char * xaccSessionGetFilePath (Session *);
/* FIXME: This isn't as thorough as we might want it to be... */
gboolean xaccSessionSaveMayClobberData(Session *s);
void xaccSessionSave (Session *);
void xaccSessionEnd (Session *);
char * xaccResolveFilePath (const char * filefrag);
#endif /* __XACC_SESSION_H__ */
/* ==================== END OF FILE ================== */

@ -25,8 +25,8 @@
#define MAX_HISTORY 4
void gnc_history_add_file(char *);
void gnc_history_update_menu(void);
const char * gnc_history_get_last(void);
void gnc_history_add_file (const char *filename);
void gnc_history_update_menu (void);
const char * gnc_history_get_last (void);
#endif

@ -99,7 +99,7 @@ __gnc_history_file_cb(GtkWidget *w, char *data)
}
void
gnc_history_add_file(char *newfile)
gnc_history_add_file(const char *newfile)
{
int i, max_files;
gboolean used_default, matched = FALSE;

@ -457,7 +457,7 @@ static void
gnc_refresh_main_window_title()
{
GtkWidget *main_window;
Session *session;
GNCBook *book;
gchar *filename;
gchar *title;
@ -465,12 +465,9 @@ gnc_refresh_main_window_title()
if (main_window == NULL)
return;
session = gncGetCurrentSession();
book = gncGetCurrentBook ();
if (session == NULL)
filename = _("Untitled");
else
filename = xaccSessionGetFilePath(session);
filename = gnc_book_get_file_path (book);
if ((filename == NULL) || (*filename == '\0'))
filename = _("Untitled");

Loading…
Cancel
Save