Missed some.

git-svn-id: svn+ssh://svn.gnucash.org/repo/gnucash/trunk@5076 57a11ea4-9604-0410-9ed3-97b8803252fd
This commit is contained in:
Dave Peticolas 2001-08-07 23:36:04 +00:00
parent 939f78a1cc
commit 4cc2bfb9a9
63 changed files with 26453 additions and 0 deletions

2163
src/engine/Account.c Normal file

File diff suppressed because it is too large Load Diff

375
src/engine/Account.h Normal file
View File

@ -0,0 +1,375 @@
/********************************************************************\
* Account.h -- Account handling public routines *
* Copyright (C) 1997 Robin D. Clark *
* Copyright (C) 1997-2000 Linas Vepstas <linas@linas.org> *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License*
* along with this program; if not, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
* Boston, MA 02111-1307, USA gnu@gnu.org *
* *
\********************************************************************/
#ifndef XACC_ACCOUNT_H
#define XACC_ACCOUNT_H
#include "config.h"
#include "GNCId.h"
#include "Transaction.h"
#include "kvp_frame.h"
/** PROTOTYPES ******************************************************/
/*
* The account types are used to determine how the transaction data
* in the account is displayed. These values can be safely changed
* from one release to the next. Note that if values are added,
* the file IO translation routines need to be updated. Note
* also that GUI code depends on these numbers.
*
* ***IMPORTANT***: If you do change the enumeration names (not the
* numbers), you need to update xaccAccountTypeEnumAsString --- used
* for text file exports */
typedef enum
{
BAD_TYPE = -1,
NO_TYPE = -1,
/* Not a type */
BANK = 0,
/* The bank account type denotes a savings or checking account
* held at a bank. Often interest bearing.
*/
CASH = 1,
/* The cash account type is used to denote a shoe-box or pillowcase
* stuffed with cash.
*/
CREDIT = 3,
/* The Credit card account is used to denote credit (e.g. amex) and
* debit (e.g. visa, mastercard) card accounts
*/
ASSET = 2,
LIABILITY = 4,
/* asset and liability accounts indicate generic, generalized accounts
* that are none of the above.
*/
STOCK = 5,
MUTUAL= 6,
/* Stock and Mutual Fund accounts will typically be shown in registers
* which show three columns: price, number of shares, and value.
*/
CURRENCY = 7,
/* The currency account type indicates that the account is a
* currency trading account. In many ways, a currency trading
* account is like a stock trading account, where both values
* and share quantities are set.
*/
INCOME = 8,
EXPENSE = 9,
/* Income and expense accounts are used to denote income and expenses. */
EQUITY = 10,
/* Equity account is used to balance the balance sheet. */
NUM_ACCOUNT_TYPES = 11,
/* stop here; the following types just aren't ready for prime time */
/* bank account types */
CHECKING = 11,
SAVINGS = 12,
MONEYMRKT = 13,
CREDITLINE = 14, /* line of credit */
} GNCAccountType;
const char * xaccAccountGetTypeStr (GNCAccountType type); /* GUI names */
/* Conversion routines for the account types to/from strings.
* Critical for the text communication mechanisms. i.e. INCOME ->
* "INCOME". */
char * xaccAccountTypeEnumAsString (GNCAccountType type);
gboolean xaccAccountStringToType (const char* str, GNCAccountType *type);
GNCAccountType xaccAccountStringToEnum (const char* str);
/* Return TRUE if accounts of type parent_type can have accounts
* of type child_type as children. */
gboolean xaccAccountTypesCompatible (GNCAccountType parent_type,
GNCAccountType child_type);
/* Compare two accounts for equality - this is a deep compare. */
gboolean xaccAccountEqual(Account *a, Account* b, gboolean check_guids);
/*
* The xaccAccountBeginEdit() and xaccAccountCommitEdit() subroutines
* provide a two-phase-commit wrapper for account updates.
* They are incompletely implemented.
*
* The xaccAccountDestroy() routine can be used to get rid of an
* account. The account should have been opened for editing
* (by calling xaccAccountBeginEdit()) before calling this routine.
*/
Account * xaccMallocAccount (void);
Account * xaccCloneAccountSimple(const Account *from);
void xaccAccountBeginEdit (Account *account);
void xaccAccountCommitEdit (Account *account);
void xaccAccountDestroy (Account *account);
kvp_frame * xaccAccountGetSlots (Account *account);
void xaccAccountSetSlots_nc(Account *account, kvp_frame *frame);
/*
* The xaccAccountGetGUID() subroutine will return the
* globally unique id associated with that account.
*
* The xaccAccountLookup() subroutine will return the
* account associated with the given id, or NULL
* if there is no such account.
*/
const GUID * xaccAccountGetGUID (Account *account);
Account * xaccAccountLookup (const GUID *guid);
/*
* The xaccAccountInsertSplit() method will insert the indicated
* split into the indicated account. If the split already
* belongs to another account, it will be removed from that
* account first.
*/
void xaccAccountInsertSplit (Account *account, Split *split);
/* The xaccAccountFixSplitDateOrder() subroutine checks to see if
* a split is in proper sorted date order with respect
* to the other splits in this account.
*
* The xaccTransFixSplitDateOrder() checks to see if
* all of the splits in this transaction are in
* proper date order.
*/
void xaccAccountFixSplitDateOrder (Account *account, Split *split);
void xaccTransFixSplitDateOrder (Transaction *trans);
/* The xaccAccountOrder() subroutine defines a sorting order
* on accounts. It takes pointers to two accounts, and
* returns -1 if the first account is "less than" the second,
* returns +1 if the first is "greater than" the second, and
* 0 if they are equal. To determine the sort order, first
* the account codes are compared, and if these are equal, then
* account types, and, if these are equal, the account names.
*/
int xaccAccountOrder (Account **account_1, Account **account_2);
void xaccAccountSetType (Account *account, int);
void xaccAccountSetName (Account *account, const char *name);
void xaccAccountSetCode (Account *account, const char *code);
void xaccAccountSetDescription (Account *account, const char *desc);
void xaccAccountSetNotes (Account *account, const char *notes);
GNCAccountType xaccAccountGetType (Account *account);
const char * xaccAccountGetName (Account *account);
const char * xaccAccountGetCode (Account *account);
const char * xaccAccountGetDescription (Account *account);
const char * xaccAccountGetNotes (Account *account);
/* New commodity access routines.
*
* The account structure no longer stores two commodities ('currency'
* and 'security'). Instead it stores only one commodity, that is the
* one formerly known as 'security'. Use xaccAccountSetCommodity()
* and xaccAccountGetCommodity() to set and fetch it.
*
* Basically, the engine eliminates the 'currency' field of the
* Account structure. Instead, the common currency is stored with the
* transaction. The 'value' of a split is a translation of the
* Split's 'amount' (which is the amount of the Account's commodity
* involved) into the Transaction's balancing currency. */
void xaccAccountSetCommodity (Account *account, gnc_commodity *comm);
gnc_commodity * xaccAccountGetCommodity (Account *account);
int xaccAccountGetCommoditySCU (Account *account);
void xaccAccountSetCommoditySCU (Account *account, int frac);
/* Deprecated currency/security access routines.
* The current API associates only one thing with an account:
* the 'commodity'. Use xaccAccountGetCommodity() to fetch it.
*/
/* these two funcs take control of their gnc_commodity args. Don't free */
void DxaccAccountSetCurrency (Account *account, gnc_commodity *currency);
void DxaccAccountSetSecurity (Account *account, gnc_commodity *security);
gnc_commodity * DxaccAccountGetCurrency (Account *account);
gnc_commodity * DxaccAccountGetSecurity (Account *account);
void DxaccAccountSetCurrencySCU (Account *account, int frac);
int DxaccAccountGetCurrencySCU (Account *account);
/* Delete any old data in the account's kvp data.
* This includes the old currency and security fields. */
void xaccAccountDeleteOldData (Account *account);
AccountGroup * xaccAccountGetChildren (Account *account);
AccountGroup * xaccAccountGetParent (Account *account);
Account * xaccAccountGetParentAccount (Account *account);
gnc_numeric xaccAccountGetBalance (Account *account);
gnc_numeric xaccAccountGetClearedBalance (Account *account);
gnc_numeric xaccAccountGetReconciledBalance (Account *account);
gnc_numeric xaccAccountGetBalanceAsOfDate (Account *account, time_t date);
GList* xaccAccountGetSplitList (Account *account);
gboolean xaccAccountGetTaxRelated (Account *account);
void xaccAccountSetTaxRelated (Account *account,
gboolean tax_related);
const char * xaccAccountGetTaxUSCode (Account *account);
void xaccAccountSetTaxUSCode (Account *account, const char *code);
const char * xaccAccountGetTaxUSPayerNameSource (Account *account);
void xaccAccountSetTaxUSPayerNameSource (Account *account,
const char *source);
/* The xaccAccountGetFullName routine returns the fully qualified name
* of the account using the given separator char. The name must be freed
* after use. The fully qualified name of an account is the concatenation
* of the names of the account and all its ancestor accounts starting with
* the topmost account and ending with the given account. Each name is
* separated by the given character.
*
* WAKE UP!
* Unlike all other gets, the string returned by xaccAccountGetFullName()
* must be freed by you the user !!!
* hack alert -- since it breaks the rule of string allocation, maybe this
* routine should not be in this library, but some utility library?
*/
char * xaccAccountGetFullName (Account *account, const char separator);
/* Returns true if the account has 'ancestor' as an ancestor.
* Returns false if either is NULL. */
gboolean xaccAccountHasAncestor (Account *account, Account *ancestor);
/* Get and Set a mark on the account. The meaning of this mark is
* completely undefined. Its presented here as a utility for the
* programmer, to use as desired. Handy for performing customer traversals
* over the account tree. The mark is *not* stored in the database/file
* format. When accounts are newly created, the mark is set to zero.
*
* The xaccClearMark will find the topmost group, and clear the mark in
* the entire group tree.
* The xaccClearMarkDown will clear the mark only in this and in
* sub-accounts.
*/
short xaccAccountGetMark (Account *account);
void xaccAccountSetMark (Account *account, short mark);
void xaccClearMark (Account *account, short val);
void xaccClearMarkDown (Account *account, short val);
void xaccClearMarkDownGr (AccountGroup *group, short val);
/* The following functions get and set reconciliation information */
gboolean xaccAccountGetReconcileLastDate (Account *account,
time_t *last_date);
void xaccAccountSetReconcileLastDate (Account *account,
time_t last_date);
gboolean xaccAccountGetReconcilePostponeDate (Account *account,
time_t *postpone_date);
void xaccAccountSetReconcilePostponeDate (Account *account,
time_t postpone_date);
gboolean xaccAccountGetReconcilePostponeBalance (Account *account,
gnc_numeric *balance);
void xaccAccountSetReconcilePostponeBalance (Account *account,
gnc_numeric balance);
void xaccAccountClearReconcilePostpone (Account *account);
gboolean xaccAccountGetAutoInterestXfer (Account *account, gboolean default_value);
void xaccAccountSetAutoInterestXfer (Account *account, gboolean option);
/* Get and set the last num field of an Account */
const char * xaccAccountGetLastNum (Account *account);
void xaccAccountSetLastNum (Account *account, const char *num);
/* The xaccAccountSetPriceSrc() and xaccAccountGetPriceSrc() routines
are used to get and set a string that identifies the Finance::Quote
backend that should be used to retrieve online prices. See
price-quotes.scm for more information.
xaccAccountGetQuoteTZ() and xaccAccountSetQuoteTZ() set the
timezone to be used when interpreting the results from a given
Finance::Quote backend. Unfortunately, the upstream sources don't
label their output, so the user has to specify this bit.
Since prices are not going to be stored in the accounts in the
future, and since the whole commodities infrastructure is changing
radically as we speak, this interface is not long for this
world. */
void xaccAccountSetPriceSrc (Account *account, const char *src);
const char * xaccAccountGetPriceSrc (Account *account);
void xaccAccountSetQuoteTZ (Account *account, const char *tz);
const char * xaccAccountGetQuoteTZ (Account *account);
typedef gpointer (*SplitCallback)(Split *s, gpointer data);
gpointer xaccAccountForEachSplit(Account *account,
SplitCallback,
gpointer data);
/* Traverse all of the transactions in the given account. Continue
processing IFF proc does not return FALSE. This function does not
descend recursively to traverse transactions in child accounts.
Proc will be called exactly once for each transaction that is
pointed to by at least one split in the given account.
Note too, that if you call this function on two separate accounts
and those accounts share transactions, proc will be called once per
account for the shared transactions.
The result of this function will not be FALSE IFF every relevant
transaction was traversed exactly once. */
typedef gboolean (*TransactionCallback)(Transaction *t, void *data);
gboolean
xaccAccountForEachTransaction(Account *account,
TransactionCallback,
void *data);
/* Visit every transaction in the account that hasn't already been
visited exactly once. visited_txns must be a hash table created
via guid_hash_table_new() and is the authority about which
transactions have already been visited. Further, when this
procedure returns visited_txns will have been modified to reflect
all the newly visited transactions.
The result of this function will not be FALSE IFF every relevant
transaction was traversed exactly once. */
gboolean
xaccAccountVisitUnvisitedTransactions(Account *account,
TransactionCallback,
void *data,
GHashTable *visited_txns);
/* Returns a pointer to the transaction, not a copy. */
Transaction *
xaccAccountFindTransByDesc(Account *account, const char *description);
Split *
xaccAccountFindSplitByDesc(Account *account, const char *description);
#endif /* XACC_ACCOUNT_H */

207
src/engine/AccountP.h Normal file
View File

@ -0,0 +1,207 @@
/********************************************************************\
* AccountP.h -- Account engine-private data structure *
* Copyright (C) 1997 Robin D. Clark *
* Copyright (C) 1997-2000, Linas Vepstas <linas@linas.org> *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License*
* along with this program; if not, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
* Boston, MA 02111-1307, USA gnu@gnu.org *
* *
\********************************************************************/
/*
* FILE:
* AccountP.h
*
* FUNCTION:
* This is the *private* header for the account structure.
* No one outside of the engine should ever include this file.
*
* This header includes prototypes for "dangerous" functions.
* Invoking any of these functions potentially leave the account
* in an inconsistent state. If they are not used in the proper
* setting, they can leave the account structures in an inconsistent
* state. Thus, these methods should never be used outside of
* the engine, which is why they are "hidden" here.
*
*/
#ifndef XACC_ACCOUNT_P_H
#define XACC_ACCOUNT_P_H
#include "config.h"
#include "Account.h"
#include "GNCId.h"
#include "Transaction.h"
#include "gnc-commodity.h"
#include "gnc-numeric.h"
#include "kvp_frame.h"
/** STRUCTS *********************************************************/
struct _account {
/* public data, describes account */
GUID guid; /* globally unique account id */
/* The accountName is an arbitrary string assigned by the user.
* It is intended to a short, 5 to 30 character long string that
* is displayed by the GUI as the account mnemonic.
*/
char *accountName;
/* The accountCode is an arbitrary string assigned by the user.
* It is intended to be reporting code that is a synonym for the
* accountName. Typically, it will be a numeric value that follows
* the numbering assignments commonly used by accountants, such
* as 100, 200 or 600 for top-level * accounts, and 101, 102.. etc.
* for detail accounts.
*/
char *accountCode;
/* The description is an arbitrary string assigned by the user.
* It is intended to be a longer, 1-5 sentence description of what
* this account is all about.
*/
char *description;
/* kvp_data is a key-value pair database for storing simple "extra"
* information in splits, transactions, and accounts. it's NULL
* until accessed. See ??? for a list and description of the
* important keys. */
kvp_frame * kvp_data;
/* The type field is the account type, picked from the enumerated
* list that includes BANK, STOCK, CREDIT, INCOME, etc. Its
* intended use is to be a hint to the GUI as to how to display
* and format the transaction data.
*/
GNCAccountType type;
/* Old semantics: The currency field denotes the default currency in
* which all splits in this account are denominated. Currency
* trading accounts allow splits between accounts when the currency
* string matches the security string.
*
* The gnc_commodity type represents the namespace, full name, and
* symbol for the currency.
*
* New semantics: The account structure will no longer store a
* 'currency' and a 'security'. Instead it will store only one
* commodity (i.e. currency), that is the one formerly known as
* 'security'. The 'amount' of each split represents the
* transferred amount in the account's commodity (formerly known as
* security).
*/
gnc_commodity * commodity;
int commodity_scu;
/* The parent and children pointers are used to implement an account
* hierarchy, of accounts that have sub-accounts ("detail accounts").
*/
AccountGroup *parent; /* back-pointer to parent */
AccountGroup *children; /* pointer to sub-accounts */
/* protected data, cached parameters */
gnc_numeric starting_balance;
gnc_numeric starting_cleared_balance;
gnc_numeric starting_reconciled_balance;
gnc_numeric balance;
gnc_numeric cleared_balance;
gnc_numeric reconciled_balance;
/* version number, used for tracking multiuser updates */
gint32 version;
guint32 version_check; /* data aging timestamp */
GList *splits; /* list of split pointers */
/* keep track of nesting level of begin/end edit calls */
gint32 editlevel;
gboolean balance_dirty; /* balances in splits incorrect */
gboolean sort_dirty; /* sort order of splits is bad */
gboolean core_dirty; /* fields in this struct have changed */
gboolean do_free; /* in process of being destroyed */
/* The "mark" flag can be used by the user to mark this account
* in any way desired. Handy for specialty traversals of the
* account tree. */
short mark;
/* -------------------------------------------------------------- */
/* Backend private expansion data */
guint32 idata; /* used by the sql backend for kvp management */
};
/* The xaccAccountRemoveSplit() routine will remove the indicated split
* from the indicated account. Note that this will leave the split
* "dangling", i.e. unassigned to any account, and therefore will put
* the engine into an inconsistent state. After removing a split,
* it should be immediately destroyed, or it should be inserted into
* an account.
*/
void xaccAccountRemoveSplit (Account *, Split *);
/* the following recompute the partial balances (stored with the
* transaction) and the total balance, for this account */
void xaccAccountRecomputeBalance (Account *);
/* Set the account's GUID. This should only be done when reading
* an account from a datafile, or some other external source. Never
* call this on an existing account! */
void xaccAccountSetGUID (Account *account, const GUID *guid);
/* The xaccAccountSetStartingBalance() routine will set the starting
* commodity balance for this account. This routine is intended for
* use with backends that do not return the complete list of splits
* for an account, but rather return a partial list. In such a case,
* the backend will typically return all of the splits after some
* certain date, and the 'starting balance' will represent the summation
* of the splits up to that date.
*
* Design Note: this routine assumes that there is only one commodity
* associated with this account, and that the reporting currency will
* no longer be stored with the account.
*
* This routine is in the private .h file because only backends are
* allowed to set the starting balance. This is *not* a user interface
* function.
*/
void xaccAccountSetStartingBalance(Account *account,
const gnc_numeric start_baln,
const gnc_numeric start_cleared_baln,
const gnc_numeric start_reconciled_baln);
/* The xaccFreeAccount() routine releases memory associated with the
* account. It should never be called directly from user code;
* instead, the xaccAccountDestroy() routine should be used
* (because xaccAccountDestroy() has the correct commit semantics).
*/
void xaccFreeAccount (Account *account);
/* The xaccAccountSet/GetVersion() routines set & get the version
* numbers on this account. The version number is used to manage
* multi-user updates. These routines are private because we don't
* want anyone except the backend to mess with them.
*/
void xaccAccountSetVersion (Account*, gint32);
gint32 xaccAccountGetVersion (Account*);
#endif /* XACC_ACCOUNT_P_H */

207
src/engine/Backend.c Normal file
View File

@ -0,0 +1,207 @@
/********************************************************************\
* Backend.c -- utility routines for dealing with the data backend *
* Copyright (C) 2000 Linas Vepstas <linas@linas.org> *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License*
* along with this program; if not, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
* Boston, MA 02111-1307, USA gnu@gnu.org *
* *
\********************************************************************/
#include <glib.h>
#include "Account.h"
#include "AccountP.h"
#include "BackendP.h"
#include "Group.h"
#include "GroupP.h"
#include "gnc-engine-util.h"
#include "gnc-pricedb.h"
#include "gnc-pricedb-p.h"
#include "TransactionP.h"
/* static short module = MOD_ENGINE; */
/********************************************************************\
* error handling *
\********************************************************************/
void
xaccBackendSetError (Backend *be, GNCBackendError err)
{
if (!be) return;
/* use stack-push semantics. Only the earliest error counts */
if (ERR_BACKEND_NO_ERR != be->last_err) return;
be->last_err = err;
}
GNCBackendError
xaccBackendGetError (Backend *be)
{
GNCBackendError err;
if (!be) return ERR_BACKEND_NO_BACKEND;
/* use 'stack-pop' semantics */
err = be->last_err;
be->last_err = ERR_BACKEND_NO_ERR;
return err;
}
/********************************************************************\
* Fetch the backend *
\********************************************************************/
Backend *
xaccAccountGetBackend (Account * acc)
{
Account *parent_acc;
AccountGroup * grp;
if (!acc) return NULL;
/* find the first account group that has a backend */
grp = acc->parent;
while (grp) {
if (grp->backend) return (grp->backend);
parent_acc = grp -> parent;
grp = NULL;
if (parent_acc) {
grp = parent_acc->parent;
}
}
return NULL;
}
/********************************************************************\
* Fetch the backend *
\********************************************************************/
Backend *
xaccTransactionGetBackend (Transaction *trans)
{
GList *snode, *node;
Split *s=NULL;
if (!trans) return NULL;
/* find an account */
snode = xaccTransGetSplitList(trans);
for (node = snode; node; node=node->next)
{
s = node->data;
if (xaccSplitGetAccount(s)) break;
s = NULL;
}
/* if transaction is being deleted, it won't have any splits
* so lets take a look at the 'original' transaction */
if (!s)
{
snode = xaccTransGetSplitList(trans->orig);
for (node = snode; node; node=node->next)
{
s = node->data;
if (xaccSplitGetAccount(s)) break;
s = NULL;
}
}
if (!s) return NULL;
/* I suppose it would be more 'technically correct' to make sure that
* all splits share the same backend, and flag an error if they
* don't. However, at this point, it seems quite unlikely, so we'll
* just use the first backend we find.
*/
return xaccAccountGetBackend (xaccSplitGetAccount(s));
}
/********************************************************************\
* Set the backend *
\********************************************************************/
void
xaccGroupSetBackend (AccountGroup *grp, Backend *be)
{
if (!grp) return;
grp->backend = be;
}
Backend *
xaccGroupGetBackend (AccountGroup *grp)
{
while (grp)
{
Account *parent;
if (grp->backend) return (grp->backend);
parent = grp->parent;
if (!parent) return NULL;
grp = parent->parent;
}
return NULL;
}
/********************************************************************\
* Set the backend *
\********************************************************************/
void
xaccPriceDBSetBackend (GNCPriceDB *prdb, Backend *be)
{
if (!prdb) return;
prdb->backend = be;
}
Backend *
xaccPriceDBGetBackend (GNCPriceDB *prdb)
{
if (!prdb) return NULL;
return prdb->backend;
}
/***********************************************************************/
/* Get a clean backend */
void
xaccInitBackend(Backend *be)
{
be->book_begin = NULL;
be->book_load = NULL;
be->price_load = NULL;
be->book_end = NULL;
be->destroy_backend = NULL;
be->account_begin_edit = NULL;
be->account_commit_edit = NULL;
be->trans_begin_edit = NULL;
be->trans_commit_edit = NULL;
be->trans_rollback_edit = NULL;
be->price_begin_edit = NULL;
be->price_commit_edit = NULL;
be->run_query = NULL;
be->price_lookup = NULL;
be->all_sync = NULL;
be->sync = NULL;
be->sync_price = NULL;
be->events_pending = NULL;
be->process_events = NULL;
be->last_err = ERR_BACKEND_NO_ERR;
}
/************************* END OF FILE ********************************/

96
src/engine/Backend.h Normal file
View File

@ -0,0 +1,96 @@
/********************************************************************\
* Backend.h -- api for engine Backend *
* *
* Copyright (c) 2000, 2001 Linas Vepstas <linas@linas.org> *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License*
* along with this program; if not, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
* Boston, MA 02111-1307, USA gnu@gnu.org *
* *
\********************************************************************/
/*
* FILE:
* Backend.h
*
* FUNCTION:
* The 'backend' is a pseudo-object providing an interface between the
* engine and a persistant data store (e.g. a server, a database, or
* a file). There are no backend functions that are 'public' to
* users of the engine. The backend can, however, report errors to
* the GUI & other front-end users. This file defines these errors.
*/
#ifndef XACC_BACKEND_H
#define XACC_BACKEND_H
#include "config.h"
/* NOTE: if you modify GNCBackendError, please update src/scm/gnc.gwp */
typedef enum {
ERR_BACKEND_NO_ERR = 0,
ERR_BACKEND_NO_BACKEND, /* Backend * pointer was null the err routine */
/* or no backend handler (ENOSYS) */
ERR_BACKEND_BAD_URL, /* Can't parse url */
ERR_BACKEND_NO_SUCH_DB, /* the named database doesn't exist */
ERR_BACKEND_CANT_CONNECT, /* bad dbname/login/passwd or network failure */
ERR_BACKEND_CONN_LOST, /* Lost connection to server */
ERR_BACKEND_LOCKED, /* in use by another user (ETXTBSY) */
ERR_BACKEND_TOO_NEW, /* file/db version newer than what we can read */
ERR_BACKEND_DATA_CORRUPT, /* data in db is corrupt */
ERR_BACKEND_SERVER_ERR, /* error in response from server */
ERR_BACKEND_ALLOC, /* internal memory allocation failure */
ERR_BACKEND_PERM, /* user login successful, but no permissions
* to access the desired object */
ERR_BACKEND_MODIFIED, /* commit of object update failed because
* another user has modified the object */
ERR_BACKEND_MOD_DESTROY, /* commit of object update failed because
* another user has deleted the object */
ERR_BACKEND_MISC, /* undetermined error */
/* fileio errors */
ERR_FILEIO_FILE_BAD_READ = 1000, /* read failed or file prematurely truncated */
ERR_FILEIO_FILE_EMPTY, /* file exists, is readable, but is empty */
ERR_FILEIO_FILE_LOCKERR, /* mangled locks (unspecified error) */
ERR_FILEIO_FILE_NOT_FOUND, /* not found / no such file */
ERR_FILEIO_FILE_TOO_OLD, /* file version so old we can't read it */
ERR_FILEIO_UNKNOWN_FILE_TYPE,
/* network errors */
ERR_NETIO_SHORT_READ = 2000, /* not enough bytes received */
ERR_NETIO_WRONG_CONTENT_TYPE, /* wrong kind of server, wrong data served */
ERR_NETIO_NOT_GNCXML, /* whatever it is, we can't parse it. */
/* database errors */
ERR_SQL_MISSING_DATA = 3000, /* database doesn't contain expected data */
ERR_SQL_DB_TOO_OLD, /* database is old and needs upgrading */
ERR_SQL_DB_BUSY, /* database is busy, cannot upgrade version */
/* RPC errors */
ERR_RPC_HOST_UNK = 4000, /* Host unknown */
ERR_RPC_CANT_BIND, /* can't bind to address */
ERR_RPC_CANT_ACCEPT, /* can't accept connection */
ERR_RPC_NO_CONNECTION, /* no connection to server */
ERR_RPC_BAD_VERSION, /* RPC Version Mismatch */
ERR_RPC_FAILED, /* Operation failed */
ERR_RPC_NOT_ADDED, /* object not added */
} GNCBackendError;
/* NOTE: if you modify GNCBackendError, please update src/scm/gnc.gwp */
typedef struct _backend Backend;
#endif /* XACC_BACKEND_H */

230
src/engine/BackendP.h Normal file
View File

@ -0,0 +1,230 @@
/********************************************************************\
* Backend.h -- private api for engine Backend *
* *
* Copyright (c) 2000, 2001 Linas Vepstas <linas@linas.org> *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License*
* along with this program; if not, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
* Boston, MA 02111-1307, USA gnu@gnu.org *
* *
\********************************************************************/
/*
* FILE:
* BackendP.h
*
* FUNCTION:
* Pseudo-object defining how the engine can interact with different
* back-ends (which may be SQL databases, or network interfaces to
* remote GnuCash servers. In theory, file-io should be a type of
* backend).
*
* The callbacks will be called at the appropriate times during
* a book session to allow the backend to store the data as needed.
*
*/
#ifndef XACC_BACKEND_P_H
#define XACC_BACKEND_P_H
#include "config.h"
#include "Account.h"
#include "Backend.h"
#include "Group.h"
#include "Query.h"
#include "Transaction.h"
#include "gnc-book.h"
#include "gnc-pricedb.h"
/*
* The book_begin() routine gives the backend a second initialization
* opportunity. It is suggested that the backend check that
* the URL is syntactically correct, and that it is actually
* reachable. This is probably(?) a good time to initialize
* the actual network connection.
*
* The 'ignore_lock' argument indicates whether the single-user
* lock on the backend should be cleared. The typical GUI sequence
* leading to this is: (1) GUI attempts to open the backend
* by calling this routine with FALSE==ignore_lock. (2) If backend
* error'ed BACKEND_LOCK, then GUI asks user what to do. (3) if user
* answers 'break & enter' then this routine is called again with
* TRUE==ignore_lock.
*
* The 'create_if_nonexistent' argument indicates whether this
* routine should create a new 'database', if it doesn't already
* exist. For example, for a file-backend, this would create the
* file, if it didn't already exist. For an SQL backend, this
* would create the database (the schema) if it didn't already
* exist. This flag is used to implement the 'SaveAs' GUI, where
* the user requests to save data to a new backend.
*
* The book_load() routine should return at least an account tree,
* and all currencies. It does not have to return any transactions
* whatsoever, as these are obtained at a later stage when a user
* opens a register, resulting in a query being sent to the backend.
*
* (Its OK to send over transactions at this point, but one should
* be careful of the network load; also, its possible that whatever
* is sent is not what the user wanted anyway, which is why its
* better to wait for the query).
*
* The trans_commit_edit() routine takes two transaction arguments:
* the first is the proposed new transaction; the second is the
* 'original' transaction. The second argument is here for
* convenience; it had better be substantially equivalent to
* the argument for the trans_begin_edit() callback. (It doesn't
* have to be identical, it can be a clone).
*
* The trans_rollback_edit() routine is invoked in one of two different
* ways. In one case, the user may hit 'undo' in the GUI, resulting
* in xaccTransRollback() being called, which in turn calls this
* routine. In this manner, xaccTransRollback() implements a
* single-level undo convenience routine for the GUI. The other
* way in which this routine gets invoked involves conflicting
* edits by two users to the same transaction. The second user
* to make an edit will typically fail in trans_commit_edit(),
* with trans_commit_edit() returning an error code. This
* causes xaccTransCommitEdit() to call xaccTransRollback()
* which in turn calls this routine. Thus, this routine
* gives the backend a chance to clean up failed commits.
*
* If the second user tries to modify a transaction that
* the first user deleted, then the backend should set the error
* to ERR_BACKEND_MOD_DESTROY from this routine, so that the
* engine can properly clean up.
*
* The run_query() callback takes a GnuCash query object.
* For an SQL backend, the contents of the query object need to
* be turned into a corresponding SQL query statement, and sent
* to the database for evaluation. The database will return a
* set of splits and transactions, and this callback needs
* to poke these into the account-group hierarchy held by the
* query object.
*
* For a network-communications backend, essentially the same is
* done, except that this routine would convert the query to wire
* protocol, get an answer from the remote server, and push that
* into the account-group object.
*
* Note a peculiar design decision we've used here. The query
* callback has returned a list of splits; these could be returned
* directly to the caller. They are not. By poking them into the
* existing account hierarchy, we are essentially building a local
* cache of the split data. This will allow the GnuCash client to
* continue functioning even when disconnected from the server:
* this is because it will have its local cache of data to work from.
*
* The sync() routine synchronizes the engine contents to the backend.
* This is done by using version numbers (hack alert -- the engine
* does not currently contain version numbers).
* If the engine contents are newer than what's in the backend, the
* data is stored to the backend. If the engine contents are older,
* then the engine contents are updated.
*
* Note that this sync operation is only meant to apply to the
* current contents of the engine. This routine is not intended
* to be used to fetch account/transaction data from the backend.
* (It might pull new splits from the backend, if this is what is
* needed to update an existing transaction. It might pull new
* currencies (??))
*
* The events_pending() routines should return true if there are
* external events which need to be processed to bring the
* engine up to date with the backend.
*
* The process_events() routine should process any events indicated
* by the events_pending() routine. It should return TRUE if
* the engine was changed while engine events were suspended.
*
* The last_err member indicates the last error that occurred.
* It should probably be implemented as an array (actually,
* a stack) of all the errors that have occurred.
*/
struct _backend
{
void (*book_begin) (Backend *be, GNCBook *book, const char *book_id,
gboolean ignore_lock, gboolean create_if_nonexistent);
AccountGroup * (*book_load) (Backend *);
GNCPriceDB * (*price_load) (Backend *);
void (*book_end) (Backend *);
void (*destroy_backend) (Backend *);
void (*account_begin_edit) (Backend *, Account *);
void (*account_commit_edit) (Backend *, Account *);
void (*trans_begin_edit) (Backend *, Transaction *);
void (*trans_commit_edit) (Backend *, Transaction *new, Transaction *orig);
void (*trans_rollback_edit) (Backend *, Transaction *);
void (*price_begin_edit) (Backend *, GNCPrice *);
void (*price_commit_edit) (Backend *, GNCPrice *);
void (*run_query) (Backend *, Query *);
void (*price_lookup) (Backend *, GNCPriceLookup *);
void (*all_sync) (Backend *, AccountGroup *, GNCPriceDB *);
void (*sync) (Backend *, AccountGroup *);
void (*sync_price) (Backend *, GNCPriceDB *);
gboolean (*events_pending) (Backend *be);
gboolean (*process_events) (Backend *be);
GNCBackendError last_err;
};
/*
* The xaccBackendSetError() routine pushes an error code onto the error
* stack. (FIXME: the stack is 1 deep in current implementation).
*
* The xaccBackendGetError() routine pops an error code off the error
* stack.
*/
void xaccBackendSetError (Backend *be, GNCBackendError err);
GNCBackendError xaccBackendGetError (Backend *be);
/*
* The xaccGetAccountBackend() subroutine will find the
* persistent-data storage backend associated with this account.
* This routine traverses up the account hierarchy until it
* finds and account-group node that has a backend associated with
* it. The assumption is that all accounts in that account-group
* share a common back-end.
*
* The xaccGetTransactionBackend() subroutine does the same, for a given
* transaction.
*/
Backend * xaccAccountGetBackend (Account *account);
Backend * xaccTransactionGetBackend (Transaction *trans);
/*
* The xaccGroupSetBackend() associates a backend to a group
*/
void xaccGroupSetBackend (AccountGroup *group, Backend *be);
Backend * xaccGroupGetBackend (AccountGroup *group);
Backend * xaccGNCBookGetBackend (GNCBook *book);
/*
* Put a link to the backend that handles this pricedb
*/
void xaccPriceDBSetBackend (GNCPriceDB *prdb, Backend *be);
Backend * xaccPriceDBGetBackend (GNCPriceDB *prdb);
void xaccInitBackend(Backend *be);
#endif /* XACC_BACKEND_P_H */

71
src/engine/DateUtils.c Normal file
View File

@ -0,0 +1,71 @@
/********************************************************************\
* DateUtils.c -- Date Handling Utilities *
* Copyright (C) 1998 Linas Vepstas *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License*
* along with this program; if not, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
* Boston, MA 02111-1307, USA gnu@gnu.org *
* *
\********************************************************************/
#include "config.h"
#include <glib.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "DateUtils.h"
#define BUFFSIZE 100
/* ======================================================== */
char *
xaccDateUtilGetStamp (time_t thyme)
{
struct tm *stm;
char buf[BUFFSIZE];
char * retval;
stm = localtime (&thyme);
sprintf (buf, "%04d%02d%02d%02d%02d%02d",
(stm->tm_year + 1900),
(stm->tm_mon +1),
stm->tm_mday,
stm->tm_hour,
stm->tm_min,
stm->tm_sec
);
retval = g_strdup (buf);
return retval;
}
/* ======================================================== */
char *
xaccDateUtilGetStampNow (void)
{
time_t now;
time (&now);
return xaccDateUtilGetStamp (now);
}
/************************ END OF ************************************\
\************************* FILE *************************************/

37
src/engine/DateUtils.h Normal file
View File

@ -0,0 +1,37 @@
/********************************************************************\
* DateUtils.h -- Date Handling Utilities *
* Copyright (C) 1998 Linas Vepstas *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License*
* along with this program; if not, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
* Boston, MA 02111-1307, USA gnu@gnu.org *
* *
\********************************************************************/
#ifndef XACC_DATE_UTILS_H
#define XACC_DATE_UTILS_H
#include <time.h>
#include "config.h"
char * xaccDateUtilGetStamp (time_t thyme);
char * xaccDateUtilGetStampNow (void);
#endif /* XACC_DATE_UTILS_H */
/************************ END OF ************************************\
\************************* FILE *************************************/

878
src/engine/FreqSpec.c Normal file
View File

@ -0,0 +1,878 @@
/********************************************************************\
* FreqSpec.c -- Frequency specifier implementation. *
* Copyright (C) 2001 Joshua Sled <jsled@asynchronous.org> *
* Copyright (C) 2001 Ben Stanley <bds02@uow.edu.au> *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License*
* along with this program; if not, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
* Boston, MA 02111-1307, USA gnu@gnu.org *
* *
\********************************************************************/
/********************************************************************\
Current status
All kinds of repeats work, including composites. This is tested -
although composites need some more test cases to be put into the test
suite - ../test/test-freq-spec.c
FreqSpec objects are currently 'set' to give them the information
they need. Separate methods for modifying currently existing FreqSpec
objects are not provided. In the case of composites, you may add FreqSpec
objects to a composite, and you may access a list of the FreqSpec objects
which form the composite. This interface allows you to do damage...
TODO list
Ben Stanley 2001-04-02
* Write xaccFreqSpecGetFreqStr (I wonder how this will be
internationalised... I suspect that this code will need to be
re-written for each language, because the code will have to
generate grammar... It's more than just translating strings.)
However, the first priority is to write one that works for
English.
* Write a function to allow you to query whether a given
date forms part of the recurrence.
* Write a method to get the previous recurrence
* provide XML Load/Save functionality for this object.
* Figure out xaccFreqSpecIsValidDate - I suspect that this is the
'query whether a given date forms part of the recurrence'
above.
* FIGURE OUT WHAT'S GOING ON WITH xaccFreqSpecGetUIType AND
xaccFreqSpecSetUIType.
* Try to reduce the size of the data structure. There are quite a few
32 bit fields which could be stored in about 8 bits.
* Add public methods to allow for recurrences with an interval
of 1 to be set without reference to an initial 'date' - monthly
things in particular. Try to reduce the dependence on an initial
date for the input to set up the recurrence.
Questions:
Is it best that the public interface stay as GDate, or should it
really be a timespec? I have no problem with converting GDates to
timespecs for load/save if that makes life easier.
However, I chose to use GDate internally because I have used a *lot*
of the date calculating ability of GDate in the internal implementation.
GDate has simplified this work enormously compared to using struct tm
and time_t. The engine's timespec object doesn't appear to have the
required functionality either, so I would need to write the required
functions for timespec (perhaps by implementing in terms of GDate?).
Hopefully it's not too painful to leave GDate in the public interface
and change other code to use it.
\********************************************************************/
#include "config.h"
/* #include <time.h> // should be config'd */
#include <glib.h>
#include <string.h>
#include "FreqSpecP.h"
#include "GNCIdP.h"
/*#include "Transaction.h"*/
/*#include "TransactionP.h"*/
#include "date.h"
#include "gnc-engine-util.h"
#include "gnc-event-p.h"
#include "messages.h"
/* I have done this to prevent compiler warnings...
* This is used to convert a const GDate* to a GDate* for passing
* to the glib g_date_xxx functions which don't use const...
* Strangely, most of the rest of glib does use const, so
* perhaps this will change? When it does, just define this macro to
* nothing and the compiler will check the constness of each pointer....
*/
#define CONST_HACK (GDate*)
static short module = MOD_SX;
/** PROTOTYPES ******************************************************/
/**
* Destroys all sub-FreqSpecs in a composite FreqSpec.
* Assertion error if it's not a COMPOSITE FreqSpec.
**/
void xaccFreqSpecCompositesClear( FreqSpec *fs );
void subSpecsListMapDelete( gpointer data, gpointer user_data );
/** Local data defs *****/
/**
* The number of days in each month.
**/
struct monthDesc {
char *dshort;
char *dlong;
gint numDays;
};
/* This stuff is going to need i18n.
* wouldn't it be simpler to use the system
* date conversion functions?
* glib already knows about this.
* *libc* already knows about this
* and the month names below. Both
* of these should go away! */
static char *weekDayNames[] = {
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday"
};
/* hmmm... glib already knows all about this. */
struct monthDesc monthInfo[] = {
#define M_JAN 0
{ "Jan", "January", 31 },
#define M_FEB 1
{ "Feb", "February", 28 },
#define M_MAR 2
{ "Mar", "March", 31 },
#define M_APR 3
{ "Apr", "April", 30 },
#define M_MAY 4
{ "May", "May", 31 },
#define M_JUN 5
{ "Jun", "June", 30 },
#define M_JUL 6
{ "Jul", "July", 31 },
#define M_AUG 7
{ "Aug", "August", 31 },
#define M_SEP 8
{ "Sep", "September", 30 },
#define M_OCT 9
{ "Oct", "October", 31 },
#define M_NOV 10
{ "Nov", "November", 30 },
#define M_DEC 11
{ "Dec", "December", 31 }
};
/** Local Prototypes *****/
static void xaccFreqSpecInit( FreqSpec *fs );
/**
* Initializes a FreqSpec by setting it's to type INVALID.
* Use this to initialise a stack object.
* FreqSpec objects must be initalised before being used by
* any other method.
**/
static void
xaccFreqSpecInit( FreqSpec *fs )
{
g_return_if_fail( fs );
xaccGUIDNew( &fs->guid );
xaccStoreEntity( fs, &fs->guid, GNC_ID_FREQSPEC );
fs->type = INVALID;
fs->uift = UIFREQ_ONCE;
memset( &(fs->s), 0, sizeof(fs->s) );
}
FreqSpec*
xaccFreqSpecMalloc(void)
{
FreqSpec *fs = g_new0(FreqSpec, 1);
xaccFreqSpecInit( fs );
/* FIXME:event */
gnc_engine_generate_event( &fs->guid, GNC_EVENT_CREATE );
return fs;
}
void
xaccFreqSpecCleanUp( FreqSpec *fs )
{
g_return_if_fail( fs );
switch ( fs->type ) {
case INVALID:
case ONCE:
case DAILY:
case WEEKLY:
case MONTHLY:
case MONTH_RELATIVE:
break;
case COMPOSITE:
xaccFreqSpecCompositesClear( fs );
g_list_free( fs->s.composites.subSpecs );
break;
default:
g_return_if_fail(FALSE);
}
fs->type = INVALID;
}
void
xaccFreqSpecFree( FreqSpec *fs )
{
if ( fs == NULL ) return;
gnc_engine_generate_event( &fs->guid, GNC_EVENT_DESTROY );
xaccRemoveEntity( &fs->guid );
xaccFreqSpecCleanUp( fs );
g_free( fs );
}
FreqType
xaccFreqSpecGetType( FreqSpec *fs )
{
g_return_val_if_fail( fs, INVALID );
/* Is this really a fail? */
g_return_val_if_fail( fs->type != INVALID, INVALID );
return fs->type;
}
UIFreqType
xaccFreqSpecGetUIType( FreqSpec *fs )
{
g_return_val_if_fail( fs, INVALID );
return fs->uift;
}
void
xaccFreqSpecSetUIType( FreqSpec *fs, UIFreqType newUIFreqType )
{
g_return_if_fail( fs );
fs->uift = newUIFreqType;
}
/*
void
xaccFreqSpecSetTypes( FreqSpec *fs, FreqType newFT, UIFreqType newUIFT )
{
g_return_if_fail( fs );
xaccFreqSpecSetType( fs, newFT );
xaccFreqSpecSetUIType( fs, newUIFT );
}
*/
static inline guint32 min( guint32 a, guint32 b )
{
return a > b ? b : a;
}
void
xaccFreqSpecGetNextInstance(
FreqSpec *fs,
const GDate* in_date,
GDate* out_date )
{
GList *list, *blist;
int mon;
g_return_if_fail( fs );
switch( fs->type ) {
case INVALID:
g_return_if_fail(FALSE);
case ONCE:
if ( g_date_compare( &(fs->s.once.date), CONST_HACK in_date ) > 0 ) {
*out_date = fs->s.once.date;
} else {
/* Date is past due. Return an invalid date. */
g_date_clear( out_date, 1 );
}
break;
case DAILY: {
guint32 julian_in_date, julian_next_repeat, complete_intervals;
julian_in_date = g_date_julian( CONST_HACK in_date );
complete_intervals =
(julian_in_date - fs->s.daily.offset_from_epoch) /
fs->s.daily.interval_days;
julian_next_repeat =
fs->s.daily.offset_from_epoch +
(complete_intervals + 1) * fs->s.daily.interval_days;
g_date_set_julian( out_date, julian_next_repeat );
} break;
case WEEKLY: {
/* This implementation stores the offset from epoch as the number
* of days, not week epoch offset and day in week offset.
* It is very similar to the daily repeat representation. */
guint32 julian_in_date, julian_next_repeat, complete_intervals;
julian_in_date = g_date_julian( CONST_HACK in_date );
complete_intervals =
(julian_in_date - fs->s.weekly.offset_from_epoch) /
(fs->s.weekly.interval_weeks * 7);
julian_next_repeat =
fs->s.weekly.offset_from_epoch +
(complete_intervals + 1) * fs->s.weekly.interval_weeks * 7;
g_date_set_julian( out_date, julian_next_repeat );
/* This code passes the test, but it seems large and complicated...
* it uses a separate week offset from epoch and day in week offset. */
/* guint32 julian_in_date, julian_next_repeat, complete_intervals,
in_weeks_from_epoch, after_repeat_in_week_interval;
julian_in_date = g_date_julian( CONST_HACK in_date );
in_weeks_from_epoch = (julian_in_date-1) / 7;
complete_intervals =
(in_weeks_from_epoch -
fs->s.weekly.offset_from_epoch) /
fs->s.weekly.interval_weeks;
after_repeat_in_week_interval =
((julian_in_date-1) % 7 >= fs->s.weekly.day_of_week ||
(in_weeks_from_epoch - fs->s.weekly.offset_from_epoch) %
fs->s.weekly.interval_weeks > 0 ) ? 1 : 0;
julian_next_repeat =
(fs->s.weekly.offset_from_epoch +
(complete_intervals + after_repeat_in_week_interval) *
fs->s.weekly.interval_weeks) * 7 +
fs->s.weekly.day_of_week + 1;
g_date_set_julian( out_date, julian_next_repeat );
*/
} break;
case MONTHLY: {
guint32 in_months_from_epoch, after_repeat_in_month_interval,
complete_intervals, next_repeat_months_from_epoch, month, year;
in_months_from_epoch = (g_date_year( CONST_HACK in_date )-1) * 12 +
g_date_month( CONST_HACK in_date ) - 1;
complete_intervals =
(in_months_from_epoch - fs->s.monthly.offset_from_epoch) /
fs->s.monthly.interval_months;
after_repeat_in_month_interval =
(g_date_day( CONST_HACK in_date ) >= fs->s.monthly.day_of_month ||
(in_months_from_epoch - fs->s.monthly.offset_from_epoch) %
fs->s.monthly.interval_months > 0 ||
g_date_day( CONST_HACK in_date ) >=
g_date_days_in_month( g_date_month( CONST_HACK in_date ),
g_date_year( CONST_HACK in_date ) ) ) ? 1 : 0;
next_repeat_months_from_epoch =
fs->s.monthly.offset_from_epoch +
(complete_intervals + after_repeat_in_month_interval) *
fs->s.monthly.interval_months;
/* Hmmm... what happens if the day of the month is greater than the
* number of days in this month?
* Here I have constrained the day of the month by the number
* of days in the month. This is compensated for above by checking if
* the input day is the last day of that month, in which case it will
* move to the next month interval.
*/
month = next_repeat_months_from_epoch % 12 + 1;
year = next_repeat_months_from_epoch / 12 + 1;
g_date_set_dmy( out_date,
min( fs->s.monthly.day_of_month,
g_date_days_in_month( month, year ) ),
month,
year );
} break;
case MONTH_RELATIVE: {
guint32 in_months_from_epoch, after_repeat_in_month_interval,
complete_intervals, next_repeat_months_from_epoch, month, year,
wday_of_1st, day_of_repeat;
GDate date1;
in_months_from_epoch = (g_date_year( CONST_HACK in_date )-1) * 12 +
g_date_month( CONST_HACK in_date ) - 1;
complete_intervals =
(in_months_from_epoch - fs->s.month_relative.offset_from_epoch) /
fs->s.month_relative.interval_months;
month = g_date_month( CONST_HACK in_date );
year = g_date_year( CONST_HACK in_date );
g_date_set_dmy( &date1, 1, month, year );
wday_of_1st = g_date_weekday( &date1 );
day_of_repeat = (fs->s.month_relative.occurrence-1)*7 +
((fs->s.month_relative.weekday + 7 - wday_of_1st)%7 + 1);
after_repeat_in_month_interval =
(g_date_day( CONST_HACK in_date ) >= day_of_repeat ||
day_of_repeat > g_date_days_in_month( month, year ) ||
(in_months_from_epoch - fs->s.month_relative.offset_from_epoch) %
fs->s.month_relative.interval_months > 0 ) ? 1 : 0;
next_repeat_months_from_epoch =
fs->s.month_relative.offset_from_epoch +
(complete_intervals + after_repeat_in_month_interval) *
fs->s.month_relative.interval_months;
month = next_repeat_months_from_epoch % 12 + 1;
year = next_repeat_months_from_epoch / 12 + 1;
g_date_set_dmy( &date1, 1, month, year );
wday_of_1st = g_date_weekday( &date1 );
/* This calculates the day of the month in the month which forms
* the next month in the cycle after the given input date.
* However, this day may be larger than the number of days in that month... */
day_of_repeat = (fs->s.month_relative.occurrence-1)*7 +
((fs->s.month_relative.weekday + 7 - wday_of_1st)%7 + 1);
while( day_of_repeat > g_date_days_in_month( month, year ) ) {
/* If the repeat occurs after the end of the month, then
* find the next month containing a day which satisfies the request.
* Each candiate month separated by interval_months is considered
* by this loop.*/
++complete_intervals;
next_repeat_months_from_epoch =
fs->s.month_relative.offset_from_epoch +
complete_intervals * fs->s.month_relative.interval_months;
month = next_repeat_months_from_epoch % 12 + 1;
year = next_repeat_months_from_epoch / 12 + 1;
g_date_set_dmy( &date1, 1, month, year );
wday_of_1st = g_date_weekday( &date1 );
day_of_repeat = (fs->s.month_relative.occurrence-1)*7 +
((fs->s.month_relative.weekday + 7 - wday_of_1st)%7 + 1);
/* Hmmm... It would be nice to know that this loop is
* guaranteed to terminate... CHECK ME! */
}
g_date_set_dmy( out_date, day_of_repeat, month, year );
} break;
case COMPOSITE:
list = fs->s.composites.subSpecs;
if ( !list ) {
/* sets date to be invalid */
g_date_clear( out_date, 1 );
break;
}
{
/* This implements || composites. */
guint32 min_julian = 0xFFFFFFFF; /* the biggest unsigned 32 bit number */
guint32 this_julian;
do {
GDate next_repeat;
xaccFreqSpecGetNextInstance(
(FreqSpec*) list->data,
in_date,
&next_repeat );
this_julian = g_date_julian( &next_repeat );
min_julian = min( min_julian, this_julian );
} while ( (list = g_list_next(list)) );
g_date_set_julian( out_date, min_julian );
}
break;
default:
g_date_clear( out_date, 1 );
g_return_if_fail(FALSE);
}
}
/*
char*
xaccFreqSpecIsValidDateRelaxed( FreqSpec *fs, time_t query )
{
return "FIXME: not implemented yet!";
}
*/
void
xaccFreqSpecSetOnceDate( FreqSpec *fs, const GDate* when )
{
g_return_if_fail( fs );
g_return_if_fail( when );
xaccFreqSpecCleanUp( fs );
fs->type = ONCE;
fs->s.once.date = *when;
}
void
xaccFreqSpecSetDaily( FreqSpec *fs,
const GDate* initial_date,
guint interval_days )
{
guint32 julian_days_since_epoch;
g_return_if_fail( fs );
g_return_if_fail( interval_days > 0 );
xaccFreqSpecCleanUp( fs );
fs->type = DAILY;
fs->s.daily.interval_days = interval_days;
julian_days_since_epoch = g_date_julian( CONST_HACK initial_date );
fs->s.daily.offset_from_epoch = julian_days_since_epoch % interval_days;
}
void
xaccFreqSpecSetWeekly( FreqSpec *fs,
const GDate* initial_date,
guint interval_weeks )
{
/* pick one... make sure that the code in next matches this,
* and that the fields in the
* weekly struct match too.
*/
#if 0
/* *
* This implements weekly by using the fact that 1 week = 7 days.
* Weeks start at epoch in this representation, not necesarily Monday,
* so there is not really any difference...
* The weekly tests pass.
*/
guint32 julian_days_since_epoch;
g_return_if_fail( fs );
g_return_if_fail( interval_weeks > 0 );
xaccFreqSpecCleanUp( fs );
fs->type = DAILY;
fs->s.daily.interval_days = 7 * interval_weeks;
julian_days_since_epoch = g_date_julian( CONST_HACK initial_date );
fs->s.daily.offset_from_epoch = julian_days_since_epoch % (7*interval_weeks);
#endif
#if 1
/* simplest solution */
guint32 julian_days_since_epoch;
g_return_if_fail( fs );
g_return_if_fail( interval_weeks > 0 );
xaccFreqSpecCleanUp( fs );
fs->type = WEEKLY;
fs->s.weekly.interval_weeks = interval_weeks;
julian_days_since_epoch = g_date_julian( CONST_HACK initial_date );
fs->s.weekly.offset_from_epoch = julian_days_since_epoch % (7*interval_weeks);
#endif
#if 0
/**
* Use the weekly implementation, which seems to be more complicated...
* uses separate weekly and day in week offsets.
* works.
*/
guint32 julian_day_initial, weeks_since_epoch;
g_return_if_fail( fs );
g_return_if_fail( interval_weeks > 0 );
xaccFreqSpecCleanUp( fs );
fs->type = WEEKLY;
fs->s.weekly.interval_weeks = interval_weeks;
julian_day_initial = g_date_julian( CONST_HACK initial_date );
weeks_since_epoch = (julian_day_initial-1) / 7;
fs->s.weekly.day_of_week = (julian_day_initial-1) % 7;
fs->s.weekly.offset_from_epoch = weeks_since_epoch % interval_weeks;
g_return_if_fail( 0 <= fs->s.weekly.day_of_week );
g_return_if_fail( fs->s.weekly.day_of_week < 7 );
g_return_if_fail( fs->s.weekly.offset_from_epoch < interval_weeks );
g_return_if_fail( 0 <= fs->s.weekly.offset_from_epoch );
#endif
}
void
xaccFreqSpecSetMonthly( FreqSpec *fs,
const GDate* initial_date,
guint interval_months )
{
guint months_since_epoch;
g_return_if_fail( fs );
g_return_if_fail( interval_months > 0 );
xaccFreqSpecCleanUp( fs );
fs->type = MONTHLY;
fs->s.monthly.interval_months = interval_months;
months_since_epoch = (g_date_year( CONST_HACK initial_date )-1) * 12 +
g_date_month( CONST_HACK initial_date ) - 1;
fs->s.monthly.offset_from_epoch = months_since_epoch % interval_months;
fs->s.monthly.day_of_month = g_date_day( CONST_HACK initial_date );
g_return_if_fail( fs->s.monthly.offset_from_epoch <
fs->s.monthly.interval_months );
}
void
xaccFreqSpecSetMonthRelative( FreqSpec *fs,
const GDate* initial_date,
guint interval_months )
{
guint months_since_epoch;
g_return_if_fail( fs );
g_return_if_fail( interval_months > 0 );
xaccFreqSpecCleanUp( fs );
fs->type = MONTH_RELATIVE;
fs->s.month_relative.interval_months = interval_months;
months_since_epoch = (g_date_year( CONST_HACK initial_date )-1) * 12 +
g_date_month( CONST_HACK initial_date ) - 1;
fs->s.month_relative.offset_from_epoch = months_since_epoch % interval_months;
fs->s.month_relative.weekday = g_date_weekday( CONST_HACK initial_date );
fs->s.month_relative.occurrence = (g_date_day( CONST_HACK initial_date )-1) / 7 + 1;
g_return_if_fail( fs->s.month_relative.weekday > 0 );
g_return_if_fail( fs->s.month_relative.weekday <= 7 );
g_return_if_fail( fs->s.month_relative.occurrence > 0 );
g_return_if_fail( fs->s.month_relative.occurrence <= 5 );
g_return_if_fail( fs->s.month_relative.offset_from_epoch <
fs->s.month_relative.interval_months );
}
void
xaccFreqSpecSetComposite( FreqSpec *fs )
{
g_return_if_fail( fs );
xaccFreqSpecCleanUp( fs );
fs->type = COMPOSITE;
fs->s.composites.subSpecs = NULL;
}
int
xaccFreqSpecGetOnce( FreqSpec *fs, GDate *outGD )
{
if ( fs->type != ONCE )
return -1;
*outGD = fs->s.once.date;
return 0;
}
int
xaccFreqSpecGetDaily( FreqSpec *fs, int *outRepeat )
{
if ( fs->type != DAILY )
return -1;
*outRepeat = fs->s.daily.interval_days;
return 0;
}
int
xaccFreqSpecGetWeekly( FreqSpec *fs, int *outRepeat, int *outDayOfWeek )
{
if ( fs->type != WEEKLY )
return -1;
*outRepeat = fs->s.weekly.interval_weeks;
*outDayOfWeek = fs->s.weekly.offset_from_epoch;
return 0;
}
int
xaccFreqSpecGetMonthly( FreqSpec *fs, int *outRepeat, int *outDayOfMonth, int *outMonthOffset )
{
if ( fs->type != MONTHLY )
return -1;
*outRepeat = fs->s.monthly.interval_months;
*outDayOfMonth = fs->s.monthly.day_of_month;
*outMonthOffset = fs->s.monthly.offset_from_epoch;
return 0;
}
// FIXME: add month-relative getter
GList*
xaccFreqSpecCompositeGet( FreqSpec *fs )
{
g_return_val_if_fail( fs, NULL );
g_return_val_if_fail( fs->type == COMPOSITE, NULL );
return fs->s.composites.subSpecs;
}
void
xaccFreqSpecCompositeAdd( FreqSpec *fs, FreqSpec *fsToAdd )
{
g_return_if_fail( fs );
g_return_if_fail( fs->type == COMPOSITE );
fs->s.composites.subSpecs =
g_list_append( fs->s.composites.subSpecs, fsToAdd );
}
void
subSpecsListMapDelete( gpointer data, gpointer user_data )
{
xaccFreqSpecFree( (FreqSpec*)data );
}
void
xaccFreqSpecCompositesClear( FreqSpec *fs )
{
g_return_if_fail( fs->type == COMPOSITE );
g_list_foreach( fs->s.composites.subSpecs,
subSpecsListMapDelete, NULL );
}
void
xaccFreqSpecGetFreqStr( FreqSpec *fs, GString *str )
{
GList *list;
FreqSpec *tmpFS;
int tmpInt;
char *tmpStr;
int i;
/* FIXME: fill in. */
switch( xaccFreqSpecGetUIType( fs ) ) {
case UIFREQ_ONCE:
tmpStr = g_new0( char, 25 );
/* this is now a GDate. */
g_date_strftime( tmpStr, 25,
"%a, %b %e, %Y",
&fs->s.once.date );
g_string_sprintf( str, "Once: %s", tmpStr );
g_free( tmpStr );
break;
case UIFREQ_DAILY:
g_string_sprintf( str, "Daily" );
if ( fs->s.daily.interval_days > 1 ) {
g_string_sprintfa( str, " (x%u)",
fs->s.daily.interval_days );
}
break;
case UIFREQ_DAILY_MF:
{
FreqSpec *subFS;
if ( g_list_length( fs->s.composites.subSpecs ) != 5 ) {
PERR( "Invalid Daily[M-F] structure." );
g_string_sprintf( str, "Daily[M-F]: error" );
return;
}
/* We assume that all of the weekly FreqSpecs that make up
the Daily[M-F] FreqSpec have the same interval. */
subFS = (FreqSpec*)fs->s.composites.subSpecs->data;
g_string_sprintf( str, "Daily [M-F]" );
if ( subFS->s.weekly.interval_weeks > 1 ) {
g_string_sprintfa( str, " (x%u)",
subFS->s.weekly.interval_weeks );
}
}
break;
case UIFREQ_WEEKLY:
g_string_sprintf( str, "Weekly" );
tmpInt = -1;
tmpStr = g_new0( char, 8 );
for ( i=0; i<7; i++ ) {
tmpStr[i] = '-';
}
list = xaccFreqSpecCompositeGet( fs );
do {
int dowIdx;
tmpFS = (FreqSpec*)list->data;
if ( xaccFreqSpecGetType(tmpFS) != WEEKLY ) {
g_string_sprintf( str,
"error: UIFREQ_WEEKLY doesn't contain weekly children" );
return;
}
if ( tmpInt == -1 ) {
tmpInt = tmpFS->s.weekly.interval_weeks;
}
/* put the first letter of the weekday name in
the appropriate position. */
dowIdx = tmpFS->s.weekly.offset_from_epoch;
tmpStr[dowIdx] = weekDayNames[dowIdx][0];
} while ( (list = g_list_next(list)) );
if ( tmpInt > 1 ) {
g_string_sprintfa( str, " (x%d)", tmpInt );
}
g_string_sprintfa( str, ": %s", tmpStr );
g_free( tmpStr );
break;
case UIFREQ_BI_WEEKLY:
g_string_sprintf( str, "Bi-Weekly, %ss", weekDayNames[fs->s.weekly.offset_from_epoch % 7] );
break;
case UIFREQ_SEMI_MONTHLY:
g_string_sprintf( str, "Semi-Monthly" );
list = xaccFreqSpecCompositeGet( fs );
tmpFS = (FreqSpec*)(g_list_nth( list, 0 )->data);
if ( tmpFS->s.monthly.interval_months > 1 ) {
g_string_sprintfa( str, " (x%u)", tmpFS->s.monthly.interval_months );
}
g_string_sprintfa( str, ": " );
if ( tmpFS->s.monthly.day_of_month > 31 ) {
g_string_sprintfa( str, "%s", "last day" );
} else {
g_string_sprintfa( str, "%u", tmpFS->s.monthly.day_of_month );
}
g_string_sprintfa( str, ", " );
tmpFS = (FreqSpec*)(g_list_nth( list, 1 )->data);
if ( tmpFS->s.monthly.day_of_month > 31 ) {
g_string_sprintfa( str, "%s", "last day" );
} else {
g_string_sprintfa( str, "%u", tmpFS->s.monthly.day_of_month );
}
break;
case UIFREQ_MONTHLY:
g_string_sprintf( str, "Monthly" );
if ( fs->s.monthly.interval_months > 1 ) {
g_string_sprintfa( str, " (x%u)",
fs->s.monthly.interval_months );
}
g_string_sprintfa( str, ": %u",
fs->s.monthly.day_of_month );
break;
case UIFREQ_QUARTERLY:
g_string_sprintf( str, "Quarterly" );
if ( fs->s.monthly.interval_months != 3 ) {
g_string_sprintfa( str, " (x%u)",
fs->s.monthly.interval_months/3 );
}
g_string_sprintfa( str, ": %u",
fs->s.monthly.day_of_month );
break;
case UIFREQ_TRI_ANUALLY:
g_string_sprintf( str, "Tri-Anually" );
if ( fs->s.monthly.interval_months != 4 ) {
g_string_sprintfa( str, " (x%u)",
fs->s.monthly.interval_months/4 );
}
g_string_sprintfa( str, ": %u",
fs->s.monthly.day_of_month );
break;
case UIFREQ_SEMI_YEARLY:
g_string_sprintf( str, "Semi-Yearly" );
if ( fs->s.monthly.interval_months != 6 ) {
if ( (fs->s.monthly.interval_months % 6) != 0 ) {
PERR( "ERROR: FreqSpec Semi-Yearly month-interval "
"is not a multiple of 6 [%d]",
fs->s.monthly.interval_months );
}
g_string_sprintfa( str, " (x%u)",
fs->s.monthly.interval_months/6 );
}
g_string_sprintfa( str, ": %u",
fs->s.monthly.day_of_month );
break;
case UIFREQ_YEARLY:
g_string_sprintf( str, "Yearly" );
if ( fs->s.monthly.interval_months != 12 ) {
if ( (fs->s.monthly.interval_months % 12) != 0 ) {
PERR( "ERROR: \"Yearly\" FreqSpec month-interval "
"is not a multiple of 12 [%d]",
fs->s.monthly.interval_months );
}
g_string_sprintfa( str, " (x%u)",
fs->s.monthly.interval_months/12 );
}
g_string_sprintfa( str, ": %s/%u",
monthInfo[fs->s.monthly.offset_from_epoch].dshort,
fs->s.monthly.day_of_month );
break;
default:
g_string_sprintf( str, "Unknown" );
break;
}
}

240
src/engine/FreqSpec.h Normal file
View File

@ -0,0 +1,240 @@
/********************************************************************\
* FreqSpec.h -- Frequency Specification *
* Copyright (C) 2001 Joshua Sled <jsled@asynchronous.org> *
* Copyright (C) 2001 Ben Stanley <bds02@uow.edu.au> *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License*
* along with this program; if not, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
* Boston, MA 02111-1307, USA gnu@gnu.org *
* *
\********************************************************************/
#ifndef XACC_FREQSPEC_H
#define XACC_FREQSPEC_H
#include "config.h"
#include <glib.h>
#include "GNCId.h"
/**
* Frequency specification.
*
**/
typedef enum gncp_FreqType {
INVALID,
ONCE,
DAILY,
WEEKLY, /* Hmmm... This is sort of DAILY[7]... */
/* BI_WEEKLY: weekly[2] */
/* SEMI_MONTHLY: use composite */
MONTHLY,
MONTH_RELATIVE,
/* YEARLY: monthly[12] */
COMPOSITE,
} FreqType;
/**
* The user's conception of the frequency. It is expected that this
* list will grow, while the former [FreqType] will not.
*
* Ideally this is not here, but what can you do?
**/
typedef enum gncp_UIFreqType {
UIFREQ_NONE,
UIFREQ_ONCE,
UIFREQ_DAILY,
UIFREQ_DAILY_MF,
UIFREQ_WEEKLY,
UIFREQ_BI_WEEKLY,
UIFREQ_SEMI_MONTHLY,
UIFREQ_MONTHLY,
UIFREQ_QUARTERLY,
UIFREQ_TRI_ANUALLY,
UIFREQ_SEMI_YEARLY,
UIFREQ_YEARLY,
UIFREQ_NUM_UI_FREQSPECS
} UIFreqType;
/**
* Forward declaration of FreqSpec type for storing
* date repetition information. This is an opaque type.
*/
struct gncp_freq_spec;
typedef struct gncp_freq_spec FreqSpec;
/** PROTOTYPES ******************************************************/
/**
* Allocates memory for a FreqSpec and initializes it.
**/
FreqSpec* xaccFreqSpecMalloc(void);
/**
* destroys any private data belonging to the FreqSpec.
* Use this for a stack object.
*/
void xaccFreqSpecCleanUp( FreqSpec *fs );
/**
* Frees a heap allocated FreqSpec.
* This is the opposite of xaccFreqSpecMalloc().
**/
void xaccFreqSpecFree( FreqSpec *fs );
/**
* Gets the type of a FreqSpec.
**/
FreqType xaccFreqSpecGetType( FreqSpec *fs );
/**
* Sets the type of a FreqSpec.
* Setting the type re-initializes any spec-data; this means
* destroying any sub-types in the case of COMPOSITE.
* THESE FUNCTIONS HAVE NOT BEEN MAINTAINED THROUGH BEN'S CHANGES.
* They need to be checked.
**/
/* void xaccFreqSpecSetType( FreqSpec *fs, FreqType newType ); */
UIFreqType xaccFreqSpecGetUIType( FreqSpec *fs );
void xaccFreqSpecSetUIType( FreqSpec *fs, UIFreqType newUIFreqType );
/* Convenience function; calls the two above. */
/* void xaccFreqSpecSetTypes( FreqSpec *fs, FreqType newType, UIFreqType newUIType ); */
/**
* Sets the type to once-off, and initialises the
* date it occurs on.
* Disposes of any previous data.
*/
void xaccFreqSpecSetOnceDate( FreqSpec *fs,
const GDate* when );
/**
* Sets the type to DAILY. Disposes of any previous data.
* Uses the start date to figure
* out how many days after epoch (1/1/1900) this repeat would
* have first occurred on if it had been running back then.
* This is used later to figure out absolute repeat dates.
*/
void xaccFreqSpecSetDaily( FreqSpec *fs,
const GDate* initial_date,
guint interval_days );
/**
* Sets the type to WEEKLY. Disposes of any previous data.
* Uses the inital date to figure out the day of the week to use.
*/
void xaccFreqSpecSetWeekly( FreqSpec *fs,
const GDate* inital_date,
guint interval_weeks );
/**
* Sets the type to MONTHLY. Disposes of any previous data.
* Uses the inital date to figure out the day of the month to use.
*/
void xaccFreqSpecSetMonthly( FreqSpec *fs,
const GDate* inital_date,
guint interval_months );
/**
* Sets the type to MONTH_RELATIVE. Disposes of any previous data.
* Uses the inital date to figure out the day of the month to use.
*/
void xaccFreqSpecSetMonthRelative( FreqSpec *fs,
const GDate* inital_date,
guint interval_months );
/**
* Sets the type to COMPOSITE. Disposes of any previous data.
* You must Add some repeats to the composite before using
* it for repeating.
*/
void xaccFreqSpecSetComposite( FreqSpec *fs );
/**
* Returns a human-readable string of the Frequency. This uses
* UIFreqType to unroll the internal structure. It is an assertion
* failure if the FreqSpec data doesn't match the UIFreqType.
* Caller allocates the GString [natch].
**/
void xaccFreqSpecGetFreqStr( FreqSpec *fs, GString *str );
int xaccFreqSpecGetOnce( FreqSpec *fs, GDate *outGD );
int xaccFreqSpecGetDaily( FreqSpec *fs, int *outRepeat );
int xaccFreqSpecGetWeekly( FreqSpec *fs, int *outRepeat, int *outDayOfWeek );
int xaccFreqSpecGetMonthly( FreqSpec *fs, int *outRepeat,
int *outDayOfMonth, int *outMonthOffset );
// FIXME: add month-relative
/**
* Returns the list of FreqSpecs in a COMPOSITE FreqSpec.
* It is an error to use this if the type is not COMPOSITE.
* The caller should not modify this list;
* only add/remove composites and use this fn to get
* the perhaps-changed list head.
**/
GList* xaccFreqSpecCompositeGet( FreqSpec *fs );
/**
* Adds a FreqSpec to the list in a COMPOSITE FreqSpec; if the
* FreqSpec is not COMPOSITE, this is an assertion failure.
**/
void xaccFreqSpecCompositeAdd( FreqSpec *fs, FreqSpec *fsToAdd );
/**
* Returns the next date which the FreqSpec specifies given the
* previous occurance. Like the relaxed validity check, this doesn't
* take multipliers into account; it just returns the next possible
* occurance after the given day.
* bstanley I think this should be private.
**/
/*
time_t xaccFreqSpecGetInstanceAfter( FreqSpec *fs, time_t after );
*/
/**
* Returns the next instance of the FreqSpec after a given input date.
* Note that if the given date happens to be a repeat date,
* then the next repeat date will be returned.
**/
void xaccFreqSpecGetNextInstance( FreqSpec *fs,
const GDate* in_date,
GDate* out_date );
/**
* Returns either NULL [valid] or a descriptive string [reason not
* valid] for the given query date. This is "relaxed", in that it
* doesn't care about the frequency multiplier [e.g., For
* WEEKLY[2]:Wed, all Wednesdays are valid; for MONTHLY[12]:16, the
* 16th day of every month is valid.
**/
/*
char* xaccFreqSpecIsValidDateRelaxed( FreqSpec *fs, time_t query );
*/
/**
* A strict validity check. Given a previous and query date, returns
* NULL if the query date is valid, or a text reason if not.
**/
/*
char* xaccFreqSpecIsValidDate( FreqSpec *fs, time_t previous, time_t query );
*/
#endif /* XACC_FREQSPEC_H */

112
src/engine/FreqSpecP.h Normal file
View File

@ -0,0 +1,112 @@
/********************************************************************\
* FreqSpec.h -- Frequency Specification *
* Copyright (C) 2001 Joshua Sled <jsled@asynchronous.org> *
* Copyright (C) 2001 Ben Stanley <bds02@uow.edu.au> *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License*
* along with this program; if not, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
* Boston, MA 02111-1307, USA gnu@gnu.org *
* *
\********************************************************************/
/********************************************************************\
This file contains private definitions and should not be used by
other parts of the engine. This is private data and is subject to
change.
Currently the only files which include this file are:
FreqSpec.c
gnc-freqspec-xml-v2.c
\********************************************************************/
#ifndef XACC_FREQSPECP_H
#define XACC_FREQSPECP_H
#include "FreqSpec.h"
/**
* Scheduled transactions have a frequency defined by a frequency
* specifier. This specifier, given a start date, end date [present
* in the scheduled transaction] and last occurance date [possibly not
* present] can be used to determine that a s.transaction should be
* instantiated on a given date [the given query date].
*
* There is a split between the UIFreqType and the 'internal' FreqType
* to reduce the complexity of some of the code involved.
*
* Hmmm... having this in the private header file actually prevents
* client code from allocating this 'class' on the stack.
* Is that a problem?
*
* This still needs to deal with:
* . exceptions
* . yearly 360/365?
* . re-based frequencies [based around a non-standard [read:
* not-Jan-1-based/fiscal] year]
* . "business days" -- m-f sans holidays [per-user list thereof]
**/
struct gncp_freq_spec {
FreqType type;
UIFreqType uift;
union u {
struct {
/** The date on which the single event occurs. */
GDate date;
} once;
struct {
/** number of days from one repeat to the next. */
guint interval_days;
/** epoch is defined by glib to be 1/1/1. Offset
measured in days. 0 <= offset < interval */
guint offset_from_epoch;
} daily;
struct {
/* A week here is measured as 7 days. The first week starts at epoch.
* 1/1/1 was a ?. */
/** number of weeks from one repeat to the next. */
guint interval_weeks;
/* offset measured in days. This combines the week
* offset and the day of the week offset. */
guint offset_from_epoch;
/* guint offset_from_epoch;*/ /* offset measured in weeks, 0 <= offset < interval */
/* guint day_of_week;*/ /* I'm not sure what days each value represents, but it's not important. */
} weekly;
struct {
/** number of months from one repeat to the next. */
guint interval_months;
/* offset measured in months */
guint offset_from_epoch;
/* Which day of the month it occurs on. */
guint day_of_month;
} monthly;
struct {
/** Number of months from one repeat to the next. */
guint interval_months;
/* offset measured in months */
guint offset_from_epoch;
/* stores a value equivalent to a GDateWeekday. */
guint weekday;
/* the 1st occurrence to the 5th occurrence. */
guint occurrence;
} month_relative;
struct {
/** A list of specs for a composite freq. */
GList *subSpecs;
} composites;
} s;
GUID guid;
};
#endif /* XACC_FREQSPECP_H */

326
src/engine/GNCId.c Normal file
View File

@ -0,0 +1,326 @@
/********************************************************************\
* GNCId.c -- Gnucash entity identifier implementation *
* Copyright (C) 2000 Dave Peticolas <dave@krondo.com> *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License*
* along with this program; if not, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
* Boston, MA 02111-1307, USA gnu@gnu.org *
* *
\********************************************************************/
#include "config.h"
#include <string.h>
#include <glib.h>
#include "GNCIdP.h"
#include "gnc-engine-util.h"
/** #defines ********************************************************/
#define GNCID_DEBUG 0
/** Type definitions ************************************************/
typedef struct entity_node
{
GNCIdType entity_type;
gpointer entity;
} EntityNode;
/** Static global variables *****************************************/
static GHashTable * entity_table = NULL;
static GMemChunk *guid_memchunk = NULL;
static short module = MOD_ENGINE;
/** Function implementations ****************************************/
void
xaccGUIDInit (void)
{
if (!guid_memchunk)
guid_memchunk = g_mem_chunk_create (GUID, 512, G_ALLOC_AND_FREE);
}
void
xaccGUIDShutdown (void)
{
if (guid_memchunk)
{
g_mem_chunk_destroy (guid_memchunk);
guid_memchunk = NULL;
}
}
GUID *
xaccGUIDMalloc (void)
{
return g_chunk_new (GUID, guid_memchunk);
}
void
xaccGUIDFree (GUID *guid)
{
if (!guid)
return;
g_chunk_free (guid, guid_memchunk);
}
static gboolean
entity_node_destroy(gpointer key, gpointer value, gpointer not_used)
{
GUID *guid = key;
EntityNode *e_node = value;
e_node->entity_type = GNC_ID_NONE;
e_node->entity = NULL;
xaccGUIDFree(guid);
g_free(e_node);
return TRUE;
}
static void
entity_table_destroy (void)
{
if (entity_table == NULL)
return;
g_hash_table_foreach_remove(entity_table, entity_node_destroy, NULL);
g_hash_table_destroy(entity_table);
entity_table = NULL;
}
static guint
id_hash (gconstpointer key)
{
const GUID *guid = key;
if (key == NULL)
return 0;
if (sizeof(guint) <= 16)
return *((guint *) guid->data);
else
{
guint hash = 0;
int i, j;
for (i = 0, j = 0; i < sizeof(guint); i++, j++)
{
if (j == 16)
j = 0;
hash <<= 4;
hash |= guid->data[j];
}
return hash;
}
}
static gboolean
id_compare(gconstpointer key_1, gconstpointer key_2)
{
return guid_equal (key_1, key_2);
}
#if GNCID_DEBUG
static void
print_node(gpointer key, gpointer value, gpointer not_used)
{
GUID *guid = key;
EntityNode *node = value;
fprintf(stderr, "%s %d %p\n",
guid_to_string(guid), node->entity_type, node->entity);
}
static void
summarize_table(void)
{
if (entity_table == NULL)
return;
g_hash_table_foreach(entity_table, print_node, NULL);
}
#endif
static void
entity_table_init(void)
{
if (entity_table != NULL)
entity_table_destroy();
entity_table = g_hash_table_new(id_hash, id_compare);
xaccStoreEntity(NULL, xaccGUIDNULL(), GNC_ID_NULL);
#if GNCID_DEBUG
atexit(summarize_table);
#endif
}
GNCIdType
xaccGUIDType(const GUID * guid)
{
EntityNode *e_node;
GNCIdType entity_type;
if (guid == NULL)
return GNC_ID_NONE;
if (entity_table == NULL)
entity_table_init();
e_node = g_hash_table_lookup(entity_table, guid->data);
if (e_node == NULL)
return GNC_ID_NONE;
entity_type = e_node->entity_type;
if ((entity_type <= GNC_ID_NONE) || (entity_type > LAST_GNC_ID))
return GNC_ID_NONE;
return entity_type;
}
void
xaccGUIDNew(GUID *guid)
{
if (guid == NULL)
return;
do
{
guid_new(guid);
if (xaccGUIDType(guid) == GNC_ID_NONE)
break;
PWARN("duplicate id created, trying again");
} while(1);
}
const GUID *
xaccGUIDNULL(void)
{
static int null_inited = (0 == 1);
static GUID null_guid;
if (!null_inited)
{
int i;
for (i = 0; i < 16; i++)
null_guid.data[i] = 0;
null_inited = (0 == 0);
}
return &null_guid;
}
void *
xaccLookupEntity(const GUID * guid, GNCIdType entity_type)
{
EntityNode *e_node;
if (guid == NULL)
return NULL;
if (entity_table == NULL)
entity_table_init();
e_node = g_hash_table_lookup(entity_table, guid->data);
if (e_node == NULL)
return NULL;
if (e_node->entity_type != entity_type)
return NULL;
return e_node->entity;
}
void
xaccStoreEntity(void * entity, const GUID * guid, GNCIdType entity_type)
{
EntityNode *e_node;
GUID *new_guid;
if (guid == NULL)
return;
if ((entity_type <= GNC_ID_NONE) || (entity_type > LAST_GNC_ID))
return;
if (guid_equal(guid, xaccGUIDNULL())) return;
xaccRemoveEntity(guid);
e_node = g_new(EntityNode, 1);
e_node->entity_type = entity_type;
e_node->entity = entity;
new_guid = xaccGUIDMalloc();
if(!new_guid) return;
*new_guid = *guid;
g_hash_table_insert(entity_table, new_guid, e_node);
}
void
xaccRemoveEntity(const GUID * guid)
{
EntityNode *e_node;
gpointer old_guid;
gpointer node;
if (guid == NULL)
return;
if (entity_table == NULL)
entity_table_init();
if (g_hash_table_lookup_extended(entity_table, guid, &old_guid, &node))
{
e_node = node;
if (e_node->entity_type == GNC_ID_NULL)
return;
g_hash_table_remove(entity_table, old_guid);
entity_node_destroy(old_guid, node, NULL);
}
}
GHashTable *
xaccGetAndResetEntityTable(void) {
GHashTable *result = entity_table;
entity_table = NULL;
return(result);
}
void
xaccSetEntityTable(GHashTable *et) {
if(entity_table) entity_table_destroy();
entity_table = et;
}

72
src/engine/GNCId.h Normal file
View File

@ -0,0 +1,72 @@
/********************************************************************\
* GNCId.h -- Gnucash entity identifier API *
* Copyright (C) 2000 Dave Peticolas <peticola@cs.ucdavis.edu> *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License*
* along with this program; if not, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
* Boston, MA 02111-1307, USA gnu@gnu.org *
* *
\********************************************************************/
#ifndef GNC_ID_H
#define GNC_ID_H 1
/* This file defines an API for using gnucash entity identifiers.
*
* Identifiers can be used to reference Accounts, Transactions, and
* Splits. These four Gnucash types are referred to as Gnucash
* entities. Identifiers are globally-unique and permanent, i.e., once
* an entity has been assigned an identifier, it retains that same
* identifier for its lifetime.
*
* Identifiers can be encoded as hex strings. */
#include "guid.h"
/* Identifiers are 'typed' with integers. The ids used in gnucash are
* defined below. An id with type GNC_ID_NONE does not refer to any
* entity, although that may change as new ids are created. An id with
* type GNC_ID_NULL does not refer to any entity, and will never refer
* to any entity. An identifier with any other type may refer to an
* actual entity, but that is not guaranteed. If an id does refer to
* an entity, the type of the entity will match the type of the
* identifier. */
typedef enum
{
GNC_ID_NONE = 0,
GNC_ID_NULL,
GNC_ID_ACCOUNT,
GNC_ID_TRANS,
GNC_ID_SPLIT,
GNC_ID_PRICE,
GNC_ID_SCHEDXACTION,
GNC_ID_FREQSPEC,
LAST_GNC_ID = GNC_ID_FREQSPEC
} GNCIdType;
/* Return the type of an identifier. */
GNCIdType xaccGUIDType (const GUID * guid);
/* Returns a GUID which is guaranteed to never reference any entity. */
const GUID * xaccGUIDNULL (void);
/* Efficiently allocate & free memory for GUIDs */
GUID * xaccGUIDMalloc (void);
void xaccGUIDFree (GUID *guid);
#endif

57
src/engine/GNCIdP.h Normal file
View File

@ -0,0 +1,57 @@
/********************************************************************\
* GNCIdP.h -- Gnucash entity identifier engine-only API *
* Copyright (C) 2000 Dave Peticolas <peticola@cs.ucdavis.edu> *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License*
* along with this program; if not, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
* Boston, MA 02111-1307, USA gnu@gnu.org *
* *
\********************************************************************/
#ifndef GNC_ID_P_H
#define GNC_ID_P_H 1
#include "GNCId.h"
/* This file defines an engine-only API for using gnucash entity
* identifiers. */
/* Generate a new id. This function is guaranteed to return an id that
* is unique within the scope of all GnuCash entities being managed by
* the current invocation of GnuCash. GnuCash routines should always
* use this function and not guid_new! */
void xaccGUIDNew (GUID *guid);
/* Lookup an entity given an id and a type. If there is no entity
* associated with the id, or if it has a different type, NULL
* is returned. */
void * xaccLookupEntity (const GUID * guid, GNCIdType entity_type);
/* Store the given entity under the given id with the given type. */
void xaccStoreEntity (void * entity, const GUID * guid, GNCIdType entity_type);
/* Remove any existing association between an entity and the given
* id. The entity is not changed in any way. */
void xaccRemoveEntity (const GUID * guid);
GHashTable *xaccGetAndResetEntityTable (void);
void xaccSetEntityTable (GHashTable *et);
/* Initialize and shutdown the GNC Id system. */
void xaccGUIDInit (void);
void xaccGUIDShutdown (void);
#endif

1111
src/engine/Group.c Normal file

File diff suppressed because it is too large Load Diff

327
src/engine/Group.h Normal file
View File

@ -0,0 +1,327 @@
/********************************************************************\
* Group.h -- chart of accounts (hierarchical tree of accounts) *
* Copyright (C) 1997 Robin D. Clark *
* Copyright (C) 1997, 1998, 1999, 2000 Linas Vepstas *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License*
* along with this program; if not, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
* Boston, MA 02111-1307, USA gnu@gnu.org *
* *
\********************************************************************/
#ifndef XACC_ACCOUNT_GROUP_H
#define XACC_ACCOUNT_GROUP_H
#include "config.h"
#include <glib.h>
#include "Account.h"
#include "GNCId.h"
#include "gnc-common.h"
/** PROTOTYPES ******************************************************/
AccountGroup *xaccMallocAccountGroup (void);
void xaccFreeAccountGroup (AccountGroup *account_group);
void xaccAccountGroupBeginEdit (AccountGroup *grp);
void xaccAccountGroupCommitEdit (AccountGroup *grp);
/*
* The xaccGroupConcatGroup() subroutine will move all accounts
* from the "from" group to the "to" group
*
* The xaccGroupMergeAccounts() subroutine will go through a group,
* merging all accounts that have the same name and description.
* This function is useful when importing Quicken(TM) files.
*/
void xaccGroupConcatGroup (AccountGroup *to, AccountGroup *from);
void xaccGroupMergeAccounts (AccountGroup *grp);
/*
* The xaccGroupNotSaved() subroutine will return TRUE
* if any account in the group or in any subgroup
* hasn't been saved.
*
* The xaccGroupMarkSaved() subroutine will mark
* the entire group as having been saved, including
* all of the child accounts.
*
* The xaccGroupMarkNotSaved() subroutine will mark
* the given group as not having been saved.
*
* The xaccGroupMarkDoFree() subroutine will mark
* all accounts in the group as being destroyed.
*/
gboolean xaccGroupNotSaved (AccountGroup *grp);
void xaccGroupMarkSaved (AccountGroup *grp);
void xaccGroupMarkNotSaved (AccountGroup *grp);
void xaccGroupMarkDoFree (AccountGroup *grp);
/*
* The xaccGroupRemoveAccount() subroutine will remove the indicated
* account from its parent account group. It will NOT free the
* associated memory or otherwise alter the account: the account
* can now be reparented to a new location.
* Note, however, that it will mark the old parents as having
* been modified.
*
* The xaccAccountRemoveGroup() subroutine will remove the indicated
* account group from its parent account. It will NOT free the
* associated memory or otherwise alter the account group: the
* account group can now be reparented to a new location.
* Note, however, that it will mark the old parents as having
* been modified.
*/
void xaccGroupRemoveAccount (AccountGroup *grp, Account *account);
void xaccAccountRemoveGroup (Account *acc);
void xaccGroupInsertAccount (AccountGroup *grp, Account *acc);
void xaccAccountInsertSubAccount (Account *parent, Account *child);
/*
* The xaccGroupGetNumSubAccounts() subroutine returns the number
* of accounts, including subaccounts, in the account group
*
* The xaccGroupGetNumAccounts() subroutine returns the number
* of accounts in the indicated group only (children not counted).
*
* The xaccGroupGetDepth() subroutine returns the length of the
* longest tree branch. Each link between an account and its
* (non-null) children counts as one unit of length.
*/
int xaccGroupGetNumSubAccounts (AccountGroup *grp);
int xaccGroupGetNumAccounts (AccountGroup *grp);
int xaccGroupGetDepth (AccountGroup *grp);
Account * xaccGroupGetAccount (AccountGroup *group, int index);
/*
* The xaccGroupGetSubAccounts() subroutine returns an list of the accounts,
* including subaccounts, in the account group. The returned list
* should be freed with g_list_free when no longer needed.
*
* The xaccGroupGetAccountList() subroutines returns only the immediate
* children of the account group. The returned list should *not*
* be freed by the caller.
*/
GList * xaccGroupGetSubAccounts (AccountGroup *grp);
GList * xaccGroupGetAccountList (AccountGroup *grp);
/*
* The xaccGetAccountFromName() subroutine fetches the
* account by name from the collection of accounts
* in the indicated AccountGroup group. It returns NULL if the
* account was not found.
*
* The xaccGetAccountFromFullName() subroutine works like
* xaccGetAccountFromName, but uses fully-qualified names
* using the given separator.
*
* The xaccGetPeerAccountFromName() subroutine fetches the
* account by name from the collection of accounts
* in the same AccountGroup anchor group. It returns NULL if the
* account was not found.
*
* The xaccGetPeerAccountFromFullName() subroutine works like
* xaccGetPeerAccountFromName, but uses fully-qualified
* names using the given separator.
*/
Account *xaccGetAccountFromName (AccountGroup *group, const char *name);
Account *xaccGetAccountFromFullName (AccountGroup *group,
const char *name,
const char separator);
Account *xaccGetPeerAccountFromName (Account *account, const char *name);
Account *xaccGetPeerAccountFromFullName (Account *acc,
const char * name,
const char separator);
/*
* The xaccGetAccountRoot () subroutine will find the topmost
* (root) group to which this account belongs.
*/
AccountGroup * xaccGetAccountRoot (Account *account);
/* The xaccGroupGetParentAccount() subroutine returns the parent
* account of the group, or NULL.
*/
Account * xaccGroupGetParentAccount (AccountGroup *group);
/* if the function returns null for a given item, it won't show up in
the result list */
typedef gpointer (*AccountCallback)(Account *a, gpointer data);
GSList *xaccGroupMapAccounts(AccountGroup *grp,
AccountCallback,
gpointer data);
/* The xaccGroupForEachAccount() method will traverse the AccountGroup
* tree, calling 'func' on each account. Traversal will stop when
* func returns a non-null value, and the routine wil return with that
* value. If 'deeply' is FALSE, then only the immediate children of
* the account will be traversed. If TRUE, then the whole tree will
* be traversed.
*/
gpointer xaccGroupForEachAccount (AccountGroup *grp,
AccountCallback,
gpointer data,
gboolean deeply);
gboolean xaccGroupEqual(AccountGroup *a, AccountGroup *b,
gboolean check_guids);
/*
* The following functions provide support for "staged traversals"
* over all of the transactions in an account or group. The idea
* is to be able to perform a sequence of traversals ("stages"),
* and perform an operation on each transaction exactly once
* for that stage.
*
* Only transactions whose current "stage" is less than the
* stage of the current traversal will be affected, and they will
* be "brought up" to the current stage when they are processed.
*
* For example, you could perform a stage 1 traversal of all the
* transactions in an account, and then perform a stage 1 traversal of
* the transactions in a second account. Presuming the traversal of
* the first account didn't abort prematurely, any transactions shared
* by both accounts would be ignored during the traversal of the
* second account since they had been processed while traversing the
* first account.
*
* However, if you had traversed the second account using a stage
* of 2, then all the transactions in the second account would have
* been processed.
*
* Traversal can be aborted by having the callback function return
* a non-zero value. The traversal is aborted immediately, and the
* non-zero value is returned. Note that an aborted traversal can
* be restarted; no information is lost due to an abort.
*
* The initial impetus for this particular approach came from
* generalizing a mark/sweep practice that was already being
* used in FileIO.c.
*
* Note that currently, there is a hard limit of 256 stages, which
* can be changed by enlarging "marker" in the transaction struct.
* */
/* xaccGroupBeginStagedTransactionTraversals() resets the traversal
* marker inside each of all the transactions in the group so that
* a new sequence of staged traversals can begin.
*
* xaccSplitsBeginStagedTransactionTraversals() resets the traversal
* marker for each transaction which is a parent of one of the
* splits in the list.
*
* xaccAccountBeginStagedTransactionTraversals() resets the traversal
* marker for each transaction which is a parent of one of the
* splits in the account.
*
*/
void xaccGroupBeginStagedTransactionTraversals(AccountGroup *grp);
void xaccSplitsBeginStagedTransactionTraversals(GList *splits);
void xaccAccountBeginStagedTransactionTraversals(Account *account);
/* xaccTransactionTraverse() checks the stage of the given transaction.
* If the transaction hasn't reached the given stage, the transaction
* is updated to that stage and the function returns TRUE. Otherwise
* no change is made and the function returns FALSE.
*
* xaccSplitTransactionTraverse() behaves as above using the parent of
* the given split.
*/
gboolean xaccTransactionTraverse(Transaction *trans, int stage);
gboolean xaccSplitTransactionTraverse(Split *split, int stage);
/* xaccGroupStagedTransactionTraversal() calls thunk on each
* transaction in the group whose current marker is less than the
* given `stage' and updates each transaction's marker to be `stage'.
* The traversal will stop if thunk() returns a non-zero value.
* xaccGroupStagedTransactionTraversal() function will return zero
* or the non-zero value returned by thunk(). This
* API does not handle handle recursive traversals.
*
* Currently the result of adding or removing transactions during
* a traversal is undefined, so don't do that.
*/
typedef int (*TransactionCallbackInt)(Transaction *t, void *data);
int xaccGroupStagedTransactionTraversal(AccountGroup *grp,
unsigned int stage,
TransactionCallbackInt,
void *data);
/* xaccAccountStagedTransactionTraversal() calls thunk on each
* transaction in the account whose current marker is less than the
* given `stage' and updates each transaction's marker to be `stage'.
* The traversal will stop if thunk() returns a non-zero value.
* xaccAccountStagedTransactionTraversal() function will return zero
* or the non-zero value returned by thunk().
* This API does not handle handle recursive traversals.
*
* Currently the result of adding or removing transactions during
* a traversal is undefined, so don't do that.
*/
int xaccAccountStagedTransactionTraversal(Account *a,
unsigned int stage,
TransactionCallbackInt,
void *data);
/* Traverse all of the transactions in the given account group.
Continue processing IFF proc does not return FALSE. This function
will descend recursively to traverse transactions in the
children of the accounts in the group.
Proc will be called exactly once for each transaction that is
pointed to by at least one split in any account in the hierarchy
topped by AccountGroup g.
Note too, that if you call this function on two separate account
groups and those accounts groups share transactions, proc will be
called once per account on the shared transactions.
The result of this function will not be FALSE IFF every relevant
transaction was traversed exactly once. */
gboolean
xaccGroupForEachTransaction(AccountGroup *g,
TransactionCallback,
void *data);
/* Visit every transaction in the account that hasn't already been
visited exactly once. visited_txns must be a hash table created
via guid_hash_table_new() and is the authority about which
transactions have already been visited. Further, when this
procedure returns, visited_txns will have been modified to reflect
all the newly visited transactions.
The result of this function will not be FALSE IFF every relevant
transaction was traversed exactly once. */
gboolean
xaccGroupVisitUnvisitedTransactions(AccountGroup *g,
TransactionCallback,
void *data,
GHashTable *visited_txns);
#endif /* XACC_ACCOUNT_GROUP_H */

60
src/engine/GroupP.h Normal file
View File

@ -0,0 +1,60 @@
/********************************************************************\
* GroupP.h -- private header file for chart of accounts *
* Copyright (C) 1997 Robin D. Clark *
* Copyright (C) 1997, 1998, 1999, 2000 Linas Vepstas *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License*
* along with this program; if not, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
* Boston, MA 02111-1307, USA gnu@gnu.org *
* *
\********************************************************************/
/*
* FILE:
* GroupP.h
*
* FUNCTION:
* This is the *private* account group structure.
* This header should *not* be included by any code outside of the
* engine.
*
*/
#ifndef XACC_ACCOUNT_GROUP_P_H
#define XACC_ACCOUNT_GROUP_P_H
#include "config.h"
#include "BackendP.h"
#include "GNCId.h"
#include "Transaction.h"
#include "gnc-numeric.h"
/** STRUCTS *********************************************************/
struct _account_group
{
/* The flags: */
unsigned int saved : 1;
Account *parent; /* back-pointer to parent */
GList *accounts; /* list of account pointers */
Backend *backend; /* persistant storage backend */
};
#endif /* XACC_ACCOUNT_GROUP_P_H */

326
src/engine/NetIO.c Normal file
View File

@ -0,0 +1,326 @@
/********************************************************************\
* NetIO.c -- read and write network IO *
* Copyright (C) 2001 Linas Vepstas <linas@linas.org> *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License*
* along with this program; if not, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
* Boston, MA 02111-1307, USA gnu@gnu.org *
* *
\********************************************************************/
/*
* Rudimentary implmentation right now; good enough for a demo,
* but that's all.
*
* HACK ALERT -- this should be moved into its own subdirectory
* Mostly so that the engine build doesn't require libghttp
* as a dependency. In general, backends should be dynamically
* loaded ...
*/
#include "config.h"
#include <ghttp.h>
#include <glib.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "BackendP.h"
#include "NetIO.h"
#include "gnc-book.h"
#include "gnc-engine-util.h"
#include "io-gncxml.h"
static short module = MOD_BACKEND;
typedef struct _xmlend XMLBackend;
struct _xmlend {
Backend be;
ghttp_request *request;
char * query_url;
char * auth_cookie;
};
Backend *xmlendNew (void);
#if 0
/* ==================================================================== */
/* Perform various validty checks on the reply:
* -- was the content type text/gnc-xml ?
* -- was there a reply body, of positive length?
* -- did the body appear to contain gnc xml data?
*
* Also, if the reply contained a set-cookie command, process that.
*/
static int
check_response (XMLBackend *be)
{
ghttp_request *request;
const char *bufp;
int len;
request = be->request;
/* get the content type out of the header */
bufp = ghttp_get_header(request, "Content-Type");
PINFO ("Content-Type: %s", bufp ? bufp : "(null)");
/* in principle, we should reject content that isn't
* labelled as text/gnc-xml. But for now, we'll be soft ...
*/
if (strncmp (bufp, "text/gnc-xml", 12))
{
PWARN ("content type is incorrectly labelled as %s", bufp);
be->be.last_err = ERR_NETIO_WRONG_CONTENT_TYPE;
// return 0;
}
len = ghttp_get_body_len(request);
PINFO ("reply length=%d\n", len);
/* body length must be postive */
if (0 >= len)
{
const char * errstr = ghttp_get_error (request);
const char * reason = ghttp_reason_phrase (request);
PERR ("connection failed: %s %s\n",
errstr ? errstr : "",
reason ? reason : "");
be->be.last_err = ERR_NETIO_SHORT_READ;
return len;
}
bufp = ghttp_get_body(request);
g_return_val_if_fail (bufp, 0);
DEBUG ("%s\n", bufp);
/* skip paste whitespace */
bufp += strspn (bufp, " \t\f\n\r\v\b");
/* see if this really appears to be gnc-xml content ... */
if (strncmp (bufp, "<?xml version", 13))
{
PERR ("bogus file content, file was:\n%s", bufp);
be->be.last_err = ERR_NETIO_NOT_GNCXML;
return 0;
}
/* if we got to here, the response looks good.
* if there is a cookie in the header, obey it
*/
bufp = ghttp_get_header(request, "Set-Cookie");
if (bufp)
{
if (be->auth_cookie) g_free (be->auth_cookie);
be->auth_cookie = g_strdup (bufp);
}
/* must be good */
return len;
}
/* ==================================================================== */
static void
setup_request (XMLBackend *be)
{
ghttp_request *request = be->request;
/* clean is needed to clear out old request bodies, headers, etc. */
ghttp_clean (request);
ghttp_set_header (be->request, http_hdr_Connection, "close");
ghttp_set_header (be->request, http_hdr_User_Agent,
PACKAGE "/" VERSION
" (Financial Browser for Linux; http://gnucash.org)");
ghttp_set_sync (be->request, ghttp_sync);
if (be->auth_cookie) {
ghttp_set_header (request, "Cookie", be->auth_cookie);
}
}
/* ==================================================================== */
/* Initialize the connection ... ?? */
/* validate the URL for syntactic correctness ?? */
/* maybe check to see if the server is reachable? */
static void
xmlbeBookBegin (GNCBook *book, const char *url)
{
XMLBackend *be;
if (!book || !url) return;
ENTER ("url is %s", url);
be = (XMLBackend *) xaccGNCBookGetBackend (book);
/* hack alert -- we store this first url as the url for inital
* contact & * for sending queries to
* this should be made customizable, I suppose ???? */
be->query_url = g_strdup (url);
LEAVE(" ");
}
/* ==================================================================== */
/* Load a set of accounts and currencies from the indicated URL. */
static AccountGroup *
xmlbeBookLoad (Backend *bend)
{
XMLBackend *be = (XMLBackend *) bend;
AccountGroup *grp;
ghttp_request *request;
const char *bufp;
int len;
if (!be) return NULL;
/* build up a request for the URL */
setup_request (be);
request = be->request;
ghttp_set_uri (request, be->query_url);
ghttp_set_type (request, ghttp_type_get);
ghttp_prepare (request);
ghttp_process (request);
/* perform various error and validity checking on the response */
len = check_response (be);
if (0 >= len) return NULL;
bufp = ghttp_get_body(request);
grp = gnc_book_load_from_buf ((char *)bufp, len);
LEAVE(" ");
return grp;
}
/* ==================================================================== */
static void
xmlbeRunQuery (Backend *b, Query *q)
{
XMLBackend *be = (XMLBackend *) b;
AccountGroup *grp, *reply_grp;
ghttp_request *request;
char *bufp;
int len;
if (!be || !q) return;
ENTER ("be=%p q=%p", b, q);
/* set up a new http request, of type POST */
setup_request (be);
request = be->request;
ghttp_set_uri (request, be->query_url);
ghttp_set_type (request, ghttp_type_post);
/* convert the query to XML */
gncxml_write_query_to_buf (q, &bufp, &len);
/* put the XML into the request body */
ghttp_set_body (request, bufp, len);
/* send it off the the webserver, wait for the reply */
ghttp_prepare (request);
ghttp_process (request);
/* free the query xml */
free (bufp);
/* perform various error and validity checking on the response */
len = check_response (be);
if (0 >= len) return;
/* we get back a list of splits */
bufp = ghttp_get_body(request);
reply_grp = gncxml_read_from_buf (bufp, len);
/* merge the splits into our local cache */
grp = xaccQueryGetGroup (q);
xaccGroupConcatGroup (grp, reply_grp);
xaccGroupMergeAccounts (grp);
xaccFreeAccountGroup (reply_grp);
LEAVE(" ");
return;
}
/* ==================================================================== */
static void
xmlbeBookEnd (Backend *b)
{
XMLBackend *be = (XMLBackend *) b;
ghttp_request_destroy (be->request);
g_free (be->query_url);
}
/* ==================================================================== */
#endif
Backend *
xmlendNew (void)
{
#if 0
XMLBackend *be;
be = (XMLBackend *) malloc (sizeof (XMLBackend));
/* generic backend handlers */
be->be.book_begin = NULL;
be->be.book_load = xmlbeBookLoad;
be->be.price_load = NULL;
be->be.book_end = xmlbeBookEnd;
be->be.account_begin_edit = NULL;
be->be.account_commit_edit = NULL;
be->be.trans_begin_edit = NULL;
be->be.trans_commit_edit = NULL;
be->be.trans_rollback_edit = NULL;
be->be.price_begin_edit = NULL;
be->be.price_commit_edit = NULL;
be->be.run_query = xmlbeRunQuery;
be->be.price_lookup = NULL;
be->be.sync = NULL;
be->be.sync_price = NULL;
be->be.events_pending = NULL;
be->be.process_events = NULL;
be->be.last_err = ERR_BACKEND_NO_ERR;
be->request = ghttp_request_new();
be->auth_cookie = NULL;
be->query_url = NULL;
return (Backend *) be;
#endif
return NULL;
}
/* ============================== END OF FILE ======================== */

32
src/engine/NetIO.h Normal file
View File

@ -0,0 +1,32 @@
/********************************************************************\
* NetIO.h -- read and write network IO *
* Copyright (C) 2001 Linas Vepstas <linas@linas.org> *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License*
* along with this program; if not, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
* Boston, MA 02111-1307, USA gnu@gnu.org *
* *
\********************************************************************/
#ifndef XACC_NET_IO_H
#define XACC_NET_IO_H
#include "BackendP.h"
Backend * xmlendNew (void);
#endif /* XACC_NET_IO_H */

2794
src/engine/Query.c Normal file

File diff suppressed because it is too large Load Diff

348
src/engine/Query.h Normal file
View File

@ -0,0 +1,348 @@
/********************************************************************\
* Query.h : api for finding transactions *
* Copyright 2000 Bill Gribble <grib@billgribble.com> *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License*
* along with this program; if not, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
* Boston, MA 02111-1307, USA gnu@gnu.org *
\********************************************************************/
#ifndef GNUCASH_QUERY_H
#define GNUCASH_QUERY_H
#include <sys/types.h>
#include <time.h>
#include <glib.h>
#include <regex.h>
#include "gnc-common.h"
#include "Account.h"
#include "Transaction.h"
typedef enum {
QUERY_AND=1,
QUERY_OR,
QUERY_NAND,
QUERY_NOR,
QUERY_XOR
} QueryOp;
typedef enum {
BY_STANDARD=1,
BY_DATE,
BY_DATE_ROUNDED,
BY_DATE_ENTERED,
BY_DATE_ENTERED_ROUNDED,
BY_DATE_RECONCILED,
BY_DATE_RECONCILED_ROUNDED,
BY_NUM,
BY_AMOUNT,
BY_MEMO,
BY_DESC,
BY_RECONCILE,
BY_ACCOUNT_FULL_NAME,
BY_ACCOUNT_CODE,
BY_CORR_ACCOUNT_FULL_NAME,
BY_CORR_ACCOUNT_CODE,
BY_NONE
} sort_type_t;
typedef enum {
PD_DATE=1,
PD_AMOUNT,
PD_ACCOUNT,
PD_STRING,
PD_CLEARED,
PD_BALANCE,
PD_GUID,
PD_MISC
} pd_type_t;
typedef enum {
PR_ACCOUNT=1,
PR_ACTION,
PR_AMOUNT, /* FIXME: misnamed, should be PR_VALUE */
PR_BALANCE,
PR_CLEARED,
PR_DATE,
PR_DESC,
PR_GUID,
PR_MEMO,
PR_MISC,
PR_NUM,
PR_PRICE,
PR_SHRS /* FIXME: misnamed, should be PR_AMT */
} pr_type_t;
typedef enum {
ACCT_MATCH_ALL=1,
ACCT_MATCH_ANY,
ACCT_MATCH_NONE
} acct_match_t;
typedef enum
{
AMT_MATCH_ATLEAST=1,
AMT_MATCH_ATMOST,
AMT_MATCH_EXACTLY
} amt_match_t;
typedef enum {
AMT_SGN_MATCH_EITHER=1,
AMT_SGN_MATCH_CREDIT,
AMT_SGN_MATCH_DEBIT
} amt_match_sgn_t;
typedef enum {
CLEARED_NO = 1 << 0,
CLEARED_CLEARED = 1 << 1,
CLEARED_RECONCILED = 1 << 2,
CLEARED_FROZEN = 1 << 3
} cleared_match_t;
enum {
STRING_MATCH_CASE = 1 << 0,
STRING_MATCH_REGEXP = 1 << 1
};
typedef enum {
BALANCE_BALANCED = 1 << 0,
BALANCE_UNBALANCED = 1 << 1
} balance_match_t;
/* query_run_t describes whether to require all splits or
* any to match for a transaction to be returned by
* xaccQueryGetTransactions */
typedef enum {
QUERY_MATCH_ALL=1,
QUERY_MATCH_ANY=2
} query_run_t;
typedef struct _querystruct Query;
typedef struct {
pd_type_t type;
pr_type_t term_type;
int sense;
} BasePredicateData;
typedef struct {
pd_type_t type;
pr_type_t term_type;
int sense;
int use_start;
Timespec start;
int use_end;
Timespec end;
} DatePredicateData;
typedef struct {
pd_type_t type;
pr_type_t term_type;
int sense;
amt_match_t how;
amt_match_sgn_t amt_sgn;
double amount;
} AmountPredicateData;
typedef struct {
pd_type_t type;
pr_type_t term_type;
int sense;
acct_match_t how;
GList *accounts;
GList *account_guids;
} AccountPredicateData;
typedef struct {
pd_type_t type;
pr_type_t term_type;
int sense;
int case_sens;
int use_regexp;
char *matchstring;
regex_t compiled;
} StringPredicateData;
typedef struct {
pd_type_t type;
pr_type_t term_type;
int sense;
cleared_match_t how;
} ClearedPredicateData;
typedef struct {
pd_type_t type;
pr_type_t term_type;
int sense;
balance_match_t how;
} BalancePredicateData;
typedef struct {
pd_type_t type;
pr_type_t term_type;
int sense;
GUID guid;
} GUIDPredicateData;
typedef struct {
pd_type_t type;
pr_type_t term_type;
int sense;
int how;
int data;
} MiscPredicateData;
typedef union {
pd_type_t type;
BasePredicateData base;
DatePredicateData date;
AmountPredicateData amount;
AccountPredicateData acct;
StringPredicateData str;
ClearedPredicateData cleared;
BalancePredicateData balance;
GUIDPredicateData guid;
MiscPredicateData misc;
} PredicateData;
typedef int (* Predicate)(Split * to_test, PredicateData * test_data);
typedef struct {
PredicateData data;
Predicate p;
} QueryTerm;
/*******************************************************************
* basic Query API
*******************************************************************/
Query * xaccMallocQuery(void);
void xaccFreeQuery(Query *);
Query * xaccQueryCopy(Query *q);
void xaccQuerySetGroup(Query * q, AccountGroup * group);
AccountGroup *xaccQueryGetGroup(Query * q);
Query * xaccQueryInvert(Query * q1);
Query * xaccQueryMerge(Query * q1, Query * q2, QueryOp op);
void xaccQueryClear(Query * q);
/* The xaccQueryHasTerms() routine returns the number of 'OR' terms in the query.
* The xaccQueryNumTerms() routine returns the total number of terms in the query.
*/
void xaccQueryPurgeTerms(Query * q, pd_type_t type);
int xaccQueryHasTerms(Query * q);
gboolean xaccQueryHasTermType(Query * q, pd_type_t type);
GList * xaccQueryGetTerms(Query * q);
int xaccQueryNumTerms(Query * q);
/* after the query has been set up, call this to run the query */
GList * xaccQueryGetSplits(Query * q);
GList * xaccQueryGetSplitsUniqueTrans(Query *q);
GList * xaccQueryGetTransactions(Query * q, query_run_t type);
/* compare two queries for equality. this is a simplistic
* implementation -- logical equivalences between different
* and/or trees are ignored. */
gboolean xaccQueryEqual(Query *q1, Query *q2);
/* handy for debugging */
void xaccQueryPrint(Query *q);
/*******************************************************************
* match-adding API
*******************************************************************/
void xaccQueryAddAccountMatch(Query * q, GList * accounts,
acct_match_t how, QueryOp op);
void xaccQueryAddAccountGUIDMatch(Query * q, GList * account_guids,
acct_match_t how, QueryOp op);
void xaccQueryAddSingleAccountMatch(Query * q, Account * acct,
QueryOp op);
void xaccQueryAddDescriptionMatch(Query * q, const char * matchstring,
int case_sens, int use_regexp, QueryOp op);
void xaccQueryAddNumberMatch(Query * q, const char * matchstring,
int case_sens, int use_regexp, QueryOp op);
void xaccQueryAddActionMatch(Query * q, const char * matchstring,
int case_sens, int use_regexp, QueryOp op);
void DxaccQueryAddAmountMatch(Query * q, double amount,
amt_match_sgn_t amt_sgn,
amt_match_t how, QueryOp op);
void DxaccQueryAddSharePriceMatch(Query * q, double amount,
amt_match_t how, QueryOp op);
void DxaccQueryAddSharesMatch(Query * q, double amount,
amt_match_t how, QueryOp op);
void xaccQueryAddDateMatch(Query * q,
int use_start, int sday, int smonth, int syear,
int use_end, int eday, int emonth, int eyear,
QueryOp op);
void xaccQueryAddDateMatchTS(Query * q,
int use_start, Timespec sts,
int use_end, Timespec ets,
QueryOp op);
void xaccQueryAddDateMatchTT(Query * q,
int use_start, time_t stt,
int use_end, time_t ett,
QueryOp op);
void xaccQueryAddMemoMatch(Query * q, const char * matchstring,
int case_sens, int use_regexp, QueryOp op);
void xaccQueryAddClearedMatch(Query * q, cleared_match_t how, QueryOp op);
void xaccQueryAddBalanceMatch(Query * q, balance_match_t how, QueryOp op);
void xaccQueryAddGUIDMatch(Query * q, const GUID *guid, QueryOp op);
void xaccQueryAddMiscMatch(Query * q, Predicate p, int how, int data,
QueryOp op);
void xaccQueryAddPredicate (Query * q, PredicateData *pred, QueryOp op);
/*******************************************************************
* sort-related functions
*******************************************************************/
void xaccQuerySetSortOrder(Query * q, sort_type_t primary,
sort_type_t secondary, sort_type_t tertiary);
sort_type_t xaccQueryGetPrimarySortOrder(Query * q);
sort_type_t xaccQueryGetSecondarySortOrder(Query * q);
sort_type_t xaccQueryGetTertiarySortOrder(Query * q);
void xaccQuerySetSortIncreasing(Query * q,
gboolean prim_increasing,
gboolean sec_increasing,
gboolean tert_increasing);
gboolean xaccQueryGetSortPrimaryIncreasing (Query *q);
gboolean xaccQueryGetSortSecondaryIncreasing (Query *q);
gboolean xaccQueryGetSortTertiaryIncreasing (Query *q);
void xaccQuerySetMaxSplits(Query * q, int n);
int xaccQueryGetMaxSplits(Query * q);
/*******************************************************************
* compatibility interface with old Query API
*******************************************************************/
time_t xaccQueryGetEarliestDateFound(Query * q);
time_t xaccQueryGetLatestDateFound(Query * q);
/* This is useful for network systems */
Predicate xaccQueryGetPredicate (pr_type_t term_type);
#endif

14
src/engine/README Normal file
View File

@ -0,0 +1,14 @@
This directory contains code for the accounting engine.
Its fairly clean but far from perfect, and it certainly
lacks advanced features.
There should be no GUI code in this subdirectory, and,
ideally, it should build cleanly and independently of
any GUI elements or assumptions.
For design documentation, please see the file "design.txt",
and also, look at the header files carefully. The documentation
for each routine is in the header files for that routine.
September 1998

205
src/engine/README.query-api Normal file
View File

@ -0,0 +1,205 @@
Gnucash Query API
BASIC QUERY API: With this API you can create arbitrary logical
queries to find sets of splits in an account group. To make simple
queries (1 term, such as an account query), create the appropriate
QueryTerm structure and stick it in a Query object using
xaccInitQuery. The QueryTerm should be malloced but the Query object
will handle freeing it. To make compound queries, make multiple
simple queries and combine them using xaccMergeQuery and the logical
operations of your choice.
-----------------------------------------------------------------
Query * xaccMallocQuery()
Allocates and initializes a Query structure which must be freed by the
user with xaccFreeQuery. A newly-allocated Query object matches
nothing (xaccQueryGetSplits will return NULL).
-----------------------------------------------------------------
void xaccInitQuery(Query * q, QueryTerm * qt)
Initializes an allocated Query object with initial term qt (possibly
NULL). Any previous query terms are freed.
-----------------------------------------------------------------
void xaccFreeQuery(Query * q)
Frees the resources associate with a Query object.
-----------------------------------------------------------------
void xaccQuerySetGroup(Query * q, AccountGroup * group)
Set the Gnucash account group that the query applies to.
xaccQuerySetGroup must be called before a Query object created with
xaccMallocQuery can be used. Queries created with xaccQueryInvert and
xaccQueryMerge inherit the account group of the arguments to those
functions.
-----------------------------------------------------------------
Query * xaccQueryInvert(Query * q)
Logically invert the query. xaccInvertQuery returns a newly allocated
Query object such that the union of the splits matched by query q and
query (p = xaccQueryInvert(q)) is the entire account group that q
applies to.
-----------------------------------------------------------------
Query * xaccQueryMerge(Query * q1, Query * q2, QueryOp how)
Combine queries q1 and q2 using logical operator 'how'. 'how' must be
one of QUERY_AND, QUERY_OR, QUERY_NAND, QUERY_NOR, QUERY_XOR. The
account groups of q1 and q2 must be the same. xaccQueryMerge returns
a newly-allocated Query object or NULL on error.
-----------------------------------------------------------------
void xaccQueryClear(Query * q)
Remove all query terms from q. q matches nothing after xaccQueryClear.
-----------------------------------------------------------------
void xaccQueryPurgeTerms(Query * q, pd_type_t type);
Remove query terms of a particular type from q. The "type" of a term
is determined by the type of data that gets passed to the predicate
function. The currently-supported values of 'type' are PD_DATE,
PD_AMOUNT, PD_ACCOUNT, PD_STRING, PD_CLEARED, PD_MISC. This function
is really only used in one place: in window-register.c, to modify
in-place a query to remove any date tests prior to adding new ones.
This should probably be removed from the API in favor of an extra
argument to xaccQueryMerge specifying what to do with existing terms
of that type.
-----------------------------------------------------------------
int xaccQueryHasTerms(Query * q)
Returns the number of terms in the canonical form of the query. Can
be used as a predicate to see if the query has been initialized
(return value > 0) or is "blank" (return value == 0).
-----------------------------------------------------------------
CONVENIENCE API: The remainder of the API (in particular, any function
called xaccQueryAdd***Match) is a set of convenience functions for
creating and modifying specific types of queries. All of these
functions can be duplicated using the Basic API specified above,
directly manipulating QueryTerm objects and creating and merging
queries as needed. One slight advantage of the convenience API is
that it uses a standard set of predicates that are more-or-less
opaque. This may be important later.
It's probably more useful to describe the various types of
PredicateData than the convenience functions, which are pretty
self-explanatory once you understand what the underlying process is.
For example, AddMemoMatch and AddDescriptionMatch are essentially the
same function because they both use PD_STRING predicate data; they
just use a different predicate (one compares data.string.matchstring
with the split's Memo, one compares with the parent transaction's
Description).
Each function in the convenience API takes a Query *, some arguments
which fill in the fields of the appropriate PredicateData type, and a
QueryOp. The Query object is modified in place, using the logical
operation specified by the QueryOp to combine a single new QueryTerm
with the existing Query. This works by making a new Query of one term
and combining with the existing Query using xaccQueryMerge and the
specified QueryOp. If you have an existing Query (a + b + c) and
combine using QueryOp QUERY_AND in a convenience function representing
predicate d, you will get (ad + bd + cd).
STRUCTURE OF A QUERY: A Query is a logical function of any number of
QueryTerms. A QueryTerm consists of a C function pointer (the
Predicate) and a PredicateData structure containing data passed to the
predicate funtion. The PredicateData structure is a constant
associated with the Term and is identical for every Split that is
tested.
The terms of the Query may represent any logical function and are
stored in canonical form, i.e. the function is expressed as a logical
sum of logical products. So if you have QueryTerms a, b, c, d, e and
you have the logical function a(b+c) + !(c(d+e)), it gets stored as
ab + ac + !c + !c!e +!d!c + !d!e. This may not be optimal for evaluation
of some functions but it's easy to store, easy to manipulate, and it
doesn't require a complete algebra system to deal with.
The representation is of a GList of GLists of QueryTerms. The
"backbone" GList q->terms represents the OR-chain, and every item on
the backbone is a GList of QueryTerms representing an AND-chain
corresponding to a single product-term in the canonical
representation. QueryTerms are duplicated when necessary to fill out
the canonical form, and the same predicate may be evaluated multiple
times per split for complex queries. This is a place where we could
probably optimize.
Evaluation of a Query (see xaccQueryGetSplits) is optimized as much as
possible by short-circuited evaluation. The predicates in each
AND-chain are sorted by predicate type, with Account queries sorted
first to allow the evaluator to completely eliminate accounts from the
search if there's no chance of them having splits that match.
PREDICATE DATA TYPES: All the predicate data types are rolled up into
the union type PredicateData. The "type" field specifies which type
the union is. The values of type are:
-----------------------------------------------------------------
PD_DATE : match a date range. Specify a start date and an end date.
Used in: xaccQueryAddDateMatch
xaccQueryAddDateMatchTS
xaccQueryAddDateMatchTT
-----------------------------------------------------------------
PD_AMOUNT : match a numeric amount. Specify an amount (always
positive), a funds-flow direction (credit, debit, or either), and
"how", specifying the type of amount comparison to be used :
AMT_MATCH_ATLEAST : split >= pd amount
AMT_MATCH_ATMOST : split >= pd amount
AMT_MATCH_EXACTLY : split == pd amount
Used in: xaccQueryAddAmountMatch
xaccQueryAddSharePriceMatch
xaccQueryAddSharesMatch
-----------------------------------------------------------------
PD_ACCOUNT : match an account or set of accounts. Specify a set
of accounts and "how":
ACCT_MATCH_ALL : a transaction must have at least one split
affecting each account in pd.acct.accounts.
ACCT_MATCH_ANY : a transaction must have at least one split
affecting any account in the set
ACCT_MATCH_NONE : a transaction may not affect any account in
the set.
Used in: xaccQueryAddAccountMatch
xaccQueryAddSingleAccountMatch
-----------------------------------------------------------------
PD_STRING : match a string. Specify a string, bool signifying
case sensitivity, bool signifying regexp or simple string.
Used in: xaccQueryAddDescriptionMatch
xaccQueryAddNumberMatch
xaccQueryAddActionMatch
xaccQueryAddMemoMatch
-----------------------------------------------------------------
PD_CLEARED : match the Cleared state of the transaction. Specify
a bit-mask that is an OR combination of one or more of the
following:
CLEARED_NO (state == 'n')
CLEARED_CLEARED (state == 'c')
CLEARED_RECONCILED (state == 'y')
Used in: xaccQueryAddClearedMatch
-----------------------------------------------------------------
PD_MISC : match some "other" user predicate. Not used at the moment.
-----------------------------------------------------------------

486
src/engine/SchedXaction.c Normal file
View File

@ -0,0 +1,486 @@
/********************************************************************\
* SchedXaction.c -- Scheduled Transaction implementation. *
* Copyright (C) 2001 Joshua Sled <jsled@asynchronous.org> *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License*
* along with this program; if not, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
* Boston, MA 02111-1307, USA gnu@gnu.org *
* *
\********************************************************************/
#include "config.h"
#include <glib.h>
#include <string.h>
#include "SchedXaction.h"
#include "FreqSpec.h"
#include "GNCIdP.h"
#include "Transaction.h"
#include "TransactionP.h"
#include "date.h"
#include "gnc-engine-util.h"
#include "gnc-event-p.h"
#include "messages.h"
#include "Account.h"
#include "Group.h"
#include "guid.h"
#include "gnc-book.h"
#include "FileDialog.h"
static short module = MOD_SX;
/** Local data defs *****/
void sxprivtransactionListMapDelete( gpointer data, gpointer user_data );
/** Local Prototypes *****/
static void
xaccSchedXactionInit( SchedXaction *sx, GNCBook *book)
{
AccountGroup *ag;
char *name;
sx->freq = xaccFreqSpecMalloc();
xaccGUIDNew( &sx->guid );
xaccStoreEntity( sx, &sx->guid, GNC_ID_SCHEDXACTION );
g_date_clear( &sx->last_date, 1 );
g_date_clear( &sx->start_date, 1 );
g_date_clear( &sx->end_date, 1 );
sx->num_occurances_total = -1;
sx->kvp_data = kvp_frame_new();
sx->autoCreateOption = FALSE;
sx->autoCreateNotify = FALSE;
sx->advanceCreateDays = 0;
sx->advanceRemindDays = 0;
sx->dirty = TRUE;
/* create a new template account for our splits */
sx->template_acct = xaccMallocAccount();
name = guid_to_string( &sx->guid );
xaccAccountSetName( sx->template_acct, name );
xaccAccountSetCommodity( sx->template_acct,
gnc_commodity_new( "template", "template",
"template", "template", 1 ) );
g_free( name );
xaccAccountSetType( sx->template_acct, BANK );
ag = gnc_book_get_template_group( book );
xaccGroupInsertAccount( ag, sx->template_acct );
}
SchedXaction*
xaccSchedXactionMalloc( GNCBook *book )
{
SchedXaction *sx;
sx = g_new0( SchedXaction, 1 );
xaccSchedXactionInit( sx, book );
gnc_engine_generate_event( &sx->guid, GNC_EVENT_CREATE );
return sx;
}
void
sxprivtransactionListMapDelete( gpointer data, gpointer user_data )
{
Transaction *t = (Transaction*)data;
xaccTransBeginEdit( t );
xaccTransDestroy( t );
xaccTransCommitEdit( t );
return;
}
void
sxprivsplitListMapDelete( gpointer data, gpointer user_data )
{
Split *s = (Split *) data;
Transaction *t = xaccSplitGetParent(s);
xaccTransBeginEdit( t );
xaccTransDestroy( t );
xaccTransCommitEdit( t );
}
void
xaccSchedXactionFree( SchedXaction *sx )
{
AccountGroup *group;
GList *templ_acct_splits;
if ( sx == NULL ) return;
xaccFreqSpecFree( sx->freq );
gnc_engine_generate_event( &sx->guid, GNC_EVENT_DESTROY );
xaccRemoveEntity( &sx->guid );
if ( sx->name )
g_free( sx->name );
/*
* we have to delete the transactions in the
* template account ourselves
*/
templ_acct_splits
= xaccAccountGetSplitList(sx->template_acct);
g_list_foreach(templ_acct_splits,
sxprivsplitListMapDelete,
NULL);
/*
* xaccAccountDestroy removes the account from
* its group for us AFAICT
*/
xaccAccountBeginEdit(sx->template_acct);
xaccAccountDestroy(sx->template_acct);
g_free( sx );
return;
}
FreqSpec *
xaccSchedXactionGetFreqSpec( SchedXaction *sx )
{
return sx->freq;
}
void
xaccSchedXactionSetFreqSpec( SchedXaction *sx, FreqSpec *fs )
{
g_return_if_fail( fs );
xaccFreqSpecFree( sx->freq );
sx->freq = fs;
sx->dirty = TRUE;
}
gchar *
xaccSchedXactionGetName( SchedXaction *sx )
{
return sx->name;
}
void
xaccSchedXactionSetName( SchedXaction *sx, const gchar *newName )
{
g_return_if_fail( newName != NULL );
if ( sx->name != NULL ) {
g_free( sx->name );
sx->name = NULL;
}
sx->dirty = TRUE;
sx->name = g_strdup( newName );
}
GDate*
xaccSchedXactionGetStartDate( SchedXaction *sx )
{
return &sx->start_date;
}
void
xaccSchedXactionSetStartDate( SchedXaction *sx, GDate* newStart )
{
sx->start_date = *newStart;
sx->dirty = TRUE;
}
gboolean
xaccSchedXactionHasEndDate( SchedXaction *sx )
{
return g_date_valid( &sx->end_date );
}
GDate*
xaccSchedXactionGetEndDate( SchedXaction *sx )
{
return &sx->end_date;
}
void
xaccSchedXactionSetEndDate( SchedXaction *sx, GDate *newEnd )
{
if ( g_date_valid( newEnd ) ) {
if ( g_date_compare( newEnd, &sx->start_date ) < 0 ) {
/* XXX: I reject the bad data - is this the right
* thing to do <rgmerk>.
* This warning is only human readable - the caller
* doesn't know the call failed. This is bad
*/
PWARN( "New end date before start date" );
}
else
{
sx->end_date = *newEnd;
sx->dirty = TRUE;
}
}
else
{
PWARN("New end date invalid");
}
return;
}
GDate*
xaccSchedXactionGetLastOccurDate( SchedXaction *sx )
{
return &sx->last_date;
}
void
xaccSchedXactionSetLastOccurDate( SchedXaction *sx, GDate* newLastOccur )
{
sx->last_date = *newLastOccur;
sx->dirty = TRUE;
return;
}
gboolean
xaccSchedXactionHasOccurDef( SchedXaction *sx )
{
return ( xaccSchedXactionGetNumOccur( sx ) != 0 );
}
gint
xaccSchedXactionGetNumOccur( SchedXaction *sx )
{
return sx->num_occurances_total;
}
void
xaccSchedXactionSetNumOccur( SchedXaction *sx, gint newNum )
{
sx->num_occurances_remain = sx->num_occurances_total = newNum;
sx->dirty = TRUE;
}
gint
xaccSchedXactionGetRemOccur( SchedXaction *sx )
{
return sx->num_occurances_remain;
}
void
xaccSchedXactionSetRemOccur( SchedXaction *sx,
gint numRemain )
{
/* FIXME This condition can be tightened up */
if ( numRemain > sx->num_occurances_total ) {
PWARN("The number remaining is greater than the \
total occurrences");
}
else
{
sx->num_occurances_remain = numRemain;
sx->dirty = TRUE;
}
return;
}
kvp_value *
xaccSchedXactionGetSlot( SchedXaction *sx, const char *slot )
{
if (!sx)
{
return NULL;
}
return kvp_frame_get_slot(sx->kvp_data, slot);
}
void
xaccSchedXactionSetSlot( SchedXaction *sx,
const char *slot,
const kvp_value *value )
{
if (!sx)
{
return;
}
kvp_frame_set_slot( sx->kvp_data, slot, value );
sx->dirty = TRUE;
return;
}
kvp_frame*
xaccSchedXactionGetSlots( SchedXaction *sx )
{
return sx->kvp_data;
}
void
xaccSchedXactionSetSlots( SchedXaction *sx, kvp_frame *frm )
{
sx->kvp_data = frm;
sx->dirty = TRUE;
}
const GUID*
xaccSchedXactionGetGUID( SchedXaction *sx )
{
return &sx->guid;
}
void
xaccSchedXactionSetGUID( SchedXaction *sx, GUID g )
{
sx->guid = g;
sx->dirty = TRUE;
}
void
xaccSchedXactionGetAutoCreate( SchedXaction *sx,
gboolean *outAutoCreate,
gboolean *outNotify )
{
*outAutoCreate = sx->autoCreateOption;
*outNotify = sx->autoCreateNotify;
return;
}
void
xaccSchedXactionSetAutoCreate( SchedXaction *sx,
gboolean newAutoCreate,
gboolean newNotify )
{
sx->autoCreateOption = newAutoCreate;
sx->autoCreateNotify = newNotify;
sx->dirty = TRUE;
return;
}
gint
xaccSchedXactionGetAdvanceCreation( SchedXaction *sx )
{
return sx->advanceCreateDays;
}
void
xaccSchedXactionSetAdvanceCreation( SchedXaction *sx, gint createDays )
{
sx->advanceCreateDays = createDays;
sx->dirty = TRUE;
}
gint
xaccSchedXactionGetAdvanceReminder( SchedXaction *sx )
{
return sx->advanceRemindDays;
}
void
xaccSchedXactionSetAdvanceReminder( SchedXaction *sx, gint reminderDays )
{
sx->dirty = TRUE;
sx->advanceRemindDays = reminderDays;
}
GDate
xaccSchedXactionGetNextInstance( SchedXaction *sx )
{
GDate last_occur, next_occur, tmpDate;
g_date_clear( &last_occur, 1 );
g_date_clear( &next_occur, 1 );
g_date_clear( &tmpDate, 1 );
if ( g_date_valid( &sx->last_date ) ) {
last_occur = sx->last_date;
}
if ( g_date_valid( &sx->start_date ) ) {
if ( g_date_valid(&last_occur) ) {
last_occur =
( g_date_compare( &last_occur,
&sx->start_date ) > 0 ?
last_occur : sx->start_date );
} else {
last_occur = sx->start_date;
}
}
#if 0
if ( g_date_valid( &last_occur ) ) {
g_date_set_time( &tmpDate, time(NULL) );
last_occur =
( g_date_compare( &last_occur,
&tmpDate ) > 0 ?
last_occur : tmpDate );
} else {
g_date_set_time( &last_occur, time(NULL) );
}
#endif /* 0 */
if ( g_date_valid( &sx->start_date )
&& ! g_date_valid( &sx->last_date ) ) {
/* Think about this for a second, and you realize
* that if the start date is _today_, we need a
* last-occur date such that the 'next instance' is
* after that date... one day should be good.
*
* This only holds for the first instance [read: if the
* last[-occur]_date is invalid. */
g_date_subtract_days( &last_occur, 1 );
}
xaccFreqSpecGetNextInstance( sx->freq, &last_occur, &next_occur );
return next_occur;
}
GDate xaccSchedXactionGetInstanceAfter( SchedXaction *sx, GDate *date )
{
GDate next_occur;
xaccFreqSpecGetNextInstance( sx->freq, date, &next_occur );
return next_occur;
}
/*
* XXX: This does what you want, I think <rgmerk>
*/
GList *
xaccSchedXactionGetSplits( SchedXaction *sx )
{
g_return_val_if_fail( sx, NULL );
return xaccAccountGetSplitList(sx->template_acct);
}
void
xaccSchedXactionSetDirtyness( SchedXaction *sx, gboolean dirty_p)
{
sx->dirty = dirty_p;
return;
}
gboolean
xaccSchedXactionIsDirty(SchedXaction *sx)
{
return sx->dirty;
}

214
src/engine/SchedXaction.h Normal file
View File

@ -0,0 +1,214 @@
/********************************************************************\
* SchedXaction.h -- Scheduled Transaction *
* Copyright (C) 2001 Joshua Sled <jsled@asynchronous.org> *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License*
* along with this program; if not, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
* Boston, MA 02111-1307, USA gnu@gnu.org *
* *
\********************************************************************/
#ifndef XACC_SCHEDXACTION_H
#define XACC_SCHEDXACTION_H
#include "config.h"
#include <time.h>
#include <glib.h>
#include "GNCId.h"
#include "FreqSpec.h"
#include "date.h"
#include "kvp_frame.h"
#include "gnc-book.h"
/**
* A single scheduled transaction.
*
* Scheduled transactions have a list of transactions, and a frequency
* [and associated date anchors] with which they are scheduled.
*
* Things that make sense to have in a template transaction:
* [not] Date [though eventually some/multiple template transactions
* might have relative dates].
* Memo
* Account
* Funds In/Out... or an expr involving 'amt' [A, x, y, a?] for
* variable expenses.
*
* Template transactions are instantiated by:
* . copying the fields of the template
* . setting the date to the calculated "due" date.
*
* We should be able to use the GeneralLedger [or, yet-another-subtype
* of the internal ledger] for this editing.
**/
typedef struct gncp_SchedXaction {
gchar *name;
FreqSpec *freq;
GDate last_date;
GDate start_date;
/* if end_date is invalid, then no end. */
GDate end_date;
/* if num_occurances_total == 0, then no limit */
gint num_occurances_total;
/* reminaing occurances are as-of the 'last_date'. */
gint num_occurances_remain;
gboolean autoCreateOption;
gboolean autoCreateNotify;
gint advanceCreateDays;
gint advanceRemindDays;
Account *template_acct;
GUID guid;
/* Changed since last save? */
gboolean dirty;
kvp_frame *kvp_data;
} SchedXaction;
/**
* Creates and initializes a scheduled transaction.
**/
SchedXaction *xaccSchedXactionMalloc( GNCBook *book);
/*
* returns true if the scheduled transaction is dirty and needs to
* be saved
*/
gboolean xaccSchedXactionIsDirty(SchedXaction *sx);
/*
* Set dirtyness state. Only save/load code should modify this outside
* SX engine CODE . . .
* (set it to FALSE after backend completes reading in data
*
* FIXME: put this into a private header . . . .
*/
void xaccSchedXactionSetDirtyness(SchedXaction *sx, gboolean dirty_p);
/*
* Cleans up and frees a SchedXaction and it's associated data.
**/
void xaccSchedXactionFree( SchedXaction *sx );
FreqSpec *xaccSchedXactionGetFreqSpec( SchedXaction *sx );
/**
* The FreqSpec is given to the SchedXaction for mem mgmt; it should
* not be freed by the external code.
**/
void xaccSchedXactionSetFreqSpec( SchedXaction *sx, FreqSpec *fs );
gchar *xaccSchedXactionGetName( SchedXaction *sx );
/**
* A copy of the name is made.
**/
void xaccSchedXactionSetName( SchedXaction *sx, const gchar *newName );
GDate* xaccSchedXactionGetStartDate( SchedXaction *sx );
void xaccSchedXactionSetStartDate( SchedXaction *sx, GDate* newStart );
int xaccSchedXactionHasEndDate( SchedXaction *sx );
/**
* Returns invalid date when there is no end-date specified.
**/
GDate* xaccSchedXactionGetEndDate( SchedXaction *sx );
void xaccSchedXactionSetEndDate( SchedXaction *sx, GDate* newEnd );
GDate* xaccSchedXactionGetLastOccurDate( SchedXaction *sx );
void xaccSchedXactionSetLastOccurDate( SchedXaction *sx, GDate* newLastOccur );
/**
* Returns true if the scheduled transaction has a defined number of
* occurances, false if not.
**/
gboolean xaccSchedXactionHasOccurDef( SchedXaction *sx );
gint xaccSchedXactionGetNumOccur( SchedXaction *sx );
void xaccSchedXactionSetNumOccur( SchedXaction *sx, gint numNum );
gint xaccSchedXactionGetRemOccur( SchedXaction *sx );
void xaccSchedXactionSetRemOccur( SchedXaction *sx, gint numRemain );
GList *xaccSchedXactionGetSplits( SchedXaction *sx );
void xaccSchedXactionSetSplits( SchedXaction *sx, GList *newSplits );
void xaccSchedXactionGetAutoCreate( SchedXaction *sx, gboolean *outAutoCreate, gboolean *outNotify );
void xaccSchedXactionSetAutoCreate( SchedXaction *sx, gboolean newAutoCreate, gboolean newNotify );
gint xaccSchedXactionGetAdvanceCreation( SchedXaction *sx );
void xaccSchedXactionSetAdvanceCreation( SchedXaction *sx, gint createDays );
gint xaccSchedXactionGetAdvanceReminder( SchedXaction *sx );
void xaccSchedXactionSetAdvanceReminder( SchedXaction *sx, gint reminderDays );
#if 0
#error vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
GList *xaccSchedXactionGetXactions( SchedXaction *sx );
void xaccSchedXactionClearXactions( SchedXaction *sx );
void xaccSchedXactionAddXaction( SchedXaction *sx,
Transaction *t );
#error ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
#endif /* 0 */
/*
* The following function is slightly risky. If you change
* the retrieved kvp_frame you must mark the SchedXaction
* dirty with xaccSchedXactionSetDirtyness
*/
kvp_frame *xaccSchedXactionGetSlots( SchedXaction *sx );
/**
* Sets the SX kvp data to the given kvp_frame.
* NOTE: This is not copied, but set directly.
**/
void xaccSchedXactionSetSlots( SchedXaction *sx,
kvp_frame *frm );
/**
* Use the following two functions in preference to
* the above two . . .
*/
kvp_value *xaccSchedXactionGetSlot( SchedXaction *sx,
const char *slot );
/*
* This function copies value, so you don't have to
*/
void xaccSchedXactionSetSlot( SchedXaction *sx,
const char *slot,
const kvp_value *value );
const GUID *xaccSchedXactionGetGUID( SchedXaction *sx );
void xaccSchedXactionSetGUID( SchedXaction *sx, GUID g );
/**
* Returns the next occurance of a scheduled transaction. If the
* transaction hasn't occured, then it's based off the start date.
* Otherwise, it's based off the last-occurance date.
**/
GDate xaccSchedXactionGetNextInstance( SchedXaction *sx );
GDate xaccSchedXactionGetInstanceAfter( SchedXaction *sx, GDate *date );
#endif /* XACC_SCHEDXACTION_H */

506
src/engine/Scrub.c Normal file
View File

@ -0,0 +1,506 @@
/********************************************************************\
* Scrub.c -- convert single-entry accounts into clean double-entry *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License*
* along with this program; if not, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
* Boston, MA 02111-1307, USA gnu@gnu.org *
* *
\********************************************************************/
/*
* FILE:
* Scrub.c
*
* FUNCTION:
* Provides a set of functions and utilities for scrubbing clean
* single-entry accounts so that they can be promoted into
* self-consistent, clean double-entry accounts.
*
* HISTORY:
* Created by Linas Vepstas December 1998
* Copyright (c) 1998, 1999, 2000 Linas Vepstas
*/
#include "config.h"
#include <glib.h>
#include <stdio.h>
#include <string.h>
#include "Account.h"
#include "Group.h"
#include "GroupP.h"
#include "Scrub.h"
#include "Transaction.h"
#include "TransactionP.h"
#include "gnc-engine-util.h"
#include "messages.h"
static short module = MOD_SCRUB;
static Account * GetOrMakeAccount (AccountGroup *root, Transaction *trans,
const char *name_root);
/* ================================================================ */
void
xaccGroupScrubOrphans (AccountGroup *grp)
{
GList *list;
GList *node;
if (!grp)
return;
list = xaccGroupGetAccountList (grp);
for (node = list; node; node = node->next)
{
Account *account = node->data;
xaccAccountTreeScrubOrphans (account);
}
}
void
xaccAccountTreeScrubOrphans (Account *acc)
{
if (!acc)
return;
xaccGroupScrubOrphans (xaccAccountGetChildren(acc));
xaccAccountScrubOrphans (acc);
}
void
xaccAccountScrubOrphans (Account *acc)
{
GList *node;
const char *str;
if (!acc)
return;
str = xaccAccountGetName (acc);
str = str ? str : "(null)";
PINFO ("Looking for orphans in account %s \n", str);
for (node = xaccAccountGetSplitList(acc); node; node = node->next)
{
Split *split = node->data;
xaccTransScrubOrphans (xaccSplitGetParent (split),
xaccGetAccountRoot (acc));
}
}
void
xaccTransScrubOrphans (Transaction *trans, AccountGroup *root)
{
GList *node;
if (!trans)
return;
for (node = xaccTransGetSplitList (trans); node; node = node->next)
{
Split *split = node->data;
Account *account;
Account *orph;
account = xaccSplitGetAccount (split);
if (account)
continue;
DEBUG ("Found an orphan \n");
orph = GetOrMakeAccount (root, trans, _("Orphan"));
xaccAccountBeginEdit (orph);
xaccAccountInsertSplit (orph, split);
xaccAccountCommitEdit (orph);
}
}
/* ================================================================ */
void
xaccGroupScrubSplits (AccountGroup *group)
{
GList *list;
GList *node;
if (!group) return;
list = xaccGroupGetAccountList (group);
for (node = list; node; node = node->next)
{
Account *account = node->data;
xaccAccountTreeScrubSplits (account);
}
}
void
xaccAccountTreeScrubSplits (Account *account)
{
xaccGroupScrubSplits (xaccAccountGetChildren(account));
xaccAccountScrubSplits (account);
}
void
xaccAccountScrubSplits (Account *account)
{
GList *node;
for (node = xaccAccountGetSplitList (account); node; node = node->next)
xaccSplitScrub (node->data);
}
void
xaccTransScrubSplits (Transaction *trans)
{
GList *node;
if (!trans)
return;
for (node = trans->splits; node; node = node->next)
xaccSplitScrub (node->data);
}
void
xaccSplitScrub (Split *split)
{
Account *account;
Transaction *trans;
gnc_numeric value;
gboolean trans_was_open;
gnc_commodity *commodity;
gnc_commodity *currency;
int scu;
if (!split)
return;
trans = xaccSplitGetParent (split);
if (!trans)
return;
account = xaccSplitGetAccount (split);
if (!account)
{
value = xaccSplitGetValue (split);
if (gnc_numeric_same (xaccSplitGetAmount (split),
xaccSplitGetValue (split),
value.denom, GNC_RND_ROUND))
return;
xaccSplitSetAmount (split, value);
return;
}
commodity = xaccAccountGetCommodity (account);
currency = xaccTransGetCurrency (trans);
if (!commodity || !gnc_commodity_equiv (commodity, currency))
return;
scu = MIN (xaccAccountGetCommoditySCU (account),
gnc_commodity_get_fraction (currency));
value = xaccSplitGetValue (split);
if (gnc_numeric_same (xaccSplitGetAmount (split),
value, scu, GNC_RND_ROUND))
return;
PINFO ("split with mismatched values");
trans_was_open = xaccTransIsOpen (trans);
if (!trans_was_open)
xaccTransBeginEdit (trans);
xaccSplitSetAmount (split, value);
if (!trans_was_open)
xaccTransCommitEdit (trans);
}
/* ================================================================ */
void
xaccGroupScrubImbalance (AccountGroup *grp)
{
GList *list;
GList *node;
if (!grp) return;
list = xaccGroupGetAccountList (grp);
for (node = list; node; node = node->next)
{
Account *account = node->data;
xaccAccountTreeScrubImbalance (account);
}
}
void
xaccAccountTreeScrubImbalance (Account *acc)
{
xaccGroupScrubImbalance (xaccAccountGetChildren(acc));
xaccAccountScrubImbalance (acc);
}
void
xaccAccountScrubImbalance (Account *acc)
{
GList *node;
const char *str;
str = xaccAccountGetName(acc);
str = str ? str : "(null)";
PINFO ("Looking for imbalance in account %s \n", str);
for(node = xaccAccountGetSplitList(acc); node; node = node->next)
{
Split *split = node->data;
Transaction *trans = xaccSplitGetParent(split);
xaccTransScrubImbalance (trans, xaccGetAccountRoot (acc), NULL);
}
}
void
xaccTransScrubImbalance (Transaction *trans, AccountGroup *root,
Account *parent)
{
Split *balance_split = NULL;
gnc_numeric imbalance;
if (!trans)
return;
xaccTransScrubSplits (trans);
{
Account *account;
GList *node;
imbalance = xaccTransGetImbalance (trans);
if (gnc_numeric_zero_p (imbalance))
return;
if (!parent)
account = GetOrMakeAccount (root, trans, _("Imbalance"));
else
account = parent;
for (node = xaccTransGetSplitList (trans); node; node = node->next)
{
Split *split = node->data;
if (xaccSplitGetAccount (split) == account)
{
balance_split = split;
break;
}
}
/* put split into account before setting split value */
if (!balance_split)
{
balance_split = xaccMallocSplit ();
xaccAccountBeginEdit (account);
xaccAccountInsertSplit (account, balance_split);
xaccAccountCommitEdit (account);
}
}
PINFO ("unbalanced transaction");
{
const gnc_commodity *currency;
const gnc_commodity *commodity;
gboolean trans_was_open;
gnc_numeric new_value;
Account *account;
trans_was_open = xaccTransIsOpen (trans);
if (!trans_was_open)
xaccTransBeginEdit (trans);
currency = xaccTransGetCurrency (trans);
account = xaccSplitGetAccount (balance_split);
new_value = xaccSplitGetValue (balance_split);
new_value = gnc_numeric_sub (new_value, imbalance,
new_value.denom, GNC_RND_ROUND);
xaccSplitSetValue (balance_split, new_value);
commodity = xaccAccountGetCommodity (account);
if (gnc_commodity_equiv (currency, commodity))
xaccSplitSetAmount (balance_split, new_value);
if (!parent && gnc_numeric_zero_p (new_value))
{
xaccSplitDestroy (balance_split);
balance_split = NULL;
}
if (balance_split)
xaccTransAppendSplit (trans, balance_split);
xaccSplitScrub (balance_split);
if (!trans_was_open)
xaccTransCommitEdit (trans);
}
}
/* ================================================================ */
void
xaccTransScrubCurrency (Transaction *trans)
{
gnc_commodity *currency;
if (!trans) return;
currency = xaccTransGetCurrency (trans);
if (currency) return;
currency = xaccTransFindOldCommonCurrency (trans);
if (currency)
{
xaccTransBeginEdit (trans);
xaccTransSetCurrency (trans, currency);
xaccTransCommitEdit (trans);
}
else
{
PWARN ("no common transaction currency found");
}
}
/* ================================================================ */
void
xaccAccountScrubCommodity (Account *account)
{
gnc_commodity *commodity;
if (!account) return;
commodity = xaccAccountGetCommodity (account);
if (commodity) return;
commodity = DxaccAccountGetSecurity (account);
if (commodity)
{
xaccAccountSetCommodity (account, commodity);
return;
}
commodity = DxaccAccountGetCurrency (account);
if (commodity)
{
xaccAccountSetCommodity (account, commodity);
return;
}
PERR ("account with no commodity");
}
/* ================================================================ */
static gboolean
scrub_trans_currency_helper (Transaction *t, void *unused)
{
xaccTransScrubCurrency (t);
return TRUE;
}
static gpointer
scrub_account_commodity_helper (Account *account, gpointer unused)
{
xaccAccountScrubCommodity (account);
xaccAccountDeleteOldData (account);
return NULL;
}
void
xaccGroupScrubCommodities (AccountGroup *group)
{
if (!group) return;
xaccAccountGroupBeginEdit (group);
xaccGroupForEachTransaction (group, scrub_trans_currency_helper, NULL);
xaccGroupForEachAccount (group, scrub_account_commodity_helper, NULL, TRUE);
xaccAccountGroupCommitEdit (group);
}
/* ================================================================ */
static Account *
GetOrMakeAccount (AccountGroup *root, Transaction *trans,
const char *name_root)
{
gnc_commodity * currency;
char * accname;
Account * acc;
/* build the account name */
currency = xaccTransGetCurrency (trans);
accname = g_strconcat (name_root, "-",
gnc_commodity_get_mnemonic (currency), NULL);
/* see if we've got one of these going already ... */
acc = xaccGetAccountFromName (root, accname);
if (acc == NULL)
{
/* guess not. We'll have to build one */
acc = xaccMallocAccount ();
xaccAccountBeginEdit (acc);
xaccAccountSetName (acc, accname);
xaccAccountSetCommodity (acc, currency);
xaccAccountSetType (acc, BANK);
/* hang the account off the root */
xaccGroupInsertAccount (root, acc);
xaccAccountCommitEdit (acc);
}
g_free (accname);
return acc;
}
/* ==================== END OF FILE ==================== */

99
src/engine/Scrub.h Normal file
View File

@ -0,0 +1,99 @@
/********************************************************************\
* Scrub.h -- convert single-entry accounts to clean double-entry *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License*
* along with this program; if not, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
* Boston, MA 02111-1307, USA gnu@gnu.org *
* *
\********************************************************************/
/*
* FILE:
* Scrub.h
*
* FUNCTION:
* Provides a set of functions and utilities for scrubbing clean
* single-entry accounts so that they can be promoted into
* self-consistent, clean double-entry accounts.
*
* HISTORY:
* Created by Linas Vepstas December 1998
* Copyright (c) 1998, 1999, 2000 Linas Vepstas
*/
#ifndef XACC_SCRUB_H
#define XACC_SCRUB_H
#include "Account.h"
#include "Group.h"
/* The ScrubOrphans() methods search for transacations that contain
* splits that do not have a parent account. These "orphaned splits"
* are placed into an "orphan account" which the user will have to
* go into and clean up. Kind of like the unix "Lost+Found" directory
* for orphaned inodes.
*
* The xaccTransScrubOrphans() method scrubs only the splits in the
* given transaction. A root account group must be provided.
*
* The xaccAccountScrubOrphans() method performs this scrub only for the
* indicated account, and not for any of its children.
*
* The xaccAccountTreeScrubOrphans() method performs this scrub for the
* indicated account and its children.
*
* The xaccGroupScrubOrphans() method performs this scrub for the
* child accounts of this group.
*/
void xaccTransScrubOrphans (Transaction *trans, AccountGroup *root);
void xaccAccountScrubOrphans (Account *acc);
void xaccAccountTreeScrubOrphans (Account *acc);
void xaccGroupScrubOrphans (AccountGroup *grp);
/* The ScrubSplit methods ensure that splits with the same commodity
* and command currency have the same amount and value.
*/
void xaccSplitScrub (Split *split);
void xaccTransScrubSplits (Transaction *trans);
void xaccAccountScrubSplits (Account *account);
void xaccAccountTreeScrubSplits (Account *account);
void xaccGroupScrubSplits (AccountGroup *group);
/* The xaccScrubImbalance() method searches for transactions that do
* not balance to zero. If any such transactions are found, a split
* is created to offset this amount and is added to an "imbalance"
* account.
*/
void xaccTransScrubImbalance (Transaction *trans, AccountGroup *root,
Account *parent);
void xaccAccountScrubImbalance (Account *acc);
void xaccAccountTreeScrubImbalance (Account *acc);
void xaccGroupScrubImbalance (AccountGroup *grp);
/* The xaccTransScrubCurrency method fixes transactions without a
* common_currency by using the old account currency and security
* fields of the parent accounts of the transaction's splits. */
void xaccTransScrubCurrency (Transaction *trans);
/* The xaccAccountScrubCommodity method fixed accounts without
* a commodity by using the old account currency and security. */
void xaccAccountScrubCommodity (Account *account);
/* The xaccGroupScrubCommodities will scrub the currency/commodity
* of all accounts & transactions in the group. */
void xaccGroupScrubCommodities (AccountGroup *group);
#endif /* XACC_SCRUB_H */

363
src/engine/TransLog.c Normal file
View File

@ -0,0 +1,363 @@
/********************************************************************\
* TransLog.c -- the transaction logger *
* Copyright (C) 1998 Linas Vepstas *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License*
* along with this program; if not, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
* Boston, MA 02111-1307, USA gnu@gnu.org *
* *
\********************************************************************/
#define _GNU_SOURCE
#include "config.h"
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <glib.h>
#include "Account.h"
#include "AccountP.h"
#include "DateUtils.h"
#include "date.h"
#include "Transaction.h"
#include "TransactionP.h"
#include "TransLog.h"
#include "gnc-engine-util.h"
/*
* The logfiles are useful for tracing, journalling, error recovery.
* Note that the current support for journalling is at best
* embryonic, at worst, is dangerous by setting the wrong expectations.
*/
/*
* Some design philosphy that I think would be good to keep in mind:
* (0) Simplicity and foolproofness are the over-riding design points.
* This is supposed to be a fail-safe safety net. We don't want
* our safety net to fail because of some whiz-bang shenanigans.
*
* (1) Try to keep the code simple. Want to make it simple and obvious
* that we are recording everything that we need to record.
*
* (2) Keep the printed format human readable, for the same reasons.
* (2.a) Keep the format, simple, flat, more or less unstructured,
* record oriented. This will help parsing by perl scripts.
* No, using a perl script to analyze a file that's supposed to
* be human readable is not a contradication in terms -- that's
* exactly the point.
* (2.b) Use tabs as a human freindly field separator; its also a
* character that does not (should not) appear naturally anywhere
* in the data, as it serves no formatting purpose in the current
* GUI design. (hack alert -- this is not currently tested for
* or enforced, so this is a very unsafe assumption. Maybe
* urlencoding should be used.)
* (2.c) Don't print redundant information in a single record. This
* would just confuse any potential user of this file.
* (2.d) Saving space, being compact is not a priority, I don't think.
*
* (3) There are no compatibility requirements from release to release.
* Sounds OK to me to change the format of the output when needed.
*
* (-) print transaction start and end delimiters
* (-) print a unique transaction id as a handy label for anyone
* who actually examines these logs.
* The C address pointer to the transaction struct should be fine,
* as it is simple and unique until the transaction is deleted ...
* and we log deletions, so that's OK. Just note that the id
* for a deleted transaction might be recycled.
* (-) print the current timestamp, so that if it is known that a bug
* occurred at a certain time, it can be located.
* (-) hack alert -- something better than just the account name
* is needed for identifying the account.
*/
/* ------------------------------------------------------------------ */
/*
* The engine currently uses the log mechanism with flag char set as
* follows:
*
* 'B' for 'begin edit' (followed by the transaction as it looks
* before any changes, i.e. the 'old value')
* 'D' for delete (i.e. delete the previous B; echoes the data in the
* 'old B')
* 'C' for commit (i.e. accept a previous B; data that follows is the
* 'new value')
* 'R' for rollback (i.e. revert to previous B; data that follows should
* be identical to old B)
*/
static int gen_logs = 1;
static FILE * trans_log = NULL;
static char * log_base_name = NULL;
/********************************************************************\
\********************************************************************/
void xaccLogDisable (void) { gen_logs = 0; }
void xaccLogEnable (void) { gen_logs = 1; }
/********************************************************************\
\********************************************************************/
void
xaccLogSetBaseName (const char *basepath)
{
if (!basepath) return;
g_free (log_base_name);
log_base_name = g_strdup (basepath);
if (trans_log) {
xaccCloseLog();
xaccOpenLog();
}
}
/********************************************************************\
\********************************************************************/
void
xaccOpenLog (void)
{
char * filename;
char * timestamp;
if (!gen_logs) return;
if (trans_log) return;
if (!log_base_name) log_base_name = g_strdup ("translog");
/* tag each filename with a timestamp */
timestamp = xaccDateUtilGetStampNow ();
filename = g_strconcat (log_base_name, ".", timestamp, ".log", NULL);
trans_log = fopen (filename, "a");
if (!trans_log) {
int norr = errno;
printf ("Error: xaccOpenLog(): cannot open journal \n"
"\t %d %s\n", norr, strerror (norr));
g_free (filename);
g_free (timestamp);
return;
}
g_free (filename);
g_free (timestamp);
/* use tab-separated fields */
fprintf (trans_log, "mod id time_now " \
"date_entered date_posted " \
"account num description " \
"memo action reconciled " \
"amount price date_reconciled\n");
fprintf (trans_log, "-----------------\n");
}
/********************************************************************\
\********************************************************************/
void
xaccCloseLog (void)
{
if (!trans_log) return;
fflush (trans_log);
fclose (trans_log);
trans_log = NULL;
}
/********************************************************************\
\********************************************************************/
void
xaccTransWriteLog (Transaction *trans, char flag)
{
GList *node;
char *dnow, *dent, *dpost, *drecn;
if (!gen_logs) return;
if (!trans_log) return;
dnow = xaccDateUtilGetStampNow ();
dent = xaccDateUtilGetStamp (trans->date_entered.tv_sec);
dpost = xaccDateUtilGetStamp (trans->date_posted.tv_sec);
fprintf (trans_log, "===== START\n");
for (node = trans->splits; node; node = node->next) {
Split *split = node->data;
const char * accname = "";
if (xaccSplitGetAccount(split))
accname = xaccAccountGetName (xaccSplitGetAccount(split));
drecn = xaccDateUtilGetStamp (split->date_reconciled.tv_sec);
/* use tab-separated fields */
fprintf (trans_log,
"%c\t%p/%p\t%s\t%s\t%s\t%s\t%s\t"
"%s\t%s\t%s\t%c\t%lld/%lld\t%lld/%lld\t%s\n",
flag,
trans, split, /* trans+split make up unique id */
dnow ? dnow : "",
dent ? dent : "",
dpost ? dpost : "",
accname ? accname : "",
trans->num ? trans->num : "",
trans->description ? trans->description : "",
split->memo ? split->memo : "",
split->action ? split->action : "",
split->reconciled,
(long long int) gnc_numeric_num(split->amount),
(long long int) gnc_numeric_denom(split->amount),
(long long int) gnc_numeric_num(split->value),
(long long int) gnc_numeric_denom(split->value),
drecn ? drecn : "");
g_free (drecn);
}
fprintf (trans_log, "===== END\n");
g_free (dnow);
g_free (dent);
g_free (dpost);
/* get data out to the disk */
fflush (trans_log);
}
/********************************************************************\
\********************************************************************/
#if 0
/* open_memstream seems to give various distros fits
* this has resulted in warfare on the mailing list.
* I think the truce called required changing this to asprintf
* this code is not currently used ... so its ifdef out
*/
char *
xaccSplitAsString(Split *split, const char prefix[]) {
char *result = NULL;
size_t result_size;
FILE *stream = open_memstream(&result, &result_size);
const char *split_memo = xaccSplitGetMemo(split);
const double split_value = DxaccSplitGetValue(split);
Account *split_dest = xaccSplitGetAccount(split);
const char *dest_name =
split_dest ? xaccAccountGetName(split_dest) : NULL;
assert(stream);
fputc('\n', stream);
fputs(prefix, stream);
fprintf(stream, " %10.2f | %15s | %s",
split_value,
dest_name ? dest_name : "<no-account-name>",
split_memo ? split_memo : "<no-split-memo>");
fclose(stream);
return(result);
}
static char *
xaccTransGetDateStr (Transaction *trans)
{
char buf [MAX_DATE_LENGTH];
struct tm *date;
time_t secs;
secs = xaccTransGetDate (trans);
date = localtime (&secs);
printDate(buf, date->tm_mday, date->tm_mon+1, date->tm_year +1900);
return g_strdup (buf);
}
char *
xaccTransAsString(Transaction *txn, const char prefix[]) {
char *result = NULL;
size_t result_size;
FILE *stream = open_memstream(&result, &result_size);
time_t date = xaccTransGetDate(txn);
const char *num = xaccTransGetNum(txn);
const char *desc = xaccTransGetDescription(txn);
const char *memo = xaccSplitGetMemo(xaccTransGetSplit(txn, 0));
const double total = DxaccSplitGetValue(xaccTransGetSplit(txn, 0));
assert(stream);
fputs(prefix, stream);
if(date) {
char *datestr = xaccTransGetDateStr(txn);
fprintf(stream, "%s", datestr);
free(datestr);
} else {
fprintf(stream, "<no-date>");
}
fputc(' ', stream);
if(num) {
fputs(num, stream);
} else {
fprintf(stream, "<no-num>");
}
fputc('\n', stream);
fputs(prefix, stream);
if(desc) {
fputs(" ", stream);
fputs(desc, stream);
} else {
fprintf(stream, "<no-description>");
}
fputc('\n', stream);
fputs(prefix, stream);
if(memo) {
fputs(" ", stream);
fputs(memo, stream);
} else {
fprintf(stream, "<no-transaction-memo>");
}
{
int split_count = xaccTransCountSplits(txn);
int i;
for(i = 1; i < split_count; i++) {
Split *split = xaccTransGetSplit(txn, i);
char *split_text = xaccSplitAsString(split, prefix);
fputs(split_text, stream);
free(split_text);
}
}
fputc('\n', stream);
fputs(prefix, stream);
fprintf(stream, " %10.2f -- Transaction total\n", total);
fclose(stream);
return(result);
}
#endif
/************************ END OF ************************************\
\************************* FILE *************************************/

46
src/engine/TransLog.h Normal file
View File

@ -0,0 +1,46 @@
/********************************************************************\
* TransLog.h -- the transaction logger *
* Copyright (C) 1998 Linas Vepstas *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License*
* along with this program; if not, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
* Boston, MA 02111-1307, USA gnu@gnu.org *
* *
\********************************************************************/
#ifndef XACC_TRANS_LOG_H
#define XACC_TRANS_LOG_H
#include "config.h"
#include "Account.h"
#include "Transaction.h"
void xaccOpenLog (void);
void xaccCloseLog (void);
void xaccTransWriteLog (Transaction *, char);
void xaccLogEnable (void);
void xaccLogDisable (void);
/* The xaccLogSetBaseName() method sets the base filepath and the
* root part of the journal file name. If the journal file is
* already open, it will close it and reopen it with the new
* base name.
*/
void xaccLogSetBaseName (const char *);
#endif /* XACC_TRANS_LOG_H */

2538
src/engine/Transaction.c Normal file

File diff suppressed because it is too large Load Diff

476
src/engine/Transaction.h Normal file
View File

@ -0,0 +1,476 @@
/********************************************************************\
* Transaction.h -- api for transactions & splits (journal entries) *
* Copyright (C) 1997 Robin D. Clark *
* Copyright (C) 1997, 1998, 1999, 2000 Linas Vepstas *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License*
* along with this program; if not, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
* Boston, MA 02111-1307, USA gnu@gnu.org *
* *
\********************************************************************/
#ifndef XACC_TRANSACTION_H
#define XACC_TRANSACTION_H
#include "config.h"
#include <time.h>
#include "gnc-common.h"
#include "gnc-commodity.h"
#include "gnc-numeric.h"
#include "kvp_frame.h"
#include "GNCId.h"
#include "date.h"
/* Values for the reconciled field in Splits */
#define CREC 'c' /* The Split has been cleared */
#define YREC 'y' /* The Split has been reconciled */
#define FREC 'f' /* frozen into accounting period */
#define NREC 'n' /* not reconciled or cleared */
/** STRUCTS *********************************************************/
typedef struct _account Account;
typedef struct _account_group AccountGroup;
typedef struct _split Split;
typedef struct _transaction Transaction;
/** PROTOTYPES ******************************************************/
/*
* The xaccConfigSetForceDoubleEntry() and xaccConfigGetForceDoubleEntry()
* set and get the "force_double_entry" flag. This flag determines how
* the splits in a transaction will be balanced.
*
* The following values have significance:
* 0 -- anything goes
* 1 -- The sum of all splits in a transaction will be
* forced to be zero, even if this requires the
* creation of additional splits. Note that a split
* whose value is zero (e.g. a stock price) can exist
* by itself. Otherwise, all splits must come in at
* least pairs.
* 2 -- splits without parents will be forced into a
* lost & found account. (Not implemented)
*/
void xaccConfigSetForceDoubleEntry (int force);
int xaccConfigGetForceDoubleEntry (void);
/*
* The xaccMallocTransaction() will malloc memory and initialize it.
* Once created, it is usually unsafe to merely "free" this memory;
* the xaccTransDestroy() method should be called.
*/
Transaction * xaccMallocTransaction (void);
gboolean xaccTransEqual(const Transaction *ta, const Transaction *tb,
gboolean check_guids,
gboolean check_splits);
/* The xaccTransDestroy() method will remove all
* of the splits from each of their accounts, free the memory
* associated with them. This routine must be followed by either
* an xaccTransCommitEdit(), in which case the transaction
* memory will be freed, or by xaccTransRollbackEdit(), in which
* case nothing at all is freed, and everything is put back into
* original order.
*/
void xaccTransDestroy (Transaction *trans);
/* The xaccTransBeginEdit() method must be called before any changes
* are made to a transaction or any of its component splits. If
* this is not done, errors will result.
*
* The xaccTransCommitEdit() method indicates that the changes to the
* transaction and its splits are complete and should be made
* permanent. Note this routine may result in the deletion of the
* transaction, if the transaction is "empty" (has no splits), or
* of xaccTransDestroy() was called on the transaction.
*
* The xaccTransRollbackEdit() routine rejects all edits made, and
* sets the transaction back to where it was before the editing
* started. This includes restoring any deleted splits, removing
* any added splits, and undoing the effects of xaccTransDestroy,
* as well as restoring share quantities, memos, descriptions, etc.
*
* The xaccTransIsOpen() method returns TRUE if the transaction
* is open for editing. Otherwise, it returns false. */
void xaccTransBeginEdit (Transaction *trans);
void xaccTransCommitEdit (Transaction *trans);
void xaccTransRollbackEdit (Transaction *trans);
gboolean xaccTransIsOpen (Transaction *trans);
/*
* The xaccTransGetGUID() subroutine will return the
* globally unique id associated with that transaction.
*
* The xaccTransLookup() subroutine will return the
* transaction associated with the given id, or NULL
* if there is no such transaction.
*/
const GUID * xaccTransGetGUID (Transaction *trans);
Transaction * xaccTransLookup (const GUID *guid);
/* Transaction slots are used to store arbitrary strings, numbers, and
* structures which aren't members of the transaction struct. */
kvp_frame *xaccTransGetSlots(Transaction *trans);
void xaccTransSetSlots_nc(Transaction *t, kvp_frame *frm);
/* The xaccTransSetDateSecs() method will modify the posted date
* of the transaction. (Footnote: this shouldn't matter to a user,
* but anyone modifying the engine should understand that when
* xaccTransCommitEdit() is called, the date order of each of the
* component splits will be checked, and will be restored in
* ascending date order.)
*
* The xaccTransSetDate() method does the same thing as
* xaccTransSetDateSecs(), but takes a convenient day-month-year format.
*
* The xaccTransSetDatePostedTS() method does the same thing as
* xaccTransSetDateSecs(), but takes a struct timespec64.
*
*/
void xaccTransSetDate (Transaction *trans,
int day, int mon, int year);
void xaccTransSetDateSecs (Transaction *trans, time_t time);
void xaccTransSetDatePostedTS (Transaction *trans,
const Timespec *ts);
void xaccTransSetDateEnteredSecs (Transaction *trans, time_t time);
void xaccTransSetDateEnteredTS (Transaction *trans,
const Timespec *ts);
/* set the Num, Description, and Notes fields */
void xaccTransSetNum (Transaction *trans, const char *num);
void xaccTransSetDescription (Transaction *trans, const char *desc);
void xaccTransSetNotes (Transaction *trans, const char *notes);
/* The xaccTransAppendSplit() method will append the indicated
* split to the collection of splits in this transaction.
* If the split is already a part of another transaction,
* it will be removed from that transaction first.
*/
void xaccTransAppendSplit (Transaction *trans, Split *split);
/* The xaccSplitDestroy() method will update its parent account and
* transaction in a consistent manner, resulting in the complete
* unlinking of the split, and the freeing of its associated memory.
* The goal of this routine is to perform the removal and destruction
* of the split in an atomic fashion, with no chance of accidentally
* leaving the accounting structure out-of-balance or otherwise
* inconsistent.
*
* If the deletion of the split leaves the transaction with no
* splits, then the transaction will be marked for deletion. (It
* will not be deleted until the xaccTransCommitEdit() routine is
* called.)
*/
void xaccSplitDestroy (Split *split);
/* ------------- gets --------------- */
/* The xaccTransGetSplit() method returns a pointer to each of the
* splits in this transaction. Valid values for i are zero to
* (number_of__splits-1). An invalid value of i will cause NULL to
* be returned. A convenient way of cycling through all splits is
* to start at zero, and keep incrementing until a null value is returned.
*/
Split * xaccTransGetSplit (Transaction *trans, int i);
/* The xaccTransGetSplitList() method returns a GList of the splits
* in a transaction. This list must not be modified. Do *NOT* free
* this list when you are done with it. */
GList * xaccTransGetSplitList (Transaction *trans);
/* These routines return the Num (or ID field), the description,
* the notes, and the date field.
*/
const char * xaccTransGetNum (Transaction *trans);
const char * xaccTransGetDescription (Transaction *trans);
const char * xaccTransGetNotes (Transaction *trans);
time_t xaccTransGetDate (Transaction *trans);
void xaccTransGetDatePostedTS (Transaction *trans, Timespec *ts);
void xaccTransGetDateEnteredTS (Transaction *trans, Timespec *ts);
Timespec xaccTransRetDateEnteredTS (Transaction *trans);
Timespec xaccTransRetDatePostedTS (Transaction *trans);
/* The xaccTransCountSplits() method returns the number of splits
* in a transaction.
*/
int xaccTransCountSplits (Transaction *trans);
/* --------------------------------------------------------------- */
/* Commmodity routines. Each transaction's 'currency' is by definition
* the balancing common currency for the splits in that transaction.
* */
gnc_commodity * xaccTransGetCurrency (Transaction *trans);
void xaccTransSetCurrency (Transaction *trans, gnc_commodity *curr);
/* The xaccTransGetImbalance() method returns the total value of the
* transaction. In a pure double-entry system, this imbalance
* should be exactly zero, and if it is not, something is broken.
* However, when double-entry semantics are not enforced, unbalanced
* transactions can sneak in, and this routine can be used to find
* out how much things are off by. The value returned is denominated
* in the currency that is returned by the xaccTransFindCommonCurrency()
* method.
*/
gnc_numeric xaccTransGetImbalance (Transaction * trans);
/* ------------- splits --------------- */
Split * xaccMallocSplit (void);
gboolean xaccSplitEqual(const Split *sa, const Split *sb,
gboolean check_guids,
gboolean check_txn_splits);
/* Split slots are used to store arbitrary strings, numbers, and
* structures which aren't members of the transaction struct.
*
* See kvp_doc.txt for reserved slot names.
*/
kvp_frame *xaccSplitGetSlots(Split *split);
void xaccSplitSetSlots_nc(Split *s, kvp_frame *frm);
/* The xaccSplitGetGUID() subroutine will return the
* globally unique id associated with that split.
*
* The xaccSplitLookup() subroutine will return the
* split associated with the given id, or NULL
* if there is no such split.
*/
const GUID * xaccSplitGetGUID (Split *split);
Split * xaccSplitLookup (const GUID *guid);
/* The memo is an arbitrary string associated with a split.
* Users typically type in free form text from the GUI.
*/
void xaccSplitSetMemo (Split *split, const char *memo);
/* The Action is essentially an arbitrary string, but is
* meant to be conveniently limited to a menu of selections
* such as "Buy", "Sell", "Interest", etc. However,
* as far as the engine is concerned, its an arbitrary string.
*/
void xaccSplitSetAction (Split *split, const char *action);
/* The Reconcile is a single byte, whose values are typically
* are "N", "C" and "R"
*/
void xaccSplitSetReconcile (Split *split, char reconciled_flag);
void xaccSplitSetDateReconciledSecs (Split *split, time_t time);
void xaccSplitSetDateReconciledTS (Split *split, Timespec *ts);
void xaccSplitGetDateReconciledTS (Split *split, Timespec *ts);
Timespec xaccSplitRetDateReconciledTS (Split *split);
/*
* The following four functions set the prices and amounts.
* All of the routines always maintain balance: that is,
* invoking any of them will cause other splits in the transaction
* to be modified so that the net value of the transaction is zero.
*
* IMPORTANT: The split should be parented by an account before
* any of these routines are invoked! This is because the actual
* setting of amounts/values requires SCU settings from the account.
* If these are not available, then amounts/values will be set to
* -1/0, which is an invalid value. I beleive this order dependency
* is a bug, but I'm too lazy to find, fix & test at the moment ...
*
* The xaccSplitSetAmount() (formerly xaccSplitSetShareAmount) method
* sets the amount in the account's commodity that the split
* should have.
*
* The xaccSplitSetSharePrice() method sets the price of the
* split. DEPRECATED - set the value and amount instead.
*
* The xaccSplitSetValue() method adjusts the number of shares in
* the split so that the number of shares times the share price
* equals the value passed in.
*
* The xaccSplitSetSharePriceAndAmount() method will simultaneously
* update the share price and the number of shares. This
* is a utility routine that is equivalent to a xaccSplitSetSharePrice()
* followed by and xaccSplitSetAmount(), except that it incurs the
* processing overhead of balancing only once, instead of twice. */
void DxaccSplitSetSharePriceAndAmount (Split *split, double price,
double amount);
void DxaccSplitSetShareAmount (Split *split, double amount);
void DxaccSplitSetSharePrice (Split *split, double price);
void DxaccSplitSetValue (Split *split, double value);
void DxaccSplitSetBaseValue (Split *split, double value,
const gnc_commodity * base_currency);
void xaccSplitSetSharePriceAndAmount (Split *split, gnc_numeric price,
gnc_numeric amount);
void xaccSplitSetAmount (Split *split, gnc_numeric amount);
void xaccSplitSetSharePrice (Split *split, gnc_numeric price);
void xaccSplitSetValue (Split *split, gnc_numeric value);
void xaccSplitSetBaseValue (Split *split, gnc_numeric value,
const gnc_commodity * base_currency);
/* The following four subroutines return the running balance up
* to & including the indicated split.
*
* The balance is the currency-denominated balance. For accounts
* with non-unit share prices, it is correctly adjusted for
* share prices.
*
* The share-balance is the number of shares.
* Price fluctuations do not change the share balance.
*
* The cleared-balance is the currency-denominated balance
* of all transactions that have been marked as cleared or reconciled.
* It is correctly adjusted for price fluctuations.
*
* The reconciled-balance is the currency-denominated balance
* of all transactions that have been marked as reconciled.
*/
gnc_numeric xaccSplitGetBalance (Split *split);
gnc_numeric xaccSplitGetClearedBalance (Split *split);
gnc_numeric xaccSplitGetReconciledBalance (Split *split);
gnc_numeric xaccSplitGetBaseValue (Split *split,
const gnc_commodity * base_currency);
/* return the parent transaction of the split */
Transaction * xaccSplitGetParent (Split *split);
/* return the memo, action strings */
const char * xaccSplitGetMemo (Split *split);
const char * xaccSplitGetAction (Split *split);
/* return the value of the reconcile flag */
char xaccSplitGetReconcile (Split *split);
double DxaccSplitGetShareAmount (Split * split);
double DxaccSplitGetSharePrice (Split * split);
double DxaccSplitGetValue (Split * split);
gnc_numeric xaccSplitGetAmount (Split * split);
gnc_numeric xaccSplitGetSharePrice (Split * split);
gnc_numeric xaccSplitGetValue (Split * split);
Account * xaccSplitGetAccount (Split *split);
const GUID * xaccSplitGetAccountGUID(Split *split);
void xaccSplitSetAccount(Split *s, Account *act);
void xaccSplitSetAccountGUID(Split *s, GUID id);
/* split types: normal stock-split */
const char *xaccSplitGetType(const Split *s);
/* reconfgure a split to be a stock split - after this, you shouldn't
mess with the value, just the damount. */
void xaccSplitMakeStockSplit(Split *s);
/********************************************************************\
* sorting comparison function
*
* The xaccTransOrder(ta,tb) method is useful for sorting.
* return a negative value if transaction ta is dated earlier than tb,
* return a positive value if transaction ta is dated later than tb,
* then compares num and description values, using the strcmp()
* c-library routine, returning what strcmp would return.
* Finally, it returns zero if all of the above match.
* Note that it does *NOT* compare its member splits.
*
* The xaccSplitDateOrder(sa,sb) method is useful for sorting.
* if sa and sb have different transactions, return their xaccTransOrder
* return a negative value if split sa has a smaller currency-value than sb,
* return a positive value if split sa has a larger currency-value than sb,
* return a negative value if split sa has a smaller share-price than sb,
* return a positive value if split sa has a larger share-price than sb,
* then compares memo and action using the strcmp()
* c-library routine, returning what strcmp would return.
* Then it compares the reconciled flags, then the reconciled dates,
* Finally, it returns zero if all of the above match.
*
*/
int xaccTransOrder (Transaction *ta, Transaction *tb);
int xaccSplitDateOrder (Split *sa, Split *sb);
/********************************************************************\
* Miscellaneous utility routines.
\********************************************************************/
/*
* These functions compare two splits by different criteria. The *Other*
* functions attempt to find the split on the other side of a transaction
* and compare on it. They return similar to strcmp.
*
* These functions were added because converting strings to guile
* for comparisons in the transaction report is terribly inefficient.
* More may be added here in future if it turns out that other types
* of comparisons also induces guile slowdowns.
*/
int xaccSplitCompareAccountFullNames(Split *sa, Split *sb);
int xaccSplitCompareAccountCodes(Split *sa, Split *sb);
int xaccSplitCompareOtherAccountFullNames(Split *sa, Split *sb);
int xaccSplitCompareOtherAccountCodes(Split *sa, Split *sb);
/*
* These functions take a split, get the corresponding split on the
* "other side" of the transaction, and extract either the name or code
* of that split, reverting to returning a constant "Split" if the
* transaction has more than one split on the "other side". These
* were added for the transaction report, and is in C because the code
* was already written in C for the above functions and duplication
* is silly.
*/
char * xaccSplitGetCorrAccountFullName(Split *sa, char seperator);
const char * xaccSplitGetCorrAccountName(Split *sa);
const char * xaccSplitGetCorrAccountCode(Split *sa);
/*
* The xaccGetAccountByName() is a convenience routine that
* is essentially identical to xaccGetPeerAccountFromName(),
* except that it accepts the handy transaction as root.
*
* The xaccGetAccountByFullName routine is similar, but uses
* full names using the given separator.
*/
Account * xaccGetAccountByName (Transaction *trans, const char *name);
Account * xaccGetAccountByFullName (Transaction *trans,
const char *name,
const char separator);
/*
* The xaccSplitGetOtherSplit() is a convenience routine that returns
* the other of a pair of splits. If there are more than two
* splits, it returns NULL.
*/
Split * xaccSplitGetOtherSplit (Split *split);
/* The xaccIsPeerSplit() is a convenience routine that returns
* a non-zero value if the two splits share a common
* parent transaction, else it returns zero.
*/
int xaccIsPeerSplit (Split *split_1, Split *split_2);
#endif /* XACC_TRANSACTION_H */

236
src/engine/TransactionP.h Normal file
View File

@ -0,0 +1,236 @@
/********************************************************************\
* TransactionP.h -- private header for transaction & splits *
* Copyright (C) 1997 Robin D. Clark *
* Copyright (C) 1997, 1998, 1999, 2000 Linas Vepstas *
* Copyright (C) 2000 Bill Gribble *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License*
* along with this program; if not, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
* Boston, MA 02111-1307, USA gnu@gnu.org *
* *
\********************************************************************/
/*
* FILE:
* TransactionP.h
*
* FUNCTION:
* The is the *private* transaction header file. Code outside of
* engine should *not* include this file. This is because code
* outside of the engine should *never* access any of the structure
* members directly.
*
* Note that this header file also defines prototypes for various
* routines that perform sub-atomic updates of the accounting
* structures. If these routines are not used properly, they
* can result in inconsistent, unbalanced accounting structures.
* In other words, their use is dangerous, and their use outside
* of the scope of the engine is forbidden.
*
*/
#ifndef XACC_TRANSACTION_P_H
#define XACC_TRANSACTION_P_H
#include <time.h>
#include <glib.h>
#include "config.h"
#include "kvp_frame.h"
#include "gnc-numeric.h"
#include "Transaction.h" /* for typedefs */
#include "GNCId.h"
/** STRUCTS *********************************************************/
/*
* Double-entry is forced by having at least two splits in every
* transaction. By convention, (and only by convention, not by
* any innate requirement), the first split is considered to be
* the source split or the crediting split, and the others are
* the destination, or debiting splits. The grand total of all
* of the splits must always be kept zero.
*/
/* A split transaction is one which shows up as a credit (or debit) in
* one account, and pieces of it show up as debits (or credits) in other
* accounts. Thus, a single credit-card transaction might be split
* between "dining", "tips" and "taxes" categories.
*
* A "split" is more commonly refered to as a "entry" in a "transaction".
*/
struct _split
{
GUID guid; /* globally unique id */
GUID acc_guid; /* the guid of the associated account */
Account *acc; /* back-pointer to debited/credited account */
Transaction *parent; /* parent of split */
/* The memo field is an arbitrary user-assiged value.
* It is intended to hold a short (zero to forty character) string
* that is displayed by the GUI along with this split.
*/
char * memo;
/* The action field is an arbitrary user-assigned value.
* It is meant to be a very short (one to ten cahracter) string that
* signifies the "type" of this split, such as e.g. Buy, Sell, Div,
* Withdraw, Deposit, ATM, Check, etc. The idea is that this field
* can be used to create custom reports or graphs of data.
*/
char * action; /* Buy, Sell, Div, etc. */
/* kvp_data is a key-value pair database for storing simple
* "extra" information in splits, transactions, and accounts.
* it's NULL until accessed. */
kvp_frame * kvp_data;
char reconciled; /* The reconciled field */
Timespec date_reconciled; /* date split was reconciled */
/* 'value' is the amount of the transaction balancing commodity
* (i.e. currency) involved, 'amount' is the amount of the account's
* commodity (formerly known as 'security') involved. */
gnc_numeric value;
gnc_numeric amount;
/* -------------------------------------------------------------- */
/* Below follow some 'temporary' fields */
/* The various "balances" are the sum of all of the values of
* all the splits in the account, up to and including this split.
* These balances apply to a sorting order by date posted
* (not by date entered). */
gnc_numeric balance;
gnc_numeric cleared_balance;
gnc_numeric reconciled_balance;
/* -------------------------------------------------------------- */
/* Backend private expansion data */
guint32 idata; /* used by the sql backend for kvp management */
};
struct _transaction
{
/* guid is a globally unique identifier which can be used to
* reference the transaction.
*/
GUID guid;
Timespec date_entered; /* date register entry was made */
Timespec date_posted; /* date transaction was posted at bank */
/* The num field is a arbitrary user-assigned field.
* It is intended to store a short id number, typically the check number,
* deposit number, invoice number or other tracking number.
*/
char * num;
/* The description field is an arbitrary user-assigned value.
* It is meant to be a short descriptive phrase.
*/
char * description;
/* kvp_data is a key-value pair database for storing simple
* "extra" information in splits, transactions, and accounts.
* it's NULL until accessed. */
kvp_frame * kvp_data;
/* The common_currency field is the balancing common currency for
* all the splits in the transaction.
*
* This field is going to replace the currency field in the account
* structures. However, right now we are in a transition period: we
* store it here an in the account, and test its value dynamically
* for correctness. If we can run for a few months without errors,
* then we'll make the conversion permanent.
*
* Alternate, better(?) name: "valuation currency": it is the
* currency in which all of the splits can be valued. */
gnc_commodity *common_currency;
/* version number, used for tracking multiuser updates */
gint32 version;
guint32 version_check; /* data aging timestamp */
GList * splits; /* list of splits */
/* marker is used to track the progress of transaction traversals.
* 0 is never a legitimate marker value, so we can tell is we hit
* a new transaction in the middle of a traversal. All each new
* traversal cares about is whether or not the marker stored in
* a transaction is the same as or different than the one
* corresponding to the current traversal. */
unsigned char marker;
gint32 editlevel; /* nestcount of begin/end edit calls */
gboolean do_free; /* transaction in process of being destroyed */
/* the orig pointer points at a copy of the original transaction,
* before editing was started. This orig copy is used to rollback
* any changes made if/when the edit is abandoned.
*/
Transaction *orig;
/* -------------------------------------------------------------- */
/* Backend private expansion data */
guint32 idata; /* used by the sql backend for kvp management */
};
/* Set the transaction's GUID. This should only be done when reading
* a transaction from a datafile, or some other external source. Never
* call this on an existing transaction! */
void xaccTransSetGUID (Transaction *trans, const GUID *guid);
/* Set the split's GUID. This should only be done when reading
* a split from a datafile, or some other external source. Never
* call this on an existing split! */
void xaccSplitSetGUID (Split *split, const GUID *guid);
/* The xaccFreeSplit() method simply frees all memory associated
* with the split. It does not verify that the split isn't
* referenced in some account. If the split is referenced by an
* account, then calling this method will leave the system in an
* inconsistent state.
*/
void xaccFreeSplit (Split *split); /* frees memory */
/* compute the value of a list of splits in the given currency,
* excluding the skip_me split. */
gnc_numeric xaccSplitsComputeValue (GList *splits, Split * skip_me,
const gnc_commodity * base_currency);
/* The xaccTransSet/GetVersion() routines set & get the version
* numbers on this transaction. The version number is used to manage
* multi-user updates. These routines are private because we don't
* want anyone except the backend to mess with them.
*/
void xaccTransSetVersion (Transaction*, gint32);
gint32 xaccTransGetVersion (Transaction*);
/* The xaccTransFindCommonCurrency () method returns a gnc_commodity
* indicating a currency denomination that all of the splits in this
* transaction have in common, using the old currency/security fields
* of the split accounts. */
gnc_commodity * xaccTransFindOldCommonCurrency (Transaction *trans);
#endif /* XACC_TRANSACTION_P_H */

701
src/engine/date.c Normal file
View File

@ -0,0 +1,701 @@
/********************************************************************\
* date.c -- utility functions to handle the date (adjusting, get *
* current date, etc.) for xacc (X-Accountant) *
* Copyright (C) 1997 Robin D. Clark *
* Copyright (C) 1998, 1999, 2000 Linas Vepstas *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License*
* along with this program; if not, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
* Boston, MA 02111-1307, USA gnu@gnu.org *
* *
* Author: Rob Clark rclark@cs.hmc.edu *
* *
\********************************************************************/
#define _GNU_SOURCE
#define __EXTENSIONS__
#include "config.h"
#include <ctype.h>
#ifdef HAVE_LANGINFO_D_FMT
#include <langinfo.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <glib.h>
#include "date.h"
#include "gnc-engine-util.h"
#define NANOS_PER_SECOND 1000000000
#ifdef HAVE_LANGINFO_D_FMT
# define GNC_D_FMT (nl_langinfo (D_FMT))
#else
# define GNC_D_FMT "%Y-%m-%d"
#endif
/* This is now user configured through the gnome options system() */
static DateFormat dateFormat = DATE_FORMAT_US;
/* This static indicates the debugging module that this .o belongs to. */
static short module = MOD_ENGINE;
/********************************************************************\
\********************************************************************/
static void
timespec_normalize(Timespec *t)
{
if(t->tv_nsec > NANOS_PER_SECOND)
{
t->tv_sec+= (t->tv_nsec / NANOS_PER_SECOND);
t->tv_nsec= t->tv_nsec % NANOS_PER_SECOND;
}
if(t->tv_nsec < - NANOS_PER_SECOND)
{
t->tv_sec+= - (-t->tv_nsec / NANOS_PER_SECOND);
t->tv_nsec = - (-t->tv_nsec % NANOS_PER_SECOND);
}
if (t->tv_sec > 0 && t->tv_nsec < 0)
{
t->tv_sec--;
t->tv_nsec = NANOS_PER_SECOND + t->tv_nsec;
}
if (t->tv_sec < 0 && t->tv_nsec > 0)
{
t->tv_sec++;
t->tv_nsec = - NANOS_PER_SECOND + t->tv_nsec;
}
return;
}
gboolean
timespec_equal (const Timespec *ta, const Timespec *tb)
{
if(ta == tb) return TRUE;
if(ta->tv_sec != tb->tv_sec) return FALSE;
if(ta->tv_nsec != tb->tv_nsec) return FALSE;
return TRUE;
}
gint
timespec_cmp(const Timespec *ta, const Timespec *tb)
{
if(ta == tb) return 0;
if(ta->tv_sec < tb->tv_sec) return -1;
if(ta->tv_sec > tb->tv_sec) return 1;
if(ta->tv_nsec < tb->tv_nsec) return -1;
if(ta->tv_nsec > tb->tv_nsec) return 1;
return 0;
}
Timespec
timespec_diff(const Timespec *ta, const Timespec *tb)
{
Timespec retval;
retval.tv_sec = ta->tv_sec - tb->tv_sec;
retval.tv_nsec = ta->tv_nsec - tb->tv_nsec;
timespec_normalize(&retval);
return retval;
}
Timespec
timespec_abs(const Timespec *t)
{
Timespec retval = *t;
timespec_normalize(&retval);
if (retval.tv_sec < 0)
{
retval.tv_sec = - retval.tv_sec;
retval.tv_nsec = - retval.tv_nsec;
}
return retval;
}
/**
* timespecCanonicalDayTime
* given a timepair contains any time on a certain day (local time)
* converts it to be midday that day.
*/
Timespec
timespecCanonicalDayTime(Timespec t)
{
struct tm tm, *result;
Timespec retval;
time_t t_secs = t.tv_sec + (t.tv_nsec / NANOS_PER_SECOND);
result = localtime(&t_secs);
tm = *result;
tm.tm_sec = 0;
tm.tm_min = 0;
tm.tm_hour = 12;
tm.tm_isdst = -1;
retval.tv_sec = mktime(&tm);
retval.tv_nsec = 0;
return retval;
}
/**
* setDateFormat
* set date format to one of US, UK, CE, OR ISO
* checks to make sure it's a legal value
* Args: DateFormat: enumeration indicating preferred format
* returns: nothing
*
* Globals: dateFormat
**/
void setDateFormat(DateFormat df)
{
if(df >= DATE_FORMAT_FIRST && df <= DATE_FORMAT_LAST)
{
dateFormat = df;
}
else
{ /* hack alert - is this what we should be doing here? */
PERR("non-existent date format set");
}
return;
}
/**
* printDate
* Convert a date as day / month / year integers into a localized string
* representation
*
* Args: buff - pointer to previously allocated character array; its size
* must be at lease MAX_DATE_LENTH bytes.
* day - day of the month as 1 ... 31
* month - month of the year as 1 ... 12
* year - year (4-digit)
*
* Return: nothing
*
* Globals: global dateFormat value
*/
void
printDate (char * buff, int day, int month, int year)
{
if (!buff) return;
/* Note that when printing year, we use %-4d in format string;
* this causes a one, two or three-digit year to be left-adjusted
* when printed (i.e. padded with blanks on the right). This is
* important while the user is editing the year, since erasing a
* digit can temporarily cause a three-digit year, and having the
* blank on the left is a real pain for the user. So pad on the
* right.
*/
switch(dateFormat)
{
case DATE_FORMAT_UK:
sprintf (buff, "%2d/%2d/%-4d", day, month, year);
break;
case DATE_FORMAT_CE:
sprintf (buff, "%2d.%2d.%-4d", day, month, year);
break;
case DATE_FORMAT_ISO:
sprintf (buff, "%04d-%02d-%02d", year, month, day);
break;
case DATE_FORMAT_LOCALE:
{
struct tm tm_str;
tm_str.tm_mday = day;
tm_str.tm_mon = month - 1; /* tm_mon = 0 through 11 */
tm_str.tm_year = year - 1900; /* this is what the standard
* says, it's not a Y2K thing */
tm_str.tm_hour = 0;
tm_str.tm_min = 0;
tm_str.tm_sec = 0;
tm_str.tm_isdst = -1;
strftime (buff, MAX_DATE_LENGTH, GNC_D_FMT, &tm_str);
}
break;
case DATE_FORMAT_US:
default:
sprintf (buff, "%2d/%2d/%-4d", month, day, year);
break;
}
}
void
printDateSecs (char * buff, time_t t)
{
struct tm *theTime;
if (!buff) return;
theTime = localtime (&t);
printDate (buff, theTime->tm_mday,
theTime->tm_mon + 1,
theTime->tm_year + 1900);
}
char *
xaccPrintDateSecs (time_t t)
{
char buff[100];
printDateSecs (buff, t);
return g_strdup (buff);
}
const char *
gnc_print_date (Timespec ts)
{
static char buff[MAX_DATE_LENGTH];
time_t t;
t = ts.tv_sec + (ts.tv_nsec / 1000000000.0);
printDateSecs (buff, t);
return buff;
}
/**
* scanDate
* Convert a string into day / month / year integers according to
* the current dateFormat value.
*
* Args: buff - pointer to date string
* day - will store day of the month as 1 ... 31
* month - will store month of the year as 1 ... 12
* year - will store the year (4-digit)
*
* Return: nothing
*
* Globals: global dateFormat value
*/
void
scanDate (const char *buff, int *day, int *month, int *year)
{
char *dupe, *tmp, *first_field, *second_field, *third_field;
int iday, imonth, iyear;
struct tm *now;
time_t secs;
if (!buff) return;
dupe = g_strdup (buff);
tmp = dupe;
first_field = NULL;
second_field = NULL;
third_field = NULL;
/* use strtok to find delimiters */
if (tmp) {
first_field = strtok (tmp, ".,-+/\\()");
if (first_field) {
second_field = strtok (NULL, ".,-+/\\()");
if (second_field) {
third_field = strtok (NULL, ".,-+/\\()");
}
}
}
/* if any fields appear blank, use today's date */
time (&secs);
now = localtime (&secs);
iday = now->tm_mday;
imonth = now->tm_mon+1;
iyear = now->tm_year+1900;
/* get numeric values */
switch (dateFormat)
{
case DATE_FORMAT_LOCALE:
if (buff[0] != '\0')
{
struct tm thetime;
strptime (buff, GNC_D_FMT, &thetime);
iday = thetime.tm_mday;
imonth = thetime.tm_mon + 1;
iyear = thetime.tm_year + 1900;
}
break;
case DATE_FORMAT_UK:
case DATE_FORMAT_CE:
if (first_field) iday = atoi (first_field);
if (second_field) imonth = atoi (second_field);
if (third_field) iyear = atoi (third_field);
break;
case DATE_FORMAT_ISO:
if (first_field) iyear = atoi (first_field);
if (second_field) imonth = atoi (second_field);
if (third_field) iday = atoi (third_field);
break;
case DATE_FORMAT_US:
default:
if (first_field) imonth = atoi (first_field);
if (second_field) iday = atoi (second_field);
if (third_field) iyear = atoi (third_field);
break;
}
g_free (dupe);
/* if the year entered is smaller than 100, assume we mean the current
century (and are not revising some roman emperor's books) */
if (iyear < 100)
iyear += ((int) ((now->tm_year+1950-iyear)/100)) * 100;
if (year) *year=iyear;
if (month) *month=imonth;
if (day) *day=iday;
}
/**
* dateSeparator
* Return the field separator for the current date format
*
* Args: none
*
* Return: date character
*
* Globals: global dateFormat value
*/
char dateSeparator ()
{
static char locale_separator = '\0';
switch (dateFormat)
{
case DATE_FORMAT_CE:
return '.';
case DATE_FORMAT_ISO:
return '-';
case DATE_FORMAT_US:
case DATE_FORMAT_UK:
default:
return '/';
case DATE_FORMAT_LOCALE:
if (locale_separator != '\0')
return locale_separator;
else
{ /* Make a guess */
char string[256];
struct tm *tm;
time_t secs;
char *s;
secs = time(NULL);
tm = localtime(&secs);
strftime(string, sizeof(string), GNC_D_FMT, tm);
for (s = string; s != '\0'; s++)
if (!isdigit(*s))
return (locale_separator = *s);
}
}
return '\0';
}
/********************************************************************\
* iso 8601 datetimes should look like 1998-07-02 11:00:00.68-05
\********************************************************************/
/* hack alert -- this routine returns incorrect values for
* dates before 1970 */
static Timespec
gnc_iso8601_to_timespec(const char *str, int do_localtime)
{
char buf[4];
Timespec ts;
struct tm stm;
long int nsec =0;
ts.tv_sec=0;
ts.tv_nsec=0;
if (!str) return ts;
stm.tm_year = atoi(str) - 1900;
str = strchr (str, '-'); if (str) { str++; } else { return ts; }
stm.tm_mon = atoi(str) - 1;
str = strchr (str, '-'); if (str) { str++; } else { return ts; }
stm.tm_mday = atoi(str);
str = strchr (str, ' '); if (str) { str++; } else { return ts; }
stm.tm_hour = atoi(str);
str = strchr (str, ':'); if (str) { str++; } else { return ts; }
stm.tm_min = atoi(str);
str = strchr (str, ':'); if (str) { str++; } else { return ts; }
stm.tm_sec = atoi (str);
/* the decimal point, optionally present ... */
/* hack alert -- this algo breaks if more than 9 decimal places present */
if (strchr (str, '.'))
{
int decimals, i, multiplier=1000000000;
str = strchr (str, '.') +1;
decimals = strcspn (str, "+- ");
for (i=0; i<decimals; i++) multiplier /= 10;
nsec = atoi(str) * multiplier;
}
stm.tm_isdst = -1;
/* timezone format can be +hh or +hhmm or +hh.mm (or -) */
str += strcspn (str, "+-");
buf[0] = str[0];
buf[1] = str[1];
buf[2] = str[2];
buf[3] = 0;
stm.tm_hour -= atoi(buf);
str +=3;
if ('.' == *str) str++;
if (isdigit (*str) && isdigit (*(str+1)))
{
int cyn;
/* copy sign from hour part */
if ('+' == buf[0]) { cyn = -1; } else { cyn = +1; }
buf[0] = str[0];
buf[1] = str[1];
buf[2] = str[2];
buf[3] = 0;
stm.tm_min += cyn * atoi(buf);
}
/* adjust for the local timezone */
if (do_localtime)
{
struct tm *tm;
int tz_hour;
time_t secs = mktime (&stm);
/* The call to localtime is 'bogus', but it forces 'timezone' to be set.
* Note that we must use the accurate date, since the value of 'gnc_timezone'
* includes daylight savings corrections for that date. */
tm = localtime (&secs);
tz_hour = gnc_timezone(tm)/3600;
stm.tm_hour -= tz_hour;
stm.tm_min -= (gnc_timezone(tm) - 3600*tz_hour)/60;
}
/* compute number of seconds */
ts.tv_sec = mktime (&stm);
ts.tv_nsec = nsec;
return ts;
}
Timespec
gnc_iso8601_to_timespec_local(const char *str)
{
return gnc_iso8601_to_timespec(str, 1);
}
Timespec
gnc_iso8601_to_timespec_gmt(const char *str)
{
return gnc_iso8601_to_timespec(str, 0);
}
/********************************************************************\
\********************************************************************/
char *
gnc_timespec_to_iso8601_buff (Timespec ts, char * buff)
{
int len;
int tz_hour, tz_min;
char cyn;
time_t tmp;
struct tm parsed;
tmp = ts.tv_sec;
localtime_r(&tmp, &parsed);
tz_hour = gnc_timezone (&parsed) / 3600;
tz_min = (gnc_timezone (&parsed) - 3600*tz_hour) / 60;
if (0>tz_min) { tz_min +=60; tz_hour --; }
if (60<=tz_min) { tz_min -=60; tz_hour ++; }
/* we also have to print the sign by hand, to work around a bug
* in the glibc 2.1.3 printf (where %+02d fails to zero-pad)
*/
cyn = '-';
if (0>tz_hour) { cyn = '+'; tz_hour = -tz_hour; }
len = sprintf (buff, "%4d-%02d-%02d %02d:%02d:%02d.%06ld %c%02d%02d",
parsed.tm_year + 1900,
parsed.tm_mon + 1,
parsed.tm_mday,
parsed.tm_hour,
parsed.tm_min,
parsed.tm_sec,
ts.tv_nsec / 1000,
cyn,
tz_hour,
tz_min);
/* return pointer to end of string */
buff += len;
return buff;
}
/********************************************************************\
\********************************************************************/
/* hack alert -- this routine returns incorrect values for
* dates before 1970 */
time_t
xaccDMYToSec (int day, int month, int year)
{
struct tm stm;
time_t secs;
stm.tm_year = year - 1900;
stm.tm_mon = month - 1;
stm.tm_mday = day;
stm.tm_hour = 0;
stm.tm_min = 0;
stm.tm_sec = 0;
stm.tm_isdst = -1;
/* compute number of seconds */
secs = mktime (&stm);
return secs;
}
time_t
xaccScanDateS (const char *str)
{
int month, day, year;
scanDate (str, &day, &month, &year);
return xaccDMYToSec (day,month,year);
}
#define THIRTY_TWO_YEARS 0x3c30fc00LL
static Timespec
gnc_dmy2timespec_internal (int day, int month, int year, gboolean start_of_day)
{
Timespec result;
struct tm date;
long long secs = 0;
long long era = 0;
year -= 1900;
/* make a crude attempt to deal with dates outside the range of Dec
* 1901 to Jan 2038. Note we screw up centennial leap years here so
* hack alert */
if ((2 > year) || (136 < year))
{
era = year / 32;
year %= 32;
if (0 > year) { year += 32; era -= 1; }
}
date.tm_year = year;
date.tm_mon = month - 1;
date.tm_mday = day;
if (start_of_day)
{
date.tm_hour = 0;
date.tm_min = 0;
date.tm_sec = 0;
}
else
{
date.tm_hour = 23;
date.tm_min = 59;
date.tm_sec = 59;
}
date.tm_isdst = -1;
/* compute number of seconds */
secs = mktime (&date);
secs += era * THIRTY_TWO_YEARS;
result.tv_sec = secs;
result.tv_nsec = 0;
return result;
}
Timespec
gnc_dmy2timespec (int day, int month, int year)
{
return gnc_dmy2timespec_internal (day, month, year, TRUE);
}
Timespec
gnc_dmy2timespec_end (int day, int month, int year)
{
return gnc_dmy2timespec_internal (day, month, year, FALSE);
}
/********************************************************************\
\********************************************************************/
long int
gnc_timezone (struct tm *tm)
{
g_return_val_if_fail (tm != NULL, 0);
#ifdef HAVE_STRUCT_TM_GMTOFF
/* tm_gmtoff is seconds *east* of UTC and is
* already adjusted for daylight savings time. */
return -(tm->tm_gmtoff);
#else
/* timezone is seconds *west* of UTC and is
* not adjusted for daylight savings time.
* In Spring, we spring forward, wheee! */
return timezone - (tm->tm_isdst > 0 ? 60 * 60 : 0);
#endif
}
void
timespecFromTime_t( Timespec *ts, time_t t )
{
ts->tv_sec = t;
ts->tv_nsec = 0;
}
/********************** END OF FILE *********************************\
\********************************************************************/

206
src/engine/date.h Normal file
View File

@ -0,0 +1,206 @@
/********************************************************************\
* date.h -- utility functions to handle the date (adjusting, get *
* current date, etc.) for GnuCash *
* Copyright (C) 1997 Robin D. Clark (rclark@cs.hmc.edu) *
* Copyright (C) 1998, 1999, 2000 Linas Vepstas *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License*
* along with this program; if not, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
* Boston, MA 02111-1307, USA gnu@gnu.org *
\********************************************************************/
/* hack alert -- the scan and print routines should probably be moved
* to somewhere else. The engine really isn't involved with things
* like printing formats. This is needed mostly by the GUI and so on.
* If a file-io thing needs date handling, it should do it itself,
* instead of depending on the routines here. */
#ifndef XACC_DATE_H
#define XACC_DATE_H
#include <time.h>
/** Constants *******************************************************/
typedef enum
{
DATE_FORMAT_US, /* United states: mm/dd/yyyy */
DATE_FORMAT_UK, /* Britain: dd/mm/yyyy */
DATE_FORMAT_CE, /* Continental Europe: dd.mm.yyyy */
DATE_FORMAT_ISO, /* ISO: yyyy-mm-dd */
DATE_FORMAT_LOCALE /* Take from locale information */
} DateFormat;
#define DATE_FORMAT_FIRST DATE_FORMAT_US
#define DATE_FORMAT_LAST DATE_FORMAT_LOCALE
/* the maximum length of a string created by the date printers */
#define MAX_DATE_LENGTH 11
/** Datatypes *******************************************************/
/* struct timespec64 is just like timespec except that we use a 64-bit
* signed int to store the seconds. This should adequately cover
* dates in the distant future as well as the distant past, as long as
* they're not more than a couple dozen times the age of the universe.
* Note that both gcc and the IBM Toronto xlC compiler (aka CSet,
* VisualAge, etc) correctly handle long long as a 64 bit quantity,
* even on the 32-bit Intel x86 and PowerPC architectures. I'm
* assuming that all the other modern compilers are clean on this
* issue too. */
#ifndef SWIG /* swig 1.1p5 can't hack the long long type */
struct timespec64
{
long long int tv_sec;
long int tv_nsec;
};
#endif /* SWIG */
typedef struct timespec64 Timespec;
/** Prototypes ******************************************************/
/* strict equality */
gboolean timespec_equal(const Timespec *ta, const Timespec *tb);
/* comparison: if (ta < tb) -1; else if (ta > tb) 1; else 0; */
int timespec_cmp(const Timespec *ta, const Timespec *tb);
/* difference between ta and tb, results are normalised
* ie tv_sec and tv_nsec of the result have the same size
* abs(result.tv_nsec) <= 1000000000
*/
Timespec timespec_diff(const Timespec *ta, const Timespec *tb);
/*
* absolute value, also normalised
*/
Timespec timespec_abs(const Timespec *t);
/*
* convert a timepair on a certain day (localtime) to
* the timepair representing midday on that day
*/
Timespec timespecCanonicalDayTime(Timespec t);
void setDateFormat(DateFormat df);
/**
* printDate
* Convert a date as day / month / year integers into a localized string
* representation
*
* Args: buff - pointer to previously allocated character array; its size
* must be at lease MAX_DATE_LENTH bytes.
* day - day of the month as 1 ... 31
* month - month of the year as 1 ... 12
* year - year (4-digit)
*
* Return: nothing
*
* Globals: global dateFormat value
*/
void printDate (char * buff, int day, int month, int year);
void printDateSecs (char * buff, time_t secs);
char * xaccPrintDateSecs (time_t secs);
const char * gnc_print_date(Timespec ts);
/**
* Turns a time_t into a Timespec
**/
void timespecFromTime_t( Timespec *ts, time_t t );
/**
* scanDate
* Convert a string into day / month / year integers according to
* the current dateFormat value.
*
* Args: buff - pointer to date string
* day - will store day of the month as 1 ... 31
* month - will store month of the year as 1 ... 12
* year - will store the year (4-digit)
*
* Return: nothing
*
* Globals: global dateFormat value
*/
void scanDate (const char *buff, int *day, int *month, int *year);
/**
* dateSeparator
* Return the field separator for the current date format
*
* Args: none
*
* Return: date character
*
* Globals: global dateFormat value
*/
char dateSeparator(void);
/*
* hack alert XXX FIXME -- these date routines return incorrect
* values for dates before 1970. Most of them are good only up
* till 2038. This needs fixing ...
*/
time_t xaccDMYToSec (int day, int month, int year);
time_t xaccScanDateS (const char *buff);
/* Convert a day, month, and year to a Timespec */
Timespec gnc_dmy2timespec (int day, int month, int year);
/* Same as gnc_dmy2timespec, but last second of the day */
Timespec gnc_dmy2timespec_end (int day, int month, int year);
/* The gnc_iso8601_to_timespec_xxx() routines converts an ISO-8601 style
* date/time string to Timespec.
* For example: 1998-07-17 11:00:00.68-05
* is 680 milliseconds after 11 o'clock, central daylight time
* The _gmt() routine returns the time in gmt. The _local() routine
* returns the local time.
*
* The gnc_timespec_to_iso8601_buff() routine prints a Timespec
* as an ISO-8601 style string. The buffer must be long enough
* to contain the string. The string is null-terminated. This
* routine returns a pointer to the null terminator (and can
* thus be used in the 'stpcpy' metaphor of string concatenation).
*/
Timespec gnc_iso8601_to_timespec_local(const char *);
Timespec gnc_iso8601_to_timespec_gmt(const char *);
char * gnc_timespec_to_iso8601_buff (Timespec ts, char * buff);
/* The gnc_timezone function returns the number of seconds *west*
* of UTC represented by the tm argument, adjusted for daylight
* savings time.
*
* This function requires a tm argument returned by localtime or set
* by mktime. This is a strange function! It requires that localtime
* or mktime be called before use. Subsequent calls to localtime or
* mktime *may* invalidate the result! The actual contents of tm *may*
* be used for both timezone offset and daylight savings time, or only
* daylight savings time! Timezone stuff under unix is not
* standardized and is a big mess.
*/
long int gnc_timezone (struct tm *tm);
#endif /* XACC_DATE_H */

397
src/engine/design.txt Normal file
View File

@ -0,0 +1,397 @@
This document is becoming obsolete. Please refer to the design
documentation in src/doc/design for a complete description of the
Engine architecture.
Accounting Engine
-----------------
This document reviews the operation of, and various design points
pertinent to the GnuCash accounting engine. The latest version
of this document can be found in the engine source-code directory.
Stocks, non-Currency-Denominated Assets
---------------------------------------
The engine includes support for non-currency-denominated assets,
such as stocks, bonds, mutual funds, inventory. This is done with
two values in the Split structure:
double share_price;
double damount;
"damount" is the number of shares/items. It is an "immutable" quantity,
in that it cannot change except by transfer (sale/purchase). It is the
quantity that is used when computing balances.
"share_price" is the price of the item in question. The share-price is
of course subject to fluctuation.
The net-value of a split is the product of "damount" and "share_price".
The currency balance of an account is the sum of all "damounts" times
the latest, newest share-price.
Currency accounts should use a share price of 1.0 for all splits.
To maintain the double-entry consistency, one must have the following
hold true:
0.0 == sum of all split values.
If all splits are in the same currency, then this becomes:
0.0 == sum of all ((split->damount) * (split->share_price))
Thus, for example, the purchase of shares can be represented as:
source:
debit ABC Bank for $1045 (1045 dollars * dollar "price" of 1.00)
destination:
credit PQR Stock for $1000 (100 shares at $10 per share)
credit StockBroker category $45 in fees
If the splits are in mixed currencies and securities, then there must
be at least one common currency/security between all of them. Thus,
for example:
source:
debit ABC Bank for $1045 (1045 dollars * dollar "price" of 1.00)
destination:
credit VolkTrader for 2000 DM (1000 dollars at 2.0 mark per dollar)
credit Fees category $45 in fees
If the "currency" field is set to "DM" for the VolksTrader account,
and the "security" field is set to "USD", while the currency for ABC bank is
"USD", then the balancing equation becomes:
0.0 = 1045 * $1.00 - $1000 - 45 * $1.00
Note that we ignored the price when adding the second split.
Recoding a Stock Price
----------------------
A stock price may be recorded in a brokerage account with a single
split that has zero value:
(share price) * (zero shares) == (zero dollars)
This transaction does not violate the rules that all transactions must
have zero value. This transaction is ideal for recording prices. Its
the only transaction type that may have a single split; everything else
requires at least two splits to balance. (at least when double-entry
is enabled).
Recording a Stock Split
-----------------------
Stock splits (i.e. when a company issues x shares of new stock for every
share already owned) may be recorded with a pair of journal entries as
follows:
(-old num shrs) * (old price) + (new num shrs) * (new price) == 0.0
where each journal entry credits/debits the same account.
Of course (new num shrs) == (1+x) * (old num shrs)
and the price goes inversely.
Stock Options
-------------
Stock options are not currently supported. To support them, the
following needs to be added:
A stock option is an option to purchase stock at a specified price.
Options have an expiration date. When you purchase an option it is
pretty much like buying stock. However, some extra information needs
to be recorded. To fully record an option purchase, you need to record
the underlying stock that the option is on, the strike price (i.e. the
price that the underlying stock can be purchases for), an expiration date,
and whether the option is a put or a call. A put option is the option
to sell stock at the strike price, and a call option is the option to
purchase stock at the strike price. Once an option is bought, it can
have one of three dispositions: it can be sold, in which case, it is
pretty much just like a stock transaction. It can expire, in which
case the option is worthless, and (IIRC) can be/is treated as a sale
at a zero price. Thirdly, it can be exercised, which is a single
transaction whereby stock is purchased at the strike price, and
the option becomes worthless.
Another point: with standardized options one option contract represents
the ability to purchase (with a call option) or sell (with a put option)
100 shares of the underlying stock.
Error Reporting
---------------
The error reporting architecture (partially implemented), uses a globally
visible subroutine to return an error. In the naivest possible implementation,
the error reporting mechanism would look like this:
int error_num; /* global error number */
int xaccGetError (void) { return error_num; }
void xaccSomeFunction (Args *various_args) {
if (bad_thing_happened) error_num = 42;
}
Many programmers are used to a different interface, e.g.
int xaccSomeFunction (Args *various_args) {
if (bad_thing_happened) return (42);
}
Because of this, it is important to explain why the former design was
choosen over the latter. Let us begin by listing how the choosen design
is as good as, and in many ways can be better to the later design.
(1) Allows programmer to check for errors asynchronously, e.g. outside
of a performance critical loop, or far away, after the return of
several subroutines.
(2) (with the right implementation) Allows reporting of multiple, complex
errors. For example, it can be used to implement a trace mechanism.
(3) (with the right implementation) Can be thread safe.
(4) Allows errors that occurred deep in the implementation to be reported
up to much higher levels without requiring baggage in the middle.
The right implementation for (2) is to implement not a single
variable, but a stack or a ring (circular queue) on which error codes
are placed, and from which error codes can be retreived. The right
implementation for (3) is the use pthread_getspecific() to define a
per-thread global and/or ring/queue.
Engine Isolation
----------------
Goals of engine isolation:
o Hide the engine behind an API so that multiple, pluggable engines
could be created, e.g. SQL or CORBA.
o Engine users are blocked from being able to put engine internal
structures in an inconsistent state. Updates are "atomic".
Some half-finished thoughts about the engine API:
-- The engine structures should not be accessible to any code outside
of the engine. Thus, the engine structures have been moved to
AccountP.h, TransactionP.h, etc.
The *P.h files should not be included by code outside of the engine.
-- The down-side of hiding is that it can hurt performance. Even trivial data
accesses require a subroutine call. Maybe a smarter idea would be to leave
the structures exposed, allow direct manipulation, and then "copy-in" and
"copy-out" the structures into parallel structures when a hidden back end
needs to be invoked.
-- the upside of hiding behind an API is that the engine can be
instrumented with extension language (perl, scheme, tcl, python) hooks
for pre/post processing of the data. To further enable such hooks, we
should probably surround all operations on structures with "begin-edit"
and "end-edit" calls.
-- begin/end braces could potentially be useful for two-phase commit schemes.
where "end-edit" is replaced by "commit-edit" or "reject-edit".
Reconciliation
--------------
The 'reconcile' state of a transaction can have one of the following values:
/* Values for the reconciled field in Transaction: */
#define NREC 'n' /* not reconciled or cleared */
#define CREC 'c' /* The transaction has been cleared */
#define YREC 'y' /* The transaction has been reconciled */
#define FREC 'f' /* frozen into accounting period */
(Note that FREC is not yet used/implemented ...)
The process of reconciliation works as follows:
1) User enters new transaction. All splits are marked 'n' for 'new'
or 'no, not yet reconciled'.
2) User beleives that the transaction has cleared the bank,
e.g. that a cheque written out has been deposited/cashed.
User clicks in the 'R' column in the register gui,
marking the split 'c' for 'cleared'. User can freely
toggle this flag from the GUI with essentially no penalty,
no complaints. This is a 'safe' operation. Note that the
register shows the 'cleared' subtotal, which is, essentially,
a guess of the banks view of the account balance.
3) When user gets the bank statement, user launches the
reconcile dialogue. This dialogue is used to match transactions
on the bank statement with which is recorded locally.
Reconciled transactions are marked with a 'y'.
Note that once a transaction has been marked 'y', and the
user has 'finished' with the reconcile dialogue, then it
should be 'hard' to change the reconcile state from
the ordinary register dialogue. It should be sort-of
'set in stone'. (The engine does NOT enforce this,
only the gui enforces this.)
4) When the books are closed, all splits should be marked
'frozen', and become truly un-editable. The engine should
enforce 'frozen', and just plain not allow editing of closed
books, period. The only wat to change this would have been
to re-open the book. (and the reopening of a book would
change all 'f' to 'y'.)
About storing dates associated with reconcile:
> I think that there should be a date stamp attached to the reconciliation
> field so that as well as knowing that it has been reconciled, you also
> know *when* it was reconciled.
>
> This isn't so important for personal finances for the periodic user; I
> have in the past wanted to know when a particular transaction was
> reconciled. This is useful if you want to trace back from the
> electronic record to determine when the item actually cleared through
> the bank.
>
> This means that I can look at Cheque #428, written Jan 1/97, cashed in May
> 1997 (it sat in someone's desk for a while) in the computer system and say
> "Ah. It was marked as reconciled on June 12th/97. That was when I did the
> reconciliation of the May bank statements. Ergo, the cheque cleared in May,
> and that's the statement to go to to find a copy of the cheque..."
>
> It's not terribly important for cheques that get cashed right away; it *is*
> for things that hang around uncashed for a while.
If the above is implemented, what date should be stored if the user
toggles the recn flag a few time? The date of the last toggle?
The very first date that it was recn'ed?
Automatic Backup
----------------
The following has been implemented:
Have (by default) xacc create a backup file
filename-timestamp.xac on every save. This will eat up some disk
space until you go back and kill the older versions, but it's
better than not realizing that the data's subtly corrupt a week
later.
A lot of small-office/home systems do this. primarily useful as a
historical record, in case you accidentally wipe out something, and
don't spot it until later. Limited usefulness, but very nice in case
you accidentally delete an entire account.
To a limited degree, it provides atomicity/consistency/etc at the
course-grained account-group level.
Transaction Processing
----------------------
There is a rudimentary level of "TP" build in, via the routines
xaccTransBeginEdit(), xaccTransRollbackEdit(), and xaccTransCommitEdit(),
which allow changes to be made to a transaction, and then commited,
or rejected at the end. Handy for the GUI, if the user makes a bunch
of changes which they don't want to keep; also handy for an SQL back end,
where the Commit() routine triggers the actual update of the SQL database.
Note: 'Commit' is sometimes called 'post' in other systems: The
act of 'commiting' a transaction is the same as 'posting' the
transaction to the general ledger.
Some important implementation details to understand: the GUI currently
uses begin/end as a convenience, and thus, may hold a transaction in
the 'edit' state for a portentially long time (minutes or hours).
Thus, begin/end should not map naively to a table-lock/unlock.
Instead, an optimistic-locking scheme, as below, is prefered.
The SQL back-end should implement posting entirely in the
'Commit()' routine. The pseudo-algorithms should look as follows:
BeginEdit () {
// save a copy of what it was before we start editing
old_transaction = this;
}
SetValue (float amount) {
// some example editing done here
this->value = amount;
}
Commit () {
LockTable();
current = QueryTransaction();
// check ton make sure that what the sql db stores
// is identical to what we have on record as the
// 'original' tansaction. If its not, then someone
// got in there and modified it while we weren't
// looking; so we better report this back toi user.
if (current != old_transaction) Error(); Unlock(); return err;
// otherwise, all is OK, we have successfully obtained
// the lock and validated the data, so now just update things.
StoreTransaction (new_transaction);
UnlockTable();
old_transaction = NULL;
}
Rollback () {
// throw away the edits
this = old_transaction;
old_transaction = NULL;
}
The GUI should check to make sure that the Commit() routine didn't fail.
If it did fail, then the GUI should pop up a notice to the user stating
that the fundamental underlying data has changed, and that the user
should try doing the edit again. (It might be nice to indicate which
other human user might have changed the data so that they could
coordinate as needed.)
Journal Logs
------------
The following has been implemented; see TransLog.c for details.
Transaction logs. The idea was that every time a transaction
was called that would cause a data update, the information that
was updated would be dumped out into an "append only" log file.
This somewhat parallels what better database systems do to ensure
integrity; Oracle, for instance, has what is called an "archive log."
You have a copy of the database "synced up" as at some point in time,
and can apply "archive logs" to bring that old copy of the database
up to date should something go wrong to trash today's copy.
In effect, you'd have things like
=== 97/01/01 04:32:00 === Add Transaction ==== [whatever was added] ====
=== 97/01/01 04:32:02 === Delete Transaction ==== [whatever was deleted] ====
It also is a useful debugging tool, as if you make sure that the
"log_transaction()" call starts by opening the log file, writes, and
then closes and syncs, you know what is going on with the data even if
horrible things happen to the "master" database file.
Session Mgmt
------------
To allow the user of the engine some guarantee of atomic updates,
serialized file I/O, related miscellany, the concept of a session
is supported. No file IO can be performed until a session has
been created, and file updates are not guaranteed atomic unless
performed within a SessionBegin/SessionEnd pair.
Note that (in the current implementation) data can be manipulated
outside of the session; its just that it cannot be saved/made persistent.
The goal of session management is to ensure that e.g. two users don't end
up editing the same file at the same time, or, e.g. that an automatic
stock-quote update daemon running under a different pid doesn't trash
data being currently edited by the user.
Remaining Work Items
--------------------
To find other remaining work items in the code, grep for the string
"hack alert".
Ideas for engine enhancements:
-----------------------------
1) Have (by default) gnucash immediately re-read a file after every
write, and compare the two resulting AccountGroups for equality.
During development, this is a good idea, as it will help uncover
thinko's more quickly, instead of letting them hide for weeks or months
(as the last one did). Its not a bad self-consistency check when
monkeying with the internals, damn the performance.
It can be removed/disabled for product versions.
This document is dated May 2000

76
src/engine/extensions.txt Normal file
View File

@ -0,0 +1,76 @@
Proposed Extensions
-------------------
The following are proposals for various, as-yet-unimplemented
enhancements. The goal of this document is to understand some
of the changes that will come soon to the interfaces.
Accounting Periods
------------------
Acconting periods are implemented by creating an object that
describes the accounting period, and then assigning every
transaction to at least one period.
typedef struct _accountingPeriod {
GUID guid; // id
char *name; // user-selectable name
int kind; // enum {none, week, month, quarter, year }
Timespec * start_date; //
Timespec * end_date;
AccountGroup *topgrp; // chart of accounts for this period.
} AccountingPeriod;
The Transaction struct has to be extended with a period guid.
Every transaction can belong to at most one accounting period.
In addition, each chart of accounts needs to be extended with
an accounting period as well. This allows 'old' accounts to be deleted
from 'open books', without having to delete that same account from old
closed books. Basically, a new chart of accounts needs to be written
out/read from the engine for each new accounting period.
The xaccPeriodClose() subroutine performs the following:
-- crawl over all transactions and mark them as being part of this
accounting period (actually, new transactions should by default be
getting put into the currently open period...)
-- find the equity account (what if there is more than one equity account
?? what if equity has sub-accounts ?? )
-- transfer all income/expense to equity (shouldn't the equity account
match the income/expense heirarchy?)
-- return a new account group with new opening balances ...
Changes to Transaction Structure
--------------------------------
Add an accounting period guid (see above).
Changes to Journal Entry (Split) Structure
------------------------------------------
Voucher Reference
-----------------
Add a char * voucher; This is a generic id string that somehow
identifies the piece of paper/electronic document(s) that indicate
where/how to find the paper trial for this entry. This list of id's
should probably be a list of key-value pairs, where the keys & values
can be completely configured by the user, and hold the needed data
that the user wants to store. For the SQL backend, this is a key
to a user-definable table.
====================================================================
Additional Banking Info
BankId -- routing & transit number (US) or Bankleitzan (DE) or Banque (FR)
BranchID -- Agence (FR), blank for other countries
AcctKey -- Cle (FR), blank in others
Account type enumerants:
bank account types:
checking, savings, moneymarket, creditline, cma (cash amangement account)

View File

@ -0,0 +1,491 @@
/********************************************************************\
* gnc-associate-account.h : api for associating income and *
* expense accounts with stock/mutual fund accounts, for tracking *
* dividends, brokerage, and other stock-related expenses and *
* income so that they can be reported *
* Copyright 2000 Gnumatic Incorporated *
* Written by Robert Merkel <rgmerk@mira.net> *
* *
* WARNING WARNING WARNING: THIS STUFF IS TOTALLY UNTESTED AND *
* IS ONLY IN CVS FOR SAFEKEEPING *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License*
* along with this program; if not, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
* Boston, MA 02111-1307, USA gnu@gnu.org *
\********************************************************************/
#include "config.h"
#include "gnc-associate-account.h"
#include "gnc-engine-util.h"
static short module = MOD_ENGINE;
/* Maps GNCTrackingIncomeCategory to string keys. If this enum
changes, update */
static char * income_to_key[] = {"income-miscellaneous",
"income-interest",
"income-dividend"
"income-long-term-capital-gain",
"income-short-term-capital-gain"};
/* Maps GNCTrackingExpenseCategory to string keys. If this enum
changes, update */
static char * expense_to_key[] = {"expense-miscellaneous",
"expense-commission"};
static kvp_frame *
get_assoc_acc_frame(kvp_frame *account_frame)
{
kvp_frame *assoc_acc_frame;
kvp_value *assoc_acc_frame_kvpvalue =
kvp_frame_get_slot(account_frame, "associated-accounts");
assoc_acc_frame = kvp_value_get_frame(assoc_acc_frame_kvpvalue);
if(!assoc_acc_frame)
{
assoc_acc_frame = kvp_frame_new();
assoc_acc_frame_kvpvalue = kvp_value_new_frame(assoc_acc_frame);
kvp_frame_set_slot(account_frame, "associated-accounts",
assoc_acc_frame_kvpvalue);
}
return assoc_acc_frame;
}
static void
back_associate_expense_accounts(Account *stock_account,
GList *accounts,
GNCTrackingExpenseCategory category)
{
kvp_frame *acc_frame;
kvp_value *val, *stock_acc_guid_kvpval, *stock_acc_category_kvpval;
const GUID *stock_acc_guid;
const GUID *existing_acc_guid;
stock_acc_guid = xaccAccountGetGUID(stock_account);
stock_acc_guid_kvpval = kvp_value_new_guid(stock_acc_guid);
stock_acc_category_kvpval = kvp_value_new_string(expense_to_key[category]);
for(; accounts; accounts = g_list_next(accounts))
{
acc_frame = xaccAccountGetSlots(accounts->data);
g_return_if_fail(val = kvp_frame_get_slot(acc_frame,
"associated-stock-account"));
g_return_if_fail(kvp_value_get_type(val) == KVP_TYPE_GUID);
existing_acc_guid = kvp_value_get_guid(val);
g_return_if_fail(xaccGUIDType(existing_acc_guid) == GNC_ID_NONE);
kvp_frame_set_slot_nc(acc_frame, "associated-stock-account",
stock_acc_guid_kvpval);
kvp_frame_set_slot_nc(acc_frame, "associated-stock-account-category",
stock_acc_category_kvpval);
}
return;
}
static void
back_associate_income_accounts(Account *stock_account,
GList *accounts,
GNCTrackingIncomeCategory category)
{
kvp_frame *acc_frame;
kvp_value *val, *stock_acc_guid_kvpval, *stock_acc_category_kvpval;
const GUID *stock_acc_guid;
const GUID *existing_acc_guid;
stock_acc_guid = xaccAccountGetGUID(stock_account);
stock_acc_guid_kvpval = kvp_value_new_guid(stock_acc_guid);
stock_acc_category_kvpval = kvp_value_new_string(income_to_key[category]);
for(; accounts; accounts = g_list_next(accounts))
{
acc_frame = xaccAccountGetSlots(accounts->data);
g_return_if_fail(val = kvp_frame_get_slot(acc_frame,
"associated-stock-account"));
g_return_if_fail(kvp_value_get_type(val) == KVP_TYPE_GUID);
existing_acc_guid = kvp_value_get_guid(val);
g_return_if_fail(xaccGUIDType(existing_acc_guid) == GNC_ID_NONE);
kvp_frame_set_slot_nc(acc_frame, "associated-stock-account",
stock_acc_guid_kvpval);
kvp_frame_set_slot_nc(acc_frame, "associated-stock-account-category",
stock_acc_category_kvpval);
}
return;
}
static kvp_value *
make_kvpd_on_list(GList *account_list)
{
GList *iter;
kvp_value *retval;
kvp_value *guid_kvp;
GList *kvp_acc_list = NULL;
const GUID *acc_id;
for(iter = account_list; iter; iter = g_list_next(iter))
{
GNCAccountType type;
Account *current_account;
current_account = iter->data;
type = xaccAccountGetType(current_account);
g_return_val_if_fail(type == INCOME || type == EXPENSE, NULL);
acc_id = xaccAccountGetGUID(current_account);
guid_kvp = kvp_value_new_guid(acc_id);
kvp_acc_list = g_list_prepend(kvp_acc_list, guid_kvp);
}
kvp_acc_list = g_list_reverse(kvp_acc_list);
retval = kvp_value_new_glist_nc(kvp_acc_list);
return retval;
}
static GList *
de_kvp_account_list(kvp_value *kvpd_list)
{
GList *guid_account_list = kvp_value_get_glist(kvpd_list);
if (guid_account_list)
{
GList *expense_acc_list= NULL;
for(; guid_account_list; guid_account_list=g_list_next(guid_account_list))
{
g_list_prepend(expense_acc_list,
xaccAccountLookup(guid_account_list->data));
}
expense_acc_list = g_list_reverse(expense_acc_list);
return expense_acc_list;
}
else
{
return NULL;
}
}
/*********************************************************************\
* gnc_tracking_associate_income_accounts *
* associate a list of income accounts with a stock account *
* *
* NOTE: Please disassociate all the accounts in account_list *
* using gnc_tracking_dissociate_accounts if necessary, BEFORE *
* calling this function *
* *
* Args: stock_account - the stock account *
* category - the type of association *
* account_list - a GList of Account *'s of the accounts *
* to associate with the stock account *
* *
* Returns : void *
\*********************************************************************/
void
gnc_tracking_associate_income_accounts(Account *stock_account,
GNCTrackingIncomeCategory category,
GList *account_list)
{
kvp_frame *account_frame, *inc_account_frame;
kvp_value *kvpd_on_account_list;
GNCAccountType type;
g_return_if_fail(stock_account);
type = xaccAccountGetType(stock_account);
g_return_if_fail(type == STOCK || type == MUTUAL);
account_frame = xaccAccountGetSlots(stock_account);
g_return_if_fail(account_frame);
g_return_if_fail(category >= 0);
g_return_if_fail(category < GNC_TR_INC_N_CATEGORIES);
inc_account_frame = get_assoc_acc_frame(account_frame);
kvpd_on_account_list = make_kvpd_on_list(account_list);
back_associate_income_accounts(stock_account, account_list, category);
kvp_frame_set_slot_nc(inc_account_frame,
income_to_key[category],
kvpd_on_account_list);
}
/*********************************************************************\
* gnc_tracking_associate_expense_accounts *
* associate a list of expense accounts with a stock account *
* *
* NOTE: Please disassociate all the accounts in account_list *
* using gnc_tracking_dissociate_accounts if necessary, BEFORE *
* calling this function *
* *
* Args: stock_account - the stock account *
* category - the type of association *
* account_list - a GList of Account *'s of the accounts *
* to associate with the stock account *
* *
* Returns : void *
\*********************************************************************/
void
gnc_tracking_asssociate_expense_account(Account *stock_account,
GNCTrackingExpenseCategory category,
GList *account_list)
{
kvp_frame *account_frame, *expense_acc_frame;
kvp_value *kvpd_on_account_list;
GNCAccountType type;
g_return_if_fail(stock_account);
type = xaccAccountGetType(stock_account);
g_return_if_fail(type == STOCK || type == MUTUAL);
account_frame = xaccAccountGetSlots(stock_account);
g_return_if_fail(account_frame);
g_return_if_fail(category >= 0);
g_return_if_fail(category < GNC_TR_EXP_N_CATEGORIES);
expense_acc_frame = get_assoc_acc_frame(account_frame);
kvpd_on_account_list = make_kvpd_on_list(account_list);
back_associate_expense_accounts(stock_account, account_list, category);
kvp_frame_set_slot_nc(expense_acc_frame,
expense_to_key[category],
kvpd_on_account_list);
}
/*********************************************************************\
* gnc_tracking_find_expense_accounts *
* find out which accounts are associated with a particular *
* account in a particular way *
* *
* *
* Args: stock_account - the stock account *
* category - the type of association *
* *
* Returns : A GList of Account *'s listing the accounts *
\*********************************************************************/
GList *
gnc_tracking_find_expense_accounts(Account *stock_account,
GNCTrackingExpenseCategory category)
{
GNCAccountType type;
kvp_frame *account_frame, *expense_acc_frame;
kvp_value *kvpd_on_account_list;
type = xaccAccountGetType(stock_account);
g_return_val_if_fail(category >= 0 && category < GNC_TR_EXP_N_CATEGORIES,
NULL);
g_return_val_if_fail(type == STOCK || type == MUTUAL, NULL);
account_frame = xaccAccountGetSlots(stock_account);
g_return_val_if_fail(account_frame, NULL);
expense_acc_frame = get_assoc_acc_frame(account_frame);
kvpd_on_account_list = kvp_frame_get_slot(account_frame,
expense_to_key[category]);
return de_kvp_account_list(kvpd_on_account_list);
}
/*********************************************************************\
* gnc_tracking_find_income_accounts *
* find out which accounts are associated with a particular *
* account in a particular way *
* *
* *
* Args: stock_account - the stock account *
* category - the type of association *
* *
* Returns : A GList of Account *'s listing the accounts *
\*********************************************************************/
GList *
gnc_tracking_find_income_accounts(Account *stock_account,
GNCTrackingIncomeCategory category)
{
GNCAccountType type;
kvp_frame *account_frame, *income_acc_frame;
kvp_value *kvpd_on_account_list;
type = xaccAccountGetType(stock_account);
g_return_val_if_fail(category >= 0 && category < GNC_TR_INC_N_CATEGORIES,
NULL);
g_return_val_if_fail(type == STOCK || type == MUTUAL, NULL);
account_frame = xaccAccountGetSlots(stock_account);
g_return_val_if_fail(account_frame, NULL);
income_acc_frame = get_assoc_acc_frame(account_frame);
kvpd_on_account_list = kvp_frame_get_slot(income_acc_frame,
income_to_key[category]);
return de_kvp_account_list(kvpd_on_account_list);
}
/*********************************************************************\
* gnc_tracking_find_all_expense_accounts *
* find all expense accounts associated with a stock account *
* *
* Args: stock_account - the stock account *
* *
* Returns : A GList of Account *'s listing the accounts *
\*********************************************************************/
GList *
gnc_tracking_find_all_expense_accounts(Account *stock_account)
{
GList *complete_list=NULL;
int i;
for(i = 0; i < GNC_TR_EXP_N_CATEGORIES; i++)
{
complete_list =
g_list_concat(complete_list,
gnc_tracking_find_expense_accounts(stock_account, i));
}
return complete_list;
}
/*********************************************************************\
* gnc_tracking_find_all_income_accounts *
* find all income accounts associated with a stock account *
* *
* Args: stock_account - the stock account *
* *
* Returns : A GList of Account *'s listing the accounts *
\*********************************************************************/
GList *
gnc_tracking_find_all_income_accounts(Account *stock_account)
{
GList *complete_list= NULL;
int i;
for(i = 0; i < GNC_TR_EXP_N_CATEGORIES; i++)
{
g_list_concat(complete_list,
gnc_tracking_find_expense_accounts(stock_account,
i));
}
return complete_list;
}
/*********************************************************************\
* gnc_tracking_find_stock_account *
* find the stock account associated with this expense/income *
* account. If there is no association, return NULL *
* *
* Args: inc_or_expense_acc - the expense/income account *
* *
* *
* Returns : The associated stock account *
\*********************************************************************/
Account *
gnc_tracking_find_stock_account(Account *inc_or_expense_acc)
{
return NULL;
}
/*********************************************************************\
* gnc_tracking_dissociate_account *
* remove any association between this income/expense account *
* and any stock account it is presently associated with *
* account. *
* *
* Args: inc_or_expense_acc - the expense/income account *
* *
* *
* Returns : void *
\*********************************************************************/
void
gnc_tracking_dissociate_account(Account *inc_or_expense_account)
{
GNCAccountType type;
kvp_frame *stock_account_kvpframe, *assoc_acc_kvpframe;
kvp_frame *current_account_kvpframe;
kvp_value *stock_account_kvpval, *acc_list_kvpval, *category_kvpval;
const GUID *stock_account_guid, *inc_or_expense_account_guid, *current_guid;
Account *stock_account;
char *category_name;
GList *assoc_acc_list, *assoc_acc_list_start;
type = xaccAccountGetType(inc_or_expense_account);
g_return_if_fail(type == INCOME || type == EXPENSE);
current_account_kvpframe = xaccAccountGetSlots(inc_or_expense_account);
stock_account_kvpval = kvp_frame_get_slot(current_account_kvpframe,
"associated-stock-account");
stock_account_guid = kvp_value_get_guid(stock_account_kvpval);
if(xaccGUIDType(stock_account_guid) == GNC_ID_NULL)
return;
category_kvpval = kvp_frame_get_slot(current_account_kvpframe,
"associated-stock-account-category");
category_name = kvp_value_get_string(category_kvpval);
inc_or_expense_account_guid = xaccAccountGetGUID(inc_or_expense_account);
stock_account = xaccAccountLookup(stock_account_guid);
stock_account_kvpframe = xaccAccountGetSlots(stock_account);
g_return_if_fail(stock_account_kvpval =
kvp_frame_get_slot(stock_account_kvpframe,
"associated-accounts"));
assoc_acc_kvpframe = kvp_value_get_frame(stock_account_kvpval);
g_return_if_fail(acc_list_kvpval = kvp_frame_get_slot(assoc_acc_kvpframe,
category_name));
g_return_if_fail(assoc_acc_list_start =
kvp_value_get_glist(acc_list_kvpval));
for(assoc_acc_list = assoc_acc_list_start;
assoc_acc_list;
assoc_acc_list = g_list_next(assoc_acc_list))
{
g_return_if_fail(current_guid = kvp_value_get_guid(assoc_acc_list->data));
if(guid_equal(current_guid, inc_or_expense_account_guid))
{
assoc_acc_list_start =
g_list_remove_link(assoc_acc_list_start, assoc_acc_list);
g_list_free_1(assoc_acc_list);
acc_list_kvpval = kvp_value_new_glist_nc(assoc_acc_list);
kvp_frame_set_slot_nc(assoc_acc_kvpframe,
category_name,
acc_list_kvpval);
return;
}
}
/* should never happen */
PERR("Income/Expense account and stock account disagree on association");
}
/* ========================== END OF FILE ===================== */

View File

@ -0,0 +1,89 @@
/********************************************************************\
* gnc-associate-account.h : api for associating income and *
* expense accounts with stock/mutual fund accounts, for tracking *
* dividends, brokerage, and other stock-related expenses and *
* income so that they can be reported *
* Copyright 2000 Gnumatic Incorporated *
* Written by Robert Merkel <rgmerk@mira.net> *
*
* WARNING WARNING WARNING: THIS CODE IS TOTALLY UNTESTED. *
* THE ONLY REASON IT'S IN CVS IS FOR SAFEKEEPING *
* *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License*
* along with this program; if not, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
* Boston, MA 02111-1307, USA gnu@gnu.org *
\********************************************************************/
#include <Account.h>
#include <glib.h>
/*
* account_list is a list of account *'s, all of which much be expense
* accounts
*/
typedef enum {GNC_TR_INC_MISC,
GNC_TR_INC_INTEREST,
GNC_TR_INC__DIVIDEND,
GNC_TR_INC_LT_CG,
GNC_TR_INC_ST_CG,
GNC_TR_INC_N_CATEGORIES} GNCTrackingIncomeCategory;
typedef enum {GNC_TR_EXP_MISC,
GNC_TR_EXP_COMMISSION,
GNC_TR_EXP_N_CATEGORIES} GNCTrackingExpenseCategory;
/*
* account_list is a list of account *'s, all of which much be expense
* accounts. You can clear associations by setting account_list to NULL
*/
void gnc_tracking_associate_income_accounts(Account *stock_account,
GNCTrackingIncomeCategory category,
GList *account_list);
void gnc_tracking_asssociate_expense_account(Account *stock_account,
GNCTrackingExpenseCategory category,
GList *account_list);
/*
* returns a list of account *'s,
* returns null if no association specified
*/
GList *gnc_tracking_find_expense_accounts(Account *stock_account,
GNCTrackingExpenseCategory category);
GList *gnc_tracking_find_income_accounts(Account *stock_account,
GNCTrackingIncomeCategory category);
/* for ROI purposes we don't care about categories, these are "grab
all" for that purpose */
GList *gnc_tracking_find_all_expense_accounts(Account *stock_account);
GList *gnc_tracking_find_all_income_accounts(Account *stock_account);
/*
* reverse lookup - obviously returns a stock account (or NULL if none
* associated), and argument must be an income or expense account
*/
Account *gnc_tracking_find_stock_account(Account *inc_or_expense_acc);
void gnc_tracking_dissociate_account(Account *inc_or_expense_account);

102
src/engine/gnc-book-p.h Normal file
View File

@ -0,0 +1,102 @@
/********************************************************************\
* gnc-book-p.h -- private functions for gnc books. *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License*
* along with this program; if not, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
* Boston, MA 02111-1307, USA gnu@gnu.org *
* *
\********************************************************************/
/*
* HISTORY:
* Created 2001 by Rob Browning
* Copyright (c) 2001 Rob Browning
*/
#ifndef GNC_BOOK_P_H
#define GNC_BOOK_P_H
#include "Group.h"
#include "Backend.h"
#include "BackendP.h"
#include "gnc-pricedb.h"
#include "TransLog.h"
#include "gnc-engine-util.h"
#include "gnc-pricedb-p.h"
#include "DateUtils.h"
#include "gnc-engine.h"
#include "gnc-engine-util.h"
#include "gnc-book.h"
#include "gnc-pricedb.h"
#include "Group.h"
#define GNC_BACKEND_INTERFACE 0
struct gnc_book_struct
{
AccountGroup *topgroup;
GNCPriceDB *pricedb;
GList *sched_xactions;
AccountGroup *template_group;
/*
* should be set true if sched_xactions is changed
* before saving
*/
gboolean sx_notsaved;
/* the requested book id, in the form or a URI, such as
* file:/some/where, or sql:server.host.com:555
*/
char *book_id;
/* if any book subroutine failed, this records the failure reason
* (file not found, etc).
* This is a 'stack' that is one deep.
* FIXME: This is a hack. I'm trying to move us away from static
* global vars. This may be a temp fix if we decide to integrate
* FileIO errors into GNCBook errors.
*/
GNCBackendError last_err;
char *error_message;
char *fullpath;
/* ---------------------------------------------------- */
/* This struct member applies for network, rpc and SQL i/o */
/* It is not currently used for file i/o, but it should be. */
Backend *backend;
};
void gnc_book_set_group(GNCBook *book, AccountGroup *grp);
void gnc_book_set_pricedb(GNCBook *book, GNCPriceDB *db);
/*
* used by backends to mark the notsaved as FALSE just after
* loading. Do not use otherwise!
*/
void gnc_book_mark_saved(GNCBook *book);
void gnc_book_push_error (GNCBook *book, GNCBackendError err, char *message);
Backend* gncBackendInit_file(const char *book_id, void *data);
#endif /* GNC_BOOK_P_H */

1026
src/engine/gnc-book.c Normal file

File diff suppressed because it is too large Load Diff

210
src/engine/gnc-book.h Normal file
View File

@ -0,0 +1,210 @@
/********************************************************************\
* gnc-book.h -- dataset access (set of accounting books) *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License*
* along with this program; if not, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
* Boston, MA 02111-1307, USA gnu@gnu.org *
* *
\********************************************************************/
/*
* FILE:
* gnc-book.h
*
* FUNCTION:
* Encapsulate all the information about a gnucash dataset, including
* the methods used to read and write them to datastores.
*
* This class provides several important services:
*
* 1) Prevents multiple users from editing the same file at the same
* time, thus avoiding lost data due to race conditions. Thus
* an open session implies that the associated file is locked.
*
* 2) Provides a search path for the file to be edited. This should
* simplify install & maintenance problems for naive users who
* may not have a good grasp on what a file system is, or where
* they want to keep their data files.
*
* The current implementations assumes the use of files and file
* locks; however, the API was designed to be general enough to
* allow the use of generic URL's, and/or implementation on top
* of SQL or other database/persistant object technology.
*
* HISTORY:
* Created by Linas Vepstas December 1998
* Copyright (c) 1998, 1999 Linas Vepstas
* Copyright (c) 2000 Dave Peticolas
*/
#ifndef GNC_BOOK_H
#define GNC_BOOK_H
#include "gnc-pricedb.h"
#include "Backend.h"
#include "Group.h"
/** TYPES **********************************************************/
struct gnc_book_struct;
typedef struct gnc_book_struct GNCBook;
/** PROTOTYPES ******************************************************/
GNCBook * gnc_book_new (void);
void gnc_book_destroy (GNCBook *book);
/* The gnc_book_begin () method begins a new book. It takes as an argument
* the book id. The book id must be a string in the form of a URI/URL.
* In the current implementation, the following URL's are supported
* -- File URI of the form
* "file:/home/somewhere/somedir/file.xac"
* The path part must be a valid path. The file-part must be
* a valid old-style-xacc or new-style-gnucash-format file. Paths
* may be relative or absolute. If the path is relative; that is,
* if the argument is "file:somefile.xac" then a sequence of
* search paths are checked for a file of this name.
*
* -- Postgres URI of the form
* "postgres://hostname.com/dbname"
* See the sql subdirectory for more info.
*
* The 'ignore_lock' argument, if set to TRUE, will cause this routine
* to ignore any file locks that it finds. If set to FALSE, then
* file locks will be tested and obeyed.
*
* If the file exists, can be opened and read, and a lock can be obtained
* then a lock will be obtained and the function returns TRUE.
*
* If the file/database doesn't exist, and the create_if_nonexistent
* flag is set to TRUE, then the database is created.
*
* Otherwise the function returns FALSE.
*/
gboolean gnc_book_begin (GNCBook *book, const char * book_id,
gboolean ignore_lock, gboolean create_if_nonexistent);
/* The gnc_book_load() method loads the data associated with the book.
* The function returns TRUE on success.
*/
gboolean gnc_book_load (GNCBook *book);
/* The gnc_book_get_error() routine can be used to obtain the reason
* for any failure. Calling this routine returns the current error.
*
* The gnc_book_pop_error() routine can be used to obtain the reason
* for any failure. Calling this routine resets the error value.
*
* This routine allows an implementation of multiple error values,
* e.g. in a stack, where this routine pops the top value. The current
* implementation has a stack that is one-deep.
*
* See Backend.h for a listing of returned errors.
*/
GNCBackendError gnc_book_get_error (GNCBook *book);
const char * gnc_book_get_error_message(GNCBook *book);
GNCBackendError gnc_book_pop_error (GNCBook *book);
AccountGroup *gnc_book_get_group (GNCBook *book);
void gnc_book_set_group(GNCBook *book, AccountGroup *group);
GNCPriceDB *gnc_book_get_pricedb (GNCBook *book);
guint gnc_book_count_transactions(GNCBook *book);
/*
* gnc_book_get_commodity_table returns the commodity table associated with
* the BOOK. At the moment this just returns the global commodity table,
* but if we get everything using this we can make it a non-global table :)
*/
gnc_commodity_table* gnc_book_get_commodity_table(GNCBook *book);
/**
* Returns the list of scheduled transactions.
**/
GList * gnc_book_get_schedxactions( GNCBook *book );
void gnc_book_set_schedxactions( GNCBook *book, GList *newList );
AccountGroup *gnc_book_get_template_group( GNCBook *book );
void gnc_book_set_template_group( GNCBook *book, AccountGroup *templateGroup );
/* The gnc_book_get_file_path() routine returns the fully-qualified file
* path for the book. That is, if a relative or partial filename
* was for the book, then it had to have been fully resolved to
* open the book. This routine returns the result of this resolution.
* The path is always guarenteed to reside in the local file system,
* even if the book itself was opened as a URL. (currently, the
* filepath is derived from the url by substituting commas for
* slashes).
*
* The gnc_book_get_url() routine returns the url that was opened.
* URL's for local files take the form of
* file:/some/where/some/file.gml
*/
const char * gnc_book_get_file_path (GNCBook *book);
const char * gnc_book_get_url (GNCBook *book);
/*
* The gnc_book_not_saved() subroutine will return TRUE
* if any data in the book hasn't been saved to long-term storage.
*/
gboolean gnc_book_not_saved(GNCBook *book);
/* FIXME: This isn't as thorough as we might want it to be... */
gboolean gnc_book_save_may_clobber_data (GNCBook *book);
/* The gnc_book_save() method will commit all changes that have been
* made to the book. In the current implementation, this is nothing
* more than a write to the file of the current AccountGroup of the
* book.
*
* The gnc_book_end() method will release the session lock. It will *not*
* save the account group to a file. Thus, this method acts as an "abort"
* or "rollback" primitive.
*/
void gnc_book_save (GNCBook *book);
void gnc_book_end (GNCBook *book);
/* The gnc_book_events_pending() method will return TRUE if the backend
* has pending events which must be processed to bring the engine
* up to date with the backend.
*
* The gnc_book_process_events() method will process any events indicated
* by the gnc_book_events_pending() method. It returns TRUE if the
* engine was modified while engine events were suspended.
*/
gboolean gnc_book_events_pending (GNCBook *book);
gboolean gnc_book_process_events (GNCBook *book);
/* The xaccResolveFilePath() routine is a utility that will accept
* a fragmentary filename as input, and resolve it into a fully
* qualified path in the file system, i.e. a path that begins with
* a leading slash. First, the current working directory is
* searched for the file. Next, the directory $HOME/.gnucash/data,
* and finally, a list of other (configurable) paths. If the file
* is not found, then the path $HOME/.gnucash/data is used. If
* $HOME is not defined, then the current working directory is
* used.
*/
char * xaccResolveFilePath (const char * filefrag);
char * xaccResolveURL (const char * pathfrag);
/* Run the RPC Server */
void gnc_run_rpc_server (void);
#endif /* GNC_BOOK_H */

755
src/engine/gnc-commodity.c Normal file
View File

@ -0,0 +1,755 @@
/********************************************************************
* gnc-commodity.c -- api for tradable commodities (incl. currency) *
* Copyright (C) 2000 Bill Gribble *
* Copyright (C) 2001 Linas Vepstas *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License*
* along with this program; if not, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
* Boston, MA 02111-1307, USA gnu@gnu.org *
* *
*******************************************************************/
#define _GNU_SOURCE
#include "config.h"
#include <limits.h>
#include <string.h>
#include <stdio.h>
#include <glib.h>
#include "gnc-commodity.h"
#include "gnc-engine-util.h"
/* parts per unit is nominal, i.e. number of 'partname' units in
* a 'unitname' unit. fraction is transactional, i.e. how many
* of the smallest-transactional-units of the currency are there
* in a 'unitname' unit. */
struct _gnc_commodity {
char * fullname;
char * namespace;
char * mnemonic;
char * printname;
char * exchange_code; /* CUSIP or other identifying code */
int fraction;
char * unique_name;
gint16 mark; /* user-defined mark, handy for traversals */
};
struct _gnc_commodity_namespace {
GHashTable * table;
};
struct _gnc_commodity_table {
GHashTable * table;
};
typedef struct _gnc_commodity_namespace gnc_commodity_namespace;
/********************************************************************
* gnc_commodity_new
********************************************************************/
static void
reset_printname(gnc_commodity *com)
{
g_free(com->printname);
com->printname = g_strdup_printf("%s (%s)",
com->mnemonic ? com->mnemonic : "",
com->fullname ? com->fullname : "");
}
static void
reset_unique_name(gnc_commodity *com)
{
g_free(com->unique_name);
com->unique_name = g_strdup_printf("%s::%s",
com->namespace ? com->namespace : "",
com->mnemonic ? com->mnemonic : "");
}
gnc_commodity *
gnc_commodity_new(const char * fullname,
const char * namespace, const char * mnemonic,
const char * exchange_code,
int fraction)
{
gnc_commodity * retval = g_new0(gnc_commodity, 1);
retval->fullname = g_strdup(fullname);
retval->namespace = g_strdup(namespace);
retval->mnemonic = g_strdup(mnemonic);
retval->exchange_code = g_strdup(exchange_code);
retval->fraction = fraction;
retval->mark = 0;
reset_printname(retval);
reset_unique_name(retval);
return retval;
}
/********************************************************************
* gnc_commodity_destroy
********************************************************************/
void
gnc_commodity_destroy(gnc_commodity * cm) {
if(!cm) return;
g_free(cm->fullname);
g_free(cm->printname);
g_free(cm->namespace);
g_free(cm->exchange_code);
g_free(cm->mnemonic);
g_free(cm->unique_name);
cm->mark = 0;
g_free(cm);
}
/********************************************************************
* gnc_commodity_get_mnemonic
********************************************************************/
const char *
gnc_commodity_get_mnemonic(const gnc_commodity * cm) {
if(!cm) return NULL;
return cm->mnemonic;
}
/********************************************************************
* gnc_commodity_get_printname
********************************************************************/
const char *
gnc_commodity_get_printname(const gnc_commodity * cm) {
if(!cm) return NULL;
return cm->printname;
}
/********************************************************************
* gnc_commodity_get_namespace
********************************************************************/
const char *
gnc_commodity_get_namespace(const gnc_commodity * cm) {
if(!cm) return NULL;
return cm->namespace;
}
/********************************************************************
* gnc_commodity_get_fullname
********************************************************************/
const char *
gnc_commodity_get_fullname(const gnc_commodity * cm) {
if(!cm) return NULL;
return cm->fullname;
}
/********************************************************************
* gnc_commodity_get_unique_name
********************************************************************/
const char *
gnc_commodity_get_unique_name(const gnc_commodity * cm) {
if(!cm) return NULL;
return cm->unique_name;
}
/********************************************************************
* gnc_commodity_get_exchange_code
********************************************************************/
const char *
gnc_commodity_get_exchange_code(const gnc_commodity * cm) {
if(!cm) return NULL;
return cm->exchange_code;
}
/********************************************************************
* gnc_commodity_get_fraction
********************************************************************/
int
gnc_commodity_get_fraction(const gnc_commodity * cm) {
if(!cm) return 0;
return cm->fraction;
}
/********************************************************************
* gnc_commodity_get_mark
********************************************************************/
gint16
gnc_commodity_get_mark(const gnc_commodity * cm) {
if(!cm) return 0;
return cm->mark;
}
/********************************************************************
* gnc_commodity_set_mnemonic
********************************************************************/
void
gnc_commodity_set_mnemonic(gnc_commodity * cm, const char * mnemonic) {
if(!cm) return;
if(cm->mnemonic == mnemonic) return;
g_free(cm->mnemonic);
cm->mnemonic = g_strdup(mnemonic);
reset_printname(cm);
reset_unique_name(cm);
}
/********************************************************************
* gnc_commodity_set_namespace
********************************************************************/
void
gnc_commodity_set_namespace(gnc_commodity * cm, const char * namespace) {
if(!cm) return;
if(cm->namespace == namespace) return;
g_free(cm->namespace);
cm->namespace = g_strdup(namespace);
reset_printname(cm);
reset_unique_name(cm);
}
/********************************************************************
* gnc_commodity_set_fullname
********************************************************************/
void
gnc_commodity_set_fullname(gnc_commodity * cm, const char * fullname) {
if(!cm) return;
if(cm->fullname == fullname) return;
g_free(cm->fullname);
cm->fullname = g_strdup(fullname);
reset_printname(cm);
}
/********************************************************************
* gnc_commodity_set_exchange_code
********************************************************************/
void
gnc_commodity_set_exchange_code(gnc_commodity * cm,
const char * exchange_code) {
if(!cm) return;
if(cm->exchange_code == exchange_code) return;
g_free(cm->exchange_code);
cm->exchange_code = g_strdup(exchange_code);
}
/********************************************************************
* gnc_commodity_set_fraction
********************************************************************/
void
gnc_commodity_set_fraction(gnc_commodity * cm, int fraction) {
if(!cm) return;
cm->fraction = fraction;
}
/********************************************************************
* gnc_commodity_get_mark
********************************************************************/
void
gnc_commodity_set_mark(gnc_commodity * cm, gint16 mark) {
if(!cm) return;
cm->mark = mark;
}
/********************************************************************
* gnc_commodity_equiv
* are two commodities the same?
********************************************************************/
gboolean
gnc_commodity_equiv(const gnc_commodity * a, const gnc_commodity * b) {
if(a == b) return TRUE;
if(!a || !b) return FALSE;
if(safe_strcmp(a->namespace, b->namespace) != 0) return FALSE;
if(safe_strcmp(a->mnemonic, b->mnemonic) != 0) return FALSE;
return TRUE;
}
/********************************************************************
* gnc_commodity_table_new
* make a new commodity table
********************************************************************/
gnc_commodity_table *
gnc_commodity_table_new(void) {
gnc_commodity_table * retval = g_new0(gnc_commodity_table, 1);
retval->table = g_hash_table_new(&g_str_hash, &g_str_equal);
return retval;
}
/********************************************************************
* gnc_commodity_get_size
* get the size of the commodity table
********************************************************************/
guint
gnc_commodity_table_get_number_of_namespaces(gnc_commodity_table* tbl)
{
g_return_val_if_fail(tbl, 0);
g_return_val_if_fail(tbl->table, 0);
return g_hash_table_size(tbl->table);
}
static void
count_coms(gpointer key, gpointer value, gpointer user_data)
{
GHashTable *tbl = ((gnc_commodity_namespace*)value)->table;
guint *count = (guint*)user_data;
if(safe_strcmp((char*)key, GNC_COMMODITY_NS_ISO) == 0)
{
/* don't count default commodities */
return;
}
if(!value) return;
*count += g_hash_table_size(tbl);
}
guint
gnc_commodity_table_get_size(gnc_commodity_table* tbl)
{
guint count = 0;
g_return_val_if_fail(tbl, 0);
g_return_val_if_fail(tbl->table, 0);
g_hash_table_foreach(tbl->table, count_coms, (gpointer)&count);
return count;
}
/********************************************************************
* gnc_commodity_table_lookup
* locate a commodity by namespace and mnemonic.
********************************************************************/
gnc_commodity *
gnc_commodity_table_lookup(const gnc_commodity_table * table,
const char * namespace, const char * mnemonic) {
gnc_commodity_namespace * nsp = NULL;
if (!table || !namespace || !mnemonic) return NULL;
nsp = g_hash_table_lookup(table->table, (gpointer)namespace);
if(nsp) {
return g_hash_table_lookup(nsp->table, (gpointer)mnemonic);
}
else {
return NULL;
}
}
/********************************************************************
* gnc_commodity_table_lookup
* locate a commodity by unique name.
********************************************************************/
gnc_commodity *
gnc_commodity_table_lookup_unique(const gnc_commodity_table *table,
const char * unique_name)
{
char *namespace;
char *mnemonic;
gnc_commodity *commodity;
if (!table || !unique_name) return NULL;
namespace = g_strdup (unique_name);
mnemonic = strstr (namespace, "::");
if (!mnemonic)
{
g_free (namespace);
return NULL;
}
*mnemonic = '\0';
mnemonic += 2;
commodity = gnc_commodity_table_lookup (table, namespace, mnemonic);
g_free (namespace);
return commodity;
}
/********************************************************************
* gnc_commodity_table_find_full
* locate a commodity by namespace and printable name
********************************************************************/
gnc_commodity *
gnc_commodity_table_find_full(const gnc_commodity_table * table,
const char * namespace,
const char * fullname) {
gnc_commodity * retval=NULL;
GList * all;
GList * iterator;
if (!fullname || (fullname[0] == '\0'))
return NULL;
all = gnc_commodity_table_get_commodities(table, namespace);
for(iterator = all; iterator; iterator=iterator->next) {
if(!strcmp(fullname,
gnc_commodity_get_printname(iterator->data))) {
retval = iterator->data;
break;
}
}
g_list_free (all);
return retval;
}
/********************************************************************
* gnc_commodity_table_insert
* add a commodity to the table.
********************************************************************/
gnc_commodity *
gnc_commodity_table_insert(gnc_commodity_table * table,
gnc_commodity * comm) {
gnc_commodity_namespace * nsp = NULL;
gnc_commodity *c;
if (!table) return NULL;
if (!comm) return NULL;
c = gnc_commodity_table_lookup (table, comm->namespace, comm->mnemonic);
if (c) {
if (c == comm)
return c;
gnc_commodity_set_fullname (c, gnc_commodity_get_fullname (comm));
gnc_commodity_set_fraction (c, gnc_commodity_get_fraction (comm));
gnc_commodity_set_exchange_code (c,
gnc_commodity_get_exchange_code (comm));
gnc_commodity_destroy (comm);
return c;
}
nsp = g_hash_table_lookup(table->table, (gpointer)(comm->namespace));
if(!nsp) {
nsp = g_new0(gnc_commodity_namespace, 1);
nsp->table = g_hash_table_new(g_str_hash, g_str_equal);
g_hash_table_insert(table->table,
g_strdup(comm->namespace),
(gpointer)nsp);
}
g_hash_table_insert(nsp->table,
(gpointer)g_strdup(comm->mnemonic),
(gpointer)comm);
return comm;
}
/********************************************************************
* gnc_commodity_table_remove
* remove a commodity from the table.
********************************************************************/
void
gnc_commodity_table_remove(gnc_commodity_table * table,
gnc_commodity * comm)
{
gnc_commodity_namespace * nsp;
gnc_commodity *c;
if (!table) return;
if (!comm) return;
c = gnc_commodity_table_lookup (table, comm->namespace, comm->mnemonic);
if (c != comm) return;
nsp = g_hash_table_lookup (table->table, comm->namespace);
if (!nsp) return;
g_hash_table_remove (nsp->table, comm->mnemonic);
}
/********************************************************************
* gnc_commodity_table_has_namespace
* see if any commodities in the namespace exist
********************************************************************/
int
gnc_commodity_table_has_namespace(const gnc_commodity_table * table,
const char * namespace) {
gnc_commodity_namespace * nsp = NULL;
if(!table || !namespace) { return 0; }
nsp = g_hash_table_lookup(table->table, (gpointer)namespace);
if(nsp) {
return 1;
}
else {
return 0;
}
}
static void
hash_keys_helper(gpointer key, gpointer value, gpointer data) {
GList ** l = data;
*l = g_list_prepend(*l, key);
}
static GList *
g_hash_table_keys(GHashTable * table) {
GList * l = NULL;
g_hash_table_foreach(table, &hash_keys_helper, (gpointer) &l);
return l;
}
static void
hash_values_helper(gpointer key, gpointer value, gpointer data) {
GList ** l = data;
*l = g_list_prepend(*l, value);
}
static GList *
g_hash_table_values(GHashTable * table) {
GList * l = NULL;
g_hash_table_foreach(table, &hash_values_helper, (gpointer) &l);
return l;
}
/********************************************************************
* gnc_commodity_table_get_namespaces
* see if any commodities in the namespace exist
********************************************************************/
GList *
gnc_commodity_table_get_namespaces(const gnc_commodity_table * table) {
if (!table)
return NULL;
return g_hash_table_keys(table->table);
}
/********************************************************************
* gnc_commodity_table_get_commodities
* list commodities in a give namespace
********************************************************************/
GList *
gnc_commodity_table_get_commodities(const gnc_commodity_table * table,
const char * namespace) {
gnc_commodity_namespace * ns = NULL;
if(table) {
ns = g_hash_table_lookup(table->table, (gpointer)namespace);
}
if(ns) {
return g_hash_table_values(ns->table);
}
else {
return NULL;
}
}
/********************************************************************
* gnc_commodity_table_add_namespace
* add an empty namespace if it does not exist
********************************************************************/
void
gnc_commodity_table_add_namespace(gnc_commodity_table * table,
const char * namespace) {
gnc_commodity_namespace * ns = NULL;
if(table) {
ns = g_hash_table_lookup(table->table, (gpointer)namespace);
}
if(!ns) {
ns = g_new0(gnc_commodity_namespace, 1);
ns->table = g_hash_table_new(g_str_hash, g_str_equal);
g_hash_table_insert(table->table,
(gpointer) g_strdup(namespace),
(gpointer) ns);
}
}
/********************************************************************
* gnc_commodity_table_delete_namespace
* delete a namespace
********************************************************************/
static int
ns_helper(gpointer key, gpointer value, gpointer user_data) {
gnc_commodity * c = value;
gnc_commodity_destroy(c);
g_free(key);
return TRUE;
}
void
gnc_commodity_table_delete_namespace(gnc_commodity_table * table,
const char * namespace) {
gpointer orig_key;
gnc_commodity_namespace * value;
if(table) {
if(g_hash_table_lookup_extended(table->table,
(gpointer) namespace,
&orig_key,
(gpointer)&value)) {
g_hash_table_remove(table->table, namespace);
g_hash_table_foreach_remove(value->table, ns_helper, NULL);
g_hash_table_destroy(value->table);
g_free(value);
g_free(orig_key);
}
}
}
void
gnc_commodity_table_remove_non_iso (gnc_commodity_table *t)
{
GList *namespaces;
GList *node;
if (!t) return;
namespaces = gnc_commodity_table_get_namespaces (t);
for (node = namespaces; node; node = node->next)
{
char *ns = node->data;
if (safe_strcmp (ns, GNC_COMMODITY_NS_ISO) == 0)
continue;
gnc_commodity_table_delete_namespace (t, ns);
}
g_list_free (namespaces);
}
/********************************************************************
* gnc_commodity_table_foreach_commodity
* call user-defined function once for every commodity in every
* namespace
********************************************************************/
typedef struct {
gboolean ok;
gboolean (*func)(gnc_commodity *, gpointer);
gpointer user_data;
} IterData;
static void
iter_commodity (gpointer key, gpointer value, gpointer user_data)
{
IterData *iter_data = (IterData *) user_data;
gnc_commodity *cm = (gnc_commodity *) value;
if (iter_data->ok)
{
iter_data->ok = (iter_data->func)(cm, iter_data->user_data);
}
}
static void
iter_namespace (gpointer key, gpointer value, gpointer user_data)
{
GHashTable *namespace_hash = ((gnc_commodity_namespace *) value)->table;
g_hash_table_foreach (namespace_hash, iter_commodity, user_data);
}
gboolean
gnc_commodity_table_foreach_commodity (gnc_commodity_table * tbl,
gboolean (*f)(gnc_commodity *, gpointer),
gpointer user_data)
{
IterData iter_data;
iter_data.ok = TRUE;
iter_data.func = f;
iter_data.user_data = user_data;
g_hash_table_foreach(tbl->table, iter_namespace, (gpointer)&iter_data);
return iter_data.ok;
}
/********************************************************************
* gnc_commodity_table_destroy
* cleanup and free.
********************************************************************/
static int
ct_helper(gpointer key, gpointer value, gpointer data) {
gnc_commodity_namespace * ns = value;
g_hash_table_foreach_remove(ns->table, ns_helper, NULL);
g_hash_table_destroy(ns->table);
ns->table = NULL;
g_free(ns);
g_free(key);
return TRUE;
}
void
gnc_commodity_table_destroy(gnc_commodity_table * t) {
if (!t) return;
g_hash_table_foreach_remove(t->table, ct_helper, t);
g_hash_table_destroy(t->table);
g_free(t);
}
/* ========================= END OF FILE ============================== */

116
src/engine/gnc-commodity.h Normal file
View File

@ -0,0 +1,116 @@
/********************************************************************
* gnc-commodity.h -- api for tradable commodities (incl. currency) *
* Copyright (C) 2000 Bill Gribble *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License*
* along with this program; if not, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
* Boston, MA 02111-1307, USA gnu@gnu.org *
* *
*******************************************************************/
#ifndef GNC_COMMODITY_H
#define GNC_COMMODITY_H
#include <glib.h>
typedef struct _gnc_commodity gnc_commodity;
typedef struct _gnc_commodity_table gnc_commodity_table;
#define GNC_COMMODITY_NS_LEGACY "GNC_LEGACY_CURRENCIES"
#define GNC_COMMODITY_NS_ISO "ISO4217"
#define GNC_COMMODITY_NS_NASDAQ "NASDAQ"
#define GNC_COMMODITY_NS_NYSE "NYSE"
#define GNC_COMMODITY_NS_EUREX "EUREX"
#define GNC_COMMODITY_NS_MUTUAL "FUND"
#define GNC_COMMODITY_NS_AMEX "AMEX"
#define GNC_COMMODITY_NS_ASX "ASX"
/* gnc_commodity functions */
gnc_commodity * gnc_commodity_new(const char * fullname,
const char * namespace,
const char * mnemonic,
const char * exchange_code,
int fraction);
void gnc_commodity_destroy(gnc_commodity * cm);
const char * gnc_commodity_get_mnemonic(const gnc_commodity * cm);
const char * gnc_commodity_get_namespace(const gnc_commodity * cm);
const char * gnc_commodity_get_fullname(const gnc_commodity * cm);
const char * gnc_commodity_get_printname(const gnc_commodity * cm);
const char * gnc_commodity_get_exchange_code(const gnc_commodity * cm);
const char * gnc_commodity_get_unique_name(const gnc_commodity * cm);
int gnc_commodity_get_fraction(const gnc_commodity * cm);
gint16 gnc_commodity_get_mark(const gnc_commodity * cm);
void gnc_commodity_set_mnemonic(gnc_commodity * cm, const char * mnemonic);
void gnc_commodity_set_namespace(gnc_commodity * cm, const char * namespace);
void gnc_commodity_set_fullname(gnc_commodity * cm, const char * fullname);
void gnc_commodity_set_exchange_code(gnc_commodity * cm,
const char * exchange_code);
void gnc_commodity_set_fraction(gnc_commodity * cm, int smallest_fraction);
void gnc_commodity_set_mark(gnc_commodity * cm, gint16 mark);
gboolean gnc_commodity_equiv(const gnc_commodity * a, const gnc_commodity * b);
/* gnc_commodity_table functions : operate on a database of commodity
* info */
gnc_commodity_table * gnc_commodity_table_new(void);
void gnc_commodity_table_destroy(gnc_commodity_table * table);
gnc_commodity * gnc_commodity_table_lookup(const gnc_commodity_table * table,
const char * namespace,
const char * mnemonic);
gnc_commodity *
gnc_commodity_table_lookup_unique(const gnc_commodity_table *table,
const char * unique_name);
gnc_commodity * gnc_commodity_table_find_full(const gnc_commodity_table * t,
const char * namespace,
const char * fullname);
gnc_commodity * gnc_commodity_table_insert(gnc_commodity_table * table,
gnc_commodity * comm);
void gnc_commodity_table_remove(gnc_commodity_table * table,
gnc_commodity * comm);
int gnc_commodity_table_has_namespace(const gnc_commodity_table * t,
const char * namespace);
guint gnc_commodity_table_get_size(gnc_commodity_table* tbl);
guint gnc_commodity_table_get_number_of_namespaces(gnc_commodity_table* tbl);
/* The next two functions return newly allocated lists which should
* be freed with g_list_free. */
GList * gnc_commodity_table_get_namespaces(const gnc_commodity_table * t);
GList * gnc_commodity_table_get_commodities(const gnc_commodity_table * t,
const char * namespace);
void gnc_commodity_table_add_namespace(gnc_commodity_table * table,
const char * namespace);
void gnc_commodity_table_delete_namespace(gnc_commodity_table * t,
const char * namespace);
void gnc_commodity_table_remove_non_iso (gnc_commodity_table *t);
/* gnc_commodity_table_foreach_commodity - call f once for each commodity in
* table, until and unless f returns FALSE.
*/
gboolean gnc_commodity_table_foreach_commodity(gnc_commodity_table * table,
gboolean (*f)(gnc_commodity *cm,
gpointer user_data),
gpointer user_data);
#endif

31
src/engine/gnc-common.h Normal file
View File

@ -0,0 +1,31 @@
/********************************************************************\
* gnc-common.h -- define platform independent items *
* *
* Copyright (C) 1999, 2000 Rob Browning *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License*
* along with this program; if not, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
* Boston, MA 02111-1307, USA gnu@gnu.org *
* *
\********************************************************************/
#ifndef GNC_COMMON_H
#define GNC_COMMON_H
#include "config.h"
#include <glib.h>
#endif

View File

@ -0,0 +1,392 @@
/********************************************************************\
* gnc-engine-util.c -- GnuCash engine utility functions *
* Copyright (C) 1997 Robin D. Clark *
* Copyright (C) 1997-2001 Linas Vepstas <linas@linas.org> *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License*
* along with this program; if not, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
* Boston, MA 02111-1307, USA gnu@gnu.org *
* *
* Author: Rob Clark (rclark@cs.hmc.edu) *
* Author: Linas Vepstas (linas@linas.org) *
\********************************************************************/
#include "config.h"
#include <ctype.h>
#include <errno.h>
#include <glib.h>
#include <limits.h>
#include <math.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include "gnc-engine-util.h"
#include "gnc-common.h"
#include "gnc-engine.h"
/** GLOBALS *********************************************************/
/* This static indicates the debugging module that this .o belongs to. */
/* static short module = MOD_ENGINE; */
static gncLogLevel loglevel[MOD_LAST + 1] =
{
GNC_LOG_FATAL, /* DUMMY */
GNC_LOG_WARNING, /* ENGINE */
GNC_LOG_WARNING, /* IO */
GNC_LOG_WARNING, /* REGISTER */
GNC_LOG_WARNING, /* LEDGER */
GNC_LOG_WARNING, /* HTML */
GNC_LOG_WARNING, /* GUI */
GNC_LOG_WARNING, /* SCRUB */
GNC_LOG_WARNING, /* GTK_REG */
GNC_LOG_WARNING, /* GUILE */
GNC_LOG_WARNING, /* BACKEND */
GNC_LOG_WARNING, /* QUERY */
GNC_LOG_WARNING, /* PRICE */
GNC_LOG_WARNING, /* SQL EVENT */
GNC_LOG_WARNING, /* SQL TXN */
GNC_LOG_WARNING, /* KVP */
GNC_LOG_DEBUG, /* SX */
};
/* Set the logging level of the given module. */
void
gnc_set_log_level(gncModuleType module, gncLogLevel level)
{
if ((module < 0) || (module > MOD_LAST))
return;
loglevel[module] = level;
}
/* Set the logging level for all modules. */
void
gnc_set_log_level_global(gncLogLevel level)
{
gncModuleType module;
for (module = 0; module <= MOD_LAST; module++)
loglevel[module] = level;
}
/* prettify() cleans up subroutine names. AIX/xlC has the habit of
* printing signatures not names; clean this up. On other operating
* systems, truncate name to 30 chars. Note this routine is not thread
* safe. Note we wouldn't need this routine if AIX did something more
* reasonable. Hope thread safety doesn't poke us in eye. */
static const char *
prettify (const char *name)
{
static char bf[128];
char *p;
if (!name)
return "";
strncpy (bf, name, 29); bf[28] = 0;
p = strchr (bf, '(');
if (p)
{
*(p+1) = ')';
*(p+2) = 0x0;
}
else
strcpy (&bf[26], "...()");
return bf;
}
gboolean
gnc_should_log (gncModuleType module, gncLogLevel log_level)
{
if (module < 0 || module > MOD_LAST)
{
PERR ("Bad module: %d", module);
return FALSE;
}
if (log_level > loglevel[module])
return FALSE;
return TRUE;
}
void
gnc_log (gncModuleType module, gncLogLevel log_level, const char *prefix,
const char *function_name, const char *format, ...)
{
va_list ap;
if (!gnc_should_log (module, log_level))
return;
fprintf (stderr, "%s: %s: ",
prefix ? prefix : "(null)",
prettify (function_name));
va_start (ap, format);
vfprintf (stderr, format, ap);
va_end (ap);
fprintf (stderr, "\n");
}
/********************************************************************\
\********************************************************************/
#define NUM_CLOCKS 10
static
struct timeval gnc_clock[NUM_CLOCKS] = {
{0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0},
{0, 0}, {0, 0}, {0, 0}, {0, 0}, {0, 0},
};
void
gnc_start_clock (int clockno, gncModuleType module, gncLogLevel log_level,
const char *function_name, const char *format, ...)
{
struct timezone tz;
va_list ap;
if ((0>clockno) || (NUM_CLOCKS <= clockno)) return;
gettimeofday (&gnc_clock[clockno], &tz);
fprintf (stderr, "Clock %d Start: %s: ",
clockno, prettify (function_name));
va_start (ap, format);
vfprintf (stderr, format, ap);
va_end (ap);
fprintf (stderr, "\n");
}
void
gnc_report_clock (int clockno, gncModuleType module, gncLogLevel log_level,
const char *function_name, const char *format, ...)
{
struct timezone tz;
struct timeval now;
va_list ap;
if ((0>clockno) || (NUM_CLOCKS <= clockno)) return;
gettimeofday (&now, &tz);
/* need to borrow to make differnce */
if (now.tv_usec < gnc_clock[clockno].tv_usec)
{
now.tv_sec --;
now.tv_usec += 1000000;
}
now.tv_sec -= gnc_clock[clockno].tv_sec;
now.tv_usec -= gnc_clock[clockno].tv_usec;
fprintf (stderr, "Clock %d Elapsed: %ld.%06ld %s: ",
clockno, now.tv_sec, now.tv_usec, prettify (function_name));
va_start (ap, format);
vfprintf (stderr, format, ap);
va_end (ap);
fprintf (stderr, "\n");
}
/********************************************************************\
\********************************************************************/
/* Search for str2 in first nchar chars of str1, ignore case.. Return
* pointer to first match, or null. */
char *
strncasestr(const char *str1, const char *str2, size_t len)
{
while (*str1 && len--)
{
if (toupper(*str1) == toupper(*str2))
{
if (strncasecmp(str1,str2,strlen(str2)) == 0)
{
return (char *) str1;
}
}
str1++;
}
return NULL;
}
/* Search for str2 in str1, ignore case. Return pointer to first
* match, or null. */
char *
strcasestr(const char *str1, const char *str2)
{
size_t len = strlen (str1);
char * retval = strncasestr (str1, str2, len);
return retval;
}
/********************************************************************\
\********************************************************************/
int
safe_strcmp (const char * da, const char * db)
{
SAFE_STRCMP (da, db);
return 0;
}
int
null_strcmp (const char * da, const char * db)
{
if (da && db) return strcmp (da, db);
if (!da && db && 0==db[0]) return 0;
if (!db && da && 0==da[0]) return 0;
if (!da && db) return -1;
if (da && !db) return +1;
return 0;
}
/********************************************************************\
\********************************************************************/
#define MAX_DIGITS 50
/* inverse of strtoul */
char *
ultostr (unsigned long val, int base)
{
char buf[MAX_DIGITS];
unsigned long broke[MAX_DIGITS];
int i;
unsigned long places=0, reval;
if ((2>base) || (36<base)) return NULL;
/* count digits */
places = 0;
for (i=0; i<MAX_DIGITS; i++) {
broke[i] = val;
places ++;
val /= base;
if (0 == val) break;
}
/* normalize */
reval = 0;
for (i=places-2; i>=0; i--) {
reval += broke[i+1];
reval *= base;
broke[i] -= reval;
}
/* print */
for (i=0; i<places; i++) {
if (10>broke[i]) {
buf[places-1-i] = 0x30+broke[i]; /* ascii digit zero */
} else {
buf[places-1-i] = 0x41-10+broke[i]; /* ascii capital A */
}
}
buf[places] = 0x0;
return g_strdup (buf);
}
/********************************************************************\
* returns TRUE if the string is a number, possibly with whitespace
\********************************************************************/
gboolean
gnc_strisnum(const char *s)
{
if (s == NULL) return FALSE;
if (*s == 0) return FALSE;
while (*s && isspace(*s))
s++;
if (*s == 0) return FALSE;
if (!isdigit(*s)) return FALSE;
while (*s && isdigit(*s))
s++;
if (*s == 0) return TRUE;
while (*s && isspace(*s))
s++;
if (*s == 0) return TRUE;
return FALSE;
}
/********************************************************************\
* our own version of stpcpy
\********************************************************************/
char *
gnc_stpcpy (char *dest, const char *src)
{
strcpy (dest, src);
return (dest + strlen (src));
}
/********************************************************************\
See header for docs.
\********************************************************************/
static void
kv_pair_helper(gpointer key, gpointer val, gpointer user_data)
{
GSList **result = (GSList **) user_data;
GHashTableKVPair *kvp = g_new(GHashTableKVPair, 1);
kvp->key = key;
kvp->value = val;
*result = g_slist_prepend(*result, kvp);
}
GSList *
g_hash_table_key_value_pairs(GHashTable *table)
{
GSList *result_list = NULL;
g_hash_table_foreach(table, kv_pair_helper, &result_list);
return result_list;
}
void
g_hash_table_kv_pair_free_gfunc(gpointer data, gpointer user_data)
{
GHashTableKVPair *kvp = (GHashTableKVPair *) data;
g_free(kvp);
}
/************************* END OF FILE ******************************\
\********************************************************************/

View File

@ -0,0 +1,269 @@
/********************************************************************\
* gnc-engine-util.h -- GnuCash engine utility functions *
* Copyright (C) 1997 Robin D. Clark *
* Copyright (C) 1998-2001 Linas Vepstas <linas@linas.org> *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License*
* along with this program; if not, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
* Boston, MA 02111-1307, USA gnu@gnu.org *
* *
* Author: Rob Clark (rclark@cs.hmc.edu) *
* Author: Linas Vepstas (linas@linas.org) *
\********************************************************************/
#ifndef GNC_ENGINE_UTIL_H
#define GNC_ENGINE_UTIL_H
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <assert.h>
#include <errno.h>
#include <glib.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include "gnc-common.h"
#include "gnc-commodity.h"
#include "gnc-numeric.h"
/** DEBUGGING MACROS ************************************************/
/* The debuging macros enable the setting of trace messages */
/** If you modify this, modify the loglevel table in the .c file. */
typedef enum
{
MOD_DUMMY = 0,
MOD_ENGINE = 1,
MOD_IO = 2,
MOD_REGISTER= 3,
MOD_LEDGER = 4,
MOD_HTML = 5,
MOD_GUI = 6,
MOD_SCRUB = 7,
MOD_GTK_REG = 8,
MOD_GUILE = 9,
MOD_BACKEND = 10,
MOD_QUERY = 11,
MOD_PRICE = 12,
MOD_EVENT = 13,
MOD_TXN = 14,
MOD_KVP = 15,
MOD_SX = 16,
MOD_LAST = 16
} gncModuleType;
typedef enum
{
GNC_LOG_FATAL = 0,
GNC_LOG_ERROR = 1,
GNC_LOG_WARNING = 2,
GNC_LOG_INFO = 3,
GNC_LOG_DEBUG = 4,
GNC_LOG_DETAIL = 5,
GNC_LOG_TRACE = 6,
} gncLogLevel;
/* FIXME: these logging functions should proably get replaced by
* the glib.h g_error(), etc functions. That way, we would have
* unified logging mechanism, instead of having some messages
* work one way, and other a different way ...
*/
gboolean gnc_should_log (gncModuleType module, gncLogLevel log_level);
void gnc_log (gncModuleType module, gncLogLevel log_level,
const char *prefix, const char *function_name,
const char *format, ...) G_GNUC_PRINTF(5,6);
#define FATAL(format, args...) { \
if (gnc_should_log (module, GNC_LOG_FATAL)) \
gnc_log (module, GNC_LOG_FATAL, "Fatal Error", \
__FUNCTION__, format, ## args); \
}
#define PERR(format, args...) { \
if (gnc_should_log (module, GNC_LOG_ERROR)) \
gnc_log (module, GNC_LOG_ERROR, "Error", \
__FUNCTION__, format, ##args); \
}
#define PWARN(format, args...) { \
if (gnc_should_log (module, GNC_LOG_WARNING)) \
gnc_log (module, GNC_LOG_WARNING, "Warning", \
__FUNCTION__, format, ## args); \
}
#define PINFO(format, args...) { \
if (gnc_should_log (module, GNC_LOG_INFO)) \
gnc_log (module, GNC_LOG_INFO, "Info", \
__FUNCTION__, format, ## args); \
}
#define DEBUG(format, args...) { \
if (gnc_should_log (module, GNC_LOG_DEBUG)) \
gnc_log (module, GNC_LOG_DEBUG, "Debug", \
__FUNCTION__, format, ## args); \
}
#define ENTER(format, args...) { \
if (gnc_should_log (module, GNC_LOG_DEBUG)) \
gnc_log (module, GNC_LOG_DEBUG, "Enter", \
__FUNCTION__, format, ## args); \
}
#define LEAVE(format, args...) { \
if (gnc_should_log (module, GNC_LOG_DEBUG)) \
gnc_log (module, GNC_LOG_DEBUG, "Leave", \
__FUNCTION__, format, ## args); \
}
#define DETAIL(format, args...) { \
if (gnc_should_log (module, GNC_LOG_DETAIL)) \
gnc_log (module, GNC_LOG_DETAIL, "Detail", \
__FUNCTION__, format, ## args); \
}
#define DEBUGCMD(x) { if (gnc_should_log (module, GNC_LOG_DEBUG)) { x; }}
#define ERROR() fprintf(stderr,"%s: Line %d, error = %s\n", \
__FILE__, __LINE__, strerror(errno));
#define TRACE(format, args...) { \
if (gnc_should_log (module, GNC_LOG_TRACE)) \
gnc_log (module, GNC_LOG_TRACE, "Trace", \
__FUNCTION__, format, ## args); \
}
void gnc_start_clock (int clockno, gncModuleType module, gncLogLevel log_level,
const char *function_name, const char *format, ...);
void gnc_report_clock (int clockno, gncModuleType module, gncLogLevel log_level,
const char *function_name, const char *format, ...);
#define START_CLOCK(clockno,format, args...) { \
if (gnc_should_log (module, GNC_LOG_INFO)) \
gnc_start_clock (clockno, module, GNC_LOG_INFO,\
__FUNCTION__, format, ## args); \
}
#define REPORT_CLOCK(clockno,format, args...) { \
if (gnc_should_log (module, GNC_LOG_INFO)) \
gnc_report_clock (clockno, module, GNC_LOG_INFO,\
__FUNCTION__, format, ## args); \
}
/* Set the logging level of the given module. */
void gnc_set_log_level(gncModuleType module, gncLogLevel level);
/* Set the logging level for all modules. */
void gnc_set_log_level_global(gncLogLevel level);
/** Macros *****************************************************/
#define EPS (1.0e-6)
#define DEQEPS(x,y,eps) (((((x)+(eps))>(y)) ? 1 : 0) && ((((x)-(eps))<(y)) ? 1 : 0))
#define DEQ(x,y) DEQEPS(x,y,EPS)
#define SAFE_STRCMP(da,db) { \
if ((da) && (db)) { \
int retval = strcmp ((da), (db)); \
/* if strings differ, return */ \
if (retval) return retval; \
} else \
if ((!(da)) && (db)) { \
return -1; \
} else \
if ((da) && (!(db))) { \
return +1; \
} \
}
/* Define the long long int conversion for scanf */
#if HAVE_SCANF_LLD
# define GNC_SCANF_LLD "%lld"
#else
# define GNC_SCANF_LLD "%qd"
#endif
/** Prototypes *************************************************/
/* The safe_strcmp compares strings a and b the same way that strcmp()
* does, except that either may be null. This routine assumes that
* a non-null string is always greater than a null string.
*/
int safe_strcmp (const char * da, const char * db);
/* The null_strcmp compares strings a and b the same way that strcmp()
* does, except that either may be null. This routine assumes that
* a null string is equal to the empty string.
*/
int null_strcmp (const char * da, const char * db);
/* Search for str2 in first nchar chars of str1, ignore case. Return
* pointer to first match, or null. These are just like that strnstr
* and the strstr functions, except that they ignore the case. */
extern char *strncasestr(const char *str1, const char *str2, size_t len);
extern char *strcasestr(const char *str1, const char *str2);
/* The ultostr() subroutine is the inverse of strtoul(). It accepts a
* number and prints it in the indicated base. The returned string
* should be g_freed when done. */
char * ultostr (unsigned long val, int base);
/* Returns true if string s is a number, possibly surrounded by
* whitespace. */
gboolean gnc_strisnum(const char *s);
/* Define a gnucash stpcpy */
char * gnc_stpcpy (char *dest, const char *src);
#ifndef HAVE_STPCPY
#define stpcpy gnc_stpcpy
#endif
/***********************************************************************\
g_hash_table_key_value_pairs(hash): Returns a GSList* of all the
keys and values in a given hash table. Data elements of lists are
actual hash elements, so be careful, and deallocation of the
GHashTableKVPairs in the result list are the caller's
responsibility. A typical sequence might look like this:
GSList *kvps = g_hash_table_key_value_pairs(hash);
... use kvps->data->key and kvps->data->val, etc. here ...
g_slist_foreach(kvps, g_hash_table_kv_pair_free_gfunc, NULL);
g_slist_free(kvps);
*/
typedef struct {
gpointer key;
gpointer value;
} GHashTableKVPair;
GSList *g_hash_table_key_value_pairs(GHashTable *table);
void g_hash_table_kv_pair_free_gfunc(gpointer data, gpointer user_data);
/***********************************************************************/
#endif

117
src/engine/gnc-engine.c Normal file
View File

@ -0,0 +1,117 @@
/********************************************************************
* gnc-engine.c -- top-level initialization for Gnucash Engine *
* Copyright 2000 Bill Gribble <grib@billgribble.com> *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License*
* along with this program; if not, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
* Boston, MA 02111-1307, USA gnu@gnu.org *
* *
********************************************************************/
#include "config.h"
#include <assert.h>
#include <glib.h>
#include "GNCIdP.h"
#include "gnc-engine.h"
static GList * engine_init_hooks = NULL;
static gnc_commodity_table * known_commodities = NULL;
static int engine_is_initialized = 0;
GCache * gnc_string_cache = NULL;
/********************************************************************
* gnc_engine_init
* initialize backend, load any necessary databases, etc.
********************************************************************/
void
gnc_engine_init(int argc, char ** argv) {
gnc_engine_init_hook_t hook;
GList * cur;
if (1 == engine_is_initialized) return;
engine_is_initialized = 1;
/* initialize the string cache */
gnc_engine_get_string_cache();
xaccGUIDInit ();
/* initialize the commodity table (it starts empty) */
known_commodities = gnc_commodity_table_new();
/* call any engine hooks */
for(cur = engine_init_hooks; cur; cur = cur->next) {
hook = (gnc_engine_init_hook_t)cur->data;
if(hook) {
(*hook)(argc, argv);
}
}
}
GCache*
gnc_engine_get_string_cache(void)
{
if(!gnc_string_cache)
{
gnc_string_cache = g_cache_new(
(GCacheNewFunc) g_strdup, g_free,
(GCacheDupFunc) g_strdup, g_free, g_str_hash,
g_str_hash, g_str_equal);
}
return gnc_string_cache;
}
/********************************************************************
* gnc_engine_shutdown
* shutdown backend, destroy any global data, etc.
********************************************************************/
void
gnc_engine_shutdown (void)
{
g_cache_destroy (gnc_string_cache);
gnc_string_cache = NULL;
xaccGUIDShutdown ();
gnc_commodity_table_destroy (known_commodities);
known_commodities = NULL;
}
/********************************************************************
* gnc_engine_add_init_hook
* add a startup hook
********************************************************************/
void
gnc_engine_add_init_hook(gnc_engine_init_hook_t h) {
engine_init_hooks = g_list_append(engine_init_hooks, (gpointer)h);
}
/********************************************************************
* gnc_engine_commodities()
* get the global gnc_engine commodity table
********************************************************************/
gnc_commodity_table *
gnc_engine_commodities(void) {
assert(engine_is_initialized);
return known_commodities;
}

69
src/engine/gnc-engine.h Normal file
View File

@ -0,0 +1,69 @@
/********************************************************************
* gnc-engine.h -- top-level include file for Gnucash Engine *
* Copyright 2000 Bill Gribble <grib@billgribble.com> *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License*
* along with this program; if not, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
* Boston, MA 02111-1307, USA gnu@gnu.org *
* *
********************************************************************/
#ifndef GNC_ENGINE_H
#define GNC_ENGINE_H
#include "gnc-commodity.h"
typedef void (* gnc_engine_init_hook_t)(int, char **);
/** PROTOTYPES ******************************************************/
/* gnc_engine_init MUST be called before gnc engine functions can
* be used. */
void gnc_engine_init(int argc, char ** argv);
/* called to shutdown the engine */
void gnc_engine_shutdown (void);
/* pass a function pointer to gnc_engine_add_init_hook and
* it will be called during the evaluation of gnc_engine_init */
void gnc_engine_add_init_hook(gnc_engine_init_hook_t hook);
/* this is a global table of known commodity types. */
gnc_commodity_table * gnc_engine_commodities(void);
/* Many strings used throughout the engine are likely to be duplicated.
* So we provide a reference counted cache system for the strings, which
* shares strings whenever possible.
*
* Use g_cache_insert to insert a string into the cache (it will return a
* pointer to the cached string).
* Basically you should use this instead of g_strdup.
*
* Use g_cache_remove (giving it a pointer to a cached string) if the string
* is unused. If this is the last reference to the string it will be
* removed from the cache, otherwise it will just decrement the
* reference count.
* Basically you should use this instead of g_free.
*
* Note that all the work is done when inserting or removing. Once
* cached the strings are just plain C strings.
*/
/* get the gnc_string_cache. Create it if it doesn't exist already */
GCache* gnc_engine_get_string_cache(void);
#endif

45
src/engine/gnc-event-p.h Normal file
View File

@ -0,0 +1,45 @@
/********************************************************************
* gnc-event-p.h -- private engine event handling interface *
* Copyright 2000 Dave Peticolas <dave@krondo.com> *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License*
* along with this program; if not, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
* Boston, MA 02111-1307, USA gnu@gnu.org *
* *
********************************************************************/
#ifndef GNC_EVENT_P_H
#define GNC_EVENT_P_H
#include "gnc-event.h"
/* gnc_engine_generate_event
* Invoke all registered event handlers using the given arguments.
*
* GNC_EVENT_CREATE events should be generated after the object
* has been created and registered in the engine entity table.
* GNC_EVENT_MODIFY events should be generated whenever any data
* member or submember (i.e., splits) is changed.
* GNC_EVENT_DESTROY events should be called before the object
* has been destroyed or removed from the entity table.
*
* entity: the GUID of the entity generating the event
* event_type: the type of event -- this should be one of the
* single-bit GNCEngineEventType values, not a combination.
*/
void gnc_engine_generate_event (GUID *entity, GNCEngineEventType event_type);
#endif

185
src/engine/gnc-event.c Normal file
View File

@ -0,0 +1,185 @@
/********************************************************************
* gnc-event.c -- engine event handling implementation *
* Copyright 2000 Dave Peticolas <dave@krondo.com> *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License*
* along with this program; if not, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
* Boston, MA 02111-1307, USA gnu@gnu.org *
* *
********************************************************************/
#include "config.h"
#include "gnc-engine-util.h"
#include "gnc-event-p.h"
/** Declarations ****************************************************/
typedef struct
{
GNCEngineEventHandler handler;
gpointer user_data;
gint handler_id;
} HandlerInfo;
/** Static Variables ************************************************/
static guint suspend_counter = 0;
static gint next_handler_id = 0;
static GList *handlers = NULL;
/* This static indicates the debugging module that this .o belongs to. */
static short module = MOD_ENGINE;
/** Implementations *************************************************/
gint
gnc_engine_register_event_handler (GNCEngineEventHandler handler,
gpointer user_data)
{
HandlerInfo *hi;
gint handler_id;
GList *node;
/* sanity check */
if (!handler)
{
PERR ("no handler specified");
return 0;
}
/* look for a free handler id */
handler_id = next_handler_id;
node = handlers;
while (node)
{
hi = node->data;
if (hi->handler_id == handler_id)
{
handler_id++;
node = handlers;
continue;
}
node = node->next;
}
/* found one, add the handler */
hi = g_new0 (HandlerInfo, 1);
hi->handler = handler;
hi->user_data = user_data;
hi->handler_id = handler_id;
handlers = g_list_prepend (handlers, hi);
/* update id for next registration */
next_handler_id = handler_id + 1;
return handler_id;
}
void
gnc_engine_unregister_event_handler (gint handler_id)
{
GList *node;
for (node = handlers; node; node = node->next)
{
HandlerInfo *hi = node->data;
if (hi->handler_id != handler_id)
continue;
/* found it */
/* take out of list */
handlers = g_list_remove_link (handlers, node);
/* safety */
hi->handler = NULL;
g_list_free_1 (node);
g_free (hi);
return;
}
PERR ("no such handler: %d", handler_id);
}
void
gnc_engine_suspend_events (void)
{
suspend_counter++;
if (suspend_counter == 0)
{
PERR ("suspend counter overflow");
}
}
void
gnc_engine_resume_events (void)
{
if (suspend_counter == 0)
{
PERR ("suspend counter underflow");
return;
}
suspend_counter--;
}
void
gnc_engine_generate_event (GUID *entity, GNCEngineEventType event_type)
{
GList *node;
if (!entity)
return;
if (suspend_counter)
return;
switch (event_type)
{
case GNC_EVENT_NONE:
return;
case GNC_EVENT_CREATE:
case GNC_EVENT_MODIFY:
case GNC_EVENT_DESTROY:
break;
default:
PERR ("bad event type %d", event_type);
return;
}
for (node = handlers; node; node = node->next)
{
HandlerInfo *hi = node->data;
if (hi->handler)
hi->handler (entity, event_type, hi->user_data);
}
}

84
src/engine/gnc-event.h Normal file
View File

@ -0,0 +1,84 @@
/********************************************************************
* gnc-event.h -- engine event handling interface *
* Copyright 2000 Dave Peticolas <dave@krondo.com> *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License*
* along with this program; if not, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
* Boston, MA 02111-1307, USA gnu@gnu.org *
* *
********************************************************************/
#ifndef GNC_EVENT_H
#define GNC_EVENT_H
#include <glib.h>
#include "guid.h"
typedef enum
{
GNC_EVENT_NONE = 0,
GNC_EVENT_CREATE = 1 << 0,
GNC_EVENT_MODIFY = 1 << 1,
GNC_EVENT_DESTROY = 1 << 2,
GNC_EVENT_ALL = 0xff
} GNCEngineEventType;
/* GNCEngineEventHandler
* Handler invoked when an engine event occurs.
*
* entity: GUID of entity generating event
* event_type: one of the single-bit GNCEngineEventTypes, not a combination
* user_data: user_data supplied when handler was registered.
*/
typedef void (*GNCEngineEventHandler) (GUID *entity,
GNCEngineEventType event_type,
gpointer user_data);
/* gnc_engine_register_event_handler
* Register a handler for engine events.
*
* handler: handler to register
* user_data: data provided when handler is invoked
*
* Returns: id identifying handler
*/
gint gnc_engine_register_event_handler (GNCEngineEventHandler handler,
gpointer user_data);
/* gnc_engine_unregister_event_handler
* Unregister an engine event handler.
*
* handler_id: the id of the handler to unregister
*/
void gnc_engine_unregister_event_handler (gint handler_id);
/* gnc_engine_suspend_events
* Suspend all engine events. This function may be
* called multiple times. To resume event generation,
* an equal number of calls to gnc_engine_resume_events
* must be made.
*/
void gnc_engine_suspend_events (void);
/* gnc_engine_resume_events
* Resume engine event generation.
*/
void gnc_engine_resume_events (void);
#endif

1268
src/engine/gnc-numeric.c Normal file

File diff suppressed because it is too large Load Diff

167
src/engine/gnc-numeric.h Normal file
View File

@ -0,0 +1,167 @@
/********************************************************************
* gnc-numeric.h -- an exact-number library for gnucash. *
* Copyright (C) 2000 Bill Gribble *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License*
* along with this program; if not, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
* Boston, MA 02111-1307, USA gnu@gnu.org *
* *
*******************************************************************/
#ifndef GNC_NUMERIC_H
#define GNC_NUMERIC_H
#include <glib.h>
struct _gnc_numeric {
gint64 num;
gint64 denom;
};
typedef struct _gnc_numeric gnc_numeric;
/* bitmasks for HOW flags */
#define GNC_NUMERIC_RND_MASK 0x0000000f
#define GNC_NUMERIC_DENOM_MASK 0x000000f0
#define GNC_NUMERIC_SIGFIGS_MASK 0x0000ff00
/* rounding/truncation modes for operations */
enum {
GNC_RND_FLOOR = 0x01,
GNC_RND_CEIL = 0x02,
GNC_RND_TRUNC = 0x03,
GNC_RND_PROMOTE = 0x04,
GNC_RND_ROUND_HALF_DOWN = 0x05,
GNC_RND_ROUND_HALF_UP = 0x06,
GNC_RND_ROUND = 0x07,
GNC_RND_NEVER = 0x08
};
/* auto-denominator types */
enum {
GNC_DENOM_EXACT = 0x10,
GNC_DENOM_REDUCE = 0x20,
GNC_DENOM_LCD = 0x30,
GNC_DENOM_FIXED = 0x40,
GNC_DENOM_SIGFIG = 0x50
};
/* bits 8-15 of 'how' are reserved for the number of significant
* digits to use in the output with GNC_DENOM_SIGFIG */
/* errors */
enum {
GNC_ERROR_OK = 0,
GNC_ERROR_ARG = -1,
GNC_ERROR_OVERFLOW = -2,
GNC_ERROR_DENOM_DIFF = -3,
GNC_ERROR_REMAINDER = -4
};
#define GNC_DENOM_AUTO 0
#define GNC_DENOM_RECIPROCAL( a ) (- ( a ))
#define GNC_DENOM_SIGFIGS( a ) ( ((( a ) & 0xff) << 8) | GNC_DENOM_SIGFIG)
#define GNC_NUMERIC_GET_SIGFIGS( a ) ( (( a ) & 0xff00 ) >> 8)
/* make a gnc_numeric from numerator and denominator */
gnc_numeric gnc_numeric_create(gint64 num, gint64 denom);
/* create a zero-value gnc_numeric */
gnc_numeric gnc_numeric_zero(void);
/* make a special error-signalling gnc_numeric */
gnc_numeric gnc_numeric_error(int error_code);
/* check for error signal in value */
int gnc_numeric_check(gnc_numeric a);
/* get parts */
gint64 gnc_numeric_num(gnc_numeric a);
gint64 gnc_numeric_denom(gnc_numeric a);
/* tests */
int gnc_numeric_zero_p(gnc_numeric a); /* 1 if 0, 0 else */
int gnc_numeric_compare(gnc_numeric a, gnc_numeric b);
int gnc_numeric_negative_p(gnc_numeric a);
int gnc_numeric_positive_p(gnc_numeric a);
/* equivalence predicates :
* eq : a and b are exactly the same (same numerator and denominator)
* equal : a and b represent exactly the same number (ratio of numerator
* to denominator is exactly equal)
* same : after both are converted to DENOM using method HOW, a and b
* are equal().
*/
int gnc_numeric_eq(gnc_numeric a, gnc_numeric b);
int gnc_numeric_equal(gnc_numeric a, gnc_numeric b);
int gnc_numeric_same(gnc_numeric a, gnc_numeric b,
gint64 denom, gint how);
/* arithmetic operations */
gnc_numeric gnc_numeric_add(gnc_numeric a, gnc_numeric b,
gint64 denom, gint how);
gnc_numeric gnc_numeric_sub(gnc_numeric a, gnc_numeric b,
gint64 denom, gint how);
gnc_numeric gnc_numeric_mul(gnc_numeric a, gnc_numeric b,
gint64 denom, gint how);
gnc_numeric gnc_numeric_div(gnc_numeric a, gnc_numeric b,
gint64 denom, gint how);
gnc_numeric gnc_numeric_neg(gnc_numeric a);
gnc_numeric gnc_numeric_abs(gnc_numeric a);
/* some shortcuts for common operations */
gnc_numeric gnc_numeric_add_fixed(gnc_numeric a, gnc_numeric b);
gnc_numeric gnc_numeric_sub_fixed(gnc_numeric a, gnc_numeric b);
/* arithmetic functions with exact error returns */
gnc_numeric gnc_numeric_add_with_error(gnc_numeric a, gnc_numeric b,
gint64 denom, gint how,
gnc_numeric * error);
gnc_numeric gnc_numeric_sub_with_error(gnc_numeric a, gnc_numeric b,
gint64 denom, gint how,
gnc_numeric * error);
gnc_numeric gnc_numeric_mul_with_error(gnc_numeric a, gnc_numeric b,
gint64 denom, gint how,
gnc_numeric * error);
gnc_numeric gnc_numeric_div_with_error(gnc_numeric a, gnc_numeric b,
gint64 denom, gint how,
gnc_numeric * error);
/* change the denominator of a gnc_numeric value */
gnc_numeric gnc_numeric_convert(gnc_numeric in, gint64 denom,
gint how);
gnc_numeric gnc_numeric_convert_with_error(gnc_numeric in, gint64 denom,
gint how,
gnc_numeric * error);
/* reduce by GCF elimination */
gnc_numeric gnc_numeric_reduce(gnc_numeric in);
/* convert to and from floating-point values */
gnc_numeric double_to_gnc_numeric(double in, gint64 denom,
gint how);
double gnc_numeric_to_double(gnc_numeric in);
gchar *gnc_numeric_to_string(gnc_numeric n);
/* Read a gnc_numeric from str, skipping any leading whitespace, and
returning a pointer to just past the last byte read. Return NULL
on error. */
const gchar *string_to_gnc_numeric(const gchar* str, gnc_numeric *n);
#endif

View File

@ -0,0 +1,89 @@
/********************************************************************
* gnc-pricedb-p.h -- a simple price database for gnucash. *
* Copyright (C) 2001 Rob Browning *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License*
* along with this program; if not, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
* Boston, MA 02111-1307, USA gnu@gnu.org *
* *
*******************************************************************/
#ifndef GNC_PRICEDB_P_H
#define GNC_PRICEDB_P_H
#include <glib.h>
#include "gnc-book.h"
#include "BackendP.h"
#include "gnc-pricedb.h"
struct _GNCPrice {
/* 'public' data fields */
GUID guid; /* globally unique price id */
GNCPriceDB *db;
gnc_commodity *commodity;
gnc_commodity *currency;
Timespec time;
char *source;
char *type;
gnc_numeric value;
gint32 version; /* version number, for syncing with backend */
guint32 version_check; /* data aging timestamp */
/* 'private' object management fields */
guint32 refcount; /* garbage collection reference count */
gint32 editlevel; /* nesting level of begin/end edit calls */
gboolean not_saved; /* price edit saved flag */
gboolean do_free; /* price is going to be destroyed soon */
};
struct _GNCPriceDB {
GHashTable *commodity_hash;
Backend *backend;
gboolean dirty;
};
/* These structs define the kind of price lookup being done
* so that it can be passed to the backend. This is a rather
* cheesy, low-brow interface. It could stand improvement.
*/
typedef enum {
LOOKUP_LATEST = 1,
LOOKUP_ALL,
LOOKUP_AT_TIME,
LOOKUP_NEAREST_IN_TIME,
LOOKUP_LATEST_BEFORE,
LOOKUP_EARLIEST_AFTER
} PriceLookupType;
struct _GNCPriceLookup {
PriceLookupType type;
GNCPriceDB *prdb;
gnc_commodity *commodity;
gnc_commodity *currency;
Timespec date;
};
void gnc_pricedb_mark_clean(GNCPriceDB *db);
void gnc_pricedb_substitute_commodity(GNCPriceDB *db,
gnc_commodity *old_c,
gnc_commodity *new_c);
void gnc_price_set_guid (GNCPrice *p, const GUID *guid);
#endif

1257
src/engine/gnc-pricedb.c Normal file

File diff suppressed because it is too large Load Diff

289
src/engine/gnc-pricedb.h Normal file
View File

@ -0,0 +1,289 @@
/********************************************************************
* gnc-pricedb.h -- a simple price database for gnucash. *
* Copyright (C) 2001 Rob Browning, Linas Vepstas *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License*
* along with this program; if not, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
* Boston, MA 02111-1307, USA gnu@gnu.org *
* *
*******************************************************************/
#ifndef GNC_PRICEDB_H
#define GNC_PRICEDB_H
#include "date.h"
#include "gnc-numeric.h"
#include "gnc-commodity.h"
#include "guid.h"
#include <stdio.h>
/**********************************************************************\
Introduction:
The PriceDB is intended to be a database of price quotes, or more
specifically, a database of GNCPrices. For the time being, it is
still a fairly simple database supporting only fairly simple
queries. It is expected that new queries will be added as needed,
and that there is some advantage to delaying complex queries for
now in the hope that we get a real DB implementation before
they're really needed.
Every GNCBook contains a GNCPriceDB, accessable via
gnc_book_get_pricedb.
*/
/**********************************************************************\
GNCPrice:
Each price in the database represents an "instantaneous" quote for
a given commodity with respect to another commodity. For example,
a given price might represent the value of LNUX in USD on
2001-02-03.
Fields:
commodity: the item being priced.
currency: the denomination of the value of the item being priced.
value: the value of the item being priced.
time: the time the price was valid.
source: a string describing the source of the quote. These
strings will be something like this: "Finance::Quote",
"user:misc", "user:foo", etc. If the quote came from a user,
as a matter of policy, you *must* prefix the string you give
with "user:". For now, the only other reserved values are
"Finance::Quote" and "old-file-import".
type: the type of quote - types possible right now are bid, ask,
last, nav, and unknown.
Implementation Details:
NOTE: for source and type, NULL and the empty string are
considered the same, so if one of these is "", then after a file
save/restore, it might be NULL. Behave accordingly.
GNCPrices are reference counted. When you gnc_price_create one
or clone it, the new price's count is set to 1. When you are
finished with a price, call gnc_price_unref. If you hand the
price pointer to some other code that needs to keep it, make
sure it calls gnc_price_ref to indicate its interest in that
price, and calls gnc_price_unref when it's finished with the
price. For those unfamiliar with reference counting, basically
each price stores an integer count which starts at 1 and is
incremented every time someone calls gnc_price_ref. Conversely,
the count is decremented every time someone calls
gnc_price_unref. If the count ever reaches 0, the price is
destroyed.
All of the getters return data that's internal to the GNCPrice,
not copies, so don't free these values.
All of the setters store copies of the data given, with the
exception of the commodity field which just stores the pointer
given. It is assumed that commodities are a global resource and
are pointer unique.
*/
typedef struct _GNCPrice GNCPrice;
typedef struct _GNCPriceLookup GNCPriceLookup;
/****************/
/* constructors */
/* gnc_price_create - returns a newly allocated and initialized price
with a reference count of 1. */
GNCPrice *gnc_price_create(void);
/* gnc_price_clone - returns a newly allocated price that's a
content-wise duplicate of the given price, p. The returned clone
will have a reference count of 1. */
GNCPrice *gnc_price_clone(GNCPrice* p);
/*********************/
/* memory management */
/* gnc_price_ref - indicate your need for a given price to stick
around (i.e. increase its reference count by 1). */
void gnc_price_ref(GNCPrice *p);
/* gnc_price_ref - indicate you're finished with a price
(i.e. decrease its reference count by 1). */
void gnc_price_unref(GNCPrice *p);
/***********/
/* setters */
/* As mentioned above, all of the setters store copies of the data
* given, with the exception of the commodity field which just stores
* the pointer given. It is assumed that commodities are a global
* resource and are pointer unique.
*
* Invocations of the setters should be wrapped with calls to
* gnc_price_begin_edit() and commit_edit(). The begin/commit
* calls help ensure that the local price db is synchronized with
* the backend.
*/
void gnc_price_begin_edit (GNCPrice *p);
void gnc_price_commit_edit (GNCPrice *p);
void gnc_price_set_commodity(GNCPrice *p, gnc_commodity *c);
void gnc_price_set_currency(GNCPrice *p, gnc_commodity *c);
void gnc_price_set_time(GNCPrice *p, Timespec t);
void gnc_price_set_source(GNCPrice *p, const char *source);
void gnc_price_set_type(GNCPrice *p, const char* type);
void gnc_price_set_value(GNCPrice *p, gnc_numeric value);
void gnc_price_set_version(GNCPrice *p, gint32 versn);
/***********/
/* getters */
/* As mentioned above all of the getters return data that's internal
to the GNCPrice, not copies, so don't free these values. */
GNCPrice * gnc_price_lookup (const GUID *guid);
const GUID * gnc_price_get_guid (GNCPrice *p);
gnc_commodity * gnc_price_get_commodity(GNCPrice *p);
gnc_commodity * gnc_price_get_currency(GNCPrice *p);
Timespec gnc_price_get_time(GNCPrice *p);
const char * gnc_price_get_source(GNCPrice *p);
const char * gnc_price_get_type(GNCPrice *p);
gnc_numeric gnc_price_get_value(GNCPrice *p);
gint32 gnc_price_get_version(GNCPrice *p);
gboolean gnc_price_equal(GNCPrice *p1, GNCPrice *p2);
/**********************************************************************
GNCPrice lists:
The database communicates multiple prices in and out via gnc price
lists. These are just time sorted GLists of GNCPrice pointers.
Functions for manipulating these lists are provided. These
functions are helpful in that they handle maintaining proper
reference counts on behalf of the price list for every price being
held in a given list. I.e. insert "refs" the prices being
inserted, remove and destroy "unref" the prices that will no
longer be referred to by the list.
*/
/* gnc_price_list_insert - insert a price into the given list, calling
gnc_price_ref on it during the process. */
gboolean gnc_price_list_insert(GList **prices, GNCPrice *p);
/* gnc_price_list_remove - remove the price, p, from the given list,
calling gnc_price_unref on it during the process. */
gboolean gnc_price_list_remove(GList **prices, GNCPrice *p);
/* gnc_price_list_destroy - destroy the given price list, calling
gnc_price_unref on all the prices included in the list. */
void gnc_price_list_destroy(GList *prices);
/**********************************************************************
GNCPriceDB
Whenever a you store a price in the pricedb, the pricedb adds its
own reference to the price, so you can safely unref that price after
inserting it into the DB if you're finished with it otherwise.
Similarly, when the pricedb returns a price to you, either singly,
or in a price list, the price will have had a ref added for you, so
you only need to unref the price(s) when you're finished with
it/them.
*/
typedef struct _GNCPriceDB GNCPriceDB;
/* gnc_pricedb_create - create a new pricedb. Normally you won't need
this; you will get the pricedb via gnc_book_get_pricedb. */
GNCPriceDB * gnc_pricedb_create(void);
/* gnc_pricedb_destroy - destroy the given pricedb and unref all of
the prices it contains. This may not deallocate all of those
prices. Other code may still be holding references to them. */
void gnc_pricedb_destroy(GNCPriceDB *db);
/* gnc_pricedb_add_price - add a price to the pricedb, you may drop
your reference to the price (i.e. call unref) after this
succeeds, whenever you're finished with the price. */
gboolean gnc_pricedb_add_price(GNCPriceDB *db, GNCPrice *p);
/* gnc_pricedb_remove_price - removes the given price, p, from the
pricedb. Returns TRUE if successful, FALSE otherwise. */
gboolean gnc_pricedb_remove_price(GNCPriceDB *db, GNCPrice *p);
/* gnc_pricedb_lookup_latest - find the most recent price for the
given commodity in the given currency. Returns NULL on
failure. */
GNCPrice * gnc_pricedb_lookup_latest(GNCPriceDB *db,
gnc_commodity *commodity,
gnc_commodity *currency);
/* gnc_pricedb_get_prices - return all the prices for a given
commodity in the given currency. Returns NULL on failure. The
result is a GNCPrice list (see above). */
GList * gnc_pricedb_get_prices(GNCPriceDB *db,
gnc_commodity *commodity,
gnc_commodity *currency);
/* gnc_pricedb_lookup_at_time - return all prices that match the given
commodity, currency, and timespec. Prices will be returned as a
GNCPrice list (see above). */
GList * gnc_pricedb_lookup_at_time(GNCPriceDB *db,
gnc_commodity *commodity,
gnc_commodity *currency,
Timespec t);
/* gnc_pricedb_lookup_nearest_in_time - return the price for the given
commodity in the given currency nearest to the given time t. */
GNCPrice *
gnc_pricedb_lookup_nearest_in_time(GNCPriceDB *db,
gnc_commodity *c,
gnc_commodity *currency,
Timespec t);
/* gnc_pricedb_foreach_price - call f once for each price in db, until
and unless f returns FALSE. If stable_order is not FALSE, make
sure the ordering of the traversal is stable (i.e. the same order
every time given the same db contents -- stable traversals may be
less efficient). */
gboolean gnc_pricedb_foreach_price(GNCPriceDB *db,
gboolean (*f)(GNCPrice *p,
gpointer user_data),
gpointer user_data,
gboolean stable_order);
/* gnc_pricedb_dirty - return FALSE if the database has not been
modified. */
gboolean gnc_pricedb_dirty(GNCPriceDB *db);
/* gnc_pricedb_get_num_prices - return the number of prices
in the database. */
guint gnc_pricedb_get_num_prices(GNCPriceDB *db);
/* semi-lame debugging code */
void gnc_price_print(GNCPrice *db, FILE *f, int indent);
void gnc_pricedb_print_contents(GNCPriceDB *db, FILE *f);
#endif

571
src/engine/guid.c Normal file
View File

@ -0,0 +1,571 @@
/********************************************************************\
* guid.c -- globally unique ID implementation *
* Copyright (C) 2000 Dave Peticolas <peticola@cs.ucdavis.edu> *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License*
* along with this program; if not, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
* Boston, MA 02111-1307, USA gnu@gnu.org *
* *
\********************************************************************/
#define _GNU_SOURCE
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <ctype.h>
#include <dirent.h>
#include <glib.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/times.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#include "guid.h"
#include "md5.h"
#include "gnc-engine-util.h"
# ifndef P_tmpdir
# define P_tmpdir "/tmp"
# endif
/** Constants *******************************************************/
#define DEBUG_GUID 0
#define BLOCKSIZE 4096
#define THRESHOLD (2 * BLOCKSIZE)
/** Static global variables *****************************************/
static gboolean guid_initialized = FALSE;
static struct md5_ctx guid_context;
/* This static indicates the debugging module that this .o belongs to. */
static short module = MOD_ENGINE;
/** Function implementations ****************************************/
/* This code is based on code in md5.c in GNU textutils. */
static size_t
init_from_stream(FILE *stream, size_t max_size)
{
char buffer[BLOCKSIZE + 72];
size_t sum, block_size, total;
if (max_size <= 0)
return 0;
total = 0;
/* Iterate over file contents. */
while (1)
{
/* We read the file in blocks of BLOCKSIZE bytes. One call of the
* computation function processes the whole buffer so that with the
* next round of the loop another block can be read. */
size_t n;
sum = 0;
if (max_size < BLOCKSIZE)
block_size = max_size;
else
block_size = BLOCKSIZE;
/* Read block. Take care for partial reads. */
do
{
n = fread (buffer + sum, 1, block_size - sum, stream);
sum += n;
}
while (sum < block_size && n != 0);
max_size -= sum;
if (n == 0 && ferror (stream))
return total;
/* If end of file or max_size is reached, end the loop. */
if ((n == 0) || (max_size == 0))
break;
/* Process buffer with BLOCKSIZE bytes. Note that
* BLOCKSIZE % 64 == 0 */
md5_process_block (buffer, BLOCKSIZE, &guid_context);
total += sum;
}
/* Add the last bytes if necessary. */
if (sum > 0)
{
md5_process_bytes (buffer, sum, &guid_context);
total += sum;
}
return total;
}
static size_t
init_from_file(const char *filename, size_t max_size)
{
struct stat stats;
size_t total = 0;
size_t file_bytes;
FILE *fp;
if (stat(filename, &stats) != 0)
return 0;
md5_process_bytes(&stats, sizeof(stats), &guid_context);
total += sizeof(stats);
if (max_size <= 0)
return total;
fp = fopen (filename, "r");
if (fp == NULL)
return total;
file_bytes = init_from_stream(fp, max_size);
#if DEBUG_GUID
g_warning ("guid_init got %u bytes from %s", file_bytes, filename);
#endif
total += file_bytes;
fclose(fp);
return total;
}
static size_t
init_from_dir(const char *dirname, unsigned int max_files)
{
char filename[1024];
struct dirent *de;
struct stat stats;
size_t total;
int result;
DIR *dir;
if (max_files <= 0)
return 0;
dir = opendir (dirname);
if (dir == NULL)
return 0;
total = 0;
do
{
de = readdir(dir);
if (de == NULL)
break;
md5_process_bytes(de, sizeof(struct dirent), &guid_context);
total += sizeof(struct dirent);
result = snprintf(filename, sizeof(filename),
"%s/%s", dirname, de->d_name);
if ((result < 0) || (result >= sizeof(filename)))
continue;
if (stat(filename, &stats) != 0)
continue;
md5_process_bytes(&stats, sizeof(stats), &guid_context);
total += sizeof(stats);
max_files--;
} while (max_files > 0);
closedir(dir);
return total;
}
static size_t
init_from_time(void)
{
size_t total;
time_t t_time;
clock_t clocks;
struct tms tms_buf;
total = 0;
t_time = time(NULL);
md5_process_bytes(&t_time, sizeof(t_time), &guid_context);
total += sizeof(t_time);
clocks = times(&tms_buf);
md5_process_bytes(&clocks, sizeof(clocks), &guid_context);
md5_process_bytes(&tms_buf, sizeof(tms_buf), &guid_context);
total += sizeof(clocks) + sizeof(tms_buf);
return total;
}
void
guid_init(void)
{
size_t bytes = 0;
md5_init_ctx(&guid_context);
/* entropy pool */
bytes += init_from_file ("/dev/urandom", 512);
/* files */
{
const char * files[] =
{ "/etc/passwd",
"/proc/loadavg",
"/proc/meminfo",
"/proc/net/dev",
"/proc/rtc",
"/proc/self/environ",
"/proc/self/stat",
"/proc/stat",
"/proc/uptime",
NULL
};
int i;
for (i = 0; files[i] != NULL; i++)
bytes += init_from_file(files[i], BLOCKSIZE);
}
/* directories */
{
const char * dirname;
const char * dirs[] =
{
"/proc",
P_tmpdir,
"/var/lock",
"/var/log",
"/var/mail",
"/var/spool/mail",
"/var/run",
NULL
};
int i;
for (i = 0; dirs[i] != NULL; i++)
bytes += init_from_dir(dirs[i], 32);
dirname = getenv("HOME");
if (dirname != NULL)
bytes += init_from_dir(dirname, 32);
}
/* process and parent ids */
{
pid_t pid;
pid = getpid();
md5_process_bytes(&pid, sizeof(pid), &guid_context);
bytes += sizeof(pid);
pid = getppid();
md5_process_bytes(&pid, sizeof(pid), &guid_context);
bytes += sizeof(pid);
}
/* user info */
{
uid_t uid;
gid_t gid;
char *s;
s = getlogin();
if (s != NULL)
{
md5_process_bytes(s, strlen(s), &guid_context);
bytes += strlen(s);
}
uid = getuid();
md5_process_bytes(&uid, sizeof(uid), &guid_context);
bytes += sizeof(uid);
gid = getgid();
md5_process_bytes(&gid, sizeof(gid), &guid_context);
bytes += sizeof(gid);
}
/* host info */
{
char string[1024];
gethostname(string, sizeof(string));
md5_process_bytes(string, sizeof(string), &guid_context);
bytes += sizeof(string);
}
/* plain old random */
{
int n, i;
srand((unsigned int) time(NULL));
for (i = 0; i < 32; i++)
{
n = rand();
md5_process_bytes(&n, sizeof(n), &guid_context);
bytes += sizeof(n);
}
}
/* time in secs and clock ticks */
bytes += init_from_time();
#if DEBUG_GUID
g_warning ("guid_init got %u bytes", bytes);
#endif
if (bytes < THRESHOLD)
g_warning("WARNING: guid_init only got %u bytes.\n"
"The identifiers might not be very random.\n", bytes);
guid_initialized = TRUE;
}
void
guid_init_with_salt(const void *salt, size_t salt_len)
{
guid_init();
md5_process_bytes(salt, salt_len, &guid_context);
}
void
guid_init_only_salt(const void *salt, size_t salt_len)
{
md5_init_ctx(&guid_context);
md5_process_bytes(salt, salt_len, &guid_context);
guid_initialized = TRUE;
}
#define GUID_PERIOD 5000
void
guid_new(GUID *guid)
{
static int counter = 0;
struct md5_ctx ctx;
if (guid == NULL)
return;
if (!guid_initialized)
guid_init();
/* make the id */
ctx = guid_context;
md5_finish_ctx(&ctx, guid->data);
/* update the global context */
init_from_time();
if (counter == 0)
{
FILE *fp;
fp = fopen ("/dev/urandom", "r");
if (fp == NULL)
return;
init_from_stream(fp, 32);
fclose(fp);
counter = GUID_PERIOD;
}
counter--;
}
/* needs 32 bytes exactly, doesn't print a null char */
static void
encode_md5_data(const unsigned char *data, char *buffer)
{
size_t count;
for (count = 0; count < 16; count++, buffer += 2)
sprintf(buffer, "%02x", data[count]);
}
/* returns true if the first 32 bytes of buffer encode
* a hex number. returns false otherwise. Decoded number
* is packed into data in little endian order. */
static gboolean
decode_md5_string(const char *string, unsigned char *data)
{
unsigned char n1, n2;
size_t count = -1;
char c1, c2;
if (NULL == data) return FALSE;
if (NULL == string) goto badstring;
for (count = 0; count < 16; count++)
{
/* check for a short string e.g. null string ... */
if ((0==string[2*count]) || (0==string[2*count+1])) goto badstring;
c1 = tolower(string[2 * count]);
if (!isxdigit(c1)) goto badstring;
c2 = tolower(string[2 * count + 1]);
if (!isxdigit(c2)) goto badstring;
if (isdigit(c1))
n1 = c1 - '0';
else
n1 = c1 - 'a' + 10;
if (isdigit(c2))
n2 = c2 - '0';
else
n2 = c2 - 'a' + 10;
data[count] = (n1 << 4) | n2;
}
return TRUE;
badstring:
PERR ("bad string, stopped at %d\n", count);
for (count = 0; count < 16; count++)
{
data[count] = 0;
}
return FALSE;
}
char *
guid_to_string(const GUID * guid)
{
char *string;
if(!guid) return(NULL);
string = g_malloc(GUID_ENCODING_LENGTH+1);
encode_md5_data(guid->data, string);
string[GUID_ENCODING_LENGTH] = '\0';
return string;
}
char *
guid_to_string_buff(const GUID * guid, char *string)
{
if (!string || !guid) return NULL;
encode_md5_data(guid->data, string);
string[GUID_ENCODING_LENGTH] = '\0';
return &string[GUID_ENCODING_LENGTH];
}
gboolean
string_to_guid(const char * string, GUID * guid)
{
return decode_md5_string(string, (guid != NULL) ? guid->data : NULL);
}
gboolean
guid_equal(const GUID *guid_1, const GUID *guid_2)
{
if (guid_1 && guid_2)
return (memcmp(guid_1, guid_2, sizeof(GUID)) == 0);
else
return FALSE;
}
gint
guid_compare(const GUID *guid_1, const GUID *guid_2)
{
if (guid_1 == guid_2)
return 0;
/* nothing is always less than something */
if (!guid_1 && guid_2)
return -1;
if (guid_1 && !guid_2)
return 1;
return memcmp (guid_1, guid_2, sizeof (GUID));
}
guint
guid_hash_to_guint (gconstpointer ptr)
{
const GUID *guid = ptr;
if (!guid)
{
PERR ("received NULL guid pointer.");
return 0;
}
if (sizeof(guint) <= sizeof(guid->data))
{
return (*((guint *) guid->data));
}
else
{
guint hash = 0;
int i, j;
for (i = 0, j = 0; i < sizeof(guint); i++, j++) {
if (j == 16) j = 0;
hash <<= 4;
hash |= guid->data[j];
}
return hash;
}
}
static gint
guid_g_hash_table_equal (gconstpointer guid_a, gconstpointer guid_b)
{
return guid_equal (guid_a, guid_b);
}
GHashTable *
guid_hash_table_new (void)
{
return g_hash_table_new (guid_hash_to_guint, guid_g_hash_table_equal);
}

112
src/engine/guid.h Normal file
View File

@ -0,0 +1,112 @@
/********************************************************************\
* guid.h -- globally unique ID User API *
* Copyright (C) 2000 Dave Peticolas <peticola@cs.ucdavis.edu> *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License*
* along with this program; if not, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
* Boston, MA 02111-1307, USA gnu@gnu.org *
* *
\********************************************************************/
#ifndef GUID_H
#define GUID_H
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <glib.h>
#include <stddef.h>
/* This file defines an API for using globally unique identifiers. */
/* The type used to store guids */
typedef union _GUID
{
unsigned char data[16];
int __align_me; /* this just ensures that GUIDs are 32-bit
* aligned on systems that need them to be. */
} GUID;
/* number of characters needed to encode a guid as a string
* not including the null terminator. */
#define GUID_ENCODING_LENGTH 32
/* Three functions to initialize the id generator. Only one needs to
* be called. Calling any initialization function a second time will
* reset the generator and erase the effect of the first call.
*
* guid_init() will initialize the generator with a variety of random
* sources.
*
* guid_init_with_salt() will initialize the generator with guid_init()
* and with the data given in the salt argument. This argument can be
* used to add additional randomness to the generated ids.
*
* guid_init_only_salt() will initialize the generator with the data
* given in the salt argument, but not with any other source. Calling
* guid_init_only_salt() with a specific argument will produce a
* specific sequence of ids reliably. */
void guid_init(void);
void guid_init_with_salt(const void *salt, size_t salt_len);
void guid_init_only_salt(const void *salt, size_t salt_len);
/* Generate a new id. If no initialization function has been called,
* guid_init() will be called before the id is created. */
void guid_new(GUID *guid);
/* The guid_to_string() routine returns a null-terminated string
* encoding of the id. String encodings of identifiers are hex
* numbers printed only with the characters '0' through '9' and
* 'a' through 'f'. The encoding will always be GUID_ENCODING_LENGTH
* characters long. The returned string should be freed when no
* longer needed.
*
* The guid_to_string_buff() routine does the same, except that the
* string is written into the memory pointed at by buff. The
* buffer must be at least GUID_ENCODING_LENGTH+1 characters long.
* This routine is handy for avoiding a malloc/free cycle.
* It returns a pointer to the >>end<< of what was written.
* (i.e. it can be used like 'stpcpy' during string concatenation)
*/
char * guid_to_string (const GUID * guid);
char * guid_to_string_buff (const GUID * guid, char *buff);
/* Given a string, decode the id into the guid if guid is non-NULL.
* The function returns TRUE if the string was a valid 32 character
* hexadecimal number. This function accepts both upper and lower case
* hex digits. If the return value is FALSE, the effect on guid is
* undefined. */
gboolean string_to_guid(const char * string, GUID * guid);
/* Given two GUIDs, return TRUE if they are non-NULL and equal.
* Return FALSE, otherwise. */
gboolean guid_equal(const GUID *guid_1, const GUID *guid_2);
gint guid_compare(const GUID *g1, const GUID *g2);
/* Given a GUID *, hash it to a guint */
guint guid_hash_to_guint(gconstpointer ptr);
GHashTable *guid_hash_table_new(void);
#endif

176
src/engine/kvp_doc.txt Normal file
View File

@ -0,0 +1,176 @@
This file documents the use of keys in the key-value pair system
used by the Engine. Before assigning keys for use, please read
the Key-Value Policy in the GnuCash Design Document located
under src/doc/design.
The format of the data below is:
Name: The name of the key, including key names of parent frames
with the parent frames listed first, as in a fully-qualified
filename. Use the '/' character to separate keys.
Type: The type of value stored in the key. The types are listed in
'kvp_frame.h'.
Entities: Which engine entities (Accounts, Transactions, Splits)
can use this key. Use 'All' if every entity could have
the key.
Use: The use to which the key will be put. Include any requirements
for using the key here. Also include any API calls which use
the key. If more than one entity can use the key,
Example:
Name: my-favorite-thing
Type: string
Entities: Accounts
Use: xaccGetMyFavoriteThing(), xaccSetMyFavoriteThing()
This key stores a text description of the user's
favorite thing. Do not use this key to store things
which the user merely likes!
Please put the keys in alphabetical order.
--------------------------------------------------------------------------
Name: last-num
Type: string
Entities: Account
Use: xaccAccountGetLastNum, xaccAccountSetLastNum
Store the last number used in an account's transactions.
Used in auto-filling the Num field.
Name: memo
Type: string
Entities: Split
Use: xaccSplitGetMemo, xaccSplitSetMemo
Store the 'Memo' string associated with Splits.
Name: notes
Type: string
Entities: Account, Transaction
Use: xaccAccountGetNotes, xaccAccountSetNotes,
xaccTransGetNotes, xaccTransSetNotes
Store the 'Notes' string which was previously stored in
the Account structure directly.
Name: old-currency
Type: string
Entities: Account
Use: This string holds the canonical name of the old Account
currency. This may be deleted at some point in the future.
Name: old-currency-scu
Type: gint64
Entities: Account
Use: This holds the old currency scu. This may be deleted at
some point in the future.
Name: old-docref
Type: string
Entities: Transactions, Splits
Use: This string holds the old Docref value which was supported in earlier
versions of GnuCash but which was never used for any purpose. The
value is retained in case any users were making use of it.
Name: old-price-source
Type: string
Entities: Account
Use: This string holds the old Price Source code used by earlier versions
of GnuCash and stored in the obsolete AccInfo structure. The use of
this value will be deprecated when the new version of Finance::Quote
is fully supported. The new version of Finance::Quote uses a different
scheme to identify sources for price quotes.
Name: old-security
Type: string
Entities: Account
Use: This string holds the canonical name of the old Account
security. This may be deleted at some point in the future.
Name: old-security-scu
Type: gint64
Entities: Account
Use: This holds the old security scu. This may be deleted at
some point in the future.
Name: reconcile-info
Type: frame
Entities: Account
Use: store reconcile information about accounts
Name: reconcile-info/last-date
Type: frame
Entities: Account
Use: store the statement date of the last reconciliation
Name: reconcile-info/postpone/date
Type: gint64
Entities: Account
Use: store the ending statement date of a postponed reconciliation
Name: reconcile-info/postpone/balance
Type: numeric
Entities: Account
Use: store the ending balance of a postponed reconciliation
Name: reconcile-info/auto-interest-transfer
Type: string
Entities: Account
Use: allows the user to override the global reconcile option
"Automatic interest transfer" on a per-account basis.
Acceptable values are "true" and "false".
(This really could use a KVP_TYPE_BOOLEAN.)
Name: split-type
Entities: Split
Use: xaccSplitGetType, xaccSplitMakeStockSplit
Store a string representing the type of split, if not normal.
Name: tax-US/code
Type: string
Entities: Account
Use: see src/scm/report/[tax,txf]*.scm
Name: tax-US/payer-name-source
Type: string
Entities: Account
Use: see src/scm/report/[tax,txf]*.scm
Name: tax-related
Type: gint64
Entities: Account
Use: A boolean flag indicated whether the Account is tax-related.
Name: sched-xaction
Type: frame
Entities: Split in a SchedXaction
Use: Storage for the various fields of a scheduled transaction's
template Splits.
Name: sched-xaction/xfrm
Type: GUID
Entities: Split associated with a SchedXaction
Use: The GUID of this Split's xfrm account.
Name: sched-xaction/amnt
Type: string
Entities: Split associated with a SchedXaction
Use: The amount field of a SchedXaction might be a formula, which we
store here.
Name: from-sched-xaction
Type: GUID
Entities: Transaction
Use: Identifies that the Transaction was created from a ScheduledTransaction,
and stores the GUID of that SX.
Name: user-keys
Type: frame
Entities: All
Use: This frame is used to store keys which are editable directly by
the user. The program should not attach any semantics to keys
under this frame.

1107
src/engine/kvp_frame.c Normal file

File diff suppressed because it is too large Load Diff

197
src/engine/kvp_frame.h Normal file
View File

@ -0,0 +1,197 @@
/********************************************************************\
* kvp_frame.h -- a key-value frame system for gnucash. *
* Copyright (C) 2000 Bill Gribble *
* *
* This program is free software; you can redistribute it and/or *
* modify it under the terms of the GNU General Public License as *
* published by the Free Software Foundation; either version 2 of *
* the License, or (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License*
* along with this program; if not, contact: *
* *
* Free Software Foundation Voice: +1-617-542-5942 *
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
* Boston, MA 02111-1307, USA gnu@gnu.org *
* *
\********************************************************************/
#ifndef KVP_FRAME_H
#define KVP_FRAME_H
#include <glib.h>
#include "gnc-numeric.h"
#include "guid.h"
/* a kvp_frame is a set of associations between character strings
* (keys) and kvp_value structures. A kvp_value is a union with
* possible types enumerated in the kvp_value_t enum.
*
* Pointers passed as arguments into set_slot and get_slot are the
* responsibility of the caller. Pointers returned by get_slot are
* owned by the kvp_frame. Make copies as needed.
*
* kvp_frame_delete and kvp_value_delete are deep (recursive) deletes.
* kvp_frame_copy and kvp_value_copy are deep value copies.
*/
typedef enum {
KVP_TYPE_GINT64,
KVP_TYPE_DOUBLE,
KVP_TYPE_NUMERIC,
KVP_TYPE_STRING,
KVP_TYPE_GUID,
KVP_TYPE_BINARY,
KVP_TYPE_GLIST,
KVP_TYPE_FRAME
} kvp_value_t;
typedef struct _kvp_frame kvp_frame;
typedef struct _kvp_value kvp_value;
/* kvp_frame functions */
kvp_frame * kvp_frame_new(void);
void kvp_frame_delete(kvp_frame * frame);
kvp_frame * kvp_frame_copy(const kvp_frame * frame);
gboolean kvp_frame_is_empty(kvp_frame * frame);
gchar* kvp_frame_to_string(const kvp_frame *frame);
gchar* binary_to_string(const void *data, guint32 size);
gchar* kvp_value_glist_to_string(const GList *list);
GHashTable* kvp_frame_get_hash(const kvp_frame *frame);
/* The kvp_frame_set_slot() routine copies the value into the frame,
* associating it with 'key'.
* The kvp_frame_set_slot_nc() routine puts the value (without copying
* it) into the frame, associating it with 'key'. This routine is
* handy for avoiding excess memory allocations & frees.
* Pointers passed as arguments into set_slot and get_slot are the
* responsibility of the caller. Pointers returned by get_slot are
* owned by the kvp_frame. Make copies as needed.
*/
void kvp_frame_set_slot(kvp_frame * frame,
const char * key, const kvp_value * value);
void kvp_frame_set_slot_nc(kvp_frame * frame,
const char * key, kvp_value * value);
kvp_value * kvp_frame_get_slot(kvp_frame * frame,
const char * key);
/* The kvp_frame_set_slot_path() routines walk the hierarchy,
* using the key falues to pick each branch. When the terminal node
* is reached, the value is copied into it.
*/
void kvp_frame_set_slot_path (kvp_frame *frame,
const kvp_value *value,
const char *first_key, ...);
void kvp_frame_set_slot_path_gslist (kvp_frame *frame,
const kvp_value *value,
GSList *key_path);
/* The following routines return the last frame of the path.
* If the frame path doesn't exist, it is created.
*
* The kvp_frame_get_frame_slash() routine takes a single string
* where the keys are separated by slashes; thus, for example:
* /this/is/a/valid/path and///so//is////this/
* Multiple slashes are compresed. leading slash is optional.
* The pointers . and .. are *not* followed/obeyed. (This is
* arguably a bug that needs fixing).
*
*
*/
kvp_frame * kvp_frame_get_frame (kvp_frame *frame, ...);
kvp_frame * kvp_frame_get_frame_gslist (kvp_frame *frame,
GSList *key_path);
kvp_frame * kvp_frame_get_frame_slash (kvp_frame *frame,
const char *path);
/* The following routines return the value att the end of the
* path, or NULL if any portion of the path doesn't exist.
*/
kvp_value * kvp_frame_get_slot_path (kvp_frame *frame,
const char *first_key, ...);
kvp_value * kvp_frame_get_slot_path_gslist (kvp_frame *frame,
GSList *key_path);
gint kvp_frame_compare(const kvp_frame *fa, const kvp_frame *fb);
gint double_compare(double v1, double v2);
void kvp_value_delete(kvp_value * value);
kvp_value * kvp_value_copy(const kvp_value * value);
/**
* Similar returns as strcmp.
**/
gint kvp_value_compare(const kvp_value *va, const kvp_value *vb);
/* list convenience funcs. */
gint kvp_glist_compare(const GList * list1, const GList * list2);
GList * kvp_glist_copy(const GList * list);
/* deep copy: same as mapping kvp_value_copy over the
elements and then copying the spine. */
void kvp_glist_delete(GList * list);
/* deep delete: same as mapping kvp_value_delete over the
elements and then deleting the GList. */
/* value constructors (copying for pointer args) */
kvp_value * kvp_value_new_gint64(gint64 value);
kvp_value * kvp_value_new_double(double value);
kvp_value * kvp_value_new_gnc_numeric(gnc_numeric value);
kvp_value * kvp_value_new_string(const char * value);
kvp_value * kvp_value_new_guid(const GUID * guid);
kvp_value * kvp_value_new_binary(const void * data, guint64 datasize);
kvp_value * kvp_value_new_glist(const GList * value);
kvp_value * kvp_value_new_frame(const kvp_frame * value);
/* value constructors (non-copying - kvp_value takes pointer ownership)
values *must* have been allocated via glib allocators! (gnew, etc.) */
kvp_value * kvp_value_new_binary_nc(void * data, guint64 datasize);
kvp_value * kvp_value_new_glist_nc(GList *lst);
/* value accessors (NON-copying for frames/lists/guids/binary) */
kvp_value_t kvp_value_get_type(const kvp_value * value);
gint64 kvp_value_get_gint64(const kvp_value * value);
double kvp_value_get_double(const kvp_value * value);
gnc_numeric kvp_value_get_numeric(const kvp_value * value);
char * kvp_value_get_string(const kvp_value * value);
GUID * kvp_value_get_guid(const kvp_value * value);
void * kvp_value_get_binary(const kvp_value * value,
guint64 * size_return);
GList * kvp_value_get_glist(const kvp_value * value);
kvp_frame * kvp_value_get_frame(const kvp_value * value);
gchar* kvp_value_to_string(const kvp_value *val);
/* manipulators */
gboolean kvp_value_binary_append(kvp_value *v, void *data, guint64 size);
/* copying - but more efficient than creating a new kvp_value manually. */
/* iterators */
/* Traverse all of the slots in the given kvp_frame. This function
does not descend recursively to traverse any kvp_frames stored as
slot values. You must handle that in proc, with a suitable
recursive call if desired. */
void kvp_frame_for_each_slot(kvp_frame *f,
void (*proc)(const char *key,
kvp_value *value,
gpointer data),
gpointer data);
#endif

444
src/engine/md5.c Normal file
View File

@ -0,0 +1,444 @@
/* md5.c - Functions to compute MD5 message digest of files or memory blocks
according to the definition of MD5 in RFC 1321 from April 1992.
Copyright (C) 1995, 1996 Free Software Foundation, Inc.
NOTE: The canonical source of this file is maintained with the GNU C
Library. Bugs can be reported to bug-glibc@prep.ai.mit.edu.
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software Foundation,
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
/* Written by Ulrich Drepper <drepper@gnu.ai.mit.edu>, 1995. */
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <sys/types.h>
#if STDC_HEADERS || defined _LIBC
# include <stdlib.h>
# include <string.h>
#else
# ifndef HAVE_MEMCPY
# define memcpy(d, s, n) bcopy ((s), (d), (n))
# endif
#endif
#include "md5.h"
#ifdef _LIBC
# include <endian.h>
# if __BYTE_ORDER == __BIG_ENDIAN
# define WORDS_BIGENDIAN 1
# endif
#endif
#ifdef WORDS_BIGENDIAN
# define SWAP(n) \
(((n) << 24) | (((n) & 0xff00) << 8) | (((n) >> 8) & 0xff00) | ((n) >> 24))
#else
# define SWAP(n) (n)
#endif
/* This array contains the bytes used to pad the buffer to the next
64-byte boundary. (RFC 1321, 3.1: Step 1) */
static const unsigned char fillbuf[64] = { 0x80, 0 /* , 0, 0, ... */ };
/* Initialize structure containing state of computation.
(RFC 1321, 3.3: Step 3) */
void
md5_init_ctx (ctx)
struct md5_ctx *ctx;
{
ctx->A = 0x67452301;
ctx->B = 0xefcdab89;
ctx->C = 0x98badcfe;
ctx->D = 0x10325476;
ctx->total[0] = ctx->total[1] = 0;
ctx->buflen = 0;
}
/* Put result from CTX in first 16 bytes following RESBUF. The result
must be in little endian byte order.
IMPORTANT: On some systems it is required that RESBUF is correctly
aligned for a 32 bits value. */
void *
md5_read_ctx (ctx, resbuf)
const struct md5_ctx *ctx;
void *resbuf;
{
((md5_uint32 *) resbuf)[0] = SWAP (ctx->A);
((md5_uint32 *) resbuf)[1] = SWAP (ctx->B);
((md5_uint32 *) resbuf)[2] = SWAP (ctx->C);
((md5_uint32 *) resbuf)[3] = SWAP (ctx->D);
return resbuf;
}
/* Process the remaining bytes in the internal buffer and the usual
prolog according to the standard and write the result to RESBUF.
IMPORTANT: On some systems it is required that RESBUF is correctly
aligned for a 32 bits value. */
void *
md5_finish_ctx (ctx, resbuf)
struct md5_ctx *ctx;
void *resbuf;
{
/* Take yet unprocessed bytes into account. */
md5_uint32 bytes = ctx->buflen;
size_t pad;
/* Now count remaining bytes. */
ctx->total[0] += bytes;
if (ctx->total[0] < bytes)
++ctx->total[1];
pad = bytes >= 56 ? 64 + 56 - bytes : 56 - bytes;
memcpy (&ctx->buffer[bytes], fillbuf, pad);
/* Put the 64-bit file length in *bits* at the end of the buffer. */
*(md5_uint32 *) &ctx->buffer[bytes + pad] = SWAP (ctx->total[0] << 3);
*(md5_uint32 *) &ctx->buffer[bytes + pad + 4] = SWAP ((ctx->total[1] << 3) |
(ctx->total[0] >> 29));
/* Process last bytes. */
md5_process_block (ctx->buffer, bytes + pad + 8, ctx);
return md5_read_ctx (ctx, resbuf);
}
/* Compute MD5 message digest for bytes read from STREAM. The
resulting message digest number will be written into the 16 bytes
beginning at RESBLOCK. */
int
md5_stream (stream, resblock)
FILE *stream;
void *resblock;
{
/* Important: BLOCKSIZE must be a multiple of 64. */
#define BLOCKSIZE 4096
struct md5_ctx ctx;
char buffer[BLOCKSIZE + 72];
size_t sum;
/* Initialize the computation context. */
md5_init_ctx (&ctx);
/* Iterate over full file contents. */
while (1)
{
/* We read the file in blocks of BLOCKSIZE bytes. One call of the
computation function processes the whole buffer so that with the
next round of the loop another block can be read. */
size_t n;
sum = 0;
/* Read block. Take care for partial reads. */
do
{
n = fread (buffer + sum, 1, BLOCKSIZE - sum, stream);
sum += n;
}
while (sum < BLOCKSIZE && n != 0);
if (n == 0 && ferror (stream))
return 1;
/* If end of file is reached, end the loop. */
if (n == 0)
break;
/* Process buffer with BLOCKSIZE bytes. Note that
BLOCKSIZE % 64 == 0
*/
md5_process_block (buffer, BLOCKSIZE, &ctx);
}
/* Add the last bytes if necessary. */
if (sum > 0)
md5_process_bytes (buffer, sum, &ctx);
/* Construct result in desired memory. */
md5_finish_ctx (&ctx, resblock);
return 0;
}
/* Compute MD5 message digest for LEN bytes beginning at BUFFER. The
result is always in little endian byte order, so that a byte-wise
output yields to the wanted ASCII representation of the message
digest. */
void *
md5_buffer (buffer, len, resblock)
const char *buffer;
size_t len;
void *resblock;
{
struct md5_ctx ctx;
/* Initialize the computation context. */
md5_init_ctx (&ctx);
/* Process whole buffer but last len % 64 bytes. */
md5_process_bytes (buffer, len, &ctx);
/* Put result in desired memory area. */
return md5_finish_ctx (&ctx, resblock);
}
void
md5_process_bytes (buffer, len, ctx)
const void *buffer;
size_t len;
struct md5_ctx *ctx;
{
#define NUM_MD5_WORDS 1024
size_t add = 0;
/* When we already have some bits in our internal buffer concatenate
both inputs first. */
if (ctx->buflen != 0)
{
size_t left_over = ctx->buflen;
add = 128 - left_over > len ? len : 128 - left_over;
memcpy (&ctx->buffer[left_over], buffer, add);
ctx->buflen += add;
if (left_over + add > 64)
{
md5_process_block (ctx->buffer, (left_over + add) & ~63, ctx);
/* The regions in the following copy operation cannot overlap. */
memcpy (ctx->buffer, &ctx->buffer[(left_over + add) & ~63],
(left_over + add) & 63);
ctx->buflen = (left_over + add) & 63;
}
buffer = (const char *) buffer + add;
len -= add;
}
/* Process available complete blocks. */
if (len > 64)
{
if ((add & 3) == 0) /* buffer is still 32-bit aligned */
{
md5_process_block (buffer, len & ~63, ctx);
buffer = (const char *) buffer + (len & ~63);
}
else /* buffer is not 32-bit aligned */
{
md5_uint32 md5_buffer[NUM_MD5_WORDS];
size_t num_bytes;
size_t buf_bytes;
num_bytes = len & ~63;
while (num_bytes > 0)
{
buf_bytes = (num_bytes < sizeof(md5_buffer)) ?
num_bytes : sizeof(md5_buffer);
memcpy (md5_buffer, buffer, buf_bytes);
md5_process_block (md5_buffer, buf_bytes, ctx);
num_bytes -= buf_bytes;
buffer = (const char *) buffer + buf_bytes;
}
}
len &= 63;
}
/* Move remaining bytes in internal buffer. */
if (len > 0)
{
memcpy (ctx->buffer, buffer, len);
ctx->buflen = len;
}
}
/* These are the four functions used in the four steps of the MD5 algorithm
and defined in the RFC 1321. The first function is a little bit optimized
(as found in Colin Plumbs public domain implementation). */
/* #define FF(b, c, d) ((b & c) | (~b & d)) */
#define FF(b, c, d) (d ^ (b & (c ^ d)))
#define FG(b, c, d) FF (d, b, c)
#define FH(b, c, d) (b ^ c ^ d)
#define FI(b, c, d) (c ^ (b | ~d))
/* Process LEN bytes of BUFFER, accumulating context into CTX.
It is assumed that LEN % 64 == 0. */
void
md5_process_block (buffer, len, ctx)
const void *buffer;
size_t len;
struct md5_ctx *ctx;
{
md5_uint32 correct_words[16];
const md5_uint32 *words = buffer;
size_t nwords = len / sizeof (md5_uint32);
const md5_uint32 *endp = words + nwords;
md5_uint32 A = ctx->A;
md5_uint32 B = ctx->B;
md5_uint32 C = ctx->C;
md5_uint32 D = ctx->D;
/* First increment the byte count. RFC 1321 specifies the possible
length of the file up to 2^64 bits. Here we only compute the
number of bytes. Do a double word increment. */
ctx->total[0] += len;
if (ctx->total[0] < len)
++ctx->total[1];
/* Process all bytes in the buffer with 64 bytes in each round of
the loop. */
while (words < endp)
{
md5_uint32 *cwp = correct_words;
md5_uint32 A_save = A;
md5_uint32 B_save = B;
md5_uint32 C_save = C;
md5_uint32 D_save = D;
/* First round: using the given function, the context and a constant
the next context is computed. Because the algorithms processing
unit is a 32-bit word and it is determined to work on words in
little endian byte order we perhaps have to change the byte order
before the computation. To reduce the work for the next steps
we store the swapped words in the array CORRECT_WORDS. */
#define OP(a, b, c, d, s, T) \
do \
{ \
a += FF (b, c, d) + (*cwp++ = SWAP (*words)) + T; \
++words; \
CYCLIC (a, s); \
a += b; \
} \
while (0)
/* It is unfortunate that C does not provide an operator for
cyclic rotation. Hope the C compiler is smart enough. */
#define CYCLIC(w, s) (w = (w << s) | (w >> (32 - s)))
/* Before we start, one word to the strange constants.
They are defined in RFC 1321 as
T[i] = (int) (4294967296.0 * fabs (sin (i))), i=1..64
*/
/* Round 1. */
OP (A, B, C, D, 7, 0xd76aa478);
OP (D, A, B, C, 12, 0xe8c7b756);
OP (C, D, A, B, 17, 0x242070db);
OP (B, C, D, A, 22, 0xc1bdceee);
OP (A, B, C, D, 7, 0xf57c0faf);
OP (D, A, B, C, 12, 0x4787c62a);
OP (C, D, A, B, 17, 0xa8304613);
OP (B, C, D, A, 22, 0xfd469501);
OP (A, B, C, D, 7, 0x698098d8);
OP (D, A, B, C, 12, 0x8b44f7af);
OP (C, D, A, B, 17, 0xffff5bb1);
OP (B, C, D, A, 22, 0x895cd7be);
OP (A, B, C, D, 7, 0x6b901122);
OP (D, A, B, C, 12, 0xfd987193);
OP (C, D, A, B, 17, 0xa679438e);
OP (B, C, D, A, 22, 0x49b40821);
/* For the second to fourth round we have the possibly swapped words
in CORRECT_WORDS. Redefine the macro to take an additional first
argument specifying the function to use. */
#undef OP
#define OP(f, a, b, c, d, k, s, T) \
do \
{ \
a += f (b, c, d) + correct_words[k] + T; \
CYCLIC (a, s); \
a += b; \
} \
while (0)
/* Round 2. */
OP (FG, A, B, C, D, 1, 5, 0xf61e2562);
OP (FG, D, A, B, C, 6, 9, 0xc040b340);
OP (FG, C, D, A, B, 11, 14, 0x265e5a51);
OP (FG, B, C, D, A, 0, 20, 0xe9b6c7aa);
OP (FG, A, B, C, D, 5, 5, 0xd62f105d);
OP (FG, D, A, B, C, 10, 9, 0x02441453);
OP (FG, C, D, A, B, 15, 14, 0xd8a1e681);
OP (FG, B, C, D, A, 4, 20, 0xe7d3fbc8);
OP (FG, A, B, C, D, 9, 5, 0x21e1cde6);
OP (FG, D, A, B, C, 14, 9, 0xc33707d6);
OP (FG, C, D, A, B, 3, 14, 0xf4d50d87);
OP (FG, B, C, D, A, 8, 20, 0x455a14ed);
OP (FG, A, B, C, D, 13, 5, 0xa9e3e905);
OP (FG, D, A, B, C, 2, 9, 0xfcefa3f8);
OP (FG, C, D, A, B, 7, 14, 0x676f02d9);
OP (FG, B, C, D, A, 12, 20, 0x8d2a4c8a);
/* Round 3. */
OP (FH, A, B, C, D, 5, 4, 0xfffa3942);
OP (FH, D, A, B, C, 8, 11, 0x8771f681);
OP (FH, C, D, A, B, 11, 16, 0x6d9d6122);
OP (FH, B, C, D, A, 14, 23, 0xfde5380c);
OP (FH, A, B, C, D, 1, 4, 0xa4beea44);
OP (FH, D, A, B, C, 4, 11, 0x4bdecfa9);
OP (FH, C, D, A, B, 7, 16, 0xf6bb4b60);
OP (FH, B, C, D, A, 10, 23, 0xbebfbc70);
OP (FH, A, B, C, D, 13, 4, 0x289b7ec6);
OP (FH, D, A, B, C, 0, 11, 0xeaa127fa);
OP (FH, C, D, A, B, 3, 16, 0xd4ef3085);
OP (FH, B, C, D, A, 6, 23, 0x04881d05);
OP (FH, A, B, C, D, 9, 4, 0xd9d4d039);
OP (FH, D, A, B, C, 12, 11, 0xe6db99e5);
OP (FH, C, D, A, B, 15, 16, 0x1fa27cf8);
OP (FH, B, C, D, A, 2, 23, 0xc4ac5665);
/* Round 4. */
OP (FI, A, B, C, D, 0, 6, 0xf4292244);
OP (FI, D, A, B, C, 7, 10, 0x432aff97);
OP (FI, C, D, A, B, 14, 15, 0xab9423a7);
OP (FI, B, C, D, A, 5, 21, 0xfc93a039);
OP (FI, A, B, C, D, 12, 6, 0x655b59c3);
OP (FI, D, A, B, C, 3, 10, 0x8f0ccc92);
OP (FI, C, D, A, B, 10, 15, 0xffeff47d);
OP (FI, B, C, D, A, 1, 21, 0x85845dd1);
OP (FI, A, B, C, D, 8, 6, 0x6fa87e4f);
OP (FI, D, A, B, C, 15, 10, 0xfe2ce6e0);
OP (FI, C, D, A, B, 6, 15, 0xa3014314);
OP (FI, B, C, D, A, 13, 21, 0x4e0811a1);
OP (FI, A, B, C, D, 4, 6, 0xf7537e82);
OP (FI, D, A, B, C, 11, 10, 0xbd3af235);
OP (FI, C, D, A, B, 2, 15, 0x2ad7d2bb);
OP (FI, B, C, D, A, 9, 21, 0xeb86d391);
/* Add the starting values of the context. */
A += A_save;
B += B_save;
C += C_save;
D += D_save;
}
/* Put checksum in context given as argument. */
ctx->A = A;
ctx->B = B;
ctx->C = C;
ctx->D = D;
}

163
src/engine/md5.h Normal file
View File

@ -0,0 +1,163 @@
/* md5.h - Declaration of functions and data types used for MD5 sum
computing library functions.
Copyright (C) 1995, 1996 Free Software Foundation, Inc.
NOTE: The canonical source of this file is maintained with the GNU C
Library. Bugs can be reported to bug-glibc@prep.ai.mit.edu.
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software Foundation,
Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
#ifndef _MD5_H
#define _MD5_H 1
#include <stddef.h>
#include <stdio.h>
#if defined HAVE_LIMITS_H || _LIBC
# include <limits.h>
#endif
/* The following contortions are an attempt to use the C preprocessor
to determine an unsigned integral type that is 32 bits wide. An
alternative approach is to use autoconf's AC_CHECK_SIZEOF macro, but
doing that would require that the configure script compile and *run*
the resulting executable. Locally running cross-compiled executables
is usually not possible. */
#ifdef _LIBC
# include <sys/types.h>
typedef u_int32_t md5_uint32;
#else
# if defined __STDC__ && __STDC__
# define UINT_MAX_32_BITS 4294967295U
# else
# define UINT_MAX_32_BITS 0xFFFFFFFF
# endif
/* If UINT_MAX isn't defined, assume it's a 32-bit type.
This should be valid for all systems GNU cares about because
that doesn't include 16-bit systems, and only modern systems
(that certainly have <limits.h>) have 64+-bit integral types. */
# ifndef UINT_MAX
# define UINT_MAX UINT_MAX_32_BITS
# endif
# if UINT_MAX == UINT_MAX_32_BITS
typedef unsigned int md5_uint32;
# else
# if USHRT_MAX == UINT_MAX_32_BITS
typedef unsigned short md5_uint32;
# else
# if ULONG_MAX == UINT_MAX_32_BITS
typedef unsigned long md5_uint32;
# else
/* The following line is intended to evoke an error.
Using #error is not portable enough. */
"Cannot determine unsigned 32-bit data type."
# endif
# endif
# endif
#endif
#undef __P
#if defined (__STDC__) && __STDC__
#define __P(x) x
#else
#define __P(x) ()
#endif
/* Structure to save state of computation between the single steps. */
struct md5_ctx
{
md5_uint32 A;
md5_uint32 B;
md5_uint32 C;
md5_uint32 D;
md5_uint32 total[2];
md5_uint32 buflen;
char buffer[128];
};
/*
* The following three functions are build up the low level used in
* the functions `md5_stream' and `md5_buffer'.
*/
/* Initialize structure containing state of computation.
(RFC 1321, 3.3: Step 3) */
extern void md5_init_ctx __P ((struct md5_ctx *ctx));
/* Starting with the result of former calls of this function (or the
initialization function update the context for the next LEN bytes
starting at BUFFER.
It is necessary that LEN is a multiple of 64!!!
IMPORTANT: On some systems it is required that buffer be 32-bit
aligned. */
extern void md5_process_block __P ((const void *buffer, size_t len,
struct md5_ctx *ctx));
/* Starting with the result of former calls of this function (or the
initialization function) update the context for the next LEN bytes
starting at BUFFER.
It is NOT required that LEN is a multiple of 64.
IMPORTANT: On some systems it is required that buffer be 32-bit
aligned. */
extern void md5_process_bytes __P ((const void *buffer, size_t len,
struct md5_ctx *ctx));
/* Process the remaining bytes in the buffer and put result from CTX
in first 16 bytes following RESBUF. The result is always in little
endian byte order, so that a byte-wise output yields to the wanted
ASCII representation of the message digest.
IMPORTANT: On some systems it is required that RESBUF be correctly
aligned for a 32 bits value. */
extern void *md5_finish_ctx __P ((struct md5_ctx *ctx, void *resbuf));
/* Put result from CTX in first 16 bytes following RESBUF. The result is
always in little endian byte order, so that a byte-wise output yields
to the wanted ASCII representation of the message digest.
IMPORTANT: On some systems it is required that RESBUF be correctly
aligned for a 32 bits value. */
extern void *md5_read_ctx __P ((const struct md5_ctx *ctx, void *resbuf));
/* Compute MD5 message digest for bytes read from STREAM. The
resulting message digest number will be written into the 16 bytes
beginning at RESBLOCK.
IMPORTANT: On some systems it is required that resblock be 32-bit
aligned. */
extern int md5_stream __P ((FILE *stream, void *resblock));
/* Compute MD5 message digest for LEN bytes beginning at BUFFER. The
result is always in little endian byte order, so that a byte-wise
output yields to the wanted ASCII representation of the message
digest.
IMPORTANT: On some systems it is required that buffer and resblock
be correctly 32-bit aligned. */
extern void *md5_buffer __P ((const char *buffer, size_t len, void *resblock));
#endif