1(* $Id: database.ml,v 5.19 2007-06-06 15:22:35 ddr Exp $ *) 2(* Copyright (c) 1998-2007 INRIA *) 3 4open Dbdisk; 5open Def; 6open Dutil; 7open Mutil; 8 9type person = dsk_person; 10type ascend = dsk_ascend; 11type union = dsk_union; 12type family = dsk_family; 13type couple = dsk_couple; 14type descend = dsk_descend; 15 16(* 17 Files in base (directory .gwb) 18 19 base - the base itself 20 magic number (magic_gwb) : string of length 8 21 number of persons : binary_int 22 number of families : binary_int 23 number of strings : binary_int 24 persons array offset in file : binary_int 25 ascends array offset in file : binary_int 26 unions array offset in file : binary_int 27 families array offset in file : binary_int 28 couples array offset in file : binary_int 29 descends array offset in file : binary_int 30 strings array offset in file : binary_int 31 notes origin file : value 32 persons array : value 33 ascends array : value 34 unions array : value 35 families array : value 36 couples array : value 37 descends array : value 38 strings array : value 39 40 base.acc - direct accesses to arrays inside base 41 persons offsets : array of binary_ints 42 ascends offsets : array of binary_ints 43 unions offsets : array of binary_ints 44 families offsets : array of binary_ints 45 couples offsets : array of binary_ints 46 descends offsets : array of binary_ints 47 strings offsets : array of binary_ints 48 49 names.inx - index for names, strings of first names and surnames 50 offset to 2nd index : binary_int 51 1st index (names) : value 52 array, length = "table_size", associating: 53 - a hash value of a "crushed" (module "Name") name (modulo length) 54 - to the array of indexes of the corresponding persons 55 2nd index (first names and surnames strings) : value 56 array, length = "table_size", associating: 57 - a hash value of the "crushed" (module "Name") first name or 58 surname (modulo length) 59 - to the array of the corresponding string indexes 60 61 names.acc - direct accesses to arrays inside names.inx 62 63 strings.inx - index for strings, surnames, first names 64 length of the strings offset array : binary_int 65 offset of surnames index : binary_int 66 offset of first names index : binary_int 67 strings hash table index : 2 arrays of binary_ints 68 strings offset array (length = prime after 10 * strings array length) 69 - associating a hash value of the string modulo length 70 - to its index in the string array 71 strings list array (length = string array length) 72 - associating a string index 73 - to the index of the next index holding the same hash value 74 -- the following table has been obsolete since version 4.10 75 -- it has been replaced by snames.inx/sname.dat which use 76 -- much less memory 77 surnames index : value 78 binary tree 79 - associating the string index of a surname 80 - to the corresponding list of persons holding this surname 81 -- the following table has been obsolete since version 4.10 82 -- it has been replaced by fnames.inx/fname.dat which use 83 -- much less memory 84 first_names index : value 85 binary tree 86 - associating the string index of a first name 87 - to the corresponding list of persons holding this first name 88 89 snames.inx - index for surnames 90 binary tree 91 - associating the string index of a surname 92 - to a pointer (int) to snames.dat 93 94 snames.dat - data associated with snames.inx 95 table of list of persons holding a surname 96 97 fnames.inx - index for first names 98 binary tree 99 - associating the string index of a first name 100 - to a pointer (int) to fnames.dat 101 102 fnames.dat - data associated with fnames.inx 103 table of list of persons holding a first name 104 105the corresponding list of persons holding this surname 106 107 patches - patches 108 When updated, none of the previous files are modified. Only this one 109 is written and rewritten. It holds a record of type "patches", composed 110 of association lists "index" - "new value". 111*) 112 113(* Search index of a given string in file strings.inx *) 114 115value string_piece s = 116 let s = String.escaped s in 117 if String.length s > 20 then 118 String.sub s 0 10 ^ " ... " ^ String.sub s (String.length s - 10) 10 119 else s 120; 121 122exception Found of int; 123 124value hashtbl_right_assoc s ht = 125 try 126 do { 127 Hashtbl.iter 128 (fun i1 s1 -> if s = s1 then raise (Found i1) else ()) ht; 129 raise Not_found; 130 } 131 with 132 [ Found x -> x ] 133; 134 135value index_of_string strings ic start_pos hash_len string_patches s = 136 try Adef.istr_of_int (hashtbl_right_assoc s string_patches) with 137 [ Not_found -> 138 match (ic, hash_len) with 139 [ (Some ic, Some hash_len) -> 140 let ia = Hashtbl.hash s mod hash_len in 141 do { 142 seek_in ic (start_pos + ia * int_size); 143 let i1 = input_binary_int ic in 144 let rec loop i = 145 if i = -1 then raise Not_found 146 else if strings.get i = s then Adef.istr_of_int i 147 else do { 148 seek_in ic (start_pos + (hash_len + i) * int_size); 149 loop (input_binary_int ic) 150 } 151 in 152 loop i1 153 } 154 | _ -> do { 155 Printf.eprintf "Sorry. I really need string.inx\n"; 156 flush stderr; 157 failwith "database access" 158 } ] ] 159; 160 161value initial s = 162 loop 0 where rec loop i = 163 if i = String.length s then 0 164 else 165 match s.[i] with 166 [ 'A'..'Z' | '�'..'�' -> i 167 | _ -> loop (succ i) ] 168; 169 170value rec list_remove_elemq x = 171 fun 172 [ [y :: l] -> if x = y then l else [y :: list_remove_elemq x l] 173 | [] -> [] ] 174; 175 176(* compatibility with databases created with versions <= 4.09 *) 177(* should be removed after some time (when all databases will have 178 been rebuilt with version >= 4.10 *) 179value old_persons_of_first_name_or_surname base_data strings params = 180 let (ic2, start_pos, proj, person_patches, _, _, _) = params in 181 let module IstrTree = 182 Btree.Make 183 (struct 184 type t = dsk_istr; 185 value compare = compare_istr_fun base_data; 186 end) 187 in 188 let bt = 189 let btr = ref None in 190 let completed = ref False in 191 let update_bt gistro bt = 192 do { 193 let bt = ref bt in 194 Hashtbl.iter 195 (fun i p -> 196 let istr = proj p in 197 if gistro <> None && gistro <> Some istr then () 198 else 199 let ipera = 200 try IstrTree.find istr bt.val with [ Not_found -> [] ] 201 in 202 if List.mem (Adef.iper_of_int i) ipera then () 203 else 204 bt.val := 205 IstrTree.add istr [Adef.iper_of_int i :: ipera] bt.val) 206 person_patches; 207 if gistro = None then completed.val := True else (); 208 bt.val 209 } 210 in 211 fun gistro -> 212 match btr.val with 213 [ Some bt -> 214 if completed.val then bt 215 else 216 let bt = update_bt gistro bt in 217 do { btr.val := Some bt; bt } 218 | None -> 219 match (ic2, start_pos) with 220 [ (Some ic2, Some start_pos) -> do { 221 seek_in ic2 start_pos; 222(* 223let ab1 = Gc.allocated_bytes () in 224*) 225 let bt : IstrTree.t (list iper) = input_value ic2 in 226(* 227let ab2 = Gc.allocated_bytes () in 228*) 229 let bt = update_bt gistro bt in 230 btr.val := Some bt; 231(* 232Printf.eprintf "*** old database created by version <= 4.09\n"; flush stderr; 233Printf.eprintf "*** using index allocating here %.0f bytes\n" 234 (ab2 -. ab1); 235flush stderr; 236*) 237 bt 238 } 239 | _ -> do { 240 Printf.eprintf "Sorry, I really need strings.inx.\n"; 241 flush stderr; 242 failwith "database access" 243 } ] ] 244 in 245 let compare = compare_istr_fun base_data in 246 let check_patches istr ipl = 247 let ipl = ref ipl in 248 do { 249 Hashtbl.iter 250 (fun i p -> 251 if List.mem (Adef.iper_of_int i) ipl.val then 252 if compare istr p.first_name = 0 || 253 compare istr p.surname = 0 254 then 255 () 256 else ipl.val := list_remove_elemq (Adef.iper_of_int i) ipl.val 257 else ()) 258 person_patches; 259 ipl.val 260 } 261 in 262 let find istr = 263 try check_patches istr (IstrTree.find istr (bt (Some istr))) with 264 [ Not_found -> [] ] 265 in 266 let cursor str = 267 IstrTree.key_after 268 (fun key -> 269 compare_names base_data str (strings.get (Adef.int_of_istr key))) 270 (bt None) 271 in 272 let next key = IstrTree.next key (bt None) in 273 {find = find; cursor = cursor; next = next} 274; 275 276value new_persons_of_first_name_or_surname base_data strings params = 277 let (_, _, proj, person_patches, names_inx, names_dat, bname) = params in 278 let module IstrTree = 279 Btree.Make 280 (struct 281 type t = dsk_istr; 282 value compare = compare_istr_fun base_data; 283 end) 284 in 285 let fname_dat = Filename.concat bname names_dat in 286 let bt = 287 let btr = ref None in 288 fun () -> 289 match btr.val with 290 [ Some bt -> bt 291 | None -> 292 do { 293 let fname_inx = Filename.concat bname names_inx in 294 let ic_inx = Secure.open_in_bin fname_inx in 295(* 296let ab1 = Gc.allocated_bytes () in 297*) 298 let bt : IstrTree.t int = input_value ic_inx in 299(* 300let ab2 = Gc.allocated_bytes () in 301Printf.eprintf "*** new database created by version >= 4.10\n"; 302Printf.eprintf "*** using index '%s' allocating here only %.0f bytes\n" 303 names_inx (ab2 -. ab1); 304flush stderr; 305*) 306 close_in ic_inx; 307 btr.val := Some bt; 308 bt 309 } ] 310 in 311 let find istr = 312 let ipera = 313 try 314 let pos = IstrTree.find istr (bt ()) in 315 let ic_dat = Secure.open_in_bin fname_dat in 316 do { 317 seek_in ic_dat pos; 318 let len = input_binary_int ic_dat in 319 let rec read_loop ipera len = 320 if len = 0 then ipera 321 else 322 let iper = Adef.iper_of_int (input_binary_int ic_dat) in 323 read_loop [iper :: ipera] (len - 1) 324 in 325 let ipera = read_loop [] len in 326 close_in ic_dat; 327 ipera 328 } 329 with 330 [ Not_found -> [] ] 331 in 332 let ipera = ref ipera in 333 do { 334 Hashtbl.iter 335 (fun i p -> 336 let istr1 = proj p in 337 if istr1 <> istr then () 338 else if List.mem (Adef.iper_of_int i) ipera.val then () 339 else ipera.val := [Adef.iper_of_int i :: ipera.val]) 340 person_patches; 341 ipera.val 342 } 343 in 344 let bt_patched = 345 let btr = ref None in 346 fun () -> 347 match btr.val with 348 [ Some bt -> bt 349 | None -> 350 let bt = ref (bt ()) in 351 do { 352 Hashtbl.iter 353 (fun i p -> 354 let istr1 = proj p in 355 try 356 let _ = IstrTree.find istr1 bt.val in 357 () 358 with 359 [ Not_found -> bt.val := IstrTree.add istr1 0 bt.val ]) 360 person_patches; 361 btr.val := Some bt.val; 362 bt.val 363 } ] 364 in 365 let cursor str = 366 IstrTree.key_after 367 (fun key -> 368 compare_names base_data str (strings.get (Adef.int_of_istr key))) 369 (bt_patched ()) 370 in 371 let next key = IstrTree.next key (bt_patched ()) in 372 {find = find; cursor = cursor; next = next} 373; 374 375value persons_of_first_name_or_surname base_data strings params = 376 let (_, _, _, _, names_inx, _, bname) = params in 377 if Sys.file_exists (Filename.concat bname names_inx) then 378 new_persons_of_first_name_or_surname base_data strings params 379 else 380 old_persons_of_first_name_or_surname base_data strings params 381; 382 383(* Search index for a given name in file names.inx *) 384 385value persons_of_name bname patches = 386 let t = ref None in 387 fun s -> 388 let s = Name.crush_lower s in 389 let i = Hashtbl.hash s in 390 let ai = 391 let ic_inx = Secure.open_in_bin (Filename.concat bname "names.inx") in 392 let ai = 393 let i = i mod table_size in 394 let fname_inx_acc = Filename.concat bname "names.acc" in 395 if Sys.file_exists fname_inx_acc then 396 let ic_inx_acc = Secure.open_in_bin fname_inx_acc in 397 do { 398 seek_in ic_inx_acc (Iovalue.sizeof_long * i); 399 let pos = input_binary_int ic_inx_acc in 400 close_in ic_inx_acc; 401 seek_in ic_inx pos; 402 (Iovalue.input ic_inx : array iper) 403 } 404 else (* compatibility *) 405 let a = 406 match t.val with 407 [ Some a -> a 408 | None -> 409 do { 410 seek_in ic_inx int_size; 411 let a : name_index_data = input_value ic_inx in 412 t.val := Some a; 413 a 414 } ] 415 in 416 a.(i) 417 in 418 do { close_in ic_inx; ai } 419 in 420 try 421 let l = Hashtbl.find patches i in 422 l @ Array.to_list ai 423 with 424 [ Not_found -> Array.to_list ai ] 425; 426 427value strings_of_fsname bname strings (_, person_patches) = 428 let t = ref None in 429 fun s -> 430 let s = Name.crush_lower s in 431 let i = Hashtbl.hash s in 432 let r = 433 let ic_inx = Secure.open_in_bin (Filename.concat bname "names.inx") in 434 let ai = 435 let i = i mod table_size in 436 let fname_inx_acc = Filename.concat bname "names.acc" in 437 if Sys.file_exists fname_inx_acc then 438 let ic_inx_acc = Secure.open_in_bin fname_inx_acc in 439 do { 440 seek_in ic_inx_acc (Iovalue.sizeof_long * (table_size + i)); 441 let pos = input_binary_int ic_inx_acc in 442 close_in ic_inx_acc; 443 seek_in ic_inx pos; 444 (Iovalue.input ic_inx : array dsk_istr) 445 } 446 else (* compatibility *) 447 let a = 448 match t.val with 449 [ Some a -> a 450 | None -> 451 let pos = input_binary_int ic_inx in 452 do { 453 seek_in ic_inx pos; 454 let a : strings_of_fsname = input_value ic_inx in 455 t.val := Some a; 456 a 457 } ] 458 in 459 a.(i) 460 in 461 do { close_in ic_inx; ai } 462 in 463 let l = ref (Array.to_list r) in 464 do { 465 Hashtbl.iter 466 (fun _ p -> 467 do { 468 if not (List.mem p.first_name l.val) then 469 let s1 = strings.get (Adef.int_of_istr p.first_name) in 470 let s1 = nominative s1 in 471 if s = Name.crush_lower s1 then 472 l.val := [p.first_name :: l.val] 473 else () 474 else (); 475 if not (List.mem p.surname l.val) then 476 let s1 = strings.get (Adef.int_of_istr p.surname) in 477 let s1 = nominative s1 in 478 if s = Name.crush_lower s1 then 479 l.val := [p.surname :: l.val] 480 else () 481 else (); 482 }) 483 person_patches; 484 l.val 485 } 486; 487(**) 488 489value lock_file bname = 490 let bname = 491 if Filename.check_suffix bname ".gwb" then 492 Filename.chop_suffix bname ".gwb" 493 else bname 494 in 495 bname ^ ".lck" 496; 497 498(* Restrict file *) 499 500type visible_state = [ VsNone | VsTrue | VsFalse ]; 501 502value make_visible_record_access bname persons = 503 let visible_ref = ref None in 504 let fname = Filename.concat bname "restrict" in 505 let read_or_create_visible () = 506 let visible = 507 match try Some (Secure.open_in fname) with [ Sys_error _ -> None ] with 508 [ Some ic -> 509 do { 510 IFDEF UNIX THEN 511 if verbose.val then do { 512 Printf.eprintf "*** read restrict file\n"; 513 flush stderr; 514 } 515 else () 516 ELSE () END; 517 let visible = input_value ic in 518 close_in ic; 519 visible 520 } 521 | None -> Array.make persons.len VsNone ] 522 in 523 do { visible_ref.val := Some visible; visible } 524 in 525 let v_write () = 526 match visible_ref.val with 527 [ Some visible -> 528 try do { 529 let oc = Secure.open_out fname in 530 IFDEF UNIX THEN 531 if verbose.val then do { 532 Printf.eprintf "*** write restrict file\n"; 533 flush stderr; 534 } 535 else () 536 ELSE () END; 537 output_value oc visible; 538 close_out oc 539 } 540 with [ Sys_error _ -> () ] 541 | None -> () ] 542 in 543 let v_get fct i = 544 let visible = 545 match visible_ref.val with 546 [ Some visible -> visible 547 | None -> read_or_create_visible () ] 548 in 549 if i < Array.length visible then 550 match visible.(i) with 551 [ VsNone -> 552 let status = fct (persons.get i) in 553 do { 554 visible.(i) := if status then VsTrue else VsFalse; 555 visible_ref.val := Some visible; 556 status 557 } 558 | VsTrue -> True 559 | VsFalse -> False ] 560 else fct (persons.get i) 561 in 562 { v_write = v_write; v_get = v_get } 563; 564 565(* Input *) 566 567value apply_patches tab f patches plen = 568 if plen = 0 then tab 569 else do { 570 let new_tab = 571 if plen > Array.length tab then do { 572 let new_tab = Array.make plen (Obj.magic 0) in 573 Array.blit tab 0 new_tab 0 (Array.length tab); 574 new_tab 575 } 576 else tab 577 in 578 Hashtbl.iter (fun i v -> new_tab.(i) := f v) patches; 579 new_tab 580 } 581; 582 583type patches_ht = 584 { h_person : (ref int * Hashtbl.t int person); 585 h_ascend : (ref int * Hashtbl.t int ascend); 586 h_union : (ref int * Hashtbl.t int union); 587 h_family : (ref int * Hashtbl.t int family); 588 h_couple : (ref int * Hashtbl.t int couple); 589 h_descend : (ref int * Hashtbl.t int descend); 590 h_string : (ref int * Hashtbl.t int string); 591 h_name : Hashtbl.t int (list iper) } 592; 593 594(* Old structure of file "patches", kept for backward compatibility. 595 After conversion, a new change will be saved with a magic number 596 (magic_patch) and a record "patch_ht" above. *) 597 598module Old = 599 struct 600 type patches = 601 { p_person : ref (list (int * person)); 602 p_ascend : ref (list (int * ascend)); 603 p_union : ref (list (int * union)); 604 p_family : ref (list (int * family)); 605 p_couple : ref (list (int * couple)); 606 p_descend : ref (list (int * descend)); 607 p_string : ref (list (int * string)); 608 p_name : ref (list (int * list iper)) } 609 ; 610 end 611; 612 613value phony_person = 614 {first_name = 0; surname = 0; 615 occ = 0; image = 0; first_names_aliases = []; 616 surnames_aliases = []; public_name = 0; qualifiers = []; 617 aliases = []; titles = []; rparents = []; related = []; 618 occupation = 0; sex = Neuter; access = IfTitles; 619 birth = Adef.codate_None; birth_place = 0; 620 birth_src = 0; baptism = Adef.codate_None; 621 baptism_place = 0; baptism_src = 0; 622 death = DontKnowIfDead; death_place = 0; 623 death_src = 0; burial = UnknownBurial; 624 burial_place = 0; burial_src = 0; 625 notes = 0; psources = 0; 626 key_index = Adef.iper_of_int 0} 627; 628 629value phony_family = 630 {marriage = Adef.codate_None; 631 marriage_place = 0; marriage_src = 0; 632 witnesses = [| |]; relation = Married; 633 divorce = NotDivorced; comment = 0; 634 origin_file = 0; fsources = 0; 635 fam_index = Adef.ifam_of_int 0} 636; 637 638value ext phony v = 639 let rlen = Array.length (Obj.magic v) in 640 let alen = Array.length (Obj.magic phony) in 641 if rlen = alen then v 642 else if rlen < alen then do { 643 let x = Array.copy (Obj.magic phony) in 644 Array.blit (Obj.magic v) 0 x 0 rlen; 645 Obj.magic x 646 } 647 else 648 failwith "this is a GeneWeb base, but not compatible; please upgrade" 649; 650 651value array_ext phony fa = 652 let a = Obj.magic fa in 653 if Array.length a = 0 then fa 654 else 655 let rlen = Array.length a.(0) in 656 let alen = Array.length (Obj.magic phony) in 657 if rlen = alen then fa 658 else if rlen < alen then do { 659 IFDEF UNIX THEN 660 if verbose.val then do { 661 Printf.eprintf 662 "*** extending records from size %d to size %d\n" 663 rlen alen; 664 flush stderr; 665 } 666 else () 667 ELSE () END; 668 for i = 0 to Array.length a - 1 do { 669 let x = Array.copy (Obj.magic phony) in 670 Array.blit a.(i) 0 x 0 rlen; 671 a.(i) := x; 672 }; 673 fa 674 } 675 else 676 failwith "this is a GeneWeb base, but not compatible; please upgrade" 677; 678 679value make_record_access ic ic_acc shift array_pos (plenr, patches) len name 680 input_array input_item 681= 682 let v_ext v = 683 if name = "persons" then ext phony_person v 684 else if name = "families" then ext phony_family v 685 else v 686 in 687 let v_arr_ext v = 688 if name = "persons" then array_ext phony_person v 689 else if name = "families" then array_ext phony_family v 690 else v 691 in 692 let tab = ref None in 693 let cleared = ref False in 694 let gen_get i = 695 match tab.val with 696 [ Some x -> x.(i) 697 | None -> 698 try 699 let v = Hashtbl.find patches i in 700 v_ext v 701 with 702 [ Not_found -> 703 if i < 0 || i >= len then 704 failwith 705 ("access " ^ name ^ " out of bounds; i = " ^ string_of_int i) 706 else 707 match ic_acc with 708 [ Some ic_acc -> do { 709 seek_in ic_acc (shift + Iovalue.sizeof_long * i); 710 let pos = input_binary_int ic_acc in 711 seek_in ic pos; 712 let v = input_item ic in 713 v_ext v 714 } 715 | None -> do { 716 Printf.eprintf "Sorry; I really need base.acc\n"; 717 flush stderr; 718 failwith "cannot access database" } ] ] ] 719 in 720 let rec array () = 721 match tab.val with 722 [ Some x -> x 723 | None -> do { 724 IFDEF UNIX THEN 725 if verbose.val then do { 726 Printf.eprintf "*** read %s%s\n" name 727 (if cleared.val then " (again)" else ""); 728 flush stderr; 729 } 730 else () 731 ELSE () END; 732 seek_in ic array_pos; 733 let v = input_array ic in 734 let v = v_arr_ext v in 735 let t = apply_patches v v_ext patches r.len in 736 tab.val := Some t; 737 t 738 } ] 739 and r = 740 {load_array () = let _ = array () in (); get = gen_get; 741 set i v = (array ()).(i) := v; len = max len plenr.val; 742 output_array oc = output_value_no_sharing oc (array () : array _); 743 clear_array () = do { cleared.val := True; tab.val := None }} 744 in 745 r 746; 747 748value magic_patch = "GnPa0001"; 749value check_patch_magic = 750 let b = Bytes.create (String.length magic_patch) in 751 fun ic -> do { 752 really_input ic b 0 (String.length b); 753 b = magic_patch 754 } 755; 756 757value input_patches bname = 758 match 759 try Some (Secure.open_in_bin (Filename.concat bname "patches")) with _ -> 760 None 761 with 762 [ Some ic -> do { 763 let r = 764 if check_patch_magic ic then (input_value ic : patches_ht) 765 else do { 766 (* old implementation of patches *) 767 seek_in ic 0; 768 let patches : Old.patches = input_value ic in 769 let ht = 770 {h_person = (ref 0, Hashtbl.create 1); 771 h_ascend = (ref 0, Hashtbl.create 1); 772 h_union = (ref 0, Hashtbl.create 1); 773 h_family = (ref 0, Hashtbl.create 1); 774 h_couple = (ref 0, Hashtbl.create 1); 775 h_descend = (ref 0, Hashtbl.create 1); 776 h_string = (ref 0, Hashtbl.create 1); 777 h_name = Hashtbl.create 1} 778 in 779 let add (ir, ht) (k, v) = do { 780 if k >= ir.val then ir.val := k + 1 else (); 781 Hashtbl.add ht k v; 782 } 783 in 784 List.iter (add ht.h_person) patches.Old.p_person.val; 785 List.iter (add ht.h_ascend) patches.Old.p_ascend.val; 786 List.iter (add ht.h_union) patches.Old.p_union.val; 787 List.iter (add ht.h_family) patches.Old.p_family.val; 788 List.iter (add ht.h_couple) patches.Old.p_couple.val; 789 List.iter (add ht.h_descend) patches.Old.p_descend.val; 790 List.iter (add ht.h_string) patches.Old.p_string.val; 791 List.iter (add (ref 0, ht.h_name)) patches.Old.p_name.val; 792 ht 793 } 794 in 795 close_in ic; 796 r 797 } 798 | None -> 799 {h_person = (ref 0, Hashtbl.create 1); 800 h_ascend = (ref 0, Hashtbl.create 1); 801 h_union = (ref 0, Hashtbl.create 1); 802 h_family = (ref 0, Hashtbl.create 1); 803 h_couple = (ref 0, Hashtbl.create 1); 804 h_descend = (ref 0, Hashtbl.create 1); 805 h_string = (ref 0, Hashtbl.create 1); 806 h_name = Hashtbl.create 1} ] 807; 808 809value person_of_key persons strings persons_of_name first_name surname occ = 810 if first_name = "?" || surname = "?" then None 811 else 812 let first_name = nominative first_name in 813 let surname = nominative surname in 814 let ipl = persons_of_name (first_name ^ " " ^ surname) in 815 let first_name = Name.lower first_name in 816 let surname = Name.lower surname in 817 let rec find = 818 fun 819 [ [ip :: ipl] -> 820 let p = persons.get (Adef.int_of_iper ip) in 821 if occ = p.occ && 822 first_name = 823 Name.lower (strings.get (Adef.int_of_istr p.first_name)) && 824 surname = Name.lower (strings.get (Adef.int_of_istr p.surname)) 825 then 826 Some ip 827 else find ipl 828 | _ -> None ] 829 in 830 find ipl 831; 832 833value opendb bname = 834 let bname = 835 if Filename.check_suffix bname ".gwb" then bname else bname ^ ".gwb" 836 in 837 let patches = input_patches bname in 838 let particles = 839 Mutil.input_particles (Filename.concat bname "particles.txt") 840 in 841 let ic = 842 let ic = Secure.open_in_bin (Filename.concat bname "base") in 843 do { check_magic ic; ic } 844 in 845 let persons_len = input_binary_int ic in 846 let families_len = input_binary_int ic in 847 let strings_len = input_binary_int ic in 848 let persons_array_pos = input_binary_int ic in 849 let ascends_array_pos = input_binary_int ic in 850 let unions_array_pos = input_binary_int ic in 851 let families_array_pos = input_binary_int ic in 852 let couples_array_pos = input_binary_int ic in 853 let descends_array_pos = input_binary_int ic in 854 let strings_array_pos = input_binary_int ic in 855 let norigin_file = input_value ic in 856 let ic_acc = 857 try Some (Secure.open_in_bin (Filename.concat bname "base.acc")) with 858 [ Sys_error _ -> do { 859 Printf.eprintf "File base.acc not found; trying to continue...\n"; 860 flush stderr; 861 None } ] 862 in 863 let ic2 = 864 try Some (Secure.open_in_bin (Filename.concat bname "strings.inx")) with 865 [ Sys_error _ -> do { 866 Printf.eprintf "File strings.inx not found; trying to continue...\n"; 867 flush stderr; 868 None } ] 869 in 870 let ic2_string_start_pos = 3 * int_size in 871 let ic2_string_hash_len = 872 match ic2 with 873 [ Some ic2 -> Some (input_binary_int ic2) 874 | None -> None ] 875 in 876 let ic2_surname_start_pos = 877 match ic2 with 878 [ Some ic2 -> Some (input_binary_int ic2) 879 | None -> None ] 880 in 881 let ic2_first_name_start_pos = 882 match ic2 with 883 [ Some ic2 -> Some (input_binary_int ic2) 884 | None -> None ] 885 in 886 let shift = 0 in 887 let persons = 888 make_record_access ic ic_acc shift persons_array_pos patches.h_person 889 persons_len "persons" (input_value : _ -> array person) 890 (Iovalue.input : _ -> person) 891 in 892 let shift = shift + persons_len * Iovalue.sizeof_long in 893 let ascends = 894 make_record_access ic ic_acc shift ascends_array_pos patches.h_ascend 895 persons_len "ascends" (input_value : _ -> array ascend) 896 (Iovalue.input : _ -> ascend) 897 898 in 899 let shift = shift + persons_len * Iovalue.sizeof_long in 900 let unions = 901 make_record_access ic ic_acc shift unions_array_pos patches.h_union 902 persons_len "unions" (input_value : _ -> array union) 903 (Iovalue.input : _ -> union) 904 905 in 906 let shift = shift + persons_len * Iovalue.sizeof_long in 907 let families = 908 make_record_access ic ic_acc shift families_array_pos patches.h_family 909 families_len "families" (input_value : _ -> array family) 910 (Iovalue.input : _ -> family) 911 912 in 913 let shift = shift + families_len * Iovalue.sizeof_long in 914 let couples = 915 make_record_access ic ic_acc shift couples_array_pos patches.h_couple 916 families_len "couples" (input_value : _ -> array couple) 917 (Iovalue.input : _ -> couple) 918 919 in 920 let shift = shift + families_len * Iovalue.sizeof_long in 921 let descends = 922 make_record_access ic ic_acc shift descends_array_pos patches.h_descend 923 families_len "descends" (input_value : _ -> array descend) 924 (Iovalue.input : _ -> descend) 925 926 in 927 let shift = shift + families_len * Iovalue.sizeof_long in 928 let strings = 929 make_record_access ic ic_acc shift strings_array_pos patches.h_string 930 strings_len "strings" (input_value : _ -> array string) 931 (Iovalue.input : _ -> string) 932 933 in 934 let cleanup_ref = 935 ref 936 (fun () -> do { 937 close_in ic; 938 match ic_acc with 939 [ Some ic_acc -> close_in ic_acc 940 | None -> () ]; 941 match ic2 with 942 [ Some ic2 -> close_in ic2 943 | None -> () ]; 944 }) 945 in 946 let cleanup () = cleanup_ref.val () in 947 let commit_patches () = do { 948 let tmp_fname = Filename.concat bname "1patches" in 949 let fname = Filename.concat bname "patches" in 950 let oc9 = 951 try Secure.open_out_bin tmp_fname with 952 [ Sys_error _ -> 953 raise (Adef.Request_failure "the database is not writable") ] 954 in 955 output_string oc9 magic_patch; 956 output_value_no_sharing oc9 (patches : patches_ht); 957 close_out oc9; 958 remove_file (fname ^ "~"); 959 try Sys.rename fname (fname ^ "~") with [ Sys_error _ -> () ]; 960 try Sys.rename tmp_fname fname with [ Sys_error _ -> () ]; 961 } 962 in 963 let patched_ascends () = 964 let r = ref [] in 965 do { 966 Hashtbl.iter (fun i _ -> r.val := [Adef.iper_of_int i :: r.val]) 967 (snd patches.h_ascend); 968 r.val 969 } 970 in 971 let is_patched_person ip = 972 Hashtbl.mem (snd patches.h_person) (Adef.int_of_iper ip) 973 in 974 let patch_person i p = 975 let i = Adef.int_of_iper i in 976 do { 977 persons.len := max persons.len (i + 1); 978 (fst patches.h_person).val := persons.len; 979 Hashtbl.replace (snd patches.h_person) i p; 980 } 981 in 982 let patch_ascend i a = 983 let i = Adef.int_of_iper i in 984 do { 985 ascends.len := max ascends.len (i + 1); 986 (fst patches.h_ascend).val := ascends.len; 987 Hashtbl.replace (snd patches.h_ascend) i a; 988 } 989 in 990 let patch_union i a = 991 let i = Adef.int_of_iper i in 992 do { 993 unions.len := max unions.len (i + 1); 994 (fst patches.h_union).val := ascends.len; 995 Hashtbl.replace (snd patches.h_union) i a; 996 } 997 in 998 let patch_family i f = 999 let i = Adef.int_of_ifam i in 1000 do { 1001 families.len := max families.len (i + 1); 1002 (fst patches.h_family).val := families.len; 1003 Hashtbl.replace (snd patches.h_family) i f; 1004 } 1005 in 1006 let patch_couple i c = 1007 let i = Adef.int_of_ifam i in 1008 do { 1009 couples.len := max couples.len (i + 1); 1010 (fst patches.h_couple).val := couples.len; 1011 Hashtbl.replace (snd patches.h_couple) i c; 1012 } 1013 in 1014 let patch_descend i c = 1015 let i = Adef.int_of_ifam i in 1016 do { 1017 descends.len := max descends.len (i + 1); 1018 (fst patches.h_descend).val := descends.len; 1019 Hashtbl.replace (snd patches.h_descend) i c; 1020 } 1021 in 1022 let index_of_string = 1023 index_of_string strings ic2 ic2_string_start_pos ic2_string_hash_len 1024 (snd patches.h_string) 1025 in 1026 let insert_string s = 1027 try index_of_string s with 1028 [ Not_found -> do { 1029 let i = strings.len in 1030 strings.len := max strings.len (i + 1); 1031 (fst patches.h_string).val := strings.len; 1032 Hashtbl.replace (snd patches.h_string) i s; 1033 Adef.istr_of_int i 1034 } ] 1035 in 1036 let patch_name s ip = 1037 let s = Name.crush_lower s in 1038 let i = Hashtbl.hash s in 1039 try 1040 let ipl = Hashtbl.find patches.h_name i in 1041 if List.mem ip ipl then () 1042 else Hashtbl.replace patches.h_name i [ip :: ipl] 1043 with 1044 [ Not_found -> Hashtbl.add patches.h_name i [ip] ] 1045 in 1046 let read_notes fnotes rn_mode = 1047 let fname = 1048 if fnotes = "" then "notes" 1049 else Filename.concat "notes_d" (fnotes ^ ".txt") 1050 in 1051 match 1052 try Some (Secure.open_in (Filename.concat bname fname)) with 1053 [ Sys_error _ -> None ] 1054 with 1055 [ Some ic -> do { 1056 let str = 1057 match rn_mode with 1058 [ RnDeg -> if in_channel_length ic = 0 then "" else " " 1059 | Rn1Ln -> try input_line ic with [ End_of_file -> "" ] 1060 | RnAll -> 1061 loop 0 where rec loop len = 1062 match 1063 try Some (input_char ic) with [ End_of_file -> None ] 1064 with 1065 [ Some c -> loop (Buff.store len c) 1066 | _ -> Buff.get len ] ] 1067 in 1068 close_in ic; 1069 str 1070 } 1071 | None -> "" ] 1072 in 1073 let commit_notes fnotes s = 1074 let fname = 1075 if fnotes = "" then "notes" 1076 else do { 1077 try Unix.mkdir (Filename.concat bname "notes_d") 0o755 with _ -> (); 1078 Filename.concat "notes_d" (fnotes ^ ".txt") 1079 } 1080 in 1081 let fname = Filename.concat bname fname in 1082 do { 1083 try Sys.remove (fname ^ "~") with [ Sys_error _ -> () ]; 1084 try Sys.rename fname (fname ^ "~") with _ -> (); 1085 if s = "" then () 1086 else do { 1087 let oc = Secure.open_out fname in output_string oc s; close_out oc; () 1088 } 1089 } 1090 in 1091 let ext_files () = 1092 let top = Filename.concat bname "notes_d" in 1093 loop [] (Filename.current_dir_name) where rec loop list subdir = 1094 let dir = Filename.concat top subdir in 1095 match try Some (Sys.readdir dir) with [ Sys_error _ -> None ] with 1096 [ Some files -> 1097 List.fold_left 1098 (fun files file -> 1099 let f = Filename.concat subdir file in 1100 if Filename.check_suffix f ".txt" then 1101 [Filename.chop_suffix f ".txt" :: files] 1102 else loop files f) 1103 list (Array.to_list files) 1104 | None -> list ] 1105 in 1106 let bnotes = 1107 {nread = read_notes; norigin_file = norigin_file; efiles = ext_files} 1108 in 1109 let base_data = 1110 {persons = persons; ascends = ascends; unions = unions; 1111 visible = make_visible_record_access bname persons; 1112 families = families; couples = couples; descends = descends; 1113 strings = strings; particles = particles; bnotes = bnotes; 1114 bdir = bname} 1115 in 1116 let persons_of_name = persons_of_name bname patches.h_name in 1117 let base_func = 1118 {person_of_key = person_of_key persons strings persons_of_name; 1119 persons_of_name = persons_of_name; 1120 strings_of_fsname = strings_of_fsname bname strings patches.h_person; 1121 persons_of_surname = 1122 persons_of_first_name_or_surname base_data strings 1123 (ic2, ic2_surname_start_pos, fun p -> p.surname, 1124 snd patches.h_person, "snames.inx", "snames.dat", bname); 1125 persons_of_first_name = 1126 persons_of_first_name_or_surname base_data strings 1127 (ic2, ic2_first_name_start_pos, fun p -> p.first_name, 1128 snd patches.h_person, "fnames.inx", "fnames.dat", bname); 1129 patch_person = patch_person; patch_ascend = patch_ascend; 1130 patch_union = patch_union; patch_family = patch_family; 1131 patch_couple = patch_couple; patch_descend = patch_descend; 1132 patch_name = patch_name; insert_string = insert_string; 1133 commit_patches = commit_patches; 1134 patched_ascends = patched_ascends; 1135 is_patched_person = is_patched_person; 1136 commit_notes = commit_notes; cleanup = cleanup} 1137 in 1138 {data = base_data; func = base_func} 1139; 1140