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