]> matita.cs.unibo.it Git - helm.git/blob - helm/hbugs/client/hbugs_client.ml
8eac8a022c8ec9f679ec59f5823aed6d2859535c
[helm.git] / helm / hbugs / client / hbugs_client.ml
1 (*
2  * Copyright (C) 2003:
3  *    Stefano Zacchiroli <zack@cs.unibo.it>
4  *    for the HELM Team http://helm.cs.unibo.it/
5  *
6  *  This file is part of HELM, an Hypertextual, Electronic
7  *  Library of Mathematics, developed at the Computer Science
8  *  Department, University of Bologna, Italy.
9  *
10  *  HELM is free software; you can redistribute it and/or
11  *  modify it under the terms of the GNU General Public License
12  *  as published by the Free Software Foundation; either version 2
13  *  of the License, or (at your option) any later version.
14  *
15  *  HELM is distributed in the hope that it will be useful,
16  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  *  GNU General Public License for more details.
19  *
20  *  You should have received a copy of the GNU General Public License
21  *  along with HELM; if not, write to the Free Software
22  *  Foundation, Inc., 59 Temple Place - Suite 330, Boston,
23  *  MA  02111-1307, USA.
24  *
25  *  For details, see the HELM World-Wide-Web page,
26  *  http://helm.cs.unibo.it/
27  *)
28
29 open Hbugs_common;;
30 open Hbugs_types;;
31 open Printf;;
32
33 exception Invalid_URL of string;;
34
35 let do_nothing _ = ();;
36
37 module SmartHbugs_client_gui =
38  struct
39   class ['a] oneColumnCList gtree_view ~column_type ~column_title
40   =
41    let obj =
42     ((Gobject.unsafe_cast gtree_view#as_widget) : Gtk.tree_view Gtk.obj) in
43    let columns = new GTree.column_list in
44    let col = columns#add column_type in
45    let vcol = GTree.view_column ~title:column_title ()
46     ~renderer:(GTree.cell_renderer_text[], ["text",col]) in
47    let store = GTree.list_store columns in
48    object(self)
49     inherit GTree.view obj
50     method clear = store#clear
51     method append (v : 'a) =
52      let row = store#append () in
53       store#set ~row ~column:col v;
54     method column = col
55     initializer
56      self#set_model (Some (store :> GTree.model)) ;
57      ignore (self#append_column vcol)
58    end
59
60   class ['a,'b] twoColumnsCList gtree_view ~column1_type ~column2_type
61    ~column1_title ~column2_title
62   =
63    let obj =
64     ((Gobject.unsafe_cast gtree_view#as_widget) : Gtk.tree_view Gtk.obj) in
65    let columns = new GTree.column_list in
66    let col1 = columns#add column1_type in
67    let vcol1 = GTree.view_column ~title:column1_title ()
68     ~renderer:(GTree.cell_renderer_text[], ["text",col1]) in
69    let col2 = columns#add column2_type in
70    let vcol2 = GTree.view_column ~title:column2_title ()
71     ~renderer:(GTree.cell_renderer_text[], ["text",col2]) in
72    let store = GTree.list_store columns in
73    object(self)
74     inherit GTree.view obj
75     method clear = store#clear
76     method append (v1 : 'a) (v2 : 'b) =
77      let row = store#append () in
78       store#set ~row ~column:col1 v1;
79       store#set ~row ~column:col2 v2
80     method column1 = col1
81     method column2 = col2
82     initializer
83      self#set_model (Some (store :> GTree.model)) ;
84      ignore (self#append_column vcol1) ;
85      ignore (self#append_column vcol2) ;
86    end
87
88   class subscribeWindow () =
89    object(self)
90     inherit Hbugs_client_gui.subscribeWindow ()
91     val mutable tutorsSmartCList = None
92     method tutorsSmartCList =
93      match tutorsSmartCList with
94         None -> assert false
95       | Some w -> w
96     initializer
97      tutorsSmartCList <-
98       Some
99        (new twoColumnsCList self#tutorsCList
100          ~column1_type:Gobject.Data.string ~column2_type:Gobject.Data.string
101          ~column1_title:"Id" ~column2_title:"Description")
102    end
103
104   class hbugsMainWindow () =
105    object(self)
106     inherit Hbugs_client_gui.hbugsMainWindow ()
107     val mutable subscriptionSmartCList = None
108     val mutable hintsSmartCList = None
109     method subscriptionSmartCList =
110      match subscriptionSmartCList with
111         None -> assert false
112       | Some w -> w
113     method hintsSmartCList =
114      match hintsSmartCList with
115         None -> assert false
116       | Some w -> w
117     initializer
118      subscriptionSmartCList <-
119       Some
120        (new oneColumnCList self#subscriptionCList
121          ~column_type:Gobject.Data.string ~column_title:"Description")
122     initializer
123      hintsSmartCList <-
124       Some
125        (new oneColumnCList self#hintsCList
126          ~column_type:Gobject.Data.string ~column_title:"Description")
127    end
128
129  end
130 ;;
131
132 class hbugsClient
133   ?(use_hint_callback: hint -> unit = do_nothing)
134   ?(describe_hint_callback: hint -> unit = do_nothing)
135   ?(destroy_callback: unit -> unit = do_nothing)
136   ()
137   =
138
139   let http_url_RE = Pcre.regexp "^(http://)?(.*):(\\d+)" in
140   let port_of_http_url url =
141     try
142       let subs = Pcre.extract ~rex:http_url_RE url in
143       int_of_string subs.(3)
144     with e -> raise (Invalid_URL url)
145   in
146
147   object (self)
148
149     val mainWindow = new SmartHbugs_client_gui.hbugsMainWindow ()
150     val subscribeWindow = new SmartHbugs_client_gui.subscribeWindow ()
151     val messageDialog = new Hbugs_client_gui.messageDialog ()
152     val myOwnId = Hbugs_id_generator.new_client_id ()
153     val mutable use_hint_callback = use_hint_callback
154     val mutable myOwnUrl = "localhost:49082"
155     val mutable brokerUrl = "localhost:49081"
156     val mutable brokerId: broker_id option = None
157       (* all available tutors, saved last time a List_tutors message was sent to
158       broker *)
159     val mutable availableTutors: tutor_dsc list = []
160     val mutable statusContext = None
161     val mutable subscribeWindowStatusContext = None
162     val mutable debug = false (* enable/disable debugging buttons *)
163     val mutable hints = []  (* actually available hints *)
164
165     initializer
166       self#initGui;
167       self#startLocalHttpDaemon ();
168       self#testLocalHttpDaemon ();
169       self#testBroker ();
170       self#registerToBroker ();
171       self#reconfigDebuggingButtons
172
173     method show = mainWindow#hbugsMainWindow#show
174     method hide = mainWindow#hbugsMainWindow#misc#hide
175
176     method setUseHintCallback callback =
177      use_hint_callback <- callback
178
179     method private debugButtons =
180       List.map
181         (fun (b: GButton.button) -> new GObj.misc_ops b#as_widget)
182         [ mainWindow#startLocalHttpDaemonButton;
183         mainWindow#testLocalHttpDaemonButton; mainWindow#testBrokerButton ]
184
185     method private initGui =
186
187         (* GUI: main window *)
188
189           (* ignore delete events so that hbugs window is closable only using
190           menu; on destroy (e.g. while quitting gTopLevel) self#quit is invoked
191           *)
192
193       ignore (mainWindow#hbugsMainWindow#event#connect#delete (fun _ -> true));
194       ignore (mainWindow#hbugsMainWindow#event#connect#destroy
195         (fun _ -> self#quit (); false));
196
197         (* GUI main window's menu *)
198       mainWindow#toggleDebuggingMenuItem#set_active debug;
199       ignore (mainWindow#toggleDebuggingMenuItem#connect#toggled
200         self#toggleDebug);
201
202         (* GUI: local HTTP daemon settings *)
203       ignore (mainWindow#clientUrlEntry#connect#changed
204         (fun _ -> myOwnUrl <- mainWindow#clientUrlEntry#text));
205       mainWindow#clientUrlEntry#set_text myOwnUrl;
206       ignore (mainWindow#startLocalHttpDaemonButton#connect#clicked
207         self#startLocalHttpDaemon);
208       ignore (mainWindow#testLocalHttpDaemonButton#connect#clicked
209         self#testLocalHttpDaemon);
210
211         (* GUI: broker choice *)
212       ignore (mainWindow#brokerUrlEntry#connect#changed
213         (fun _ -> brokerUrl <- mainWindow#brokerUrlEntry#text));
214       mainWindow#brokerUrlEntry#set_text brokerUrl;
215       ignore (mainWindow#testBrokerButton#connect#clicked self#testBroker);
216       mainWindow#clientIdLabel#set_text myOwnId;
217
218         (* GUI: client registration *)
219       ignore (mainWindow#registerClientButton#connect#clicked
220         self#registerToBroker);
221
222         (* GUI: subscriptions *)
223       ignore (mainWindow#showSubscriptionWindowButton#connect#clicked
224         (fun () ->
225           self#listTutors ();
226           subscribeWindow#subscribeWindow#show ()));
227
228         (* GUI: hints list *)
229       ignore (mainWindow#hintsCList#selection#set_select_function
230         (fun path already_selected ->
231           let row =
232 prerr_endline ("**** BEFORE CRASH: " ^ if already_selected then "yes" else "no") ;
233            match GTree.Path.get_indices path with
234               [|n|] -> n
235             | _ -> assert false
236           in
237 prerr_endline ("**** AFTER CRASH: " ^ string_of_int row) ;
238            (*CSC: there used to be an event whose type was checked against *)
239            (*CSC: `TWO_BUTTON_PRESS. This is just a bad approximation.     *)
240            if already_selected then
241             use_hint_callback (self#hint row)
242            else
243             describe_hint_callback (self#hint row) ;
244            true)) ;
245
246         (* GUI: main status bar *)
247       let ctxt = mainWindow#mainWindowStatusBar#new_context "0" in
248       statusContext <- Some ctxt;
249       ignore (ctxt#push "Ready");
250
251         (* GUI: subscription window *)
252       subscribeWindow#tutorsCList#selection#set_mode `MULTIPLE;
253       ignore (subscribeWindow#subscribeWindow#event#connect#delete
254         (fun _ -> subscribeWindow#subscribeWindow#misc#hide (); true));
255       ignore (subscribeWindow#listTutorsButton#connect#clicked self#listTutors);
256       ignore (subscribeWindow#subscribeButton#connect#clicked
257         self#subscribeSelected);
258       ignore (subscribeWindow#subscribeAllButton#connect#clicked
259         self#subscribeAll);
260       (subscribeWindow#tutorsCList#get_column 0)#set_visible false;
261       let ctxt = subscribeWindow#subscribeWindowStatusBar#new_context "0" in
262       subscribeWindowStatusContext <- Some ctxt;
263       ignore (ctxt#push "Ready");
264
265         (* GUI: message dialog *)
266       ignore (messageDialog#messageDialog#event#connect#delete
267         (fun _ -> messageDialog#messageDialog#misc#hide (); true));
268       ignore (messageDialog#okDialogButton#connect#clicked
269         (fun _ -> messageDialog#messageDialog#misc#hide ()))
270
271     (* accessory methods *)
272
273       (** pop up a (modal) dialog window showing msg to the user *)
274     method private showDialog msg =
275       messageDialog#dialogLabel#set_text msg;
276       messageDialog#messageDialog#show ()
277       (** use showDialog to display an hbugs message to the user *)
278     method private showMsgInDialog msg =
279       self#showDialog (Hbugs_messages.string_of_msg msg)
280
281       (** create a new thread which sends msg to broker, wait for an answer and
282       invoke callback passing response message as argument *)
283     method private sendReq ?(wait = false) ~msg callback =
284       let thread () =
285         try
286           callback (Hbugs_messages.submit_req ~url:(brokerUrl ^ "/act") msg)
287         with 
288         | (Hbugs_messages.Parse_error (subj, reason)) as e ->
289             self#showDialog
290               (sprintf
291 "Parse_error, unable to fullfill request. Details follow.
292 Request: %s
293 Error: %s"
294                 (Hbugs_messages.string_of_msg msg) (Printexc.to_string e));
295         | (Unix.Unix_error _) as e ->
296             self#showDialog
297               (sprintf
298 "Can't connect to HBugs Broker
299 Url: %s
300 Error: %s"
301                 brokerUrl (Printexc.to_string e))
302         | e ->
303             self#showDialog
304               (sprintf "hbugsClient#sendReq: Uncaught exception: %s"
305                 (Printexc.to_string e))
306       in
307       let th = Thread.create thread () in
308       if wait then
309         Thread.join th
310       else ()
311
312       (** check if a broker is authenticated using its broker_id
313       [ Background: during client registration, client save broker_id of its
314       broker, further messages from broker are accepted only if they carry the
315       same broker id ] *)
316     method private isAuthenticated id =
317       match brokerId with
318       | None -> false
319       | Some broker_id -> (id = broker_id)
320
321     (* actions *)
322
323     method private startLocalHttpDaemon =
324         (* flatten an hint tree to an hint list *)
325       let rec flatten_hint = function
326         | Hints hints -> List.concat (List.map flatten_hint hints)
327         | hint -> [hint]
328       in
329       fun () ->
330       let callback req outchan =
331         try
332           (match Hbugs_messages.msg_of_string req#body with
333           | Help ->
334               Hbugs_messages.respond_msg
335                 (Usage "Local Http Daemon up and running!") outchan
336           | Hint (broker_id, hint) ->
337               if self#isAuthenticated broker_id then begin
338                 let received_hints = flatten_hint hint in
339                 List.iter
340                   (fun h ->
341                     (match h with Hints _ -> assert false | _ -> ());
342                     ignore(mainWindow#hintsSmartCList#append(string_of_hint h)))
343                   received_hints;
344                 hints <- hints @ received_hints;
345                 Hbugs_messages.respond_msg (Wow myOwnId) outchan
346               end else  (* msg from unauthorized broker *)
347                 Hbugs_messages.respond_exc "forbidden" broker_id outchan
348           | msg ->
349               Hbugs_messages.respond_exc
350                 "unexpected_msg" (Hbugs_messages.string_of_msg msg) outchan)
351         with (Hbugs_messages.Parse_error _) as e ->
352           Hbugs_messages.respond_exc
353             "parse_error" (Printexc.to_string e) outchan
354       in
355       let addr = "0.0.0.0" in (* TODO actually user specified "My URL" is used
356                               only as a value to be sent to broker, local HTTP
357                               daemon will listen on "0.0.0.0", port is parsed
358                               from My URL though *)
359       let httpDaemonThread () =
360         try
361           Http_daemon.start'
362             ~addr ~port:(port_of_http_url myOwnUrl) ~mode:`Single callback
363         with
364         | Invalid_URL url -> self#showDialog (sprintf "Invalid URL: \"%s\"" url)
365         | e ->
366             self#showDialog (sprintf "Can't start local HTTP daemon: %s"
367               (Printexc.to_string e))
368       in
369       ignore (Thread.create httpDaemonThread ())
370
371     method private testLocalHttpDaemon () =
372       try
373         let msg =
374           Hbugs_misc.http_post ~body:(Hbugs_messages.string_of_msg Help)
375             myOwnUrl
376         in
377         ignore msg
378 (*         self#showDialog msg *)
379       with
380       | Hbugs_misc.Malformed_URL url ->
381           self#showDialog
382             (sprintf
383               "Handshake with local HTTP daemon failed, Invalid URL: \"%s\""
384               url)
385       | Hbugs_misc.Malformed_HTTP_response res ->
386           self#showDialog
387             (sprintf
388     "Handshake with local HTTP daemon failed, can't parse HTTP response: \"%s\""
389               res)
390       | (Unix.Unix_error _) as e ->
391           self#showDialog
392             (sprintf
393               "Handshake with local HTTP daemon failed, can't connect: \"%s\""
394               (Printexc.to_string e))
395
396     method private testBroker () =
397       self#sendReq ~msg:Help
398         (function
399           | Usage _ -> ()
400           | unexpected_msg ->
401               self#showDialog
402                 (sprintf
403                   "Handshake with HBugs Broker failed, unexpected message:\n%s"
404                   (Hbugs_messages.string_of_msg unexpected_msg)))
405
406     method registerToBroker () =
407       (match brokerId with  (* undo previous registration, if any *)
408       | Some id -> self#unregisterFromBroker ()
409       | _ -> ());
410       self#sendReq ~msg:(Register_client (myOwnId, myOwnUrl))
411         (function
412           | Client_registered broker_id -> (brokerId <- Some broker_id)
413           | unexpected_msg ->
414               self#showDialog
415                 (sprintf "Client NOT registered, unexpected message:\n%s"
416                   (Hbugs_messages.string_of_msg unexpected_msg)))
417
418     method unregisterFromBroker () =
419       self#sendReq ~wait:true ~msg:(Unregister_client myOwnId)
420         (function
421           | Client_unregistered _ -> (brokerId <- None)
422           | unexpected_msg -> ())
423 (*
424               self#showDialog
425                 (sprintf "Client NOT unregistered, unexpected message:\n%s"
426                   (Hbugs_messages.string_of_msg unexpected_msg)))
427 *)
428
429     method stateChange new_state =
430       mainWindow#hintsSmartCList#clear ();
431       hints <- [];
432       self#sendReq
433         ~msg:(State_change (myOwnId, new_state))
434         (function
435           | State_accepted _ -> ()
436           | unexpected_msg ->
437               self#showDialog
438                 (sprintf "State NOT accepted by Hbugs, unexpected message:\n%s"
439                   (Hbugs_messages.string_of_msg unexpected_msg)))
440
441     method hint = List.nth hints
442
443     method private listTutors () =
444         (* wait is set to true just to make sure that after invoking listTutors
445         "availableTutors" is correctly filled *)
446       self#sendReq ~wait:true ~msg:(List_tutors myOwnId)
447         (function
448           | Tutor_list (_, descriptions) ->
449               availableTutors <-  (* sort accordingly to tutor description *)
450                 List.sort (fun (a,b) (c,d) -> compare (b,a) (d,c)) descriptions;
451               subscribeWindow#tutorsSmartCList#clear ();
452               List.iter
453                 (fun (id, dsc) ->
454                   ignore (subscribeWindow#tutorsSmartCList#append id dsc))
455                 availableTutors
456           | unexpected_msg ->
457               self#showDialog
458                 (sprintf "Can't list tutors, unexpected message:\n%s"
459                   (Hbugs_messages.string_of_msg unexpected_msg)))
460
461       (* low level used by subscribeSelected and subscribeAll *)
462     method private subscribe' tutors_id =
463       self#sendReq ~msg:(Subscribe (myOwnId, tutors_id))
464         (function
465           | (Subscribed (_, subscribedTutors)) as msg ->
466               let sort = List.sort compare in
467               mainWindow#subscriptionSmartCList#clear ();
468               List.iter
469                 (fun tutor_id ->
470                   ignore
471                     (mainWindow#subscriptionSmartCList#append
472                       ( try
473                           List.assoc tutor_id availableTutors
474                         with Not_found -> assert false )))
475                 tutors_id;
476               subscribeWindow#subscribeWindow#misc#hide ();
477               if sort subscribedTutors <> sort tutors_id then
478                 self#showDialog
479                   (sprintf "Subscription mismatch\n: %s"
480                     (Hbugs_messages.string_of_msg msg))
481           | unexpected_msg ->
482               mainWindow#subscriptionSmartCList#clear ();
483               self#showDialog
484                 (sprintf "Subscription FAILED, unexpected message:\n%s"
485                   (Hbugs_messages.string_of_msg unexpected_msg)))
486
487     method private subscribeSelected () =
488      let tutorsSmartCList = subscribeWindow#tutorsSmartCList in
489      let selectedTutors =
490        List.map
491         (fun p ->
492           tutorsSmartCList#model#get
493            ~row:(tutorsSmartCList#model#get_iter p)
494            ~column:tutorsSmartCList#column1)
495         tutorsSmartCList#selection#get_selected_rows
496      in
497       self#subscribe' selectedTutors
498
499     method subscribeAll () =
500       self#listTutors ();  (* this fills 'availableTutors' field *)
501       self#subscribe' (List.map fst availableTutors)
502
503     method private quit () =
504       self#unregisterFromBroker ();
505       destroy_callback ()
506
507       (** enable/disable debugging *)
508     method private setDebug value = debug <- value
509
510     method private reconfigDebuggingButtons =
511       List.iter (* debug value changed, reconfigure buttons *)
512         (fun (b: GObj.misc_ops) -> if debug then b#show () else b#hide ())
513         self#debugButtons;
514     
515     method private toggleDebug () =
516       self#setDebug (not debug);
517       self#reconfigDebuggingButtons
518
519   end
520 ;;
521