1 /* BitTorrent specific dialogs */
9 #include "dialogs/document.h"
10 #include "dialogs/download.h"
11 #include "dialogs/progress.h"
12 #include "intl/gettext/libintl.h"
13 #include "mime/mime.h"
14 #include "network/connection.h"
15 #include "network/state.h"
16 #include "protocol/bittorrent/bencoding.h"
17 #include "protocol/bittorrent/bittorrent.h"
18 #include "protocol/bittorrent/common.h"
19 #include "protocol/bittorrent/piececache.h"
20 #include "protocol/uri.h"
21 #include "session/download.h"
22 #include "session/session.h"
23 #include "terminal/draw.h"
24 #include "util/conv.h"
25 #include "util/string.h"
28 struct bittorrent_download_info
{
29 LIST_OF(struct string_list_item
) labels
;
36 done_bittorrent_download_info(struct bittorrent_download_info
*info
)
38 free_string_list(&info
->labels
);
39 mem_free_if(info
->selection
);
40 mem_free_if(info
->name
);
44 static struct bittorrent_download_info
*
45 init_bittorrent_download_info(struct bittorrent_meta
*meta
)
47 struct bittorrent_download_info
*info
;
48 struct bittorrent_file
*file
;
51 info
= mem_calloc(1, sizeof(*info
));
52 if (!info
) return NULL
;
54 init_list(info
->labels
);
56 info
->name
= stracpy(meta
->name
);
62 foreach (file
, meta
->files
) {
66 if (!init_string(&string
))
71 add_xnum_to_string(&string
, file
->length
);
75 spaces
= string
.length
- strcspn(string
.source
, " ") - 1;
76 add_xchar_to_string(&string
, ' ', int_max(4 - spaces
, 1));
78 add_to_string_list(&info
->labels
, string
.source
, string
.length
);
79 if (string
.length
> max_label
)
80 max_label
= string
.length
;
85 info
->selection
= mem_calloc(info
->size
, sizeof(*info
->selection
));
86 if (!info
->selection
|| info
->size
!= list_size(&meta
->files
)) {
87 done_bittorrent_download_info(info
);
93 foreach (file
, meta
->files
) {
94 struct string_list_item
*item
;
97 foreach (item
, info
->labels
)
98 if (pos
++ == info
->size
)
101 info
->selection
[info
->size
++] = 1;
102 pos
= max_label
- item
->string
.length
;
104 add_to_string(&item
->string
, file
->name
);
106 for (; pos
> 0; pos
--) {
107 insert_in_string(&item
->string
.source
, 0, " ", 1);
115 /* Add information from the meta file struct to a string. */
117 add_bittorrent_meta_to_string(struct string
*msg
, struct bittorrent_meta
*meta
,
118 struct terminal
*term
, int add_files
)
120 if (meta
->malicious_paths
) {
121 add_format_to_string(msg
, "%s\n\n",
122 _("Warning: potential malicious path detected", term
));
125 add_format_to_string(msg
, "\n%s: ",
127 add_xnum_to_string(msg
,
128 (off_t
) (meta
->pieces
- 1) * meta
->piece_length
129 + meta
->last_piece_length
);
131 if (meta
->last_piece_length
== meta
->piece_length
) {
132 add_format_to_string(msg
, " (%ld * %ld)",
133 meta
->pieces
, meta
->piece_length
);
135 add_format_to_string(msg
, " (%ld * %ld + %ld)",
136 meta
->pieces
- 1, meta
->piece_length
,
137 meta
->last_piece_length
);
140 add_format_to_string(msg
, "\n%s: %s",
141 _("Info hash", term
),
142 get_hexed_bittorrent_id(meta
->info_hash
));
144 add_format_to_string(msg
, "\n%s: %s",
145 _("Announce URI", term
),
146 struri(meta
->tracker_uris
.uris
[0]));
149 if (meta
->creation_date
) {
150 add_format_to_string(msg
, "\n%s: ",
151 _("Creation date", term
));
152 add_date_to_string(msg
,
153 get_opt_str("ui.date_format", NULL
),
154 &meta
->creation_date
);
158 if (meta
->type
== BITTORRENT_MULTI_FILE
) {
159 add_format_to_string(msg
, "\n%s: %s",
160 _("Directory", term
), meta
->name
);
164 struct bittorrent_download_info
*info
;
165 struct string_list_item
*item
;
167 info
= init_bittorrent_download_info(meta
);
169 add_format_to_string(msg
, "\n%s:",
172 foreach (item
, info
->labels
) {
173 add_format_to_string(msg
, "\n %s",
174 item
->string
.source
);
177 done_bittorrent_download_info(info
);
181 if (meta
->comment
&& *meta
->comment
) {
182 add_format_to_string(msg
, "\n%s:\n %s",
183 _("Comment", term
), meta
->comment
);
188 /* ************************************************************************** */
189 /* Download dialog button hooks and helpers: */
190 /* ************************************************************************** */
193 set_bittorrent_files_for_deletion(struct download
*download
)
195 struct bittorrent_connection
*bittorrent
;
197 if (!download
->conn
|| !download
->conn
->info
)
200 bittorrent
= download
->conn
->info
;
201 bittorrent
->cache
->delete_files
= 1;
205 set_bittorrent_notify_on_completion(struct download
*download
, struct terminal
*term
)
207 struct bittorrent_connection
*bittorrent
;
209 if (!download
->conn
|| !download
->conn
->info
)
212 bittorrent
= download
->conn
->info
;
213 bittorrent
->cache
->notify_complete
= 1;
214 bittorrent
->term
= term
;
218 notify_bittorrent_download_complete(struct bittorrent_connection
*bittorrent
)
220 struct connection
*conn
= bittorrent
->conn
;
221 unsigned char *url
= get_uri_string(conn
->uri
, URI_PUBLIC
);
225 assert(bittorrent
->term
);
227 info_box(bittorrent
->term
, MSGBOX_FREE_TEXT
,
228 N_("Download"), ALIGN_CENTER
,
229 msg_text(bittorrent
->term
, N_("Download complete:\n%s"), url
));
233 /* Handler for the Info-button available in the download dialog. */
234 widget_handler_status_T
235 dlg_show_bittorrent_info(struct dialog_data
*dlg_data
, struct widget_data
*widget_data
)
237 struct file_download
*file_download
= dlg_data
->dlg
->udata
;
238 struct download
*download
= &file_download
->download
;
242 && download
->conn
->info
243 && init_string(&msg
)) {
244 struct terminal
*term
= file_download
->term
;
245 struct bittorrent_connection
*bittorrent
;
246 enum msgbox_flags flags
= MSGBOX_FREE_TEXT
;
248 bittorrent
= download
->conn
->info
;
250 add_bittorrent_meta_to_string(&msg
, &bittorrent
->meta
, term
, 1);
252 if (list_size(&bittorrent
->meta
.files
) > 1)
253 flags
|= MSGBOX_SCROLLABLE
;
255 info_box(term
, flags
,
256 N_("Download info"), ALIGN_LEFT
, msg
.source
);
259 return EVENT_PROCESSED
;
263 /* ************************************************************************** */
264 /* Download status message creation: */
265 /* ************************************************************************** */
267 /* Compose and return the status message about current download speed and
268 * BitTorrent swarm info. It is called fairly often so most values used in here
269 * should be easily accessible. */
271 get_bittorrent_message(struct download
*download
, struct terminal
*term
,
272 int wide
, int full
, unsigned char *separator
)
274 /* Cooresponds to the connection mode enum. */
275 static unsigned char *modes_text
[] = {
276 N_("downloading (random)"),
277 N_("downloading (rarest first)"),
278 N_("downloading (end game)"),
281 struct bittorrent_connection
*bittorrent
;
282 struct bittorrent_peer_connection
*peer
;
283 struct string string
;
288 || !download
->conn
->info
289 || !init_string(&string
))
292 /* Get the download speed information message. */
293 msg
= get_progress_msg(download
->progress
, term
, wide
, full
, separator
);
295 done_string(&string
);
299 bittorrent
= download
->conn
->info
;
301 add_to_string(&string
, msg
);
306 add_format_to_string(&string
, "\n\n%s: %s",
307 _("Status", term
), _(modes_text
[bittorrent
->mode
], term
));
309 if (bittorrent
->cache
->partial
)
310 add_format_to_string(&string
, " (%s)",
315 add_format_to_string(&string
, "\n%s: ", _("Peers", term
));
317 value
= list_size(&bittorrent
->peers
);
318 add_format_to_string(&string
,
319 n_("%u connection", "%u connections", value
, term
), value
);
321 add_to_string(&string
, ", ");
324 foreach (peer
, bittorrent
->peers
)
325 if (peer
->remote
.seeder
)
328 add_format_to_string(&string
,
329 n_("%u seeder", "%u seeders", value
, term
), value
);
331 add_to_string(&string
, ", ");
333 value
= list_size(&bittorrent
->peer_pool
);
334 add_format_to_string(&string
,
335 n_("%u available", "%u available", value
, term
), value
);
339 if (bittorrent
->complete
> 0 || bittorrent
->incomplete
> 0) {
340 add_format_to_string(&string
, "\n%s: ", _("Swarm info", term
));
342 if (bittorrent
->complete
> 0) {
343 value
= bittorrent
->complete
;
344 add_format_to_string(&string
,
345 n_("%u seeder", "%u seeders", value
, term
), value
);
348 if (bittorrent
->incomplete
> 0) {
349 if (bittorrent
->complete
> 0)
350 add_to_string(&string
, ", ");
351 value
= bittorrent
->incomplete
;
352 add_format_to_string(&string
,
353 n_("%u downloader", "%u downloaders", value
, term
), value
);
359 add_format_to_string(&string
, "\n%s: ", _("Upload", term
));
360 add_xnum_to_string(&string
, bittorrent
->uploaded
);
361 add_to_string(&string
, ", ");
363 add_xnum_to_string(&string
, bittorrent
->upload_progress
.current_speed
);
364 add_to_string(&string
, "/s, ");
366 add_xnum_to_string(&string
, bittorrent
->upload_progress
.average_speed
);
367 add_format_to_string(&string
, "/s %s", _("average", term
));
369 if (bittorrent
->uploaded
< bittorrent
->downloaded
) {
370 struct progress
*progress
= &bittorrent
->upload_progress
;
372 add_format_to_string(&string
, ", %s ", _("1:1 in", term
));
373 add_timeval_to_string(&string
, &progress
->estimated_time
);
378 add_format_to_string(&string
, "\n%s: ", _("Sharing", term
));
379 if (bittorrent
->downloaded
) {
380 add_format_to_string(&string
, "%.3f", bittorrent
->sharing_rate
);
382 /* Idea taken from btdownloadcurses .. 'oo' means infinite. */
383 add_to_string(&string
, "oo");
385 add_to_string(&string
, " (");
386 add_xnum_to_string(&string
, bittorrent
->uploaded
);
387 add_format_to_string(&string
, " %s / ", _("uploaded", term
));
388 add_xnum_to_string(&string
, bittorrent
->downloaded
);
389 add_format_to_string(&string
, " %s)", _("downloaded", term
));
393 add_format_to_string(&string
, "\n%s: ", _("Pieces", term
));
395 value
= bittorrent
->cache
->completed_pieces
;
396 add_format_to_string(&string
,
397 n_("%u completed", "%u completed", value
, term
), value
);
399 value
= bittorrent
->cache
->loading_pieces
;
401 add_to_string(&string
, ", ");
402 add_format_to_string(&string
,
403 n_("%u in progress", "%u in progress", value
, term
), value
);
406 if (bittorrent
->cache
->partial
)
407 value
= bittorrent
->cache
->partial_pieces
;
409 value
= bittorrent
->meta
.pieces
;
410 value
-= bittorrent
->cache
->completed_pieces
;
412 add_to_string(&string
, ", ");
413 add_format_to_string(&string
,
414 n_("%u remaining", "%u remaining", value
, term
), value
);
419 add_format_to_string(&string
, "\n%s: ", _("Statistics", term
));
421 value
= list_size(&bittorrent
->cache
->queue
);
422 add_format_to_string(&string
,
423 n_("%u in memory", "%u in memory", value
, term
), value
);
425 value
= bittorrent
->cache
->locked_pieces
;
427 add_to_string(&string
, ", ");
428 add_format_to_string(&string
,
429 n_("%u locked", "%u locked", value
, term
), value
);
432 value
= bittorrent
->cache
->rejected_pieces
;
434 add_to_string(&string
, ", ");
435 add_format_to_string(&string
,
436 n_("%u rejected", "%u rejected", value
, term
), value
);
439 value
= bittorrent
->cache
->unavailable_pieces
;
441 add_to_string(&string
, ", ");
442 add_format_to_string(&string
,
443 n_("%u unavailable", "%u unavailable", value
, term
), value
);
446 return string
.source
;
450 draw_bittorrent_piece_progress(struct download
*download
, struct terminal
*term
,
451 int x
, int y
, int width
, unsigned char *text
,
452 struct color_pair
*color
)
454 struct bittorrent_connection
*bittorrent
;
458 if (!download
->conn
|| !download
->conn
->info
)
461 bittorrent
= download
->conn
->info
;
463 /* Draw the progress meter part "[### ]" */
464 if (!text
&& width
> 2) {
466 draw_text(term
, x
++, y
, "[", 1, 0, NULL
);
467 draw_text(term
, x
+ width
, y
, "]", 1, 0, NULL
);
472 if (width
<= 0 || !bittorrent
->cache
)
475 if (!color
) color
= get_bfu_color(term
, "dialog.meter");
477 if (bittorrent
->meta
.pieces
<= width
) {
478 int chars_per_piece
= width
/ bittorrent
->meta
.pieces
;
479 int remainder
= width
% bittorrent
->meta
.pieces
;
481 for (piece
= 0; piece
< bittorrent
->meta
.pieces
; piece
++) {
484 set_box(&piecebox
, x
, y
, chars_per_piece
+ !!remainder
, 1);
486 if (bittorrent
->cache
->entries
[piece
].completed
)
487 draw_box(term
, &piecebox
, ' ', 0, color
);
489 x
+= chars_per_piece
+ !!remainder
;
490 if (remainder
> 0) remainder
--;
494 int pieces_per_char
= bittorrent
->meta
.pieces
/ width
;
495 int remainder
= bittorrent
->meta
.pieces
% width
;
496 struct color_pair inverted
;
497 uint32_t completed
= 0, remaining
= 0;
498 int steps
= pieces_per_char
+ !!remainder
;
500 inverted
.background
= color
->foreground
;
501 inverted
.foreground
= color
->background
;
503 for (piece
= 0; piece
< bittorrent
->meta
.pieces
; piece
++) {
504 if (bittorrent
->cache
->entries
[piece
].completed
)
512 assert(completed
<= pieces_per_char
+ !!remainder
);
513 assert(remaining
<= pieces_per_char
+ !!remainder
);
515 if (!remaining
) /* 100% */
516 draw_char_color(term
, x
, y
, color
);
518 else if (completed
> remaining
) /* > 50% */
519 draw_char(term
, x
, y
, BORDER_SVLINE
,
520 SCREEN_ATTR_FRAME
, color
);
522 else if (completed
) /* > 0% */
523 draw_char(term
, x
, y
, BORDER_SVLINE
,
524 SCREEN_ATTR_FRAME
, &inverted
);
527 if (remainder
> 0) remainder
--;
529 remaining
= completed
= 0;
530 steps
= pieces_per_char
+ !!remainder
;
534 if (is_in_state(download
->state
, S_RESUME
)) {
535 static unsigned char s
[] = "????"; /* Reduce or enlarge at will. */
536 unsigned int slen
= 0;
537 int max
= int_min(sizeof(s
), width
) - 1;
540 percent
= (int) ((longlong
) 100 * bittorrent
->cache
->resume_pos
541 / bittorrent
->meta
.pieces
);
543 if (ulongcat(s
, &slen
, percent
, max
, 0)) {
550 /* Draw the percentage centered in the progress meter */
551 x_start
+= (1 + width
- slen
) / 2;
553 assert(slen
<= width
);
555 draw_text(term
, x_start
, y
, s
, slen
, 0, NULL
);
560 /* ************************************************************************** */
561 /* Display Failure Reason from the tracker: */
562 /* ************************************************************************** */
565 bittorrent_message_dialog(struct session
*ses
, void *data
)
567 struct bittorrent_message
*message
= data
;
568 struct string string
;
569 unsigned char *uristring
;
571 /* Don't show error dialogs for missing CSS stylesheets */
572 if (!init_string(&string
))
575 uristring
= get_uri_string(message
->uri
, URI_PUBLIC
);
578 if (ses
->tab
->term
->utf8_cp
)
579 decode_uri(uristring
);
581 #endif /* CONFIG_UTF8 */
582 decode_uri_for_display(uristring
);
583 add_format_to_string(&string
,
584 _("Unable to retrieve %s", ses
->tab
->term
),
587 add_to_string(&string
, ":\n\n");
590 if (!is_in_state(message
->state
, S_OK
)) {
591 add_format_to_string(&string
, "%s: %s",
592 get_state_message(connection_state(S_BITTORRENT_TRACKER
),
594 get_state_message(message
->state
, ses
->tab
->term
));
596 add_to_string(&string
, message
->string
);
599 info_box(ses
->tab
->term
, MSGBOX_FREE_TEXT
,
600 N_("Error"), ALIGN_CENTER
,
603 done_bittorrent_message(message
);
607 /* ************************************************************************** */
608 /* BitTorrent download querying: */
609 /* ************************************************************************** */
612 abort_bittorrent_download_query(struct dialog_data
*dlg_data
)
614 struct bittorrent_download_info
*info
= dlg_data
->dlg
->udata
;
616 done_bittorrent_download_info(info
);
619 /** The download button handler. Basicly it redirects \<uri> to
620 * bittorrent:\<uri> and starts displaying the download.
622 * bittorrent_query_callback() passes this function as a
623 * ::widget_handler_T to add_dlg_button(). */
624 static widget_handler_status_T
625 bittorrent_download(struct dialog_data
*dlg_data
, struct widget_data
*widget_data
)
627 struct type_query
*type_query
= dlg_data
->dlg
->udata2
;
628 struct bittorrent_download_info
*info
= dlg_data
->dlg
->udata
;
629 struct file_download
*file_download
;
630 struct session
*ses
= type_query
->ses
;
631 struct string redirect
;
633 struct connection conn
;
635 if (!init_string(&redirect
))
636 return cancel_dialog(dlg_data
, widget_data
);
638 add_to_string(&redirect
, "bittorrent:");
639 add_uri_to_string(&redirect
, type_query
->uri
, URI_ORIGINAL
);
641 uri
= get_uri(redirect
.source
, 0);
643 done_string(&redirect
);
644 tp_cancel(type_query
);
647 return cancel_dialog(dlg_data
, widget_data
);
649 file_download
= init_file_download(uri
, ses
, info
->name
, -1);
653 return cancel_dialog(dlg_data
, widget_data
);
655 update_dialog_data(dlg_data
);
657 /* Put the meta info in the store. */
658 add_bittorrent_selection(file_download
->uri
, info
->selection
, info
->size
);
659 info
->selection
= NULL
;
662 /* XXX: Hackery to get the Info button installed */
663 conn
.uri
= file_download
->uri
;
664 file_download
->download
.conn
= &conn
;
666 /* Done here and not in init_file_download() so that the external
667 * handler can become initialized. */
668 display_download(ses
->tab
->term
, file_download
, ses
);
670 file_download
->download
.conn
= NULL
;
672 load_uri(file_download
->uri
, ses
->referrer
, &file_download
->download
,
673 PRI_DOWNLOAD
, CACHE_MODE_NORMAL
, file_download
->seek
);
675 return cancel_dialog(dlg_data
, widget_data
);
679 /* Show the protocol header. */
680 /* XXX: Code duplication with session/download.h */
681 static widget_handler_status_T
682 tp_show_header(struct dialog_data
*dlg_data
, struct widget_data
*widget_data
)
684 struct type_query
*type_query
= widget_data
->widget
->data
;
686 cached_header_dialog(type_query
->ses
, type_query
->cached
);
688 return EVENT_PROCESSED
;
691 /** Build a dialog querying the user on how to handle a .torrent file.
693 * query_bittorrent_dialog() passes this function as a
694 * ::bittorrent_fetch_callback_T to init_bittorrent_fetch(). */
696 bittorrent_query_callback(void *data
, struct connection_state state
,
697 struct bittorrent_const_string
*response
)
699 /* [gettext_accelerator_context(.bittorrent_query_callback)] */
700 struct type_query
*type_query
= data
;
701 struct string filename
;
704 #define BITTORRENT_QUERY_WIDGETS_COUNT 6
705 int widgets
= BITTORRENT_QUERY_WIDGETS_COUNT
;
706 struct terminal
*term
= type_query
->ses
->tab
->term
;
707 struct bittorrent_download_info
*info
;
708 struct bittorrent_meta meta
;
709 struct dialog_data
*dlg_data
;
711 struct memory_list
*ml
;
715 if (!is_in_state(state
, S_OK
))
718 /* This should never happen, since setup_download_handler() should make
719 * sure to handle application/x-bittorrent document types in the default
720 * type query handler. */
721 if (get_cmd_opt_bool("anonymous")) {
722 INTERNAL("BitTorrent downloads not allowed in anonymous mode.");
726 if (!init_string(&msg
))
729 if (init_string(&filename
)) {
730 add_mime_filename_to_string(&filename
, type_query
->uri
);
732 /* Let's make the filename pretty for display & save */
733 /* TODO: The filename can be the empty string here. See bug 396. */
736 decode_uri_string(&filename
);
738 #endif /* CONFIG_UTF8 */
739 decode_uri_string_for_display(&filename
);
742 add_format_to_string(&msg
,
743 _("What would you like to do with the file '%s'?", term
),
746 done_string(&filename
);
748 if (parse_bittorrent_metafile(&meta
, response
) != BITTORRENT_STATE_OK
) {
749 print_error_dialog(type_query
->ses
,
750 connection_state(S_BITTORRENT_METAINFO
),
751 type_query
->uri
, PRI_CANCEL
);
752 tp_cancel(type_query
);
757 files
= list_size(&meta
.files
);
759 add_format_to_string(&msg
, "\n%s:",
760 _("Information about the torrent", term
));
762 add_bittorrent_meta_to_string(&msg
, &meta
, term
, files
== 1);
764 info
= init_bittorrent_download_info(&meta
);
765 done_bittorrent_meta(&meta
);
771 dlg
= calloc_dialog(widgets
+ files
, msg
.length
+ 1);
773 done_bittorrent_download_info(info
);
778 text
= get_dialog_offset(dlg
, widgets
+ files
);
779 memcpy(text
, msg
.source
, msg
.length
+ 1);
782 dlg
->title
= _("What to do?", term
);
783 dlg
->abort
= abort_bittorrent_download_query
;
784 dlg
->layouter
= generic_dialog_layouter
;
785 dlg
->layout
.padding_top
= 1;
786 dlg
->layout
.fit_datalen
= 1;
787 dlg
->udata2
= type_query
;
790 add_dlg_text(dlg
, text
, ALIGN_LEFT
, 0);
791 dlg
->widgets
->info
.text
.is_scrollable
= 1;
794 struct string_list_item
*item
;
797 foreach (item
, info
->labels
) {
798 add_dlg_checkbox(dlg
, item
->string
.source
, &info
->selection
[index
++]);
803 selected_widget
= dlg
->number_of_widgets
;
804 add_dlg_button(dlg
, _("Down~load", term
), B_ENTER
,
805 bittorrent_download
, type_query
);
807 add_dlg_ok_button(dlg
, _("Sa~ve", term
), B_ENTER
,
808 (done_handler_T
*) tp_save
, type_query
);
810 add_dlg_ok_button(dlg
, _("~Display", term
), B_ENTER
,
811 (done_handler_T
*) tp_display
, type_query
);
813 if (type_query
->cached
&& type_query
->cached
->head
&& *type_query
->cached
->head
) {
814 add_dlg_button(dlg
, _("Show ~header", term
), B_ENTER
,
815 tp_show_header
, type_query
);
820 add_dlg_ok_button(dlg
, _("~Cancel", term
), B_ESC
,
821 (done_handler_T
*) tp_cancel
, type_query
);
823 add_dlg_end(dlg
, widgets
);
825 ml
= getml(dlg
, (void *) NULL
);
827 /* XXX: Assume that the allocated @external_handler will be
828 * freed when releasing the @type_query. */
829 done_bittorrent_download_info(info
);
834 dlg_data
= do_dialog(term
, dlg
, ml
);
836 select_widget_by_id(dlg_data
, selected_widget
);
840 query_bittorrent_dialog(struct type_query
*type_query
)
842 init_bittorrent_fetch(NULL
, type_query
->uri
,
843 bittorrent_query_callback
, type_query
, 0);