File I/O
Locating Application Data
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
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
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
Design Philosophy
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
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Â
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(¤t_time); tm = localtime(¤t_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
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.
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Â
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Â
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Â
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
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_ (use the
with_columns()...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:
- Create a ThumbnailItem object by calling
alp_contacts_dml_item_create()oralp_contacts_dml_item_create_with_custom_fields(). - Set the image either by:
- Insert the ThumbnailItem object into the database with
alp_contacts_dml_insert(). - 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Â
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_ (use the
columns()...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
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_ (use the
with_columns()...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
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.
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
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Â
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,
¬e);
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
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
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
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
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
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.










