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