GnuCash 2.4.99
qofbook.c
00001 /********************************************************************\
00002  * qofbook.c -- dataset access (set of books of entities)           *
00003  *                                                                  *
00004  * This program is free software; you can redistribute it and/or    *
00005  * modify it under the terms of the GNU General Public License as   *
00006  * published by the Free Software Foundation; either version 2 of   *
00007  * the License, or (at your option) any later version.              *
00008  *                                                                  *
00009  * This program is distributed in the hope that it will be useful,  *
00010  * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
00011  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
00012  * GNU General Public License for more details.                     *
00013  *                                                                  *
00014  * You should have received a copy of the GNU General Public License*
00015  * along with this program; if not, contact:                        *
00016  *                                                                  *
00017  * Free Software Foundation           Voice:  +1-617-542-5942       *
00018  * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
00019  * Boston, MA  02110-1301,  USA       gnu@gnu.org                   *
00020 \********************************************************************/
00021 
00022 /*
00023  * FILE:
00024  * qofbook.c
00025  *
00026  * FUNCTION:
00027  * Encapsulate all the information about a QOF dataset.
00028  *
00029  * HISTORY:
00030  * Created by Linas Vepstas December 1998
00031  * Copyright (c) 1998-2001,2003 Linas Vepstas <linas@linas.org>
00032  * Copyright (c) 2000 Dave Peticolas
00033  * Copyright (c) 2007 David Hampton <hampton@employees.org>
00034  */
00035 
00036 #include "config.h"
00037 
00038 #include <stdlib.h>
00039 #include <string.h>
00040 
00041 #include <glib.h>
00042 
00043 #include "qof.h"
00044 #include "qofevent-p.h"
00045 #include "qofbackend-p.h"
00046 #include "qofbook-p.h"
00047 #include "qofid-p.h"
00048 #include "qofobject-p.h"
00049 #include "qofbookslots.h"
00050 
00051 static QofLogModule log_module = QOF_MOD_ENGINE;
00052 
00053 QOF_GOBJECT_IMPL(qof_book, QofBook, QOF_TYPE_INSTANCE);
00054 
00055 /* ====================================================================== */
00056 /* constructor / destructor */
00057 
00058 static void coll_destroy(gpointer col)
00059 {
00060     qof_collection_destroy((QofCollection *) col);
00061 }
00062 
00063 static void
00064 qof_book_init (QofBook *book)
00065 {
00066     if (!book) return;
00067 
00068     book->hash_of_collections = g_hash_table_new_full(
00069                                     g_str_hash, g_str_equal,
00070                                     (GDestroyNotify)qof_string_cache_remove,  /* key_destroy_func   */
00071                                     coll_destroy);                            /* value_destroy_func */
00072 
00073     qof_instance_init_data (&book->inst, QOF_ID_BOOK, book);
00074 
00075     book->data_tables = g_hash_table_new (g_str_hash, g_str_equal);
00076     book->data_table_finalizers = g_hash_table_new (g_str_hash, g_str_equal);
00077 
00078     book->book_open = 'y';
00079     book->read_only = FALSE;
00080     book->session_dirty = FALSE;
00081     book->version = 0;
00082 }
00083 
00084 QofBook *
00085 qof_book_new (void)
00086 {
00087     QofBook *book;
00088 
00089     ENTER (" ");
00090     book = g_object_new(QOF_TYPE_BOOK, NULL);
00091     qof_object_book_begin (book);
00092 
00093     qof_event_gen (&book->inst, QOF_EVENT_CREATE, NULL);
00094     LEAVE ("book=%p", book);
00095     return book;
00096 }
00097 
00098 static void
00099 book_final (gpointer key, gpointer value, gpointer booq)
00100 {
00101     QofBookFinalCB cb = value;
00102     QofBook *book = booq;
00103 
00104     gpointer user_data = g_hash_table_lookup (book->data_tables, key);
00105     (*cb) (book, key, user_data);
00106 }
00107 
00108 static void
00109 qof_book_dispose_real (GObject *bookp)
00110 {
00111 }
00112 
00113 static void
00114 qof_book_finalize_real (GObject *bookp)
00115 {
00116 }
00117 
00118 void
00119 qof_book_destroy (QofBook *book)
00120 {
00121     GHashTable* cols;
00122 
00123     if (!book) return;
00124     ENTER ("book=%p", book);
00125 
00126     book->shutting_down = TRUE;
00127     qof_event_force (&book->inst, QOF_EVENT_DESTROY, NULL);
00128 
00129     /* Call the list of finalizers, let them do their thing.
00130      * Do this before tearing into the rest of the book.
00131      */
00132     g_hash_table_foreach (book->data_table_finalizers, book_final, book);
00133 
00134     qof_object_book_end (book);
00135 
00136     g_hash_table_destroy (book->data_table_finalizers);
00137     book->data_table_finalizers = NULL;
00138     g_hash_table_destroy (book->data_tables);
00139     book->data_tables = NULL;
00140 
00141     /* qof_instance_release (&book->inst); */
00142 
00143     /* Note: we need to save this hashtable until after we remove ourself
00144      * from it, otherwise we'll crash in our dispose() function when we
00145      * DO remove ourself from the collection but the collection had already
00146      * been destroyed.
00147      */
00148     cols = book->hash_of_collections;
00149     g_object_unref (book);
00150     g_hash_table_destroy (cols);
00151     /*book->hash_of_collections = NULL;*/
00152 
00153     LEAVE ("book=%p", book);
00154 }
00155 /* ====================================================================== */
00156 
00157 gboolean
00158 qof_book_session_not_saved (const QofBook *book)
00159 {
00160     if (!book) return FALSE;
00161     return book->session_dirty;
00162 
00163 }
00164 
00165 void
00166 qof_book_mark_session_saved (QofBook *book)
00167 {
00168     if (!book) return;
00169 
00170     book->dirty_time = 0;
00171     if (book->session_dirty)
00172     {
00173         /* Set the session clean upfront, because the callback will check. */
00174         book->session_dirty = FALSE;
00175         if (book->dirty_cb)
00176             book->dirty_cb(book, FALSE, book->dirty_data);
00177     }
00178 }
00179 
00180 void qof_book_mark_session_dirty (QofBook *book)
00181 {
00182     if (!book) return;
00183     if (!book->session_dirty)
00184     {
00185         /* Set the session dirty upfront, because the callback will check. */
00186         book->session_dirty = TRUE;
00187         book->dirty_time = time(NULL);
00188         if (book->dirty_cb)
00189             book->dirty_cb(book, TRUE, book->dirty_data);
00190     }
00191 }
00192 
00193 time_t
00194 qof_book_get_session_dirty_time (const QofBook *book)
00195 {
00196     return book->dirty_time;
00197 }
00198 
00199 void
00200 qof_book_set_dirty_cb(QofBook *book, QofBookDirtyCB cb, gpointer user_data)
00201 {
00202     if (book->dirty_cb)
00203         g_warning("qof_book_set_dirty_cb: Already existing callback %p, will be overwritten by %p\n",
00204                   book->dirty_cb, cb);
00205     book->dirty_data = user_data;
00206     book->dirty_cb = cb;
00207 }
00208 
00209 /* ====================================================================== */
00210 /* getters */
00211 
00212 QofBackend *
00213 qof_book_get_backend (const QofBook *book)
00214 {
00215     if (!book) return NULL;
00216     return book->backend;
00217 }
00218 
00219 gboolean
00220 qof_book_shutting_down (const QofBook *book)
00221 {
00222     if (!book) return FALSE;
00223     return book->shutting_down;
00224 }
00225 
00226 /* ====================================================================== */
00227 /* setters */
00228 
00229 void
00230 qof_book_set_backend (QofBook *book, QofBackend *be)
00231 {
00232     if (!book) return;
00233     ENTER ("book=%p be=%p", book, be);
00234     book->backend = be;
00235     LEAVE (" ");
00236 }
00237 
00238 void qof_book_kvp_changed (QofBook *book)
00239 {
00240     qof_book_begin_edit(book);
00241     qof_instance_set_dirty (QOF_INSTANCE (book));
00242     qof_book_commit_edit(book);
00243 }
00244 
00245 /* ====================================================================== */
00246 
00247 KvpFrame *qof_book_get_slots(const QofBook *book)
00248 {
00249     return qof_instance_get_slots(QOF_INSTANCE(book));
00250 }
00251 
00252 /* Store arbitrary pointers in the QofBook for data storage extensibility */
00253 /* XXX if data is NULL, we should remove the key from the hash table!
00254  */
00255 void
00256 qof_book_set_data (QofBook *book, const char *key, gpointer data)
00257 {
00258     if (!book || !key) return;
00259     g_hash_table_insert (book->data_tables, (gpointer)key, data);
00260 }
00261 
00262 void
00263 qof_book_set_data_fin (QofBook *book, const char *key, gpointer data, QofBookFinalCB cb)
00264 {
00265     if (!book || !key) return;
00266     g_hash_table_insert (book->data_tables, (gpointer)key, data);
00267 
00268     if (!cb) return;
00269     g_hash_table_insert (book->data_table_finalizers, (gpointer)key, cb);
00270 }
00271 
00272 gpointer
00273 qof_book_get_data (const QofBook *book, const char *key)
00274 {
00275     if (!book || !key) return NULL;
00276     return g_hash_table_lookup (book->data_tables, (gpointer)key);
00277 }
00278 
00279 /* ====================================================================== */
00280 gboolean
00281 qof_book_is_readonly(const QofBook *book)
00282 {
00283     g_return_val_if_fail( book != NULL, TRUE );
00284     return book->read_only;
00285 }
00286 
00287 void
00288 qof_book_mark_readonly(QofBook *book)
00289 {
00290     g_return_if_fail( book != NULL );
00291     book->read_only = TRUE;
00292 }
00293 /* ====================================================================== */
00294 
00295 QofCollection *
00296 qof_book_get_collection (const QofBook *book, QofIdType entity_type)
00297 {
00298     QofCollection *col;
00299 
00300     if (!book || !entity_type) return NULL;
00301 
00302     col = g_hash_table_lookup (book->hash_of_collections, entity_type);
00303     if (!col)
00304     {
00305         col = qof_collection_new (entity_type);
00306         g_hash_table_insert(
00307             book->hash_of_collections,
00308             qof_string_cache_insert((gpointer) entity_type), col);
00309     }
00310     return col;
00311 }
00312 
00313 struct _iterate
00314 {
00315     QofCollectionForeachCB  fn;
00316     gpointer                data;
00317 };
00318 
00319 static void
00320 foreach_cb (gpointer key, gpointer item, gpointer arg)
00321 {
00322     struct _iterate *iter = arg;
00323     QofCollection *col = item;
00324 
00325     iter->fn (col, iter->data);
00326 }
00327 
00328 void
00329 qof_book_foreach_collection (const QofBook *book,
00330                              QofCollectionForeachCB cb, gpointer user_data)
00331 {
00332     struct _iterate iter;
00333 
00334     g_return_if_fail (book);
00335     g_return_if_fail (cb);
00336 
00337     iter.fn = cb;
00338     iter.data = user_data;
00339 
00340     g_hash_table_foreach (book->hash_of_collections, foreach_cb, &iter);
00341 }
00342 
00343 /* ====================================================================== */
00344 
00345 void qof_book_mark_closed (QofBook *book)
00346 {
00347     if (!book)
00348     {
00349         return;
00350     }
00351     book->book_open = 'n';
00352 }
00353 
00354 gint64
00355 qof_book_get_counter (QofBook *book, const char *counter_name)
00356 {
00357     KvpFrame *kvp;
00358     KvpValue *value;
00359 
00360     if (!book)
00361     {
00362         PWARN ("No book!!!");
00363         return -1;
00364     }
00365 
00366     if (!counter_name || *counter_name == '\0')
00367     {
00368         PWARN ("Invalid counter name.");
00369         return -1;
00370     }
00371 
00372     /* Use the KVP in the book */
00373     kvp = qof_book_get_slots (book);
00374 
00375     if (!kvp)
00376     {
00377         PWARN ("Book has no KVP_Frame");
00378         return -1;
00379     }
00380 
00381     value = kvp_frame_get_slot_path (kvp, "counters", counter_name, NULL);
00382     if (value)
00383     {
00384         /* found it */
00385         return kvp_value_get_gint64 (value);
00386     }
00387     else
00388     {
00389         /* New counter */
00390         return 0;
00391     }
00392 }
00393 
00394 gchar *
00395 qof_book_increment_and_format_counter (QofBook *book, const char *counter_name)
00396 {
00397     QofBackend *be;
00398     KvpFrame *kvp;
00399     KvpValue *value;
00400     gint64 counter;
00401     gchar* format;
00402 
00403     if (!book)
00404     {
00405         PWARN ("No book!!!");
00406         return NULL;
00407     }
00408 
00409     if (!counter_name || *counter_name == '\0')
00410     {
00411         PWARN ("Invalid counter name.");
00412         return NULL;
00413     }
00414 
00415     /* Get the current counter value from the KVP in the book. */
00416     counter = qof_book_get_counter(book, counter_name);
00417 
00418     /* Check if an error occured */
00419     if (counter < 0)
00420         return NULL;
00421 
00422     /* Increment the counter */
00423     counter++;
00424 
00425     /* Get the KVP from the current book */
00426     kvp = qof_book_get_slots (book);
00427 
00428     if (!kvp)
00429     {
00430         PWARN ("Book has no KVP_Frame");
00431         return NULL;
00432     }
00433 
00434     /* Save off the new counter */
00435     qof_book_begin_edit(book);
00436     value = kvp_value_new_gint64 (counter);
00437     kvp_frame_set_slot_path (kvp, value, "counters", counter_name, NULL);
00438     kvp_value_delete (value);
00439     qof_instance_set_dirty (QOF_INSTANCE (book));
00440     qof_book_commit_edit(book);
00441 
00442     format = qof_book_get_counter_format(book, counter_name);
00443 
00444     if (!format)
00445     {
00446         PWARN("Cannot get format for counter");
00447         return NULL;
00448     }
00449 
00450     /* Generate a string version of the counter */
00451     return g_strdup_printf(format, counter);
00452 }
00453 
00454 gchar *
00455 qof_book_get_counter_format(const QofBook *book, const char *counter_name)
00456 {
00457     KvpFrame *kvp;
00458     gchar *format;
00459     KvpValue *value;
00460     gchar *error;
00461 
00462     if (!book)
00463     {
00464         PWARN ("No book!!!");
00465         return NULL;
00466     }
00467 
00468     if (!counter_name || *counter_name == '\0')
00469     {
00470         PWARN ("Invalid counter name.");
00471         return NULL;
00472     }
00473 
00474     /* Get the KVP from the current book */
00475     kvp = qof_book_get_slots (book);
00476 
00477     if (!kvp)
00478     {
00479         PWARN ("Book has no KVP_Frame");
00480         return NULL;
00481     }
00482 
00483     format = NULL;
00484 
00485     /* Get the format string */
00486     value = kvp_frame_get_slot_path (kvp, "counter_formats", counter_name, NULL);
00487     if (value)
00488     {
00489         format = kvp_value_get_string (value);
00490         error = qof_book_validate_counter_format(format);
00491         if (error != NULL)
00492         {
00493             PWARN("Invalid counter format string. Format string: '%s' Counter: '%s' Error: '%s')", format, counter_name, error);
00494             /* Invalid format string */
00495             format = NULL;
00496             g_free(error);
00497         }
00498     }
00499 
00500     /* If no (valid) format string was found, use the default format
00501      * string */
00502     if (!format)
00503     {
00504         /* Use the default format */
00505         format = "%.6" G_GINT64_FORMAT;
00506     }
00507     return format;
00508 }
00509 
00510 gchar *
00511 qof_book_validate_counter_format(const gchar *p)
00512 {
00513     return qof_book_validate_counter_format_internal(p, G_GINT64_FORMAT);
00514 }
00515 
00516 gchar *
00517 qof_book_validate_counter_format_internal(const gchar *p,
00518         const gchar *gint64_format)
00519 {
00520     const gchar *conv_start, *tmp = NULL;
00521 
00522     /* Validate a counter format. This is a very simple "parser" that
00523      * simply checks for a single gint64 conversion specification,
00524      * allowing all modifiers and flags that printf(3) specifies (except
00525      * for the * width and precision, which need an extra argument). */
00526 
00527     /* Skip a prefix of any character except % */
00528     while (*p)
00529     {
00530         /* Skip two adjacent percent marks, which are literal percent
00531          * marks */
00532         if (p[0] == '%' && p[1] == '%')
00533         {
00534             p += 2;
00535             continue;
00536         }
00537         /* Break on a single percent mark, which is the start of the
00538          * conversion specification */
00539         if (*p == '%')
00540             break;
00541         /* Skip all other characters */
00542         p++;
00543     }
00544 
00545     if (!*p)
00546         return g_strdup("Format string ended without any conversion specification");
00547 
00548     /* Store the start of the conversion for error messages */
00549     conv_start = p;
00550 
00551     /* Skip the % */
00552     p++;
00553 
00554     /* See whether we have already reached the correct format
00555      * specification (e.g. "li" on Unix, "I64i" on Windows). */
00556     tmp = strstr(p, gint64_format);
00557 
00558     /* Skip any number of flag characters */
00559     while (*p && (tmp != p) && strchr("#0- +'I", *p))
00560     {
00561         p++;
00562         tmp = strstr(p, gint64_format);
00563     }
00564 
00565     /* Skip any number of field width digits */
00566     while (*p && (tmp != p) && strchr("0123456789", *p))
00567     {
00568         p++;
00569         tmp = strstr(p, gint64_format);
00570     }
00571 
00572     /* A precision specifier always starts with a dot */
00573     if (*p && *p == '.')
00574     {
00575         /* Skip the . */
00576         p++;
00577         /* Skip any number of precision digits */
00578         while (*p && strchr("0123456789", *p)) p++;
00579     }
00580 
00581     if (!*p)
00582         return g_strdup_printf("Format string ended during the conversion specification. Conversion seen so far: %s", conv_start);
00583 
00584     /* See if the format string starts with the correct format
00585      * specification. */
00586     tmp = strstr(p, gint64_format);
00587     if (tmp == NULL)
00588     {
00589         return g_strdup_printf("Invalid length modifier and/or conversion specifier ('%.4s'), it should be: %s", p, gint64_format);
00590     }
00591     else if (tmp != p)
00592     {
00593         return g_strdup_printf("Garbage before length modifier and/or conversion specifier: '%*s'", (int)(tmp - p), p);
00594     }
00595 
00596     /* Skip length modifier / conversion specifier */
00597     p += strlen(gint64_format);
00598 
00599     /* Skip a suffix of any character except % */
00600     while (*p)
00601     {
00602         /* Skip two adjacent percent marks, which are literal percent
00603          * marks */
00604         if (p[0] == '%' && p[1] == '%')
00605         {
00606             p += 2;
00607             continue;
00608         }
00609         /* Break on a single percent mark, which is the start of the
00610          * conversion specification */
00611         if (*p == '%')
00612             return g_strdup_printf("Format string contains unescaped %% signs (or multiple conversion specifications) at '%s'", p);
00613         /* Skip all other characters */
00614         p++;
00615     }
00616 
00617     /* If we end up here, the string was valid, so return no error
00618      * message */
00619     return NULL;
00620 }
00621 
00622 /* Determine whether this book uses trading accounts */
00623 gboolean
00624 qof_book_use_trading_accounts (const QofBook *book)
00625 {
00626     const char *opt;
00627     kvp_value *kvp_val;
00628 
00629     kvp_val = kvp_frame_get_slot_path (qof_book_get_slots (book),
00630                                        KVP_OPTION_PATH,
00631                                        OPTION_SECTION_ACCOUNTS,
00632                                        OPTION_NAME_TRADING_ACCOUNTS,
00633                                        NULL);
00634     if (kvp_val == NULL)
00635         return FALSE;
00636 
00637     opt = kvp_value_get_string (kvp_val);
00638 
00639     if (opt && opt[0] == 't' && opt[1] == 0)
00640         return TRUE;
00641     return FALSE;
00642 }
00643 
00644 gboolean qof_book_uses_autoreadonly (const QofBook *book)
00645 {
00646     g_assert(book);
00647     return (qof_book_get_num_days_autoreadonly(book) != 0);
00648 }
00649 
00650 gint qof_book_get_num_days_autoreadonly (const QofBook *book)
00651 {
00652     kvp_value *kvp_val;
00653     double tmp;
00654     g_assert(book);
00655     kvp_val = kvp_frame_get_slot_path (qof_book_get_slots (book),
00656                                        KVP_OPTION_PATH,
00657                                        OPTION_SECTION_ACCOUNTS,
00658                                        OPTION_NAME_AUTO_READONLY_DAYS,
00659                                        NULL);
00660 
00661     if (kvp_val == NULL)
00662     {
00663         //g_warning("kvp_val for slot '%s' is NULL", OPTION_NAME_AUTO_READONLY_DAYS);
00664         return 0;
00665     }
00666 
00667     tmp = kvp_value_get_double (kvp_val);
00668     return (gint) tmp;
00669 }
00670 
00671 GDate* qof_book_get_autoreadonly_gdate (const QofBook *book)
00672 {
00673     gint num_days;
00674     GDate* result = NULL;
00675 
00676     g_assert(book);
00677     num_days = qof_book_get_num_days_autoreadonly(book);
00678     if (num_days > 0)
00679     {
00680         result = gnc_g_date_new_today();
00681         g_date_subtract_days(result, num_days);
00682     }
00683     return result;
00684 }
00685 
00686 const char*
00687 qof_book_get_string_option(const QofBook* book, const char* opt_name)
00688 {
00689     return kvp_frame_get_string(qof_book_get_slots(book), opt_name);
00690 }
00691 
00692 void
00693 qof_book_set_string_option(QofBook* book, const char* opt_name, const char* opt_val)
00694 {
00695     qof_book_begin_edit(book);
00696     kvp_frame_set_string(qof_book_get_slots(book), opt_name, opt_val);
00697     qof_instance_set_dirty (QOF_INSTANCE (book));
00698     qof_book_commit_edit(book);
00699 }
00700 
00701 void
00702 qof_book_begin_edit (QofBook *book)
00703 {
00704     qof_begin_edit(&book->inst);
00705 }
00706 
00707 static void commit_err (QofInstance *inst, QofBackendError errcode)
00708 {
00709     PERR ("Failed to commit: %d", errcode);
00710 //  gnc_engine_signal_commit_error( errcode );
00711 }
00712 
00713 static void noop (QofInstance *inst) {}
00714 
00715 void
00716 qof_book_commit_edit(QofBook *book)
00717 {
00718     if (!qof_commit_edit (QOF_INSTANCE(book))) return;
00719     qof_commit_edit_part2 (&book->inst, commit_err, noop, noop/*lot_free*/);
00720 }
00721 
00722 /* QofObject function implementation and registration */
00723 gboolean qof_book_register (void)
00724 {
00725     static QofParam params[] =
00726     {
00727         { QOF_PARAM_GUID, QOF_TYPE_GUID, (QofAccessFunc)qof_entity_get_guid, NULL },
00728         { QOF_PARAM_KVP,  QOF_TYPE_KVP,  (QofAccessFunc)qof_instance_get_slots, NULL },
00729         { NULL },
00730     };
00731 
00732     qof_class_register (QOF_ID_BOOK, NULL, params);
00733 
00734     return TRUE;
00735 }
00736 
00737 /* ========================== END OF FILE =============================== */
 All Data Structures Files Functions Variables Typedefs Enumerations Enumerator Defines