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