qofundo.c

00001 /***************************************************************************
00002  *            qofundo.c
00003  *
00004  *  Thu Aug 25 09:19:17 2005
00005  *  Copyright  2005,2006  Neil Williams
00006  *  linux@codehelp.co.uk
00007  ****************************************************************************/
00008 /*
00009  *  This program is free software; you can redistribute it and/or modify
00010  *  it under the terms of the GNU General Public License as published by
00011  *  the Free Software Foundation; either version 2 of the License, or
00012  *  (at your option) any later version.
00013  *
00014  *  This program is distributed in the hope that it will be useful,
00015  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00016  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00017  *  GNU General Public License for more details.
00018  *
00019  *  You should have received a copy of the GNU General Public License
00020  *  along with this program; if not, write to the Free Software
00021  *  Foundation, Inc., 51 Franklin Street, Fifth Floor Boston, MA  02110-1301,  USA
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;      /* static anyway so only store a pointer */
00049     const GUID *guid;           /* enable re-creation of this entity */
00050     QofIdType type;             /* ditto param, static. */
00051     gchar *value;               /* cached string? */
00052     gchar *path;                /* for KVP */
00053     QofIdType choice;           /* For QOF_TYPE_CHOICE */
00054     QofUndoAction how;          /* how to act on the undo */
00055 };
00056 
00057 struct QofUndoOperation_t
00058 {
00059     const gchar *label;
00060     QofTime *qt;
00061     GList *entity_list;         /* GList of qof_undo_entity* */
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     /* need to do COLLECT and CHOICE */
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     // to undo a create, use a delete.
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     // now need to store each parameter in a second entity, MODIFY.
00535     type = instance->entity.e_type;
00536     qof_class_param_foreach (type, undo_get_entity, instance);
00537     undo_entity = g_new0 (QofUndoEntity, 1);
00538     // to undo a delete, use a create.
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     // handle if record is called without a commit.
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     // set the initial state that undo will reinstate.
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 /* ====================== END OF FILE ======================== */

Generated on Thu Jan 31 22:50:26 2008 for QOF by  doxygen 1.5.4