1
0
mirror of https://github.com/rkd77/elinks.git synced 2025-02-02 15:09:23 -05:00
2022-02-17 21:14:36 +01:00

851 lines
22 KiB
C

/* BitTorrent specific dialogs */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <sys/types.h>
#ifdef HAVE_SYS_SOCKET_H
#include <sys/socket.h> /* OS/2 needs this after sys/types.h */
#endif
#include "elinks.h"
#include "dialogs/document.h"
#include "dialogs/download.h"
#include "dialogs/progress.h"
#include "intl/libintl.h"
#include "mime/mime.h"
#include "network/connection.h"
#include "network/state.h"
#include "protocol/bittorrent/bencoding.h"
#include "protocol/bittorrent/bittorrent.h"
#include "protocol/bittorrent/common.h"
#include "protocol/bittorrent/dialogs.h"
#include "protocol/bittorrent/piececache.h"
#include "protocol/uri.h"
#include "session/download.h"
#include "session/session.h"
#include "terminal/draw.h"
#include "util/conv.h"
#include "util/string.h"
struct bittorrent_download_info {
LIST_OF(struct string_list_item) labels;
char *name;
int *selection;
size_t size;
};
static void
done_bittorrent_download_info(struct bittorrent_download_info *info)
{
free_string_list(&info->labels);
mem_free_if(info->selection);
mem_free_if(info->name);
mem_free(info);
}
static struct bittorrent_download_info *
init_bittorrent_download_info(struct bittorrent_meta *meta)
{
struct bittorrent_download_info *info;
struct bittorrent_file *file;
size_t max_label = 0;
info = (struct bittorrent_download_info *)mem_calloc(1, sizeof(*info));
if (!info) return NULL;
init_list(info->labels);
info->name = stracpy(meta->name);
if (!info->name) {
mem_free(info);
return NULL;
}
foreach (file, meta->files) {
struct string string;
int spaces;
if (!init_string(&string))
break;
info->size++;
add_xnum_to_string(&string, file->length);
/* 40 K file1
* 2300 MiB file2 */
spaces = string.length - strcspn(string.source, " ") - 1;
add_xchar_to_string(&string, ' ', int_max(4 - spaces, 1));
add_to_string_list(&info->labels, string.source, string.length);
if (string.length > max_label)
max_label = string.length;
done_string(&string);
}
info->selection = (int *)mem_calloc(info->size, sizeof(*info->selection));
if (!info->selection || info->size != list_size(&meta->files)) {
done_bittorrent_download_info(info);
return NULL;
}
info->size = 0;
foreach (file, meta->files) {
struct string_list_item *item;
size_t pos = 0;
foreach (item, info->labels)
if (pos++ == info->size)
break;
info->selection[info->size++] = 1;
pos = max_label - item->string.length;
add_to_string(&item->string, file->name);
for (; pos > 0; pos--) {
insert_in_string(&item->string.source, 0, " ", 1);
}
}
return info;
}
/* Add information from the meta file struct to a string. */
static void
add_bittorrent_meta_to_string(struct string *msg, struct bittorrent_meta *meta,
struct terminal *term, int add_files)
{
if (meta->malicious_paths) {
add_format_to_string(msg, "%s\n\n",
_("Warning: potential malicious path detected", term));
}
add_format_to_string(msg, "\n%s: ",
_("Size", term));
add_xnum_to_string(msg,
(off_t) (meta->pieces - 1) * meta->piece_length
+ meta->last_piece_length);
if (meta->last_piece_length == meta->piece_length) {
add_format_to_string(msg, " (%ld * %ld)",
meta->pieces, meta->piece_length);
} else {
add_format_to_string(msg, " (%ld * %ld + %ld)",
meta->pieces - 1, meta->piece_length,
meta->last_piece_length);
}
add_format_to_string(msg, "\n%s: %s",
_("Info hash", term),
get_hexed_bittorrent_id(meta->info_hash));
add_format_to_string(msg, "\n%s: %s",
_("Announce URI", term),
struri(meta->tracker_uris.uris[0]));
#ifdef HAVE_STRFTIME
if (meta->creation_date) {
add_format_to_string(msg, "\n%s: ",
_("Creation date", term));
add_date_to_string(msg,
get_opt_str("ui.date_format", NULL),
&meta->creation_date);
}
#endif
if (meta->type == BITTORRENT_MULTI_FILE) {
add_format_to_string(msg, "\n%s: %s",
_("Directory", term), meta->name);
}
if (add_files) {
struct bittorrent_download_info *info;
struct string_list_item *item;
info = init_bittorrent_download_info(meta);
if (info) {
add_format_to_string(msg, "\n%s:",
_("Files", term));
foreach (item, info->labels) {
add_format_to_string(msg, "\n %s",
item->string.source);
}
done_bittorrent_download_info(info);
}
}
if (meta->comment && *meta->comment) {
add_format_to_string(msg, "\n%s:\n %s",
_("Comment", term), meta->comment);
}
}
/* ************************************************************************** */
/* Download dialog button hooks and helpers: */
/* ************************************************************************** */
void
set_bittorrent_files_for_deletion(struct download *download)
{
struct bittorrent_connection *bittorrent;
if (!download->conn || !download->conn->info)
return;
bittorrent = (struct bittorrent_connection *)download->conn->info;
bittorrent->cache->delete_files = 1;
}
void
set_bittorrent_notify_on_completion(struct download *download, struct terminal *term)
{
struct bittorrent_connection *bittorrent;
if (!download->conn || !download->conn->info)
return;
bittorrent = (struct bittorrent_connection *)download->conn->info;
bittorrent->cache->notify_complete = 1;
bittorrent->term = term;
}
void
notify_bittorrent_download_complete(struct bittorrent_connection *bittorrent)
{
struct connection *conn = bittorrent->conn;
char *url = get_uri_string(conn->uri, URI_PUBLIC);
if (!url) return;
assert(bittorrent->term);
info_box(bittorrent->term, MSGBOX_FREE_TEXT,
N_("Download"), ALIGN_CENTER,
msg_text(bittorrent->term, N_("Download complete:\n%s"), url));
mem_free(url);
}
/* Handler for the Info-button available in the download dialog. */
widget_handler_status_T
dlg_show_bittorrent_info(struct dialog_data *dlg_data, struct widget_data *widget_data)
{
struct file_download *file_download = (struct file_download *)dlg_data->dlg->udata;
struct download *download = &file_download->download;
struct string msg;
if (download->conn
&& download->conn->info
&& init_string(&msg)) {
struct terminal *term = file_download->term;
struct bittorrent_connection *bittorrent;
msgbox_flags_T flags = MSGBOX_FREE_TEXT;
bittorrent = (struct bittorrent_connection *)download->conn->info;
add_bittorrent_meta_to_string(&msg, &bittorrent->meta, term, 1);
if (list_size(&bittorrent->meta.files) > 1)
flags |= MSGBOX_SCROLLABLE;
info_box(term, flags,
N_("Download info"), ALIGN_LEFT, msg.source);
}
return EVENT_PROCESSED;
}
/* ************************************************************************** */
/* Download status message creation: */
/* ************************************************************************** */
/* Compose and return the status message about current download speed and
* BitTorrent swarm info. It is called fairly often so most values used in here
* should be easily accessible. */
char *
get_bittorrent_message(struct download *download, struct terminal *term,
int wide, int full, const char *separator)
{
/* Cooresponds to the connection mode enum. */
static char *modes_text[] = {
N_("downloading (random)"),
N_("downloading (rarest first)"),
N_("downloading (end game)"),
N_("seeding"),
};
struct bittorrent_connection *bittorrent;
struct bittorrent_peer_connection *peer;
struct string string;
char *msg;
uint32_t value;
if (!download->conn
|| !download->conn->info
|| !init_string(&string))
return NULL;
/* Get the download speed information message. */
msg = get_progress_msg(download->progress, term, wide, full, separator);
if (!msg) {
done_string(&string);
return NULL;
}
bittorrent = (struct bittorrent_connection *)download->conn->info;
add_to_string(&string, msg);
mem_free(msg);
/* Status: */
add_format_to_string(&string, "\n\n%s: %s",
_("Status", term), _(modes_text[bittorrent->mode], term));
if (bittorrent->cache->partial)
add_format_to_string(&string, " (%s)",
_("partial", term));
/* Peers: */
add_format_to_string(&string, "\n%s: ", _("Peers", term));
value = list_size(&bittorrent->peers);
add_format_to_string(&string,
n_("%u connection", "%u connections", value, term), value);
add_to_string(&string, ", ");
value = 0;
foreach (peer, bittorrent->peers)
if (peer->remote.seeder)
value++;
add_format_to_string(&string,
n_("%u seeder", "%u seeders", value, term), value);
add_to_string(&string, ", ");
value = list_size(&bittorrent->peer_pool);
add_format_to_string(&string,
n_("%u available", "%u available", value, term), value);
/* Swarm info: */
if (bittorrent->complete > 0 || bittorrent->incomplete > 0) {
add_format_to_string(&string, "\n%s: ", _("Swarm info", term));
if (bittorrent->complete > 0) {
value = bittorrent->complete;
add_format_to_string(&string,
n_("%u seeder", "%u seeders", value, term), value);
}
if (bittorrent->incomplete > 0) {
if (bittorrent->complete > 0)
add_to_string(&string, ", ");
value = bittorrent->incomplete;
add_format_to_string(&string,
n_("%u downloader", "%u downloaders", value, term), value);
}
}
/* Upload: */
add_format_to_string(&string, "\n%s: ", _("Upload", term));
add_xnum_to_string(&string, bittorrent->uploaded);
add_to_string(&string, ", ");
add_xnum_to_string(&string, bittorrent->upload_progress.current_speed);
add_to_string(&string, "/s, ");
add_xnum_to_string(&string, bittorrent->upload_progress.average_speed);
add_format_to_string(&string, "/s %s", _("average", term));
if (bittorrent->uploaded < bittorrent->downloaded) {
struct progress *progress = &bittorrent->upload_progress;
add_format_to_string(&string, ", %s ", _("1:1 in", term));
add_timeval_to_string(&string, &progress->estimated_time);
}
/* Sharing: */
add_format_to_string(&string, "\n%s: ", _("Sharing", term));
if (bittorrent->downloaded) {
add_format_to_string(&string, "%.3f", bittorrent->sharing_rate);
} else {
/* Idea taken from btdownloadcurses .. 'oo' means infinite. */
add_to_string(&string, "oo");
}
add_to_string(&string, " (");
add_xnum_to_string(&string, bittorrent->uploaded);
add_format_to_string(&string, " %s / ", _("uploaded", term));
add_xnum_to_string(&string, bittorrent->downloaded);
add_format_to_string(&string, " %s)", _("downloaded", term));
/* Pieces: */
add_format_to_string(&string, "\n%s: ", _("Pieces", term));
value = bittorrent->cache->completed_pieces;
add_format_to_string(&string,
n_("%u completed", "%u completed", value, term), value);
value = bittorrent->cache->loading_pieces;
if (value) {
add_to_string(&string, ", ");
add_format_to_string(&string,
n_("%u in progress", "%u in progress", value, term), value);
}
if (bittorrent->cache->partial)
value = bittorrent->cache->partial_pieces;
else
value = bittorrent->meta.pieces;
value -= bittorrent->cache->completed_pieces;
if (value) {
add_to_string(&string, ", ");
add_format_to_string(&string,
n_("%u remaining", "%u remaining", value, term), value);
}
/* Statistics: */
add_format_to_string(&string, "\n%s: ", _("Statistics", term));
value = list_size(&bittorrent->cache->queue);
add_format_to_string(&string,
n_("%u in memory", "%u in memory", value, term), value);
value = bittorrent->cache->locked_pieces;
if (value) {
add_to_string(&string, ", ");
add_format_to_string(&string,
n_("%u locked", "%u locked", value, term), value);
}
value = bittorrent->cache->rejected_pieces;
if (value) {
add_to_string(&string, ", ");
add_format_to_string(&string,
n_("%u rejected", "%u rejected", value, term), value);
}
value = bittorrent->cache->unavailable_pieces;
if (value) {
add_to_string(&string, ", ");
add_format_to_string(&string,
n_("%u unavailable", "%u unavailable", value, term), value);
}
return string.source;
}
void
draw_bittorrent_piece_progress(struct download *download, struct terminal *term,
int x, int y, int width, char *text,
struct color_pair *color)
{
struct bittorrent_connection *bittorrent;
uint32_t piece;
int x_start;
if (!download->conn || !download->conn->info)
return;
bittorrent = (struct bittorrent_connection *)download->conn->info;
/* Draw the progress meter part "[### ]" */
if (!text && width > 2) {
width -= 2;
draw_text(term, x++, y, "[", 1, 0, NULL);
draw_text(term, x + width, y, "]", 1, 0, NULL);
}
x_start = x;
if (width <= 0 || !bittorrent->cache)
return;
if (!color) color = get_bfu_color(term, "dialog.meter");
if (bittorrent->meta.pieces <= width) {
int chars_per_piece = width / bittorrent->meta.pieces;
int remainder = width % bittorrent->meta.pieces;
for (piece = 0; piece < bittorrent->meta.pieces; piece++) {
struct el_box piecebox;
set_box(&piecebox, x, y, chars_per_piece + !!remainder, 1);
if (bittorrent->cache->entries[piece].completed)
draw_box(term, &piecebox, ' ', 0, color);
x += chars_per_piece + !!remainder;
if (remainder > 0) remainder--;
}
} else {
int pieces_per_char = bittorrent->meta.pieces / width;
int remainder = bittorrent->meta.pieces % width;
struct color_pair inverted;
uint32_t completed = 0, remaining = 0;
int steps = pieces_per_char + !!remainder;
inverted.background = color->foreground;
inverted.foreground = color->background;
for (piece = 0; piece < bittorrent->meta.pieces; piece++) {
if (bittorrent->cache->entries[piece].completed)
completed++;
else
remaining++;
if (--steps > 0)
continue;
assert(completed <= pieces_per_char + !!remainder);
assert(remaining <= pieces_per_char + !!remainder);
if (!remaining) /* 100% */
draw_char_color(term, x, y, color);
else if (completed > remaining) /* > 50% */
draw_char(term, x, y, BORDER_SVLINE,
SCREEN_ATTR_FRAME, color);
else if (completed) /* > 0% */
draw_char(term, x, y, BORDER_SVLINE,
SCREEN_ATTR_FRAME, &inverted);
x++;
if (remainder > 0) remainder--;
remaining = completed = 0;
steps = pieces_per_char + !!remainder;
}
}
if (is_in_state(download->state, S_RESUME)) {
static char s[] = "????"; /* Reduce or enlarge at will. */
unsigned int slen = 0;
int max = int_min(sizeof(s), width) - 1;
int percent = 0;
percent = (int) ((longlong) 100 * bittorrent->cache->resume_pos
/ bittorrent->meta.pieces);
if (ulongcat(s, &slen, percent, max, 0)) {
s[0] = '?';
slen = 1;
}
s[slen++] = '%';
/* Draw the percentage centered in the progress meter */
x_start += (1 + width - slen) / 2;
assert(slen <= width);
draw_text(term, x_start, y, s, slen, 0, NULL);
}
}
/* ************************************************************************** */
/* Display Failure Reason from the tracker: */
/* ************************************************************************** */
void
bittorrent_message_dialog(struct session *ses, void *data)
{
struct bittorrent_message *message = (struct bittorrent_message *)data;
struct string string;
char *uristring;
/* Don't show error dialogs for missing CSS stylesheets */
if (!init_string(&string))
return;
uristring = get_uri_string(message->uri, URI_PUBLIC);
if (uristring) {
#ifdef CONFIG_UTF8
if (ses->tab->term->utf8_cp)
decode_uri(uristring);
else
#endif /* CONFIG_UTF8 */
decode_uri_for_display(uristring);
add_format_to_string(&string,
_("Unable to retrieve %s", ses->tab->term),
uristring);
mem_free(uristring);
add_to_string(&string, ":\n\n");
}
if (!is_in_state(message->state, S_OK)) {
add_format_to_string(&string, "%s: %s",
get_state_message(connection_state(S_BITTORRENT_TRACKER),
ses->tab->term),
get_state_message(message->state, ses->tab->term));
} else {
add_to_string(&string, message->string);
}
info_box(ses->tab->term, MSGBOX_FREE_TEXT,
N_("Error"), ALIGN_CENTER,
string.source);
done_bittorrent_message(message);
}
/* ************************************************************************** */
/* BitTorrent download querying: */
/* ************************************************************************** */
static void
abort_bittorrent_download_query(struct dialog_data *dlg_data)
{
struct bittorrent_download_info *info = (struct bittorrent_download_info *)dlg_data->dlg->udata;
done_bittorrent_download_info(info);
}
/** The download button handler. Basicly it redirects \<uri> to
* bittorrent:\<uri> and starts displaying the download.
*
* bittorrent_query_callback() passes this function as a
* ::widget_handler_T to add_dlg_button(). */
static widget_handler_status_T
bittorrent_download(struct dialog_data *dlg_data, struct widget_data *widget_data)
{
struct type_query *type_query = (struct type_query *)dlg_data->dlg->udata2;
struct bittorrent_download_info *info = (struct bittorrent_download_info *)dlg_data->dlg->udata;
struct file_download *file_download;
struct session *ses = type_query->ses;
struct string redirect;
struct uri *uri;
struct connection conn;
if (!init_string(&redirect))
return cancel_dialog(dlg_data, widget_data);
add_to_string(&redirect, "bittorrent:");
add_uri_to_string(&redirect, type_query->uri, URI_ORIGINAL);
uri = get_uri(redirect.source, URI_NONE);
done_string(&redirect);
tp_cancel(type_query);
if (!uri)
return cancel_dialog(dlg_data, widget_data);
file_download = init_file_download(uri, ses, info->name, -1);
done_uri(uri);
if (!file_download)
return cancel_dialog(dlg_data, widget_data);
update_dialog_data(dlg_data);
/* Put the meta info in the store. */
add_bittorrent_selection(file_download->uri, info->selection, info->size);
info->selection = NULL;
info->name = NULL;
/* XXX: Hackery to get the Info button installed */
conn.uri = file_download->uri;
file_download->download.conn = &conn;
/* Done here and not in init_file_download() so that the external
* handler can become initialized. */
display_download(ses->tab->term, file_download, ses);
file_download->download.conn = NULL;
load_uri(file_download->uri, ses->referrer, &file_download->download,
PRI_DOWNLOAD, CACHE_MODE_NORMAL, file_download->seek);
return cancel_dialog(dlg_data, widget_data);
}
/* Show the protocol header. */
/* XXX: Code duplication with session/download.h */
static widget_handler_status_T
tp_show_header(struct dialog_data *dlg_data, struct widget_data *widget_data)
{
struct type_query *type_query = (struct type_query *)widget_data->widget->data;
cached_header_dialog(type_query->ses, type_query->cached);
return EVENT_PROCESSED;
}
/** Build a dialog querying the user on how to handle a .torrent file.
*
* query_bittorrent_dialog() passes this function as a
* ::bittorrent_fetch_callback_T to init_bittorrent_fetch(). */
static void
bittorrent_query_callback(void *data, struct connection_state state,
struct bittorrent_const_string *response)
{
/* [gettext_accelerator_context(.bittorrent_query_callback)] */
struct type_query *type_query = (struct type_query *)data;
struct string filename;
char *text;
struct dialog *dlg;
#define BITTORRENT_QUERY_WIDGETS_COUNT 6
int widgets = BITTORRENT_QUERY_WIDGETS_COUNT;
struct terminal *term = type_query->ses->tab->term;
struct bittorrent_download_info *info;
struct bittorrent_meta meta;
struct dialog_data *dlg_data;
int selected_widget;
struct memory_list *ml;
struct string msg;
int files;
if (!is_in_state(state, S_OK))
return;
/* This should never happen, since setup_download_handler() should make
* sure to handle application/x-bittorrent document types in the default
* type query handler. */
if (get_cmd_opt_bool("anonymous")) {
INTERNAL("BitTorrent downloads not allowed in anonymous mode.");
return;
}
if (!init_string(&msg))
return;
if (init_string(&filename)) {
add_mime_filename_to_string(&filename, type_query->uri);
/* Let's make the filename pretty for display & save */
/* TODO: The filename can be the empty string here. See bug 396. */
#ifdef CONFIG_UTF8
if (term->utf8_cp)
decode_uri_string(&filename);
else
#endif /* CONFIG_UTF8 */
decode_uri_string_for_display(&filename);
}
add_format_to_string(&msg,
_("What would you like to do with the file '%s'?", term),
filename.source);
done_string(&filename);
if (parse_bittorrent_metafile(&meta, response) != BITTORRENT_STATE_OK) {
print_error_dialog(type_query->ses,
connection_state(S_BITTORRENT_METAINFO),
type_query->uri, PRI_CANCEL);
tp_cancel(type_query);
done_string(&msg);
return;
}
files = list_size(&meta.files);
add_format_to_string(&msg, "\n%s:",
_("Information about the torrent", term));
add_bittorrent_meta_to_string(&msg, &meta, term, files == 1);
info = init_bittorrent_download_info(&meta);
done_bittorrent_meta(&meta);
if (!info) {
done_string(&msg);
return;
}
dlg = calloc_dialog(widgets + files, msg.length + 1);
if (!dlg) {
done_bittorrent_download_info(info);
done_string(&msg);
return;
}
text = get_dialog_offset(dlg, widgets + files);
memcpy(text, msg.source, msg.length + 1);
done_string(&msg);
dlg->title = _("What to do?", term);
dlg->abort = abort_bittorrent_download_query;
dlg->layouter = generic_dialog_layouter;
dlg->layout.padding_top = 1;
dlg->layout.fit_datalen = 1;
dlg->udata2 = type_query;
dlg->udata = info;
add_dlg_text(dlg, text, ALIGN_LEFT, 0);
dlg->widgets->info.text.is_scrollable = 1;
if (files > 1) {
struct string_list_item *item;
size_t index = 0;
foreach (item, info->labels) {
add_dlg_checkbox(dlg, item->string.source, &info->selection[index++]);
widgets++;
}
}
selected_widget = dlg->number_of_widgets;
add_dlg_button(dlg, _("Down~load", term), B_ENTER,
bittorrent_download, type_query);
add_dlg_ok_button(dlg, _("Sa~ve", term), B_ENTER,
(done_handler_T *) tp_save, type_query);
add_dlg_ok_button(dlg, _("~Display", term), B_ENTER,
(done_handler_T *) tp_display, type_query);
if (type_query->cached && type_query->cached->head && *type_query->cached->head) {
add_dlg_button(dlg, _("Show ~header", term), B_ENTER,
tp_show_header, type_query);
} else {
widgets--;
}
add_dlg_ok_button(dlg, _("~Cancel", term), B_ESC,
(done_handler_T *) tp_cancel, type_query);
add_dlg_end(dlg, widgets);
ml = getml(dlg, (void *) NULL);
if (!ml) {
/* XXX: Assume that the allocated @external_handler will be
* freed when releasing the @type_query. */
done_bittorrent_download_info(info);
mem_free(dlg);
return;
}
dlg_data = do_dialog(term, dlg, ml);
if (dlg_data)
select_widget_by_id(dlg_data, selected_widget);
}
void
query_bittorrent_dialog(struct type_query *type_query)
{
init_bittorrent_fetch(NULL, type_query->uri,
bittorrent_query_callback, type_query, 0);
}