added SSL support for POP using OpenSSL
[claws.git] / src / jpilot.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 2001 Match Grun
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18  */
19
20 /*
21  * Functions necessary to access JPilot database files.
22  * JPilot is Copyright(c) by Judd Montgomery.
23  * Visit http://www.jpilot.org for more details.
24  */
25
26 #ifdef HAVE_CONFIG_H
27 #  include "config.h"
28 #endif
29
30 #ifdef USE_JPILOT
31
32 #include <time.h>
33 #include <stdio.h>
34 #include <sys/stat.h>
35 #include <dlfcn.h>
36
37 #include <pi-args.h>
38 #include <pi-appinfo.h>
39 #include <pi-address.h>
40
41 #include "mgutils.h"
42 #include "jpilot.h"
43
44 #define JPILOT_DBHOME_DIR   ".jpilot"
45 #define JPILOT_DBHOME_FILE  "AddressDB.pdb"
46 #define PILOT_LINK_LIB_NAME "libpisock.so"
47
48 #define IND_LABEL_LASTNAME  0   // Index of last name in address data
49 #define IND_LABEL_FIRSTNAME 1   // Index of first name in address data
50 #define IND_PHONE_EMAIL     4   // Index of E-Mail address in phone labels
51 #define OFFSET_PHONE_LABEL  3   // Offset to phone data in address data
52 #define IND_CUSTOM_LABEL    14  // Offset to custom label names
53 #define NUM_CUSTOM_LABEL    4   // Number of custom labels
54
55 typedef struct _JPilotCategory JPilotCategory;
56 struct _JPilotCategory {
57         AddressItem *category;
58         GList *addressList;
59         gint count;
60 };
61
62 /*
63 * Specify name to be used.
64 */
65 void jpilot_set_name( JPilotFile* pilotFile, const gchar *name ) {
66         if( pilotFile->name ) g_free( pilotFile->name );
67         if( name ) pilotFile->name = g_strdup( name );
68 }
69
70 /*
71 * Specify file to be used.
72 */
73 void jpilot_set_file( JPilotFile* pilotFile, const gchar *path ) {
74         g_return_if_fail( pilotFile != NULL );
75         mgu_refresh_cache( pilotFile->addressCache );
76         pilotFile->readMetadata = FALSE;
77
78         /* Copy file path */
79         if( pilotFile->path ) g_free( pilotFile->path );
80         if( path ) pilotFile->path = g_strdup( path );
81 }
82
83 /*
84 * Create new pilot file object.
85 */
86 JPilotFile *jpilot_create() {
87         JPilotFile *pilotFile;
88         pilotFile = g_new( JPilotFile, 1 );
89         pilotFile->name = NULL;
90         pilotFile->path = NULL;
91         pilotFile->file = NULL;
92         pilotFile->addressCache = mgu_create_cache();
93         pilotFile->readMetadata = FALSE;
94         pilotFile->customLabels = NULL;
95         pilotFile->labelInd = NULL;
96         pilotFile->retVal = MGU_SUCCESS;
97         pilotFile->categoryList = NULL;
98         pilotFile->catAddrList = NULL;
99         return pilotFile;
100 }
101
102 /*
103 * Create new pilot file object for specified file.
104 */
105 JPilotFile *jpilot_create_path( const gchar *path ) {
106         JPilotFile *pilotFile;
107         pilotFile = jpilot_create();
108         jpilot_set_file( pilotFile, path );
109         return pilotFile;
110 }
111
112 /*
113 * Test whether file was modified since last access.
114 * Return: TRUE if file was modified.
115 */
116 gboolean jpilot_get_modified( JPilotFile *pilotFile ) {
117         g_return_if_fail( pilotFile != NULL );
118         return mgu_check_file( pilotFile->addressCache, pilotFile->path );
119 }
120
121 /*
122 * Free up custom label list.
123 */
124 void jpilot_clear_custom_labels( JPilotFile *pilotFile ) {
125         GSList *node;
126         g_return_if_fail( pilotFile != NULL );
127
128         // Release custom labels
129         mgu_free_list( pilotFile->customLabels );
130         pilotFile->customLabels = NULL;
131
132         // Release indexes
133         node = pilotFile->labelInd;
134         while( node ) {
135                 node->data = NULL;
136                 node = g_slist_next( node );
137         }
138         g_slist_free( pilotFile->labelInd );
139         pilotFile->labelInd = NULL;
140
141         // Force a fresh read
142         mgu_refresh_cache( pilotFile->addressCache );
143 }
144
145 /*
146 * Append a custom label, representing an E-Mail address field to the
147 * custom label list.
148 */
149 void jpilot_add_custom_label( JPilotFile *pilotFile, const gchar *labelName ) {
150         g_return_if_fail( pilotFile != NULL );
151
152         if( labelName ) {
153                 gchar *labelCopy = g_strdup( labelName );
154                 g_strstrip( labelCopy );
155                 if( *labelCopy == '\0' ) {
156                         g_free( labelCopy );
157                 }
158                 else {
159                         pilotFile->customLabels = g_slist_append( pilotFile->customLabels, labelCopy );
160                         // Force a fresh read
161                         mgu_refresh_cache( pilotFile->addressCache );
162                 }
163         }
164 }
165
166 /*
167 * Get list of custom labels.
168 * Return: List of labels. Must use g_free() when done.
169 */
170 GList *jpilot_get_custom_labels( JPilotFile *pilotFile ) {
171         GList *retVal = NULL;
172         GSList *node;
173         g_return_if_fail( pilotFile != NULL );
174         node = pilotFile->customLabels;
175         while( node ) {
176                 retVal = g_list_append( retVal, g_strdup( node->data ) );
177                 node = g_slist_next( node );
178         }
179         return retVal;
180 }
181
182 /*
183 * Free up pilot file object by releasing internal memory.
184 */
185 void jpilot_free( JPilotFile *pilotFile ) {
186         g_return_if_fail( pilotFile != NULL );
187
188         /* Free internal stuff */
189         g_free( pilotFile->path );
190
191         // Release custom labels
192         jpilot_clear_custom_labels( pilotFile );
193
194         /* Clear cache */
195         mgu_clear_cache( pilotFile->addressCache );
196         mgu_free_cache( pilotFile->addressCache );
197         mgu_free_dlist( pilotFile->categoryList );
198         pilotFile->addressCache = NULL;
199         pilotFile->readMetadata = FALSE;
200         pilotFile->categoryList = NULL;
201         pilotFile->catAddrList = NULL;
202
203         /* Now release file object */
204         g_free( pilotFile );
205 }
206
207 /*
208 * Refresh internal variables to force a file read.
209 */
210 void jpilot_force_refresh( JPilotFile *pilotFile ) {
211         mgu_refresh_cache( pilotFile->addressCache );
212 }
213
214 /*
215 * Print category address list for specified category.
216 */
217 void jpilot_print_category( JPilotCategory *jpcat, FILE *stream ) {
218         fprintf( stream, "category: %s : count: %d\n", jpcat->category->name, jpcat->count );
219         if( jpcat->addressList ) {
220                 mgu_print_address_list( jpcat->addressList, stream );
221         }
222         fprintf( stream, "=========================================\n" );
223 }
224
225 /*
226 * Print address list for all categories.
227 */
228 void jpilot_print_category_list( GList *catAddrList, FILE *stream ) {
229         GList *node;
230         JPilotCategory *jpcat;
231         if( catAddrList == NULL ) return;
232         
233         fprintf( stream, "Address list by category\n" );
234         node = catAddrList;
235         while( node ) {
236                 jpcat = node->data;
237                 jpilot_print_category( jpcat, stream );
238                 node = g_list_next( node );
239         }
240 }
241
242 /*
243 * Print object to specified stream.
244 */
245 void jpilot_print_file( JPilotFile *pilotFile, FILE *stream ) {
246         GSList *node;
247         g_return_if_fail( pilotFile != NULL );
248         fprintf( stream, "JPilotFile:\n" );
249         fprintf( stream, "file spec: '%s'\n", pilotFile->path );
250         fprintf( stream, " metadata: %s\n", pilotFile->readMetadata ? "yes" : "no" );
251         fprintf( stream, "  ret val: %d\n", pilotFile->retVal );
252
253         node = pilotFile->customLabels;
254         while( node ) {
255                 fprintf( stream, "  c label: %s\n", node->data );
256                 node = g_slist_next( node );
257         }
258
259         node = pilotFile->labelInd;
260         while( node ) {
261                 fprintf( stream, " labelind: %d\n", GPOINTER_TO_INT(node->data) );
262                 node = g_slist_next( node );
263         }
264
265         mgu_print_cache( pilotFile->addressCache, stream );
266         jpilot_print_category_list( pilotFile->catAddrList, stream );
267 }
268
269 // Shamelessly copied from JPilot (libplugin.c)
270 static unsigned int bytes_to_bin(unsigned char *bytes, unsigned int num_bytes) {
271    unsigned int i, n;
272    n=0;
273    for (i=0;i<num_bytes;i++) {
274       n = n*256+bytes[i];
275    }
276    return n;
277 }
278
279 // Shamelessly copied from JPilot (utils.c)
280 /*These next 2 functions were copied from pi-file.c in the pilot-link app */
281 /* Exact value of "Jan 1, 1970 0:00:00 GMT" - "Jan 1, 1904 0:00:00 GMT" */
282 #define PILOT_TIME_DELTA (unsigned)(2082844800)
283
284 time_t pilot_time_to_unix_time ( unsigned long raw_time ) {
285    return (time_t)(raw_time - PILOT_TIME_DELTA);
286 }
287
288 // Shamelessly copied from JPilot (libplugin.c)
289 static int raw_header_to_header(RawDBHeader *rdbh, DBHeader *dbh) {
290    unsigned long temp;
291    strncpy(dbh->db_name, rdbh->db_name, 31);
292    dbh->db_name[31] = '\0';
293    dbh->flags = bytes_to_bin(rdbh->flags, 2);
294    dbh->version = bytes_to_bin(rdbh->version, 2);
295    temp = bytes_to_bin(rdbh->creation_time, 4);
296    dbh->creation_time = pilot_time_to_unix_time(temp);
297    temp = bytes_to_bin(rdbh->modification_time, 4);
298    dbh->modification_time = pilot_time_to_unix_time(temp);
299    temp = bytes_to_bin(rdbh->backup_time, 4);
300    dbh->backup_time = pilot_time_to_unix_time(temp);
301    dbh->modification_number = bytes_to_bin(rdbh->modification_number, 4);
302    dbh->app_info_offset = bytes_to_bin(rdbh->app_info_offset, 4);
303    dbh->sort_info_offset = bytes_to_bin(rdbh->sort_info_offset, 4);
304    strncpy(dbh->type, rdbh->type, 4);
305    dbh->type[4] = '\0';
306    strncpy(dbh->creator_id, rdbh->creator_id, 4);
307    dbh->creator_id[4] = '\0';
308    strncpy(dbh->unique_id_seed, rdbh->unique_id_seed, 4);
309    dbh->unique_id_seed[4] = '\0';
310    dbh->next_record_list_id = bytes_to_bin(rdbh->next_record_list_id, 4);
311    dbh->number_of_records = bytes_to_bin(rdbh->number_of_records, 2);
312    return 0;
313 }
314
315 // Shamelessly copied from JPilot (libplugin.c)
316 /*returns 1 if found */
317 /*        0 if eof */
318 static int find_next_offset( mem_rec_header *mem_rh, long fpos,
319         unsigned int *next_offset, unsigned char *attrib, unsigned int *unique_id )
320 {
321         mem_rec_header *temp_mem_rh;
322         unsigned char found = 0;
323         unsigned long found_at;
324
325         found_at=0xFFFFFF;
326         for (temp_mem_rh=mem_rh; temp_mem_rh; temp_mem_rh = temp_mem_rh->next) {
327                 if ((temp_mem_rh->offset > fpos) && (temp_mem_rh->offset < found_at)) {
328                         found_at = temp_mem_rh->offset;
329                         /* *attrib = temp_mem_rh->attrib; */
330                         /* *unique_id = temp_mem_rh->unique_id; */
331                 }
332                 if ((temp_mem_rh->offset == fpos)) {
333                         found = 1;
334                         *attrib = temp_mem_rh->attrib;
335                         *unique_id = temp_mem_rh->unique_id;
336                 }
337         }
338         *next_offset = found_at;
339         return found;
340 }
341
342 // Shamelessly copied from JPilot (libplugin.c)
343 static void free_mem_rec_header(mem_rec_header **mem_rh) {
344         mem_rec_header *h, *next_h;
345         for (h=*mem_rh; h; h=next_h) {
346                 next_h=h->next;
347                 free(h);
348         }
349         *mem_rh = NULL;
350 }
351
352 // Shamelessly copied from JPilot (libplugin.c)
353 int jpilot_free_db_list( GList **br_list ) {
354         GList *temp_list, *first;
355         buf_rec *br;
356
357         /* Go to first entry in the list */
358         first=NULL;
359         for( temp_list = *br_list; temp_list; temp_list = temp_list->prev ) {
360                 first = temp_list;
361         }
362         for (temp_list = first; temp_list; temp_list = temp_list->next) {
363                 if (temp_list->data) {
364                         br=temp_list->data;
365                         if (br->buf) {
366                                 free(br->buf);
367                                 temp_list->data=NULL;
368                         }
369                         free(br);
370                 }
371         }
372         g_list_free(*br_list);
373         *br_list=NULL;
374         return 0;
375 }
376
377 // Shamelessly copied from JPilot (libplugin.c)
378 // Read file size.
379 int jpilot_get_info_size( FILE *in, int *size ) {
380         RawDBHeader rdbh;
381         DBHeader dbh;
382         unsigned int offset;
383         record_header rh;
384
385         fseek(in, 0, SEEK_SET);
386         fread(&rdbh, sizeof(RawDBHeader), 1, in);
387         if (feof(in)) {
388                 // fprintf( stderr, "error reading file in 'jpilot_get_info_size'\n" );
389                 return MGU_EOF;
390         }
391
392         raw_header_to_header(&rdbh, &dbh);
393         if (dbh.app_info_offset==0) {
394                 *size=0;
395                 return MGU_SUCCESS;
396         }
397         if (dbh.sort_info_offset!=0) {
398                 *size = dbh.sort_info_offset - dbh.app_info_offset;
399                 return MGU_SUCCESS;
400         }
401         if (dbh.number_of_records==0) {
402                 fseek(in, 0, SEEK_END);
403                 *size=ftell(in) - dbh.app_info_offset;
404                 return MGU_SUCCESS;
405         }
406
407         fread(&rh, sizeof(record_header), 1, in);
408         offset = ((rh.Offset[0]*256+rh.Offset[1])*256+rh.Offset[2])*256+rh.Offset[3];
409         *size=offset - dbh.app_info_offset;
410
411         return MGU_SUCCESS;
412 }
413
414 // Read address file into address list. Based on JPilot's
415 // libplugin.c (jp_get_app_info)
416 gint jpilot_get_file_info( JPilotFile *pilotFile, unsigned char **buf, int *buf_size ) {
417         FILE *in;
418         int num;
419         unsigned int rec_size;
420         RawDBHeader rdbh;
421         DBHeader dbh;
422
423         if( ( !buf_size ) || ( ! buf ) ) {
424                 return MGU_BAD_ARGS;
425         }
426
427         *buf = NULL;
428         *buf_size=0;
429
430         if( pilotFile->path ) {
431                 in = fopen( pilotFile->path, "r" );
432                 if( !in ) {
433                         // fprintf( stderr, "can't open %s\n", pilotFile->path );
434                         return MGU_OPEN_FILE;
435                 }
436         }
437         else {
438                 // fprintf( stderr, "file not specified\n" );
439                 return MGU_NO_FILE;
440         }
441
442         num = fread( &rdbh, sizeof( RawDBHeader ), 1, in );
443         if( num != 1 ) {
444                 if( ferror(in) ) {
445                         // fprintf( stderr, "error reading %s\n", pilotFile->path );
446                         fclose(in);
447                         return MGU_ERROR_READ;
448                 }
449         }
450         if (feof(in)) {
451                 fclose(in);
452                 return MGU_EOF;
453         }
454
455         // Convert header into something recognizable
456         raw_header_to_header(&rdbh, &dbh);
457
458         num = jpilot_get_info_size(in, &rec_size);
459         if (num) {
460                 fclose(in);
461                 return MGU_ERROR_READ;
462         }
463
464         fseek(in, dbh.app_info_offset, SEEK_SET);
465         *buf = ( char * ) malloc(rec_size);
466         if (!(*buf)) {
467                 // fprintf( stderr, "jpilot_get_file_info(): Out of memory\n" );
468                 fclose(in);
469                 return MGU_OO_MEMORY;
470         }
471         num = fread(*buf, rec_size, 1, in);
472         if (num != 1) {
473                 if (ferror(in)) {
474                         fclose(in);
475                         free(*buf);
476                         // fprintf( stderr, "Error reading %s\n", pilotFile->path );
477                         return MGU_ERROR_READ;
478                 }
479         }
480         fclose(in);
481
482         *buf_size = rec_size;
483
484         return MGU_SUCCESS;
485 }
486
487 #define FULLNAME_BUFSIZE   256
488 #define EMAIL_BUFSIZE      256
489 // Read address file into address cache. Based on JPilot's
490 // jp_read_DB_files (from libplugin.c)
491 gint jpilot_read_cache( JPilotFile *pilotFile ) {
492         FILE *in;
493         char *buf;
494         int num_records, recs_returned, i, num, r;
495         unsigned int offset, prev_offset, next_offset, rec_size;
496         int out_of_order;
497         long fpos;  /*file position indicator */
498         unsigned char attrib;
499         unsigned int unique_id;
500         gint cat_id;
501         mem_rec_header *mem_rh, *temp_mem_rh, *last_mem_rh;
502         record_header rh;
503         RawDBHeader rdbh;
504         DBHeader dbh;
505         gint retVal;
506         struct Address addr;
507         struct AddressAppInfo *ai;
508         char **addrEnt;
509         int inum, k;
510         gchar fullName[ FULLNAME_BUFSIZE ];
511         gchar bufEMail[ EMAIL_BUFSIZE ];
512         gchar* extID;
513         AddressItem *addrItem = NULL;
514         int *indPhoneLbl;
515         char *labelEntry;
516         GSList *node;
517
518         retVal = MGU_SUCCESS;
519         mem_rh = last_mem_rh = NULL;
520         recs_returned = 0;
521
522         // Pointer to address metadata.
523         ai = & pilotFile->addrInfo;
524
525         // Open file for read
526         if( pilotFile->path ) {
527                 in = fopen( pilotFile->path, "r" );
528                 if( !in ) {
529                         // fprintf( stderr, "can't open %s\n", pilotFile->path );
530                         return MGU_OPEN_FILE;
531                 }
532         }
533         else {
534                 // fprintf( stderr, "file not specified\n" );
535                 return MGU_NO_FILE;
536         }
537
538         /* Read the database header */
539         num = fread(&rdbh, sizeof(RawDBHeader), 1, in);
540         if (num != 1) {
541                 if (ferror(in)) {
542                         // fprintf( stderr, "error reading '%s'\n", pilotFile->path );
543                         fclose(in);
544                         return MGU_ERROR_READ;
545                 }
546                 if (feof(in)) {
547                         return MGU_EOF;
548                 }
549         }
550
551         raw_header_to_header(&rdbh, &dbh);
552
553         /*Read each record entry header */
554         num_records = dbh.number_of_records;
555         out_of_order = 0;
556         prev_offset = 0;
557
558         for (i=1; i<num_records+1; i++) {
559                 num = fread( &rh, sizeof( record_header ), 1, in );
560                 if (num != 1) {
561                         if (ferror(in)) {
562                                 // fprintf( stderr, "error reading '%s'\n", pilotFile->path );
563                                 retVal = MGU_ERROR_READ;
564                                 break;
565                         }
566                         if (feof(in)) {
567                                 return MGU_EOF;
568                         }
569                 }
570
571                 offset = ((rh.Offset[0]*256+rh.Offset[1])*256+rh.Offset[2])*256+rh.Offset[3];
572                 if (offset < prev_offset) {
573                         out_of_order = 1;
574                 }
575                 prev_offset = offset;
576
577                 temp_mem_rh = (mem_rec_header *)malloc(sizeof(mem_rec_header));
578                 if (!temp_mem_rh) {
579                         // fprintf( stderr, "jpilot_read_db_file(): Out of memory 1\n" );
580                         retVal = MGU_OO_MEMORY;
581                         break;
582                 }
583
584                 temp_mem_rh->next = NULL;
585                 temp_mem_rh->rec_num = i;
586                 temp_mem_rh->offset = offset;
587                 temp_mem_rh->attrib = rh.attrib;
588                 temp_mem_rh->unique_id = (rh.unique_ID[0]*256+rh.unique_ID[1])*256+rh.unique_ID[2];
589
590                 if (mem_rh == NULL) {
591                         mem_rh = temp_mem_rh;
592                         last_mem_rh = temp_mem_rh;
593                 }
594                 else {
595                         last_mem_rh->next = temp_mem_rh;
596                         last_mem_rh = temp_mem_rh;
597                 }
598
599         }       // for( ;; )
600
601         temp_mem_rh = mem_rh;
602
603         if (num_records) {
604                 if (out_of_order) {
605                         find_next_offset(mem_rh, 0, &next_offset, &attrib, &unique_id);
606                 }
607                 else {
608                         if (mem_rh) {
609                                 next_offset = mem_rh->offset;
610                                 attrib = mem_rh->attrib;
611                                 unique_id = mem_rh->unique_id;
612                         }
613                 }
614                 fseek(in, next_offset, SEEK_SET);
615
616                 // Now go load all records              
617                 while(!feof(in)) {
618                         struct CategoryAppInfo *cat = & ai->category;
619                         fpos = ftell(in);
620                         if (out_of_order) {
621                                 find_next_offset(mem_rh, fpos, &next_offset, &attrib, &unique_id);
622                         }
623                         else {
624                                 next_offset = 0xFFFFFF;
625                                 if (temp_mem_rh) {
626                                         attrib = temp_mem_rh->attrib;
627                                         unique_id = temp_mem_rh->unique_id;
628                                         cat_id = attrib & 0x0F;
629                                         if (temp_mem_rh->next) {
630                                                 temp_mem_rh = temp_mem_rh->next;
631                                                 next_offset = temp_mem_rh->offset;
632                                         }
633                                 }
634                         }
635                         rec_size = next_offset - fpos;
636
637                         buf = ( char * ) malloc(rec_size);
638                         if (!buf) break;
639                         num = fread(buf, rec_size, 1, in);
640                         if ((num != 1)) {
641                                 if (ferror(in)) {
642                                         // fprintf( stderr, "Error reading %s 5\n", pilotFile );
643                                         free(buf);
644                                         retVal = MGU_ERROR_READ;
645                                         break;
646                                 }
647                         }
648
649                         // Retrieve address
650                         inum = unpack_Address( & addr, buf, rec_size );
651                         if( inum > 0 ) {
652                                 addrEnt = addr.entry;
653
654                                 *fullName = *bufEMail = '\0';
655                                 if( addrEnt[ IND_LABEL_FIRSTNAME ] ) {
656                                         strcat( fullName, addrEnt[ IND_LABEL_FIRSTNAME ] );
657                                 }
658
659                                 if( addrEnt[ IND_LABEL_LASTNAME ] ) {
660                                         strcat( fullName, " " );
661                                         strcat( fullName, addrEnt[ IND_LABEL_LASTNAME ] );
662                                 }
663                                 g_strchug( fullName );
664                                 g_strchomp( fullName );
665                                 extID = g_strdup_printf( "%d", unique_id );
666
667                                 // Add entry for each email address listed under phone labels.
668                                 indPhoneLbl = addr.phoneLabel;
669                                 for( k = 0; k < JPILOT_NUM_ADDR_PHONE; k++ ) {
670                                         int ind;
671                                         ind = indPhoneLbl[k];
672                                         // fprintf( stdout, "%d : %d : %20s : %s\n", k, ind, ai->phoneLabels[ind], addrEnt[3+k] );
673                                         if( indPhoneLbl[k] == IND_PHONE_EMAIL ) {
674                                                 labelEntry = addrEnt[ OFFSET_PHONE_LABEL + k ];
675                                                 if( labelEntry ) {
676                                                         strcpy( bufEMail, labelEntry );
677                                                         g_strchug( bufEMail );
678                                                         g_strchomp( bufEMail );
679
680                                                         addrItem = mgu_create_address();
681                                                         addrItem->name = g_strdup( fullName );
682                                                         addrItem->address = g_strdup( bufEMail );
683                                                         addrItem->remarks = g_strdup( "" );
684                                                         addrItem->externalID = g_strdup( extID );
685                                                         addrItem->categoryID = cat_id;
686                                                         mgu_add_cache( pilotFile->addressCache, addrItem );
687                                                 }
688                                         }
689                                 }
690
691                                 // Add entry for each custom label
692                                 node = pilotFile->labelInd;
693                                 while( node ) {
694                                         gint ind;
695                                         ind = GPOINTER_TO_INT( node->data );
696                                         if( ind > -1 ) {
697                                                 // fprintf( stdout, "%d : %20s : %s\n", ind, ai->labels[ind], addrEnt[ind] );
698                                                 labelEntry = addrEnt[ind];
699                                                 if( labelEntry ) {
700                                                         strcpy( bufEMail, labelEntry );
701                                                         g_strchug( bufEMail );
702                                                         g_strchomp( bufEMail );
703
704                                                         addrItem = mgu_create_address();
705                                                         addrItem->name = g_strdup( fullName );
706                                                         addrItem->address = g_strdup( bufEMail );
707                                                         addrItem->remarks = g_strdup( ai->labels[ind] );
708                                                         addrItem->externalID = g_strdup( extID );
709                                                         addrItem->categoryID = cat_id;
710                                                         mgu_add_cache( pilotFile->addressCache, addrItem );
711                                                 }
712
713                                         }
714
715                                         node = g_slist_next( node );
716                                 }
717
718                                 g_free( extID );
719                                 extID = NULL;
720                         }
721                         recs_returned++;
722                 }
723         }
724         fclose(in);
725         free_mem_rec_header(&mem_rh);
726         return retVal;
727 }
728
729 /*
730 * Read metadata from file.
731 */
732 gint jpilot_read_metadata( JPilotFile *pilotFile ) {
733         gint retVal;
734         unsigned int rec_size;
735         unsigned char *buf;
736         int num;
737
738         g_return_if_fail( pilotFile != NULL );
739
740         pilotFile->readMetadata = FALSE;
741
742         // Read file info
743         retVal = jpilot_get_file_info( pilotFile, &buf, &rec_size);
744         if( retVal != MGU_SUCCESS ) {
745                 pilotFile->retVal = retVal;
746                 return pilotFile->retVal;
747         }
748
749         num = unpack_AddressAppInfo( &pilotFile->addrInfo, buf, rec_size );
750         if( buf ) {
751                 free(buf);
752         }
753         if( num <= 0 ) {
754                 // fprintf( stderr, "error reading '%s'\n", pilotFile->path );
755                 pilotFile->retVal = MGU_ERROR_READ;
756                 return pilotFile->retVal;
757         }
758
759         pilotFile->readMetadata = TRUE;
760         pilotFile->retVal = MGU_SUCCESS;
761         return pilotFile->retVal;
762 }
763
764 /*
765 * Setup labels and indexes from metadata.
766 * Return: TRUE is setup successfully.
767 */
768 gboolean jpilot_setup_labels( JPilotFile *pilotFile ) {
769         gboolean retVal = FALSE;
770         struct AddressAppInfo *ai;
771         GSList *node;
772
773         g_return_if_fail( pilotFile != NULL );
774
775         // Release indexes
776         node = pilotFile->labelInd;
777         while( node ) {
778                 node->data = NULL;
779                 node = g_slist_next( node );
780         }
781         pilotFile->labelInd = NULL;
782
783         if( pilotFile->readMetadata ) {
784                 ai = & pilotFile->addrInfo;
785                 node = pilotFile->customLabels;
786                 while( node ) {
787                         gchar *lbl, *labelName;
788                         int i;
789                         gint ind;
790                         ind = -1;
791                         lbl = node->data;
792                         for( i = 0; i < JPILOT_NUM_LABELS; i++ ) {
793                                 labelName = ai->labels[i];
794                                 if( g_strcasecmp( labelName, lbl ) == 0 ) {
795                                         ind = i;
796                                         break;
797                                 }
798                         }
799                         pilotFile->labelInd = g_slist_append( pilotFile->labelInd, GINT_TO_POINTER(ind) );
800                         node = g_slist_next( node );
801                 }
802                 retVal = TRUE;
803         }
804         return retVal;
805 }
806
807 /*
808 * Load list with character strings of label names.
809 */
810 GSList *jpilot_load_label( JPilotFile *pilotFile, GSList *labelList ) {
811         int i;
812         g_return_if_fail( pilotFile != NULL );
813         if( pilotFile->readMetadata ) {
814                 struct AddressAppInfo *ai = & pilotFile->addrInfo;
815                 for( i = 0; i < JPILOT_NUM_LABELS; i++ ) {
816                         gchar *labelName = ai->labels[i];
817                         if( labelName ) {
818                                 labelList = g_slist_append( labelList, g_strdup( labelName ) );
819                         }
820                         else {
821                                 labelList = g_slist_append( labelList, g_strdup( "" ) );
822                         }
823                 }
824         }
825         return labelList;
826 }
827
828 /*
829 * Return category name for specified category ID.
830 * Enter:  Category ID.
831 * Return: Name, or empty string if not invalid ID. Name should be g_free() when done.
832 */
833 gchar *jpilot_get_category_name( JPilotFile *pilotFile, gint catID ) {
834         gchar *catName = NULL;
835         g_return_if_fail( pilotFile != NULL );
836         if( pilotFile->readMetadata ) {
837                 struct AddressAppInfo *ai = & pilotFile->addrInfo;
838                 struct CategoryAppInfo *cat = & ai->category;
839                 if( catID < 0 || catID > JPILOT_NUM_CATEG ) {
840                 }
841                 else {
842                         catName = g_strdup( cat->name[catID] );
843                 }
844         }
845         if( ! catName ) catName = g_strdup( "" );
846         return catName;
847 }
848
849 /*
850 * Load list with character strings of phone label names.
851 */
852 GSList *jpilot_load_phone_label( JPilotFile *pilotFile, GSList *labelList ) {
853         int i;
854         g_return_if_fail( pilotFile != NULL );
855         if( pilotFile->readMetadata ) {
856                 struct AddressAppInfo *ai = & pilotFile->addrInfo;
857                 for( i = 0; i < JPILOT_NUM_PHONELABELS; i++ ) {
858                         gchar   *labelName = ai->phoneLabels[i];
859                         if( labelName ) {
860                                 labelList = g_slist_append( labelList, g_strdup( labelName ) );
861                         }
862                         else {
863                                 labelList = g_slist_append( labelList, g_strdup( "" ) );
864                         }
865                 }
866         }
867         return labelList;
868 }
869
870 /*
871 * Load list with character strings of label names. Only none blank names
872 * are loaded.
873 */
874 GList *jpilot_load_custom_label( JPilotFile *pilotFile, GList *labelList ) {
875         int i;
876         g_return_if_fail( pilotFile != NULL );
877
878         if( pilotFile->readMetadata ) {
879                 struct AddressAppInfo *ai = & pilotFile->addrInfo;
880                 for( i = 0; i < NUM_CUSTOM_LABEL; i++ ) {
881                         gchar *labelName = ai->labels[i+IND_CUSTOM_LABEL];
882                         if( labelName ) {
883                                 g_strchomp( labelName );
884                                 g_strchug( labelName );
885                                 if( *labelName != '\0' ) {
886                                         labelList = g_list_append( labelList, g_strdup( labelName ) );
887                                 }
888                         }
889                 }
890         }
891         return labelList;
892 }
893
894 /*
895 * Load list with character strings of category names.
896 */
897 GSList *jpilot_get_category_list( JPilotFile *pilotFile ) {
898         GSList *catList = NULL;
899         int i;
900         g_return_if_fail( pilotFile != NULL );
901         if( pilotFile->readMetadata ) {
902                 struct AddressAppInfo *ai = & pilotFile->addrInfo;
903                 struct CategoryAppInfo *cat = & ai->category;
904                 for( i = 0; i < JPILOT_NUM_CATEG; i++ ) {
905                         gchar *catName = cat->name[i];
906                         if( catName ) {
907                                 catList = g_slist_append( catList, g_strdup( catName ) );
908                         }
909                         else {
910                                 catList = g_slist_append( catList, g_strdup( "" ) );
911                         }
912                 }
913         }
914         return catList;
915 }
916
917 /*
918 * Free category address list.
919 */
920 void jpilot_free_address_list( JPilotFile *pilotFile ) {
921         GList *addrList;
922         g_return_if_fail( pilotFile != NULL );
923
924         addrList = pilotFile->catAddrList;
925         while( addrList ) {
926                 JPilotCategory *jpcat = addrList->data;
927                 mgu_free_address( jpcat->category );
928                 mgu_free_address_list( jpcat->addressList );
929                 jpcat->category = NULL;
930                 jpcat->addressList = NULL;
931                 jpcat->count = 0;
932                 g_free( jpcat );
933                 jpcat = NULL;
934                 addrList->data = NULL;
935                 addrList = g_list_next( addrList );
936         }
937         pilotFile->catAddrList = NULL;
938 }
939
940 /*
941 * Move data from address list and group into category address list.
942 */
943 void jpilot_load_category_items( JPilotFile *pilotFile ) {
944         JPilotCategory *jpcat[ 1 + JPILOT_NUM_CATEG ];
945         struct AddressAppInfo *ai = & pilotFile->addrInfo;
946         struct CategoryAppInfo *cat = & ai->category;
947         AddressItem *itemCat;
948         GList *addrList;
949         int i;
950
951         // Create array for data by category
952         for( i = 0; i < 1 + JPILOT_NUM_CATEG; i++ ) {
953                 itemCat = mgu_create_address_item( ADDR_CATEGORY );
954                 itemCat->categoryID = i;
955                 jpcat[i] = g_new( JPilotCategory, 1 );
956                 jpcat[i]->category = itemCat;
957                 jpcat[i]->addressList = NULL;
958                 jpcat[i]->count = 0;
959                 if( i < JPILOT_NUM_CATEG ) {
960                         gchar *catName = cat->name[i];
961                         if( catName && strlen( catName ) > 0 ) {
962                                 itemCat->name = g_strdup( catName );
963                         }
964                 }
965         }
966
967         // Process address list moving data to category
968         addrList = pilotFile->addressCache->addressList;
969         while( addrList ) {
970                 GList *addrLink;
971                 AddressItem *item;
972                 item = addrList->data;
973                 i = item->categoryID;
974                 if( i < 0 || i > JPILOT_NUM_CATEG ) i = JPILOT_NUM_CATEG; // Position at end of array
975
976                 // Add item to category list
977                 addrLink = jpcat[i]->addressList;
978                 addrLink = g_list_append( addrLink, item );
979                 jpcat[i]->addressList = addrLink;
980                 jpcat[i]->count++;
981
982                 // Clear from cache list
983                 addrList->data = NULL;
984                 addrList = g_list_next( addrList );
985         }
986
987         // Remove entries from address cache
988         mgu_clear_cache_null( pilotFile->addressCache );
989
990 /*
991         printf( "dump jpcArray[]...\n" );
992         for( i = 0; i < 1 + JPILOT_NUM_CATEG; i++ ) {
993                 jpilot_print_category( jpcat[i], stdout );
994         }
995 */
996
997         // Free up existing category address list
998         jpilot_free_address_list( pilotFile );
999
1000         // Move categories from array to category address list
1001         addrList = NULL;
1002         for( i = 0; i < 1 + JPILOT_NUM_CATEG; i++ ) {
1003                 if( jpcat[i]->count > 0 ) {
1004                         itemCat = jpcat[i]->category;
1005                         if( ! itemCat->name ) {
1006                                 // Create a category name
1007                                 itemCat->name = g_strdup_printf( "? %d", itemCat->categoryID );
1008                         }
1009                 }
1010                 addrList = g_list_append( addrList, jpcat[i] );
1011                 jpcat[i] = NULL;
1012         }
1013         pilotFile->catAddrList = addrList;
1014
1015         // jpilot_print_category_list( pilotFile->catAddrList, stdout );
1016
1017 }
1018
1019 /*
1020 * Build linked list of address items for each category.
1021 */
1022 GList *jpilot_build_category_list( JPilotFile *pilotFile ) {
1023         GList *catList = NULL;
1024         GList *node;
1025         node = pilotFile->catAddrList;
1026         while( node ) {
1027                 JPilotCategory *jpcat = node->data;
1028                 AddressItem *catItem = jpcat->category;
1029
1030                 catItem = jpcat->category;
1031                 if( jpcat->count > 0 || catItem->name ) {
1032                         AddressItem *itemNew = mgu_copy_address_item( catItem );
1033                         if( itemNew ) {
1034                                 catList = g_list_append( catList, itemNew );
1035                         }
1036                 }
1037                 node = g_list_next( node );
1038         }
1039         mgu_free_address_list( pilotFile->categoryList );
1040         pilotFile->categoryList = catList;
1041         // mgu_print_address_list( catList, stdout );
1042         return catList;
1043 }
1044
1045 // ============================================================================================
1046 /*
1047 * Read file into list. Main entry point
1048 * Return: TRUE if file read successfully.
1049 */
1050 // ============================================================================================
1051 gint jpilot_read_data( JPilotFile *pilotFile ) {
1052         g_return_if_fail( pilotFile != NULL );
1053         pilotFile->retVal = MGU_SUCCESS;
1054         if( mgu_check_file( pilotFile->addressCache, pilotFile->path ) ) {
1055                 mgu_clear_cache( pilotFile->addressCache );
1056                 jpilot_read_metadata( pilotFile );
1057                 if( pilotFile->retVal == MGU_SUCCESS ) {
1058                         jpilot_setup_labels( pilotFile );
1059                         pilotFile->retVal = jpilot_read_cache( pilotFile );
1060                         if( pilotFile->retVal == MGU_SUCCESS ) {
1061                                 mgu_mark_cache( pilotFile->addressCache, pilotFile->path );
1062                                 pilotFile->addressCache->modified = FALSE;
1063                                 pilotFile->addressCache->dataRead = TRUE;
1064                         }
1065                         jpilot_load_category_items( pilotFile );
1066                         jpilot_build_category_list( pilotFile );
1067                 }
1068         }
1069         return pilotFile->retVal;
1070 }
1071
1072 /*
1073 * Return linked list of address items for loaded category names.
1074 */
1075 GList *jpilot_get_category_items( JPilotFile *pilotFile ) {
1076         g_return_if_fail( pilotFile != NULL );
1077         return pilotFile->categoryList;
1078 }
1079
1080 /*
1081 * Return address list for specified category.
1082 */
1083 GList *jpilot_get_address_list_cat( JPilotFile *pilotFile, const gint catID ) {
1084         GList *addrList = NULL, *node;
1085         g_return_if_fail( pilotFile != NULL );
1086
1087         node = pilotFile->catAddrList;
1088         while( node ) {
1089                 JPilotCategory *jpcat = node->data;
1090                 AddressItem *catItem = jpcat->category;
1091                 if( catItem->categoryID == catID ) {
1092                         addrList = jpcat->addressList;
1093                         break;
1094                 }
1095                 node = g_list_next( node );
1096         }
1097         return addrList;
1098 }
1099
1100 /*
1101 * Return linked list of address items.
1102 */
1103 GList *jpilot_get_address_list( JPilotFile *pilotFile ) {
1104         g_return_if_fail( pilotFile != NULL );
1105         return pilotFile->addressCache->addressList;
1106 }
1107
1108 /*
1109 * Check label list for specified label.
1110 */
1111 gint jpilot_check_label( struct AddressAppInfo *ai, gchar *lblCheck ) {
1112         int i;
1113         gchar   *lblName;
1114         if( lblCheck == NULL ) return -1;
1115         if( strlen( lblCheck ) < 1 ) return -1;
1116         for( i = 0; i < JPILOT_NUM_LABELS; i++ ) {
1117                 lblName = ai->labels[i];
1118                 if( lblName ) {
1119                         if( strlen( lblName ) ) {
1120                                 if( g_strcasecmp( lblName, lblCheck ) == 0 ) return i;
1121                         }
1122                 }
1123         }
1124         return -2;
1125 }
1126
1127 /*
1128 * Validate that all parameters specified.
1129 * Return: TRUE if data is good.
1130 */
1131 gboolean jpilot_validate( const JPilotFile *pilotFile ) {
1132         gboolean retVal;
1133         g_return_if_fail( pilotFile != NULL );
1134
1135         retVal = TRUE;
1136         if( pilotFile->path ) {
1137                 if( strlen( pilotFile->path ) < 1 ) retVal = FALSE;
1138         }
1139         else {
1140                 retVal = FALSE;
1141         }
1142         if( pilotFile->name ) {
1143                 if( strlen( pilotFile->name ) < 1 ) retVal = FALSE;
1144         }
1145         else {
1146                 retVal = FALSE;
1147         }
1148         return retVal;
1149 }
1150
1151 #define WORK_BUFLEN 1024
1152
1153 /*
1154 * Attempt to find a valid JPilot file.
1155 * Return: Filename, or home directory if not found, or empty string if
1156 * no home. Filename should be g_free() when done.
1157 */
1158 gchar *jpilot_find_pilotdb( void ) {
1159         gchar *homedir;
1160         gchar str[ WORK_BUFLEN ];
1161         gint len;
1162         FILE *fp;
1163
1164         homedir = g_get_home_dir();
1165         if( ! homedir ) return g_strdup( "" );
1166
1167         strcpy( str, homedir );
1168         len = strlen( str );
1169         if( len > 0 ) {
1170                 if( str[ len-1 ] != G_DIR_SEPARATOR ) {
1171                         str[ len ] = G_DIR_SEPARATOR;
1172                         str[ ++len ] = '\0';
1173                 }
1174         }
1175         strcat( str, JPILOT_DBHOME_DIR );
1176         strcat( str, G_DIR_SEPARATOR_S );
1177         strcat( str, JPILOT_DBHOME_FILE );
1178
1179         // Attempt to open\e
1180         if( ( fp = fopen( str, "r" ) ) != NULL ) {
1181                 fclose( fp );
1182         }
1183         else {
1184                 // Truncate filename
1185                 str[ len ] = '\0';
1186         }
1187         return g_strdup( str );
1188 }
1189
1190 /*
1191 * Attempt to read file, testing for valid JPilot format.
1192 * Return: TRUE if file appears to be valid format.
1193 */
1194 gint jpilot_test_read_file( const gchar *fileSpec ) {
1195         JPilotFile *pilotFile;
1196         gint retVal;
1197         if( fileSpec ) {
1198                 pilotFile = jpilot_create_path( fileSpec );
1199                 retVal = jpilot_read_metadata( pilotFile );
1200                 jpilot_free( pilotFile );
1201                 pilotFile = NULL;
1202         }
1203         else {
1204                 retVal = MGU_NO_FILE;
1205         }
1206         return retVal;
1207 }
1208
1209 /*
1210 * Check whether label is in custom labels.
1211 * Return: TRUE if found.
1212 */
1213 gboolean jpilot_test_custom_label( JPilotFile *pilotFile, const gchar *labelName ) {
1214         gboolean retVal;
1215         GSList *node;
1216         g_return_if_fail( pilotFile != NULL );
1217
1218         retVal = FALSE;
1219         if( labelName ) {
1220                 node = pilotFile->customLabels;
1221                 while( node ) {
1222                         if( g_strcasecmp( labelName, node->data ) == 0 ) {
1223                                 retVal = TRUE;
1224                                 break;
1225                         }
1226                         node = g_slist_next( node );
1227                 }
1228         }
1229         return retVal;
1230 }
1231
1232 /*
1233 * Test whether pilot link library installed.
1234 * Return: TRUE if library available.
1235 */
1236 gboolean jpilot_test_pilot_lib() {
1237         void *handle, *fun;
1238
1239         handle = dlopen( PILOT_LINK_LIB_NAME, RTLD_LAZY );
1240         if( ! handle ) {
1241                 return FALSE;
1242         }
1243
1244         // Test for symbols we need
1245         fun = dlsym( handle, "unpack_Address" );
1246         if( ! fun ) {
1247                 dlclose( handle );
1248                 return FALSE;
1249         }
1250
1251         fun = dlsym( handle, "unpack_AddressAppInfo" );
1252         if( ! fun ) {
1253                 dlclose( handle );
1254                 return FALSE;
1255         }
1256         dlclose( handle );
1257         return TRUE;
1258 }
1259
1260 #endif  /* USE_JPILOT */
1261
1262 /*
1263 * End of Source.
1264 */