|
GnuCash 2.4.99
|
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 =============================== */
1.7.4