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