diff --git a/doc/Doxyfile.in b/doc/Doxyfile.in index 7e515835..5fe7641f 100644 --- a/doc/Doxyfile.in +++ b/doc/Doxyfile.in @@ -231,7 +231,9 @@ PREDEFINED = "LIST_OF(element_T)=element_T list" \ HAVE_SETENV \ HAVE_VARIADIC_MACROS EXPAND_AS_DEFINED = LIST_HEAD \ - INIT_LIST_OF + INIT_LIST_OF \ + ACTION_ \ + OBJECT_HEAD SKIP_FUNCTION_MACROS = YES #--------------------------------------------------------------------------- diff --git a/src/bfu/button.h b/src/bfu/button.h index d6ed7d2c..8fb14ed5 100644 --- a/src/bfu/button.h +++ b/src/bfu/button.h @@ -29,6 +29,47 @@ struct widget_info_button { /* Define to find buttons without keyboard accelerator. */ /* #define DEBUG_BUTTON_HOTKEY */ +/** @def add_dlg_ok_button + * Add a button that will close the dialog if pressed. + * + * void add_dlg_ok_button(struct dialog *dlg, unsigned char *text, int flags, + * ::done_handler_T *done, void *data); + * + * @param dlg + * The dialog in which the button is to be added. + * + * @param text + * Text displayed in the button. This string should contain a + * keyboard accelerator, marked with a preceding '~'. The pointer + * must remain valid as long as the dialog exists. + * + * @param flags + * Can be ::B_ENTER, ::B_ESC, or 0. + * + * @param done + * A function that BFU calls when the user presses this button. + * Before calling this, BFU checks the values of widgets. + * After the function returns, BFU closes the dialog. + * + * @param data + * A pointer to be passed to the @a done callback. */ + +/** @def add_dlg_button + * Add a button that need not close the dialog if pressed. + * + * void add_dlg_button(struct dialog *dlg, unsigned char *text, int flags, + * ::widget_handler_T *handler, void *data); + * + * @param handler + * A function that BFU calls when the user presses this button. + * BFU does not automatically check the values of widgets + * or close the dialog. + * + * @param data + * A pointer to any data needed by @a handler. It does not get this + * pointer as a parameter but can read it from widget_data->widget->data. + * + * The other parameters are as in ::add_dlg_ok_button. */ #ifdef DEBUG_BUTTON_HOTKEY void add_dlg_button_do(const unsigned char *file, int line, struct dialog *dlg, unsigned char *text, int flags, widget_handler_T *handler, void *data, done_handler_T *done, void *done_data); diff --git a/src/config/dialogs.c b/src/config/dialogs.c index e43a6687..cbb51a01 100644 --- a/src/config/dialogs.c +++ b/src/config/dialogs.c @@ -549,10 +549,11 @@ get_keybinding_action_box_item(enum keymap_id keymap_id, action_id_T action_id) struct listbox_item *keymap_box_item[KEYMAP_MAX]; void -init_keybinding_listboxes(struct keymap keymap_table[KEYMAP_MAX], struct action_list actions[]) +init_keybinding_listboxes(struct keymap keymap_table[KEYMAP_MAX], + const struct action_list actions[]) { struct listbox_item *root = &keybinding_browser.root; - struct action *act; + const struct action *act; enum keymap_id keymap_id; /* Do it backwards because add_listbox_item() add to front @@ -577,7 +578,8 @@ init_keybinding_listboxes(struct keymap keymap_table[KEYMAP_MAX], struct action_ assert(act->desc); #endif - item = add_listbox_item(NULL, keymap_box, BI_FOLDER, act, -1); + item = add_listbox_item(NULL, keymap_box, BI_FOLDER, + (void *) act, -1); if (!item) continue; item->expanded = 1; @@ -644,7 +646,7 @@ get_keybinding_text(struct listbox_item *item, struct terminal *term) return stracpy(keybinding_text_toggle ? keymap->str : _(keymap->desc, term)); } else if (item->depth < 2) { - struct action *action = item->udata; + const struct action *action = item->udata; return stracpy(keybinding_text_toggle ? action->str : _(action->desc, term)); @@ -686,7 +688,7 @@ get_keybinding_root(struct listbox_item *item) if (item->depth == 0) return NULL; if (item->depth == 1) { - struct action *action = item->udata; + const struct action *action = item->udata; return keymap_box_item[action->keymap_id]; } else { @@ -700,7 +702,7 @@ static enum listbox_match match_keybinding(struct listbox_item *item, struct terminal *term, unsigned char *text) { - struct action *action = item->udata; + const struct action *action = item->udata; unsigned char *desc; if (item->depth != 1) @@ -872,7 +874,7 @@ push_kbdbind_add_button(struct dialog_data *dlg_data, hop->action_id = keybinding->action_id; hop->keymap_id = keybinding->keymap_id; } else { - struct action *action = item->udata; + const struct action *action = item->udata; hop->action_id = action->num; hop->keymap_id = action->keymap_id; diff --git a/src/config/dialogs.h b/src/config/dialogs.h index e5f01ed3..60ec88f7 100644 --- a/src/config/dialogs.h +++ b/src/config/dialogs.h @@ -16,7 +16,8 @@ void options_manager(struct session *); void keybinding_manager(struct session *); struct listbox_item *get_keybinding_action_box_item(enum keymap_id keymap_id, action_id_T action_id); -void init_keybinding_listboxes(struct keymap keymap_table[], struct action_list actions[]); +void init_keybinding_listboxes(struct keymap keymap_table[], + const struct action_list actions[]); void done_keybinding_listboxes(void); #endif diff --git a/src/config/kbdbind.c b/src/config/kbdbind.c index 2096a56d..53ca020c 100644 --- a/src/config/kbdbind.c +++ b/src/config/kbdbind.c @@ -26,7 +26,7 @@ /* Fix namespace clash on MacOS. */ #define table table_elinks -static struct action_list action_table[KEYMAP_MAX]; +static const struct action_list action_table[KEYMAP_MAX]; static struct keymap keymap_table[KEYMAP_MAX]; static LIST_OF(struct keybinding) keymaps[KEYMAP_MAX]; @@ -229,7 +229,7 @@ static struct keymap keymap_table[] = { * Config file helpers. */ -static struct action * +static const struct action * get_action_from_keystroke(enum keymap_id keymap_id, const unsigned char *keystroke_str) { @@ -243,8 +243,8 @@ unsigned char * get_action_name_from_keystroke(enum keymap_id keymap_id, const unsigned char *keystroke_str) { - struct action *action = get_action_from_keystroke(keymap_id, - keystroke_str); + const struct action *action = get_action_from_keystroke(keymap_id, + keystroke_str); return action ? action->str : NULL; } @@ -252,7 +252,7 @@ get_action_name_from_keystroke(enum keymap_id keymap_id, action_id_T get_action_from_string(enum keymap_id keymap_id, unsigned char *str) { - struct action *action; + const struct action *action; assert(keymap_id >= 0 && keymap_id < KEYMAP_MAX); @@ -263,7 +263,7 @@ get_action_from_string(enum keymap_id keymap_id, unsigned char *str) return -1; } -struct action * +const struct action * get_action(enum keymap_id keymap_id, action_id_T action_id) { assert(keymap_id >= 0 && keymap_id < KEYMAP_MAX); @@ -277,7 +277,7 @@ get_action(enum keymap_id keymap_id, action_id_T action_id) unsigned char * get_action_name(enum keymap_id keymap_id, action_id_T action_id) { - struct action *action = get_action(keymap_id, action_id); + const struct action *action = get_action(keymap_id, action_id); return action ? action->str : NULL; } @@ -285,7 +285,7 @@ get_action_name(enum keymap_id keymap_id, action_id_T action_id) static unsigned char * get_action_desc(enum keymap_id keymap_id, action_id_T action_id) { - struct action *action = get_action(keymap_id, action_id); + const struct action *action = get_action(keymap_id, action_id); return action ? (action->desc ? action->desc : action->str) : NULL; @@ -528,23 +528,23 @@ add_actions_to_string(struct string *string, action_id_T action_ids[], #undef KEYMAP_ID #define KEYMAP_ID KEYMAP_MAIN -static struct action main_action_table[MAIN_ACTIONS + 1] = { +static const struct action main_action_table[MAIN_ACTIONS + 1] = { #include "config/actions-main.inc" }; #undef KEYMAP_ID #define KEYMAP_ID KEYMAP_EDIT -static struct action edit_action_table[EDIT_ACTIONS + 1] = { +static const struct action edit_action_table[EDIT_ACTIONS + 1] = { #include "config/actions-edit.inc" }; #undef KEYMAP_ID #define KEYMAP_ID KEYMAP_MENU -static struct action menu_action_table[MENU_ACTIONS + 1] = { +static const struct action menu_action_table[MENU_ACTIONS + 1] = { #include "config/actions-menu.inc" }; -static struct action_list action_table[KEYMAP_MAX] = { +static const struct action_list action_table[KEYMAP_MAX] = { { main_action_table, sizeof_array(main_action_table) }, { edit_action_table, sizeof_array(edit_action_table) }, { menu_action_table, sizeof_array(menu_action_table) }, @@ -849,11 +849,11 @@ add_default_keybindings(void) */ struct action_alias { - unsigned char *str; + const unsigned char *str; action_id_T action_id; }; -static struct action_alias main_action_aliases[] = { +static const struct action_alias main_action_aliases[] = { { "back", ACT_MAIN_HISTORY_MOVE_BACK }, { "down", ACT_MAIN_MOVE_LINK_NEXT }, { "download", ACT_MAIN_LINK_DOWNLOAD }, @@ -874,13 +874,13 @@ static struct action_alias main_action_aliases[] = { { NULL, 0 } }; -static struct action_alias edit_action_aliases[] = { +static const struct action_alias edit_action_aliases[] = { { "edit", ACT_EDIT_OPEN_EXTERNAL }, { NULL, 0 } }; -static struct action_alias *action_aliases[KEYMAP_MAX] = { +static const struct action_alias *action_aliases[KEYMAP_MAX] = { main_action_aliases, edit_action_aliases, NULL, @@ -892,7 +892,7 @@ get_aliased_action(enum keymap_id keymap_id, unsigned char *action_str) assert(keymap_id >= 0 && keymap_id < KEYMAP_MAX); if (action_aliases[keymap_id]) { - struct action_alias *alias; + const struct action_alias *alias; for (alias = action_aliases[keymap_id]; alias->str; alias++) if (!strcmp(alias->str, action_str)) diff --git a/src/config/kbdbind.h b/src/config/kbdbind.h index e046fd8b..8facc190 100644 --- a/src/config/kbdbind.h +++ b/src/config/kbdbind.h @@ -30,7 +30,7 @@ struct action { }; struct action_list { - struct action *actions; + const struct action *actions; int num_actions; }; struct keymap { @@ -53,42 +53,26 @@ enum action_flags { * and also update the table action_table[] in kbdbind.c. */ #define ACTION_(map, name, action, caption, flags) \ - ACT_##map##_OFFSET_##action + ACT_##map##_##action -enum main_action_offset { +enum main_action { #include "config/actions-main.inc" MAIN_ACTIONS, }; -enum edit_action_offset { +enum edit_action { #include "config/actions-edit.inc" EDIT_ACTIONS }; -enum menu_action_offset { +enum menu_action { #include "config/actions-menu.inc" MENU_ACTIONS }; -#undef ACTION_ -#define ACTION_(map, name, action, caption, flags) \ - ACT_##map##_##action - -enum main_action { -#include "config/actions-main.inc" -}; - -enum edit_action { -#include "config/actions-edit.inc" -}; - -enum menu_action { -#include "config/actions-menu.inc" -}; - #undef ACTION_ enum kbdbind_flags { @@ -120,7 +104,7 @@ struct keybinding *add_keybinding(enum keymap_id keymap_id, action_id_T action_i int keybinding_exists(enum keymap_id keymap_id, struct term_event_keyboard *kbd, action_id_T *action_id); void free_keybinding(struct keybinding *); -struct action *get_action(enum keymap_id keymap_id, action_id_T action_id); +const struct action *get_action(enum keymap_id keymap_id, action_id_T action_id); unsigned char *get_action_name(enum keymap_id keymap_id, action_id_T action_id); action_id_T get_action_from_string(enum keymap_id keymap_id, unsigned char *str); unsigned char *get_action_name_from_keystroke(enum keymap_id keymap_id, @@ -129,7 +113,7 @@ unsigned char *get_action_name_from_keystroke(enum keymap_id keymap_id, static inline unsigned int action_is_anonymous_safe(enum keymap_id keymap_id, action_id_T action_id) { - struct action *action = get_action(keymap_id, action_id); + const struct action *action = get_action(keymap_id, action_id); return action && !(action->flags & ACTION_RESTRICT_ANONYMOUS); } @@ -137,7 +121,7 @@ action_is_anonymous_safe(enum keymap_id keymap_id, action_id_T action_id) static inline unsigned int action_requires_view_state(enum keymap_id keymap_id, action_id_T action_id) { - struct action *action = get_action(keymap_id, action_id); + const struct action *action = get_action(keymap_id, action_id); return action && (action->flags & ACTION_REQUIRE_VIEW_STATE); } @@ -145,7 +129,7 @@ action_requires_view_state(enum keymap_id keymap_id, action_id_T action_id) static inline unsigned int action_requires_location(enum keymap_id keymap_id, action_id_T action_id) { - struct action *action = get_action(keymap_id, action_id); + const struct action *action = get_action(keymap_id, action_id); return action && (action->flags & ACTION_REQUIRE_LOCATION); } @@ -153,7 +137,7 @@ action_requires_location(enum keymap_id keymap_id, action_id_T action_id) static inline unsigned int action_prefix_is_link_number(enum keymap_id keymap_id, action_id_T action_id) { - struct action *action = get_action(keymap_id, action_id); + const struct action *action = get_action(keymap_id, action_id); return action && (action->flags & ACTION_JUMP_TO_LINK); } @@ -161,7 +145,7 @@ action_prefix_is_link_number(enum keymap_id keymap_id, action_id_T action_id) static inline unsigned int action_requires_link(enum keymap_id keymap_id, action_id_T action_id) { - struct action *action = get_action(keymap_id, action_id); + const struct action *action = get_action(keymap_id, action_id); return action && (action->flags & ACTION_REQUIRE_LINK); } @@ -169,7 +153,7 @@ action_requires_link(enum keymap_id keymap_id, action_id_T action_id) static inline unsigned int action_requires_form(enum keymap_id keymap_id, action_id_T action_id) { - struct action *action = get_action(keymap_id, action_id); + const struct action *action = get_action(keymap_id, action_id); return action && (action->flags & ACTION_REQUIRE_FORM); } diff --git a/src/network/state.h b/src/network/state.h index e099ddcf..41f13378 100644 --- a/src/network/state.h +++ b/src/network/state.h @@ -117,7 +117,7 @@ struct connection_state { * structure holds a system error instead. */ enum connection_basic_state basic; - /** When #state is ::S_ERRNO, syserr is the saved value of + /** When #basic is ::S_ERRNO, syserr is the saved value of * errno. Otherwise, syserr should be 0. */ int syserr; }; diff --git a/src/protocol/bittorrent/common.h b/src/protocol/bittorrent/common.h index cd1b2ded..f39d8bc4 100644 --- a/src/protocol/bittorrent/common.h +++ b/src/protocol/bittorrent/common.h @@ -32,7 +32,7 @@ struct terminal; #define BITTORRENT_REQUEST_LENGTH (1 << 14) /** The length of requested blocks of pieces should not exceed 2^17 bytes. - * Used for the protocol.bittorrent.max_request_length option + * Used for the protocol.bittorrent.max_request_length option. * Bram uses 2^23 here. */ #define BITTORRENT_REQUEST_ACCEPT_LENGTH (1 << 23) diff --git a/src/protocol/bittorrent/dialogs.c b/src/protocol/bittorrent/dialogs.c index 26a9545f..af454734 100644 --- a/src/protocol/bittorrent/dialogs.c +++ b/src/protocol/bittorrent/dialogs.c @@ -616,8 +616,11 @@ abort_bittorrent_download_query(struct dialog_data *dlg_data) done_bittorrent_download_info(info); } -/* The download button handler. Basicly it redirects to bittorrent: - * and starts displaying the download. */ +/** The download button handler. Basicly it redirects to bittorrent: + * 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) { @@ -675,7 +678,7 @@ bittorrent_download(struct dialog_data *dlg_data, struct widget_data *widget_dat /* Show the protocol header. */ /* XXX: Code duplication with session/download.h */ -widget_handler_status_T +static widget_handler_status_T tp_show_header(struct dialog_data *dlg_data, struct widget_data *widget_data) { struct type_query *type_query = widget_data->widget->data; @@ -685,7 +688,10 @@ tp_show_header(struct dialog_data *dlg_data, struct widget_data *widget_data) return EVENT_PROCESSED; } -/* Build a dialog querying the user on how to handle a .torrent file. */ +/** 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) diff --git a/src/scripting/smjs/core.c b/src/scripting/smjs/core.c index 24939040..ef46d250 100644 --- a/src/scripting/smjs/core.c +++ b/src/scripting/smjs/core.c @@ -251,7 +251,7 @@ utf8_to_jsstring(JSContext *ctx, const unsigned char *str, int length) * @param[in] utf16 * Pointer to the first element in an array of jschars. * - * @param[i] len + * @param[in] len * Number of jschars in the @a utf16 array. * * @return @a utf8 if successful, or NULL if not. */ diff --git a/src/session/download.c b/src/session/download.c index 00cb83bc..90d7d3e6 100644 --- a/src/session/download.c +++ b/src/session/download.c @@ -87,6 +87,8 @@ are_there_downloads(void) static void download_data(struct download *download, struct file_download *file_download); +/*! @note If this fails, the caller is responsible of freeing @a file + * and closing @a fd. */ struct file_download * init_file_download(struct uri *uri, struct session *ses, unsigned char *file, int fd) { @@ -458,126 +460,235 @@ download_data(struct download *download, struct file_download *file_download) download_data_store(download, file_download); } +/** Type of the callback function that will be called when the user + * answers the question posed by lookup_unique_name(). + * + * @param term + * The terminal on which the callback should display any windows. + * Comes directly from the @a term argument of lookup_unique_name(). + * + * @param file + * The name of the local file to which the data should be downloaded, + * or NULL if the download should not begin. The callback is + * responsible of doing mem_free(@a file). + * + * @param data + * A pointer to any data that the callback cares about. + * Comes directly from the @a data argument of lookup_unique_name(). + * + * @param flags + * The same as the @a flags argument of create_download_file(), + * except the ::DOWNLOAD_RESUME_SELECTED bit will be changed to match + * what the user chose. + * + * @relates lun_hop */ +typedef void lun_callback_T(struct terminal *term, unsigned char *file, + void *data, enum download_flags flags); -/* XXX: We assume that resume is everytime zero in lun's callbacks. */ +/** The user is being asked what to do when the local file for + * the download already exists. This structure is allocated by + * lookup_unique_name() and freed by each lun_* function: + * lun_alternate(), lun_cancel(), lun_overwrite(), and lun_resume(). */ struct lun_hop { + /** The terminal in which ELinks is asking the question. + * This gets passed to #callback. */ struct terminal *term; - unsigned char *ofile, *file; - void (*callback)(struct terminal *, unsigned char *, void *, download_flags_T); + /** The name of the local file into which the data was + * originally going to be downloaded, but which already + * exists. In this string, "~" has already been expanded + * to the home directory. The string must be freed with + * mem_free(). */ + unsigned char *ofile; + + /** An alternative file name that the user may choose instead + * of #ofile. The string must be freed with mem_free(). */ + unsigned char *file; + + /** This function will be called when the user answers. */ + lun_callback_T *callback; + + /** A pointer to be passed to #callback. */ void *data; + + /** Saved flags to be passed to #callback. + * If the user chooses to resume, then lun_resume() sets + * ::DOWNLOAD_RESUME_SELECTED when it calls #callback. + * + * @invariant The ::DOWNLOAD_RESUME_SELECTED bit should be + * clear here because otherwise there would have been no + * reason to ask the user and initialize this structure. */ + enum download_flags flags; }; -enum { - COMMON_DOWNLOAD_DO = 0, - CONTINUE_DOWNLOAD_DO -}; - +/** Data saved by common_download() for the common_download_do() + * callback. */ struct cmdw_hop { - int magic; /* Must be first --witekfl */ struct session *ses; + + /** The URI from which the data will be downloaded. */ + struct uri *download_uri; + + /** The name of the local file to which the data will be + * downloaded. This is initially NULL, but its address is + * given to create_download_file(), which arranges for the + * pointer to be set before common_download_do() is called. + * The string must be freed with mem_free(). */ unsigned char *real_file; }; +/** Data saved by continue_download() for the continue_download_do() + * callback. */ struct codw_hop { - int magic; /* must be first --witekfl */ struct type_query *type_query; + + /** The name of the local file to which the data will be + * downloaded. This is initially NULL, but its address is + * given to create_download_file(), which arranges for the + * pointer to be set before continue_download_do() is called. + * The string must be freed with mem_free(). */ unsigned char *real_file; + unsigned char *file; }; +/** Data saved by create_download_file() for the create_download_file_do() + * callback. */ struct cdf_hop { + /** Where to save the name of the file that was actually + * opened. One of the arguments of #callback is a file + * descriptor for this file. @c real_file can be NULL if + * #callback does not care about the name. */ unsigned char **real_file; - void (*callback)(struct terminal *, int, void *, download_flags_T); + + /** This function will be called when the file has been opened, + * or when it is known that the file will not be opened. */ + cdf_callback_T *callback; + + /** A pointer to be passed to #callback. */ void *data; }; +/** The use chose "Save under the alternative name" when asked where + * to download a file. + * + * lookup_unique_name() passes this function as a ::done_handler_T to + * msg_box(). + * + * @relates lun_hop */ static void lun_alternate(void *lun_hop_) { struct lun_hop *lun_hop = lun_hop_; - lun_hop->callback(lun_hop->term, lun_hop->file, lun_hop->data, DOWNLOAD_START); + lun_hop->callback(lun_hop->term, lun_hop->file, lun_hop->data, + lun_hop->flags); mem_free_if(lun_hop->ofile); mem_free(lun_hop); } +/** The use chose "Cancel" when asked where to download a file. + * + * lookup_unique_name() passes this function as a ::done_handler_T to + * msg_box(). + * + * @relates lun_hop */ static void lun_cancel(void *lun_hop_) { struct lun_hop *lun_hop = lun_hop_; - lun_hop->callback(lun_hop->term, NULL, lun_hop->data, DOWNLOAD_START); + lun_hop->callback(lun_hop->term, NULL, lun_hop->data, + lun_hop->flags); mem_free_if(lun_hop->ofile); mem_free_if(lun_hop->file); mem_free(lun_hop); } +/** The use chose "Overwrite the original file" when asked where to + * download a file. + * + * lookup_unique_name() passes this function as a ::done_handler_T to + * msg_box(). + * + * @relates lun_hop */ static void lun_overwrite(void *lun_hop_) { struct lun_hop *lun_hop = lun_hop_; - lun_hop->callback(lun_hop->term, lun_hop->ofile, lun_hop->data, 0); + lun_hop->callback(lun_hop->term, lun_hop->ofile, lun_hop->data, + lun_hop->flags); mem_free_if(lun_hop->file); mem_free(lun_hop); } -static void common_download_do(struct terminal *term, int fd, void *data, download_flags_T flags); - +/** The user chose "Resume download of the original file" when asked + * where to download a file. + * + * lookup_unique_name() passes this function as a ::done_handler_T to + * msg_box(). + * + * @relates lun_hop */ static void lun_resume(void *lun_hop_) { struct lun_hop *lun_hop = lun_hop_; - struct cdf_hop *cdf_hop = lun_hop->data; - int magic = *(int *)cdf_hop->data; - - if (magic == CONTINUE_DOWNLOAD_DO) { - struct cmdw_hop *cmdw_hop = mem_calloc(1, sizeof(*cmdw_hop)); - - if (!cmdw_hop) { - lun_cancel(lun_hop); - return; - } else { - struct codw_hop *codw_hop = cdf_hop->data; - struct type_query *type_query = codw_hop->type_query; - - cmdw_hop->magic = COMMON_DOWNLOAD_DO; - cmdw_hop->ses = type_query->ses; - /* FIXME: Current ses->download_uri is overwritten here --witekfl */ - cmdw_hop->ses->download_uri = get_uri_reference(type_query->uri); - - if (type_query->external_handler) mem_free_if(codw_hop->file); - tp_cancel(type_query); - mem_free(codw_hop); - - cdf_hop->real_file = &cmdw_hop->real_file; - cdf_hop->data = cmdw_hop; - cdf_hop->callback = common_download_do; - } - } - lun_hop->callback(lun_hop->term, lun_hop->ofile, lun_hop->data, DOWNLOAD_RESUME); + lun_hop->callback(lun_hop->term, lun_hop->ofile, lun_hop->data, + lun_hop->flags | DOWNLOAD_RESUME_SELECTED); mem_free_if(lun_hop->file); mem_free(lun_hop); } +/** If attempting to download to an existing file, perhaps ask + * the user whether to resume, overwrite, or save elsewhere. + * This function constructs a struct lun_hop, which will be freed + * when the user answers the question. + * + * @param term + * The terminal in which this function should show its UI. + * + * @param[in] ofile + * A proposed name for the local file to which the data would be + * downloaded. "~" here refers to the home directory. + * lookup_unique_name() treats this original string as read-only. + * + * @param[in] flags + * Flags controlling how to download the file. + * ::DOWNLOAD_RESUME_ALLOWED adds a "Resume" button to the dialog. + * ::DOWNLOAD_RESUME_SELECTED means the user already chose to resume + * downloading (with ::ACT_MAIN_LINK_DOWNLOAD_RESUME), before ELinks + * even asked for the file name; thus don't ask whether to overwrite. + * Other flags, such as ::DOWNLOAD_EXTERNAL, have no effect at this + * level but they get passed to @a callback. + * + * @param callback + * Will be called when the user answers, or right away if the question + * need not or cannot be asked. + * + * @param data + * A pointer to be passed to @a callback. + * + * @relates lun_hop */ static void -lookup_unique_name(struct terminal *term, unsigned char *ofile, download_flags_T flags, - void (*callback)(struct terminal *, unsigned char *, void *, download_flags_T flags), - void *data) +lookup_unique_name(struct terminal *term, unsigned char *ofile, + enum download_flags flags, + lun_callback_T *callback, void *data) { /* [gettext_accelerator_context(.lookup_unique_name)] */ - struct lun_hop *lun_hop; - unsigned char *file; + struct lun_hop *lun_hop = NULL; + unsigned char *file = NULL; + struct dialog_data *dialog_data; int overwrite; ofile = expand_tilde(ofile); + if (!ofile) goto error; /* Minor code duplication to prevent useless call to get_opt_int() * if possible. --Zas */ - if (flags & DOWNLOAD_RESUME) { + if (flags & DOWNLOAD_RESUME_SELECTED) { callback(term, ofile, data, flags); return; } @@ -598,9 +709,7 @@ lookup_unique_name(struct terminal *term, unsigned char *ofile, download_flags_T N_("Download error"), ALIGN_CENTER, msg_text(term, N_("'%s' is a directory."), ofile)); - mem_free(ofile); - callback(term, NULL, data, flags); - return; + goto error; } /* Check if the file already exists (file != ofile). */ @@ -609,7 +718,7 @@ lookup_unique_name(struct terminal *term, unsigned char *ofile, download_flags_T if (!file || overwrite == 1 || file == ofile) { /* Still nothing special to do... */ if (file != ofile) mem_free(ofile); - callback(term, file, data, flags); + callback(term, file, data, flags & ~DOWNLOAD_RESUME_SELECTED); return; } @@ -617,19 +726,16 @@ lookup_unique_name(struct terminal *term, unsigned char *ofile, download_flags_T * 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, flags); - return; - } + if (!lun_hop) goto error; lun_hop->term = term; lun_hop->ofile = ofile; - lun_hop->file = (file != ofile) ? file : stracpy(ofile); + lun_hop->file = file; /* file != ofile verified above */ lun_hop->callback = callback; lun_hop->data = data; + lun_hop->flags = flags; - msg_box(term, NULL, MSGBOX_FREE_TEXT, + dialog_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" @@ -640,15 +746,34 @@ lookup_unique_name(struct terminal *term, unsigned char *ofile, download_flags_T lun_hop, 4, MSG_BOX_BUTTON(N_("Sa~ve under the alternative name"), lun_alternate, B_ENTER), MSG_BOX_BUTTON(N_("~Overwrite the original file"), lun_overwrite, 0), - MSG_BOX_BUTTON(N_("~Resume download of the original file"), lun_resume, 0), + MSG_BOX_BUTTON((flags & DOWNLOAD_RESUME_ALLOWED + ? N_("~Resume download of the original file") + : NULL), + lun_resume, 0), MSG_BOX_BUTTON(N_("~Cancel"), lun_cancel, B_ESC)); + if (!dialog_data) goto error; + return; + +error: + mem_free_if(lun_hop); + if (file != ofile) mem_free_if(file); + mem_free_if(ofile); + callback(term, NULL, data, flags & ~DOWNLOAD_RESUME_SELECTED); } +/** Now that the final name of the download file has been chosen, + * open the file and call the ::cdf_callback_T that was originally + * given to create_download_file(). + * + * create_download_file() passes this function as a ::lun_callback_T + * to lookup_unique_name(). + * + * @relates cdf_hop */ static void -create_download_file_do(struct terminal *term, unsigned char *file, void *data, - download_flags_T flags) +create_download_file_do(struct terminal *term, unsigned char *file, + void *data, enum download_flags flags) { struct cdf_hop *cdf_hop = data; unsigned char *wd; @@ -657,7 +782,7 @@ create_download_file_do(struct terminal *term, unsigned char *file, void *data, #ifdef NO_FILE_SECURITY int sf = 0; #else - int sf = flags & DOWNLOAD_EXTERNAL; + int sf = !!(flags & DOWNLOAD_EXTERNAL); #endif if (!file) goto finish; @@ -671,8 +796,9 @@ create_download_file_do(struct terminal *term, unsigned char *file, void *data, /* O_APPEND means repositioning at the end of file before each write(), * thus ignoring seek()s and that can hide mysterious bugs. IMHO. * --pasky */ - h = open(file, O_CREAT | O_WRONLY | (flags & DOWNLOAD_RESUME ? 0 : O_TRUNC) - | (sf && !(flags & DOWNLOAD_RESUME) ? O_EXCL : 0), + h = open(file, O_CREAT | O_WRONLY + | (flags & DOWNLOAD_RESUME_SELECTED ? 0 : O_TRUNC) + | (sf && !(flags & DOWNLOAD_RESUME_SELECTED) ? O_EXCL : 0), sf ? 0600 : 0666); saved_errno = errno; /* Saved in case of ... --Zas */ @@ -717,17 +843,51 @@ finish: mem_free(cdf_hop); } +/** Create a file to which data can be downloaded. + * This function constructs a struct cdf_hop that will be freed + * when @a callback returns. + * + * @param term + * If any dialog boxes are needed, show them in this terminal. + * + * @param fi + * A proposed name for the local file to which the data would be + * downloaded. "~" here refers to the home directory. + * create_download_file() treats this original string as read-only. + * + * @param real_file + * If non-NULL, prepare to save in *@a real_file the name of the local + * file that was eventually opened. @a callback must then arrange for + * this string to be freed with mem_free(). + * + * @param flags + * Flags controlling how to download the file. + * ::DOWNLOAD_RESUME_ALLOWED adds a "Resume" button to the dialog. + * ::DOWNLOAD_RESUME_SELECTED skips the dialog entirely. + * ::DOWNLOAD_EXTERNAL causes the file to be created with settings + * suitable for a temporary file: give only the user herself access to + * the file (even if the umask is looser), and create the file with + * @c O_EXCL unless resuming. + * + * @param callback + * This function will be called when the file has been opened, + * or when it is known that the file will not be opened. + * + * @param data + * A pointer to be passed to @a callback. + * + * @relates cdf_hop */ void create_download_file(struct terminal *term, unsigned char *fi, - unsigned char **real_file, download_flags_T flags, - void (*callback)(struct terminal *, int, void *, download_flags_T), - void *data) + unsigned char **real_file, + enum download_flags flags, + cdf_callback_T *callback, void *data) { struct cdf_hop *cdf_hop = mem_calloc(1, sizeof(*cdf_hop)); unsigned char *wd; if (!cdf_hop) { - callback(term, -1, data, 0); + callback(term, -1, data, flags & ~DOWNLOAD_RESUME_SELECTED); return; } @@ -833,32 +993,56 @@ subst_file(unsigned char *prog, unsigned char *file) +/*! common_download() passes this function as a ::cdf_callback_T to + * create_download_file(). + * + * @relates cmdw_hop */ static void -common_download_do(struct terminal *term, int fd, void *data, download_flags_T flags) +common_download_do(struct terminal *term, int fd, void *data, + enum download_flags flags) { struct file_download *file_download; struct cmdw_hop *cmdw_hop = data; + struct uri *download_uri = cmdw_hop->download_uri; unsigned char *file = cmdw_hop->real_file; struct session *ses = cmdw_hop->ses; struct stat buf; mem_free(cmdw_hop); - if (!file || fstat(fd, &buf)) return; + if (!file || fstat(fd, &buf)) goto finish; - file_download = init_file_download(ses->download_uri, ses, file, fd); - if (!file_download) return; + file_download = init_file_download(download_uri, ses, file, fd); + if (!file_download) goto finish; + /* If init_file_download succeeds, it takes ownership of file + * and fd. */ + file = NULL; + fd = -1; - if (flags & DOWNLOAD_RESUME) file_download->seek = buf.st_size; + if (flags & DOWNLOAD_RESUME_SELECTED) + file_download->seek = buf.st_size; display_download(ses->tab->term, file_download, ses); load_uri(file_download->uri, ses->referrer, &file_download->download, PRI_DOWNLOAD, CACHE_MODE_NORMAL, file_download->seek); + +finish: + mem_free_if(file); + if (fd != -1) close(fd); + done_uri(download_uri); } +/** Begin or resume downloading from session.download_uri to the + * @a file specified by the user. + * + * This function contains the code shared between start_download() and + * resume_download(). + * + * @relates cmdw_hop */ static void -common_download(struct session *ses, unsigned char *file, download_flags_T flags) +common_download(struct session *ses, unsigned char *file, + enum download_flags flags) { struct cmdw_hop *cmdw_hop; @@ -867,30 +1051,81 @@ common_download(struct session *ses, unsigned char *file, download_flags_T flags cmdw_hop = mem_calloc(1, sizeof(*cmdw_hop)); if (!cmdw_hop) return; cmdw_hop->ses = ses; - cmdw_hop->magic = COMMON_DOWNLOAD_DO; + cmdw_hop->download_uri = ses->download_uri; + ses->download_uri = NULL; kill_downloads_to_file(file); - create_download_file(ses->tab->term, file, &cmdw_hop->real_file, flags, - common_download_do, cmdw_hop); + create_download_file(ses->tab->term, file, &cmdw_hop->real_file, + flags, common_download_do, cmdw_hop); } +/** Begin downloading from session.download_uri to the @a file + * specified by the user. + * + * The ::ACT_MAIN_SAVE_AS, ::ACT_MAIN_SAVE_URL_AS, + * ::ACT_MAIN_LINK_DOWNLOAD, and ::ACT_MAIN_LINK_DOWNLOAD_IMAGE + * actions pass this function as the @c std callback to query_file(). + * + * @relates cmdw_hop */ void start_download(void *ses, unsigned char *file) { - common_download(ses, file, DOWNLOAD_START); + common_download(ses, file, + DOWNLOAD_RESUME_ALLOWED); } + +/** Resume downloading from session.download_uri to the @a file + * specified by the user. + * + * The ::ACT_MAIN_LINK_DOWNLOAD_RESUME action passes this function as + * the @c std callback to query_file(). + * + * @relates cmdw_hop */ void resume_download(void *ses, unsigned char *file) { - common_download(ses, file, DOWNLOAD_RESUME); + common_download(ses, file, + DOWNLOAD_RESUME_ALLOWED | DOWNLOAD_RESUME_SELECTED); } - - +/** Resume downloading a file, based on information in struct + * codw_hop. This function actually starts a new download from the + * current end of the file, even though a download from the beginning + * is already in progress at codw_hop->type_query->download. The + * caller will cancel the preexisting download after this function + * returns. + * + * @relates codw_hop */ static void -continue_download_do(struct terminal *term, int fd, void *data, download_flags_T flags) +transform_codw_to_cmdw(struct terminal *term, int fd, + struct codw_hop *codw_hop, + enum download_flags flags) +{ + struct type_query *type_query = codw_hop->type_query; + struct cmdw_hop *cmdw_hop = mem_calloc(1, sizeof(*cmdw_hop)); + + if (!cmdw_hop) { + close(fd); + return; + } + + cmdw_hop->ses = type_query->ses; + cmdw_hop->download_uri = get_uri_reference(type_query->uri); + cmdw_hop->real_file = codw_hop->real_file; + codw_hop->real_file = NULL; + + common_download_do(term, fd, cmdw_hop, flags); +} + +/*! continue_download() passes this function as a ::cdf_callback_T to + * create_download_file(). + * + * @relates codw_hop */ +static void +continue_download_do(struct terminal *term, int fd, void *data, + enum download_flags flags) { struct codw_hop *codw_hop = data; struct file_download *file_download = NULL; @@ -904,9 +1139,19 @@ continue_download_do(struct terminal *term, int fd, void *data, download_flags_T type_query = codw_hop->type_query; if (!codw_hop->real_file) goto cancel; + if (flags & DOWNLOAD_RESUME_SELECTED) { + transform_codw_to_cmdw(term, fd, codw_hop, flags); + fd = -1; /* ownership transfer */ + goto cancel; + } + file_download = init_file_download(type_query->uri, type_query->ses, codw_hop->real_file, fd); if (!file_download) goto cancel; + /* If init_file_download succeeds, it takes ownership of + * codw_hop->real_file and fd. */ + codw_hop->real_file = NULL; + fd = -1; if (type_query->external_handler) { file_download->external_handler = subst_file(type_query->external_handler, @@ -929,11 +1174,22 @@ continue_download_do(struct terminal *term, int fd, void *data, download_flags_T return; cancel: + mem_free_if(codw_hop->real_file); + if (fd != -1) close(fd); if (type_query->external_handler) mem_free_if(codw_hop->file); tp_cancel(type_query); mem_free(codw_hop); } +/** When asked what to do with a file, the user chose to download it + * to a local file named @a file. + * Or an external handler was selected, in which case + * type_query.external_handler is non-NULL and @a file does not + * matter because this function will generate a name. + * + * tp_save() passes this function as the @c std callback to query_file(). + * + * @relates codw_hop */ static void continue_download(void *data, unsigned char *file) { @@ -957,19 +1213,19 @@ continue_download(void *data, unsigned char *file) codw_hop->type_query = type_query; codw_hop->file = file; - codw_hop->magic = CONTINUE_DOWNLOAD_DO; kill_downloads_to_file(file); create_download_file(type_query->ses->tab->term, file, &codw_hop->real_file, type_query->external_handler - ? DOWNLOAD_START | DOWNLOAD_EXTERNAL - : DOWNLOAD_START, + ? DOWNLOAD_RESUME_ALLOWED | DOWNLOAD_EXTERNAL + : DOWNLOAD_RESUME_ALLOWED, continue_download_do, codw_hop); } +/*! @relates type_query */ static struct type_query * find_type_query(struct session *ses) { @@ -982,6 +1238,11 @@ find_type_query(struct session *ses) return NULL; } +/** Prepare to ask the user what to do with a file, but don't display + * the window yet. To display it, do_type_query() must be called + * separately. setup_download_handler() takes care of that. + * + * @relates type_query */ static struct type_query * init_type_query(struct session *ses, struct download *download, struct cache_entry *cached) @@ -1007,6 +1268,10 @@ init_type_query(struct session *ses, struct download *download, return type_query; } +/** Cancel any download started for @a type_query, remove the structure + * from the session.type_queries list, and free it. + * + * @relates type_query */ void done_type_query(struct type_query *type_query) { @@ -1022,6 +1287,14 @@ done_type_query(struct type_query *type_query) } +/** The user chose "Cancel" when asked what to do with a file, + * or the type query was cancelled for some other reason. + * + * do_type_query() and bittorrent_query_callback() pass this function + * as a ::done_handler_T to add_dlg_ok_button(), and tp_save() passes + * this function as a @c cancel callback to query_file(). + * + * @relates type_query */ void tp_cancel(void *data) { @@ -1033,6 +1306,13 @@ tp_cancel(void *data) } +/** The user chose "Save" when asked what to do with a file. + * Now ask her where to save the file. + * + * do_type_query() and bittorrent_query_callback() pass this function + * as a ::done_handler_T to add_dlg_ok_button(). + * + * @relates type_query */ void tp_save(struct type_query *type_query) { @@ -1040,8 +1320,14 @@ tp_save(struct type_query *type_query) query_file(type_query->ses, type_query->uri, type_query, continue_download, tp_cancel, 1); } -/** This button handler uses the add_dlg_button() interface so that pressing - * 'Show header' will not close the type query dialog. */ +/** The user chose "Show header" when asked what to do with a file. + * + * do_type_query() passes this function as a ::widget_handler_T to + * add_dlg_button(). Unlike with add_dlg_ok_button(), pressing this + * button does not close the dialog box. This way, the user can + * first examine the header and then choose what to do. + * + * @relates type_query */ static widget_handler_status_T tp_show_header(struct dialog_data *dlg_data, struct widget_data *widget_data) { @@ -1053,10 +1339,18 @@ tp_show_header(struct dialog_data *dlg_data, struct widget_data *widget_data) } -/** @bug FIXME: We need to modify this function to take frame data +/** The user chose "Display" when asked what to do with a file, + * or she chose "Open" and there is no external handler. + * + * do_type_query() and bittorrent_query_callback() pass this function + * as a ::done_handler_T to add_dlg_ok_button(). + * + * @bug FIXME: We need to modify this function to take frame data * instead, as we want to use this function for frames as well (now, * when frame has content type text/plain, it is ignored and displayed - * as HTML). */ + * as HTML). + * + * @relates type_query */ void tp_display(struct type_query *type_query) { @@ -1086,6 +1380,14 @@ tp_display(struct type_query *type_query) done_type_query(type_query); } +/** The user chose "Open" when asked what to do with a file. + * Or an external handler was found and it has been configured + * to run without asking. + * + * do_type_query() passes this function as a ::done_handler_T to + * add_dlg_ok_button(). + * + * @relates type_query */ static void tp_open(struct type_query *type_query) { @@ -1119,6 +1421,13 @@ tp_open(struct type_query *type_query) } +/*! Ask the user what to do with a file. + * + * This function does not support BitTorrent downloads. + * For those, query_bittorrent_dialog() must be called instead. + * setup_download_handler() takes care of this. + * + * @relates type_query */ static void do_type_query(struct type_query *type_query, unsigned char *ct, struct mime_handler *handler) { @@ -1211,7 +1520,7 @@ do_type_query(struct type_query *type_query, unsigned char *ct, struct mime_hand 0, 0, NULL, MAX_STR_LEN, field, NULL); type_query->external_handler = field; - add_dlg_radio(dlg, _("Block the terminal", term), 0, 0, &type_query->block); + add_dlg_checkbox(dlg, _("Block the terminal", term), &type_query->block); selected_widget = 3; } else if (handler) { @@ -1300,6 +1609,7 @@ struct { { NULL, 1 }, }; +/*! @relates type_query */ int setup_download_handler(struct session *ses, struct download *loading, struct cache_entry *cached, int frame) diff --git a/src/session/download.h b/src/session/download.h index 4449dd49..baec17d5 100644 --- a/src/session/download.h +++ b/src/session/download.h @@ -20,11 +20,22 @@ struct download; typedef void (download_callback_T)(struct download *, void *); -typedef uint32_t download_flags_T; -enum download_flag { - DOWNLOAD_START = 0, - DOWNLOAD_RESUME = 1, - DOWNLOAD_EXTERNAL = 2, +/** Flags controlling how to download a file. This is a bit mask. + * Unrecognized bits should be preserved and ignored. */ +enum download_flags { + /** Downloading cannot be resumed; do not offer such an option + * to the user. All bits clear. */ + DOWNLOAD_RESUME_DISABLED = 0, + + /** Downloading can be resumed. This is the usual value. */ + DOWNLOAD_RESUME_ALLOWED = 1, + + /** The user wants to resume downloading. This must not occur + * without #DOWNLOAD_RESUME_ALLOWED. */ + DOWNLOAD_RESUME_SELECTED = 2, + + /** The file will be opened in an external handler. */ + DOWNLOAD_EXTERNAL = 4 }; struct download { @@ -46,16 +57,60 @@ struct download { enum connection_priority pri; }; +/** The user has navigated to a resource that ELinks does not display + * automatically because of its MIME type, and ELinks is asking what + * to do. + * + * These structures are kept in the session.type_queries list, and + * destroy_session() calls done_type_query() to destroy them too. */ struct type_query { LIST_HEAD(struct type_query); + + /** After ELinks has downloaded enough of the resource to see + * that a type query is needed, it moves the download here and + * continues it while the user decides what to do. */ struct download download; + + /** Cache entry loaded from #uri. Apparently used only for + * displaying the header. */ struct cache_entry *cached; + + /** The session in which the user navigated to #uri. The + * type_query is in the session.type_queries list of this + * session. */ struct session *ses; + + /** The URI of the resource about which ELinks is asking. + * This reference must be released with done_uri(). */ struct uri *uri; + + /** The name of the frame in which the user navigated to #uri. + * If the user chooses to display the resource, it goes into + * this frame. This string must be freed with mem_free(). */ unsigned char *target_frame; + + /** Command line for an external handler, to be run when the + * download finishes. When ELinks displays the type query, + * it copies this from mime_handler.program of the default + * handler of the type. The user can then edit the string. + * This string must be freed with mem_free(). */ unsigned char *external_handler; + + /** Whether the external handler is going to use the terminal. + * When ELinks displays the type query, it copies this from + * mime_handler.block of the default handler of the type. + * The user can then change the flag with a checkbox. */ int block; + + /** Whether the resource was generated by ELinks running + * a local CGI program. If the user chooses to open the + * resource with an external handler, ELinks normally saves + * the resource to a temporary file and passes the name of + * that to the external handler. However, if the resource is + * from a "file" URI that does not refer to a local CGI, then + * Elinks need not copy the file. */ unsigned int cgi:1; + /* int frame; */ }; @@ -116,12 +171,37 @@ int download_is_progressing(struct download *download); int are_there_downloads(void); +/** Type of the callback function that will be called when the file + * has been opened, or when it is known that the file will not be + * opened. + * + * @param term + * The terminal on which the callback should display any windows. + * Comes directly from the @a term argument of create_download_file(). + * + * @param fd + * A file descriptor to the opened file, or -1 if the file will not be + * opened. If the @a real_file argument of create_download_file() + * was not NULL, the callback may read the name of this file from + * *@a real_file. + * + * @param data + * A pointer to any data that the callback cares about. + * Comes directly from the @a data argument of create_download_file(). + * + * @param flags + * The same as the @a flags argument of create_download_file(), + * except the ::DOWNLOAD_RESUME_SELECTED bit will be changed to match + * what the user chose. + * + * @relates cdf_hop */ +typedef void cdf_callback_T(struct terminal *term, int fd, + void *data, enum download_flags flags); + void start_download(void *, unsigned char *); void resume_download(void *, unsigned char *); void create_download_file(struct terminal *, unsigned char *, unsigned char **, - download_flags_T, - void (*)(struct terminal *, int, void *, download_flags_T), - void *); + enum download_flags, cdf_callback_T *, void *); void abort_all_downloads(void); void destroy_downloads(struct session *); diff --git a/src/session/session.h b/src/session/session.h index 52248b22..a37cc170 100644 --- a/src/session/session.h +++ b/src/session/session.h @@ -164,6 +164,17 @@ struct session { struct document_view *doc_view; LIST_OF(struct document_view) scrn_frames; + /** The URI from which the next start_download() or resume_download() + * call should download, or NULL if no such call is pending. + * + * When the user requests a download, one of those functions + * is given as a callback to query_file(), which asks the user + * where to save the downloaded file. The URI cannot be given + * to the callback as a parameter because query_file() + * supports only one void * parameter for the callback and + * that one is already used for the struct session *. + * Instead, the URI is saved here before the query_file() + * call. */ struct uri *download_uri; /** The URI which is the referrer to the current loaded document diff --git a/src/viewer/text/view.c b/src/viewer/text/view.c index cf19e418..3a031b33 100644 --- a/src/viewer/text/view.c +++ b/src/viewer/text/view.c @@ -1617,8 +1617,11 @@ save_as(struct session *ses, struct document_view *doc_view, int magic) return FRAME_EVENT_OK; } +/*! save_formatted() passes this function as a ::cdf_callback_T to + * create_download_finish(). */ static void -save_formatted_finish(struct terminal *term, int h, void *data, download_flags_T flags) +save_formatted_finish(struct terminal *term, int h, + void *data, enum download_flags flags) { struct document *document = data; @@ -1645,7 +1648,8 @@ save_formatted(void *data, unsigned char *file) assert(doc_view && doc_view->document); if_assert_failed return; - create_download_file(ses->tab->term, file, NULL, DOWNLOAD_START, + create_download_file(ses->tab->term, file, NULL, + DOWNLOAD_RESUME_DISABLED, save_formatted_finish, doc_view->document); }