2006-12-19 [colin] 2.6.1cvs54
[claws.git] / src / jpilot.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 2001-2006 Match Grun and the Claws Mail team
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18  */
19
20 /*
21  * Functions necessary to access JPilot database files.
22  * JPilot is Copyright(c) by Judd Montgomery.
23  * Visit http://www.jpilot.org for more details.
24  */
25
26 #ifdef HAVE_CONFIG_H
27 #  include "config.h"
28 #endif
29
30 #ifdef USE_JPILOT
31
32 #include <glib.h>
33 #include <time.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <sys/stat.h>
38 #include <netinet/in.h>
39
40 #ifdef HAVE_LIBPISOCK_PI_ARGS_H
41 #  include <libpisock/pi-util.h>
42 #  include <libpisock/pi-args.h>
43 #  include <libpisock/pi-appinfo.h>
44 #  include <libpisock/pi-address.h>
45 #  include <libpisock/pi-version.h>
46 #else
47 #  include <pi-util.h>
48 #  include <pi-args.h>
49 #  include <pi-appinfo.h>
50 #  include <pi-address.h>
51 #  include <pi-version.h>
52 #endif
53
54 #include "mgutils.h"
55 #include "addritem.h"
56 #include "addrcache.h"
57 #include "jpilot.h"
58 #include "codeconv.h"
59 #include "adbookbase.h"
60 #include "utils.h"
61
62 #define JPILOT_DBHOME_DIR   ".jpilot"
63 #define JPILOT_DBHOME_FILE  "AddressDB.pdb"
64 #define PILOT_LINK_LIB_NAME "libpisock.so"
65
66 #define IND_LABEL_LASTNAME  0   /* Index of last name in address data */
67 #define IND_LABEL_FIRSTNAME 1   /* Index of first name in address data */
68 #define IND_PHONE_EMAIL     4   /* Index of E-Mail address in phone labels */
69 #define OFFSET_PHONE_LABEL  3   /* Offset to phone data in address data */
70 #define IND_CUSTOM_LABEL    14  /* Offset to custom label names */
71 #define NUM_CUSTOM_LABEL    4   /* Number of custom labels */
72
73 /* Shamelessly copied from JPilot (libplugin.h) */
74 typedef struct {
75         unsigned char db_name[32];
76         unsigned char flags[2];
77         unsigned char version[2];
78         unsigned char creation_time[4];
79         unsigned char modification_time[4];
80         unsigned char backup_time[4];
81         unsigned char modification_number[4];
82         unsigned char app_info_offset[4];
83         unsigned char sort_info_offset[4];
84         unsigned char type[4];/*Database ID */
85         unsigned char creator_id[4];/*Application ID */
86         unsigned char unique_id_seed[4];
87         unsigned char next_record_list_id[4];
88         unsigned char number_of_records[2];
89 } RawDBHeader;
90
91 /* Shamelessly copied from JPilot (libplugin.h) */
92 typedef struct {
93         char db_name[32];
94         unsigned int flags;
95         unsigned int version;
96         time_t creation_time;
97         time_t modification_time;
98         time_t backup_time;
99         unsigned int modification_number;
100         unsigned int app_info_offset;
101         unsigned int sort_info_offset;
102         char type[5];/*Database ID */
103         char creator_id[5];/*Application ID */
104         char unique_id_seed[5];
105         unsigned int next_record_list_id;
106         unsigned int number_of_records;
107 } DBHeader;
108
109 /* Shamelessly copied from JPilot (libplugin.h) */
110 typedef struct {
111         unsigned char Offset[4];  /*4 bytes offset from BOF to record */
112         unsigned char attrib;
113         unsigned char unique_ID[3];
114 } record_header;
115
116 /* Shamelessly copied from JPilot (libplugin.h) */
117 typedef struct mem_rec_header_s {
118         unsigned int rec_num;
119         unsigned int offset;
120         unsigned int unique_id;
121         unsigned char attrib;
122         struct mem_rec_header_s *next;
123 } mem_rec_header;
124
125 /* Shamelessly copied from JPilot (libplugin.h) */
126 #define SPENT_PC_RECORD_BIT     256
127
128 typedef enum {
129         PALM_REC = 100L,
130         MODIFIED_PALM_REC = 101L,
131         DELETED_PALM_REC = 102L,
132         NEW_PC_REC = 103L,
133         DELETED_PC_REC = SPENT_PC_RECORD_BIT + 104L,
134         DELETED_DELETED_PALM_REC = SPENT_PC_RECORD_BIT + 105L
135 } PCRecType;
136
137 /* Shamelessly copied from JPilot (libplugin.h) */
138 typedef struct {
139         PCRecType rt;
140         unsigned int unique_id;
141         unsigned char attrib;
142         void *buf;
143         int size;
144 } buf_rec;
145
146 /* Shamelessly copied from JPilot (libplugin.h) */
147 typedef struct {
148         unsigned long header_len;
149         unsigned long header_version;
150         unsigned long rec_len;
151         unsigned long unique_id;
152         unsigned long rt; /* Record Type */
153         unsigned char attrib;
154 } PC3RecordHeader;
155
156 enum {
157         FAMILY_LAST = 0,
158         FAMILY_FIRST = 1
159 } name_order;
160
161 gboolean convert_charcode = TRUE;
162
163 /*
164 * Create new pilot file object.
165 * \return Initialized JPilot file object.
166 */
167 JPilotFile *jpilot_create() {
168         JPilotFile *pilotFile;
169         pilotFile = g_new0( JPilotFile, 1 );
170         pilotFile->type = ADBOOKTYPE_JPILOT;
171         pilotFile->addressCache = addrcache_create();
172         pilotFile->retVal = MGU_SUCCESS;
173
174         pilotFile->file = NULL;
175         pilotFile->path = NULL;
176         pilotFile->readMetadata = FALSE;
177         pilotFile->customLabels = NULL;
178         pilotFile->labelInd = NULL;
179         pilotFile->havePC3 = FALSE;
180         pilotFile->pc3ModifyTime = 0;
181         return pilotFile;
182 }
183
184 /**
185  * Create new pilot file object for specified file.
186  * \param path Path to JPilot address book.
187  * \return Initialized JPilot file object.
188  */
189 JPilotFile *jpilot_create_path( const gchar *path ) {
190         JPilotFile *pilotFile;
191         pilotFile = jpilot_create();
192         jpilot_set_file( pilotFile, path );
193         return pilotFile;
194 }
195
196 /*
197 * Properties...
198 */
199 void jpilot_set_name( JPilotFile* pilotFile, const gchar *value ) {
200         g_return_if_fail( pilotFile != NULL );
201         addrcache_set_name( pilotFile->addressCache, value );
202 }
203 void jpilot_set_file( JPilotFile* pilotFile, const gchar *value ) {
204         g_return_if_fail( pilotFile != NULL );
205         addrcache_refresh( pilotFile->addressCache );
206         pilotFile->readMetadata = FALSE;
207         pilotFile->path = mgu_replace_string( pilotFile->path, value );
208 }
209 void jpilot_set_accessed( JPilotFile *pilotFile, const gboolean value ) {
210         g_return_if_fail( pilotFile != NULL );
211         pilotFile->addressCache->accessFlag = value;
212 }
213
214 gint jpilot_get_status( JPilotFile *pilotFile ) {
215         g_return_val_if_fail( pilotFile != NULL, -1 );
216         return pilotFile->retVal;
217 }
218 ItemFolder *jpilot_get_root_folder( JPilotFile *pilotFile ) {
219         g_return_val_if_fail( pilotFile != NULL, NULL );
220         return addrcache_get_root_folder( pilotFile->addressCache );
221 }
222 gchar *jpilot_get_name( JPilotFile *pilotFile ) {
223         g_return_val_if_fail( pilotFile != NULL, NULL );
224         return addrcache_get_name( pilotFile->addressCache );
225 }
226
227 /*
228  * Test whether file was read.
229  * \param pilotFile  JPilot control data.
230  * \return <i>TRUE</i> if file was read.
231  */
232 gboolean jpilot_get_read_flag( JPilotFile *pilotFile ) {
233         g_return_val_if_fail( pilotFile != NULL, FALSE );
234         return pilotFile->addressCache->dataRead;
235 }
236
237 /**
238  * Free up custom label list.
239  * \param pilotFile  JPilot control data.
240  */
241 void jpilot_clear_custom_labels( JPilotFile *pilotFile ) {
242         GList *node;
243
244         g_return_if_fail( pilotFile != NULL );
245
246         /* Release custom labels */
247         mgu_free_dlist( pilotFile->customLabels );
248         pilotFile->customLabels = NULL;
249
250         /* Release indexes */
251         node = pilotFile->labelInd;
252         while( node ) {
253                 node->data = NULL;
254                 node = g_list_next( node );
255         }
256         g_list_free( pilotFile->labelInd );
257         pilotFile->labelInd = NULL;
258
259         /* Force a fresh read */
260         addrcache_refresh( pilotFile->addressCache );
261 }
262
263 /**
264  * Append a custom label, representing an E-Mail address field to the
265  * custom label list.
266  * \param pilotFile  JPilot control data.
267  */
268 void jpilot_add_custom_label( JPilotFile *pilotFile, const gchar *labelName ) {
269         g_return_if_fail( pilotFile != NULL );
270
271         if( labelName ) {
272                 gchar *labelCopy = g_strdup( labelName );
273                 g_strstrip( labelCopy );
274                 if( *labelCopy == '\0' ) {
275                         g_free( labelCopy );
276                 }
277                 else {
278                         pilotFile->customLabels = g_list_append( pilotFile->customLabels, labelCopy );
279                         /* Force a fresh read */
280                         addrcache_refresh( pilotFile->addressCache );
281                 }
282         }
283 }
284
285 /**
286  * Get list of custom labels.
287  * \param pilotFile  JPilot control data.
288  * \return List of labels. Must use g_free() when done.
289  */
290 GList *jpilot_get_custom_labels( JPilotFile *pilotFile ) {
291         GList *retVal = NULL;
292         GList *node;
293
294         g_return_val_if_fail( pilotFile != NULL, NULL );
295
296         node = pilotFile->customLabels;
297         while( node ) {
298                 retVal = g_list_append( retVal, g_strdup( node->data ) );
299                 node = g_list_next( node );
300         }
301         return retVal;
302 }
303
304 /**
305  * Return filespec of PC3 file corresponding to JPilot PDB file.
306  * \param pilotFile  JPilot control data.
307  * \return File specification; should be g_free() when done.
308  */
309 static gchar *jpilot_get_pc3_file( JPilotFile *pilotFile ) {
310         gchar *fileSpec, *r;
311         gint i, len, pos;
312
313         if( pilotFile == NULL ) return NULL;
314         if( pilotFile->path == NULL ) return NULL;
315
316         fileSpec = g_strdup( pilotFile->path );
317         len = strlen( fileSpec );
318         pos = -1;
319         r = NULL;
320         for( i = len; i > 0; i-- ) {
321                 if( *(fileSpec + i) == '.' ) {
322                         pos = i + 1;
323                         r = fileSpec + pos;
324                         break;
325                 }
326         }
327         if( r ) {
328                 if( len - pos == 3 ) {
329                         *r++ = 'p'; *r++ = 'c'; *r = '3';
330                         return fileSpec;
331                 }
332         }
333         g_free( fileSpec );
334         return NULL;
335 }
336
337 /**
338  * Save PC3 file time to cache.
339  * \param pilotFile  JPilot control data.
340  * \return <i>TRUE</i> if time marked.
341  */
342 static gboolean jpilot_mark_files( JPilotFile *pilotFile ) {
343         gboolean retVal = FALSE;
344         struct stat filestat;
345         gchar *pcFile;
346
347         /* Mark PDB file cache */
348         retVal = addrcache_mark_file( pilotFile->addressCache, pilotFile->path );
349
350         /* Now mark PC3 file */
351         pilotFile->havePC3 = FALSE;
352         pilotFile->pc3ModifyTime = 0;
353         pcFile = jpilot_get_pc3_file( pilotFile );
354         if( pcFile == NULL ) return retVal;
355         if( 0 == stat( pcFile, &filestat ) ) {
356                 pilotFile->havePC3 = TRUE;
357                 pilotFile->pc3ModifyTime = filestat.st_mtime;
358                 retVal = TRUE;
359         }
360         g_free( pcFile );
361         return retVal;
362 }
363
364 /**
365  * Check whether JPilot PDB or PC3 file has changed by comparing
366  * with cached data.
367  * \param pilotFile  JPilot control data.
368  * \return <i>TRUE</i> if file has changed.
369  */
370 static gboolean jpilot_check_files( JPilotFile *pilotFile ) {
371         gboolean retVal = TRUE;
372         struct stat filestat;
373         gchar *pcFile;
374
375         /* Check main file */
376         if( addrcache_check_file( pilotFile->addressCache, pilotFile->path ) )
377                 return TRUE;
378
379         /* Test PC3 file */
380         if( ! pilotFile->havePC3 ) return FALSE;
381         pcFile = jpilot_get_pc3_file( pilotFile );
382         if( pcFile == NULL ) return FALSE;
383
384         if( 0 == stat( pcFile, &filestat ) ) {
385                 if( filestat.st_mtime == pilotFile->pc3ModifyTime ) retVal = FALSE;
386         }
387         g_free( pcFile );
388         return retVal;
389 }
390
391 /*
392 * Test whether file was modified since last access.
393 * Return: TRUE if file was modified.
394 */
395 gboolean jpilot_get_modified( JPilotFile *pilotFile ) {
396         g_return_val_if_fail( pilotFile != NULL, FALSE );
397         pilotFile->addressCache->modified = jpilot_check_files( pilotFile );
398         return pilotFile->addressCache->modified;
399 }
400 void jpilot_set_modified( JPilotFile *pilotFile, const gboolean value ) {
401         g_return_if_fail( pilotFile != NULL );
402         pilotFile->addressCache->modified = value;
403 }
404 gboolean jpilot_get_accessed( JPilotFile *pilotFile ) {
405         g_return_val_if_fail( pilotFile != NULL, FALSE );
406         return pilotFile->addressCache->accessFlag;
407 }
408
409 /**
410  * Free up pilot file object by releasing internal memory.
411  * \param pilotFile  JPilot control data.
412  */
413 void jpilot_free( JPilotFile *pilotFile ) {
414         g_return_if_fail( pilotFile != NULL );
415
416         /* Release custom labels */
417         jpilot_clear_custom_labels( pilotFile );
418
419         /* Clear cache */
420         addrcache_clear( pilotFile->addressCache );
421         addrcache_free( pilotFile->addressCache );
422
423         /* Free internal stuff */
424         g_free( pilotFile->path );
425
426         pilotFile->file = NULL;
427         pilotFile->path = NULL;
428         pilotFile->readMetadata = FALSE;
429         pilotFile->havePC3 = FALSE;
430         pilotFile->pc3ModifyTime = 0;
431
432         pilotFile->type = ADBOOKTYPE_NONE;
433         pilotFile->addressCache = NULL;
434         pilotFile->retVal = MGU_SUCCESS;
435
436         /* Now release file object */
437         g_free( pilotFile );
438 }
439
440 /*
441 * Refresh internal variables to force a file read.
442 */
443 void jpilot_force_refresh( JPilotFile *pilotFile ) {
444         addrcache_refresh( pilotFile->addressCache );
445 }
446
447 /*
448 * Print object to specified stream.
449 */
450 void jpilot_print_file( JPilotFile *pilotFile, FILE *stream ) {
451         GList *node;
452
453         g_return_if_fail( pilotFile != NULL );
454
455         fprintf( stream, "JPilotFile:\n" );
456         fprintf( stream, "file spec: '%s'\n", pilotFile->path );
457         fprintf( stream, " metadata: %s\n", pilotFile->readMetadata ? "yes" : "no" );
458         fprintf( stream, "  ret val: %d\n", pilotFile->retVal );
459
460         node = pilotFile->customLabels;
461         while( node ) {
462                 fprintf( stream, "  c label: %s\n", (gchar *)node->data );
463                 node = g_list_next( node );
464         }
465
466         node = pilotFile->labelInd;
467         while( node ) {
468                 fprintf( stream, " labelind: %d\n", GPOINTER_TO_INT(node->data) );
469                 node = g_list_next( node );
470         }
471
472         addrcache_print( pilotFile->addressCache, stream );
473         fprintf( stream, "  ret val: %d\n", pilotFile->retVal );
474         fprintf( stream, " have pc3: %s\n", pilotFile->havePC3 ? "yes" : "no" );
475         fprintf( stream, " pc3 time: %lu\n", pilotFile->pc3ModifyTime );
476         addritem_print_item_folder( pilotFile->addressCache->rootFolder, stream );
477 }
478
479 /*
480 * Print summary of object to specified stream.
481 */
482 void jpilot_print_short( JPilotFile *pilotFile, FILE *stream ) {
483         GList *node;
484         g_return_if_fail( pilotFile != NULL );
485         fprintf( stream, "JPilotFile:\n" );
486         fprintf( stream, "file spec: '%s'\n", pilotFile->path );
487         fprintf( stream, " metadata: %s\n", pilotFile->readMetadata ? "yes" : "no" );
488         fprintf( stream, "  ret val: %d\n", pilotFile->retVal );
489
490         node = pilotFile->customLabels;
491         while( node ) {
492                 fprintf( stream, "  c label: %s\n", (gchar *)node->data );
493                 node = g_list_next( node );
494         }
495
496         node = pilotFile->labelInd;
497         while( node ) {
498                 fprintf( stream, " labelind: %d\n", GPOINTER_TO_INT(node->data) );
499                 node = g_list_next( node );
500         }
501         addrcache_print( pilotFile->addressCache, stream );
502         fprintf( stream, " have pc3: %s\n", pilotFile->havePC3 ? "yes" : "no" );
503         fprintf( stream, " pc3 time: %lu\n", pilotFile->pc3ModifyTime );
504 }
505
506 /* Shamelessly copied from JPilot (libplugin.c) */
507 static unsigned int bytes_to_bin(unsigned char *bytes, unsigned int num_bytes) {
508 unsigned int i, n;
509         n=0;
510         for (i=0;i<num_bytes;i++) {
511                 n = n*256+bytes[i];
512         }
513         return n;
514 }
515
516 /* Shamelessly copied from JPilot (utils.c) */
517 /* These next 2 functions were copied from pi-file.c in the pilot-link app */
518 /* Exact value of "Jan 1, 1970 0:00:00 GMT" - "Jan 1, 1904 0:00:00 GMT" */
519 #define PILOT_TIME_DELTA (unsigned)(2082844800)
520
521 time_t pilot_time_to_unix_time ( unsigned long raw_time ) {
522    return (time_t)(raw_time - PILOT_TIME_DELTA);
523 }
524
525 /* Shamelessly copied from JPilot (libplugin.c) */
526 static int raw_header_to_header(RawDBHeader *rdbh, DBHeader *dbh) {
527         unsigned long temp;
528
529         strncpy(dbh->db_name, rdbh->db_name, 31);
530         dbh->db_name[31] = '\0';
531         dbh->flags = bytes_to_bin(rdbh->flags, 2);
532         dbh->version = bytes_to_bin(rdbh->version, 2);
533         temp = bytes_to_bin(rdbh->creation_time, 4);
534         dbh->creation_time = pilot_time_to_unix_time(temp);
535         temp = bytes_to_bin(rdbh->modification_time, 4);
536         dbh->modification_time = pilot_time_to_unix_time(temp);
537         temp = bytes_to_bin(rdbh->backup_time, 4);
538         dbh->backup_time = pilot_time_to_unix_time(temp);
539         dbh->modification_number = bytes_to_bin(rdbh->modification_number, 4);
540         dbh->app_info_offset = bytes_to_bin(rdbh->app_info_offset, 4);
541         dbh->sort_info_offset = bytes_to_bin(rdbh->sort_info_offset, 4);
542         strncpy(dbh->type, rdbh->type, 4);
543         dbh->type[4] = '\0';
544         strncpy(dbh->creator_id, rdbh->creator_id, 4);
545         dbh->creator_id[4] = '\0';
546         strncpy(dbh->unique_id_seed, rdbh->unique_id_seed, 4);
547         dbh->unique_id_seed[4] = '\0';
548         dbh->next_record_list_id = bytes_to_bin(rdbh->next_record_list_id, 4);
549         dbh->number_of_records = bytes_to_bin(rdbh->number_of_records, 2);
550         return 0;
551 }
552
553 /* Shamelessly copied from JPilot (libplugin.c) */
554 /* returns 1 if found */
555 /*         0 if eof */
556 static int find_next_offset( mem_rec_header *mem_rh, long fpos,
557         unsigned int *next_offset, unsigned char *attrib, unsigned int *unique_id )
558 {
559         mem_rec_header *temp_mem_rh;
560         unsigned char found = 0;
561         unsigned long found_at;
562
563         found_at=0xFFFFFF;
564         for (temp_mem_rh=mem_rh; temp_mem_rh; temp_mem_rh = temp_mem_rh->next) {
565                 if ((temp_mem_rh->offset > fpos) && (temp_mem_rh->offset < found_at)) {
566                         found_at = temp_mem_rh->offset;
567                         /* *attrib = temp_mem_rh->attrib; */
568                         /* *unique_id = temp_mem_rh->unique_id; */
569                 }
570                 if ((temp_mem_rh->offset == fpos)) {
571                         found = 1;
572                         *attrib = temp_mem_rh->attrib;
573                         *unique_id = temp_mem_rh->unique_id;
574                 }
575         }
576         *next_offset = found_at;
577         return found;
578 }
579
580 /* Shamelessly copied from JPilot (libplugin.c) */
581 static void free_mem_rec_header(mem_rec_header **mem_rh) {
582         mem_rec_header *h, *next_h;
583         for (h=*mem_rh; h; h=next_h) {
584                 next_h=h->next;
585                 free(h);
586         }
587         *mem_rh = NULL;
588 }
589
590 /* Shamelessly copied from JPilot (libplugin.c) */
591 /* Read file size */
592 static int jpilot_get_info_size( FILE *in, int *size ) {
593         RawDBHeader rdbh;
594         DBHeader dbh;
595         unsigned int offset;
596         record_header rh;
597
598         fseek(in, 0, SEEK_SET);
599         fread(&rdbh, sizeof(RawDBHeader), 1, in);
600         if (feof(in)) {
601                 return MGU_EOF;
602         }
603
604         raw_header_to_header(&rdbh, &dbh);
605         if (dbh.app_info_offset==0) {
606                 *size=0;
607                 return MGU_SUCCESS;
608         }
609         if (dbh.sort_info_offset!=0) {
610                 *size = dbh.sort_info_offset - dbh.app_info_offset;
611                 return MGU_SUCCESS;
612         }
613         if (dbh.number_of_records==0) {
614                 fseek(in, 0, SEEK_END);
615                 *size=ftell(in) - dbh.app_info_offset;
616                 return MGU_SUCCESS;
617         }
618
619         fread(&rh, sizeof(record_header), 1, in);
620         offset = ((rh.Offset[0]*256+rh.Offset[1])*256+rh.Offset[2])*256+rh.Offset[3];
621         *size=offset - dbh.app_info_offset;
622
623         return MGU_SUCCESS;
624 }
625
626 /*
627  * Read address file into address list. Based on JPilot's
628  * libplugin.c (jp_get_app_info)
629  */
630 static gint jpilot_get_file_info( JPilotFile *pilotFile, unsigned char **buf, int *buf_size ) {
631         FILE *in;
632         int num;
633         unsigned int rec_size;
634         RawDBHeader rdbh;
635         DBHeader dbh;
636
637         if( ( !buf_size ) || ( ! buf ) ) {
638                 return MGU_BAD_ARGS;
639         }
640
641         *buf = NULL;
642         *buf_size=0;
643
644         if( pilotFile->path ) {
645                 in = g_fopen( pilotFile->path, "rb" );
646                 if( !in ) {
647                         return MGU_OPEN_FILE;
648                 }
649         }
650         else {
651                 return MGU_NO_FILE;
652         }
653
654         num = fread( &rdbh, sizeof( RawDBHeader ), 1, in );
655         if( num != 1 ) {
656                 if( ferror(in) ) {
657                         fclose(in);
658                         return MGU_ERROR_READ;
659                 }
660         }
661         if (feof(in)) {
662                 fclose(in);
663                 return MGU_EOF;
664         }
665
666         /* Convert header into something recognizable */
667         raw_header_to_header(&rdbh, &dbh);
668
669         num = jpilot_get_info_size(in, &rec_size);
670         if (num) {
671                 fclose(in);
672                 return MGU_ERROR_READ;
673         }
674
675         fseek(in, dbh.app_info_offset, SEEK_SET);
676         *buf = ( char * ) malloc(rec_size);
677         if (!(*buf)) {
678                 fclose(in);
679                 return MGU_OO_MEMORY;
680         }
681         num = fread(*buf, rec_size, 1, in);
682         if (num != 1) {
683                 if (ferror(in)) {
684                         fclose(in);
685                         free(*buf);
686                         return MGU_ERROR_READ;
687                 }
688         }
689         fclose(in);
690
691         *buf_size = rec_size;
692
693         return MGU_SUCCESS;
694 }
695
696 /* Shamelessly copied from JPilot (libplugin.c) */
697 static int unpack_header(PC3RecordHeader *header, unsigned char *packed_header) {
698         unsigned char *p;
699         guint32 l;
700
701         p = packed_header;
702
703         memcpy(&l, p, sizeof(l));
704         header->header_len=ntohl(l);
705         p+=sizeof(l);
706
707         memcpy(&l, p, sizeof(l));
708         header->header_version=ntohl(l);
709         p+=sizeof(l);
710
711         memcpy(&l, p, sizeof(l));
712         header->rec_len=ntohl(l);
713         p+=sizeof(l);
714
715         memcpy(&l, p, sizeof(l));
716         header->unique_id=ntohl(l);
717         p+=sizeof(l);
718
719         memcpy(&l, p, sizeof(l));
720         header->rt=ntohl(l);
721         p+=sizeof(l);
722
723         memcpy(&(header->attrib), p, sizeof(unsigned char));
724         p+=sizeof(unsigned char);
725
726         return 0;
727 }
728
729 /* Shamelessly copied from JPilot (libplugin.c) */
730 static int read_header(FILE *pc_in, PC3RecordHeader *header) {
731         guint32 l;
732         unsigned long len;
733         unsigned char packed_header[256];
734         int num;
735
736         num = fread(&l, sizeof(l), 1, pc_in);
737         if (feof(pc_in)) {
738                 return -1;
739         }
740         if (num!=1) {
741                 return num;
742         }
743         memcpy(packed_header, &l, sizeof(l));
744         len=ntohl(l);
745         if (len > 255) {
746                 return -1;
747         }
748         num = fread(packed_header+sizeof(l), len-sizeof(l), 1, pc_in);
749         if (feof(pc_in)) {
750                 return -1;
751         }
752         if (num!=1) {
753                 return num;
754         }
755         unpack_header(header, packed_header);
756         return 1;
757 }
758
759 /**
760  * Read next record from PC3 file. Based on JPilot function
761  * <code>pc_read_next_rec()</code> (libplugin.c)
762  *
763  * \param in File handle.
764  * \param br Record buffer.
765  * \return Status/error code. <code>MGU_SUCCESS</code> if data read
766  *         successfully.
767  */
768 static gint jpilot_read_next_pc( FILE *in, buf_rec *br ) {
769         PC3RecordHeader header;
770         int rec_len, num;
771         char *record;
772
773         if( feof( in ) ) {
774                 return MGU_EOF;
775         }
776         num = read_header( in, &header );
777         if( num < 1 ) {
778                 if( ferror( in ) ) {
779                         return MGU_ERROR_READ;
780                 }
781                 if( feof( in ) ) {
782                         return MGU_EOF;
783                 }
784         }
785         rec_len = header.rec_len;
786         record = malloc( rec_len );
787         if( ! record ) {
788                 return MGU_OO_MEMORY;
789         }
790         num = fread( record, rec_len, 1, in );
791         if( num != 1 ) {
792                 if( ferror( in ) ) {
793                         free( record );
794                         return MGU_ERROR_READ;
795                 }
796         }
797         br->rt = header.rt;
798         br->unique_id = header.unique_id;
799         br->attrib = header.attrib;
800         br->buf = record;
801         br->size = rec_len;
802
803         return MGU_SUCCESS;
804 }
805
806 /**
807  * Read address file into a linked list. Based on JPilot function
808  * <code>jp_read_DB_files()</code> (from libplugin.c)
809  *
810  * \param pilotFile  JPilot control data.
811  * \param records Pointer to linked list of records read.
812  * \return Status/error code. <code>MGU_SUCCESS</code> if data read
813  *         successfully.
814  */
815 static gint jpilot_read_db_files( JPilotFile *pilotFile, GList **records ) {
816         FILE *in, *pc_in;
817         char *buf;
818         GList *temp_list;
819         int num_records, recs_returned, i, num, r;
820         unsigned int offset, prev_offset, next_offset, rec_size;
821         int out_of_order;
822         long fpos;  /*file position indicator */
823         unsigned char attrib;
824         unsigned int unique_id;
825         mem_rec_header *mem_rh, *temp_mem_rh, *last_mem_rh;
826         record_header rh;
827         RawDBHeader rdbh;
828         DBHeader dbh;
829         buf_rec *temp_br;
830         gchar *pcFile;
831
832         mem_rh = last_mem_rh = NULL;
833         *records = NULL;
834         recs_returned = 0;
835
836         if( pilotFile->path == NULL ) {
837                 return MGU_BAD_ARGS;
838         }
839
840         in = g_fopen( pilotFile->path, "rb" );
841         if (!in) {
842                 return MGU_OPEN_FILE;
843         }
844
845         /* Read the database header */
846         num = fread( &rdbh, sizeof( RawDBHeader ), 1, in );
847         if( num != 1 ) {
848                 if( ferror( in ) ) {
849                         fclose( in );
850                         return MGU_ERROR_READ;
851                 }
852                 if( feof( in ) ) {
853                         fclose( in );
854                         return MGU_EOF;
855                 }
856         }
857         raw_header_to_header( &rdbh, &dbh );
858
859         /* Read each record entry header */
860         num_records = dbh.number_of_records;
861         out_of_order = 0;
862         prev_offset = 0;
863
864         for( i = 1; i < num_records + 1; i++ ) {
865                 num = fread( &rh, sizeof( record_header ), 1, in );
866                 if( num != 1 ) {
867                         if( ferror( in ) ) {
868                                 break;
869                         }
870                         if( feof( in ) ) {
871                                 fclose( in );
872                                 return MGU_EOF;
873                         }
874                 }
875
876                 offset =
877                         ( ( rh.Offset[0] * 256 + rh.Offset[1] ) * 256
878                         + rh.Offset[2] ) * 256
879                         + rh.Offset[3];
880                 if( offset < prev_offset ) {
881                         out_of_order = 1;
882                 }
883                 prev_offset = offset;
884                 temp_mem_rh = ( mem_rec_header * ) malloc( sizeof( mem_rec_header ) );
885                 if( ! temp_mem_rh ) {
886                         break;
887                 }
888                 temp_mem_rh->next = NULL;
889                 temp_mem_rh->rec_num = i;
890                 temp_mem_rh->offset = offset;
891                 temp_mem_rh->attrib = rh.attrib;
892                 temp_mem_rh->unique_id =
893                         ( rh.unique_ID[0] * 256 + rh.unique_ID[1] ) * 256
894                         + rh.unique_ID[2];
895                 if( mem_rh == NULL ) {
896                         mem_rh = temp_mem_rh;
897                         last_mem_rh = temp_mem_rh;
898                 }
899                 else {
900                         last_mem_rh->next = temp_mem_rh;
901                         last_mem_rh = temp_mem_rh;
902                 }
903         }
904
905         temp_mem_rh = mem_rh;
906
907         if( num_records ) {
908                 if( out_of_order ) {
909                         find_next_offset(
910                                 mem_rh, 0, &next_offset, &attrib, &unique_id );
911                 }
912                 else {
913                         if( mem_rh ) {
914                                 next_offset = mem_rh->offset;
915                                 attrib = mem_rh->attrib;
916                                 unique_id = mem_rh->unique_id;
917                         }
918                 }
919                 fseek( in, next_offset, SEEK_SET );
920                 while( ! feof( in ) ) {
921                         fpos = ftell( in );
922                         if( out_of_order ) {
923                                 find_next_offset(
924                                         mem_rh, fpos, &next_offset, &attrib,
925                                         &unique_id );
926                         } else {
927                                 next_offset = 0xFFFFFF;
928                                 if( temp_mem_rh ) {
929                                         attrib = temp_mem_rh->attrib;
930                                         unique_id = temp_mem_rh->unique_id;
931                                         if ( temp_mem_rh->next ) {
932                                                 temp_mem_rh = temp_mem_rh->next;
933                                                 next_offset = temp_mem_rh->offset;
934                                         }
935                                 }
936                         }
937                         rec_size = next_offset - fpos;
938                         buf = malloc( rec_size );
939                         if( ! buf ) break;
940                         num = fread( buf, rec_size, 1, in );
941                         if( ( num != 1 ) ) {
942                                 if( ferror( in ) ) {
943                                         free( buf );
944                                         break;
945                                 }
946                         }
947
948                         temp_br = malloc( sizeof( buf_rec ) );
949                         if( ! temp_br ) {
950                                 free( buf );
951                                 break;
952                         }
953                         temp_br->rt = PALM_REC;
954                         temp_br->unique_id = unique_id;
955                         temp_br->attrib = attrib;
956                         temp_br->buf = buf;
957                         temp_br->size = rec_size;
958
959                         *records = g_list_append( *records, temp_br );
960
961                         recs_returned++;
962                 }
963         }
964         fclose( in );
965         free_mem_rec_header( &mem_rh );
966
967         /* Read the PC3 file, if present */
968         pcFile = jpilot_get_pc3_file( pilotFile );
969         if( pcFile == NULL ) return MGU_SUCCESS;
970         pc_in = g_fopen( pcFile, "rb");
971         g_free( pcFile );
972
973         if( pc_in == NULL ) {
974                 return MGU_SUCCESS;
975         }
976
977         while( ! feof( pc_in ) ) {
978                 gboolean linked;
979
980                 temp_br = malloc( sizeof( buf_rec ) );
981                 if( ! temp_br ) {
982                         break;
983                 }
984                 r = jpilot_read_next_pc( pc_in, temp_br );
985                 if( r != MGU_SUCCESS ) {
986                         if( (r != MGU_EOF) && (r != MGU_ERROR_READ) ) {
987                                 free( temp_br->buf );
988                         }
989                         free( temp_br );
990                         break;
991                 }
992
993                 linked = FALSE;
994                 if( ( temp_br->rt != DELETED_PC_REC )
995                  && ( temp_br->rt != DELETED_PALM_REC )
996                  && ( temp_br->rt != MODIFIED_PALM_REC )
997                  && ( temp_br->rt != DELETED_DELETED_PALM_REC ) )
998                 {
999                         *records = g_list_append( *records, temp_br );
1000                         recs_returned++;
1001                         linked = TRUE;
1002                 }
1003
1004                 if( ( temp_br->rt == DELETED_PALM_REC )
1005                  || ( temp_br->rt == MODIFIED_PALM_REC ) )
1006                 {
1007                         temp_list = *records;
1008                         if( *records ) {
1009                                 while( temp_list->next ) {
1010                                         temp_list=temp_list->next;
1011                                 }
1012                         }
1013                         for( ; temp_list; temp_list=temp_list->prev ) {
1014                                 if( ( ( buf_rec * )temp_list->data )->unique_id ==
1015                                     temp_br->unique_id ) {
1016                                         ( ( buf_rec * )temp_list->data )->rt =
1017                                                 temp_br->rt;
1018                                 }
1019                         }
1020                 }
1021
1022                 if( ! linked ) {
1023                         free( temp_br->buf );
1024                         free( temp_br );
1025                 }
1026         }
1027         fclose( pc_in );
1028
1029         return MGU_SUCCESS;
1030 }
1031
1032 /**
1033  * Parse buffer containing multiple e-mail addresses into a linked list of
1034  * addresses. Separator characters are " ,;|" and control characters. Address
1035  * is only extracted if it contains an "at" (@) character.
1036  * 
1037  * \param buf Buffer to process.
1038  * \return List of strings.
1039  */
1040 static GList *jpilot_parse_email( gchar *buf ) {
1041         GList *list;
1042         gchar *p, *st, *em;
1043         gchar lch;
1044         gint len;
1045         gboolean valid, done;
1046
1047         valid = done = FALSE;
1048         lch = ' ';
1049         list = NULL;
1050         p = st = buf;
1051         while( ! done ) {
1052                 if( *p == ' ' || *p == ',' || *p == ';' || *p == '|' || *p < 32 ) {
1053                         if( *p == '\0' ) {
1054                                 done = TRUE;
1055                         }
1056                         else {
1057                                 *p = ' ';
1058                         }
1059
1060                         if( *p == lch ) {
1061                                 st++;
1062                         }
1063                         else {
1064                                 len = p - st;
1065                                 if( len > 0 ) {
1066                                         if( valid ) {
1067                                                 em = g_strndup( st, len );
1068                                                 list = g_list_append( list, em );
1069                                         }
1070                                         st = p;
1071                                         ++st;
1072                                         valid = FALSE;
1073                                 }
1074                         }
1075                 }
1076                 if( *p == '@' ) valid = TRUE;
1077                 lch = *p;
1078                 ++p;
1079         }
1080
1081         return list;    
1082 }
1083
1084 #define FULLNAME_BUFSIZE        256
1085 #define EMAIL_BUFSIZE           256
1086
1087 /**
1088  * Process a single label entry field, parsing multiple e-mail address entries.
1089  *
1090  * \param pilotFile  JPilot control data.
1091  * \param labelEntry Label entry data.
1092  * \param person     Person.
1093  */
1094 static void jpilot_parse_label( JPilotFile *pilotFile, gchar *labelEntry, ItemPerson *person ) {
1095         gchar buffer[ EMAIL_BUFSIZE ];
1096         ItemEMail *email;
1097         GList *list, *node;
1098
1099         if( labelEntry ) {
1100                 *buffer = '\0';
1101                 strcpy( buffer, labelEntry );
1102                 node = list = jpilot_parse_email( buffer );
1103                 while( node ) {
1104                         email = addritem_create_item_email();
1105                         addritem_email_set_address( email, node->data );
1106                         if (convert_charcode) {
1107                                 gchar *convertBuff = NULL;
1108 #if (PILOT_LINK_MAJOR > 11)
1109                                 convert_FromPilotChar(CS_INTERNAL, labelEntry, 
1110                                                 strlen(labelEntry), &convertBuff);
1111 #else
1112                                 convertBuff = conv_codeset_strdup( labelEntry, 
1113                                                 conv_get_locale_charset_str_no_utf8(), 
1114                                                 CS_INTERNAL );
1115 #endif
1116                                 if (convertBuff)
1117                                         addritem_email_set_remarks( email, convertBuff );
1118                                 g_free( convertBuff );
1119                         }
1120                         else {
1121                                 addritem_email_set_remarks(email, buffer);
1122                         }
1123
1124                         addrcache_id_email( pilotFile->addressCache, email );
1125                         addrcache_person_add_email( pilotFile->addressCache, person, email );
1126                         node = g_list_next( node );
1127                 }
1128                 mgu_free_dlist( list );
1129                 list = NULL;
1130         }
1131 }
1132         
1133 /**
1134  * Unpack address, building new data inside cache.
1135  * \param pilotFile  JPilot control data.
1136  * \param buf        Record buffer.
1137  * \param folderInd  Array of (category) folders to load.
1138  */
1139 static void jpilot_load_address(
1140                 JPilotFile *pilotFile, buf_rec *buf, ItemFolder *folderInd[] )
1141 {
1142         struct Address addr;
1143         gchar **addrEnt;
1144         gint num, k;
1145         gint cat_id = 0;
1146         guint unique_id;
1147         guchar attrib;
1148         gchar fullName[ FULLNAME_BUFSIZE ];
1149         ItemPerson *person;
1150         gint *indPhoneLbl;
1151         gchar *labelEntry;
1152         GList *node;
1153         gchar* extID;
1154         struct AddressAppInfo *ai;
1155         gchar **firstName = NULL;
1156         gchar **lastName = NULL;
1157 #if (PILOT_LINK_MAJOR > 11)
1158         pi_buffer_t *RecordBuffer;
1159 #endif /* PILOT_LINK_0_12 */
1160
1161         /* Retrieve address */
1162 #if (PILOT_LINK_MAJOR < 12)
1163         num = unpack_Address(&addr, buf->buf, buf->size);
1164         if (num <= 0) {
1165                 return;
1166         }
1167 #else /* PILOT_LINK_0_12 */
1168         RecordBuffer = pi_buffer_new(buf->size);
1169         memcpy(RecordBuffer->data, buf->buf, buf->size);
1170         RecordBuffer->used = buf->size;
1171         if (unpack_Address(&addr, RecordBuffer, address_v1) == -1) {
1172                 pi_buffer_free(RecordBuffer);
1173                 return;
1174         }
1175         pi_buffer_free(RecordBuffer);
1176 #endif
1177         addrEnt = addr.entry;
1178         attrib = buf->attrib;
1179         unique_id = buf->unique_id;
1180         cat_id = attrib & 0x0F;
1181
1182         *fullName = '\0';
1183         if( addrEnt[ IND_LABEL_FIRSTNAME ] ) {
1184                 firstName = g_strsplit( addrEnt[ IND_LABEL_FIRSTNAME ], "\01", 2 );
1185         }
1186
1187         if( addrEnt[ IND_LABEL_LASTNAME ] ) {
1188                 lastName = g_strsplit( addrEnt[ IND_LABEL_LASTNAME ], "\01", 2 );
1189         }
1190
1191         if( name_order == FAMILY_LAST ) {
1192                 g_snprintf( fullName, FULLNAME_BUFSIZE, "%s %s",
1193                             firstName ? firstName[0] : "",
1194                             lastName ? lastName[0] : "" );
1195         }
1196         else {
1197                 g_snprintf( fullName, FULLNAME_BUFSIZE, "%s %s",
1198                             lastName ? lastName[0] : "",
1199                             firstName ? firstName[0] : "" );
1200         }
1201
1202         if( firstName ) {
1203                 g_strfreev( firstName );
1204         }
1205         if( lastName ) {
1206                 g_strfreev( lastName );
1207         }
1208
1209         g_strstrip( fullName );
1210
1211         if( convert_charcode ) {
1212                 gchar *nameConv = NULL;
1213 #if (PILOT_LINK_MAJOR > 11)
1214                 convert_FromPilotChar(CS_INTERNAL, fullName, 
1215                                 strlen(fullName), &nameConv);
1216 #else
1217                 nameConv = conv_codeset_strdup( fullName, 
1218                                 conv_get_locale_charset_str_no_utf8(), 
1219                                 CS_INTERNAL );
1220 #endif
1221                 if (nameConv)
1222                         strncpy2( fullName, nameConv, FULLNAME_BUFSIZE );
1223                 g_free( nameConv );
1224         }
1225
1226         person = addritem_create_item_person();
1227         addritem_person_set_common_name( person, fullName );
1228         addritem_person_set_first_name( person, addrEnt[ IND_LABEL_FIRSTNAME ] );
1229         addritem_person_set_last_name( person, addrEnt[ IND_LABEL_LASTNAME ] );
1230         addrcache_id_person( pilotFile->addressCache, person );
1231
1232         extID = g_strdup_printf( "%d", unique_id );
1233         addritem_person_set_external_id( person, extID );
1234         g_free( extID );
1235         extID = NULL;
1236
1237         /* Pointer to address metadata. */
1238         ai = & pilotFile->addrInfo;
1239
1240         /* Add entry for each email address listed under phone labels. */
1241         indPhoneLbl = addr.phoneLabel;
1242         for( k = 0; k < JPILOT_NUM_ADDR_PHONE; k++ ) {
1243                 gint ind;
1244
1245                 ind = indPhoneLbl[k];
1246                 /*
1247                 * fprintf( stdout, "%d : %d : %20s : %s\n", k, ind,
1248                 * ai->phoneLabels[ind], addrEnt[3+k] );
1249                 */
1250                 if( indPhoneLbl[k] == IND_PHONE_EMAIL ) {
1251                         labelEntry = addrEnt[ OFFSET_PHONE_LABEL + k ];
1252                         jpilot_parse_label( pilotFile, labelEntry, person );
1253                 }
1254         }
1255
1256         /* Add entry for each custom label */
1257         node = pilotFile->labelInd;
1258         while( node ) {
1259                 gint ind;
1260
1261                 ind = GPOINTER_TO_INT( node->data );
1262                 if( ind > -1 ) {
1263                         /*
1264                         * fprintf( stdout, "%d : %20s : %s\n", ind, ai->labels[ind],
1265                         * addrEnt[ind] );
1266                         */
1267                         labelEntry = addrEnt[ind];
1268                         jpilot_parse_label( pilotFile, labelEntry, person );
1269                 }
1270
1271                 node = g_list_next( node );
1272         }
1273
1274         if( person->listEMail ) {
1275                 if( cat_id > -1 && cat_id < JPILOT_NUM_CATEG ) {
1276                         /* Add to specified category */
1277                         addrcache_folder_add_person(
1278                                 pilotFile->addressCache,
1279                                 folderInd[cat_id], person );
1280                 }
1281                 else {
1282                         /* Add to root folder */
1283                         addrcache_add_person(
1284                                 pilotFile->addressCache, person );
1285                 }
1286         }
1287         else {
1288                 addritem_free_item_person( person );
1289                 person = NULL;
1290         }
1291         /* Free up pointer allocated inside address */
1292         free_Address( & addr );
1293 }
1294
1295 /**
1296  * Free up address list.
1297  * \param records List of records to free.
1298  */
1299 static void jpilot_free_addrlist( GList *records ) {
1300         GList *node;
1301         buf_rec *br;
1302
1303         node = records;
1304         while( node ) {
1305                 br = node->data;
1306                 free( br->buf );
1307                 free( br );
1308                 node->data = NULL;
1309                 node = g_list_next( node );
1310         }
1311
1312         /* Free up list */
1313         g_list_free( records );
1314 }
1315
1316 /**
1317  * Read metadata from file.
1318  * \param pilotFile  JPilot control data.
1319  * \return Status/error code. <code>MGU_SUCCESS</code> if data read
1320  *         successfully.
1321  */
1322 static gint jpilot_read_metadata( JPilotFile *pilotFile ) {
1323         gint retVal;
1324         unsigned int rec_size;
1325         unsigned char *buf;
1326         int num;
1327
1328         g_return_val_if_fail( pilotFile != NULL, -1 );
1329
1330         pilotFile->readMetadata = FALSE;
1331         addrcache_clear( pilotFile->addressCache );
1332
1333         /* Read file info */
1334         retVal = jpilot_get_file_info( pilotFile, &buf, &rec_size);
1335         if( retVal != MGU_SUCCESS ) {
1336                 pilotFile->retVal = retVal;
1337                 return pilotFile->retVal;
1338         }
1339
1340         num = unpack_AddressAppInfo( &pilotFile->addrInfo, buf, rec_size );
1341         if( buf ) {
1342                 free(buf);
1343         }
1344         if( num <= 0 ) {
1345                 pilotFile->retVal = MGU_ERROR_READ;
1346                 return pilotFile->retVal;
1347         }
1348
1349         pilotFile->readMetadata = TRUE;
1350         pilotFile->retVal = MGU_SUCCESS;
1351         return pilotFile->retVal;
1352 }
1353
1354 /**
1355  * Setup labels and indexes from metadata.
1356  * \param pilotFile  JPilot control data.
1357  * \return <i>TRUE</i> is setup successfully.
1358  */
1359 static gboolean jpilot_setup_labels( JPilotFile *pilotFile ) {
1360         gboolean retVal = FALSE;
1361         struct AddressAppInfo *ai;
1362         GList *node;
1363
1364         g_return_val_if_fail( pilotFile != NULL, -1 );
1365
1366         /* Release indexes */
1367         node = pilotFile->labelInd;
1368         while( node ) {
1369                 node->data = NULL;
1370                 node = g_list_next( node );
1371         }
1372         pilotFile->labelInd = NULL;
1373
1374         if( pilotFile->readMetadata ) {
1375                 ai = & pilotFile->addrInfo;
1376                 node = pilotFile->customLabels;
1377                 while( node ) {
1378                         gchar *lbl = node->data;
1379                         gint ind = -1;
1380                         gint i;
1381                         for( i = 0; i < JPILOT_NUM_LABELS; i++ ) {
1382                                 gchar *labelName = ai->labels[i];
1383
1384                                 if( convert_charcode ) {
1385                                         gchar *convertBuff = NULL;
1386 #if (PILOT_LINK_MAJOR > 11)
1387                                         convert_FromPilotChar(CS_INTERNAL, labelName, 
1388                                                         strlen(labelName), &convertBuff);
1389 #else
1390                                         convertBuff = conv_codeset_strdup( labelName, 
1391                                                         conv_get_locale_charset_str_no_utf8(), 
1392                                                         CS_INTERNAL );
1393 #endif
1394                                         if (convertBuff) {
1395                                                 labelName = convertBuff;
1396                                         }
1397                                 }
1398
1399                                 if( g_utf8_collate( labelName, lbl ) == 0 ) {
1400                                         ind = i;
1401                                         break;
1402                                 }
1403                         }
1404                         pilotFile->labelInd = g_list_append(
1405                                 pilotFile->labelInd, GINT_TO_POINTER(ind) );
1406                         node = g_list_next( node );
1407                 }
1408                 retVal = TRUE;
1409         }
1410         return retVal;
1411 }
1412
1413 /**
1414  * Load list with character strings of label names.
1415  * \param pilotFile  JPilot control data.
1416  * \param labelList List of label names to load.
1417  * \return List of label names loaded.
1418  */
1419 GList *jpilot_load_label( JPilotFile *pilotFile, GList *labelList ) {
1420         int i;
1421
1422         g_return_val_if_fail( pilotFile != NULL, NULL );
1423
1424         if( pilotFile->readMetadata ) {
1425                 struct AddressAppInfo *ai = & pilotFile->addrInfo;
1426                 for( i = 0; i < JPILOT_NUM_LABELS; i++ ) {
1427                         gchar *labelName = ai->labels[i];
1428
1429                         if( labelName ) {
1430                                 if( convert_charcode ) {
1431                                         gchar *convertBuff = NULL;
1432 #if (PILOT_LINK_MAJOR > 11)
1433                                         convert_FromPilotChar(CS_INTERNAL, labelName, 
1434                                                         strlen(labelName), &convertBuff);
1435 #else
1436                                         convertBuff = conv_codeset_strdup( labelName, 
1437                                                         conv_get_locale_charset_str_no_utf8(), 
1438                                                         CS_INTERNAL );
1439 #endif
1440                                         if (convertBuff) {
1441                                                 labelName = convertBuff;
1442                                         }
1443                                 }
1444                                 else {
1445                                         labelName = g_strdup( labelName );
1446                                 }
1447                                 labelList = g_list_append( labelList, labelName );
1448                         }
1449                         else {
1450                                 labelList = g_list_append(
1451                                         labelList, g_strdup( "" ) );
1452                         }
1453                 }
1454         }
1455         return labelList;
1456 }
1457
1458 /**
1459  * Return category name for specified category ID.
1460  * \param pilotFile  JPilot control data.
1461  * \param catID      Category ID.
1462  * \return Category name, or empty string if not invalid ID. Name should be
1463  *         <code>g_free()</code> when done.
1464  */
1465 gchar *jpilot_get_category_name( JPilotFile *pilotFile, gint catID ) {
1466         gchar *catName = NULL;
1467
1468         g_return_val_if_fail( pilotFile != NULL, NULL );
1469
1470         if( pilotFile->readMetadata ) {
1471                 struct AddressAppInfo *ai = & pilotFile->addrInfo;
1472                 struct CategoryAppInfo *cat = & ai->category;
1473                 if( catID < 0 || catID > JPILOT_NUM_CATEG ) {
1474                 }
1475                 else {
1476                         catName = g_strdup( cat->name[catID] );
1477                 }
1478         }
1479         if( ! catName ) catName = g_strdup( "" );
1480         return catName;
1481 }
1482
1483 /**
1484  * Load list with character strings of phone label names.
1485  * \param pilotFile  JPilot control data.
1486  * \param labelList List of label names to load.
1487  * \return List of label names loaded.
1488  */
1489 GList *jpilot_load_phone_label( JPilotFile *pilotFile, GList *labelList ) {
1490         gint i;
1491
1492         g_return_val_if_fail( pilotFile != NULL, NULL );
1493
1494         if( pilotFile->readMetadata ) {
1495                 struct AddressAppInfo *ai = & pilotFile->addrInfo;
1496                 for( i = 0; i < JPILOT_NUM_PHONELABELS; i++ ) {
1497                         gchar   *labelName = ai->phoneLabels[i];
1498                         if( labelName ) {
1499                                 labelList = g_list_append(
1500                                         labelList, g_strdup( labelName ) );
1501                         }
1502                         else {
1503                                 labelList = g_list_append(
1504                                         labelList, g_strdup( "" ) );
1505                         }
1506                 }
1507         }
1508         return labelList;
1509 }
1510
1511 /**
1512  * Load list with character strings of custom label names. Only none blank
1513  * names are loaded.
1514  * \param pilotFile  JPilot control data.
1515  * \param labelList List of label names to load.
1516  * \return List of label names loaded. Should be freed when done.
1517  */
1518 GList *jpilot_load_custom_label( JPilotFile *pilotFile, GList *labelList ) {
1519         gint i;
1520
1521         g_return_val_if_fail( pilotFile != NULL, NULL );
1522
1523         if( pilotFile->readMetadata ) {
1524                 struct AddressAppInfo *ai = & pilotFile->addrInfo;
1525                 for( i = 0; i < NUM_CUSTOM_LABEL; i++ ) {
1526                         gchar *labelName = ai->labels[i+IND_CUSTOM_LABEL];
1527                         if( labelName ) {
1528                                 g_strchomp( labelName );
1529                                 g_strchug( labelName );
1530                                 if( *labelName != '\0' ) {
1531                                         if( convert_charcode ) {
1532                                                 gchar *convertBuff = NULL;
1533 #if (PILOT_LINK_MAJOR > 11)
1534                                                 convert_FromPilotChar(CS_INTERNAL, labelName, 
1535                                                                 strlen(labelName), &convertBuff);
1536 #else
1537                                                 convertBuff = conv_codeset_strdup( labelName, 
1538                                                                 conv_get_locale_charset_str_no_utf8(), 
1539                                                                 CS_INTERNAL );
1540 #endif
1541                                                 if (convertBuff) {
1542                                                         labelName = convertBuff;
1543                                                 }
1544                                         }
1545                                         else {
1546                                                 labelName = g_strdup( labelName );
1547                                         }
1548                                         labelList = g_list_append( labelList, labelName );
1549                                 }
1550                         }
1551                 }
1552         }
1553         return labelList;
1554 }
1555
1556 /**
1557  * Load list with character strings of category names.
1558  * \param pilotFile  JPilot control data.
1559  * \return List of label names loaded. Should be freed when done.
1560  */
1561 GList *jpilot_get_category_list( JPilotFile *pilotFile ) {
1562         GList *catList = NULL;
1563         gint i;
1564
1565         g_return_val_if_fail( pilotFile != NULL, NULL );
1566
1567         if( pilotFile->readMetadata ) {
1568                 struct AddressAppInfo *ai = & pilotFile->addrInfo;
1569                 struct CategoryAppInfo *cat = & ai->category;
1570                 for( i = 0; i < JPILOT_NUM_CATEG; i++ ) {
1571                         gchar *catName = cat->name[i];
1572                         if( catName ) {
1573                                 catList = g_list_append(
1574                                         catList, g_strdup( catName ) );
1575                         }
1576                         else {
1577                                 catList = g_list_append(
1578                                         catList, g_strdup( "" ) );
1579                         }
1580                 }
1581         }
1582         return catList;
1583 }
1584
1585 /**
1586  * Build folder in address book for each category.
1587  * \param pilotFile  JPilot control data.
1588  */
1589 static void jpilot_build_category_list( JPilotFile *pilotFile ) {
1590         struct AddressAppInfo *ai = & pilotFile->addrInfo;
1591         struct CategoryAppInfo *cat = & ai->category;
1592         gint i;
1593
1594         for( i = 0; i < JPILOT_NUM_CATEG; i++ ) {
1595                 ItemFolder *folder = addritem_create_item_folder();
1596
1597                 if( convert_charcode ) {
1598                         gchar *convertBuff = NULL;
1599 #if (PILOT_LINK_MAJOR > 11)
1600                         convert_FromPilotChar(CS_INTERNAL, cat->name[i], 
1601                                         strlen(cat->name[i]), &convertBuff);
1602 #else
1603                         convertBuff = conv_codeset_strdup( cat->name[i], 
1604                                         conv_get_locale_charset_str_no_utf8(), 
1605                                         CS_INTERNAL );
1606 #endif
1607                         if (convertBuff) {
1608                                 addritem_folder_set_name( folder, convertBuff );
1609                                 g_free( convertBuff );
1610                         } else {
1611                                 addritem_folder_set_name( folder, cat->name[i] );
1612                         }
1613                 }
1614                 else {
1615                         addritem_folder_set_name( folder, cat->name[i] );
1616                 }
1617
1618                 addrcache_id_folder( pilotFile->addressCache, folder );
1619                 addrcache_add_folder( pilotFile->addressCache, folder );
1620         }
1621 }
1622
1623 /**
1624  * Remove empty (category) folders.
1625  * \param pilotFile  JPilot control data.
1626  */
1627 static void jpilot_remove_empty( JPilotFile *pilotFile ) {
1628         GList *listFolder;
1629         GList *remList;
1630         GList *node;
1631         gint i = 0;
1632
1633         listFolder = addrcache_get_list_folder( pilotFile->addressCache );
1634         node = listFolder;
1635         remList = NULL;
1636         while( node ) {
1637                 ItemFolder *folder = node->data;
1638                 if( ADDRITEM_NAME(folder) == NULL || *ADDRITEM_NAME(folder) == '\0' ) {
1639                         if( folder->listPerson ) {
1640                                 /* Give name to folder */
1641                                 gchar name[20];
1642                                 sprintf( name, "? %d", i );
1643                                 addritem_folder_set_name( folder, name );
1644                         }
1645                         else {
1646                                 /* Mark for removal */
1647                                 remList = g_list_append( remList, folder );
1648                         }
1649                 }
1650                 node = g_list_next( node );
1651                 i++;
1652         }
1653         node = remList;
1654         while( node ) {
1655                 ItemFolder *folder = node->data;
1656                 addrcache_remove_folder( pilotFile->addressCache, folder );
1657                 node = g_list_next( node );
1658         }
1659         g_list_free( remList );
1660 }
1661
1662 /**
1663  * Read address file into address cache.
1664  * \param pilotFile  JPilot control data.
1665  * \return Error/status code. <code>MGU_SUCCESS</code> if data read
1666  *         successfully.
1667  */
1668 static gint jpilot_read_file( JPilotFile *pilotFile ) {
1669         gint retVal, i;
1670         GList *records = NULL;
1671         GList *node;
1672         buf_rec *br;
1673         ItemFolder *folderInd[ JPILOT_NUM_CATEG ];
1674
1675         /* Read list of records from JPilot files */
1676         retVal = jpilot_read_db_files( pilotFile, &records );
1677         if( retVal != MGU_SUCCESS ) {
1678                 jpilot_free_addrlist( records );
1679                 return retVal;
1680         }
1681
1682         /* Setup labels and category folders */
1683         jpilot_setup_labels( pilotFile );
1684         jpilot_build_category_list( pilotFile );
1685
1686         /* Build array of pointers to categories */
1687         i = 0;
1688         node = addrcache_get_list_folder( pilotFile->addressCache );
1689         while( node ) {
1690                 if( i < JPILOT_NUM_CATEG ) {
1691                         folderInd[i] = node->data;
1692                 }
1693                 node = g_list_next( node );
1694                 i++;
1695         }
1696
1697         /* Load all addresses, free up old stuff as we go */
1698         node = records;
1699         while( node ) {
1700                 br = node->data;
1701                 if( ( br->rt != DELETED_PC_REC ) &&
1702                     ( br->rt != DELETED_PALM_REC ) &&
1703                     ( br->rt != MODIFIED_PALM_REC ) &&
1704                     ( br->rt != DELETED_DELETED_PALM_REC ) ) {
1705                         jpilot_load_address( pilotFile, br, folderInd );
1706                 }
1707                 free( br->buf );
1708                 free( br );
1709                 node->data = NULL;
1710                 node = g_list_next( node );
1711         }
1712
1713         /* Free up list */
1714         g_list_free( records );
1715
1716         /* Remove empty category folders */
1717         jpilot_remove_empty( pilotFile );
1718         jpilot_mark_files( pilotFile );
1719
1720         return retVal;
1721 }
1722
1723 /**
1724  * Read file into list. Main entry point
1725  * \param pilotFile  JPilot control data.
1726  * \return Error/status code. <code>MGU_SUCCESS</code> if data read
1727  *         successfully.
1728  */
1729 gint jpilot_read_data( JPilotFile *pilotFile ) {
1730         const gchar *cur_locale;
1731
1732         name_order = FAMILY_LAST;
1733
1734         cur_locale = conv_get_current_locale();
1735
1736         if( g_ascii_strncasecmp( cur_locale, "ja", 2 ) == 0 ) {
1737                 name_order = FAMILY_FIRST;
1738         }
1739
1740         g_return_val_if_fail( pilotFile != NULL, -1 );
1741
1742         pilotFile->retVal = MGU_SUCCESS;
1743         pilotFile->addressCache->accessFlag = FALSE;
1744         if( jpilot_check_files( pilotFile ) ) {
1745                 addrcache_clear( pilotFile->addressCache );
1746                 jpilot_read_metadata( pilotFile );
1747                 if( pilotFile->retVal == MGU_SUCCESS ) {
1748                         pilotFile->retVal = jpilot_read_file( pilotFile );
1749                         if( pilotFile->retVal == MGU_SUCCESS ) {
1750                                 pilotFile->addressCache->modified = FALSE;
1751                                 pilotFile->addressCache->dataRead = TRUE;
1752                         }
1753                 }
1754         }
1755         return pilotFile->retVal;
1756 }
1757
1758 /**
1759  * Return linked list of persons. This is a list of references to ItemPerson
1760  * objects. Do <b>NOT</b> attempt to use the <code>addrbook_free_xxx()</code>
1761  * functions... this will destroy the addressbook data!
1762  *
1763  * \param  pilotFile  JPilot control data.
1764  * \return List of persons.
1765  */
1766 GList *jpilot_get_list_person( JPilotFile *pilotFile ) {
1767         g_return_val_if_fail( pilotFile != NULL, NULL );
1768         return addrcache_get_list_person( pilotFile->addressCache );
1769 }
1770
1771 /**
1772  * Return linked list of folders. This is a list of references to non-empty
1773  * category folders. Do <b>NOT</b> attempt to use the
1774  * <code>addrbook_free_xxx()</code> functions... this will destroy the
1775  * addressbook data!
1776  *
1777  * \param  pilotFile  JPilot control data.
1778  * \return List of ItemFolder objects. This should not be freed.
1779  */
1780 GList *jpilot_get_list_folder( JPilotFile *pilotFile ) {
1781         g_return_val_if_fail( pilotFile != NULL, NULL );
1782         return addrcache_get_list_folder( pilotFile->addressCache );
1783 }
1784
1785 /**
1786  * Return linked list of all persons. Note that the list contains references
1787  * to items. Do <b>NOT</b> attempt to use the <code>addrbook_free_xxx()</code>
1788  * functions... this will destroy the addressbook data!
1789  *
1790  * \param pilotFile  JPilot control data.
1791  * \return List of items, or NULL if none.
1792  */
1793 GList *jpilot_get_all_persons( JPilotFile *pilotFile ) {
1794         g_return_val_if_fail( pilotFile != NULL, NULL );
1795         return addrcache_get_all_persons( pilotFile->addressCache );
1796 }
1797
1798 /**
1799  * Validate that all parameters specified.
1800  * \param pilotFile  JPilot control data.
1801  * \return <i>TRUE</i> if data is good.
1802  */
1803 gboolean jpilot_validate( JPilotFile *pilotFile ) {
1804         gboolean retVal;
1805         gchar *name;
1806
1807         g_return_val_if_fail( pilotFile != NULL, FALSE );
1808
1809         retVal = TRUE;
1810         if( pilotFile->path ) {
1811                 if( strlen( pilotFile->path ) < 1 ) retVal = FALSE;
1812         }
1813         else {
1814                 retVal = FALSE;
1815         }
1816         name = jpilot_get_name( pilotFile );
1817         if( name ) {
1818                 if( strlen( name ) < 1 ) retVal = FALSE;
1819         }
1820         else {
1821                 retVal = FALSE;
1822         }
1823         return retVal;
1824 }
1825
1826 #define WORK_BUFLEN 1024
1827
1828 /**
1829  * Attempt to find a valid JPilot file.
1830  * \param pilotFile  JPilot control data.
1831  * \return Filename, or home directory if not found, or empty string if
1832  *         no home. Filename should be <code>g_free()</code> when done.
1833  */
1834 gchar *jpilot_find_pilotdb( void ) {
1835         const gchar *homedir;
1836         gchar str[ WORK_BUFLEN ];
1837         gint len;
1838         FILE *fp;
1839
1840         homedir = get_home_dir();
1841         if( ! homedir ) return g_strdup( "" );
1842
1843         strcpy( str, homedir );
1844         len = strlen( str );
1845         if( len > 0 ) {
1846                 if( str[ len-1 ] != G_DIR_SEPARATOR ) {
1847                         str[ len ] = G_DIR_SEPARATOR;
1848                         str[ ++len ] = '\0';
1849                 }
1850         }
1851         strcat( str, JPILOT_DBHOME_DIR );
1852         strcat( str, G_DIR_SEPARATOR_S );
1853         strcat( str, JPILOT_DBHOME_FILE );
1854
1855         /* Attempt to open */
1856         if( ( fp = g_fopen( str, "rb" ) ) != NULL ) {
1857                 fclose( fp );
1858         }
1859         else {
1860                 /* Truncate filename */
1861                 str[ len ] = '\0';
1862         }
1863         return g_strdup( str );
1864 }
1865
1866 /**
1867  * Attempt to read file, testing for valid JPilot format.
1868  * \param fileSpec File specification to read.
1869  * \return <i>TRUE<i> if file appears to be valid format.
1870  */
1871 gint jpilot_test_read_file( const gchar *fileSpec ) {
1872         JPilotFile *pilotFile;
1873         gint retVal;
1874
1875         if( fileSpec ) {
1876                 pilotFile = jpilot_create_path( fileSpec );
1877                 retVal = jpilot_read_metadata( pilotFile );
1878                 jpilot_free( pilotFile );
1879                 pilotFile = NULL;
1880         }
1881         else {
1882                 retVal = MGU_NO_FILE;
1883         }
1884         return retVal;
1885 }
1886
1887 /**
1888  * Check whether label is in list of custom labels.
1889  * \param pilotFile JPilot control data.
1890  * \param labelName to test.
1891  * \return <i>TRUE</i> if found.
1892  */
1893 gboolean jpilot_test_custom_label( JPilotFile *pilotFile, const gchar *labelName ) {
1894         gboolean retVal;
1895         GList *node;
1896
1897         g_return_val_if_fail( pilotFile != NULL, FALSE );
1898
1899         retVal = FALSE;
1900         if( labelName ) {
1901                 node = pilotFile->customLabels;
1902                 while( node ) {
1903                         if( g_utf8_collate( labelName, ( gchar * ) node->data ) == 0 ) {
1904                                 retVal = TRUE;
1905                                 break;
1906                         }
1907                         node = g_list_next( node );
1908                 }
1909         }
1910         return retVal;
1911 }
1912
1913 /**
1914  * Test whether pilot link library installed.
1915  * \return <i>TRUE</i> if library available.
1916  */
1917 gboolean jpilot_test_pilot_lib( void ) {
1918         return TRUE;
1919 }
1920
1921 #endif  /* USE_JPILOT */
1922
1923 /*
1924 * End of Source.
1925 */