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