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