Changeset 3 in code for trunk/lib/file_store.ml
- Timestamp:
- Apr 15, 2022, 1:17:01 PM (3 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/lib/file_store.ml
r2 r3 1 1 type t = string 2 type item_t = string 3 type archive_t = { 4 name: string; archivists: Person.Set.t; id: Id.t; 5 kv: string Store.KV.t; store: t } 2 type item_t = t list 6 3 type record_t = Text.t * item_t 7 4 8 5 let extension = ".txt" 6 let def_dir = try Sys.getenv "LOGARION_DIR" with Not_found -> "." 9 7 10 8 let to_string f = 11 9 let ic = open_in f in 12 let n = in_channel_length ic in 13 let s = Bytes.create n in 14 really_input ic s 0 n; 10 let s = really_input_string ic (in_channel_length ic) in 15 11 close_in ic; 16 Bytes.to_strings12 s 17 13 18 let file path content = let out = open_out path in 19 output_string out content; close_out out 14 let fold_file_line fn init file = match open_in file with 15 | exception (Sys_error msg) -> prerr_endline msg; init 16 | file -> 17 let rec read acc = match input_line file with 18 | "" as s | s when String.get s 0 = '#' -> read acc 19 | s -> read (fn s acc) 20 | exception End_of_file -> close_in file; acc 21 in read init 20 22 21 let (//) a b = a ^ "/" ^ b23 let file path str = let o = open_out path in output_string o str; close_out o 22 24 23 25 let to_text path = 24 26 if Filename.extension path = extension then 25 27 (to_string path |> Text.of_string |> Result.map_error (fun m -> path ^": "^ m)) 26 else Error "Not txt"28 else Error (Printf.sprintf "Not txt: %s" path) 27 29 28 30 let newest (a,_pa) (b,_pb) = Text.newest a b 29 31 let oldest (a,_pa) (b,_pb) = Text.oldest a b 30 32 31 let list_iter fn {store;_}paths =32 let link f = match to_text (Filename.concat store f)33 with Ok t -> fn storet f | Error s -> prerr_endline s in33 let list_iter fn dir paths = 34 let link f = match to_text (Filename.concat dir f) with 35 | Ok t -> fn dir t f | Error s -> prerr_endline s in 34 36 List.iter link paths 35 37 36 let iter_valid_text pred fn p = 37 match to_text p with Error _ -> () | Ok t -> if pred t then fn (t, p) 38 module TextMap = Map.Make(Text) 38 39 39 let fold_valid_text pred fn acc p = 40 match to_text p with Error _ -> acc | Ok t -> if pred t then fn acc (t, p) else acc 40 type iteration_t = item_t TextMap.t 41 let new_iteration = TextMap.empty 41 42 42 let list_fs dir = 43 (*let iter_valid_text pred fn path =*) 44 (* match to_text path with Error _ -> () | Ok t -> if pred t then fn (t, p)*) 45 46 let fold_valid_text pred it path = 47 match to_text path with Error _ -> it 48 | Ok t -> if pred t then (TextMap.update t 49 (function None -> Some [path] | Some ps -> Some (path::ps)) it 50 ) else it 51 52 (* Compare file system nodes to skip reparsing? *) 53 let list_fs ?(r=false) dir = 54 let valid_dir f = r && String.get f 0 <> '.' && Sys.is_directory f in 55 let expand_dir d = Array.(to_list @@ map (Filename.concat d) (Sys.readdir d)) in 43 56 let rec loop result = function 44 | [] -> result 45 | f::fs when Sys.is_directory f -> 46 Array.map (Filename.concat f) (Sys.readdir f) 47 |> Array.to_list |> List.append fs |> loop result 57 | f::fs when valid_dir f -> expand_dir f |> List.append fs |> loop result 48 58 | f::fs -> loop (f::result) fs 49 in loop [] [dir] 59 | [] -> result in 60 let dirs = if dir = "." then Array.to_list (Sys.readdir dir) else 61 if not r then expand_dir dir else [dir] in 62 loop [] dirs 50 63 51 64 let list_take n = … … 55 68 in take [] n 56 69 57 let iter ?(predicate=fun _ -> true) ?order ?number fn {store;_} = 58 match order with 59 | None -> List.iter (iter_valid_text predicate fn) @@ list_fs store 60 | Some comp -> 61 List.iter fn 62 @@ (match number with None -> (fun x -> x) | Some n -> list_take n) 63 @@ List.fast_sort comp 64 @@ List.fold_left (fold_valid_text predicate (fun a e -> List.cons e a)) [] 65 @@ list_fs store 70 let fold_sort_take ?(predicate=fun _ -> true) ?(number=None) comp flist = 71 (match number with None -> (fun x -> x) | Some n -> list_take n) 72 @@ List.fast_sort comp @@ TextMap.bindings 73 @@ List.fold_left (fold_valid_text predicate) new_iteration flist 66 74 67 let fold ?(predicate=fun _ -> true) ?order ?number fn acc {store;_} = 68 match order with 69 | None -> List.fold_left (fold_valid_text predicate fn) acc @@ list_fs store 70 | Some comp -> 71 List.fold_left fn acc 72 @@ (match number with None -> (fun x -> x) | Some n -> list_take n) 73 @@ List.fast_sort comp 74 @@ List.fold_left (fold_valid_text predicate (fun a e -> List.cons e a)) [] 75 @@ list_fs store 75 let iter ?(r=false) ?(dir=def_dir) ?(predicate=fun _ -> true) ?order ?number fn = 76 let flist = list_fs ~r dir in match order with 77 | Some comp -> List.iter fn @@ fold_sort_take ~predicate ~number comp flist 78 | None -> List.iter fn @@ TextMap.bindings @@ 79 List.fold_left (fold_valid_text predicate) new_iteration flist 76 80 77 let with_id { store; _ } id = 81 let fold ?(r=false) ?(dir=def_dir) ?(predicate=fun _ -> true) ?order ?number fn acc = 82 let flist = list_fs ~r dir in match order with 83 | Some comp -> List.fold_left fn acc @@ fold_sort_take ~predicate ~number comp flist 84 | None -> List.fold_left fn acc @@ TextMap.bindings @@ 85 List.fold_left (fold_valid_text predicate) new_iteration flist 86 87 let with_id ?(r=false) ?(dir=def_dir) id = 78 88 let matched acc path = 79 89 match to_text path with 80 90 | Error x -> prerr_endline x; acc 81 | Ok text when text.Text. uuid <> id -> acc91 | Ok text when text.Text.id <> id -> acc 82 92 | Ok text -> 83 93 match acc with … … 85 95 | Ok (Some prev) -> if prev = text then acc else Error [text; prev] 86 96 | Error x -> Error (text :: x) 87 in List.fold_left matched (Ok None) (list_fs store)97 in List.fold_left matched (Ok None) (list_fs ~r dir) 88 98 89 module Directory = struct 90 let print ?(descr="") dir result = 91 let () = match result with 92 | Ok true -> print_endline ("Created " ^ descr ^ " directory " ^ dir) 93 | Ok false -> print_endline ("Using existing " ^ descr ^ " directory " ^ dir) 94 | Error (`Msg msg) -> prerr_endline @@ "Failed making " ^ descr ^ ". " ^ msg 95 in 96 result 99 let with_dir ?(descr="") ?(perm=0o740) dir = 100 let mkdir dir = match Unix.mkdir dir perm with 101 | exception Unix.Unix_error (EEXIST, _, _) -> () 102 | exception Unix.Unix_error (code, _fn, arg) -> 103 failwith @@ Printf.sprintf "Error %s making %s dir: %s" 104 (Unix.error_message code) descr arg 105 | _ -> () in 106 let rec mkeach path = function [] | [""] -> () | ""::t -> mkeach path t 107 | hd::t -> let d = Filename.concat path hd in mkdir d; mkeach d t in 108 mkeach 109 (if Filename.is_relative dir then "" else "/") 110 (String.split_on_char '/' dir) 97 111 98 let directory dir = Result.bind (Fpath.of_string dir) Bos.OS.Dir.create 99 100 let rec directories = function 101 | [] -> Ok () 102 | (d, descr)::tl -> 103 match directory d |> print ~descr d with 104 | Ok _ -> directories tl 105 | Error _ -> Error (d, descr) 106 end 107 108 let copy ?(recursive = false) src dst = 109 Bos.OS.Cmd.run (Bos.Cmd.(v "cp" %% (on recursive @@ v "-r") % src % dst)) 112 let rec with_dirs = function [] -> () | (d, descr)::tl -> with_dir ~descr d; with_dirs tl 110 113 111 114 let versioned_basename_of_title ?(version=0) repo extension (title : string) = 112 115 let basename = Text.string_alias title in 113 116 let rec next version = 114 let candidate = repo // basename ^ "." ^ string_of_int version ^ extension in 117 let candidate = Filename.concat repo 118 (basename ^ "." ^ string_of_int version ^ extension) in 115 119 if Sys.file_exists candidate then next (succ version) else candidate 116 120 in 117 121 next version 118 122 119 let uuid_filename repo extension text =123 let id_filename repo extension text = 120 124 let basename = Text.alias text in 121 let candidate = repo // String.sub (Id.to_string text.uuid) 0 6 ^ "." ^ basename ^ extensionin125 let candidate = Filename.concat repo (text.id ^ "." ^ basename ^ extension) in 122 126 if Sys.file_exists candidate then Error "Name clash, try again" else Ok candidate 123 127 124 let with_text {store;_} new_text = 125 Result.bind (uuid_filename store extension new_text) @@ 126 fun path -> 127 try file path (Text.to_string new_text); Ok (path, new_text) with Sys_error s -> Error s 128 129 let basic_config () = 130 "Archive-Name: " 131 ^ "\nArchive-ID: " ^ Id.(generate () |> to_string) 132 ^ "\nArchivists: " ^ Bos.OS.Env.opt_var "USER" ~absent:"" 133 |> Bytes.of_string 134 135 let init ?(dotdir=".logarion/") () = 136 match Directory.directories [dotdir, "dotdir"] with 137 | Error (_dir, _desc) -> () 138 | Ok () -> 139 let config_file = 140 open_out_gen [Open_creat; Open_excl; Open_wronly] 141 0o700 (dotdir // "config") in 142 output_bytes config_file (basic_config ()); 143 close_out config_file 128 let with_text ?(dir=def_dir) new_text = 129 match id_filename dir extension new_text with 130 | Error _ as e -> e 131 | Ok path -> 132 try file path (Text.to_string new_text); Ok (path, new_text) 133 with Sys_error s -> Error s 144 134 145 135 module Config = struct 146 type t = archive_t 147 let key_value k v a = match k with 148 | "Archive-Name" -> { a with name = String.trim v } 149 | "Archive-ID" -> { a with id = Option.get (Id.of_string (String.trim v)) } 150 | "Archivists" -> { a with archivists = Person.Set.of_string v } 151 | _ -> { a with kv = Store.KV.add k (String.trim v) a.kv } 136 type t = string Store.KV.t 137 let key_value k v a = Store.KV.add k (String.trim v) a 152 138 end 153 139 154 let of_ path store=140 let of_kv_file path = 155 141 let open Text_parse in 156 let subsyntaxes = [| (module Parsers.Key_value.Make (Config) : Parser.S with type t = Config.t); (module Parsers.Key_value.Make (Config)); |] in 157 let of_string text acc = Parser.parse subsyntaxes { text; pos = 0; right_boundary = String.length text - 1 } acc in 158 Ok ( 159 of_string (to_string @@ store ^ "/.logarion/config") { 160 name = ""; 161 archivists = Person.Set.empty; 162 id = Id.nil; 163 kv = Store.KV.empty; 164 store = try Sys.getenv "LOGARION_DIR" with Not_found -> "." 165 } 166 ) 142 let subsyntaxes = Parsers.Key_value.[| 143 (module Make (Config) : Parser.S with type t = Config.t); (module Make (Config)); |] in 144 let of_string text acc = 145 Parser.parse subsyntaxes { text; pos = 0; right_boundary = String.length text - 1 } acc in 146 of_string (to_string @@ path) Store.KV.empty
Note:
See TracChangeset
for help on using the changeset viewer.