572 lines
18 KiB
OCaml
572 lines
18 KiB
OCaml
(* Copyright (c) 1998-2007 INRIA *)
|
|
|
|
open Dbdisk
|
|
|
|
type istr = int
|
|
type ifam = int
|
|
type iper = int
|
|
|
|
let string_of_iper = string_of_int
|
|
let string_of_ifam = string_of_int
|
|
let string_of_istr = string_of_int
|
|
let iper_of_string = int_of_string
|
|
let ifam_of_string = int_of_string
|
|
let istr_of_string = int_of_string
|
|
let dummy_iper = -1
|
|
let dummy_ifam = -1
|
|
let empty_string = 0
|
|
let quest_string = 1
|
|
let eq_istr i1 i2 = i1 = i2
|
|
let eq_ifam i1 i2 = i1 = i2
|
|
let eq_iper i1 i2 = i1 = i2
|
|
let is_empty_string istr = istr = 0
|
|
let is_quest_string istr = istr = 1
|
|
|
|
type string_person_index = Dbdisk.string_person_index
|
|
|
|
let spi_find spi = spi.find
|
|
let spi_first spi = spi.cursor
|
|
let spi_next (spi : string_person_index) istr = spi.next istr
|
|
|
|
type base = dsk_base
|
|
|
|
let open_base bname : base = Database.opendb bname
|
|
let sou base i = base.data.strings.get i
|
|
let bname base = Filename.(remove_extension @@ basename base.data.bdir)
|
|
let nb_of_persons base = base.data.persons.len
|
|
let nb_of_real_persons base = base.func.nb_of_real_persons ()
|
|
let nb_of_families base = base.data.families.len
|
|
|
|
let insert_string base s =
|
|
base.func.Dbdisk.insert_string @@ Mutil.normalize_utf_8 s
|
|
|
|
let commit_patches base = base.func.Dbdisk.commit_patches ()
|
|
let commit_notes base s = base.func.Dbdisk.commit_notes s
|
|
let person_of_key base = base.func.Dbdisk.person_of_key
|
|
let persons_of_name base = base.func.Dbdisk.persons_of_name
|
|
let persons_of_first_name base = base.func.Dbdisk.persons_of_first_name
|
|
let persons_of_surname base = base.func.Dbdisk.persons_of_surname
|
|
let base_particles base = Lazy.force base.data.particles
|
|
let base_strings_of_first_name base s = base.func.strings_of_fname s
|
|
let base_strings_of_surname base s = base.func.strings_of_sname s
|
|
let load_ascends_array base = base.data.ascends.load_array ()
|
|
let load_unions_array base = base.data.unions.load_array ()
|
|
let load_couples_array base = base.data.couples.load_array ()
|
|
let load_descends_array base = base.data.descends.load_array ()
|
|
let load_strings_array base = base.data.strings.load_array ()
|
|
let load_persons_array base = base.data.persons.load_array ()
|
|
let load_families_array base = base.data.families.load_array ()
|
|
let clear_ascends_array base = base.data.ascends.clear_array ()
|
|
let clear_unions_array base = base.data.unions.clear_array ()
|
|
let clear_couples_array base = base.data.couples.clear_array ()
|
|
let clear_descends_array base = base.data.descends.clear_array ()
|
|
let clear_strings_array base = base.data.strings.clear_array ()
|
|
let clear_persons_array base = base.data.persons.clear_array ()
|
|
let clear_families_array base = base.data.families.clear_array ()
|
|
|
|
let close_base base =
|
|
base.func.cleanup ();
|
|
clear_ascends_array base;
|
|
clear_unions_array base;
|
|
clear_couples_array base;
|
|
clear_descends_array base;
|
|
clear_strings_array base;
|
|
clear_persons_array base;
|
|
clear_families_array base;
|
|
()
|
|
|
|
let date_of_last_change base =
|
|
let s =
|
|
let bdir = base.data.bdir in
|
|
try Unix.stat (Filename.concat bdir "patches")
|
|
with Unix.Unix_error (_, _, _) -> Unix.stat (Filename.concat bdir "base")
|
|
in
|
|
s.Unix.st_mtime
|
|
|
|
let gen_gen_person_misc_names = Dutil.dsk_person_misc_names
|
|
|
|
let patch_misc_names base ip (p : (iper, iper, istr) Def.gen_person) =
|
|
let p = { p with Def.key_index = ip } in
|
|
List.iter
|
|
(fun s -> base.func.Dbdisk.patch_name s ip)
|
|
(gen_gen_person_misc_names base p (fun p -> p.Def.titles))
|
|
|
|
let patch_person base ip (p : (iper, iper, istr) Def.gen_person) =
|
|
base.func.Dbdisk.patch_person ip p;
|
|
let s = sou base p.first_name ^ " " ^ sou base p.surname in
|
|
base.func.Dbdisk.patch_name s ip;
|
|
patch_misc_names base ip p;
|
|
Array.iter
|
|
(fun i ->
|
|
let cpl = base.data.couples.get i in
|
|
let m = Adef.mother cpl in
|
|
let f = Adef.father cpl in
|
|
patch_misc_names base m (base.data.persons.get m);
|
|
patch_misc_names base f (base.data.persons.get f);
|
|
Array.iter
|
|
(fun i -> patch_misc_names base i (base.data.persons.get i))
|
|
(base.data.descends.get i).children)
|
|
(base.data.unions.get ip).family
|
|
|
|
let patch_ascend base ip a = base.func.Dbdisk.patch_ascend ip a
|
|
let patch_union base ip u = base.func.Dbdisk.patch_union ip u
|
|
let patch_family base ifam f = base.func.Dbdisk.patch_family ifam f
|
|
let patch_couple base ifam c = base.func.Dbdisk.patch_couple ifam c
|
|
let patch_descend base ifam d = base.func.Dbdisk.patch_descend ifam d
|
|
let insert_person = patch_person
|
|
let insert_ascend = patch_ascend
|
|
let insert_union = patch_union
|
|
let insert_family = patch_family
|
|
let insert_couple = patch_couple
|
|
let insert_descend = patch_descend
|
|
|
|
let delete_person base ip =
|
|
patch_person base ip
|
|
{
|
|
first_name = quest_string;
|
|
surname = quest_string;
|
|
occ = 0;
|
|
image = empty_string;
|
|
first_names_aliases = [];
|
|
surnames_aliases = [];
|
|
public_name = empty_string;
|
|
qualifiers = [];
|
|
titles = [];
|
|
rparents = [];
|
|
related = [];
|
|
aliases = [];
|
|
occupation = empty_string;
|
|
sex = Neuter;
|
|
access = Private;
|
|
birth = Date.cdate_None;
|
|
birth_place = empty_string;
|
|
birth_note = empty_string;
|
|
birth_src = empty_string;
|
|
baptism = Date.cdate_None;
|
|
baptism_place = empty_string;
|
|
baptism_note = empty_string;
|
|
baptism_src = empty_string;
|
|
death = DontKnowIfDead;
|
|
death_place = empty_string;
|
|
death_note = empty_string;
|
|
death_src = empty_string;
|
|
burial = UnknownBurial;
|
|
burial_place = empty_string;
|
|
burial_note = empty_string;
|
|
burial_src = empty_string;
|
|
pevents = [];
|
|
notes = empty_string;
|
|
psources = empty_string;
|
|
key_index = ip;
|
|
}
|
|
|
|
let delete_ascend base ip =
|
|
patch_ascend base ip { parents = None; consang = Adef.no_consang }
|
|
|
|
let delete_union base ip = patch_union base ip { family = [||] }
|
|
|
|
let delete_family base ifam =
|
|
patch_family base ifam
|
|
{
|
|
marriage = Date.cdate_None;
|
|
marriage_place = empty_string;
|
|
marriage_note = empty_string;
|
|
marriage_src = empty_string;
|
|
relation = Married;
|
|
divorce = NotDivorced;
|
|
fevents = [];
|
|
witnesses = [||];
|
|
comment = empty_string;
|
|
origin_file = empty_string;
|
|
fsources = empty_string;
|
|
fam_index = dummy_ifam;
|
|
}
|
|
|
|
let delete_couple base ifam =
|
|
patch_couple base ifam (Adef.couple dummy_iper dummy_iper)
|
|
|
|
let delete_descend base ifam = patch_descend base ifam { children = [||] }
|
|
let new_iper base = base.data.persons.len
|
|
let new_ifam base = base.data.families.len
|
|
|
|
(* FIXME: lock *)
|
|
let sync ?(scratch = false) base =
|
|
if base.data.perm = RDONLY && not scratch then
|
|
raise Def.(HttpExn (Forbidden, __LOC__))
|
|
else Outbase.output base
|
|
|
|
let make bname particles arrays : Dbdisk.dsk_base =
|
|
sync ~scratch:true (Database.make bname particles arrays);
|
|
open_base bname
|
|
|
|
let bfname base fname = Filename.concat base.data.bdir fname
|
|
|
|
module NLDB = struct
|
|
let magic = "GWNL0010"
|
|
|
|
let read base =
|
|
let fname = bfname base "notes_links" in
|
|
match try Some (open_in_bin fname) with Sys_error _ -> None with
|
|
| Some ic ->
|
|
let r =
|
|
if Mutil.check_magic magic ic then
|
|
(input_value ic : (iper, iper) Def.NLDB.t)
|
|
else failwith "unsupported nldb format"
|
|
in
|
|
close_in ic;
|
|
r
|
|
| None -> []
|
|
|
|
let write base db =
|
|
if base.data.perm = RDONLY then raise Def.(HttpExn (Forbidden, __LOC__))
|
|
else
|
|
let fname_tmp = bfname base "1notes_links" in
|
|
let fname_def = bfname base "notes_links" in
|
|
let fname_back = bfname base "notes_links~" in
|
|
let oc = open_out_bin fname_tmp in
|
|
output_string oc magic;
|
|
output_value oc (db : (iper, ifam) Def.NLDB.t);
|
|
close_out oc;
|
|
Mutil.rm fname_back;
|
|
Mutil.mv fname_def fname_back;
|
|
Sys.rename fname_tmp fname_def
|
|
end
|
|
|
|
let read_nldb = NLDB.read
|
|
let write_nldb = NLDB.write
|
|
let base_notes_origin_file base = base.data.bnotes.Def.norigin_file
|
|
let base_notes_dir _base = "notes_d"
|
|
let base_wiznotes_dir _base = "wiznotes"
|
|
|
|
let base_notes_read_aux base fnotes mode =
|
|
let fname =
|
|
if fnotes = "" then "notes" else Filename.concat "notes_d" (fnotes ^ ".txt")
|
|
in
|
|
try
|
|
let ic = Secure.open_in @@ Filename.concat base.data.bdir fname in
|
|
let str =
|
|
match mode with
|
|
| Def.RnDeg -> if in_channel_length ic = 0 then "" else " "
|
|
| Def.Rn1Ln -> ( try input_line ic with End_of_file -> "")
|
|
| Def.RnAll -> Mutil.input_file_ic ic
|
|
in
|
|
close_in ic;
|
|
str
|
|
with Sys_error _ -> ""
|
|
|
|
let base_notes_read base fnotes = base_notes_read_aux base fnotes Def.RnAll
|
|
|
|
let base_notes_read_first_line base fnotes =
|
|
base_notes_read_aux base fnotes Def.Rn1Ln
|
|
|
|
let base_notes_are_empty base fnotes =
|
|
base_notes_read_aux base fnotes Def.RnDeg = ""
|
|
|
|
type relation = (iper, istr) Def.gen_relation
|
|
type title = istr Def.gen_title
|
|
type pers_event = (iper, istr) Def.gen_pers_event
|
|
type fam_event = (iper, istr) Def.gen_fam_event
|
|
|
|
let cache f a get set x =
|
|
match get x with
|
|
| Some v -> v
|
|
| None ->
|
|
let v = f a in
|
|
set x (Some v);
|
|
v
|
|
|
|
(** Persons *)
|
|
|
|
type person = {
|
|
base : base;
|
|
iper : iper;
|
|
mutable p : (iper, iper, istr) gen_person option;
|
|
mutable a : ifam gen_ascend option;
|
|
mutable u : ifam gen_union option;
|
|
}
|
|
|
|
let cache_per f ({ base; iper; _ } as p) =
|
|
f (cache base.data.persons.get iper (fun p -> p.p) (fun p v -> p.p <- v) p)
|
|
|
|
let cache_asc f ({ base; iper; _ } as p) =
|
|
f (cache base.data.ascends.get iper (fun p -> p.a) (fun p v -> p.a <- v) p)
|
|
|
|
let cache_uni f ({ base; iper; _ } as p) =
|
|
f (cache base.data.unions.get iper (fun p -> p.u) (fun p v -> p.u <- v) p)
|
|
|
|
let gen_person_of_person = cache_per (fun p -> p)
|
|
let gen_ascend_of_person = cache_asc (fun p -> p)
|
|
let gen_union_of_person = cache_uni (fun p -> p)
|
|
let get_access = cache_per (fun p -> p.Def.access)
|
|
let get_aliases = cache_per (fun p -> p.Def.aliases)
|
|
let get_baptism = cache_per (fun p -> p.Def.baptism)
|
|
let get_baptism_note = cache_per (fun p -> p.Def.baptism_note)
|
|
let get_baptism_place = cache_per (fun p -> p.Def.baptism_place)
|
|
let get_baptism_src = cache_per (fun p -> p.Def.baptism_src)
|
|
let get_birth = cache_per (fun p -> p.Def.birth)
|
|
let get_birth_note = cache_per (fun p -> p.Def.birth_note)
|
|
let get_birth_place = cache_per (fun p -> p.Def.birth_place)
|
|
let get_birth_src = cache_per (fun p -> p.Def.birth_src)
|
|
let get_burial = cache_per (fun p -> p.Def.burial)
|
|
let get_burial_note = cache_per (fun p -> p.Def.burial_note)
|
|
let get_burial_place = cache_per (fun p -> p.Def.burial_place)
|
|
let get_burial_src = cache_per (fun p -> p.Def.burial_src)
|
|
let get_consang = cache_asc (fun a -> a.Def.consang)
|
|
let get_death = cache_per (fun p -> p.Def.death)
|
|
let get_death_note = cache_per (fun p -> p.Def.death_note)
|
|
let get_death_place = cache_per (fun p -> p.Def.death_place)
|
|
let get_death_src = cache_per (fun p -> p.Def.death_src)
|
|
let get_family = cache_uni (fun u -> u.Def.family)
|
|
let get_first_name = cache_per (fun p -> p.Def.first_name)
|
|
let get_first_names_aliases = cache_per (fun p -> p.Def.first_names_aliases)
|
|
let get_image = cache_per (fun p -> p.Def.image)
|
|
let get_iper = cache_per (fun p -> p.Def.key_index)
|
|
let get_notes = cache_per (fun p -> p.Def.notes)
|
|
let get_occ = cache_per (fun p -> p.Def.occ)
|
|
let get_occupation = cache_per (fun p -> p.Def.occupation)
|
|
let get_parents = cache_asc (fun a -> a.Def.parents)
|
|
let get_pevents = cache_per (fun p -> p.Def.pevents)
|
|
let get_psources = cache_per (fun p -> p.Def.psources)
|
|
let get_public_name = cache_per (fun p -> p.Def.public_name)
|
|
let get_qualifiers = cache_per (fun p -> p.Def.qualifiers)
|
|
let get_related = cache_per (fun p -> p.Def.related)
|
|
let get_rparents = cache_per (fun p -> p.Def.rparents)
|
|
let get_sex = cache_per (fun p -> p.Def.sex)
|
|
let get_surname = cache_per (fun p -> p.Def.surname)
|
|
let get_surnames_aliases = cache_per (fun p -> p.Def.surnames_aliases)
|
|
let get_titles = cache_per (fun p -> p.Def.titles)
|
|
|
|
(** Families *)
|
|
|
|
type family = {
|
|
base : base;
|
|
ifam : ifam;
|
|
mutable f : (iper, ifam, istr) gen_family option;
|
|
mutable c : iper gen_couple option;
|
|
mutable d : iper gen_descend option;
|
|
}
|
|
|
|
let cache_fam f ({ base; ifam; _ } as fam) =
|
|
f (cache base.data.families.get ifam (fun f -> f.f) (fun f v -> f.f <- v) fam)
|
|
|
|
let cache_cpl f ({ base; ifam; _ } as fam) =
|
|
f (cache base.data.couples.get ifam (fun f -> f.c) (fun f v -> f.c <- v) fam)
|
|
|
|
let cache_des f ({ base; ifam; _ } as fam) =
|
|
f (cache base.data.descends.get ifam (fun f -> f.d) (fun f v -> f.d <- v) fam)
|
|
|
|
let gen_couple_of_family = cache_cpl (fun c -> c)
|
|
let gen_descend_of_family = cache_des (fun d -> d)
|
|
let gen_family_of_family = cache_fam (fun f -> f)
|
|
let get_children = cache_des (fun d -> d.Def.children)
|
|
let get_comment = cache_fam (fun f -> f.Def.comment)
|
|
let get_ifam = cache_fam (fun f -> f.Def.fam_index)
|
|
let get_divorce = cache_fam (fun f -> f.Def.divorce)
|
|
let get_father = cache_cpl (fun c -> Adef.father c)
|
|
let get_fevents = cache_fam (fun f -> f.Def.fevents)
|
|
let get_fsources = cache_fam (fun f -> f.Def.fsources)
|
|
let get_marriage = cache_fam (fun f -> f.Def.marriage)
|
|
let get_marriage_note = cache_fam (fun f -> f.Def.marriage_note)
|
|
let get_marriage_place = cache_fam (fun f -> f.Def.marriage_place)
|
|
let get_marriage_src = cache_fam (fun f -> f.Def.marriage_src)
|
|
let get_mother = cache_cpl (fun c -> Adef.mother c)
|
|
let get_origin_file = cache_fam (fun f -> f.Def.origin_file)
|
|
let get_parent_array = cache_cpl (fun c -> Adef.parent_array c)
|
|
let get_relation = cache_fam (fun f -> f.Def.relation)
|
|
let get_witnesses = cache_fam (fun f -> f.Def.witnesses)
|
|
|
|
let no_person ip =
|
|
{ (Mutil.empty_person empty_string empty_string) with key_index = ip }
|
|
|
|
let no_ascend = { parents = None; consang = Adef.no_consang }
|
|
let no_union = { family = [||] }
|
|
|
|
let empty_person base iper =
|
|
{
|
|
base;
|
|
iper;
|
|
p = Some (no_person iper);
|
|
a = Some no_ascend;
|
|
u = Some no_union;
|
|
}
|
|
[@ocaml.warning "-42"]
|
|
|
|
let person_of_gen_person base (p, a, u) =
|
|
{ base; iper = p.key_index; p = Some p; a = Some a; u = Some u }
|
|
[@ocaml.warning "-42"]
|
|
|
|
let family_of_gen_family base (f, c, d) =
|
|
{ base; ifam = f.fam_index; f = Some f; c = Some c; d = Some d }
|
|
[@ocaml.warning "-42"]
|
|
|
|
let iper_exists base = base.func.iper_exists
|
|
let ifam_exists base = base.func.ifam_exists
|
|
|
|
let poi base iper =
|
|
if iper = dummy_iper then empty_person base iper
|
|
else { base; iper; p = None; a = None; u = None } [@ocaml.warning "-42"]
|
|
|
|
let no_family ifam = { (Mutil.empty_family empty_string) with fam_index = ifam }
|
|
let no_couple = Adef.couple dummy_iper dummy_iper
|
|
let no_descend = { Def.children = [||] }
|
|
|
|
let empty_family base ifam =
|
|
{
|
|
base;
|
|
ifam;
|
|
f = Some (no_family ifam);
|
|
c = Some no_couple;
|
|
d = Some no_descend;
|
|
}
|
|
|
|
let foi base ifam =
|
|
if ifam = dummy_ifam then empty_family base ifam
|
|
else { base; ifam; f = None; c = None; d = None }
|
|
|
|
module Collection = struct
|
|
type 'a t = { length : int; get : int -> 'a option }
|
|
|
|
let map (fn : 'a -> 'b) c =
|
|
{
|
|
length = c.length;
|
|
get = (fun i -> match c.get i with Some x -> Some (fn x) | None -> None);
|
|
}
|
|
|
|
let length { length; _ } = length
|
|
|
|
let iter fn { get; length } =
|
|
for i = 0 to length - 1 do
|
|
match get i with Some x -> fn x | None -> ()
|
|
done
|
|
|
|
let iteri fn { get; length } =
|
|
for i = 0 to length - 1 do
|
|
match get i with Some x -> fn i x | None -> ()
|
|
done
|
|
|
|
let fold ?from ?until fn acc { get; length } =
|
|
let from = match from with Some x -> x | None -> 0 in
|
|
let until = match until with Some x -> x + 1 | None -> length in
|
|
let rec loop acc i =
|
|
if i = until then acc
|
|
else loop (match get i with Some x -> fn acc x | None -> acc) (i + 1)
|
|
in
|
|
loop acc from
|
|
|
|
let fold_until continue fn acc { get; length } =
|
|
let rec loop acc i =
|
|
if (not (continue acc)) || i = length then acc
|
|
else loop (match get i with Some x -> fn acc x | None -> acc) (i + 1)
|
|
in
|
|
loop acc 0
|
|
|
|
let iterator { get; length } =
|
|
let cursor = ref 0 in
|
|
let rec next () =
|
|
if !cursor < length then (
|
|
match get !cursor with
|
|
| None ->
|
|
incr cursor;
|
|
next ()
|
|
| v ->
|
|
incr cursor;
|
|
v)
|
|
else None
|
|
in
|
|
next
|
|
end
|
|
|
|
module Marker = struct
|
|
type ('k, 'v) t = { get : 'k -> 'v; set : 'k -> 'v -> unit }
|
|
|
|
let make (k : 'a -> int) (c : 'a Collection.t) (i : 'v) : ('a, 'v) t =
|
|
let a = Array.make c.Collection.length i in
|
|
{
|
|
get = (fun x -> Array.get a (k x));
|
|
set = (fun x v -> Array.set a (k x) v);
|
|
}
|
|
|
|
let get ({ get; _ } : _ t) k = get k
|
|
let set ({ set; _ } : _ t) k = set k
|
|
end
|
|
|
|
let persons base =
|
|
{ Collection.length = nb_of_persons base; get = (fun i -> Some (poi base i)) }
|
|
|
|
let ipers base =
|
|
{ Collection.length = nb_of_persons base; get = (fun i -> Some i) }
|
|
|
|
let iper_marker c i = Marker.make (fun i -> i) c i
|
|
|
|
let ifams ?(select = fun _ -> true) base =
|
|
{
|
|
Collection.length = nb_of_families base;
|
|
get =
|
|
(fun i ->
|
|
if select i then
|
|
if get_ifam (foi base i) = dummy_ifam then None else Some i
|
|
else None);
|
|
}
|
|
|
|
let families ?(select = fun _ -> true) base =
|
|
{
|
|
Collection.length = nb_of_families base;
|
|
get =
|
|
(fun i ->
|
|
let f = foi base i in
|
|
if get_ifam f <> dummy_ifam && select f then Some f else None);
|
|
}
|
|
|
|
let dummy_collection _ = { Collection.length = -1; get = (fun _ -> None) }
|
|
let ifam_marker c i = Marker.make (fun i -> i) c i
|
|
|
|
let dummy_marker (_ : 'a) (v : 'b) : ('a, 'b) Marker.t =
|
|
{ Marker.get = (fun _ -> v); set = (fun _ _ -> ()) }
|
|
|
|
(* Restrict file *)
|
|
|
|
(* FIXME: these values should not be global *)
|
|
let visible_ref : (iper, bool) Hashtbl.t option ref = ref None
|
|
|
|
let read_or_create_visible base =
|
|
let fname = Filename.concat base.data.bdir "restrict" in
|
|
let visible =
|
|
if Sys.file_exists fname then (
|
|
let ic = Secure.open_in fname in
|
|
let visible =
|
|
if Mutil.check_magic Mutil.executable_magic ic then input_value ic
|
|
else Hashtbl.create (nb_of_persons base)
|
|
in
|
|
close_in ic;
|
|
visible)
|
|
else Hashtbl.create (nb_of_persons base)
|
|
in
|
|
visible_ref := Some visible;
|
|
visible
|
|
|
|
let base_visible_write base =
|
|
if base.data.perm = RDONLY then raise Def.(HttpExn (Forbidden, __LOC__))
|
|
else
|
|
let fname = Filename.concat base.data.bdir "restrict" in
|
|
match !visible_ref with
|
|
| Some visible ->
|
|
let oc = Secure.open_out fname in
|
|
output_string oc Mutil.executable_magic;
|
|
output_value oc visible;
|
|
close_out oc
|
|
| None -> ()
|
|
|
|
let base_visible_get base fct i =
|
|
let visible =
|
|
match !visible_ref with
|
|
| Some visible -> visible
|
|
| None -> read_or_create_visible base
|
|
in
|
|
match Hashtbl.find_opt visible i with
|
|
| None ->
|
|
let status = fct (poi base i) in
|
|
Hashtbl.add visible i status;
|
|
visible_ref := Some visible;
|
|
status
|
|
| Some b -> b
|