2 * Claws Mail -- a GTK based, lightweight, and fast e-mail client
3 * Copyright (C) 2001-2015 Match Grun and the Claws Mail team
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
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.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
20 * Functions necessary to access JPilot database files.
21 * JPilot is Copyright(c) by Judd Montgomery.
22 * Visit http://www.jpilot.org for more details.
27 #include "claws-features.h"
38 #include <netinet/in.h>
40 #ifdef HAVE_LIBPISOCK_PI_ARGS_H
41 # include <libpisock/pi-args.h>
42 # include <libpisock/pi-appinfo.h>
43 # include <libpisock/pi-address.h>
44 # include <libpisock/pi-version.h>
47 # include <pi-appinfo.h>
48 # include <pi-address.h>
49 # include <pi-version.h>
54 #include "addrcache.h"
57 #include "adbookbase.h"
59 #include "file-utils.h"
61 #define JPILOT_DBHOME_DIR ".jpilot"
62 #define JPILOT_DBHOME_FILE "AddressDB.pdb"
63 #define PILOT_LINK_LIB_NAME "libpisock.so"
65 #define IND_LABEL_LASTNAME 0 /* Index of last name in address data */
66 #define IND_LABEL_FIRSTNAME 1 /* Index of first name in address data */
67 #define IND_PHONE_EMAIL 4 /* Index of E-Mail address in phone labels */
68 #define OFFSET_PHONE_LABEL 3 /* Offset to phone data in address data */
69 #define IND_CUSTOM_LABEL 14 /* Offset to custom label names */
70 #define NUM_CUSTOM_LABEL 4 /* Number of custom labels */
72 /* Shamelessly copied from JPilot (libplugin.h) */
74 unsigned char db_name
[32];
75 unsigned char flags
[2];
76 unsigned char version
[2];
77 unsigned char creation_time
[4];
78 unsigned char modification_time
[4];
79 unsigned char backup_time
[4];
80 unsigned char modification_number
[4];
81 unsigned char app_info_offset
[4];
82 unsigned char sort_info_offset
[4];
83 unsigned char type
[4];/*Database ID */
84 unsigned char creator_id
[4];/*Application ID */
85 unsigned char unique_id_seed
[4];
86 unsigned char next_record_list_id
[4];
87 unsigned char number_of_records
[2];
90 /* Shamelessly copied from JPilot (libplugin.h) */
96 time_t modification_time
;
98 unsigned int modification_number
;
99 unsigned int app_info_offset
;
100 unsigned int sort_info_offset
;
101 char type
[5];/*Database ID */
102 char creator_id
[5];/*Application ID */
103 char unique_id_seed
[5];
104 unsigned int next_record_list_id
;
105 unsigned int number_of_records
;
108 /* Shamelessly copied from JPilot (libplugin.h) */
110 unsigned char Offset
[4]; /*4 bytes offset from BOF to record */
111 unsigned char attrib
;
112 unsigned char unique_ID
[3];
115 /* Shamelessly copied from JPilot (libplugin.h) */
116 typedef struct mem_rec_header_s
{
117 unsigned int rec_num
;
119 unsigned int unique_id
;
120 unsigned char attrib
;
121 struct mem_rec_header_s
*next
;
124 /* Shamelessly copied from JPilot (libplugin.h) */
125 #define SPENT_PC_RECORD_BIT 256
129 MODIFIED_PALM_REC
= 101L,
130 DELETED_PALM_REC
= 102L,
132 DELETED_PC_REC
= SPENT_PC_RECORD_BIT
+ 104L,
133 DELETED_DELETED_PALM_REC
= SPENT_PC_RECORD_BIT
+ 105L
136 /* Shamelessly copied from JPilot (libplugin.h) */
139 unsigned int unique_id
;
140 unsigned char attrib
;
145 /* Shamelessly copied from JPilot (libplugin.h) */
147 unsigned long header_len
;
148 unsigned long header_version
;
149 unsigned long rec_len
;
150 unsigned long unique_id
;
151 unsigned long rt
; /* Record Type */
152 unsigned char attrib
;
160 gboolean convert_charcode
= TRUE
;
162 static const gchar
*jpilot_get_charset(void)
164 static const gchar
*charset
= NULL
;
167 charset
= g_getenv("PILOT_CHARSET");
176 * Create new pilot file object.
177 * \return Initialized JPilot file object.
179 JPilotFile
*jpilot_create() {
180 JPilotFile
*pilotFile
;
181 pilotFile
= g_new0( JPilotFile
, 1 );
182 pilotFile
->type
= ADBOOKTYPE_JPILOT
;
183 pilotFile
->addressCache
= addrcache_create();
184 pilotFile
->retVal
= MGU_SUCCESS
;
186 pilotFile
->file
= NULL
;
187 pilotFile
->path
= NULL
;
188 pilotFile
->readMetadata
= FALSE
;
189 pilotFile
->customLabels
= NULL
;
190 pilotFile
->labelInd
= NULL
;
191 pilotFile
->havePC3
= FALSE
;
192 pilotFile
->pc3ModifyTime
= 0;
197 * Create new pilot file object for specified file.
198 * \param path Path to JPilot address book.
199 * \return Initialized JPilot file object.
201 JPilotFile
*jpilot_create_path( const gchar
*path
) {
202 JPilotFile
*pilotFile
;
203 pilotFile
= jpilot_create();
204 jpilot_set_file( pilotFile
, path
);
211 void jpilot_set_name( JPilotFile
* pilotFile
, const gchar
*value
) {
212 cm_return_if_fail( pilotFile
!= NULL
);
213 addrcache_set_name( pilotFile
->addressCache
, value
);
215 void jpilot_set_file( JPilotFile
* pilotFile
, const gchar
*value
) {
216 cm_return_if_fail( pilotFile
!= NULL
);
217 addrcache_refresh( pilotFile
->addressCache
);
218 pilotFile
->readMetadata
= FALSE
;
219 pilotFile
->path
= mgu_replace_string( pilotFile
->path
, value
);
221 void jpilot_set_accessed( JPilotFile
*pilotFile
, const gboolean value
) {
222 cm_return_if_fail( pilotFile
!= NULL
);
223 pilotFile
->addressCache
->accessFlag
= value
;
226 gint
jpilot_get_status( JPilotFile
*pilotFile
) {
227 cm_return_val_if_fail( pilotFile
!= NULL
, -1 );
228 return pilotFile
->retVal
;
230 ItemFolder
*jpilot_get_root_folder( JPilotFile
*pilotFile
) {
231 cm_return_val_if_fail( pilotFile
!= NULL
, NULL
);
232 return addrcache_get_root_folder( pilotFile
->addressCache
);
234 gchar
*jpilot_get_name( JPilotFile
*pilotFile
) {
235 cm_return_val_if_fail( pilotFile
!= NULL
, NULL
);
236 return addrcache_get_name( pilotFile
->addressCache
);
240 * Test whether file was read.
241 * \param pilotFile JPilot control data.
242 * \return <i>TRUE</i> if file was read.
244 gboolean
jpilot_get_read_flag( JPilotFile
*pilotFile
) {
245 cm_return_val_if_fail( pilotFile
!= NULL
, FALSE
);
246 return pilotFile
->addressCache
->dataRead
;
250 * Free up custom label list.
251 * \param pilotFile JPilot control data.
253 void jpilot_clear_custom_labels( JPilotFile
*pilotFile
) {
256 cm_return_if_fail( pilotFile
!= NULL
);
258 /* Release custom labels */
259 g_list_free_full( pilotFile
->customLabels
, g_free
);
260 pilotFile
->customLabels
= NULL
;
262 /* Release indexes */
263 node
= pilotFile
->labelInd
;
266 node
= g_list_next( node
);
268 g_list_free( pilotFile
->labelInd
);
269 pilotFile
->labelInd
= NULL
;
271 /* Force a fresh read */
272 addrcache_refresh( pilotFile
->addressCache
);
276 * Append a custom label, representing an E-Mail address field to the
278 * \param pilotFile JPilot control data.
280 void jpilot_add_custom_label( JPilotFile
*pilotFile
, const gchar
*labelName
) {
281 cm_return_if_fail( pilotFile
!= NULL
);
284 gchar
*labelCopy
= g_strdup( labelName
);
285 g_strstrip( labelCopy
);
286 if( *labelCopy
== '\0' ) {
290 pilotFile
->customLabels
= g_list_append( pilotFile
->customLabels
, labelCopy
);
291 /* Force a fresh read */
292 addrcache_refresh( pilotFile
->addressCache
);
298 * Get list of custom labels.
299 * \param pilotFile JPilot control data.
300 * \return List of labels. Must use g_free() when done.
302 GList
*jpilot_get_custom_labels( JPilotFile
*pilotFile
) {
303 GList
*retVal
= NULL
;
306 cm_return_val_if_fail( pilotFile
!= NULL
, NULL
);
308 node
= pilotFile
->customLabels
;
310 retVal
= g_list_append( retVal
, g_strdup( node
->data
) );
311 node
= g_list_next( node
);
317 * Return filespec of PC3 file corresponding to JPilot PDB file.
318 * \param pilotFile JPilot control data.
319 * \return File specification; should be g_free() when done.
321 static gchar
*jpilot_get_pc3_file( JPilotFile
*pilotFile
) {
325 if( pilotFile
== NULL
) return NULL
;
326 if( pilotFile
->path
== NULL
) return NULL
;
328 fileSpec
= g_strdup( pilotFile
->path
);
329 len
= strlen( fileSpec
);
332 for( i
= len
; i
> 0; i
-- ) {
333 if( *(fileSpec
+ i
) == '.' ) {
340 if( len
- pos
== 3 ) {
341 *r
++ = 'p'; *r
++ = 'c'; *r
= '3';
350 * Save PC3 file time to cache.
351 * \param pilotFile JPilot control data.
352 * \return <i>TRUE</i> if time marked.
354 static gboolean
jpilot_mark_files( JPilotFile
*pilotFile
) {
355 gboolean retVal
= FALSE
;
359 /* Mark PDB file cache */
360 retVal
= addrcache_mark_file( pilotFile
->addressCache
, pilotFile
->path
);
362 /* Now mark PC3 file */
363 pilotFile
->havePC3
= FALSE
;
364 pilotFile
->pc3ModifyTime
= 0;
365 pcFile
= jpilot_get_pc3_file( pilotFile
);
366 if( pcFile
== NULL
) return retVal
;
367 if( 0 == g_stat( pcFile
, &filestat
) ) {
368 pilotFile
->havePC3
= TRUE
;
369 pilotFile
->pc3ModifyTime
= filestat
.st_mtime
;
377 * Check whether JPilot PDB or PC3 file has changed by comparing
379 * \param pilotFile JPilot control data.
380 * \return <i>TRUE</i> if file has changed.
382 static gboolean
jpilot_check_files( JPilotFile
*pilotFile
) {
383 gboolean retVal
= TRUE
;
387 /* Check main file */
388 if( addrcache_check_file( pilotFile
->addressCache
, pilotFile
->path
) )
392 if( ! pilotFile
->havePC3
) return FALSE
;
393 pcFile
= jpilot_get_pc3_file( pilotFile
);
394 if( pcFile
== NULL
) return FALSE
;
396 if( 0 == g_stat( pcFile
, &filestat
) ) {
397 if( filestat
.st_mtime
== pilotFile
->pc3ModifyTime
) retVal
= FALSE
;
404 * Test whether file was modified since last access.
405 * Return: TRUE if file was modified.
407 gboolean
jpilot_get_modified( JPilotFile
*pilotFile
) {
408 cm_return_val_if_fail( pilotFile
!= NULL
, FALSE
);
409 pilotFile
->addressCache
->modified
= jpilot_check_files( pilotFile
);
410 return pilotFile
->addressCache
->modified
;
412 gboolean
jpilot_get_accessed( JPilotFile
*pilotFile
) {
413 cm_return_val_if_fail( pilotFile
!= NULL
, FALSE
);
414 return pilotFile
->addressCache
->accessFlag
;
418 * Free up pilot file object by releasing internal memory.
419 * \param pilotFile JPilot control data.
421 void jpilot_free( JPilotFile
*pilotFile
) {
422 cm_return_if_fail( pilotFile
!= NULL
);
424 /* Release custom labels */
425 jpilot_clear_custom_labels( pilotFile
);
428 addrcache_clear( pilotFile
->addressCache
);
429 addrcache_free( pilotFile
->addressCache
);
431 /* Free internal stuff */
432 g_free( pilotFile
->path
);
434 pilotFile
->file
= NULL
;
435 pilotFile
->path
= NULL
;
436 pilotFile
->readMetadata
= FALSE
;
437 pilotFile
->havePC3
= FALSE
;
438 pilotFile
->pc3ModifyTime
= 0;
440 pilotFile
->type
= ADBOOKTYPE_NONE
;
441 pilotFile
->addressCache
= NULL
;
442 pilotFile
->retVal
= MGU_SUCCESS
;
444 /* Now release file object */
448 /* Shamelessly copied from JPilot (libplugin.c) */
449 static unsigned int bytes_to_bin(unsigned char *bytes
, unsigned int num_bytes
) {
452 for (i
=0;i
<num_bytes
;i
++) {
458 /* Shamelessly copied from JPilot (utils.c) */
459 /* These next 2 functions were copied from pi-file.c in the pilot-link app */
460 /* Exact value of "Jan 1, 1970 0:00:00 GMT" - "Jan 1, 1904 0:00:00 GMT" */
461 #define PILOT_TIME_DELTA (unsigned)(2082844800)
463 static time_t pilot_time_to_unix_time ( unsigned long raw_time
) {
464 return (time_t)(raw_time
- PILOT_TIME_DELTA
);
467 /* Shamelessly copied from JPilot (libplugin.c) */
468 static int raw_header_to_header(RawDBHeader
*rdbh
, DBHeader
*dbh
) {
471 strncpy(dbh
->db_name
, rdbh
->db_name
, 31);
472 dbh
->db_name
[31] = '\0';
473 dbh
->flags
= bytes_to_bin(rdbh
->flags
, 2);
474 dbh
->version
= bytes_to_bin(rdbh
->version
, 2);
475 temp
= bytes_to_bin(rdbh
->creation_time
, 4);
476 dbh
->creation_time
= pilot_time_to_unix_time(temp
);
477 temp
= bytes_to_bin(rdbh
->modification_time
, 4);
478 dbh
->modification_time
= pilot_time_to_unix_time(temp
);
479 temp
= bytes_to_bin(rdbh
->backup_time
, 4);
480 dbh
->backup_time
= pilot_time_to_unix_time(temp
);
481 dbh
->modification_number
= bytes_to_bin(rdbh
->modification_number
, 4);
482 dbh
->app_info_offset
= bytes_to_bin(rdbh
->app_info_offset
, 4);
483 dbh
->sort_info_offset
= bytes_to_bin(rdbh
->sort_info_offset
, 4);
484 strncpy(dbh
->type
, rdbh
->type
, 4);
486 strncpy(dbh
->creator_id
, rdbh
->creator_id
, 4);
487 dbh
->creator_id
[4] = '\0';
488 strncpy(dbh
->unique_id_seed
, rdbh
->unique_id_seed
, 4);
489 dbh
->unique_id_seed
[4] = '\0';
490 dbh
->next_record_list_id
= bytes_to_bin(rdbh
->next_record_list_id
, 4);
491 dbh
->number_of_records
= bytes_to_bin(rdbh
->number_of_records
, 2);
495 /* Shamelessly copied from JPilot (libplugin.c) */
496 /* returns 1 if found */
498 static int find_next_offset( mem_rec_header
*mem_rh
, long fpos
,
499 unsigned int *next_offset
, unsigned char *attrib
, unsigned int *unique_id
)
501 mem_rec_header
*temp_mem_rh
;
502 unsigned char found
= 0;
503 unsigned long found_at
;
506 for (temp_mem_rh
=mem_rh
; temp_mem_rh
; temp_mem_rh
= temp_mem_rh
->next
) {
507 if ((temp_mem_rh
->offset
> fpos
) && (temp_mem_rh
->offset
< found_at
)) {
508 found_at
= temp_mem_rh
->offset
;
509 /* *attrib = temp_mem_rh->attrib; */
510 /* *unique_id = temp_mem_rh->unique_id; */
512 if (temp_mem_rh
->offset
== fpos
) {
514 *attrib
= temp_mem_rh
->attrib
;
515 *unique_id
= temp_mem_rh
->unique_id
;
518 *next_offset
= found_at
;
522 /* Shamelessly copied from JPilot (libplugin.c) */
523 static void free_mem_rec_header(mem_rec_header
**mem_rh
) {
524 mem_rec_header
*h
, *next_h
;
525 for (h
=*mem_rh
; h
; h
=next_h
) {
532 /* Shamelessly copied from JPilot (libplugin.c) */
534 static int jpilot_get_info_size( FILE *in
, int *size
) {
541 fseek(in
, 0, SEEK_SET
);
542 r
= claws_fread(&rdbh
, sizeof(RawDBHeader
), 1, in
);
544 return MGU_ERROR_READ
;
545 if (claws_feof(in
)) {
549 raw_header_to_header(&rdbh
, &dbh
);
550 if (dbh
.app_info_offset
==0) {
554 if (dbh
.sort_info_offset
!=0) {
555 *size
= dbh
.sort_info_offset
- dbh
.app_info_offset
;
558 if (dbh
.number_of_records
==0) {
559 fseek(in
, 0, SEEK_END
);
560 *size
=ftell(in
) - dbh
.app_info_offset
;
564 r
= claws_fread(&rh
, sizeof(record_header
), 1, in
);
566 return MGU_ERROR_READ
;
568 offset
= ((rh
.Offset
[0]*256+rh
.Offset
[1])*256+rh
.Offset
[2])*256+rh
.Offset
[3];
569 *size
=offset
- dbh
.app_info_offset
;
575 * Read address file into address list. Based on JPilot's
576 * libplugin.c (jp_get_app_info)
578 static gint
jpilot_get_file_info( JPilotFile
*pilotFile
, unsigned char **buf
, int *buf_size
) {
581 unsigned int rec_size
;
585 if( ( !buf_size
) || ( ! buf
) ) {
592 if( pilotFile
->path
) {
593 in
= claws_fopen( pilotFile
->path
, "rb" );
595 return MGU_OPEN_FILE
;
602 num
= claws_fread( &rdbh
, sizeof( RawDBHeader
), 1, in
);
604 if( claws_ferror(in
) ) {
606 return MGU_ERROR_READ
;
609 if (claws_feof(in
)) {
614 /* Convert header into something recognizable */
615 raw_header_to_header(&rdbh
, &dbh
);
617 num
= jpilot_get_info_size(in
, &rec_size
);
620 return MGU_ERROR_READ
;
623 if (fseek(in
, dbh
.app_info_offset
, SEEK_SET
) < 0) {
625 return MGU_ERROR_READ
;
627 *buf
= ( char * ) malloc(rec_size
);
630 return MGU_OO_MEMORY
;
632 num
= claws_fread(*buf
, rec_size
, 1, in
);
634 if (claws_ferror(in
)) {
637 return MGU_ERROR_READ
;
642 *buf_size
= rec_size
;
647 /* Shamelessly copied from JPilot (libplugin.c) */
648 static int unpack_header(PC3RecordHeader
*header
, unsigned char *packed_header
) {
654 memcpy(&l
, p
, sizeof(l
));
655 header
->header_len
=ntohl(l
);
658 memcpy(&l
, p
, sizeof(l
));
659 header
->header_version
=ntohl(l
);
662 memcpy(&l
, p
, sizeof(l
));
663 header
->rec_len
=ntohl(l
);
666 memcpy(&l
, p
, sizeof(l
));
667 header
->unique_id
=ntohl(l
);
670 memcpy(&l
, p
, sizeof(l
));
674 memcpy(&(header
->attrib
), p
, sizeof(unsigned char));
675 p
+=sizeof(unsigned char);
680 /* Shamelessly copied from JPilot (libplugin.c) */
681 static int read_header(FILE *pc_in
, PC3RecordHeader
*header
) {
684 unsigned char packed_header
[256];
687 memset(header
, 0, sizeof(PC3RecordHeader
));
689 num
= claws_fread(&l
, sizeof(l
), 1, pc_in
);
690 if (claws_feof(pc_in
)) {
696 memcpy(packed_header
, &l
, sizeof(l
));
698 if (len
> 255 || len
< sizeof(l
)) {
701 num
= claws_fread(packed_header
+sizeof(l
), len
-sizeof(l
), 1, pc_in
);
702 if (claws_feof(pc_in
)) {
708 unpack_header(header
, packed_header
);
713 * Read next record from PC3 file. Based on JPilot function
714 * <code>pc_read_next_rec()</code> (libplugin.c)
716 * \param in File handle.
717 * \param br Record buffer.
718 * \return Status/error code. <code>MGU_SUCCESS</code> if data read
721 static gint
jpilot_read_next_pc( FILE *in
, buf_rec
*br
) {
722 PC3RecordHeader header
;
726 if( claws_feof( in
) ) {
729 num
= read_header( in
, &header
);
731 if( claws_ferror( in
) )
732 return MGU_ERROR_READ
;
733 else if( claws_feof( in
) )
738 rec_len
= header
.rec_len
;
739 record
= malloc( rec_len
);
741 return MGU_OO_MEMORY
;
743 num
= claws_fread( record
, rec_len
, 1, in
);
745 if( claws_ferror( in
) ) {
747 return MGU_ERROR_READ
;
751 br
->unique_id
= header
.unique_id
;
752 br
->attrib
= header
.attrib
;
760 * Read address file into a linked list. Based on JPilot function
761 * <code>jp_read_DB_files()</code> (from libplugin.c)
763 * \param pilotFile JPilot control data.
764 * \param records Pointer to linked list of records read.
765 * \return Status/error code. <code>MGU_SUCCESS</code> if data read
768 static gint
jpilot_read_db_files( JPilotFile
*pilotFile
, GList
**records
) {
772 int num_records
, recs_returned
, i
, num
, r
;
773 unsigned int offset
, prev_offset
, next_offset
= 0, rec_size
;
775 long fpos
; /*file position indicator */
776 unsigned char attrib
= '\0';
777 unsigned int unique_id
= 0;
778 mem_rec_header
*mem_rh
, *temp_mem_rh
, *last_mem_rh
;
785 mem_rh
= last_mem_rh
= NULL
;
789 if( pilotFile
->path
== NULL
) {
793 in
= claws_fopen( pilotFile
->path
, "rb" );
795 return MGU_OPEN_FILE
;
798 /* Read the database header */
799 num
= claws_fread( &rdbh
, sizeof( RawDBHeader
), 1, in
);
801 if( claws_ferror( in
) ) {
803 return MGU_ERROR_READ
;
805 if( claws_feof( in
) ) {
810 raw_header_to_header( &rdbh
, &dbh
);
812 /* Read each record entry header */
813 num_records
= dbh
.number_of_records
;
817 for( i
= 1; i
< num_records
+ 1; i
++ ) {
818 num
= claws_fread( &rh
, sizeof( record_header
), 1, in
);
820 if( claws_ferror( in
) ) {
823 if( claws_feof( in
) ) {
826 free_mem_rec_header( &mem_rh
);
832 ( ( rh
.Offset
[0] * 256 + rh
.Offset
[1] ) * 256
833 + rh
.Offset
[2] ) * 256
835 if( offset
< prev_offset
) {
838 prev_offset
= offset
;
839 temp_mem_rh
= ( mem_rec_header
* ) malloc( sizeof( mem_rec_header
) );
840 if( ! temp_mem_rh
) {
843 temp_mem_rh
->next
= NULL
;
844 temp_mem_rh
->rec_num
= i
;
845 temp_mem_rh
->offset
= offset
;
846 temp_mem_rh
->attrib
= rh
.attrib
;
847 temp_mem_rh
->unique_id
=
848 ( rh
.unique_ID
[0] * 256 + rh
.unique_ID
[1] ) * 256
850 if( mem_rh
== NULL
) {
851 mem_rh
= temp_mem_rh
;
852 last_mem_rh
= temp_mem_rh
;
855 last_mem_rh
->next
= temp_mem_rh
;
856 last_mem_rh
= temp_mem_rh
;
860 temp_mem_rh
= mem_rh
;
865 mem_rh
, 0, &next_offset
, &attrib
, &unique_id
);
869 next_offset
= mem_rh
->offset
;
870 attrib
= mem_rh
->attrib
;
871 unique_id
= mem_rh
->unique_id
;
874 if (fseek( in
, next_offset
, SEEK_SET
) < 0) {
875 free_mem_rec_header( &mem_rh
);
877 return MGU_ERROR_READ
;
879 while( ! claws_feof( in
) ) {
883 mem_rh
, fpos
, &next_offset
, &attrib
,
886 next_offset
= 0xFFFFFF;
888 attrib
= temp_mem_rh
->attrib
;
889 unique_id
= temp_mem_rh
->unique_id
;
890 if ( temp_mem_rh
->next
) {
891 temp_mem_rh
= temp_mem_rh
->next
;
892 next_offset
= temp_mem_rh
->offset
;
896 rec_size
= next_offset
- fpos
;
897 buf
= malloc( rec_size
);
899 num
= claws_fread( buf
, rec_size
, 1, in
);
901 if( claws_ferror( in
) ) {
907 temp_br
= malloc( sizeof( buf_rec
) );
912 temp_br
->rt
= PALM_REC
;
913 temp_br
->unique_id
= unique_id
;
914 temp_br
->attrib
= attrib
;
916 temp_br
->size
= rec_size
;
918 *records
= g_list_append( *records
, temp_br
);
924 free_mem_rec_header( &mem_rh
);
926 /* Read the PC3 file, if present */
927 pcFile
= jpilot_get_pc3_file( pilotFile
);
928 if( pcFile
== NULL
) return MGU_SUCCESS
;
929 pc_in
= claws_fopen( pcFile
, "rb");
932 if( pc_in
== NULL
) {
936 while( ! claws_feof( pc_in
) ) {
939 temp_br
= malloc( sizeof( buf_rec
) );
943 r
= jpilot_read_next_pc( pc_in
, temp_br
);
944 if( r
!= MGU_SUCCESS
) {
945 if( (r
!= MGU_EOF
) && (r
!= MGU_ERROR_READ
) ) {
946 free( temp_br
->buf
);
953 if( ( temp_br
->rt
!= DELETED_PC_REC
)
954 && ( temp_br
->rt
!= DELETED_PALM_REC
)
955 && ( temp_br
->rt
!= MODIFIED_PALM_REC
)
956 && ( temp_br
->rt
!= DELETED_DELETED_PALM_REC
) )
958 *records
= g_list_append( *records
, temp_br
);
963 if( ( temp_br
->rt
== DELETED_PALM_REC
)
964 || ( temp_br
->rt
== MODIFIED_PALM_REC
) )
966 temp_list
= *records
;
968 while( temp_list
->next
) {
969 temp_list
=temp_list
->next
;
972 for( ; temp_list
; temp_list
=temp_list
->prev
) {
973 if( ( ( buf_rec
* )temp_list
->data
)->unique_id
==
974 temp_br
->unique_id
) {
975 ( ( buf_rec
* )temp_list
->data
)->rt
=
982 free( temp_br
->buf
);
986 claws_fclose( pc_in
);
992 * Parse buffer containing multiple e-mail addresses into a linked list of
993 * addresses. Separator characters are " ,;|" and control characters. Address
994 * is only extracted if it contains an "at" (@) character.
996 * \param buf Buffer to process.
997 * \return List of strings.
999 static GList
*jpilot_parse_email( gchar
*buf
) {
1004 gboolean valid
, done
;
1006 valid
= done
= FALSE
;
1011 if( *p
== ' ' || *p
== ',' || *p
== ';' || *p
== '|' || *p
< 32 ) {
1026 em
= g_strndup( st
, len
);
1027 list
= g_list_append( list
, em
);
1035 if( *p
== '@' ) valid
= TRUE
;
1043 #define FULLNAME_BUFSIZE 256
1044 #define EMAIL_BUFSIZE 256
1047 * Process a single label entry field, parsing multiple e-mail address entries.
1049 * \param pilotFile JPilot control data.
1050 * \param labelEntry Label entry data.
1051 * \param person Person.
1053 static void jpilot_parse_label( JPilotFile
*pilotFile
, gchar
*labelEntry
, ItemPerson
*person
) {
1054 gchar buffer
[ EMAIL_BUFSIZE
];
1060 g_strlcpy( buffer
, labelEntry
, sizeof(buffer
) );
1061 node
= list
= jpilot_parse_email( buffer
);
1063 email
= addritem_create_item_email();
1064 addritem_email_set_address( email
, node
->data
);
1065 if (convert_charcode
) {
1066 gchar
*convertBuff
= NULL
;
1067 convertBuff
= conv_codeset_strdup( labelEntry
,
1068 jpilot_get_charset(),
1071 addritem_email_set_remarks( email
, convertBuff
);
1072 g_free( convertBuff
);
1075 addritem_email_set_remarks(email
, buffer
);
1078 addrcache_id_email( pilotFile
->addressCache
, email
);
1079 addrcache_person_add_email( pilotFile
->addressCache
, person
, email
);
1080 node
= g_list_next( node
);
1082 g_list_free_full( list
, g_free
);
1088 * Unpack address, building new data inside cache.
1089 * \param pilotFile JPilot control data.
1090 * \param buf Record buffer.
1091 * \param folderInd Array of (category) folders to load.
1093 static void jpilot_load_address(
1094 JPilotFile
*pilotFile
, buf_rec
*buf
, ItemFolder
*folderInd
[] )
1096 struct Address addr
;
1102 gchar fullName
[ FULLNAME_BUFSIZE
];
1108 gchar
**firstName
= NULL
;
1109 gchar
**lastName
= NULL
;
1110 #if (PILOT_LINK_MAJOR > 11)
1111 pi_buffer_t
*RecordBuffer
;
1112 RecordBuffer
= pi_buffer_new(buf
->size
);
1114 memcpy(RecordBuffer
->data
, buf
->buf
, buf
->size
);
1115 RecordBuffer
->used
= buf
->size
;
1116 if (unpack_Address(&addr
, RecordBuffer
, address_v1
) == -1) {
1117 pi_buffer_free(RecordBuffer
);
1120 pi_buffer_free(RecordBuffer
);
1124 num
= unpack_Address(&addr
, buf
->buf
, buf
->size
);
1128 #endif /* PILOT_LINK_0_12 */
1130 addrEnt
= addr
.entry
;
1131 attrib
= buf
->attrib
;
1132 unique_id
= buf
->unique_id
;
1133 cat_id
= attrib
& 0x0F;
1136 if( addrEnt
[ IND_LABEL_FIRSTNAME
] ) {
1137 firstName
= g_strsplit( addrEnt
[ IND_LABEL_FIRSTNAME
], "\01", 2 );
1140 if( addrEnt
[ IND_LABEL_LASTNAME
] ) {
1141 lastName
= g_strsplit( addrEnt
[ IND_LABEL_LASTNAME
], "\01", 2 );
1144 if( name_order
== FAMILY_LAST
) {
1145 g_snprintf( fullName
, FULLNAME_BUFSIZE
, "%s %s",
1146 firstName
? firstName
[0] : "",
1147 lastName
? lastName
[0] : "" );
1150 g_snprintf( fullName
, FULLNAME_BUFSIZE
, "%s %s",
1151 lastName
? lastName
[0] : "",
1152 firstName
? firstName
[0] : "" );
1156 g_strfreev( firstName
);
1159 g_strfreev( lastName
);
1162 g_strstrip( fullName
);
1164 if( convert_charcode
) {
1165 gchar
*nameConv
= NULL
;
1166 nameConv
= conv_codeset_strdup( fullName
,
1167 jpilot_get_charset(),
1170 strncpy2( fullName
, nameConv
, FULLNAME_BUFSIZE
);
1174 person
= addritem_create_item_person();
1175 addritem_person_set_common_name( person
, fullName
);
1176 addritem_person_set_first_name( person
, addrEnt
[ IND_LABEL_FIRSTNAME
] );
1177 addritem_person_set_last_name( person
, addrEnt
[ IND_LABEL_LASTNAME
] );
1178 addrcache_id_person( pilotFile
->addressCache
, person
);
1180 extID
= g_strdup_printf( "%d", unique_id
);
1181 addritem_person_set_external_id( person
, extID
);
1185 /* Add entry for each email address listed under phone labels. */
1186 indPhoneLbl
= addr
.phoneLabel
;
1187 for( k
= 0; k
< JPILOT_NUM_ADDR_PHONE
; k
++ ) {
1188 if( indPhoneLbl
[k
] == IND_PHONE_EMAIL
) {
1189 labelEntry
= addrEnt
[ OFFSET_PHONE_LABEL
+ k
];
1190 jpilot_parse_label( pilotFile
, labelEntry
, person
);
1194 /* Add entry for each custom label */
1195 node
= pilotFile
->labelInd
;
1199 ind
= GPOINTER_TO_INT( node
->data
);
1202 * g_print( "%d : %20s : %s\n", ind, ai->labels[ind],
1205 labelEntry
= addrEnt
[ind
];
1206 jpilot_parse_label( pilotFile
, labelEntry
, person
);
1209 node
= g_list_next( node
);
1212 if( person
->listEMail
) {
1213 /* Add to specified category */
1214 addrcache_folder_add_person(
1215 pilotFile
->addressCache
,
1216 folderInd
[cat_id
], person
);
1219 addritem_free_item_person( person
);
1222 /* Free up pointer allocated inside address */
1223 free_Address( & addr
);
1227 * Free up address list.
1228 * \param records List of records to free.
1230 static void jpilot_free_addrlist( GList
*records
) {
1240 node
= g_list_next( node
);
1244 g_list_free( records
);
1248 * Read metadata from file.
1249 * \param pilotFile JPilot control data.
1250 * \return Status/error code. <code>MGU_SUCCESS</code> if data read
1253 static gint
jpilot_read_metadata( JPilotFile
*pilotFile
) {
1255 unsigned int rec_size
;
1259 cm_return_val_if_fail( pilotFile
!= NULL
, -1 );
1261 pilotFile
->readMetadata
= FALSE
;
1262 addrcache_clear( pilotFile
->addressCache
);
1264 /* Read file info */
1265 retVal
= jpilot_get_file_info( pilotFile
, &buf
, &rec_size
);
1266 if( retVal
!= MGU_SUCCESS
) {
1267 pilotFile
->retVal
= retVal
;
1268 return pilotFile
->retVal
;
1271 num
= unpack_AddressAppInfo( &pilotFile
->addrInfo
, buf
, rec_size
);
1276 pilotFile
->retVal
= MGU_ERROR_READ
;
1277 return pilotFile
->retVal
;
1280 pilotFile
->readMetadata
= TRUE
;
1281 pilotFile
->retVal
= MGU_SUCCESS
;
1282 return pilotFile
->retVal
;
1286 * Setup labels and indexes from metadata.
1287 * \param pilotFile JPilot control data.
1288 * \return <i>TRUE</i> is setup successfully.
1290 static gboolean
jpilot_setup_labels( JPilotFile
*pilotFile
) {
1291 gboolean retVal
= FALSE
;
1292 struct AddressAppInfo
*ai
;
1295 cm_return_val_if_fail( pilotFile
!= NULL
, -1 );
1297 /* Release indexes */
1298 node
= pilotFile
->labelInd
;
1301 node
= g_list_next( node
);
1303 pilotFile
->labelInd
= NULL
;
1305 if( pilotFile
->readMetadata
) {
1306 ai
= & pilotFile
->addrInfo
;
1307 node
= pilotFile
->customLabels
;
1309 gchar
*lbl
= node
->data
;
1312 for( i
= 0; i
< JPILOT_NUM_LABELS
; i
++ ) {
1313 gchar
*labelName
= ai
->labels
[i
];
1315 if( convert_charcode
) {
1316 gchar
*convertBuff
= NULL
;
1317 convertBuff
= conv_codeset_strdup( labelName
,
1318 jpilot_get_charset(),
1321 labelName
= convertBuff
;
1325 if( g_utf8_collate( labelName
, lbl
) == 0 ) {
1330 pilotFile
->labelInd
= g_list_append(
1331 pilotFile
->labelInd
, GINT_TO_POINTER(ind
) );
1332 node
= g_list_next( node
);
1340 * Load list with character strings of custom label names. Only none blank
1342 * \param pilotFile JPilot control data.
1343 * \param labelList List of label names to load.
1344 * \return List of label names loaded. Should be freed when done.
1346 GList
*jpilot_load_custom_label( JPilotFile
*pilotFile
, GList
*labelList
) {
1349 cm_return_val_if_fail( pilotFile
!= NULL
, NULL
);
1351 if( pilotFile
->readMetadata
) {
1352 struct AddressAppInfo
*ai
= & pilotFile
->addrInfo
;
1353 for( i
= 0; i
< NUM_CUSTOM_LABEL
; i
++ ) {
1354 gchar
*labelName
= ai
->labels
[i
+IND_CUSTOM_LABEL
];
1356 g_strstrip( labelName
);
1357 if( *labelName
!= '\0' ) {
1358 if( convert_charcode
) {
1359 gchar
*convertBuff
= NULL
;
1360 convertBuff
= conv_codeset_strdup( labelName
,
1361 jpilot_get_charset(),
1364 labelName
= convertBuff
;
1368 labelName
= g_strdup( labelName
);
1370 labelList
= g_list_append( labelList
, labelName
);
1379 * Build folder in address book for each category.
1380 * \param pilotFile JPilot control data.
1382 static void jpilot_build_category_list( JPilotFile
*pilotFile
) {
1383 struct AddressAppInfo
*ai
= & pilotFile
->addrInfo
;
1384 struct CategoryAppInfo
*cat
= & ai
->category
;
1387 for( i
= 0; i
< JPILOT_NUM_CATEG
; i
++ ) {
1388 ItemFolder
*folder
= addritem_create_item_folder();
1390 if( convert_charcode
) {
1391 gchar
*convertBuff
= NULL
;
1392 convertBuff
= conv_codeset_strdup( cat
->name
[i
],
1393 jpilot_get_charset(),
1396 addritem_folder_set_name( folder
, convertBuff
);
1397 g_free( convertBuff
);
1399 addritem_folder_set_name( folder
, cat
->name
[i
] );
1403 addritem_folder_set_name( folder
, cat
->name
[i
] );
1406 addrcache_id_folder( pilotFile
->addressCache
, folder
);
1407 addrcache_add_folder( pilotFile
->addressCache
, folder
);
1412 * Remove empty (category) folders.
1413 * \param pilotFile JPilot control data.
1415 static void jpilot_remove_empty( JPilotFile
*pilotFile
) {
1421 listFolder
= addrcache_get_list_folder( pilotFile
->addressCache
);
1425 ItemFolder
*folder
= node
->data
;
1426 if( ADDRITEM_NAME(folder
) == NULL
|| *ADDRITEM_NAME(folder
) == '\0' ) {
1427 if( folder
->listPerson
) {
1428 /* Give name to folder */
1430 sprintf( name
, "? %d", i
);
1431 addritem_folder_set_name( folder
, name
);
1434 /* Mark for removal */
1435 remList
= g_list_append( remList
, folder
);
1438 node
= g_list_next( node
);
1443 ItemFolder
*folder
= node
->data
;
1444 addrcache_remove_folder( pilotFile
->addressCache
, folder
);
1445 node
= g_list_next( node
);
1447 g_list_free( remList
);
1451 * Read address file into address cache.
1452 * \param pilotFile JPilot control data.
1453 * \return Error/status code. <code>MGU_SUCCESS</code> if data read
1456 static gint
jpilot_read_file( JPilotFile
*pilotFile
) {
1458 GList
*records
= NULL
;
1461 ItemFolder
*folderInd
[ JPILOT_NUM_CATEG
];
1463 /* Read list of records from JPilot files */
1464 retVal
= jpilot_read_db_files( pilotFile
, &records
);
1465 if( retVal
!= MGU_SUCCESS
) {
1466 jpilot_free_addrlist( records
);
1470 /* Setup labels and category folders */
1471 jpilot_setup_labels( pilotFile
);
1472 jpilot_build_category_list( pilotFile
);
1474 /* Build array of pointers to categories */
1476 node
= addrcache_get_list_folder( pilotFile
->addressCache
);
1478 if( i
< JPILOT_NUM_CATEG
) {
1479 folderInd
[i
] = node
->data
;
1481 node
= g_list_next( node
);
1485 /* Load all addresses, free up old stuff as we go */
1489 if( ( br
->rt
!= DELETED_PC_REC
) &&
1490 ( br
->rt
!= DELETED_PALM_REC
) &&
1491 ( br
->rt
!= MODIFIED_PALM_REC
) &&
1492 ( br
->rt
!= DELETED_DELETED_PALM_REC
) ) {
1493 jpilot_load_address( pilotFile
, br
, folderInd
);
1498 node
= g_list_next( node
);
1502 g_list_free( records
);
1504 /* Remove empty category folders */
1505 jpilot_remove_empty( pilotFile
);
1506 jpilot_mark_files( pilotFile
);
1512 * Read file into list. Main entry point
1513 * \param pilotFile JPilot control data.
1514 * \return Error/status code. <code>MGU_SUCCESS</code> if data read
1517 gint
jpilot_read_data( JPilotFile
*pilotFile
) {
1518 const gchar
*cur_locale
;
1520 name_order
= FAMILY_LAST
;
1522 cur_locale
= conv_get_current_locale();
1524 if( g_ascii_strncasecmp( cur_locale
, "hu", 2 ) == 0 ||
1525 g_ascii_strncasecmp( cur_locale
, "ja", 2 ) == 0 ||
1526 g_ascii_strncasecmp( cur_locale
, "ko", 2 ) == 0 ||
1527 g_ascii_strncasecmp( cur_locale
, "vi", 2 ) == 0 ||
1528 g_ascii_strncasecmp( cur_locale
, "zh", 2 ) == 0 ) {
1529 name_order
= FAMILY_FIRST
;
1532 cm_return_val_if_fail( pilotFile
!= NULL
, -1 );
1534 pilotFile
->retVal
= MGU_SUCCESS
;
1535 pilotFile
->addressCache
->accessFlag
= FALSE
;
1536 if( jpilot_check_files( pilotFile
) ) {
1537 addrcache_clear( pilotFile
->addressCache
);
1538 jpilot_read_metadata( pilotFile
);
1539 if( pilotFile
->retVal
== MGU_SUCCESS
) {
1540 pilotFile
->retVal
= jpilot_read_file( pilotFile
);
1541 if( pilotFile
->retVal
== MGU_SUCCESS
) {
1542 pilotFile
->addressCache
->modified
= FALSE
;
1543 pilotFile
->addressCache
->dataRead
= TRUE
;
1547 return pilotFile
->retVal
;
1551 * Return linked list of persons. This is a list of references to ItemPerson
1552 * objects. Do <b>NOT</b> attempt to use the <code>addrbook_free_xxx()</code>
1553 * functions... this will destroy the addressbook data!
1555 * \param pilotFile JPilot control data.
1556 * \return List of persons.
1558 GList
*jpilot_get_list_person( JPilotFile
*pilotFile
) {
1559 cm_return_val_if_fail( pilotFile
!= NULL
, NULL
);
1560 return addrcache_get_list_person( pilotFile
->addressCache
);
1564 * Return linked list of folders. This is a list of references to non-empty
1565 * category folders. Do <b>NOT</b> attempt to use the
1566 * <code>addrbook_free_xxx()</code> functions... this will destroy the
1569 * \param pilotFile JPilot control data.
1570 * \return List of ItemFolder objects. This should not be freed.
1572 GList
*jpilot_get_list_folder( JPilotFile
*pilotFile
) {
1573 cm_return_val_if_fail( pilotFile
!= NULL
, NULL
);
1574 return addrcache_get_list_folder( pilotFile
->addressCache
);
1578 * Return linked list of all persons. Note that the list contains references
1579 * to items. Do <b>NOT</b> attempt to use the <code>addrbook_free_xxx()</code>
1580 * functions... this will destroy the addressbook data!
1582 * \param pilotFile JPilot control data.
1583 * \return List of items, or NULL if none.
1585 GList
*jpilot_get_all_persons( JPilotFile
*pilotFile
) {
1586 cm_return_val_if_fail( pilotFile
!= NULL
, NULL
);
1587 return addrcache_get_all_persons( pilotFile
->addressCache
);
1590 #define WORK_BUFLEN 1024
1593 * Attempt to find a valid JPilot file.
1594 * \param pilotFile JPilot control data.
1595 * \return Filename, or home directory if not found, or empty string if
1596 * no home. Filename should be <code>g_free()</code> when done.
1598 gchar
*jpilot_find_pilotdb( void ) {
1599 const gchar
*homedir
;
1600 gchar str
[ WORK_BUFLEN
+ 1 ];
1604 homedir
= get_home_dir();
1605 if( ! homedir
) return g_strdup( "" );
1607 g_strlcpy( str
, homedir
, sizeof(str
));
1608 len
= strlen( str
);
1610 if( str
[ len
-1 ] != G_DIR_SEPARATOR
) {
1611 str
[ len
] = G_DIR_SEPARATOR
;
1612 str
[ ++len
] = '\0';
1615 strncat( str
, JPILOT_DBHOME_DIR
, WORK_BUFLEN
- strlen(str
) );
1616 strncat( str
, G_DIR_SEPARATOR_S
, WORK_BUFLEN
- strlen(str
) );
1617 strncat( str
, JPILOT_DBHOME_FILE
, WORK_BUFLEN
- strlen(str
) );
1619 /* Attempt to open */
1620 if( ( fp
= claws_fopen( str
, "rb" ) ) != NULL
) {
1624 /* Truncate filename */
1627 return g_strdup( str
);
1631 * Check whether label is in list of custom labels.
1632 * \param pilotFile JPilot control data.
1633 * \param labelName to test.
1634 * \return <i>TRUE</i> if found.
1636 gboolean
jpilot_test_custom_label( JPilotFile
*pilotFile
, const gchar
*labelName
) {
1640 cm_return_val_if_fail( pilotFile
!= NULL
, FALSE
);
1644 node
= pilotFile
->customLabels
;
1646 if( g_utf8_collate( labelName
, ( gchar
* ) node
->data
) == 0 ) {
1650 node
= g_list_next( node
);
1657 * Test whether pilot link library installed.
1658 * \return <i>TRUE</i> if library available.
1660 gboolean
jpilot_test_pilot_lib( void ) {
1664 #endif /* USE_JPILOT */