1 (* Copyright (C) 2004-2011, HELM Team.
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.
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.
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.
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,
22 * For details, see the HELM World-Wide-Web page,
23 * http://helm.cs.unibo.it/
26 exception SvnError of string;;
28 let exec_process cmd =
29 let (stdout, stdin, stderr) as chs = Unix.open_process_full cmd [||] in
30 let outlines = ref [] in
31 let errlines = ref [] in
34 outlines := input_line stdout :: !outlines;
40 errlines := input_line stderr :: !errlines;
44 match (Unix.close_process_full chs) with
45 | Unix.WEXITED errno -> errno, !outlines, !errlines
48 let string_of_output outlines errlines =
49 let output = "std out =\n" ^ String.concat "\n" (List.rev outlines) in
50 let errors = "std err =\n" ^ String.concat "\n" (List.rev errlines) in
51 output ^ "\n\n" ^ errors
69 let string_of_matita_flag = function
70 | MUnversioned -> "unversioned"
71 | MSynchronized -> "synchronized"
73 | MModified -> "modified"
74 | MConflict -> "conflict!"
76 exception SvnAnomaly of string
78 let stat_classify line =
80 match (line.[n], n) with
81 | _, n when n = 7 -> String.sub line 8 ((String.length line) - 8), acc
82 | ' ', _ -> aux (n+1) acc
83 | 'A',0 -> aux (n+1) (Add::acc)
84 | 'C',_ when n = 0 || n = 1 -> aux (n+1) (Conflict::acc)
85 (* | 'D',0 -> aux (n+1) (Delete::acc) *)
86 (* | 'I',0 -> aux (n+1) (Ignore::acc) *)
87 | 'M',_ when n = 0 || n = 1 -> aux (n+1) (Modified::acc)
88 (* | 'R',0 -> aux (n+1) (Replaced::acc) *)
89 (* | 'X',0 -> aux (n+1) (UnversionedDir::acc) *)
90 | '?',0 -> aux (n+1) (NotAdded::acc)
91 (* | '!',0 -> aux (n+1) (Missing::acc) *)
92 (* | '~',0 -> aux (n+1) (Obstructed::acc) *)
93 (* | 'L',2 -> aux (n+1) (Lock::acc) *)
94 (* | '+',3 -> aux (n+1) (History::acc) *)
95 (* | 'S',4 -> aux (n+1) (SwitchedUrl::acc) *)
96 (* | 'X',4 -> aux (n+1) (External::acc) *)
97 (* | 'K',5 -> aux (n+1) (LockToken::acc) *)
98 (* | 'C',6 -> aux (n+1) (TreeConflict::acc) *)
99 | _ -> raise (SvnAnomaly line)
103 List.length (List.filter p l)
105 exception Unimplemented
107 let matita_flag_of_stat fs =
108 if List.mem Conflict fs then MConflict
109 else if List.mem Modified fs then MModified
110 else if List.mem Add fs then MAdd
111 else if List.mem Delete fs then raise Unimplemented
112 else if List.mem NotAdded fs then MUnversioned
116 let rt_dir = Helm_registry.get "matita.rt_base_dir" in
117 let _repo = Helm_registry.get "matita.weblib" in
119 let errno, outlines, errlines = exec_process
120 ("svn stat " ^ rt_dir ^ "/users/" ^ user ^ "/")
122 let files, anomalies =
123 List.fold_left (fun (facc,eacc) line ->
125 (stat_classify line::facc), eacc
127 | SvnAnomaly l -> facc, l::eacc) ([],[]) outlines
130 List.map (fun (fname,flags) -> fname,Some (matita_flag_of_stat flags)) files
133 if errno = 0 then files, anomalies
134 else raise (SvnError ("Anomalies: " ^ (String.concat "\n" anomalies) ^ "\n\n" ^ (string_of_output outlines errlines)))
137 (* update and checkout *)
138 let up_classify line uid =
139 let basedir = (Helm_registry.get "matita.rt_base_dir") ^ "/users/" ^ uid ^ "/" in
141 match (line.[n], n) with
143 let fn = String.sub line 5 ((String.length line) - 5) in
144 let prefix_len = String.length basedir in
145 let fn_len = String.length fn in
146 if String.sub fn 0 prefix_len = basedir
147 then String.sub fn prefix_len (fn_len - prefix_len), acc
149 | ' ', _ -> aux (n+1) acc
150 | 'A',_ when n = 0 || n = 1 -> aux (n+1) (Add::acc)
151 | 'C',_ when n = 0 || n = 1 -> aux (n+1) (Conflict::acc)
152 | 'D',_ when n = 0 || n = 1 -> aux (n+1) (Delete::acc)
153 | 'U',_ when n = 0 || n = 1 -> aux (n+1) (Update::acc)
154 | 'G',_ when n = 0 || n = 1 -> aux (n+1) (Merge::acc)
155 (* | 'E',_ when n = 0 || n = 1 -> aux (n+1) (Exist::acc) *)
156 | _ -> raise (SvnAnomaly line)
159 let matita_flag_of_update fs =
160 if List.mem Conflict fs then Some MConflict
161 else if List.mem Delete fs then None
162 else if List.mem Merge fs then Some MModified
163 else Some MSynchronized
165 (* this should be executed only for a freshly created user so no CS is needed *)
167 let rt_dir = Helm_registry.get "matita.rt_base_dir" in
168 let repo = Helm_registry.get "matita.weblib" in
170 let errno, outlines, errlines = exec_process
171 ("svn co --non-interactive " ^ repo ^ " " ^ rt_dir ^ "/users/" ^ user ^ "/")
173 let files, anomalies =
174 List.fold_left (fun (facc,eacc) line ->
176 (up_classify line user::facc), eacc
178 | SvnAnomaly l -> facc, l::eacc) ([],[]) outlines
180 if errno = 0 then List.map (fun (f,_) -> f,MSynchronized) files
181 else raise (SvnError (string_of_output outlines errlines))
183 (* normalize qualified file name *)
184 let normalize_qfn p =
185 (* trim leading "./" *)
188 if String.sub p 0 2 <> "./" then p
189 else String.sub p 2 (String.length p - 2)
191 | Invalid_argument _ -> p
193 (* trim trailing "/" *)
195 if String.sub p (String.length p - 1) 1 <> "/" then p
196 else String.sub p 0 (String.length p - 1)
198 | Invalid_argument _ -> p
200 let html_of_library uid ft =
202 let newid () = incr i; ("node" ^ string_of_int !i) in
204 let basedir = (Helm_registry.get "matita.rt_base_dir") ^ "/users/" ^ uid in
207 let branch lpath children =
209 let name = Filename.basename lpath in
210 let name = if name <> "." then name else "cic:/matita" in
212 try List.assoc lpath ft
213 with Not_found -> MSynchronized in
214 let szflag = string_of_matita_flag flag in
215 "<span class=\"trigger\" onClick=\"showBranch('" ^ id ^ "','" ^ lpath ^ "')\">\n" ^
216 "<img src=\"treeview/closed.gif\" id=\"I" ^ id ^ "\"/>\n" ^
217 name ^ " " ^ szflag ^ "<br/></span>\n" ^
218 "<span class=\"branch\" id=\"" ^ id ^ "\">\n" ^
219 children ^ "\n</span>"
223 try List.assoc lpath ft
224 with Not_found -> MSynchronized in
225 let szflag = string_of_matita_flag flag in
226 "<img src=\"treeview/doc.gif\"/>\n" ^
227 "<a href=\"javascript:void(0)\" onClick=\"selectFile('" ^ lpath ^ "')\" onDblClick=\"dialogBox.callback('" ^ lpath ^ "')\">" ^
228 (Filename.basename lpath) ^ " " ^ szflag ^ "</a><br/>"
232 let lpath filename = path ^ "/" ^ filename in
233 let gpath filename = basedir ^ "/" ^ path ^ "/" ^ filename in
235 (* hide hidden dirs ... including svn stuff *)
237 List.filter (fun x -> String.sub x 0 1 <> ".")
238 (Array.to_list (Sys.readdir (basedir ^ "/" ^ path))) in
239 let subdirs = List.filter (fun x -> Sys.is_directory (gpath x)) dirlist in
241 (* only .ma scripts, hidden files excluded *)
243 List.filter (fun x ->
245 let i = String.rindex x '.' in
246 let len = String.length x - i in
247 not (Sys.is_directory (gpath x)) &&
248 (String.sub x 0 1 <> ".") && (String.sub x i len = ".ma")
249 with Not_found | Invalid_argument _ -> false) dirlist in
251 String.concat "\n" (List.map (fun x -> aux (normalize_qfn (lpath x ^ "/"))) subdirs) in
254 (List.map (fun x -> leaf (normalize_qfn (lpath x))) scripts)
256 branch path (subdirtags ^ "\n" ^ scripttags)
260 prerr_endline "BEGIN TREE";prerr_endline res;prerr_endline "END TREE";
265 let to_be_removed = (Helm_registry.get "matita.rt_base_dir") ^ "/users/*" in
266 ignore (Sys.command ("rm -rf " ^ to_be_removed))
269 let update_user user =
270 let rt_dir = Helm_registry.get "matita.rt_base_dir" in
271 let _repo = Helm_registry.get "matita.weblib" in
273 let errno, outlines, errlines = exec_process
274 ("svn up --non-interactive " ^ rt_dir ^ "/users/" ^ user ^ "/")
276 let files, anomalies =
277 List.fold_left (fun (facc,eacc) line ->
279 (let fname,flags = up_classify line user in
280 (fname, flags)::facc), eacc
282 | SvnAnomaly l -> facc, l::eacc) ([],[]) outlines
284 let added = count (fun (_,flags) -> List.mem Add flags) files in
285 let conflict = count (fun (_,flags) -> List.mem Conflict flags) files in
286 let del = count (fun (_,flags) -> List.mem Delete flags) files in
287 let upd = count (fun (_,flags) -> List.mem Update flags) files in
288 let merged = count (fun (_,flags) -> List.mem Merge flags) files in
291 List.map (fun (fname,flags) -> fname,matita_flag_of_update flags) files
294 if errno = 0 then files, anomalies, (added,conflict,del,upd,merged)
295 else raise (SvnError (string_of_output outlines errlines))
298 let add_files user files =
299 let rt_dir = Helm_registry.get "matita.rt_base_dir" in
300 let _repo = Helm_registry.get "matita.weblib" in
302 let files = String.concat " "
303 (List.map ((^) (rt_dir ^ "/users/" ^ user ^ "/")) files) in
305 let errno, outlines, errlines =
307 exec_process ("svn add --non-interactive " ^ files)
311 "BEGIN ADD - " ^ user ^ ":\n" ^ (string_of_output outlines errlines) ^ "END ADD - " ^ user ^ "\n\n"
312 else raise (SvnError (string_of_output outlines errlines))
315 (* this function should only be called by the server itself (or
316 * the admin) at a scheduled time, so no concurrent instances and no CS needed
317 * also, svn should already be safe as far as concurrency is concerned *)
318 let commit user files =
319 let rt_dir = Helm_registry.get "matita.rt_base_dir" in
320 let _repo = Helm_registry.get "matita.weblib" in
322 let files = String.concat " "
323 (List.map ((^) (rt_dir ^ "/users/" ^ user ^ "/")) files) in
325 let errno, outlines, errlines = exec_process
326 ("svn ci --non-interactive --message \"commit by user " ^ user ^ "\" " ^ files)
329 "BEGIN COMMIT - " ^ user ^ ":\n" ^ (string_of_output outlines errlines) ^ "END COMMIT - " ^ user ^ "\n\n"
330 else raise (SvnError (string_of_output outlines errlines))