]> matita.cs.unibo.it Git - helm.git/blob - helm/searchEngine/searchEngine.ml
implemented pagination
[helm.git] / helm / searchEngine / searchEngine.ml
1 (* Copyright (C) 2002-2004, HELM Team.
2  * 
3  * This file is part of HELM, an Hypertextual, Electronic
4  * Library of Mathematics, developed at the Computer Science
5  * Department, University of Bologna, Italy.
6  * 
7  * HELM is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  * 
12  * HELM is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with HELM; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place - Suite 330, Boston,
20  * MA  02111-1307, USA.
21  * 
22  * For details, see the HELM World-Wide-Web page,
23  * http://cs.unibo.it/helm/.
24  *)
25
26 open Printf
27
28 module DB = Dbi_mysql
29
30 let debug = true
31 let debug_print s = if debug then prerr_endline s
32 let _ = Http_common.debug := false
33
34 exception Chat_unfinished
35 exception Unbound_identifier of string
36 exception Invalid_action of string  (* invalid action for "/search" method *)
37
38 let daemon_name = "Moogle"
39 let configuration_file = "/projects/helm/etc/moogle.conf.xml" 
40
41 let placeholders = [
42   "EXPRESSION"; "ACTION"; "ADVANCED"; "ADVANCED_CHECKED"; "SIMPLE_CHECKED";
43   "TITLE"; "NO_CHOICES"; "CURRENT_CHOICES"; "CHOICES"; "MSG"; "ID_TO_URIS";
44   "ID"; "IDEN"; "INTERPRETATIONS"; "INTERPRETATIONS_LABELS"; "RESULTS";
45   "NEW_ALIASES"; "SEARCH_ENGINE_URL"; "PREV_LINK"; "PAGE"; "PAGES"; "NEXT_LINK"
46 ]
47
48 let tag =
49   let regexps = Hashtbl.create 25 in
50   List.iter
51     (fun tag -> Hashtbl.add regexps tag (Pcre.regexp (sprintf "@%s@" tag)))
52     placeholders;
53   Hashtbl.find regexps
54
55   (* First of all we load the configuration *)
56 let _ = Helm_registry.load_from configuration_file
57 let port = Helm_registry.get_int "search_engine.port"
58 let pages_dir = Helm_registry.get "search_engine.html_dir"
59
60 let interactive_interpretation_choice_TPL = pages_dir ^ "/moogle_chat2.html"
61 let moogle_TPL = pages_dir ^ "/moogle.html"
62
63 let my_own_url =
64  let ic = Unix.open_process_in "hostname -f" in
65  let hostname = input_line ic in
66  ignore (Unix.close_process_in ic);
67  sprintf "http://%s:%d" hostname port
68 let _ = Helm_registry.set "search_engine.my_own_url" my_own_url
69
70 let bad_request body outchan =
71   Http_daemon.respond_error ~code:(`Status (`Client_error `Bad_request)) ~body
72     outchan
73
74   (** chain application of Pcre substitutions *)
75 let rec apply_substs substs line =
76   match substs with
77   | [] -> line
78   | (rex, templ) :: rest -> apply_substs rest (Pcre.replace ~rex ~templ line)
79   (** fold like function on files *)
80 let fold_file f init fname =
81   let inchan = open_in fname in
82   let rec fold_lines' value =
83     try 
84       let line = input_line inchan in 
85       fold_lines' (f value line)
86     with End_of_file -> value
87   in
88   let res = (try fold_lines' init with e -> (close_in inchan; raise e)) in
89   close_in inchan;
90   res
91   (** iter like function on files *)
92 let iter_file f = fold_file (fun _ line -> f line) ()
93 let javascript_quote s =
94  let rex = Pcre.regexp "'" in
95  let rex' = Pcre.regexp "\"" in
96   Pcre.replace ~rex ~templ:"\\'"
97    (Pcre.replace ~rex:rex' ~templ:"\\\"" s)
98 let string_tail s =
99   let len = String.length s in
100   String.sub s 1 (len-1)
101
102 let add_param_substs params =
103   List.map
104     (fun (key,value) ->
105       let key' = (Pcre.extract ~pat:"param\\.(.*)" key).(1) in
106       Pcre.regexp ("@" ^ key' ^ "@"), value)
107     (List.filter
108       (fun ((key,_) as p) -> Pcre.pmatch ~pat:"^param\\." key)
109       params)
110
111 let page_RE = Pcre.regexp "&param\\.page=\\d+"
112
113 let send_results results
114   ?(id_to_uris = CicTextualParser2.EnvironmentP3.of_string "") 
115    (req: Http_types.request) outchan
116   =
117   let page_link anchor page =
118     try
119       let this = req#param "this" in
120       let target =
121         if Pcre.pmatch ~rex:page_RE this then
122           Pcre.replace ~rex:page_RE ~templ:(sprintf "&param.page=%d" page)
123             this
124         else
125           sprintf "%s&param.page=%d" this page
126       in
127       let target = Pcre.replace ~pat:"&" ~templ:"&" target in
128       sprintf "<a href=\"%s\">%s</a>" target anchor
129     with Http_types.Param_not_found _ -> ""
130   in
131   Http_daemon.send_basic_headers ~code:(`Code 200) outchan ;
132   Http_daemon.send_header "Content-Type" "text/xml" outchan;
133   Http_daemon.send_CRLF outchan ;
134   let subst, results_string =
135     match results with
136     | `Results results ->
137         let page = try int_of_string (req#param "page") with _ -> 1 in
138         let results_no = List.length results in
139         let results_per_page =
140           Helm_registry.get_int "search_engine.results_per_page"
141         in
142         let pages =
143           if results_no mod results_per_page = 0 then
144             results_no / results_per_page
145           else
146             results_no / results_per_page + 1
147         in
148         ([ tag "PAGE", string_of_int page; tag "PAGES", string_of_int pages ] @
149          [ tag "PREV_LINK",
150            if page > 1 then page_link "Prev" (page-1) else "" ] @
151          [ tag "NEXT_LINK",
152            if page < pages then page_link "Next" (page+1) else "" ]),
153         MooglePp.theory_of_result req page results
154     | `Error msg ->
155         [ tag "PAGE", ""; tag "PAGES", "";
156           tag "PREV_LINK", ""; tag "NEXT_LINK", "" ],
157         msg
158   in
159   let subst =
160     (tag "SEARCH_ENGINE_URL", my_own_url) ::
161     (tag "RESULTS", results_string) ::
162     (tag "ADVANCED", req#param "advanced") ::
163     (tag "EXPRESSION", req#param "expression") ::
164     add_param_substs req#params @
165     (if req#param "advanced" = "no" then
166       [ tag "SIMPLE_CHECKED", "checked='true'";
167         tag "ADVANCED_CHECKED", "" ]
168     else
169       [ tag "SIMPLE_CHECKED", "";
170         tag "ADVANCED_CHECKED", "checked='true'" ]) @
171     subst
172   in
173   iter_file
174     (fun line ->
175       let new_aliases =
176         CicTextualParser2.EnvironmentP3.to_string id_to_uris
177       in
178       let processed_line =
179         apply_substs
180           (* CSC: Bug here: this is a string, not an array! *)
181           ((tag "NEW_ALIASES", "'" ^ javascript_quote new_aliases ^ "'") ::
182             subst) 
183           line
184       in
185       output_string outchan (processed_line ^ "\n"))
186     moogle_TPL
187
188 let exec_action dbh (req: Http_types.request) outchan =
189   let term_str = req#param "expression" in
190   let (context, metasenv) = ([], []) in
191   let id_to_uris_raw = 
192     try req#param "aliases" 
193     with Http_types.Param_not_found _ -> ""
194   in
195   let parse_interpretation_choices choices =
196     List.map int_of_string (Pcre.split ~pat:" " choices) in
197   let parse_choices choices_raw =
198     let choices = Pcre.split ~pat:";" choices_raw in
199       List.fold_left
200         (fun f x ->
201            match Pcre.split ~pat:"\\s" x with
202              | ""::id::tail
203              | id::tail when id<>"" ->
204                  (fun id' ->
205                     if id = id' then
206                       Some (List.map (fun u -> Netencoding.Url.decode u) tail)
207                     else
208                       f id')
209              | _ -> failwith "Can't parse choices")
210         (fun _ -> None)
211         choices
212   in
213   let id_to_uris = CicTextualParser2.EnvironmentP3.of_string id_to_uris_raw in
214   let id_to_choices =
215     try
216       parse_choices (req#param "choices")
217     with Http_types.Param_not_found _ -> (fun _ -> None)
218   in
219   let interpretation_choices =
220     try
221       let choices_raw = req#param "interpretation_choices" in
222       if choices_raw = "" then None 
223       else Some (parse_interpretation_choices choices_raw)
224     with Http_types.Param_not_found _ -> None
225   in 
226   let module Chat: DisambiguateTypes.Callbacks =
227     struct
228       let interactive_user_uri_choice ~selection_mode ?ok
229         ?enable_button_for_non_vars ~(title: string) ~(msg: string)
230         ~(id: string) (choices: string list)
231       =
232         match id_to_choices id with
233         | Some choices -> choices
234         | None -> assert false
235
236       let interactive_interpretation_choice interpretations =
237         match interpretation_choices with
238         | Some l -> l
239         | None ->
240             let html_interpretations =
241               MooglePp.html_of_interpretations interpretations
242             in
243             Http_daemon.send_basic_headers ~code:(`Code 200) outchan ;
244             Http_daemon.send_CRLF outchan ;
245             iter_file
246               (fun line ->
247                  let processed_line =
248                    apply_substs
249                      [tag "ADVANCED", req#param "advanced";
250                       tag "INTERPRETATIONS", html_interpretations;
251                       tag "CURRENT_CHOICES", req#param "choices";
252                       tag "EXPRESSION", req#param "expression";
253                       tag "ACTION", string_tail req#path ]
254                       line
255                  in
256                  output_string outchan (processed_line ^ "\n"))
257               interactive_interpretation_choice_TPL;
258             raise Chat_unfinished
259
260       let input_or_locate_uri ~title ?id () =
261         match id with
262         | Some id -> raise (Unbound_identifier id)
263         | None -> assert false
264     end
265   in
266   let module Disambiguate' = Disambiguate.Make(Chat) in
267   let ast = CicTextualParser2.parse_term (Stream.of_string term_str) in
268   let (id_to_uris, metasenv, term) =
269     match
270       Disambiguate'.disambiguate_term dbh context metasenv ast id_to_uris
271     with
272     | [id_to_uris,metasenv,term] -> id_to_uris,metasenv,term
273     | _ -> assert false
274   in
275   let uris =
276     match req#path with
277     | "/match" -> MetadataQuery.match_term ~dbh term
278     | "/hint" ->
279         let status = ProofEngineTypes.initial_status term metasenv in
280         let intros = PrimitiveTactics.intros_tac () in
281         let subgoals = ProofEngineTypes.apply_tactic intros status in
282         (match subgoals with
283         | proof, [goal] ->
284             let (uri,metasenv,bo,ty) = proof in
285             List.map fst (MetadataQuery.hint ~dbh (proof, goal))
286         | _ -> assert false)
287     | "/elim" ->
288         let uri =
289           match term with
290           | Cic.MutInd (uri, typeno, _) ->
291               UriManager.string_of_uriref (uri, [typeno])
292           | _ -> assert false
293         in
294         MetadataQuery.elim ~dbh uri
295     | _ -> assert false
296   in
297   send_results ~id_to_uris (`Results uris) req outchan
298
299 let callback dbh (req: Http_types.request) outchan =
300   try
301     debug_print (sprintf "Received request: %s" req#path);
302     (match req#path with
303     | "/getpage" ->
304           (* TODO implement "is_permitted" *)
305         (let is_permitted _ = true in
306         let page = req#param "url" in
307         let preprocess =
308           (try
309             bool_of_string (req#param "preprocess")
310           with Invalid_argument _ | Http_types.Param_not_found _ -> false)
311         in
312         (match page with
313         | page when is_permitted page ->
314             (let fname = sprintf "%s/%s" pages_dir page in
315             Http_daemon.send_basic_headers ~code:(`Code 200) outchan;
316             Http_daemon.send_header "Content-Type" "text/html" outchan;
317             Http_daemon.send_CRLF outchan;
318             if preprocess then begin
319               iter_file
320                 (fun line ->
321                   output_string outchan
322                     ((apply_substs
323                        ((tag "SEARCH_ENGINE_URL", my_own_url) ::
324                         (tag "ADVANCED", "no") ::
325                         (tag "RESULTS", "") ::
326                         add_param_substs req#params)
327                        line) ^
328                     "\n"))
329                 fname
330             end else
331               Http_daemon.send_file ~src:(Http_types.FileSrc fname) outchan)
332         | page -> Http_daemon.respond_forbidden ~url:page outchan))
333     | "/help" -> Http_daemon.respond ~body:daemon_name outchan
334     | "/locate" ->
335         let initial_expression =
336           try req#param "expression" with Http_types.Param_not_found _ -> ""
337         in
338         let expression =
339           Pcre.replace ~pat:"\\s*$"
340             (Pcre.replace ~pat:"^\\s*" initial_expression)
341         in
342         if expression = "" then
343           send_results (`Results []) req outchan
344         else
345           let results = MetadataQuery.locate ~dbh expression in
346           send_results (`Results results) req outchan
347     | "/hint"
348     | "/elim"
349     | "/match" -> exec_action dbh req outchan
350     | invalid_request ->
351         Http_daemon.respond_error ~code:(`Status (`Client_error `Bad_request))
352           outchan);
353     debug_print (sprintf "%s done!" req#path)
354   with
355   | Chat_unfinished -> ()
356   | Http_types.Param_not_found attr_name ->
357       bad_request (sprintf "Parameter '%s' is missing" attr_name) outchan
358   | CicTextualParser2.Parse_error msg ->
359       send_results (`Error (MooglePp.pp_error "Parse_error" msg)) req outchan
360   | Unbound_identifier id ->
361       send_results (`Error (MooglePp.pp_error "Unbound identifier" id)) req
362         outchan
363   | exn ->
364       let exn_string = Printexc.to_string exn in
365       debug_print exn_string;
366       let msg = MooglePp.pp_error "Uncaught exception" exn_string in
367       send_results (`Error msg) req outchan
368
369 let _ =
370   printf "%s started and listening on port %d\n" daemon_name port;
371   printf "Current directory is %s\n" (Sys.getcwd ());
372   printf "HTML directory is %s\n" pages_dir;
373   flush stdout;
374   Unix.putenv "http_proxy" "";
375   let dbh =
376     new DB.connection ~host:(Helm_registry.get "db.host")
377       ~user:(Helm_registry.get "db.user") (Helm_registry.get "db.database")
378   in
379   Http_daemon.start' ~port (callback dbh);
380   printf "%s is terminating, bye!\n" daemon_name
381