mirror of
https://github.com/Gnucash/gnucash.git
synced 2026-09-03 20:53:02 -05:00
Any of the callbacks passed to this function may be NULL to decline the
use of that callback. Also, the three callbacks are now called in three
mutually exclusive cases, corresponding to:
1) a failed commit, ('on_error')
2) a successful commit where the object remains valid, ('on_done'), and
3) a successful commit where the object has been destroyed, ('on_free').
git-svn-id: svn+ssh://svn.gnucash.org/repo/gnucash/trunk@13390 57a11ea4-9604-0410-9ed3-97b8803252fd
458 lines
12 KiB
C
458 lines
12 KiB
C
/********************************************************************\
|
|
* qofbackend.c -- utility routines for dealing with the data backend *
|
|
* Copyright (C) 2000 Linas Vepstas <linas@linas.org> *
|
|
* Copyright (C) 2004-5 Neil Williams <linux@codehelp.co.uk> *
|
|
* *
|
|
* 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 *
|
|
* 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
|
|
* Boston, MA 02110-1301, USA gnu@gnu.org *
|
|
* *
|
|
\********************************************************************/
|
|
|
|
#define _GNU_SOURCE
|
|
#include "config.h"
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <stdarg.h>
|
|
#include <regex.h>
|
|
#include <glib.h>
|
|
#include <gmodule.h>
|
|
#include <dlfcn.h>
|
|
#include <errno.h>
|
|
#include "qof.h"
|
|
#include "qofbackend-p.h"
|
|
|
|
static QofLogModule log_module = QOF_MOD_BACKEND;
|
|
|
|
#define QOF_CONFIG_DESC "desc"
|
|
#define QOF_CONFIG_TIP "tip"
|
|
|
|
/* *******************************************************************\
|
|
* error handling *
|
|
\********************************************************************/
|
|
|
|
void
|
|
qof_backend_set_error (QofBackend *be, QofBackendError err)
|
|
{
|
|
if (!be) return;
|
|
|
|
/* use stack-push semantics. Only the earliest error counts */
|
|
if (ERR_BACKEND_NO_ERR != be->last_err) return;
|
|
be->last_err = err;
|
|
}
|
|
|
|
QofBackendError
|
|
qof_backend_get_error (QofBackend *be)
|
|
{
|
|
QofBackendError err;
|
|
if (!be) return ERR_BACKEND_NO_BACKEND;
|
|
|
|
/* use 'stack-pop' semantics */
|
|
err = be->last_err;
|
|
be->last_err = ERR_BACKEND_NO_ERR;
|
|
return err;
|
|
}
|
|
|
|
void
|
|
qof_backend_set_message (QofBackend *be, const char *format, ...)
|
|
{
|
|
va_list args;
|
|
char * buffer;
|
|
|
|
if (!be) return;
|
|
|
|
/* If there's already something here, free it */
|
|
if (be->error_msg) g_free(be->error_msg);
|
|
|
|
if (!format) {
|
|
be->error_msg = NULL;
|
|
return;
|
|
}
|
|
|
|
va_start(args, format);
|
|
buffer = (char *)g_strdup_vprintf(format, args);
|
|
va_end(args);
|
|
|
|
be->error_msg = buffer;
|
|
}
|
|
|
|
char *
|
|
qof_backend_get_message (QofBackend *be)
|
|
{
|
|
char * msg;
|
|
|
|
if (!be) return g_strdup("ERR_BACKEND_NO_BACKEND");
|
|
if (!be->error_msg) return NULL;
|
|
|
|
/*
|
|
* Just return the contents of the error_msg and then set it to
|
|
* NULL. This is necessary, because the Backends don't seem to
|
|
* have a destroy_backend function to take care of freeing stuff
|
|
* up. The calling function should free the copy.
|
|
* Also, this is consistent with the qof_backend_get_error() popping.
|
|
*/
|
|
|
|
msg = be->error_msg;
|
|
be->error_msg = NULL;
|
|
return msg;
|
|
}
|
|
|
|
/***********************************************************************/
|
|
/* Get a clean backend */
|
|
void
|
|
qof_backend_init(QofBackend *be)
|
|
{
|
|
be->session_begin = NULL;
|
|
be->session_end = NULL;
|
|
be->destroy_backend = NULL;
|
|
|
|
be->load = NULL;
|
|
|
|
be->begin = NULL;
|
|
be->commit = NULL;
|
|
be->rollback = NULL;
|
|
|
|
be->compile_query = NULL;
|
|
be->free_query = NULL;
|
|
be->run_query = NULL;
|
|
|
|
be->sync = NULL;
|
|
be->load_config = NULL;
|
|
|
|
be->events_pending = NULL;
|
|
be->process_events = NULL;
|
|
|
|
be->last_err = ERR_BACKEND_NO_ERR;
|
|
if (be->error_msg) g_free (be->error_msg);
|
|
be->error_msg = NULL;
|
|
be->percentage = NULL;
|
|
be->backend_configuration = kvp_frame_new();
|
|
|
|
/* to be removed */
|
|
be->price_lookup = NULL;
|
|
be->export = NULL;
|
|
}
|
|
|
|
void
|
|
qof_backend_run_begin(QofBackend *be, QofInstance *inst)
|
|
{
|
|
if(!be || !inst) { return; }
|
|
if(!be->begin) { return; }
|
|
(be->begin) (be, inst);
|
|
}
|
|
|
|
gboolean
|
|
qof_backend_begin_exists(QofBackend *be)
|
|
{
|
|
if(be->begin) { return TRUE; }
|
|
else { return FALSE; }
|
|
}
|
|
|
|
void
|
|
qof_backend_run_commit(QofBackend *be, QofInstance *inst)
|
|
{
|
|
if(!be || !inst) { return; }
|
|
if(!be->commit) { return; }
|
|
(be->commit) (be, inst);
|
|
}
|
|
|
|
/* =========== Backend Configuration ================ */
|
|
|
|
void qof_backend_prepare_frame(QofBackend *be)
|
|
{
|
|
g_return_if_fail(be);
|
|
if(!kvp_frame_is_empty(be->backend_configuration)) {
|
|
kvp_frame_delete(be->backend_configuration);
|
|
be->backend_configuration = kvp_frame_new();
|
|
}
|
|
be->config_count = 0;
|
|
}
|
|
|
|
void qof_backend_prepare_option(QofBackend *be, QofBackendOption *option)
|
|
{
|
|
KvpValue *value;
|
|
gchar *temp;
|
|
gint count;
|
|
|
|
g_return_if_fail(be || option);
|
|
count = be->config_count;
|
|
count++;
|
|
value = NULL;
|
|
switch (option->type)
|
|
{
|
|
case KVP_TYPE_GINT64 : {
|
|
value = kvp_value_new_gint64(GPOINTER_TO_INT(option->value));
|
|
break;
|
|
}
|
|
case KVP_TYPE_DOUBLE : {
|
|
value = kvp_value_new_double(*(double*)option->value);
|
|
break;
|
|
}
|
|
case KVP_TYPE_NUMERIC : {
|
|
value = kvp_value_new_numeric(*(gnc_numeric*)option->value);
|
|
break;
|
|
}
|
|
case KVP_TYPE_STRING : {
|
|
value = kvp_value_new_string((const char*)option->value);
|
|
break;
|
|
}
|
|
case KVP_TYPE_GUID : { break; } /* unsupported */
|
|
case KVP_TYPE_TIMESPEC : {
|
|
value = kvp_value_new_timespec(*(Timespec*)option->value);
|
|
break;
|
|
}
|
|
case KVP_TYPE_BINARY : { break; } /* unsupported */
|
|
case KVP_TYPE_GLIST : { break; } /* unsupported */
|
|
case KVP_TYPE_FRAME : { break; } /* unsupported */
|
|
}
|
|
if(value) {
|
|
temp = g_strdup_printf("/%s", option->option_name);
|
|
kvp_frame_set_value(be->backend_configuration, temp, value);
|
|
g_free(temp);
|
|
temp = g_strdup_printf("/%s/%s", QOF_CONFIG_DESC, option->option_name);
|
|
kvp_frame_set_string(be->backend_configuration, temp, option->description);
|
|
g_free(temp);
|
|
temp = g_strdup_printf("/%s/%s", QOF_CONFIG_TIP, option->option_name);
|
|
kvp_frame_set_string(be->backend_configuration, temp, option->tooltip);
|
|
g_free(temp);
|
|
/* only increment the counter if successful */
|
|
be->config_count = count;
|
|
}
|
|
}
|
|
|
|
KvpFrame* qof_backend_complete_frame(QofBackend *be)
|
|
{
|
|
g_return_val_if_fail(be, NULL);
|
|
be->config_count = 0;
|
|
return be->backend_configuration;
|
|
}
|
|
|
|
struct config_iterate {
|
|
QofBackendOptionCB fcn;
|
|
gpointer data;
|
|
gint count;
|
|
KvpFrame *recursive;
|
|
};
|
|
|
|
static void
|
|
config_foreach_cb (const char *key, KvpValue *value, gpointer data)
|
|
{
|
|
QofBackendOption option;
|
|
gint64 int64;
|
|
double db;
|
|
gnc_numeric num;
|
|
Timespec ts;
|
|
gchar *parent;
|
|
struct config_iterate *helper;
|
|
|
|
g_return_if_fail(key || value || data);
|
|
helper = (struct config_iterate*)data;
|
|
if(!helper->recursive) { PERR (" no parent frame"); return; }
|
|
// skip the presets.
|
|
if(0 == safe_strcmp(key, QOF_CONFIG_DESC)) { return; }
|
|
if(0 == safe_strcmp(key, QOF_CONFIG_TIP)) { return; }
|
|
ENTER (" key=%s", key);
|
|
option.option_name = key;
|
|
option.type = kvp_value_get_type(value);
|
|
if(!option.type) { return; }
|
|
switch (option.type)
|
|
{
|
|
case KVP_TYPE_GINT64 : {
|
|
int64 = kvp_value_get_gint64(value);
|
|
option.value = (gpointer)&int64;
|
|
break;
|
|
}
|
|
case KVP_TYPE_DOUBLE : {
|
|
db = kvp_value_get_double(value);
|
|
option.value = (gpointer)&db;
|
|
break;
|
|
}
|
|
case KVP_TYPE_NUMERIC : {
|
|
num = kvp_value_get_numeric(value);
|
|
option.value = (gpointer)#
|
|
break;
|
|
}
|
|
case KVP_TYPE_STRING : {
|
|
option.value = (gpointer)kvp_value_get_string(value);
|
|
break;
|
|
}
|
|
case KVP_TYPE_GUID : { break; } /* unsupported */
|
|
case KVP_TYPE_TIMESPEC : {
|
|
ts = kvp_value_get_timespec(value);
|
|
option.value = (gpointer)&ts;
|
|
break;
|
|
}
|
|
case KVP_TYPE_BINARY : { break; } /* unsupported */
|
|
case KVP_TYPE_GLIST : { break; } /* unsupported */
|
|
case KVP_TYPE_FRAME : { break; } /* unsupported */
|
|
}
|
|
parent = g_strdup_printf("/%s/%s", QOF_CONFIG_DESC, key);
|
|
option.description = kvp_frame_get_string(helper->recursive, parent);
|
|
g_free(parent);
|
|
parent = g_strdup_printf("/%s/%s", QOF_CONFIG_TIP, key);
|
|
option.tooltip = kvp_frame_get_string(helper->recursive, parent);
|
|
helper->count++;
|
|
helper->fcn (&option, helper->data);
|
|
LEAVE (" desc=%s tip=%s", option.description, option.tooltip);
|
|
}
|
|
|
|
void qof_backend_option_foreach(KvpFrame *config, QofBackendOptionCB cb, gpointer data)
|
|
{
|
|
struct config_iterate helper;
|
|
|
|
if(!config || !cb) { return; }
|
|
ENTER (" ");
|
|
helper.fcn = cb;
|
|
helper.count = 1;
|
|
helper.data = data;
|
|
helper.recursive = config;
|
|
kvp_frame_for_each_slot(config, config_foreach_cb, &helper);
|
|
LEAVE (" ");
|
|
}
|
|
|
|
void
|
|
qof_backend_load_config(QofBackend *be, KvpFrame *config)
|
|
{
|
|
if(!be || !config) { return; }
|
|
if(!be->load_config) { return; }
|
|
(be->load_config) (be, config);
|
|
}
|
|
|
|
KvpFrame*
|
|
qof_backend_get_config(QofBackend *be)
|
|
{
|
|
if(!be) { return NULL; }
|
|
if(!be->get_config) { return NULL; }
|
|
return (be->get_config) (be);
|
|
}
|
|
|
|
gboolean
|
|
qof_backend_commit_exists(QofBackend *be)
|
|
{
|
|
if(!be) { return FALSE; }
|
|
if(be->commit) { return TRUE; }
|
|
else { return FALSE; }
|
|
}
|
|
|
|
gboolean
|
|
qof_begin_edit(QofInstance *inst)
|
|
{
|
|
QofBackend * be;
|
|
|
|
if (!inst) { return FALSE; }
|
|
(inst->editlevel)++;
|
|
if (1 < inst->editlevel) { return FALSE; }
|
|
if (0 >= inst->editlevel) { inst->editlevel = 1; }
|
|
be = qof_book_get_backend (inst->book);
|
|
if (be && qof_backend_begin_exists(be)) {
|
|
qof_backend_run_begin(be, inst);
|
|
} else { inst->dirty = TRUE; }
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean qof_commit_edit(QofInstance *inst)
|
|
{
|
|
QofBackend * be;
|
|
|
|
if (!inst) { return FALSE; }
|
|
(inst->editlevel)--;
|
|
if (0 < inst->editlevel) { return FALSE; }
|
|
if ((-1 == inst->editlevel) && inst->dirty)
|
|
{
|
|
be = qof_book_get_backend ((inst)->book);
|
|
if (be && qof_backend_begin_exists(be)) {
|
|
qof_backend_run_begin(be, inst);
|
|
}
|
|
inst->editlevel = 0;
|
|
}
|
|
if (0 > inst->editlevel) { inst->editlevel = 0; }
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
gboolean
|
|
qof_commit_edit_part2(QofInstance *inst,
|
|
void (*on_error)(QofInstance *, QofBackendError),
|
|
void (*on_done)(QofInstance *),
|
|
void (*on_free)(QofInstance *))
|
|
{
|
|
QofBackend * be;
|
|
|
|
/* See if there's a backend. If there is, invoke it. */
|
|
be = qof_book_get_backend(inst->book);
|
|
if (be && qof_backend_commit_exists(be)) {
|
|
QofBackendError errcode;
|
|
|
|
/* clear errors */
|
|
do {
|
|
errcode = qof_backend_get_error(be);
|
|
} while (ERR_BACKEND_NO_ERR != errcode);
|
|
|
|
qof_backend_run_commit(be, inst);
|
|
errcode = qof_backend_get_error(be);
|
|
if (ERR_BACKEND_NO_ERR != errcode) {
|
|
/* XXX Should perform a rollback here */
|
|
inst->do_free = FALSE;
|
|
|
|
/* Push error back onto the stack */
|
|
qof_backend_set_error (be, errcode);
|
|
if (on_error)
|
|
on_error(inst, errcode);
|
|
return FALSE;
|
|
}
|
|
/* XXX the backend commit code should clear dirty!! */
|
|
inst->dirty = FALSE;
|
|
}
|
|
if (inst->do_free) {
|
|
if (on_free)
|
|
on_free(inst);
|
|
return TRUE;
|
|
}
|
|
if (on_done)
|
|
on_done(inst);
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean
|
|
qof_load_backend_library (const char *directory,
|
|
const char* filename, const char* init_fcn)
|
|
{
|
|
gchar *fullpath;
|
|
typedef void (* backend_init) (void);
|
|
GModule *backend;
|
|
backend_init gmod_init;
|
|
gpointer g;
|
|
|
|
g_return_val_if_fail(g_module_supported(), FALSE);
|
|
fullpath = g_module_build_path(directory, filename);
|
|
backend = g_module_open(fullpath, G_MODULE_BIND_LAZY);
|
|
if(!backend) {
|
|
g_message ("%s: %s\n", PACKAGE, g_module_error ());
|
|
return FALSE;
|
|
}
|
|
g = &gmod_init;
|
|
if (!g_module_symbol (backend, init_fcn, g))
|
|
{
|
|
g_message ("%s: %s\n", PACKAGE, g_module_error ());
|
|
return FALSE;
|
|
}
|
|
g_module_make_resident(backend);
|
|
gmod_init();
|
|
return TRUE;
|
|
}
|
|
|
|
/************************* END OF FILE ********************************/
|