QOF  0.8.7
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 
00291     book_undo = book->undo_data;
00292     if (book_undo->index_position > 1)
00293         book_undo->index_position--;
00294     else
00295         book_undo->index_position = 0;
00296     undo_operation =
00297         (QofUndoOperation
00298         *) (g_list_nth (book_undo->undo_list,
00299             book_undo->index_position))->data;
00300     g_return_if_fail (undo_operation);
00301     ent_list = undo_operation->entity_list;
00302     while (ent_list != NULL)
00303     {
00304         undo_entity = (QofUndoEntity *) ent_list->data;
00305         if (!undo_entity)
00306             break;
00307         switch (undo_entity->how)
00308         {
00309         case UNDO_MODIFY:
00310             {
00311                 qof_reinstate_entity (undo_entity, book);
00312                 break;
00313             }
00314         case UNDO_CREATE:
00315             {
00316                 qof_recreate_entity (undo_entity, book);
00317                 break;
00318             }
00319         case UNDO_DELETE:
00320             {
00321                 qof_dump_entity (undo_entity, book);
00322                 break;
00323             }
00324         case UNDO_NOOP:
00325             {
00326                 break;
00327             }
00328         }
00329         ent_list = g_list_next (ent_list);
00330     }
00331 }
00332 
00333 void
00334 qof_book_redo (QofBook * book)
00335 {
00336     QofUndoOperation *undo_operation;
00337     QofUndoEntity *undo_entity;
00338     QofUndo *book_undo;
00339     GList *ent_list;
00340     gint length;
00341 
00342     book_undo = book->undo_data;
00343     undo_operation =
00344         (QofUndoOperation
00345         *) (g_list_nth (book_undo->undo_list,
00346             book_undo->index_position))->data;
00347     if (!undo_operation)
00348         return;
00349     ent_list = undo_operation->entity_list;
00350     while (ent_list != NULL)
00351     {
00352         undo_entity = (QofUndoEntity *) ent_list->data;
00353         if (!undo_entity)
00354             break;
00355         switch (undo_entity->how)
00356         {
00357         case UNDO_MODIFY:
00358             {
00359                 qof_reinstate_entity (undo_entity, book);
00360                 break;
00361             }
00362         case UNDO_CREATE:
00363             {
00364                 qof_dump_entity (undo_entity, book);
00365                 break;
00366             }
00367         case UNDO_DELETE:
00368             {
00369                 qof_recreate_entity (undo_entity, book);
00370                 break;
00371             }
00372         case UNDO_NOOP:
00373             {
00374                 break;
00375             }
00376         }
00377         ent_list = g_list_next (ent_list);
00378     }
00379     length = g_list_length (book_undo->undo_list);
00380     if (book_undo->index_position < length)
00381         book_undo->index_position++;
00382     else
00383         book_undo->index_position = length;
00384 }
00385 
00386 void
00387 qof_book_clear_undo (QofBook * book)
00388 {
00389     QofUndoOperation *operation;
00390     QofUndo *book_undo;
00391 
00392     if (!book)
00393         return;
00394     book_undo = book->undo_data;
00395     while (book_undo != NULL)
00396     {
00397         operation = (QofUndoOperation *) book_undo->undo_list->data;
00398         if(operation->entity_list)
00399             g_list_free (operation->entity_list);
00400         book_undo->undo_list = g_list_next (book_undo->undo_list);
00401     }
00402     book_undo->index_position = 0;
00403     g_free (book_undo->undo_label);
00404 }
00405 
00406 gboolean
00407 qof_book_can_undo (QofBook * book)
00408 {
00409     QofUndo *book_undo;
00410     gint length;
00411 
00412     book_undo = book->undo_data;
00413     length = g_list_length (book_undo->undo_list);
00414     if ((book_undo->index_position == 0) || (length == 0))
00415         return FALSE;
00416     return TRUE;
00417 }
00418 
00419 gboolean
00420 qof_book_can_redo (QofBook * book)
00421 {
00422     QofUndo *book_undo;
00423     gint length;
00424 
00425     book_undo = book->undo_data;
00426     length = g_list_length (book_undo->undo_list);
00427     if ((book_undo->index_position == length) || (length == 0))
00428         return FALSE;
00429     return TRUE;
00430 }
00431 
00432 QofUndoOperation *
00433 qof_undo_new_operation (QofBook * book, gchar * label)
00434 {
00435     QofUndoOperation *undo_operation;
00436     QofUndo *book_undo;
00437 
00438     undo_operation = NULL;
00439     book_undo = book->undo_data;
00440     undo_operation = g_new0 (QofUndoOperation, 1);
00441     undo_operation->label = label;
00442     undo_operation->qt = qof_time_get_current();
00443     undo_operation->entity_list = NULL;
00444     g_list_foreach (book_undo->undo_cache,
00445         qof_undo_new_entry, undo_operation);
00446     return undo_operation;
00447 }
00448 
00449 void
00450 qof_undo_new_entry (gpointer cache, gpointer operation)
00451 {
00452     QofUndoOperation *undo_operation;
00453     QofUndoEntity *undo_entity;
00454 
00455     g_return_if_fail (operation || cache);
00456     undo_operation = (QofUndoOperation *) operation;
00457     undo_entity = (QofUndoEntity *) cache;
00458     g_return_if_fail (undo_operation || undo_entity);
00459     undo_operation->entity_list =
00460         g_list_prepend (undo_operation->entity_list, undo_entity);
00461 }
00462 
00463 void
00464 qof_undo_create (QofInstance * instance)
00465 {
00466     QofUndoEntity *undo_entity;
00467     QofBook *book;
00468     QofUndo *book_undo;
00469 
00470     if (!instance)
00471         return;
00472     book = instance->book;
00473     book_undo = book->undo_data;
00474     undo_entity = g_new0 (QofUndoEntity, 1);
00475     // to undo a create, use a delete.
00476     undo_entity->how = UNDO_DELETE;
00477     undo_entity->guid = qof_instance_get_guid (instance);
00478     undo_entity->type = instance->entity.e_type;
00479     book_undo->undo_cache =
00480         g_list_prepend (book_undo->undo_cache, undo_entity);
00481 }
00482 
00483 static void
00484 undo_get_entity (QofParam * param, gpointer data)
00485 {
00486     QofBook *book;
00487     QofUndo *book_undo;
00488     QofInstance *instance;
00489     QofUndoEntity *undo_entity;
00490 
00491     instance = (QofInstance *) data;
00492     book = instance->book;
00493     book_undo = book->undo_data;
00494     g_return_if_fail (instance || param);
00495     undo_entity = qof_prepare_undo (&instance->entity, param);
00496     book_undo->undo_cache =
00497         g_list_prepend (book_undo->undo_cache, undo_entity);
00498 }
00499 
00500 void
00501 qof_undo_delete (QofInstance * instance)
00502 {
00503     QofUndoEntity *undo_entity;
00504     QofIdType type;
00505     QofUndo *book_undo;
00506     QofBook *book;
00507 
00508     if (!instance)
00509         return;
00510     book = instance->book;
00511     book_undo = book->undo_data;
00512     // now need to store each parameter in a second entity, MODIFY.
00513     type = instance->entity.e_type;
00514     qof_class_param_foreach (type, undo_get_entity, instance);
00515     undo_entity = g_new0 (QofUndoEntity, 1);
00516     // to undo a delete, use a create.
00517     undo_entity->how = UNDO_CREATE;
00518     undo_entity->guid = qof_instance_get_guid (instance);
00519     undo_entity->type = type;
00520     book_undo->undo_cache =
00521         g_list_prepend (book_undo->undo_cache, undo_entity);
00522 }
00523 
00524 void
00525 qof_undo_modify (QofInstance * instance, const QofParam * param)
00526 {
00527     QofBook *book;
00528     QofUndo *book_undo;
00529     QofUndoEntity *undo_entity;
00530 
00531     if (!instance || !param)
00532         return;
00533     book = instance->book;
00534     book_undo = book->undo_data;
00535     // handle if record is called without a commit.
00536     undo_entity = qof_prepare_undo (&instance->entity, param);
00537     book_undo->undo_cache =
00538         g_list_prepend (book_undo->undo_cache, undo_entity);
00539     // set the initial state that undo will reinstate.
00540     if (book_undo->index_position == 0)
00541     {
00542         book_undo->undo_list = g_list_prepend (book_undo->undo_list,
00543             qof_undo_new_operation (book, "initial"));
00544         book_undo->index_position++;
00545     }
00546 }
00547 
00548 void
00549 qof_undo_commit (QofInstance * instance, const QofParam * param)
00550 {
00551     QofUndoEntity *undo_entity;
00552     QofUndo *book_undo;
00553     QofBook *book;
00554 
00555     if (!instance || !param)
00556         return;
00557     book = instance->book;
00558     book_undo = book->undo_data;
00559     undo_entity = qof_prepare_undo (&instance->entity, param);
00560     book_undo->undo_cache =
00561         g_list_prepend (book_undo->undo_cache, undo_entity);
00562 }
00563 
00564 void
00565 qof_book_start_operation (QofBook * book, gchar * label)
00566 {
00567     QofUndo *book_undo;
00568 
00569     book_undo = book->undo_data;
00570     if (book_undo->undo_operation_open && book_undo->undo_cache)
00571     {
00572         g_list_free (book_undo->undo_cache);
00573         book_undo->undo_operation_open = FALSE;
00574         if (book_undo->undo_label)
00575             g_free (book_undo->undo_label);
00576     }
00577     book_undo->undo_label = g_strdup (label);
00578     book_undo->undo_operation_open = TRUE;
00579 }
00580 
00581 void
00582 qof_book_end_operation (QofBook * book)
00583 {
00584     QofUndo *book_undo;
00585 
00586     book_undo = book->undo_data;
00587     book_undo->undo_list = g_list_prepend (book_undo->undo_list,
00588         qof_undo_new_operation (book, book_undo->undo_label));
00589     book_undo->index_position++;
00590     g_list_free (book_undo->undo_cache);
00591     book_undo->undo_operation_open = FALSE;
00592 }
00593 
00594 QofTime *
00595 qof_book_undo_first_modified (QofBook * book)
00596 {
00597     QofUndoOperation *undo_operation;
00598     QofUndo *book_undo;
00599 
00600     book_undo = book->undo_data;
00601     undo_operation =
00602         (QofUndoOperation *) g_list_last (book_undo->undo_list);
00603     return undo_operation->qt;
00604 }
00605 
00606 gint
00607 qof_book_undo_count (QofBook * book)
00608 {
00609     QofUndo *book_undo;
00610 
00611     book_undo = book->undo_data;
00612     return g_list_length (book_undo->undo_list);
00613 }
00614 
00615 /* ====================== END OF FILE ======================== */