00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024 #include "config.h"
00025 #include <glib.h>
00026 #include <qof.h>
00027 #include <stdio.h>
00028 #include <stdlib.h>
00029 #include <libintl.h>
00030 #include <locale.h>
00031 #include <errno.h>
00032 #include "qofbook-p.h"
00033 #include "qofundo-p.h"
00034 #include "qofundo.h"
00035
00036 static QofLogModule log_module = QOF_MOD_UNDO;
00037
00038 typedef enum
00039 {
00040 UNDO_NOOP = 0,
00041 UNDO_CREATE,
00042 UNDO_DELETE,
00043 UNDO_MODIFY
00044 } QofUndoAction;
00045
00046 struct QofUndoEntity_t
00047 {
00048 const QofParam *param;
00049 const GUID *guid;
00050 QofIdType type;
00051 gchar *value;
00052 gchar *path;
00053 QofIdType choice;
00054 QofUndoAction how;
00055 };
00056
00057 struct QofUndoOperation_t
00058 {
00059 const gchar *label;
00060 QofTime *qt;
00061 GList *entity_list;
00062 };
00063
00064 static void
00065 set_param (QofEntity * ent, const QofParam * param,
00066 gchar * value)
00067 {
00068 gchar *tail;
00069 QofNumeric cli_numeric;
00070 gboolean cli_bool;
00071 gint32 cli_i32;
00072 gint64 cli_i64;
00073 QofTime *cli_time;
00074 GUID *cm_guid;
00075 void (*string_setter) (QofEntity *, gchar *);
00076 void (*time_setter) (QofEntity *, QofTime *);
00077 void (*i32_setter) (QofEntity *, gint32);
00078 void (*i64_setter) (QofEntity *, gint64);
00079 void (*numeric_setter) (QofEntity *, QofNumeric);
00080 void (*boolean_setter) (QofEntity *, gboolean);
00081 void (*guid_setter) (QofEntity *, const GUID *);
00082
00083 if (0 == safe_strcmp (param->param_type, QOF_TYPE_STRING))
00084 {
00085 string_setter =
00086 (void (*)(QofEntity *, gchar *)) param->param_setfcn;
00087 if (string_setter)
00088 {
00089 param->param_setfcn (ent, value);
00090 }
00091 }
00092 if (0 == safe_strcmp (param->param_type, QOF_TYPE_GUID))
00093 {
00094 cm_guid = g_new (GUID, 1);
00095 if (TRUE == string_to_guid (value, cm_guid))
00096 {
00097 guid_setter =
00098 (void (*)(QofEntity *, const GUID *)) param->param_setfcn;
00099 if (guid_setter != NULL)
00100 {
00101 guid_setter (ent, cm_guid);
00102 }
00103 }
00104 }
00105 if ((0 == safe_strcmp (param->param_type, QOF_TYPE_NUMERIC)) ||
00106 (safe_strcmp (param->param_type, QOF_TYPE_DEBCRED) == 0))
00107 {
00108 numeric_setter =
00109 (void (*)(QofEntity *, QofNumeric)) param->param_setfcn;
00110 qof_numeric_from_string (value, &cli_numeric);
00111 if (numeric_setter != NULL)
00112 {
00113 numeric_setter (ent, cli_numeric);
00114 }
00115 }
00116 if (0 == safe_strcmp (param->param_type, QOF_TYPE_BOOLEAN))
00117 {
00118 cli_bool = FALSE;
00119 if (qof_util_bool_to_int (value) == 1)
00120 {
00121 cli_bool = TRUE;
00122 }
00123 boolean_setter =
00124 (void (*)(QofEntity *, gboolean)) param->param_setfcn;
00125 if (boolean_setter != NULL)
00126 {
00127 boolean_setter (ent, cli_bool);
00128 }
00129 }
00130 if (0 == safe_strcmp (param->param_type, QOF_TYPE_INT32))
00131 {
00132 errno = 0;
00133 cli_i32 = (gint32) strtol (value, &tail, 0);
00134 if (errno == 0)
00135 {
00136 i32_setter =
00137 (void (*)(QofEntity *, gint32)) param->param_setfcn;
00138 if (i32_setter != NULL)
00139 {
00140 i32_setter (ent, cli_i32);
00141 }
00142 }
00143 else
00144 {
00145 PERR (" Cannot convert %s into a number: "
00146 "an overflow has been detected.", value);
00147 }
00148 }
00149 if (0 == safe_strcmp (param->param_type, QOF_TYPE_INT64))
00150 {
00151 errno = 0;
00152 cli_i64 = (gint64) strtol (value, &tail, 0);
00153 if (errno == 0)
00154 {
00155 i64_setter =
00156 (void (*)(QofEntity *, gint64)) param->param_setfcn;
00157 if (i64_setter != NULL)
00158 {
00159 i64_setter (ent, cli_i64);
00160 }
00161 }
00162 else
00163 {
00164 PERR (" Cannot convert %s into a number: "
00165 "an overflow has been detected.", value);
00166 }
00167 }
00168 if (0 ==safe_strcmp (param->param_type, QOF_TYPE_TIME))
00169 {
00170 QofDate *qd;
00171
00172 qd = qof_date_parse (value, QOF_DATE_FORMAT_UTC);
00173 cli_time = qof_date_to_qtime (qd);
00174 time_setter =
00175 (void (*)(QofEntity *, QofTime *)) param->param_setfcn;
00176 if ((time_setter != NULL) && qof_time_is_valid (cli_time))
00177 {
00178 time_setter (ent, cli_time);
00179 }
00180 }
00181 #ifndef QOF_DISABLE_DEPRECATED
00182 if (0 == safe_strcmp (param->param_type, QOF_TYPE_DATE))
00183 {
00184 Timespec cli_date;
00185 time_t cli_time_t;
00186 void (*date_setter) (QofEntity *, Timespec);
00187 struct tm cli_time;
00188
00189 date_setter =
00190 (void (*)(QofEntity *, Timespec)) param->param_setfcn;
00191 strptime (value, QOF_UTC_DATE_FORMAT, &cli_time);
00192 cli_time_t = mktime (&cli_time);
00193 timespecFromTime_t (&cli_date, cli_time_t);
00194 if (date_setter != NULL)
00195 {
00196 date_setter (ent, cli_date);
00197 }
00198 }
00199 #endif
00200 if (0 == safe_strcmp (param->param_type, QOF_TYPE_CHAR))
00201 {
00202 param->param_setfcn (ent, value);
00203 }
00204 }
00205
00206 void
00207 qof_undo_set_param (QofEntity * ent, const QofParam * param,
00208 gchar * value)
00209 {
00210 qof_undo_modify ((QofInstance*)ent, param);
00211 set_param (ent, param, value);
00212 qof_undo_commit ((QofInstance*)ent, param);
00213 }
00214
00215 static void
00216 undo_from_kvp_helper (const gchar * path, KvpValue * content,
00217 gpointer data)
00218 {
00219 QofUndoEntity *undo_entity;
00220
00221 undo_entity = (QofUndoEntity *) data;
00222 undo_entity->path = g_strdup (path);
00223 undo_entity->value = kvp_value_to_bare_string (content);
00224 }
00225
00226 QofUndoEntity *
00227 qof_prepare_undo (QofEntity * ent, const QofParam * param)
00228 {
00229 QofUndoEntity *undo_entity;
00230 KvpFrame *undo_frame;
00231
00232 undo_frame = NULL;
00233 undo_entity = g_new0 (QofUndoEntity, 1);
00234 undo_entity->guid = qof_entity_get_guid (ent);
00235 undo_entity->param = param;
00236 undo_entity->how = UNDO_MODIFY;
00237 undo_entity->type = ent->e_type;
00238 undo_entity->value =
00239 qof_book_merge_param_as_string ((QofParam*) param, ent);
00240 if (0 == (safe_strcmp (param->param_type, QOF_TYPE_KVP)))
00241 {
00242 undo_frame = kvp_frame_copy (param->param_getfcn (ent, param));
00243 kvp_frame_for_each_slot (undo_frame, undo_from_kvp_helper,
00244 undo_entity);
00245 }
00246
00247 return undo_entity;
00248 }
00249
00250 static void
00251 qof_reinstate_entity (QofUndoEntity * undo_entity, QofBook * book)
00252 {
00253 const QofParam *undo_param;
00254 QofCollection *coll;
00255 QofEntity *ent;
00256
00257 undo_param = undo_entity->param;
00258 if (!undo_param)
00259 return;
00260 PINFO (" reinstate:%s", undo_entity->type);
00261 coll = qof_book_get_collection (book, undo_entity->type);
00262 if (!coll)
00263 return;
00264 ent = qof_collection_lookup_entity (coll, undo_entity->guid);
00265 if (!ent)
00266 return;
00267 PINFO (" undoing %s %s", undo_param->param_name, undo_entity->value);
00268 set_param (ent, undo_param, undo_entity->value);
00269 }
00270
00271 static void
00272 qof_recreate_entity (QofUndoEntity * undo_entity, QofBook * book)
00273 {
00274 QofEntity *ent;
00275 const GUID *guid;
00276 QofIdType type;
00277 QofInstance *inst;
00278
00279 guid = undo_entity->guid;
00280 type = undo_entity->type;
00281 g_return_if_fail (guid || type);
00282 inst = (QofInstance *) qof_object_new_instance (type, book);
00283 ent = (QofEntity *) inst;
00284 qof_entity_set_guid (ent, guid);
00285 }
00286
00287 static void
00288 qof_dump_entity (QofUndoEntity * undo_entity, QofBook * book)
00289 {
00290 QofCollection *coll;
00291 QofEntity *ent;
00292 const GUID *guid;
00293 QofIdType type;
00294
00295 type = undo_entity->type;
00296 guid = undo_entity->guid;
00297 g_return_if_fail (type || book);
00298 coll = qof_book_get_collection (book, type);
00299 ent = qof_collection_lookup_entity (coll, guid);
00300 qof_entity_release (ent);
00301 }
00302
00303 void
00304 qof_book_undo (QofBook * book)
00305 {
00306 QofUndoOperation *undo_operation;
00307 QofUndoEntity *undo_entity;
00308 QofUndo *book_undo;
00309 GList *ent_list;
00310 gint length;
00311
00312 book_undo = book->undo_data;
00313 length = g_list_length (book_undo->undo_list);
00314 if (book_undo->index_position > 1)
00315 book_undo->index_position--;
00316 else
00317 book_undo->index_position = 0;
00318 undo_operation =
00319 (QofUndoOperation
00320 *) (g_list_nth (book_undo->undo_list,
00321 book_undo->index_position))->data;
00322 g_return_if_fail (undo_operation);
00323 ent_list = undo_operation->entity_list;
00324 while (ent_list != NULL)
00325 {
00326 undo_entity = (QofUndoEntity *) ent_list->data;
00327 if (!undo_entity)
00328 break;
00329 switch (undo_entity->how)
00330 {
00331 case UNDO_MODIFY:
00332 {
00333 qof_reinstate_entity (undo_entity, book);
00334 break;
00335 }
00336 case UNDO_CREATE:
00337 {
00338 qof_recreate_entity (undo_entity, book);
00339 break;
00340 }
00341 case UNDO_DELETE:
00342 {
00343 qof_dump_entity (undo_entity, book);
00344 break;
00345 }
00346 case UNDO_NOOP:
00347 {
00348 break;
00349 }
00350 }
00351 ent_list = g_list_next (ent_list);
00352 }
00353 }
00354
00355 void
00356 qof_book_redo (QofBook * book)
00357 {
00358 QofUndoOperation *undo_operation;
00359 QofUndoEntity *undo_entity;
00360 QofUndo *book_undo;
00361 GList *ent_list;
00362 gint length;
00363
00364 book_undo = book->undo_data;
00365 undo_operation =
00366 (QofUndoOperation
00367 *) (g_list_nth (book_undo->undo_list,
00368 book_undo->index_position))->data;
00369 if (!undo_operation)
00370 return;
00371 ent_list = undo_operation->entity_list;
00372 while (ent_list != NULL)
00373 {
00374 undo_entity = (QofUndoEntity *) ent_list->data;
00375 if (!undo_entity)
00376 break;
00377 switch (undo_entity->how)
00378 {
00379 case UNDO_MODIFY:
00380 {
00381 qof_reinstate_entity (undo_entity, book);
00382 break;
00383 }
00384 case UNDO_CREATE:
00385 {
00386 qof_dump_entity (undo_entity, book);
00387 break;
00388 }
00389 case UNDO_DELETE:
00390 {
00391 qof_recreate_entity (undo_entity, book);
00392 break;
00393 }
00394 case UNDO_NOOP:
00395 {
00396 break;
00397 }
00398 }
00399 ent_list = g_list_next (ent_list);
00400 }
00401 length = g_list_length (book_undo->undo_list);
00402 if (book_undo->index_position < length)
00403 book_undo->index_position++;
00404 else
00405 book_undo->index_position = length;
00406 }
00407
00408 void
00409 qof_book_clear_undo (QofBook * book)
00410 {
00411 QofUndoOperation *operation;
00412 QofUndo *book_undo;
00413
00414 if (!book)
00415 return;
00416 book_undo = book->undo_data;
00417 while (book_undo != NULL)
00418 {
00419 operation = (QofUndoOperation *) book_undo->undo_list->data;
00420 if(operation->entity_list)
00421 g_list_free (operation->entity_list);
00422 book_undo->undo_list = g_list_next (book_undo->undo_list);
00423 }
00424 book_undo->index_position = 0;
00425 g_free (book_undo->undo_label);
00426 }
00427
00428 gboolean
00429 qof_book_can_undo (QofBook * book)
00430 {
00431 QofUndo *book_undo;
00432 gint length;
00433
00434 book_undo = book->undo_data;
00435 length = g_list_length (book_undo->undo_list);
00436 if ((book_undo->index_position == 0) || (length == 0))
00437 return FALSE;
00438 return TRUE;
00439 }
00440
00441 gboolean
00442 qof_book_can_redo (QofBook * book)
00443 {
00444 QofUndo *book_undo;
00445 gint length;
00446
00447 book_undo = book->undo_data;
00448 length = g_list_length (book_undo->undo_list);
00449 if ((book_undo->index_position == length) || (length == 0))
00450 return FALSE;
00451 return TRUE;
00452 }
00453
00454 QofUndoOperation *
00455 qof_undo_new_operation (QofBook * book, gchar * label)
00456 {
00457 QofUndoOperation *undo_operation;
00458 QofUndo *book_undo;
00459
00460 undo_operation = NULL;
00461 book_undo = book->undo_data;
00462 undo_operation = g_new0 (QofUndoOperation, 1);
00463 undo_operation->label = label;
00464 undo_operation->qt = qof_time_get_current();
00465 undo_operation->entity_list = NULL;
00466 g_list_foreach (book_undo->undo_cache,
00467 qof_undo_new_entry, undo_operation);
00468 return undo_operation;
00469 }
00470
00471 void
00472 qof_undo_new_entry (gpointer cache, gpointer operation)
00473 {
00474 QofUndoOperation *undo_operation;
00475 QofUndoEntity *undo_entity;
00476
00477 g_return_if_fail (operation || cache);
00478 undo_operation = (QofUndoOperation *) operation;
00479 undo_entity = (QofUndoEntity *) cache;
00480 g_return_if_fail (undo_operation || undo_entity);
00481 undo_operation->entity_list =
00482 g_list_prepend (undo_operation->entity_list, undo_entity);
00483 }
00484
00485 void
00486 qof_undo_create (QofInstance * instance)
00487 {
00488 QofUndoEntity *undo_entity;
00489 QofBook *book;
00490 QofUndo *book_undo;
00491
00492 if (!instance)
00493 return;
00494 book = instance->book;
00495 book_undo = book->undo_data;
00496 undo_entity = g_new0 (QofUndoEntity, 1);
00497
00498 undo_entity->how = UNDO_DELETE;
00499 undo_entity->guid = qof_instance_get_guid (instance);
00500 undo_entity->type = instance->entity.e_type;
00501 book_undo->undo_cache =
00502 g_list_prepend (book_undo->undo_cache, undo_entity);
00503 }
00504
00505 static void
00506 undo_get_entity (QofParam * param, gpointer data)
00507 {
00508 QofBook *book;
00509 QofUndo *book_undo;
00510 QofInstance *instance;
00511 QofUndoEntity *undo_entity;
00512
00513 instance = (QofInstance *) data;
00514 book = instance->book;
00515 book_undo = book->undo_data;
00516 g_return_if_fail (instance || param);
00517 undo_entity = qof_prepare_undo (&instance->entity, param);
00518 book_undo->undo_cache =
00519 g_list_prepend (book_undo->undo_cache, undo_entity);
00520 }
00521
00522 void
00523 qof_undo_delete (QofInstance * instance)
00524 {
00525 QofUndoEntity *undo_entity;
00526 QofIdType type;
00527 QofUndo *book_undo;
00528 QofBook *book;
00529
00530 if (!instance)
00531 return;
00532 book = instance->book;
00533 book_undo = book->undo_data;
00534
00535 type = instance->entity.e_type;
00536 qof_class_param_foreach (type, undo_get_entity, instance);
00537 undo_entity = g_new0 (QofUndoEntity, 1);
00538
00539 undo_entity->how = UNDO_CREATE;
00540 undo_entity->guid = qof_instance_get_guid (instance);
00541 undo_entity->type = type;
00542 book_undo->undo_cache =
00543 g_list_prepend (book_undo->undo_cache, undo_entity);
00544 }
00545
00546 void
00547 qof_undo_modify (QofInstance * instance, const QofParam * param)
00548 {
00549 QofBook *book;
00550 QofUndo *book_undo;
00551 QofUndoEntity *undo_entity;
00552
00553 if (!instance || !param)
00554 return;
00555 book = instance->book;
00556 book_undo = book->undo_data;
00557
00558 undo_entity = qof_prepare_undo (&instance->entity, param);
00559 book_undo->undo_cache =
00560 g_list_prepend (book_undo->undo_cache, undo_entity);
00561
00562 if (book_undo->index_position == 0)
00563 {
00564 book_undo->undo_list = g_list_prepend (book_undo->undo_list,
00565 qof_undo_new_operation (book, "initial"));
00566 book_undo->index_position++;
00567 }
00568 }
00569
00570 void
00571 qof_undo_commit (QofInstance * instance, const QofParam * param)
00572 {
00573 QofUndoEntity *undo_entity;
00574 QofUndo *book_undo;
00575 QofBook *book;
00576
00577 if (!instance || !param)
00578 return;
00579 book = instance->book;
00580 book_undo = book->undo_data;
00581 undo_entity = qof_prepare_undo (&instance->entity, param);
00582 book_undo->undo_cache =
00583 g_list_prepend (book_undo->undo_cache, undo_entity);
00584 }
00585
00586 void
00587 qof_book_start_operation (QofBook * book, gchar * label)
00588 {
00589 QofUndo *book_undo;
00590
00591 book_undo = book->undo_data;
00592 if (book_undo->undo_operation_open && book_undo->undo_cache)
00593 {
00594 g_list_free (book_undo->undo_cache);
00595 book_undo->undo_operation_open = FALSE;
00596 if (book_undo->undo_label)
00597 g_free (book_undo->undo_label);
00598 }
00599 book_undo->undo_label = g_strdup (label);
00600 book_undo->undo_operation_open = TRUE;
00601 }
00602
00603 void
00604 qof_book_end_operation (QofBook * book)
00605 {
00606 QofUndo *book_undo;
00607
00608 book_undo = book->undo_data;
00609 book_undo->undo_list = g_list_prepend (book_undo->undo_list,
00610 qof_undo_new_operation (book, book_undo->undo_label));
00611 book_undo->index_position++;
00612 g_list_free (book_undo->undo_cache);
00613 book_undo->undo_operation_open = FALSE;
00614 }
00615
00616 QofTime *
00617 qof_book_undo_first_modified (QofBook * book)
00618 {
00619 QofUndoOperation *undo_operation;
00620 QofUndo *book_undo;
00621
00622 book_undo = book->undo_data;
00623 undo_operation =
00624 (QofUndoOperation *) g_list_last (book_undo->undo_list);
00625 return undo_operation->qt;
00626 }
00627
00628 gint
00629 qof_book_undo_count (QofBook * book)
00630 {
00631 QofUndo *book_undo;
00632
00633 book_undo = book->undo_data;
00634 return g_list_length (book_undo->undo_list);
00635 }
00636
00637