cleanup the handling of network io a bit, add some explanatory text

git-svn-id: svn+ssh://svn.gnucash.org/repo/gnucash/trunk@3437 57a11ea4-9604-0410-9ed3-97b8803252fd
This commit is contained in:
Linas Vepstas
2001-01-11 07:24:13 +00:00
parent 3c0808f7d0
commit c11dc13c18
4 changed files with 216 additions and 82 deletions

View File

@@ -1,8 +1,12 @@
/*
* FILE:
* BackendP.h
*
* FUNCTION:
* Pseudo-object defining how the engine can interact with different
* back-ends (which will probably be sql databases).
* 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.
@@ -20,28 +24,75 @@
#include "Transaction.h"
#include "gnc-book.h"
typedef enum {
ERR_BACKEND_NONE = 0,
ERR_BACKEND_MISC,
/* fileio errors */
ERR_BFILEIO_FILE_BAD_READ,
ERR_BFILEIO_FILE_EMPTY,
ERR_BFILEIO_FILE_NOT_FOUND,
ERR_BFILEIO_FILE_TOO_NEW,
ERR_BFILEIO_FILE_TOO_OLD,
ERR_BFILEIO_ALLOC,
/* network errors */
ERR_NETIO_SHORT_READ,
ERR_NETIO_WRONG_CONTENT_TYPE,
ERR_NETIO_NOT_GNCXML
} GNCBackendError;
typedef struct _backend Backend;
/*
* trans_commit_edit() takes two transaction arguments:
* the first is the proposed new transaction; the second is the
* 'original' transaction. The second argument is here for
* convencience; 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_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
* convencience; 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 run_query() callback takes a gnucash query object.
* For an sql backend, the contents of the query object need to
* be turned into a corresponsing 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 heirarchy held by the
* query object.
*
* For a network-communications backend, esentially 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 heirarchy, 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 last_err member indicates the last error that occured.
* It should probably be implemented as an array (actually,
* a stack) of all the errors that have occurred.
*/
struct _backend
{
AccountGroup * (*book_load) (GNCBook *, const char * book_id);
int (*book_end) (GNCBook *);
void (*book_end) (GNCBook *);
int (*account_begin_edit) (Backend *, Account *, int defer);
int (*account_commit_edit) (Backend *, Account *);
int (*trans_begin_edit) (Backend *, Transaction *);
int (*trans_commit_edit) (Backend *, Transaction *new, Transaction *orig);
int (*trans_rollback_edit) (Backend *, Transaction *);
int (*run_query) (Backend *, Query *);
void (*run_query) (Backend *, Query *);
GNCBackendError last_err;
};
/*

View File

@@ -22,8 +22,9 @@
\********************************************************************/
/*
* ultra super rudimentary right now
* Rudimentary implmentation right now; good enough for a demo,
* but that's all.
*
* HACK ALRT -- this should be moved into its own sbdirectory
* Mostly so that the engine build doesn't require libghttp
* as a dependency.
@@ -42,7 +43,7 @@
#include "gnc-engine-util.h"
#include "io-gncxml.h"
static short module = MOD_IO;
static short module = MOD_BACKEND;
typedef struct _xmlend XMLBackend;
@@ -51,11 +52,106 @@ struct _xmlend {
ghttp_request *request;
char * query_url;
char * auth_cookie;
};
Backend *xmlendNew (void);
/* ==================================================================== */
/* Perform vaious 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);
/* 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, 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,
"gnucash/1.5 (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);
}
}
/* ==================================================================== */
/* Load a set of accounts and currencies from the indicated URL. */
@@ -65,70 +161,58 @@ xmlbeBookLoad (GNCBook *book, const char *url)
XMLBackend *be;
AccountGroup *grp;
ghttp_request *request;
char *bufp;
const char *bufp;
int len;
if (!book) return NULL;
ENTER ("url is %s\n", url);
ENTER ("url is %s", url);
be = (XMLBackend *) xaccGNCBookGetBackend (book);
/* hack alert -- some bogus url for sending queries to */
/* this should be made customizable, I suppose ???? */
/* hack alert -- we store this first url as some bogus url
* for sending queries to
* this should be made customizable, I suppose ???? */
be->query_url = g_strdup (url);
/* build up a request for the URL */
setup_request (be);
request = be->request;
ghttp_set_uri (request, (char *) url);
ghttp_set_type (request, ghttp_type_get);
ghttp_set_header (request, http_hdr_Connection, "close");
ghttp_set_sync (request, ghttp_sync);
ghttp_clean (request);
ghttp_prepare (request);
ghttp_process (request);
len = ghttp_get_body_len(request);
/* perform various error and validity checking on the response */
len = check_response (be);
if (0 >= len) return NULL;
if (0 < len)
{
bufp = ghttp_get_body(request);
PINFO ("reply length=%d\n", len);
DEBUG ("%s\n", bufp);
grp = gncxml_read_from_buf (bufp, len);
return grp;
}
else
{
const char * errstr = ghttp_get_error (request);
const char * reason = ghttp_reason_phrase (request);
PERR ("connection failed: %s %s\n", errstr, reason);
return NULL;
}
bufp = ghttp_get_body(request);
grp = gncxml_read_from_buf (bufp, len);
LEAVE("\n");
return NULL;
LEAVE(" ");
return grp;
}
/* ==================================================================== */
static int
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 999;
if (!be || !q) return;
ENTER ("be=%p q=%p", b, q);
/* set up a new http request, of type POST */
request = ghttp_request_new();
setup_request (be);
request = be->request;
ghttp_set_uri (request, be->query_url);
ghttp_set_type (request, ghttp_type_post);
ghttp_set_header (request, http_hdr_Connection, "close");
ghttp_set_sync (request, ghttp_sync);
ghttp_clean (request);
/* convert the query to XML */
gncxml_write_query_to_buf (q, &bufp, &len);
@@ -143,41 +227,35 @@ xmlbeRunQuery (Backend *b, Query *q)
/* free the query xml */
free (bufp);
len = ghttp_get_body_len(request);
/* 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);
if (0 < len)
{
bufp = ghttp_get_body(request);
PINFO ("reply length=%d\n", len);
DEBUG ("%s\n", bufp);
/* we got back a list of splits, these need to be merged in */
// grp = gncxml_read_from_buf (bufp, len);
return 0;
}
else
{
const char * errstr = ghttp_get_error (request);
const char * reason = ghttp_reason_phrase (request);
PERR ("connection failed: %s %s\n", errstr, reason);
return 444;
}
LEAVE("\n");
return 0;
/* merge the splits into our local cache */
grp = xaccQueryGetGroup (q);
xaccGroupConcatGroup (grp, reply_grp);
xaccGroupMergeAccounts (grp);
xaccFreeAccountGroup (reply_grp);
LEAVE(" ");
return;
}
/* ==================================================================== */
#if 0
xmlbeBookEnd ()
static void
xmlbeBookEnd (GNCBook *book)
{
XMLBackend *be;
be = (XMLBackend *) xaccGNCBookGetBackend (book);
ghttp_request_destroy (be->request);
g_free (be->query_url);
}
#endif
/* ==================================================================== */
@@ -185,11 +263,12 @@ Backend *
xmlendNew (void)
{
XMLBackend *be;
be = (XMLBackend *) malloc (sizeof (XMLBackend));
/* generic backend handlers */
be->be.book_load = xmlbeBookLoad;
be->be.book_end = NULL;
be->be.book_end = xmlbeBookEnd;
be->be.account_begin_edit = NULL;
be->be.account_commit_edit = NULL;
@@ -198,7 +277,11 @@ xmlendNew (void)
be->be.trans_rollback_edit = NULL;
be->be.run_query = xmlbeRunQuery;
be->be.last_err = ERR_BACKEND_NONE;
be->request = ghttp_request_new();
be->auth_cookie = NULL;
be->query_url = NULL;
return (Backend *) be;

View File

@@ -253,7 +253,7 @@ static gboolean
gnc_book_begin_file (GNCBook *book, const char * filefrag,
gboolean ignore_lock)
{
ENTER ("filefrag=%s\n", filefrag);
ENTER ("filefrag=%s", filefrag);
/* Try to find or build an absolute file path */
@@ -282,7 +282,7 @@ gnc_book_begin_file (GNCBook *book, const char * filefrag,
return FALSE;
}
LEAVE ("\n");
LEAVE (" ");
return TRUE;
}
@@ -294,7 +294,7 @@ gnc_book_begin (GNCBook *book, const char * book_id, gboolean ignore_lock)
int rc;
if (!book) return FALSE;
ENTER (" book-id=%s\n", book_id);
ENTER (" book-id=%s", book_id);
/* clear the error condition of previous errors */
book->errtype = 0;
@@ -376,7 +376,7 @@ gnc_book_load (GNCBook *book)
if (!book) return FALSE;
if (!book->book_id) return FALSE;
ENTER ("book_id=%s\n", book->book_id);
ENTER ("book_id=%s", book->book_id);
if (strncmp(book->book_id, "file:", 5) == 0)
{
@@ -407,7 +407,7 @@ gnc_book_load (GNCBook *book)
xaccGroupScrubSplits (book->topgroup);
LEAVE("\n");
LEAVE(" ");
return TRUE;
}
@@ -445,7 +445,7 @@ gnc_book_load (GNCBook *book)
return FALSE;
}
LEAVE("\n");
LEAVE(" ");
return TRUE;
}
else
@@ -604,7 +604,7 @@ xaccResolveFilePath (const char * filefrag)
/* seriously invalid */
if (!filefrag) return NULL;
ENTER ("filefrag=%s\n", filefrag);
ENTER ("filefrag=%s", filefrag);
/* ---------------------------------------------------- */
/* OK, now we try to find or build an absolute file path */

View File

@@ -960,19 +960,19 @@ xml_add_qterm_restorer(xmlNodePtr qxml, QueryTerm *qt)
/* however, many of the types share a generic structure. */
switch (qt->data.type) {
case PD_ACCOUNT:
PERR ("unimplemented");
PERR ("account query unimplemented");
break;
case PD_AMOUNT:
PERR ("unimplemented");
PERR ("amount query unimplemented");
break;
case PD_BALANCE:
PERR ("unimplemented");
PERR ("balance query unimplemented");
break;
case PD_CLEARED:
PERR ("unimplemented");
PERR ("cleared query unimplemented");
break;
case PD_DATE: