28th September 2002
This document was last updated 28 September 2002, for J-Pilot-0.99.3
A plugin is code that can extend the functionality of j-pilot without adding
any code to j-pilot, or recompiling it. It is basically just a shared
library that contains pre-defined callback functions.
A callback function is a function that is not called from the application
itself, but from an external program. When J-Pilot starts up it will
scan the plugin directories for any shared libraries (~/.jpilot/plugins/
and $BASE_DIR/lib/jpilot/plugins). When it finds a shared library
it will find callback functions inside of the library and call them when
needed. So, a plugin can be an integral part of the overall program
just by its existence and when taken away the main program will still run
only missing the functionality that the plugin provided.
Plugins are relatively easy to write for J-Pilot. All you need to do is implement the plugin callback functions as needed and write the application specific code. I have provided an example plugin for the Expense application. I used this application because it is included in the palm pilot ROM and everyone should have it. Since then I have been proved wrong. The new m100s don't have Expense and I think some old Pilots don't have it. It took me about 10 hours to write it with a lot of code reuse from other parts of j-pilot.
I have created a library of useful functions for writing J-Pilot conduits. The code is inside j-pilot and the header file is libplugin.h. The naming convention is that all of these functions start with "jp_". There are some I threw in there and didn't add a jp_ prefix because I would have had to change too much existing code.
If you do create a plugin I would appreciate it if you would give me a link to the site so that I can put it on my website. This will encourage more people to use J-Pilot and your plugin. Even if you are working on a plugin you can let me know and I will put it down as in progress so that someone doesn't duplicate your effort. Its GNU licensed code so you are free to not tell me of course, as long as you follow the GNU license. My email is Judd Montgomery <judd@engineer.com>. The official J-Pilot website is at http://jpilot.org.
I have written Expense and SyncTime as example plugins. Expense is a GUI (Graphical User Interface) application and SyncTime has no GUI. It shouldn't be too hard to use these as a base point for writing your own.
These functions are functions that you may implement in your plugin application. Most of them are not required. All you have to do is write them and they will be called at the appropriate times. The naming convention is that all of these functions start with "plugin_".
int plugin_search(char *search_string, int case_sense, struct search_result **sr);
struct search_result
{
char *line;
unsigned int unique_id;
struct search_result *next;
};
char *search_string - input parameter, a string that
the user is searching on.
int case_sense - input parameter, will be TRUE if a
case sensitive match is required.
struct search_result **sr - output parameter, a null
terminated linked list of search matches to be returned to jpilot.
struct search_result
{
char *line - This is the search result text as you
want it to appear in the search result window.
unsigned int unique_id - This is the unique_id of the
record that the search match appeared in. This number will be passed
back to plugin_gui if the record is selected in the search window.
struct search_result *next - A pointer to the next
node in the linked list.
};
This function will be called from the search window of the main program. The output parameter search_result will be displayed in the search window.
int plugin_get_name(char *name, int len);
char *name - output parameter, a pointer to a pre-allocated
buffer in j-pilots address space. The plugin should copy its name
into this string.
int len - input parameter, the length of this buffer.
Don't overwrite this buffer. Its currently 50 characters.
This function is used to get the name of the plugin, e.g. "Expense 1.0". This function must be implemented or the plugin won't be loaded..
int plugin_get_menu_name(char *name, int len);
char *name - output parameter, a pointer to a pre-allocated
buffer in j-pilots address space. The plugin should copy its name
into this string.
int len - input parameter, the length of this buffer.
Don't overwrite this buffer. Its currently 50 characters.
This function is used to get the name of the plugin, e.g. "Expense". This is the name that will appear in the j-pilot menu under plugins. It is possible to have a plugin that isn't accessible under the menu, in that case this function should not be implemented.
int plugin_get_help_name(char *name, int len);
char *name - output parameter, a pointer to a pre-allocated
buffer in j-pilots address space. The plugin should copy its name
into this string.
int len - input parameter, the length of this
buffer.
Don't overwrite this buffer. Its currently 50 characters.
This function is used to get the name of the plugin as it wishes to appear in the help menu pulldown, e.g. "About Expense". This function is not mandatory.
int plugin_help(char **text, int *width, int *height);
char **text - output parameter, the plugin should point
this string pointer to a string to be displayed in the help box.
int *width - output parameter, the width of the help
window.
int *height - output parameter, the height of the help
window.
int plugin_get_db_name(char *name, int len);
char *name - output parameter, a pointer to a pre-allocated
buffer in j-pilots address space.
int len - input parameter, the length of this buffer.
Please don't overwrite this buffer. Its currently 50 characters.
This function is used to get the name of the palm database (pdb file) that is to be synced. This DB will be automatically synced when j-pilot performs a sync.
For example the expense plugin uses the DB file "ExpenseDB.pdb", it would copy "ExpenseDB" to name, leaving off the extension. A normal plugin that just adds, deletes, and changes DB records using the plugin API will not have to do any special work during a sync process, it will be automatic.
int plugin_startup(jp_startup_info *info);
typedef struct
{
char *base_dir;
} jp_startup_info;
This plugin function is called when j-pilot starts up. Any initialization
needed should be done here.
The base_dir is the directory where j-pilot was compiled to be installed
(default is "/usr/local"). More fields may be added to this structure
later as needed without changing the API.
int plugin_gui(GtkWidget *vbox, GtkWidget *hbox, unsigned int unique_id);
GtkWidget *vbox - input parameter, the box underneath
the sync and quit buttons. This is where the main applications put
the delete button.
GtkWidget *hbox - input parameter, the box that makes
up the main part of the screen.
unsigned int unique_id - input parameter, a record
id that the application should go to. It is used by the search window.
This will be a non-null when this function is called from the search screen,
null otherwise.
This plugin function is called when the plugin is selected from the j-pilot menu, or from the search window. This is where the plugin can draw on the screen and provide the GUI interface, etc. If unique_id is non-null then the application should go directly to that record. This is how the search window forces the applications to go to the search result records.
char **text - output parameter, the text to be displayed
on the about dialog window.
int *width - output parameter, the width of the dialog
window.
int *height - output parameter, the height of the dialog
window.
This plugin function is called when the plugin is selected from the
j-pilot help menu pulldown. 2 things can be done here.
1. allocate memory for a text string to pass back to jpilot and it
will be displayed in a dialog window with width and height as the width
and height of the window.
2. set *text=NULL to prevent jpilot from putting up a dialog
window and then implement the help portion yourself.
3. I guess you could do both 1 and 2.
This plugin function is called when another application has been called and the boxes for the plugin GUI screen are about to be destroyed. Most widgets will be destroyed when their parents are destroyed, so this function normally is not needed.
int plugin_pre_sync_pre_connect(void);
This function is called after the j-pilot sync button is pressed and before the sync connection with the pilot is established.
int plugin_pre_sync(void);
This function is called after the j-pilot sync button is pressed, after the sync connection with the pilot is established, but before any syncing is done by jpilot.
int plugin_sync(int sd);
int sd - input parameter, the handle to the pilot sync
connection.
This function is called after the sync button is pressed and during
the sync process.
Unless something special is needed to be done with the sync handle
this function does not need to be used.
The sync process in j-pilot is a forked process, and this function will be called from the forked process. Therefore the previous state of global data in the plugin will not be available when this function is called. The order of operations during a sync is as follows:
int plugin_post_sync(void);
This function is called after the sync process is completed. Screen redraws may be a good thing to do here, as categories and records may have changed during the sync.
int plugin_exit_cleanup(void);
This function is called when j-pilot is shutting down.
int plugin_print(?,?);
There will be a print API here, but printing has not been implemented yet.
These functions are provided to make it easier to develop plugins for J-Pilot. If you need something that isn't here and it is something that J-Pilot already does, or something that you think others might need in the future, it should probably be added into this library.
int jp_init(void);
This function must be called in the plugin. Preferable in plugin_startup, but it should be in the first function to be called.
int jp_logf(int level, char *format, ...);
int level - is the logging level. It may be user configurable in the future, but right now I don't see a need for it.
You can turn on and off the debug level by using the -d command line
option though.
char *format,... - is the same type of things that
you would pass to printf.
example:
plugin_logf(JP_LOG_WARN, "error number %d: %s\n", n, err);
There are 3 places for logging output to go to. Standard output (stdout, usually the terminal window), the log file in ~/.jpilot/, and the ouput window (the one that pops up while you are syncing).
Logging levels are bitmasks, so they may be combined with a logical OR.
You can run J-Pilot in debug mode by executing "jpilot -d" to force
debug output.
level | stdout | log file | GUI | Intended use |
JP_LOG_DEBUG |
only |
only |
Goes to stdout These messages will not normally show up unless jpilot is in debug mode. These message should be things like "x=%d", or "inside while loop", etc. | |
JP_LOG_INFO |
|
|
|
Informational messages that aren't serious. e.g. "Put the palm in the cradle now", "Syncing xxx plugin", etc. |
JP_LOG_WARN |
|
|
|
Warning messages, that are not fatal, but may affect execution. e.g. "Out of Memory", "Could not open file". |
JP_LOG_FATAL |
|
|
|
Fatal messages in which execution can not continue. e.g. "could not open display". "Out of Memory" may also belong here. |
I didn't intend for the following levels to be used unless they are needed. They provide more precise control over the logging output, but also override any future configurability. | ||||
JP_LOG_STDOUT |
|
Using this will force a message to be displayed on stdout.* | ||
JP_LOG_FILE |
|
Using this will force a message to be written to the log file.* | ||
JP_LOG_GUI |
|
Using this will force a message to be displayed in the output window. |
plugin foo started.
scanning yahoo.com
scanning bizwax.com
parsing data
loading indexes
printing info log messages...
plugin foo done data collecting.
And then if the user doesn't want to see this he can set the log level higher to JP_LOG_WARN and only see LOG_WARN, or higher, or he can set it to JP_LOG_FATAL to only see memory errors and other fatal messages.
I only have the log level hard coded right now and with the "-d" flag DEBUG is turned on, but it was coded to be flexible.
By using JP_LOG_STDOUT, JP_LOG_FILE, and JP_LOG_GUI you override this flexibility.
int jp_install_append_line(char *line);
char *line - input parameter, this is just a line to write to the install file.
The install file is a file that is read line by line during the sync
process. Each line is the full path to a file name to be installed.
Once the file is installed on the pilot successfully then that line is
removed from the file.
example: "/home/base/AddressDB.pdb" will install this AddressDB to
the pilot.
int jp_install_remove_line(int deleted_line);
int deleted_line - input parameter, This is the line number to be deleted from the install file. The first line is line zero.
const char *jp_strstr(const char *haystack, const char *needle, int case_sense);
This function is analogous to the C function strstr except that it has a case sensitive parameter.
int jp_get_app_info(char *DB_name, void **buf, int buf_size);
char *DB_name - input parameter, the name of the DB
file to be read. For example to read the Expense data you would pass
"ExpenseDB", leaving off the .pdb extension.
void **buf - output parameter, a pointer to a pointer
to a memory area to be allocated for the application info to be copied
into. Memory is allocated and must be freed by the calling
function.
int buf_size - output parameter, the size of the memory
block that is allocated during this call.
This function retrieves the application info from a PDB file. It is packed and must be unpacked into a format that is specific to each palm application.
int jp_open_home_file(char *filename, char *mode);
char *filename - input parameter, the name of the file to be opened.
char *mode - input parameter, mode to open file in. See man page for fopen.
This function opens a file in $JPILOT_HOME/.jpilot/ Its analogous to fopen except that it looks in the jpilot directory for the file.
To write plugins that utililize J-Pilot's built in database and sync abilities it is necessary to understand how the databases are used. J-Pilot treats the pdb files as read-only, except at sync time. It will not modify the pdb file in anyway. Actually it never does, it lets the palm pilot do that work. I may change this in the future to achieve faster syncs, but I will try to keep this API the same. J-Pilot keeps a "pc" file of all the changes the user has made to the database. These are "reconciled" at sync time, and the PC file is cleared out. So, after a successful sync all of the data should be in the pdb file and the pc file should be 0 in size. The pc files have the extension ".pc". So there will be an AddressDB.pdb file and an AddressDB.pc file in ~/.jpilot/. The pdb file belongs to the pilot and the pc file belongs to j-pilot.
Records are just chunks of data. They must be packed and unpacked into some meaningful form by the desktop application. To do this it is neccessary to know the record format of the palm application. The palm processor is Motorola 68000 based and the palm stores integers native to its own processor, so some translations need to be made on integer data. There are examples of this in the pilot-link library code.
To modify a record you must write the new record to the pc file and then delete the original record. Actually if its a record in the pdb file it won't actually get deleted. A "deleted record" with the same unique_id will have to be written to the pc file. You don't really have to worry about the details of doing this since there is a library to do it for you.
So, when you read a database using the libplugin calls you will get records back with different record types depending on where they came from and what they are scheduled to do at sync time.
This is a short explanation of each record type:
PALM_REC: This record was read from the palm pdb file. It is unchanged by j-pilot.
MODIFIED_PALM_REC: This record has been modified by j-pilot. What this means is that this record has been deleted and a new record has been created to reflect the changes. This is the original record that still exists in the pdb file and it will be deleted at sync time. This record is kept so that at sync time it can be compared with the palm pilots record to see if it was modified on the pilot and in j-pilot. This record shouldn't be shown to the user.
DELETED_PALM_REC: This record has been scheduled to be deleted from the pdb file at sync time. This is the record retrieved from the pdb file and there is another record in the pc file with a matching unique_id to mark this record as deleted. This record should not be shown to the user.
NEW_PC_REC: This is a new record that has been added by j-pilot. It is in the pc file and will be written to the palm during the next sync and then retrieved back from the palm into the pdb file.
DELETED_PC_REC: This record was created in the pc file and then later deleted. It shouldn't be shown to the user.
DELETED_DELETED_PALM_REC: This record should only exist during a sync process. During the sync when a DELETED_PALM_REC gets deleted from the pilot it will be marked with this record type and then as soon as the sync is finished the pc file will get cleaned up and it should be removed. If a crash occurs it could be possible for this record to be in the pc file.
REPLACEMENT_PALM_REC: When a user modifies a record if its a pc side record then the pc record will be deleted and a new pc record written. If the original record was a palm record then a MODIFIED_PALM_REC is written out and then a REPLACEMENT_PALM_REC for the new record. Older versions of J-Pilot would just delete a palm rec and write a new one. This would give the record a new unique ID and break some programs which tracked the unique IDs of palm records.
The SPENT_PC_RECORD_BIT means that this record will be removed
during the next pc file cleanup. If this bit is set then the record
can be totally ignored.
typedef enum {
PALM_REC = 100L,
MODIFIED_PALM_REC = 101L,
DELETED_PALM_REC = 102L,
NEW_PC_REC = 103L,
DELETED_PC_REC = SPENT_PC_RECORD_BIT + 104L,
DELETED_DELETED_PALM_REC = SPENT_PC_RECORD_BIT +
105L,
REPLACEMENT_PALM_REC = 106L
} PCRecType;
int jp_read_DB_files(char *DB_name, GList **records);
char *DB_name is a pointer to the name of the DB file
to be read. For example to read the Expense data you would pass "ExpenseDB",
leaving off the .pdb extension. Both the pdb and the
pc files are read.
GList **records is a pointer to a pointer to a list
of records to be read from the database files. Memory is allocated
and should be freed by calling jp_free_DB_records().
This function will read the pdb file and the pc file out of the $(HOME)/.jpilot/ directory and put all the records into a list. The list contains structures of the following:
typedef struct
{
PCRecType rt;
unsigned int unique_id;
unsigned char attrib;
void *buf;
int size;
} jp_buf_rec;
PCRecType rt - This has been explained in the previous
paragraph.
unsigned int unique_id - This is the unique_id of a
record. The palm assigns these when they are created. The PC
records will have their own set of unique ids.
unsigned char attrib - This is the attributes of the
record. Look at the pilot-link code to understand these.
void *buf - This is the raw record as read from the
DB.
int size - This is the size of the raw record.
int jp_free_DB_records(GList **records);
GList **records is a pointer to pointer to a list of
records to be freed.
This function will free the records list and set the pointer to NULL
on completion.
This call should be used to free the record list allocated by jp_read_DB_files().
int jp_delete_record(char *DB_name, buf_rec *br, int flag);
char *DB_name is the DB name to be witten to.
For example to write to the Expense application database you would pass
"ExpenseDB". Do not pass the file extension (".pdb").
buf_rec *br is a pointer to a record to be deleted.
int flag can be either DELETE_FLAG
or MODIFY_FLAG. DELETE_FLAG should be used if the
record is being deleted. MODIFY_FLAG should be used if the record
is being modified.
char *DB_name is the DB name to be witten to.
For example to write to the Expense application database you would pass
"ExpenseDB". Do not pass the file extension (".pdb").
buf_rec *br is a pointer to a record to be written
to the DB file.
typedef struct
{
PCRecType rt;
unsigned int unique_id;
unsigned char attrib;
void *buf;
int size;
} jp_buf_rec;
This function is used for writing a record to the pc database file.
The record type should almost always be NEW_PC_REC. Unique_id can
be left blank since it is an outgoing parameter. A new unique id
will be assigned and placed in the structure. Attrib is the record
attributes, buf is a raw database record, and size is the size of the record.
int unlink_file(char *filename);
char *filename is the filename to remove.
This function is like unlink except that it looks for the file in the jpilot directory.
int rename_file(char *old_filename, char *new_filename);
char *old_filename is the filename to rename.
char *new_filename is the filename to rename to.
This function is like rename except that it looks for the files in the jpilot directory.
int get_app_info_size(FILE *in, int *size);
FILE *in input parameter, an open file pointer to a palm pdb file.
int *size output parameter, is the size of the application info block.
int read_header(FILE *pc_in, PC3RecordHeader *header);
FILE *pc_in input parameter, an open file pointer to a J-Pilot pc3 file pdb file.
PC3RecordHeader *header output parameter, the header from the file read in.
int write_header(FILE *pc_out, PC3RecordHeader *header);
FILE *pc_out input parameter, an open file pointer to a J-Pilot pc3 file pdb file.
PC3RecordHeader *header input parameter, the header to be written to the file.