From c2bb7956c234af4babe33f84628dff9e869ba2e0 Mon Sep 17 00:00:00 2001 From: Guy Harris Date: Tue, 22 May 2012 22:17:57 +0000 Subject: [PATCH] "Save As" always saves everything and, when the save is done, makes the new file the current file, as is the case in most if not all other GUI applications. A new "Export Specified Packets" menu option allows you to specify which packets to write out, with the default being the displayed packets (and those on which the displayed packets depend for, e.g. reassembly), and never makes the resulting file the current file. The two operations are conceptually distinct. Lumping them into one menu item, with the default for "Save As" being "displayed packets only" and thus making it behave like the latter operation, was causing some confusion; see, for example, bug 6640. Make the dialog popped up if you try to "Save As" or "Export Specified Packets" on top of an existing file ask the "do you want to do this?" question in the main part of the message, and note in the secondary text that doing that will overwrite what's in the file; that matches what TextEdit on OS X and the GNOME text editor say. svn path=/trunk/; revision=42792 --- file.c | 209 +++++++++++++++------- file.h | 24 ++- ui/gtk/capture_dlg.c | 2 +- ui/gtk/capture_file_dlg.c | 368 +++++++++++++++++++++++++++++++------- ui/gtk/capture_file_dlg.h | 32 ++-- ui/gtk/drag_and_drop.c | 2 +- ui/gtk/file_import_dlg.c | 2 +- ui/gtk/main.c | 5 +- ui/gtk/main_menubar.c | 20 ++- ui/gtk/new_packet_list.c | 2 +- 10 files changed, 506 insertions(+), 160 deletions(-) diff --git a/file.c b/file.c index a1272a834b..fa6decd9a1 100644 --- a/file.c +++ b/file.c @@ -3806,13 +3806,14 @@ cf_can_save_as(capture_file *cf) } static cf_status_t -cf_save_packets(capture_file *cf, const char *fname, packet_range_t *range, - guint save_format, gboolean compressed, gboolean do_overwrite) +cf_save_packets(capture_file *cf, const char *fname, guint save_format, + gboolean compressed, gboolean do_overwrite) { gchar *fname_new = NULL; int err; gboolean do_copy; wtap_dumper *pdh; + packet_range_t range; save_callback_args_t callback_args; cf_callback_invoke(cf_cb_file_save_started, (gpointer)fname); @@ -3843,14 +3844,10 @@ cf_save_packets(capture_file *cf, const char *fname, packet_range_t *range, } } - packet_range_process_init(range); - - if (packet_range_process_all(range) && save_format == cf->cd_t - && !cf->unsaved_changes) { - /* We're not filtering packets, and we're saving it in the format - it's already in, and there are no changes we have in memory - that aren't saved to the file, so we can just move or copy the - raw data. */ + if (save_format == cf->cd_t && !cf->unsaved_changes) { + /* We're saving in the format it's already in, and there are no + changes we have in memory that aren't saved to the file, so + we can just move or copy the raw data. */ if (cf->is_tempfile) { /* The file being saved is a temporary file from a live @@ -3905,12 +3902,11 @@ cf_save_packets(capture_file *cf, const char *fname, packet_range_t *range, goto fail; } } else { - /* Either we're filtering packets, or we're saving in a different - format, or we're saving changes, such as added, modified, or - removed comments, that haven't yet been written to the - underlying file; we can't do that by copying or moving the - capture file, we have to do it by writing the packets out in - Wiretap. */ + /* Either we're saving in a different format or we're saving changes, + such as added, modified, or removed comments, that haven't yet + been written to the underlying file; we can't do that by copying + or moving the capture file, we have to do it by writing the packets + out in Wiretap. */ wtapng_section_t *shb_hdr = NULL; wtapng_iface_descriptions_t *idb_inf = NULL; @@ -3943,23 +3939,15 @@ cf_save_packets(capture_file *cf, const char *fname, packet_range_t *range, /* Add address resolution */ wtap_dump_set_addrinfo_list(pdh, get_addrinfo_list()); - /* XXX - we let the user save a subset of the packets. + /* Create a packet range that's set to the default "save everything" + state. */ + packet_range_init(&range); - If we do that, should we make that file the current file? If so, - it means we can no longer get at the other packets. What does - NetMon do? */ - - /* Iterate through the list of packets, processing the packets we were - told to process. - - XXX - we've already called "packet_range_process_init(range)", but - "process_specified_packets()" will do it again. Fortunately, - that's harmless in this case, as we haven't done anything to - "range" since we initialized it. */ + /* Iterate through the list of packets, processing all the packets. */ callback_args.pdh = pdh; callback_args.fname = fname; callback_args.file_type = save_format; - switch (process_specified_packets(cf, range, "Saving", "selected packets", + switch (process_specified_packets(cf, &range, "Saving", "selected packets", TRUE, save_packet, &callback_args)) { case PSP_FINISHED: @@ -4006,44 +3994,41 @@ cf_save_packets(capture_file *cf, const char *fname, packet_range_t *range, cf_callback_invoke(cf_cb_file_save_finished, NULL); - if (packet_range_process_all(range)) { - /* We saved the entire capture, not just some packets from it. - Open and read the file we saved it to. + /* Open and read the file we saved to. - XXX - this is somewhat of a waste; we already have the - packets, all this gets us is updated file type information - (which we could just stuff into "cf"), and having the new - file be the one we have opened and from which we're reading - the data, and it means we have to spend time opening and - reading the file, which could be a significant amount of - time if the file is large. + XXX - this is somewhat of a waste; we already have the + packets, all this gets us is updated file type information + (which we could just stuff into "cf"), and having the new + file be the one we have opened and from which we're reading + the data, and it means we have to spend time opening and + reading the file, which could be a significant amount of + time if the file is large. - If the capture-file-writing code were to return the - seek offset of each packet it writes, we could save that - in the frame_data structure for the frame, and just open - the file without reading it again. */ - cf->unsaved_changes = FALSE; + If the capture-file-writing code were to return the + seek offset of each packet it writes, we could save that + in the frame_data structure for the frame, and just open + the file without reading it again. */ + cf->unsaved_changes = FALSE; - if ((cf_open(cf, fname, FALSE, &err)) == CF_OK) { - /* XXX - report errors if this fails? - What should we return if it fails or is aborted? */ + if ((cf_open(cf, fname, FALSE, &err)) == CF_OK) { + /* XXX - report errors if this fails? + What should we return if it fails or is aborted? */ - switch (cf_read(cf, TRUE)) { + switch (cf_read(cf, TRUE)) { - case CF_READ_OK: - case CF_READ_ERROR: - /* Just because we got an error, that doesn't mean we were unable - to read any of the file; we handle what we could get from the - file. */ - break; + case CF_READ_OK: + case CF_READ_ERROR: + /* Just because we got an error, that doesn't mean we were unable + to read any of the file; we handle what we could get from the + file. */ + break; - case CF_READ_ABORTED: - /* The user bailed out of re-reading the capture file; the - capture file has been closed - just return (without - changing any menu settings; "cf_close()" set them - correctly for the "no capture file open" state). */ - break; - } + case CF_READ_ABORTED: + /* The user bailed out of re-reading the capture file; the + capture file has been closed - just return (without + changing any menu settings; "cf_close()" set them + correctly for the "no capture file open" state). */ + break; } } return CF_OK; @@ -4056,18 +4041,104 @@ fail: cf_status_t cf_save(capture_file *cf, const char *fname, guint save_format, gboolean compressed) { - packet_range_t range; - - /* This only does a "save all", so we have our own packet_range_t - structure, which is set to the default "save everything" state. */ - packet_range_init(&range); - return cf_save_packets(cf, fname, &range, save_format, compressed, TRUE); + return cf_save_packets(cf, fname, save_format, compressed, TRUE); } cf_status_t -cf_save_as(capture_file *cf, const char *fname, packet_range_t *range, guint save_format, gboolean compressed) +cf_save_as(capture_file *cf, const char *fname, guint save_format, gboolean compressed) { - return cf_save_packets(cf, fname, range, save_format, compressed, FALSE); + return cf_save_packets(cf, fname, save_format, compressed, FALSE); +} + +cf_status_t +cf_export_specified_packets(capture_file *cf, const char *fname, + packet_range_t *range, guint save_format, + gboolean compressed) +{ + int err; + wtap_dumper *pdh; + save_callback_args_t callback_args; + wtapng_section_t *shb_hdr = NULL; + wtapng_iface_descriptions_t *idb_inf = NULL; + + cf_callback_invoke(cf_cb_file_save_started, (gpointer)fname); + + if (file_exists(fname)) { + /* don't write over an existing file. */ + /* this should've been already checked by our caller, just to be sure... */ + if (file_exists(fname)) { + simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, + "%sCapture file: \"%s\" already exists!%s\n\n" + "Please choose a different filename.", + simple_dialog_primary_start(), + fname, + simple_dialog_primary_end()); + goto fail; + } + } + + packet_range_process_init(range); + + /* We're writing out specified packets from the specified capture + file to another file. Even if all captured packets are to be + written, don't special-case the operation - read each packet + and then write it out if it's one of the specified ones. */ + + shb_hdr = wtap_file_get_shb_info(cf->wth); + idb_inf = wtap_file_get_idb_info(cf->wth); + + pdh = wtap_dump_open_ng(fname, save_format, cf->lnk_t, cf->snap, + compressed, shb_hdr, idb_inf, &err); + g_free(idb_inf); + idb_inf = NULL; + + if (pdh == NULL) { + cf_open_failure_alert_box(fname, err, NULL, TRUE, save_format); + goto fail; + } + + /* Add address resolution */ + wtap_dump_set_addrinfo_list(pdh, get_addrinfo_list()); + + /* Iterate through the list of packets, processing the packets we were + told to process. + + XXX - we've already called "packet_range_process_init(range)", but + "process_specified_packets()" will do it again. Fortunately, + that's harmless in this case, as we haven't done anything to + "range" since we initialized it. */ + callback_args.pdh = pdh; + callback_args.fname = fname; + callback_args.file_type = save_format; + switch (process_specified_packets(cf, range, "Writing", "specified packets", + TRUE, save_packet, &callback_args)) { + + case PSP_FINISHED: + /* Completed successfully. */ + break; + + case PSP_STOPPED: + /* The user decided to abort the writing. + XXX - remove the output file? */ + break; + + case PSP_FAILED: + /* Error while writing. */ + wtap_dump_close(pdh, &err); + goto fail; + } + + if (!wtap_dump_close(pdh, &err)) { + cf_close_failure_alert_box(fname, err); + goto fail; + } + + cf_callback_invoke(cf_cb_file_save_finished, NULL); + return CF_OK; + +fail: + cf_callback_invoke(cf_cb_file_save_failed, NULL); + return CF_ERROR; } static void diff --git a/file.h b/file.h index 6371b611c4..ea6b908e90 100644 --- a/file.h +++ b/file.h @@ -208,17 +208,33 @@ gboolean cf_can_save_as(capture_file *cf); cf_status_t cf_save(capture_file * cf, const char *fname, guint save_format, gboolean compressed); /** - * Save a capture file (or a range of it). Fails if the specified - * pathname already exists. + * Save all packets in a capture file to a new file, and, if that succeeds, + * make that file the current capture file. Fails if the specified + * target file already exists. * * @param cf the capture file to save to * @param fname the filename to save to - * @param range the range of packets to save * @param save_format the format of the file to save (libpcap, ...) * @param compressed whether to gzip compress the file * @return one of cf_status_t */ -cf_status_t cf_save_as(capture_file * cf, const char *fname, packet_range_t *range, guint save_format, gboolean compressed); +cf_status_t cf_save_as(capture_file * cf, const char *fname, guint save_format, gboolean compressed); + +/** + * Export some or all packets from a capture file to a new file. Fails if + * the specified target file already exists. + * + * @param cf the capture file to write to + * @param fname the filename to write to + * @param range the range of packets to write + * @param save_format the format of the file to write (libpcap, ...) + * @param compressed whether to gzip compress the file + * @return one of cf_status_t + */ +cf_status_t cf_export_specified_packets(capture_file *cf, const char *fname, + packet_range_t *range, + guint save_format, + gboolean compressed); /** * Get a displayable name of the capture file. diff --git a/ui/gtk/capture_dlg.c b/ui/gtk/capture_dlg.c index 86d5a8fcd3..fbcf7f6a5d 100644 --- a/ui/gtk/capture_dlg.c +++ b/ui/gtk/capture_dlg.c @@ -4201,7 +4201,7 @@ capture_start_answered_cb(gpointer dialog _U_, gint btn, gpointer data) switch(btn) { case(ESD_BTN_SAVE): /* save file first */ - file_save_as_cmd(after_save_capture_dialog, data, FALSE); + file_save_as_cmd(after_save_capture_dialog, data); break; case(ESD_BTN_DONT_SAVE): /* XXX - unlink old file? */ diff --git a/ui/gtk/capture_file_dlg.c b/ui/gtk/capture_file_dlg.c index 8bc09c21dd..a826b3cc4c 100644 --- a/ui/gtk/capture_file_dlg.c +++ b/ui/gtk/capture_file_dlg.c @@ -85,6 +85,9 @@ static void file_merge_destroy_cb(GtkWidget *win, gpointer user_data); static void file_save_as_select_file_type_cb(GtkWidget *w, gpointer data); static void file_save_as_ok_cb(GtkWidget *w, gpointer fs); static void file_save_as_destroy_cb(GtkWidget *win, gpointer user_data); +static void file_export_specified_packets_cb(GtkWidget *w, gpointer fs); +static void file_export_specified_packets_ok_cb(GtkWidget *w, gpointer fs); +static void file_export_specified_packets_destroy_cb(GtkWidget *win, gpointer user_data); static void file_color_import_ok_cb(GtkWidget *w, gpointer filter_list); static void file_color_import_destroy_cb(GtkWidget *win, gpointer user_data); static void file_color_export_ok_cb(GtkWidget *w, gpointer filter_list); @@ -120,6 +123,14 @@ static void set_file_type_list(GtkWidget *combo_box, int default_file_type); */ static GtkWidget *file_save_as_w; +/* + * Keep a static pointer to the current "Export Specified Packets" window, if + * any, so that if somebody tries to do "File:Export Specified Packets" + * while there's already a "Export Specified Packets" window up, we just pop + * up the existing one, rather than creating a new one. + */ +static GtkWidget *file_export_specified_packets_w; + /* XXX - can we make these not be static? */ static packet_range_t range; static gboolean color_selected; @@ -587,7 +598,7 @@ static void file_open_answered_cb(gpointer dialog _U_, gint btn, gpointer data) switch(btn) { case(ESD_BTN_SAVE): /* save file first */ - file_save_as_cmd(after_save_open_dialog, data, FALSE); + file_save_as_cmd(after_save_open_dialog, data); break; case(ESD_BTN_DONT_SAVE): cf_close(&cfile); @@ -897,7 +908,7 @@ static void file_merge_answered_cb(gpointer dialog _U_, gint btn, gpointer data switch(btn) { case(ESD_BTN_OK): /* save file first */ - file_save_as_cmd(after_save_merge_dialog, data, FALSE); + file_save_as_cmd(after_save_merge_dialog, data); break; case(ESD_BTN_CANCEL): break; @@ -1065,7 +1076,7 @@ static void file_close_answered_cb(gpointer dialog _U_, gint btn, gpointer data switch(btn) { case(ESD_BTN_SAVE): /* save file first */ - file_save_as_cmd(after_save_close_file, NULL, FALSE); + file_save_as_cmd(after_save_close_file, NULL); break; case(ESD_BTN_DONT_SAVE): cf_close(&cfile); @@ -1103,7 +1114,7 @@ file_save_cmd_cb(GtkWidget *w _U_, gpointer data _U_) { if (cfile.is_tempfile) { /* This is a temporary capture file, so saving it means saving it to a permanent file. */ - file_save_as_cmd(after_save_no_action, NULL, FALSE); + file_save_as_cmd(after_save_no_action, NULL); } else { if (cfile.unsaved_changes) { /* This is not a temporary capture file, but it has unsaved @@ -1159,21 +1170,6 @@ file_save_as_select_file_type_cb(GtkWidget *w, gpointer data _U_) gtk_widget_set_sensitive(compressed_cb, wtap_dump_can_compress(new_file_type)); } -/* - * Update various dynamic parts of the range controls; called from outside - * the file dialog code whenever the packet counts change. - */ -void -file_save_update_dynamics(void) -{ - if (file_save_as_w == NULL) { - /* We don't currently have a "Save As..." dialog box up. */ - return; - } - - range_update_dynamics(range_tb); -} - action_after_save_e action_after_save_g; gpointer action_after_save_data_g; @@ -1197,12 +1193,12 @@ file_save_cmd(action_after_save_e action_after_save, gpointer action_after_save_ } void -file_save_as_cmd(action_after_save_e action_after_save, gpointer action_after_save_data, gboolean save_only_displayed) +file_save_as_cmd(action_after_save_e action_after_save, gpointer action_after_save_data) { #if _WIN32 win32_save_as_file(GDK_WINDOW_HWND(gtk_widget_get_window(top_level)), action_after_save, action_after_save_data); #else /* _WIN32 */ - GtkWidget *main_vb, *ft_hb, *ft_lb, *ft_combo_box, *range_fr, *compressed_cb; + GtkWidget *main_vb, *ft_hb, *ft_lb, *ft_combo_box, *compressed_cb; if (file_save_as_w != NULL) { /* There's already an "Save Capture File As" dialog box; reactivate it. */ @@ -1210,12 +1206,7 @@ file_save_as_cmd(action_after_save_e action_after_save, gpointer action_after_sa return; } - /* Default to saving all packets, in the file's current format. */ - - /* init the packet range */ - packet_range_init(&range); - range.process_filtered = save_only_displayed; - range.include_dependents = TRUE; + /* Default to saving in the file's current format. */ /* build the file selection */ file_save_as_w = file_selection_new ("Wireshark: Save Capture File As", @@ -1233,17 +1224,8 @@ file_save_as_cmd(action_after_save_e action_after_save, gpointer action_after_sa file_selection_set_extra_widget(file_save_as_w, main_vb); gtk_widget_show(main_vb); - /*** Packet Range frame ***/ - range_fr = gtk_frame_new("Packet Range"); - gtk_box_pack_start(GTK_BOX(main_vb), range_fr, FALSE, FALSE, 0); - gtk_widget_show(range_fr); - - /* range table */ - range_tb = range_new(&range, TRUE); - gtk_container_add(GTK_CONTAINER(range_fr), range_tb); - gtk_widget_show(range_tb); - /* File type row */ + range_tb = NULL; ft_hb = gtk_hbox_new(FALSE, 3); gtk_container_add(GTK_CONTAINER(main_vb), ft_hb); gtk_widget_show(ft_hb); @@ -1260,9 +1242,6 @@ file_save_as_cmd(action_after_save_e action_after_save, gpointer action_after_sa gtk_widget_show(ft_combo_box); g_object_set_data(G_OBJECT(file_save_as_w), E_FILE_TYPE_COMBO_BOX_KEY, ft_combo_box); - /* dynamic values in the range frame */ - range_update_dynamics(range_tb); - /* compressed */ compressed_cb = gtk_check_button_new_with_label("Compress with gzip"); gtk_container_add(GTK_CONTAINER(ft_hb), compressed_cb); @@ -1292,10 +1271,9 @@ file_save_as_cmd(action_after_save_e action_after_save, gpointer action_after_sa void file_save_as_cmd_cb(GtkWidget *w _U_, gpointer data _U_) { - file_save_as_cmd(after_save_no_action, NULL, TRUE); + file_save_as_cmd(after_save_no_action, NULL); } - /* all tests ok, we only have to save the file */ /* (and probably continue with a pending operation) */ static void @@ -1321,9 +1299,8 @@ file_save_as_cb(GtkWidget *w _U_, gpointer fs) { /* ask in a dialog if that's intended */ /* currently, cf_save_as() will simply deny it */ - /* Write out the packets (all, or only the ones from the current - range) to the file with the specified name. */ - if (cf_save_as(&cfile, cf_name, &range, file_type, + /* Write out all the packets to the file with the specified name. */ + if (cf_save_as(&cfile, cf_name, file_type, gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compressed_cb))) != CF_OK) { /* The write failed; don't dismiss the open dialog box, just leave it around so that the user can, after they @@ -1408,7 +1385,7 @@ static void file_save_as_exists_answered_cb(gpointer dialog _U_, gint btn, gpoin } -/* user pressed "Save" dialog "Ok" button */ +/* user pressed "Save As" dialog "Ok" button */ static void file_save_as_ok_cb(GtkWidget *w _U_, gpointer fs) { gchar *cf_name; @@ -1427,21 +1404,6 @@ file_save_as_ok_cb(GtkWidget *w _U_, gpointer fs) { return; } - /* Check whether the range is valid. */ - if (!range_check_validity(&range)) { - /* The range isn't valid; don't dismiss the open dialog box, - just leave it around so that the user can, after they - dismiss the alert box popped up for the error, try again. */ - g_free(cf_name); - /* XXX - as we cannot start a new event loop (using gtk_dialog_run()), - * as this will prevent the user from closing the now existing error - * message, simply close the dialog (this is the best we can do here). */ - if (file_save_as_w) - window_destroy(GTK_WIDGET (fs)); - - return; - } - /* * Check that the from file is not the same as to file * We do it here so we catch all cases ... @@ -1474,10 +1436,13 @@ file_save_as_ok_cb(GtkWidget *w _U_, gpointer fs) { return; } - /* the file exists, ask the user to remove it first */ + /* The file exists. Ask the user if they want to overwrite it. */ dialog = simple_dialog(ESD_TYPE_CONFIRMATION, ESD_BTNS_OK_CANCEL, - "%sA file named \"%s\" already exists.%s\n\n" - "Do you want to replace it with the capture you are saving?", + "%s" + "A file named \"%s\" already exists. Do you want to replace it?" + "%s\n\n" + "A file or folder with the same name already exists in that folder. " + "Replacing it will overwrite its current contents.", simple_dialog_primary_start(), cf_name, simple_dialog_primary_end()); simple_dialog_set_cb(dialog, file_save_as_exists_answered_cb, fs); @@ -1498,6 +1463,281 @@ file_save_as_destroy_cb(GtkWidget *win _U_, gpointer user_data _U_) file_save_as_w = NULL; } +/* + * Update various dynamic parts of the range controls; called from outside + * the file dialog code whenever the packet counts change. + */ +void +file_export_specified_packets_update_dynamics(void) +{ + if (file_export_specified_packets_w == NULL) { + /* We don't currently have a "Export Specified Packets..." dialog box up. */ + return; + } + + range_update_dynamics(range_tb); +} + +void +file_export_specified_packets_cmd_cb(GtkWidget *widget _U_, gpointer data _U_) +{ +#if _WIN32 + win32_save_as_file(GDK_WINDOW_HWND(gtk_widget_get_window(top_level)), action_after_save, action_after_save_data); +#else /* _WIN32 */ + GtkWidget *main_vb, *ft_hb, *ft_lb, *ft_combo_box, *range_fr, *compressed_cb; + + if (file_export_specified_packets_w != NULL) { + /* There's already an "Export Specified Packets" dialog box; reactivate it. */ + reactivate_window(file_export_specified_packets_w); + return; + } + + /* Default to writing out all displayed packets, in the file's current format. */ + + /* init the packet range */ + packet_range_init(&range); + range.process_filtered = TRUE; + range.include_dependents = TRUE; + + /* build the file selection */ + file_export_specified_packets_w = file_selection_new ("Wireshark: Export Specified Packets", + FILE_SELECTION_SAVE); + + /* Container for each row of widgets */ + + main_vb = gtk_vbox_new(FALSE, 5); + gtk_container_set_border_width(GTK_CONTAINER(main_vb), 5); + file_selection_set_extra_widget(file_export_specified_packets_w, main_vb); + gtk_widget_show(main_vb); + + /*** Packet Range frame ***/ + range_fr = gtk_frame_new("Packet Range"); + gtk_box_pack_start(GTK_BOX(main_vb), range_fr, FALSE, FALSE, 0); + gtk_widget_show(range_fr); + + /* range table */ + range_tb = range_new(&range, TRUE); + gtk_container_add(GTK_CONTAINER(range_fr), range_tb); + gtk_widget_show(range_tb); + + /* File type row */ + ft_hb = gtk_hbox_new(FALSE, 3); + gtk_container_add(GTK_CONTAINER(main_vb), ft_hb); + gtk_widget_show(ft_hb); + + ft_lb = gtk_label_new("File type:"); + gtk_box_pack_start(GTK_BOX(ft_hb), ft_lb, FALSE, FALSE, 0); + gtk_widget_show(ft_lb); + + ft_combo_box = ws_combo_box_new_text_and_pointer(); + + /* Generate the list of file types we can save. */ + set_file_type_list(ft_combo_box, cfile.cd_t); + gtk_box_pack_start(GTK_BOX(ft_hb), ft_combo_box, FALSE, FALSE, 0); + gtk_widget_show(ft_combo_box); + g_object_set_data(G_OBJECT(file_export_specified_packets_w), E_FILE_TYPE_COMBO_BOX_KEY, ft_combo_box); + + /* dynamic values in the range frame */ + range_update_dynamics(range_tb); + + /* compressed */ + compressed_cb = gtk_check_button_new_with_label("Compress with gzip"); + gtk_container_add(GTK_CONTAINER(ft_hb), compressed_cb); + /* XXX - disable output compression for now, as this doesn't work with the + * current optimization to simply copy a capture file if it's using the same + * encapsulation ... */ + /* the rest of the implementation is just working fine :-( */ +#if 0 + gtk_widget_show(compressed_cb); +#endif + g_object_set_data(G_OBJECT(file_export_specified_packets_w), E_COMPRESSED_CB_KEY, compressed_cb); + /* Ok: now "select" the default filetype which invokes file_save_as_select_file_type_cb */ + g_signal_connect(ft_combo_box, "changed", G_CALLBACK(file_save_as_select_file_type_cb), NULL); + ws_combo_box_set_active(GTK_COMBO_BOX(ft_combo_box), 0); + + g_signal_connect(file_export_specified_packets_w, "destroy", + G_CALLBACK(file_export_specified_packets_destroy_cb), NULL); + + if (gtk_dialog_run(GTK_DIALOG(file_export_specified_packets_w)) == GTK_RESPONSE_ACCEPT) { + file_export_specified_packets_ok_cb(file_export_specified_packets_w, file_export_specified_packets_w); + } else { + window_destroy(file_export_specified_packets_w); + } +#endif /* _WIN32 */ +} + +static void +file_export_specified_packets_exists_answered_cb(gpointer dialog _U_, gint btn, gpointer data) +{ + gchar *cf_name; + + cf_name = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(data)); + + switch(btn) { + case(ESD_BTN_OK): + /* save file */ + ws_unlink(cf_name); + file_export_specified_packets_cb(NULL, data); + break; + case(ESD_BTN_CANCEL): + /* XXX - as we cannot start a new event loop (using gtk_dialog_run()), + * as this will prevent the user from closing the now existing error + * message, simply close the dialog (this is the best we can do here). */ + if (file_export_specified_packets_w) + window_destroy(file_export_specified_packets_w); + break; + default: + g_assert_not_reached(); + } + g_free(cf_name); +} + +/* user pressed "Export Specified Packets" dialog "Ok" button */ +static void +file_export_specified_packets_ok_cb(GtkWidget *w _U_, gpointer fs) { + gchar *cf_name; + gpointer dialog; + + cf_name = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(fs)); + + /* Perhaps the user specified a directory instead of a file. + Check whether they did. */ + if (test_for_directory(cf_name) == EISDIR) { + /* It's a directory - set the file selection box to display that + directory, and leave the selection box displayed. */ + set_last_open_dir(cf_name); + g_free(cf_name); + file_selection_set_current_folder(fs, get_last_open_dir()); + return; + } + + /* Check whether the range is valid. */ + if (!range_check_validity(&range)) { + /* The range isn't valid; don't dismiss the open dialog box, + just leave it around so that the user can, after they + dismiss the alert box popped up for the error, try again. */ + g_free(cf_name); + /* XXX - as we cannot start a new event loop (using gtk_dialog_run()), + * as this will prevent the user from closing the now existing error + * message, simply close the dialog (this is the best we can do here). */ + if (file_save_as_w) + window_destroy(GTK_WIDGET (fs)); + + return; + } + + /* + * Check that the from file is not the same as to file + * We do it here so we catch all cases ... + * Unfortunately, the file requester gives us an absolute file + * name and the read file name may be relative (if supplied on + * the command line). From Joerg Mayer. + */ + if (files_identical(cfile.filename, cf_name)) { + simple_dialog(ESD_TYPE_ERROR, ESD_BTN_OK, + "%sCapture file: \"%s\" identical to loaded file!%s\n\n" + "Please choose a different filename.", + simple_dialog_primary_start(), cf_name, simple_dialog_primary_end()); + g_free(cf_name); + /* XXX - as we cannot start a new event loop (using gtk_dialog_run()), + * as this will prevent the user from closing the now existing error + * message, simply close the dialog (this is the best we can do here). */ + if (file_export_specified_packets_w) + window_destroy(GTK_WIDGET (fs)); + + return; + } + + /* don't show the dialog while saving (or asking) */ + gtk_widget_hide(GTK_WIDGET (fs)); + + /* it the file doesn't exist, simply try to write the packets to it */ + if (!file_exists(cf_name)) { + file_export_specified_packets_cb(NULL, fs); + g_free(cf_name); + return; + } + + /* The file exists. Ask the user if they want to overwrite it. */ + dialog = simple_dialog(ESD_TYPE_CONFIRMATION, ESD_BTNS_OK_CANCEL, + "%s" + "A file named \"%s\" already exists. Do you want to replace it?" + "%s\n\n" + "A file or folder with the same name already exists in that folder. " + "Replacing it will overwrite its current contents.", + simple_dialog_primary_start(), cf_name, simple_dialog_primary_end()); + simple_dialog_set_cb(dialog, file_export_specified_packets_exists_answered_cb, fs); + + g_free(cf_name); +} + +/* all tests ok, we only have to write out the packets */ +/* (and probably continue with a pending operation) */ +static void +file_export_specified_packets_cb(GtkWidget *w _U_, gpointer fs) { + GtkWidget *ft_combo_box; + GtkWidget *compressed_cb; + gchar *cf_name; + gchar *dirname; + gpointer ptr; + int file_type; + + cf_name = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(fs)); + + compressed_cb = (GtkWidget *)g_object_get_data(G_OBJECT(fs), E_COMPRESSED_CB_KEY); + ft_combo_box = (GtkWidget *)g_object_get_data(G_OBJECT(fs), E_FILE_TYPE_COMBO_BOX_KEY); + + if (! ws_combo_box_get_active_pointer(GTK_COMBO_BOX(ft_combo_box), &ptr)) { + g_assert_not_reached(); /* Programming error: somehow nothing is active */ + } + file_type = GPOINTER_TO_INT(ptr); + + /* XXX - if the user requests to save to an already existing filename, */ + /* ask in a dialog if that's intended */ + /* currently, cf_export_specified_packets() will simply deny it */ + + /* Write out the specified packets to the file with the specified name. */ + if (cf_export_specified_packets(&cfile, cf_name, &range, file_type, + gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(compressed_cb))) != CF_OK) { + /* The write failed; don't dismiss the open dialog box, + just leave it around so that the user can, after they + dismiss the alert box popped up for the error, try again. */ + g_free(cf_name); + /* XXX - as we cannot start a new event loop (using gtk_dialog_run()), + * as this will prevent the user from closing the now existing error + * message, simply close the dialog (this is the best we can do here). */ + if (file_export_specified_packets_w) + window_destroy(GTK_WIDGET (fs)); + return; + } + + /* The write succeeded; get rid of the file selection box. */ + /* cf_export_specified_packets() might already closed our dialog! */ + if (file_export_specified_packets_w) + window_destroy(GTK_WIDGET (fs)); + + /* Save the directory name for future file dialogs. + XXX - should there be separate ones for "Save As" and + "Export Specified Packets"? */ + dirname = get_dirname(cf_name); /* Overwrites cf_name */ + set_last_open_dir(dirname); + g_free(cf_name); +} + +void +file_export_specified_packets_destroy(void) +{ + if (file_export_specified_packets_w) + window_destroy(file_export_specified_packets_w); +} + +static void +file_export_specified_packets_destroy_cb(GtkWidget *win _U_, gpointer user_data _U_) +{ + /* Note that we no longer have a "Export Specified Packets" dialog box. */ + file_export_specified_packets_w = NULL; +} + /* Reload a file using the current read and display filters */ void file_reload_cmd_cb(GtkWidget *w _U_, gpointer data _U_) { diff --git a/ui/gtk/capture_file_dlg.h b/ui/gtk/capture_file_dlg.h index 3bea625102..e1473b7772 100644 --- a/ui/gtk/capture_file_dlg.h +++ b/ui/gtk/capture_file_dlg.h @@ -53,14 +53,17 @@ void file_save_cmd(action_after_save_e action_after_save, gpointer action_after_ * * @param action_after_save the action to take, when save completed * @param action_after_save_data data for action_after_save - * @param save_only_displayed Save only the displayed packets */ -void file_save_as_cmd(action_after_save_e action_after_save, gpointer action_after_save_data, gboolean save_only_displayed); +void file_save_as_cmd(action_after_save_e action_after_save, gpointer action_after_save_data); -/** Destroy the save as dialog. +/** Destroy the "Save As" dialog. */ void file_save_as_destroy(void); +/** Destroy the "Export Specified Packets" dialog. + */ +void file_export_specified_packets_destroy(void); + /** User requested the "Open" dialog box. * * @param widget parent widget @@ -96,6 +99,21 @@ void file_save_as_cmd_cb(GtkWidget *widget, gpointer data); */ void file_close_cmd_cb(GtkWidget *widget, gpointer data); +/** User requested the "Export Specified Packets" dialog box. + * + * @param widget parent widget + * @param data unused + */ +void file_export_specified_packets_cmd_cb(GtkWidget *widget, gpointer data); + +/* + * Set the "Save only marked packets" toggle button as appropriate for + * the current output file type and count of marked packets. + * Called when the "Export Specified Packets..." dialog box is created + * and when either the file type or the marked count changes. + */ +void file_export_specified_packets_update_dynamics(void); + /** User requested "Reload". * * @param widget parent widget @@ -117,12 +135,4 @@ void file_color_import_cmd_cb(GtkWidget *widget, gpointer data); */ void file_color_export_cmd_cb(GtkWidget *widget, gpointer data); -/* - * Set the "Save only marked packets" toggle button as appropriate for - * the current output file type and count of marked packets. - * Called when the "Save As..." dialog box is created and when either - * the file type or the marked count changes. - */ -void file_save_update_dynamics(void); - #endif /* capture_file_dlg.h */ diff --git a/ui/gtk/drag_and_drop.c b/ui/gtk/drag_and_drop.c index a11853d2b7..ecbe5bf476 100644 --- a/ui/gtk/drag_and_drop.c +++ b/ui/gtk/drag_and_drop.c @@ -294,7 +294,7 @@ dnd_save_file_answered_cb(gpointer dialog _U_, gint btn, gpointer data) switch(btn) { case(ESD_BTN_SAVE): /* save file first */ - file_save_as_cmd(after_save_open_dnd_file, data, FALSE); + file_save_as_cmd(after_save_open_dnd_file, data); break; case(ESD_BTN_DONT_SAVE): cf_close(&cfile); diff --git a/ui/gtk/file_import_dlg.c b/ui/gtk/file_import_dlg.c index 70174184a5..c525c81809 100644 --- a/ui/gtk/file_import_dlg.c +++ b/ui/gtk/file_import_dlg.c @@ -779,7 +779,7 @@ file_import_answered_cb(gpointer dialog _U_, gint btn, gpointer data) switch (btn) { case ESD_BTN_SAVE: /* save file first */ - file_save_as_cmd(after_save_no_action, NULL, FALSE); + file_save_as_cmd(after_save_no_action, NULL); break; case ESD_BTN_DONT_SAVE: cf_close(&cfile); diff --git a/ui/gtk/main.c b/ui/gtk/main.c index ec953f31b9..662b1fd70a 100644 --- a/ui/gtk/main.c +++ b/ui/gtk/main.c @@ -1102,7 +1102,7 @@ static void file_quit_answered_cb(gpointer dialog _U_, gint btn, gpointer data _ switch(btn) { case(ESD_BTN_SAVE): /* save file first */ - file_save_as_cmd(after_save_exit, NULL, FALSE); + file_save_as_cmd(after_save_exit, NULL); break; case(ESD_BTN_QUIT_DONT_SAVE): main_do_quit(); @@ -1419,10 +1419,11 @@ main_cf_cb_file_closing(capture_file *cf) gtk_window_set_position(GTK_WINDOW(close_dlg), GTK_WIN_POS_CENTER_ON_PARENT); } - /* Destroy all windows, which refer to the + /* Destroy all windows that refer to the capture file we're closing. */ destroy_packet_wins(); file_save_as_destroy(); + file_export_specified_packets_destroy(); /* Restore the standard title bar message. */ set_main_window_name("The Wireshark Network Analyzer"); diff --git a/ui/gtk/main_menubar.c b/ui/gtk/main_menubar.c index 6066fe7c05..68eaa9c7b3 100644 --- a/ui/gtk/main_menubar.c +++ b/ui/gtk/main_menubar.c @@ -1003,6 +1003,7 @@ static const char *ui_desc_menubar = " \n" " \n" " \n" +" \n" " \n" " \n" " \n" @@ -1445,6 +1446,7 @@ static const GtkActionEntry main_menu_bar_entries[] = { { "/File/SaveAs", GTK_STOCK_SAVE_AS, "Save _As...", "S", NULL, G_CALLBACK(file_save_as_cmd_cb) }, { "/File/Set", NULL, "File Set", NULL, NULL, NULL }, + { "/File/ExportSpecifiedPackets", NULL, "Export Specified Packets...", NULL, NULL, G_CALLBACK(file_export_specified_packets_cmd_cb) }, { "/File/ExportPacketDissections", NULL, "Export Packet Dissections", NULL, NULL, NULL }, { "/File/ExportSelectedPacketBytes", NULL, "Export Selected Packet _Bytes...", "H", NULL, G_CALLBACK(savehex_cb) }, { "/File/ExportSSLSessionKeys", NULL, "Export SSL Session Keys...", NULL, NULL, G_CALLBACK(savesslkeys_cb) }, @@ -4176,7 +4178,7 @@ static void menu_open_recent_file_answered_cb(gpointer dialog _U_, gint btn, gpo switch(btn) { case(ESD_BTN_YES): /* save file first */ - file_save_as_cmd(after_save_open_recent_file, data, FALSE); + file_save_as_cmd(after_save_open_recent_file, data); break; case(ESD_BTN_NO): cf_close(&cfile); @@ -4635,6 +4637,7 @@ set_menus_for_capture_file(capture_file *cf) set_menu_sensitivity(ui_manager_main_menubar, "/Menubar/FileMenu/Close", FALSE); set_menu_sensitivity(ui_manager_main_menubar, "/Menubar/FileMenu/Save", FALSE); set_menu_sensitivity(ui_manager_main_menubar, "/Menubar/FileMenu/SaveAs", FALSE); + set_menu_sensitivity(ui_manager_main_menubar, "/Menubar/FileMenu/ExportSpecifiedPackets", FALSE); set_menu_sensitivity(ui_manager_main_menubar, "/Menubar/FileMenu/ExportPacketDissections", FALSE); set_menu_sensitivity(ui_manager_main_menubar, "/Menubar/FileMenu/ExportSelectedPacketBytes", FALSE); set_menu_sensitivity(ui_manager_main_menubar, "/Menubar/FileMenu/ExportSSLSessionKeys", FALSE); @@ -4650,13 +4653,18 @@ set_menus_for_capture_file(capture_file *cf) set_menu_sensitivity(ui_manager_main_menubar, "/Menubar/FileMenu/Save", (cf->is_tempfile || cf->unsaved_changes)); /* - * "Save As..." works only if we can write the file out in at least - * one format (so we can save the whole file or just a subset) or - * if the file is a temporary file (so writing the whole file out - * with a raw data copy makes sense). + * "Save As..." should be available only if we have no unsaved + * changes (so saving just involves copying the raw file) or if + * we can write the file out in at least one format. */ set_menu_sensitivity(ui_manager_main_menubar, "/Menubar/FileMenu/SaveAs", - cf_can_save_as(cf) || cf->is_tempfile); + (!cf->unsaved_changes || cf_can_save_as(cf))); + /* + * "Export Specified Packets..." should be available only if + * we can write the file out in at least one format. + */ + set_menu_sensitivity(ui_manager_main_menubar, "/Menubar/FileMenu/ExportSpecifiedPackets", + cf_can_save_as(cf)); set_menu_sensitivity(ui_manager_main_menubar, "/Menubar/FileMenu/ExportPacketDissections", TRUE); set_menu_sensitivity(ui_manager_main_menubar, "/Menubar/FileMenu/ExportSelectedPacketBytes", TRUE); set_menu_sensitivity(ui_manager_main_menubar, "/Menubar/FileMenu/ExportSSLSessionKeys", TRUE); diff --git a/ui/gtk/new_packet_list.c b/ui/gtk/new_packet_list.c index e6e6fbf0d9..f6eebdc370 100644 --- a/ui/gtk/new_packet_list.c +++ b/ui/gtk/new_packet_list.c @@ -1450,7 +1450,7 @@ new_packet_list_set_font(PangoFontDescription *font) static void mark_frames_ready(void) { - file_save_update_dynamics(); + file_export_specified_packets_update_dynamics(); packets_bar_update(); new_packet_list_queue_draw(); }