QOF  0.8.0
qofundo.c
00001 /***************************************************************************
00002  *            qofundo.c
00003  *
00004  *  Thu Aug 25 09:19:17 2005
00005  *  Copyright  2005,2006,2008  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     if (0 == safe_strcmp (param->param_type, QOF_TYPE_CHAR))
00182     {
00183         param->param_setfcn (ent, value);
00184     }
00185 }
00186 
00187 void
00188 qof_undo_set_param (QofEntity * ent, const QofParam * param, 
00189                       gchar * value)
00190 {
00191     qof_undo_modify ((QofInstance*)ent, param);
00192     set_param (ent, param, value);
00193     qof_undo_commit ((QofInstance*)ent, param);
00194 }
00195 
00196 static void
00197 undo_from_kvp_helper (const gchar * path, KvpValue * content,
00198     gpointer data)
00199 {
00200     QofUndoEntity *undo_entity;
00201 
00202     undo_entity = (QofUndoEntity *) data;
00203     undo_entity->path = g_strdup (path);
00204     undo_entity->value = kvp_value_to_bare_string (content);
00205 }
00206 
00207 QofUndoEntity *
00208 qof_prepare_undo (QofEntity * ent, const QofParam * param)
00209 {
00210     QofUndoEntity *undo_entity;
00211     KvpFrame *undo_frame;
00212 
00213     undo_frame = NULL;
00214     undo_entity = g_new0 (QofUndoEntity, 1);
00215     undo_entity->guid = qof_entity_get_guid (ent);
00216     undo_entity->param = param;
00217     undo_entity->how = UNDO_MODIFY;
00218     undo_entity->type = ent->e_type;
00219     undo_entity->value = qof_util_param_to_string (ent, param);
00220     if (0 == (safe_strcmp (param->param_type, QOF_TYPE_KVP)))
00221     {
00222         undo_frame = kvp_frame_copy (param->param_getfcn (ent, param));
00223         kvp_frame_for_each_slot (undo_frame, undo_from_kvp_helper,
00224             undo_entity);
00225     }
00226     /* need to do COLLECT and CHOICE */
00227     return undo_entity;
00228 }
00229 
00230 static void
00231 qof_reinstate_entity (QofUndoEntity * undo_entity, QofBook * book)
00232 {
00233     const QofParam *undo_param;
00234     QofCollection *coll;
00235     QofEntity *ent;
00236 
00237     undo_param = undo_entity->param;
00238     if (!undo_param)
00239         return;
00240     PINFO (" reinstate:%s", undo_entity->type);
00241     coll = qof_book_get_collection (book, undo_entity->type);
00242     if (!coll)
00243         return;
00244     ent = qof_collection_lookup_entity (coll, undo_entity->guid);
00245     if (!ent)
00246         return;
00247     PINFO (" undoing %s %s", undo_param->param_name, undo_entity->value);
00248     set_param (ent, undo_param, undo_entity->value);
00249 }
00250 
00251 static void
00252 qof_recreate_entity (QofUndoEntity * undo_entity, QofBook * book)
00253 {
00254     QofEntity *ent;
00255     const GUID *guid;
00256     QofIdType type;
00257     QofInstance *inst;
00258 
00259     guid = undo_entity->guid;
00260     type = undo_entity->type;
00261     g_return_if_fail (guid || type);
00262     inst = (QofInstance *) qof_object_new_instance (type, book);
00263     ent = (QofEntity *) inst;
00264     qof_entity_set_guid (ent, guid);
00265 }
00266 
00267 static void
00268 qof_dump_entity (QofUndoEntity * undo_entity, QofBook * book)
00269 {
00270     QofCollection *coll;
00271     QofEntity *ent;
00272     const GUID *guid;
00273     QofIdType type;
00274 
00275     type = undo_entity->type;
00276     guid = undo_entity->guid;
00277     g_return_if_fail (type || book);
00278     coll = qof_book_get_collection (book, type);
00279     ent = qof_collection_lookup_entity (coll, guid);
00280     qof_entity_release (ent);
00281 }
00282 
00283 void
00284 qof_book_undo (QofBook * book)
00285 {
00286     QofUndoOperation *undo_operation;
00287     QofUndoEntity *undo_entity;
00288     QofUndo *book_undo;
00289     GList *ent_list;
00290     gint length;
00291 
00292     book_undo = book->undo_data;
00293     length = g_list_length (book_undo->undo_list);
00294     if (book_undo->index_position > 1)
00295         book_undo->index_position--;
00296     else
00297         book_undo->index_position = 0;
00298     undo_operation =
00299         (QofUndoOperation
00300         *) (g_list_nth (book_undo->undo_list,
00301             book_undo->index_position))->data;
00302     g_return_if_fail (undo_operation);
00303     ent_list = undo_operation->entity_list;
00304     while (ent_list != NULL)
00305     {
00306         undo_entity = (QofUndoEntity *) ent_list->data;
00307         if (!undo_entity)
00308             break;
00309         switch (undo_entity->how)
00310         {
00311         case UNDO_MODIFY:
00312             {
00313                 qof_reinstate_entity (undo_entity, book);
00314                 break;
00315             }
00316         case UNDO_CREATE:
00317             {
00318                 qof_recreate_entity (undo_entity, book);
00319                 break;
00320             }
00321         case UNDO_DELETE:
00322             {
00323                 qof_dump_entity (undo_entity, book);
00324                 break;
00325             }
00326         case UNDO_NOOP:
00327             {
00328                 break;
00329             }
00330         }
00331         ent_list = g_list_next (ent_list);
00332     }
00333 }
00334 
00335 void
00336 qof_book_redo (QofBook * book)
00337 {
00338     QofUndoOperation *undo_operation;
00339     QofUndoEntity *undo_entity;
00340     QofUndo *book_undo;
00341     GList *ent_list;
00342     gint length;
00343 
00344     book_undo = book->undo_data;
00345     undo_operation =
00346         (QofUndoOperation
00347         *) (g_list_nth (book_undo->undo_list,
00348             book_undo->index_position))->data;
00349     if (!undo_operation)
00350         return;
00351     ent_list = undo_operation->entity_list;
00352     while (ent_list != NULL)
00353     {
00354         undo_entity = (QofUndoEntity *) ent_list->data;
00355         if (!undo_entity)
00356             break;
00357         switch (undo_entity->how)
00358         {
00359         case UNDO_MODIFY:
00360             {
00361                 qof_reinstate_entity (undo_entity, book);
00362                 break;
00363             }
00364         case UNDO_CREATE:
00365             {
00366                 qof_dump_entity (undo_entity, book);
00367                 break;
00368             }
00369         case UNDO_DELETE:
00370             {
00371                 qof_recreate_entity (undo_entity, book);
00372                 break;
00373             }
00374         case UNDO_NOOP:
00375             {
00376                 break;
00377             }
00378         }
00379         ent_list = g_list_next (ent_list);
00380     }
00381     length = g_list_length (book_undo->undo_list);
00382     if (book_undo->index_position < length)
00383         book_undo->index_position++;
00384     else
00385         book_undo->index_position = length;
00386 }
00387 
00388 void
00389 qof_book_clear_undo (QofBook * book)
00390 {
00391     QofUndoOperation *operation;
00392     QofUndo *book_undo;
00393 
00394     if (!book)
00395         return;
00396     book_undo = book->undo_data;
00397     while (book_undo != NULL)
00398     {
00399         operation = (QofUndoOperation *) book_undo->undo_list->data;
00400         if(operation->entity_list)
00401             g_list_free (operation->entity_list);
00402         book_undo->undo_list = g_list_next (book_undo->undo_list);
00403     }
00404     book_undo->index_position = 0;
00405     g_free (book_undo->undo_label);
00406 }
00407 
00408 gboolean
00409 qof_book_can_undo (QofBook * book)
00410 {
00411     QofUndo *book_undo;
00412     gint length;
00413 
00414     book_undo = book->undo_data;
00415     length = g_list_length (book_undo->undo_list);
00416     if ((book_undo->index_position == 0) || (length == 0))
00417         return FALSE;
00418     return TRUE;
00419 }
00420 
00421 gboolean
00422 qof_book_can_redo (QofBook * book)
00423 {
00424     QofUndo *book_undo;
00425     gint length;
00426 
00427     book_undo = book->undo_data;
00428     length = g_list_length (book_undo->undo_list);
00429     if ((book_undo->index_position == length) || (length == 0))
00430         return FALSE;
00431     return TRUE;
00432 }
00433 
00434 QofUndoOperation *
00435 qof_undo_new_operation (QofBook * book, gchar * label)
00436 {
00437     QofUndoOperation *undo_operation;
00438     QofUndo *book_undo;
00439 
00440     undo_operation = NULL;
00441     book_undo = book->undo_data;
00442     undo_operation = g_new0 (QofUndoOperation, 1);
00443     undo_operation->label = label;
00444     undo_operation->qt = qof_time_get_current();
00445     undo_operation->entity_list = NULL;
00446     g_list_foreach (book_undo->undo_cache,
00447         qof_undo_new_entry, undo_operation);
00448     return undo_operation;
00449 }
00450 
00451 void
00452 qof_undo_new_entry (gpointer cache, gpointer operation)
00453 {
00454     QofUndoOperation *undo_operation;
00455     QofUndoEntity *undo_entity;
00456 
00457     g_return_if_fail (operation || cache);
00458     undo_operation = (QofUndoOperation *) operation;
00459     undo_entity = (QofUndoEntity *) cache;
00460     g_return_if_fail (undo_operation || undo_entity);
00461     undo_operation->entity_list =
00462         g_list_prepend (undo_operation->entity_list, undo_entity);
00463 }
00464 
00465 void
00466 qof_undo_create (QofInstance * instance)
00467 {
00468     QofUndoEntity *undo_entity;
00469     QofBook *book;
00470     QofUndo *book_undo;
00471 
00472     if (!instance)
00473         return;
00474     book = instance->book;
00475     book_undo = book->undo_data;
00476     undo_entity = g_new0 (QofUndoEntity, 1);
00477     // to undo a create, use a delete.
00478     undo_entity->how = UNDO_DELETE;
00479     undo_entity->guid = qof_instance_get_guid (instance);
00480     undo_entity->type = instance->entity.e_type;
00481     book_undo->undo_cache =
00482         g_list_prepend (book_undo->undo_cache, undo_entity);
00483 }
00484 
00485 static void
00486 undo_get_entity (QofParam * param, gpointer data)
00487 {
00488     QofBook *book;
00489     QofUndo *book_undo;
00490     QofInstance *instance;
00491     QofUndoEntity *undo_entity;
00492 
00493     instance = (QofInstance *) data;
00494     book = instance->book;
00495     book_undo = book->undo_data;
00496     g_return_if_fail (instance || param);
00497     undo_entity = qof_prepare_undo (&instance->entity, param);
00498     book_undo->undo_cache =
00499         g_list_prepend (book_undo->undo_cache, undo_entity);
00500 }
00501 
00502 void
00503 qof_undo_delete (QofInstance * instance)
00504 {
00505     QofUndoEntity *undo_entity;
00506     QofIdType type;
00507     QofUndo *book_undo;
00508     QofBook *book;
00509 
00510     if (!instance)
00511         return;
00512     book = instance->book;
00513     book_undo = book->undo_data;
00514     // now need to store each parameter in a second entity, MODIFY.
00515     type = instance->entity.e_type;
00516     qof_class_param_foreach (type, undo_get_entity, instance);
00517     undo_entity = g_new0 (QofUndoEntity, 1);
00518     // to undo a delete, use a create.
00519     undo_entity->how = UNDO_CREATE;
00520     undo_entity->guid = qof_instance_get_guid (instance);
00521     undo_entity->type = type;
00522     book_undo->undo_cache =
00523         g_list_prepend (book_undo->undo_cache, undo_entity);
00524 }
00525 
00526 void
00527 qof_undo_modify (QofInstance * instance, const QofParam * param)
00528 {
00529     QofBook *book;
00530     QofUndo *book_undo;
00531     QofUndoEntity *undo_entity;
00532 
00533     if (!instance || !param)
00534         return;
00535     book = instance->book;
00536     book_undo = book->undo_data;
00537     // handle if record is called without a commit.
00538     undo_entity = qof_prepare_undo (&instance->entity, param);
00539     book_undo->undo_cache =
00540         g_list_prepend (book_undo->undo_cache, undo_entity);
00541     // set the initial state that undo will reinstate.
00542     if (book_undo->index_position == 0)
00543     {
00544         book_undo->undo_list = g_list_prepend (book_undo->undo_list,
00545             qof_undo_new_operation (book, "initial"));
00546         book_undo->index_position++;
00547     }
00548 }
00549 
00550 void
00551 qof_undo_commit (QofInstance * instance, const QofParam * param)
00552 {
00553     QofUndoEntity *undo_entity;
00554     QofUndo *book_undo;
00555     QofBook *book;
00556 
00557     if (!instance || !param)
00558         return;
00559     book = instance->book;
00560     book_undo = book->undo_data;
00561     undo_entity = qof_prepare_undo (&instance->entity, param);
00562     book_undo->undo_cache =
00563         g_list_prepend (book_undo->undo_cache, undo_entity);
00564 }
00565 
00566 void
00567 qof_book_start_operation (QofBook * book, gchar * label)
00568 {
00569     QofUndo *book_undo;
00570 
00571     book_undo = book->undo_data;
00572     if (book_undo->undo_operation_open && book_undo->undo_cache)
00573     {
00574         g_list_free (book_undo->undo_cache);
00575         book_undo->undo_operation_open = FALSE;
00576         if (book_undo->undo_label)
00577             g_free (book_undo->undo_label);
00578     }
00579     book_undo->undo_label = g_strdup (label);
00580     book_undo->undo_operation_open = TRUE;
00581 }
00582 
00583 void
00584 qof_book_end_operation (QofBook * book)
00585 {
00586     QofUndo *book_undo;
00587 
00588     book_undo = book->undo_data;
00589     book_undo->undo_list = g_list_prepend (book_undo->undo_list,
00590         qof_undo_new_operation (book, book_undo->undo_label));
00591     book_undo->index_position++;
00592     g_list_free (book_undo->undo_cache);
00593     book_undo->undo_operation_open = FALSE;
00594 }
00595 
00596 QofTime *
00597 qof_book_undo_first_modified (QofBook * book)
00598 {
00599     QofUndoOperation *undo_operation;
00600     QofUndo *book_undo;
00601 
00602     book_undo = book->undo_data;
00603     undo_operation =
00604         (QofUndoOperation *) g_list_last (book_undo->undo_list);
00605     return undo_operation->qt;
00606 }
00607 
00608 gint
00609 qof_book_undo_count (QofBook * book)
00610 {
00611     QofUndo *book_undo;
00612 
00613     book_undo = book->undo_data;
00614     return g_list_length (book_undo->undo_list);
00615 }
00616 
00617 /* ====================== END OF FILE ======================== */