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