Removed simple-gettext.c, as it is not being used at all.
[claws.git] / src / jpilot.c
1 /*
2  * Claws Mail -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 2001-2015 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 3 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, see <http://www.gnu.org/licenses/>.
17  */
18
19 /*
20  * Functions necessary to access JPilot database files.
21  * JPilot is Copyright(c) by Judd Montgomery.
22  * Visit http://www.jpilot.org for more details.
23  */
24
25 #ifdef HAVE_CONFIG_H
26 #  include "config.h"
27 #include "claws-features.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 = g_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         cm_return_if_fail( pilotFile != NULL );
212         addrcache_set_name( pilotFile->addressCache, value );
213 }
214 void jpilot_set_file( JPilotFile* pilotFile, const gchar *value ) {
215         cm_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         cm_return_if_fail( pilotFile != NULL );
222         pilotFile->addressCache->accessFlag = value;
223 }
224
225 gint jpilot_get_status( JPilotFile *pilotFile ) {
226         cm_return_val_if_fail( pilotFile != NULL, -1 );
227         return pilotFile->retVal;
228 }
229 ItemFolder *jpilot_get_root_folder( JPilotFile *pilotFile ) {
230         cm_return_val_if_fail( pilotFile != NULL, NULL );
231         return addrcache_get_root_folder( pilotFile->addressCache );
232 }
233 gchar *jpilot_get_name( JPilotFile *pilotFile ) {
234         cm_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         cm_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         cm_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         cm_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         cm_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         GStatBuf 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 == g_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         GStatBuf 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 == g_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         cm_return_val_if_fail( pilotFile != NULL, FALSE );
408         pilotFile->addressCache->modified = jpilot_check_files( pilotFile );
409         return pilotFile->addressCache->modified;
410 }
411 gboolean jpilot_get_accessed( JPilotFile *pilotFile ) {
412         cm_return_val_if_fail( pilotFile != NULL, FALSE );
413         return pilotFile->addressCache->accessFlag;
414 }
415
416 /**
417  * Free up pilot file object by releasing internal memory.
418  * \param pilotFile  JPilot control data.
419  */
420 void jpilot_free( JPilotFile *pilotFile ) {
421         cm_return_if_fail( pilotFile != NULL );
422
423         /* Release custom labels */
424         jpilot_clear_custom_labels( pilotFile );
425
426         /* Clear cache */
427         addrcache_clear( pilotFile->addressCache );
428         addrcache_free( pilotFile->addressCache );
429
430         /* Free internal stuff */
431         g_free( pilotFile->path );
432
433         pilotFile->file = NULL;
434         pilotFile->path = NULL;
435         pilotFile->readMetadata = FALSE;
436         pilotFile->havePC3 = FALSE;
437         pilotFile->pc3ModifyTime = 0;
438
439         pilotFile->type = ADBOOKTYPE_NONE;
440         pilotFile->addressCache = NULL;
441         pilotFile->retVal = MGU_SUCCESS;
442
443         /* Now release file object */
444         g_free( pilotFile );
445 }
446
447 /* Shamelessly copied from JPilot (libplugin.c) */
448 static unsigned int bytes_to_bin(unsigned char *bytes, unsigned int num_bytes) {
449 unsigned int i, n;
450         n=0;
451         for (i=0;i<num_bytes;i++) {
452                 n = n*256+bytes[i];
453         }
454         return n;
455 }
456
457 /* Shamelessly copied from JPilot (utils.c) */
458 /* These next 2 functions were copied from pi-file.c in the pilot-link app */
459 /* Exact value of "Jan 1, 1970 0:00:00 GMT" - "Jan 1, 1904 0:00:00 GMT" */
460 #define PILOT_TIME_DELTA (unsigned)(2082844800)
461
462 static time_t pilot_time_to_unix_time ( unsigned long raw_time ) {
463    return (time_t)(raw_time - PILOT_TIME_DELTA);
464 }
465
466 /* Shamelessly copied from JPilot (libplugin.c) */
467 static int raw_header_to_header(RawDBHeader *rdbh, DBHeader *dbh) {
468         unsigned long temp;
469
470         strncpy(dbh->db_name, rdbh->db_name, 31);
471         dbh->db_name[31] = '\0';
472         dbh->flags = bytes_to_bin(rdbh->flags, 2);
473         dbh->version = bytes_to_bin(rdbh->version, 2);
474         temp = bytes_to_bin(rdbh->creation_time, 4);
475         dbh->creation_time = pilot_time_to_unix_time(temp);
476         temp = bytes_to_bin(rdbh->modification_time, 4);
477         dbh->modification_time = pilot_time_to_unix_time(temp);
478         temp = bytes_to_bin(rdbh->backup_time, 4);
479         dbh->backup_time = pilot_time_to_unix_time(temp);
480         dbh->modification_number = bytes_to_bin(rdbh->modification_number, 4);
481         dbh->app_info_offset = bytes_to_bin(rdbh->app_info_offset, 4);
482         dbh->sort_info_offset = bytes_to_bin(rdbh->sort_info_offset, 4);
483         strncpy(dbh->type, rdbh->type, 4);
484         dbh->type[4] = '\0';
485         strncpy(dbh->creator_id, rdbh->creator_id, 4);
486         dbh->creator_id[4] = '\0';
487         strncpy(dbh->unique_id_seed, rdbh->unique_id_seed, 4);
488         dbh->unique_id_seed[4] = '\0';
489         dbh->next_record_list_id = bytes_to_bin(rdbh->next_record_list_id, 4);
490         dbh->number_of_records = bytes_to_bin(rdbh->number_of_records, 2);
491         return 0;
492 }
493
494 /* Shamelessly copied from JPilot (libplugin.c) */
495 /* returns 1 if found */
496 /*         0 if eof */
497 static int find_next_offset( mem_rec_header *mem_rh, long fpos,
498         unsigned int *next_offset, unsigned char *attrib, unsigned int *unique_id )
499 {
500         mem_rec_header *temp_mem_rh;
501         unsigned char found = 0;
502         unsigned long found_at;
503
504         found_at=0xFFFFFF;
505         for (temp_mem_rh=mem_rh; temp_mem_rh; temp_mem_rh = temp_mem_rh->next) {
506                 if ((temp_mem_rh->offset > fpos) && (temp_mem_rh->offset < found_at)) {
507                         found_at = temp_mem_rh->offset;
508                         /* *attrib = temp_mem_rh->attrib; */
509                         /* *unique_id = temp_mem_rh->unique_id; */
510                 }
511                 if ((temp_mem_rh->offset == fpos)) {
512                         found = 1;
513                         *attrib = temp_mem_rh->attrib;
514                         *unique_id = temp_mem_rh->unique_id;
515                 }
516         }
517         *next_offset = found_at;
518         return found;
519 }
520
521 /* Shamelessly copied from JPilot (libplugin.c) */
522 static void free_mem_rec_header(mem_rec_header **mem_rh) {
523         mem_rec_header *h, *next_h;
524         for (h=*mem_rh; h; h=next_h) {
525                 next_h=h->next;
526                 free(h);
527         }
528         *mem_rh = NULL;
529 }
530
531 /* Shamelessly copied from JPilot (libplugin.c) */
532 /* Read file size */
533 static int jpilot_get_info_size( FILE *in, int *size ) {
534         RawDBHeader rdbh;
535         DBHeader dbh;
536         unsigned int offset;
537         record_header rh;
538         int r;
539
540         fseek(in, 0, SEEK_SET);
541         r = fread(&rdbh, sizeof(RawDBHeader), 1, in);
542         if (r < 1)
543                 return MGU_ERROR_READ;
544         if (feof(in)) {
545                 return MGU_EOF;
546         }
547
548         raw_header_to_header(&rdbh, &dbh);
549         if (dbh.app_info_offset==0) {
550                 *size=0;
551                 return MGU_SUCCESS;
552         }
553         if (dbh.sort_info_offset!=0) {
554                 *size = dbh.sort_info_offset - dbh.app_info_offset;
555                 return MGU_SUCCESS;
556         }
557         if (dbh.number_of_records==0) {
558                 fseek(in, 0, SEEK_END);
559                 *size=ftell(in) - dbh.app_info_offset;
560                 return MGU_SUCCESS;
561         }
562
563         r = fread(&rh, sizeof(record_header), 1, in);
564         if (r < 1)
565                 return MGU_ERROR_READ;
566
567         offset = ((rh.Offset[0]*256+rh.Offset[1])*256+rh.Offset[2])*256+rh.Offset[3];
568         *size=offset - dbh.app_info_offset;
569
570         return MGU_SUCCESS;
571 }
572
573 /*
574  * Read address file into address list. Based on JPilot's
575  * libplugin.c (jp_get_app_info)
576  */
577 static gint jpilot_get_file_info( JPilotFile *pilotFile, unsigned char **buf, int *buf_size ) {
578         FILE *in;
579         int num;
580         unsigned int rec_size;
581         RawDBHeader rdbh;
582         DBHeader dbh;
583
584         if( ( !buf_size ) || ( ! buf ) ) {
585                 return MGU_BAD_ARGS;
586         }
587
588         *buf = NULL;
589         *buf_size=0;
590
591         if( pilotFile->path ) {
592                 in = g_fopen( pilotFile->path, "rb" );
593                 if( !in ) {
594                         return MGU_OPEN_FILE;
595                 }
596         }
597         else {
598                 return MGU_NO_FILE;
599         }
600
601         num = fread( &rdbh, sizeof( RawDBHeader ), 1, in );
602         if( num != 1 ) {
603                 if( ferror(in) ) {
604                         fclose(in);
605                         return MGU_ERROR_READ;
606                 }
607         }
608         if (feof(in)) {
609                 fclose(in);
610                 return MGU_EOF;
611         }
612
613         /* Convert header into something recognizable */
614         raw_header_to_header(&rdbh, &dbh);
615
616         num = jpilot_get_info_size(in, &rec_size);
617         if (num) {
618                 fclose(in);
619                 return MGU_ERROR_READ;
620         }
621
622         if (fseek(in, dbh.app_info_offset, SEEK_SET) < 0) {
623                 fclose(in);
624                 return MGU_ERROR_READ;
625         }
626         *buf = ( char * ) malloc(rec_size);
627         if (!(*buf)) {
628                 fclose(in);
629                 return MGU_OO_MEMORY;
630         }
631         num = fread(*buf, rec_size, 1, in);
632         if (num != 1) {
633                 if (ferror(in)) {
634                         fclose(in);
635                         free(*buf);
636                         return MGU_ERROR_READ;
637                 }
638         }
639         fclose(in);
640
641         *buf_size = rec_size;
642
643         return MGU_SUCCESS;
644 }
645
646 /* Shamelessly copied from JPilot (libplugin.c) */
647 static int unpack_header(PC3RecordHeader *header, unsigned char *packed_header) {
648         unsigned char *p;
649         guint32 l;
650
651         p = packed_header;
652
653         memcpy(&l, p, sizeof(l));
654         header->header_len=ntohl(l);
655         p+=sizeof(l);
656
657         memcpy(&l, p, sizeof(l));
658         header->header_version=ntohl(l);
659         p+=sizeof(l);
660
661         memcpy(&l, p, sizeof(l));
662         header->rec_len=ntohl(l);
663         p+=sizeof(l);
664
665         memcpy(&l, p, sizeof(l));
666         header->unique_id=ntohl(l);
667         p+=sizeof(l);
668
669         memcpy(&l, p, sizeof(l));
670         header->rt=ntohl(l);
671         p+=sizeof(l);
672
673         memcpy(&(header->attrib), p, sizeof(unsigned char));
674         p+=sizeof(unsigned char);
675
676         return 0;
677 }
678
679 /* Shamelessly copied from JPilot (libplugin.c) */
680 static int read_header(FILE *pc_in, PC3RecordHeader *header) {
681         guint32 l;
682         unsigned long len;
683         unsigned char packed_header[256];
684         int num;
685
686         memset(header, 0, sizeof(PC3RecordHeader));
687
688         num = fread(&l, sizeof(l), 1, pc_in);
689         if (feof(pc_in)) {
690                 return -1;
691         }
692         if (num!=1) {
693                 return num;
694         }
695         memcpy(packed_header, &l, sizeof(l));
696         len=ntohl(l);
697         if (len > 255 || len < sizeof(l)) {
698                 return -1;
699         }
700         num = fread(packed_header+sizeof(l), len-sizeof(l), 1, pc_in);
701         if (feof(pc_in)) {
702                 return -1;
703         }
704         if (num!=1) {
705                 return num;
706         }
707         unpack_header(header, packed_header);
708         return 1;
709 }
710
711 /**
712  * Read next record from PC3 file. Based on JPilot function
713  * <code>pc_read_next_rec()</code> (libplugin.c)
714  *
715  * \param in File handle.
716  * \param br Record buffer.
717  * \return Status/error code. <code>MGU_SUCCESS</code> if data read
718  *         successfully.
719  */
720 static gint jpilot_read_next_pc( FILE *in, buf_rec *br ) {
721         PC3RecordHeader header;
722         int rec_len, num;
723         char *record;
724
725         if( feof( in ) ) {
726                 return MGU_EOF;
727         }
728         num = read_header( in, &header );
729         if( num < 1 ) {
730                 if( ferror( in ) )
731                         return MGU_ERROR_READ;
732                 else if( feof( in ) )
733                         return MGU_EOF;
734                 else
735                         return -1;
736         }
737         rec_len = header.rec_len;
738         record = malloc( rec_len );
739         if( ! record ) {
740                 return MGU_OO_MEMORY;
741         }
742         num = fread( record, rec_len, 1, in );
743         if( num != 1 ) {
744                 if( ferror( in ) ) {
745                         free( record );
746                         return MGU_ERROR_READ;
747                 }
748         }
749         br->rt = header.rt;
750         br->unique_id = header.unique_id;
751         br->attrib = header.attrib;
752         br->buf = record;
753         br->size = rec_len;
754
755         return MGU_SUCCESS;
756 }
757
758 /**
759  * Read address file into a linked list. Based on JPilot function
760  * <code>jp_read_DB_files()</code> (from libplugin.c)
761  *
762  * \param pilotFile  JPilot control data.
763  * \param records Pointer to linked list of records read.
764  * \return Status/error code. <code>MGU_SUCCESS</code> if data read
765  *         successfully.
766  */
767 static gint jpilot_read_db_files( JPilotFile *pilotFile, GList **records ) {
768         FILE *in, *pc_in;
769         char *buf;
770         GList *temp_list;
771         int num_records, recs_returned, i, num, r;
772         unsigned int offset, prev_offset, next_offset = 0, rec_size;
773         int out_of_order;
774         long fpos;  /*file position indicator */
775         unsigned char attrib = '\0';
776         unsigned int unique_id = 0;
777         mem_rec_header *mem_rh, *temp_mem_rh, *last_mem_rh;
778         record_header rh;
779         RawDBHeader rdbh;
780         DBHeader dbh;
781         buf_rec *temp_br;
782         gchar *pcFile;
783
784         mem_rh = last_mem_rh = NULL;
785         *records = NULL;
786         recs_returned = 0;
787
788         if( pilotFile->path == NULL ) {
789                 return MGU_BAD_ARGS;
790         }
791
792         in = g_fopen( pilotFile->path, "rb" );
793         if (!in) {
794                 return MGU_OPEN_FILE;
795         }
796
797         /* Read the database header */
798         num = fread( &rdbh, sizeof( RawDBHeader ), 1, in );
799         if( num != 1 ) {
800                 if( ferror( in ) ) {
801                         fclose( in );
802                         return MGU_ERROR_READ;
803                 }
804                 if( feof( in ) ) {
805                         fclose( in );
806                         return MGU_EOF;
807                 }
808         }
809         raw_header_to_header( &rdbh, &dbh );
810
811         /* Read each record entry header */
812         num_records = dbh.number_of_records;
813         out_of_order = 0;
814         prev_offset = 0;
815
816         for( i = 1; i < num_records + 1; i++ ) {
817                 num = fread( &rh, sizeof( record_header ), 1, in );
818                 if( num != 1 ) {
819                         if( ferror( in ) ) {
820                                 break;
821                         }
822                         if( feof( in ) ) {
823                                 fclose( in );
824                                 if (mem_rh)
825                                         free_mem_rec_header( &mem_rh );
826                                 return MGU_EOF;
827                         }
828                 }
829
830                 offset =
831                         ( ( rh.Offset[0] * 256 + rh.Offset[1] ) * 256
832                         + rh.Offset[2] ) * 256
833                         + rh.Offset[3];
834                 if( offset < prev_offset ) {
835                         out_of_order = 1;
836                 }
837                 prev_offset = offset;
838                 temp_mem_rh = ( mem_rec_header * ) malloc( sizeof( mem_rec_header ) );
839                 if( ! temp_mem_rh ) {
840                         break;
841                 }
842                 temp_mem_rh->next = NULL;
843                 temp_mem_rh->rec_num = i;
844                 temp_mem_rh->offset = offset;
845                 temp_mem_rh->attrib = rh.attrib;
846                 temp_mem_rh->unique_id =
847                         ( rh.unique_ID[0] * 256 + rh.unique_ID[1] ) * 256
848                         + rh.unique_ID[2];
849                 if( mem_rh == NULL ) {
850                         mem_rh = temp_mem_rh;
851                         last_mem_rh = temp_mem_rh;
852                 }
853                 else {
854                         last_mem_rh->next = temp_mem_rh;
855                         last_mem_rh = temp_mem_rh;
856                 }
857         }
858
859         temp_mem_rh = mem_rh;
860
861         if( num_records ) {
862                 if( out_of_order ) {
863                         find_next_offset(
864                                 mem_rh, 0, &next_offset, &attrib, &unique_id );
865                 }
866                 else {
867                         if( mem_rh ) {
868                                 next_offset = mem_rh->offset;
869                                 attrib = mem_rh->attrib;
870                                 unique_id = mem_rh->unique_id;
871                         }
872                 }
873                 if (fseek( in, next_offset, SEEK_SET ) < 0) {
874                         free_mem_rec_header( &mem_rh );
875                         fclose(in);
876                         return MGU_ERROR_READ;
877                 }
878                 while( ! feof( in ) ) {
879                         fpos = ftell( in );
880                         if( out_of_order ) {
881                                 find_next_offset(
882                                         mem_rh, fpos, &next_offset, &attrib,
883                                         &unique_id );
884                         } else {
885                                 next_offset = 0xFFFFFF;
886                                 if( temp_mem_rh ) {
887                                         attrib = temp_mem_rh->attrib;
888                                         unique_id = temp_mem_rh->unique_id;
889                                         if ( temp_mem_rh->next ) {
890                                                 temp_mem_rh = temp_mem_rh->next;
891                                                 next_offset = temp_mem_rh->offset;
892                                         }
893                                 }
894                         }
895                         rec_size = next_offset - fpos;
896                         buf = malloc( rec_size );
897                         if( ! buf ) break;
898                         num = fread( buf, rec_size, 1, in );
899                         if( ( num != 1 ) ) {
900                                 if( ferror( in ) ) {
901                                         free( buf );
902                                         break;
903                                 }
904                         }
905
906                         temp_br = malloc( sizeof( buf_rec ) );
907                         if( ! temp_br ) {
908                                 free( buf );
909                                 break;
910                         }
911                         temp_br->rt = PALM_REC;
912                         temp_br->unique_id = unique_id;
913                         temp_br->attrib = attrib;
914                         temp_br->buf = buf;
915                         temp_br->size = rec_size;
916
917                         *records = g_list_append( *records, temp_br );
918
919                         recs_returned++;
920                 }
921         }
922         fclose( in );
923         free_mem_rec_header( &mem_rh );
924
925         /* Read the PC3 file, if present */
926         pcFile = jpilot_get_pc3_file( pilotFile );
927         if( pcFile == NULL ) return MGU_SUCCESS;
928         pc_in = g_fopen( pcFile, "rb");
929         g_free( pcFile );
930
931         if( pc_in == NULL ) {
932                 return MGU_SUCCESS;
933         }
934
935         while( ! feof( pc_in ) ) {
936                 gboolean linked;
937
938                 temp_br = malloc( sizeof( buf_rec ) );
939                 if( ! temp_br ) {
940                         break;
941                 }
942                 r = jpilot_read_next_pc( pc_in, temp_br );
943                 if( r != MGU_SUCCESS ) {
944                         if( (r != MGU_EOF) && (r != MGU_ERROR_READ) ) {
945                                 free( temp_br->buf );
946                         }
947                         free( temp_br );
948                         break;
949                 }
950
951                 linked = FALSE;
952                 if( ( temp_br->rt != DELETED_PC_REC )
953                  && ( temp_br->rt != DELETED_PALM_REC )
954                  && ( temp_br->rt != MODIFIED_PALM_REC )
955                  && ( temp_br->rt != DELETED_DELETED_PALM_REC ) )
956                 {
957                         *records = g_list_append( *records, temp_br );
958                         recs_returned++;
959                         linked = TRUE;
960                 }
961
962                 if( ( temp_br->rt == DELETED_PALM_REC )
963                  || ( temp_br->rt == MODIFIED_PALM_REC ) )
964                 {
965                         temp_list = *records;
966                         if( *records ) {
967                                 while( temp_list->next ) {
968                                         temp_list=temp_list->next;
969                                 }
970                         }
971                         for( ; temp_list; temp_list=temp_list->prev ) {
972                                 if( ( ( buf_rec * )temp_list->data )->unique_id ==
973                                     temp_br->unique_id ) {
974                                         ( ( buf_rec * )temp_list->data )->rt =
975                                                 temp_br->rt;
976                                 }
977                         }
978                 }
979
980                 if( ! linked ) {
981                         free( temp_br->buf );
982                         free( temp_br );
983                 }
984         }
985         fclose( pc_in );
986
987         return MGU_SUCCESS;
988 }
989
990 /**
991  * Parse buffer containing multiple e-mail addresses into a linked list of
992  * addresses. Separator characters are " ,;|" and control characters. Address
993  * is only extracted if it contains an "at" (@) character.
994  * 
995  * \param buf Buffer to process.
996  * \return List of strings.
997  */
998 static GList *jpilot_parse_email( gchar *buf ) {
999         GList *list;
1000         gchar *p, *st, *em;
1001         gchar lch;
1002         gint len;
1003         gboolean valid, done;
1004
1005         valid = done = FALSE;
1006         lch = ' ';
1007         list = NULL;
1008         p = st = buf;
1009         while( ! done ) {
1010                 if( *p == ' ' || *p == ',' || *p == ';' || *p == '|' || *p < 32 ) {
1011                         if( *p == '\0' ) {
1012                                 done = TRUE;
1013                         }
1014                         else {
1015                                 *p = ' ';
1016                         }
1017
1018                         if( *p == lch ) {
1019                                 st++;
1020                         }
1021                         else {
1022                                 len = p - st;
1023                                 if( len > 0 ) {
1024                                         if( valid ) {
1025                                                 em = g_strndup( st, len );
1026                                                 list = g_list_append( list, em );
1027                                         }
1028                                         st = p;
1029                                         ++st;
1030                                         valid = FALSE;
1031                                 }
1032                         }
1033                 }
1034                 if( *p == '@' ) valid = TRUE;
1035                 lch = *p;
1036                 ++p;
1037         }
1038
1039         return list;    
1040 }
1041
1042 #define FULLNAME_BUFSIZE        256
1043 #define EMAIL_BUFSIZE           256
1044
1045 /**
1046  * Process a single label entry field, parsing multiple e-mail address entries.
1047  *
1048  * \param pilotFile  JPilot control data.
1049  * \param labelEntry Label entry data.
1050  * \param person     Person.
1051  */
1052 static void jpilot_parse_label( JPilotFile *pilotFile, gchar *labelEntry, ItemPerson *person ) {
1053         gchar buffer[ EMAIL_BUFSIZE ];
1054         ItemEMail *email;
1055         GList *list, *node;
1056
1057         if( labelEntry ) {
1058                 *buffer = '\0';
1059                 g_strlcpy( buffer, labelEntry, sizeof(buffer) );
1060                 node = list = jpilot_parse_email( buffer );
1061                 while( node ) {
1062                         email = addritem_create_item_email();
1063                         addritem_email_set_address( email, node->data );
1064                         if (convert_charcode) {
1065                                 gchar *convertBuff = NULL;
1066                                 convertBuff = conv_codeset_strdup( labelEntry, 
1067                                                 jpilot_get_charset(), 
1068                                                 CS_INTERNAL );
1069                                 if (convertBuff)
1070                                         addritem_email_set_remarks( email, convertBuff );
1071                                 g_free( convertBuff );
1072                         }
1073                         else {
1074                                 addritem_email_set_remarks(email, buffer);
1075                         }
1076
1077                         addrcache_id_email( pilotFile->addressCache, email );
1078                         addrcache_person_add_email( pilotFile->addressCache, person, email );
1079                         node = g_list_next( node );
1080                 }
1081                 mgu_free_dlist( list );
1082                 list = NULL;
1083         }
1084 }
1085         
1086 /**
1087  * Unpack address, building new data inside cache.
1088  * \param pilotFile  JPilot control data.
1089  * \param buf        Record buffer.
1090  * \param folderInd  Array of (category) folders to load.
1091  */
1092 static void jpilot_load_address(
1093                 JPilotFile *pilotFile, buf_rec *buf, ItemFolder *folderInd[] )
1094 {
1095         struct Address addr;
1096         gchar **addrEnt;
1097         gint k;
1098         gint cat_id = 0;
1099         guint unique_id;
1100         guchar attrib;
1101         gchar fullName[ FULLNAME_BUFSIZE ];
1102         ItemPerson *person;
1103         gint *indPhoneLbl;
1104         gchar *labelEntry;
1105         GList *node;
1106         gchar* extID;
1107         gchar **firstName = NULL;
1108         gchar **lastName = NULL;
1109 #if (PILOT_LINK_MAJOR > 11)
1110         pi_buffer_t *RecordBuffer;
1111         RecordBuffer = pi_buffer_new(buf->size);
1112
1113         memcpy(RecordBuffer->data, buf->buf, buf->size);
1114         RecordBuffer->used = buf->size;
1115         if (unpack_Address(&addr, RecordBuffer, address_v1) == -1) {
1116                 pi_buffer_free(RecordBuffer);
1117                 return;
1118         }
1119         pi_buffer_free(RecordBuffer);
1120 #else
1121         gint num;
1122
1123         num = unpack_Address(&addr, buf->buf, buf->size);
1124         if (num <= 0) {
1125                 return;
1126         }
1127 #endif /* PILOT_LINK_0_12 */
1128
1129         addrEnt = addr.entry;
1130         attrib = buf->attrib;
1131         unique_id = buf->unique_id;
1132         cat_id = attrib & 0x0F;
1133
1134         *fullName = '\0';
1135         if( addrEnt[ IND_LABEL_FIRSTNAME ] ) {
1136                 firstName = g_strsplit( addrEnt[ IND_LABEL_FIRSTNAME ], "\01", 2 );
1137         }
1138
1139         if( addrEnt[ IND_LABEL_LASTNAME ] ) {
1140                 lastName = g_strsplit( addrEnt[ IND_LABEL_LASTNAME ], "\01", 2 );
1141         }
1142
1143         if( name_order == FAMILY_LAST ) {
1144                 g_snprintf( fullName, FULLNAME_BUFSIZE, "%s %s",
1145                             firstName ? firstName[0] : "",
1146                             lastName ? lastName[0] : "" );
1147         }
1148         else {
1149                 g_snprintf( fullName, FULLNAME_BUFSIZE, "%s %s",
1150                             lastName ? lastName[0] : "",
1151                             firstName ? firstName[0] : "" );
1152         }
1153
1154         if( firstName ) {
1155                 g_strfreev( firstName );
1156         }
1157         if( lastName ) {
1158                 g_strfreev( lastName );
1159         }
1160
1161         g_strstrip( fullName );
1162
1163         if( convert_charcode ) {
1164                 gchar *nameConv = NULL;
1165                 nameConv = conv_codeset_strdup( fullName, 
1166                                 jpilot_get_charset(), 
1167                                 CS_INTERNAL );
1168                 if (nameConv)
1169                         strncpy2( fullName, nameConv, FULLNAME_BUFSIZE );
1170                 g_free( nameConv );
1171         }
1172
1173         person = addritem_create_item_person();
1174         addritem_person_set_common_name( person, fullName );
1175         addritem_person_set_first_name( person, addrEnt[ IND_LABEL_FIRSTNAME ] );
1176         addritem_person_set_last_name( person, addrEnt[ IND_LABEL_LASTNAME ] );
1177         addrcache_id_person( pilotFile->addressCache, person );
1178
1179         extID = g_strdup_printf( "%d", unique_id );
1180         addritem_person_set_external_id( person, extID );
1181         g_free( extID );
1182         extID = NULL;
1183
1184         /* Add entry for each email address listed under phone labels. */
1185         indPhoneLbl = addr.phoneLabel;
1186         for( k = 0; k < JPILOT_NUM_ADDR_PHONE; k++ ) {
1187                 if( indPhoneLbl[k] == IND_PHONE_EMAIL ) {
1188                         labelEntry = addrEnt[ OFFSET_PHONE_LABEL + k ];
1189                         jpilot_parse_label( pilotFile, labelEntry, person );
1190                 }
1191         }
1192
1193         /* Add entry for each custom label */
1194         node = pilotFile->labelInd;
1195         while( node ) {
1196                 gint ind;
1197
1198                 ind = GPOINTER_TO_INT( node->data );
1199                 if( ind > -1 ) {
1200                         /*
1201                         * g_print( "%d : %20s : %s\n", ind, ai->labels[ind],
1202                         * addrEnt[ind] );
1203                         */
1204                         labelEntry = addrEnt[ind];
1205                         jpilot_parse_label( pilotFile, labelEntry, person );
1206                 }
1207
1208                 node = g_list_next( node );
1209         }
1210
1211         if( person->listEMail ) {
1212                 /* Add to specified category */
1213                 addrcache_folder_add_person(
1214                         pilotFile->addressCache,
1215                         folderInd[cat_id], person );
1216         }
1217         else {
1218                 addritem_free_item_person( person );
1219                 person = NULL;
1220         }
1221         /* Free up pointer allocated inside address */
1222         free_Address( & addr );
1223 }
1224
1225 /**
1226  * Free up address list.
1227  * \param records List of records to free.
1228  */
1229 static void jpilot_free_addrlist( GList *records ) {
1230         GList *node;
1231         buf_rec *br;
1232
1233         node = records;
1234         while( node ) {
1235                 br = node->data;
1236                 free( br->buf );
1237                 free( br );
1238                 node->data = NULL;
1239                 node = g_list_next( node );
1240         }
1241
1242         /* Free up list */
1243         g_list_free( records );
1244 }
1245
1246 /**
1247  * Read metadata from file.
1248  * \param pilotFile  JPilot control data.
1249  * \return Status/error code. <code>MGU_SUCCESS</code> if data read
1250  *         successfully.
1251  */
1252 static gint jpilot_read_metadata( JPilotFile *pilotFile ) {
1253         gint retVal;
1254         unsigned int rec_size;
1255         unsigned char *buf;
1256         int num;
1257
1258         cm_return_val_if_fail( pilotFile != NULL, -1 );
1259
1260         pilotFile->readMetadata = FALSE;
1261         addrcache_clear( pilotFile->addressCache );
1262
1263         /* Read file info */
1264         retVal = jpilot_get_file_info( pilotFile, &buf, &rec_size);
1265         if( retVal != MGU_SUCCESS ) {
1266                 pilotFile->retVal = retVal;
1267                 return pilotFile->retVal;
1268         }
1269
1270         num = unpack_AddressAppInfo( &pilotFile->addrInfo, buf, rec_size );
1271         if( buf ) {
1272                 free(buf);
1273         }
1274         if( num <= 0 ) {
1275                 pilotFile->retVal = MGU_ERROR_READ;
1276                 return pilotFile->retVal;
1277         }
1278
1279         pilotFile->readMetadata = TRUE;
1280         pilotFile->retVal = MGU_SUCCESS;
1281         return pilotFile->retVal;
1282 }
1283
1284 /**
1285  * Setup labels and indexes from metadata.
1286  * \param pilotFile  JPilot control data.
1287  * \return <i>TRUE</i> is setup successfully.
1288  */
1289 static gboolean jpilot_setup_labels( JPilotFile *pilotFile ) {
1290         gboolean retVal = FALSE;
1291         struct AddressAppInfo *ai;
1292         GList *node;
1293
1294         cm_return_val_if_fail( pilotFile != NULL, -1 );
1295
1296         /* Release indexes */
1297         node = pilotFile->labelInd;
1298         while( node ) {
1299                 node->data = NULL;
1300                 node = g_list_next( node );
1301         }
1302         pilotFile->labelInd = NULL;
1303
1304         if( pilotFile->readMetadata ) {
1305                 ai = & pilotFile->addrInfo;
1306                 node = pilotFile->customLabels;
1307                 while( node ) {
1308                         gchar *lbl = node->data;
1309                         gint ind = -1;
1310                         gint i;
1311                         for( i = 0; i < JPILOT_NUM_LABELS; i++ ) {
1312                                 gchar *labelName = ai->labels[i];
1313
1314                                 if( convert_charcode ) {
1315                                         gchar *convertBuff = NULL;
1316                                         convertBuff = conv_codeset_strdup( labelName, 
1317                                                         jpilot_get_charset(), 
1318                                                         CS_INTERNAL );
1319                                         if (convertBuff) {
1320                                                 labelName = convertBuff;
1321                                         }
1322                                 }
1323
1324                                 if( g_utf8_collate( labelName, lbl ) == 0 ) {
1325                                         ind = i;
1326                                         break;
1327                                 }
1328                         }
1329                         pilotFile->labelInd = g_list_append(
1330                                 pilotFile->labelInd, GINT_TO_POINTER(ind) );
1331                         node = g_list_next( node );
1332                 }
1333                 retVal = TRUE;
1334         }
1335         return retVal;
1336 }
1337
1338 /**
1339  * Load list with character strings of custom label names. Only none blank
1340  * names are loaded.
1341  * \param pilotFile  JPilot control data.
1342  * \param labelList List of label names to load.
1343  * \return List of label names loaded. Should be freed when done.
1344  */
1345 GList *jpilot_load_custom_label( JPilotFile *pilotFile, GList *labelList ) {
1346         gint i;
1347
1348         cm_return_val_if_fail( pilotFile != NULL, NULL );
1349
1350         if( pilotFile->readMetadata ) {
1351                 struct AddressAppInfo *ai = & pilotFile->addrInfo;
1352                 for( i = 0; i < NUM_CUSTOM_LABEL; i++ ) {
1353                         gchar *labelName = ai->labels[i+IND_CUSTOM_LABEL];
1354                         if( labelName ) {
1355                                 g_strstrip( labelName );
1356                                 if( *labelName != '\0' ) {
1357                                         if( convert_charcode ) {
1358                                                 gchar *convertBuff = NULL;
1359                                                 convertBuff = conv_codeset_strdup( labelName, 
1360                                                                 jpilot_get_charset(), 
1361                                                                 CS_INTERNAL );
1362                                                 if (convertBuff) {
1363                                                         labelName = convertBuff;
1364                                                 }
1365                                         }
1366                                         else {
1367                                                 labelName = g_strdup( labelName );
1368                                         }
1369                                         labelList = g_list_append( labelList, labelName );
1370                                 }
1371                         }
1372                 }
1373         }
1374         return labelList;
1375 }
1376
1377 /**
1378  * Build folder in address book for each category.
1379  * \param pilotFile  JPilot control data.
1380  */
1381 static void jpilot_build_category_list( JPilotFile *pilotFile ) {
1382         struct AddressAppInfo *ai = & pilotFile->addrInfo;
1383         struct CategoryAppInfo *cat = & ai->category;
1384         gint i;
1385
1386         for( i = 0; i < JPILOT_NUM_CATEG; i++ ) {
1387                 ItemFolder *folder = addritem_create_item_folder();
1388
1389                 if( convert_charcode ) {
1390                         gchar *convertBuff = NULL;
1391                         convertBuff = conv_codeset_strdup( cat->name[i], 
1392                                         jpilot_get_charset(), 
1393                                         CS_INTERNAL );
1394                         if (convertBuff) {
1395                                 addritem_folder_set_name( folder, convertBuff );
1396                                 g_free( convertBuff );
1397                         } else {
1398                                 addritem_folder_set_name( folder, cat->name[i] );
1399                         }
1400                 }
1401                 else {
1402                         addritem_folder_set_name( folder, cat->name[i] );
1403                 }
1404
1405                 addrcache_id_folder( pilotFile->addressCache, folder );
1406                 addrcache_add_folder( pilotFile->addressCache, folder );
1407         }
1408 }
1409
1410 /**
1411  * Remove empty (category) folders.
1412  * \param pilotFile  JPilot control data.
1413  */
1414 static void jpilot_remove_empty( JPilotFile *pilotFile ) {
1415         GList *listFolder;
1416         GList *remList;
1417         GList *node;
1418         gint i = 0;
1419
1420         listFolder = addrcache_get_list_folder( pilotFile->addressCache );
1421         node = listFolder;
1422         remList = NULL;
1423         while( node ) {
1424                 ItemFolder *folder = node->data;
1425                 if( ADDRITEM_NAME(folder) == NULL || *ADDRITEM_NAME(folder) == '\0' ) {
1426                         if( folder->listPerson ) {
1427                                 /* Give name to folder */
1428                                 gchar name[20];
1429                                 sprintf( name, "? %d", i );
1430                                 addritem_folder_set_name( folder, name );
1431                         }
1432                         else {
1433                                 /* Mark for removal */
1434                                 remList = g_list_append( remList, folder );
1435                         }
1436                 }
1437                 node = g_list_next( node );
1438                 i++;
1439         }
1440         node = remList;
1441         while( node ) {
1442                 ItemFolder *folder = node->data;
1443                 addrcache_remove_folder( pilotFile->addressCache, folder );
1444                 node = g_list_next( node );
1445         }
1446         g_list_free( remList );
1447 }
1448
1449 /**
1450  * Read address file into address cache.
1451  * \param pilotFile  JPilot control data.
1452  * \return Error/status code. <code>MGU_SUCCESS</code> if data read
1453  *         successfully.
1454  */
1455 static gint jpilot_read_file( JPilotFile *pilotFile ) {
1456         gint retVal, i;
1457         GList *records = NULL;
1458         GList *node;
1459         buf_rec *br;
1460         ItemFolder *folderInd[ JPILOT_NUM_CATEG ];
1461
1462         /* Read list of records from JPilot files */
1463         retVal = jpilot_read_db_files( pilotFile, &records );
1464         if( retVal != MGU_SUCCESS ) {
1465                 jpilot_free_addrlist( records );
1466                 return retVal;
1467         }
1468
1469         /* Setup labels and category folders */
1470         jpilot_setup_labels( pilotFile );
1471         jpilot_build_category_list( pilotFile );
1472
1473         /* Build array of pointers to categories */
1474         i = 0;
1475         node = addrcache_get_list_folder( pilotFile->addressCache );
1476         while( node ) {
1477                 if( i < JPILOT_NUM_CATEG ) {
1478                         folderInd[i] = node->data;
1479                 }
1480                 node = g_list_next( node );
1481                 i++;
1482         }
1483
1484         /* Load all addresses, free up old stuff as we go */
1485         node = records;
1486         while( node ) {
1487                 br = node->data;
1488                 if( ( br->rt != DELETED_PC_REC ) &&
1489                     ( br->rt != DELETED_PALM_REC ) &&
1490                     ( br->rt != MODIFIED_PALM_REC ) &&
1491                     ( br->rt != DELETED_DELETED_PALM_REC ) ) {
1492                         jpilot_load_address( pilotFile, br, folderInd );
1493                 }
1494                 free( br->buf );
1495                 free( br );
1496                 node->data = NULL;
1497                 node = g_list_next( node );
1498         }
1499
1500         /* Free up list */
1501         g_list_free( records );
1502
1503         /* Remove empty category folders */
1504         jpilot_remove_empty( pilotFile );
1505         jpilot_mark_files( pilotFile );
1506
1507         return retVal;
1508 }
1509
1510 /**
1511  * Read file into list. Main entry point
1512  * \param pilotFile  JPilot control data.
1513  * \return Error/status code. <code>MGU_SUCCESS</code> if data read
1514  *         successfully.
1515  */
1516 gint jpilot_read_data( JPilotFile *pilotFile ) {
1517         const gchar *cur_locale;
1518
1519         name_order = FAMILY_LAST;
1520
1521         cur_locale = conv_get_current_locale();
1522
1523         if( g_ascii_strncasecmp( cur_locale, "hu", 2 ) == 0 ||
1524                 g_ascii_strncasecmp( cur_locale, "ja", 2 ) == 0 ||
1525                 g_ascii_strncasecmp( cur_locale, "ko", 2 ) == 0 ||
1526                 g_ascii_strncasecmp( cur_locale, "vi", 2 ) == 0 ||
1527                 g_ascii_strncasecmp( cur_locale, "zh", 2 ) == 0 ) {
1528                 name_order = FAMILY_FIRST;
1529         }
1530
1531         cm_return_val_if_fail( pilotFile != NULL, -1 );
1532
1533         pilotFile->retVal = MGU_SUCCESS;
1534         pilotFile->addressCache->accessFlag = FALSE;
1535         if( jpilot_check_files( pilotFile ) ) {
1536                 addrcache_clear( pilotFile->addressCache );
1537                 jpilot_read_metadata( pilotFile );
1538                 if( pilotFile->retVal == MGU_SUCCESS ) {
1539                         pilotFile->retVal = jpilot_read_file( pilotFile );
1540                         if( pilotFile->retVal == MGU_SUCCESS ) {
1541                                 pilotFile->addressCache->modified = FALSE;
1542                                 pilotFile->addressCache->dataRead = TRUE;
1543                         }
1544                 }
1545         }
1546         return pilotFile->retVal;
1547 }
1548
1549 /**
1550  * Return linked list of persons. This is a list of references to ItemPerson
1551  * objects. Do <b>NOT</b> attempt to use the <code>addrbook_free_xxx()</code>
1552  * functions... this will destroy the addressbook data!
1553  *
1554  * \param  pilotFile  JPilot control data.
1555  * \return List of persons.
1556  */
1557 GList *jpilot_get_list_person( JPilotFile *pilotFile ) {
1558         cm_return_val_if_fail( pilotFile != NULL, NULL );
1559         return addrcache_get_list_person( pilotFile->addressCache );
1560 }
1561
1562 /**
1563  * Return linked list of folders. This is a list of references to non-empty
1564  * category folders. Do <b>NOT</b> attempt to use the
1565  * <code>addrbook_free_xxx()</code> functions... this will destroy the
1566  * addressbook data!
1567  *
1568  * \param  pilotFile  JPilot control data.
1569  * \return List of ItemFolder objects. This should not be freed.
1570  */
1571 GList *jpilot_get_list_folder( JPilotFile *pilotFile ) {
1572         cm_return_val_if_fail( pilotFile != NULL, NULL );
1573         return addrcache_get_list_folder( pilotFile->addressCache );
1574 }
1575
1576 /**
1577  * Return linked list of all persons. Note that the list contains references
1578  * to items. Do <b>NOT</b> attempt to use the <code>addrbook_free_xxx()</code>
1579  * functions... this will destroy the addressbook data!
1580  *
1581  * \param pilotFile  JPilot control data.
1582  * \return List of items, or NULL if none.
1583  */
1584 GList *jpilot_get_all_persons( JPilotFile *pilotFile ) {
1585         cm_return_val_if_fail( pilotFile != NULL, NULL );
1586         return addrcache_get_all_persons( pilotFile->addressCache );
1587 }
1588
1589 #define WORK_BUFLEN 1024
1590
1591 /**
1592  * Attempt to find a valid JPilot file.
1593  * \param pilotFile  JPilot control data.
1594  * \return Filename, or home directory if not found, or empty string if
1595  *         no home. Filename should be <code>g_free()</code> when done.
1596  */
1597 gchar *jpilot_find_pilotdb( void ) {
1598         const gchar *homedir;
1599         gchar str[ WORK_BUFLEN + 1 ];
1600         gint len;
1601         FILE *fp;
1602
1603         homedir = get_home_dir();
1604         if( ! homedir ) return g_strdup( "" );
1605
1606         g_strlcpy( str, homedir , sizeof(str));
1607         len = strlen( str );
1608         if( len > 0 ) {
1609                 if( str[ len-1 ] != G_DIR_SEPARATOR ) {
1610                         str[ len ] = G_DIR_SEPARATOR;
1611                         str[ ++len ] = '\0';
1612                 }
1613         }
1614         strncat( str, JPILOT_DBHOME_DIR, WORK_BUFLEN - strlen(str) );
1615         strncat( str, G_DIR_SEPARATOR_S, WORK_BUFLEN - strlen(str) );
1616         strncat( str, JPILOT_DBHOME_FILE, WORK_BUFLEN - strlen(str) );
1617
1618         /* Attempt to open */
1619         if( ( fp = g_fopen( str, "rb" ) ) != NULL ) {
1620                 fclose( fp );
1621         }
1622         else {
1623                 /* Truncate filename */
1624                 str[ len ] = '\0';
1625         }
1626         return g_strdup( str );
1627 }
1628
1629 /**
1630  * Check whether label is in list of custom labels.
1631  * \param pilotFile JPilot control data.
1632  * \param labelName to test.
1633  * \return <i>TRUE</i> if found.
1634  */
1635 gboolean jpilot_test_custom_label( JPilotFile *pilotFile, const gchar *labelName ) {
1636         gboolean retVal;
1637         GList *node;
1638
1639         cm_return_val_if_fail( pilotFile != NULL, FALSE );
1640
1641         retVal = FALSE;
1642         if( labelName ) {
1643                 node = pilotFile->customLabels;
1644                 while( node ) {
1645                         if( g_utf8_collate( labelName, ( gchar * ) node->data ) == 0 ) {
1646                                 retVal = TRUE;
1647                                 break;
1648                         }
1649                         node = g_list_next( node );
1650                 }
1651         }
1652         return retVal;
1653 }
1654
1655 /**
1656  * Test whether pilot link library installed.
1657  * \return <i>TRUE</i> if library available.
1658  */
1659 gboolean jpilot_test_pilot_lib( void ) {
1660         return TRUE;
1661 }
1662
1663 #endif  /* USE_JPILOT */
1664
1665 /*
1666 * End of Source.
1667 */