mirror of
https://github.com/Gnucash/gnucash.git
synced 2025-02-25 18:55:30 -06:00
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:
@@ -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;
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user