1 (* Copyright 2001, 2002 b8_bavard, b8_fee_carabine, INRIA *)
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
28 open TcpBufferedSocket
42 open CommonComplexOptions
44 let log_prefix = "[cInt]"
47 lprintf_nl2
log_prefix fmt
50 lprintf2
log_prefix fmt
52 (************* ADD/REMOVE FUNCTIONS ************)
53 let check_forbidden_chars (uc
: Charset.uchar
) =
56 | 92 (* '\\' *) -> 95 (* '_' *)
57 (* Windows can't do these *)
65 | 37 (* '%' *) when Autoconf.windows
-> 95 (* '_' *)
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
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''
83 let uc'
= check_forbidden_chars uc in
84 Charset.add_uchar
buf uc'
87 if not
Autoconf.windows
&& not
!!filenames_utf8
then
88 Charset.Locale.to_locale
(Buffer.contents
buf)
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
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
120 let module M
= Mailer
in
121 let subject = Printf.sprintf
"[mldonkey@%s] AUTOMATED WARNING: %s %s" (Unix.gethostname
()) dir
status in
124 M.mail_from
= List.hd mails
;
125 M.mail_subject
= subject;
127 M.smtp_login
= !!smtp_login
;
128 M.smtp_password
= !!smtp_password
;
131 M.sendmail
!!smtp_server
!!smtp_port
!!add_mail_brackets
mail
136 let file_committed_name incoming_dir file
=
137 (try Unix2.safe_mkdir incoming_dir
with _
-> ());
138 let fs = Unix32.filesystem incoming_dir
in
140 match Unix32.fnamelen incoming_dir
with
145 Filename2.filesystem_compliant
146 (canonize_basename (file_best_name file
)) fs namemax in
149 if Sys.file_exists
(Filename.concat incoming_dir
new_name) then
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
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
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
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
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
190 MlUnix.fork_and_exec
!!file_completed_cmd
191 [| (* keep those for compatibility *)
197 ~vars
:([("TEMPNAME", temp_name);
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
212 match String2.tokens
!!mail with [] -> "" | x
::_
-> x
));
213 ("FILE_GROUP_CNT", string_of_int
(fst
(file_group_info)));
215 @ snd
(file_group_info))
218 lprintf_nl "Exception %s while executing %s"
219 (Printexc2.to_string e
) !!file_completed_cmd
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
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
237 primary
:: secondary_files
->
238 if primary
== file
then
240 let file_name = file_disk_name file
in
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
;
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;
272 if not
(Unix2.is_directory
new_name) then
273 ignore
(CommonShared.new_shared
274 incoming.shdir_dirname
incoming.shdir_priority
277 lprintf_nl "Exception %s while trying to share committed file"
278 (Printexc2.to_string e
);
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
;
295 lprintf_nl "Exception %s in file_commit secondaries" (Printexc2.to_string e
);
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
))
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
;
318 lprintf_nl "Exception %s in file_cancel" (Printexc2.to_string e
);
321 let fd = file_fd file
in
325 lprintf_nl "Sys.remove %s exception %s"
326 (file_disk_name file
)
327 (Printexc2.to_string 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
)
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
)
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"
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"
375 Printf.sprintf
"\r\nUser/Group: %s:%s" (file_owner file
).user_name
(user2_print_group
(file_group file
))
379 Printf.sprintf
"\r\nHost: %s\r\n" (Unix.gethostname
())
382 let send_mail address admin
=
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
;
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
);
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;
420 done_files
=:= file :: !!done_files
;
423 | FileCancelled
-> ()
429 files
=:= !!files
@ [file]);
430 update_file_state
impl state
433 lprintf_nl "[cInt] Exception in file_add: %s" (Printexc2.to_string e
)
435 let server_remove server
=
437 match server_state server
with
439 | _
-> server_disconnect server
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
447 servers
=:= Intmap.remove
(server_num server
) !!servers
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);
461 let impl = as_client_impl c
in
462 if not
(is_friend c
) then begin
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
473 let friend_remove c
=
475 let impl = as_client_impl c
in
476 if is_friend c
then begin
478 client_must_update c
;
479 friends
=:= List2.removeq c
!!friends
;
480 impl.impl_client_ops
.op_client_clear_files
impl.impl_client_val
482 if is_contact c
then begin
484 client_must_update c
;
485 contacts
:= List2.removeq c
!contacts
;
486 impl.impl_client_ops
.op_client_clear_files
impl.impl_client_val
490 lprintf_nl "Exception in friend_remove: %s" (Printexc2.to_string e
)
493 let impl = as_client_impl c
in
494 if not
(is_friend c
|| is_contact c
) then begin
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
507 client_must_update c
;
508 contacts
:= List2.removeq c
!contacts
;
509 impl.impl_client_ops
.op_client_clear_files
impl.impl_client_val
512 lprintf_nl "Exception in contact_remove: %s" (Printexc2.to_string e
)
516 let can_exit = networks_for_all network_clean_exit
in
517 if can_exit then exit_properly n
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
524 else retry_later (retry_counter
+ 1)) in
527 if (upnp_port_forwarding
()) then
529 if !!clear_upnp_port_at_exit
then
530 UpnpClient.remove_all_maps
0 ;
531 UpnpClient.job_stop
3;
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
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
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
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
575 MlUnix.fork_and_exec
!!file_started_cmd
579 string_of_int
(CommonFile.file_num
file);
581 ~vars
:([("TEMPNAME", temp_name);
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
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"
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;
615 | _
-> "could not start download"
618 let start_search user query
buf =
619 let s = CommonSearch.new_search
user query
in
621 match s.search_type
with
623 CommonSearch.local_search
s
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);
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
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
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
); ]
657 Printf.bprintf
buf (_b
"--- Connected to %d servers on the %s network ---\n")
658 (List.length
list) r
.network_name
;
660 if use_html_mods o
&& List.length
list > 0 then server_print_html_header
buf "C";
662 html_mods_cntr_init
();
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
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\\>"
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; *)
687 (label
, value) :: tail
->
689 if label
= arg_name
then value else begin
690 Printf.bprintf
buf "Error expecting argument %s instead of %s" arg_name label
;
694 Printf.bprintf
buf "Error while expecting argument %s" arg_name
;
699 | Q_COMBO _
-> assert false
701 let value = get_arg "keywords" in
702 want_and_not andnot
(fun w
-> QHasWord w
) QNone
value
708 try ands := (iter q) :: !ands with _
-> ()) list;
710 [] -> raise Not_found
713 List.fold_left
(fun q1 q2
-> QAnd
(q1
,q2
)) q1 tail
720 try ands := (iter q) :: !ands with _
-> ()) list;
722 [] -> raise Not_found
725 List.fold_left
(fun q1 q2
-> QAnd
(q1
,q2
)) q1 tail
732 try ands := (iter q) :: !ands with _
-> ()) list;
734 [] -> raise Not_found
737 List.fold_left
(fun q1 q2
-> QOr
(q1
,q2
)) q1 tail
740 | Q_ANDNOT
(q1
, q2
) ->
748 | Q_MODULE
(s, q) -> iter q
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)
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)
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
775 (fun w
-> QHasField
(Field_Format
, w
)) QNone
format
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
784 QHasField
(Field_Type
, media)
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
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
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
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 !!!";
815 let module G
= GuiTypes
in
817 G.search_query
= request;
818 G.search_type
= RemoteSearch
;
819 G.search_max_hits
= 10000;
822 let net = get_arg "network" in
823 (network_find_by_name
net).network_num
827 ignore
(start_search user s buf)
830 Printf.bprintf
buf "Void query %s" query
833 Printf.bprintf
buf "No such custom search %s" query
836 Printf.bprintf
buf "Error %s while parsing request"
837 (Printexc2.to_string e
)
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
860 let parse_simple_options args =
861 let v = all_simple_options () in
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)
871 List.filter
(fun o
-> Str.string_match
options_filter o
.option_name
0) v
873 let some_simple_options num
=
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
)
883 ) r
.network_config_file
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
896 let apply_on_fully_qualified_options name f
=
897 let rec iter prefix opfile
=
898 let args = simple_options
prefix opfile
true in
900 if o
.option_name
= name
then
901 (f opfile o
.option_shortname o
.option_value
; raise Exit
))
905 iter "" downloads_ini
;
907 if not
(networks_iter_all_until_true
(fun r
->
909 List.iter (fun opfile
->
910 let prefix = r
.network_shortname ^
"-" in
913 r
.network_config_file
;
917 lprintf_nl "Could not set option %s" name
;
922 let get_fully_qualified_options name
=
923 let value = ref None
in
925 apply_on_fully_qualified_options name
(fun opfile old_name old_value
->
926 value := Some
(get_simple_option opfile old_name
)
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
941 | None
-> "IP unknown"
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)
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
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
) ->
970 | _
-> keywords := (String2.split_simplify w ' '
) @ !keywords
972 | QHasMinVal
(field
, value) ->
975 Field_KNOWN
"bitrate"
979 | QHasMaxVal
(field
, value) ->
982 Field_KNOWN
"bitrate"
987 lprintf_nl "start_search: QNone in query";
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
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
;
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
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 **************************************************************)
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
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
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
1069 (match owner.user_max_concurrent_downloads
with
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
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
}
1098 (* sort the assoc list itself with user with highest quota first *)
1100 List.sort
(fun (_owner1
, { downloads_allowed
= allowed1
})
1101 (_owner2
, { downloads_allowed
= allowed2
}) ->
1102 match allowed1
, allowed2
with
1106 | Some allowed1
, Some allowed2
-> compare allowed2 allowed1
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
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
1133 file_resume first_file
(admin_user
());
1136 if is_downloading then
1137 iter (downloads_left
- 1) others
1139 file_list
= other_files
;
1140 downloads_allowed
= match allowed
with
1142 | Some i
-> Some
(i
- 1)
1145 iter downloads_left others
1147 file_list
= other_files
;
1148 downloads_allowed
= allowed
1150 iter !!max_concurrent_downloads
files_by_user []
1153 option_hook max_concurrent_downloads
(fun _ ->
1154 ignore
(force_download_quotas ())