USERNAME PASSWORD LOST PASSWORD? REGISTER
"A Complete Mobile Application Development Environment"
Advertisement

Downloads
Documentation
Forums
Blog
Press
Contact Us




Data Storage PDF Print E-mail

File I/O ^TOP^

Locating Application Data ^TOP^

Applications often need to access extra data files: localized text and images to support their UI, for example. These files are stored in the package, inside the rsc subdirectory. Localized data is stored in further subdirectories underneath rsc. The Bundle Manager provides APIs to access that data.

Icons, images, and other read-only data that are particular to your application will be placed in your package's read-only directory. Do not hard-code a path to your data! The location of your package can change. Instead, you'll need to get the path to your data from the Bundle Manager. Here's an example of how to access a PNG used by your application:


resource_name = alp_package_ref_ro_pathname(
    alp_package_ref_me(), "rsc/myfile.png"); 
do_something(resource_name); 
g_free(resource_name); 

The example below illustrates how to access your read-write package data (in this case, a database):


resource_name = alp_package_ref_rw_pathname(
    alp_package_ref_me(), "yourapp.db"); 
do_something(resource_name); 
g_free(resource_name); 

Working Directory, and Home ^TOP^

The working directory for applications is set at startup to the root of their read-write package directory. The environment variables listed below are also set.

$PACKAGE_ROOT
Set to the root of your read-only package directory.
$PACKAGE_HOME
Set to the root of your read-write package directory. This is where the working directory is set at startup.
$HOME
Set to the shared directory (/home).

SQL Abstraction Layer ^TOP^

The SQL Abstraction Layer (SAL) provides a very simple API with many convenience functions, very few entry points, and no data structures. Code written using the SAL is typically four times shorter than equivalent code written directly on top of SQLite. The SQL Abstraction Layer also includes a set of APIs for the Category Manager to allow programmatic queries or manipulation of categories and category membership in a way that is consistent across the platform and that supports HotSync.

The SQL Abstraction Layer is typically used by applications that wish to share data with the system or with other applications using a data model. In such cases, the following layers are used:

  • The top layer is a set of applications or "views" that use the shared data. This top layer includes the UI for the application, as well as any third party or system software that wishes to access the data. To interact with its data, views must go through the next layer, the data model.
  • The data model provides a shared library and header file containing C data structures and APIs to read and write to the data. In addition to controlling data access, the data model is responsible for notifications (registering views that wish to be notified of certain data changes) and supporting HotSync (telling HotSync which records need syncing).
  • The SQL Abstraction Layer is a library provided by the system that all data models are supposed to use to actually read and write to their database. It's not required, but if used, it provides consistency for the platform as well as category management support.
  • SQLite is the lowest layer: the open-source SQL library.

This layering is illustrated in the following diagram:

PIM APIs ^TOP^

Design Philosophy ^TOP^


NOTE: The data format that ACCESS Linux Platform uses for PIM applications is not exposed to third-party developers. The data access API is the sole means for an application to access the PIM data.

The PIM APIs in ACCESS Linux Platform have been designed so that:

  • The data store details and structure are abstracted.

    The API supports the concept of a single, logical table. This provides the flexibility to move to a different database engine, or to modify the structure of the database, without breaking third-party applications which may be relying on access to the PIM data.

  • They serve as the single access mechanism to the PIM data.
  • All types of PIM data are accessible.
  • Simultaneous safe access to PIM data from multiple processes is provided. Where possible, record-level locking is supported.
  • Referential data is supported.

    Support for referential data must be intrinsic to the data and synchronization models in order to efficiently, and correctly, support synchronization of this data in loop topologies. Also, referential data can significantly reduce the required storage, and improve sync efficiency by effectively providing field-level synchronization for those specific fields.

  • Change notification is supported.

    Change notification is essential to support a Model-View-Controller architecture.

  • Efficient data access is enabled.

    SQL queries allow ACCESS Linux Platform to take advantage of the indexing provided by the underlying database. Contrast this approach to having to step through every record in the database to find events within a particular date range, for example.

  • Implementation of the SyncML APIs is enabled.
  • A handle-based storage model is not assumed.
  • A flexible category/group mechanism is supported.

Query Model

The PIM ACCESS APIs are a thin layer on top of the SQL Abstraction Layer, which itself is a thin layer on top of SQLite. These layers contain just enough infrastructure so that the database implementation could be switched or the database structure could be modified at a later point if it made sense to do so. The query interface closely matches a "raw" SQL query.

Locking

Concurrent writes to the same record is not supported. One of the processes will block until the write action completes.

Calendar ^TOP^

The Calendar data model APIs provide functions to:

  • Create, delete, and modify calendar entries.
  • Iterate through the calendar entries.
  • Group calendar operations into one or more transactions.
  • Change or remove the category memberships for a given calendar entry.

The Calendar data model APIs are declared in <alp/calendar_dml.h>. Note that this header file is not included by alp.h; you'll need to explicitly include it in any source files that reference the Calendar DML APIs. As well, your makefile will need to include the alp_calendar_dml library among the list of libraries with which your application links.

The key API is the AlpCalendarDmlColumnType enum, which declares the data fields (or columns) you can get (using the alp_calendar_dml_statement_get_... functions) and set (using the alp_calendar_dml_item_set_... functions) for a given calendar event. These columns, along with their data types, are listed in Table 8.1.

Table 8.1  Calendar event columns 

Column

Data type

Description

kCalRowID

int32_t

Row id of the record (a luid)

kCalRepeatingEvent

Not used. Instead, set kCalRepeatType to 0 (none).

kCalUntimedEvent

bool

Indicates whether or not the event is an untimed one

kCalStartDateTime

time_t

Time the appointment starts

kCalEndDateTime

time_t

Time the appointment ends

kCalTimeZone

char *

Timezone info

kCalTimeAdvance

int32_t

The number of minutes, hours or days before the event when the alarm should be triggered. Set this value to -1 if the event has no associated alarm. Use kCalTimeAdvanceUnit to specify whether this value represents minutes, hours, or days.

kCalTimeAdvanceUnit

enum

Units to advance the alarm: minutes = 0, hours = 1, days = 2

kCalRepeatType

enum

Type of repeat: none = 0, daily = 1, weekly = 2, monthly by day = 3, monthly by date = 4, yearly = 5

kCalRepeatEndDate

time_t

The date upon which the repeating event ends, or -1 if it repeats forever

kCalRepeatFrequency

uint32_t

The frequency of repeat. For instance, if repeating is set to "daily" and the frequency is set to 2, the event occurs every other day.

kCalRepeatOn

uint32_t

Only used by weekly and monthly-by-day repeating events. For weekly, this is treated as a bit field that contains the days of the week upon which the events occur (bit: 0-sun, 1-mon, 2-tue, etc.). For monthly-by-day it contains the day the appointments occur. This is an enum value that is sequenced as follows: first Sunday of the month, first Monday of the month, ..., first Saturday of the month, second Sunday of the month, ..., fourth Saturday of the month, last Sunday of the month, ..., last Saturday of the month.

kCalRepeatStartOfWeek

uint32_t

repeat weekly only

kCalExceptionDates

time_t[]

An array of exceptions to a repeating event

kCalDescription

char *

Description of the event

kCalLocation

char *

Location of the meeting

kCalNote

char *

A note attached to the event

kCalEventType

uint8_t

The type of event. Normal = 0, Birthday = 1, Anniversary = 2

kCalContactID

uint32_t

The row ID of the contact (from the Contacts DML) if this is a birthday or anniversary event

kCalAttendeeList

char *

List of attendees, separated by semicolons

kCalSubject

char *

Subject of the meeting

kCalIsPrivate

uint8_t

Indicates whether or not the event record should be considered private.

kCalSmartJoinInfo

char *

Smart text string that can be used for storing conference call info or chat session Info. For conference calls, this contains a phone number and a passcode.

To get and set numeric or boolean column values (such as int32_t, uint8_t, bool, etc.), use alp_calendar_dml_statement_get_int32() and alp_calendar_dml_item_set_int32(). To get and set strings (char *), use alp_calendar_dml_statement_get_string() and alp_calendar_dml_item_set_string(). Finally, get and set arrays (as is stored in the kCalExceptionDates column) using the alp_calendar_dml_statement_get_blob() and alp_calendar_dml_item_set_blob() functions.

Inserting an Event

The code in Listing 8.1 consists of two functions, one that fills a newly-created Calendar event, and another that creates an event and inserts it into the Calendar database. The created event is a simple one: a one-hour appointment at 10:00 on the current day, titled "Sample event", with an alarm set to go off 10 minutes prior to the event.

Listing 8.1  Inserting a Calendar event


#include <alp/alp.h>    // ALP declarations 
#include <alp/calendar_dml.h> 
 
void FillEvent(AlpDmlItemH eventH) { 
	time_t current_time; 
	struct tm *tm; 
	alp_status_t status; 
	time_t time_value; 
	int32_t advance_value; 
	int32_t advance_unit; 
	char *description; 
	 
	 
	// construct a start time (10:00am today) 
	time(&current_time); 
	tm = localtime(&current_time); 
	tm->tm_hour = 10; 
	tm->tm_min = 0; 
	tm->tm_sec = 0; 
	time_value = mktime(tm); 
	 
	// set the event start and end times 
	status = alp_calendar_dml_item_set_int32(eventH, kCalStartDateTime, 
		(uint32_t *)&time_value); 
	 
	tm->tm_hour += 1;   // end time is one hour after start time 
	time_value = mktime(tm); 
	status = alp_calendar_dml_item_set_int32(eventH, kCalEndDateTime, 
		(uint32_t *)&time_value); 
	    
	// set an alarm to trigger 10 minutes before the event 
	advance_value = 10; 
	status = alp_calendar_dml_item_set_int32(eventH, kCalTimeAdvance,
		&advance_value); 
	advance_unit = 0;   // minutes 
	status = alp_calendar_dml_item_set_int32(eventH, kCalTimeAdvanceUnit,
		&advance_unit); 
	    
	// set event description 
	description = (char *)malloc(255); 
	strcpy(description, "Sample event"); 
	status = alp_calendar_dml_item_set_string(eventH, kCalDescription,
		description); 
} 
 
void InsertNewEvent(){ 
	AlpDmlItemH eventH; 
	AlpDmlH sal; 
	AlpLuid eventID; 
	alp_status_t status; 
	            
	status = alp_calendar_dml_item_create(&eventH); 
	if(status == ALP_STATUS_OK){ 
		FillEvent(eventH); 
		 
		// Open Data Model 
		status = alp_calendar_dml_open(&sal, true); 
		if(status == ALP_STATUS_OK){ 
			// Insert the event 
			status = alp_calendar_dml_insert(sal, eventH, &eventID); 
 
			status = alp_calendar_dml_close(sal); 
		} 
 
		alp_calendar_dml_item_destroy(eventH); 
	} 
} 

It is important to note that alp_calendar_dml_item_create() creates an in-memory object that represents the calendar item. When you insert it into the Calendar database, the alp_calendar_dml_insert() function doesn't actually insert the object but instead creates an entry in the Calendar database that contains copies of the data in your in-memory object. Thus, after you are done with the object you created, be sure to destroy it with alp_calendar_dml_item_destroy(). Otherwise, the memory consumed by the object will be leaked.

Locating an Event

If you already have the locally-unique ID (luid) of the event (perhaps because you created it), you can use alp_calendar_dml_get_item() function to get the corresponding AlpDmlStatementH, with which you can then use the various alp_calendar_dml_statement_get_... functions to examine the various aspects of the event. Or, you can use the alp_calendar_dml_delete() function to delete it.


NOTE: Do not confuse alp_calendar_dml_delete() with alp_calendar_dml_item_destroy(). alp_calendar_dml_delete() deletes an event from the Calendar database. alp_calendar_dml_item_destroy(), on the other hand, simply frees up memory allocated by alp_calendar_dml_item_create() for an in-memory representation of the event.

If you do not have the locally-unique ID, you can either iterate through all of the events within a given date-and-time range, or you can search for events that contain the specified text in either the description or location columns. Listing 8.2 shows a simple function that iterates through all of the current day's events. Note that this example simply uses printf() to display the event descriptions in the Simulator console; in a real ACCESS Linux Platform application you'd do something more meaningful with the event data, of course.

Listing 8.2  Iterating through Calendar events


void IterateEvents() { 
	alp_status_t status; 
	time_t day; 
	struct tm *tm; 
	time_t startRange, endRange; 
	AlpDmlH dml; 
	AlpDmlStatementH stmtH; 
	int i; 
	int32_t numResults; 
	      
	// construct the range - in this case, the entire day (today) 
	time(&day);	   // today 
	tm = (struct tm *)localtime(&day); 
	tm->tm_hour = 0; 
	tm->tm_sec = 0; 
	tm->tm_min = 0; 
	startRange = mktime(tm); 
	tm->tm_hour = 23; 
	tm->tm_sec = 59; 
	tm->tm_min = 59; 
	endRange = mktime(tm); 
	    
	// Open Calendar data model 
	status = alp_calendar_dml_open(&dml, true); 
	if(status == ALP_STATUS_OK) { 
		    
		status = alp_calendar_dml_statement_begin(dml, 0, &stmtH, 0, false,
			startRange, endRange, "StartDateTime", &numResults); 
		if(status == ALP_STATUS_OK){ 
			 
			for(i = 0; i < numResults; i++) { 
				status = alp_calendar_dml_statement_next(dml, &stmtH); 
				if(status == ALP_STATUS_CALENDAR_DML_NO_RECORDS) { 
					break; 
				} else { 
					// got an event 
					char *description; 
					 
					status = alp_calendar_dml_statement_get_string (stmtH,
						kCalDescription, false, &description); 
					printf("%s\n", description); 
					fflush(NULL); 
				} 
			} 
			 
			alp_calendar_dml_statement_end(dml, stmtH); 
		} 
		 
		alp_calendar_dml_close(dml); 
	} 
} 

If you simply want to locate events based upon description or location, the process is slightly simpler, since the alp_calendar_dml_search_statement_begin() does this for you. This function performs a case-insensitive search for all events whose location or description columns contain the specified text. Listing 8.3 is very similar to Listing 8.2 except that rather than constructing a range of dates and times, you need only pass on the string to be matched. The resulting set of events, which you iterate through in exactly the same way as you do when using alp_calendar_dml_statement_begin(), are those that have the specified string somewhere within their description and/or location columns.

Listing 8.3  Searching for Calendar events


void SearchEvents(char *searchString) { 
	alp_status_t status; 
	AlpDmlH dml; 
	AlpDmlStatementH stmtH; 
	int i; 
	int32_t numResults; 
	      
	// Open Calendar data model 
	status = alp_calendar_dml_open(&dml, true); 
	if(status == ALP_STATUS_OK) { 
		    
		status = alp_calendar_dml_search_statement_begin(dml, &stmtH,
			searchString, &numResults); 
		if(status == ALP_STATUS_OK){ 
			 
			for(i = 0; i < numResults; i++) { 
				status = alp_calendar_dml_statement_next(dml, &stmtH); 
				if(status == ALP_STATUS_CALENDAR_DML_NO_RECORDS) { 
					break; 
				} else { 
					// got an event 
					char *description; 
					 
					status = alp_calendar_dml_statement_get_string (stmtH,
						kCalDescription, false, &description); 
					printf("%s\n", description); 
					fflush(NULL); 
				} 
			} 
			 
			alp_calendar_dml_statement_end(dml, stmtH); 
		} 
		 
		alp_calendar_dml_close(dml); 
	} 
} 

Contacts ^TOP^

The Contacts data model is exposed using a header file that can be included by any application that wishes to interface with the Contacts database (<alp/contacts_dml.h>). This header file exposes C data structures and a set of C functions that operate on those data structures to modify and query the Contacts database. When writing applications that use the Contacts data model APIs, be sure to link against libalp_contacts_dml.so (typically, you add alp_contacts_dml to the list of libraries with which you are linking).

In addition to normal data access, the Contacts data model supports special queries to handle SearchLib and CallerID functionality.

Contacts Database Structure

The Contacts database is structured into a number of separate "items"; the AlpContactsDmlItemType enum is used to specify which item you are working with. Which column types you can read and write depend upon the item type, as indicated in the following sections.


NOTE: ListViewItem and ListViewThumbnailItem are not currently used.

The PersonItem table represents a person; there is one entry in this table for each person in the Contacts list. The ContactItem, AddressItem, ExtraFieldItem, and ThumbnailItem tables contain additional, optional information about a person. Records in each of these tables must be associated with an existing PersonItem table record; you cannot enter an address, for instance, that is not linked to a row in the PersonItem table.

The tables in the Contacts database are described in the following sections.

PersonItem

Each record represents a person (or company). In the Contacts application, each contact corresponds to a record in this table (and probably one or more records in other linked tables). Table 8.2 lists the "standard" columns in the PersonItem table; note that because licensees can add additional columns to this table, the list of column identifiers shown here may not be complete on certain devices.

Table 8.2  PersonItem table columns 

Column

Data type

Description

Prefix

char *

The person's title, or name prefix. "Mr." or "Sir" are sample prefix values

Suffix

char *

A name suffix, such as "Esq."

FirstName

char *

First name

LastName

char *

Last name

MiddleName

char *

Middle name

Title

char *

Typically, a job title, such as "President"

Company

char *

Company name

NickName

char *

Nickname

YomiFirstName

char *

YomiLastName

char *

YomiCompany

char *

Ringtone

char *

Ringtone that should be played when the phone number relayed via caller ID matches one of the phone numbers associated with this record

Note

char *

An attached note

Birthday

uint32_t

The person's birthday, as a packed int (year:23 month:4 day:5). Use the ALP_CONTACTS_DML_GET_... and ALP_CONTACTS_DML_SET_... macros to unpack and pack these date values.

BirthdayAlarm

uint32_t

The number of days before the birthday when an alarm should be triggered. A value of -1 signifies "no alarm".

Anniversary

uint32_t

The person's anniversary, as a packed int (year:23 month:4 day:5). Use the ALP_CONTACTS_DML_GET_... and ALP_CONTACTS_DML_SET_... macros to unpack and pack these date values.

AnniversaryAlarm

uint32_t

The number of days before the anniversary when an alarm should be triggered. A value of -1 signifies "no alarm".

EnglishName

char *

UseCount

int32_t;

DontBeamNote

bool

Indicates whether the note contents should be "beamed" along with the record.

IsBusinessCard

bool

Indicates whether this record acts as the user's "business card." Only one record in the PersonItem database can be so designated.

SIMPhoneBook

int32_t

SIMEntryID

int32_t

When inserting a PersonItem record into the Contacts database, the value of the variable indicated by the itemLuid parameter (of the alp_contacts_dml_insert() call) is ignored. Upon return, however, it is set to the luid of the inserted PersonItem record. You will need to supply this value when inserting related ContactItem, AddressItem, ExtraFieldItem, and ThumbnailItem records, however. See Listing 8.5 for an example of this technique.

To iterate through the contents of this table, you typically call either alp_contacts_dml_person_statement_begin() or alp_contacts_dml_person_statement_begin_with_columns() (use the ...with_columns version if you only need to retrieve a limited set of table columns). If you have the luid (locally unique ID) of a PersonItem record and want to retrieve only that record, supply it as the personLuid parameter. Use the categoryLuid and/or unfiled parameters to only fetch records based upon their category assignments (a value of zero for categoryLuid fetches records from all categories). Finally, use the contactTypes (and numContactTypes) parameter to restrict the results to those that have particular types of ContactItem records associated with them (only those with phone numbers, for instance). Listing 8.4 shows how to iterate through the PersonItem table in the Contacts database.

To fetch the set of PersonItem records in which the birthday and/or anniversary dates between a specified start day and month and end day and month, use alp_contacts_dml_event_statement_begin() or alp_contacts_dml_event_statement_begin_with_columns().

ContactItem

ContactItem records are used to represent the ways in which a person can be contacted electronically: they hold phone numbers, email addresses, and URLs. Each person can have zero or more associated ContactItem records.

In addition to the columns listed in Table 8.3, each row in the ContactItem table contains two luids: one to uniquely identify the ContactItem itself, and one to identify the PersonItem with which the ContactItem row is associated. (Use alp_contacts_dml_statement_get_luids() to get the luids for a given record, based upon its statement handle.) As well, licensees can add additional columns to this table, so the list of column identifiers shown here may not be complete on certain devices.

Table 8.3  ContactItem table columns 

Column

Data type

Description

UseCount

int32_t

Identifier

char *

A unique identifier for the contact. Note that the ContactType needn't be unique. The Contacts application uses identifiers such as "Contact1", "Contact2", "ContactIM2", "ContactURL".

VCardSrc

char *

Value

char *

The phone number, email address, URL, or IM screen name.

ContactType

char *

A string such as "Email", "Home", "Work", "Mobile", "URL", "Yahoo", "AIM", ICQ", "Fax", "Other" identifying the type of contact. These needn't be unique; you can have two "Email" addresses for a given person, for instance.

SpeedDial

uint32_t

ASCII value of the key that is assigned to quickly dial this phone number. For instance, a value of "50" (decimal) represents the '2' key.

DefaultContact

bool

Indicates whether this ContactItem is the default way to contact the associated person.

When inserting a ContactItem record into the table you must associate it with an existing PersonItem record. Do this by setting the variable indicated by the itemLuid parameter (of the alp_contacts_dml_insert() call) to the luid of the appropriate PersonItem record. Upon return, the alp_contacts_dml_insert() function will have set the variable indicated by the itemLuid parameter to the luid of the inserted ContactItem record. See Listing 8.5 for an example of this technique.

To iterate through the contents of this table, you typically call either alp_contacts_dml_contact_statement_begin() or alp_contacts_dml_contact_statement_begin_with_columns() (use the ...with_columns version if you only need to retrieve a limited set of table columns). Typically, you'll have the luid (locally unique ID) of a PersonItem record and want to retrieve only that person's contacts; supply it as the personLuid parameter. Use the contactTypes (and numContactTypes) parameter to restrict the results to contacts of a particular set of types.

AddressItem

AddressItem records are used to represent the various physical locations associated with a person: they hold work addresses, home addresses, and the like. Each person can have zero or more associated AddressItem records.

In addition to the columns listed in Table 8.4, each row in the AddressItem table contains two luids: one to uniquely identify the AddressItem itself, and one to identify the PersonItem with which the AddressItem row is associated. Use alp_contacts_dml_statement_get_luids() to get these luids for a given record, based upon its statement handle. As well, licensees can add additional columns to this table, so the list of column identifiers shown here may not be complete on certain devices.

Table 8.4  AddressItem table fields 

Column

Data type

Description

Identifier

char *

A unique identifier for the address, such as "Work", "Home", or "Other"

Address

char *

The address, up to but not including the city

City

char *

The city

State

char *

The state

PostalCode

char *

The postal code (in the US, the "Zip" code)

Country

char *

The country

VCardSrc

char *

When inserting an AddressItem record into the table you must associate it with an existing PersonItem record. Do this by setting the variable indicated by the itemLuid parameter (of the alp_contacts_dml_insert() call) to the luid of the appropriate PersonItem record. Upon return, the alp_contacts_dml_insert() function will have set the variable indicated by the itemLuid parameter to the luid of the inserted AddressItem record. See Listing 8.5 for an example of this technique.

To iterate through the contents of this table, you typically call either alp_contacts_dml_address_statement_begin() or alp_contacts_dml_address_statement_begin_with_columns() (use the ...with_columns version if you only need to retrieve a limited set of table columns). Typically, you'll have the luid (locally unique ID) of a PersonItem record and want to retrieve only that person's addresses; supply it as the personLuid parameter.

ExtraFieldItem

This table is used to hold the contents of the custom fields that in the Contacts application can be used to associate extra data with each PersonItem record. Initially, these fields are labeled "Custom1", "Custom2", etc. in the Contacts application (the user can rename them from the Options menu; the names of these fields are stored in the CustomLabelItem table; see "CustomLabelItem"). This is a simple table that does little more than associate a string value with a string identifier.

In addition to the columns listed in Table 8.5, each row in the ExtraFieldItem table contains two luids: one to uniquely identify the ExtraFieldItem itself, and one to identify the PersonItem with which the ExtraFieldItem row is associated. Use alp_contacts_dml_statement_get_luids() to get these luids for a given record, based upon its statement handle. As well, licensees can add additional columns to this table, so the list of column identifiers shown here may not be complete on certain devices.

Table 8.5  ExtraField table columns

Column

Data type

Description

Identifier

char *

A unique identifier for the field. The Contacts application compares this string against the Identifier field value from the CustomLabelItem table; if they match, the corresponding Label field value from that table is displayed to the user.

VCardSrc

char *

Value

char *

The value of the extra field

When inserting an ExtraFieldItem record into the table you must associate it with an existing PersonItem record. Do this by setting the variable indicated by the itemLuid parameter (of the alp_contacts_dml_insert() call) to the luid of the appropriate PersonItem record. Upon return, the alp_contacts_dml_insert() function will have set the variable indicated by the itemLuid parameter to the luid of the inserted ExtraFieldItem record. See Listing 8.5 for an example of this technique.

To iterate through the contents of this table, you typically call either alp_contacts_dml_extrafields_statement_begin() or alp_contacts_dml_extrafields_statement_begin_
with_columns()
(use the ...with_columns version if you only need to retrieve a limited set of table columns). Typically, you'll have the luid (locally unique ID) of a PersonItem record and want to retrieve only that person's extra fields; supply it as the personLuid parameter.

ThumbnailItem

The ThumbnailItem table holds the images associated with each PersonItem record.

In addition to the columns listed in Table 8.6, each row in the ThumbnailItem table contains two luids: one to uniquely identify the ThumbnailItem itself, and one to identify the PersonItem with which the ThumbnailItem row is associated. Use alp_contacts_dml_statement_get_luids() to get these luids for a given record, based upon its statement handle. As well, licensees can add additional columns to this table, so the list of column identifiers shown here may not be complete on certain devices.

Unlike the other Contacts database tables, the fields in this database cannot all be read and written. To set an image for a PersonItem:

  1. Create a ThumbnailItem object by calling alp_contacts_dml_item_create() or alp_contacts_dml_item_create_with_custom_fields().
  2. Set the image either by:
    • Specifying the name of the file that contains the image as the value of the ThumnailFileName field.
    • Specifying the actual bits that make up the image as the value of the ThumnailData field.
  3. Insert the ThumbnailItem object into the database with alp_contacts_dml_insert().
  4. Dispose of the ThumbnailItem object created in step 1 by calling alp_contacts_dml_delete().

You can retrieve the image in two different ways:

  • As a GdkPixbuf, scaled to the size of a "small thumbnail" as specified by the device manufacturer. Using alp_contacts_dml_statement_get_blob(), retrieve the value of the SmallThumbnail field.
  • As a JPG, scaled to the size of a "large thumbnail" as specified by the device manufacturer. Using alp_contacts_dml_statement_get_blob(), retrieve the value of the LargeThumbnail field.

Table 8.6  ThumbnailItem table columns 

Column

Data type

Description

SmallThumbnail

guint8 *

A small version of the image. This field is read-only; it cannot be set. The value will be a GdkPixbuf scaled to a size defined in the global settings by a licensee.

LargeThumbnail

void *

A large version of the image. This field is read-only; it cannot be set. The value will be a JPG image scaled to a size defined in the global settings by a licensee.

ThumbnailFileName

char *

Used to set the image when it is stored in a file. This property is write-only; it cannot be read. Either this field or ThumbnailData must be set in the ThumbnailItem, but if either or both are set more than once, the most recent property setting is stored in the ThumbnailItem.

ThumbnailData

Blob

Used to set the image when you have the bits in memory. This property is write-only; it cannot be read. Either this field or ThumbnailFileName must be set in the ThumbnailItem, but if either or both are set more than once, the most recent property setting is stored in the ThumbnailItem.

When inserting a ThumbnailItem record into the table you must associate it with an existing PersonItem record. Do this by setting the variable indicated by the itemLuid parameter (of the alp_contacts_dml_insert() call) to the luid of the appropriate PersonItem record. Upon return, the alp_contacts_dml_insert() function will have set the variable indicated by the itemLuid parameter to the luid of the inserted ThumbnailItem record. See Listing 8.5 for an example of this technique.

To iterate through the contents of this table, you typically call either alp_contacts_dml_thumbnail_statement_begin() or alp_contacts_dml_thumbnail_statement_begin_with_
columns()
(use the ...with_columns version if you only need to retrieve a limited set of table columns). Typically, you'll have the luid (locally unique ID) of a PersonItem record and want to retrieve only that person's thumbnail; supply it as the personLuid parameter.

CustomLabelItem

Unlike most of the other tables in the Contacts database, the rows in this table are not associated with a single PersonItem record. Instead, this table simply contains the labels that the end-user sees for the nine custom fields. While the values for the custom fields can vary from one person to another (these values are stored in the ExtraFieldItem table; see "ExtraFieldItem"), the labels for those fields cannot; if the user changes the label of the first custom field to "Spouse," for instance, that field will be so labeled for each entry in the Contacts application.

Each row in the CustomLabelItem table has an associated luid; use alp_contacts_dml_statement_get_luids() to get the luid for a given record, based upon its statement handle. As well, licensees can add additional columns to this table, so the list of column identifiers shown in Table 8.7 may not be complete on certain devices.

Table 8.7  CustomLabelItem table columns

Column

Data type

Description

Identifier

char *

A unique identifier for the custom label. This is an internal value that is not displayed to the user. The Contacts application compares this string against the Identifier field value from the ExtraFieldItem table; if they match, the corresponding Label field value is displayed to the user.

Label

char *

The field's label; this is displayed in the Contacts application.

To iterate through the contents of this table, you typically call either alp_contacts_dml_custom_label_statement_begin() or alp_contacts_dml_custom_label_statement_begin_
with_columns()
(use the ...with_columns version if you only need to retrieve a limited set of table columns).

Locating a Person

Because the data for a given person is typically spread across multiple tables, locating the data associated with a person is a slightly more complex operation than when, say, looking up an event in the Calendar.

Once the data model has been opened, iterating through the PersonItem table is simply a matter of calling alp_contacts_dml_person_statement_begin(), specifying the parameters as needed to filter and order the results, and then calling alp_contacts_dml_statement_next() to retrieve each person record.

Once you have a person record, you'll need the record's luid in order to look up related data in the ContactItem, AddressItem, ExtraFieldItem, and ThumbnailItem tables; all records in these other tables that are associated with a given PersonItem record contain the PersonItem record's luid in one of their columns. You iterate through these other tables in much the same way as you iterate through the person records, but when doing if you supply the luid of the PersonItem record as the personLuid parameter to the alp_contacts_dml_..._statement_begin() function, the only records retrieved will be those that are associated with the PersonItem record.

The code in Listing 8.4 illustrates this process. This function sends to the Simulator console the "friendly name" of each record in the PersonItem table, plus each address associated with that record. Note the call to alp_contacts_dml_statement_get_luids(); since the alp_contacts_dml_..._statement_begin() functions do not return a record luid, you need to call this function whenever you need the luids associated with a record.

Also note that if a particular string or blob field is empty, the alp_contacts_dml_statement_get_string() or alp_contacts_dml_statement_get_blob() function will return a null pointer.

Listing 8.4  Iterating through person and address records


void IteratePeople() { 
	alp_status_t status; 
	time_t day; 
	struct tm *tm; 
	time_t startRange, endRange; 
	AlpDmlH dmlH; 
	AlpDmlStatementH personStmtH; 
	AlpLuid personLuid; 
	int i, j; 
	int32_t numPersons; 
	char *name; 
	      
	// Open Contacts data model 
	status = alp_contacts_dml_open(&dmlH, true); 
	if(status == ALP_STATUS_OK) { 
		    
		status = alp_contacts_dml_person_statement_begin(dmlH, 0, 0, false, NULL,
			0, OrderByLastFirstCompany, &personStmtH, &numPersons); 
		if(status == ALP_STATUS_OK){ 
			for(i = 0; i < numPersons; i++) { 
				status = alp_contacts_dml_statement_next(personStmtH); 
				if(status == ALP_STATUS_CONTACTS_DML_NO_RECORDS) { 
					break; 
				} else { 
					// got a person 
					char *description; 
					AlpDmlStatementH addressStmtH; 
					int32_t numAddresses; 
 
					// print out their name in "friendly" format using the handy 
					// "get name" function 
					status = alp_contacts_dml_person_get_name(personStmtH,
						FriendlyName, &name); 
					printf("%s\n", name); 
					 
					// now iterate through the addresses associated with this person 
					status = alp_contacts_dml_statement_get_luids(personStmtH,
						&personLuid, NULL); 
 
					status = alp_contacts_dml_address_statement_begin(dmlH, personLuid,
						0, &addressStmtH, &numAddresses); 
					if(status == ALP_STATUS_OK){ 
						for(j = 0; j < numAddresses; j++) { 
							status = alp_contacts_dml_statement_next(addressStmtH); 
							if(status == ALP_STATUS_CONTACTS_DML_NO_RECORDS) { 
								break; 
							} else { 
								// got an address 
								char *address, *city, *state; 
								 
								status = alp_contacts_dml_statement_get_string(
									addressStmtH, Address, false, &address); 
								status = alp_contacts_dml_statement_get_string(
									addressStmtH, City, false, &city); 
								status = alp_contacts_dml_statement_get_string(
									addressStmtH, State, false, &state); 
 
								printf("   %s, %s, %s\n", address ? address : "", 
									city ? city : "", state ? state : ""); 
							} 
						} 
						alp_contacts_dml_statement_end(addressStmtH); 
					} 
				} 
			} 
			 
			alp_contacts_dml_statement_end(personStmtH); 
		} 
		 
		alp_contacts_dml_close(dmlH); 
	} 
} 

Adding a Person

As you can see from the code in Listing 8.5, creating and inserting a person into the Contacts database is a slightly more complex operation than inserting an event into the Calendar database. This is due to the fact that the information about a person is spread across multiple tables; all but the most simple entries in the Contacts database require multiple insert operations.

When the data is spread across multiple tables as it is in this example, you'll likely want to enclose the set of related insert operations within a transaction. Then, if any one insert operation fails, you can rollback the transaction. This guarantees that either all data related to the person is inserted, or none of it.

The code in Listing 8.5 is very straightforward: it creates both a person object and an address object, and fills both with sample data. Then, a transaction is started and both objects are inserted. Note that prior to inserting the address, the addressID parameter is set to the value of the luid returned when the PersonItem record was inserted. This is a required step: it associates the AddressItem record with the PersonItem record. You cannot insert an AddressItem (or ContactItem, ExtraFieldItem, or ThumbnailItem) record that is not associated with an existing PersonItem record.

Listing 8.5  Creating and inserting a person into the Contacts database


void FillPerson(AlpDmlItemH personH) { 
	alp_status_t status; 
	int32_t birthday; 
	 
	status = alp_contacts_dml_item_set_string(personH, Prefix, "Mr."); 
	status = alp_contacts_dml_item_set_string(personH, FirstName, "Fred"); 
	status = alp_contacts_dml_item_set_string(personH, LastName, "Flintstone"); 
	// set birthday to September 30, 1960 
	ALP_CONTACTS_DML_SET_MONTH(birthday, 9-1);	// months are zero-based 
	ALP_CONTACTS_DML_SET_DAY(birthday, 30); 
	ALP_CONTACTS_DML_SET_YEAR(birthday, 1960); 
	status = alp_contacts_dml_item_set_int32(personH, Birthday, &birthday); 
} 
 
void FillAddress(AlpDmlItemH addressH) { 
	alp_status_t status; 
 
	status = alp_contacts_dml_item_set_string(addressH, Identifier, "Home"); 
	status = alp_contacts_dml_item_set_string(addressH, Address, 
		"301 Cobblestone Way"); 
	status = alp_contacts_dml_item_set_string(addressH, City, "Bedrock"); 
} 
 
void InsertNewPerson(){ 
	AlpDmlItemH personH, addressH; 
	AlpDmlH sal; 
	AlpLuid personID, addressID; 
	alp_status_t status; 
	            
	status = alp_contacts_dml_item_create(PersonItem, &personH); 
	if(status == ALP_STATUS_OK){ 
		status = alp_contacts_dml_item_create(AddressItem, &addressH); 
		if(status == ALP_STATUS_OK){ 
			FillPerson(personH); 
			FillAddress(addressH); 
			 
			// Open Data Model 
			status = alp_contacts_dml_open(&sal, true); 
			if(status == ALP_STATUS_OK){ 
				// Insert the person and their address 
				alp_contacts_dml_transaction_begin(sal, true); 
				status = alp_contacts_dml_insert(sal, personH, &personID); 
				if(status == ALP_STATUS_OK){ 
					addressID = personID; // needed to associate address with person 
					status = alp_contacts_dml_insert(sal, addressH, &addressID); 
				} 
				if(status == ALP_STATUS_OK){	// did both inserts go OK? 
					alp_contacts_dml_transaction_end(sal); 
				} else { 
					alp_contacts_dml_transaction_rollback(sal); 
				} 
	 
				status = alp_contacts_dml_close(sal); 
			} 
			 
			alp_contacts_dml_item_destroy(addressH); 
			alp_contacts_dml_item_destroy(personH); 
		} 
	} 
} 

Deleting a Person

Deleting a person and all of their associated data from the Contacts database is simply a matter of deleting the PersonItem with alp_contacts_dml_delete(). When instructed to delete a record from the PersonItem table, this function also deletes all of the records from the ContactItem, AddressItem, ExtraFieldItem, and ThumbnailItem tables that contain the luid of the PersonItem record being deleted. It does all of this from within a transaction, to ensure that either all or none of the related data is deleted.

To delete an individual ContactItem, AddressItem, ExtraFieldItem, ThumbnailItem, or CustomLabelItem record, simply pass the record's luid (not the luid of the associated PersonItem record) along with the item type to alp_contacts_dml_delete().

Memo ^TOP^

The Memo data model API consists of opaque C data structures and a set of C functions that operate on those data structures to read and write the Memo database. The API is published as a public header file, in <alp/memo_dml.h>, and implemented as a library that needs to be linked with any code that uses these APIs: libalp_memo_dml.so (include alp_memo_dml in your makefile's list of libraries). Any application that wants access to the Memo database needs to use these APIs.

Like many of the other PIM applications, exported functions allow applications to:

  • Create, modify, and delete new records (memos, in this case).
  • Iterate through all of the memos stored in the database.
  • Group database operations into one or more transactions.
  • Change or remove the category memberships for a given memo.

The Memo Database

The Memo database is an extremely simple one; it consists of a single table in which each memo is stored in a single record, along with a locally unique ID (luid) plus creation and last-modification time stamps. Table 8.8 lists the table columns and their data types.

Table 8.8  Memo table columns 

Column

Data type

Description

kMemoLUID

AlpLuid (uint32_t)

The locally unique ID for the record. Although a constant is provided for this column, you cannot actually read or write this value using the alp_memo_dml_statement_get_... or alp_memo_dml_item_set_... functions. When you insert a record its luid is returned. You can obtain the luid for a record when iterating through the database by calling alp_memo_dml_statement_get_luid().

kMemoText

char *

The text of the memo

kMemoCreationDate

uint32_t

The date and time the memo was created, in time_t format

kMemoModificationDate

uint32_t

The date and time the memo was last modified, in time_t format

As you would expect with a schema so simple, the process of adding and retrieving memos is a simple one. The code in Listing 8.6 illustrates two simple functions for doing just this. The first function, InsertNewMemo(), takes the C string passed into it and turns it into a new memo. The second function prints the text of all memos in the database, irrespective of their categories, to the Simulator console in alphabetical order. This function uses alp_memo_dml_listview_statement_begin() to obtain the memos in sorted order; if you do not care about the order, use alp_memo_dml_statement_begin() instead. To locate memos containing a particular text string, see alp_memo_dml_search_statement_begin().

Listing 8.6  Reading and writing Memos


void InsertNewMemo(const char *memoText){ 
	AlpDmlItemH memoH; 
	AlpDmlH sal; 
	AlpLuid memoID; 
	alp_status_t status; 
	time_t creationDate, modificationDate; 
	 
	status = alp_memo_dml_item_create(&memoH); 
	if(status == ALP_STATUS_OK){ 
		// construct the memo 
		status = alp_memo_dml_item_set_string(memoH, kMemoText, memoText); 
		creationDate = time(NULL); 
		status = alp_memo_dml_item_set_uint32(memoH, kMemoCreationDate, 
			(uint32_t *)&creationDate); 
		modificationDate = modificationDate; 
		status = alp_memo_dml_item_set_uint32(memoH, kMemoModificationDate,
			(uint32_t *)&modificationDate); 
		 
		// Open Data Model 
		status = alp_memo_dml_open(&sal, true); 
		if(status == ALP_STATUS_OK){ 
			// Insert the memo 
			status = alp_memo_dml_insert(sal, memoH, &memoID); 
 
			status = alp_memo_dml_close(sal); 
		} 
		 
		alp_memo_dml_item_destroy(memoH); 
	} 
} 
 
void IterateMemos() { 
	alp_status_t status; 
	AlpDmlH dml; 
	AlpDmlStatementH stmtH; 
	int i; 
	int32_t numResults; 
	      
	// Open Memo data model 
	status = alp_memo_dml_open(&dml, true); 
	if(status == ALP_STATUS_OK) { 
		    
		status = alp_memo_dml_listview_statement_begin(dml, 0, false, &stmtH,
			kMemoDmlSelectAllOrderByAlpha, 0, 0, &numResults); 
		if(status == ALP_STATUS_OK){ 
			 
			for(i = 0; i < numResults; i++) { 
				status = alp_memo_dml_statement_next(stmtH); 
				if(status == ALP_STATUS_MEMO_DML_NO_RECORD) { 
					break; 
				} else { 
					// got a memo 
					char *text; 
					 
					status = alp_memo_dml_statement_get_string (stmtH, kMemoText,
						&text); 
					printf("%s\n", text); 
					fflush(NULL); 
				} 
			} 
			 
			alp_memo_dml_statement_end(stmtH); 
		} 
		 
		alp_memo_dml_close(dml); 
	} 
} 

Tasks ^TOP^

The Tasks data model API consists of opaque C data structures and a set of C functions that operate on those data structures to read and write the Tasks database. The API is published as a public header file, in <alp/tasks_dml.h>, and implemented as a library that needs to be linked with any code that uses these APIs: libalp_tasks_dml.so (include alp_tasks_dml in your makefile's list of libraries). Any application that wants access to the Tasks database needs to use these APIs.

The Tasks data model APIs provide functions to:

  • Create, delete, and modify tasks.
  • Iterate through the tasks stored in the database.
  • Group database operations into one or more transactions.
  • Change or remove the category memberships for a given task.

The Tasks Database

The Tasks database is a relatively simple one; it consists of a single table in which each task is stored in a single record. Table 8.9 lists the table columns and their data types.

Table 8.9  Tasks table columns 

Column

Data type

Description

ColumnPriority

uint32_t

Task priority

ColumnDueDate

time_t

Date the task is due

ColumnCompleted

time_t

Date the task was completed

ColumnPrivate

bool

Indicates whether the task is to be regarded as private

ColumnDescription

char *

The task

ColumnNotePrivate

bool

Indicates whether a note associated with the task is to be regarded as private

ColumnNoteText

char *

A note associated with the task

ColumnRemindAlarmID

uint32_t

(not used by the Tasks application) A unique identifier for the alarm

ColumnRemindDaysAhead

uint32_t

The number of days prior to the task due date when the user should be reminded of the task

ColumnRemindTime

time_t

The time the reminder should be triggered. Note that only the time portion of the time_t value is relevant; the date is ignored.

ColumnRemindFilename

char *

(not used by the Tasks application) Path to the alarm sound that should be played when the reminder alarm is triggered

ColumnRecurType

uint32_t

(not used by the Tasks application)

ColumnRecurEndDate

time_t

(not used by the Tasks application)

ColumnRecurFrequency

uint32_t

(not used by the Tasks application)

ColumnRecurOn

uint32_t

(not used by the Tasks application)

ColumnRecurStartOfWeek

uint32_t

(not used by the Tasks application)


IMPORTANT: When retrieving values from the Tasks database using the alp_tasks_dml_statement_get_... functions, be sure to check the return value from the function call. Unless the return value is ALP_STATUS_OK, the retrieved value is not valid.

As long as you are careful to check the function return codes, the process of retrieving task information is extremely straightforward. For instance, Listing 8.7 shows a function that walks through the tasks database and prints to the Simulator console some of the more interesting fields for each task—but only if those fields are set. Note the use of alp_tasks_dml_listview_statement_begin(), which allows you to filter the retrieved tasks and sort them either by priority or due date.

Listing 8.7  Iterating through the tasks database


void IterateTasks() { 
	alp_status_t status; 
	AlpDmlH dml; 
	AlpDmlStatementH stmtH; 
	int i; 
	int32_t numResults; 
	      
	// Open data model 
	status = alp_tasks_dml_open(&dml, true); 
	if(status == ALP_STATUS_OK) { 
		    
		status = alp_tasks_dml_listview_statement_begin(dml, &stmtH, NULL, 0,
			SelectAll, OrderByPriority, 0, 0, &numResults); 
		if(status == ALP_STATUS_OK){ 
			 
			for(i = 0; i < numResults; i++) { 
				status = alp_tasks_dml_statement_next(stmtH); 
				if(status == ALP_STATUS_MEMO_DML_NO_RECORD) { 
					break; 
				} else { 
					// got a task 
					char *text, *note; 
					time_t due; 
					char dueStr[DATE_LEN+1]; 
					size_t dateLen; 
					uint32_t remindDaysAhead; 
					time_t remindTime; 
					char remindTimeStr[DATE_LEN+1]; 
					 
					// Get the task description 
					status = alp_tasks_dml_statement_get_string (stmtH,
						ColumnDescription, &text); 
					if(status != ALP_STATUS_OK){ 
						text[0] = '\0'; 
					} 
					printf("%s\n", text); 
					 
					// Find out when (or if) it is due 
					status = alp_tasks_dml_statement_get_time(stmtH, ColumnDueDate,
						&due); 
					if(status == ALP_STATUS_OK){ 
						dateLen = strftime(dueStr, DATE_LEN, "%x", localtime(&due)); 
						printf("    Due: %s\n", dueStr); 
					} 
					 
					// Get any associated note 
					status = alp_tasks_dml_statement_get_string (stmtH, ColumnNoteText,
						&note); 
					if(status == ALP_STATUS_OK){ 
						printf("    Note: %s\n", note); 
					} 
					 
					// Get alarm info 
					status = alp_tasks_dml_statement_get_uint32(stmtH,
						ColumnRemindDaysAhead, &remindDaysAhead); 
					if(status == ALP_STATUS_OK){ 
						status = alp_tasks_dml_statement_get_time(stmtH,
							ColumnRemindTime, &remindTime); 
						if(status == ALP_STATUS_OK){ 
							dateLen = strftime(remindTimeStr, DATE_LEN, "%X",
								localtime(&remindTime)); 
						} else { 
							strcpy(remindTimeStr, "(no time set)"); 
						} 
 
						printf("    Alarm: %u days before, at %s\n", remindDaysAhead,
							remindTimeStr); 
					} 
					 
					fflush(NULL); 
				} 
			} 
			 
			alp_tasks_dml_statement_end(stmtH); 
		} 
		 
		alp_tasks_dml_close(dml); 
	} 
} 

Note that a number of fields in the tasks database are not used by the Tasks application (as of this writing).

Launcher ^TOP^

The Launcher APIs provide functions to:

  • Delete or uninstall an application.
  • Get, add, or remove category membership information.
  • Enumerate the installed applications and obtain information about them.

Interacting with PIM Applications ^TOP^

Most often you will want to use the PIM APIs to interact with the PIM application data. For instance, you can use the Calendar DML APIs to add events to the user's calendar, or to search their calendar for a specific event. However, there are times when you want to interact with the PIM application itself. For instance, if you want to allow the user to create a new appointment, you may want to launch the Calendar application with the "new appointment dialog" already open. The following sections guide you through some of the more common tasks that involve interacting with the PIM applications themselves.

Adding a Calendar Appointment ^TOP^

The Calendar application responds to the exchange verb "store" with MIME type "empty_vcal". Code along the lines of the following causes the Calendar application to launch with the "new event" dialog open, enabling the user to create a new appointment.


AlpExgRequest request; 
 
alp_status_t status = alp_exg_request_create( &request ); 
status = alp_exg_request_set_verb( request, "store" ); 
status = alp_exg_request_set_data_type( request, 
	"empty_vcal" ); 
status = alp_exg_request_set_generic_mode (request); 
status = alp_exg_request_set_string_parameter ( request,
	ALP_EXGMGR_GENERIC_DATA_PARAMETER_NAME, "Your Ad Here"); 
status = alp_exg_request_execute( request ); 

Viewing a Specified Date ^TOP^

When passed the ALP_CALENDAR_OPEN_TO_DATE ("--alp-calendar-date-launch") launch code, the Calendar application displays the specified date in day view. In the argv parameter immediately following the launch code, supply the desired date as a string representation of a time_t value.

Viewing a Specified Appointment ^TOP^

Launching the Calendar so that it presents a specific appointment is a simple matter: just supply the ALP_CALENDAR_OPEN_TO_EVENT ("--alp-calendar-event-launch") launch code in the argv array, and set the following argv element to the luid (locally-unique ID) of the appointment that is to be presented.

 

Add as favourites (212) | Quote this article on your site | Views: 4751

Be first to comment this article
RSS comments

Only registered users can write comments.
Please login or register.

Powered by AkoComment Tweaked Special Edition v.1.4.6
AkoComment © Copyright 2004 by Arthur Konze - www.mamboportal.com
All right reserved

 


© 2009 ACCESS Developer Network    |    Joomla! is Free Software released under the GNU/GPL License.    |    ACCESS Global Website
Events Support Community Platforms Home