/* * This file Copyright (C) Mnemosyne LLC * * This file is licensed by the GPL version 2. Works owned by the * Transmission project are granted a special exemption to clause 2 (b) * so that the bulk of its code can remain under the MIT license. * This exemption does not extend to derived works not owned by * the Transmission project. * * $Id$ */ #include #include #include #include #include /* tr_formatter_mem_B () */ #include "hig.h" #include "makemeta-ui.h" #include "tr-core.h" #include "tr-prefs.h" #include "util.h" #define FILE_CHOSEN_KEY "file-is-chosen" typedef struct { char * target; guint progress_tag; GtkWidget * file_radio; GtkWidget * file_chooser; GtkWidget * folder_radio; GtkWidget * folder_chooser; GtkWidget * pieces_lb; GtkWidget * destination_chooser; GtkWidget * comment_check; GtkWidget * comment_entry; GtkWidget * private_check; GtkWidget * progress_label; GtkWidget * progress_bar; GtkWidget * progress_dialog; GtkWidget * dialog; GtkTextBuffer * announce_text_buffer; TrCore * core; tr_metainfo_builder * builder; } MakeMetaUI; static void freeMetaUI (gpointer p) { MakeMetaUI * ui = p; tr_metaInfoBuilderFree (ui->builder); g_free (ui->target); memset (ui, ~0, sizeof (MakeMetaUI)); g_free (ui); } static gboolean onProgressDialogRefresh (gpointer data) { char * str = NULL; MakeMetaUI * ui = data; const tr_metainfo_builder * b = ui->builder; GtkDialog * d = GTK_DIALOG (ui->progress_dialog); GtkProgressBar * p = GTK_PROGRESS_BAR (ui->progress_bar); const double fraction = b->pieceCount ? ((double)b->pieceIndex / b->pieceCount) : 0; char * base = g_path_get_basename (b->top); /* progress label */ if (!b->isDone) str = g_strdup_printf (_("Creating \"%s\""), base); else if (b->result == TR_MAKEMETA_OK) str = g_strdup_printf (_("Created \"%s\"!"), base); else if (b->result == TR_MAKEMETA_URL) str = g_strdup_printf (_("Error: invalid announce URL \"%s\""), b->errfile); else if (b->result == TR_MAKEMETA_CANCELLED) str = g_strdup_printf (_("Cancelled")); else if (b->result == TR_MAKEMETA_IO_READ) str = g_strdup_printf (_("Error reading \"%s\": %s"), b->errfile, g_strerror (b->my_errno)); else if (b->result == TR_MAKEMETA_IO_WRITE) str = g_strdup_printf (_("Error writing \"%s\": %s"), b->errfile, g_strerror (b->my_errno)); else g_assert_not_reached (); if (str != NULL) { gtr_label_set_text (GTK_LABEL (ui->progress_label), str); g_free (str); } /* progress bar */ if (!b->pieceIndex) str = g_strdup (""); else { char sizebuf[128]; tr_strlsize (sizebuf, (uint64_t)b->pieceIndex * (uint64_t)b->pieceSize, sizeof (sizebuf)); /* how much data we've scanned through to generate checksums */ str = g_strdup_printf (_("Scanned %s"), sizebuf); } gtk_progress_bar_set_fraction (p, fraction); gtk_progress_bar_set_text (p, str); g_free (str); /* buttons */ gtk_dialog_set_response_sensitive (d, GTK_RESPONSE_CANCEL, !b->isDone); gtk_dialog_set_response_sensitive (d, GTK_RESPONSE_CLOSE, b->isDone); gtk_dialog_set_response_sensitive (d, GTK_RESPONSE_ACCEPT, b->isDone && !b->result); g_free (base); return TRUE; } static void onProgressDialogDestroyed (gpointer data, GObject * dead UNUSED) { MakeMetaUI * ui = data; g_source_remove (ui->progress_tag); } static void addTorrent (MakeMetaUI * ui) { char * path; const tr_metainfo_builder * b = ui->builder; tr_ctor * ctor = tr_ctorNew (gtr_core_session (ui->core)); tr_ctorSetMetainfoFromFile (ctor, ui->target); path = g_path_get_dirname (b->top); tr_ctorSetDownloadDir (ctor, TR_FORCE, path); g_free (path); gtr_core_add_ctor (ui->core, ctor); } static void onProgressDialogResponse (GtkDialog * d, int response, gpointer data) { MakeMetaUI * ui = data; switch (response) { case GTK_RESPONSE_CANCEL: ui->builder->abortFlag = TRUE; gtk_widget_destroy (GTK_WIDGET (d)); break; case GTK_RESPONSE_ACCEPT: addTorrent (ui); /* fall-through */ case GTK_RESPONSE_CLOSE: gtk_widget_destroy (ui->builder->result ? GTK_WIDGET (d) : ui->dialog); break; default: g_assert (0 && "unhandled response"); } } static void makeProgressDialog (GtkWidget * parent, MakeMetaUI * ui) { GtkWidget *d, *l, *w, *v, *fr; d = gtk_dialog_new_with_buttons (_("New Torrent"), GTK_WINDOW (parent), GTK_DIALOG_MODAL|GTK_DIALOG_DESTROY_WITH_PARENT, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE, GTK_STOCK_ADD, GTK_RESPONSE_ACCEPT, NULL); ui->progress_dialog = d; g_signal_connect (d, "response", G_CALLBACK (onProgressDialogResponse), ui); fr = gtk_frame_new (NULL); gtk_container_set_border_width (GTK_CONTAINER (fr), GUI_PAD_BIG); gtk_frame_set_shadow_type (GTK_FRAME (fr), GTK_SHADOW_NONE); v = gtk_box_new (GTK_ORIENTATION_VERTICAL, GUI_PAD); gtk_container_add (GTK_CONTAINER (fr), v); l = gtk_label_new (_("Creating torrent…")); gtk_misc_set_alignment (GTK_MISC (l), 0.0, 0.5); gtk_label_set_justify (GTK_LABEL (l), GTK_JUSTIFY_LEFT); ui->progress_label = l; gtk_box_pack_start (GTK_BOX (v), l, FALSE, FALSE, 0); w = gtk_progress_bar_new (); ui->progress_bar = w; gtk_box_pack_start (GTK_BOX (v), w, FALSE, FALSE, 0); ui->progress_tag = gdk_threads_add_timeout_seconds (SECONDARY_WINDOW_REFRESH_INTERVAL_SECONDS, onProgressDialogRefresh, ui); g_object_weak_ref (G_OBJECT (d), onProgressDialogDestroyed, ui); onProgressDialogRefresh (ui); gtr_dialog_set_content (GTK_DIALOG (d), fr); gtk_widget_show (d); } static void onResponse (GtkDialog* d, int response, gpointer user_data) { MakeMetaUI * ui = user_data; if (response == GTK_RESPONSE_ACCEPT) { if (ui->builder != NULL) { int i; int n; int tier; GtkTextIter start, end; char * dir; char * base; char * tracker_text; char ** tracker_strings; GtkEntry * c_entry = GTK_ENTRY (ui->comment_entry); GtkToggleButton * p_check = GTK_TOGGLE_BUTTON (ui->private_check); GtkToggleButton * c_check = GTK_TOGGLE_BUTTON (ui->comment_check); const char * comment = gtk_entry_get_text (c_entry); const gboolean isPrivate = gtk_toggle_button_get_active (p_check); const gboolean useComment = gtk_toggle_button_get_active (c_check); tr_tracker_info * trackers; /* destination file */ dir = gtk_file_chooser_get_filename ( GTK_FILE_CHOOSER (ui->destination_chooser)); base = g_path_get_basename (ui->builder->top); g_free (ui->target); ui->target = g_strdup_printf ("%s/%s.torrent", dir, base); /* build the array of trackers */ gtk_text_buffer_get_bounds (ui->announce_text_buffer, &start, &end); tracker_text = gtk_text_buffer_get_text (ui->announce_text_buffer, &start, &end, FALSE); tracker_strings = g_strsplit (tracker_text, "\n", 0); for (i=0; tracker_strings[i];) ++i; trackers = g_new0 (tr_tracker_info, i); for (i=n=tier=0; tracker_strings[i]; ++i) { const char * str = tracker_strings[i]; if (!*str) ++tier; else { trackers[n].tier = tier; trackers[n].announce = tracker_strings[i]; ++n; } } /* build the .torrent */ makeProgressDialog (GTK_WIDGET (d), ui); tr_makeMetaInfo (ui->builder, ui->target, trackers, n, useComment ? comment : NULL, isPrivate); /* cleanup */ g_free (trackers); g_strfreev (tracker_strings); g_free (tracker_text); g_free (base); g_free (dir); } } else if (response == GTK_RESPONSE_CLOSE) { gtk_widget_destroy (GTK_WIDGET (d)); } } /*** **** ***/ static void onSourceToggled (GtkToggleButton * tb, gpointer user_data) { gtk_widget_set_sensitive (GTK_WIDGET (user_data), gtk_toggle_button_get_active (tb)); } static void updatePiecesLabel (MakeMetaUI * ui) { const tr_metainfo_builder * builder = ui->builder; const char * filename = builder ? builder->top : NULL; GString * gstr = g_string_new (NULL); g_string_append (gstr, ""); if (!filename) { g_string_append (gstr, _("No source selected")); } else { char buf[128]; tr_strlsize (buf, builder->totalSize, sizeof (buf)); g_string_append_printf (gstr, ngettext ("%1$s; %2$'d File", "%1$s; %2$'d Files", builder->fileCount), buf, builder->fileCount); g_string_append (gstr, "; "); tr_formatter_mem_B (buf, builder->pieceSize, sizeof (buf)); g_string_append_printf (gstr, ngettext ("%1$'d Piece @ %2$s", "%1$'d Pieces @ %2$s", builder->pieceCount), builder->pieceCount, buf); } g_string_append (gstr, ""); gtk_label_set_markup (GTK_LABEL (ui->pieces_lb), gstr->str); g_string_free (gstr, TRUE); } static void setFilename (MakeMetaUI * ui, const char * filename) { if (ui->builder) { tr_metaInfoBuilderFree (ui->builder); ui->builder = NULL; } if (filename) ui->builder = tr_metaInfoBuilderCreate (filename); updatePiecesLabel (ui); } static void onChooserChosen (GtkFileChooser * chooser, gpointer user_data) { char * filename; MakeMetaUI * ui = user_data; g_object_set_data (G_OBJECT (chooser), FILE_CHOSEN_KEY, GINT_TO_POINTER (TRUE)); filename = gtk_file_chooser_get_filename (chooser); setFilename (ui, filename); g_free (filename); } static void onSourceToggled2 (GtkToggleButton * tb, GtkWidget * chooser, MakeMetaUI * ui) { if (gtk_toggle_button_get_active (tb)) { if (g_object_get_data (G_OBJECT (chooser), FILE_CHOSEN_KEY) != NULL) onChooserChosen (GTK_FILE_CHOOSER (chooser), ui); else setFilename (ui, NULL); } } static void onFolderToggled (GtkToggleButton * tb, gpointer data) { MakeMetaUI * ui = data; onSourceToggled2 (tb, ui->folder_chooser, ui); } static void onFileToggled (GtkToggleButton * tb, gpointer data) { MakeMetaUI * ui = data; onSourceToggled2 (tb, ui->file_chooser, ui); } static const char * getDefaultSavePath (void) { return g_get_user_special_dir (G_USER_DIRECTORY_DESKTOP); } static void on_drag_data_received (GtkWidget * widget UNUSED, GdkDragContext * drag_context, gint x UNUSED, gint y UNUSED, GtkSelectionData * selection_data, guint info UNUSED, guint time_, gpointer user_data) { gboolean success = FALSE; MakeMetaUI * ui = user_data; char ** uris = gtk_selection_data_get_uris (selection_data); if (uris && uris[0]) { const char * uri = uris[ 0 ]; gchar * filename = g_filename_from_uri (uri, NULL, NULL); if (g_file_test (filename, G_FILE_TEST_IS_DIR)) { /* a directory was dragged onto the dialog... */ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ui->folder_radio), TRUE); gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (ui->folder_chooser), filename); success = TRUE; } else if (g_file_test (filename, G_FILE_TEST_IS_REGULAR)) { /* a file was dragged on to the dialog... */ gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (ui->file_radio), TRUE); gtk_file_chooser_set_filename (GTK_FILE_CHOOSER (ui->file_chooser), filename); success = TRUE; } g_free (filename); } g_strfreev (uris); gtk_drag_finish (drag_context, success, FALSE, time_); } GtkWidget* gtr_torrent_creation_dialog_new (GtkWindow * parent, TrCore * core) { const char * str; GtkWidget * d, *t, *w, *l, *fr, *sw, *v; GSList * slist; guint row = 0; MakeMetaUI * ui = g_new0 (MakeMetaUI, 1); ui->core = core; d = gtk_dialog_new_with_buttons (_("New Torrent"), parent, GTK_DIALOG_DESTROY_WITH_PARENT, GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE, GTK_STOCK_NEW, GTK_RESPONSE_ACCEPT, NULL); ui->dialog = d; g_signal_connect (d, "response", G_CALLBACK (onResponse), ui); g_object_set_data_full (G_OBJECT (d), "ui", ui, freeMetaUI); t = hig_workarea_create (); hig_workarea_add_section_title (t, &row, _("Files")); str = _("Sa_ve to:"); w = gtk_file_chooser_button_new (NULL, GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER); gtk_file_chooser_set_current_folder (GTK_FILE_CHOOSER (w), getDefaultSavePath ()); ui->destination_chooser = w; hig_workarea_add_row (t, &row, str, w, NULL); l = gtk_radio_button_new_with_mnemonic (NULL, _("Source F_older:")); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (l), FALSE); w = gtk_file_chooser_button_new (NULL, GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER); g_signal_connect (l, "toggled", G_CALLBACK (onFolderToggled), ui); g_signal_connect (l, "toggled", G_CALLBACK (onSourceToggled), w); g_signal_connect (w, "selection-changed", G_CALLBACK (onChooserChosen), ui); ui->folder_radio = l; ui->folder_chooser = w; gtk_widget_set_sensitive (GTK_WIDGET (w), FALSE); hig_workarea_add_row_w (t, &row, l, w, NULL); slist = gtk_radio_button_get_group (GTK_RADIO_BUTTON (l)), l = gtk_radio_button_new_with_mnemonic (slist, _("Source _File:")); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (l), TRUE); w = gtk_file_chooser_button_new (NULL, GTK_FILE_CHOOSER_ACTION_OPEN); g_signal_connect (l, "toggled", G_CALLBACK (onFileToggled), ui); g_signal_connect (l, "toggled", G_CALLBACK (onSourceToggled), w); g_signal_connect (w, "selection-changed", G_CALLBACK (onChooserChosen), ui); ui->file_radio = l; ui->file_chooser = w; hig_workarea_add_row_w (t, &row, l, w, NULL); w = gtk_label_new (NULL); ui->pieces_lb = w; gtk_label_set_markup (GTK_LABEL (w), _("No source selected")); hig_workarea_add_row (t, &row, NULL, w, NULL); hig_workarea_add_section_divider (t, &row); hig_workarea_add_section_title (t, &row, _("Properties")); str = _("_Trackers:"); v = gtk_box_new (GTK_ORIENTATION_VERTICAL, GUI_PAD_SMALL); ui->announce_text_buffer = gtk_text_buffer_new (NULL); w = gtk_text_view_new_with_buffer (ui->announce_text_buffer); gtk_widget_set_size_request (w, -1, 80); sw = gtk_scrolled_window_new (NULL, NULL); gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); gtk_container_add (GTK_CONTAINER (sw), w); fr = gtk_frame_new (NULL); gtk_frame_set_shadow_type (GTK_FRAME (fr), GTK_SHADOW_IN); gtk_container_add (GTK_CONTAINER (fr), sw); gtk_box_pack_start (GTK_BOX (v), fr, TRUE, TRUE, 0); l = gtk_label_new (NULL); gtk_label_set_markup (GTK_LABEL (l), _("To add a backup URL, add it on the line after the primary URL.\n" "To add another primary URL, add it after a blank line.")); gtk_label_set_justify (GTK_LABEL (l), GTK_JUSTIFY_LEFT); gtk_misc_set_alignment (GTK_MISC (l), 0.0, 0.5); gtk_box_pack_start (GTK_BOX (v), l, FALSE, FALSE, 0); hig_workarea_add_tall_row (t, &row, str, v, NULL); l = gtk_check_button_new_with_mnemonic (_("Co_mment:")); ui->comment_check = l; gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (l), FALSE); w = gtk_entry_new (); ui->comment_entry = w; gtk_widget_set_sensitive (GTK_WIDGET (w), FALSE); g_signal_connect (l, "toggled", G_CALLBACK (onSourceToggled), w); hig_workarea_add_row_w (t, &row, l, w, NULL); w = hig_workarea_add_wide_checkbutton (t, &row, _("_Private torrent"), FALSE); ui->private_check = w; gtr_dialog_set_content (GTK_DIALOG (d), t); gtk_drag_dest_set (d, GTK_DEST_DEFAULT_ALL, NULL, 0, GDK_ACTION_COPY); gtk_drag_dest_add_uri_targets (d); g_signal_connect (d, "drag-data-received", G_CALLBACK (on_drag_data_received), ui); return d; }