iconv: Bail out of the loop when an illegal sequence of bytes occurs.
[elinks/elinks-j605.git] / src / protocol / bittorrent / dialogs.c
blob2481581ccff5c64338de87ce901418f0c5f7fb27
1 /* BitTorrent specific dialogs */
3 #ifdef HAVE_CONFIG_H
4 #include "config.h"
5 #endif
7 #include "elinks.h"
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;
30 unsigned char *name;
31 int *selection;
32 size_t size;
35 static void
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);
41 mem_free(info);
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;
49 size_t max_label = 0;
51 info = mem_calloc(1, sizeof(*info));
52 if (!info) return NULL;
54 init_list(info->labels);
56 info->name = stracpy(meta->name);
57 if (!info->name) {
58 mem_free(info);
59 return NULL;
62 foreach (file, meta->files) {
63 struct string string;
64 int spaces;
66 if (!init_string(&string))
67 break;
69 info->size++;
71 add_xnum_to_string(&string, file->length);
73 /* 40 K file1
74 * 2300 MiB file2 */
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;
82 done_string(&string);
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);
88 return NULL;
91 info->size = 0;
93 foreach (file, meta->files) {
94 struct string_list_item *item;
95 size_t pos = 0;
97 foreach (item, info->labels)
98 if (pos++ == info->size)
99 break;
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);
111 return info;
115 /* Add information from the meta file struct to a string. */
116 static void
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: ",
126 _("Size", term));
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);
134 } else {
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]));
148 #ifdef HAVE_STRFTIME
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);
156 #endif
158 if (meta->type == BITTORRENT_MULTI_FILE) {
159 add_format_to_string(msg, "\n%s: %s",
160 _("Directory", term), meta->name);
163 if (add_files) {
164 struct bittorrent_download_info *info;
165 struct string_list_item *item;
167 info = init_bittorrent_download_info(meta);
168 if (info) {
169 add_format_to_string(msg, "\n%s:",
170 _("Files", term));
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 /* ************************************************************************** */
192 void
193 set_bittorrent_files_for_deletion(struct download *download)
195 struct bittorrent_connection *bittorrent;
197 if (!download->conn || !download->conn->info)
198 return;
200 bittorrent = download->conn->info;
201 bittorrent->cache->delete_files = 1;
204 void
205 set_bittorrent_notify_on_completion(struct download *download, struct terminal *term)
207 struct bittorrent_connection *bittorrent;
209 if (!download->conn || !download->conn->info)
210 return;
212 bittorrent = download->conn->info;
213 bittorrent->cache->notify_complete = 1;
214 bittorrent->term = term;
217 void
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);
223 if (!url) return;
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));
230 mem_free(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;
239 struct string msg;
241 if (download->conn
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. */
270 unsigned char *
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)"),
279 N_("seeding"),
281 struct bittorrent_connection *bittorrent;
282 struct bittorrent_peer_connection *peer;
283 struct string string;
284 unsigned char *msg;
285 uint32_t value;
287 if (!download->conn
288 || !download->conn->info
289 || !init_string(&string))
290 return NULL;
292 /* Get the download speed information message. */
293 msg = get_progress_msg(download->progress, term, wide, full, separator);
294 if (!msg) {
295 done_string(&string);
296 return NULL;
299 bittorrent = download->conn->info;
301 add_to_string(&string, msg);
302 mem_free(msg);
304 /* Status: */
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)",
311 _("partial", term));
313 /* Peers: */
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, ", ");
323 value = 0;
324 foreach (peer, bittorrent->peers)
325 if (peer->remote.seeder)
326 value++;
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);
337 /* Swarm info: */
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);
357 /* Upload: */
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);
376 /* Sharing: */
378 add_format_to_string(&string, "\n%s: ", _("Sharing", term));
379 if (bittorrent->downloaded) {
380 add_format_to_string(&string, "%.3f", bittorrent->sharing_rate);
381 } else {
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));
391 /* Pieces: */
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;
400 if (value) {
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;
408 else
409 value = bittorrent->meta.pieces;
410 value -= bittorrent->cache->completed_pieces;
411 if (value) {
412 add_to_string(&string, ", ");
413 add_format_to_string(&string,
414 n_("%u remaining", "%u remaining", value, term), value);
417 /* Statistics: */
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;
426 if (value) {
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;
433 if (value) {
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;
440 if (value) {
441 add_to_string(&string, ", ");
442 add_format_to_string(&string,
443 n_("%u unavailable", "%u unavailable", value, term), value);
446 return string.source;
449 void
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;
455 uint32_t piece;
456 int x_start;
458 if (!download->conn || !download->conn->info)
459 return;
461 bittorrent = download->conn->info;
463 /* Draw the progress meter part "[### ]" */
464 if (!text && width > 2) {
465 width -= 2;
466 draw_text(term, x++, y, "[", 1, 0, NULL);
467 draw_text(term, x + width, y, "]", 1, 0, NULL);
470 x_start = x;
472 if (width <= 0 || !bittorrent->cache)
473 return;
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++) {
482 struct box piecebox;
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--;
493 } else {
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)
505 completed++;
506 else
507 remaining++;
509 if (--steps > 0)
510 continue;
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);
526 x++;
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;
538 int percent = 0;
540 percent = (int) ((longlong) 100 * bittorrent->cache->resume_pos
541 / bittorrent->meta.pieces);
543 if (ulongcat(s, &slen, percent, max, 0)) {
544 s[0] = '?';
545 slen = 1;
548 s[slen++] = '%';
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 /* ************************************************************************** */
564 void
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))
573 return;
575 uristring = get_uri_string(message->uri, URI_PUBLIC);
576 if (uristring) {
577 #ifdef CONFIG_UTF8
578 if (ses->tab->term->utf8_cp)
579 decode_uri(uristring);
580 else
581 #endif /* CONFIG_UTF8 */
582 decode_uri_for_display(uristring);
583 add_format_to_string(&string,
584 _("Unable to retrieve %s", ses->tab->term),
585 uristring);
586 mem_free(uristring);
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),
593 ses->tab->term),
594 get_state_message(message->state, ses->tab->term));
595 } else {
596 add_to_string(&string, message->string);
599 info_box(ses->tab->term, MSGBOX_FREE_TEXT,
600 N_("Error"), ALIGN_CENTER,
601 string.source);
603 done_bittorrent_message(message);
607 /* ************************************************************************** */
608 /* BitTorrent download querying: */
609 /* ************************************************************************** */
611 static void
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;
632 struct uri *uri;
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);
646 if (!uri)
647 return cancel_dialog(dlg_data, widget_data);
649 file_download = init_file_download(uri, ses, info->name, -1);
650 done_uri(uri);
652 if (!file_download)
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;
660 info->name = 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(). */
695 static void
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;
702 unsigned char *text;
703 struct dialog *dlg;
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;
710 int selected_widget;
711 struct memory_list *ml;
712 struct string msg;
713 int files;
715 if (!is_in_state(state, S_OK))
716 return;
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.");
723 return;
726 if (!init_string(&msg))
727 return;
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. */
734 #ifdef CONFIG_UTF8
735 if (term->utf8_cp)
736 decode_uri_string(&filename);
737 else
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),
744 filename.source);
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);
753 done_string(&msg);
754 return;
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);
766 if (!info) {
767 done_string(&msg);
768 return;
771 dlg = calloc_dialog(widgets + files, msg.length + 1);
772 if (!dlg) {
773 done_bittorrent_download_info(info);
774 done_string(&msg);
775 return;
778 text = get_dialog_offset(dlg, widgets + files);
779 memcpy(text, msg.source, msg.length + 1);
780 done_string(&msg);
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;
788 dlg->udata = info;
790 add_dlg_text(dlg, text, ALIGN_LEFT, 0);
791 dlg->widgets->info.text.is_scrollable = 1;
793 if (files > 1) {
794 struct string_list_item *item;
795 size_t index = 0;
797 foreach (item, info->labels) {
798 add_dlg_checkbox(dlg, item->string.source, &info->selection[index++]);
799 widgets++;
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);
816 } else {
817 widgets--;
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);
826 if (!ml) {
827 /* XXX: Assume that the allocated @external_handler will be
828 * freed when releasing the @type_query. */
829 done_bittorrent_download_info(info);
830 mem_free(dlg);
831 return;
834 dlg_data = do_dialog(term, dlg, ml);
835 if (dlg_data)
836 select_widget_by_id(dlg_data, selected_widget);
839 void
840 query_bittorrent_dialog(struct type_query *type_query)
842 init_bittorrent_fetch(NULL, type_query->uri,
843 bittorrent_query_callback, type_query, 0);