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