[Gnucash-changes] r11801 - gnucash/branches/cashutil/cashutil -
Adding cashutil source
Neil Williams
codehelp at cvs.gnucash.org
Thu Nov 3 05:52:53 EST 2005
Author: codehelp
Date: 2005-11-03 05:52:51 -0500 (Thu, 03 Nov 2005)
New Revision: 11801
Added:
gnucash/branches/cashutil/cashutil/src/
gnucash/branches/cashutil/cashutil/src/Makefile.am
gnucash/branches/cashutil/cashutil/src/README
gnucash/branches/cashutil/cashutil/src/backend-bus.c
gnucash/branches/cashutil/cashutil/src/backend-bus.h
gnucash/branches/cashutil/cashutil/src/cashutil.c
gnucash/branches/cashutil/cashutil/src/cashutil.h
gnucash/branches/cashutil/cashutil/src/qof-main.c
gnucash/branches/cashutil/cashutil/src/qof-main.h
gnucash/branches/cashutil/cashutil/src/qofundo-p.h
gnucash/branches/cashutil/cashutil/src/qofundo.c
gnucash/branches/cashutil/cashutil/src/qofundo.h
Log:
Adding cashutil source
Added: gnucash/branches/cashutil/cashutil/src/Makefile.am
===================================================================
--- gnucash/branches/cashutil/cashutil/src/Makefile.am 2005-11-03 10:39:45 UTC (rev 11800)
+++ gnucash/branches/cashutil/cashutil/src/Makefile.am 2005-11-03 10:52:51 UTC (rev 11801)
@@ -0,0 +1,38 @@
+#SUBDIRS = objects . test backend
+
+AM_CFLAGS = \
+ -I.. -I../.. \
+ -DLOCALE_DIR=\""$(datadir)/locale"\" \
+ -I${top_srcdir}/src/backend/file \
+ -I${top_srcdir}/src/business/business-core \
+ -I${top_srcdir}/src/engine \
+ ${GOBJECT_CFLAGS} \
+ ${GLIB_CFLAGS} \
+ ${QOF_CFLAGS}
+
+bin_PROGRAMS= \
+ cashutil
+
+cashutil_SOURCES = \
+ qof-main.c \
+ qofundo.c \
+ cashutil.c
+
+EXTRA_DIST = \
+ qof-main.h \
+ qofundo.h \
+ gncla-dir.h.in
+
+noinst_HEADERS = qofundo-p.h
+
+cashutil_LDADD = \
+ ${top_builddir}/src/objects/libcashobjects.la \
+ ${top_builddir}/src/objects/libcashbusobjects.la \
+ -lltdl \
+ ${QOF_LIBS} \
+ ${GLIB_LIBS} \
+ ${GOBJECT_LIBS} \
+ ${GMODULE_LIBS} \
+ ${RL_LIBS} \
+ -lltdl \
+ -lpopt
Added: gnucash/branches/cashutil/cashutil/src/README
===================================================================
--- gnucash/branches/cashutil/cashutil/src/README 2005-11-03 10:39:45 UTC (rev 11800)
+++ gnucash/branches/cashutil/cashutil/src/README 2005-11-03 10:52:51 UTC (rev 11801)
@@ -0,0 +1,29 @@
+CashUtil source readme
+======================
+
+CashUtil is intended to be built separately from
+GnuCash whilst being developed in the same CVS tree.
+
+The modifications within GnuCash mean that the object
+files and the backend files need to be reorganised.
+Also, the translation files, man page and configuration
+scripts are just for CashUtil.
+
+CashUtil also uses QOF as an external library, requiring
+libqof1 (>= 0.6.0).
+
+A lot of the translatable strings are similar to GnuCash
+but have to be formatted slightly differently for a command
+line interface.
+
+Important: When debugging this code, remember that CashUtil
+is linked against an installed version of QOF, AND it *only*
+uses the installed copies of libcashobjects.la and
+libgnc-backend-file.la. You must 'make install' to test any
+changes in the source files for these libraries. If you
+change any QOF files, run 'make install' in QOF and then
+run 'make install' in CashUtil.
+
+If you have your own books to test during development, CVS
+will ignore a src/books directory as a convenience.
+
Added: gnucash/branches/cashutil/cashutil/src/backend-bus.c
===================================================================
--- gnucash/branches/cashutil/cashutil/src/backend-bus.c 2005-11-03 10:39:45 UTC (rev 11800)
+++ gnucash/branches/cashutil/cashutil/src/backend-bus.c 2005-11-03 10:52:51 UTC (rev 11801)
@@ -0,0 +1,72 @@
+/***************************************************************************
+ * backend-bus.c
+ *
+ * Sun Sep 25 15:59:50 2005
+ * Copyright 2005 Neil Williams
+ * linux at 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include <libxml/xmlmemory.h>
+#include <libxml/tree.h>
+#include <libxml/parser.h>
+#include <libxml/xmlschemas.h>
+
+#include "gnc-address-xml-v2.h"
+#include "gnc-bill-term-xml-v2.h"
+#include "gnc-customer-xml-v2.h"
+#include "gnc-employee-xml-v2.h"
+#include "gnc-entry-xml-v2.h"
+#include "gnc-invoice-xml-v2.h"
+#include "gnc-job-xml-v2.h"
+#include "gnc-order-xml-v2.h"
+#include "gnc-owner-xml-v2.h"
+#include "gnc-tax-table-xml-v2.h"
+#include "gnc-vendor-xml-v2.h"
+
+void
+backend_business_add (void)
+{
+ gnc_address_xml_initialize ();
+ gnc_billterm_xml_initialize ();
+ gnc_customer_xml_initialize ();
+ gnc_employee_xml_initialize ();
+ gnc_entry_xml_initialize ();
+ gnc_invoice_xml_initialize ();
+ gnc_job_xml_initialize ();
+ gnc_order_xml_initialize ();
+ gnc_owner_xml_initialize ();
+ gnc_taxtable_xml_initialize ();
+ gnc_vendor_xml_initialize ();
+}
+
+
+gboolean bus_cashobjects_register(void)
+{
+ g_return_val_if_fail(gncInvoiceRegister(), FALSE);
+ g_return_val_if_fail ( gncJobRegister (), FALSE);
+ g_return_val_if_fail(gncBillTermRegister(), FALSE);
+ g_return_val_if_fail(gncCustomerRegister(), FALSE);
+ g_return_val_if_fail(gncAddressRegister(), FALSE);
+ g_return_val_if_fail(gncEmployeeRegister(), FALSE);
+ g_return_val_if_fail ( gncEntryRegister (), FALSE);
+ g_return_val_if_fail (gncVendorRegister (), FALSE);
+ g_return_val_if_fail(gncTaxTableRegister(), FALSE);
+ g_return_val_if_fail ( gncOrderRegister (), FALSE);
+ return TRUE;
+}
Added: gnucash/branches/cashutil/cashutil/src/backend-bus.h
===================================================================
--- gnucash/branches/cashutil/cashutil/src/backend-bus.h 2005-11-03 10:39:45 UTC (rev 11800)
+++ gnucash/branches/cashutil/cashutil/src/backend-bus.h 2005-11-03 10:52:51 UTC (rev 11801)
@@ -0,0 +1,28 @@
+/***************************************************************************
+ * backend-bus.h
+ *
+ * Sun Sep 25 16:02:07 2005
+ * Copyright 2005 Neil Williams
+ * linux at 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+void backend_business_add (void);
+
+gboolean bus_cashobjects_register (void);
+
Added: gnucash/branches/cashutil/cashutil/src/cashutil.c
===================================================================
--- gnucash/branches/cashutil/cashutil/src/cashutil.c 2005-11-03 10:39:45 UTC (rev 11800)
+++ gnucash/branches/cashutil/cashutil/src/cashutil.c 2005-11-03 10:52:51 UTC (rev 11801)
@@ -0,0 +1,364 @@
+/***************************************************************************
+ * cashutil.c
+ *
+ * Sat Feb 5 10:40:03 GMT 2005
+ * Copyright 2005 Neil Williams
+ * linux at 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+*/
+
+/** @addtogroup QOFCLI
+ @{ */
+
+/** @addtogroup cashutil A command line interface for GnuCash data
+
+cashutil provides an executable interface to GnuCash data
+It supports writing the QSF XML files and SQL-type queries.
+
+The types of SQL queries that are allowed at this point are a little limited.
+In general, only the following types of queries are supported: \n
+SELECT * FROM SomeObj WHERE (param_a < 10.0) AND (param_b = "asdf") SORT BY param_c DESC;\n
+INSERT INTO SomeObj (param_a, param_b, param_c) VALUES ("value_a", true, "0/1");
+
+Joins are not supported directly.\n
+SELECT * FROM ObjA,ObjB WHERE (ObjA.param_id = ObjB.param_other_id);\n
+The problem with the above is that the search requires a nested search loop, aka a 'join',
+which is not currently supported in the underlying QofQuery code.
+
+However, by repeating queries and adding the entities to a new session using
+::qof_entity_copy_list, a series of queries can be added to a single book.
+e.g. You can insert multiple entities and save out as a QSF XML file or use multiple
+SELECT queries to build a precise list - this can be used to replicate most of the
+functionality of a SQL join.
+
+SELECT * from ObjA where param_id = value; SELECT * from ObjB where param_other_id = value;
+
+Equivalent to:\n
+SELECT * from ObjA,ObjB where param_id = param_other_id and param_id = value;
+
+When combined with a foreach callback on the value of param_id for each entity in the
+QofBook, you can produce the effect of a join from running the two SELECT queries for each
+value of param_id held in 'value'.
+
+See ::QofEntityForeachCB and ::qof_object_foreach.
+
+SELECT a,b,c FROM ...
+
+Used to convert QOF objects between applications by using the returned parameter values
+to create a second object. One application using QOF could register objects from two
+applications and convert data from one to the other by using\n
+SELECT a,b,c FROM ObjA; SELECT d,f,k FROM ObjB; qof_object_new_instance();
+ObjC_set_a(value_c); ObjC_set_b(value_k) etc.
+
+What's needed is for the SELECT to return a complete object that only contains the
+parameters selected.
+
+Unsupported: UPDATE, DELETE.
+
+It will not be possible to support CREATE, AMEND or DROP for understandable reasons.
+
+@{
+*/
+/** @file cashutil.c
+ @brief Command line interface.
+ @author Copyright (c) 2005 Neil Williams <linux at codehelp.co.uk>
+*/
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <libintl.h>
+#include <locale.h>
+#include <errno.h>
+#include <gmodule.h>
+#include <sys/stat.h>
+#include "config.h"
+#include "qof-main.h"
+#include "cashutil.h"
+#include "cashobjects.h"
+#include "gncla-dir.h"
+#include "qofundo-p.h"
+#include "backend-bus.h"
+
+static gchar* log_module = CU_MOD_CLI;
+static gboolean debug_on = FALSE;
+
+static gboolean
+load_bus_backend (const char *directory)
+{
+ typedef void (* bus_backend_init) (void);
+ GModule *bus_backend;
+ gchar *fullpath;
+ struct stat sbuf;
+ bus_backend_init bus_init;
+ gpointer g;
+
+ g_return_val_if_fail(bus_cashobjects_register(), FALSE);
+ g_return_val_if_fail(g_module_supported(), FALSE);
+ fullpath = g_module_build_path(directory, "libgnc-backend-bus.la");
+ g_return_val_if_fail((stat(fullpath, &sbuf) == 0), FALSE);
+ bus_backend = g_module_open(fullpath, G_MODULE_BIND_LAZY);
+ if(!bus_backend) {
+ PWARN ("%s: %s\n", PACKAGE, g_module_error ());
+ return FALSE;
+ }
+ g = &bus_init;
+ if (!g_module_symbol (bus_backend, "backend_business_add", g))
+ {
+ PWARN ("%s: %s\n", PACKAGE, g_module_error ());
+ return FALSE;
+ }
+ g_module_make_resident(bus_backend);
+ bus_init();
+ return TRUE;
+}
+
+int
+main (int argc, const char *argv[])
+{
+ const char *exclude, *date_time, *database;
+ const char *sql_file, *write_file, *sql_query, *filename;
+ const char *help_header_text;
+ gboolean use_stdin;
+ qof_data *context;
+ int optc, gz_level;
+ poptContext pc;
+ qof_op_type command;
+ QofSession *session;
+ FILE *f;
+
+ struct poptOption options[] = {
+ QOF_CLI_OPTIONS
+ {"input", 'i', POPT_ARG_STRING, &filename, qof_op_input,
+ _("Load a GnuCash or QSF book from <filename>"), "filename"},
+ POPT_TABLEEND
+ };
+ #ifdef ENABLE_NLS
+ setlocale (LC_ALL, "");
+ bindtextdomain (GETTEXT_PACKAGE, LOCALE_DIR);
+ bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
+ textdomain (GETTEXT_PACKAGE);
+ #endif
+ use_stdin = TRUE;
+ command = qof_op_noop;
+ exclude = NULL;
+ database = NULL;
+ sql_file = NULL;
+ write_file = NULL;
+ sql_query = NULL;
+ filename = NULL;
+ f = NULL;
+ gnc_set_log_level_global(1);
+
+ help_header_text = _(
+ "\n"
+ " Command line query interface to GnuCash using QOF -\n"
+ " the Query Object Framework.\n"
+ " Supports reading personal and business GnuCash data and running \n"
+ " SQL-type queries on the live data or XML file. \n"
+ " Data can be added, edited, removed, printed, merged and written \n"
+ " out in the QOF interactive shell.\n"
+ " QSF XML can be imported into other QOF applications.\n\n"
+ " Use exactly one of -i -l --explain --shell; \n"
+ " options are -tde, with either -s or -f, then -w.\n\n");
+
+ pc = poptGetContext (PACKAGE, argc, argv, options, 0);
+
+ poptSetOtherOptionHelp (pc, help_header_text);
+
+ if (argc < 2)
+ {
+ poptPrintUsage (pc, stderr, 0);
+ return 1;
+ }
+ qof_init();
+ g_return_val_if_fail(cashobjects_register(), -1);
+ context = qof_create();
+ if(!context) {
+ fprintf(stderr, _("Fatal error: Cannot initialise QOF.\n\n"));
+ return -1;
+ }
+ session = qof_session_new ();
+ context->book = qof_session_get_book(session);
+ /* Read user alias settings */
+ poptReadDefaultConfig(pc, 0);
+ /* could use poptAddAlias(poptContext pc, struct poptAlias alias, 0) to specify
+ * a default alias, perhaps to ease GnuCash or PilotQOF usage. */
+ while ((optc = poptGetNextOpt (pc)) >= 0)
+ {
+ switch (optc)
+ {
+ /* commands - mutually exclusive */
+ case qof_op_input:
+ case qof_op_list:
+ case qof_op_explain:
+ case qof_op_shell:
+ {
+ if (qof_op_noop != command)
+ {
+ fprintf (stderr,
+ _("%s: ERROR. Specify only one command out of -i, -l."), PACKAGE);
+ fprintf (stderr, _("--explain or --shell\n"));
+ return 1;
+ }
+ command = optc;
+ use_stdin = FALSE;
+ break;
+ }
+ case qof_op_vers :
+ {
+ fprintf (stdout, "\n (c) Copyright 2005 Neil Williams <linux at codehelp.co.uk>\n");
+ fprintf (stdout, _(" For CashUtil support, join the QOF-devel mailing list at\n"));
+ fprintf (stdout, " http://lists.sourceforge.net/mailman/listinfo/qof-devel\n");
+ fprintf (stdout, _("\n This is CashUtil v%s\n"), VERSION);
+ fprintf (stdout, _(" The GnuCash Command Line Interface.\n"));
+// fprintf (stdout, _(" Build target..: %s\n"), HOST_OS);
+ fprintf (stdout, _(" Build date....: %s %s\n\n"), __DATE__, __TIME__);
+ fprintf (stdout, _(" Please use --help for more detailed options.\n\n"));
+ return 0;
+ }
+ /* optional modifiers - store to act on later. */
+ case qof_op_database:
+ {
+ qof_mod_database (database, context);
+ break;
+ }
+ case qof_op_timespec:
+ {
+ qof_mod_timespec (date_time, context);
+ break;
+ }
+ case qof_op_exclude:
+ {
+ qof_mod_exclude (exclude, context);
+ break;
+ }
+ case qof_op_sql:
+ {
+ qof_mod_sql (sql_query, context);
+ break;
+ }
+ case qof_op_sql_file:
+ {
+ qof_mod_sql_file (sql_file, context);
+ break;
+ }
+ case qof_op_write:
+ {
+ qof_mod_write (write_file, context);
+ break;
+ }
+ case qof_op_compress:
+ {
+ context->gz_level = gz_level;
+ break;
+ }
+ case qof_op_debug:
+ {
+ debug_on = TRUE;
+ break;
+ }
+ default:
+ {
+ fprintf (stderr, _("%s: ERROR. Unknown option %d, argument: %s\n"),
+ PACKAGE, optc, poptGetOptArg (pc));
+ return 1;
+ }
+ }
+ }
+ if (use_stdin && command == qof_op_noop) {
+ fprintf (stderr, _("%s: Please specify only one command out of -i, -l."), PACKAGE);
+ fprintf (stderr, _("--explain or --shell\n"));
+ poptPrintUsage(pc, stderr, 0);
+ return 1;
+
+ }
+ if (use_stdin) {
+ fprintf (stderr, _("%s: Sorry, %s cannot yet read STDIN.\n"), PACKAGE, PACKAGE);
+ poptPrintUsage(pc, stderr, 0);
+ return 1;
+ }
+ if (optc < -1)
+ {
+ fprintf(stderr, "%s: %s %s\n\n", PACKAGE,
+ poptBadOption(pc, POPT_BADOPTION_NOALIAS),
+ poptStrerror(optc));
+ poptPrintUsage(pc, stderr, 0);
+ return 1;
+ }
+ /* If we get this far, we should have sensible options: start the work. */
+ if(debug_on) {
+ f = fopen("/tmp/cashutil.trace", "w");
+ gnc_set_logfile(f);
+ gnc_log_init();
+ qof_log_set_default(GNC_LOG_DETAIL);
+ gnc_set_log_level(CU_MOD_CLI, GNC_LOG_DETAIL);
+ gnc_set_log_level(CU_MOD_ENGINE, GNC_LOG_DETAIL);
+ }
+ g_return_val_if_fail((qof_load_backend_library
+ (QOF_LIB_DIR, "libqof-backend-qsf.la", "qsf_provider_init")), -1);
+ g_return_val_if_fail((qof_load_backend_library
+ (GNC_LIBDIR, GNC_LIB_NAME, GNC_LIB_INIT)), -1);
+ g_return_val_if_fail(load_bus_backend(GNC_LIBDIR), -1);
+ context->input_session = session;
+ qof_book_clear_undo(context->book);
+ qof_object_foreach_type(qof_select_all, context);
+ switch (command)
+ {
+ case qof_op_input:
+ {
+ context->filename = g_strdup(filename);
+ qof_cmd_offline (context);
+ break;
+ }
+ case qof_op_list:
+ {
+ qof_cmd_list ();
+ break;
+ }
+ case qof_op_explain:
+ {
+ if(!context->database)
+ {
+ fprintf (stderr,
+ _("%s: Error. please specify the database to explain.\n\n"),
+ PACKAGE);
+ break;
+ }
+ qof_cmd_explain(context);
+ break;
+ }
+ case qof_op_shell:
+ {
+ qof_cmd_shell(context);
+ break;
+ }
+ default:
+ {
+ /* should be impossible */
+ break;
+ }
+ }
+ poptFreeContext(pc);
+ qof_data_free(context);
+ qof_close();
+ if(f) { fclose(f); }
+ return 0;
+}
+
+/** @} */
+/** @} */
Added: gnucash/branches/cashutil/cashutil/src/cashutil.h
===================================================================
--- gnucash/branches/cashutil/cashutil/src/cashutil.h 2005-11-03 10:39:45 UTC (rev 11800)
+++ gnucash/branches/cashutil/cashutil/src/cashutil.h 2005-11-03 10:52:51 UTC (rev 11801)
@@ -0,0 +1,32 @@
+/***************************************************************************
+ * cashutil.h
+ *
+ * Sun Apr 17 17:56:17 2005
+ * Copyright 2005 Neil Williams
+ * linux at 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef _CASHUTIL_H
+#define _CASHUTIL_H
+
+#include "qof-main.h"
+
+#define CU_MOD_CLI "cashutil-cli"
+
+#endif /* _PILOT_QOF_H */
Added: gnucash/branches/cashutil/cashutil/src/qof-main.c
===================================================================
--- gnucash/branches/cashutil/cashutil/src/qof-main.c 2005-11-03 10:39:45 UTC (rev 11800)
+++ gnucash/branches/cashutil/cashutil/src/qof-main.c 2005-11-03 10:52:51 UTC (rev 11801)
@@ -0,0 +1,1875 @@
+/***************************************************************************
+ * qof-main.c
+ *
+ * Thu Jan 13 10:55:44 2005
+ * Copyright 2005 Neil Williams
+ * linux at 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+/** @addtogroup QOFCLI
+
+Includes common functions for all QOF CLI programs and provides generic
+functions to implement command line and interactive shell options.
+@{
+*/
+/** @file qof-main.c
+ @brief Common functions for the QOF external framework
+ @author Copyright (c) 2005 Neil Williams <linux at codehelp.co.uk>
+*/
+#define _GNU_SOURCE
+#include "config.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <regex.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+#include "qof-main.h"
+/** \todo temporary until undo goes into QofBook */
+#include "qofundo-p.h"
+
+#ifdef HAVE_LIBREADLINE
+#include <readline/readline.h>
+#include <readline/history.h>
+#endif
+
+static GHashTable *backend_extensions;
+static gchar* log_module = CU_MOD_ENGINE;
+
+#define QSF_COMPRESS "compression_level"
+
+/** standardise tab output to help output line up nicely. */
+#define QOF_TAB " "
+
+/** the maximum number of entities to offer for a single selection.
+
+\todo Improve the maximum to allow scrolling / next page.
+*/
+#define CLI_MAX_SELECT 20
+
+/** Relate the command to the function. */
+typedef int (*cmd_fcn) (qof_data *context);
+/** \todo Reorganise the C into multiple files. */
+static int qof_parse_command (const char *cmd, qof_data *context);
+
+/** Provide a simple numerical index for selectable objects.
+
+ at param ent each entity in turn of the selected type.
+ at param data The qof_data context for the CLI.
+
+Ensure counter is reset to zero between runs.
+*/
+static void
+cli_select_cb (QofEntity *ent, gpointer data)
+{
+ qof_data *context;
+ gchar *temp;
+
+ context = (qof_data*)data;
+ g_return_if_fail(context);
+ context->counter++;
+ if(context->counter >= CLI_MAX_SELECT) { return; }
+ fprintf (stdout, "%d %s\n", context->counter,
+ qof_object_printable(context->inst_type, (QofInstance*)ent));
+ temp = g_strdup_printf("%d", context->counter);
+ g_hash_table_insert(context->select_table, temp, ent);
+}
+
+/** \brief Relate the commands to the functions within the shell. */
+struct shell_cmd
+{
+ const char *name; /**< Name of the command. */
+ cmd_fcn func; /**< Name of function to execute the command. */
+};
+
+/** command object param value */
+struct search_helper
+{
+ QofInstance *inst;
+ QofParam *param; /**< the parameter in argv[2] */
+ const char *term; /**< argv[3] */
+ char *value; /**< argv[4] */
+};
+
+/** check_for_single_instance */
+struct count_helper
+{
+ gint count;
+ QofInstance *first_inst;
+};
+
+/** pre-selects if only one entity of this type exists. */
+static void
+count_cb (QofEntity *ent, gpointer user_data)
+{
+ struct count_helper *ch;
+
+ ch = (struct count_helper*)user_data;
+ ch->count++;
+ if(ch->count == 1)
+ {
+ ch->first_inst = (QofInstance*)ent;
+ }
+}
+
+/** select_table helper
+
+The select_table hashtable relies on a conversion from
+the integer counter to a string. This frees the temporary
+conversion string when the hashtable is no longer needed.
+*/
+static void
+cli_free_select (gpointer key, gpointer value, gpointer data)
+{
+ g_free(key);
+}
+
+static void option_cb (QofBackendOption *option, gpointer data)
+{
+ gint gz_level;
+
+ gz_level = *(gint*)data;
+ if(0 == safe_strcmp(QSF_COMPRESS, option->option_name)) {
+ option->value = (gpointer)&gz_level;
+ }
+}
+
+static void
+qof_mod_compression (gint gz_level, qof_data *data)
+{
+ KvpFrame *be_config;
+ QofBook *book;
+ QofBackend *be;
+
+ if((gz_level > 0) && (gz_level <= 9))
+ {
+ book = qof_session_get_book(data->export_session);
+ be = qof_book_get_backend(book);
+ be_config = qof_backend_get_config(be);
+ qof_backend_option_foreach(be_config, option_cb, &gz_level);
+ qof_backend_load_config(be, be_config);
+ }
+}
+
+/** select an instance
+
+Offers multiple entities for selection. Ensure that
+check_for_single_instance has been run first.
+
+\todo Offer multiple listings of CLI_MAX_SELECT each.
+*/
+static int select_fcn (qof_data *context)
+{
+ gint choice, max;
+ gchar *temp;
+
+ if(!context->inst_type) { return -1; }
+ choice = 0;
+ context->counter = 0;
+ context->instance = NULL;
+ context->select_table = g_hash_table_new(g_str_hash, g_str_equal);
+ qof_object_foreach(context->inst_type, context->book, cli_select_cb, context);
+ max = g_hash_table_size(context->select_table);
+ if(max > CLI_MAX_SELECT) { max = CLI_MAX_SELECT; }
+ /* Translators: %s is an object name, %d the number of objects in the book. */
+ switch (context->cli_mode)
+ {
+ case EDIT_MODE : {
+ fprintf (stdout, _("Choose the %s to edit: (1 - %d): "),
+ context->inst_type, max);
+ break;
+ }
+ case DELETE_MODE : {
+ fprintf (stdout, _("Choose the %s to DELETE: (1 - %d): "),
+ context->inst_type, max);
+ break;
+ }
+ case PRINT_MODE : {
+ fprintf (stdout, _("Choose the %s to print: (1 - %d): "),
+ context->inst_type, max);
+ break;
+ }
+ case NO_OP : { break; }
+ }
+ scanf("%2i", &choice);
+ fprintf (stdout, "\n");
+ while(choice <= 0 || choice > max)
+ {
+ char c;
+ c = getchar();
+ while (c != '\n') { c = getchar(); }
+ fprintf (stdout, _("Only %d objects are available of type '%s'.\n"),
+ max, context->inst_type);
+ fprintf (stdout, _("Choose the %s to use: (1 - %d): "), context->inst_type, max);
+ scanf("%2i", &choice);
+ fprintf (stdout, "\n");
+ }
+ temp = g_strdup_printf("%d", choice);
+ context->instance = (QofInstance*)g_hash_table_lookup(context->select_table, temp);
+ g_free(temp);
+ g_hash_table_foreach(context->select_table, cli_free_select, NULL);
+ g_hash_table_destroy(context->select_table);
+ return 0;
+}
+
+/** \brief Shorthand search routine.
+
+Rather than using a QofQuery, this shorthand version uses a
+string for all parameter types. It will support the existing
+-t --date shorthand versions for compatibility with the non-interactive
+command set by actually using an underlying QofQuery.
+
+If this quick search fails, an interactive QofQuery could be used.
+A new query would have been needed anyway, with options enabled to
+isolate only one match. QofQuery relies on the term being the same
+type as the param_type. SQL queries will also be supported via the
+sql command in the shell. */
+
+static void
+cli_search_cb (QofEntity * ent, gpointer data)
+{
+ struct search_helper *sh;
+ gchar *value;
+ const QofParam *param;
+ gboolean found;
+
+ found = FALSE;
+ sh = (struct search_helper*)data;
+ param = sh->param;
+/* if param_type == QOF_TYPE_DATE, process term as -t --date and skip to a QofQuery format. */
+ if(0 == safe_strcmp(param->param_type, QOF_TYPE_DATE)) { return; }
+ value = qof_book_merge_param_as_string((QofParam*)param, ent);
+ if(0 == safe_strcmp(value, sh->term))
+ {
+ /* if term matches more than once, fallback to SQL */
+ if(found) { sh->inst = NULL; return; }
+ found = TRUE;
+ sh->inst = (QofInstance*)ent;
+ }
+}
+
+/** Shortcut if only one entity of this type exists. */
+static gboolean
+check_for_single_instance (qof_data *context)
+{
+ QofCollection *coll;
+ struct count_helper ch;
+ struct search_helper sh;
+
+ ch.count = 0;
+ qof_object_foreach(context->inst_type, context->book, count_cb, &ch);
+ if(!ch.count)
+ {
+ fprintf (stderr, _("%s: No objects of type '%s' found.\n"),
+ PACKAGE, context->inst_type);
+ context->inst_type = NULL;
+ return FALSE;
+ }
+ if(ch.count == 1)
+ {
+ context->instance = ch.first_inst;
+ if(!context->argv[2] || !context->argv[3]) { return TRUE; }
+ }
+ if(!context->argv[2] || !context->argv[3]) { return FALSE; }
+ sh.inst = NULL;
+ sh.param = NULL;
+ sh.term = context->argv[3];
+ sh.param = (QofParam*)qof_class_get_parameter(context->inst_type, context->argv[2]);
+ if(sh.param == NULL) {
+ fprintf (stderr, _("No parameter named '%s' found for object '%s'\n"),
+ sh.term, sh.param->param_name);
+ return FALSE;
+ }
+ coll = qof_book_get_collection(context->book, context->inst_type);
+ if(!coll) { return FALSE; }
+ qof_collection_foreach(coll, cli_search_cb, &sh);
+ if(sh.inst)
+ {
+ context->instance = sh.inst;
+ if(context->argv[4]) {
+ g_message("argv[4]=%s param=%s", context->argv[4], sh.param->param_name);
+ sh.value = g_strdup(context->argv[4]);
+ qof_begin_edit(context->instance);
+ undo_edit_record(context->instance, sh.param);
+ qof_entity_set_param(&context->instance->entity, (QofParam*)sh.param, sh.value);
+ qof_commit_edit(context->instance);
+ undo_edit_commit(context->instance, sh.param);
+ }
+ return TRUE;
+ }
+ return FALSE;
+}
+
+/** @name Top level shell functions.
+@{
+*/
+
+/** Run a sub-shell to either select or edit an instance. */
+static int
+qof_sub_shell (qof_data *context)
+{
+#ifdef HAVE_LIBREADLINE
+ char *line;
+ char *prompt;
+
+ if(!context->inst_type) { return 0; }
+ line = (char*)malloc(256 * sizeof(char));
+ prompt = g_strdup_printf("%s/%s> ", context->shortname, context->inst_type);
+#else
+ char buf[256];
+
+ if(!context->inst_type) { return 0; }
+#endif
+ for (;;) {
+ fflush (stdout);
+#ifdef HAVE_LIBREADLINE
+ line = readline(prompt);
+ if (line == NULL) { break; }
+ if (*line) { add_history(line); }
+ if (qof_parse_command(line, context) < 0) { break; }
+ g_free (line);
+#else
+ fprintf (stdout, "%s/%s> ", context->shortname, context->inst_type);
+ if (qof_parse_command(buf, context) < 0) { break; }
+#endif
+ }
+ fprintf(stdout, "\n");
+ return 0;
+}
+
+/** \brief add a new instance and edit the values
+
+Expects the second argument (context->argv[1]) to be the name of a registed object.
+*/
+static int add_fcn(qof_data *context)
+{
+ QofInstance *inst;
+ gint result;
+ gchar *temp;
+
+ result = 0;
+ if(!qof_class_is_registered(context->argv[1]))
+ {
+ fprintf (stdout, _("%s: Cannot add '%s' - object name not found\n"),
+ PACKAGE, context->argv[1]);
+ return 0;
+ }
+ context->inst_type = g_strdup(context->argv[1]);
+ temp = g_strdup_printf("add-%s", context->inst_type);
+ qof_book_start_operation(context->book, temp);
+ inst = (QofInstance*)qof_object_new_instance(context->inst_type, context->book);
+ if(!inst) {
+ fprintf (stdout, _("Failed to add an instance of %s\n"), context->inst_type);
+ return 0;
+ }
+ fprintf (stdout, _("Added an instance of %s\n\n"), context->inst_type);
+ fprintf (stdout, _("Now edit the parameter details of this instance.\n"));
+ fprintf (stdout, _("Type 'help' for available commands or parameters.\n\n"));
+ undo_create_record(inst);
+ qof_book_end_operation(context->book);
+ context->shell_type = EDIT_SHELL;
+ context->instance = inst;
+ result = qof_sub_shell(context);
+ context->shell_type = TOP_SHELL;
+ return result;
+}
+
+/** \brief edit [object]
+
+\todo skip over user-friendly forms that may use
+where or =.
+
+*/
+static int edit_fcn(qof_data *context)
+{
+ gint result;
+ gchar *temp;
+
+ if(!qof_class_is_registered(context->argv[1]))
+ {
+ fprintf (stdout, _("%s: Cannot edit '%s' - object name not found\n"),
+ PACKAGE, context->argv[1]);
+ return 0;
+ }
+ result = 0;
+ context->inst_type = g_strdup(context->argv[1]);
+ if(!check_for_single_instance(context)) {
+ fprintf (stdout, _("Select an instance of %s to edit:\n"), context->inst_type);
+ fprintf (stdout, _("Type 'help' for available commands or parameters.\n\n"));
+ context->cli_mode = EDIT_MODE;
+ result = select_fcn(context);
+ context->cli_mode = NO_OP;
+ if (result) { return result; }
+ }
+ context->shell_type = EDIT_SHELL;
+ temp = g_strdup_printf("modify-%s", context->inst_type);
+ qof_book_start_operation(context->book, temp);
+ fprintf (stdout, _("Edit the parameter details of the selected instance.\n"));
+ fprintf (stdout, _("Type 'help' for available commands or parameters.\n\n"));
+ if(context->instance) { result = qof_sub_shell(context); }
+ context->shell_type = TOP_SHELL;
+ context->inst_type = NULL;
+ qof_book_end_operation(context->book);
+ return result;
+}
+/** \brief delete an instance of an object.
+
+Selects one instance and frees the entity.
+Warns the user AFTER deletion.
+
+\todo a bespoke undo command could be useful, rather
+than quitting the program completely! :-)
+*/
+static int delete_fcn(qof_data *context)
+{
+ gint result;
+ QofEntity *ent;
+ gchar *temp;
+
+ result = 0;
+ if(!qof_class_is_registered(context->argv[1]))
+ {
+ fprintf (stdout, _("%s: Cannot delete '%s' - object name not found\n"),
+ PACKAGE, context->argv[1]);
+ return 0;
+ }
+ context->inst_type = g_strdup(context->argv[1]);
+ if(!check_for_single_instance(context))
+ {
+ fprintf (stdout, _("Select the instance of %s to delete\n"), context->inst_type);
+ fprintf (stdout, _("Type 'help' for available commands or parameters.\n\n"));
+ context->cli_mode = DELETE_MODE;
+ result = select_fcn(context);
+ context->cli_mode = NO_OP;
+ if ((result)||(!context->instance)) { return result; }
+ }
+ temp = g_strdup_printf("delete_%s", context->inst_type);
+ qof_book_start_operation(context->book, temp);
+ undo_delete_record(context->instance);
+ ent = (QofEntity*)context->instance;
+ qof_collection_mark_dirty(qof_book_get_collection(context->book, ent->e_type));
+ qof_entity_release(ent);
+ qof_book_end_operation(context->book);
+ fprintf (stdout, _("The instance has been DELETED. Type 'quit' to exit and undo. "
+ "The file will not be changed until you type 'write'.\n"));
+ return 0;
+}
+
+static void
+qof_list_cb(QofObject *obj, gpointer data)
+{
+ fprintf(stdout, "%-20s\t%s\n", obj->e_type, obj->type_label);
+}
+
+static int list_fcn(qof_data *context)
+{
+ fprintf (stdout, _("\nThe QOF shell supports these object names:\n"
+ "You can use the names with 'edit', 'print' or 'delete'\n"
+ "and in SQL queries (as the table name) with 'sql'\n"
+ "Descriptions are shown only for readability.\n\n"));
+ fprintf (stdout, "%-20s%s", _("Object Name"), _("Description\n"));
+ qof_object_foreach_type(qof_list_cb, NULL);
+ fprintf (stdout, _("\nUse 'explain <database>' to see the list of fields within\n"
+ "any supported database.\n"));
+ return 0;
+}
+
+static void
+qof_print_cb (QofParam *param, gpointer data)
+{
+ gchar *str;
+ qof_data *context;
+
+ context = (qof_data*)data;
+ g_return_if_fail(context);
+ str = qof_book_merge_param_as_string(param, (QofEntity*)context->instance);
+ fprintf (stdout, _("%-24s %-12s %s\n"), param->param_name, param->param_type, str);
+}
+
+/** \brief print an instance to the terminal. */
+static int print_fcn(qof_data *context)
+{
+ gint result;
+
+ result = 0;
+ if(!qof_class_is_registered(context->argv[1]))
+ {
+ fprintf (stdout, _("%s: Cannot print '%s' - object name not found\n"),
+ PACKAGE, context->argv[1]);
+ return 0;
+ }
+ context->inst_type = g_strdup(context->argv[1]);
+ if(!check_for_single_instance(context))
+ {
+ if(!context->inst_type) { return 0; }
+ fprintf (stdout, _("Select the instance of '%s' to print.\n"), context->inst_type);
+ fprintf (stdout, _(" Type 'help' for available commands or parameters.\n\n"));
+ context->cli_mode = PRINT_MODE;
+ result = select_fcn(context);
+ context->cli_mode = NO_OP;
+ if ((result)||(!context->instance)) { return result; }
+ }
+ fprintf (stdout, "%-24s %-12s %s\n\n", _("Name"), _("Type"), _("Value"));
+ qof_class_param_foreach (context->inst_type, qof_print_cb, context);
+ fprintf(stdout, "\n");
+ return 0;
+}
+
+static void
+qof_explain_cb (QofParam* param, gpointer user_data)
+{
+ if(param->param_getfcn && param->param_setfcn)
+ {
+ fprintf (stdout, _("Type: %-12s\tName: %-12s\n"), param->param_type, param->param_name);
+ }
+}
+
+/** print a list of available parameters for this object type */
+static int explain_fcn(qof_data *context)
+{
+ if(qof_class_is_registered(context->argv[1])) {
+ fprintf (stdout, _("\nParameters of the %s database:\n\n"), context->argv[1]);
+ qof_class_param_foreach(context->argv[1], qof_explain_cb, NULL);
+ fprintf (stdout, "\n\n");
+ }
+ else {
+ fprintf (stderr, _("\n%s: %s object not found.\n"), PACKAGE, context->argv[1]);
+ }
+ return 0;
+}
+
+/** \brief load [filename]
+
+Expects a readable filename in context->argv[1]
+*/
+static int load_fcn(qof_data *context)
+{
+ char* answer;
+
+ answer = g_strdup(" ");
+ if(qof_book_not_saved(context->book))
+ {
+ fprintf (stderr, _("%s: The current book has not been saved.\n"
+ "Do you still want to overwrite it? [y/n]\n"), PACKAGE);
+ scanf("%1s", answer);
+ if((0 != safe_strcmp(answer, "y")) && (0 != safe_strcmp(answer, "Y"))) {
+ qof_session_save(context->input_session, NULL);
+ }
+ }
+ qof_session_end(context->input_session);
+ context->input_session = qof_session_new();
+ if(context->argv[1]) {
+ context->filename = g_strdup(context->argv[1]);
+ }
+ if(context->filename) {
+ qof_session_begin(context->input_session, context->filename, FALSE, TRUE);
+ qof_mod_compression(context->gz_level, context);
+ }
+ else {
+ qof_session_begin(context->input_session, QOF_STDOUT, FALSE, FALSE);
+ }
+ qof_session_load(context->input_session, NULL);
+ context->book = qof_session_get_book(context->input_session);
+ qof_show_error(context->input_session, context->filename);
+ /* implement in the backend eventually */
+ qof_book_clear_undo(context->book);
+ return 0;
+}
+/** \brief write [filename]
+
+write the current book to file.
+ If no filename is provided, save to the original file
+ (to STDOUT if STDIN used). Analagous to Save / Save As...
+
+\todo fix problems with writing to an alternative file.
+*/
+static int write_fcn(qof_data *context)
+{
+ QofSession *save_as;
+ gchar current_work[PATH_MAX];
+ gchar *temp;
+
+ if(context->argv[1])
+ {
+ save_as = qof_session_new();
+ if(*context->argv[1] != '/')
+ {
+ getcwd(current_work, PATH_MAX);
+ temp = g_strconcat(current_work, "/", context->argv[1], NULL);
+ context->argv[1] = temp;
+ }
+ qof_session_begin(save_as, context->argv[1], FALSE, TRUE);
+ qof_session_swap_data(save_as, context->input_session);
+ qof_session_save(save_as, NULL);
+ qof_show_error(save_as, context->argv[1]);
+ qof_session_end(save_as);
+ return 0;
+ }
+ if(context->write_file)
+ {
+ qof_session_save(context->export_session, NULL);
+ qof_show_error(context->export_session, context->write_file);
+ }
+ else {
+ qof_session_save(context->input_session, NULL);
+ qof_show_error(context->input_session, context->filename);
+ }
+ /* implement in the backend eventually */
+ if(qof_book_can_undo(context->book)) { qof_book_clear_undo(context->book); }
+ return 0;
+}
+
+/** \brief merge UI
+
+\todo the entire merge routine needs testing.
+*/
+static void
+qof_merge_loop (qof_book_mergeData *mergeData, qof_book_mergeRule *rule, guint remainder)
+{
+ GSList *user_reports;
+ QofParam *one_param;
+ gchar *importstring, *targetstring, *buffer;
+ gint count, resolution;
+ gboolean input_ok;
+ gchar y;
+
+ buffer = "";
+ count = 0;
+ input_ok = FALSE;
+ user_reports = rule->mergeParam;
+ while(user_reports != NULL) {
+ one_param = user_reports->data;
+ buffer = g_strconcat(buffer, g_strdup_printf(_("%i:Parameter name: %s "),
+ count, one_param->param_name), NULL);
+ importstring = qof_book_merge_param_as_string(one_param, rule->importEnt);
+ buffer = g_strconcat(buffer, g_strdup_printf(_("Import data : %s "), importstring), NULL);
+ targetstring = qof_book_merge_param_as_string(one_param, rule->targetEnt);
+ buffer = g_strconcat(buffer, g_strdup_printf(_("Original data : %s\n"), targetstring), NULL);
+ user_reports = g_slist_next(user_reports);
+ count++;
+ }
+ while(!input_ok) {
+ resolution = 0;
+ fprintf (stdout, _("\nPlease resolve this conflict. Enter\n"
+ " 1 to use the import data or \n"
+ " 2 to keep the original data or"));
+ if(rule->mergeAbsolute == FALSE) {
+ fprintf (stdout, _("\n 3 to import the data as a NEW object or"));
+ }
+ fprintf (stdout, _("\n 9 to abort the entire merge operation.\n"));
+ fprintf (stdout, "> (1, 2");
+ if(rule->mergeAbsolute == FALSE) {
+ fprintf (stdout, ", 3 ");
+ }
+ fprintf (stdout, _("or 9) : "));
+ scanf("%1i", &resolution);
+ switch(resolution) {
+ case 1 : {
+ mergeData = qof_book_mergeUpdateResult(mergeData, MERGE_UPDATE);
+ input_ok = TRUE;
+ break;
+ }
+ case 2 : {
+ if(rule->mergeAbsolute == FALSE) {
+ mergeData = qof_book_mergeUpdateResult(mergeData, MERGE_DUPLICATE);
+ }
+ if(rule->mergeAbsolute == TRUE) {
+ mergeData = qof_book_mergeUpdateResult(mergeData, MERGE_ABSOLUTE);
+ }
+ input_ok = TRUE;
+ break;
+ }
+ case 3 : {
+ if(rule->mergeAbsolute == FALSE) {
+ mergeData = qof_book_mergeUpdateResult(mergeData, MERGE_NEW);
+ input_ok = TRUE;
+ }
+ break;
+ }
+ case 9 : {
+ fprintf (stdout, _("Are you sure you want to abort the entire merge operation?\n"
+ "The rest of the import data will not be processed.\n"
+ "Your original data will not be modified. Abort? y/n : "));
+ scanf("%1s", &y);
+ if((safe_strcmp(_("y"),&y) == 0)||(safe_strcmp("",&y) == 0)) {
+ fprintf (stdout, _("Aborting . . \n\n"));
+ mergeData = qof_book_mergeUpdateResult(mergeData, MERGE_INVALID);
+ input_ok = TRUE;
+ }
+ break;
+ }
+ default : break;
+ }
+ }
+}
+
+/** \brief merge [filename]
+
+Accept a filename from the shell, check it is readable and
+load it, then if the book is valid, merge it into the
+current book.
+
+If the current book is NULL, this is equivalent to 'load' but
+a LOT more longwinded. (Not recommended.)
+*/
+static int merge_fcn(qof_data *context)
+{
+ qof_book_mergeData *mergeData;
+ QofBook *importBook, *targetBook;
+ gint result;
+
+ /** \todo check the filename can be read. */
+ if(!context->argv[1]) { return 0; }
+ qof_session_begin(context->export_session, context->argv[1], FALSE, FALSE);
+ qof_session_load(context->export_session, NULL);
+ qof_show_error(context->export_session, context->write_file);
+ importBook = qof_session_get_book(context->export_session);
+ targetBook = context->book;
+ mergeData = qof_book_mergeInit(importBook, targetBook);
+ g_return_val_if_fail((mergeData != NULL), -1);
+ qof_book_mergeRuleForeach(mergeData, qof_merge_loop, MERGE_REPORT);
+ result = qof_book_mergeCommit(mergeData);
+ return result;
+}
+
+/** check if book is dirty, show errors (if any) and exit */
+static int exit_fcn(qof_data *context)
+{
+ char* answer;
+
+ answer = g_strdup(" ");
+ if(qof_book_not_saved(context->book))
+ {
+ fprintf (stdout, _("The current book has not been saved.\n"
+ "Do you still want to quit without saving? %s"), "[y/n]");
+ scanf("%1s", answer);
+ fprintf (stdout, "\n");
+ if(0 != safe_strcmp(answer, "y") && (0 != safe_strcmp(answer, "Y"))) {
+ qof_session_save(context->input_session, NULL);
+ }
+ }
+ if(qof_book_not_saved(qof_session_get_book(context->export_session)))
+ {
+ qof_show_error(context->export_session, context->write_file);
+ }
+ qof_show_error(context->input_session, context->filename);
+ qof_session_end(context->input_session);
+ if(context->export_session) { qof_session_end(context->export_session); }
+ fprintf (stdout, _("\nThank you for using %s.\n"), PACKAGE);
+ return -1;
+}
+
+static int help_fcn(qof_data *context)
+{
+ fprintf (stdout, _("Commands available in the shell are:\n\n"));
+ fprintf (stdout, QOF_SHELL_FORMAT, "add [object]",
+ _("Add a new instance of the object.\n"));
+ fprintf (stdout, QOF_SHELL_FORMAT, "edit [object]",
+ _("Select one instance and edit (set) the parameter values.\n"));
+ fprintf (stdout, QOF_SHELL_FORMAT, "delete [object]",
+ _("Select one instance for deletion.\n"));
+ fprintf (stdout, QOF_SHELL_FORMAT, "list",
+ _("Synonym for the --list command outside the shell.\n"));
+ fprintf (stdout, QOF_SHELL_FORMAT, "print [object]",
+ _("Select one instance and print the parameter values.\n"));
+ fprintf (stdout, QOF_SHELL_FORMAT, "explain [object]",
+ _("Synonym for the --explain command outside the shell.\n"));
+ fprintf (stdout, QOF_SHELL_FORMAT, "load [filename]",
+ _("Replace the current book with data from the file.\n"));
+ fprintf (stdout, QOF_SHELL_FORMAT, " ",
+ _("Prompts to save the current book, if any.\n"));
+ fprintf (stdout, QOF_SHELL_FORMAT, "write [filename]",
+ _("Write out the current book. If no filename is given,\n"));
+ fprintf (stdout, QOF_SHELL_FORMAT, " ", _("write to the original file.\n"));
+ fprintf (stdout, QOF_SHELL_FORMAT, " ", _("(uses STDOUT if STDIN is used.)\n"));
+ fprintf (stdout, QOF_SHELL_FORMAT, "merge [filename]",
+ _("Merge data from the file into the current book.\n"));
+ fprintf (stdout, QOF_SHELL_FORMAT, "sql [sql_query]",
+ _("Run a \"quoted\" SQL statement.\n"));
+ fprintf (stdout, QOF_SHELL_FORMAT, "help | ?", _("This screen.\n"));
+ fprintf (stdout, QOF_SHELL_FORMAT, "quit | q | exit", _("Quit the shell\n"));
+ fprintf (stdout, "\n");
+ return 0;
+}
+
+/** \brief sql - take a SQL statement.
+
+\todo improve the sql_parser - it is very noisy and
+yet not very helpful.
+*/
+static int sql_fcn(qof_data *context)
+{
+ QofSqlQuery *q;
+ gchar *sql, *temp;
+ gint result;
+ QofQuery *qq;
+ QofBook *book;
+ GList *results;
+
+ q = qof_sql_query_new();
+ sql = g_strdup(context->sql_str);
+ if(!sql) { sql = context->argv[1]; }
+ qof_sql_query_parse(q, sql);
+ qq = qof_sql_query_get_query(q);
+ book = qof_session_get_book(context->input_session);
+ qof_query_set_book(qq, book);
+ qof_query_set_sort_order(qq, NULL, NULL, NULL);
+ results = qof_query_run (qq);
+ if(!results) { return 0; }
+ if(g_list_length(results) == 1) {
+ context->instance = (QofInstance*)results->data;
+ context->shell_type = EDIT_SHELL;
+ temp = g_strdup_printf("modify-%s", context->inst_type);
+ qof_book_start_operation(context->book, temp);
+ fprintf (stdout, _("Edit the parameter details of the selected instance.\n"));
+ fprintf (stdout, _("Type 'help' for available commands or parameters.\n\n"));
+ if(context->instance) { result = qof_sub_shell(context); }
+ context->shell_type = TOP_SHELL;
+ context->inst_type = NULL;
+ qof_book_end_operation(context->book);
+ return 0;
+ }
+ fprintf (stdout, _("Query returned %d entities\n"), g_list_length(results));
+ return 0;
+}
+
+/** @} */
+/** \name Edit shell functions
+@{
+*/
+
+/** \brief Set a value in a parameter
+
+\todo improve error handling - currently it is almost silent.
+
+Boolean values accept TRUE, 1, or translated values for Y, YES or TRUE
+and are matched case insensitively, so y, yes and true (and their translations)
+also match. If the match fails, false is set.
+
+*/
+static int set_edit_fcn (qof_data *context)
+{
+ QofParam *param;
+ QofType type;
+ gchar *value;
+
+ if(!context->instance || !context->argv[1] || !context->argv[2]) { return 0; }
+ value = g_strdup(context->argv[2]);
+ param = (QofParam*)qof_class_get_parameter(context->inst_type, context->argv[1]);
+ if(!param)
+ {
+ fprintf (stderr, _("%s: parameter name '%s' of object '%s' not recognised.\n"),
+ PACKAGE, context->argv[1], context->inst_type);
+ fprintf (stderr, _("Type 'explain' for more information.\n"));
+ return 0;
+ }
+ type = qof_class_get_parameter_type(context->inst_type, context->argv[1]);
+ qof_begin_edit(context->instance);
+ /* undo_edit_record will be called by begin_edit eventually. */
+ undo_edit_record(context->instance, param);
+ qof_entity_set_param(&context->instance->entity, param, value);
+ qof_commit_edit(context->instance);
+ undo_edit_commit(context->instance, param);
+ g_free(value);
+ return 0;
+}
+
+/** List the available parameters for this edit */
+static int explain_edit_fcn (qof_data *context)
+{
+ fprintf (stdout, _("\nEditable parameters of the %s object:\n\n"), context->inst_type);
+ qof_class_param_foreach(context->inst_type, qof_explain_cb, NULL);
+ fprintf (stdout, "\n\n");
+ return 0;
+}
+
+/** \brief Commit data to instance and leave the sub-shell.
+
+\todo Should this clear the undo??
+*/
+static int commit_edit_fcn (qof_data *context)
+{
+// if(qof_book_can_undo(context->book)) { qof_book_clear_undo(context->book); }
+ return -1;
+}
+
+/** print the instance being edited.*/
+static int print_edit_fcn (qof_data *context)
+{
+ if(!context->inst_type) { return 0; }
+ fprintf (stdout, "%-24s %-12s %s\n\n", _("Name"), _("Type"), _("Value"));
+ qof_class_param_foreach (context->inst_type, qof_print_cb, context);
+ fprintf (stdout, _("\nNot all parameters are editable, use 'explain' "
+ "to see the list of editable parameters for the object '%s'.\n\n"), context->inst_type);
+ return 0;
+}
+
+static int help_edit_fcn (qof_data *context)
+{
+ gchar *temp;
+
+ fprintf (stdout, _("Commands available in this sub-shell are:\n\n"));
+ fprintf (stdout, QOF_SHELL_FORMAT, "edit [Name] [Value]",
+ _("Edit the parameter 'Name' to have the 'Value' given.\n"));
+ /* Translators: TRUE and numeral one are always acceptable for boolean values here. */
+ fprintf (stdout, QOF_SHELL_FORMAT, " ",
+ _("Boolean values accept TRUE|true, 1, Y|y or yes|YES\n"));
+ fprintf (stdout, QOF_SHELL_FORMAT, " ", _("String values that include spaces should be quoted\n"));
+ fprintf (stdout, QOF_SHELL_FORMAT, "set [Name] [Value]", _("Synonym for edit.\n"));
+ temp = g_strdup_printf(_("Print the current values for this '%s' instance.\n"), context->inst_type);
+ fprintf (stdout, QOF_SHELL_FORMAT, "print", temp);
+ g_free(temp);
+ temp = g_strdup_printf(_("Show the editable parameters for '%s'\n"), context->inst_type);
+ fprintf (stdout, QOF_SHELL_FORMAT, "explain", temp);
+ g_free(temp);
+ fprintf (stdout, QOF_SHELL_FORMAT, "commit", _("Set the edited parameter values.\n"));
+ fprintf (stdout, QOF_SHELL_FORMAT, "help | ?", _("This screen.\n"));
+ fprintf (stdout, QOF_SHELL_FORMAT, "quit | q | exit",
+ _("Return to the top shell without setting changes.\n"));
+ fprintf (stdout, "\n");
+ return 0;
+}
+
+static int quit_edit_fcn (qof_data *context)
+{
+ if(qof_book_can_undo(context->book)) { qof_book_undo(context->book); }
+ return -1;
+}
+
+/** @} */
+/** \name Shell control handlers
+@{
+*/
+struct shell_cmd shell_list[] = {
+ { "add", add_fcn },
+ { "edit", edit_fcn },
+ { "delete", delete_fcn },
+ { "list", list_fcn },
+ { "print", print_fcn },
+ { "explain", explain_fcn },
+ { "load", load_fcn },
+ { "write", write_fcn },
+ { "merge", merge_fcn },
+ { "sql", sql_fcn },
+ { "help", help_fcn },
+ { "?", help_fcn },
+ { "q", exit_fcn },
+ { "quit", exit_fcn },
+ { "exit", exit_fcn },
+ { "bye", exit_fcn },
+ { NULL, NULL }
+};
+
+struct shell_cmd edit_list[] = {
+ { "edit", set_edit_fcn },
+ { "set", set_edit_fcn },
+ { "explain", explain_edit_fcn },
+ { "commit", commit_edit_fcn },
+ { "print", print_edit_fcn },
+ { "quit", quit_edit_fcn },
+ { "help", help_edit_fcn },
+ { "?", help_edit_fcn },
+ { "q", quit_edit_fcn },
+ { "exit", quit_edit_fcn },
+ { "bye", quit_edit_fcn },
+ { NULL, NULL }
+};
+
+static char *strtoke(char *str, const char *ws, const char *delim)
+{
+ int inc;
+ static char *start, *s = NULL;
+
+ if (str != NULL) { s = str; }
+ inc = strspn(s, ws);
+ s += inc;
+ start = s;
+ if (*s == '\0') { return NULL; }
+ else if (strchr(delim, *s) != NULL) {
+ start++;
+ s = strchr(s + 1, *s);
+ *s = '\0';
+ s++;
+ }
+ else {
+ inc = strcspn(s, ws);
+ if (s[inc] == '\0') { s += inc; }
+ else {
+ s[inc] = '\0';
+ s += inc + 1;
+ }
+ }
+ return start;
+}
+
+gboolean
+qof_check_sql(const char *sql)
+{
+ regex_t *r;
+ int reg_exp_check;
+ static char *pattern = QOF_SQL_SUPPORTED;
+// QofSqlQuery *q;
+ gboolean result;
+
+ result = FALSE;
+ r = g_new(regex_t, 1);
+ reg_exp_check = regcomp(r, pattern,
+ REG_ICASE | REG_NOSUB | REG_EXTENDED);
+ g_return_val_if_fail(reg_exp_check == 0, FALSE);
+ if(0 == regexec(r, sql, 0, NULL, 0)) { result = TRUE; }
+ regfree(r);
+ g_free(r);
+ return result;
+}
+
+/** \brief Relate the command to the function.
+
+\todo Stop changing the input with strtoke ?
+*/
+static int
+qof_parse_command (const char *cmd, qof_data *context)
+{
+ char *argv[32];
+ int inc, argc;
+ char *cmd_dup;
+ gboolean good;
+
+ argc = 0;
+ good = FALSE;
+ memset(argv, 0, sizeof(argv) / sizeof(char*));
+ cmd_dup = strdup(cmd);
+ argv[0] = strtoke(cmd_dup, " \t\n", "\"'");
+ while (argv[argc] != NULL) {
+ argc++;
+ argv[argc] = strtoke(NULL, " \t\n", "\"'");
+ }
+ if (argc == 0) {
+ free(cmd_dup);
+ return 0;
+ }
+ context->argc = argc;
+ context->argv = argv;
+ switch(context->shell_type)
+ {
+ case TOP_SHELL :
+ {
+ for (inc = 0; shell_list[inc].name != NULL; inc++) {
+ if (strcasecmp(argv[0], shell_list[inc].name) == 0) {
+ good = TRUE;
+ if((shell_list[inc].func(context)) < 0) { return -1; }
+ }
+ }
+ if(!good) {
+ fprintf(stderr, _("%s: bad option - %s, available commands are:\n"),
+ PACKAGE, argv[0]);
+ for (inc = 0; shell_list[inc].name != NULL; inc++) {
+ fprintf (stderr, "%s ", shell_list[inc].name);
+ }
+ fprintf (stderr, _("\nUse help for more information.\n\n"));
+ }
+ break;
+ }
+ case EDIT_SHELL :
+ {
+ for (inc = 0; edit_list[inc].name != NULL; inc++) {
+ if(strcasecmp(argv[0], edit_list[inc].name) == 0) {
+ good = TRUE;
+ if((edit_list[inc].func(context)) < 0) { return -1; }
+ }
+ }
+ if(!good) {
+ fprintf(stderr, _("%s: bad option - %s, available commands are:\n"),
+ PACKAGE, argv[0]);
+ for (inc = 0; edit_list[inc].name != NULL; inc++) {
+ fprintf (stderr, "%s ", edit_list[inc].name);
+ }
+ fprintf (stderr, _("\nUse help for more information.\n\n"));
+ }
+ break;
+ }
+ default : { break; }
+ }
+ free(cmd_dup);
+ return 0;
+}
+
+void
+qof_cmd_shell(qof_data *context)
+{
+ QofSession *input_session;
+#ifdef HAVE_LIBREADLINE
+ char *line;
+ char *prompt;
+
+ line = (char *)malloc(256*sizeof(char));
+ prompt = g_strdup_printf("%s> ", PACKAGE);
+#else
+ char buf[256];
+
+#endif
+ context->argc = 0;
+ context->argv = NULL;
+ input_session = context->input_session;
+ if(0 == safe_strcmp(context->exclude, context->database)
+ &&(context->exclude != NULL))
+ {
+ fprintf(stderr, _("%s: Error: Cannot exclude database \"%s\" with option -e\n"
+ " because option -d is set to the include the same database: \"%s\"\n"
+ "Use the \'-l\' command to see the full list of supported databases.\n"),
+ PACKAGE, context->exclude, context->database);
+ qof_session_end(input_session);
+ return;
+ }
+ if(context->filename) {
+ qof_session_begin(input_session, context->filename, TRUE, TRUE);
+ qof_session_load(input_session, NULL);
+ }
+ else { qof_session_begin(input_session, QOF_STDOUT, TRUE, FALSE); }
+ qof_show_error(input_session, context->filename);
+ context->book = qof_session_get_book(context->input_session);
+ context->shell_type = TOP_SHELL;
+ context->inst_type = NULL;
+ context->instance = NULL;
+ if(!context->shortname) { context->shortname = g_strndup(PACKAGE, 4); }
+ context->argc = 0;
+ fprintf (stdout, _("\nWelcome to the QOF interactive shell ...\n"));
+ fprintf (stdout, _(" Type 'help' for additional information\n\n"));
+
+ for (;;) {
+ fflush(stdout);
+#ifdef HAVE_LIBREADLINE
+ line = readline(prompt);
+ /* user pressed ^d or so */
+ if (line == NULL) {
+ fprintf(stdout, _("\n\nThank you for using %s.\n"), PACKAGE);
+ break;
+ }
+ /* skip blanks */
+ if (*line) { add_history(line); }
+ if(qof_parse_command(line, context) != 0) { break; }
+ free(line);
+#else
+ fprintf (stdout, "%s> ", PACKAGE);
+ if(qof_parse_command(buf, context) != 0) { break; }
+ if (fgets(buf, 256, stdin) == NULL) { break; }
+#endif
+ }
+ fprintf(stdout, "\n");
+}
+/** @} */
+
+qof_data*
+qof_create(void)
+{
+ qof_data *context;
+
+ context = g_new0(qof_data, 1);
+ return context;
+}
+
+/** Prints helpful error messages
+
+\todo make sure the file is always available, some
+errors still print (null).
+*/
+void
+qof_show_error(QofSession *session, const char *newfile)
+{
+ QofBackendError io_error;
+ gboolean uh_oh;
+ const char *fmt;
+
+ uh_oh = TRUE;
+ io_error = qof_session_get_error(session);
+ switch (io_error)
+ {
+ case ERR_BACKEND_NO_ERR : {
+ uh_oh = FALSE;
+ return;
+ }
+ case ERR_BACKEND_NO_HANDLER: {
+ fmt = _("%s: No suitable backend was found for %s.\n");
+ fprintf(stderr, fmt, PACKAGE, newfile);
+ break;
+ }
+ case ERR_BACKEND_NO_BACKEND: {
+ fmt = _("%s: The URL '%s' is not supported by this "
+ "version of %s.\n");
+ fprintf(stderr, fmt, PACKAGE, newfile, PACKAGE);
+ break;
+ }
+ case ERR_BACKEND_BAD_URL: {
+ fmt = _("%s: Cannot parse the URL '%s'\n");
+ fprintf(stderr, fmt, PACKAGE, newfile);
+ break;
+ }
+ case ERR_BACKEND_CANT_CONNECT: {
+ fmt = _("%s: Cannot connect to '%s'. "
+ "The host, username or password were incorrect.\n");
+ fprintf(stderr, fmt, PACKAGE, newfile);
+ break;
+ }
+ case ERR_BACKEND_CONN_LOST: {
+ fmt = _("%s: Cannot connect to '%s'. "
+ "Connection was lost, unable to send data.\n");
+ fprintf(stderr, fmt, PACKAGE, newfile);
+ break;
+ }
+ case ERR_BACKEND_TOO_NEW: {
+ fmt = _("%s: This file/URL appears to be from a newer "
+ "version of %s.\n");
+ fprintf (stderr, fmt, PACKAGE, PACKAGE);
+ break;
+ }
+ case ERR_BACKEND_NO_SUCH_DB: {
+ fmt = _("%s: The database '%s' does not seem to exist.\n");
+ fprintf (stderr, fmt, PACKAGE, newfile);
+ break;
+ }
+ case ERR_BACKEND_LOCKED: {
+ fmt = _("%s: Could not obtain the lock for '%s'.\n");
+ fprintf (stderr, fmt, PACKAGE, newfile);
+ break;
+ }
+ case ERR_BACKEND_READONLY: {
+ fmt = _("%s could not write to '%s'. "
+ "That database may be on a read-only file system, "
+ "or you may not have write permission for the directory.\n");
+ fprintf (stderr, fmt, PACKAGE, newfile);
+ break;
+ }
+ case ERR_BACKEND_DATA_CORRUPT: {
+ fmt = _("%s: The file/URL '%s' does not contain %s "
+ "data or the data is corrupt.\n");
+ fprintf (stderr, fmt, PACKAGE, newfile, PACKAGE);
+ break;
+ }
+ case ERR_BACKEND_SERVER_ERR: {
+ fmt = _("%s: The server at URL '%s' "
+ "experienced an error or encountered bad or corrupt data.\n");
+ fprintf (stderr, fmt, PACKAGE, newfile);
+ break;
+ }
+ case ERR_BACKEND_PERM: {
+ fmt = _("%s: You do not have permission to access '%s'.\n");
+ fprintf (stderr, fmt, PACKAGE, newfile);
+ break;
+ }
+ case ERR_BACKEND_MISC: {
+ fmt = _("%s: An error occurred while processing '%s'.\n");
+ fprintf (stderr, fmt, PACKAGE, newfile);
+ break;
+ }
+ /* QSF additions */
+ case ERR_QSF_INVALID_OBJ: {
+ fmt = _("%s: Invalid QSF Object file! The QSF object file '%s' "
+ " failed to validate against the QSF object schema. "
+ "The XML structure of the file is either not well-formed "
+ "or the file contains illegal data.\n");
+ fprintf (stderr, fmt, PACKAGE, newfile);
+ break;
+ }
+ case ERR_QSF_INVALID_MAP: {
+ fmt = _("%s: Invalid QSF Map file! The QSF map file "
+ "failed to validate against the QSF map schema. "
+ "The XML structure of the file is either not well-formed "
+ "or the file contains illegal data.\n");
+ fprintf (stderr, fmt, PACKAGE);
+ break;
+ }
+ case ERR_QSF_BAD_QOF_VERSION: {
+ fmt = _("%s: The QSF Map file '%s' was written for a different "
+ "version of QOF. It may need to be modified to work with "
+ "your current QOF installation.\n");
+ fprintf (stderr, fmt, PACKAGE, newfile);
+ break;
+ }
+ case ERR_QSF_BAD_MAP: {
+ fmt = _("%s: The selected QSF map '%s' contains unusable or missing data. "
+ "This is usually because not all the required parameters for "
+ "the defined objects have calculations described in the map.\n");
+ fprintf (stderr, fmt, PACKAGE, newfile);
+ break;
+ }
+ case ERR_QSF_BAD_OBJ_GUID: {
+ fmt = _("%s: The selected QSF object file '%s' contains one or "
+ "more invalid GUIDs. The file cannot be processed - "
+ "please check the source of the file and try again.\n");
+ fprintf (stderr, fmt, PACKAGE, newfile);
+ break;
+ }
+ case ERR_QSF_NO_MAP: {
+ fmt = _("%s: The selected QSF Object file '%s' requires a map"
+ "but it was not provided.\n");
+ fprintf (stderr, fmt, PACKAGE, newfile);
+ break;
+ }
+ case ERR_QSF_WRONG_MAP: {
+ fmt = _("%s: Wrong QSF map selected. The selected map, validates "
+ "but was written for different QOF objects. "
+ "The list of objects defined in this map does not include "
+ "all the objects described in the current QSF object file.\n");
+ fprintf (stderr, fmt, PACKAGE);
+ break;
+ }
+ case ERR_QSF_MAP_NOT_OBJ: {
+ fmt = _("%s: The selected file '%s' is a QSF map and cannot"
+ "be opened as a QSF object.\n");
+ fprintf (stderr, fmt, PACKAGE, newfile);
+ break;
+ }
+ case ERR_QSF_OVERFLOW : {
+ fmt = _("%s: When converting XML strings into numbers, an overflow "
+ "has been detected. The QSF object file '%s' contains invalid "
+ "data in a field that is meant to hold a number.\n");
+ fprintf (stderr, fmt, PACKAGE, newfile);
+ break;
+ }
+ case ERR_QSF_OPEN_NOT_MERGE : {
+ fmt = _("%s: The QSF object file '%s' should be merged, "
+ "not opened directly.\n");
+ fprintf (stderr, fmt, PACKAGE, newfile);
+ break;
+ }
+ case ERR_FILEIO_FILE_BAD_READ: {
+ fmt = _("%s: There was an error reading the file '%s'.\n");
+ fprintf (stderr, fmt, PACKAGE, newfile);
+ break;
+ }
+ case ERR_FILEIO_PARSE_ERROR: {
+ fmt = _("%s: There was an error parsing the file '%s'.\n");
+ fprintf (stderr, fmt, PACKAGE, newfile);
+ break;
+ }
+ case ERR_FILEIO_FILE_EMPTY: {
+ fmt = _("%s: The file '%s' is empty.\n");
+ fprintf (stderr, fmt, PACKAGE, newfile);
+ break;
+ }
+ case ERR_FILEIO_FILE_NOT_FOUND: {
+ fmt = _("%s: The file '%s' could not be found.\n");
+ fprintf (stderr, fmt, PACKAGE, newfile);
+ break;
+ }
+ case ERR_FILEIO_FILE_TOO_OLD: {
+ fmt = _("%s: This file is from an older version.\n");
+ fprintf (stderr, fmt, PACKAGE);
+ break;
+ }
+ case ERR_FILEIO_UNKNOWN_FILE_TYPE: {
+ fmt = _("%s: Unknown file type, '%s'.\n");
+ fprintf (stderr, fmt, PACKAGE, newfile);
+ break;
+ }
+ case ERR_FILEIO_BACKUP_ERROR: {
+ fmt = _("%s: Could not make a backup of '%s'.\n");
+ fprintf (stderr, fmt, PACKAGE, newfile);
+ break;
+ }
+ case ERR_FILEIO_WRITE_ERROR: {
+ fmt = _("%s: Could not write to '%s'. Check that you have "
+ "permission to write to this file and that there is sufficient "
+ "space to create it.\n");
+ fprintf (stderr, fmt, PACKAGE, newfile);
+ break;
+ }
+ case ERR_SQL_DB_TOO_OLD: {
+ fmt = _("%s: This database is from an older version.\n");
+ fprintf (stderr, fmt, PACKAGE);
+ break;
+ }
+ case ERR_SQL_DB_BUSY: {
+ fmt = _("%s: The SQL database is in use by other users.\n");
+ fprintf (stderr, fmt, PACKAGE);
+ break;
+ }
+ default:
+ fmt = _("%s: An unknown I/O error occurred.\n");
+ fprintf (stderr, fmt, PACKAGE);
+ break;
+ }
+ fprintf (stderr, "\n");
+}
+
+struct param_ref_list
+{
+ GSList *slist;
+ QofType param_type;
+ int i;
+};
+
+static void
+find_param_cb(QofParam *param, gpointer user_data)
+{
+ struct param_ref_list *b;
+ char *buf;
+
+ b = (struct param_ref_list*)user_data;
+ if((param->param_getfcn == NULL)||(param->param_setfcn == NULL)) { return; }
+ if(0 == safe_strcmp(b->param_type, param->param_type))
+ {
+ b->i++;
+ buf = g_strdup(param->param_name);
+ if(buf != NULL) {
+ b->slist = g_slist_append(b->slist, buf);
+ }
+ return;
+ }
+}
+
+GSList*
+qof_get_param_list(QofIdTypeConst object_type, QofType param_type)
+{
+ GSList *param_list;
+ char *i;
+ struct param_ref_list p;
+
+ param_list = NULL;
+ p.slist = NULL;
+ p.i = 0;
+ g_return_val_if_fail(object_type != NULL, NULL);
+ p.param_type = g_strdup(param_type);
+ qof_class_param_foreach(object_type, find_param_cb, &p);
+ param_list = g_slist_copy(p.slist);
+ i = g_strdup(object_type);
+ return param_list;
+}
+
+void
+qof_data_free(qof_data *data)
+{
+ g_free(data->filename);
+ g_free(data->write_file);
+ g_free(data->sql_file);
+ g_free(data->sql_str);
+ g_free(data->database);
+ g_free(data->shortname);
+}
+
+/** \name SQL handlers
+@{
+*/
+static QofQuery*
+qof_main_run_sql(qof_data *context)
+{
+ QofSqlQuery *q;
+ gchar *sql;
+ QofQuery *qq;
+
+ q = qof_sql_query_new();
+ sql = g_strdup(context->sql_str);
+ qof_sql_query_parse(q, sql);
+ qq = qof_sql_query_get_query(q);
+ return qq;
+}
+
+static void
+qof_main_run_query(QofQuery *qq, qof_data *context)
+{
+ QofBook *book;
+ GList *results;
+
+ g_return_if_fail(qq);
+ results = NULL;
+ book = qof_session_get_book(context->input_session);
+ qof_query_set_book(qq, book);
+ qof_query_set_sort_order(qq, NULL, NULL, NULL);
+ results = qof_query_run (qq);
+#ifdef PRINT_DEBUG
+ qof_query_print(qq);
+#endif
+ if(results == NULL) { return; }
+ if(results != NULL) {
+ qof_entity_copy_list(context->export_session, results);
+ }
+}
+
+/** \brief Assemble the components of the query.
+
+If any SQL statements are found, run
+separately from any -c, -d or -t options.
+
+All queries are additive: Successive queries add
+more entities to the result set but no entity is
+set more than once.
+*/
+static void
+qof_moderate_query(qof_data *context)
+{
+ Timespec min_ts;
+ Timespec max_ts;
+ QofQueryPredData *date_pred_data;
+// QofQueryPredData *category_pred;
+ QofQuery *q;
+ QofIdTypeConst find;
+// char *buf;
+ GSList *date_param_list, *category_param_list;
+ GList *f;
+ gboolean all;
+
+ all = TRUE;
+ q = qof_query_create();
+ date_param_list = NULL;
+ category_param_list = NULL;
+ for (f = context->sql_list; f ; f = context->sql_list->next)
+ {
+ context->sql_str = g_strdup(f->data);
+ q = qof_main_run_sql(context);
+ qof_main_run_query(q, context);
+ if(q) { qof_query_clear(q); }
+ g_free(context->sql_str);
+ all = FALSE;
+ }
+ if(0 < g_list_length(context->sql_list)) {
+ context->sql_str = NULL;
+ g_list_free(context->sql_list);
+ all = FALSE;
+ }
+ if(context->sql_str != NULL) {
+ q = qof_main_run_sql(context);
+ qof_main_run_query(q, context);
+ if(q) { qof_query_clear(q); }
+ all = FALSE;
+ }
+ if((context->database != NULL)&&(qof_class_is_registered(context->database)))
+ {
+ qof_query_search_for(q, context->database);
+ find = qof_query_get_search_for(q);
+ if(context->min_ts.tv_sec > 0) {
+ min_ts = context->min_ts;
+ max_ts = context->max_ts;
+ date_param_list = g_slist_copy(qof_get_param_list(find, QOF_TYPE_DATE));
+ if(!date_param_list) { qof_query_clear(q); return;}
+ date_pred_data = qof_query_date_predicate(QOF_COMPARE_GTE, QOF_DATE_MATCH_NORMAL, min_ts);
+ qof_query_add_term(q, date_param_list, date_pred_data, QOF_QUERY_AND);
+ date_param_list = qof_get_param_list(qof_query_get_search_for(q), QOF_TYPE_DATE);
+ date_pred_data = qof_query_date_predicate(QOF_COMPARE_LTE, QOF_DATE_MATCH_NORMAL, max_ts);
+ qof_query_add_term(q, date_param_list, date_pred_data, QOF_QUERY_AND);
+ }
+ qof_main_run_query(q, context);
+ if(q) { qof_query_clear(q); }
+ all = FALSE;
+ }
+ if(all == TRUE)
+ {
+ while(context->all_objects)
+ {
+ q = qof_query_create_for(context->all_objects->data);
+ find = qof_query_get_search_for(q);
+ if(context->min_ts.tv_sec > 0) {
+ min_ts = context->min_ts;
+ max_ts = context->max_ts;
+ date_param_list = g_slist_copy(qof_get_param_list(find, QOF_TYPE_DATE));
+ if(!date_param_list) {
+ if(q) { qof_query_clear(q); }
+ context->all_objects = context->all_objects->next;
+ continue;
+ }
+ date_pred_data = qof_query_date_predicate(QOF_COMPARE_GTE, QOF_DATE_MATCH_NORMAL, min_ts);
+ qof_query_add_term(q, date_param_list, date_pred_data, QOF_QUERY_AND);
+ date_param_list = qof_get_param_list(qof_query_get_search_for(q), QOF_TYPE_DATE);
+ date_pred_data = qof_query_date_predicate(QOF_COMPARE_LTE, QOF_DATE_MATCH_NORMAL, max_ts);
+ qof_query_add_term(q, date_param_list, date_pred_data, QOF_QUERY_AND);
+ }
+ qof_main_run_query(q, context);
+ if(q) { qof_query_clear(q); }
+ context->all_objects = context->all_objects->next;
+ }
+ }
+}
+/** @} */
+
+static void
+print_config_cb (QofBackendOption *option, gpointer data)
+{
+ fprintf (stdout, "option name=%s\n", option->option_name);
+ fprintf (stdout, "option desc=%s\n", option->description);
+ fprintf (stdout, "option tip =%s\n", option->tooltip);
+ switch(option->type) {
+ case KVP_TYPE_GINT64 : {
+ fprintf (stdout, "option value=%" G_GINT64_FORMAT,
+ *(gint64*)option->value);
+ fprintf (stdout, "\noption type=%s\n", QOF_TYPE_INT64);
+ break;
+ }
+ case KVP_TYPE_DOUBLE : {
+ break;
+ }
+ case KVP_TYPE_NUMERIC : {
+ break;
+ }
+ case KVP_TYPE_STRING : {
+ fprintf (stdout, "option value=%s\n", (char*)option->value);
+ fprintf (stdout, "option type=%s\n", QOF_TYPE_STRING);
+ break;
+ }
+ case KVP_TYPE_GUID : { break; } /* unsupported */
+ case KVP_TYPE_TIMESPEC : {
+ break;
+ }
+ case KVP_TYPE_BINARY : { break; } /* unsupported */
+ case KVP_TYPE_GLIST : { break; } /* unsupported */
+ case KVP_TYPE_FRAME : { break; } /* unsupported */
+ }
+}
+
+void
+qof_cmd_offline (qof_data *context)
+{
+ QofSession *input_session, *export_session;
+ gchar current_work[PATH_MAX];
+ gchar *temp;
+ QofBackend *be;
+ KvpFrame *backend_config;
+
+ backend_config = NULL;
+ input_session = context->input_session;
+ if(0 == safe_strcmp(context->exclude, context->database)
+ &&(context->exclude != NULL))
+ {
+ fprintf(stderr, _("%s: Error: Cannot exclude database \"%s\" with option -e\n"
+ " because option -d is set to the include the same database: \"%s\"\n"
+ "Use the \'-l\' command to see the full list of supported databases.\n"),
+ PACKAGE, context->exclude, context->database);
+ qof_session_end(input_session);
+ return;
+ }
+ qof_session_begin(input_session, context->filename, FALSE, TRUE);
+ qof_session_load(input_session, NULL);
+ if(ERR_BACKEND_LOCKED == qof_session_get_error(input_session))
+ {
+ /** \todo ask the user if it is OK to ignore the lock. */
+ qof_session_begin(input_session, context->filename, TRUE, FALSE);
+ qof_session_load(input_session, NULL);
+ }
+ context->book = qof_session_get_book(input_session);
+ be = qof_book_get_backend(context->book);
+ backend_config = qof_backend_get_config(be);
+ PINFO (" trying to get backend config");
+ if(backend_config)
+ {
+ qof_backend_option_foreach(backend_config,
+ print_config_cb, context);
+ }
+ else { PINFO (" failed"); }
+ export_session = qof_session_new();
+ context->export_session = export_session;
+ if(context->write_file != NULL) {
+ if(*context->write_file != '/')
+ {
+ getcwd(current_work, PATH_MAX);
+ temp = g_strconcat(current_work, "/", context->write_file, NULL);
+ context->write_file = temp;
+ }
+ qof_session_begin(export_session, context->write_file, FALSE, TRUE);
+ }
+ else { qof_session_begin(export_session, QOF_STDOUT, TRUE, FALSE); }
+ qof_session_set_current_session(input_session);
+ qof_moderate_query(context);
+ qof_session_save(export_session, NULL);
+ qof_show_error(export_session, context->write_file);
+ qof_show_error(input_session, context->filename);
+ qof_session_end(input_session);
+ qof_session_end(export_session);
+}
+
+
+void
+qof_cmd_list (void)
+{
+ fprintf(stdout, _("\n%s currently supports these database names:\n"
+ "You can use the names with %s -d\n"
+ "and in SQL queries (as the table name) with %s -s|f\n"
+ "Descriptions are shown only for readability.\n\n"
+ "Name Description\n\n"
+ )
+ , PACKAGE, PACKAGE, PACKAGE);
+ qof_object_foreach_type(qof_list_cb, NULL);
+ fprintf(stdout, _("\nUse '-d <database> --explain' to see the list of fields within\n"
+ "any supported database.\n"));
+ fprintf(stdout, _("\nThank you for using %s\n\n"), PACKAGE);
+}
+
+void
+qof_select_all(QofObject *obj, gpointer data)
+{
+ qof_data *context;
+ char* type;
+
+ context = (qof_data*)data;
+ type = g_strdup(obj->e_type);
+ if(!qof_class_is_registered(type)) { return; }
+ context->all_objects = g_list_prepend(context->all_objects, type);
+}
+
+void
+qof_cmd_explain (gpointer user_data)
+{
+ qof_data *context;
+
+ context = (qof_data*)user_data;
+ if(context->error) { return; }
+ fprintf(stdout, _("\nParameters of the %s database:\n\n"), context->database);
+ qof_class_param_foreach(context->database, qof_explain_cb, NULL);
+ fprintf(stdout, _("\nThank you for using %s\n\n"), PACKAGE);
+}
+
+void
+qof_mod_database (const char *database, qof_data *data)
+{
+ if(qof_class_is_registered(database)) {
+ data->database = g_strdup(database);
+ }
+}
+
+void
+qof_mod_timespec (const char *date_time, qof_data *data)
+{
+ gchar *temp;
+ int year, month, day;
+ gboolean takemonth, takeyear, scanned;
+ char *first_field, *second_field, *third_field;
+ static char *delims = ".,-+/\\() ";
+
+ takemonth = takeyear = scanned = FALSE;
+ day = month = year = 0;
+ second_field = "";
+ third_field = "";
+ temp = g_strdup(date_time);
+ qof_date_format_set(QOF_DATE_FORMAT_UTC);
+ scanned = qof_scan_date(temp, &day, &month, &year);
+ if(scanned == FALSE)
+ {
+ first_field = strtok (temp, delims);
+ if (first_field)
+ {
+ second_field = strtok (NULL, delims);
+ if (second_field)
+ {
+ third_field = strtok (NULL, delims);
+ }
+ }
+ if (third_field && second_field)
+ {
+ year = atoi(first_field);
+ month = atoi(second_field);
+ day = atoi(third_field);
+ } else if (second_field)
+ {
+ year = atoi(first_field);
+ month = atoi(second_field);
+ takemonth = TRUE;
+ } else if (first_field)
+ {
+ year = atoi(first_field);
+ takeyear = TRUE;
+ }
+ }
+ if(takemonth) { day = 1; }
+ if(takeyear) { day = 1; month = 1; }
+ data->min_ts = gnc_dmy2timespec(day, month, year);
+ if(takemonth) { day = gnc_date_my_last_mday(month, year); }
+ if(takeyear) {
+ month = 12;
+ day = gnc_date_my_last_mday(month, year);
+ }
+ data->max_ts = gnc_dmy2timespec_end(day, month, year);
+}
+
+void
+qof_mod_exclude (const char *exclude, qof_data *data)
+{
+ if(qof_class_is_registered(exclude)) {
+ data->exclude = g_strdup(exclude);
+ }
+}
+
+void
+qof_mod_sql (const char *sql_query, qof_data *data)
+{
+ if(!qof_check_sql(sql_query)) { return; }
+ data->sql_str = g_strdup(sql_query);
+}
+
+void
+qof_mod_sql_file (const char *sql_file, qof_data *data)
+{
+ FILE *filehandle;
+#ifndef HAVE_GETLINE
+ char lineptr[1024];
+#else
+ char *lineptr;
+#endif
+ char *buf;
+ size_t n;
+ QofQuery *q;
+ struct stat sbuf;
+
+ data->sql_file = g_strdup(sql_file);
+ n = 0;
+ q = NULL;
+ data->sql_list = NULL;
+ if (stat(sql_file, &sbuf) <0) {
+ fprintf(stderr,"%s: ERROR. Unable to open %s (%s)\n\n",
+ PACKAGE, sql_file, strerror(errno));
+ return;
+ }
+ filehandle = fopen(sql_file, "r");
+#ifndef HAVE_GETLINE
+ while (NULL != (fgets(lineptr, sizeof(lineptr), filehandle)))
+#else
+ lineptr = NULL;
+ while (0 < getline(&lineptr, &n, filehandle))
+#endif
+ {
+ if(!qof_check_sql(lineptr)) { continue; }
+ if(0 == safe_strcmp(lineptr, "\n")) { continue; }
+ buf = g_strdup(lineptr);
+ data->sql_list = g_list_append(data->sql_list, buf);
+ }
+
+ fclose(filehandle);
+}
+
+void
+qof_mod_write (const char *write_file, qof_data *data)
+{
+ data->write_file = g_strdup(write_file);
+}
+
+void extensions_init(void)
+{
+ backend_extensions = g_hash_table_new(g_str_hash, g_str_equal);
+}
+
+void qof_backend_extension_add(char *IDstring, gpointer data)
+{
+ g_hash_table_insert(backend_extensions, IDstring, data);
+}
+
+gpointer qof_backend_extension(const char* IDstring)
+{
+ gpointer func;
+
+ func = g_hash_table_lookup(backend_extensions, IDstring);
+ if(func) { return func; }
+ return NULL;
+}
+
+/** @} */
+/** @} */
Added: gnucash/branches/cashutil/cashutil/src/qof-main.h
===================================================================
--- gnucash/branches/cashutil/cashutil/src/qof-main.h 2005-11-03 10:39:45 UTC (rev 11800)
+++ gnucash/branches/cashutil/cashutil/src/qof-main.h 2005-11-03 10:52:51 UTC (rev 11801)
@@ -0,0 +1,459 @@
+/***************************************************************************
+ * qof-main.h
+ *
+ * Thu Jan 13 12:15:41 2005
+ * Copyright 2005 Neil Williams
+ * linux at 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+/** @addtogroup QOFCLI Query Object Framework Command Line Interface and shell.
+
+The CLI uses a top level shell and two sub shells. The top level shell provides
+general interactivity with object types, entire books and SQL support.
+
+The select sub-shell identifies a single instance of an object. e.g. the print
+command in the top shell uses the select subshell to locate the instance to be
+printed and the delete command, the instance to be deleted. These commands then
+return to the top shell.
+
+The edit sub-shell allows data to be set in a selected instance. The edit command
+uses a select sub-shell to identify the instance then changes to an edit sub-shell
+to handle setting the individual parameters of that instance. Before returning to the
+top shell, the edited data can be saved to the instance using 'commit' or the edit can
+be aborted using 'quit'.
+
+The add command creates a new instance and passes that instance, already selected,
+to the edit sub-shell for data entry.
+
+\note CashUtil relies on installed versions of the QOF, libcashobjects and
+libgnc-backend-file libraries. If you change any source files for these libraries,
+ensure you run 'make install' rather than just 'make' or your changes will have no
+effect. There is no support for loading local or 'test' versions of the libraries.
+You can usually run cashutil against a freshly installed set of QOF libraries without
+recompiling cashutil, depending on the level of changes in QOF.
+
+@{
+*/
+/** @file qof-main.h
+ @brief Common functions for the QOF external framework
+ @author Copyright (c) 2005 Neil Williams <linux at codehelp.co.uk>
+*/
+
+#define _GNU_SOURCE
+#include "config.h"
+#ifndef _QOF_MAIN_H
+#define _QOF_MAIN_H
+#include <popt.h>
+#include "qof.h"
+#include "qofundo.h"
+
+#if defined(HAVE_GETTEXT) /* HAVE_GETTEXT */
+
+#include <libintl.h>
+#include <locale.h>
+
+#undef _
+#undef Q_
+
+#ifdef DISABLE_GETTEXT_UNDERSCORE
+#define _(String) (String)
+#define Q_(String) gnc_qualifier_prefix_noop(String)
+#else /* ENABLE_GETTEXT_UNDERSCORE */
+#define _(String) gettext(String)
+#define Q_(String) gnc_qualifier_prefix_gettext(String)
+#endif /* End ENABLE_GETTEXT_UNDERSCORE */
+
+#else /* Not HAVE_GETTEXT */
+#if !defined(__USE_GNU_GETTEXT)
+
+#undef _
+#undef Q_
+#define _(String) (String)
+#define Q_(String) gnc_qualifier_prefix_noop(String)
+#define gettext(String) (String)
+#define ngettext(msgid, msgid_plural, n) (((n)==1) ? \
+ (msgid) : (msgid_plural))
+
+#endif /* End not__USE_GNU_GETTEXT */
+#endif /* End Not HAVE_GETTEXT */
+
+#undef N_
+#define N_(String) (String)
+
+/** gnc file backend library name */
+#define GNC_LIB_NAME "libgnc-backend-file.la"
+/** init_fcn for gnc file backend library. */
+#define GNC_LIB_INIT "gnc_provider_init"
+
+#define CU_MOD_ENGINE "cashutil-engine"
+
+gpointer qof_backend_extension(const char* IDstring);
+
+/** \name Control functions.
+@{
+*/
+/** \brief List of all parameters for this object of one type.
+
+Return a GSList of all parameters of this object that are a
+particular QOF type, QOF_TYPE_STRING, QOF_TYPE_BOOLEAN etc.
+
+The returned GSList should be freed by the caller.
+
+\note The return list is a singly linked list - GSList -
+\b not the doubly-linked list - GList - returned by
+::qof_class_get_referenceList.
+
+\param object_type object->e_type for the relevant object.
+\param param_type The type of parameter to match, QOF_TYPE_STRING etc.
+
+\return GSList of all matching parameters or NULL if none exist.
+*/
+GSList*
+qof_get_param_list(QofIdTypeConst object_type, QofType param_type);
+
+#define QOF_DATE_STRING_LENGTH 31 /**< Inherited from QSF */
+#define QOF_UTC_DATE_FORMAT "%Y-%m-%dT%H:%M:%SZ" /**< Inherited from QSF */
+
+/** \brief The SQL commands supported by QOF
+
+A regular expression used to exclude unsupported commands
+from SQL files. Anything that does \b not match the expression
+will be silently ignored by cashutil. This allows genuine
+SQL dump files to be parsed by cashutil without errors.
+
+ A QOF object is similar to a definition of a SQL table.\n
+ A QOF entity is similar to an instance of a SQL record.\n
+ A QOF parameter is similar to data in a SQL field.
+
+Certain SQL commands have no QOF equivalent and should
+always be ignored silently:
+ - ALTER (the object parameters cannot be changed at runtime)
+ - CREATE (new tables - new objects - cannot be created at runtime)
+ - DROP (an object cannot be "de-registered" without re-compiling)
+ - FLUSH (QOF has no permissions system)
+ - GRANT
+ - KILL
+ - LOCK
+ - OPTIMIZE
+ - REVOKE
+ - USE (QOF only has one database, itself.)
+*/
+#define QOF_SQL_SUPPORTED "^SELECT|INSERT"
+
+/** Indent and pad the shell output nicely.*/
+#define QOF_SHELL_FORMAT " %-30s%s"
+
+/** \brief Common QOF CLI options
+
+ * These are definitions for popt support in the CLI. Every program's
+ * popt table should start with QOF_CLI_OPTIONS to insert
+ * the standard options into it. Also enables autohelp.
+ */
+#define QOF_CLI_OPTIONS POPT_AUTOHELP \
+ {"list", 'l', POPT_ARG_NONE, NULL, qof_op_list, \
+ _("List all databases supported by the current QOF framework and exit."), \
+ NULL}, \
+ {"explain", 0, POPT_ARG_NONE, NULL, qof_op_explain, \
+ _("List the fields within the specified database and exit, requires -d."), \
+ NULL}, \
+ {"date", 't', POPT_ARG_STRING, &date_time, qof_op_timespec, \
+ _("Shorthand to only query objects that contain the specified date."), \
+ "string"}, \
+ {"database", 'd', POPT_ARG_STRING, &database, qof_op_database, \
+ _("Shorthand to only query objects within a specific supported database. "), \
+ "string"}, \
+ {"exclude", 'e', POPT_ARG_STRING, &exclude, qof_op_exclude, \
+ _("Shorthand to exclude a supported database from the query."), \
+ "string"}, \
+ {"sql", 's', POPT_ARG_STRING, &sql_query, qof_op_sql, \
+ _("Specify a SQL query on the command line."), "string"}, \
+ {"sql-file", 'f', POPT_ARG_STRING, &sql_file, qof_op_sql_file, \
+ _("Specify one or more SQL queries contained in a file."), \
+ "filename"}, \
+ {"write", 'w', POPT_ARG_STRING, &write_file, qof_op_write, \
+ _("Write the results of any query to the file"), "filename"}, \
+ {"compress", 0, POPT_ARG_INT, &gz_level, qof_op_compress, \
+ _("Compress output files, 0 for none, 9 for maximum"), "integer"}, \
+ {"debug", 0, POPT_ARG_NONE, NULL, qof_op_debug, \
+ _("Print debugging information to a temporary file."), NULL}, \
+ {"shell", 0, POPT_ARG_NONE, NULL, qof_op_shell, \
+ _("Enter the QOF interactive shell"), NULL}, \
+ {"version", 0, POPT_ARG_NONE, NULL, qof_op_vers, \
+ _("Display version information"), NULL},
+
+/** \brief Output error messages from QOF
+
+QOF will set errors in the QofSession. The
+application determines how to output those
+messages and for CLI programw, this will be to
+stderr. Some of these error messages are not used in
+all CLI programs.
+*/
+void qof_show_error(QofSession *session, const char *file);
+
+/** \brief Handle the type of each subshell. */
+typedef enum {
+ NO_SHELL,
+ TOP_SHELL, /**< the first, top level shell. */
+ EDIT_SHELL, /**< Edit the selected instance */
+}qof_subshell;
+
+typedef enum {
+ NO_OP,
+ PRINT_MODE,
+ DELETE_MODE,
+ EDIT_MODE,
+}qof_cli_mode;
+
+/** \brief The QOF CLI context struct */
+typedef struct qofdata_s {
+ gchar *filename; /**< Input filename containing QSF XML, if any.*/
+ gchar *write_file; /**< Export filename, if any.*/
+ gchar *sql_file; /**< SQL file, if any. */
+ gchar *sql_str; /**< The current SQL, overwritten each iteration if using a file.*/
+ gchar *database; /**< The database to include with -d. */
+ gchar *exclude; /**< The database to exclude with -e. */
+ gchar *shortname; /**< A shortname for this program if truncation to first 4 characters is not suitable. */
+ Timespec min_ts; /**< Matches objects above minimum time_t value. */
+ Timespec max_ts; /**< Matches objects below maximum time_t value. */
+ QofSession *input_session; /**< The input session. */
+ QofSession *export_session; /**< The query results session, for STDOUT or -w. */
+ gboolean error; /**< general error, abort. */
+ GList *all_objects; /**< List of all supported databases. */
+ GList *sql_list; /**< List of sql commands from a file. */
+ int argc; /**< Shell copy of argc */
+ char **argv; /**< Shell copy of commands */
+ QofBook *book; /**< the current book for the shell function. */
+ qof_subshell shell_type; /**< the type of subshell, top or edit */
+ qof_cli_mode cli_mode; /**< current operation mode. */
+ QofIdTypeConst inst_type; /**< The current registered QofObject type. */
+ QofInstance *instance; /**< The currently selected instance. */
+ gint counter;
+ GHashTable *select_table;
+ gint gz_level;
+}qof_data;
+
+/** \brief Register all QOF objects.
+
+If new objects are added, call the register func()
+here.
+
+qof_init must be called by any program wanting to
+use the QOF framework with GnuCash objects.
+
+\return A usable qof_data* context.
+*/
+void qof_init (void);
+
+/** \brief initialise the QOF CLI context.
+
+All QOF CLI programs must create a context.
+*/
+qof_data* qof_create(void);
+
+/** \brief Shutdown the QOF framework
+*/
+void qof_close(void);
+
+/* \brief Clear up the qof_data context */
+void qof_data_free(qof_data *data);
+
+/** \brief Check that the SQL command is supported.*/
+gboolean qof_check_sql(const char *sql);
+
+/** \enum qof_op_type
+
+main operator enum
+*/
+/** \enum qof_op_type::qof_op_noop
+
+undefined check value
+*/
+/**\enum qof_op_type::qof_op_input
+
+execute input command
+*/
+/** \enum qof_op_type::qof_op_list
+
+List supported databases command.
+*/
+
+/** \brief command line command options.*/
+typedef enum {
+ qof_op_noop = 0,
+ qof_op_input,
+ qof_op_list,
+ qof_op_shell,
+ qof_op_vers,
+ qof_op_database,
+ qof_op_timespec,
+ qof_op_exclude,
+ qof_op_sql,
+ qof_op_sql_file,
+ qof_op_write,
+ qof_op_explain,
+ qof_op_compress,
+ qof_op_debug
+}qof_op_type;
+
+/** \brief Build a list of all available objects */
+void qof_select_all(QofObject *obj, gpointer data);
+
+/** @} */
+/** @name Command handlers.
+@{
+*/
+
+/** \brief load the QOF interactive shell
+
+Where available, uses READLINE to store a history of
+previous shell commands.
+*/
+void qof_cmd_shell(qof_data *context);
+
+/** \brief List each parameter for the selected object. */
+void qof_cmd_explain (gpointer user_data);
+
+/** \brief List the supported databases.
+
+Uses a callback to ::qof_class_is_registered.
+*/
+void qof_cmd_list (void);
+
+/** \brief query a QSF XML file
+
+Query the QSF XML in <filename>.
+*/
+void qof_cmd_offline (qof_data *context);
+
+/** \brief Lists all databases supported by the current QOF framework.
+
+Prints the name and description for each object type
+registered with this instance of QOF. No options are used.
+*/
+void qof_cmd_list (void);
+/** @} */
+
+/** @name Command modulators.
+@{
+*/
+
+/** \brief Shorthand to only query objects within one specific supported database.
+
+Used to only query objects within the specified
+database. In a hotsync, all other supported databases are skipped
+and data is only read from the named database. Without a HotSync (using
+offline storage), only objects of this type are queried.
+*/
+void qof_mod_database (const char *database, qof_data *data);
+
+/** \brief Shorthand to only query objects that contain the specified date.
+
+Used to modify the QOF query to only query objects that contain
+at least one parameter containing a QOF_TYPE_DATE that
+matches the range specified. Dates need to be specified as YY-MM-DD.
+
+You can specify a UTC timestring, just as normally output by QSF,
+but the time will not be matched when using the shorthand option,
+only the year, month and day.
+
+For more precise time matches or to set a defined period that doesn't follow
+whole calendar months, (e.g. the UK financial year) use a SQL statement:
+
+Partial matches are allowed, so YY-MM matches
+any object where a date is within the specified month and year,
+YY matches any object where a date is within the specified year.
+
+The query range starts at midnight on the first day of the range
+and ends at 1 second to midnight on the last day of the range.
+*/
+void qof_mod_timespec (const char *date_time, qof_data *data);
+
+/** \brief Shorthand to exclude a supported database from the query.
+
+Excludes the (single) specified database from the query.
+During a hotsync, data in that database is not read from the Palm.
+When working offline, the objects of that type are not queried.
+*/
+void qof_mod_exclude (const char *exclude, qof_data *data);
+
+/** \brief Specify a SQL query on the command line.
+
+For SELECT, the returned list is a list of all of the instances of 'SomeObj' that
+match the query. The 'SORT' term is optional. The 'WHERE' term is optional; but
+if you don't include 'WHERE', you will get a list of all of the object instances.
+The Boolean operations 'AND' and 'OR' together with parenthesis can be used to construct
+arbitrarily nested predicates.
+
+For INSERT, the returned list is a list containing the newly created instance of 'SomeObj'.
+
+Date queries handle full date and time strings, using the format exported by the QSF
+backend. To query dates and times, convert user input into UTC time using the
+QOF_UTC_DATE_FORMAT string. e.g. set the UTC date format and call qof_print_time_buff
+with a time_t obtained via timespecToTime_t.
+
+If the param is a KVP frame, then we use a special markup to indicate frame values.
+The markup should look like /some/kvp/path:value. Thus, for example,\n
+SELECT * FROM SomeObj WHERE (param_a < '/some/kvp:10.0')\n
+will search for the object where param_a is a KVP frame, and this KVP frame contains
+a path '/some/kvp' and the value stored at that path is floating-point and that float
+value is less than 10.0.
+
+ at param sql_query Examples:
+
+"select * from gncAddress"
+
+*/
+void qof_mod_sql (const char *sql_query, qof_data *data);
+
+/** \brief Specify one or more SQL queries contained in a file.
+
+The rules for single SQL commands also apply with regard to the lack of explicit
+support for joins and the pending support for selecting only certain parameters
+from a certain object.
+
+See ::qof_mod_sql for information on the queries supported.
+
+\note Where possible, this function uses the safer GNU extension: getline().
+On Mac OSX and other platforms that do not provide getline, the call uses
+the less reliable fgets(). If the input file contains a NULL, fgets will
+get confused and the read may terminate early on such platforms.\n
+http://www.gnu.org/software/libc/manual/html_node/Line-Input.html
+
+*/
+void qof_mod_sql_file (const char *sql_file, qof_data *data);
+
+/** \brief Write the results of any query to the file
+
+Sets the \a filename of the file to be written out using
+the QSF XML QofBackend.
+
+*/
+void qof_mod_write (const char *write_file, qof_data *data);
+
+void extensions_init(void);
+
+void qof_backend_extension_add(char *IDstring, gpointer data);
+
+gpointer qof_backend_extension(const char* IDstring);
+
+
+/** @} */
+/** @} */
+
+#endif /* _QOF_MAIN_H */
Added: gnucash/branches/cashutil/cashutil/src/qofundo-p.h
===================================================================
--- gnucash/branches/cashutil/cashutil/src/qofundo-p.h 2005-11-03 10:39:45 UTC (rev 11800)
+++ gnucash/branches/cashutil/cashutil/src/qofundo-p.h 2005-11-03 10:52:51 UTC (rev 11801)
@@ -0,0 +1,79 @@
+/***************************************************************************
+ * qofundo-p.h
+ *
+ * Thu Aug 25 09:20:14 2005
+ * Copyright 2005 Neil Williams
+ * linux at 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#ifndef _QOFUNDO_P_H
+#define _QOFUNDO_P_H
+
+#include "qof.h"
+#include "qofundo.h"
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+/* Undo is limited, not infinite. */
+#define MAX_UNDO_LENGTH 300
+
+/* Free the entire undo list for this book. */
+void qof_book_clear_undo(QofBook *book);
+
+/* reads the data from this parameter to allow undo
+
+To be able to undo and then redo an action, QOF needs to know the
+before and after states. Initially, the before state is the same as
+the file but after that point, the state of the entity needs to be
+tracked whenever it is opened for editing.
+*/
+qof_undo_entity* qof_prepare_undo (QofEntity *ent, QofParam *param);
+
+/* Add the changes to be undone to the event.
+
+Designed to be used with g_list_foreach, simply adds
+any number of undo_entity pointers (representing the
+entity changes relating to this event) to the list
+of changes for this event.
+*/
+void qof_undo_new_entry(gpointer event, gpointer changes);
+
+/* Add an undo event to the list.
+
+type holds the type of event that has just occurred.
+
+If the event follows a successful qof_commit_edit, then the
+cached undo_entity changes are placed into this undo_event.
+*/
+qof_undo_operation* qof_undo_new_operation(char* label);
+
+/* dummy routines for testing only */
+void undo_edit_record(QofInstance *inst, QofParam *param);
+void undo_edit_commit(QofInstance *inst, QofParam *param);
+void undo_create_record(QofInstance *inst);
+void undo_delete_record(QofInstance *inst);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _QOFUNDO_P_H */
Added: gnucash/branches/cashutil/cashutil/src/qofundo.c
===================================================================
--- gnucash/branches/cashutil/cashutil/src/qofundo.c 2005-11-03 10:39:45 UTC (rev 11800)
+++ gnucash/branches/cashutil/cashutil/src/qofundo.c 2005-11-03 10:52:51 UTC (rev 11801)
@@ -0,0 +1,473 @@
+/***************************************************************************
+ * qofundo.c
+ *
+ * Thu Aug 25 09:19:17 2005
+ * Copyright 2005 Neil Williams
+ * linux at 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <libintl.h>
+#include <locale.h>
+#include <errno.h>
+#include "qofundo-p.h"
+#include "qofundo.h"
+/* for gettext support - change this later. */
+#include "qof-main.h"
+
+/* the undo list itself
+ definition will later be placed within the QofBook opaque struct
+ GList of qof_undo_event*
+ */
+static GList *book_undo;
+/* list of undo_entity, pending commit */
+static GList *undo_cache;
+static gchar* undo_label;
+/* the current position within the undo list within this book */
+static gint index_position;
+static gboolean undo_operation_open = FALSE;
+
+typedef enum
+{
+ UNDO_NOOP = 0,
+ UNDO_CREATE,
+ UNDO_DELETE,
+ UNDO_MODIFY
+}undo_action;
+
+struct qof_undo_entity_t
+{
+ QofParam *param; /* static anyway so only store a pointer */
+ const GUID *guid; /* enable re-creation of this entity */
+ QofIdType type; /* ditto param, static. */
+ char *value; /* cached string? */
+ char *path; /* for KVP */
+ QofIdType choice; /* For QOF_TYPE_CHOICE */
+ undo_action how; /* how to act on the undo */
+};
+
+struct qof_undo_operation_t
+{
+ const char* label;
+ Timespec ts;
+ GList *entity_list; /* GList of qof_undo_entity* */
+};
+
+void
+qof_entity_set_param(QofEntity *ent, QofParam *param, char *value)
+{
+ gchar *tail;
+ gnc_numeric cli_numeric;
+ gboolean cli_bool;
+ gint32 cli_i32;
+ gint64 cli_i64;
+ Timespec cli_date;
+ GUID *cm_guid;
+ struct tm cli_time;
+ time_t cli_time_t;
+ const char *fmt;
+ void (*string_setter) (QofEntity*, char*);
+ void (*date_setter) (QofEntity*, Timespec);
+ void (*i32_setter) (QofEntity*, gint32);
+ void (*i64_setter) (QofEntity*, gint64);
+ void (*numeric_setter) (QofEntity*, gnc_numeric);
+ void (*boolean_setter) (QofEntity*, gboolean);
+ void (*guid_setter) (QofEntity*, const GUID*);
+
+ if(0 == safe_strcmp(param->param_type, QOF_TYPE_STRING)) {
+ string_setter = (void(*)(QofEntity*, char*))param->param_setfcn;
+ if(string_setter) { param->param_setfcn(ent, value); }
+ }
+ if(0 == safe_strcmp(param->param_type, QOF_TYPE_GUID)) {
+ cm_guid = g_new(GUID, 1);
+ if(TRUE == string_to_guid(value, cm_guid))
+ {
+ guid_setter = (void(*)(QofEntity*, const GUID*))param->param_setfcn;
+ if(guid_setter != NULL) { guid_setter(ent, cm_guid); }
+ }
+ }
+ if((0 == safe_strcmp(param->param_type, QOF_TYPE_NUMERIC)) ||
+ (safe_strcmp(param->param_type, QOF_TYPE_DEBCRED) == 0)) {
+ numeric_setter = (void(*)(QofEntity*, gnc_numeric))param->param_setfcn;
+ string_to_gnc_numeric(value, &cli_numeric);
+ if(numeric_setter != NULL) { numeric_setter(ent, cli_numeric); }
+ }
+ if(0 == safe_strcmp(param->param_type, QOF_TYPE_BOOLEAN)) {
+ cli_bool = FALSE;
+ if(qof_util_bool_to_int(value) == 1) { cli_bool = TRUE; }
+ boolean_setter = (void(*)(QofEntity*, gboolean))param->param_setfcn;
+ if(boolean_setter != NULL) { boolean_setter(ent, cli_bool); }
+ }
+ if(0 == safe_strcmp(param->param_type, QOF_TYPE_INT32)) {
+ errno = 0;
+ cli_i32 = (gint32)strtol (value, &tail, 0);
+ if(errno == 0) {
+ i32_setter = (void(*)(QofEntity*, gint32))param->param_setfcn;
+ if(i32_setter != NULL) { i32_setter(ent, cli_i32); }
+ }
+ else {
+ fmt = _("%s: Cannot convert %s into a number: an overflow has been detected.");
+ fprintf (stderr, fmt, PACKAGE, value);
+ }
+ }
+ if(0 == safe_strcmp(param->param_type, QOF_TYPE_INT64)) {
+ errno = 0;
+ cli_i64 = (gint64)strtol (value, &tail, 0);
+ if(errno == 0) {
+ i64_setter = (void(*)(QofEntity*, gint64))param->param_setfcn;
+ if(i64_setter != NULL) { i64_setter(ent, cli_i64); }
+ }
+ else {
+ fmt = _("%s: Cannot convert %s into a number: an overflow has been detected.");
+ fprintf (stderr, fmt, PACKAGE, value);
+ }
+ }
+ if(0 == safe_strcmp(param->param_type, QOF_TYPE_DATE)) {
+ date_setter = (void(*)(QofEntity*, Timespec))param->param_setfcn;
+ strptime(value, QOF_UTC_DATE_FORMAT, &cli_time);
+ cli_time_t = mktime(&cli_time);
+ timespecFromTime_t(&cli_date, cli_time_t);
+ if(date_setter != NULL) { date_setter(ent, cli_date); }
+ }
+ if(0 == safe_strcmp(param->param_type, QOF_TYPE_CHAR)) {
+ param->param_setfcn(ent, value);
+ }
+}
+
+static void
+undo_from_kvp_helper(const char *path, KvpValue *content, gpointer data)
+{
+ qof_undo_entity *undo_entity;
+
+ undo_entity = (qof_undo_entity*)data;
+ undo_entity->path = g_strdup(path);
+ undo_entity->value = kvp_value_to_bare_string(content);
+}
+
+qof_undo_entity*
+qof_prepare_undo (QofEntity *ent, QofParam *param)
+{
+ qof_undo_entity *undo_entity;
+ KvpFrame *undo_frame;
+
+ undo_frame = NULL;
+ undo_entity = g_new0(qof_undo_entity, 1);
+ undo_entity->guid = qof_entity_get_guid(ent);
+ undo_entity->param = param;
+ undo_entity->how = UNDO_MODIFY;
+ undo_entity->type = ent->e_type;
+ undo_entity->value = qof_book_merge_param_as_string(param, ent);
+ if(0 == (safe_strcmp(param->param_type, QOF_TYPE_KVP)))
+ {
+ undo_frame = kvp_frame_copy(param->param_getfcn(ent,param));
+ kvp_frame_for_each_slot(undo_frame, undo_from_kvp_helper, undo_entity);
+ }
+ /* need to do COLLECT and CHOICE */
+ return undo_entity;
+}
+
+static void
+qof_reinstate_entity (qof_undo_entity *undo_entity, QofBook *book)
+{
+ QofParam *undo_param;
+ QofCollection *coll;
+ QofEntity *ent;
+
+ undo_param = undo_entity->param;
+ if(!undo_param) { return; }
+ g_message("reinstate:%s", undo_entity->type);
+ coll = qof_book_get_collection(book, undo_entity->type);
+ if(!coll) { return; }
+ ent = qof_collection_lookup_entity(coll, undo_entity->guid);
+ if(!ent) { return; }
+ g_message("undoing %s %s", undo_param->param_name, undo_entity->value);
+ qof_entity_set_param(ent, undo_param, undo_entity->value);
+}
+
+static void
+qof_recreate_entity (qof_undo_entity *undo_entity, QofBook *book)
+{
+ QofEntity *ent;
+ const GUID *guid;
+ QofIdType type;
+ QofInstance *inst;
+
+ guid = undo_entity->guid;
+ type = undo_entity->type;
+ g_return_if_fail(guid || type);
+ inst = (QofInstance*)qof_object_new_instance(type, book);
+ ent = (QofEntity*)inst;
+ qof_entity_set_guid(ent, guid);
+}
+
+static void
+qof_dump_entity (qof_undo_entity *undo_entity, QofBook *book)
+{
+ QofCollection *coll;
+ QofEntity *ent;
+ const GUID *guid;
+ QofIdType type;
+
+ type = undo_entity->type;
+ guid = undo_entity->guid;
+ g_return_if_fail(type || book);
+ coll = qof_book_get_collection(book, type);
+ ent = qof_collection_lookup_entity(coll, guid);
+ qof_entity_release(ent);
+}
+
+void
+qof_book_undo(QofBook *book)
+{
+ qof_undo_operation *undo_operation;
+ qof_undo_entity *undo_entity;
+ GList *ent_list;
+ gint length;
+
+ length = g_list_length(book_undo);
+ if (index_position >1 ) { index_position--; }
+ else { index_position = 0; }
+ undo_operation = (qof_undo_operation*)(g_list_nth(book_undo, index_position))->data;
+ g_return_if_fail(undo_operation);
+ ent_list = undo_operation->entity_list;
+ while (ent_list != NULL)
+ {
+ undo_entity = (qof_undo_entity*)ent_list->data;
+ if(!undo_entity) { break; }
+ switch(undo_entity->how) {
+ case UNDO_MODIFY : { qof_reinstate_entity(undo_entity, book); break; }
+ case UNDO_CREATE : { qof_recreate_entity(undo_entity, book); break; }
+ case UNDO_DELETE : { qof_dump_entity (undo_entity, book); break; }
+ case UNDO_NOOP : { break; }
+ }
+ ent_list = g_list_next(ent_list);
+ }
+}
+
+void
+qof_book_redo(QofBook *book)
+{
+ qof_undo_operation *undo_operation;
+ qof_undo_entity *undo_entity;
+ GList *ent_list;
+ gint length;
+
+ undo_operation = (qof_undo_operation*)(g_list_nth(book_undo, index_position))->data;
+ if(!undo_operation) { return; }
+ ent_list = undo_operation->entity_list;
+ while (ent_list != NULL)
+ {
+ undo_entity = (qof_undo_entity*)ent_list->data;
+ if(!undo_entity) { break; }
+ switch(undo_entity->how) {
+ case UNDO_MODIFY : { qof_reinstate_entity(undo_entity, book); break; }
+ case UNDO_CREATE : { qof_dump_entity (undo_entity, book); break; }
+ case UNDO_DELETE : { qof_recreate_entity(undo_entity, book); break; }
+ case UNDO_NOOP : { break; }
+ }
+ ent_list = g_list_next(ent_list);
+ }
+ length = g_list_length(book_undo);
+ if (index_position < length ) { index_position++; }
+ else { index_position = length; }
+}
+
+void
+qof_book_clear_undo(QofBook *book)
+{
+ qof_undo_operation *operation;
+
+ if(!book || !book_undo) { return; }
+ while (book_undo != NULL)
+ {
+ operation = (qof_undo_operation*)book_undo->data;
+ g_list_free(operation->entity_list);
+ book_undo = g_list_next(book_undo);
+ }
+ index_position = 0;
+ g_free(undo_label);
+ book_undo = NULL;
+ undo_cache = NULL;
+}
+
+gboolean
+qof_book_can_undo(QofBook *book)
+{
+ gint length;
+
+ length = g_list_length(book_undo);
+ if ((index_position == 0) || (length == 0)) { return FALSE; }
+ return TRUE;
+}
+
+gboolean
+qof_book_can_redo(QofBook *book)
+{
+ gint length;
+
+ length = g_list_length(book_undo);
+ if ((index_position == length) || (length == 0)) { return FALSE; }
+ return TRUE;
+}
+
+qof_undo_operation*
+qof_undo_new_operation(char* label)
+{
+ qof_undo_operation *undo_operation;
+ time_t t;
+ Timespec ts;
+
+ undo_operation = NULL;
+ t = time (NULL);
+ timespecFromTime_t(&ts, t);
+ undo_operation = g_new0(qof_undo_operation, 1);
+ undo_operation->label = label;
+ undo_operation->ts = ts;
+ undo_operation->entity_list = NULL;
+ g_list_foreach(undo_cache, qof_undo_new_entry, undo_operation);
+ undo_cache = NULL;
+ return undo_operation;
+}
+
+void
+qof_undo_new_entry(gpointer cache, gpointer operation)
+{
+ qof_undo_operation *undo_operation;
+ qof_undo_entity *undo_entity;
+
+ g_return_if_fail(operation || cache);
+ undo_operation = (qof_undo_operation*)operation;
+ undo_entity = (qof_undo_entity*)cache;
+ g_return_if_fail(undo_operation || undo_entity);
+ undo_operation->entity_list = g_list_prepend(undo_operation->entity_list, undo_entity);
+}
+
+void
+undo_create_record (QofInstance *instance)
+{
+ qof_undo_entity *undo_entity;
+
+ if(!instance) { return; }
+ undo_entity = g_new0(qof_undo_entity, 1);
+ // to undo a create, use a delete.
+ undo_entity->how = UNDO_DELETE;
+ undo_entity->guid = qof_instance_get_guid(instance);
+ undo_entity->type = instance->entity.e_type;
+ undo_cache = g_list_prepend(undo_cache, undo_entity);
+}
+
+static void
+undo_get_entity (QofParam *param, gpointer data)
+{
+ QofInstance *instance;
+ qof_undo_entity *undo_entity;
+
+ instance = (QofInstance*)data;
+ g_return_if_fail(instance || param);
+ undo_entity = qof_prepare_undo(&instance->entity, param);
+ undo_cache = g_list_prepend(undo_cache, undo_entity);
+}
+
+void
+undo_delete_record (QofInstance *instance)
+{
+ qof_undo_entity *undo_entity;
+ QofIdType type;
+
+ if(!instance) { return; }
+ // now need to store each parameter in a second entity, MODIFY.
+ type = instance->entity.e_type;
+ qof_class_param_foreach(type, undo_get_entity, instance);
+ undo_entity = g_new0(qof_undo_entity, 1);
+ // to undo a delete, use a create.
+ undo_entity->how = UNDO_CREATE;
+ undo_entity->guid = qof_instance_get_guid(instance);
+ undo_entity->type = type;
+ undo_cache = g_list_prepend(undo_cache, undo_entity);
+}
+
+void
+undo_edit_record (QofInstance *instance, QofParam *param)
+{
+ qof_undo_entity *undo_entity;
+
+ if(!instance || !param) { return; }
+ // handle if record is called without a commit.
+ undo_entity = qof_prepare_undo(&instance->entity, param);
+ // get book from the instance.
+ undo_cache = g_list_prepend(undo_cache, undo_entity);
+ // set the initial state that undo will reinstate.
+ if(index_position == 0)
+ {
+ book_undo = g_list_prepend(book_undo, qof_undo_new_operation("initial"));
+ index_position++;
+ }
+}
+
+void
+undo_edit_commit (QofInstance *instance, QofParam *param)
+{
+ qof_undo_entity *undo_entity;
+
+ if(!instance || !param) { return; }
+ undo_entity = qof_prepare_undo(&instance->entity, param);
+ undo_cache = g_list_prepend(undo_cache, undo_entity);
+ // get book from the instance.
+}
+
+void
+qof_book_start_operation(QofBook *book, char *label)
+{
+ if(undo_operation_open && undo_cache) {
+ g_list_free(undo_cache);
+ undo_operation_open = FALSE;
+ if(undo_label) { g_free(undo_label); }
+ }
+ /** \todo handle the book parameter. */
+ undo_label = g_strdup(label);
+ undo_cache = NULL;
+ undo_operation_open = TRUE;
+}
+
+void
+qof_book_end_operation(QofBook *book)
+{
+ book_undo = g_list_prepend(book_undo, qof_undo_new_operation(undo_label));
+ index_position++;
+// g_list_free(undo_cache);
+ undo_operation_open = FALSE;
+}
+
+Timespec
+qof_book_undo_first_modified(QofBook *book)
+{
+ qof_undo_operation *undo_operation;
+
+ undo_operation = (qof_undo_operation*)g_list_last(book_undo);
+ return undo_operation->ts;
+}
+
+gint
+qof_book_undo_count(QofBook *book)
+{
+ return g_list_length(book_undo);
+}
+
+/* ====================== END OF FILE ======================== */
Added: gnucash/branches/cashutil/cashutil/src/qofundo.h
===================================================================
--- gnucash/branches/cashutil/cashutil/src/qofundo.h 2005-11-03 10:39:45 UTC (rev 11800)
+++ gnucash/branches/cashutil/cashutil/src/qofundo.h 2005-11-03 10:52:51 UTC (rev 11801)
@@ -0,0 +1,154 @@
+/***************************************************************************
+ * qofundo.h
+ *
+ * Thu Aug 25 09:19:25 2005
+ * Copyright 2005 Neil Williams
+ * linux at 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+ /** @addtogroup UNDO Undo: track and undo or redo entity changes
+ @ingroup QOFCLI
+
+\b EXPERIMENTAL!
+
+ QOF Undo operates within a QofBook. In order to undo the changes to
+ the entity, the initial state of each parameter is cached when an operation
+ begins. If the entity changes are not successful, the lack of a
+ ::qof_book_end_operation call before a ::qof_book_start_operation will cause
+ the cached data to be freed. If the entity is changed successfully,
+ ::qof_book_end_operation will create the undo data using the operation
+ label and each of the entity changes that were successful.
+
+ Undo data consists of a list of operations that have changed data in this book and a
+ list of entity changes for each of those operations. Each operation can relate to
+ more than one entity change and cover more than one entity but must only relate to
+ one book.
+
+-# Only QOF parameter changes can be undone or redone. Data from structs that
+ are not QOF objects or which have no QofParam to get <b>and set</b> the data
+ will not be available to the undo process.
+-# Undo relates to 'user interface operations', not engine events. This is
+because an operation (like an import or voiding a transaction) can involve
+multiple, possibly conflicting, engine events - e.g. removing an entity from one
+reference and inserting it as another. Therefore, the UI developer alone can
+decide where an operation begins and ends. All changes between the two will be
+undone or redone in one call to qof_book_undo.
+-# Undo operations \b cannot be nested. Be careful where you start and end an undo operation,
+if your application calls qof_book_start_operation() before calling qof_book_end_operation(),
+the undo cache will be freed and QOF Undo will not notify you of this. The API is designed to
+silently handle user aborts during a user operation. As undo data is cached as soon as editing
+begins, if the edit is never completed the cache must be cleared before the next operation.
+i.e. if the user starts to edit an entity but then cancels the operation, there are no changes
+to undo. It follows that any one book can only be the subject of one operation at a time.
+
+\todo Change operations to return a handler that can distinguish each operation then
+make it: QofOperation qof_book_start_operation(QofBook* book, char *label) and
+void qof_book_end_operation(QofOperation oper);
+
+@{
+ */
+/** @file qofundo.h
+ @brief Experimental QOF undo handling
+ @author Copyright (c) 2005 Neil Williams <linux at codehelp.co.uk>
+*/
+#ifndef _QOFUNDO_H
+#define _QOFUNDO_H
+
+/** @brief The parameter changes, >=1 per affected entity
+
+One per parameter change - the bottom level of any undo. A single click of
+Undo could use the data from one or many parameter changes - as determined by
+the event. Each parameter change can be for any entity of any registered type
+in the book and parameter changes can be repeated for the multiple changes to
+different parameters of the same entity. The combination of param, guid and
+type will be unique per event. (i.e. no event will ever set the same
+parameter of the same entity twice (with or without different data) in one
+undo operation.)
+*/
+typedef struct qof_undo_entity_t qof_undo_entity;
+
+/** @brief The affected entities, >=1 per operation
+
+The top level of any undo. Contains a GList that keeps the type of operation and
+the GList of qof_undo_entity* instances relating to that operation. Some form of
+index / counter probably too in order to speed up freeing unwanted operations and
+undo data upon resumption of editing and in controlling the total number of
+operations that can be undone.
+
+Each qof_undo_event.entity_list can contain data about >1 type of entity.
+*/
+typedef struct qof_undo_operation_t qof_undo_operation;
+
+/** \brief Set a value in this parameter of the entity.
+
+Setting an arbitrary parameter in an entity can involve
+repetitive string comparisons and setter function prototypes.
+This function accepts a QofParam (which determines the type of
+value) and a string representing the value. e.g. for a boolean,
+pass "TRUE", for a GUID pass the result of guid_to_string_buff.
+
+It's a convenience wrapper for routines that take values from
+files (e.g. XML) and need to convert into real data in the entity.
+
+ at param ent An initialized QofEntity from an accessible QofBook.
+ at param param The QofParam that needs to be set, including the
+get_fcn, set_fcn, param_type and param_name.
+ at param value A string representation of the required value - original
+type as specified in param->param_type.
+
+*/
+void qof_entity_set_param(QofEntity *ent, QofParam *param, char *value);
+
+/** @brief Set parameter values from before the previous event. */
+void qof_book_undo(QofBook *book);
+
+/** @brief Set parameter values from after the previous event. */
+void qof_book_redo(QofBook *book);
+
+/** @brief event handler for undo widget
+
+ @return FALSE if length == 0 or index_position == 0,
+ otherwise TRUE.
+*/
+gboolean qof_book_can_undo(QofBook *book);
+
+/** @brief event handler for redo widget
+
+ at return FALSE if index_position == 0 or index_position == length
+otherwise TRUE.
+*/
+gboolean qof_book_can_redo(QofBook *book);
+
+/** \brief Start recording operation.
+
+*/
+void qof_book_start_operation(QofBook *book, char *label);
+
+/** \brief End recording the current operation. */
+void qof_book_end_operation(QofBook *book);
+
+/** \brief HIG compliance aid to report time of first change. */
+Timespec qof_book_undo_first_modified(QofBook *book);
+
+/** \brief Number of undo operations available. */
+gint qof_book_undo_count(QofBook *book);
+
+#endif /* _QOFUNDO_H */
+
+/** @} */
More information about the gnucash-changes
mailing list