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