mirror of
https://github.com/Gnucash/gnucash.git
synced 2026-09-03 20:53:02 -05:00
Remove the file-path utilities from qofsession.c and move them into
a temp file 'gnc-filepath-utils.c' which is used by both the file backend and by the app-file GUI routines. Some of the file-resolution logic was oved to the file backend. git-svn-id: svn+ssh://svn.gnucash.org/repo/gnucash/trunk@10063 57a11ea4-9604-0410-9ed3-97b8803252fd
This commit is contained in:
@@ -36,6 +36,7 @@
|
||||
#include "gnc-file-dialog.h"
|
||||
#include "gnc-file-history.h"
|
||||
#include "gnc-file-p.h"
|
||||
#include "gnc-filepath-utils.h"
|
||||
#include "gnc-gui-query.h"
|
||||
#include "gnc-ui.h"
|
||||
#include "gnc-ui-util.h"
|
||||
@@ -43,6 +44,7 @@
|
||||
#include "qofbook.h"
|
||||
#include "qofsession.h"
|
||||
#include "messages.h"
|
||||
#include "TransLog.h"
|
||||
|
||||
/** GLOBALS *********************************************************/
|
||||
/* This static indicates the debugging module that this .o belongs to. */
|
||||
@@ -287,7 +289,9 @@ gnc_file_new (void)
|
||||
SCM_BOOL_F));
|
||||
|
||||
gnc_close_gui_component_by_session (session);
|
||||
xaccLogDisable();
|
||||
qof_session_destroy (session);
|
||||
xaccLogEnable();
|
||||
|
||||
/* start a new book */
|
||||
qof_session_get_current_session ();
|
||||
@@ -375,7 +379,9 @@ gnc_post_file_open (const char * filename)
|
||||
gw_wcp_assimilate_ptr (current_session,
|
||||
scm_c_eval_string("<gnc:Session*>")) :
|
||||
SCM_BOOL_F));
|
||||
xaccLogDisable();
|
||||
qof_session_destroy (current_session);
|
||||
xaccLogEnable();
|
||||
|
||||
/* load the accounts from the users datafile */
|
||||
/* but first, check to make sure we've got a session going. */
|
||||
@@ -462,6 +468,11 @@ gnc_post_file_open (const char * filename)
|
||||
{
|
||||
AccountGroup *new_group;
|
||||
|
||||
char * logpath = xaccResolveFilePath(newfile);
|
||||
PINFO ("logpath=%s", logpath ? logpath : "(null)");
|
||||
xaccLogSetBaseName (logpath);
|
||||
xaccLogDisable();
|
||||
|
||||
if (file_percentage_func) {
|
||||
file_percentage_func(_("Reading file..."), 0.0);
|
||||
qof_session_load (new_session, file_percentage_func);
|
||||
@@ -469,6 +480,7 @@ gnc_post_file_open (const char * filename)
|
||||
} else {
|
||||
qof_session_load (new_session, NULL);
|
||||
}
|
||||
xaccLogEnable();
|
||||
|
||||
/* check for i/o error, put up appropriate error dialog */
|
||||
io_err = qof_session_get_error (new_session);
|
||||
@@ -490,7 +502,9 @@ gnc_post_file_open (const char * filename)
|
||||
/* going down -- abandon ship */
|
||||
if (uh_oh)
|
||||
{
|
||||
xaccLogDisable();
|
||||
qof_session_destroy (new_session);
|
||||
xaccLogEnable();
|
||||
|
||||
/* well, no matter what, I think it's a good idea to have a
|
||||
* topgroup around. For example, early in the gnucash startup
|
||||
@@ -645,7 +659,9 @@ gnc_file_export_file(const char * newfile)
|
||||
ok = qof_session_export (new_session, current_session, NULL);
|
||||
}
|
||||
gnc_unset_busy_cursor (NULL);
|
||||
xaccLogDisable();
|
||||
qof_session_destroy (new_session);
|
||||
xaccLogEnable();
|
||||
gnc_engine_resume_events();
|
||||
|
||||
if (!ok)
|
||||
@@ -801,7 +817,9 @@ gnc_file_save_as (void)
|
||||
if (ERR_BACKEND_NO_ERR != io_err)
|
||||
{
|
||||
show_session_error (io_err, newfile);
|
||||
xaccLogDisable();
|
||||
qof_session_destroy (new_session);
|
||||
xaccLogEnable();
|
||||
g_free (newfile);
|
||||
return;
|
||||
}
|
||||
@@ -809,7 +827,9 @@ gnc_file_save_as (void)
|
||||
/* if we got to here, then we've successfully gotten a new session */
|
||||
/* close up the old file session (if any) */
|
||||
qof_session_swap_data (session, new_session);
|
||||
xaccLogDisable();
|
||||
qof_session_destroy (session);
|
||||
xaccLogEnable();
|
||||
session = NULL;
|
||||
|
||||
/* XXX At this point, we should really mark the data in the new session
|
||||
@@ -859,7 +879,9 @@ gnc_file_quit (void)
|
||||
gw_wcp_assimilate_ptr (session, scm_c_eval_string("<gnc:Session*>")) :
|
||||
SCM_BOOL_F));
|
||||
|
||||
xaccLogDisable();
|
||||
qof_session_destroy (session);
|
||||
xaccLogEnable();
|
||||
|
||||
qof_session_get_current_session ();
|
||||
|
||||
|
||||
+471
-451
@@ -46,6 +46,7 @@
|
||||
#include "gnc-date.h"
|
||||
#include "gnc-trace.h"
|
||||
#include "gnc-engine-util.h"
|
||||
#include "gnc-filepath-utils.h"
|
||||
|
||||
#include "io-gncxml.h"
|
||||
#include "io-gncbin.h"
|
||||
@@ -63,18 +64,6 @@ static short module = MOD_BACKEND;
|
||||
static int file_retention_days = 0;
|
||||
static gboolean file_compression = FALSE;
|
||||
|
||||
static void gnc_file_be_load_from_file(QofBackend *, QofBook *);
|
||||
|
||||
static gboolean gnc_file_be_get_file_lock (FileBackend *be);
|
||||
static gboolean gnc_file_be_write_to_file(FileBackend *be, QofBook *,
|
||||
const gchar *filepath,
|
||||
gboolean make_backup);
|
||||
static void gnc_file_be_write_accounts_to_file(QofBackend *be,
|
||||
QofBook *book);
|
||||
static void gnc_file_be_remove_old_files(FileBackend *be);
|
||||
|
||||
QofBackend * libgncmod_backend_file_LTX_gnc_backend_new(void);
|
||||
|
||||
void
|
||||
gnc_file_be_set_retention_days (int days)
|
||||
{
|
||||
@@ -89,263 +78,6 @@ gnc_file_be_set_compression (gboolean compress)
|
||||
|
||||
/* ================================================================= */
|
||||
|
||||
static void
|
||||
file_session_begin(QofBackend *be_start, QofSession *session,
|
||||
const char *book_id,
|
||||
gboolean ignore_lock, gboolean create_if_nonexistent)
|
||||
{
|
||||
FileBackend *be = (FileBackend*) be_start;
|
||||
char *p;
|
||||
|
||||
ENTER (" ");
|
||||
|
||||
/* Make sure the directory is there */
|
||||
be->dirname = g_strdup (qof_session_get_file_path (session));
|
||||
be->fullpath = g_strdup (be->dirname);
|
||||
p = strrchr (be->dirname, '/');
|
||||
if (p && p != be->dirname)
|
||||
{
|
||||
struct stat statbuf;
|
||||
int rc;
|
||||
|
||||
*p = '\0';
|
||||
rc = stat (be->dirname, &statbuf);
|
||||
if (rc != 0 || !S_ISDIR(statbuf.st_mode))
|
||||
{
|
||||
qof_backend_set_error (be_start, ERR_FILEIO_FILE_NOT_FOUND);
|
||||
g_free (be->fullpath); be->fullpath = NULL;
|
||||
g_free (be->dirname); be->dirname = NULL;
|
||||
return;
|
||||
}
|
||||
rc = stat (be->fullpath, &statbuf);
|
||||
if (rc == 0 && S_ISDIR(statbuf.st_mode))
|
||||
{
|
||||
qof_backend_set_error (be_start, ERR_FILEIO_UNKNOWN_FILE_TYPE);
|
||||
g_free (be->fullpath); be->fullpath = NULL;
|
||||
g_free (be->dirname); be->dirname = NULL;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------- */
|
||||
/* We should now have a fully resolved path name.
|
||||
* Lets see if we can get a lock on it. */
|
||||
|
||||
be->lockfile = g_strconcat(be->fullpath, ".LCK", NULL);
|
||||
|
||||
if (!ignore_lock && !gnc_file_be_get_file_lock (be))
|
||||
{
|
||||
g_free (be->lockfile); be->lockfile = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
LEAVE (" ");
|
||||
return;
|
||||
}
|
||||
|
||||
/* ================================================================= */
|
||||
|
||||
static void
|
||||
file_session_end(QofBackend *be_start)
|
||||
{
|
||||
FileBackend *be = (FileBackend*)be_start;
|
||||
ENTER (" ");
|
||||
|
||||
if (be->linkfile)
|
||||
unlink (be->linkfile);
|
||||
|
||||
if (be->lockfd > 0)
|
||||
close (be->lockfd);
|
||||
|
||||
if (be->lockfile)
|
||||
unlink (be->lockfile);
|
||||
|
||||
g_free (be->dirname);
|
||||
be->dirname = NULL;
|
||||
|
||||
g_free (be->fullpath);
|
||||
be->fullpath = NULL;
|
||||
|
||||
g_free (be->lockfile);
|
||||
be->lockfile = NULL;
|
||||
|
||||
g_free (be->linkfile);
|
||||
be->linkfile = NULL;
|
||||
LEAVE (" ");
|
||||
}
|
||||
|
||||
static void
|
||||
file_destroy_backend(QofBackend *be)
|
||||
{
|
||||
g_free(be);
|
||||
}
|
||||
|
||||
static void
|
||||
file_sync_all(QofBackend* be, QofBook *book)
|
||||
{
|
||||
FileBackend *fbe = (FileBackend *) be;
|
||||
ENTER ("book=%p, primary=%p", book, fbe->primary_book);
|
||||
|
||||
/* We make an important assumption here, that we might want to change
|
||||
* in the future: when the user says 'save', we really save the one,
|
||||
* the only, the current open book, and nothing else. We do this
|
||||
* because we assume that any other books that we are dealing with
|
||||
* are 'read-only', non-editable, because they are closed books.
|
||||
* If we ever want to have more than one book open read-write,
|
||||
* this will have to change.
|
||||
*/
|
||||
if (NULL == fbe->primary_book) fbe->primary_book = book;
|
||||
if (book != fbe->primary_book) return;
|
||||
|
||||
gnc_file_be_write_to_file (fbe, book, fbe->fullpath, TRUE);
|
||||
gnc_file_be_remove_old_files (fbe);
|
||||
LEAVE ("book=%p", book);
|
||||
}
|
||||
|
||||
/* ================================================================= */
|
||||
/* Routines to deal with the creation of multiple books.
|
||||
* The core design assumption here is that the book
|
||||
* begin-edit/commit-edit routines are used solely to write out
|
||||
* closed accounting periods to files. They're not currently
|
||||
* designed to do anything other than this. (Although they could be).
|
||||
*/
|
||||
|
||||
static char *
|
||||
build_period_filepath (FileBackend *fbe, QofBook *book)
|
||||
{
|
||||
int len;
|
||||
char *str, *p, *q;
|
||||
|
||||
len = strlen (fbe->fullpath) + GUID_ENCODING_LENGTH + 14;
|
||||
str = g_new (char, len);
|
||||
strcpy (str, fbe->fullpath);
|
||||
|
||||
/* XXX it would be nice for the user if we made the book
|
||||
* closing date and/or title part of the file-name. */
|
||||
p = strrchr (str, '/');
|
||||
p++;
|
||||
p = stpcpy (p, "book-");
|
||||
p = guid_to_string_buff (qof_book_get_guid(book), p);
|
||||
p = stpcpy (p, "-");
|
||||
q = strrchr (fbe->fullpath, '/');
|
||||
q++;
|
||||
p = stpcpy (p, q);
|
||||
p = stpcpy (p, ".gml");
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
static void
|
||||
file_begin_edit (QofBackend *be, QofInstance *inst)
|
||||
{
|
||||
if (0) build_period_filepath(0, 0);
|
||||
#if BORKEN_FOR_NOW
|
||||
FileBackend *fbe = (FileBackend *) be;
|
||||
QofBook *book = gp;
|
||||
const char * filepath;
|
||||
|
||||
QofIdTypeConst typ = QOF_ENTITY(inst)->e_type;
|
||||
if (strcmp (GNC_ID_PERIOD, typ)) return;
|
||||
filepath = build_period_filepath(fbe, book);
|
||||
PINFO (" ====================== book=%p filepath=%s\n", book, filepath);
|
||||
|
||||
if (NULL == fbe->primary_book)
|
||||
{
|
||||
PERR ("You should have saved the data "
|
||||
"at least once before closing the books!\n");
|
||||
}
|
||||
/* XXX To be anal about it, we should really be checking to see
|
||||
* if there already is a file with this book GUID, and disallowing
|
||||
* further progress. This is because we are not allowed to
|
||||
* modify books that are closed (They should be treated as
|
||||
* 'read-only').
|
||||
*/
|
||||
#endif
|
||||
}
|
||||
|
||||
static void
|
||||
file_rollback_edit (QofBackend *be, QofInstance *inst)
|
||||
{
|
||||
#if BORKEN_FOR_NOW
|
||||
QofBook *book = gp;
|
||||
|
||||
if (strcmp (GNC_ID_PERIOD, typ)) return;
|
||||
PINFO ("book=%p", book);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void
|
||||
file_commit_edit (QofBackend *be, QofInstance *inst)
|
||||
{
|
||||
#if BORKEN_FOR_NOW
|
||||
FileBackend *fbe = (FileBackend *) be;
|
||||
QofBook *book = gp;
|
||||
const char * filepath;
|
||||
|
||||
if (strcmp (GNC_ID_PERIOD, typ)) return;
|
||||
filepath = build_period_filepath(fbe, book);
|
||||
PINFO (" ====================== book=%p filepath=%s\n", book, filepath);
|
||||
gnc_file_be_write_to_file(fbe, book, filepath, FALSE);
|
||||
|
||||
/* We want to force a save of the current book at this point,
|
||||
* because if we don't, and the user forgets to do so, then
|
||||
* there'll be the same transactions in the closed book,
|
||||
* and also in the current book. */
|
||||
gnc_file_be_write_to_file (fbe, fbe->primary_book, fbe->fullpath, TRUE);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* ================================================================= */
|
||||
|
||||
QofBackend *
|
||||
libgncmod_backend_file_LTX_gnc_backend_new(void)
|
||||
{
|
||||
FileBackend *fbe;
|
||||
QofBackend *be;
|
||||
|
||||
fbe = g_new0(FileBackend, 1);
|
||||
be = (QofBackend*)fbe;
|
||||
qof_backend_init(be);
|
||||
|
||||
be->session_begin = file_session_begin;
|
||||
be->session_end = file_session_end;
|
||||
be->destroy_backend = file_destroy_backend;
|
||||
|
||||
be->load = gnc_file_be_load_from_file;
|
||||
|
||||
/* The file backend treats accounting periods transactionally. */
|
||||
be->begin = file_begin_edit;
|
||||
be->commit = file_commit_edit;
|
||||
be->rollback = file_rollback_edit;
|
||||
|
||||
/* The file backend always loads all data ... */
|
||||
be->compile_query = NULL;
|
||||
be->free_query = NULL;
|
||||
be->run_query = NULL;
|
||||
be->price_lookup = NULL;
|
||||
|
||||
be->counter = NULL;
|
||||
|
||||
/* The file backend will never be multi-user... */
|
||||
be->events_pending = NULL;
|
||||
be->process_events = NULL;
|
||||
|
||||
be->sync = file_sync_all;
|
||||
be->export = gnc_file_be_write_accounts_to_file;
|
||||
|
||||
fbe->dirname = NULL;
|
||||
fbe->fullpath = NULL;
|
||||
fbe->lockfile = NULL;
|
||||
fbe->linkfile = NULL;
|
||||
fbe->lockfd = -1;
|
||||
|
||||
fbe->primary_book = NULL;
|
||||
|
||||
return be;
|
||||
}
|
||||
|
||||
/* ================================================================= */
|
||||
|
||||
static gboolean
|
||||
gnc_file_be_get_file_lock (FileBackend *be)
|
||||
{
|
||||
@@ -444,96 +176,106 @@ gnc_file_be_get_file_lock (FileBackend *be)
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
static gboolean
|
||||
is_gzipped_file(const gchar *name)
|
||||
{
|
||||
unsigned char buf[2];
|
||||
int fd = open(name, O_RDONLY);
|
||||
|
||||
if(fd == 0)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if(read(fd, buf, 2) != 2)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if(buf[0] == 037 && buf[1] == 0213)
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static QofBookFileType
|
||||
gnc_file_be_determine_file_type(const char *path)
|
||||
{
|
||||
if(gnc_is_xml_data_file_v2(path)) {
|
||||
return GNC_BOOK_XML2_FILE;
|
||||
} else if(gnc_is_xml_data_file(path)) {
|
||||
return GNC_BOOK_XML1_FILE;
|
||||
} else if(is_gzipped_file(path)) {
|
||||
return GNC_BOOK_XML2_FILE;
|
||||
} else {
|
||||
return GNC_BOOK_BIN_FILE;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Load financial data from a file into the book, automtically
|
||||
detecting the format of the file, if possible. Return FALSE on
|
||||
error, and set the error parameter to indicate what went wrong if
|
||||
it's not NULL. This function does not manage file locks in any
|
||||
way. */
|
||||
/* ================================================================= */
|
||||
|
||||
static void
|
||||
gnc_file_be_load_from_file (QofBackend *bend, QofBook *book)
|
||||
file_session_begin(QofBackend *be_start, QofSession *session,
|
||||
const char *book_id,
|
||||
gboolean ignore_lock, gboolean create_if_nonexistent)
|
||||
{
|
||||
QofBackendError error = ERR_BACKEND_NO_ERR;
|
||||
gboolean rc;
|
||||
FileBackend *be = (FileBackend *) bend;
|
||||
FileBackend *be = (FileBackend*) be_start;
|
||||
char *p;
|
||||
|
||||
be->primary_book = book;
|
||||
ENTER (" ");
|
||||
|
||||
switch (gnc_file_be_determine_file_type(be->fullpath))
|
||||
/* Make sure the directory is there */
|
||||
be->dirname = xaccResolveFilePath(book_id);
|
||||
if (NULL == be->dirname)
|
||||
{
|
||||
case GNC_BOOK_XML2_FILE:
|
||||
rc = qof_session_load_from_xml_file_v2 (be, book);
|
||||
if (FALSE == rc) error = ERR_FILEIO_PARSE_ERROR;
|
||||
break;
|
||||
qof_backend_set_error (be_start, ERR_FILEIO_FILE_NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
be->fullpath = g_strdup (be->dirname);
|
||||
be->be.fullpath = be->fullpath;
|
||||
p = strrchr (be->dirname, '/');
|
||||
if (p && p != be->dirname)
|
||||
{
|
||||
struct stat statbuf;
|
||||
int rc;
|
||||
|
||||
case GNC_BOOK_XML1_FILE:
|
||||
rc = qof_session_load_from_xml_file (book, be->fullpath);
|
||||
if (FALSE == rc) error = ERR_FILEIO_PARSE_ERROR;
|
||||
break;
|
||||
|
||||
case GNC_BOOK_BIN_FILE:
|
||||
/* presume it's an old-style binary file */
|
||||
qof_session_load_from_binfile(book, be->fullpath);
|
||||
error = gnc_get_binfile_io_error();
|
||||
break;
|
||||
|
||||
default:
|
||||
PWARN("File not any known type");
|
||||
error = ERR_FILEIO_UNKNOWN_FILE_TYPE;
|
||||
break;
|
||||
*p = '\0';
|
||||
rc = stat (be->dirname, &statbuf);
|
||||
if (rc != 0 || !S_ISDIR(statbuf.st_mode))
|
||||
{
|
||||
qof_backend_set_error (be_start, ERR_FILEIO_FILE_NOT_FOUND);
|
||||
g_free (be->fullpath); be->fullpath = NULL;
|
||||
g_free (be->dirname); be->dirname = NULL;
|
||||
return;
|
||||
}
|
||||
rc = stat (be->fullpath, &statbuf);
|
||||
if (rc == 0 && S_ISDIR(statbuf.st_mode))
|
||||
{
|
||||
qof_backend_set_error (be_start, ERR_FILEIO_UNKNOWN_FILE_TYPE);
|
||||
g_free (be->fullpath); be->fullpath = NULL;
|
||||
g_free (be->dirname); be->dirname = NULL;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(error != ERR_BACKEND_NO_ERR)
|
||||
/* ---------------------------------------------------- */
|
||||
/* We should now have a fully resolved path name.
|
||||
* Lets see if we can get a lock on it. */
|
||||
|
||||
be->lockfile = g_strconcat(be->fullpath, ".LCK", NULL);
|
||||
|
||||
if (!ignore_lock && !gnc_file_be_get_file_lock (be))
|
||||
{
|
||||
qof_backend_set_error(bend, error);
|
||||
g_free (be->lockfile); be->lockfile = NULL;
|
||||
return;
|
||||
}
|
||||
|
||||
/* We just got done loading, it can't possibly be dirty !! */
|
||||
qof_book_mark_saved (book);
|
||||
LEAVE (" ");
|
||||
return;
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
/* ================================================================= */
|
||||
|
||||
static void
|
||||
file_session_end(QofBackend *be_start)
|
||||
{
|
||||
FileBackend *be = (FileBackend*)be_start;
|
||||
ENTER (" ");
|
||||
|
||||
if (be->linkfile)
|
||||
unlink (be->linkfile);
|
||||
|
||||
if (be->lockfd > 0)
|
||||
close (be->lockfd);
|
||||
|
||||
if (be->lockfile)
|
||||
unlink (be->lockfile);
|
||||
|
||||
g_free (be->dirname);
|
||||
be->dirname = NULL;
|
||||
|
||||
g_free (be->fullpath);
|
||||
be->fullpath = NULL;
|
||||
|
||||
g_free (be->lockfile);
|
||||
be->lockfile = NULL;
|
||||
|
||||
g_free (be->linkfile);
|
||||
be->linkfile = NULL;
|
||||
LEAVE (" ");
|
||||
}
|
||||
|
||||
static void
|
||||
file_destroy_backend(QofBackend *be)
|
||||
{
|
||||
g_free(be);
|
||||
}
|
||||
|
||||
/* ================================================================= */
|
||||
/* Write the financial data in a book to a file, returning FALSE on
|
||||
error and setting the error_result to indicate what went wrong if
|
||||
it's not NULL. This function does not manage file locks in any
|
||||
@@ -594,6 +336,8 @@ copy_file(const char *orig, const char *bkup)
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* ================================================================= */
|
||||
|
||||
static gboolean
|
||||
gnc_int_link_or_make_backup(FileBackend *be, const char *orig, const char *bkup)
|
||||
{
|
||||
@@ -617,6 +361,47 @@ gnc_int_link_or_make_backup(FileBackend *be, const char *orig, const char *bkup)
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* ================================================================= */
|
||||
|
||||
static gboolean
|
||||
is_gzipped_file(const gchar *name)
|
||||
{
|
||||
unsigned char buf[2];
|
||||
int fd = open(name, O_RDONLY);
|
||||
|
||||
if(fd == 0)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if(read(fd, buf, 2) != 2)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if(buf[0] == 037 && buf[1] == 0213)
|
||||
{
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static QofBookFileType
|
||||
gnc_file_be_determine_file_type(const char *path)
|
||||
{
|
||||
if(gnc_is_xml_data_file_v2(path)) {
|
||||
return GNC_BOOK_XML2_FILE;
|
||||
} else if(gnc_is_xml_data_file(path)) {
|
||||
return GNC_BOOK_XML1_FILE;
|
||||
} else if(is_gzipped_file(path)) {
|
||||
return GNC_BOOK_XML2_FILE;
|
||||
} else {
|
||||
return GNC_BOOK_BIN_FILE;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static gboolean
|
||||
gnc_file_be_backup_file(FileBackend *be)
|
||||
{
|
||||
@@ -662,112 +447,8 @@ gnc_file_be_backup_file(FileBackend *be)
|
||||
return bkup_ret;
|
||||
}
|
||||
|
||||
static int
|
||||
gnc_file_be_select_files (const struct dirent *d)
|
||||
{
|
||||
int len = strlen(d->d_name) - 4;
|
||||
|
||||
if (len <= 0)
|
||||
return(0);
|
||||
|
||||
return((strcmp(d->d_name + len, ".LNK") == 0) ||
|
||||
(strcmp(d->d_name + len, ".xac") == 0) ||
|
||||
(strcmp(d->d_name + len, ".log") == 0));
|
||||
}
|
||||
|
||||
static void
|
||||
gnc_file_be_remove_old_files(FileBackend *be)
|
||||
{
|
||||
struct dirent *dent;
|
||||
DIR *dir;
|
||||
struct stat lockstatbuf, statbuf;
|
||||
int pathlen;
|
||||
time_t now;
|
||||
|
||||
if (stat (be->lockfile, &lockstatbuf) != 0)
|
||||
return;
|
||||
pathlen = strlen(be->fullpath);
|
||||
|
||||
/*
|
||||
* Clean up any lockfiles from prior crashes, and clean up old
|
||||
* data and log files. Scandir will do a fist pass on the
|
||||
* filenames and cull the directory down to just files with the
|
||||
* appropriate extensions. Pity you can't pass user data into
|
||||
* scandir...
|
||||
*/
|
||||
|
||||
/*
|
||||
* Unfortunately scandir() is not portable, so re-write this
|
||||
* function without it. Note that this version will be even a bit
|
||||
* faster because it does not have to sort, malloc, or anything
|
||||
* else that scandir did, and it only performs a single pass
|
||||
* through the directory rather than one pass through the
|
||||
* directory and then one pass over the 'matching' files. --
|
||||
* warlord@MIT.EDU 2002-05-06
|
||||
*/
|
||||
|
||||
dir = opendir (be->dirname);
|
||||
if (!dir)
|
||||
return;
|
||||
|
||||
now = time(NULL);
|
||||
while((dent = readdir(dir)) != NULL) {
|
||||
char *name;
|
||||
int len;
|
||||
|
||||
if (gnc_file_be_select_files (dent) == 0)
|
||||
continue;
|
||||
|
||||
name = g_strconcat(be->dirname, "/", dent->d_name, NULL);
|
||||
len = strlen(name) - 4;
|
||||
|
||||
/* Is this file associated with the current data file */
|
||||
if (strncmp(name, be->fullpath, pathlen) == 0)
|
||||
{
|
||||
if ((safe_strcmp(name + len, ".LNK") == 0) &&
|
||||
/* Is a lock file. Skip the active lock file */
|
||||
(safe_strcmp(name, be->linkfile) != 0) &&
|
||||
/* Only delete lock files older than the active one */
|
||||
(stat(name, &statbuf) == 0) &&
|
||||
(statbuf.st_mtime <lockstatbuf.st_mtime))
|
||||
{
|
||||
PINFO ("unlink lock file: %s", name);
|
||||
unlink(name);
|
||||
}
|
||||
else if (file_retention_days > 0)
|
||||
{
|
||||
time_t file_time;
|
||||
struct tm file_tm;
|
||||
int days;
|
||||
const char* res;
|
||||
|
||||
PINFO ("file retention = %d days", file_retention_days);
|
||||
|
||||
/* Is the backup file old enough to delete */
|
||||
memset(&file_tm, 0, sizeof(file_tm));
|
||||
res = strptime(name+pathlen+1, "%Y%m%d%H%M%S", &file_tm);
|
||||
file_time = mktime(&file_tm);
|
||||
days = (int)(difftime(now, file_time) / 86400);
|
||||
|
||||
/* Make sure this file actually has a date before unlinking */
|
||||
if (res && res != name+pathlen+1 &&
|
||||
/* We consumed some but not all of the filename */
|
||||
file_time > 0 &&
|
||||
/* we actually have a reasonable time and it is old enough */
|
||||
days > file_retention_days)
|
||||
{
|
||||
PINFO ("unlink stale (%d days old) file: %s", days, name);
|
||||
unlink(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
g_free(name);
|
||||
}
|
||||
closedir (dir);
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
/* ================================================================= */
|
||||
|
||||
static gboolean
|
||||
gnc_file_be_write_to_file(FileBackend *fbe,
|
||||
QofBook *book,
|
||||
@@ -892,6 +573,295 @@ gnc_file_be_write_to_file(FileBackend *fbe,
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* ================================================================= */
|
||||
|
||||
static int
|
||||
gnc_file_be_select_files (const struct dirent *d)
|
||||
{
|
||||
int len = strlen(d->d_name) - 4;
|
||||
|
||||
if (len <= 0)
|
||||
return(0);
|
||||
|
||||
return((strcmp(d->d_name + len, ".LNK") == 0) ||
|
||||
(strcmp(d->d_name + len, ".xac") == 0) ||
|
||||
(strcmp(d->d_name + len, ".log") == 0));
|
||||
}
|
||||
|
||||
static void
|
||||
gnc_file_be_remove_old_files(FileBackend *be)
|
||||
{
|
||||
struct dirent *dent;
|
||||
DIR *dir;
|
||||
struct stat lockstatbuf, statbuf;
|
||||
int pathlen;
|
||||
time_t now;
|
||||
|
||||
if (stat (be->lockfile, &lockstatbuf) != 0)
|
||||
return;
|
||||
pathlen = strlen(be->fullpath);
|
||||
|
||||
/*
|
||||
* Clean up any lockfiles from prior crashes, and clean up old
|
||||
* data and log files. Scandir will do a fist pass on the
|
||||
* filenames and cull the directory down to just files with the
|
||||
* appropriate extensions. Pity you can't pass user data into
|
||||
* scandir...
|
||||
*/
|
||||
|
||||
/*
|
||||
* Unfortunately scandir() is not portable, so re-write this
|
||||
* function without it. Note that this version will be even a bit
|
||||
* faster because it does not have to sort, malloc, or anything
|
||||
* else that scandir did, and it only performs a single pass
|
||||
* through the directory rather than one pass through the
|
||||
* directory and then one pass over the 'matching' files. --
|
||||
* warlord@MIT.EDU 2002-05-06
|
||||
*/
|
||||
|
||||
dir = opendir (be->dirname);
|
||||
if (!dir)
|
||||
return;
|
||||
|
||||
now = time(NULL);
|
||||
while((dent = readdir(dir)) != NULL) {
|
||||
char *name;
|
||||
int len;
|
||||
|
||||
if (gnc_file_be_select_files (dent) == 0)
|
||||
continue;
|
||||
|
||||
name = g_strconcat(be->dirname, "/", dent->d_name, NULL);
|
||||
len = strlen(name) - 4;
|
||||
|
||||
/* Is this file associated with the current data file */
|
||||
if (strncmp(name, be->fullpath, pathlen) == 0)
|
||||
{
|
||||
if ((safe_strcmp(name + len, ".LNK") == 0) &&
|
||||
/* Is a lock file. Skip the active lock file */
|
||||
(safe_strcmp(name, be->linkfile) != 0) &&
|
||||
/* Only delete lock files older than the active one */
|
||||
(stat(name, &statbuf) == 0) &&
|
||||
(statbuf.st_mtime <lockstatbuf.st_mtime))
|
||||
{
|
||||
PINFO ("unlink lock file: %s", name);
|
||||
unlink(name);
|
||||
}
|
||||
else if (file_retention_days > 0)
|
||||
{
|
||||
time_t file_time;
|
||||
struct tm file_tm;
|
||||
int days;
|
||||
const char* res;
|
||||
|
||||
PINFO ("file retention = %d days", file_retention_days);
|
||||
|
||||
/* Is the backup file old enough to delete */
|
||||
memset(&file_tm, 0, sizeof(file_tm));
|
||||
res = strptime(name+pathlen+1, "%Y%m%d%H%M%S", &file_tm);
|
||||
file_time = mktime(&file_tm);
|
||||
days = (int)(difftime(now, file_time) / 86400);
|
||||
|
||||
/* Make sure this file actually has a date before unlinking */
|
||||
if (res && res != name+pathlen+1 &&
|
||||
/* We consumed some but not all of the filename */
|
||||
file_time > 0 &&
|
||||
/* we actually have a reasonable time and it is old enough */
|
||||
days > file_retention_days)
|
||||
{
|
||||
PINFO ("unlink stale (%d days old) file: %s", days, name);
|
||||
unlink(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
g_free(name);
|
||||
}
|
||||
closedir (dir);
|
||||
}
|
||||
|
||||
static void
|
||||
file_sync_all(QofBackend* be, QofBook *book)
|
||||
{
|
||||
FileBackend *fbe = (FileBackend *) be;
|
||||
ENTER ("book=%p, primary=%p", book, fbe->primary_book);
|
||||
|
||||
/* We make an important assumption here, that we might want to change
|
||||
* in the future: when the user says 'save', we really save the one,
|
||||
* the only, the current open book, and nothing else. We do this
|
||||
* because we assume that any other books that we are dealing with
|
||||
* are 'read-only', non-editable, because they are closed books.
|
||||
* If we ever want to have more than one book open read-write,
|
||||
* this will have to change.
|
||||
*/
|
||||
if (NULL == fbe->primary_book) fbe->primary_book = book;
|
||||
if (book != fbe->primary_book) return;
|
||||
|
||||
gnc_file_be_write_to_file (fbe, book, fbe->fullpath, TRUE);
|
||||
gnc_file_be_remove_old_files (fbe);
|
||||
LEAVE ("book=%p", book);
|
||||
}
|
||||
|
||||
/* ================================================================= */
|
||||
/* Routines to deal with the creation of multiple books.
|
||||
* The core design assumption here is that the book
|
||||
* begin-edit/commit-edit routines are used solely to write out
|
||||
* closed accounting periods to files. They're not currently
|
||||
* designed to do anything other than this. (Although they could be).
|
||||
*/
|
||||
|
||||
static char *
|
||||
build_period_filepath (FileBackend *fbe, QofBook *book)
|
||||
{
|
||||
int len;
|
||||
char *str, *p, *q;
|
||||
|
||||
len = strlen (fbe->fullpath) + GUID_ENCODING_LENGTH + 14;
|
||||
str = g_new (char, len);
|
||||
strcpy (str, fbe->fullpath);
|
||||
|
||||
/* XXX it would be nice for the user if we made the book
|
||||
* closing date and/or title part of the file-name. */
|
||||
p = strrchr (str, '/');
|
||||
p++;
|
||||
p = stpcpy (p, "book-");
|
||||
p = guid_to_string_buff (qof_book_get_guid(book), p);
|
||||
p = stpcpy (p, "-");
|
||||
q = strrchr (fbe->fullpath, '/');
|
||||
q++;
|
||||
p = stpcpy (p, q);
|
||||
p = stpcpy (p, ".gml");
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
static void
|
||||
file_begin_edit (QofBackend *be, QofInstance *inst)
|
||||
{
|
||||
if (0) build_period_filepath(0, 0);
|
||||
#if BORKEN_FOR_NOW
|
||||
FileBackend *fbe = (FileBackend *) be;
|
||||
QofBook *book = gp;
|
||||
const char * filepath;
|
||||
|
||||
QofIdTypeConst typ = QOF_ENTITY(inst)->e_type;
|
||||
if (strcmp (GNC_ID_PERIOD, typ)) return;
|
||||
filepath = build_period_filepath(fbe, book);
|
||||
PINFO (" ====================== book=%p filepath=%s\n", book, filepath);
|
||||
|
||||
if (NULL == fbe->primary_book)
|
||||
{
|
||||
PERR ("You should have saved the data "
|
||||
"at least once before closing the books!\n");
|
||||
}
|
||||
/* XXX To be anal about it, we should really be checking to see
|
||||
* if there already is a file with this book GUID, and disallowing
|
||||
* further progress. This is because we are not allowed to
|
||||
* modify books that are closed (They should be treated as
|
||||
* 'read-only').
|
||||
*/
|
||||
#endif
|
||||
}
|
||||
|
||||
static void
|
||||
file_rollback_edit (QofBackend *be, QofInstance *inst)
|
||||
{
|
||||
#if BORKEN_FOR_NOW
|
||||
QofBook *book = gp;
|
||||
|
||||
if (strcmp (GNC_ID_PERIOD, typ)) return;
|
||||
PINFO ("book=%p", book);
|
||||
#endif
|
||||
}
|
||||
|
||||
static void
|
||||
file_commit_edit (QofBackend *be, QofInstance *inst)
|
||||
{
|
||||
#if BORKEN_FOR_NOW
|
||||
FileBackend *fbe = (FileBackend *) be;
|
||||
QofBook *book = gp;
|
||||
const char * filepath;
|
||||
|
||||
if (strcmp (GNC_ID_PERIOD, typ)) return;
|
||||
filepath = build_period_filepath(fbe, book);
|
||||
PINFO (" ====================== book=%p filepath=%s\n", book, filepath);
|
||||
gnc_file_be_write_to_file(fbe, book, filepath, FALSE);
|
||||
|
||||
/* We want to force a save of the current book at this point,
|
||||
* because if we don't, and the user forgets to do so, then
|
||||
* there'll be the same transactions in the closed book,
|
||||
* and also in the current book. */
|
||||
gnc_file_be_write_to_file (fbe, fbe->primary_book, fbe->fullpath, TRUE);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
|
||||
/* Load financial data from a file into the book, automtically
|
||||
detecting the format of the file, if possible. Return FALSE on
|
||||
error, and set the error parameter to indicate what went wrong if
|
||||
it's not NULL. This function does not manage file locks in any
|
||||
way. */
|
||||
|
||||
static void
|
||||
gnc_file_be_load_from_file (QofBackend *bend, QofBook *book)
|
||||
{
|
||||
QofBackendError error = ERR_BACKEND_NO_ERR;
|
||||
gboolean rc;
|
||||
FileBackend *be = (FileBackend *) bend;
|
||||
|
||||
be->primary_book = book;
|
||||
|
||||
switch (gnc_file_be_determine_file_type(be->fullpath))
|
||||
{
|
||||
case GNC_BOOK_XML2_FILE:
|
||||
rc = qof_session_load_from_xml_file_v2 (be, book);
|
||||
if (FALSE == rc) error = ERR_FILEIO_PARSE_ERROR;
|
||||
break;
|
||||
|
||||
case GNC_BOOK_XML1_FILE:
|
||||
rc = qof_session_load_from_xml_file (book, be->fullpath);
|
||||
if (FALSE == rc) error = ERR_FILEIO_PARSE_ERROR;
|
||||
break;
|
||||
|
||||
case GNC_BOOK_BIN_FILE:
|
||||
/* presume it's an old-style binary file */
|
||||
qof_session_load_from_binfile(book, be->fullpath);
|
||||
error = gnc_get_binfile_io_error();
|
||||
break;
|
||||
|
||||
default:
|
||||
PWARN("File not any known type");
|
||||
error = ERR_FILEIO_UNKNOWN_FILE_TYPE;
|
||||
break;
|
||||
}
|
||||
|
||||
if(error != ERR_BACKEND_NO_ERR)
|
||||
{
|
||||
qof_backend_set_error(bend, error);
|
||||
}
|
||||
|
||||
/* We just got done loading, it can't possibly be dirty !! */
|
||||
qof_book_mark_saved (book);
|
||||
}
|
||||
|
||||
/* ---------------------------------------------------------------------- */
|
||||
|
||||
static gboolean
|
||||
gnc_file_be_save_may_clobber_data (QofBackend *bend)
|
||||
{
|
||||
struct stat statbuf;
|
||||
|
||||
if (!bend->fullpath) return FALSE;
|
||||
|
||||
/* FIXME: Make sure this doesn't need more sophisticated semantics
|
||||
* in the face of special file, devices, pipes, symlinks, etc. */
|
||||
if (stat(bend->fullpath, &statbuf) == 0) return TRUE;
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
gnc_file_be_write_accounts_to_file(QofBackend *be, QofBook *book)
|
||||
{
|
||||
@@ -901,4 +871,54 @@ gnc_file_be_write_accounts_to_file(QofBackend *be, QofBook *book)
|
||||
gnc_book_write_accounts_to_xml_file_v2(be, book, datafile);
|
||||
}
|
||||
|
||||
/* ================================================================= */
|
||||
|
||||
QofBackend *
|
||||
libgncmod_backend_file_LTX_gnc_backend_new(void)
|
||||
{
|
||||
FileBackend *fbe;
|
||||
QofBackend *be;
|
||||
|
||||
fbe = g_new0(FileBackend, 1);
|
||||
be = (QofBackend*)fbe;
|
||||
qof_backend_init(be);
|
||||
|
||||
be->session_begin = file_session_begin;
|
||||
be->session_end = file_session_end;
|
||||
be->destroy_backend = file_destroy_backend;
|
||||
|
||||
be->load = gnc_file_be_load_from_file;
|
||||
be->save_may_clobber_data = gnc_file_be_save_may_clobber_data;
|
||||
|
||||
/* The file backend treats accounting periods transactionally. */
|
||||
be->begin = file_begin_edit;
|
||||
be->commit = file_commit_edit;
|
||||
be->rollback = file_rollback_edit;
|
||||
|
||||
/* The file backend always loads all data ... */
|
||||
be->compile_query = NULL;
|
||||
be->free_query = NULL;
|
||||
be->run_query = NULL;
|
||||
be->price_lookup = NULL;
|
||||
|
||||
be->counter = NULL;
|
||||
|
||||
/* The file backend will never be multi-user... */
|
||||
be->events_pending = NULL;
|
||||
be->process_events = NULL;
|
||||
|
||||
be->sync = file_sync_all;
|
||||
be->export = gnc_file_be_write_accounts_to_file;
|
||||
|
||||
fbe->dirname = NULL;
|
||||
fbe->fullpath = NULL;
|
||||
fbe->lockfile = NULL;
|
||||
fbe->linkfile = NULL;
|
||||
fbe->lockfd = -1;
|
||||
|
||||
fbe->primary_book = NULL;
|
||||
|
||||
return be;
|
||||
}
|
||||
|
||||
/* ========================== END OF FILE ===================== */
|
||||
|
||||
@@ -57,4 +57,6 @@ typedef enum
|
||||
GNC_BOOK_XML2_FILE,
|
||||
} QofBookFileType;
|
||||
|
||||
QofBackend * libgncmod_backend_file_LTX_gnc_backend_new(void);
|
||||
|
||||
#endif /* GNC_BACKEND_FILE_H_ */
|
||||
|
||||
@@ -30,6 +30,7 @@ libgncmod_engine_la_SOURCES = \
|
||||
gnc-engine-util.c \
|
||||
gnc-engine.c \
|
||||
gnc-event.c \
|
||||
gnc-filepath-utils.c \
|
||||
gnc-lot.c \
|
||||
gnc-numeric.c \
|
||||
gnc-pricedb.c \
|
||||
@@ -83,6 +84,7 @@ gncinclude_HEADERS = \
|
||||
gnc-engine-util.h \
|
||||
gnc-engine.h \
|
||||
gnc-event.h \
|
||||
gnc-filepath-utils.h \
|
||||
gnc-numeric.h \
|
||||
gnc-pricedb.h \
|
||||
gnc-session.h \
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
ws
|
||||
(lambda (wrapset client-wrapset)
|
||||
(list
|
||||
"#include <config.h>\n"
|
||||
"#include <glib.h>\n"
|
||||
"#include <guid.h>\n"
|
||||
"#include <Group.h>\n"
|
||||
|
||||
+28
-19
@@ -1,8 +1,6 @@
|
||||
/********************************************************************\
|
||||
* qofbackend-p.h -- private api for data storage backend *
|
||||
* *
|
||||
* Copyright (c) 2000, 2001 Linas Vepstas <linas@linas.org> *
|
||||
* *
|
||||
* 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 *
|
||||
@@ -34,6 +32,10 @@
|
||||
|
||||
The callbacks will be called at the appropriate times during
|
||||
a book session to allow the backend to store the data as needed.
|
||||
|
||||
@file qofbackend-p.h
|
||||
@brief private api for data storage backend
|
||||
@author Copyright (c) 2000,2001,2004 Linas Vepstas <linas@linas.org>
|
||||
@{ */
|
||||
|
||||
#ifndef QOF_BACKEND_P_H
|
||||
@@ -242,7 +244,6 @@ struct QofBackendProvider_s
|
||||
|
||||
struct QofBackend_s
|
||||
{
|
||||
|
||||
void (*session_begin) (QofBackend *be,
|
||||
QofSession *session,
|
||||
const char *book_id,
|
||||
@@ -265,14 +266,24 @@ struct QofBackend_s
|
||||
|
||||
gint64 (*counter) (QofBackend *, const char *counter_name);
|
||||
|
||||
gboolean (*events_pending) (QofBackend *be);
|
||||
gboolean (*process_events) (QofBackend *be);
|
||||
gboolean (*events_pending) (QofBackend *);
|
||||
gboolean (*process_events) (QofBackend *);
|
||||
|
||||
QofBePercentageFunc percentage;
|
||||
|
||||
/** Document Me !!! what is this supposed to do ?? */
|
||||
gboolean (*save_may_clobber_data) (QofBackend *);
|
||||
|
||||
QofBackendError last_err;
|
||||
char * error_msg;
|
||||
|
||||
/** XXX the file backend resolves the if to a fully-qualified file
|
||||
* path. This holds the filepath and communicates it to the GUI.
|
||||
* This is temprary scaffolding and should be removed. Deprecated.
|
||||
*/
|
||||
char * fullpath;
|
||||
|
||||
#ifdef GNUCASH_MAJOR_VERSION
|
||||
/** XXX price_lookup should be removed during the redesign
|
||||
* of the SQL backend... prices can now be queried using
|
||||
* the generic query mechanism.
|
||||
@@ -285,12 +296,15 @@ struct QofBackend_s
|
||||
|
||||
/** XXX Export should really _NOT_ be here, but is left here for now.
|
||||
* I'm not sure where this should be going to. It should be
|
||||
* removed ASAP.
|
||||
* removed ASAP. This is a temporary hack-around until period-closing
|
||||
* is fully implemented.
|
||||
*/
|
||||
void (*export) (QofBackend *, QofBook *);
|
||||
#endif
|
||||
|
||||
};
|
||||
|
||||
/** Let the ssytem know about a new provider of backends. This function
|
||||
/** Let the sytem know about a new provider of backends. This function
|
||||
* is typically called by the provider library at library load time.
|
||||
* This function allows the backend library to tell the QOF infrastructure
|
||||
* that it can handle URL's of a certain type. Note that a single
|
||||
@@ -299,26 +313,21 @@ struct QofBackend_s
|
||||
*/
|
||||
void qof_backend_register_provider (QofBackendProvider *);
|
||||
|
||||
/**
|
||||
* The qof_backend_set_error() routine pushes an error code onto the error
|
||||
* stack. (FIXME: the stack is 1 deep in current implementation).
|
||||
/** The qof_backend_set_error() routine pushes an error code onto the error
|
||||
* stack. (FIXME: the stack is 1 deep in current implementation).
|
||||
*/
|
||||
void qof_backend_set_error (QofBackend *be, QofBackendError err);
|
||||
|
||||
/**
|
||||
* The qof_backend_get_error() routine pops an error code off the error
|
||||
* stack.
|
||||
/** The qof_backend_get_error() routine pops an error code off the error stack.
|
||||
*/
|
||||
QofBackendError qof_backend_get_error (QofBackend *be);
|
||||
/**
|
||||
* The qof_backend_set_message() assigns a string to the backend error
|
||||
* message.
|
||||
|
||||
/** The qof_backend_set_message() assigns a string to the backend error message.
|
||||
*/
|
||||
void qof_backend_set_message(QofBackend *be, const char *format, ...);
|
||||
|
||||
/**
|
||||
* The qof_backend_get_message() pops the error message string from
|
||||
* the Backend. This string should be freed with g_free().
|
||||
/** The qof_backend_get_message() pops the error message string from
|
||||
* the Backend. This string should be freed with g_free().
|
||||
*/
|
||||
char * qof_backend_get_message(QofBackend *be);
|
||||
|
||||
|
||||
@@ -130,9 +130,12 @@ qof_backend_init(QofBackend *be)
|
||||
be->error_msg = NULL;
|
||||
be->percentage = NULL;
|
||||
|
||||
#ifdef GNUCASH_MAJOR_VERSION
|
||||
/* XXX remove these */
|
||||
be->fullpath = NULL;
|
||||
be->price_lookup = NULL;
|
||||
be->export = NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
/************************* END OF FILE ********************************/
|
||||
|
||||
@@ -53,9 +53,6 @@ struct _QofSession
|
||||
QofBackendError last_err;
|
||||
char *error_message;
|
||||
|
||||
char *fullpath;
|
||||
char *logpath;
|
||||
|
||||
/* ---------------------------------------------------- */
|
||||
/* Pointer to the backend that is actually used to move data
|
||||
* between the persistant store and the local engine. */
|
||||
|
||||
+30
-352
@@ -59,13 +59,8 @@
|
||||
#include "qofsession-p.h"
|
||||
|
||||
/* Some gnucash-specific code */
|
||||
#ifdef GNUCASH
|
||||
#ifdef GNUCASH_MAJOR_VERSION
|
||||
#include "gnc-module.h"
|
||||
#include "TransLog.h"
|
||||
#else
|
||||
#define xaccLogSetBaseName(x)
|
||||
#define xaccLogEnable()
|
||||
#define xaccLogDisable()
|
||||
#endif /* GNUCASH */
|
||||
|
||||
static QofSession * current_session = NULL;
|
||||
@@ -172,8 +167,6 @@ qof_session_init (QofSession *session)
|
||||
|
||||
session->books = g_list_append (NULL, qof_book_new ());
|
||||
session->book_id = NULL;
|
||||
session->fullpath = NULL;
|
||||
session->logpath = NULL;
|
||||
session->backend = NULL;
|
||||
|
||||
qof_session_clear_error (session);
|
||||
@@ -265,7 +258,8 @@ const char *
|
||||
qof_session_get_file_path (QofSession *session)
|
||||
{
|
||||
if (!session) return NULL;
|
||||
return session->fullpath;
|
||||
if (!session->backend) return NULL;
|
||||
return session->backend->fullpath;
|
||||
}
|
||||
|
||||
const char *
|
||||
@@ -277,7 +271,7 @@ qof_session_get_url (QofSession *session)
|
||||
|
||||
/* ====================================================================== */
|
||||
|
||||
#ifdef GNUCASH
|
||||
#ifdef GNUCASH_MAJOR_VERSION
|
||||
|
||||
static void
|
||||
qof_session_int_backend_load_error(QofSession *session,
|
||||
@@ -285,12 +279,6 @@ qof_session_int_backend_load_error(QofSession *session,
|
||||
{
|
||||
PWARN ("%s %s", message, dll_err ? dll_err : "");
|
||||
|
||||
g_free(session->fullpath);
|
||||
session->fullpath = NULL;
|
||||
|
||||
g_free(session->logpath);
|
||||
session->logpath = NULL;
|
||||
|
||||
g_free(session->book_id);
|
||||
session->book_id = NULL;
|
||||
|
||||
@@ -437,57 +425,30 @@ qof_session_begin (QofSession *session, const char * book_id,
|
||||
LEAVE("push error missing book_id");
|
||||
return;
|
||||
}
|
||||
/* Store the sessionid URL */
|
||||
|
||||
/* Store the session URL */
|
||||
session->book_id = g_strdup (book_id);
|
||||
|
||||
/* XXX we should probably move this resolve function to the
|
||||
* file backend. I think the idea would be to open the backend
|
||||
* and then ask it if it can contact it's storage media (disk,
|
||||
* network, server, etc.) and abort if it can't. Mal-formed
|
||||
* file URL's would be handled the same way!
|
||||
*/
|
||||
/* ResolveURL tries to find the file in the file system. */
|
||||
session->fullpath = xaccResolveURL(book_id);
|
||||
if (!session->fullpath)
|
||||
{
|
||||
qof_session_push_error (session, ERR_FILEIO_FILE_NOT_FOUND, NULL);
|
||||
LEAVE("push error: can't resolve file path");
|
||||
return;
|
||||
}
|
||||
PINFO ("filepath=%s", session->fullpath ? session->fullpath : "(null)");
|
||||
|
||||
session->logpath = xaccResolveFilePath(session->fullpath);
|
||||
PINFO ("logpath=%s", session->logpath ? session->logpath : "(null)");
|
||||
|
||||
/* destroy the old backend */
|
||||
qof_session_destroy_backend(session);
|
||||
|
||||
/* check to see if this is a type we know how to handle */
|
||||
if (!g_strncasecmp(book_id, "file:", 5) ||
|
||||
*session->fullpath == '/')
|
||||
/* Look for somthing of the form of "file:/", "http://" or
|
||||
* "postgres://". Everything before the colon is the access
|
||||
* method. Load the first backend found for that access method.
|
||||
*/
|
||||
char * p = strchr (book_id, ':');
|
||||
if (p)
|
||||
{
|
||||
qof_session_load_backend(session, "file" );
|
||||
char * access_method = g_strdup (book_id);
|
||||
p = strchr (access_method, ':');
|
||||
*p = 0;
|
||||
qof_session_load_backend(session, access_method);
|
||||
g_free (access_method);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Look for somthing of the form of "http://" or
|
||||
* "postgres://". Everything before the colon is the access
|
||||
* method. Load the first backend found for that access method.
|
||||
*/
|
||||
char * p = strchr (book_id, ':');
|
||||
if (p)
|
||||
{
|
||||
char * access_method = g_strdup (book_id);
|
||||
p = strchr (access_method, ':');
|
||||
*p = 0;
|
||||
qof_session_load_backend(session, access_method);
|
||||
g_free (access_method);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* If no colon found, assume it must be a file-path */
|
||||
qof_session_load_backend(session, "file");
|
||||
}
|
||||
/* If no colon found, assume it must be a file-path */
|
||||
qof_session_load_backend(session, "file");
|
||||
}
|
||||
|
||||
/* No backend was found. That's bad. */
|
||||
@@ -513,10 +474,6 @@ qof_session_begin (QofSession *session, const char * book_id,
|
||||
msg = qof_backend_get_message(session->backend);
|
||||
if (err != ERR_BACKEND_NO_ERR)
|
||||
{
|
||||
g_free(session->fullpath);
|
||||
session->fullpath = NULL;
|
||||
g_free(session->logpath);
|
||||
session->logpath = NULL;
|
||||
g_free(session->book_id);
|
||||
session->book_id = NULL;
|
||||
qof_session_push_error (session, err, msg);
|
||||
@@ -560,8 +517,6 @@ qof_session_load (QofSession *session,
|
||||
session->books = g_list_append (NULL, newbook);
|
||||
PINFO ("new book=%p", newbook);
|
||||
|
||||
xaccLogSetBaseName(session->logpath);
|
||||
|
||||
qof_session_clear_error (session);
|
||||
|
||||
/* This code should be sufficient to initialize *any* backend,
|
||||
@@ -580,7 +535,6 @@ qof_session_load (QofSession *session,
|
||||
*/
|
||||
if (be)
|
||||
{
|
||||
xaccLogDisable();
|
||||
be->percentage = percentage_func;
|
||||
|
||||
if (be->load)
|
||||
@@ -588,7 +542,6 @@ qof_session_load (QofSession *session,
|
||||
be->load (be, newbook);
|
||||
qof_session_push_error (session, qof_backend_get_error(be), NULL);
|
||||
}
|
||||
xaccLogEnable();
|
||||
}
|
||||
|
||||
err = qof_session_get_error(session);
|
||||
@@ -597,24 +550,20 @@ qof_session_load (QofSession *session,
|
||||
(err != ERR_SQL_DB_TOO_OLD))
|
||||
{
|
||||
/* Something broke, put back the old stuff */
|
||||
xaccLogDisable();
|
||||
qof_book_set_backend (newbook, NULL);
|
||||
qof_book_destroy (newbook);
|
||||
g_list_free (session->books);
|
||||
session->books = oldbooks;
|
||||
LEAVE("error from backend %d", qof_session_get_error(session));
|
||||
xaccLogEnable();
|
||||
return;
|
||||
}
|
||||
|
||||
xaccLogDisable();
|
||||
for (node=oldbooks; node; node=node->next)
|
||||
{
|
||||
QofBook *ob = node->data;
|
||||
qof_book_set_backend (ob, NULL);
|
||||
qof_book_destroy (ob);
|
||||
}
|
||||
xaccLogEnable();
|
||||
|
||||
LEAVE ("sess = %p, book_id=%s", session, qof_session_get_url(session)
|
||||
? qof_session_get_url(session) : "(null)");
|
||||
@@ -625,19 +574,11 @@ qof_session_load (QofSession *session,
|
||||
gboolean
|
||||
qof_session_save_may_clobber_data (QofSession *session)
|
||||
{
|
||||
struct stat statbuf;
|
||||
|
||||
if (!session) return FALSE;
|
||||
if (!session->fullpath) return FALSE;
|
||||
if (!session->backend) return FALSE;
|
||||
if (!session->backend->save_may_clobber_data) return FALSE;
|
||||
|
||||
/* FIXME: This should really be sent to the backend. The stat is
|
||||
* correct only for the file backend */
|
||||
|
||||
/* FIXME: Make sure this doesn't need more sophisticated semantics
|
||||
* in the face of special file, devices, pipes, symlinks, etc. */
|
||||
if (stat(session->fullpath, &statbuf) == 0) return TRUE;
|
||||
|
||||
return FALSE;
|
||||
return (*(session->backend->save_may_clobber_data)) (session->backend);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
@@ -717,7 +658,11 @@ qof_session_save (QofSession *session,
|
||||
}
|
||||
|
||||
/* ====================================================================== */
|
||||
/* XXX what does this function do ?? */
|
||||
/* XXX This exports the list of accounts to a file. It does not export
|
||||
* any transactions. Its a place-holder until full book-closing is implemented.
|
||||
*/
|
||||
|
||||
#ifdef GNUCASH_MAJOR_VERSION
|
||||
|
||||
gboolean
|
||||
qof_session_export (QofSession *tmp_session,
|
||||
@@ -752,6 +697,7 @@ qof_session_export (QofSession *tmp_session,
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
#endif /* GNUCASH_MAJOR_VERSION */
|
||||
|
||||
/* ====================================================================== */
|
||||
|
||||
@@ -771,12 +717,6 @@ qof_session_end (QofSession *session)
|
||||
|
||||
qof_session_clear_error (session);
|
||||
|
||||
g_free (session->fullpath);
|
||||
session->fullpath = NULL;
|
||||
|
||||
g_free (session->logpath);
|
||||
session->logpath = NULL;
|
||||
|
||||
g_free (session->book_id);
|
||||
session->book_id = NULL;
|
||||
|
||||
@@ -794,7 +734,6 @@ qof_session_destroy (QofSession *session)
|
||||
qof_session_get_url(session)
|
||||
? qof_session_get_url(session) : "(null)");
|
||||
|
||||
xaccLogDisable();
|
||||
qof_session_end (session);
|
||||
|
||||
/* destroy the backend */
|
||||
@@ -811,8 +750,6 @@ qof_session_destroy (QofSession *session)
|
||||
if (session == current_session)
|
||||
current_session = NULL;
|
||||
|
||||
xaccLogEnable();
|
||||
|
||||
g_free (session);
|
||||
|
||||
LEAVE ("sess=%p", session);
|
||||
@@ -874,273 +811,14 @@ qof_session_process_events (QofSession *session)
|
||||
}
|
||||
|
||||
/* ====================================================================== */
|
||||
/*
|
||||
* 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/",
|
||||
"/usr/local/share/gnucash/data/",
|
||||
"/usr/share/gnucash/accounts/",
|
||||
"/usr/local/share/gnucash/accounts/",
|
||||
NULL,
|
||||
};
|
||||
|
||||
typedef gboolean (*pathGenerator)(char *pathbuf, int which);
|
||||
|
||||
static gboolean
|
||||
xaccAddEndPath(char *pathbuf, const char *ending, int len)
|
||||
{
|
||||
if(len + strlen(pathbuf) >= PATH_MAX)
|
||||
return FALSE;
|
||||
|
||||
strcat (pathbuf, ending);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
xaccCwdPathGenerator(char *pathbuf, int which)
|
||||
{
|
||||
if(which != 0)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* try to find a file by this name in the cwd ... */
|
||||
if (getcwd (pathbuf, PATH_MAX) == NULL)
|
||||
return FALSE;
|
||||
|
||||
strcat (pathbuf, "/");
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
xaccDataPathGenerator(char *pathbuf, int which)
|
||||
{
|
||||
char *path;
|
||||
|
||||
if(which != 0)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
else
|
||||
{
|
||||
path = getenv ("HOME");
|
||||
if (!path)
|
||||
return FALSE;
|
||||
|
||||
if (PATH_MAX <= (strlen (path) + 20))
|
||||
return FALSE;
|
||||
|
||||
strcpy (pathbuf, path);
|
||||
strcat (pathbuf, "/.gnucash/data/");
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
static gboolean
|
||||
xaccUserPathPathGenerator(char *pathbuf, int which)
|
||||
{
|
||||
char *path = NULL;
|
||||
|
||||
if(searchpaths[which] == NULL)
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
else
|
||||
{
|
||||
path = searchpaths[which];
|
||||
|
||||
if (PATH_MAX <= strlen(path))
|
||||
return FALSE;
|
||||
|
||||
strcpy (pathbuf, path);
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
/* ====================================================================== */
|
||||
|
||||
char *
|
||||
xaccResolveFilePath (const char * filefrag)
|
||||
{
|
||||
struct stat statbuf;
|
||||
char pathbuf[PATH_MAX];
|
||||
pathGenerator gens[4];
|
||||
char *filefrag_dup;
|
||||
int namelen;
|
||||
int i;
|
||||
|
||||
/* seriously invalid */
|
||||
if (!filefrag)
|
||||
{
|
||||
PERR("filefrag is NULL");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ENTER ("filefrag=%s", filefrag);
|
||||
|
||||
/* ---------------------------------------------------- */
|
||||
/* OK, now we try to find or build an absolute file path */
|
||||
|
||||
/* check for an absolute file path */
|
||||
if (*filefrag == '/')
|
||||
return g_strdup (filefrag);
|
||||
|
||||
if (!g_strncasecmp(filefrag, "file:", 5))
|
||||
{
|
||||
char *ret = g_new(char, strlen(filefrag) - 5 + 1);
|
||||
strcpy(ret, filefrag + 5);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* get conservative on the length so that sprintf(getpid()) works ... */
|
||||
/* strlen ("/.LCK") + sprintf (%x%d) */
|
||||
namelen = strlen (filefrag) + 25;
|
||||
|
||||
gens[0] = xaccCwdPathGenerator;
|
||||
gens[1] = xaccDataPathGenerator;
|
||||
gens[2] = xaccUserPathPathGenerator;
|
||||
gens[3] = NULL;
|
||||
|
||||
for (i = 0; gens[i] != NULL; i++)
|
||||
{
|
||||
int j;
|
||||
for(j = 0; gens[i](pathbuf, j) ; j++)
|
||||
{
|
||||
if(xaccAddEndPath(pathbuf, filefrag, namelen))
|
||||
{
|
||||
int rc = stat (pathbuf, &statbuf);
|
||||
if ((!rc) && (S_ISREG(statbuf.st_mode)))
|
||||
{
|
||||
return (g_strdup (pathbuf));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/* OK, we didn't find the file. */
|
||||
|
||||
/* make sure that the gnucash home dir exists. */
|
||||
MakeHomeDir();
|
||||
|
||||
filefrag_dup = g_strdup (filefrag);
|
||||
|
||||
/* Replace '/' with ',' for non file backends */
|
||||
if (strstr (filefrag, "://"))
|
||||
{
|
||||
char *p;
|
||||
|
||||
p = strchr (filefrag_dup, '/');
|
||||
while (p) {
|
||||
*p = ',';
|
||||
p = strchr (filefrag_dup, '/');
|
||||
}
|
||||
}
|
||||
|
||||
/* Lets try creating a new file in $HOME/.gnucash/data */
|
||||
if (xaccDataPathGenerator(pathbuf, 0))
|
||||
{
|
||||
if(xaccAddEndPath(pathbuf, filefrag_dup, namelen))
|
||||
{
|
||||
g_free (filefrag_dup);
|
||||
return (g_strdup (pathbuf));
|
||||
}
|
||||
}
|
||||
|
||||
/* OK, we still didn't find the file */
|
||||
/* Lets try creating a new file in the cwd */
|
||||
if (xaccCwdPathGenerator(pathbuf, 0))
|
||||
{
|
||||
if(xaccAddEndPath(pathbuf, filefrag_dup, namelen))
|
||||
{
|
||||
g_free (filefrag_dup);
|
||||
return (g_strdup (pathbuf));
|
||||
}
|
||||
}
|
||||
|
||||
g_free (filefrag_dup);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* ====================================================================== */
|
||||
|
||||
char *
|
||||
xaccResolveURL (const char * pathfrag)
|
||||
{
|
||||
/* seriously invalid */
|
||||
if (!pathfrag) return NULL;
|
||||
|
||||
/* At this stage of checking, URL's are always, by definition,
|
||||
* resolved. If there's an error connecting, we'll find out later.
|
||||
*
|
||||
* FIXME -- we should probably use ghttp_uri_validate
|
||||
* to make sure the uri is in good form.
|
||||
*/
|
||||
|
||||
if (!g_strncasecmp (pathfrag, "http://", 7) ||
|
||||
!g_strncasecmp (pathfrag, "https://", 8) ||
|
||||
!g_strncasecmp (pathfrag, "postgres://", 11) ||
|
||||
!g_strncasecmp (pathfrag, "rpc://", 6))
|
||||
{
|
||||
return g_strdup(pathfrag);
|
||||
}
|
||||
|
||||
if (!g_strncasecmp (pathfrag, "file:", 5)) {
|
||||
return (xaccResolveFilePath (pathfrag));
|
||||
}
|
||||
|
||||
return (xaccResolveFilePath (pathfrag));
|
||||
}
|
||||
|
||||
/* ====================================================================== */
|
||||
#ifdef GNUCASH_MAJOR_VERSION
|
||||
|
||||
/* this should go in a separate binary to create a rpc server */
|
||||
|
||||
void
|
||||
gnc_run_rpc_server (void)
|
||||
{
|
||||
#ifdef GNUCASH
|
||||
const char * dll_err;
|
||||
void * dll_handle;
|
||||
int (*rpc_run)(short);
|
||||
@@ -1173,7 +851,7 @@ gnc_run_rpc_server (void)
|
||||
ret = (*rpc_run)(0);
|
||||
|
||||
/* XXX How do we force an exit? */
|
||||
#endif /* GNUCASH */
|
||||
}
|
||||
#endif /* GNUCASH_MAJOR_VERSION */
|
||||
|
||||
/* =================== END OF FILE ====================================== */
|
||||
|
||||
+11
-18
@@ -156,11 +156,6 @@ typedef void (*QofPercentageFunc) (const char *message, double percent);
|
||||
void qof_session_load (QofSession *session,
|
||||
QofPercentageFunc percentage_func);
|
||||
|
||||
/** XXX session_export really doesn't belong here */
|
||||
gboolean qof_session_export (QofSession *tmp_session,
|
||||
QofSession *real_session,
|
||||
QofPercentageFunc percentage_func);
|
||||
|
||||
/** @name Session Errors
|
||||
@{ */
|
||||
/** The qof_session_get_error() routine can be used to obtain the reason
|
||||
@@ -251,22 +246,20 @@ gboolean qof_session_events_pending (QofSession *session);
|
||||
gboolean qof_session_process_events (QofSession *session);
|
||||
/* @} */
|
||||
|
||||
/** 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.
|
||||
*/
|
||||
char * xaccResolveFilePath (const char * filefrag);
|
||||
char * xaccResolveURL (const char * pathfrag);
|
||||
|
||||
#ifdef GNUCASH_MAJOR_VERSION
|
||||
/** Run the RPC Server
|
||||
* @deprecated will go away */
|
||||
void gnc_run_rpc_server (void);
|
||||
|
||||
/** XXX session_export really doesn't belong here .
|
||||
* This functino exports the list of accounts to a file. Its a stop-gap
|
||||
* measure until full book-closing is implemented.
|
||||
*/
|
||||
gboolean qof_session_export (QofSession *tmp_session,
|
||||
QofSession *real_session,
|
||||
QofPercentageFunc percentage_func);
|
||||
|
||||
#endif /* GNUCASH_MJOR_VERSION */
|
||||
|
||||
#endif /* QOF_SESSION_H */
|
||||
/** @} */
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include "test-stuff.h"
|
||||
#include "gnc-engine-util.h"
|
||||
#include "gnc-filepath-utils.h"
|
||||
#include "qofsession.h"
|
||||
|
||||
struct test_strings_struct
|
||||
|
||||
Reference in New Issue
Block a user