QOF  0.8.7
qofbookmerge.c
1 /*********************************************************************
2  * QofBookMerge.c -- api for QoFBook merge with collision handling *
3  * Copyright (C) 2004-2008 *
4  * Neil Williams <linux@codehelp.co.uk> *
5  * *
6  * This program is free software; you can redistribute it and/or *
7  * modify it under the terms of the GNU General Public License as *
8  * published by the Free Software Foundation; either version 2 of *
9  * the License, or (at your option) any later version. *
10  * *
11  * This program is distributed in the hope that it will be useful, *
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of *
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
14  * GNU General Public License for more details. *
15  * *
16  * You should have received a copy of the GNU General Public License *
17  * along with this program; if not, contact: *
18  * *
19  * Free Software Foundation Voice: +1-617-542-5942 *
20  * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
21  * Boston, MA 02110-1301, USA gnu@gnu.org *
22  * *
23  ********************************************************************/
24 
25 #include "config.h"
26 #include <glib.h>
27 #include "qof.h"
28 
29 static QofLogModule log_module = QOF_MOD_MERGE;
30 
31 /* private rule iteration struct */
32 struct QofBookMergeRuleIterate
33 {
35  QofBookMergeData *data;
36  QofBookMergeRule *rule;
37  GList *ruleList;
38  guint remainder;
39 };
40 
41 /* Make string type parameters 3 times more
42  important in the match than default types.
43  i.e. even if two other parameters differ,
44  a string match will still provide a better target
45  than when other types match and the string does not.
46 */
47 #define DEFAULT_MERGE_WEIGHT 1
48 #define QOF_STRING_WEIGHT 3
49 #define QOF_DATE_STRING_LENGTH MAX_DATE_LENGTH
50 
51 static QofBookMergeRule *
52 qof_book_merge_update_rule (QofBookMergeRule * currentRule, gboolean match,
53  gint weight)
54 {
55  gboolean absolute;
56 
57  absolute = currentRule->mergeAbsolute;
58  if (absolute && match && currentRule->mergeResult == MERGE_UNDEF)
59  currentRule->mergeResult = MERGE_ABSOLUTE;
60  if (absolute && !match)
61  currentRule->mergeResult = MERGE_UPDATE;
62  if (!absolute && match && currentRule->mergeResult == MERGE_UNDEF)
63  currentRule->mergeResult = MERGE_DUPLICATE;
64  if (!absolute && !match)
65  {
66  currentRule->difference += weight;
67  if (currentRule->mergeResult == MERGE_DUPLICATE)
68  currentRule->mergeResult = MERGE_REPORT;
69  }
70  return currentRule;
71 }
72 
73 struct collect_list_s
74 {
75  GSList *linkedEntList;
76 };
77 
78 static void
79 collect_reference_cb (QofEntity * ent, gpointer user_data)
80 {
81  struct collect_list_s *s;
82 
83  s = (struct collect_list_s *) user_data;
84  if (!ent || !s)
85  return;
86  s->linkedEntList = g_slist_prepend (s->linkedEntList, ent);
87 }
88 
89 static gint
90 qof_book_merge_compare (QofBookMergeData * mergeData)
91 {
92  QofBookMergeRule *currentRule;
93  QofCollection *mergeColl, *targetColl;
94  gchar *stringImport, *stringTarget;
95  QofEntity *mergeEnt, *targetEnt, *referenceEnt;
96  const GUID *guidImport, *guidTarget;
97  QofParam *qtparam;
98  KvpFrame *kvpImport, *kvpTarget;
99  QofIdType mergeParamName;
100  QofType mergeType;
101  GSList *paramList;
102  gboolean G_GNUC_UNUSED absolute, G_GNUC_UNUSED mergeError, knowntype, mergeMatch, booleanImport,
103  booleanTarget, (*boolean_getter) (QofEntity *, QofParam *);
104  QofNumeric numericImport, numericTarget,
105  (*numeric_getter) (QofEntity *, QofParam *);
106  gdouble doubleImport, doubleTarget, (*double_getter) (QofEntity *,
107  QofParam *);
108  gint32 i32Import, i32Target, (*int32_getter) (QofEntity *, QofParam *);
109  gint64 i64Import, i64Target, (*int64_getter) (QofEntity *, QofParam *);
110  gchar charImport, charTarget, (*char_getter) (QofEntity *, QofParam *);
111 
112  g_return_val_if_fail ((mergeData != NULL), -1);
113  currentRule = mergeData->currentRule;
114  g_return_val_if_fail ((currentRule != NULL), -1);
115  absolute = currentRule->mergeAbsolute;
116  mergeEnt = currentRule->importEnt;
117  targetEnt = currentRule->targetEnt;
118  paramList = currentRule->mergeParam;
119  currentRule->difference = 0;
120  currentRule->mergeResult = MERGE_UNDEF;
121  currentRule->linkedEntList = NULL;
122  g_return_val_if_fail ((targetEnt) || (mergeEnt) || (paramList), -1);
123  kvpImport = kvp_frame_new ();
124  kvpTarget = kvp_frame_new ();
125  mergeError = FALSE;
126  while (paramList != NULL)
127  {
128  mergeMatch = FALSE;
129  knowntype = FALSE;
130  qtparam = paramList->data;
131  mergeParamName = qtparam->param_name;
132  g_return_val_if_fail (mergeParamName != NULL, -1);
133  mergeType = qtparam->param_type;
134  if (safe_strcmp (mergeType, QOF_TYPE_STRING) == 0)
135  {
136  stringImport = qtparam->param_getfcn (mergeEnt, qtparam);
137  stringTarget = qtparam->param_getfcn (targetEnt, qtparam);
138  /* very strict string matches may need to be relaxed. */
139  if (stringImport == NULL)
140  stringImport = "";
141  if (stringTarget == NULL)
142  stringTarget = "";
143  if (safe_strcmp (stringImport, stringTarget) == 0)
144  mergeMatch = TRUE;
145  /* Give special weight to a string match */
146  currentRule = qof_book_merge_update_rule (currentRule,
147  mergeMatch, QOF_STRING_WEIGHT);
148  stringImport = stringTarget = NULL;
149  knowntype = TRUE;
150  }
151  if (safe_strcmp (mergeType, QOF_TYPE_TIME) == 0)
152  {
153  QofTime *qtImport, *qtTarget;
154 
155  qtImport = qtparam->param_getfcn (mergeEnt, qtparam);
156  qtTarget = qtparam->param_getfcn (targetEnt, qtparam);
157  if (qof_time_cmp (qtImport, qtTarget) == 0)
158  currentRule = qof_book_merge_update_rule (currentRule,
159  mergeMatch, DEFAULT_MERGE_WEIGHT);
160  knowntype = TRUE;
161  }
162  if ((safe_strcmp (mergeType, QOF_TYPE_NUMERIC) == 0) ||
163  (safe_strcmp (mergeType, QOF_TYPE_DEBCRED) == 0))
164  {
165  numeric_getter =
166  (QofNumeric (*)(QofEntity *, QofParam *)) qtparam->
167  param_getfcn;
168  numericImport = numeric_getter (mergeEnt, qtparam);
169  numericTarget = numeric_getter (targetEnt, qtparam);
170  if (qof_numeric_compare (numericImport, numericTarget) == 0)
171  mergeMatch = TRUE;
172  currentRule = qof_book_merge_update_rule (currentRule,
173  mergeMatch, DEFAULT_MERGE_WEIGHT);
174  knowntype = TRUE;
175  }
176  if (safe_strcmp (mergeType, QOF_TYPE_GUID) == 0)
177  {
178  guidImport = qtparam->param_getfcn (mergeEnt, qtparam);
179  guidTarget = qtparam->param_getfcn (targetEnt, qtparam);
180  if (guid_compare (guidImport, guidTarget) == 0)
181  mergeMatch = TRUE;
182  currentRule = qof_book_merge_update_rule (currentRule,
183  mergeMatch, DEFAULT_MERGE_WEIGHT);
184  knowntype = TRUE;
185  }
186  if (safe_strcmp (mergeType, QOF_TYPE_INT32) == 0)
187  {
188  int32_getter =
189  (gint32 (*)(QofEntity *,
190  QofParam *)) qtparam->param_getfcn;
191  i32Import = int32_getter (mergeEnt, qtparam);
192  i32Target = int32_getter (targetEnt, qtparam);
193  if (i32Target == i32Import)
194  mergeMatch = TRUE;
195  currentRule = qof_book_merge_update_rule (currentRule,
196  mergeMatch, DEFAULT_MERGE_WEIGHT);
197  knowntype = TRUE;
198  }
199  if (safe_strcmp (mergeType, QOF_TYPE_INT64) == 0)
200  {
201  int64_getter =
202  (gint64 (*)(QofEntity *,
203  QofParam *)) qtparam->param_getfcn;
204  i64Import = int64_getter (mergeEnt, qtparam);
205  i64Target = int64_getter (targetEnt, qtparam);
206  if (i64Target == i64Import)
207  mergeMatch = TRUE;
208  currentRule = qof_book_merge_update_rule (currentRule,
209  mergeMatch, DEFAULT_MERGE_WEIGHT);
210  knowntype = TRUE;
211  }
212  if (safe_strcmp (mergeType, QOF_TYPE_DOUBLE) == 0)
213  {
214  double_getter =
215  (double (*)(QofEntity *,
216  QofParam *)) qtparam->param_getfcn;
217  doubleImport = double_getter (mergeEnt, qtparam);
218  doubleTarget = double_getter (mergeEnt, qtparam);
219  if (doubleImport == doubleTarget)
220  mergeMatch = TRUE;
221  currentRule = qof_book_merge_update_rule (currentRule,
222  mergeMatch, DEFAULT_MERGE_WEIGHT);
223  knowntype = TRUE;
224  }
225  if (safe_strcmp (mergeType, QOF_TYPE_BOOLEAN) == 0)
226  {
227  boolean_getter =
228  (gboolean (*)(QofEntity *,
229  QofParam *)) qtparam->param_getfcn;
230  booleanImport = boolean_getter (mergeEnt, qtparam);
231  booleanTarget = boolean_getter (targetEnt, qtparam);
232  if (booleanImport != FALSE && booleanImport != TRUE)
233  booleanImport = FALSE;
234  if (booleanTarget != FALSE && booleanTarget != TRUE)
235  booleanTarget = FALSE;
236  if (booleanImport == booleanTarget)
237  mergeMatch = TRUE;
238  currentRule = qof_book_merge_update_rule (currentRule,
239  mergeMatch, DEFAULT_MERGE_WEIGHT);
240  knowntype = TRUE;
241  }
242  if (safe_strcmp (mergeType, QOF_TYPE_KVP) == 0)
243  {
244  kvpImport =
245  kvp_frame_copy (qtparam->param_getfcn (mergeEnt, qtparam));
246  kvpTarget =
247  kvp_frame_copy (qtparam->param_getfcn (targetEnt,
248  qtparam));
249  if (kvp_frame_compare (kvpImport, kvpTarget) == 0)
250  mergeMatch = TRUE;
251  currentRule = qof_book_merge_update_rule (currentRule,
252  mergeMatch, DEFAULT_MERGE_WEIGHT);
253  knowntype = TRUE;
254  }
255  if (safe_strcmp (mergeType, QOF_TYPE_CHAR) == 0)
256  {
257  char_getter =
258  (gchar (*)(QofEntity *, QofParam *)) qtparam->param_getfcn;
259  charImport = char_getter (mergeEnt, qtparam);
260  charTarget = char_getter (targetEnt, qtparam);
261  if (charImport == charTarget)
262  mergeMatch = TRUE;
263  currentRule = qof_book_merge_update_rule (currentRule,
264  mergeMatch, DEFAULT_MERGE_WEIGHT);
265  knowntype = TRUE;
266  }
267  /* No object should have QofSetterFunc defined for the book,
268  but just to be safe, do nothing. */
269  if (safe_strcmp (mergeType, QOF_ID_BOOK) == 0)
270  knowntype = TRUE;
271  if (safe_strcmp (mergeType, QOF_TYPE_COLLECT) == 0)
272  {
273  struct collect_list_s s;
274  s.linkedEntList = NULL;
275  mergeColl = qtparam->param_getfcn (mergeEnt, qtparam);
276  targetColl = qtparam->param_getfcn (targetEnt, qtparam);
277  s.linkedEntList = g_slist_copy (currentRule->linkedEntList);
278  qof_collection_foreach (mergeColl, collect_reference_cb, &s);
279  currentRule->linkedEntList = g_slist_copy (s.linkedEntList);
280  if (0 == qof_collection_compare (mergeColl, targetColl))
281  mergeMatch = TRUE;
282  currentRule = qof_book_merge_update_rule (currentRule,
283  mergeMatch, DEFAULT_MERGE_WEIGHT);
284  knowntype = TRUE;
285  }
286  if (safe_strcmp (mergeType, QOF_TYPE_CHOICE) == 0)
287  {
288  referenceEnt = qtparam->param_getfcn (mergeEnt, qtparam);
289  currentRule->linkedEntList =
290  g_slist_prepend (currentRule->linkedEntList, referenceEnt);
291  if (referenceEnt == qtparam->param_getfcn (targetEnt, qtparam))
292  mergeMatch = TRUE;
293  knowntype = TRUE;
294  }
295  if (knowntype == FALSE)
296  {
297  referenceEnt = qtparam->param_getfcn (mergeEnt, qtparam);
298  if ((referenceEnt != NULL)
299  && (safe_strcmp (referenceEnt->e_type, mergeType) == 0))
300  {
301  currentRule->linkedEntList =
302  g_slist_prepend (currentRule->linkedEntList,
303  referenceEnt);
304  if (referenceEnt ==
305  qtparam->param_getfcn (targetEnt, qtparam))
306  mergeMatch = TRUE;
307  currentRule = qof_book_merge_update_rule (currentRule,
308  mergeMatch, DEFAULT_MERGE_WEIGHT);
309  }
310  }
311  paramList = g_slist_next (paramList);
312  }
313  mergeData->currentRule = currentRule;
314  g_free (kvpImport);
315  g_free (kvpTarget);
316  return 0;
317 }
318 
319 static void
320 qof_book_merge_commit_foreach_cb (gpointer rule, gpointer arg)
321 {
322  struct QofBookMergeRuleIterate *qiter;
323 
324  g_return_if_fail (arg != NULL);
325  qiter = (struct QofBookMergeRuleIterate *) arg;
326  g_return_if_fail (qiter->data != NULL);
327  qiter->fcn (qiter->data, (QofBookMergeRule *) rule,
328  qiter->remainder);
329  qiter->remainder--;
330 }
331 
332 static void
333 qof_book_merge_commit_foreach (QofBookMergeRuleForeachCB cb,
334  QofBookMergeResult mergeResult, QofBookMergeData * mergeData)
335 {
336  struct QofBookMergeRuleIterate qiter;
337  QofBookMergeRule *currentRule;
338  GList *subList, *node;
339 
340  g_return_if_fail (cb != NULL);
341  g_return_if_fail (mergeData != NULL);
342  currentRule = mergeData->currentRule;
343  g_return_if_fail (currentRule != NULL);
344  g_return_if_fail (mergeResult > 0);
345  g_return_if_fail ((mergeResult != MERGE_INVALID) ||
346  (mergeResult != MERGE_UNDEF) ||
347  (mergeResult != MERGE_REPORT));
348 
349  qiter.fcn = cb;
350  subList = NULL;
351  qiter.ruleList = NULL;
352  for (node = mergeData->mergeList; node != NULL; node = node->next)
353  {
354  currentRule = node->data;
355  if (currentRule->mergeResult == mergeResult)
356  subList = g_list_prepend (subList, currentRule);
357  }
358  qiter.remainder = g_list_length (subList);
359  qiter.data = mergeData;
360  g_list_foreach (subList, qof_book_merge_commit_foreach_cb, &qiter);
361 }
362 
363 /* build the table of target comparisons
364 
365 This can get confusing, so bear with me. (!)
366 
367 Whilst iterating through the entities in the mergeBook, qof_book_mergeForeach assigns
368 a targetEnt to each mergeEnt (until it runs out of targetEnt or mergeEnt). Each match
369 is made against the one targetEnt that best matches the mergeEnt. Fine so far.
370 
371 Any mergeEnt is only ever assigned a targetEnt if the calculated difference between
372 the two is less than the difference between that targetEnt and any previous mergeEnt
373 match.
374 
375 The next mergeEnt may be a much better match for that targetEnt and the target_table
376 is designed to solve the issues that result from this conflict. The previous match
377 must be re-assigned because if two mergeEnt's are matched with only one targetEnt,
378 data loss \b WILL follow. Equally, the current mergeEnt must replace the previous
379 one as it is a better match. qof_entity_rating holds the details required to identify
380 the correct mergeEnt to be re-assigned and these mergeEnt entities are therefore
381 orphaned - to be re-matched later.
382 
383 Meanwhile, the current mergeEnt is entered into target_table with it's difference and
384 rule data, in case an even better match is found later in the mergeBook.
385 
386 Finally, each mergeEnt in the orphan_list is now put through the comparison again.
387 
388 */
389 static gboolean
390 qof_book_merge_rule_cmp (gconstpointer a, gconstpointer b)
391 {
394  if (ra->difference == rb->difference)
395  return TRUE;
396  else
397  return FALSE;
398 }
399 
400 static void
401 qof_book_merge_orphan_check (double difference,
402  QofBookMergeRule * mergeRule, QofBookMergeData * mergeData)
403 {
404  /* Called when difference is lower than previous
405  Lookup target to find previous match
406  and re-assign mergeEnt to orphan_list */
407  QofBookMergeRule *rule;
408 
409  g_return_if_fail (mergeRule != NULL);
410  g_return_if_fail (mergeData != NULL);
411  if (g_hash_table_size (mergeData->target_table) == 0)
412  return;
413  rule =
414  (QofBookMergeRule *) g_hash_table_lookup (mergeData->target_table,
415  mergeRule->targetEnt);
416  /* If NULL, no match was found. */
417  if (rule == NULL)
418  return;
419  /* Only orphan if this is a better match than already exists. */
420  if (difference >= rule->difference)
421  return;
422  rule->targetEnt = NULL;
423  rule->mergeResult = MERGE_UNDEF;
424  mergeData->orphan_list = g_slist_append (mergeData->orphan_list, rule);
425 }
426 
427 static void
428 qof_book_merge_match_orphans (QofBookMergeData * mergeData)
429 {
430  GSList *orphans, *targets;
431  QofBookMergeRule *rule, *currentRule;
432  QofEntity * G_GNUC_UNUSED best_matchEnt;
433  double difference;
434 
435  g_return_if_fail (mergeData != NULL);
436  currentRule = mergeData->currentRule;
437  g_return_if_fail (currentRule != NULL);
438  /* This routine does NOT copy the orphan list, it
439  is used recursively until empty. */
440  orphans = mergeData->orphan_list;
441  targets = g_slist_copy (mergeData->targetList);
442  while (orphans != NULL)
443  {
444  rule = orphans->data;
445  g_return_if_fail (rule != NULL);
446  difference = g_slist_length (mergeData->mergeObjectParams);
447  if (rule->targetEnt == NULL)
448  {
449  rule->mergeResult = MERGE_NEW;
450  rule->difference = 0;
451  mergeData->mergeList =
452  g_list_prepend (mergeData->mergeList, rule);
453  orphans = g_slist_next (orphans);
454  continue;
455  }
456  mergeData->currentRule = rule;
457  g_return_if_fail (qof_book_merge_compare (mergeData) != -1);
458  if (difference > mergeData->currentRule->difference)
459  {
460  best_matchEnt = currentRule->targetEnt;
461  difference = currentRule->difference;
462  rule = currentRule;
463  mergeData->mergeList =
464  g_list_prepend (mergeData->mergeList, rule);
465  qof_book_merge_orphan_check (difference, rule, mergeData);
466  }
467  orphans = g_slist_next (orphans);
468  }
469  g_slist_free (mergeData->orphan_list);
470  g_slist_free (targets);
471 }
472 
473 static void
474 qof_book_merge_foreach_target (QofEntity * targetEnt, gpointer user_data)
475 {
476  QofBookMergeData *mergeData;
477 
478  g_return_if_fail (user_data != NULL);
479  mergeData = (QofBookMergeData *) user_data;
480  g_return_if_fail (targetEnt != NULL);
481  mergeData->targetList =
482  g_slist_prepend (mergeData->targetList, targetEnt);
483 }
484 
485 static void
486 qof_book_merge_foreach_type_target (QofObject * merge_obj,
487  gpointer user_data)
488 {
489  QofBookMergeData *mergeData;
490  QofBookMergeRule *currentRule;
491 
492  g_return_if_fail (user_data != NULL);
493  mergeData = (QofBookMergeData *) user_data;
494  currentRule = mergeData->currentRule;
495  g_return_if_fail (currentRule != NULL);
496  g_return_if_fail (merge_obj != NULL);
497  if (safe_strcmp (merge_obj->e_type,
498  currentRule->importEnt->e_type) == 0)
499  {
500  qof_object_foreach (currentRule->importEnt->e_type,
501  mergeData->targetBook,
502  qof_book_merge_foreach_target, user_data);
503  }
504 }
505 
506 static void
507 qof_book_merge_foreach (QofEntity * mergeEnt, gpointer user_data)
508 {
509  QofBookMergeRule *mergeRule, *currentRule;
510  QofBookMergeData *mergeData;
511  QofEntity *targetEnt, *best_matchEnt;
512  GUID *g;
513  double difference;
514  GSList *c;
515 
516  g_return_if_fail (user_data != NULL);
517  mergeData = (QofBookMergeData *) user_data;
518  g_return_if_fail (mergeEnt != NULL);
519  currentRule = mergeData->currentRule;
520  g_return_if_fail (currentRule != NULL);
521  g = guid_malloc ();
522  *g = mergeEnt->guid;
523  mergeRule = g_new0 (QofBookMergeRule, 1);
524  mergeRule->importEnt = mergeEnt;
525  mergeRule->difference = difference = 0;
526  mergeRule->mergeAbsolute = FALSE;
527  mergeRule->mergeResult = MERGE_UNDEF;
528  mergeRule->updated = FALSE;
529  mergeRule->mergeType = mergeEnt->e_type;
530  mergeRule->mergeLabel = qof_object_get_type_label (mergeEnt->e_type);
531  mergeRule->mergeParam = g_slist_copy (mergeData->mergeObjectParams);
532  mergeRule->linkedEntList = NULL;
533  mergeData->currentRule = mergeRule;
534  targetEnt = best_matchEnt = NULL;
535  targetEnt =
537  (mergeData->targetBook, mergeEnt->e_type), g);
538  if (targetEnt != NULL)
539  {
540  mergeRule->mergeAbsolute = TRUE;
541  mergeRule->targetEnt = targetEnt;
542  g_return_if_fail (qof_book_merge_compare (mergeData) != -1);
543  mergeRule->linkedEntList =
544  g_slist_copy (currentRule->linkedEntList);
545  mergeData->mergeList =
546  g_list_prepend (mergeData->mergeList, mergeRule);
547  return;
548  }
549  /* no absolute match exists */
550  g_slist_free (mergeData->targetList);
551  mergeData->targetList = NULL;
552  qof_object_foreach_type (qof_book_merge_foreach_type_target,
553  mergeData);
554  if (g_slist_length (mergeData->targetList) == 0)
555  mergeRule->mergeResult = MERGE_NEW;
556  difference = g_slist_length (mergeRule->mergeParam);
557  c = g_slist_copy (mergeData->targetList);
558  while (c != NULL)
559  {
560  mergeRule->targetEnt = c->data;
561  currentRule = mergeRule;
562  /* compare two entities and sum the differences */
563  g_return_if_fail (qof_book_merge_compare (mergeData) != -1);
564  if (mergeRule->difference == 0)
565  {
566  /* check if this is a better match than one already assigned */
567  best_matchEnt = mergeRule->targetEnt;
568  mergeRule->mergeResult = MERGE_DUPLICATE;
569  difference = 0;
570  mergeRule->linkedEntList =
571  g_slist_copy (currentRule->linkedEntList);
572  g_slist_free (c);
573  guid_free (g);
574  /* exact match, return */
575  return;
576  }
577  if (difference > mergeRule->difference)
578  {
579  /* The chosen targetEnt determines the parenting of any child object */
580  /* check if this is a better match than one already assigned */
581  best_matchEnt = mergeRule->targetEnt;
582  difference = mergeRule->difference;
583  /* Use match to lookup the previous entity that matched this targetEnt (if any)
584  and remove targetEnt from the rule for that mergeEnt.
585  Add the previous mergeEnt to orphan_list.
586  */
587  qof_book_merge_orphan_check (difference, mergeRule, mergeData);
588  }
589  c = g_slist_next (c);
590  }
591  g_slist_free (c);
592  if (best_matchEnt != NULL)
593  {
594  mergeRule->targetEnt = best_matchEnt;
595  mergeRule->difference = difference;
596  /* Set this entity in the target_table in case a better match can be made
597  with the next mergeEnt. */
598  g_hash_table_insert (mergeData->target_table, mergeRule->targetEnt,
599  mergeRule);
600  /* compare again with the best partial match */
601  g_return_if_fail (qof_book_merge_compare (mergeData) != -1);
602  mergeRule->linkedEntList =
603  g_slist_copy (currentRule->linkedEntList);
604  }
605  else
606  {
607  mergeRule->targetEnt = NULL;
608  mergeRule->difference = 0;
609  mergeRule->mergeResult = MERGE_NEW;
610  mergeRule->linkedEntList =
611  g_slist_copy (currentRule->linkedEntList);
612  }
613  mergeData->mergeList =
614  g_list_prepend (mergeData->mergeList, mergeRule);
615  guid_free (g);
616  /* return to qof_book_merge_init */
617 }
618 
619 static void
620 qof_book_merge_foreach_param (QofParam * param, gpointer user_data)
621 {
622  QofBookMergeData *mergeData;
623 
624  g_return_if_fail (user_data != NULL);
625  mergeData = (QofBookMergeData *) user_data;
626  g_return_if_fail (param != NULL);
627  if ((param->param_getfcn != NULL) && (param->param_setfcn != NULL))
628  {
629  mergeData->mergeObjectParams =
630  g_slist_append (mergeData->mergeObjectParams, param);
631  }
632 }
633 
634 static void
635 qof_book_merge_foreach_type (QofObject * merge_obj, gpointer user_data)
636 {
637  QofBookMergeData *mergeData;
638 
639  g_return_if_fail (user_data != NULL);
640  mergeData = (QofBookMergeData *) user_data;
641  g_return_if_fail ((merge_obj != NULL));
642  /* Skip unsupported objects */
643  if ((merge_obj->create == NULL) || (merge_obj->foreach == NULL))
644  {
645  DEBUG (" merge_obj QOF support failed %s", merge_obj->e_type);
646  return;
647  }
648  if (mergeData->mergeObjectParams != NULL)
649  g_slist_free (mergeData->mergeObjectParams);
650  mergeData->mergeObjectParams = NULL;
651  qof_class_param_foreach (merge_obj->e_type,
652  qof_book_merge_foreach_param, mergeData);
653  qof_object_foreach (merge_obj->e_type, mergeData->mergeBook,
654  qof_book_merge_foreach, mergeData);
655 }
656 
657 static void
658 qof_book_merge_rule_cb (gpointer rule, gpointer arg)
659 {
660  struct QofBookMergeRuleIterate *qiter;
661  QofBookMergeData *mergeData;
662 
663  g_return_if_fail (arg != NULL);
664  qiter = (struct QofBookMergeRuleIterate *) arg;
665  mergeData = qiter->data;
666  g_return_if_fail (mergeData != NULL);
667  g_return_if_fail (mergeData->abort == FALSE);
668  qiter->fcn (mergeData, (QofBookMergeRule *) rule, qiter->remainder);
669  qiter->data = mergeData;
670  qiter->remainder--;
671 }
672 
673 static void
674 qof_book_merge_commit_rule_loop (QofBookMergeData * mergeData,
675  QofBookMergeRule * rule, guint remainder __attribute__ ((unused)))
676 {
677  QofInstance *inst;
678  gboolean registered_type;
679  QofEntity *referenceEnt;
680  /* cm_ prefix used for variables that hold the data to commit */
681  QofCollection *cm_coll;
682  QofParam *cm_param;
683  gchar *cm_string;
684  const GUID *cm_guid;
685  KvpFrame *cm_kvp;
686  QofTime *cm_qt;
687  /* function pointers and variables for parameter getters that don't use pointers normally */
688  QofNumeric cm_numeric, (*numeric_getter) (QofEntity *, QofParam *);
689  gdouble cm_double, (*double_getter) (QofEntity *, QofParam *);
690  gboolean cm_boolean, (*boolean_getter) (QofEntity *, QofParam *);
691  gint32 cm_i32, (*int32_getter) (QofEntity *, QofParam *);
692  gint64 cm_i64, (*int64_getter) (QofEntity *, QofParam *);
693  gchar cm_char, (*char_getter) (QofEntity *, QofParam *);
694  /* function pointers to the parameter setters */
695  void (*string_setter) (QofEntity *, const gchar *);
696  void (*time_setter) (QofEntity *, QofTime *);
697  void (*numeric_setter) (QofEntity *, QofNumeric);
698  void (*guid_setter) (QofEntity *, const GUID *);
699  void (*double_setter) (QofEntity *, double);
700  void (*boolean_setter) (QofEntity *, gboolean);
701  void (*i32_setter) (QofEntity *, gint32);
702  void (*i64_setter) (QofEntity *, gint64);
703  void (*char_setter) (QofEntity *, gchar);
704  void (*kvp_frame_setter) (QofEntity *, KvpFrame *);
705  void (*reference_setter) (QofEntity *, QofEntity *);
706  void (*collection_setter) (QofEntity *, QofCollection *);
707 
708  g_return_if_fail (rule != NULL);
709  g_return_if_fail (mergeData != NULL);
710  g_return_if_fail (mergeData->targetBook != NULL);
711  g_return_if_fail ((rule->mergeResult != MERGE_NEW)
712  || (rule->mergeResult != MERGE_UPDATE));
713  /* create a new object for MERGE_NEW */
714  /* The new object takes the GUID from the import to retain an absolute match */
715  if (rule->mergeResult == MERGE_NEW)
716  {
717  inst =
719  e_type, mergeData->targetBook);
720  g_return_if_fail (inst != NULL);
721  rule->targetEnt = &inst->entity;
724  }
725  /* currentRule->targetEnt is now set,
726  1. by an absolute GUID match or
727  2. by best_matchEnt and difference or
728  3. by MERGE_NEW.
729  */
730  while (rule->mergeParam != NULL)
731  {
732  registered_type = FALSE;
733  g_return_if_fail (rule->mergeParam->data);
734  cm_param = rule->mergeParam->data;
735  rule->mergeType = cm_param->param_type;
736  if (safe_strcmp (rule->mergeType, QOF_TYPE_STRING) == 0)
737  {
738  cm_string = cm_param->param_getfcn (rule->importEnt, cm_param);
739  string_setter =
740  (void (*)(QofEntity *,
741  const gchar *)) cm_param->param_setfcn;
742  if (string_setter != NULL)
743  string_setter (rule->targetEnt, cm_string);
744  registered_type = TRUE;
745  }
746  if (safe_strcmp (rule->mergeType, QOF_TYPE_TIME) == 0)
747  {
748  QofTime *(*time_getter) (QofEntity *, QofParam *);
749 
750  time_getter =
751  (QofTime* (*)(QofEntity *, QofParam *))cm_param->param_getfcn;
752  cm_qt = qof_time_copy (
753  time_getter (rule->importEnt, cm_param));
754  time_setter =
755  (void (*)(QofEntity *, QofTime *))
756  cm_param->param_setfcn;
757  if ((time_setter != NULL) && (qof_time_is_valid (cm_qt)))
758  time_setter (rule->targetEnt, cm_qt);
759  registered_type = TRUE;
760  }
761  if ((safe_strcmp (rule->mergeType, QOF_TYPE_NUMERIC) == 0) ||
762  (safe_strcmp (rule->mergeType, QOF_TYPE_DEBCRED) == 0))
763  {
764  numeric_getter =
765  (QofNumeric (*)(QofEntity *, QofParam *)) cm_param->
766  param_getfcn;
767  cm_numeric = numeric_getter (rule->importEnt, cm_param);
768  numeric_setter =
769  (void (*)(QofEntity *,
770  QofNumeric)) cm_param->param_setfcn;
771  if (numeric_setter != NULL)
772  numeric_setter (rule->targetEnt, cm_numeric);
773  registered_type = TRUE;
774  }
775  if (safe_strcmp (rule->mergeType, QOF_TYPE_GUID) == 0)
776  {
777  cm_guid = cm_param->param_getfcn (rule->importEnt, cm_param);
778  guid_setter =
779  (void (*)(QofEntity *,
780  const GUID *)) cm_param->param_setfcn;
781  if (guid_setter != NULL)
782  guid_setter (rule->targetEnt, cm_guid);
783  registered_type = TRUE;
784  }
785  if (safe_strcmp (rule->mergeType, QOF_TYPE_INT32) == 0)
786  {
787  int32_getter =
788  (gint32 (*)(QofEntity *,
789  QofParam *)) cm_param->param_getfcn;
790  cm_i32 = int32_getter (rule->importEnt, cm_param);
791  i32_setter =
792  (void (*)(QofEntity *, gint32)) cm_param->param_setfcn;
793  if (i32_setter != NULL)
794  i32_setter (rule->targetEnt, cm_i32);
795  registered_type = TRUE;
796  }
797  if (safe_strcmp (rule->mergeType, QOF_TYPE_INT64) == 0)
798  {
799  int64_getter =
800  (gint64 (*)(QofEntity *,
801  QofParam *)) cm_param->param_getfcn;
802  cm_i64 = int64_getter (rule->importEnt, cm_param);
803  i64_setter =
804  (void (*)(QofEntity *, gint64)) cm_param->param_setfcn;
805  if (i64_setter != NULL)
806  i64_setter (rule->targetEnt, cm_i64);
807  registered_type = TRUE;
808  }
809  if (safe_strcmp (rule->mergeType, QOF_TYPE_DOUBLE) == 0)
810  {
811  double_getter =
812  (double (*)(QofEntity *,
813  QofParam *)) cm_param->param_getfcn;
814  cm_double = double_getter (rule->importEnt, cm_param);
815  double_setter =
816  (void (*)(QofEntity *, double)) cm_param->param_setfcn;
817  if (double_setter != NULL)
818  double_setter (rule->targetEnt, cm_double);
819  registered_type = TRUE;
820  }
821  if (safe_strcmp (rule->mergeType, QOF_TYPE_BOOLEAN) == 0)
822  {
823  boolean_getter =
824  (gboolean (*)(QofEntity *, QofParam *)) cm_param->
825  param_getfcn;
826  cm_boolean = boolean_getter (rule->importEnt, cm_param);
827  boolean_setter =
828  (void (*)(QofEntity *, gboolean)) cm_param->param_setfcn;
829  if (boolean_setter != NULL)
830  boolean_setter (rule->targetEnt, cm_boolean);
831  registered_type = TRUE;
832  }
833  if (safe_strcmp (rule->mergeType, QOF_TYPE_KVP) == 0)
834  {
835  cm_kvp =
836  kvp_frame_copy (cm_param->
837  param_getfcn (rule->importEnt, cm_param));
838  kvp_frame_setter =
839  (void (*)(QofEntity *, KvpFrame *)) cm_param->param_setfcn;
840  if (kvp_frame_setter != NULL)
841  kvp_frame_setter (rule->targetEnt, cm_kvp);
842  registered_type = TRUE;
843  }
844  if (safe_strcmp (rule->mergeType, QOF_TYPE_CHAR) == 0)
845  {
846  char_getter =
847  (gchar (*)(QofEntity *,
848  QofParam *)) cm_param->param_getfcn;
849  cm_char = char_getter (rule->importEnt, cm_param);
850  char_setter =
851  (void (*)(QofEntity *, gchar)) cm_param->param_setfcn;
852  if (char_setter != NULL)
853  char_setter (rule->targetEnt, cm_char);
854  registered_type = TRUE;
855  }
856  if (safe_strcmp (rule->mergeType, QOF_TYPE_COLLECT) == 0)
857  {
858  cm_coll = cm_param->param_getfcn (rule->importEnt, cm_param);
859  collection_setter =
860  (void (*)(QofEntity *, QofCollection *)) cm_param->
861  param_setfcn;
862  if (collection_setter != NULL)
863  collection_setter (rule->targetEnt, cm_coll);
864  registered_type = TRUE;
865  }
866  if (safe_strcmp (rule->mergeType, QOF_TYPE_CHOICE) == 0)
867  {
868  referenceEnt =
869  cm_param->param_getfcn (rule->importEnt, cm_param);
870  reference_setter =
871  (void (*)(QofEntity *,
872  QofEntity *)) cm_param->param_setfcn;
873  if (reference_setter != NULL)
874  reference_setter (rule->targetEnt, referenceEnt);
875  registered_type = TRUE;
876  }
877  if (registered_type == FALSE)
878  {
879  referenceEnt =
880  cm_param->param_getfcn (rule->importEnt, cm_param);
881  if (referenceEnt)
882  {
883  reference_setter =
884  (void (*)(QofEntity *, QofEntity *)) cm_param->
885  param_setfcn;
886  if (reference_setter != NULL)
887  {
888  reference_setter (rule->targetEnt, referenceEnt);
889  }
890  }
891  }
892  rule->mergeParam = g_slist_next (rule->mergeParam);
893  }
894 }
895 
896 /* ================================================================ */
897 /* API functions. */
898 
900 qof_book_merge_init (QofBook * importBook, QofBook * targetBook)
901 {
902  QofBookMergeData *mergeData;
903  QofBookMergeRule *currentRule;
904  GList *check;
905 
906  g_return_val_if_fail ((importBook != NULL)
907  && (targetBook != NULL), NULL);
908  mergeData = g_new0 (QofBookMergeData, 1);
909  mergeData->abort = FALSE;
910  mergeData->mergeList = NULL;
911  mergeData->targetList = NULL;
912  mergeData->mergeBook = importBook;
913  mergeData->targetBook = targetBook;
914  mergeData->mergeObjectParams = NULL;
915  mergeData->orphan_list = NULL;
916  mergeData->target_table =
917  g_hash_table_new (g_direct_hash, qof_book_merge_rule_cmp);
918  currentRule = g_new0 (QofBookMergeRule, 1);
919  mergeData->currentRule = currentRule;
920  qof_object_foreach_type (qof_book_merge_foreach_type, mergeData);
921  g_return_val_if_fail (mergeData->mergeObjectParams, NULL);
922  if (mergeData->orphan_list != NULL)
923  qof_book_merge_match_orphans (mergeData);
924  for (check = mergeData->mergeList; check != NULL; check = check->next)
925  {
926  currentRule = check->data;
927  if (currentRule->mergeResult == MERGE_INVALID)
928  {
929  mergeData->abort = TRUE;
930  return (NULL);
931  }
932  }
933  return mergeData;
934 }
935 
936 void
938 {
939  QofBookMergeRule *currentRule;
940 
941  g_return_if_fail (mergeData != NULL);
942  while (mergeData->mergeList != NULL)
943  {
944  currentRule = mergeData->mergeList->data;
945  g_slist_free (currentRule->linkedEntList);
946  g_slist_free (currentRule->mergeParam);
947  g_free (mergeData->mergeList->data);
948  if (currentRule)
949  {
950  g_slist_free (currentRule->linkedEntList);
951  g_slist_free (currentRule->mergeParam);
952  g_free (currentRule);
953  }
954  mergeData->mergeList = g_list_next (mergeData->mergeList);
955  }
956  g_list_free (mergeData->mergeList);
957  g_slist_free (mergeData->mergeObjectParams);
958  g_slist_free (mergeData->targetList);
959  if (mergeData->orphan_list != NULL)
960  g_slist_free (mergeData->orphan_list);
961  g_hash_table_destroy (mergeData->target_table);
962  g_free (mergeData);
963 }
964 
967  QofBookMergeResult tag)
968 {
969  QofBookMergeRule *resolved;
970 
971  g_return_val_if_fail ((mergeData != NULL), NULL);
972  g_return_val_if_fail ((tag > 0), NULL);
973  g_return_val_if_fail ((tag != MERGE_REPORT), NULL);
974  resolved = mergeData->currentRule;
975  g_return_val_if_fail ((resolved != NULL), NULL);
976  if ((resolved->mergeAbsolute == TRUE) && (tag == MERGE_DUPLICATE))
977  tag = MERGE_ABSOLUTE;
978  if ((resolved->mergeAbsolute == TRUE) && (tag == MERGE_NEW))
979  tag = MERGE_UPDATE;
980  if ((resolved->mergeAbsolute == FALSE) && (tag == MERGE_ABSOLUTE))
981  tag = MERGE_DUPLICATE;
982  if ((resolved->mergeResult == MERGE_NEW) && (tag == MERGE_UPDATE))
983  tag = MERGE_NEW;
984  if (resolved->updated == FALSE)
985  resolved->mergeResult = tag;
986  resolved->updated = TRUE;
987  if (tag >= MERGE_INVALID)
988  {
989  mergeData->abort = TRUE;
990  mergeData->currentRule = resolved;
991  return NULL;
992  }
993  mergeData->currentRule = resolved;
994  return mergeData;
995 }
996 
997 gint
999 {
1000  QofBookMergeRule *currentRule;
1001  GList *check, *node;
1002 
1003  g_return_val_if_fail (mergeData != NULL, -1);
1004  g_return_val_if_fail (mergeData->mergeList != NULL, -1);
1005  g_return_val_if_fail (mergeData->targetBook != NULL, -1);
1006  if (mergeData->abort == TRUE)
1007  return -1;
1008  check = g_list_copy (mergeData->mergeList);
1009  g_return_val_if_fail (check != NULL, -1);
1010  for (node = check; node != NULL; node = node->next)
1011  {
1012  currentRule = node->data;
1013  if (currentRule->mergeResult == MERGE_INVALID)
1014  {
1015  qof_book_merge_abort (mergeData);
1016  g_list_free (check);
1017  return (-2);
1018  }
1019  if (currentRule->mergeResult == MERGE_REPORT)
1020  {
1021  g_list_free (check);
1022  return 1;
1023  }
1024  }
1025  g_list_free (check);
1026  qof_book_merge_commit_foreach (qof_book_merge_commit_rule_loop,
1027  MERGE_NEW, mergeData);
1028  qof_book_merge_commit_foreach (qof_book_merge_commit_rule_loop,
1029  MERGE_UPDATE, mergeData);
1030  /* Placeholder for QofObject merge_helper_cb - all objects
1031  and all parameters set */
1032  while (mergeData->mergeList != NULL)
1033  {
1034  currentRule = mergeData->mergeList->data;
1035  g_slist_free (currentRule->mergeParam);
1036  g_slist_free (currentRule->linkedEntList);
1037  mergeData->mergeList = g_list_next (mergeData->mergeList);
1038  }
1039  g_list_free (mergeData->mergeList);
1040  g_slist_free (mergeData->mergeObjectParams);
1041  g_slist_free (mergeData->targetList);
1042  if (mergeData->orphan_list != NULL)
1043  g_slist_free (mergeData->orphan_list);
1044  g_hash_table_destroy (mergeData->target_table);
1045  g_free (mergeData);
1046  return 0;
1047 }
1048 
1049 void
1052 {
1053  struct QofBookMergeRuleIterate qiter;
1054  QofBookMergeRule *currentRule;
1055  GList *matching_rules, *node;
1056 
1057  g_return_if_fail (cb != NULL);
1058  g_return_if_fail (mergeData != NULL);
1059  currentRule = mergeData->currentRule;
1060  g_return_if_fail (mergeResult > 0);
1061  g_return_if_fail (mergeResult != MERGE_INVALID);
1062  g_return_if_fail (mergeData->abort == FALSE);
1063  qiter.fcn = cb;
1064  qiter.data = mergeData;
1065  matching_rules = NULL;
1066  for (node = mergeData->mergeList; node != NULL; node = node->next)
1067  {
1068  currentRule = node->data;
1069  if (currentRule->mergeResult == mergeResult)
1070  matching_rules = g_list_prepend (matching_rules,
1071  currentRule);
1072  }
1073  qiter.remainder = g_list_length (matching_rules);
1074  g_list_foreach (matching_rules, qof_book_merge_rule_cb, &qiter);
1075  g_list_free (matching_rules);
1076 }
1077 
1078 /* ============================================================== */
const gchar * mergeLabel
Definition: qofbookmerge.h:176
gpointer qof_object_new_instance(QofIdTypeConst type_name, QofBook *book)
Definition: qofobject.c:42
gint qof_collection_compare(QofCollection *target, QofCollection *merge)
Compare two secondary collections.
Definition: qofid.c:264
#define QOF_TYPE_COLLECT
secondary collections are used for one-to-many references between entities and are implemented using ...
Definition: qofclass.h:121
QofBook * mergeBook
Definition: qofbookmerge.h:217
const gchar * QofIdType
Definition: qofid.h:81
gint qof_numeric_compare(QofNumeric a, QofNumeric b)
Definition: qofnumeric.c:169
QofCollection * qof_book_get_collection(QofBook *book, QofIdType entity_type)
Definition: qofbook.c:220
struct _QofNumeric QofNumeric
A rational-number type.
Definition: qofnumeric.h:61
void qof_class_param_foreach(QofIdTypeConst obj_name, QofParamForeachCB cb, gpointer user_data)
Definition: qofclass.c:268
gint qof_book_merge_commit(QofBookMergeData *mergeData)
Commits the import data to the target book.
Definition: qofbookmerge.c:998
GUID * guid_malloc(void)
Definition: guid.c:64
void qof_object_foreach(QofIdTypeConst type_name, QofBook *book, QofEntityForeachCB cb, gpointer user_data)
Definition: qofobject.c:174
gint kvp_frame_compare(const KvpFrame *fa, const KvpFrame *fb)
Definition: kvpframe.c:1743
KvpFrame * kvp_frame_copy(const KvpFrame *frame)
Definition: kvpframe.c:153
struct _KvpFrame KvpFrame
Definition: kvpframe.h:74
QofTime * qof_time_copy(const QofTime *qt)
Create a copy of a QofTime.
Definition: qoftime.c:223
gpointer(* create)(QofBook *)
Definition: qofobject.h:80
QofIdType mergeType
Definition: qofbookmerge.h:174
const gchar * qof_object_get_type_label(QofIdTypeConst type_name)
Definition: qofobject.c:223
GSList * mergeParam
Definition: qofbookmerge.h:178
QofEntity * importEnt
Definition: qofbookmerge.h:189
struct QofCollection_s QofCollection
Definition: qofid.h:138
GHashTable * target_table
Definition: qofbookmerge.h:232
QofEntity * qof_collection_lookup_entity(QofCollection *col, const GUID *guid)
Definition: qofid.c:292
QofBookMergeResult mergeResult
Definition: qofbookmerge.h:188
GSList * linkedEntList
Definition: qofbookmerge.h:179
void qof_collection_foreach(QofCollection *col, QofEntityForeachCB cb_func, gpointer user_data)
Definition: qofid.c:392
gint qof_time_cmp(const QofTime *ta, const QofTime *tb)
Definition: qoftime.c:165
void qof_book_merge_rule_foreach(QofBookMergeData *mergeData, QofBookMergeRuleForeachCB cb, QofBookMergeResult mergeResult)
Dialogue Control Callback.
#define DEBUG(format, args...)
Definition: qoflog.h:208
One rule per entity, built into a single GList for the entire merge.
Definition: qofbookmerge.h:164
QofEntity entity
Definition: qofinstance-p.h:39
const gchar * QofType
Definition: qofclass.h:125
QofBookMergeData * qof_book_merge_init(QofBook *importBook, QofBook *targetBook)
Initialise the QofBookMerge process.
Definition: qofbookmerge.c:900
Definition: guid.h:53
gboolean mergeAbsolute
Definition: qofbookmerge.h:167
QofBookMergeRule * currentRule
Definition: qofbookmerge.h:222
QofBook * targetBook
Definition: qofbookmerge.h:219
struct QofTime64 QofTime
Use a 64-bit signed int QofTime.
Definition: qoftime.h:112
QofBookMergeResult
Results of collisions and user resolution.
Definition: qofbookmerge.h:125
const GUID * qof_entity_get_guid(QofEntity *ent)
Definition: qofid.c:105
void(* QofBookMergeRuleForeachCB)(QofBookMergeData *, QofBookMergeRule *, guint)
Definition of the dialogue control callback routine.
Definition: qofbookmerge.h:321
void qof_book_merge_abort(QofBookMergeData *mergeData)
Abort the merge and free all memory allocated by the merge.
Definition: qofbookmerge.c:937
GSList * mergeObjectParams
Definition: qofbookmerge.h:211
void qof_entity_set_guid(QofEntity *ent, const GUID *guid)
Definition: qofid.c:92
KvpFrame * kvp_frame_new(void)
Definition: kvpframe.c:97
void(* foreach)(QofCollection *, QofEntityForeachCB, gpointer)
Definition: qofobject.h:105
gint safe_strcmp(const gchar *da, const gchar *db)
Definition: qofutil.c:75
QofBookMergeData * qof_book_merge_update_result(QofBookMergeData *mergeData, QofBookMergeResult tag)
called by dialogue callback to set the result of user intervention
Definition: qofbookmerge.c:966
GSList * orphan_list
Definition: qofbookmerge.h:224
QofEntity * targetEnt
Definition: qofbookmerge.h:190
#define QOF_TYPE_CHOICE
Identify an object as containing a choice.
Definition: qofchoice.h:103
void qof_object_foreach_type(QofForeachTypeCB cb, gpointer user_data)
Definition: qofobject.c:140
const gchar * QofLogModule
Definition: qofid.h:85
GSList * targetList
Definition: qofbookmerge.h:215
mergeData contains the essential context data for any merge.
Definition: qofbookmerge.h:209