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