4.3. Building QOF onto pilot-link

QOF and pilot-link. Merging QofBook's within GnuCash is one thing, to achieve my initial goal, I have to get the PDA to talk in QOF as well. I've now begun the work of QOF enabling pilot-link by wrapping existing structures in a QOF environment.

The principle is to wrap the existing struct inside a new QOF-compatible struct. The essential addition is QofInstance inst; then define a get and a set routine for each parameter to be supported by QOF. In each routine, cast the address of the wrapped struct to a suitable pointer and use that to store or retrieve the real data via QOF.

NoteLocaltime is determined by the process running QOF.
 

There are also issues of dates and times between QOF and pilot-link. Pilot-link usually works on localtime - the Palm only sets the timezone in Preferences, it doesn't use it internally - so the link to QOF will have to make this crucial assumption. When converting Palm localtimes into QOF universal (UTC) times, the time will be converted by QOF using the localtime of the QOF process.

In real terms, QOF will be running as a framework around pilot-link so the offline storage, (QSF XML), will be written by pilot-link. Pilot-link itself does not directly support users who cross timezones with their Palm - it may be wise that users are reminded that if timezone issues are important to their DateBook or Expenses data, the user should take the sensible precaution of changing the Palm Preferences to use the same timezone as the computer running pilot-link. Once in QOF, all dates and times are UTC so the QSF XML files can be freely exchanged across timezones without complication.

QOF has now been configured to build as a framework around pilot-link when compiled from source. Using the --enable-qof=<path> option to ./autogen.sh, the QOF code is built into libpilotqof.so and a pilot_qof executable will be installed. Full documentation for pilot_qof is included in this documentation as manpages for pilot_qof.

The ability to import QSF data directly into the Palm will follow at a later date.

4.3.1. Converting existing objects to QOF

Existing objects need to describe themselves to QOF and this can be achieved by using QOF as an external framework around the existing structs. This is how pilot-link is now able to use QOF. The ToDo database will be used as an example. Not all the steps will be shown here, see the source for full information.

  1. Create a new struct - this will be used to contain your existing structs as well as adding the necessary QofInstance instance that will provide the GUID that is fundamental to QOF. You can add other variables if you like.

    typedef struct {
    	QofInstance inst;
    	ToDo_t wrap;
    }qof_todo;
  2. Identify the parameters to use with QOF by comparing your struct variables with the basic QOF data types:

    • QOF_TYPE_STRING - suitable for char*, const char*, gchar*, GString->str and enumerators (see note).

      char* todo_getNote(qof_todo*);
      void todo_setNote(qof_todo*, const char*);

      These would be represented in the QofClass parameter list as:

      {TODO_NOTE,QOF_TYPE_STRING,(QofAccessFunc)todo_getNote,(QofSetterFunc)todo_setNote},
    • QOF_TYPE_GUID - suitable for the QOF GUID itself as well as use as references to other QOF objects.

    • QOF_TYPE_BOOLEAN - suitable for gboolean and other boolean values. Note that QSF (the XML file backend for QOF) uses a single value for each boolean value: false or true - lowercase. If you set or get a QOF_TYPE_BOOLEAN, for conversion to or from a string etc., FALSE, TRUE, 1, 0 etc. may cause errors. A QOF_TYPE_DEBCRED is also available for financial purposes - it is essentially a boolean with values for debit or credit.

    • QOF-TYPE_NUMERIC - suitable for all non-integer numeric values - numerics provide rational number handling with rounding error control. See the QOF documentation for more information. Routines exist to convert to and from double and string. QOF_TYPE_DOUBLE exists but has essentially been replaced by numeric.

    • QOF_TYPE_DATE - suitable for all date values. All QOF times are in UTC and are converted to use the full date and time (even if you only need the date or just the time). The example shows how to convert a QOF Timespec into a struct tm used by the underlying struct variable.

      Timespec todo_getDateDue(qof_todo *t)
      {
      	ToDo_t *qt;
      	Timespec ts;
      
      	ts.tv_sec = 0;
      	ts.tv_nsec = 0;
      	g_return_val_if_fail(t != NULL, ts);
      	qt = &t->wrap;
      	timespecFromTime_t(&ts, mktime(&qt->due));
      	return ts;
      }
      
      void todo_setDateDue(qof_todo *t, Timespec ts)
      {
      	ToDo_t *qt;
      	time_t s;
      
      	g_return_if_fail(t != NULL);
      	qt = &t->wrap;
      	s = timespecToTime_t(ts);
      	qt->due = *localtime(&s);
      }

      The QofClass parameter for these functions would be:

      {TODO_DATE,QOF_TYPE_DATE,(QofAccessFunc)todo_getDateDue,(QofSetterFunc)todo_setDateDue},
    • QOF_TYPE_INT32 and QOF_TYPE_INT64 - integer values.

      NoteGuidance on enumerators.
       

      Use strings instead of numerical enumerators for values that will appear in the QSF XML. This increases readability of the XML and guards against future changes in the enumerator itself. Configure enumerator parameters to be set and fetched as strings - clear and concise descriptions of the meaning of the enumerator value - and do the conversion inside the object.

      For example:

      <string type="expense_type">Airfare</string>

      is more easily understood than:

      <gint32 type="expense_type">0</gint32>.
    • QOF_TYPE_CHAR - suitable for single character values. e.g. GnuCash uses char for the flag that indicates whether a transaction has been fully reconciled.

    • QOF_TYPE_KVP - Key:Value pairs. Sets of associations between character strings (keys) and KvpValue structures. A KvpValue is a union with possible types enumerated in the KvpValueType enum, and includes, among other things, ints, doubles, strings, guid's, lists, time and numeric values. KvpValues may also be other frames, so KVP is inherently heirarchical.

  3. Now decide on how these parameters should be used. The simple decision is whether a parameter should be calculated or set. For example, an account object has a description string that would be set when creating a new account. However, the balance of that account cannot be set, it is calculated from the transactions held within the account. Values that should be set need a set function defined in QOF, these use QofSetterFunc prototypes, as well as a get function (QofAccessFunc). Calculated values should only be provided with a QofAccessFunc - a get function. Use the examples of the pilot-link structs where necessary.

  4. Create the functions that will provide the link between QOF and your existing functions. You'll need a get function for each parameter and a set function for relevant parameters. These functions can handle any necessary conversions, e.g. in pilot-link, dates are converted to UTC, doubles and floats converted to numerics.

    void exp_setAmount(qof_exp *e, gnc_numeric h) {
    	Expense_t *qe;
    	double exp_d;
    
    	g_return_if_fail(e != NULL);
    	qe = &e->wrap;
    	exp_d = gnc_numeric_to_double(h);
    	qe->amount = g_strdup_printf("%f", exp_d);
    }
    
    gnc_numeric exp_getAmount(qof_exp *e) {
    	Expense_t *qe;
    	gnc_numeric amount;
    	double pi_amount;
    
    	amount = gnc_numeric_zero();
    	g_return_val_if_fail(e != NULL, amount);
    	qe = &e->wrap;
    	/* floating point as a string converts to gnc_numeric */
    	pi_amount = strtod(qe->amount, NULL);
    	amount = double_to_gnc_numeric(pi_amount, 0, GNC_HOW_DENOM_EXACT);
    	if(gnc_numeric_check(amount) ==  GNC_ERROR_OK) {
    		return amount;
    	}
    	return gnc_numeric_zero();
    }
  5. Use the pilot-link examples to create a QofObject definition and a create function that properly initialises each parameter.

    WarningThe object type has strict syntax
     

    Although you can have spaces and punctuation in the type_label (which is intended to be descriptive), the e_type cannot include spaces OR hyphens. You can use underscores in the e_type. Remember also that the e_type may be visible as well as the description and is more often used to identify an object type - users may have to type the object type in certain applications, so make it usable!

    valid types include: pilotAddress, pilot_address, pilotaddress

    Some of the types that will fail: pilot address, pilot-address, pilot.address, pilot/address pilot=address, pilot(address)

    Similarly, a QofClass set of parameters that sets out the parameter names, the parameter type and each of the get and set functions. If no set function exists, set the final value to NULL. Each object definition should include two special cases:

    {QOF_PARAM_BOOK,QOF_ID_BOOK,(QofAccessFunc)qof_instance_get_book,NULL},
    {QOF_PARAM_GUID,QOF_TYPE_GUID,(QofAccessFunc)qof_instance_get_guid,NULL},

    These are part of how the object links into the framework and all objects need to include them. Note that you should not expect to set the book or GUID - this is done by QOF when merging books and/or loading from files.