[3] | 1 | let writer accum data =
|
---|
| 2 | Buffer.add_string accum data;
|
---|
| 3 | String.length data
|
---|
| 4 |
|
---|
| 5 | let showContent content =
|
---|
| 6 | Printf.printf "%s" (Buffer.contents content);
|
---|
| 7 | flush stdout
|
---|
| 8 |
|
---|
| 9 | let showInfo connection =
|
---|
| 10 | Printf.printf "Time: %f for: %s\n"
|
---|
| 11 | (Curl.get_totaltime connection)
|
---|
| 12 | (Curl.get_effectiveurl connection)
|
---|
| 13 |
|
---|
| 14 | let getContent connection url =
|
---|
| 15 | Curl.set_url connection url;
|
---|
| 16 | Curl.perform connection
|
---|
| 17 |
|
---|
| 18 | let curl_pull url =
|
---|
| 19 | let result = Buffer.create 4069
|
---|
| 20 | and errorBuffer = ref "" in
|
---|
| 21 | let connection = Curl.init () in
|
---|
| 22 | try
|
---|
| 23 | Curl.set_errorbuffer connection errorBuffer;
|
---|
| 24 | Curl.set_writefunction connection (writer result);
|
---|
| 25 | Curl.set_followlocation connection true;
|
---|
| 26 | Curl.set_url connection url;
|
---|
| 27 | Curl.perform connection;
|
---|
| 28 | (* showContent result;*)
|
---|
| 29 | (* showInfo connection;*)
|
---|
| 30 | Curl.cleanup connection;
|
---|
| 31 | Ok result
|
---|
| 32 | with
|
---|
| 33 | | Curl.CurlException (_reason, _code, _str) ->
|
---|
| 34 | Curl.cleanup connection;
|
---|
| 35 | Error (Printf.sprintf "Error: %s %s" url !errorBuffer)
|
---|
| 36 | | Failure s ->
|
---|
| 37 | Curl.cleanup connection;
|
---|
| 38 | Error (Printf.sprintf "Caught exception: %s" s)
|
---|
| 39 |
|
---|
| 40 | let newer time id dir =
|
---|
| 41 | match Logarion.File_store.to_text @@ Filename.(concat dir (Logarion.Id.short id) ^ ".txt") with
|
---|
| 42 | | Error x -> prerr_endline x; true
|
---|
| 43 | | Ok txt -> time > (Logarion.(Header_pack.date (Date.listing txt.date)))
|
---|
| 44 | | exception (Sys_error _) -> true
|
---|
| 45 |
|
---|
| 46 | let print_peers p =
|
---|
| 47 | let open Logarion.Header_pack in
|
---|
| 48 | match Msgpck.to_list p.peers with [] -> ()
|
---|
| 49 | | ps -> print_endline @@
|
---|
| 50 | List.fold_left (fun a x -> Printf.sprintf "%s %s" a (Msgpck.to_string x)) "peers: " ps
|
---|
| 51 |
|
---|
[7] | 52 | type filter_t = { authors: Logarion.Person.Set.t; topics: Logarion.String_set.t }
|
---|
[3] | 53 |
|
---|
[7] | 54 | let print_pull_start width total title dir =
|
---|
| 55 | Printf.printf "%*d/%s %s => %s %!" width 0 total title dir
|
---|
| 56 |
|
---|
| 57 | let print_pull width total i =
|
---|
| 58 | Printf.printf "\r%*d/%s %!" width (i+1) total
|
---|
| 59 |
|
---|
| 60 | let printers total title dir =
|
---|
| 61 | let width = String.length total in
|
---|
| 62 | print_pull_start width total title dir;
|
---|
| 63 | print_pull width total
|
---|
| 64 |
|
---|
[3] | 65 | let fname dir text = Filename.concat dir (Logarion.Text.short_id text ^ ".txt")
|
---|
[7] | 66 |
|
---|
[3] | 67 | let pull_text url dir id =
|
---|
| 68 | let u = Filename.concat url ((Logarion.Id.short id) ^ ".txt") in
|
---|
| 69 | match curl_pull u with
|
---|
| 70 | | Error msg -> Printf.eprintf "Failed getting %s: %s" u msg
|
---|
[7] | 71 | | Ok txt -> let txt = Buffer.contents txt in
|
---|
[3] | 72 | match Logarion.Text.of_string txt with
|
---|
| 73 | | Error s -> prerr_endline s
|
---|
| 74 | | Ok text ->
|
---|
| 75 | let file = open_out_gen [Open_creat; Open_trunc; Open_wronly] 0o640 (fname dir text) in
|
---|
| 76 | output_string file txt; close_out file
|
---|
| 77 |
|
---|
[42] | 78 | let per_text url dir filter print i id time title authors topics _refs _reps = match id with
|
---|
[7] | 79 | | "" -> Printf.eprintf "\nInvalid id for %s\n" title
|
---|
| 80 | | id -> let open Logarion in
|
---|
| 81 | print i;
|
---|
| 82 | if newer time id dir
|
---|
| 83 | && (String_set.empty = filter.topics
|
---|
| 84 | || String_set.exists (fun t -> List.mem t topics) filter.topics)
|
---|
| 85 | && (Person.Set.empty = filter.authors
|
---|
| 86 | || Person.Set.exists (fun t -> List.mem (Person.to_string t) authors) filter.authors)
|
---|
| 87 | then pull_text url dir id
|
---|
| 88 |
|
---|
[31] | 89 | (*TODO: integrate in lib*)
|
---|
[30] | 90 | let validate_id_length s = String.length s <= 32
|
---|
| 91 | let validate_id_chars s = try
|
---|
| 92 | String.iter (function 'a'..'z'|'A'..'Z'|'0'..'9'-> () | _ -> raise (Invalid_argument "")) s;
|
---|
| 93 | true
|
---|
| 94 | with Invalid_argument _ -> false
|
---|
| 95 |
|
---|
[7] | 96 | let pull_index url authors_opt topics_opt =
|
---|
[31] | 97 | let index_url = Filename.concat url "index.pck" in
|
---|
[3] | 98 | match curl_pull index_url with
|
---|
| 99 | | Error s -> prerr_endline s; false
|
---|
| 100 | | Ok body ->
|
---|
| 101 | match Logarion.Header_pack.of_string (Buffer.contents body) with
|
---|
| 102 | | Error s -> Printf.printf "Error with %s: %s\n" url s; false
|
---|
[29] | 103 | | Ok pk when pk.info.id = "" ->
|
---|
| 104 | Printf.printf "Empty ID index.pck, skipping %s\n" url; false
|
---|
[30] | 105 | | Ok pk when not (validate_id_length pk.info.id) ->
|
---|
| 106 | Printf.printf "Index pack ID longer than 32 characters, skipping %s\n" url; false
|
---|
| 107 | | Ok pk when not (validate_id_chars pk.info.id) ->
|
---|
| 108 | Printf.printf "Index pack contains invalid ID characters, skipping %s\n" url; false
|
---|
[3] | 109 | | Ok pk ->
|
---|
| 110 | let dir = Filename.concat Logarion.Peers.text_dir pk.info.id in
|
---|
| 111 | Logarion.File_store.with_dir dir;
|
---|
[7] | 112 | let file = open_out_gen [Open_creat; Open_trunc; Open_wronly] 0o640
|
---|
| 113 | (Filename.concat dir "index.pck") in
|
---|
[3] | 114 | output_string file ( Logarion.Header_pack.string {
|
---|
| 115 | pk with info = { pk.info with locations = url::pk.info.locations }});
|
---|
| 116 | close_out file;
|
---|
[7] | 117 | let filter = let open Logarion in {
|
---|
| 118 | authors = (match authors_opt with Some s -> Person.Set.of_string s | None -> Person.Set.empty);
|
---|
| 119 | topics =( match topics_opt with Some s -> String_set.of_string s | None -> String_set.empty);
|
---|
| 120 | } in
|
---|
[28] | 121 | let name = match pk.info.title with "" -> url | title -> title in
|
---|
| 122 | let print = printers (string_of_int @@ Logarion.Header_pack.numof_texts pk) name dir in
|
---|
[7] | 123 | try Logarion.Header_pack.iteri (per_text url dir filter print) pk; print_newline (); true
|
---|
[29] | 124 | with Invalid_argument msg -> Printf.printf "\nFailed to parse %s: %s\n%!" url msg; false
|
---|
[3] | 125 |
|
---|
| 126 | let pull_list auths topics =
|
---|
| 127 | Curl.global_init Curl.CURLINIT_GLOBALALL;
|
---|
| 128 | let pull got_one peer_url = if got_one then got_one else
|
---|
| 129 | (pull_index peer_url auths topics) in
|
---|
[42] | 130 | let open Logarion in
|
---|
[31] | 131 | let fold_locations init peer =
|
---|
[42] | 132 | ignore @@ List.fold_left pull init peer.Peers.pack.Header_pack.info.locations;
|
---|
[31] | 133 | false
|
---|
| 134 | in
|
---|
[42] | 135 | ignore @@ Peers.fold fold_locations false;
|
---|
[3] | 136 | Curl.global_cleanup ()
|
---|
| 137 |
|
---|
| 138 | let pull url auths topics = match url with
|
---|
| 139 | | "" -> pull_list auths topics | x -> ignore (pull_index x auths topics)
|
---|
| 140 |
|
---|
| 141 | open Cmdliner
|
---|
| 142 | let term =
|
---|
| 143 | let authors = Arg.(value & opt (some string) None & info ["a"; "authors"]
|
---|
| 144 | ~docv:"comma-separated names" ~doc:"filter by authors") in
|
---|
| 145 | let topics = Arg.(value & opt (some string) None & info ["t"; "topics"]
|
---|
| 146 | ~docv:"comma-separated topics" ~doc:"filter by topics") in
|
---|
| 147 | let url = Arg.(value & pos 0 string "" & info [] ~docv:"URL"
|
---|
| 148 | ~doc:"Repository location") in
|
---|
| 149 | Term.(const pull $ url $ authors $ topics),
|
---|
| 150 | Term.info "pull" ~doc:"pull listed texts" ~man:[ `S "DESCRIPTION";
|
---|
| 151 | `P "Pull texts from known repositories. To add a new repository use:";
|
---|
| 152 | `P "txt pull [url]";
|
---|
| 153 | `P ("This creates a directory in " ^ Logarion.Peers.text_dir
|
---|
| 154 | ^ " and downloads the text index.pck file in it")]
|
---|
| 155 |
|
---|
| 156 | (*module Msg = struct*)
|
---|
| 157 | (* type t = string * string*)
|
---|
| 158 | (* let compare (x0,y0) (x1,y1) =*)
|
---|
| 159 | (* match compare x1 x0 with 0 -> String.compare y0 y1 | c -> c*)
|
---|
| 160 | (*end*)
|
---|
| 161 | (*module MsgSet = Set.Make(Msg)*)
|
---|
| 162 | (*let pull_msgs url _authors _topics =*)
|
---|
| 163 | (* match http_apply response url with*)
|
---|
| 164 | (* | Error msg ->*)
|
---|
| 165 | (* Printf.eprintf "Failed index request for %s %s" url msg*)
|
---|
| 166 | (* | Ok body ->*)
|
---|
| 167 | (* let rec fold_msgs s a fn =*)
|
---|
| 168 | (* let t, msg = Scanf.bscanf s "%s %s@\n" (fun t m -> t, m) in*)
|
---|
| 169 | (* if t <> "" then fold_msgs s (fn a t msg) fn else a*)
|
---|
| 170 | (* in*)
|
---|
| 171 | (* let s = Scanf.Scanning.from_string body in*)
|
---|
| 172 | (* let msgs = MsgSet.empty in*)
|
---|
| 173 | (* let date_string t = Ptime.to_date t |>*)
|
---|
| 174 | (* fun (y, m, d) -> Printf.sprintf "%04d-%02d-%02d" y m d in*)
|
---|
| 175 | (* let msgs = fold_msgs s msgs*)
|
---|
| 176 | (* (fun msgs t m -> match Ptime.of_rfc3339 t with*)
|
---|
| 177 | (* | Ok (v,_,_) -> let open MsgSet in*)
|
---|
| 178 | (* let msgs = if cardinal msgs > 1 then remove (max_elt msgs) msgs else msgs in*)
|
---|
| 179 | (* add (v,m) msgs*)
|
---|
| 180 | (* | _ -> msgs) in*)
|
---|
| 181 | (* let msg_string = MsgSet.fold*)
|
---|
| 182 | (* (fun (t,m) a -> a ^ Printf.sprintf " %s 𐄁 %s\n" (date_string t) m)*)
|
---|
| 183 | (* msgs "" in*)
|
---|
| 184 | (* Printf.printf "┌───{ %s }───┐\n%s" url msg_string*)
|
---|