QOF  0.7.5
qofundo.c
1 /***************************************************************************
2  * qofundo.c
3  *
4  * Thu Aug 25 09:19:17 2005
5  * Copyright 2005,2006 Neil Williams
6  * linux@codehelp.co.uk
7  ****************************************************************************/
8 /*
9  * This program is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin Street, Fifth Floor Boston, MA 02110-1301, USA
22  */
23 
24 #include "config.h"
25 #include <glib.h>
26 #include <qof.h>
27 #include <stdio.h>
28 #include <stdlib.h>
29 #include <libintl.h>
30 #include <locale.h>
31 #include <errno.h>
32 #include "qofbook-p.h"
33 #include "qofundo-p.h"
34 #include "qofundo.h"
35 
36 static QofLogModule log_module = QOF_MOD_UNDO;
37 
38 typedef enum
39 {
40  UNDO_NOOP = 0,
41  UNDO_CREATE,
42  UNDO_DELETE,
43  UNDO_MODIFY
44 } QofUndoAction;
45 
46 struct QofUndoEntity_t
47 {
48  const QofParam *param; /* static anyway so only store a pointer */
49  const GUID *guid; /* enable re-creation of this entity */
50  QofIdType type; /* ditto param, static. */
51  gchar *value; /* cached string? */
52  gchar *path; /* for KVP */
53  QofIdType choice; /* For QOF_TYPE_CHOICE */
54  QofUndoAction how; /* how to act on the undo */
55 };
56 
57 struct QofUndoOperation_t
58 {
59  const gchar *label;
60  QofTime *qt;
61  GList *entity_list; /* GList of qof_undo_entity* */
62 };
63 
64 static void
65 set_param (QofEntity * ent, const QofParam * param,
66  gchar * value)
67 {
68  gchar *tail;
69  QofNumeric cli_numeric;
70  gboolean cli_bool;
71  gint32 cli_i32;
72  gint64 cli_i64;
73  QofTime *cli_time;
74  GUID *cm_guid;
75  void (*string_setter) (QofEntity *, gchar *);
76  void (*time_setter) (QofEntity *, QofTime *);
77  void (*i32_setter) (QofEntity *, gint32);
78  void (*i64_setter) (QofEntity *, gint64);
79  void (*numeric_setter) (QofEntity *, QofNumeric);
80  void (*boolean_setter) (QofEntity *, gboolean);
81  void (*guid_setter) (QofEntity *, const GUID *);
82 
83  if (0 == safe_strcmp (param->param_type, QOF_TYPE_STRING))
84  {
85  string_setter =
86  (void (*)(QofEntity *, gchar *)) param->param_setfcn;
87  if (string_setter)
88  {
89  param->param_setfcn (ent, value);
90  }
91  }
92  if (0 == safe_strcmp (param->param_type, QOF_TYPE_GUID))
93  {
94  cm_guid = g_new (GUID, 1);
95  if (TRUE == string_to_guid (value, cm_guid))
96  {
97  guid_setter =
98  (void (*)(QofEntity *, const GUID *)) param->param_setfcn;
99  if (guid_setter != NULL)
100  {
101  guid_setter (ent, cm_guid);
102  }
103  }
104  }
105  if ((0 == safe_strcmp (param->param_type, QOF_TYPE_NUMERIC)) ||
106  (safe_strcmp (param->param_type, QOF_TYPE_DEBCRED) == 0))
107  {
108  numeric_setter =
109  (void (*)(QofEntity *, QofNumeric)) param->param_setfcn;
110  qof_numeric_from_string (value, &cli_numeric);
111  if (numeric_setter != NULL)
112  {
113  numeric_setter (ent, cli_numeric);
114  }
115  }
116  if (0 == safe_strcmp (param->param_type, QOF_TYPE_BOOLEAN))
117  {
118  cli_bool = FALSE;
119  if (qof_util_bool_to_int (value) == 1)
120  {
121  cli_bool = TRUE;
122  }
123  boolean_setter =
124  (void (*)(QofEntity *, gboolean)) param->param_setfcn;
125  if (boolean_setter != NULL)
126  {
127  boolean_setter (ent, cli_bool);
128  }
129  }
130  if (0 == safe_strcmp (param->param_type, QOF_TYPE_INT32))
131  {
132  errno = 0;
133  cli_i32 = (gint32) strtol (value, &tail, 0);
134  if (errno == 0)
135  {
136  i32_setter =
137  (void (*)(QofEntity *, gint32)) param->param_setfcn;
138  if (i32_setter != NULL)
139  {
140  i32_setter (ent, cli_i32);
141  }
142  }
143  else
144  {
145  PERR (" Cannot convert %s into a number: "
146  "an overflow has been detected.", value);
147  }
148  }
149  if (0 == safe_strcmp (param->param_type, QOF_TYPE_INT64))
150  {
151  errno = 0;
152  cli_i64 = (gint64) strtol (value, &tail, 0);
153  if (errno == 0)
154  {
155  i64_setter =
156  (void (*)(QofEntity *, gint64)) param->param_setfcn;
157  if (i64_setter != NULL)
158  {
159  i64_setter (ent, cli_i64);
160  }
161  }
162  else
163  {
164  PERR (" Cannot convert %s into a number: "
165  "an overflow has been detected.", value);
166  }
167  }
168  if (0 ==safe_strcmp (param->param_type, QOF_TYPE_TIME))
169  {
170  QofDate *qd;
171 
172  qd = qof_date_parse (value, QOF_DATE_FORMAT_UTC);
173  cli_time = qof_date_to_qtime (qd);
174  time_setter =
175  (void (*)(QofEntity *, QofTime *)) param->param_setfcn;
176  if ((time_setter != NULL) && qof_time_is_valid (cli_time))
177  {
178  time_setter (ent, cli_time);
179  }
180  }
181 #ifndef QOF_DISABLE_DEPRECATED
182  if (0 == safe_strcmp (param->param_type, QOF_TYPE_DATE))
183  {
184  Timespec cli_date;
185  time_t cli_time_t;
186  void (*date_setter) (QofEntity *, Timespec);
187  struct tm cli_time;
188 
189  date_setter =
190  (void (*)(QofEntity *, Timespec)) param->param_setfcn;
191  strptime (value, QOF_UTC_DATE_FORMAT, &cli_time);
192  cli_time_t = mktime (&cli_time);
193  timespecFromTime_t (&cli_date, cli_time_t);
194  if (date_setter != NULL)
195  {
196  date_setter (ent, cli_date);
197  }
198  }
199 #endif
200  if (0 == safe_strcmp (param->param_type, QOF_TYPE_CHAR))
201  {
202  param->param_setfcn (ent, value);
203  }
204 }
205 
206 void
207 qof_undo_set_param (QofEntity * ent, const QofParam * param,
208  gchar * value)
209 {
210  qof_undo_modify ((QofInstance*)ent, param);
211  set_param (ent, param, value);
212  qof_undo_commit ((QofInstance*)ent, param);
213 }
214 
215 static void
216 undo_from_kvp_helper (const gchar * path, KvpValue * content,
217  gpointer data)
218 {
219  QofUndoEntity *undo_entity;
220 
221  undo_entity = (QofUndoEntity *) data;
222  undo_entity->path = g_strdup (path);
223  undo_entity->value = kvp_value_to_bare_string (content);
224 }
225 
226 QofUndoEntity *
227 qof_prepare_undo (QofEntity * ent, const QofParam * param)
228 {
229  QofUndoEntity *undo_entity;
230  KvpFrame *undo_frame;
231 
232  undo_frame = NULL;
233  undo_entity = g_new0 (QofUndoEntity, 1);
234  undo_entity->guid = qof_entity_get_guid (ent);
235  undo_entity->param = param;
236  undo_entity->how = UNDO_MODIFY;
237  undo_entity->type = ent->e_type;
238  undo_entity->value =
240  if (0 == (safe_strcmp (param->param_type, QOF_TYPE_KVP)))
241  {
242  undo_frame = kvp_frame_copy (param->param_getfcn (ent, param));
243  kvp_frame_for_each_slot (undo_frame, undo_from_kvp_helper,
244  undo_entity);
245  }
246  /* need to do COLLECT and CHOICE */
247  return undo_entity;
248 }
249 
250 static void
251 qof_reinstate_entity (QofUndoEntity * undo_entity, QofBook * book)
252 {
253  const QofParam *undo_param;
254  QofCollection *coll;
255  QofEntity *ent;
256 
257  undo_param = undo_entity->param;
258  if (!undo_param)
259  return;
260  PINFO (" reinstate:%s", undo_entity->type);
261  coll = qof_book_get_collection (book, undo_entity->type);
262  if (!coll)
263  return;
264  ent = qof_collection_lookup_entity (coll, undo_entity->guid);
265  if (!ent)
266  return;
267  PINFO (" undoing %s %s", undo_param->param_name, undo_entity->value);
268  set_param (ent, undo_param, undo_entity->value);
269 }
270 
271 static void
272 qof_recreate_entity (QofUndoEntity * undo_entity, QofBook * book)
273 {
274  QofEntity *ent;
275  const GUID *guid;
276  QofIdType type;
277  QofInstance *inst;
278 
279  guid = undo_entity->guid;
280  type = undo_entity->type;
281  g_return_if_fail (guid || type);
282  inst = (QofInstance *) qof_object_new_instance (type, book);
283  ent = (QofEntity *) inst;
284  qof_entity_set_guid (ent, guid);
285 }
286 
287 static void
288 qof_dump_entity (QofUndoEntity * undo_entity, QofBook * book)
289 {
290  QofCollection *coll;
291  QofEntity *ent;
292  const GUID *guid;
293  QofIdType type;
294 
295  type = undo_entity->type;
296  guid = undo_entity->guid;
297  g_return_if_fail (type || book);
298  coll = qof_book_get_collection (book, type);
299  ent = qof_collection_lookup_entity (coll, guid);
300  qof_entity_release (ent);
301 }
302 
303 void
305 {
306  QofUndoOperation *undo_operation;
307  QofUndoEntity *undo_entity;
308  QofUndo *book_undo;
309  GList *ent_list;
310  gint length;
311 
312  book_undo = book->undo_data;
313  length = g_list_length (book_undo->undo_list);
314  if (book_undo->index_position > 1)
315  book_undo->index_position--;
316  else
317  book_undo->index_position = 0;
318  undo_operation =
319  (QofUndoOperation
320  *) (g_list_nth (book_undo->undo_list,
321  book_undo->index_position))->data;
322  g_return_if_fail (undo_operation);
323  ent_list = undo_operation->entity_list;
324  while (ent_list != NULL)
325  {
326  undo_entity = (QofUndoEntity *) ent_list->data;
327  if (!undo_entity)
328  break;
329  switch (undo_entity->how)
330  {
331  case UNDO_MODIFY:
332  {
333  qof_reinstate_entity (undo_entity, book);
334  break;
335  }
336  case UNDO_CREATE:
337  {
338  qof_recreate_entity (undo_entity, book);
339  break;
340  }
341  case UNDO_DELETE:
342  {
343  qof_dump_entity (undo_entity, book);
344  break;
345  }
346  case UNDO_NOOP:
347  {
348  break;
349  }
350  }
351  ent_list = g_list_next (ent_list);
352  }
353 }
354 
355 void
357 {
358  QofUndoOperation *undo_operation;
359  QofUndoEntity *undo_entity;
360  QofUndo *book_undo;
361  GList *ent_list;
362  gint length;
363 
364  book_undo = book->undo_data;
365  undo_operation =
366  (QofUndoOperation
367  *) (g_list_nth (book_undo->undo_list,
368  book_undo->index_position))->data;
369  if (!undo_operation)
370  return;
371  ent_list = undo_operation->entity_list;
372  while (ent_list != NULL)
373  {
374  undo_entity = (QofUndoEntity *) ent_list->data;
375  if (!undo_entity)
376  break;
377  switch (undo_entity->how)
378  {
379  case UNDO_MODIFY:
380  {
381  qof_reinstate_entity (undo_entity, book);
382  break;
383  }
384  case UNDO_CREATE:
385  {
386  qof_dump_entity (undo_entity, book);
387  break;
388  }
389  case UNDO_DELETE:
390  {
391  qof_recreate_entity (undo_entity, book);
392  break;
393  }
394  case UNDO_NOOP:
395  {
396  break;
397  }
398  }
399  ent_list = g_list_next (ent_list);
400  }
401  length = g_list_length (book_undo->undo_list);
402  if (book_undo->index_position < length)
403  book_undo->index_position++;
404  else
405  book_undo->index_position = length;
406 }
407 
408 void
410 {
411  QofUndoOperation *operation;
412  QofUndo *book_undo;
413 
414  if (!book)
415  return;
416  book_undo = book->undo_data;
417  while (book_undo != NULL)
418  {
419  operation = (QofUndoOperation *) book_undo->undo_list->data;
420  if(operation->entity_list)
421  g_list_free (operation->entity_list);
422  book_undo->undo_list = g_list_next (book_undo->undo_list);
423  }
424  book_undo->index_position = 0;
425  g_free (book_undo->undo_label);
426 }
427 
428 gboolean
430 {
431  QofUndo *book_undo;
432  gint length;
433 
434  book_undo = book->undo_data;
435  length = g_list_length (book_undo->undo_list);
436  if ((book_undo->index_position == 0) || (length == 0))
437  return FALSE;
438  return TRUE;
439 }
440 
441 gboolean
443 {
444  QofUndo *book_undo;
445  gint length;
446 
447  book_undo = book->undo_data;
448  length = g_list_length (book_undo->undo_list);
449  if ((book_undo->index_position == length) || (length == 0))
450  return FALSE;
451  return TRUE;
452 }
453 
454 QofUndoOperation *
455 qof_undo_new_operation (QofBook * book, gchar * label)
456 {
457  QofUndoOperation *undo_operation;
458  QofUndo *book_undo;
459 
460  undo_operation = NULL;
461  book_undo = book->undo_data;
462  undo_operation = g_new0 (QofUndoOperation, 1);
463  undo_operation->label = label;
464  undo_operation->qt = qof_time_get_current();
465  undo_operation->entity_list = NULL;
466  g_list_foreach (book_undo->undo_cache,
467  qof_undo_new_entry, undo_operation);
468  return undo_operation;
469 }
470 
471 void
472 qof_undo_new_entry (gpointer cache, gpointer operation)
473 {
474  QofUndoOperation *undo_operation;
475  QofUndoEntity *undo_entity;
476 
477  g_return_if_fail (operation || cache);
478  undo_operation = (QofUndoOperation *) operation;
479  undo_entity = (QofUndoEntity *) cache;
480  g_return_if_fail (undo_operation || undo_entity);
481  undo_operation->entity_list =
482  g_list_prepend (undo_operation->entity_list, undo_entity);
483 }
484 
485 void
487 {
488  QofUndoEntity *undo_entity;
489  QofBook *book;
490  QofUndo *book_undo;
491 
492  if (!instance)
493  return;
494  book = instance->book;
495  book_undo = book->undo_data;
496  undo_entity = g_new0 (QofUndoEntity, 1);
497  // to undo a create, use a delete.
498  undo_entity->how = UNDO_DELETE;
499  undo_entity->guid = qof_instance_get_guid (instance);
500  undo_entity->type = instance->entity.e_type;
501  book_undo->undo_cache =
502  g_list_prepend (book_undo->undo_cache, undo_entity);
503 }
504 
505 static void
506 undo_get_entity (QofParam * param, gpointer data)
507 {
508  QofBook *book;
509  QofUndo *book_undo;
510  QofInstance *instance;
511  QofUndoEntity *undo_entity;
512 
513  instance = (QofInstance *) data;
514  book = instance->book;
515  book_undo = book->undo_data;
516  g_return_if_fail (instance || param);
517  undo_entity = qof_prepare_undo (&instance->entity, param);
518  book_undo->undo_cache =
519  g_list_prepend (book_undo->undo_cache, undo_entity);
520 }
521 
522 void
524 {
525  QofUndoEntity *undo_entity;
526  QofIdType type;
527  QofUndo *book_undo;
528  QofBook *book;
529 
530  if (!instance)
531  return;
532  book = instance->book;
533  book_undo = book->undo_data;
534  // now need to store each parameter in a second entity, MODIFY.
535  type = instance->entity.e_type;
536  qof_class_param_foreach (type, undo_get_entity, instance);
537  undo_entity = g_new0 (QofUndoEntity, 1);
538  // to undo a delete, use a create.
539  undo_entity->how = UNDO_CREATE;
540  undo_entity->guid = qof_instance_get_guid (instance);
541  undo_entity->type = type;
542  book_undo->undo_cache =
543  g_list_prepend (book_undo->undo_cache, undo_entity);
544 }
545 
546 void
547 qof_undo_modify (QofInstance * instance, const QofParam * param)
548 {
549  QofBook *book;
550  QofUndo *book_undo;
551  QofUndoEntity *undo_entity;
552 
553  if (!instance || !param)
554  return;
555  book = instance->book;
556  book_undo = book->undo_data;
557  // handle if record is called without a commit.
558  undo_entity = qof_prepare_undo (&instance->entity, param);
559  book_undo->undo_cache =
560  g_list_prepend (book_undo->undo_cache, undo_entity);
561  // set the initial state that undo will reinstate.
562  if (book_undo->index_position == 0)
563  {
564  book_undo->undo_list = g_list_prepend (book_undo->undo_list,
565  qof_undo_new_operation (book, "initial"));
566  book_undo->index_position++;
567  }
568 }
569 
570 void
571 qof_undo_commit (QofInstance * instance, const QofParam * param)
572 {
573  QofUndoEntity *undo_entity;
574  QofUndo *book_undo;
575  QofBook *book;
576 
577  if (!instance || !param)
578  return;
579  book = instance->book;
580  book_undo = book->undo_data;
581  undo_entity = qof_prepare_undo (&instance->entity, param);
582  book_undo->undo_cache =
583  g_list_prepend (book_undo->undo_cache, undo_entity);
584 }
585 
586 void
587 qof_book_start_operation (QofBook * book, gchar * label)
588 {
589  QofUndo *book_undo;
590 
591  book_undo = book->undo_data;
592  if (book_undo->undo_operation_open && book_undo->undo_cache)
593  {
594  g_list_free (book_undo->undo_cache);
595  book_undo->undo_operation_open = FALSE;
596  if (book_undo->undo_label)
597  g_free (book_undo->undo_label);
598  }
599  book_undo->undo_label = g_strdup (label);
600  book_undo->undo_operation_open = TRUE;
601 }
602 
603 void
605 {
606  QofUndo *book_undo;
607 
608  book_undo = book->undo_data;
609  book_undo->undo_list = g_list_prepend (book_undo->undo_list,
610  qof_undo_new_operation (book, book_undo->undo_label));
611  book_undo->index_position++;
612  g_list_free (book_undo->undo_cache);
613  book_undo->undo_operation_open = FALSE;
614 }
615 
616 QofTime *
618 {
619  QofUndoOperation *undo_operation;
620  QofUndo *book_undo;
621 
622  book_undo = book->undo_data;
623  undo_operation =
624  (QofUndoOperation *) g_list_last (book_undo->undo_list);
625  return undo_operation->qt;
626 }
627 
628 gint
630 {
631  QofUndo *book_undo;
632 
633  book_undo = book->undo_data;
634  return g_list_length (book_undo->undo_list);
635 }
636 
637 /* ====================== END OF FILE ======================== */