Files
gnucash/lib/libqof/backend/file/qsf-backend.c
Derek Atkins 4e43ae10f3 Preparation for QofEntity -> QofInstance:
- Convert (QofEntity*)x to QOF_ENTITY(x)
- Move qof_entity_init() and friends into qof_instance and rename
- Move QofEntity contents into QofInstance
(note:  this doesn't compile)


git-svn-id: svn+ssh://svn.gnucash.org/repo/gnucash/gobject-engine-dev-warlord@15772 57a11ea4-9604-0410-9ed3-97b8803252fd
2007-04-01 22:18:30 +00:00

1294 lines
41 KiB
C

/***************************************************************************
* qsf-backend.c
*
* Sat Jan 1 15:07:14 2005
* Copyright 2005, 2006 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 Library General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "config.h"
#include <glib.h>
#include <glib/gstdio.h>
#include "qof.h"
#include "qofbackend-p.h"
#include "qof-backend-qsf.h"
#include <libxml/xmlmemory.h>
#include <libxml/tree.h>
#include <libxml/parser.h>
#include <libxml/xmlschemas.h>
#include "qsf-xml.h"
#include "qsf-dir.h"
#include <errno.h>
#include <sys/stat.h>
#ifndef HAVE_STRPTIME
#include "strptime.h"
#endif
#define QSF_TYPE_BINARY "binary"
#define QSF_TYPE_GLIST "glist"
#define QSF_TYPE_FRAME "frame"
static QofLogModule log_module = QOF_MOD_QSF;
static void qsf_object_commitCB(gpointer key, gpointer value, gpointer data);
struct QSFBackend_s
{
QofBackend be;
qsf_param *params;
gchar *fullpath;
};
typedef struct QSFBackend_s QSFBackend;
static void option_cb (QofBackendOption *option, gpointer data)
{
qsf_param *params;
params = (qsf_param*)data;
g_return_if_fail(params);
if(0 == safe_strcmp(QSF_COMPRESS, option->option_name)) {
params->use_gz_level = (*(gint64*)option->value);
DEBUG (" gz=%" G_GINT64_FORMAT,params->use_gz_level);
}
if (0 == safe_strcmp(QSF_MAP_FILES, option->option_name)) {
params->map_files = g_list_copy((GList*)option->value);
}
if (0 == safe_strcmp(QSF_ENCODING, option->option_name)) {
params->encoding = g_strdup(option->value);
DEBUG (" encoding=%s", params->encoding);
}
}
static void
qsf_load_config(QofBackend *be, KvpFrame *config)
{
QSFBackend *qsf_be;
qsf_param *params;
ENTER (" ");
qsf_be = (QSFBackend*)be;
g_return_if_fail(qsf_be->params);
params = qsf_be->params;
qof_backend_option_foreach(config, option_cb, params);
LEAVE (" ");
}
static KvpFrame*
qsf_get_config(QofBackend *be)
{
QofBackendOption *option;
QSFBackend *qsf_be;
qsf_param *params;
if(!be) { return NULL; }
ENTER (" ");
qsf_be = (QSFBackend*)be;
g_return_val_if_fail(qsf_be->params, NULL);
params = qsf_be->params;
qof_backend_prepare_frame(be);
option = g_new0(QofBackendOption, 1);
option->option_name = QSF_COMPRESS;
option->description = _("Level of compression to use: 0 for none, 9 for highest.");
option->tooltip = _("QOF can compress QSF XML files using gzip. "
"Note that compression is not used when outputting to STDOUT.");
option->type = KVP_TYPE_GINT64;
option->value = (gpointer)&params->use_gz_level;
qof_backend_prepare_option(be, option);
g_free(option);
option = g_new0(QofBackendOption, 1);
option->option_name = QSF_MAP_FILES;
option->description = _("List of QSF map files to use for this session.");
option->tooltip = _("QOF can convert objects within QSF XML files "
"using a map of the changes required.");
option->type = KVP_TYPE_GLIST;
option->value = (gpointer)params->map_files;
qof_backend_prepare_option(be, option);
g_free(option);
option = g_new0(QofBackendOption, 1);
option->option_name = QSF_ENCODING;
option->description = _("String encoding to use when writing the XML file.");
option->tooltip = _("QSF defaults to UTF-8. Other encodings are supported by "
"passing the string encoding in this option.");
option->type = KVP_TYPE_STRING;
option->value = (gpointer)params->encoding;
qof_backend_prepare_option(be, option);
g_free(option);
LEAVE (" ");
return qof_backend_complete_frame(be);
}
GList**
qsf_map_prepare_list(GList **maps)
{
*maps = g_list_prepend(*maps, "pilot-qsf-GnuCashInvoice.xml");
*maps = g_list_prepend(*maps, "pilot-qsf-gncCustomer.xml");
return maps;
}
static void
qsf_param_init(qsf_param *params)
{
Timespec *qsf_ts;
gchar qsf_time_string[QSF_DATE_LENGTH];
gchar qsf_enquiry_date[QSF_DATE_LENGTH];
gchar qsf_time_match[QSF_DATE_LENGTH];
gchar qsf_time_now[QSF_DATE_LENGTH];
time_t qsf_time_now_t;
gchar *qsf_time_precision;
g_return_if_fail(params != NULL);
params->count = 0;
params->use_gz_level = 0;
params->supported_types = NULL;
params->file_type = QSF_UNDEF;
params->qsf_ns = NULL;
params->output_doc = NULL;
params->output_node = NULL;
params->lister = NULL;
params->full_kvp_path = NULL;
params->map_ns = NULL;
params->map_files = NULL;
params->map_path = NULL;
params->encoding = "UTF-8";
params->qsf_object_list = NULL;
params->qsf_parameter_hash = g_hash_table_new(g_str_hash, g_str_equal);
params->qsf_default_hash = g_hash_table_new(g_str_hash, g_str_equal);
params->qsf_define_hash = g_hash_table_new(g_str_hash, g_str_equal);
params->qsf_calculate_hash = g_hash_table_new(g_str_hash, g_str_equal);
params->referenceList = NULL;
params->supported_types = g_slist_append(params->supported_types, QOF_TYPE_STRING);
params->supported_types = g_slist_append(params->supported_types, QOF_TYPE_GUID);
params->supported_types = g_slist_append(params->supported_types, QOF_TYPE_BOOLEAN);
params->supported_types = g_slist_append(params->supported_types, QOF_TYPE_NUMERIC);
params->supported_types = g_slist_append(params->supported_types, QOF_TYPE_DATE);
params->supported_types = g_slist_append(params->supported_types, QOF_TYPE_INT32);
params->supported_types = g_slist_append(params->supported_types, QOF_TYPE_INT64);
params->supported_types = g_slist_append(params->supported_types, QOF_TYPE_DOUBLE);
params->supported_types = g_slist_append(params->supported_types, QOF_TYPE_CHAR);
params->supported_types = g_slist_append(params->supported_types, QOF_TYPE_KVP);
params->supported_types = g_slist_append(params->supported_types, QOF_TYPE_COLLECT);
params->supported_types = g_slist_append(params->supported_types, QOF_TYPE_CHOICE);
qsf_time_precision = "%j";
qsf_time_now_t = time(NULL);
qsf_ts = g_new(Timespec, 1);
timespecFromTime_t(qsf_ts, qsf_time_now_t);
strftime(qsf_enquiry_date, QSF_DATE_LENGTH, QSF_XSD_TIME, gmtime(&qsf_time_now_t));
strftime(qsf_time_match, QSF_DATE_LENGTH, qsf_time_precision, gmtime(&qsf_time_now_t));
strftime(qsf_time_string, QSF_DATE_LENGTH, "%F", gmtime(&qsf_time_now_t));
strftime(qsf_time_now, QSF_DATE_LENGTH, QSF_XSD_TIME, gmtime(&qsf_time_now_t));
g_hash_table_insert(params->qsf_default_hash, "qsf_enquiry_date", qsf_enquiry_date);
g_hash_table_insert(params->qsf_default_hash, "qsf_time_now", &qsf_time_now_t);
g_hash_table_insert(params->qsf_default_hash, "qsf_time_string", qsf_time_string);
/* default map files */
params->map_files = *qsf_map_prepare_list(&params->map_files);
}
static gboolean
qsf_determine_file_type(const gchar *path)
{
struct stat sbuf;
if (!path) { return TRUE; }
if (0 == safe_strcmp(path, QOF_STDOUT)) { return TRUE; }
if (g_stat(path, &sbuf) <0) { return FALSE; }
if (sbuf.st_size == 0) { return TRUE; }
if(is_our_qsf_object(path)) { return TRUE; }
else if(is_qsf_object(path)) { return TRUE; }
else if(is_qsf_map(path)) { return TRUE; }
return FALSE;
}
/* GnuCash does LOTS of filesystem work, QSF is going to leave most of it to libxml2. :-)
Just strip the file: from the start of the book_path URL. Locks are not implemented.
*/
static void
qsf_session_begin(QofBackend *be, QofSession *session, const gchar *book_path,
gboolean ignore_lock, gboolean create_if_nonexistent)
{
QSFBackend *qsf_be;
gchar *p, *path;
PINFO (" ignore_lock=%d create_if_nonexistent=%d", ignore_lock, create_if_nonexistent);
g_return_if_fail(be != NULL);
qsf_be = (QSFBackend*)be;
g_return_if_fail(qsf_be->params != NULL);
qsf_be->fullpath = NULL;
if(book_path == NULL)
{
/* use stdout */
qof_backend_set_error(be, ERR_BACKEND_NO_ERR);
return;
}
p = strchr (book_path, ':');
if (p) {
path = g_strdup (book_path);
if (!g_ascii_strncasecmp(path, "file:", 5)) {
p = g_new(gchar, strlen(path) - 5 + 1);
strcpy(p, path + 5);
}
qsf_be->fullpath = g_strdup(p);
g_free (path);
}
else {
qsf_be->fullpath = g_strdup(book_path);
}
if(create_if_nonexistent)
{
FILE *f;
f = g_fopen(qsf_be->fullpath, "a+");
if(f) {fclose(f); }
else
{
qof_backend_set_error(be, ERR_BACKEND_READONLY);
return;
}
}
qof_backend_set_error(be, ERR_BACKEND_NO_ERR);
}
static void
qsf_free_params(qsf_param *params)
{
g_hash_table_destroy(params->qsf_calculate_hash);
g_hash_table_destroy(params->qsf_default_hash);
if(params->referenceList) {
g_list_free(params->referenceList);
}
g_slist_free(params->supported_types);
if(params->map_ns) { xmlFreeNs(params->map_ns); }
}
static void
qsf_session_end( QofBackend *be)
{
QSFBackend *qsf_be;
qsf_be = (QSFBackend*)be;
g_return_if_fail(qsf_be != NULL);
qsf_free_params(qsf_be->params);
g_free(qsf_be->fullpath);
qsf_be->fullpath = NULL;
xmlCleanupParser();
}
static void
qsf_destroy_backend (QofBackend *be)
{
g_free(be);
}
static void
ent_ref_cb (QofEntity* ent, gpointer user_data)
{
qsf_param *params;
QofEntityReference *ref;
void (*reference_setter) (QofEntity*, QofEntity*);
QofEntity *reference;
QofCollection *coll;
QofIdType type;
params = (qsf_param*)user_data;
g_return_if_fail(params);
while(params->referenceList)
{
ref = (QofEntityReference*)params->referenceList->data;
if(qof_object_is_choice(ent->e_type)) { type = ref->choice_type; }
else { type = ref->type; }
coll = qof_book_get_collection(params->book, type);
reference = qof_collection_lookup_entity(coll, ref->ref_guid);
reference_setter = (void(*)(QofEntity*, QofEntity*))ref->param->param_setfcn;
if(reference_setter != NULL)
{
qof_begin_edit((QofInstance*)ent);
qof_begin_edit((QofInstance*)reference);
reference_setter(ent, reference);
qof_commit_edit((QofInstance*)ent);
qof_commit_edit((QofInstance*)reference);
}
params->referenceList = g_list_next(params->referenceList);
}
}
static void
insert_ref_cb(QofObject *obj, gpointer user_data)
{
qsf_param *params;
params = (qsf_param*)user_data;
g_return_if_fail(params);
qof_object_foreach(obj->e_type, params->book, ent_ref_cb, params);
}
/*================================================
Load QofEntity into QofBook from XML in memory
==================================================*/
static gboolean
qsfdoc_to_qofbook(xmlDocPtr doc, qsf_param *params)
{
QofInstance *inst;
struct qsf_node_iterate iter;
QofBook *book;
GList *object_list;
xmlNodePtr qsf_root;
xmlNsPtr qsf_ns;
g_return_val_if_fail(params != NULL, FALSE);
g_return_val_if_fail(params->input_doc != NULL, FALSE);
g_return_val_if_fail(params->book != NULL, FALSE);
g_return_val_if_fail(params->file_type == OUR_QSF_OBJ, FALSE);
qsf_root = xmlDocGetRootElement(params->input_doc);
if(!qsf_root) { return FALSE; }
qsf_ns = qsf_root->ns;
iter.ns = qsf_ns;
book = params->book;
params->referenceList = (GList*)qof_book_get_data(book, ENTITYREFERENCE);
qsf_node_foreach(qsf_root, qsf_book_node_handler, &iter, params);
object_list = g_list_copy(params->qsf_object_list);
while(object_list != NULL)
{
params->object_set = object_list->data;
object_list = g_list_next(object_list);
params->qsf_parameter_hash = params->object_set->parameters;
if(!qof_class_is_registered(params->object_set->object_type)) { continue; }
inst = (QofInstance*)qof_object_new_instance(params->object_set->object_type, book);
g_return_val_if_fail(inst != NULL, FALSE);
params->qsf_ent = &inst->entity;
qof_begin_edit(inst);
g_hash_table_foreach(params->qsf_parameter_hash, qsf_object_commitCB, params);
qof_commit_edit(inst);
}
qof_object_foreach_type(insert_ref_cb, params);
qof_book_set_data(book, ENTITYREFERENCE, params->referenceList);
return TRUE;
}
/* QofBackend routine to load from file - needs a map.
*/
static gboolean
load_qsf_object(QofBook *book, const gchar *fullpath, qsf_param *params)
{
xmlNodePtr qsf_root, map_root;
xmlDocPtr mapDoc, foreign_doc;
gchar *map_path, *map_file;
map_file = params->map_path;
mapDoc = NULL;
/* use selected map */
if(!map_file) {
qof_backend_set_error(params->be, ERR_QSF_NO_MAP);
return FALSE;
}
foreign_doc = xmlParseFile(fullpath);
if (foreign_doc == NULL) {
qof_backend_set_error(params->be, ERR_FILEIO_PARSE_ERROR);
return FALSE;
}
qsf_root = NULL;
qsf_root = xmlDocGetRootElement(foreign_doc);
params->qsf_ns = qsf_root->ns;
params->book = book;
map_path = g_strdup_printf("%s/%s", QSF_SCHEMA_DIR, map_file);
if(!map_path) {
qof_backend_set_error(params->be, ERR_QSF_NO_MAP);
return FALSE;
}
mapDoc = xmlParseFile(map_path);
if(!mapDoc) {
qof_backend_set_error(params->be, ERR_QSF_NO_MAP);
return FALSE;
}
map_root = xmlDocGetRootElement(mapDoc);
params->map_ns = map_root->ns;
params->input_doc = qsf_object_convert(mapDoc, qsf_root, params);
qsfdoc_to_qofbook(params->input_doc, params);
return TRUE;
}
static gboolean
load_our_qsf_object(QofBook *book, const gchar *fullpath, qsf_param *params)
{
xmlNodePtr qsf_root;
params->input_doc = xmlParseFile(fullpath);
if (params->input_doc == NULL) {
qof_backend_set_error(params->be, ERR_FILEIO_PARSE_ERROR);
return FALSE;
}
qsf_root = NULL;
qsf_root = xmlDocGetRootElement(params->input_doc);
params->qsf_ns = qsf_root->ns;
return qsfdoc_to_qofbook(params->input_doc, params);
}
/* Determine the type of QSF and load it into the QofBook
- is_our_qsf_object, OUR_QSF_OBJ, QSF object file using only QOF objects known
to the calling process. No map is required.
- is_qsf_object, IS_QSF_OBJ, QSF object file that may or may not have a QSF map
to convert external objects. This temporary type will be set to HAVE_QSF_MAP
if a suitable map exists, or an error value returned: ERR_QSF_NO_MAP,
ERR_QSF_BAD_MAP or ERR_QSF_WRONG_MAP. This allows the calling process to inform
the user that the QSF itself is valid but a suitable map cannot be found.
- is_qsf_map, IS_QSF_MAP, QSF map file. In the backend, this generates
ERR_QSF_MAP_NOT_OBJ but it can be used internally when processing maps to
match a QSF object.
returns NULL on error, otherwise a pointer to the QofBook. Use
the qof_book_merge API to merge the new data into the current
QofBook.
*/
static void
qsf_file_type(QofBackend *be, QofBook *book)
{
QSFBackend *qsf_be;
qsf_param *params;
FILE *f;
gchar *path;
gboolean result;
g_return_if_fail(be != NULL);
g_return_if_fail(book != NULL);
qsf_be = (QSFBackend*) be;
g_return_if_fail(qsf_be != NULL);
g_return_if_fail(qsf_be->fullpath != NULL);
g_return_if_fail(qsf_be->params != NULL);
params = qsf_be->params;
params->book = book;
path = g_strdup(qsf_be->fullpath);
f = g_fopen(path, "r");
if(!f) { qof_backend_set_error(be, ERR_FILEIO_READ_ERROR); }
fclose(f);
params->filepath = g_strdup(path);
qof_backend_get_error(be);
result = is_our_qsf_object_be(params);
if(result) {
params->file_type = OUR_QSF_OBJ;
result = load_our_qsf_object(book, path, params);
if(!result) { qof_backend_set_error(be, ERR_FILEIO_PARSE_ERROR); }
return;
}
else if(is_qsf_object_be(params)) {
params->file_type = IS_QSF_OBJ;
result = load_qsf_object(book, path, params);
if(!result) { qof_backend_set_error(be, ERR_FILEIO_PARSE_ERROR); }
return;
}
if(result == FALSE) {
if(is_qsf_map_be(params)) {
params->file_type = IS_QSF_MAP;
qof_backend_set_error(be, ERR_QSF_MAP_NOT_OBJ);
}
}
}
static void
qsf_object_sequence(QofParam *qof_param, gpointer data)
{
qsf_param *params;
GSList *checklist, *result;
g_return_if_fail(data != NULL);
params = (qsf_param*) data;
result = NULL;
checklist = NULL;
params->knowntype = FALSE;
checklist = g_slist_copy(params->supported_types);
for(result = checklist; result != NULL; result = result->next)
{
if(0 == safe_strcmp((QofIdType)result->data, qof_param->param_type))
{
params->knowntype = TRUE;
}
}
g_slist_free(checklist);
if(0 == safe_strcmp(qof_param->param_type, params->qof_type))
{
params->qsf_sequence = g_slist_append(params->qsf_sequence, qof_param);
params->knowntype = TRUE;
}
/* handle params->qof_type = QOF_TYPE_GUID and qof_param->param_type != known type */
if(0 == safe_strcmp(params->qof_type, QOF_TYPE_GUID)
&& (params->knowntype == FALSE))
{
params->qsf_sequence = g_slist_append(params->qsf_sequence, qof_param);
params->knowntype = TRUE;
}
}
/* receives each entry from supported_types in sequence
type = qof data type from supported list
user_data = params. Holds object type
*/
static void
qsf_supported_parameters(gpointer type, gpointer user_data)
{
qsf_param *params;
g_return_if_fail(user_data != NULL);
params = (qsf_param*) user_data;
params->qof_type = (QofIdType)type;
params->knowntype = FALSE;
qof_class_param_foreach(params->qof_obj_type, qsf_object_sequence, params);
}
static KvpValueType
qsf_to_kvp_helper(const char *type_string)
{
if(0 == safe_strcmp(QOF_TYPE_INT64, type_string)) { return KVP_TYPE_GINT64; }
if(0 == safe_strcmp(QOF_TYPE_DOUBLE, type_string)) { return KVP_TYPE_DOUBLE; }
if(0 == safe_strcmp(QOF_TYPE_NUMERIC, type_string)) { return KVP_TYPE_NUMERIC; }
if(0 == safe_strcmp(QOF_TYPE_STRING, type_string)) { return KVP_TYPE_STRING; }
if(0 == safe_strcmp(QOF_TYPE_GUID, type_string)) { return KVP_TYPE_GUID; }
if(0 == safe_strcmp(QOF_TYPE_DATE, type_string)) { return KVP_TYPE_TIMESPEC; }
if(0 == safe_strcmp(QSF_TYPE_BINARY, type_string)) { return KVP_TYPE_BINARY; }
if(0 == safe_strcmp(QSF_TYPE_GLIST, type_string)) { return KVP_TYPE_GLIST; }
if(0 == safe_strcmp(QSF_TYPE_FRAME, type_string)) { return KVP_TYPE_FRAME; }
return 0;
}
static QofIdTypeConst
kvp_value_to_qof_type_helper(KvpValueType n)
{
switch(n)
{
case KVP_TYPE_GINT64 : { return QOF_TYPE_INT64; break; }
case KVP_TYPE_DOUBLE : { return QOF_TYPE_DOUBLE; break; }
case KVP_TYPE_NUMERIC : { return QOF_TYPE_NUMERIC; break; }
case KVP_TYPE_STRING : { return QOF_TYPE_STRING; break; }
case KVP_TYPE_GUID : { return QOF_TYPE_GUID; break; }
case KVP_TYPE_TIMESPEC : { return QOF_TYPE_DATE; break; }
case KVP_TYPE_BINARY : { return QSF_TYPE_BINARY; break; }
case KVP_TYPE_GLIST : { return QSF_TYPE_GLIST; break; }
case KVP_TYPE_FRAME : { return QSF_TYPE_FRAME; break; }
default : { return NULL; }
}
}
static void
qsf_from_kvp_helper(const gchar *path, KvpValue *content, gpointer data)
{
qsf_param *params;
QofParam *qof_param;
xmlNodePtr node;
KvpValueType n;
gchar *full_path;
params = (qsf_param*)data;
qof_param = params->qof_param;
full_path = NULL;
g_return_if_fail(params && path && content);
ENTER (" ");
n = kvp_value_get_type(content);
switch(n)
{
case KVP_TYPE_GINT64 :
case KVP_TYPE_DOUBLE :
case KVP_TYPE_NUMERIC :
case KVP_TYPE_STRING :
case KVP_TYPE_GUID :
case KVP_TYPE_TIMESPEC :
case KVP_TYPE_BINARY :
case KVP_TYPE_GLIST :
{
node = xmlAddChild(params->output_node, xmlNewNode(params->qsf_ns,
BAD_CAST qof_param->param_type));
xmlNodeAddContent(node, BAD_CAST kvp_value_to_bare_string(content));
xmlNewProp(node, BAD_CAST QSF_OBJECT_TYPE, BAD_CAST qof_param->param_name);
full_path = g_strconcat(params->full_kvp_path, "/", path, NULL);
xmlNewProp(node, BAD_CAST QSF_OBJECT_KVP, BAD_CAST full_path);
xmlNewProp(node, BAD_CAST QSF_OBJECT_VALUE,
BAD_CAST kvp_value_to_qof_type_helper(n));
PINFO (" set %s", kvp_value_to_qof_type_helper(n));
break;
}
case KVP_TYPE_FRAME:
{
if(!params->full_kvp_path) { params->full_kvp_path = g_strdup(path); }
else {
params->full_kvp_path = g_strconcat(params->full_kvp_path,
"/", path, NULL);
}
PINFO (" full=%s, path=%s ", params->full_kvp_path, path);
kvp_frame_for_each_slot(kvp_value_get_frame(content),
qsf_from_kvp_helper, params);
g_free(params->full_kvp_path);
params->full_kvp_path = NULL;
break;
}
default:
{
PERR (" unsupported value = %d", kvp_value_get_type(content));
break;
}
}
LEAVE (" ");
}
static void
qsf_from_coll_cb (QofEntity *ent, gpointer user_data)
{
qsf_param *params;
QofParam *qof_param;
xmlNodePtr node;
gchar qsf_guid[GUID_ENCODING_LENGTH + 1];
params = (qsf_param*)user_data;
if(!ent || !params) { return; }
qof_param = params->qof_param;
guid_to_string_buff(qof_entity_get_guid(ent), qsf_guid);
node = xmlAddChild(params->output_node, xmlNewNode(params->qsf_ns,
BAD_CAST qof_param->param_type));
xmlNodeAddContent(node, BAD_CAST qsf_guid);
xmlNewProp(node, BAD_CAST QSF_OBJECT_TYPE, BAD_CAST qof_param->param_name);
}
/******* reference handling ***********/
static gint
qof_reference_list_cb(gconstpointer a, gconstpointer b)
{
const QofEntityReference *aa;
const QofEntityReference *bb;
aa = (QofEntityReference*) a;
bb = (QofEntityReference*) b;
if(aa == NULL) { return 1; }
g_return_val_if_fail((bb != NULL), 1);
g_return_val_if_fail((aa->type != NULL), 1);
if((0 == guid_compare(bb->ent_guid, aa->ent_guid))
&&(0 == safe_strcmp(bb->type, aa->type))
&&(0 == safe_strcmp(bb->param->param_name, aa->param->param_name)))
{
return 0;
}
return 1;
}
static QofEntityReference*
qof_reference_lookup(GList *referenceList, QofEntityReference *find)
{
GList *single_ref;
QofEntityReference *ent_ref;
if(referenceList == NULL) { return NULL; }
g_return_val_if_fail(find != NULL, NULL);
single_ref = NULL;
ent_ref = NULL;
single_ref = g_list_find_custom(referenceList, find, qof_reference_list_cb);
if(single_ref == NULL) { return ent_ref; }
ent_ref = (QofEntityReference*)single_ref->data;
g_list_free(single_ref);
return ent_ref;
}
static void
reference_list_lookup(gpointer data, gpointer user_data)
{
QofEntity *ent;
QofParam *ref_param;
QofEntityReference *reference, *starter;
qsf_param *params;
const GUID *guid;
xmlNodePtr node, object_node;
xmlNsPtr ns;
GList *copy_list;
gchar qsf_guid[GUID_ENCODING_LENGTH + 1], *ref_name;
params = (qsf_param*)user_data;
ref_param = (QofParam*)data;
object_node = params->output_node;
ent = params->qsf_ent;
ns = params->qsf_ns;
starter = g_new(QofEntityReference, 1);
starter->ent_guid = qof_entity_get_guid(ent);
starter->type = g_strdup(ent->e_type);
starter->param = ref_param;
starter->ref_guid = NULL;
copy_list = g_list_copy(params->referenceList);
reference = qof_reference_lookup(copy_list, starter);
g_free(starter);
if(reference != NULL) {
if((ref_param->param_getfcn == NULL)||(ref_param->param_setfcn == NULL))
{
return;
}
ref_name = g_strdup(reference->param->param_name);
node = xmlAddChild(object_node, xmlNewNode(ns, BAD_CAST QOF_TYPE_GUID));
guid_to_string_buff(reference->ref_guid, qsf_guid);
xmlNodeAddContent(node, BAD_CAST qsf_guid);
xmlNewProp(node, BAD_CAST QSF_OBJECT_TYPE, BAD_CAST ref_name);
g_free(ref_name);
}
else {
ent = QOF_ENTITY(ref_param->param_getfcn(ent, ref_param));
if(!ent) { return; }
if((0 == safe_strcmp(ref_param->param_type, QOF_TYPE_COLLECT)) ||
(0 == safe_strcmp(ref_param->param_type, QOF_TYPE_CHOICE)))
{ return; }
node = xmlAddChild(object_node, xmlNewNode(ns, BAD_CAST QOF_TYPE_GUID));
guid = qof_entity_get_guid(ent);
guid_to_string_buff(guid, qsf_guid);
xmlNodeAddContent(node, BAD_CAST qsf_guid);
xmlNewProp(node, BAD_CAST QSF_OBJECT_TYPE, BAD_CAST ref_param->param_name);
}
}
/*=====================================
Convert QofEntity to QSF XML node
qof_param holds the parameter sequence.
=======================================*/
static void
qsf_entity_foreach(QofEntity *ent, gpointer data)
{
qsf_param *params;
GSList *param_list, *supported;
GList *ref;
xmlNodePtr node, object_node;
xmlNsPtr ns;
gchar *string_buffer;
QofParam *qof_param;
QofEntity *choice_ent;
KvpFrame *qsf_kvp;
QofCollection *qsf_coll;
gint param_count;
gboolean own_guid;
const GUID *cm_guid;
gchar cm_sa[GUID_ENCODING_LENGTH + 1];
g_return_if_fail(data != NULL);
params = (qsf_param*)data;
param_count = ++params->count;
ns = params->qsf_ns;
qsf_kvp = kvp_frame_new();
own_guid = FALSE;
choice_ent = NULL;
object_node = xmlNewChild(params->book_node, params->qsf_ns,
BAD_CAST QSF_OBJECT_TAG, NULL);
xmlNewProp(object_node, BAD_CAST QSF_OBJECT_TYPE, BAD_CAST ent->e_type);
string_buffer = g_strdup_printf("%i", param_count);
xmlNewProp(object_node, BAD_CAST QSF_OBJECT_COUNT, BAD_CAST string_buffer);
g_free(string_buffer);
param_list = g_slist_copy(params->qsf_sequence);
while(param_list != NULL) {
qof_param = (QofParam*)param_list->data;
g_return_if_fail(qof_param != NULL);
if(0 == safe_strcmp(qof_param->param_type, QOF_TYPE_GUID))
{
if(!own_guid)
{
cm_guid = qof_entity_get_guid(ent);
node = xmlAddChild(object_node, xmlNewNode(ns, BAD_CAST QOF_TYPE_GUID));
guid_to_string_buff(cm_guid, cm_sa);
string_buffer = g_strdup(cm_sa);
xmlNodeAddContent(node, BAD_CAST string_buffer);
xmlNewProp(node, BAD_CAST QSF_OBJECT_TYPE , BAD_CAST QOF_PARAM_GUID);
g_free(string_buffer);
own_guid = TRUE;
}
params->qsf_ent = ent;
params->output_node = object_node;
ref = qof_class_get_referenceList(ent->e_type);
if(ref != NULL) {
g_list_foreach(ref, reference_list_lookup, params);
}
}
if(0 == safe_strcmp(qof_param->param_type, QOF_TYPE_COLLECT))
{
qsf_coll = qof_param->param_getfcn(ent, qof_param);
if(qsf_coll) {
params->qof_param = qof_param;
params->output_node = object_node;
if(qof_collection_count(qsf_coll) > 0) {
qof_collection_foreach(qsf_coll, qsf_from_coll_cb, params);
}
}
param_list = g_slist_next(param_list);
continue;
}
if(0 == safe_strcmp(qof_param->param_type, QOF_TYPE_CHOICE))
{
/** \todo use the reference list here. */
choice_ent = QOF_ENTITY(qof_param->param_getfcn(ent, qof_param));
if(!choice_ent) {
param_list = g_slist_next(param_list);
continue;
}
node = xmlAddChild(object_node, xmlNewNode(ns, BAD_CAST qof_param->param_type));
cm_guid = qof_entity_get_guid(choice_ent);
guid_to_string_buff(cm_guid, cm_sa);
string_buffer = g_strdup(cm_sa);
xmlNodeAddContent(node, BAD_CAST string_buffer);
xmlNewProp(node, BAD_CAST QSF_OBJECT_TYPE, BAD_CAST qof_param->param_name);
xmlNewProp(node, BAD_CAST "name", BAD_CAST choice_ent->e_type);
g_free(string_buffer);
param_list = g_slist_next(param_list);
continue;
}
if(0 == safe_strcmp(qof_param->param_type, QOF_TYPE_KVP))
{
qsf_kvp = (KvpFrame*)qof_param->param_getfcn(ent,qof_param);
if(kvp_frame_is_empty(qsf_kvp)) { LEAVE(" "); return; }
params->qof_param = qof_param;
params->output_node = object_node;
kvp_frame_for_each_slot(qsf_kvp, qsf_from_kvp_helper, params);
}
if((qof_param->param_setfcn != NULL) && (qof_param->param_getfcn != NULL))
{
for( supported = g_slist_copy(params->supported_types);
supported != NULL; supported = g_slist_next(supported))
{
if(0 == safe_strcmp((const gchar*)supported->data, (const gchar*)qof_param->param_type))
{
node = xmlAddChild(object_node, xmlNewNode(ns, BAD_CAST qof_param->param_type));
string_buffer = g_strdup(qof_book_merge_param_as_string(qof_param, ent));
xmlNodeAddContent(node, BAD_CAST string_buffer);
xmlNewProp(node, BAD_CAST QSF_OBJECT_TYPE, BAD_CAST qof_param->param_name);
g_free(string_buffer);
}
}
}
param_list = g_slist_next(param_list);
}
}
static void
qsf_foreach_obj_type(QofObject *qsf_obj, gpointer data)
{
qsf_param *params;
QofBook *book;
GSList *support;
g_return_if_fail(data != NULL);
params = (qsf_param*) data;
/* Skip unsupported objects */
if((qsf_obj->create == NULL)||(qsf_obj->foreach == NULL)){
PINFO (" qsf_obj QOF support failed %s", qsf_obj->e_type);
return;
}
params->qof_obj_type = qsf_obj->e_type;
params->qsf_sequence = NULL;
book = params->book;
support = g_slist_copy(params->supported_types);
g_slist_foreach(support,qsf_supported_parameters, params);
qof_object_foreach(qsf_obj->e_type, book, qsf_entity_foreach, params);
}
/*=====================================================
Take a QofBook and prepare a QSF XML doc in memory
=======================================================*/
/* QSF only uses one QofBook per file - count may be removed later. */
static xmlDocPtr
qofbook_to_qsf(QofBook *book, qsf_param *params)
{
xmlNodePtr top_node, node;
xmlDocPtr doc;
gchar buffer[GUID_ENCODING_LENGTH + 1];
const GUID *book_guid;
g_return_val_if_fail(book != NULL, NULL);
params->book = book;
params->referenceList =
g_list_copy((GList*)qof_book_get_data(book, ENTITYREFERENCE));
doc = xmlNewDoc(BAD_CAST QSF_XML_VERSION);
top_node = xmlNewNode(NULL, BAD_CAST QSF_ROOT_TAG);
xmlDocSetRootElement(doc, top_node);
xmlSetNs(top_node, xmlNewNs(top_node, BAD_CAST QSF_DEFAULT_NS, NULL));
params->qsf_ns = top_node->ns;
node = xmlNewChild(top_node, params->qsf_ns, BAD_CAST QSF_BOOK_TAG, NULL);
params->book_node = node;
xmlNewProp(node, BAD_CAST QSF_BOOK_COUNT, BAD_CAST "1");
book_guid = qof_book_get_guid(book);
guid_to_string_buff(book_guid, buffer);
xmlNewChild(params->book_node, params->qsf_ns,
BAD_CAST QSF_BOOK_GUID, BAD_CAST buffer);
params->output_doc = doc;
params->book_node = node;
qof_object_foreach_type(qsf_foreach_obj_type, params);
return params->output_doc;
}
static void
write_qsf_from_book(const char *path, QofBook *book, qsf_param *params)
{
xmlDocPtr qsf_doc;
gint write_result;
QofBackend *be;
be = qof_book_get_backend(book);
qsf_doc = qofbook_to_qsf(book, params);
write_result = 0;
DEBUG (" use_gz_level=%" G_GINT64_FORMAT " encoding=%s",
params->use_gz_level, params->encoding);
if((params->use_gz_level > 0) && (params->use_gz_level <= 9))
{
xmlSetDocCompressMode(qsf_doc, params->use_gz_level);
}
g_return_if_fail(qsf_is_valid(QSF_SCHEMA_DIR, QSF_OBJECT_SCHEMA, qsf_doc) == TRUE);
write_result = xmlSaveFormatFileEnc(path, qsf_doc, params->encoding, 1);
if(write_result < 0)
{
qof_backend_set_error(be, ERR_FILEIO_WRITE_ERROR);
return;
}
xmlFreeDoc(qsf_doc);
}
static void
write_qsf_to_stdout(QofBook *book, qsf_param *params)
{
xmlDocPtr qsf_doc;
qsf_doc = qofbook_to_qsf(book, params);
g_return_if_fail(qsf_is_valid(QSF_SCHEMA_DIR, QSF_OBJECT_SCHEMA, qsf_doc) == TRUE);
DEBUG (" use_gz_level=%" G_GINT64_FORMAT " encoding=%s",
params->use_gz_level, params->encoding);
xmlSaveFormatFileEnc("-", qsf_doc, params->encoding, 1);
fprintf(stdout, "\n");
xmlFreeDoc(qsf_doc);
}
static void
qsf_write_file(QofBackend *be, QofBook *book)
{
QSFBackend *qsf_be;
qsf_param *params;
char *path;
qsf_be = (QSFBackend*)be;
params = qsf_be->params;
/* if fullpath is blank, book_id was set to QOF_STDOUT */
if (!qsf_be->fullpath || (*qsf_be->fullpath == '\0')) {
write_qsf_to_stdout(book, params);
return;
}
path = strdup(qsf_be->fullpath);
write_qsf_from_book(path, book, params);
g_free(path);
}
KvpValue*
string_to_kvp_value(const gchar *content, KvpValueType type)
{
gchar *tail;
gint64 cm_i64;
double cm_double;
gnc_numeric cm_numeric;
GUID *cm_guid;
struct tm kvp_time;
time_t kvp_time_t;
Timespec cm_date;
switch(type) {
case KVP_TYPE_GINT64:
errno = 0;
cm_i64 = strtoll(content, &tail, 0);
if(errno == 0) {
return kvp_value_new_gint64(cm_i64);
}
break;
case KVP_TYPE_DOUBLE:
errno = 0;
cm_double = strtod(content, &tail);
if(errno == 0) {
return kvp_value_new_double(cm_double);
}
break;
case KVP_TYPE_NUMERIC:
string_to_gnc_numeric(content, &cm_numeric);
return kvp_value_new_gnc_numeric(cm_numeric);
break;
case KVP_TYPE_STRING:
return kvp_value_new_string(content);
break;
case KVP_TYPE_GUID:
cm_guid = g_new(GUID, 1);
if(TRUE == string_to_guid(content, cm_guid))
{
return kvp_value_new_guid(cm_guid);
}
break;
case KVP_TYPE_TIMESPEC:
strptime(content, QSF_XSD_TIME, &kvp_time);
kvp_time_t = mktime(&kvp_time);
timespecFromTime_t(&cm_date, kvp_time_t);
return kvp_value_new_timespec(cm_date);
break;
case KVP_TYPE_BINARY:
// return kvp_value_new_binary(value->value.binary.data,
// value->value.binary.datasize);
break;
case KVP_TYPE_GLIST:
// return kvp_value_new_glist(value->value.list);
break;
case KVP_TYPE_FRAME:
// return kvp_value_new_frame(value->value.frame);
break;
}
return NULL;
}
/* ======================================================
Commit XML data from file to QofEntity in a QofBook
========================================================= */
void
qsf_object_commitCB(gpointer key, gpointer value, gpointer data)
{
qsf_param *params;
qsf_objects *object_set;
xmlNodePtr node;
QofEntityReference *reference;
QofEntity *qsf_ent;
QofBook *targetBook;
const char *qof_type, *parameter_name, *timechk;
QofIdType obj_type, reference_type;
struct tm qsf_time;
time_t qsf_time_t;
gchar *tail;
/* cm_ prefix used for variables that hold the data to commit */
gnc_numeric cm_numeric;
double cm_double;
gboolean cm_boolean;
gint32 cm_i32;
gint64 cm_i64;
Timespec cm_date;
gchar cm_char, (*char_getter) (xmlNodePtr);
GUID *cm_guid;
KvpFrame *cm_kvp;
KvpValue *cm_value;
KvpValueType cm_type;
QofSetterFunc cm_setter;
const QofParam *cm_param;
void (*string_setter) (QofEntity*, const gchar*);
void (*date_setter) (QofEntity*, Timespec);
void (*numeric_setter) (QofEntity*, gnc_numeric);
void (*double_setter) (QofEntity*, double);
void (*boolean_setter) (QofEntity*, gboolean);
void (*i32_setter) (QofEntity*, gint32);
void (*i64_setter) (QofEntity*, gint64);
void (*char_setter) (QofEntity*, gchar);
g_return_if_fail(data && value && key);
params = (qsf_param*)data;
node = (xmlNodePtr)value;
parameter_name = (const gchar*)key;
qof_type = (gchar*)node->name;
qsf_ent = params->qsf_ent;
targetBook = params->book;
memset (&qsf_time, '\0', sizeof(qsf_time));
cm_date.tv_nsec = 0;
cm_date.tv_sec = 0;
obj_type = (gchar*)xmlGetProp(node->parent, BAD_CAST QSF_OBJECT_TYPE);
if(0 == safe_strcasecmp(obj_type, parameter_name)) { return; }
cm_setter = qof_class_get_parameter_setter(obj_type, parameter_name);
cm_param = qof_class_get_parameter(obj_type, parameter_name);
object_set = params->object_set;
if(safe_strcmp(qof_type, QOF_TYPE_STRING) == 0) {
string_setter = (void(*)(QofEntity*, const gchar*))cm_setter;
if(string_setter != NULL) { string_setter(qsf_ent, (gchar*)xmlNodeGetContent(node)); }
}
if(safe_strcmp(qof_type, QOF_TYPE_DATE) == 0) {
date_setter = (void(*)(QofEntity*, Timespec))cm_setter;
timechk = NULL;
timechk = strptime((char*)xmlNodeGetContent(node), QSF_XSD_TIME, &qsf_time);
g_return_if_fail(timechk != NULL);
qsf_time_t = mktime(&qsf_time);
if(qsf_time_t != -3600)
{
timespecFromTime_t(&cm_date, qsf_time_t);
if(date_setter != NULL) { date_setter(qsf_ent, cm_date); }
}
}
if((safe_strcmp(qof_type, QOF_TYPE_NUMERIC) == 0) ||
(safe_strcmp(qof_type, QOF_TYPE_DEBCRED) == 0)) {
numeric_setter = (void(*)(QofEntity*, gnc_numeric))cm_setter;
string_to_gnc_numeric((char*)xmlNodeGetContent(node), &cm_numeric);
if(numeric_setter != NULL) { numeric_setter(qsf_ent, cm_numeric); }
}
if(safe_strcmp(qof_type, QOF_TYPE_GUID) == 0) {
cm_guid = g_new(GUID, 1);
if(TRUE != string_to_guid((char*)xmlNodeGetContent(node), cm_guid))
{
qof_backend_set_error(params->be, ERR_QSF_BAD_OBJ_GUID);
PINFO (" string to guid conversion failed for %s:%s:%s",
xmlNodeGetContent(node), obj_type, qof_type);
return;
}
reference_type = (char*)xmlGetProp(node, BAD_CAST QSF_OBJECT_TYPE);
if(0 == safe_strcmp(QOF_PARAM_GUID, reference_type))
{
qof_entity_set_guid(qsf_ent, cm_guid);
}
else {
reference = qof_entity_get_reference_from(qsf_ent, cm_param);
if(reference) {
params->referenceList = g_list_append(params->referenceList, reference);
}
}
}
if(safe_strcmp(qof_type, QOF_TYPE_INT32) == 0) {
errno = 0;
cm_i32 = (gint32)strtol ((char*)xmlNodeGetContent(node), &tail, 0);
if(errno == 0) {
i32_setter = (void(*)(QofEntity*, gint32))cm_setter;
if(i32_setter != NULL) { i32_setter(qsf_ent, cm_i32); }
}
else { qof_backend_set_error(params->be, ERR_QSF_OVERFLOW); }
}
if(safe_strcmp(qof_type, QOF_TYPE_INT64) == 0) {
errno = 0;
cm_i64 = strtoll((gchar*)xmlNodeGetContent(node), &tail, 0);
if(errno == 0) {
i64_setter = (void(*)(QofEntity*, gint64))cm_setter;
if(i64_setter != NULL) { i64_setter(qsf_ent, cm_i64); }
}
else { qof_backend_set_error(params->be, ERR_QSF_OVERFLOW); }
}
if(safe_strcmp(qof_type, QOF_TYPE_DOUBLE) == 0) {
errno = 0;
cm_double = strtod((gchar*)xmlNodeGetContent(node), &tail);
if(errno == 0) {
double_setter = (void(*)(QofEntity*, double))cm_setter;
if(double_setter != NULL) { double_setter(qsf_ent, cm_double); }
}
}
if(safe_strcmp(qof_type, QOF_TYPE_BOOLEAN) == 0){
if(0 == safe_strcasecmp((gchar*)xmlNodeGetContent(node),
QSF_XML_BOOLEAN_TEST)) {
cm_boolean = TRUE;
}
else { cm_boolean = FALSE; }
boolean_setter = (void(*)(QofEntity*, gboolean))cm_setter;
if(boolean_setter != NULL) { boolean_setter(qsf_ent, cm_boolean); }
}
if(safe_strcmp(qof_type, QOF_TYPE_KVP) == 0) {
cm_type = qsf_to_kvp_helper((gchar*)xmlGetProp(node, BAD_CAST QSF_OBJECT_VALUE));
if(!cm_type) { return; }
cm_value = string_to_kvp_value((gchar*)xmlNodeGetContent(node), cm_type);
cm_kvp = (KvpFrame*)cm_param->param_getfcn(qsf_ent, cm_param);
cm_kvp = kvp_frame_set_value(cm_kvp, (gchar*)xmlGetProp(node,
BAD_CAST QSF_OBJECT_KVP), cm_value);
}
if(safe_strcmp(qof_type, QOF_TYPE_COLLECT) == 0) {
QofCollection *qsf_coll;
QofIdType type;
QofEntityReference *reference;
QofParam *copy_param;
/* retrieve the *type* of the collection, ignore any contents. */
qsf_coll = cm_param->param_getfcn(qsf_ent, cm_param);
type = qof_collection_get_type(qsf_coll);
cm_guid = g_new(GUID, 1);
if(TRUE != string_to_guid((gchar*)xmlNodeGetContent(node), cm_guid))
{
qof_backend_set_error(params->be, ERR_QSF_BAD_OBJ_GUID);
PINFO (" string to guid collect failed for %s", xmlNodeGetContent(node));
return;
}
/* create a QofEntityReference with this type and GUID.
there is only one entity each time.
cm_guid contains the GUID of the reference.
type is the type of the reference. */
reference = g_new0(QofEntityReference, 1);
reference->type = g_strdup(qsf_ent->e_type);
reference->ref_guid = cm_guid;
reference->ent_guid = &qsf_ent->guid;
copy_param = g_new0(QofParam, 1);
copy_param->param_name = g_strdup(cm_param->param_name);
copy_param->param_type = g_strdup(cm_param->param_type);
reference->param = copy_param;
params->referenceList = g_list_append(params->referenceList, reference);
}
if(safe_strcmp(qof_type, QOF_TYPE_CHAR) == 0) {
char_getter = (gchar (*)(xmlNodePtr))xmlNodeGetContent;
cm_char = char_getter(node);
char_setter = (void(*)(QofEntity*, gchar))cm_setter;
if(char_setter != NULL) { char_setter(qsf_ent, cm_char); }
}
}
static QofBackend*
qsf_backend_new(void)
{
QSFBackend *qsf_be;
QofBackend *be;
qsf_be = g_new0(QSFBackend, 1);
be = (QofBackend*) qsf_be;
qof_backend_init(be);
qsf_be->params = g_new(qsf_param, 1);
qsf_be->params->be = be;
qsf_param_init(qsf_be->params);
qsf_be->be.session_begin = qsf_session_begin;
be->session_end = qsf_session_end;
be->destroy_backend = qsf_destroy_backend;
be->load = qsf_file_type;
be->save_may_clobber_data = NULL;
/* The QSF backend will always load and save the entire QSF XML file. */
be->begin = NULL;
be->commit = NULL;
be->rollback = NULL;
/* QSF uses the built-in SQL, not a dedicated SQL server. */
be->compile_query = NULL;
be->free_query = NULL;
be->run_query = NULL;
be->counter = NULL;
/* The QSF backend is not multi-user. */
be->events_pending = NULL;
be->process_events = NULL;
be->sync = qsf_write_file;
/* use for maps, later. */
be->load_config = qsf_load_config;
be->get_config = qsf_get_config;
qsf_be->fullpath = NULL;
return be;
}
/* The QOF method of loading each backend.
QSF is loaded as a GModule using the QOF method - QofBackendProvider.
*/
static void
qsf_provider_free (QofBackendProvider *prov)
{
prov->provider_name = NULL;
prov->access_method = NULL;
g_free (prov);
}
G_MODULE_EXPORT const gchar *
g_module_check_init(GModule *module)
{
QofBackendProvider *prov;
prov = g_new0 (QofBackendProvider, 1);
prov->provider_name = "QSF Backend Version 0.2";
prov->access_method = "file";
prov->partial_book_supported = TRUE;
prov->backend_new = qsf_backend_new;
prov->check_data_type = qsf_determine_file_type;
prov->provider_free = qsf_provider_free;
qof_backend_register_provider (prov);
g_module_make_resident (module);
return NULL;
}