📄 download.c
字号:
/* Downloads managment *//* $Id: download.c,v 1.338.2.9 2005/05/13 20:06:01 jonas Exp $ */#ifdef HAVE_CONFIG_H#include "config.h"#endif#include <errno.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#ifdef HAVE_SYS_CYGWIN_H#include <sys/cygwin.h>#endif#include <sys/types.h>#ifdef HAVE_FCNTL_H#include <fcntl.h> /* OS/2 needs this after sys/types.h */#endif#include <sys/stat.h>#ifdef HAVE_UNISTD_H#include <unistd.h>#endif#include <utime.h>#include "elinks.h"#include "bfu/dialog.h"#include "cache/cache.h"#include "config/options.h"#include "dialogs/document.h"#include "dialogs/download.h"#include "dialogs/menu.h"#include "intl/gettext/libintl.h"#include "mime/mime.h"#include "osdep/osdep.h"#include "protocol/date.h"#include "protocol/protocol.h"#include "protocol/uri.h"#include "sched/connection.h"#include "sched/download.h"#include "sched/error.h"#include "sched/history.h"#include "sched/location.h"#include "sched/session.h"#include "sched/task.h"#include "terminal/draw.h"#include "terminal/screen.h"#include "terminal/terminal.h"#include "util/conv.h"#include "util/error.h"#include "util/file.h"#include "util/lists.h"#include "util/memlist.h"#include "util/memory.h"#include "util/object.h"#include "util/string.h"#include "util/ttime.h"/* TODO: tp_*() should be in separate file, I guess? --pasky */INIT_LIST_HEAD(downloads);intare_there_downloads(void){ struct file_download *file_download; foreach (file_download, downloads) if (!file_download->external_handler) return 1; return 0;}static void download_data(struct download *download, struct file_download *file_download);static struct file_download *init_file_download(struct uri *uri, struct session *ses, unsigned char *file, int fd){ struct file_download *file_download; file_download = mem_calloc(1, sizeof(*file_download)); if (!file_download) return NULL; /* Actually we could allow fragments in the URI and just change all the * places that compares and shows the URI, but for now it is much * easier this way. */ file_download->uri = get_composed_uri(uri, URI_BASE); if (!file_download->uri) { mem_free(file_download); return NULL; } file_download->box_item = add_listbox_leaf(&download_browser, NULL, file_download); file_download->file = file; file_download->handle = fd; file_download->download.callback = (void (*)(struct download *, void *)) download_data; file_download->download.data = file_download; file_download->ses = ses; /* The tab may be closed, but we will still want to ie. open the * handler on that terminal. */ file_download->term = ses->tab->term; object_nolock(file_download, "file_download"); /* Debugging purpose. */ add_to_list(downloads, file_download); return file_download;}voidabort_download(struct file_download *file_download){#if 0 /* When hacking to cleanup the download code, remove lots of duplicated * code and implement stuff from bug 435 we should reintroduce this * assertion. Currently it will trigger often and shows that the * download dialog code potentially could access free()d memory. */ assert(!is_object_used(file_download));#endif if (file_download->box_item) done_listbox_item(&download_browser, file_download->box_item); if (file_download->dlg_data) cancel_dialog(file_download->dlg_data, NULL); if (is_in_progress_state(file_download->download.state)) change_connection(&file_download->download, NULL, PRI_CANCEL, file_download->stop); if (file_download->uri) done_uri(file_download->uri); if (file_download->handle != -1) { prealloc_truncate(file_download->handle, file_download->last_pos); close(file_download->handle); } mem_free_if(file_download->external_handler); if (file_download->file) { if (file_download->delete) unlink(file_download->file); mem_free(file_download->file); } del_from_list(file_download); mem_free(file_download);}static voidkill_downloads_to_file(unsigned char *file){ struct file_download *file_download; foreach (file_download, downloads) { if (strcmp(file_download->file, file)) continue; file_download = file_download->prev; abort_download(file_download->next); }}voidabort_all_downloads(void){ while (!list_empty(downloads)) abort_download(downloads.next);}voiddestroy_downloads(struct session *ses){ struct file_download *file_download, *next; struct session *s; /* We are supposed to blat all downloads to external handlers belonging * to @ses, but we will refuse to do so if there is another session * bound to this terminal. That looks like the reasonable thing to do, * fulfilling the principle of least astonishment. */ foreach (s, sessions) { if (s == ses || s->tab->term != ses->tab->term) continue; foreach (file_download, downloads) { if (file_download->ses != ses) continue; file_download->ses = s; } return; } foreachsafe (file_download, next, downloads) { if (file_download->ses != ses) continue; if (!file_download->external_handler) { file_download->ses = NULL; continue; } abort_download(file_download); }}static voiddownload_error_dialog(struct file_download *file_download, int saved_errno){ unsigned char *emsg = (unsigned char *) strerror(saved_errno); struct session *ses = file_download->ses; struct terminal *term = file_download->term; if (!ses) return; info_box(term, MSGBOX_FREE_TEXT, N_("Download error"), ALIGN_CENTER, msg_text(term, N_("Could not create file '%s':\n%s"), file_download->file, emsg));}static intwrite_cache_entry_to_file(struct cache_entry *cached, struct file_download *file_download){ struct fragment *frag; if (file_download->download.progress && file_download->download.progress->seek) { file_download->last_pos = file_download->download.progress->seek; file_download->download.progress->seek = 0; /* This is exclusive with the prealloc, thus we can perform * this in front of that thing safely. */ if (lseek(file_download->handle, file_download->last_pos, SEEK_SET) < 0) { download_error_dialog(file_download, errno); return 0; } } foreach (frag, cached->frag) { int remain = file_download->last_pos - frag->offset; int *h = &file_download->handle; int w; if (remain < 0 || frag->length <= remain) continue;#ifdef USE_OPEN_PREALLOC if (!file_download->last_pos && (!file_download->download.prg || file_download->download.progress->size > 0)) { close(*h); *h = open_prealloc(file_download->file, O_CREAT|O_WRONLY|O_TRUNC, 0666, file_download->download.prg ? file_download->download.progress->size : cached->length); if (*h == -1) { download_error_dialog(file_download, errno); return 0; } set_bin(*h); }#endif w = safe_write(*h, frag->data + remain, frag->length - remain); if (w == -1) { download_error_dialog(file_download, errno); return 0; } file_download->last_pos += w; } return 1;}static voidabort_download_and_beep(struct file_download *file_download, struct terminal *term){ if (term && get_opt_int("document.download.notify_bell") + file_download->notify >= 2) { beep_terminal(term); } abort_download(file_download);}static voiddownload_data_store(struct download *download, struct file_download *file_download){ struct terminal *term = file_download->term; if (!term) { /* No term here, so no beep. --Zas */ abort_download(file_download); return; } if (is_in_progress_state(download->state)) { if (file_download->dlg_data) redraw_dialog(file_download->dlg_data, 1); return; } if (download->state != S_OK) { unsigned char *errmsg = get_err_msg(download->state, term); unsigned char *url = get_uri_string(file_download->uri, URI_PUBLIC); if (url) { info_box(term, MSGBOX_FREE_TEXT, N_("Download error"), ALIGN_CENTER, msg_text(term, N_("Error downloading %s:\n\n%s"), url, errmsg)); mem_free(url); } abort_download_and_beep(file_download, term); return; } if (file_download->external_handler) { prealloc_truncate(file_download->handle, file_download->last_pos); close(file_download->handle); file_download->handle = -1; exec_on_terminal(term, file_download->external_handler, file_download->file, !!file_download->external_handler_flags); file_download->delete = 0; abort_download_and_beep(file_download, term); return; } if (file_download->notify) { unsigned char *url = get_uri_string(file_download->uri, URI_PUBLIC); /* This is apparently a little racy. Deleting the box item will * update the download browser _after_ the notification dialog * has been drawn whereby it will be hidden. This should make * the download browser update before launcing any * notification. */ if (file_download->box_item) { done_listbox_item(&download_browser, file_download->box_item); file_download->box_item = NULL; } if (url) { info_box(term, MSGBOX_FREE_TEXT, N_("Download"), ALIGN_CENTER, msg_text(term, N_("Download complete:\n%s"), url)); mem_free(url); } } if (file_download->remotetime && get_opt_bool("document.download.set_original_time")) { struct utimbuf foo; foo.actime = foo.modtime = file_download->remotetime; utime(file_download->file, &foo); } abort_download_and_beep(file_download, term);}static voiddownload_data(struct download *download, struct file_download *file_download){ struct cache_entry *cached = download->cached; if (!cached || is_in_queued_state(download->state)) { download_data_store(download, file_download); return; } if (cached->last_modified) file_download->remotetime = parse_date(&cached->last_modified, NULL, 0, 1); if (cached->redirect && file_download->redirect_cnt++ < MAX_REDIRECTS) { if (is_in_progress_state(download->state)) change_connection(&file_download->download, NULL, PRI_CANCEL, 0); assertm(compare_uri(cached->uri, file_download->uri, 0), "Redirecting using bad base URI"); done_uri(file_download->uri); file_download->uri = get_uri_reference(cached->redirect); file_download->download.state = S_WAIT_REDIR; if (file_download->dlg_data) redraw_dialog(file_download->dlg_data, 1); load_uri(file_download->uri, cached->uri, &file_download->download, PRI_DOWNLOAD, CACHE_MODE_NORMAL, download->progress ? download->progress->start : 0); return; } if (!write_cache_entry_to_file(cached, file_download)) { detach_connection(download, file_download->last_pos); abort_download(file_download); return; } detach_connection(download, file_download->last_pos); download_data_store(download, file_download);}/* XXX: We assume that resume is everytime zero in lun's callbacks. */struct lun_hop { struct terminal *term; unsigned char *ofile, *file; void (*callback)(struct terminal *, unsigned char *, void *, int); void *data;};static voidlun_alternate(struct lun_hop *lun_hop){ lun_hop->callback(lun_hop->term, lun_hop->file, lun_hop->data, 0); mem_free_if(lun_hop->ofile); mem_free(lun_hop);}static voidlun_overwrite(struct lun_hop *lun_hop){ lun_hop->callback(lun_hop->term, lun_hop->ofile, lun_hop->data, 0); mem_free_if(lun_hop->file); mem_free(lun_hop);}static voidlun_resume(struct lun_hop *lun_hop){ lun_hop->callback(lun_hop->term, lun_hop->ofile, lun_hop->data, 1); mem_free_if(lun_hop->file); mem_free(lun_hop);}static voidlun_cancel(struct lun_hop *lun_hop){ lun_hop->callback(lun_hop->term, NULL, lun_hop->data, 0); mem_free_if(lun_hop->ofile); mem_free_if(lun_hop->file); mem_free(lun_hop);}static voidlookup_unique_name(struct terminal *term, unsigned char *ofile, int resume, void (*callback)(struct terminal *, unsigned char *, void *, int), void *data){ struct lun_hop *lun_hop; unsigned char *file; int overwrite; ofile = expand_tilde(ofile); /* Minor code duplication to prevent useless call to get_opt_int() * if possible. --Zas */ if (resume) { callback(term, ofile, data, resume); return; } /* !overwrite means always silently overwrite, which may be admitelly * indeed a little confusing ;-) */ overwrite = get_opt_int("document.download.overwrite"); if (!overwrite) { /* Nothing special to do... */ callback(term, ofile, data, resume); return; } /* Check if file is a directory, and use a default name if it's the * case. */ if (file_is_dir(ofile)) { info_box(term, MSGBOX_FREE_TEXT, N_("Download error"), ALIGN_CENTER, msg_text(term, N_("'%s' is a directory."), ofile)); mem_free(ofile); callback(term, NULL, data, 0); return; } /* Check if the file already exists (file != ofile). */ file = get_unique_name(ofile); if (!file || overwrite == 1 || file == ofile) { /* Still nothing special to do... */ if (file != ofile) mem_free(ofile); callback(term, file, data, 0); return; } /* overwrite == 2 (ask) and file != ofile (=> original file already * exists) */ lun_hop = mem_calloc(1, sizeof(*lun_hop)); if (!lun_hop) { if (file != ofile) mem_free(file); mem_free(ofile); callback(term, NULL, data, 0); return; } lun_hop->term = term; lun_hop->ofile = ofile; lun_hop->file = (file != ofile) ? file : stracpy(ofile); lun_hop->callback = callback; lun_hop->data = data; msg_box(term, NULL, MSGBOX_FREE_TEXT, N_("File exists"), ALIGN_CENTER, msg_text(term, N_("This file already exists:\n" "%s\n\n" "The alternative filename is:\n" "%s"), empty_string_or_(lun_hop->ofile), empty_string_or_(file)), lun_hop, 4, N_("Sa~ve under the alternative name"), lun_alternate, B_ENTER, N_("~Overwrite the original file"), lun_overwrite, 0, N_("~Resume download of the original file"), lun_resume, 0, N_("~Cancel"), lun_cancel, B_ESC);}struct cdf_hop { unsigned char **real_file; int safe; void (*callback)(struct terminal *, int, void *, int); void *data;};static voidcreate_download_file_do(struct terminal *term, unsigned char *file, void *data, int resume){ struct cdf_hop *cdf_hop = data; unsigned char *wd; int h = -1; int saved_errno;#ifdef NO_FILE_SECURITY
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -