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