diff --git a/po/POTFILES.in b/po/POTFILES.in index 7372f0b983..2a4328dd7e 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -422,7 +422,6 @@ src/libqof/qof/kvp-util.c src/libqof/qof/md5.c src/libqof/qof/qofbackend.c src/libqof/qof/qofbook.c -src/libqof/qof/qofbookmerge.c src/libqof/qof/qofchoice.c src/libqof/qof/qofclass.c src/libqof/qof/qofevent.c diff --git a/src/engine/test/Makefile.am b/src/engine/test/Makefile.am index 0c9ab685e5..5ab34db3f8 100644 --- a/src/engine/test/Makefile.am +++ b/src/engine/test/Makefile.am @@ -43,8 +43,7 @@ TESTS = \ test-transaction-reversal \ test-transaction-voiding \ test-recurrence \ - test-scm-query \ - test-book-merge + test-scm-query GNC_TEST_DEPS = \ --gnc-module-dir ${top_builddir}/src/engine \ @@ -71,7 +70,6 @@ check_PROGRAMS = \ test-period \ test-lots \ test-numeric \ - test-book-merge \ test-object \ test-query \ test-querynew \ diff --git a/src/engine/test/test-book-merge.c b/src/engine/test/test-book-merge.c deleted file mode 100644 index 82b21565c5..0000000000 --- a/src/engine/test/test-book-merge.c +++ /dev/null @@ -1,534 +0,0 @@ -/********************************************************************* - * test-book-merge.c -- test implementation api for QoFBook merge * - * Copyright (C) 2004-2005 Neil Williams * - * * - * This program is free software; you can redistribute it and/or * - * modify it under the terms of the GNU General Public License as * - * published by the Free Software Foundation; either version 2 of * - * the License, or (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, contact: * - * * - * Free Software Foundation Voice: +1-617-542-5942 * - * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 * - * Boston, MA 02110-1301, USA gnu@gnu.org * - * * - ********************************************************************/ -/* Test the qof_book_merge infrastructure. */ - -#include "config.h" -#include - -#include "qof.h" -#include "test-stuff.h" -#include "gnc-engine.h" - -#define TEST_MODULE_NAME "book-merge-test" -#define TEST_MODULE_DESC "Test Book Merge" -#define OBJ_NAME "somename" -#define OBJ_AMOUNT "anamount" -#define OBJ_DATE "nottoday" -#define OBJ_GUID "unique" -#define OBJ_DISCOUNT "hefty" -#define OBJ_VERSION "early" -#define OBJ_MINOR "tiny" -#define OBJ_ACTIVE "ofcourse" - -static void test_rule_loop (QofBookMergeData*, QofBookMergeRule*, guint); -static void test_merge (void); -gboolean myobjRegister (void); - -/* simple object structure */ -typedef struct obj_s -{ - QofInstance inst; - char *Name; - gnc_numeric Amount; - const GncGUID *obj_guid; - Timespec date; - double discount; /* cheap pun, I know. */ - gboolean active; - gint32 version; - gint64 minor; -} myobj; - -typedef struct objclass_s -{ - QofInstanceClass parent_class; -} myobjClass; - -myobj* obj_create(QofBook*); - -/* obvious setter functions */ -void obj_setName(myobj*, char*); -void obj_setGUID(myobj*, const GncGUID*); -void obj_setAmount(myobj*, gnc_numeric); -void obj_setDate(myobj*, Timespec h); -void obj_setDiscount(myobj*, double); -void obj_setActive(myobj*, gboolean); -void obj_setVersion(myobj*, gint32); -void obj_setMinor(myobj*, gint64); - -/* obvious getter functions */ -char* obj_getName(myobj*); -const GncGUID* obj_getGUID(myobj*); -gnc_numeric obj_getAmount(myobj*); -Timespec obj_getDate(myobj*); -double obj_getDiscount(myobj*); -gboolean obj_getActive(myobj*); -gint32 obj_getVersion(myobj*); -gint64 obj_getMinor(myobj*); - -/* --- type macros --- */ -#define GNC_TYPE_MYOBJ (gnc_myobj_get_type ()) -#define GNC_MYOBJ(o) \ - (G_TYPE_CHECK_INSTANCE_CAST ((o), GNC_TYPE_MYOBJ, myobj)) -#define GNC_MYOBJ_CLASS(k) \ - (G_TYPE_CHECK_CLASS_CAST((k), GNC_TYPE_MYOBJ, myobjClass)) -#define GNC_IS_MYOBJ(o) \ - (G_TYPE_CHECK_INSTANCE_TYPE ((o), GNC_TYPE_MYOBJ)) -#define GNC_IS_MYOBJ_CLASS(k) \ - (G_TYPE_CHECK_CLASS_TYPE ((k), GNC_TYPE_MYOBJ)) -#define GNC_MYOBJ_GET_CLASS(o) \ - (G_TYPE_INSTANCE_GET_CLASS ((o), GNC_TYPE_MYOBJ, myobjClass)) -GType gnc_myobj_get_type(void); - -/* GObject Initialization */ -QOF_GOBJECT_IMPL(gnc_myobj, myobj, QOF_TYPE_INSTANCE); - -static void -gnc_myobj_init(myobj* obj) -{ -} - -static void -gnc_myobj_dispose_real (GObject *objp) -{ -} - -static void -gnc_myobj_finalize_real(GObject* objp) -{ -} - -myobj* -obj_create(QofBook *book) -{ - myobj *g; - g_return_val_if_fail(book, NULL); - g = g_object_new(GNC_TYPE_MYOBJ, NULL); - qof_instance_init_data (&g->inst, TEST_MODULE_NAME, book); - obj_setGUID(g, qof_instance_get_guid(&g->inst)); - g->date.tv_nsec = 0; - g->date.tv_sec = 0; - g->discount = 0; - g->active = TRUE; - g->version = 1; - g->minor = 1; - qof_event_gen(&g->inst, QOF_EVENT_CREATE, NULL); - return g; -} - -void -obj_setMinor(myobj *g, gint64 h) -{ - g_return_if_fail(g != NULL); - g->minor = h; -} - -gint64 -obj_getMinor(myobj *g) -{ - g_return_val_if_fail((g != NULL), 0); - return g->minor; -} - -void -obj_setVersion(myobj *g, gint32 h) -{ - g_return_if_fail(g != NULL); - g->version = h; -} - -gint32 -obj_getVersion(myobj *g) -{ - if (!g) return 0; - return g->version; -} - -void -obj_setActive(myobj *g, gboolean h) -{ - if (!g) return; - g->active = h; -} - -gboolean -obj_getActive(myobj *g) -{ - if (!g) return FALSE; - return g->active; -} - -void -obj_setDiscount(myobj *g, double h) -{ - if (!g) return; - g->discount = h; -} - -double -obj_getDiscount(myobj *g) -{ - if (!g) return 0; - return g->discount; -} - -void -obj_setDate(myobj *g, Timespec h) -{ - if (!g) return; - g->date = h; -} - -Timespec -obj_getDate(myobj *g) -{ - Timespec ts = {0}; - if (!g) return ts; - ts = g->date; - return ts; -} - -void -obj_setGUID(myobj* g, const GncGUID* h) -{ - if (!g) return; - g->obj_guid = h; -} - -const GncGUID* -obj_getGUID(myobj *g) -{ - if (!g) return NULL; - return g->obj_guid; -} - -void -obj_setName(myobj* g, char* h) -{ - if (!g || !h) return; - g->Name = strdup(h); -} - -char* -obj_getName(myobj *g) -{ - if (!g) return NULL; - return g->Name; -} - -void -obj_setAmount(myobj *g, gnc_numeric h) -{ - if (!g) return; - g->Amount = h; -} - -gnc_numeric -obj_getAmount(myobj *g) -{ - if (!g) return gnc_numeric_zero(); - return g->Amount; -} - -static QofObject obj_object_def = -{ -interface_version: - QOF_OBJECT_VERSION, -e_type: - TEST_MODULE_NAME, -type_label: - TEST_MODULE_DESC, -create: - (gpointer)obj_create, -book_begin: - NULL, -book_end: - NULL, -is_dirty: - NULL, -mark_clean: - NULL, -foreach: - qof_collection_foreach, -printable: - NULL, -version_cmp: - (int (*)(gpointer, gpointer)) qof_instance_version_cmp, -}; - -gboolean myobjRegister (void) -{ - static QofParam params[] = - { - { OBJ_NAME, QOF_TYPE_STRING, (QofAccessFunc)obj_getName, (QofSetterFunc)obj_setName }, - { OBJ_AMOUNT, QOF_TYPE_NUMERIC, (QofAccessFunc)obj_getAmount, (QofSetterFunc)obj_setAmount }, - { OBJ_GUID, QOF_TYPE_GUID, (QofAccessFunc)obj_getGUID, (QofSetterFunc)obj_setGUID }, - { OBJ_DATE, QOF_TYPE_DATE, (QofAccessFunc)obj_getDate, (QofSetterFunc)obj_setDate }, - { OBJ_DISCOUNT, QOF_TYPE_DOUBLE, (QofAccessFunc)obj_getDiscount, (QofSetterFunc)obj_setDiscount }, - { OBJ_ACTIVE, QOF_TYPE_BOOLEAN, (QofAccessFunc)obj_getActive, (QofSetterFunc)obj_setActive }, - { OBJ_VERSION, QOF_TYPE_INT32, (QofAccessFunc)obj_getVersion, (QofSetterFunc)obj_setVersion }, - { OBJ_MINOR, QOF_TYPE_INT64, (QofAccessFunc)obj_getMinor, (QofSetterFunc)obj_setMinor }, - { QOF_PARAM_BOOK, QOF_ID_BOOK, (QofAccessFunc)qof_instance_get_book, NULL }, - { QOF_PARAM_GUID, QOF_TYPE_GUID, (QofAccessFunc)qof_instance_get_guid, NULL }, - { NULL }, - }; - - qof_class_register (TEST_MODULE_NAME, NULL, params); - - return qof_object_register (&obj_object_def); -} - -static void -test_merge (void) -{ - QofBook *target, *import; - double init_value, discount; - myobj *import_obj, *target_obj, *new_obj; - int result; - Timespec ts, tc; - gboolean active; - gint32 version; - gint64 minor; - gchar *import_init, *target_init; - gnc_numeric obj_amount; - QofBookMergeData *mergeData; - - target = qof_book_new(); - import = qof_book_new(); - init_value = 1.00; - result = 0; - discount = 0.5; - active = TRUE; - version = 1; - minor = 1; - import_init = "test"; - target_init = "testing"; - qof_date_format_set(QOF_DATE_FORMAT_UK); - timespecFromTime_t(&ts, time(NULL)); - - do_test ((NULL != target), "#1 target book is NULL"); - - /* import book objects - tests used */ - do_test ((NULL != import), "#2 import book is NULL"); - import_obj = g_object_new(GNC_TYPE_MYOBJ, NULL); - do_test ((NULL != import_obj), "#3 new object create"); - qof_instance_init_data (&import_obj->inst, TEST_MODULE_NAME, import); - do_test ((NULL != &import_obj->inst), "#4 instance init"); - obj_setGUID(import_obj, qof_instance_get_guid(&import_obj->inst)); - do_test ((NULL != &import_obj->obj_guid), "#5 guid set"); - qof_event_gen(&import_obj->inst, QOF_EVENT_CREATE, NULL); - do_test ((NULL != &import_obj->inst), "#6 gnc event create"); - obj_setName(import_obj, import_init); - do_test ((NULL != &import_obj->Name), "#7 string set"); - obj_amount = double_to_gnc_numeric(init_value, 1, GNC_HOW_DENOM_EXACT); - obj_setAmount(import_obj, obj_amount); - do_test ((gnc_numeric_check(obj_getAmount(import_obj)) == GNC_ERROR_OK), "#8 gnc_numeric set"); - obj_setActive(import_obj, active); - do_test ((FALSE != &import_obj->active), "#9 gboolean set"); - obj_setDiscount(import_obj, discount); - do_test ((discount == import_obj->discount), "#10 double set"); - obj_setVersion(import_obj, version); - do_test ((version == import_obj->version), "#11 gint32 set"); - obj_setMinor(import_obj, minor); - do_test ((minor == import_obj->minor), "#12 gint64 set"); - obj_setDate(import_obj, ts ); - tc = import_obj->date; - do_test ((timespec_cmp(&ts, &tc) == 0), "#13 date set"); - - obj_amount = gnc_numeric_add(obj_amount, obj_amount, 1, GNC_HOW_DENOM_EXACT); - discount = 0.25; - version = 2; - minor = 3; - - /* second import object - test results would be the same, so not tested. */ - new_obj = g_object_new(GNC_TYPE_MYOBJ, NULL); - qof_instance_init_data (&new_obj->inst, TEST_MODULE_NAME, import); - obj_setGUID(new_obj, qof_instance_get_guid(&new_obj->inst)); - qof_event_gen (&new_obj->inst, QOF_EVENT_CREATE, NULL); - obj_setName(new_obj, import_init); - obj_setAmount(new_obj, obj_amount); - obj_setActive(new_obj, active); - obj_setDiscount(new_obj, discount); - obj_setVersion(new_obj, version); - obj_setMinor(new_obj, minor); - obj_setDate(new_obj, ts); - - obj_amount = gnc_numeric_add(obj_amount, obj_amount, 1, GNC_HOW_DENOM_EXACT); - discount = 0.35; - version = 2; - minor = 3; - tc.tv_sec = ts.tv_sec - 1; - tc.tv_nsec = 0; - - /* target object - test results would be the same, so not tested. */ - target_obj = g_object_new(GNC_TYPE_MYOBJ, NULL); - qof_instance_init_data (&target_obj->inst, TEST_MODULE_NAME, target); - obj_setGUID(target_obj, qof_instance_get_guid(&target_obj->inst)); - qof_event_gen (&target_obj->inst, QOF_EVENT_CREATE, NULL); - obj_setName(target_obj, target_init); - obj_setAmount(target_obj, obj_amount); - obj_setActive(target_obj, active); - obj_setDiscount(target_obj, discount); - obj_setVersion(target_obj, version); - obj_setMinor(target_obj, minor); - obj_setDate(target_obj, tc ); - - mergeData = qof_book_merge_init(import, target); - do_test ( mergeData != NULL, "FATAL: Merge could not be initialised!\t aborting . . "); - g_return_if_fail(mergeData != NULL); - qof_book_merge_rule_foreach(mergeData, test_rule_loop, MERGE_REPORT); - qof_book_merge_rule_foreach(mergeData, test_rule_loop, MERGE_UPDATE); - qof_book_merge_rule_foreach(mergeData, test_rule_loop, MERGE_NEW); - /* reserved calls - test only */ - qof_book_merge_rule_foreach(mergeData, test_rule_loop, MERGE_ABSOLUTE); - qof_book_merge_rule_foreach(mergeData, test_rule_loop, MERGE_DUPLICATE); - - /* import should not be in the target - pass if import_init fails match with target */ - do_test (((safe_strcmp(obj_getName(import_obj), obj_getName(target_obj))) != 0), "Init value test #1"); - - /* a good commit returns zero */ - do_test (qof_book_merge_commit(mergeData) == 0, "Commit failed"); - - /* import should be in the target - pass if import_init matches target */ - do_test (((safe_strcmp(import_init, obj_getName(target_obj))) == 0), "Merged value test #1"); - - /* import should be the same as target - pass if values are the same */ - do_test (((safe_strcmp(obj_getName(target_obj), obj_getName(import_obj))) == 0), "Merged value test #2"); - - /* check that the Amount really is a gnc_numeric */ - do_test ((gnc_numeric_check(obj_getAmount(import_obj)) == GNC_ERROR_OK), "import gnc_numeric check"); - do_test ((gnc_numeric_check(obj_getAmount(target_obj)) == GNC_ERROR_OK), "target gnc_numeric check"); - - /* obj_amount was changed after the import object was set, so expect a difference. */ - do_test ((gnc_numeric_compare(obj_getAmount(import_obj), obj_amount) != GNC_ERROR_OK), - "gnc_numeric value check #1"); - - /* obj_amount is in the target object with the import value, expect a difference/ */ - do_test ((gnc_numeric_compare(obj_getAmount(target_obj), obj_amount) != GNC_ERROR_OK), - "gnc_numeric value check #2"); - - /* target had a different date, so import date should now be set */ - /* note: If sensible defaults are not set in the create: - an empty Timespec caused problems with the update - fix */ - tc = target_obj->date; - do_test ((timespec_cmp(&ts, &tc) == 0), "date value check: 1"); - tc = import_obj->date; - do_test ((timespec_cmp(&tc, &ts) == 0), "date value check: 2"); - do_test ((timespec_cmp(&import_obj->date, &target_obj->date) == 0), "date value check: 3"); - -} - -static void -test_rule_loop (QofBookMergeData *mergeData, QofBookMergeRule *rule, guint remainder) -{ - GSList *testing; - QofParam *eachParam; - char *importstring; - char *targetstring; - /* In this test rule_loop, any lines beginning with do_test() can be removed - from a working rule_loop routine. It would be wise to still use some of the - more obvious checks, e.g. that an entity or rule exists before querying the parameters. - - Take particular care with MERGE_NEW - targetEnt is always NULL until the Commit. - Do not attempt to use param_getfcn on targetEnt in the loop called by - QofBookMergeRuleForeach(rule_loop, MERGE_NEW); - - */ - gboolean skip_target; - - importstring = NULL; - targetstring = NULL; - skip_target = FALSE; - mergeData->currentRule = rule; - do_test ((rule != NULL), "loop:#1 Rule is NULL"); - do_test (remainder > 0, "loop:#2 remainder error."); - do_test ((safe_strcmp(NULL, rule->mergeLabel) != 0), "loop:#3 object label\n"); - do_test ((rule->importEnt != NULL), "loop:#4 empty import entity"); - /* targetEnt is always NULL at this stage if MERGE_NEW is set */ - if (rule->targetEnt == NULL) - { - skip_target = TRUE; - } - if (!skip_target) - { - do_test ((safe_strcmp(rule->importEnt->e_type, rule->targetEnt->e_type) == 0), - "loop: entity type mismatch"); - } - do_test ((rule->mergeParam != NULL), "loop: empty parameter list"); - testing = rule->mergeParam; - - while (testing != NULL) // start of param loop - { - eachParam = testing->data; - do_test ((eachParam != NULL), "loop:#8 no QofParam data"); - do_test ((eachParam->param_name != NULL), "loop:#9 no parameter name"); - do_test ((eachParam->param_getfcn != NULL), "loop:#10 no get function"); - do_test ((eachParam->param_setfcn != NULL), "loop:#11 no set function"); - /* non-generic - test routines only! */ - if (safe_strcmp(eachParam->param_type, QOF_TYPE_STRING) == 0) - { - /* if you use this format, you would need to check the QOF_TYPE and - configure the get_fcn pointers yourself. This example only works for strings. */ - importstring = g_strdup(eachParam->param_getfcn(rule->importEnt, eachParam)); - do_test ((importstring != NULL), "loop:#12 direct get_fcn import"); - do_test ((safe_strcmp(importstring, "test") == 0), "loop:#13 direct import comparison"); - if (!skip_target) - { - targetstring = eachParam->param_getfcn(rule->targetEnt, eachParam); - do_test ((targetstring != NULL), "loop:#14 direct get_fcn target"); - do_test ((safe_strcmp(targetstring, "testing") == 0), "loop:#15 direct target comparison"); - } - } - /* param_as_string does the conversion for display purposes only */ - /* do NOT use as_string for calculations or set_fcn */ - importstring = qof_book_merge_param_as_string(eachParam, rule->importEnt); - do_test ((importstring != NULL), "loop:#16 import param_as_string is null"); - /* printf("importstring %s\t%s Type\n", importstring, eachParam->param_type);*/ - if (!skip_target) - { - targetstring = qof_book_merge_param_as_string(eachParam, rule->targetEnt); - do_test ((targetstring != NULL), "loop:#17 target param_as_string is null"); - /* printf("targetstring %s\t%s Type\n", targetstring, eachParam->param_type);*/ - } - /* add your own code for user involvement here. */ - /* either store the importstring and targetstring values and display separately, - perhaps in alphabetical/object_type/priority order, or, obtain user input as each - string is available. */ - - testing = g_slist_next(testing); - } // end param loop - /* set each rule dependent on the user involvement response above. */ - /* test routine just sets all MERGE_REPORT to MERGE_UPDATE */ - mergeData = qof_book_merge_update_result(mergeData, MERGE_UPDATE); - do_test ((rule->mergeResult != MERGE_REPORT), "update result fail"); -} - -int -main (int argc, char **argv) -{ - qof_init(); - myobjRegister(); - test_merge(); - print_test_results(); - qof_close(); - return get_rv(); -} diff --git a/src/libqof/CMakeLists.txt b/src/libqof/CMakeLists.txt index 3e0b3cebe8..b777f9788e 100644 --- a/src/libqof/CMakeLists.txt +++ b/src/libqof/CMakeLists.txt @@ -30,7 +30,6 @@ SET (libgnc_qof_SOURCES qof/qofreference.c qof/qofutil.c qof/qofsession.c - qof/qofbookmerge.c ) IF (WIN32) SET (libgnc_qof_SOURCES ${libgnc_qof_SOURCES} @@ -72,7 +71,6 @@ SET (libgnc_qof_HEADERS qof/qofreference-p.h qof/qofsession.h qof/qofutil.h - qof/qofbookmerge.h qof/qof-gobject.h ) diff --git a/src/libqof/qof/Makefile.am b/src/libqof/qof/Makefile.am index 8afe29984e..d6042d4c2f 100644 --- a/src/libqof/qof/Makefile.am +++ b/src/libqof/qof/Makefile.am @@ -33,8 +33,7 @@ libgnc_qof_la_SOURCES = \ qofquerycore.c \ qofreference.c \ qofutil.c \ - qofsession.c \ - qofbookmerge.c + qofsession.c qofincludedir = ${pkgincludedir} @@ -65,7 +64,6 @@ qofinclude_HEADERS = \ qofquerycore.h \ qofsession.h \ qofutil.h \ - qofbookmerge.h \ qof-gobject.h noinst_HEADERS = \ diff --git a/src/libqof/qof/qof.h b/src/libqof/qof/qof.h index 13503c4f1c..9361beb5aa 100644 --- a/src/libqof/qof/qof.h +++ b/src/libqof/qof/qof.h @@ -93,6 +93,5 @@ #include "qofquerycore.h" #include "qofsession.h" #include "qofchoice.h" -#include "qofbookmerge.h" #endif /* QOF_H_ */ diff --git a/src/libqof/qof/qofbookmerge.c b/src/libqof/qof/qofbookmerge.c deleted file mode 100644 index 7cc1f738c8..0000000000 --- a/src/libqof/qof/qofbookmerge.c +++ /dev/null @@ -1,1325 +0,0 @@ -/********************************************************************* - * QofBookMerge.c -- api for QoFBook merge with collision handling * - * Copyright (C) 2004-2005 Neil Williams * - * * - * This program is free software; you can redistribute it and/or * - * modify it under the terms of the GNU General Public License as * - * published by the Free Software Foundation; either version 2 of * - * the License, or (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, contact: * - * * - * Free Software Foundation Voice: +1-617-542-5942 * - * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 * - * Boston, MA 02110-1301, USA gnu@gnu.org * - * * - ********************************************************************/ - -#include "config.h" -#include -#include "qof.h" - -static QofLogModule log_module = QOF_MOD_MERGE; - -/* private rule iteration struct */ -struct QofBookMergeRuleIterate -{ - QofBookMergeRuleForeachCB fcn; - QofBookMergeData *data; - QofBookMergeRule *rule; - GList *ruleList; - guint remainder; -}; - -/* Make string type parameters 3 times more - important in the match than default types. - i.e. even if two other parameters differ, - a string match will still provide a better target - than when other types match and the string does not. -*/ -#define DEFAULT_MERGE_WEIGHT 1 -#define QOF_STRING_WEIGHT 3 -#define QOF_DATE_STRING_LENGTH MAX_DATE_LENGTH - -static QofBookMergeRule* -qof_book_merge_update_rule(QofBookMergeRule *currentRule, gboolean match, gint weight) -{ - gboolean absolute; - - absolute = currentRule->mergeAbsolute; - if (absolute && match && currentRule->mergeResult == MERGE_UNDEF) - { - currentRule->mergeResult = MERGE_ABSOLUTE; - } - if (absolute && !match) - { - currentRule->mergeResult = MERGE_UPDATE; - } - if (!absolute && match && currentRule->mergeResult == MERGE_UNDEF) - { - currentRule->mergeResult = MERGE_DUPLICATE; - } - if (!absolute && !match) - { - currentRule->difference += weight; - if (currentRule->mergeResult == MERGE_DUPLICATE) - { - currentRule->mergeResult = MERGE_REPORT; - } - } - return currentRule; -} - -struct collect_list_s -{ - GSList *linkedEntList; -}; - -static void -collect_reference_cb (QofInstance *ent, gpointer user_data) -{ - struct collect_list_s *s; - - s = (struct collect_list_s*)user_data; - if (!ent || !s) - { - return; - } - s->linkedEntList = g_slist_prepend(s->linkedEntList, ent); -} - -static int -qof_book_merge_compare(QofBookMergeData *mergeData ) -{ - QofBookMergeRule *currentRule; - QofCollection *mergeColl, *targetColl; - gchar *stringImport, *stringTarget; - QofInstance *mergeEnt, *targetEnt, *referenceEnt; - const GncGUID *guidImport, *guidTarget; - QofParam *qtparam; - KvpFrame *kvpImport, *kvpTarget; - QofIdType mergeParamName; - QofType mergeType; - GSList *paramList; - gboolean absolute, mergeError, knowntype, mergeMatch, booleanImport, booleanTarget, - (*boolean_getter) (QofInstance*, QofParam*); - Timespec tsImport, tsTarget, (*date_getter) (QofInstance*, QofParam*); - gnc_numeric numericImport, numericTarget, (*numeric_getter) (QofInstance*, QofParam*); - double doubleImport, doubleTarget, (*double_getter) (QofInstance*, QofParam*); - gint32 i32Import, i32Target, (*int32_getter) (QofInstance*, QofParam*); - gint64 i64Import, i64Target, (*int64_getter) (QofInstance*, QofParam*); - gchar charImport, charTarget, (*char_getter) (QofInstance*, QofParam*); - - ENTER (" "); - - g_return_val_if_fail((mergeData != NULL), -1); - currentRule = mergeData->currentRule; - g_return_val_if_fail((currentRule != NULL), -1); - absolute = currentRule->mergeAbsolute; - mergeEnt = currentRule->importEnt; - targetEnt = currentRule->targetEnt; - paramList = currentRule->mergeParam; - currentRule->difference = 0; - currentRule->mergeResult = MERGE_UNDEF; - currentRule->linkedEntList = NULL; - g_return_val_if_fail((targetEnt) || (mergeEnt) || (paramList), -1); - kvpImport = kvp_frame_new(); - kvpTarget = kvp_frame_new(); - mergeError = FALSE; - while (paramList != NULL) - { - mergeMatch = FALSE; - knowntype = FALSE; - qtparam = paramList->data; - mergeParamName = qtparam->param_name; - g_return_val_if_fail(mergeParamName != NULL, -1); - mergeType = qtparam->param_type; - if (safe_strcmp(mergeType, QOF_TYPE_STRING) == 0) - { - stringImport = qtparam->param_getfcn(mergeEnt, qtparam); - stringTarget = qtparam->param_getfcn(targetEnt, qtparam); - /* very strict string matches may need to be relaxed. */ - if (stringImport == NULL) - { - stringImport = ""; - } - if (stringTarget == NULL) - { - stringTarget = ""; - } - if (safe_strcmp(stringImport, stringTarget) == 0) - { - mergeMatch = TRUE; - } - /* Give special weight to a string match */ - currentRule = qof_book_merge_update_rule(currentRule, - mergeMatch, QOF_STRING_WEIGHT); - stringImport = stringTarget = NULL; - knowntype = TRUE; - } - if (safe_strcmp(mergeType, QOF_TYPE_DATE) == 0) - { - date_getter = (Timespec (*)(QofInstance*, QofParam*))qtparam->param_getfcn; - tsImport = date_getter(mergeEnt, qtparam); - tsTarget = date_getter(targetEnt, qtparam); - if (timespec_cmp(&tsImport, &tsTarget) == 0) - { - mergeMatch = TRUE; - } - currentRule = qof_book_merge_update_rule(currentRule, - mergeMatch, DEFAULT_MERGE_WEIGHT); - knowntype = TRUE; - } - if ((safe_strcmp(mergeType, QOF_TYPE_NUMERIC) == 0) || - (safe_strcmp(mergeType, QOF_TYPE_DEBCRED) == 0)) - { - numeric_getter = (gnc_numeric (*)(QofInstance*, QofParam*)) qtparam->param_getfcn; - numericImport = numeric_getter(mergeEnt, qtparam); - numericTarget = numeric_getter(targetEnt, qtparam); - if (gnc_numeric_compare (numericImport, numericTarget) == 0) - { - mergeMatch = TRUE; - } - currentRule = qof_book_merge_update_rule(currentRule, - mergeMatch, DEFAULT_MERGE_WEIGHT); - knowntype = TRUE; - } - if (safe_strcmp(mergeType, QOF_TYPE_GUID) == 0) - { - guidImport = qtparam->param_getfcn(mergeEnt, qtparam); - guidTarget = qtparam->param_getfcn(targetEnt, qtparam); - if (guid_compare(guidImport, guidTarget) == 0) - { - mergeMatch = TRUE; - } - currentRule = qof_book_merge_update_rule(currentRule, - mergeMatch, DEFAULT_MERGE_WEIGHT); - knowntype = TRUE; - } - if (safe_strcmp(mergeType, QOF_TYPE_INT32) == 0) - { - int32_getter = (gint32 (*)(QofInstance*, QofParam*)) qtparam->param_getfcn; - i32Import = int32_getter(mergeEnt, qtparam); - i32Target = int32_getter(targetEnt, qtparam); - if (i32Target == i32Import) - { - mergeMatch = TRUE; - } - currentRule = qof_book_merge_update_rule(currentRule, - mergeMatch, DEFAULT_MERGE_WEIGHT); - knowntype = TRUE; - } - if (safe_strcmp(mergeType, QOF_TYPE_INT64) == 0) - { - int64_getter = (gint64 (*)(QofInstance*, QofParam*)) qtparam->param_getfcn; - i64Import = int64_getter(mergeEnt, qtparam); - i64Target = int64_getter(targetEnt, qtparam); - if (i64Target == i64Import) - { - mergeMatch = TRUE; - } - currentRule = qof_book_merge_update_rule(currentRule, - mergeMatch, DEFAULT_MERGE_WEIGHT); - knowntype = TRUE; - } - if (safe_strcmp(mergeType, QOF_TYPE_DOUBLE) == 0) - { - double_getter = (double (*)(QofInstance*, QofParam*)) qtparam->param_getfcn; - doubleImport = double_getter(mergeEnt, qtparam); - doubleTarget = double_getter(mergeEnt, qtparam); - if (doubleImport == doubleTarget) - { - mergeMatch = TRUE; - } - currentRule = qof_book_merge_update_rule(currentRule, - mergeMatch, DEFAULT_MERGE_WEIGHT); - knowntype = TRUE; - } - if (safe_strcmp(mergeType, QOF_TYPE_BOOLEAN) == 0) - { - boolean_getter = (gboolean (*)(QofInstance*, QofParam*)) qtparam->param_getfcn; - booleanImport = boolean_getter(mergeEnt, qtparam); - booleanTarget = boolean_getter(targetEnt, qtparam); - if (booleanImport != FALSE && booleanImport != TRUE) - { - booleanImport = FALSE; - } - if (booleanTarget != FALSE && booleanTarget != TRUE) - { - booleanTarget = FALSE; - } - if (booleanImport == booleanTarget) - { - mergeMatch = TRUE; - } - currentRule = qof_book_merge_update_rule(currentRule, - mergeMatch, DEFAULT_MERGE_WEIGHT); - knowntype = TRUE; - } - if (safe_strcmp(mergeType, QOF_TYPE_KVP) == 0) - { - kvpImport = kvp_frame_copy(qtparam->param_getfcn(mergeEnt, qtparam)); - kvpTarget = kvp_frame_copy(qtparam->param_getfcn(targetEnt, qtparam)); - if (kvp_frame_compare(kvpImport, kvpTarget) == 0) - { - mergeMatch = TRUE; - } - currentRule = qof_book_merge_update_rule(currentRule, - mergeMatch, DEFAULT_MERGE_WEIGHT); - knowntype = TRUE; - } - if (safe_strcmp(mergeType, QOF_TYPE_CHAR) == 0) - { - char_getter = (gchar (*)(QofInstance*, QofParam*)) qtparam->param_getfcn; - charImport = char_getter(mergeEnt, qtparam); - charTarget = char_getter(targetEnt, qtparam); - if (charImport == charTarget) - { - mergeMatch = TRUE; - } - currentRule = qof_book_merge_update_rule(currentRule, - mergeMatch, DEFAULT_MERGE_WEIGHT); - knowntype = TRUE; - } - /* No object should have QofSetterFunc defined for the book, - but just to be safe, do nothing. */ - if (safe_strcmp(mergeType, QOF_ID_BOOK) == 0) - { - knowntype = TRUE; - } - if (safe_strcmp(mergeType, QOF_TYPE_COLLECT) == 0) - { - struct collect_list_s s; - s.linkedEntList = NULL; - mergeColl = qtparam->param_getfcn(mergeEnt, qtparam); - targetColl = qtparam->param_getfcn(targetEnt, qtparam); - s.linkedEntList = g_slist_copy(currentRule->linkedEntList); - qof_collection_foreach(mergeColl, collect_reference_cb, &s); - currentRule->linkedEntList = g_slist_copy(s.linkedEntList); - if (0 == qof_collection_compare(mergeColl, targetColl)) - { - mergeMatch = TRUE; - } - currentRule = qof_book_merge_update_rule(currentRule, - mergeMatch, DEFAULT_MERGE_WEIGHT); - knowntype = TRUE; - } - if (safe_strcmp(mergeType, QOF_TYPE_CHOICE) == 0) - { - referenceEnt = qtparam->param_getfcn(mergeEnt, qtparam); - currentRule->linkedEntList = - g_slist_prepend(currentRule->linkedEntList, referenceEnt); - if (referenceEnt == qtparam->param_getfcn(targetEnt, qtparam)) - { - mergeMatch = TRUE; - } - knowntype = TRUE; - } - if (knowntype == FALSE) - { - referenceEnt = qtparam->param_getfcn(mergeEnt, qtparam); - - // XXX gncOwner is na object that could be returned, but does not have QofInstance - if (safe_strcmp(qtparam->param_type, "gncOwner") == 0) - referenceEnt = NULL; - - if ((referenceEnt != NULL) - && (safe_strcmp(referenceEnt->e_type, mergeType) == 0)) - { - currentRule->linkedEntList = - g_slist_prepend(currentRule->linkedEntList, referenceEnt); - if (referenceEnt == qtparam->param_getfcn(targetEnt, qtparam)) - { - mergeMatch = TRUE; - } - currentRule = qof_book_merge_update_rule(currentRule, - mergeMatch, DEFAULT_MERGE_WEIGHT); - } - } - paramList = g_slist_next(paramList); - } - mergeData->currentRule = currentRule; - g_free(kvpImport); - g_free(kvpTarget); - - LEAVE (" "); - return 0; -} - -static void -qof_book_merge_commit_foreach_cb(gpointer rule, gpointer arg) -{ - struct QofBookMergeRuleIterate *iter; - - g_return_if_fail(arg != NULL); - iter = (struct QofBookMergeRuleIterate*)arg; - g_return_if_fail(iter->data != NULL); - iter->fcn (iter->data, (QofBookMergeRule*)rule, iter->remainder); - iter->remainder--; -} - -static void -qof_book_merge_commit_foreach (QofBookMergeRuleForeachCB cb, - QofBookMergeResult mergeResult, - QofBookMergeData *mergeData) -{ - struct QofBookMergeRuleIterate iter; - QofBookMergeRule *currentRule; - GList *subList, *node; - - g_return_if_fail(cb != NULL); - g_return_if_fail(mergeData != NULL); - currentRule = mergeData->currentRule; - g_return_if_fail(currentRule != NULL); - g_return_if_fail(mergeResult > 0); - g_return_if_fail((mergeResult != MERGE_INVALID) || (mergeResult != MERGE_UNDEF) || (mergeResult != MERGE_REPORT)); - - iter.fcn = cb; - subList = NULL; - iter.ruleList = NULL; - for (node = mergeData->mergeList; node != NULL; node = node->next) - { - currentRule = node->data; - if (currentRule->mergeResult == mergeResult) - { - subList = g_list_prepend(subList, currentRule); - } - } - iter.remainder = g_list_length(subList); - iter.data = mergeData; - g_list_foreach (subList, qof_book_merge_commit_foreach_cb, &iter); -} - -/* build the table of target comparisons - -This can get confusing, so bear with me. (!) - -Whilst iterating through the entities in the mergeBook, qof_book_mergeForeach assigns -a targetEnt to each mergeEnt (until it runs out of targetEnt or mergeEnt). Each match -is made against the one targetEnt that best matches the mergeEnt. Fine so far. - -Any mergeEnt is only ever assigned a targetEnt if the calculated difference between -the two is less than the difference between that targetEnt and any previous mergeEnt -match. - -The next mergeEnt may be a much better match for that targetEnt and the target_table -is designed to solve the issues that result from this conflict. The previous match -must be re-assigned because if two mergeEnt's are matched with only one targetEnt, -data loss \b WILL follow. Equally, the current mergeEnt must replace the previous -one as it is a better match. qof_instance_rating holds the details required to identify -the correct mergeEnt to be re-assigned and these mergeEnt entities are therefore -orphaned - to be re-matched later. - -Meanwhile, the current mergeEnt is entered into target_table with it's difference and -rule data, in case an even better match is found later in the mergeBook. - -Finally, each mergeEnt in the orphan_list is now put through the comparison again. - -*/ -static gboolean -qof_book_merge_rule_cmp(gconstpointer a, gconstpointer b) -{ - QofBookMergeRule *ra = (QofBookMergeRule *) a; - QofBookMergeRule *rb = (QofBookMergeRule *) b; - if (ra->difference == rb->difference) - { - return TRUE; - } - else return FALSE; -} - -static void -qof_book_merge_orphan_check(double difference, QofBookMergeRule *mergeRule, - QofBookMergeData *mergeData) -{ - /* Called when difference is lower than previous - Lookup target to find previous match - and re-assign mergeEnt to orphan_list */ - QofBookMergeRule *rule; - - g_return_if_fail(mergeRule != NULL); - g_return_if_fail(mergeData != NULL); - if (g_hash_table_size(mergeData->target_table) == 0) - { - return; - } - rule = (QofBookMergeRule*)g_hash_table_lookup(mergeData->target_table, - mergeRule->targetEnt); - /* If NULL, no match was found. */ - if (rule == NULL) - { - return; - } - /* Only orphan if this is a better match than already exists. */ - if (difference >= rule->difference) - { - return; - } - rule->targetEnt = NULL; - rule->mergeResult = MERGE_UNDEF; - mergeData->orphan_list = g_slist_append(mergeData->orphan_list, rule); -} - -static void -qof_book_merge_match_orphans(QofBookMergeData *mergeData) -{ - GSList *orphans, *targets; - QofBookMergeRule *rule, *currentRule; - QofInstance *best_matchEnt; - double difference; - - ENTER (" "); - - g_return_if_fail(mergeData != NULL); - currentRule = mergeData->currentRule; - g_return_if_fail(currentRule != NULL); - /* This routine does NOT copy the orphan list, it - is used recursively until empty. */ - orphans = mergeData->orphan_list; - targets = g_slist_copy(mergeData->targetList); - while (orphans != NULL) - { - rule = orphans->data; - g_return_if_fail(rule != NULL); - difference = g_slist_length(mergeData->mergeObjectParams); - if (rule->targetEnt == NULL) - { - rule->mergeResult = MERGE_NEW; - rule->difference = 0; - mergeData->mergeList = g_list_prepend(mergeData->mergeList, rule); - orphans = g_slist_next(orphans); - continue; - } - mergeData->currentRule = rule; - g_return_if_fail(qof_book_merge_compare(mergeData) != -1); - if (difference > mergeData->currentRule->difference) - { - best_matchEnt = currentRule->targetEnt; - difference = currentRule->difference; - rule = currentRule; - mergeData->mergeList = g_list_prepend(mergeData->mergeList, rule); - qof_book_merge_orphan_check(difference, rule, mergeData); - } - orphans = g_slist_next(orphans); - } - g_slist_free(mergeData->orphan_list); - g_slist_free(targets); - - LEAVE (" "); -} - -static void -qof_book_merge_foreach_target (QofInstance* targetEnt, gpointer user_data) -{ - QofBookMergeData *mergeData; - - g_return_if_fail(user_data != NULL); - mergeData = (QofBookMergeData*)user_data; - g_return_if_fail(targetEnt != NULL); - mergeData->targetList = g_slist_prepend(mergeData->targetList, targetEnt); -} - -static void -qof_book_merge_foreach_type_target ( QofObject* merge_obj, gpointer user_data) -{ - QofBookMergeData *mergeData; - QofBookMergeRule *currentRule; - - g_return_if_fail(user_data != NULL); - mergeData = (QofBookMergeData*)user_data; - currentRule = mergeData->currentRule; - g_return_if_fail(currentRule != NULL); - g_return_if_fail(merge_obj != NULL); - if (safe_strcmp(merge_obj->e_type, currentRule->importEnt->e_type) == 0) - { - qof_object_foreach(currentRule->importEnt->e_type, mergeData->targetBook, - qof_book_merge_foreach_target, user_data); - } -} - -static void -qof_book_merge_foreach ( QofInstance* mergeEnt, gpointer user_data) -{ - QofBookMergeRule *mergeRule, *currentRule; - QofBookMergeData *mergeData; - QofInstance *targetEnt, *best_matchEnt; - GncGUID *g; - double difference; - GSList *c; - - ENTER (" "); - - g_return_if_fail(user_data != NULL); - mergeData = (QofBookMergeData*)user_data; - g_return_if_fail(mergeEnt != NULL); - currentRule = mergeData->currentRule; - g_return_if_fail(currentRule != NULL); - g = guid_copy(qof_instance_get_guid(mergeEnt)); - mergeRule = g_new0(QofBookMergeRule, 1); - mergeRule->importEnt = mergeEnt; - mergeRule->difference = difference = 0; - mergeRule->mergeAbsolute = FALSE; - mergeRule->mergeResult = MERGE_UNDEF; - mergeRule->updated = FALSE; - mergeRule->mergeType = mergeEnt->e_type; - mergeRule->mergeLabel = qof_object_get_type_label(mergeEnt->e_type); - mergeRule->mergeParam = g_slist_copy(mergeData->mergeObjectParams); - mergeRule->linkedEntList = NULL; - mergeData->currentRule = mergeRule; - targetEnt = best_matchEnt = NULL; - targetEnt = qof_collection_lookup_entity ( - qof_book_get_collection (mergeData->targetBook, mergeEnt->e_type), g); - if ( targetEnt != NULL) - { - mergeRule->mergeAbsolute = TRUE; - mergeRule->targetEnt = targetEnt; - g_return_if_fail(qof_book_merge_compare(mergeData) != -1); - mergeRule->linkedEntList = g_slist_copy(currentRule->linkedEntList); - mergeData->mergeList = g_list_prepend(mergeData->mergeList, mergeRule); - return; - } - /* no absolute match exists */ - g_slist_free(mergeData->targetList); - mergeData->targetList = NULL; - qof_object_foreach_type(qof_book_merge_foreach_type_target, mergeData); - if (g_slist_length(mergeData->targetList) == 0) - { - mergeRule->mergeResult = MERGE_NEW; - } - difference = g_slist_length(mergeRule->mergeParam); - c = g_slist_copy(mergeData->targetList); - while (c != NULL) - { - mergeRule->targetEnt = c->data; - currentRule = mergeRule; - /* compare two entities and sum the differences */ - g_return_if_fail(qof_book_merge_compare(mergeData) != -1); - if (mergeRule->difference == 0) - { - /* check if this is a better match than one already assigned */ - best_matchEnt = mergeRule->targetEnt; - mergeRule->mergeResult = MERGE_DUPLICATE; - difference = 0; - mergeRule->linkedEntList = g_slist_copy(currentRule->linkedEntList); - g_slist_free(c); - guid_free(g); - /* exact match, return */ - return; - } - if (difference > mergeRule->difference) - { - /* The chosen targetEnt determines the parenting of any child object */ - /* check if this is a better match than one already assigned */ - best_matchEnt = mergeRule->targetEnt; - difference = mergeRule->difference; - /* Use match to lookup the previous entity that matched this targetEnt (if any) - and remove targetEnt from the rule for that mergeEnt. - Add the previous mergeEnt to orphan_list. - */ - qof_book_merge_orphan_check(difference, mergeRule, mergeData); - } - c = g_slist_next(c); - } - g_slist_free(c); - if (best_matchEnt != NULL ) - { - mergeRule->targetEnt = best_matchEnt; - mergeRule->difference = difference; - /* Set this entity in the target_table in case a better match can be made - with the next mergeEnt. */ - g_hash_table_insert(mergeData->target_table, mergeRule->targetEnt, mergeRule); - /* compare again with the best partial match */ - g_return_if_fail(qof_book_merge_compare(mergeData) != -1); - mergeRule->linkedEntList = g_slist_copy(currentRule->linkedEntList); - } - else - { - mergeRule->targetEnt = NULL; - mergeRule->difference = 0; - mergeRule->mergeResult = MERGE_NEW; - mergeRule->linkedEntList = g_slist_copy(currentRule->linkedEntList); - } - mergeData->mergeList = g_list_prepend(mergeData->mergeList, mergeRule); - guid_free(g); - /* return to qof_book_merge_init */ - - LEAVE (" "); -} - -static void -qof_book_merge_foreach_param( QofParam* param, gpointer user_data) -{ - QofBookMergeData *mergeData; - - g_return_if_fail(user_data != NULL); - mergeData = (QofBookMergeData*)user_data; - g_return_if_fail(param != NULL); - if ((param->param_getfcn != NULL) && (param->param_setfcn != NULL)) - { - mergeData->mergeObjectParams = g_slist_append(mergeData->mergeObjectParams, param); - } -} - -static void -qof_book_merge_foreach_type ( QofObject* merge_obj, gpointer user_data) -{ - QofBookMergeData *mergeData; - - g_return_if_fail(user_data != NULL); - mergeData = (QofBookMergeData*)user_data; - g_return_if_fail((merge_obj != NULL)); - /* Skip unsupported objects */ - if ((merge_obj->create == NULL) || (merge_obj->foreach == NULL)) - { - DEBUG (" merge_obj QOF support failed %s", merge_obj->e_type); - return; - } - if (mergeData->mergeObjectParams != NULL) g_slist_free(mergeData->mergeObjectParams); - mergeData->mergeObjectParams = NULL; - qof_class_param_foreach(merge_obj->e_type, qof_book_merge_foreach_param , mergeData); - qof_object_foreach(merge_obj->e_type, mergeData->mergeBook, - qof_book_merge_foreach, mergeData); -} - -static void -qof_book_merge_rule_cb(gpointer rule, gpointer arg) -{ - struct QofBookMergeRuleIterate *iter; - QofBookMergeData *mergeData; - - g_return_if_fail(arg != NULL); - iter = (struct QofBookMergeRuleIterate*)arg; - mergeData = iter->data; - g_return_if_fail(mergeData != NULL); - g_return_if_fail(mergeData->abort == FALSE); - iter->fcn (mergeData, (QofBookMergeRule*)rule, iter->remainder); - iter->data = mergeData; - iter->remainder--; -} - - -/** - * Creates an object when the MergeResult is MERGE_NEW. Called for each MergeRule. - */ -static void -qof_book_merge_commit_rule_create_objects(QofBookMergeData *mergeData, - QofBookMergeRule *rule, - guint remainder) -{ - QofInstance *inst; - - g_return_if_fail(rule != NULL); - g_return_if_fail(mergeData != NULL); - g_return_if_fail(mergeData->targetBook != NULL); - g_return_if_fail(rule->mergeResult == MERGE_NEW); - - /* The new object takes the GncGUID from the import to retain an absolute match */ - inst = qof_object_new_instance(rule->importEnt->e_type, mergeData->targetBook); - g_return_if_fail(inst != NULL); - rule->targetEnt = inst; - qof_instance_copy_guid(rule->targetEnt, rule->importEnt); -} - - -/** - * Returns the corresponding target entity to the given importEnt - */ -static QofInstance* -qof_book_merge_map_entity(const QofBookMergeData *mergeData, const QofInstance* importEnt) -{ - QofBookMergeRule *currentRule; - GList *node; - - for (node = mergeData->mergeList; node != NULL; node = node->next) - { - currentRule = node->data; - if (currentRule->importEnt == importEnt) - { - return currentRule->targetEnt; - } - } - PINFO ("qof_book_merge_map_entity: Import Entity not found"); - return NULL; -} - -typedef struct -{ - QofBookMergeData *mergeData; - QofCollection *mapped_coll; -} QofBookMergeMapCollectionIterate; - - -/** - * Map all entities given in importEnt and add them into the mapped_coll - */ -static void -qof_book_merge_map_collection_cb(QofInstance* importEnt, gpointer user_data) -{ - QofBookMergeMapCollectionIterate *mapped_coll_iter; - QofInstance *targetEnt; - - mapped_coll_iter = (QofBookMergeMapCollectionIterate*)user_data; - targetEnt = qof_book_merge_map_entity(mapped_coll_iter->mergeData, importEnt); - qof_collection_add_entity(mapped_coll_iter->mapped_coll, targetEnt); -} - - -static void -qof_book_merge_commit_rule_loop(QofBookMergeData *mergeData, - QofBookMergeRule *rule, - guint remainder) -{ - gboolean registered_type; - QofInstance *referenceEnt; - /* cm_ prefix used for variables that hold the data to commit */ - QofCollection *cm_coll, *mapped_coll; - QofBookMergeMapCollectionIterate mapped_coll_iter; - QofParam *cm_param; - gchar *cm_string; - const GncGUID *cm_guid; - KvpFrame *cm_kvp; - /* function pointers and variables for parameter getters that don't use pointers normally */ - gnc_numeric cm_numeric, (*numeric_getter) (QofInstance*, QofParam*); - double cm_double, (*double_getter) (QofInstance*, QofParam*); - gboolean cm_boolean, (*boolean_getter) (QofInstance*, QofParam*); - gint32 cm_i32, (*int32_getter) (QofInstance*, QofParam*); - gint64 cm_i64, (*int64_getter) (QofInstance*, QofParam*); - Timespec cm_date, (*date_getter) (QofInstance*, QofParam*); - gchar cm_char, (*char_getter) (QofInstance*, QofParam*); - /* function pointers to the parameter setters */ - void (*string_setter) (QofInstance*, const gchar*); - void (*date_setter) (QofInstance*, Timespec); - void (*numeric_setter) (QofInstance*, gnc_numeric); - void (*guid_setter) (QofInstance*, const GncGUID*); - void (*double_setter) (QofInstance*, double); - void (*boolean_setter) (QofInstance*, gboolean); - void (*i32_setter) (QofInstance*, gint32); - void (*i64_setter) (QofInstance*, gint64); - void (*char_setter) (QofInstance*, gchar); - void (*kvp_frame_setter) (QofInstance*, KvpFrame*); - void (*reference_setter) (QofInstance*, QofInstance*); - void (*collection_setter)(QofInstance*, QofCollection*); - - g_return_if_fail(rule != NULL); - g_return_if_fail(mergeData != NULL); - g_return_if_fail(mergeData->targetBook != NULL); - g_return_if_fail(rule->importEnt && rule->targetEnt); - g_return_if_fail((rule->mergeResult != MERGE_NEW) || (rule->mergeResult != MERGE_UPDATE)); - - DEBUG ("qof_book_merge_commit_rule_loop rule: type: %s, result: %s, importEnt Type: %s, guid: %s", - rule->mergeType, rule->mergeResult == MERGE_NEW ? "New" : "Update", - rule->importEnt->e_type, - guid_to_string(qof_instance_get_guid(rule->importEnt))); - DEBUG ("qof_book_merge_commit_rule_loop rule (cont.): targetEnt Type: %s, guid: %s", - rule->targetEnt->e_type, - guid_to_string(qof_instance_get_guid(rule->targetEnt))); - - /* currentRule->targetEnt is now set, - 1. by an absolute GncGUID match or - 2. by best_matchEnt and difference or - 3. by MERGE_NEW. - */ - while (rule->mergeParam != NULL) - { - registered_type = FALSE; - g_return_if_fail(rule->mergeParam->data); - cm_param = rule->mergeParam->data; - rule->mergeType = cm_param->param_type; - - DEBUG ("qof_book_merge_commit_rule_loop param: Merge Type: %s, Param Name: %s", - rule->mergeType, cm_param->param_name); - - if (safe_strcmp(rule->mergeType, QOF_TYPE_STRING) == 0) - { - cm_string = cm_param->param_getfcn(rule->importEnt, cm_param); - string_setter = (void(*)(QofInstance*, const gchar*))cm_param->param_setfcn; - if (string_setter != NULL) - { - string_setter(rule->targetEnt, cm_string); - } - registered_type = TRUE; - } - if (safe_strcmp(rule->mergeType, QOF_TYPE_DATE) == 0) - { - date_getter = (Timespec (*)(QofInstance*, QofParam*))cm_param->param_getfcn; - cm_date = date_getter(rule->importEnt, cm_param); - date_setter = (void(*)(QofInstance*, Timespec))cm_param->param_setfcn; - if (date_setter != NULL) - { - date_setter(rule->targetEnt, cm_date); - } - registered_type = TRUE; - } - if ((safe_strcmp(rule->mergeType, QOF_TYPE_NUMERIC) == 0) || - (safe_strcmp(rule->mergeType, QOF_TYPE_DEBCRED) == 0)) - { - numeric_getter = (gnc_numeric (*)(QofInstance*, QofParam*))cm_param->param_getfcn; - cm_numeric = numeric_getter(rule->importEnt, cm_param); - numeric_setter = (void(*)(QofInstance*, gnc_numeric))cm_param->param_setfcn; - if (numeric_setter != NULL) - { - numeric_setter(rule->targetEnt, cm_numeric); - } - registered_type = TRUE; - } - if (safe_strcmp(rule->mergeType, QOF_TYPE_GUID) == 0) - { - cm_guid = cm_param->param_getfcn(rule->importEnt, cm_param); - guid_setter = (void(*)(QofInstance*, const GncGUID*))cm_param->param_setfcn; - if (guid_setter != NULL) - { - guid_setter(rule->targetEnt, cm_guid); - } - registered_type = TRUE; - } - if (safe_strcmp(rule->mergeType, QOF_TYPE_INT32) == 0) - { - int32_getter = (gint32 (*)(QofInstance*, QofParam*)) cm_param->param_getfcn; - cm_i32 = int32_getter(rule->importEnt, cm_param); - i32_setter = (void(*)(QofInstance*, gint32))cm_param->param_setfcn; - if (i32_setter != NULL) - { - i32_setter(rule->targetEnt, cm_i32); - } - registered_type = TRUE; - } - if (safe_strcmp(rule->mergeType, QOF_TYPE_INT64) == 0) - { - int64_getter = (gint64 (*)(QofInstance*, QofParam*)) cm_param->param_getfcn; - cm_i64 = int64_getter(rule->importEnt, cm_param); - i64_setter = (void(*)(QofInstance*, gint64))cm_param->param_setfcn; - if (i64_setter != NULL) - { - i64_setter(rule->targetEnt, cm_i64); - } - registered_type = TRUE; - } - if (safe_strcmp(rule->mergeType, QOF_TYPE_DOUBLE) == 0) - { - double_getter = (double (*)(QofInstance*, QofParam*)) cm_param->param_getfcn; - cm_double = double_getter(rule->importEnt, cm_param); - double_setter = (void(*)(QofInstance*, double))cm_param->param_setfcn; - if (double_setter != NULL) - { - double_setter(rule->targetEnt, cm_double); - } - registered_type = TRUE; - } - if (safe_strcmp(rule->mergeType, QOF_TYPE_BOOLEAN) == 0) - { - boolean_getter = (gboolean (*)(QofInstance*, QofParam*)) cm_param->param_getfcn; - cm_boolean = boolean_getter(rule->importEnt, cm_param); - boolean_setter = (void(*)(QofInstance*, gboolean))cm_param->param_setfcn; - if (boolean_setter != NULL) - { - boolean_setter(rule->targetEnt, cm_boolean); - } - registered_type = TRUE; - } - if (safe_strcmp(rule->mergeType, QOF_TYPE_KVP) == 0) - { - cm_kvp = kvp_frame_copy(cm_param->param_getfcn(rule->importEnt, cm_param)); - kvp_frame_setter = (void(*)(QofInstance*, KvpFrame*))cm_param->param_setfcn; - if (kvp_frame_setter != NULL) - { - kvp_frame_setter(rule->targetEnt, cm_kvp); - } - registered_type = TRUE; - } - if (safe_strcmp(rule->mergeType, QOF_TYPE_CHAR) == 0) - { - char_getter = (gchar (*)(QofInstance*, QofParam*)) cm_param->param_getfcn; - cm_char = char_getter(rule->importEnt, cm_param); - char_setter = (void(*)(QofInstance*, gchar))cm_param->param_setfcn; - if (char_setter != NULL) - { - char_setter(rule->targetEnt, cm_char); - } - registered_type = TRUE; - } - if (safe_strcmp(rule->mergeType, QOF_TYPE_COLLECT) == 0) - { - cm_coll = cm_param->param_getfcn(rule->importEnt, cm_param); - - /* Created mapped collection */ - mapped_coll = qof_collection_new(qof_collection_get_type(cm_coll)); - mapped_coll_iter.mergeData = mergeData; - mapped_coll_iter.mapped_coll = mapped_coll; - qof_collection_foreach(cm_coll, qof_book_merge_map_collection_cb, &mapped_coll_iter); - - collection_setter = (void(*)(QofInstance*, QofCollection*))cm_param->param_setfcn; - if (collection_setter != NULL) - { - collection_setter(rule->targetEnt, mapped_coll); - } - registered_type = TRUE; - } - if (safe_strcmp(rule->mergeType, QOF_TYPE_CHOICE) == 0) - { - referenceEnt = cm_param->param_getfcn(rule->importEnt, cm_param); - referenceEnt = qof_book_merge_map_entity(mergeData, referenceEnt); - reference_setter = (void(*)(QofInstance*, QofInstance*))cm_param->param_setfcn; - if (reference_setter != NULL) - { - reference_setter(rule->targetEnt, referenceEnt); - } - registered_type = TRUE; - } - if (registered_type == FALSE) - { - referenceEnt = cm_param->param_getfcn(rule->importEnt, cm_param); - - // XXX gncOwner is an object that could be returned, but does not have QofInstance - if (safe_strcmp(cm_param->param_type, "gncOwner") == 0) - referenceEnt = NULL; - - referenceEnt = qof_book_merge_map_entity(mergeData, referenceEnt); - - if (referenceEnt) - { - reference_setter = (void(*)(QofInstance*, QofInstance*))cm_param->param_setfcn; - if (reference_setter != NULL) - { - reference_setter(rule->targetEnt, referenceEnt); - } - } - } - rule->mergeParam = g_slist_next(rule->mergeParam); - } -} -/* ================================================================ */ -/* API functions. */ - -QofBookMergeData* -qof_book_merge_init( QofBook *importBook, QofBook *targetBook) -{ - QofBookMergeData *mergeData; - QofBookMergeRule *currentRule; - GList *node; - - ENTER (" "); - - g_return_val_if_fail((importBook != NULL) && (targetBook != NULL), NULL); - mergeData = g_new0(QofBookMergeData, 1); - mergeData->abort = FALSE; - mergeData->mergeList = NULL; - mergeData->targetList = NULL; - mergeData->mergeBook = importBook; - mergeData->targetBook = targetBook; - mergeData->mergeObjectParams = NULL; - mergeData->orphan_list = NULL; - mergeData->target_table = g_hash_table_new( g_direct_hash, qof_book_merge_rule_cmp); - currentRule = g_new0(QofBookMergeRule, 1); - mergeData->currentRule = currentRule; - qof_object_foreach_type(qof_book_merge_foreach_type, mergeData); - g_return_val_if_fail(mergeData->mergeObjectParams, NULL); - if (mergeData->orphan_list != NULL) - { - qof_book_merge_match_orphans(mergeData); - } - - for (node = mergeData->mergeList; node != NULL; node = node->next) - { - currentRule = node->data; - if (currentRule->mergeResult == MERGE_INVALID) - { - mergeData->abort = TRUE; - return(NULL); - } - } - - LEAVE (" "); - return mergeData; -} - -void -qof_book_merge_abort (QofBookMergeData *mergeData) -{ - QofBookMergeRule *currentRule; - - g_return_if_fail(mergeData != NULL); - while (mergeData->mergeList != NULL) - { - currentRule = mergeData->mergeList->data; - g_slist_free(currentRule->linkedEntList); - g_slist_free(currentRule->mergeParam); - g_free(mergeData->mergeList->data); - if (currentRule) - { - g_slist_free(currentRule->linkedEntList); - g_slist_free(currentRule->mergeParam); - g_free(currentRule); - } - mergeData->mergeList = g_list_next(mergeData->mergeList); - } - g_list_free(mergeData->mergeList); - g_slist_free(mergeData->mergeObjectParams); - g_slist_free(mergeData->targetList); - if (mergeData->orphan_list != NULL) - { - g_slist_free(mergeData->orphan_list); - } - g_hash_table_destroy(mergeData->target_table); - g_free(mergeData); -} - -/* The QOF_TYPE_DATE output format from -qof_book_merge_param_as_string has been changed to QSF_XSD_TIME, -a UTC formatted timestring: 2005-01-01T10:55:23Z -If you change QOF_UTC_DATE_FORMAT, change -backend/file/qsf-xml.c : qsf_entity_foreach to -reformat to QSF_XSD_TIME or the QSF XML will -FAIL the schema validation and QSF exports will become invalid. - -The QOF_TYPE_BOOLEAN is lowercase for the same reason. - -\todo deprecate and replace with -gchar* qof_instance_param_as_string(const QofParam*, QofInstance*); -and then add -gchar* qof_class_get_param_as_string(QofIdTypeConst, QofInstance*); ? -*/ -gchar* -qof_book_merge_param_as_string(QofParam *qtparam, QofInstance *qtEnt) -{ - gchar *param_string, param_date[QOF_DATE_STRING_LENGTH]; - gchar param_sa[GUID_ENCODING_LENGTH + 1]; - QofType paramType; - const GncGUID *param_guid; - time_t param_t; - gnc_numeric param_numeric, (*numeric_getter) (QofInstance*, QofParam*); - Timespec param_ts, (*date_getter) (QofInstance*, QofParam*); - double param_double, (*double_getter) (QofInstance*, QofParam*); - gboolean param_boolean, (*boolean_getter) (QofInstance*, QofParam*); - gint32 param_i32, (*int32_getter) (QofInstance*, QofParam*); - gint64 param_i64, (*int64_getter) (QofInstance*, QofParam*); - gchar param_char, (*char_getter) (QofInstance*, QofParam*); - - param_string = NULL; - paramType = qtparam->param_type; - if (safe_strcmp(paramType, QOF_TYPE_STRING) == 0) - { - param_string = g_strdup(qtparam->param_getfcn(qtEnt, qtparam)); - if (param_string == NULL) - { - param_string = ""; - } - return param_string; - } - if (safe_strcmp(paramType, QOF_TYPE_DATE) == 0) - { - date_getter = (Timespec (*)(QofInstance*, QofParam*))qtparam->param_getfcn; - param_ts = date_getter(qtEnt, qtparam); - param_t = timespecToTime_t(param_ts); - qof_strftime(param_date, QOF_DATE_STRING_LENGTH, QOF_UTC_DATE_FORMAT, gmtime(¶m_t)); - param_string = g_strdup(param_date); - return param_string; - } - if ((safe_strcmp(paramType, QOF_TYPE_NUMERIC) == 0) || - (safe_strcmp(paramType, QOF_TYPE_DEBCRED) == 0)) - { - numeric_getter = (gnc_numeric (*)(QofInstance*, QofParam*)) qtparam->param_getfcn; - param_numeric = numeric_getter(qtEnt, qtparam); - param_string = g_strdup(gnc_numeric_to_string(param_numeric)); - return param_string; - } - if (safe_strcmp(paramType, QOF_TYPE_GUID) == 0) - { - param_guid = qtparam->param_getfcn(qtEnt, qtparam); - guid_to_string_buff(param_guid, param_sa); - param_string = g_strdup(param_sa); - return param_string; - } - if (safe_strcmp(paramType, QOF_TYPE_INT32) == 0) - { - int32_getter = (gint32 (*)(QofInstance*, QofParam*)) qtparam->param_getfcn; - param_i32 = int32_getter(qtEnt, qtparam); - param_string = g_strdup_printf("%d", param_i32); - return param_string; - } - if (safe_strcmp(paramType, QOF_TYPE_INT64) == 0) - { - int64_getter = (gint64 (*)(QofInstance*, QofParam*)) qtparam->param_getfcn; - param_i64 = int64_getter(qtEnt, qtparam); - param_string = g_strdup_printf("%" G_GINT64_FORMAT, param_i64); - return param_string; - } - if (safe_strcmp(paramType, QOF_TYPE_DOUBLE) == 0) - { - double_getter = (double (*)(QofInstance*, QofParam*)) qtparam->param_getfcn; - param_double = double_getter(qtEnt, qtparam); - param_string = g_strdup_printf("%f", param_double); - return param_string; - } - if (safe_strcmp(paramType, QOF_TYPE_BOOLEAN) == 0) - { - boolean_getter = (gboolean (*)(QofInstance*, QofParam*)) qtparam->param_getfcn; - param_boolean = boolean_getter(qtEnt, qtparam); - /* Boolean values need to be lowercase for QSF validation. */ - if (param_boolean == TRUE) - { - param_string = g_strdup("true"); - } - else - { - param_string = g_strdup("false"); - } - return param_string; - } - /* "kvp" contains repeating values, cannot be a single string for the frame. */ - if (safe_strcmp(paramType, QOF_TYPE_KVP) == 0) - { - return param_string; - } - if (safe_strcmp(paramType, QOF_TYPE_CHAR) == 0) - { - char_getter = (gchar (*)(QofInstance*, QofParam*)) qtparam->param_getfcn; - param_char = char_getter(qtEnt, qtparam); - param_string = g_strdup_printf("%c", param_char); - return param_string; - } - return NULL; -} - -QofBookMergeData* -qof_book_merge_update_result (QofBookMergeData *mergeData, - QofBookMergeResult tag) -{ - QofBookMergeRule *resolved; - - g_return_val_if_fail((mergeData != NULL), NULL); - g_return_val_if_fail((tag > 0), NULL); - g_return_val_if_fail((tag != MERGE_REPORT), NULL); - resolved = mergeData->currentRule; - g_return_val_if_fail((resolved != NULL), NULL); - if ((resolved->mergeAbsolute == TRUE) && (tag == MERGE_DUPLICATE)) - { - tag = MERGE_ABSOLUTE; - } - if ((resolved->mergeAbsolute == TRUE) && (tag == MERGE_NEW)) - { - tag = MERGE_UPDATE; - } - if ((resolved->mergeAbsolute == FALSE) && (tag == MERGE_ABSOLUTE)) - { - tag = MERGE_DUPLICATE; - } - if ((resolved->mergeResult == MERGE_NEW) && (tag == MERGE_UPDATE)) - { - tag = MERGE_NEW; - } - if (resolved->updated == FALSE) - { - resolved->mergeResult = tag; - } - resolved->updated = TRUE; - if (tag >= MERGE_INVALID) - { - mergeData->abort = TRUE; - mergeData->currentRule = resolved; - return NULL; - } - mergeData->currentRule = resolved; - return mergeData; -} - -gint -qof_book_merge_commit(QofBookMergeData *mergeData ) -{ - QofBookMergeRule *currentRule; - GList *check, *node; - - ENTER (" "); - - g_return_val_if_fail(mergeData != NULL, -10); - g_return_val_if_fail(mergeData->mergeList != NULL, -11); - g_return_val_if_fail(mergeData->targetBook != NULL, -12); - if (mergeData->abort == TRUE) return -13; - check = g_list_copy(mergeData->mergeList); - g_return_val_if_fail(check != NULL, -14); - for (node = check; node != NULL; node = node->next) - { - currentRule = node->data; - - if (currentRule->mergeResult == MERGE_INVALID) - { - qof_book_merge_abort(mergeData); - g_list_free(check); - return(-2); - } - if (currentRule->mergeResult == MERGE_REPORT) - { - g_list_free(check); - return 1; - } - } - g_list_free(check); - qof_book_merge_commit_foreach(qof_book_merge_commit_rule_create_objects, - MERGE_NEW, mergeData); - qof_book_merge_commit_foreach(qof_book_merge_commit_rule_loop, - MERGE_NEW, mergeData); - qof_book_merge_commit_foreach(qof_book_merge_commit_rule_loop, - MERGE_UPDATE, mergeData); - - /* Placeholder for QofObject merge_helper_cb - all objects - and all parameters set */ - while (mergeData->mergeList != NULL) - { - currentRule = mergeData->mergeList->data; - g_slist_free(currentRule->mergeParam); - g_slist_free(currentRule->linkedEntList); - mergeData->mergeList = g_list_next(mergeData->mergeList); - } - g_list_free(mergeData->mergeList); - g_slist_free(mergeData->mergeObjectParams); - g_slist_free(mergeData->targetList); - if (mergeData->orphan_list != NULL) - { - g_slist_free(mergeData->orphan_list); - } - g_hash_table_destroy(mergeData->target_table); - g_free(mergeData); - - LEAVE (" "); - return 0; -} - -void -qof_book_merge_rule_foreach (QofBookMergeData *mergeData, - QofBookMergeRuleForeachCB cb, - QofBookMergeResult mergeResult ) -{ - struct QofBookMergeRuleIterate iter; - QofBookMergeRule *currentRule; - GList *matching_rules, *node; - - g_return_if_fail(cb != NULL); - g_return_if_fail(mergeData != NULL); - currentRule = mergeData->currentRule; - g_return_if_fail(mergeResult > 0); - g_return_if_fail(mergeResult != MERGE_INVALID); - g_return_if_fail(mergeData->abort == FALSE); - iter.fcn = cb; - iter.data = mergeData; - matching_rules = NULL; - iter.ruleList = NULL; - for (node = mergeData->mergeList; node != NULL; node = node->next) - { - currentRule = node->data; - if (currentRule->mergeResult == mergeResult) - { - matching_rules = g_list_prepend(matching_rules, currentRule); - } - } - iter.remainder = g_list_length(matching_rules); - g_list_foreach (matching_rules, qof_book_merge_rule_cb, &iter); - g_list_free(matching_rules); -} - -/* End of file. */ -/* ==================================================================== */ diff --git a/src/libqof/qof/qofbookmerge.h b/src/libqof/qof/qofbookmerge.h deleted file mode 100644 index 5714d21e03..0000000000 --- a/src/libqof/qof/qofbookmerge.h +++ /dev/null @@ -1,495 +0,0 @@ -/********************************************************************* - * qofbookmerge.h -- api for QofBook merge with collision handling * - * Copyright (C) 2004 Neil Williams * - * * - * This program is free software; you can redistribute it and/or * - * modify it under the terms of the GNU General Public License as * - * published by the Free Software Foundation; either version 2 of * - * the License, or (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, contact: * - * * - * Free Software Foundation Voice: +1-617-542-5942 * - * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 * - * Boston, MA 02110-1301, USA gnu@gnu.org * - * * - ********************************************************************/ - -#ifndef QOFBOOKMERGE_H -#define QOFBOOKMERGE_H - -#define QOF_MOD_MERGE "qof.merge" - -/** @addtogroup BookMerge - -Collision handling principles.\n -\n - -# Always check for a ::GncGUID first and compare. qofbookmerge only accepts - valid ::QofBook data and therefore ALL objects in the import book will - include valid GncGUID's. - -# If the original import data did not contain a GncGUID (e.g. an external - non-GnuCash source) the GncGUID values will have been created during the - import and will not match any existing GncGUID's in the target book so objects - that do not have a GncGUID match cannot be assumed to be ::MERGE_NEW - parameter - values must be checked. - -# If import contains data from closed books, store the data from the closed - books in the current book as active. i.e. re-open the books. - -- If a GncGUID match exists, set qof_book_merge_rule::mergeAbsolute to \a TRUE. - -# If ALL parameters in the import object match the target object with - the same \a GncGUID, - set ::qof_book_merge_result to \a MERGE_ABSOLUTE. - -# If any parameters differ, set ::MERGE_UPDATE. -- If the import object \a GncGUID does not match an existing object, -mergeAbsolute is unchanged from the default \a FALSE -The parameter values of the object are compared to other objects of the same -type in the target book. - -# If the same data exists in the target book with a different GncGUID, the object - is tagged as DUPLICATE. - -# If the data has changed, the object is tagged as REPORT. - -# If the data does not match, the object is tagged as NEW - -More information is at http://code.neil.williamsleesmill.me.uk/ - -Each foreach function uses g_return_if_fail checks to protect the target book. -If any essential data is missing, the loop returns without changing the target -book. Note that this will not set or return an error value. However, g_return -is only used for critical errors that arise from programming errors, not for -invalid import data which should be cleaned up before creating the import -QofBook. - -Only ::qof_book_merge_update_result and ::qof_book_merge_commit return -any error values to the calling process. ::qof_book_merge_init returns a -pointer to the ::QofBookMergeData struct - the calling process needs to -make sure this is non-NULL to know that the Init has been successful. - - @{ -*/ -/** @file qofbookmerge.h - @brief API for merging two \c QofBook structures with collision handling - @author Copyright (c) 2004-2005 Neil Williams -*/ - -#include "qofutil.h" -#include "qofbook.h" -#include "qofclass.h" -#include "qofobject.h" -#include "qofinstance.h" -#include "qoflog.h" - -/** \brief Results of collisions and user resolution. - -All rules are initialised as ::MERGE_UNDEF. -Once the comparison is complete, each object within the import will be -updated. - -::MERGE_ABSOLUTE, ::MERGE_NEW, ::MERGE_DUPLICATE and ::MERGE_UPDATE can be -reported to the user along with all ::MERGE_REPORT objects for confirmation. -It may be useful later to allow \a MERGE_ABSOLUTE, \a MERGE_NEW, -\a MERGE_DUPLICATE and \a MERGE_UPDATE to not be reported, if the user sets a -preferences option for each result. (Always accept new items: Y/N default NO, -ignores all MERGE_NEW if set to Y etc.) This option would not require any -changes to qofbookmerge. - -\a MERGE_NEW, \a MERGE_DUPLICATE and \a MERGE_UPDATE are only actioned after -conflicts are resolved by the user using a dialog and all \a MERGE_REPORT -objects are re-assigned to one of MERGE_NEW, MERGE_DUPLICATE or MERGE_UPDATE. -There is no automatic merge, even if no entities are tagged as MERGE_REPORT, -the calling process must still check for REPORT items using -::qof_book_merge_rule_foreach and call ::qof_book_merge_commit. - -\a MERGE_INVALID data should be rare and allows for user-abort - the imported -file/source may be corrupted and the prescence of invalid data should raise -concerns that the rest of the data may be corrupted, damaged or otherwise -altered. If any entity is tagged as MERGE_INVALID, the merge operation will -abort and leave the target book completely unchanged. - -\a MERGE_ABSOLUTE is only used for a complete match. The import object contains -the same data in the same parameters with no omissions or amendments. If any -data is missing, amended or added, the data is labelled \a MERGE_UPDATE. - - Every piece of data has a corresponding result. Only when the count of items - labelled \a MERGE_REPORT is equal to zero are \a MERGE_NEW and \a MERGE_UPDATE - items added to the existing book.\n \a MERGE_DUPLICATE items are silently - ignored. Aborting the dialogue/process (by the user or in a program crash) at - any point before the final commit leaves the existing book completely untouched. -*/ -typedef enum -{ - MERGE_UNDEF, /**< default value before comparison is made. */ - MERGE_ABSOLUTE, /**< GncGUID exact match, no new data - \b ignore */ - MERGE_NEW, /**< import object does \b not exist in the target - book - \b add */ - MERGE_REPORT, /**< import object needs user intervention - \b report */ - MERGE_DUPLICATE, /**< import object with different GncGUID exactly matches - existing GncGUID - \b ignore */ - MERGE_UPDATE, /**< import object matches an existing entity but includes - new or modified parameter data - \b update */ - MERGE_INVALID /**< import object didn't match registered object or - parameter types or user decided to abort - \b abort */ -} QofBookMergeResult; - -/** \brief One rule per entity, built into a single GList for the entire merge - -All rules are stored in the GList QofBookMergeData::mergeList. - -If the ::GncGUID matches it's the always same semantic object, -regardless of whether other data fields are changed. -\n -The boolean value mergeAbsolute defaults to \c FALSE - -NOTE 1: if mergeAbsolute == \c TRUE, ::QofBookMergeResult will still be set -to ::MERGE_UPDATE if parameters within this entity have been modified. - -NOTE 2: ::qof_book_merge_param_as_string returns \b string representations of -the parameter data that is causing a collision. These values must \b NOT be -used to set the target parameter - the function is provided for display -purposes only, to make it simple to explain the collision to the user using -MERGE_REPORT and the dialogue. - -The GHashTable targetTable in QofBookMergeRule will probably replace the -GSList of the -same name in mergeData. - -*/ - -typedef struct -{ - /* internal counters and reference variables */ - gboolean mergeAbsolute; /**< Only set if the GncGUID of the import matches - the target */ - double difference; /**< used to find best match in a book where no - GncGUID matches */ - gboolean updated; /**< prevent the mergeResult from being - overwritten. */ - /* rule objects set from or by external calls */ - QofIdType mergeType; /**< type of comparison required for check for - collision */ - const gchar* mergeLabel; /**< Descriptive label for the object type, - useful for the user intervention dialogue. */ - GSList *mergeParam; /**< list of usable parameters for the object type */ - GSList *linkedEntList; /**< list of complex data types included in this object. - - linkedEntList contains an ::QofInstance reference to any parameter that is not - one of the core QOF_TYPE data types. This entity must be already - registered with QOF and the results of the comparison for the linked entity - will modulate the mergeResult of this object. e.g. if an invoice is the - same value but for a different customer, the invoice will be set to - MERGE_REPORT and the customer as MERGE_NEW. - */ - QofBookMergeResult mergeResult; /**< result of comparison with main ::QofBook */ - QofInstance *importEnt; /**< pointer to the current entity in the import book. */ - QofInstance *targetEnt; /**< pointer to the corresponding entity in the - target book, if any. */ -} QofBookMergeRule; - - -/** \brief mergeData contains the essential context data for any merge. - -Used to dictate what to merge, how to merge it, where to get the new data and -where to put the amended data. - -Combines lists of \a ::QofParam, \a ::QofInstance and \a ::QofBookMergeRule -into one struct that can be easily passed between callbacks. Also holds the -pointers to the import and target ::QofBook structures. - -- targetList and mergeObjectParams change each time a new object type -is set for compare. -- mergeList is the complete list of rules for all objects in the import book. - -*/ -typedef struct -{ - GSList *mergeObjectParams; /**< GSList of ::QofParam details for each - parameter in the current object. */ - GList *mergeList; /**< GList of all ::QofBookMergeRule rules - for the merge operation. */ - GSList *targetList; /**< GSList of ::QofInstance * for each object - of this type in the target book */ - QofBook *mergeBook; /**< pointer to the import book for this - merge operation. */ - QofBook *targetBook; /**< pointer to the target book for this - merge operation. */ - gboolean abort; /**< set to TRUE if MERGE_INVALID is set. */ - QofBookMergeRule *currentRule; /**< placeholder for the rule currently - being tested or applied. */ - GSList *orphan_list; /**< List of QofInstance's that need to be rematched. - - When one QofInstance has a lower difference to the targetEnt than the - previous best_match, the new match takes precedence. This list holds those - orphaned entities that are not a good enough match so that these can be - rematched later. The ranking is handled using the private QofInstanceRating - struct and the GHashTable ::QofBookMergeData::target_table. - */ - GHashTable *target_table; /**< The GHashTable to hold the - QofInstanceRating values. */ - -} QofBookMergeData; - - -/* ======================================================================== */ -/** @name qof_book_merge API - @{ -*/ -/** \brief Initialise the QofBookMerge process - - First function of the QofBookMerge API. Every merge must begin with init. - - Requires the book to import (::QofBook *) and the book to receive the - import, the target book (::QofBook *). Returns a pointer to - ::QofBookMergeData which must be checked for a NULL before continuing. \n -Process: - - -# Invoke the callback ::qof_book_merge_foreach_type on every registered - object class definition. - -# Callback obtains the registered parameter list for each object type. - This provides run time access to all registered objects and all object - parameters without any changes to QofBookMerge - no registered object or - parameter is omitted from any merge operation. - -# Use ::qof_object_foreach to invoke the callback ::qof_book_merge_foreach, - one object at a time on every instance stored in mergeBook. This is the - first point where real data from the import book is accessed. - -# qof_book_merge_foreach obtains the ::GncGUID for the object from the import - book and runs the first check on the original book, checking for any exact - GncGUID match. With the full parameter list, the rules for this object can be - created. If there is a GncGUID match, the data in each parameter of the import - object is compared with the same semantic object in the original book. If - there is no GncGUID in the import object or no GncGUID match with the original - book, the original book is searched to find a parameter match - checking - for a ::MERGE_DUPLICATE result. - -# ::qof_book_merge_compare sets the ::QofBookMergeResult of the comparison. - -# Inserts the completed rule into QofBookMergeData::mergeList GSList. - -\return NULL in case of error, otherwise a ::QofBookMergeData* metadata context. - -*/ -QofBookMergeData* -qof_book_merge_init( QofBook *importBook, QofBook *targetBook); - - -/** \brief Definition of the dialogue control callback routine - -All ::MERGE_REPORT rules must be offered for user intervention using this -template.\n -Commit will fail if any rules are still tagged as \a MERGE_REPORT. - -Calling processes are free to also offer MERGE_NEW, MERGE_UPDATE, -MERGE_DUPLICATE and MERGE_ABSOLUTE for user intervention. Attempting to query -MERGE_INVALID rules will cause an error. - -For an example, consider test_rule_loop, declared as: - -void test_rule_loop(QofBookMergeData *mergeData, QofBookMergeRule *rule, guint remainder);\n -void test_rule_loop(QofBookMergeData *mergeData, QofBookMergeRule *rule, guint remainder) \n -{\n - g_return_if_fail(rule != NULL);\n - g_return_if_fail(mergeData != NULL); - printf("Rule Result %s", rule->mergeType);\n - qof_book_merge_update_result(mergeData, rule, MERGE_UPDATE);\n -} - -The dialogue is free to call ::qof_book_merge_update_result in the loop or at the end -as long as the link between the rule and the result is maintained, e.g. by using a -GHashTable. -\n -The parameters are: - - data : pointer to the ::QofBookMergeData metadata context returned by init. - - rule : pointer to the ::QofBookMergeRule that generated the collision report - - remainder : guint value returned from g_slist_length for the number of other - rules remaining with the same result. This might be useful for a - progress dialogue, it might not. When updating MERGE_REPORT, - remainder must equal zero before calling ::qof_book_merge_commit or - the import will abort. -\n - -If the dialogue sets \b any rule result to ::MERGE_INVALID, the import will -abort when ::qof_book_merge_commit is called. It is the responsibility of -the calling function to handle the error code from ::qof_book_merge_commit, -close the dialogue and return. The merge routines in these files will already -have halted the merge operation and freed any memory allocated to merge -structures before returning the error code. There is no need for the dialogue -process to report back to QofBookMerge in this situation. -*/ -typedef void (* QofBookMergeRuleForeachCB)( QofBookMergeData*, QofBookMergeRule*, guint); - -/** \brief Dialogue Control Callback - -This function is designed to be used to iterate over all rules tagged with a -specific ::QofBookMergeResult value. - -@param callback external loop of type QofBookMergeRuleForeachCB -@param mergeResult ::QofBookMergeResult value to look up. -@param mergeData ::QofBookMergeData merge context. - -\b Note : MERGE_NEW causes a new entity to be created in the target book at -commit which is then assigned as the targetEnt of that rule. If -mergeResult == MERGE_NEW, the rules returned by qof_book_merge_rule_foreach -will have a NULL set for the targetEnt. This is because commit has not yet -been called and no changes can be made to the target book. The calling -process must handle the NULL targetEnt and NOT call any param_getfcn -routines for the target entity. The import entity is available for display. - -Uses ::qof_book_get_collection with the QofBookMergeRule::mergeType object -type to return a collection of ::QofInstance entities from either the -QofBookMergeData::mergeBook or QofBookMergeData::targetBook. Then -uses ::qof_collection_lookup_entity to lookup the QofBookMergeRule::importEnt -and again the QofBookMergeRule::targetEnt to return the two specific entities. - -*/ -void qof_book_merge_rule_foreach( QofBookMergeData* mergeData, - QofBookMergeRuleForeachCB callback , - QofBookMergeResult mergeResult); - -/** \brief provides easy string access to parameter data for dialogue use - -Uses the param_getfcn to retrieve the parameter value as a string, suitable for -display in dialogues and user intervention output. Within a QofBookMerge context, -only the parameters used in the merge are available, i.e. parameters where both -param_getfcn and param_setfcn are not NULL. - -Note that the object type description (a full text version of the object name) is -also available to the dialogue as QofBookMergeRule::mergeLabel. - -This allows the dialog to display the description of the object and all -parameter data. - -*/ -gchar* qof_book_merge_param_as_string(QofParam *qtparam, QofInstance *qtEnt); - -/** \brief called by dialogue callback to set the result of user intervention - -Set \b any rule result to ::MERGE_INVALID to abort the import when -::qof_book_merge_commit is called, without changing the target book. - -The calling process should make it absolutely clear that a merge operation -\b cannot be undone and that a backup copy should always be available -\b before a merge is initialised. - -Recommended method: Only offer three options to the user per rule: - --# Allow import data to be merged into target data - - change MERGE_REPORT to MERGE_UPDATE --# Allow import data without an exact match to be - added as new - - change MERGE_REPORT to MERGE_NEW \b IF mergeAbsolute = FALSE --# Ignore import data and leave target data unchanged - - change MERGE_REPORT to MERGE_ABSOLUTE or MERGE_DUPLICATE - -Handle the required result changes in code: Check the value of -QofBookMergeRule::mergeAbsolute and use these principles: - -To ignore entities tagged as: -- MERGE_REPORT, you must check the value of mergeAbsolute. - - if mergeAbsolute is TRUE, change MERGE_REPORT to MERGE_ABSOLUTE - - if mergeAbsolute is FALSE, change MERGE_REPORT to MERGE_DUPLICATE -- MERGE_NEW, set MERGE_DUPLICATE. -- MERGE_UPDATE, you must check the value of mergeAbsolute. - - if mergeAbsolute is TRUE, change MERGE_UPDATE to MERGE_ABSOLUTE - - if mergeAbsolute is FALSE, change MERGE_UPDATE to MERGE_DUPLICATE - -To merge entities that are not pre-set to MERGE_NEW, set MERGE_UPDATE.\n -Attempting to merge an entity when the pre-set value was MERGE_NEW will -force a change back to MERGE_NEW because no suitable target exists for the -merge. - -To add entities, check mergeAbsolute is FALSE and set MERGE_NEW.\n -An entity \b only be added if mergeAbsolute is FALSE. Attempting to -add an entity when mergeAbsolute is TRUE will always force a MERGE_UPDATE. - -It is not possible to update the same rule more than once. - --# \b MERGE_NEW is reserved for new objects and is only pre-set if -all parameters, including GncGUID, have already failed to match any -relevant object. ::qof_book_merge_commit will create new -entities for all rules tagged as MERGE_NEW. - - if mergeAbsolute is TRUE and the user wants to import the - data, requests to set MERGE_NEW will be forced to MERGE_UPDATE - because an entity with that GncGUID already exists in the target book. - - if MERGE_NEW is pre-set, requests to change to MERGE_UPDATE will be - ignored because a new entity is needed. --# \b MERGE_UPDATE is reserved for existing objects - ::qof_book_merge_commit -will require a matching entity to update and will force a change to back to -MERGE_NEW if none is known to exist, using the principle above. --# \b MERGE_INVALID will cause an abort of the merge process. --# \b MERGE_UNDEF and \b MERGE_REPORT cannot be set - the entity result will -be unchanged. --# \b MERGE_DUPLICATE and \b MERGE_ABSOLUTE are handled identically but are - semantically different - QofBookMergeRule::mergeAbsolute is used to - dictate which to set: - - if mergeAbsolute is TRUE but MERGE_DUPLICATE is requested, - force a change to MERGE_ABSOLUTE. - - if mergeAbsolute is FALSE but MERGE_ABSOLUTE is requested, - force a change to MERGE_DUPLICATE. - -::qof_book_merge_commit only commits entities tagged -with MERGE_NEW and MERGE_UPDATE results. -\n -Entities tagged with MERGE_ABSOLUTE and MERGE_DUPLICATE results are ignored. - -The calling process must check the return value and call -::qof_book_merge_abort(mergeData) if non-zero. - -@param mergeData the merge context, ::QofBookMergeData* -@param tag the result to attempt to set, ::QofBookMergeResult - -\return -1 if supplied parameters are invalid or NULL, 0 on success. - -*/ -QofBookMergeData* -qof_book_merge_update_result(QofBookMergeData *mergeData, QofBookMergeResult tag); - -/** \brief Commits the import data to the target book - - The last function in the API and the final part of any QofBookMerge operation. - -qof_book_merge_commit will abort the \b entire merge operation if any rule -is set to ::MERGE_INVALID. It is the responsibility of the calling -function to handle the error code from ::qof_book_merge_commit, close the -dialogue and return. qof_book_merge_commit will already have halted the merge -operation and freed any memory allocated to all merge structures before -returning the error code. There is no way for the dialogue process to report -back to qof_book_merge in this situation. - -qof_book_merge_commit checks for any entities still tagged as -::MERGE_REPORT and then proceeds to import all entities tagged as -::MERGE_UPDATE or ::MERGE_NEW into the target book. -\n -This final process cannot be UNDONE!\n -\n - -@param mergeData the merge context, ::QofBookMergeData* - -\return - - -2 if any rules are tagged as ::MERGE_INVALID - - mergeData will have been g_free'd). - - note that this will be before any operations are done on the target - QofBook. - - -1 if mergeData is invalid or no merge has been initialised with - ::qof_book_merge_init - the calling process must check the value of - mergeData - - +1 if some entities are still tagged as \a MERGE_REPORT - use - ::qof_book_merge_update_rule and try again (mergeData is retained). - - 0 on success - mergeData will have been freed. -*/ -gint -qof_book_merge_commit(QofBookMergeData *mergeData ); - -/** \brief Abort the merge and free all memory allocated by the merge - -Sometimes, setting ::MERGE_INVALID is insufficient: e.g. if the user aborts the -merge from outside the functions dealing with the merge ruleset. This function -causes an immediate abort - the calling process must start again at Init if -a new merge is required. -*/ -void -qof_book_merge_abort(QofBookMergeData *mergeData); - -#endif // QOFBOOKMERGE_H -/** @} */ -/** @} */