1 | let http_body fn uri =
|
---|
2 | let open Lwt in
|
---|
3 | let open Cohttp_lwt_unix in
|
---|
4 | Client.get uri >>= fun (headers, body) ->
|
---|
5 | body |> Cohttp_lwt.Body.to_string >|= fun body -> fn (headers, body)
|
---|
6 |
|
---|
7 | let response (headers, body) =
|
---|
8 | let open Cohttp in
|
---|
9 | match Header.get (headers |> Response.headers) "content-type" with
|
---|
10 | | Some "application/msgpack" | Some "application/octet-stream"
|
---|
11 | | Some "text/plain" | Some "text/plain; charset=utf-8" -> Ok body
|
---|
12 | | Some x -> Error ("Invalid content-type: " ^ x)
|
---|
13 | | None -> Ok body
|
---|
14 |
|
---|
15 | let http_apply fn uri = Lwt_main.run (http_body fn uri)
|
---|
16 |
|
---|
17 | module S = Set.Make(String)
|
---|
18 |
|
---|
19 | (*let is_selected sl =*)
|
---|
20 | (* let check str a b c = Option.(fold ~none:(is_none b && is_none c) ~some:(fun x -> x = str) a) in*)
|
---|
21 | (* function*)
|
---|
22 | (* | `Author s -> check s sl.authors sl.topics*)
|
---|
23 | (* | `Topic s -> check s sl.topics sl.authors*)
|
---|
24 |
|
---|
25 | (* TODO: parse using Header_pack *)
|
---|
26 |
|
---|
27 | let sub_id text = Logarion.(String.sub (text.Text.uuid |> Id.to_string) 0 8)
|
---|
28 | let fname dir text = dir ^ sub_id text ^ ".txt"
|
---|
29 |
|
---|
30 | let newer time id dir =
|
---|
31 | match Logarion.File_store.to_text @@ Filename.concat dir (String.sub (Logarion.Id.to_string id) 0 8 ^ ".txt") with
|
---|
32 | | Error x -> prerr_endline x; true
|
---|
33 | | Ok txt -> time > (Logarion.(Header_pack.date (Date.listing txt.date)))
|
---|
34 | | exception (Sys_error _) -> true
|
---|
35 |
|
---|
36 | let pull_text url dir id =
|
---|
37 | let path = Uri.path url in
|
---|
38 | let u = Uri.with_path url (path ^ "/" ^ String.sub (Logarion.Id.to_string id) 0 8 ^ ".txt") in
|
---|
39 | match http_apply response u with
|
---|
40 | | Error msg -> prerr_endline @@ " Failed " ^ Uri.to_string u ^ " " ^ msg
|
---|
41 | | Ok txt ->
|
---|
42 | match Logarion.Text.of_string txt with
|
---|
43 | | Error s -> prerr_endline s
|
---|
44 | | Ok text ->
|
---|
45 | let file = open_out_gen [Open_creat; Open_trunc; Open_wronly] 0o640 (fname dir text) in
|
---|
46 | output_string file txt; close_out file
|
---|
47 |
|
---|
48 | (*TODO: adapt Archive predication function to work with free sets*)
|
---|
49 | let parse_index _is_selected fn url p =
|
---|
50 | let open Logarion.Header_pack in
|
---|
51 | let dir = "peers/" ^ match Uri.host url with
|
---|
52 | None -> "local/" ^ Uri.path url | Some s -> s ^ Uri.path url ^ "/" in
|
---|
53 | Printf.printf "%s => %s\n" p.info.name dir;
|
---|
54 | (match Msgpck.to_list p.peers with [] -> () | ps ->
|
---|
55 | print_string " peers: ";
|
---|
56 | List.iter (fun x -> print_string (" " ^ Msgpck.to_string x)) ps;
|
---|
57 | print_newline ());
|
---|
58 | match Msgpck.to_list p.texts with
|
---|
59 | | [] -> print_endline ", has empty index"
|
---|
60 | | texts ->
|
---|
61 | match Bos.OS.Dir.create ~mode:0o740 (Fpath.v dir) with
|
---|
62 | | Error (`msg s) -> prerr_endline ("Error making domain dir:" ^ s);
|
---|
63 | | _ ->
|
---|
64 | let numof_texts = string_of_int @@ List.length texts in
|
---|
65 | let text_num_len = String.length numof_texts in
|
---|
66 | let of_pck i x =
|
---|
67 | Printf.printf "\r%*d/%s %!" text_num_len (i+1) numof_texts;
|
---|
68 | match x with
|
---|
69 | | Msgpck.List (id::time::title::_authors::_topics) ->
|
---|
70 | (match Logarion.Id.of_bytes Msgpck.(to_bytes id) with
|
---|
71 | | None -> prerr_endline ("Invalid id for " ^ Msgpck.to_string title)
|
---|
72 | | Some id ->
|
---|
73 | let t = match time with Msgpck.Int i -> Int32.of_int i | Msgpck.Uint32 i -> i | x -> Msgpck.to_uint32 x in
|
---|
74 | if newer t id dir then fn url dir id)
|
---|
75 | | _ -> prerr_endline ("Invalid record structure") in
|
---|
76 | List.iteri of_pck texts;
|
---|
77 | print_newline ()
|
---|
78 |
|
---|
79 | let pull_index url _authors _topics =
|
---|
80 | let index_url = Uri.(with_path url (path url ^ "/index.pck")) in
|
---|
81 | match http_apply response index_url with
|
---|
82 | | Error msg -> prerr_endline @@ "Failed index request for " ^ Uri.to_string index_url ^ " " ^ msg
|
---|
83 | | Ok body ->
|
---|
84 | let _i, pack = Msgpck.StringBuf.read body in
|
---|
85 | (* let predicates =*)
|
---|
86 | (* A.predicate A.authored authors_opt*)
|
---|
87 | (* @ A.predicate A.topics topics_opt*)
|
---|
88 | (* in*)
|
---|
89 | let is_selected text = List.fold_left (fun a e -> a && e text) true [](*predicates*) in
|
---|
90 | match Logarion.Header_pack.unpack pack with None -> ()
|
---|
91 | | Some headers -> parse_index is_selected pull_text url headers
|
---|
92 |
|
---|
93 | module Msg = struct
|
---|
94 | type t = Ptime.t * string
|
---|
95 | let compare (x0,y0) (x1,y1) = match Ptime.compare x1 x0 with 0 -> String.compare y0 y1 | c -> c
|
---|
96 | end
|
---|
97 |
|
---|
98 | module MsgSet = Set.Make(Msg)
|
---|
99 |
|
---|
100 | let pull_msgs url _authors _topics = match http_apply response url with
|
---|
101 | | Error msg -> prerr_endline @@ "Failed index request for " ^ Uri.(to_string url) ^ " " ^ msg
|
---|
102 | | Ok body ->
|
---|
103 | let rec fold_msgs s a fn =
|
---|
104 | let t, msg = Scanf.bscanf s "%s %s@\n" (fun t m -> t, m) in
|
---|
105 | if t <> "" then fold_msgs s (fn a t msg) fn else a
|
---|
106 | in
|
---|
107 | let s = Scanf.Scanning.from_string body in
|
---|
108 | let msgs = MsgSet.empty in
|
---|
109 | let date_string t = Ptime.to_date t |>
|
---|
110 | fun (y, m, d) -> Printf.sprintf "%04d-%02d-%02d" y m d in
|
---|
111 | let msgs = fold_msgs s msgs
|
---|
112 | (fun msgs t m -> match Ptime.of_rfc3339 t with
|
---|
113 | | Ok (v,_,_) -> let open MsgSet in
|
---|
114 | let msgs = if cardinal msgs > 1 then remove (max_elt msgs) msgs else msgs in
|
---|
115 | add (v,m) msgs
|
---|
116 | | _ -> msgs) in
|
---|
117 | print_endline ("\n┌────=[ " ^ Uri.to_string url);
|
---|
118 | MsgSet.iter
|
---|
119 | (fun (t,m) -> print_endline
|
---|
120 | ("│ " ^ date_string t ^ "\n│ " ^ m ^ "\n└─────────")) msgs
|
---|
121 |
|
---|
122 | let pull_url url = match Uri.of_string url with
|
---|
123 | | x when x = Uri.empty -> (fun _ _ -> ())
|
---|
124 | | x when Uri.scheme x = Some "msg+http" -> pull_msgs Uri.(with_scheme x (Some "http"))
|
---|
125 | | x when Uri.scheme x = Some "msg+https"-> pull_msgs Uri.(with_scheme x (Some "https"))
|
---|
126 | | x -> pull_index x
|
---|
127 |
|
---|
128 | let pull_list auths topics =
|
---|
129 | let pull peer_url () = pull_url peer_url auths topics in
|
---|
130 | let open Logarion.Peers in
|
---|
131 | fold_file pull () public_fname;
|
---|
132 | fold_file pull () private_fname
|
---|
133 |
|
---|
134 | let pull = function "" -> pull_list | x -> pull_url x
|
---|
135 |
|
---|
136 | open Cmdliner
|
---|
137 |
|
---|
138 | let pull_term =
|
---|
139 | let authors = Arg.(value & opt (some string) None & info ["a"; "authors"] ~docv:"AUTHORS" ~doc:"select authors") in
|
---|
140 | let topics = Arg.(value & opt (some string) None & info ["t"; "topics"] ~docv:"TOPICS" ~doc:"select topics") in
|
---|
141 | let url = Arg.(value & pos 0 string "" & info [] ~docv:"URL" ~doc:"HTTP URL of Logarion") in
|
---|
142 | Term.(const pull $ url $ authors $ topics),
|
---|
143 | Term.info "pull" ~doc:"pull listed texts" ~man:[ `S "DESCRIPTION"; `P "Pull texts from archive at address"]
|
---|