patch #7804
[mldonkey.git] / src / daemon / common / commonInteractive.ml
blob57ab02eab8ef4818810fe5dccfbe80338903bc65
1 (* Copyright 2001, 2002 b8_bavard, b8_fee_carabine, INRIA *)
2 (*
3 This file is part of mldonkey.
5 mldonkey is free software; you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation; either version 2 of the License, or
8 (at your option) any later version.
10 mldonkey is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with mldonkey; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 open AnyEndian
21 open LittleEndian
22 open Int64ops
23 open Md4
24 open Misc
25 open Printf2
26 open CommonOptions
27 open BasicSocket
28 open TcpBufferedSocket
29 open Options
31 open CommonClient
32 open CommonServer
33 open CommonNetwork
34 open CommonOptions
35 open CommonUserDb
36 open CommonFile
37 open CommonGlobals
38 open CommonSearch
39 open CommonResult
40 open CommonServer
41 open CommonTypes
42 open CommonComplexOptions
44 let log_prefix = "[cInt]"
46 let lprintf_nl fmt =
47 lprintf_nl2 log_prefix fmt
49 let lprintf_n fmt =
50 lprintf2 log_prefix fmt
52 (************* ADD/REMOVE FUNCTIONS ************)
53 let check_forbidden_chars (uc : Charset.uchar) =
54 match uc with
55 | 47 (* '/' *)
56 | 92 (* '\\' *) -> 95 (* '_' *)
57 (* Windows can't do these *)
58 | 58 (* ':' *)
59 | 42 (* '*' *)
60 | 63 (* '?' *)
61 | 34 (* '"' *)
62 | 60 (* '<' *)
63 | 62 (* '>' *)
64 | 124 (* '|' *)
65 | 37 (* '%' *) when Autoconf.windows -> 95 (* '_' *)
66 | _ -> uc
68 let canonize_basename name =
69 let buf = Buffer.create 100 in
70 let uname = Charset.Locale.to_utf8 name in
71 for i = 0 to Charset.utf8_length uname - 1 do
72 (* replace chars on users request *)
73 let uc = Charset.utf8_get uname i in
74 try
75 let us = List.assoc uc !!utf8_filename_conversions in
76 for j = 0 to Charset.utf8_length us - 1 do
77 let uc' = Charset.utf8_get us j in
78 let uc'' = check_forbidden_chars uc' in
79 Charset.add_uchar buf uc''
80 done
81 with _ ->
82 begin
83 let uc' = check_forbidden_chars uc in
84 Charset.add_uchar buf uc'
85 end
86 done;
87 if not Autoconf.windows && not !!filenames_utf8 then
88 Charset.Locale.to_locale (Buffer.contents buf)
89 else
90 Buffer.contents buf (* Windows uses patched OCaml which always uses Unicode chars *)
92 let last_sent_dir_warning = Hashtbl.create 10
94 let hdd_full_log_closed = ref false
95 let all_temp_queued = ref false
97 let send_dirfull_warning dir full line1 =
98 if !!smtp_server <> "" && !!smtp_port <> 0 then begin
99 let status = if full then "is full" else "has enough space again" in
100 lprintf_nl "WARNING: Directory %s %s, %s" dir status line1;
101 if (not (keep_console_output ())) then
102 Printf.eprintf "\n%s WARNING: Directory %s %s, %s\n%!" (log_time ()) dir status line1;
103 if !!hdd_send_warning_interval <> 0 then
104 let current_time = last_time () in
105 let time_threshold =
106 current_time - !!hdd_send_warning_interval * Date.hour_in_secs in
107 let send_mail_again =
109 let last = Hashtbl.find last_sent_dir_warning dir in
110 last < time_threshold
111 with Not_found -> true
113 if send_mail_again then begin
114 if full then Hashtbl.replace last_sent_dir_warning dir current_time;
115 CommonEvent.add_event (Console_message_event
116 (Printf.sprintf "\nWARNING: %s %s, %s\n" dir status line1));
117 match String2.tokens !!mail with
118 | [] -> ()
119 | mails ->
120 let module M = Mailer in
121 let subject = Printf.sprintf "[mldonkey@%s] AUTOMATED WARNING: %s %s" (Unix.gethostname ()) dir status in
122 let mail = {
123 M.mail_to = mails;
124 M.mail_from = List.hd mails;
125 M.mail_subject = subject;
126 M.mail_body = line1;
127 M.smtp_login = !!smtp_login;
128 M.smtp_password = !!smtp_password;
129 } in
131 M.sendmail !!smtp_server !!smtp_port !!add_mail_brackets mail
132 with _ -> ()
136 let file_committed_name incoming_dir file =
137 (try Unix2.safe_mkdir incoming_dir with _ -> ());
138 let fs = Unix32.filesystem incoming_dir in
139 let namemax =
140 match Unix32.fnamelen incoming_dir with
141 | None -> 0
142 | Some v -> v
144 let new_name =
145 Filename2.filesystem_compliant
146 (canonize_basename (file_best_name file)) fs namemax in
148 let new_name =
149 if Sys.file_exists (Filename.concat incoming_dir new_name) then
150 let rec iter num =
151 let new_name =
152 Filename2.filesystem_compliant
153 (Printf.sprintf "%s_%d" new_name num) fs namemax in
154 if Sys.file_exists (Filename.concat incoming_dir new_name) then
155 iter (num+1)
156 else new_name
158 iter 1
159 else new_name in
160 set_file_best_name file (file_best_name file) fs namemax;
161 Filename.concat incoming_dir new_name
163 let script_for_file file incoming new_name =
164 let info = file_info file in
165 let temp_name = file_disk_name file in
166 let file_id = Filename.basename temp_name in
167 let size = Int64.to_string (file_size file) in
168 let duration =
169 string_of_int ((BasicSocket.last_time ()) - info.G.file_age)
171 let network = network_find_by_num info.G.file_network in
172 let filename = Filename.basename new_name in
173 let file_group_info =
174 match file_group file with
175 | None -> 0, []
176 | Some _ ->
177 let users = ref [] in
178 let counter = ref 0 in
179 user2_users_iter (fun u ->
180 if file_owner file <> u &&
181 user2_can_view_file u (file_owner file) (file_group file) then
182 begin
183 incr counter;
184 users := (Printf.sprintf "FILE_GROUP_USER_%d" !counter, u.user_name) ::
185 (Printf.sprintf "FILE_GROUP_DIR_%d" !counter, u.user_commit_dir) :: !users
186 end);
187 !counter, !users
189 begin try
190 MlUnix.fork_and_exec !!file_completed_cmd
191 [| (* keep those for compatibility *)
193 file_id; (* $1 *)
194 size; (* $2 *)
195 filename (* $3 *)
197 ~vars:([("TEMPNAME", temp_name);
198 ("FILEID", file_id);
199 ("FILESIZE", size);
200 ("FILENAME", filename);
201 ("FILEHASH", string_of_uids info.G.file_uids);
202 ("DURATION", duration);
203 ("DLFILES", string_of_int (List.length !!files));
204 ("INCOMING", incoming);
205 ("NETWORK", network.network_name);
206 ("ED2K_HASH", (file_print_ed2k_link filename (file_size file) info.G.file_md4));
207 ("FILE_OWNER",(file_owner file).user_name);
208 ("FILE_GROUP",user2_print_group (file_group file));
209 ("USER_MAIL", ( if (file_owner file).user_mail <> "" then
210 (file_owner file).user_mail
211 else
212 match String2.tokens !!mail with [] -> "" | x::_ -> x));
213 ("FILE_GROUP_CNT", string_of_int (fst (file_group_info)));
215 @ snd (file_group_info))
217 with e ->
218 lprintf_nl "Exception %s while executing %s"
219 (Printexc2.to_string e) !!file_completed_cmd
222 (********
223 These two functions 'file_commit' and 'file_cancel' should be the two only
224 functions in mldonkey able to destroy a file, the first one by moving it,
225 the second one by deleting it.
227 Note that when the network specific file_commit function is called, the
228 file has already been moved to the incoming/ directory under its new
229 name.
232 let file_commit file =
233 let impl = as_file_impl file in
234 if impl.impl_file_state = FileDownloaded then
235 let subfiles = file_files file in
236 match subfiles with
237 primary :: secondary_files ->
238 if primary == file then
239 (try
240 let file_name = file_disk_name file in
241 let incoming =
242 incoming_dir
243 (Unix2.is_directory file_name)
244 ~needed_space:(file_size file)
245 ~user:(file_owner file)
249 let new_name = file_committed_name incoming.shdir_dirname file in
250 if Unix2.is_directory file_name then begin
251 Unix2.safe_mkdir new_name;
252 Unix2.chmod new_name !Unix32.create_dir_mode;
253 end;
255 (* the next line really moves the file *)
256 set_file_disk_name file new_name;
258 if !!file_completed_cmd <> "" then
259 script_for_file file incoming.shdir_dirname new_name;
261 let best_name = file_best_name file in
262 Unix32.destroy (file_fd file);
264 if Unix2.is_directory file_name then Unix2.remove_all_directory file_name;
266 let impl = as_file_impl file in
267 (* When the commit action is called, the file is supposed not to exist anymore. *)
268 impl.impl_file_ops.op_file_commit impl.impl_file_val new_name;
270 begin
272 if not (Unix2.is_directory new_name) then
273 ignore (CommonShared.new_shared
274 incoming.shdir_dirname incoming.shdir_priority
275 best_name new_name);
276 with e ->
277 lprintf_nl "Exception %s while trying to share committed file"
278 (Printexc2.to_string e);
279 end;
281 update_file_state impl FileShared;
282 done_files =:= List2.removeq file !!done_files;
283 files =:= List2.removeq file !!files;
285 List.iter (fun file ->
286 (* Commit the file first, and share it after... *)
288 let impl = as_file_impl file in
289 update_file_state impl FileCancelled;
290 impl.impl_file_ops.op_file_cancel impl.impl_file_val;
291 done_files =:= List2.removeq file !!done_files;
292 files =:= List2.removeq file !!files;
294 with e ->
295 lprintf_nl "Exception %s in file_commit secondaries" (Printexc2.to_string e);
296 ) secondary_files
297 with
298 Incoming_full ->
299 send_dirfull_warning "" true
300 (Printf.sprintf "all incoming dirs are full, can not commit %s" (file_best_name file))
301 | e -> lprintf_nl "Exception in file_commit: %s" (Printexc2.to_string e))
302 | _ -> assert false
304 let file_cancel file user =
305 if user2_allow_file_admin file user then
307 let impl = as_file_impl file in
308 if impl.impl_file_state <> FileCancelled then
309 let subfiles = file_files file in
310 if file != List.hd subfiles then
311 failwith "Cannot cancel non primary file";
312 List.iter (fun file ->
314 update_file_state impl FileCancelled;
315 impl.impl_file_ops.op_file_cancel impl.impl_file_val;
316 files =:= List2.removeq file !!files;
317 with e ->
318 lprintf_nl "Exception %s in file_cancel" (Printexc2.to_string e);
319 ) subfiles;
321 let fd = file_fd file in
322 (try
323 Unix32.remove fd
324 with e ->
325 lprintf_nl "Sys.remove %s exception %s"
326 (file_disk_name file)
327 (Printexc2.to_string e));
328 Unix32.destroy fd
329 with Not_found -> ()
330 with e ->
331 lprintf_nl "Exception in file_cancel: %s" (Printexc2.to_string e)
333 let mail_for_completed_file file =
334 let usermail = (file_owner file).user_mail in
335 let mail = String2.tokens !!mail in
336 if (mail <> [] || usermail <> "") && !!smtp_server <> "" && !!smtp_port <> 0 then begin
337 let module M = Mailer in
338 let info = file_info file in
339 let line1 = "mldonkey has completed the download of:\r\n\r\n" in
341 let line2 = Printf.sprintf "\r\nFile: %s\r\nSize: %Ld bytes\r\nHash: %s\r\nFile was downloaded in %s\r\n"
342 (file_best_name file)
343 (file_size file)
344 (string_of_uids info.G.file_uids)
345 (let age = (BasicSocket.last_time ()) - info.G.file_age in Date.time_to_string age "verbose")
348 let line3 = if (file_comment file) = "" then "" else
349 Printf.sprintf "\r\nComment: %s\r\n" (file_comment file)
352 let subject = if !!filename_in_subject then
353 Printf.sprintf "mldonkey - %s complete" (file_best_name file)
354 else
355 Printf.sprintf "mldonkey - download complete"
358 (* TODO: This information can be wrong *)
359 let incoming = incoming_dir (Unix2.is_directory (file_disk_name file)) () in
361 let line4 = if !!url_in_mail = "" then "" else
362 Printf.sprintf "\r\n<%s/%s%s/%s>\r\n"
363 !!url_in_mail
364 incoming.shdir_dirname
365 (if (file_owner file).user_commit_dir = "" then ""
366 else Printf.sprintf "/%s" (file_owner file).user_commit_dir)
367 (Url.encode (file_best_name file))
370 let line5 = if !!auto_commit then "" else
371 Printf.sprintf "\r\nauto_commit is disabled, file is not committed to incoming"
374 let line6 =
375 Printf.sprintf "\r\nUser/Group: %s:%s" (file_owner file).user_name (user2_print_group (file_group file))
378 let line7 =
379 Printf.sprintf "\r\nHost: %s\r\n" (Unix.gethostname ())
382 let send_mail address admin =
383 let mail = {
384 M.mail_to = address;
385 M.mail_from = List.hd address;
386 M.mail_subject = subject;
387 M.mail_body = line1 ^ line2 ^ line3 ^ line4 ^ line5 ^ (if admin then line6 else "") ^ line7;
388 M.smtp_login = !!smtp_login;
389 M.smtp_password = !!smtp_password;
390 } in
391 M.sendmail !!smtp_server !!smtp_port !!add_mail_brackets mail
393 if mail <> [] then send_mail mail true; (* Multiuser ToDo: this mail is for the admin user, optional? *)
394 if usermail <> "" && [usermail] <> mail then (try send_mail [usermail] false with Not_found -> ())
397 let file_completed (file : file) =
399 let impl = as_file_impl file in
400 if impl.impl_file_state = FileDownloading then begin
401 CommonSwarming.duplicate_chunks ();
402 set_file_release file false (admin_user ());
403 files =:= List2.removeq file !!files;
404 done_files =:= file :: !!done_files;
405 update_file_state impl FileDownloaded;
406 (try mail_for_completed_file file with e ->
407 lprintf_nl "Exception %s in sendmail" (Printexc2.to_string e);
410 with e ->
411 lprintf_nl "Exception in file_completed: %s" (Printexc2.to_string e)
413 let file_add impl state =
415 let file = as_file impl in
416 if impl.impl_file_state = FileNew then begin
417 update_file_num impl;
418 (match state with
419 FileDownloaded ->
420 done_files =:= file :: !!done_files;
421 | FileShared
422 | FileNew
423 | FileCancelled -> ()
425 | FileAborted _
426 | FileDownloading
427 | FileQueued
428 | FilePaused ->
429 files =:= !!files @ [file]);
430 update_file_state impl state
432 with e ->
433 lprintf_nl "[cInt] Exception in file_add: %s" (Printexc2.to_string e)
435 let server_remove server =
436 begin
437 match server_state server with
438 NotConnected _ -> ()
439 | _ -> server_disconnect server
440 end;
442 let impl = as_server_impl server in
443 if impl.impl_server_state <> RemovedHost then begin
444 set_server_state server RemovedHost;
445 (try impl.impl_server_ops.op_server_remove impl.impl_server_val
446 with _ -> ());
447 servers =:= Intmap.remove (server_num server) !!servers
449 with e ->
450 lprintf_nl "[cInt] Exception in server_remove: %s" (Printexc2.to_string e)
452 let server_add impl =
453 let server = as_server impl in
454 if impl.impl_server_state = NewHost then begin
455 server_update_num impl;
456 servers =:= Intmap.add (server_num server) server !!servers;
457 impl.impl_server_state <- NotConnected (BasicSocket.Closed_by_user, -1);
460 let friend_add c =
461 let impl = as_client_impl c in
462 if not (is_friend c) then begin
463 set_friend c;
464 client_must_update c;
465 friends =:= c :: !!friends;
466 contacts := List2.removeq c !contacts;
467 if network_is_enabled ((as_client_impl c).impl_client_ops.op_client_network) then
468 impl.impl_client_ops.op_client_browse impl.impl_client_val true
471 (* Maybe we should not add the client to the contact list and completely remove
472 it ? *)
473 let friend_remove c =
475 let impl = as_client_impl c in
476 if is_friend c then begin
477 set_not_friend c;
478 client_must_update c;
479 friends =:= List2.removeq c !!friends;
480 impl.impl_client_ops.op_client_clear_files impl.impl_client_val
481 end else
482 if is_contact c then begin
483 set_not_contact c;
484 client_must_update c;
485 contacts := List2.removeq c !contacts;
486 impl.impl_client_ops.op_client_clear_files impl.impl_client_val
489 with e ->
490 lprintf_nl "Exception in friend_remove: %s" (Printexc2.to_string e)
492 let contact_add c =
493 let impl = as_client_impl c in
494 if not (is_friend c || is_contact c) then begin
495 set_contact c;
496 client_must_update c;
497 contacts := c :: !contacts;
498 if network_is_enabled ((as_client_impl c).impl_client_ops.op_client_network) then
499 impl.impl_client_ops.op_client_browse impl.impl_client_val true
502 let contact_remove c =
504 let impl = as_client_impl c in
505 if is_contact c then begin
506 set_not_contact c;
507 client_must_update c;
508 contacts := List2.removeq c !contacts;
509 impl.impl_client_ops.op_client_clear_files impl.impl_client_val
511 with e ->
512 lprintf_nl "Exception in contact_remove: %s" (Printexc2.to_string e)
514 let clean_exit n =
515 begin
516 let can_exit = networks_for_all network_clean_exit in
517 if can_exit then exit_properly n
518 else
519 let rec retry_later retry_counter =
520 add_timer 1. (fun _ ->
521 let can_exit = networks_for_all network_clean_exit in
522 if can_exit || retry_counter > !!shutdown_timeout then
523 exit_properly n
524 else retry_later (retry_counter + 1)) in
525 retry_later 0;
527 if (upnp_port_forwarding ()) then
528 begin
529 if !!clear_upnp_port_at_exit then
530 UpnpClient.remove_all_maps 0 ;
531 UpnpClient.job_stop 3;
532 end;
535 let time_of_sec sec =
536 let hours = sec / 60 / 60 in
537 let rest = sec - hours * 60 * 60 in
538 let minutes = rest / 60 in
539 let seconds = rest - minutes * 60 in
540 if hours > 0 then Printf.sprintf "%d:%02d:%02d" hours minutes seconds
541 else if minutes > 0 then Printf.sprintf "%d:%02d" minutes seconds
542 else Printf.sprintf "00:%02d" seconds
545 let display_vd = ref false
546 let display_bw_stats = ref false
548 let start_download file =
549 if !!pause_new_downloads then file_pause file (admin_user ());
550 if !!release_new_downloads then set_file_release file true (admin_user ());
551 if !!file_started_cmd <> "" then
552 begin
553 let info = file_info file in
554 let temp_name = file_disk_name file in
555 let file_id = Filename.basename temp_name in
556 let size = Int64.to_string (file_size file) in
557 let network = network_find_by_num info.G.file_network in
558 let filename = file_best_name file in
559 let file_group_info =
560 match file_group file with
561 | None -> 0, []
562 | Some _ ->
563 let users = ref [] in
564 let counter = ref 0 in
565 user2_users_iter (fun u ->
566 if file_owner file <> u &&
567 user2_can_view_file u (file_owner file) (file_group file) then
568 begin
569 incr counter;
570 users := (Printf.sprintf "FILE_GROUP_USER_%d" !counter, u.user_name) ::
571 (Printf.sprintf "FILE_GROUP_DIR_%d" !counter, u.user_commit_dir) :: !users
572 end);
573 !counter, !users
575 MlUnix.fork_and_exec !!file_started_cmd
577 !!file_started_cmd;
578 "-file";
579 string_of_int (CommonFile.file_num file);
581 ~vars:([("TEMPNAME", temp_name);
582 ("FILEID", file_id);
583 ("FILESIZE", size);
584 ("FILENAME", filename);
585 ("FILEHASH", string_of_uids info.G.file_uids);
586 ("DLFILES", string_of_int (List.length !!files));
587 ("NETWORK", network.network_name);
588 ("ED2K_HASH", (file_print_ed2k_link filename (file_size file) info.G.file_md4));
589 ("FILE_OWNER",(file_owner file).user_name);
590 ("FILE_GROUP",user2_print_group (file_group file));
591 ("USER_MAIL", ( if (file_owner file).user_mail <> "" then
592 (file_owner file).user_mail
593 else
594 match String2.tokens !!mail with [] -> "" | x::_ -> x));
595 ("FILE_GROUP_CNT", string_of_int (fst (file_group_info)));
597 @ snd (file_group_info))
600 let download_file o arg =
601 let user = o.conn_user in
602 let buf = o.conn_buf in
603 Printf.bprintf buf "%s\n" (
605 match user.ui_last_search with
606 None -> "no last search"
607 | Some s ->
608 let result = List.assoc (int_of_string arg) user.ui_last_results in
609 let files = CommonResult.result_download
610 result [] false user.ui_user in
611 List.iter start_download files;
612 "download started"
613 with
614 | Failure s -> s
615 | _ -> "could not start download"
618 let start_search user query buf =
619 let s = CommonSearch.new_search user query in
620 begin
621 match s.search_type with
622 LocalSearch ->
623 CommonSearch.local_search s
624 | _ ->
625 networks_iter (fun r ->
626 if query.GuiTypes.search_network = 0 ||
627 r.network_num = query.GuiTypes.search_network
628 then network_search r s buf);
629 end;
633 let network_must_update n =
634 CommonEvent.add_event (Network_info_event n)
636 let network_display_stats o =
637 networks_iter_all (fun r ->
639 if List.mem NetworkHasStats r.network_flags then
640 network_display_stats r o
641 with _ -> ())
643 let print_connected_servers o =
644 let buf = o.conn_buf in
645 networks_iter (fun r ->
647 let list = network_connected_servers r in
648 if List.mem NetworkHasServers r.network_flags ||
649 List.mem NetworkHasSupernodes r.network_flags
650 then begin
651 if use_html_mods o then begin
652 html_mods_table_one_row buf "serversTable" "servers" [
653 ("", "srh", Printf.sprintf (_b "--- Connected to %d servers on the %s network ---\n")
654 (List.length list) r.network_name); ]
656 else
657 Printf.bprintf buf (_b "--- Connected to %d servers on the %s network ---\n")
658 (List.length list) r.network_name;
659 end;
660 if use_html_mods o && List.length list > 0 then server_print_html_header buf "C";
662 html_mods_cntr_init ();
663 List.iter (fun s ->
664 server_print s o;
665 ) (List.sort (fun s1 s2 -> compare (server_num s1) (server_num s2)) list);
666 if use_html_mods o && List.length list > 0 then
667 Printf.bprintf buf "\\</table\\>\\</div\\>";
668 if Autoconf.donkey = "yes" && r.network_name = "Donkey" && not !!enable_servers then
669 begin
670 if use_html_mods o then Printf.bprintf buf "\\<div class=servers\\>";
671 Printf.bprintf buf (_b "You disabled server usage, therefore you are not able to connect ED2K servers.\n");
672 Printf.bprintf buf (_b "To use servers again 'set enable_servers true'\n");
673 if use_html_mods o then Printf.bprintf buf "\\</div\\>"
674 end;
675 with e ->
676 Printf.bprintf buf "Exception %s in print_connected_servers"
677 (Printexc2.to_string e);
680 let send_custom_query user buf query args =
682 let q = List.assoc query (CommonComplexOptions.customized_queries()) in
683 let args = ref args in
684 let get_arg arg_name =
685 (* lprintf "Getting %s\n" arg_name; *)
686 match !args with
687 (label, value) :: tail ->
688 args := tail;
689 if label = arg_name then value else begin
690 Printf.bprintf buf "Error expecting argument %s instead of %s" arg_name label;
691 raise Exit
693 | _ ->
694 Printf.bprintf buf "Error while expecting argument %s" arg_name;
695 raise Exit
697 let rec iter q =
698 match q with
699 | Q_COMBO _ -> assert false
700 | Q_KEYWORDS _ ->
701 let value = get_arg "keywords" in
702 want_and_not andnot (fun w -> QHasWord w) QNone value
704 | Q_AND list ->
705 begin
706 let ands = ref [] in
707 List.iter (fun q ->
708 try ands := (iter q) :: !ands with _ -> ()) list;
709 match !ands with
710 [] -> raise Not_found
711 | [q] -> q
712 | q1 :: tail ->
713 List.fold_left (fun q1 q2 -> QAnd (q1,q2)) q1 tail
716 | Q_HIDDEN list ->
717 begin
718 let ands = ref [] in
719 List.iter (fun q ->
720 try ands := (iter q) :: !ands with _ -> ()) list;
721 match !ands with
722 [] -> raise Not_found
723 | [q] -> q
724 | q1 :: tail ->
725 List.fold_left (fun q1 q2 -> QAnd (q1,q2)) q1 tail
728 | Q_OR list ->
729 begin
730 let ands = ref [] in
731 List.iter (fun q ->
732 try ands := (iter q) :: !ands with _ -> ()) list;
733 match !ands with
734 [] -> raise Not_found
735 | [q] -> q
736 | q1 :: tail ->
737 List.fold_left (fun q1 q2 -> QOr (q1,q2)) q1 tail
740 | Q_ANDNOT (q1, q2) ->
741 begin
742 let r1 = iter q1 in
744 QAndNot(r1, iter q2)
745 with Not_found -> r1
748 | Q_MODULE (s, q) -> iter q
750 | Q_MINSIZE _ ->
751 let minsize = get_arg "minsize" in
752 let unit = get_arg "minsize_unit" in
753 if minsize = "" then raise Not_found;
754 let minsize = Int64.of_string minsize in
755 let unit = Int64.of_string unit in
756 QHasMinVal (Field_Size, Int64.mul minsize unit)
758 | Q_MAXSIZE _ ->
759 let maxsize = get_arg "maxsize" in
760 let unit = get_arg "maxsize_unit" in
761 if maxsize = "" then raise Not_found;
762 let maxsize = Int64.of_string maxsize in
763 let unit = Int64.of_string unit in
764 QHasMaxVal (Field_Size, maxsize ** unit)
766 | Q_FORMAT _ ->
767 let format = get_arg "format" in
768 let format_propose = get_arg "format_propose" in
769 let format = if format = "" then
770 if format_propose = "" then raise Not_found
771 else format_propose
772 else format in
773 want_comb_not andnot
774 or_comb
775 (fun w -> QHasField(Field_Format, w)) QNone format
777 | Q_MEDIA _ ->
778 let media = get_arg "media" in
779 let media_propose = get_arg "media_propose" in
780 let media = if media = "" then
781 if media_propose = "" then raise Not_found
782 else media_propose
783 else media in
784 QHasField(Field_Type, media)
786 | Q_MP3_ARTIST _ ->
787 let artist = get_arg "artist" in
788 if artist = "" then raise Not_found;
789 want_comb_not andnot and_comb
790 (fun w -> QHasField(Field_Artist, w)) QNone artist
792 | Q_MP3_TITLE _ ->
793 let title = get_arg "title" in
794 if title = "" then raise Not_found;
795 want_comb_not andnot and_comb
796 (fun w -> QHasField(Field_Title, w)) QNone title
798 | Q_MP3_ALBUM _ ->
799 let album = get_arg "album" in
800 if album = "" then raise Not_found;
801 want_comb_not andnot and_comb
802 (fun w -> QHasField(Field_Album, w)) QNone album
804 | Q_MP3_BITRATE _ ->
805 let bitrate = get_arg "bitrate" in
806 if bitrate = "" then raise Not_found;
807 QHasMinVal(Field_KNOWN "bitrate", Int64.of_string bitrate)
811 let request = CommonIndexing.simplify_query (iter q) in
812 Printf.bprintf buf "Sending query !!!";
814 let s =
815 let module G = GuiTypes in
816 { G.search_num = 0;
817 G.search_query = request;
818 G.search_type = RemoteSearch;
819 G.search_max_hits = 10000;
820 G.search_network = (
822 let net = get_arg "network" in
823 (network_find_by_name net).network_num
824 with _ -> 0);
827 ignore (start_search user s buf)
828 with
829 Not_found ->
830 Printf.bprintf buf "Void query %s" query
831 with
832 Not_found ->
833 Printf.bprintf buf "No such custom search %s" query
834 | Exit -> ()
835 | e ->
836 Printf.bprintf buf "Error %s while parsing request"
837 (Printexc2.to_string e)
839 let sort_options l =
840 List.sort (fun o1 o2 ->
841 String.compare o1.option_name o2.option_name) l
843 let opfile_args r opfile =
844 let prefix = r.network_shortname ^ "-" in
845 simple_options prefix opfile true
847 let all_simple_options () =
848 let options = ref (sort_options
849 (simple_options "" downloads_ini true)
852 networks_iter_all (fun r ->
853 List.iter (fun opfile ->
854 options := !options @ (opfile_args r opfile)
856 r.network_config_file
858 !options
860 let parse_simple_options args =
861 let v = all_simple_options () in
862 match args with
863 [] -> v
864 | args ->
865 let match_star = Str.regexp "\\*" in
866 let options_filter = Str.regexp ("^\\("
867 ^ (List.fold_left (fun acc a -> acc
868 ^ (if acc <> "" then "\\|" else "")
869 ^ (Str.global_replace match_star ".*" a)) "" args)
870 ^ "\\)$") in
871 List.filter (fun o -> Str.string_match options_filter o.option_name 0) v
873 let some_simple_options num =
874 let cnt = ref 0 in
875 let options = ref [] in
876 networks_iter_all (fun r ->
877 List.iter (fun opfile ->
878 if !cnt = num then begin
879 options := !options @ (opfile_args r opfile)
881 end;
882 incr cnt
883 ) r.network_config_file
885 !options
887 let all_active_network_opfile_network_names () =
888 let names = ref [] in
889 networks_iter_all (fun r ->
890 List.iter (fun opfile ->
891 names := !names @ [r.network_name]
892 ) r.network_config_file
894 !names
896 let apply_on_fully_qualified_options name f =
897 let rec iter prefix opfile =
898 let args = simple_options prefix opfile true in
899 List.iter (fun o ->
900 if o.option_name = name then
901 (f opfile o.option_shortname o.option_value; raise Exit))
902 args
905 iter "" downloads_ini;
906 iter "" users_ini;
907 if not (networks_iter_all_until_true (fun r ->
909 List.iter (fun opfile ->
910 let prefix = r.network_shortname ^ "-" in
911 iter prefix opfile;
913 r.network_config_file ;
914 false
915 with Exit -> true
916 )) then begin
917 lprintf_nl "Could not set option %s" name;
918 raise Not_found
920 with Exit -> ()
922 let get_fully_qualified_options name =
923 let value = ref None in
924 (try
925 apply_on_fully_qualified_options name (fun opfile old_name old_value ->
926 value := Some (get_simple_option opfile old_name)
928 with _ -> ());
929 match !value with
930 None -> "unknown"
931 | Some s -> s
933 let set_fully_qualified_options name value ?(user = None) ?(ip = None) ?(port = None) ?(gui_type = None) () =
934 let old_value = get_fully_qualified_options name in
935 apply_on_fully_qualified_options name
936 (fun opfile old_name old_value -> set_simple_option opfile old_name value);
937 if !verbose && old_value <> get_fully_qualified_options name then
938 begin
939 let ip_port_text =
940 match ip with
941 | None -> "IP unknown"
942 | Some ip ->
943 Printf.sprintf "from host %s%s" (Ip.to_string ip)
944 (match port with | None -> "" | Some port -> Printf.sprintf ":%d" port)
946 lprintf_nl "User %s changed option %s %s %s, old: %s, new %s"
947 (match user with | None -> "unknown" | Some user -> user)
948 name ip_port_text
949 (match gui_type with
950 | None -> "GUI type unknown"
951 | Some gt -> Printf.sprintf "using %s interface" (connection_type_to_text gt))
952 old_value (get_fully_qualified_options name)
955 let keywords_of_query query =
956 let keywords = ref [] in
958 let rec iter q =
959 match q with
960 | QOr (q1,q2)
961 | QAnd (q1, q2) -> iter q1; iter q2
962 | QAndNot (q1,q2) -> iter q1
963 | QHasWord w -> keywords := (String2.split_simplify w ' ') @ !keywords
964 | QHasField(field, w) ->
965 begin
966 match field with
967 Field_Album
968 | Field_Title
969 | Field_Artist
970 | _ -> keywords := (String2.split_simplify w ' ') @ !keywords
972 | QHasMinVal (field, value) ->
973 begin
974 match field with
975 Field_KNOWN "bitrate"
976 | Field_Size
977 | _ -> ()
979 | QHasMaxVal (field, value) ->
980 begin
981 match field with
982 Field_KNOWN "bitrate"
983 | Field_Size
984 | _ -> ()
986 | QNone ->
987 lprintf_nl "start_search: QNone in query";
990 iter query;
991 !keywords
993 let gui_options_panels = ref ([] : (string * (string * string * string) list) list)
995 let register_gui_options_panel name panel =
996 if not (List.mem_assoc name !gui_options_panels) then
997 gui_options_panels := (name, panel) :: !gui_options_panels
999 let _ =
1000 add_infinite_timer filter_search_delay (fun _ ->
1001 (* if !!filter_search then *) begin
1002 (* lprintf "Filter search results\n"; *)
1003 List.iter (fun user ->
1004 List.iter (fun s -> CommonSearch.Filter.find s)
1005 user.ui_user_searches;
1006 ) !ui_users
1007 end;
1008 CommonSearch.Filter.clear ();
1011 let search_add_result filter s r =
1012 if !CommonSearch.clean_local_search <> 0 then
1013 CommonSearch.Local.add r;
1014 if not filter (*!!filter_search*) then begin
1015 (* lprintf "Adding result to filter\n"; *)
1016 CommonSearch.search_add_result_in s r
1018 else
1019 CommonSearch.Filter.add r
1021 let main_options = ref ([] : (string * Arg.spec * string) list)
1023 let add_main_options list =
1024 main_options := !main_options @ list
1027 (*************************************************************
1029 Every minute, sort the files by priority, and test if the
1030 files with the highest priority are in FileDownloading state,
1031 and the ones with lowest priority in FileQueued state, if there
1032 is a max_concurrent_downloads constraint.
1034 **************************************************************)
1036 open CommonFile
1038 type user_file_list = {
1039 file_list : file list;
1040 downloads_allowed : int option;
1043 let force_download_quotas () =
1045 let queue_files files =
1046 List.iter (fun file ->
1047 if file_state file = FileDownloading then
1048 file_queue file
1049 ) files in
1051 let queue_user_file_list (_user, user_file_list) =
1052 queue_files user_file_list.file_list in
1054 if !all_temp_queued then
1055 queue_files !!CommonComplexOptions.files
1056 else
1058 (* create the assoc list of downloads of each user *)
1059 let files_by_user = List.fold_left (fun acc f ->
1060 let owner = CommonFile.file_owner f in
1062 let owner_file_list = List.assoc owner acc in
1063 (owner, { owner_file_list with
1064 file_list = f :: owner_file_list.file_list }) ::
1065 List.remove_assoc owner acc
1066 with Not_found ->
1067 (owner, {
1068 downloads_allowed =
1069 (match owner.user_max_concurrent_downloads with
1070 | 0 -> None
1071 | i -> Some i);
1072 file_list = [f] }) :: acc
1073 ) [] !!CommonComplexOptions.files in
1075 (* sort each user's list separately *)
1076 let files_by_user = List.map (fun (owner, owner_file_list) ->
1077 owner, { owner_file_list with
1078 file_list = List.sort (fun f1 f2 ->
1079 let v = compare (file_priority f2) (file_priority f1) in
1080 if v <> 0 then v else
1081 (* [egs] do not start downloading a small file
1082 against an already active download *)
1083 let d1 = file_downloaded f1 in
1084 let d2 = file_downloaded f2 in
1085 let active1 = d1 > 0L in
1086 let active2 = d2 > 0L in
1087 if not active1 && active2 then 1
1088 else if active1 && not active2 then -1
1089 else
1090 (* Try to download in priority files with fewer bytes missing
1091 Rationale: once completed, it may allow to recover some disk space *)
1092 let remaining1 = file_size f1 -- d1 in
1093 let remaining2 = file_size f2 -- d2 in
1094 compare remaining1 remaining2
1095 ) owner_file_list.file_list }
1096 ) files_by_user in
1098 (* sort the assoc list itself with user with highest quota first *)
1099 let files_by_user =
1100 List.sort (fun (_owner1, { downloads_allowed = allowed1 })
1101 (_owner2, { downloads_allowed = allowed2 }) ->
1102 match allowed1, allowed2 with
1103 | None, None -> 0
1104 | None, _ -> -1
1105 | _, None -> 1
1106 | Some allowed1, Some allowed2 -> compare allowed2 allowed1
1107 ) files_by_user in
1109 (* serve users round-robin, starting with the one with highest quota *)
1110 let rec iter downloads_left to_serve served =
1111 if downloads_left = 0 then begin
1112 List.iter queue_user_file_list to_serve;
1113 List.iter queue_user_file_list served
1114 end else
1115 match to_serve with
1116 | [] ->
1117 if served = [] then () (* nothing left to rotate *)
1118 else (* new round *)
1119 iter downloads_left served []
1120 | (_owner, { file_list = [] }) :: others ->
1121 (* user satisfied, remove from lists *)
1122 iter downloads_left others served
1123 | ((_owner, { downloads_allowed = Some 0 }) as first) :: others ->
1124 (* reached quota, remove from future rounds *)
1125 queue_user_file_list first;
1126 iter downloads_left others served
1127 | (owner, { file_list = first_file :: other_files;
1128 downloads_allowed = allowed }) :: others ->
1129 let is_downloading =
1130 match file_state first_file with
1131 | FileDownloading -> true
1132 | FileQueued ->
1133 file_resume first_file (admin_user ());
1134 true
1135 | _ -> false in
1136 if is_downloading then
1137 iter (downloads_left - 1) others
1138 ((owner, {
1139 file_list = other_files;
1140 downloads_allowed = match allowed with
1141 | None -> None
1142 | Some i -> Some (i - 1)
1143 }) :: served)
1144 else
1145 iter downloads_left others
1146 ((owner, {
1147 file_list = other_files;
1148 downloads_allowed = allowed
1149 }) :: served) in
1150 iter !!max_concurrent_downloads files_by_user []
1152 let _ =
1153 option_hook max_concurrent_downloads (fun _ ->
1154 ignore (force_download_quotas ())