Files
gnucash/lib/libqof/qof/qofsession.c
T
Daniel Espinosa 0b121228ca LibQof now use GType to manages objects, sustituting the actual strig type based; this means less memory and faster object type test and memory usage.
QofBackend now use GError to store backend erros, you need to call commit and begin edit with a parameter GError** to get any error in these calls, or use 
the QofBackend's API to set and delete this errors.

QofEntity and QofInstance have been merged together and now QofInstance (may in the future GncObject) manages the GUID and the book the object belongs to.
The type system use the GObject's GType system to know the object's types.

QofObject and QofClass have been modified in order to fit in the new type system, but the work pretends in the next milestones deprecate them and eventualy delete
from the GC code.

KvpValue and KvpFrame doesn't change to much, just a few to fit in the new type system; but now we can sustitud KvpValue with GValue with out afect KvpFrame,
even better, this will save too many code here and there to manage the KvpValue type system (this is diferent from the Qof object type system), and get unifed type
system using just GType. All the objects managed by KvpValue has its own GType, some derived from GObject, some from QofInstance (derived from GObject too), 
and some derived from a GBoxed (any struct type).

The file backend, has been changed in order to fit in the new type system too, but actualy it breaks the load of previews files saved in the old type system,
becouse when you save a file some data is saved with its type's name, but I'll add code to manage this converting the old type names to the new type's name. The
names used in GType is in order to avoid any problem with any previews existing one, just using a prefix Gnc or Qof in each name, this is a practice in GLib and I think we need to follow, i. e. the a type name "book" is "QofBook" and "account" is "GncAccount", this is for all the objects derived from GObject and GBoxed.


git-svn-id: svn+ssh://svn.gnucash.org/repo/gnucash/branches/gobject-engine-dev@15690 57a11ea4-9604-0410-9ed3-97b8803252fd
2007-03-08 13:23:14 +00:00

1494 lines
42 KiB
C

/********************************************************************\
* qofsesssion.c -- session access (connection to backend) *
* *
* 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 *
\********************************************************************/
/**
* @file qofsession.c
* @brief Encapsulate a connection to a storage backend.
*
* HISTORY:
* Created by Linas Vepstas December 1998
@author Copyright (c) 1998-2004 Linas Vepstas <linas@linas.org>
@author Copyright (c) 2000 Dave Peticolas
@author Copyright (c) 2005 Neil Williams <linux@codehelp.co.uk>
*/
#include "config.h"
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <glib.h>
#include "qof.h"
#include "qofbackend-p.h"
#include "qofbook-p.h"
#include "qofsession-p.h"
#include "qofobject-p.h"
#include "qofla-dir.h" /* for QOF_LIB_DIR */
/** \deprecated should not be static */
static QofSession * current_session = NULL;
static GHookList * session_closed_hooks = NULL;
static QofLogModule log_module = QOF_MOD_SESSION;
static GSList *provider_list = NULL;
static gboolean qof_providers_initialized = FALSE;
/* ====================================================================== */
void
qof_backend_register_provider (QofBackendProvider *prov)
{
provider_list = g_slist_append (provider_list, prov);
}
/* ====================================================================== */
/* hook routines */
void
qof_session_add_close_hook (GFunc fn, gpointer data)
{
GHook *hook;
if (session_closed_hooks == NULL) {
session_closed_hooks = malloc(sizeof(GHookList)); /* LEAKED */
g_hook_list_init (session_closed_hooks, sizeof(GHook));
}
hook = g_hook_alloc(session_closed_hooks);
if (!hook)
return;
hook->func = (GHookFunc)fn;
hook->data = data;
g_hook_append(session_closed_hooks, hook);
}
void
qof_session_call_close_hooks (QofSession *session)
{
GHook *hook;
GFunc fn;
if (session_closed_hooks == NULL)
return;
hook = g_hook_first_valid (session_closed_hooks, FALSE);
while (hook) {
fn = (GFunc)hook->func;
fn(session, hook->data);
hook = g_hook_next_valid (session_closed_hooks, hook, FALSE);
}
}
/* ====================================================================== */
/* error handling routines */
static void
qof_session_clear_error (QofSession *session)
{
QofBackendError err;
GError *error;
session->last_err = ERR_BACKEND_NO_ERR;
g_free(session->error_message);
session->error_message = NULL;
/* pop the stack on the backend as well. */
if (session->backend)
{
qof_backend_error_free (session->backend);
}
}
void
qof_session_push_error (QofSession *session, QofBackendError err,
const char *message)
{
if (!session) return;
g_free (session->error_message);
session->last_err = err;
session->error_message = g_strdup (message);
}
QofBackendError
qof_session_get_error (QofSession * session)
{
QofBackendError err;
if (!session) return ERR_BACKEND_NO_BACKEND;
/* if we have a local error, return that. */
if (ERR_BACKEND_NO_ERR != session->last_err)
{
return session->last_err;
}
/* maybe we should return a no-backend error ??? */
if (! session->backend) return ERR_BACKEND_NO_ERR;
err = qof_backend_get_error_code (session->backend);
session->last_err = err;
return err;
}
static const char *
get_default_error_message(QofBackendError err)
{
return "";
}
const char *
qof_session_get_error_message(QofSession *session)
{
if(!session) return "";
if(!session->error_message)
return get_default_error_message(session->last_err);
return session->error_message;
}
QofBackendError
qof_session_pop_error (QofSession * session)
{
QofBackendError err;
if (!session) return ERR_BACKEND_NO_BACKEND;
err = qof_session_get_error(session);
qof_session_clear_error(session);
return err;
}
/* ====================================================================== */
static void
qof_session_init (QofSession *session)
{
if (!session) return;
/*session->entity.e_type = QOF_ID_SESSION; FIXME: ????*/
session->books = g_list_append (NULL, qof_book_new ());
session->book_id = NULL;
session->backend = NULL;
session->lock = 1;
qof_session_clear_error (session);
}
QofSession *
qof_session_new (void)
{
QofSession *session = g_new0(QofSession, 1);
qof_session_init(session);
return session;
}
/** \deprecated Each application should keep
their \b own session context. */
QofSession *
qof_session_get_current_session (void)
{
if (!current_session)
{
qof_event_suspend ();
current_session = qof_session_new ();
qof_event_resume ();
}
return current_session;
}
/** \deprecated Each application should keep
their \b own session context. */
void
qof_session_set_current_session (QofSession *session)
{
current_session = session;
}
QofBook *
qof_session_get_book (QofSession *session)
{
GList *node;
if (!session) return NULL;
for (node=session->books; node; node=node->next)
{
QofBook *book = node->data;
if (qof_book_is_open (book)) return book;
}
return NULL;
}
void
qof_session_add_book (QofSession *session, QofBook *addbook)
{
GList *node;
if (!session) return;
ENTER (" sess=%p book=%p", session, addbook);
/* See if this book is already there ... */
for (node=session->books; node; node=node->next)
{
QofBook *book = node->data;
if (addbook == book) return;
}
if (qof_book_is_open (addbook))
{
/* hack alert -- someone should free all the books in the list,
* but it should probably not be us ... since the books backends
* should be shutdown first, etc */
/* XXX this should probably be an error XXX */
g_list_free (session->books);
session->books = g_list_append (NULL, addbook);
}
else
{
/* XXX Need to tell the backend to add a book as well */
session->books = g_list_append (session->books, addbook);
}
qof_book_set_backend (addbook, session->backend);
LEAVE (" ");
}
QofBackend *
qof_session_get_backend (QofSession *session)
{
if (!session) return NULL;
return session->backend;
}
const char *
qof_session_get_file_path (QofSession *session)
{
if (!session) return NULL;
if (!session->backend) return NULL;
return session->backend->fullpath;
}
const char *
qof_session_get_url (QofSession *session)
{
if (!session) return NULL;
return session->book_id;
}
/* =============================================================== */
typedef struct qof_entity_copy_data {
QofInstance *from;
QofInstance *to;
QofParam *param;
GList *referenceList;
GSList *param_list;
QofSession *new_session;
gboolean error;
}QofInstanceCopyData;
static void
qof_book_set_partial(QofBook *book)
{
gboolean partial;
partial =
(gboolean)GPOINTER_TO_INT(qof_book_get_data(book, PARTIAL_QOFBOOK));
if(!partial) {
qof_book_set_data(book, PARTIAL_QOFBOOK, GINT_TO_POINTER(TRUE));
}
}
void
qof_session_update_reference_list(QofSession *session, QofInstanceReference *reference)
{
QofBook *book;
GList *book_ref_list;
book = qof_session_get_book(session);
book_ref_list = (GList*)qof_book_get_data(book, ENTITYREFERENCE);
book_ref_list = g_list_append(book_ref_list, reference);
qof_book_set_data(book, ENTITYREFERENCE, book_ref_list);
qof_book_set_partial(book);
}
static void
qof_entity_param_cb(QofParam *param, gpointer data)
{
QofInstanceCopyData *qecd;
g_return_if_fail(data != NULL);
qecd = (QofInstanceCopyData*)data;
g_return_if_fail(param != NULL);
/* KVP doesn't need a set routine to be copied. */
if(param->param_type == QOF_TYPE_KVP) {
qecd->param_list = g_slist_prepend(qecd->param_list, param);
return;
}
if((param->param_getfcn != NULL)&&(param->param_setfcn != NULL)) {
qecd->param_list = g_slist_prepend(qecd->param_list, param);
}
}
static void
col_ref_cb (QofInstance* ref_ent, gpointer user_data)
{
QofInstanceReference *ref;
QofInstanceCopyData *qecd;
QofInstance *ent;
const GUID *cm_guid;
char cm_sa[GUID_ENCODING_LENGTH + 1];
gchar *cm_string;
qecd = (QofInstanceCopyData*)user_data;
ent = qecd->from;
ref = g_new0(QofInstanceReference, 1);
ref->type = G_OBJECT_TYPE (G_OBJECT (ent));
ref->ref_guid = g_new(GUID, 1);
ref->inst_guid = qof_instance_get_guid (ent);
ref->param = qof_class_get_parameter(G_OBJECT_TYPE (G_OBJECT (ent)),
qecd->param->param_name);
cm_guid = qof_instance_get_guid(ref_ent);
guid_to_string_buff(cm_guid, cm_sa);
cm_string = g_strdup(cm_sa);
if(TRUE == string_to_guid(cm_string, ref->ref_guid)) {
g_free(cm_string);
qof_session_update_reference_list(qecd->new_session, ref);
}
}
static void
qof_entity_foreach_copy(gpointer data, gpointer user_data)
{
QofInstance *importEnt, *targetEnt/*, *referenceEnt*/;
QofInstanceCopyData *context;
QofInstanceReference *reference;
gboolean registered_type;
/* cm_ prefix used for variables that hold the data to commit */
QofParam *cm_param;
gchar *cm_string, *cm_char;
const GUID *cm_guid;
KvpFrame *cm_kvp;
QofCollection *cm_col;
/* function pointers and variables for parameter getters that don't use pointers normally */
gnc_numeric cm_numeric, (*numeric_getter) (QofInstance*, QofParam*);
double cm_double, (*double_getter) (QofInstance*, QofParam*);
gboolean cm_boolean, (*boolean_getter) (QofInstance*, QofParam*);
gint32 cm_i32, (*int32_getter) (QofInstance*, QofParam*);
gint64 cm_i64, (*int64_getter) (QofInstance*, QofParam*);
Timespec cm_date, (*date_getter) (QofInstance*, QofParam*);
/* function pointers to the parameter setters */
void (*string_setter) (QofInstance*, const char*);
void (*date_setter) (QofInstance*, Timespec);
void (*numeric_setter) (QofInstance*, gnc_numeric);
void (*guid_setter) (QofInstance*, const GUID*);
void (*double_setter) (QofInstance*, double);
void (*boolean_setter) (QofInstance*, gboolean);
void (*i32_setter) (QofInstance*, gint32);
void (*i64_setter) (QofInstance*, gint64);
void (*char_setter) (QofInstance*, char*);
void (*kvp_frame_setter) (QofInstance*, KvpFrame*);
g_return_if_fail(user_data != NULL);
context = (QofInstanceCopyData*) user_data;
cm_date.tv_nsec = 0;
cm_date.tv_sec = 0;
importEnt = context->from;
targetEnt = context->to;
registered_type = FALSE;
cm_param = (QofParam*) data;
g_return_if_fail(cm_param != NULL);
context->param = cm_param;
if(cm_param->param_type == QOF_TYPE_STRING) {
cm_string = (gchar*)cm_param->param_getfcn(importEnt, cm_param);
if(cm_string) {
string_setter = (void(*)(QofInstance*, const char*))cm_param->param_setfcn;
if(string_setter != NULL) { string_setter(targetEnt, cm_string); }
}
registered_type = TRUE;
}
if(cm_param->param_type == QOF_TYPE_DATE) {
date_getter = (Timespec (*)(QofInstance*, QofParam*))cm_param->param_getfcn;
cm_date = date_getter(importEnt, cm_param);
date_setter = (void(*)(QofInstance*, Timespec))cm_param->param_setfcn;
if(date_setter != NULL) { date_setter(targetEnt, cm_date); }
registered_type = TRUE;
}
if((cm_param->param_type == QOF_TYPE_NUMERIC) ||
(cm_param->param_type == QOF_TYPE_DEBCRED)) {
numeric_getter = (gnc_numeric (*)(QofInstance*, QofParam*))cm_param->param_getfcn;
cm_numeric = numeric_getter(importEnt, cm_param);
numeric_setter = (void(*)(QofInstance*, gnc_numeric))cm_param->param_setfcn;
if(numeric_setter != NULL) { numeric_setter(targetEnt, cm_numeric); }
registered_type = TRUE;
}
if(cm_param->param_type == QOF_TYPE_GUID) {
cm_guid = (const GUID*)cm_param->param_getfcn(importEnt, cm_param);
guid_setter = (void(*)(QofInstance*, const GUID*))cm_param->param_setfcn;
if(guid_setter != NULL) { guid_setter(targetEnt, cm_guid); }
registered_type = TRUE;
}
if(cm_param->param_type == QOF_TYPE_INT32) {
int32_getter = (gint32 (*)(QofInstance*, QofParam*)) cm_param->param_getfcn;
cm_i32 = int32_getter(importEnt, cm_param);
i32_setter = (void(*)(QofInstance*, gint32))cm_param->param_setfcn;
if(i32_setter != NULL) { i32_setter(targetEnt, cm_i32); }
registered_type = TRUE;
}
if(cm_param->param_type == QOF_TYPE_INT64) {
int64_getter = (gint64 (*)(QofInstance*, QofParam*)) cm_param->param_getfcn;
cm_i64 = int64_getter(importEnt, cm_param);
i64_setter = (void(*)(QofInstance*, gint64))cm_param->param_setfcn;
if(i64_setter != NULL) { i64_setter(targetEnt, cm_i64); }
registered_type = TRUE;
}
if(cm_param->param_type == QOF_TYPE_DOUBLE) {
double_getter = (double (*)(QofInstance*, QofParam*)) cm_param->param_getfcn;
cm_double = double_getter(importEnt, cm_param);
double_setter = (void(*)(QofInstance*, double))cm_param->param_setfcn;
if(double_setter != NULL) { double_setter(targetEnt, cm_double); }
registered_type = TRUE;
}
if(cm_param->param_type == QOF_TYPE_BOOLEAN){
boolean_getter = (gboolean (*)(QofInstance*, QofParam*)) cm_param->param_getfcn;
cm_boolean = boolean_getter(importEnt, cm_param);
boolean_setter = (void(*)(QofInstance*, gboolean))cm_param->param_setfcn;
if(boolean_setter != NULL) { boolean_setter(targetEnt, cm_boolean); }
registered_type = TRUE;
}
if(cm_param->param_type == QOF_TYPE_KVP) {
cm_kvp = (KvpFrame*)cm_param->param_getfcn(importEnt,cm_param);
kvp_frame_setter = (void(*)(QofInstance*, KvpFrame*))cm_param->param_setfcn;
if(kvp_frame_setter != NULL) { kvp_frame_setter(targetEnt, cm_kvp); }
else
{
QofInstance *target_inst;
target_inst = (QofInstance*) targetEnt;
qof_instance_delete_kvp_data (target_inst);
qof_instance_set_kvp_data (target_inst, cm_kvp);
}
registered_type = TRUE;
}
if(cm_param->param_type == QOF_TYPE_CHAR) {
cm_char = (gchar*)cm_param->param_getfcn(importEnt,cm_param);
char_setter = (void(*)(QofInstance*, char*))cm_param->param_setfcn;
if(char_setter != NULL) { char_setter(targetEnt, cm_char); }
registered_type = TRUE;
}
if(cm_param->param_type == QOF_TYPE_COLLECT) {
cm_col = (QofCollection*)cm_param->param_getfcn(importEnt, cm_param);
if(cm_col)
{
/* create one reference for each member of the collection. */
qof_collection_foreach(cm_col, col_ref_cb, context);
}
registered_type = TRUE;
}
if(registered_type == FALSE) {
/* referenceEnt = (QofInstance*)cm_param->param_getfcn(importEnt, cm_param);
if(!referenceEnt) { return; }
if(!referenceEnt->e_type) { return; }*/
reference = qof_instance_get_reference_from(importEnt, cm_param);
if(reference) {
qof_session_update_reference_list(context->new_session, reference);
}
}
}
static gboolean
qof_entity_guid_match(QofSession *new_session, QofInstance *original)
{
QofInstance *copy;
const GUID *g;
QofIdTypeConst type;
QofBook *targetBook;
QofCollection *coll;
copy = NULL;
g_return_val_if_fail(original != NULL, FALSE);
targetBook = qof_session_get_book(new_session);
g_return_val_if_fail(targetBook != NULL, FALSE);
g = qof_instance_get_guid(original);
type = G_OBJECT_TYPE (G_OBJECT (original));
coll = qof_book_get_collection(targetBook, type);
copy = qof_collection_lookup_element (coll, g);
if(copy) { return TRUE; }
return FALSE;
}
static void
qof_entity_list_foreach(gpointer data, gpointer user_data)
{
QofInstanceCopyData *qecd;
QofInstance *original;
QofInstance *inst;
QofBook *book;
GUID *g;
g_return_if_fail(data != NULL);
original = (QofInstance*)data;
g_return_if_fail(user_data != NULL);
qecd = (QofInstanceCopyData*)user_data;
if(qof_entity_guid_match(qecd->new_session, original)) { return; }
qecd->from = original;
if(!qof_object_compliance(G_OBJECT_TYPE (G_OBJECT (original)), FALSE))
{
qecd->error = TRUE;
return;
}
book = qof_session_get_book(qecd->new_session);
inst = (QofInstance*)qof_object_new_instance(G_OBJECT_TYPE (G_OBJECT (original)), book);
if(!inst)
{
PERR (" failed to create new entity type=%s.", g_type_name (G_OBJECT_TYPE (G_OBJECT (original))));
qecd->error = TRUE;
return;
}
qecd->to = inst;
g = qof_instance_get_guid(original);
qof_instance_set_guid(qecd->to, g);
if(qecd->param_list != NULL) {
g_slist_free(qecd->param_list);
qecd->param_list = NULL;
}
qof_class_param_foreach(G_OBJECT_TYPE (G_OBJECT (original)), qof_entity_param_cb, qecd);
qof_begin_edit(inst);
g_slist_foreach(qecd->param_list, qof_entity_foreach_copy, qecd);
qof_commit_edit(inst);
}
static void
qof_entity_coll_foreach(QofInstance *original, gpointer user_data)
{
QofInstanceCopyData *qecd;
const GUID *g;
QofBook *targetBook;
QofCollection *coll;
QofInstance *copy;
g_return_if_fail(user_data != NULL);
copy = NULL;
qecd = (QofInstanceCopyData*)user_data;
targetBook = qof_session_get_book(qecd->new_session);
g = qof_instance_get_guid(original);
coll = qof_book_get_collection(targetBook, G_OBJECT_TYPE (G_OBJECT (original)));
copy = qof_collection_lookup_element (coll, g);
if(copy) { qecd->error = TRUE; }
}
static void
qof_entity_coll_copy(QofInstance *original, gpointer user_data)
{
QofInstanceCopyData *qecd;
QofBook *book;
QofInstance *inst;
GUID *g;
g_return_if_fail(user_data != NULL);
qecd = (QofInstanceCopyData*)user_data;
book = qof_session_get_book(qecd->new_session);
if(!qof_object_compliance(G_OBJECT_TYPE (G_OBJECT (original)), TRUE)) { return; }
inst = (QofInstance*)qof_object_new_instance(G_OBJECT_TYPE (G_OBJECT (original)), book);
qecd->to = inst;
qecd->from = original;
g = qof_instance_get_guid(original);
qof_instance_set_guid(qecd->to, g);
qof_begin_edit(inst);
g_slist_foreach(qecd->param_list, qof_entity_foreach_copy, qecd);
qof_commit_edit(inst);
}
gboolean
qof_entity_copy_to_session(QofSession* new_session, QofInstance* original)
{
QofInstanceCopyData qecd;
QofInstance *inst;
QofBook *book;
if(!new_session || !original) { return FALSE; }
if(qof_entity_guid_match(new_session, original)) { return FALSE; }
if(!qof_object_compliance(G_OBJECT_TYPE (G_OBJECT (original)), TRUE)) { return FALSE; }
qof_event_suspend();
qecd.param_list = NULL;
book = qof_session_get_book(new_session);
qecd.new_session = new_session;
qof_book_set_partial(book);
inst = (QofInstance*)qof_object_new_instance(G_OBJECT_TYPE (G_OBJECT (original)), book);
qecd.to = inst;
qecd.from = original;
qof_instance_set_guid(qecd.to, qof_instance_get_guid(original));
qof_begin_edit(inst);
qof_class_param_foreach(G_OBJECT_TYPE (G_OBJECT (original)), qof_entity_param_cb, &qecd);
qof_commit_edit(inst);
if(g_slist_length(qecd.param_list) == 0) { return FALSE; }
g_slist_foreach(qecd.param_list, qof_entity_foreach_copy, &qecd);
g_slist_free(qecd.param_list);
qof_event_resume();
return TRUE;
}
gboolean qof_entity_copy_list(QofSession *new_session, GList *entity_list)
{
QofInstanceCopyData *qecd;
if(!new_session || !entity_list) { return FALSE; }
ENTER (" list=%d", g_list_length(entity_list));
qecd = g_new0(QofInstanceCopyData, 1);
qof_event_suspend();
qecd->param_list = NULL;
qecd->new_session = new_session;
qof_book_set_partial(qof_session_get_book(new_session));
g_list_foreach(entity_list, qof_entity_list_foreach, qecd);
qof_event_resume();
if(qecd->error)
{
PWARN (" some/all entities in the list could not be copied.");
}
g_free(qecd);
LEAVE (" ");
return TRUE;
}
gboolean
qof_entity_copy_coll(QofSession *new_session, QofCollection *entity_coll)
{
QofInstanceCopyData qecd;
g_return_val_if_fail(new_session, FALSE);
if(!entity_coll) { return FALSE; }
qof_event_suspend();
qecd.param_list = NULL;
qecd.new_session = new_session;
qof_book_set_partial(qof_session_get_book(qecd.new_session));
qof_collection_foreach(entity_coll, qof_entity_coll_foreach, &qecd);
qof_class_param_foreach(qof_collection_get_g_type(entity_coll),
qof_entity_param_cb, &qecd);
qof_collection_foreach(entity_coll, qof_entity_coll_copy, &qecd);
if(qecd.param_list != NULL) { g_slist_free(qecd.param_list); }
qof_event_resume();
return TRUE;
}
struct recurse_s
{
QofSession *session;
gboolean success;
GList *ref_list;
GList *ent_list;
};
static void
recurse_collection_cb (QofInstance *ent, gpointer user_data)
{
struct recurse_s *store;
if(user_data == NULL) { return; }
store = (struct recurse_s*)user_data;
if(!ent || !store) { return; }
store->success = qof_entity_copy_to_session(store->session, ent);
if(store->success) {
store->ent_list = g_list_append(store->ent_list, ent);
}
}
static void
recurse_ent_cb(QofInstance *ent, gpointer user_data)
{
GList *ref_list, *i, *j, *ent_list, *child_list;
QofParam *ref_param;
QofInstance *ref_ent, *child_ent;
QofSession *session;
struct recurse_s *store;
gboolean success;
if(user_data == NULL) { return; }
store = (struct recurse_s*)user_data;
session = store->session;
success = store->success;
ref_list = NULL;
child_ent = NULL;
ref_list = g_list_copy(store->ref_list);
if((!session)||(!ent)) { return; }
ent_list = NULL;
child_list = NULL;
i = NULL;
j = NULL;
for(i = ref_list; i != NULL; i=i->next)
{
if(i->data == NULL) { continue; }
ref_param = (QofParam*)i->data;
if(ref_param->param_name == NULL) { continue; }
if(ref_param->param_type == QOF_TYPE_COLLECT) {
QofCollection *col;
col = ref_param->param_getfcn(ent, ref_param);
if(col) {
qof_collection_foreach(col, recurse_collection_cb, store);
}
continue;
}
ref_ent = (QofInstance*)ref_param->param_getfcn(ent, ref_param);
if((ref_ent)&&(G_OBJECT_TYPE (G_OBJECT (ref_ent))))
{
store->success = qof_entity_copy_to_session(session, ref_ent);
if(store->success) { ent_list = g_list_append(ent_list, ref_ent); }
}
}
for(i = ent_list; i != NULL; i = i->next)
{
if(i->data == NULL) { continue; }
child_ent = (QofInstance*)i->data;
if(child_ent == NULL) { continue; }
ref_list = qof_class_get_referenceList(G_OBJECT_TYPE (G_OBJECT (child_ent)));
for(j = ref_list; j != NULL; j = j->next)
{
if(j->data == NULL) { continue; }
ref_param = (QofParam*)j->data;
ref_ent = ref_param->param_getfcn(child_ent, ref_param);
if(ref_ent != NULL)
{
success = qof_entity_copy_to_session(session, ref_ent);
if(success) { child_list = g_list_append(child_list, ref_ent); }
}
}
}
for(i = child_list; i != NULL; i = i->next)
{
if(i->data == NULL) { continue; }
ref_ent = (QofInstance*)i->data;
if(ref_ent == NULL) { continue; }
ref_list = qof_class_get_referenceList(G_OBJECT_TYPE (G_OBJECT (ref_ent)));
for(j = ref_list; j != NULL; j = j->next)
{
if(j->data == NULL) { continue; }
ref_param = (QofParam*)j->data;
child_ent = ref_param->param_getfcn(ref_ent, ref_param);
if(child_ent != NULL)
{
qof_entity_copy_to_session(session, child_ent);
}
}
}
}
gboolean
qof_entity_copy_coll_r(QofSession *new_session, QofCollection *coll)
{
struct recurse_s store;
gboolean success;
if((!new_session)||(!coll)) { return FALSE; }
store.session = new_session;
success = TRUE;
store.success = success;
store.ent_list = NULL;
store.ref_list = qof_class_get_referenceList(qof_collection_get_g_type(coll));
success = qof_entity_copy_coll(new_session, coll);
if(success){ qof_collection_foreach(coll, recurse_ent_cb, &store); }
return success;
}
gboolean qof_entity_copy_one_r(QofSession *new_session, QofInstance *ent)
{
struct recurse_s store;
QofCollection *coll;
gboolean success;
if((!new_session)||(!ent)) { return FALSE; }
store.session = new_session;
success = TRUE;
store.success = success;
store.ref_list = qof_class_get_referenceList(G_OBJECT_TYPE (G_OBJECT (ent)));
success = qof_entity_copy_to_session(new_session, ent);
if(success == TRUE) {
coll = qof_book_get_collection(qof_session_get_book(new_session), G_OBJECT_TYPE (G_OBJECT (ent)));
if(coll) { qof_collection_foreach(coll, recurse_ent_cb, &store); }
}
return success;
}
/* ====================================================================== */
/** Programs that use their own backends also need to call
the default QOF ones. The backends specified here are
loaded only by applications that do not have their own. */
struct backend_providers
{
const char *libdir;
const char *filename;
};
/* All available QOF backends need to be described here
and the last entry must be two NULL's.
Remember: Use the libdir from the current build environment
and use JUST the module name without .so - .so is not portable! */
struct backend_providers backend_list[] = {
{ QOF_LIB_DIR, QSF_BACKEND_LIB },
#ifdef HAVE_DWI
{ QOF_LIB_DIR, "libqof_backend_dwi"},
#endif
{ NULL, NULL }
};
static void
qof_session_load_backend(QofSession * session, char * access_method)
{
GSList *p;
GList *node;
QofBackendProvider *prov;
QofBook *book;
char *msg;
gint num;
gboolean prov_type;
gboolean (*type_check) (const char*);
ENTER (" list=%d, initted=%s", g_slist_length(provider_list),
qof_providers_initialized ? "true" : "false");
prov_type = FALSE;
if (!qof_providers_initialized)
{
for (num = 0; backend_list[num].filename != NULL; num++) {
if(!qof_load_backend_library(backend_list[num].libdir,
backend_list[num].filename))
{
PWARN (" failed to load %s from %s",
backend_list[num].filename, backend_list[num].libdir);
}
}
qof_providers_initialized = TRUE;
}
p = provider_list;
while(p != NULL)
{
prov = p->data;
/* Does this provider handle the desired access method? */
if (0 == strcasecmp (access_method, prov->access_method))
{
/* More than one backend could provide this
access method, check file type compatibility. */
type_check = (gboolean (*)(const char*)) prov->check_data_type;
if (type_check) {
prov_type = (type_check)(session->book_id);
if (!prov_type) {
PINFO(" %s not usable", prov->provider_name);
p = p->next;
continue;
}
}
PINFO (" selected %s", prov->provider_name);
if (NULL == prov->backend_new)
{
p = p->next;
continue;
}
/* Use the providers creation callback */
session->backend = (*(prov->backend_new))();
session->backend->provider = prov;
/* Tell the books about the backend that they'll be using. */
for (node=session->books; node; node=node->next)
{
book = node->data;
qof_book_set_backend (book, session->backend);
}
LEAVE (" ");
return;
}
p = p->next;
}
msg = g_strdup_printf("failed to load '%s' using access_method", access_method);
qof_session_push_error (session, ERR_BACKEND_NO_HANDLER, msg);
LEAVE (" ");
}
/* ====================================================================== */
static void
qof_session_destroy_backend (QofSession *session)
{
g_return_if_fail (session);
if (session->backend)
{
/* clear any error message */
char * msg = qof_backend_get_message (session->backend);
g_free (msg);
/* Then destroy the backend */
if (session->backend->destroy_backend)
{
session->backend->destroy_backend(session->backend);
}
else
{
g_free(session->backend);
}
}
session->backend = NULL;
}
void
qof_session_begin (QofSession *session, const char * book_id,
gboolean ignore_lock, gboolean create_if_nonexistent)
{
char *p, *access_method, *msg;
QofBackendError err;
if (!session) return;
ENTER (" sess=%p ignore_lock=%d, book-id=%s",
session, ignore_lock,
book_id ? book_id : "(null)");
/* Clear the error condition of previous errors */
qof_session_clear_error (session);
/* Check to see if this session is already open */
if (session->book_id)
{
qof_session_push_error (session, ERR_BACKEND_LOCKED, NULL);
LEAVE("push error book is already open ");
return;
}
/* seriously invalid */
if (!book_id)
{
qof_session_push_error (session, ERR_BACKEND_BAD_URL, NULL);
LEAVE("push error missing book_id");
return;
}
/* destroy the old backend */
qof_session_destroy_backend(session);
/* Store the session URL */
session->book_id = g_strdup (book_id);
/* Look for something 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.
*/
p = strchr (book_id, ':');
if (p)
{
access_method = g_strdup (book_id);
p = strchr (access_method, ':');
*p = '\0';
qof_session_load_backend(session, access_method);
g_free (access_method);
#ifdef G_OS_WIN32
if (NULL == session->backend)
{
/* Clear the error condition of previous errors */
qof_session_clear_error (session);
/* On windows, a colon can be part of a normal filename. So if
no backend was found (which means the part before the colon
wasn't an access method), fall back to the file backend. */
qof_session_load_backend(session, "file");
}
#endif
}
else
{
/* If no colon found, assume it must be a file-path */
qof_session_load_backend(session, "file");
}
/* No backend was found. That's bad. */
if (NULL == session->backend)
{
g_free(session->book_id);
session->book_id = NULL;
qof_session_push_error (session, ERR_BACKEND_BAD_URL, NULL);
LEAVE (" BAD: no backend: sess=%p book-id=%s",
session, book_id ? book_id : "(null)");
return;
}
/* If there's a begin method, call that. */
if (session->backend->session_begin)
{
(session->backend->session_begin)(session->backend, session,
session->book_id, ignore_lock,
create_if_nonexistent);
PINFO("Done running session_begin on backend");
err = qof_backend_get_error_code (session->backend);
msg = qof_backend_get_message(session->backend);
if (err != ERR_BACKEND_NO_ERR)
{
g_free(session->book_id);
session->book_id = NULL;
qof_session_push_error (session, err, msg);
LEAVE(" backend error %d %s", err, msg ? msg : "(null)");
return;
}
if (msg != NULL)
{
PWARN("%s", msg);
g_free(msg);
}
}
LEAVE (" sess=%p book-id=%s",
session, book_id ? book_id : "(null)");
}
/* ====================================================================== */
void
qof_session_load (QofSession *session,
QofPercentageFunc percentage_func)
{
QofBook *newbook, *ob;
QofBookList *oldbooks, *node;
QofBackend *be;
QofBackendError err;
if (!session) return;
if (!session->book_id) return;
ENTER ("sess=%p book_id=%s", session, session->book_id
? session->book_id : "(null)");
/* At this point, we should are supposed to have a valid book
* id and a lock on the file. */
oldbooks = session->books;
/* XXX why are we creating a book here? I think the books
* need to be handled by the backend ... especially since
* the backend may need to load multiple books ... XXX. FIXME.
*/
newbook = qof_book_new();
session->books = g_list_append (NULL, newbook);
PINFO ("new book=%p", newbook);
qof_session_clear_error (session);
/* This code should be sufficient to initialize *any* backend,
* whether http, postgres, or anything else that might come along.
* Basically, the idea is that by now, a backend has already been
* created & set up. At this point, we only need to get the
* top-level account group out of the backend, and that is a
* generic, backend-independent operation.
*/
be = session->backend;
qof_book_set_backend(newbook, be);
/* Starting the session should result in a bunch of accounts
* and currencies being downloaded, but probably no transactions;
* The GUI will need to do a query for that.
*/
if (be)
{
be->percentage = percentage_func;
if (be->load)
{
be->load (be, newbook);
qof_session_push_error (session, qof_backend_get_error_code (be), NULL);
}
}
/* XXX if the load fails, then we try to restore the old set of books;
* however, we don't undo the session id (the URL). Thus if the
* user attempts to save after a failed load, they weill be trying to
* save to some bogus URL. This is wrong. XXX FIXME.
*/
err = qof_session_get_error(session);
if ((err != ERR_BACKEND_NO_ERR) &&
(err != ERR_FILEIO_FILE_TOO_OLD) &&
(err != ERR_FILEIO_NO_ENCODING) &&
(err != ERR_SQL_DB_TOO_OLD))
{
/* Something broke, put back the old stuff */
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));
return;
}
for (node=oldbooks; node; node=node->next)
{
ob = node->data;
qof_book_set_backend (ob, NULL);
qof_book_destroy (ob);
}
g_list_free (oldbooks);
LEAVE ("sess = %p, book_id=%s", session, session->book_id
? session->book_id : "(null)");
}
/* ====================================================================== */
gboolean
qof_session_save_may_clobber_data (QofSession *session)
{
if (!session) return FALSE;
if (!session->backend) return FALSE;
if (!session->backend->save_may_clobber_data) return FALSE;
return (*(session->backend->save_may_clobber_data)) (session->backend);
}
static gboolean
save_error_handler(QofBackend *be, QofSession *session)
{
QofBackendError err;
err = qof_backend_get_error_code (be);
if (ERR_BACKEND_NO_ERR != err)
{
qof_session_push_error (session, err, NULL);
return TRUE;
}
return FALSE;
}
void
qof_session_save (QofSession *session,
QofPercentageFunc percentage_func)
{
GList *node;
QofBackend *be;
gboolean partial, change_backend;
QofBackendProvider *prov;
GSList *p;
QofBook *book, *abook;
QofBackendError err;
gint num;
char *msg, *book_id;
if (!session) return;
if (!g_atomic_int_dec_and_test(&session->lock))
goto leave;
ENTER ("sess=%p book_id=%s",
session, session->book_id ? session->book_id : "(null)");
/* Partial book handling. */
book = qof_session_get_book(session);
partial = (gboolean)GPOINTER_TO_INT(qof_book_get_data(book, PARTIAL_QOFBOOK));
change_backend = FALSE;
msg = g_strdup_printf(" ");
book_id = g_strdup(session->book_id);
if(partial == TRUE)
{
if(session->backend && session->backend->provider) {
prov = session->backend->provider;
if(TRUE == prov->partial_book_supported)
{
/* if current backend supports partial, leave alone. */
change_backend = FALSE;
}
else { change_backend = TRUE; }
}
/* If provider is undefined, assume partial not supported. */
else { change_backend = TRUE; }
}
if(change_backend == TRUE)
{
qof_session_destroy_backend(session);
if (!qof_providers_initialized)
{
for (num = 0; backend_list[num].filename != NULL; num++) {
qof_load_backend_library(backend_list[num].libdir,
backend_list[num].filename);
}
qof_providers_initialized = TRUE;
}
p = provider_list;
while(p != NULL)
{
prov = p->data;
if(TRUE == prov->partial_book_supported)
{
/** \todo check the access_method too, not in scope here, yet. */
/* if((TRUE == prov->partial_book_supported) &&
(0 == strcasecmp (access_method, prov->access_method)))
{*/
if (NULL == prov->backend_new) continue;
/* Use the providers creation callback */
session->backend = (*(prov->backend_new))();
session->backend->provider = prov;
if (session->backend->session_begin)
{
/* Call begin - backend has been changed,
so make sure a file can be written,
use ignore_lock and create_if_nonexistent */
g_free(session->book_id);
session->book_id = NULL;
(session->backend->session_begin)(session->backend, session,
book_id, TRUE, TRUE);
PINFO("Done running session_begin on changed backend");
err = qof_backend_get_error_code (session->backend);
msg = qof_backend_get_message(session->backend);
if (err != ERR_BACKEND_NO_ERR)
{
g_free(session->book_id);
session->book_id = NULL;
qof_session_push_error (session, err, msg);
LEAVE("changed backend error %d", err);
goto leave;
}
if (msg != NULL)
{
PWARN("%s", msg);
g_free(msg);
}
}
/* Tell the books about the backend that they'll be using. */
for (node=session->books; node; node=node->next)
{
book = node->data;
qof_book_set_backend (book, session->backend);
}
p = NULL;
}
if(p) {
p = p->next;
}
}
if(!session->backend)
{
msg = g_strdup_printf("failed to load backend");
qof_session_push_error(session, ERR_BACKEND_NO_HANDLER, msg);
goto leave;
}
}
/* If there is a backend, and the backend is reachable
* (i.e. we can communicate with it), then synchronize with
* the backend. If we cannot contact the backend (e.g.
* because we've gone offline, the network has crashed, etc.)
* then give the user the option to save to the local disk.
*
* hack alert -- FIXME -- XXX the code below no longer
* does what the words above say. This needs fixing.
*/
be = session->backend;
if (be)
{
for (node = session->books; node; node=node->next)
{
abook = node->data;
/* if invoked as SaveAs(), then backend not yet set */
qof_book_set_backend (abook, be);
be->percentage = percentage_func;
if (be->sync)
{
(be->sync)(be, abook);
if (save_error_handler(be, session))
goto leave;
}
}
/* If we got to here, then the backend saved everything
* just fine, and we are done. So return. */
/* Return the book_id to previous value. */
qof_session_clear_error (session);
LEAVE("Success");
goto leave;
}
else
{
msg = g_strdup_printf("failed to load backend");
qof_session_push_error(session, ERR_BACKEND_NO_HANDLER, msg);
}
LEAVE("error -- No backend!");
leave:
g_atomic_int_inc(&session->lock);
return;
}
/* ====================================================================== */
gboolean
qof_session_save_in_progress(QofSession *session)
{
return (session && g_atomic_int_get(&session->lock) != 1);
}
void
qof_session_end (QofSession *session)
{
if (!session) return;
ENTER ("sess=%p book_id=%s", session, session->book_id
? session->book_id : "(null)");
/* close down the backend first */
if (session->backend && session->backend->session_end)
{
(session->backend->session_end)(session->backend);
}
qof_session_clear_error (session);
g_free (session->book_id);
session->book_id = NULL;
LEAVE ("sess=%p book_id=%s", session, session->book_id
? session->book_id : "(null)");
}
void
qof_session_destroy (QofSession *session)
{
GList *node;
if (!session) return;
ENTER ("sess=%p book_id=%s", session, session->book_id
? session->book_id : "(null)");
qof_session_end (session);
/* destroy the backend */
qof_session_destroy_backend(session);
for (node = session->books; node; node = node->next)
{
QofBook *book = node->data;
qof_book_set_backend (book, NULL);
qof_book_destroy (book);
}
session->books = NULL;
if (session == current_session)
current_session = NULL;
g_free (session);
LEAVE ("sess=%p", session);
}
/* ====================================================================== */
/* this call is weird. */
void
qof_session_swap_data (QofSession *session_1, QofSession *session_2)
{
GList *books_1, *books_2, *node;
if (session_1 == session_2) return;
if (!session_1 || !session_2) return;
ENTER ("sess1=%p sess2=%p", session_1, session_2);
books_1 = session_1->books;
books_2 = session_2->books;
session_1->books = books_2;
session_2->books = books_1;
for (node=books_1; node; node=node->next)
{
QofBook *book_1 = node->data;
qof_book_set_backend (book_1, session_2->backend);
}
for (node=books_2; node; node=node->next)
{
QofBook *book_2 = node->data;
qof_book_set_backend (book_2, session_1->backend);
}
LEAVE (" ");
}
/* ====================================================================== */
gboolean
qof_session_events_pending (QofSession *session)
{
if (!session) return FALSE;
if (!session->backend) return FALSE;
if (!session->backend->events_pending) return FALSE;
return session->backend->events_pending (session->backend);
}
gboolean
qof_session_process_events (QofSession *session)
{
if (!session) return FALSE;
if (!session->backend) return FALSE;
if (!session->backend->process_events) return FALSE;
return session->backend->process_events (session->backend);
}
/* XXX This exports the list of accounts to a file. It does not
* export any transactions. It's a place-holder until full
* book-closing is implemented.
*/
gboolean
qof_session_export (QofSession *tmp_session,
QofSession *real_session,
QofPercentageFunc percentage_func)
{
QofBook *book, *book2;
QofBackend *be;
if ((!tmp_session) || (!real_session)) return FALSE;
book = qof_session_get_book (real_session);
ENTER ("tmp_session=%p real_session=%p book=%p book_id=%s",
tmp_session, real_session, book,
qof_session_get_url(tmp_session)
? qof_session_get_url(tmp_session) : "(null)");
/* There must be a backend or else. (It should always be the file
* backend too.)
*/
book2 = qof_session_get_book(tmp_session);
be = qof_book_get_backend(book2);
if (!be)
return FALSE;
be->percentage = percentage_func;
if (be->export) {
QofBackendError err;
(be->export)(be, book);
err = qof_backend_get_error_code (be);
if (ERR_BACKEND_NO_ERR != err) { return FALSE; }
}
return TRUE;
}
/* =================== END OF FILE ====================================== */