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