2 * This file Copyright (C) Mnemosyne LLC
4 * This file is licensed by the GPL version 2. Works owned by the
5 * Transmission project are granted a special exemption to clause 2 (b)
6 * so that the bulk of its code can remain under the MIT license.
7 * This exemption does not extend to derived works not owned by
8 * the Transmission project.
10 * $Id: util.c 14143 2013-07-24 17:11:21Z jordan $
13 #include <ctype.h> /* isxdigit () */
16 #include <string.h> /* strchr (), strrchr (), strlen (), strstr () */
19 #include <glib/gi18n.h>
20 #include <gio/gio.h> /* g_file_trash () */
22 #include <libtransmission/transmission.h> /* TR_RATIO_NA, TR_RATIO_INF */
23 #include <libtransmission/utils.h> /* tr_strratio () */
24 #include <libtransmission/web.h> /* tr_webResponseStr () */
25 #include <libtransmission/version.h> /* SHORT_VERSION_STRING */
37 const int mem_K
= 1024;
38 const char * mem_K_str
= N_("KiB");
39 const char * mem_M_str
= N_("MiB");
40 const char * mem_G_str
= N_("GiB");
41 const char * mem_T_str
= N_("TiB");
43 const int disk_K
= 1000;
44 const char * disk_K_str
= N_("kB");
45 const char * disk_M_str
= N_("MB");
46 const char * disk_G_str
= N_("GB");
47 const char * disk_T_str
= N_("TB");
49 const int speed_K
= 1000;
50 const char * speed_K_str
= N_("kB/s");
51 const char * speed_M_str
= N_("MB/s");
52 const char * speed_G_str
= N_("GB/s");
53 const char * speed_T_str
= N_("TB/s");
60 gtr_get_unicode_string (int i
)
64 case GTR_UNICODE_UP
: return "\xE2\x96\xB4";
65 case GTR_UNICODE_DOWN
: return "\xE2\x96\xBE";
66 case GTR_UNICODE_INF
: return "\xE2\x88\x9E";
67 case GTR_UNICODE_BULLET
: return "\xE2\x88\x99";
68 default: return "err";
73 tr_strlratio (char * buf
, double ratio
, size_t buflen
)
75 return tr_strratio (buf
, buflen
, ratio
, gtr_get_unicode_string (GTR_UNICODE_INF
));
79 tr_strlpercent (char * buf
, double x
, size_t buflen
)
81 return tr_strpercent (buf
, x
, buflen
);
85 tr_strlsize (char * buf
, guint64 bytes
, size_t buflen
)
88 g_strlcpy (buf
, Q_("None"), buflen
);
90 tr_formatter_size_B (buf
, bytes
, buflen
);
96 tr_strltime (char * buf
, int seconds
, size_t buflen
)
98 int days
, hours
, minutes
;
99 char d
[128], h
[128], m
[128], s
[128];
104 days
= seconds
/ 86400;
105 hours
= (seconds
% 86400) / 3600;
106 minutes
= (seconds
% 3600) / 60;
107 seconds
= (seconds
% 3600) % 60;
109 g_snprintf (d
, sizeof (d
), ngettext ("%'d day", "%'d days", days
), days
);
110 g_snprintf (h
, sizeof (h
), ngettext ("%'d hour", "%'d hours", hours
), hours
);
111 g_snprintf (m
, sizeof (m
), ngettext ("%'d minute", "%'d minutes", minutes
), minutes
);
112 g_snprintf (s
, sizeof (s
), ngettext ("%'d second", "%'d seconds", seconds
), seconds
);
116 if (days
>= 4 || !hours
)
117 g_strlcpy (buf
, d
, buflen
);
119 g_snprintf (buf
, buflen
, "%s, %s", d
, h
);
123 if (hours
>= 4 || !minutes
)
124 g_strlcpy (buf
, h
, buflen
);
126 g_snprintf (buf
, buflen
, "%s, %s", h
, m
);
130 if (minutes
>= 4 || !seconds
)
131 g_strlcpy (buf
, m
, buflen
);
133 g_snprintf (buf
, buflen
, "%s, %s", m
, s
);
137 g_strlcpy (buf
, s
, buflen
);
143 /* pattern-matching text; ie, legaltorrents.com */
145 gtr_get_host_from_url (char * buf
, size_t buflen
, const char * url
)
150 if ((pch
= strstr (url
, "://")))
152 const size_t hostlen
= strcspn (pch
+3, ":/");
153 const size_t copylen
= MIN (hostlen
, sizeof (host
)-1);
154 memcpy (host
, pch
+3, copylen
);
155 host
[copylen
] = '\0';
162 if (tr_addressIsIP (host
))
164 g_strlcpy (buf
, url
, buflen
);
168 const char * first_dot
= strchr (host
, '.');
169 const char * last_dot
= strrchr (host
, '.');
170 if ((first_dot
) && (last_dot
) && (first_dot
!= last_dot
))
171 g_strlcpy (buf
, first_dot
+ 1, buflen
);
173 g_strlcpy (buf
, host
, buflen
);
178 gtr_is_supported_url (const char * str
)
180 return ((str
!= NULL
) &&
181 (g_str_has_prefix (str
, "ftp://") ||
182 g_str_has_prefix (str
, "http://") ||
183 g_str_has_prefix (str
, "https://")));
187 gtr_is_magnet_link (const char * str
)
189 return (str
!= NULL
) && (g_str_has_prefix (str
, "magnet:?"));
193 gtr_is_hex_hashcode (const char * str
)
197 if (!str
|| (strlen (str
) != 40))
201 if (!isxdigit (str
[i
]))
208 getWindow (GtkWidget
* w
)
213 if (GTK_IS_WINDOW (w
))
214 return GTK_WINDOW (w
);
216 return GTK_WINDOW (gtk_widget_get_ancestor (w
, GTK_TYPE_WINDOW
));
220 gtr_add_torrent_error_dialog (GtkWidget
* child
,
222 tr_torrent
* duplicate_torrent
,
223 const char * filename
)
227 GtkWindow
* win
= getWindow (child
);
229 if (err
== TR_PARSE_ERR
)
230 secondary
= g_strdup_printf (_("The torrent file \"%s\" contains invalid data."), filename
);
231 else if (err
== TR_PARSE_DUPLICATE
)
232 secondary
= g_strdup_printf (_("The torrent file \"%s\" is already in use by \"%s.\""), filename
, tr_torrentName (duplicate_torrent
));
234 secondary
= g_strdup_printf (_("The torrent file \"%s\" encountered an unknown error."), filename
);
236 w
= gtk_message_dialog_new (win
,
237 GTK_DIALOG_DESTROY_WITH_PARENT
,
240 "%s", _("Error opening torrent"));
241 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (w
),
243 g_signal_connect_swapped (w
, "response",
244 G_CALLBACK (gtk_widget_destroy
), w
);
245 gtk_widget_show_all (w
);
249 typedef void (PopupFunc
)(GtkWidget
*, GdkEventButton
*);
251 /* pop up the context menu if a user right-clicks.
252 if the row they right-click on isn't selected, select it. */
255 on_tree_view_button_pressed (GtkWidget
* view
,
256 GdkEventButton
* event
,
259 GtkTreeView
* tv
= GTK_TREE_VIEW (view
);
261 if (event
->type
== GDK_BUTTON_PRESS
&& event
->button
== 3)
264 GtkTreeSelection
* selection
= gtk_tree_view_get_selection (tv
);
266 if (gtk_tree_view_get_path_at_pos (tv
,
269 &path
, NULL
, NULL
, NULL
))
271 if (!gtk_tree_selection_path_is_selected (selection
, path
))
273 gtk_tree_selection_unselect_all (selection
);
274 gtk_tree_selection_select_path (selection
, path
);
276 gtk_tree_path_free (path
);
280 ((PopupFunc
*)func
)(view
, event
);
288 /* if the user clicked in an empty area of the list,
289 * clear all the selections. */
291 on_tree_view_button_released (GtkWidget
* view
,
292 GdkEventButton
* event
,
293 gpointer unused UNUSED
)
295 GtkTreeView
* tv
= GTK_TREE_VIEW (view
);
297 if (!gtk_tree_view_get_path_at_pos (tv
,
300 NULL
, NULL
, NULL
, NULL
))
302 GtkTreeSelection
* selection
= gtk_tree_view_get_selection (tv
);
303 gtk_tree_selection_unselect_all (selection
);
310 gtr_file_trash_or_remove (const char * filename
)
313 gboolean trashed
= FALSE
;
315 g_return_val_if_fail (filename
&& *filename
, 0);
317 file
= g_file_new_for_path (filename
);
319 if (gtr_pref_flag_get (TR_KEY_trash_can_enabled
))
322 trashed
= g_file_trash (file
, NULL
, &err
);
325 g_message ("Unable to trash file \"%s\": %s", filename
, err
->message
);
326 g_clear_error (&err
);
333 g_file_delete (file
, NULL
, &err
);
336 g_message ("Unable to delete file \"%s\": %s", filename
, err
->message
);
337 g_clear_error (&err
);
341 g_object_unref (G_OBJECT (file
));
346 gtr_get_help_uri (void)
348 static char * uri
= NULL
;
353 const char * fmt
= "http://www.transmissionbt.com/help/gtk/%d.%dx";
354 sscanf (SHORT_VERSION_STRING
, "%d.%d", &major
, &minor
);
355 uri
= g_strdup_printf (fmt
, major
, minor
/ 10);
362 gtr_open_file (const char * path
)
364 GFile
* file
= g_file_new_for_path (path
);
365 gchar
* uri
= g_file_get_uri (file
);
368 g_object_unref (file
);
372 gtr_open_uri (const char * uri
)
376 gboolean opened
= FALSE
;
379 opened
= gtk_show_uri (NULL
, uri
, GDK_CURRENT_TIME
, NULL
);
382 opened
= g_app_info_launch_default_for_uri (uri
, NULL
, NULL
);
386 char * argv
[] = { (char*)"xdg-open", (char*)uri
, NULL
};
387 opened
= g_spawn_async (NULL
, argv
, NULL
, G_SPAWN_SEARCH_PATH
, NULL
, NULL
, NULL
, NULL
);
391 g_message ("Unable to open \"%s\"", uri
);
400 gtr_combo_box_set_active_enum (GtkComboBox
* combo_box
, int value
)
404 const int column
= 0;
406 GtkTreeModel
* model
= gtk_combo_box_get_model (combo_box
);
408 /* do the value and current value match? */
409 if (gtk_combo_box_get_active_iter (combo_box
, &iter
))
411 gtk_tree_model_get (model
, &iter
, column
, ¤tValue
, -1);
412 if (currentValue
== value
)
416 /* find the one to select */
418 while ((gtk_tree_model_iter_nth_child (model
, &iter
, NULL
, i
++)))
420 gtk_tree_model_get (model
, &iter
, column
, ¤tValue
, -1);
421 if (currentValue
== value
)
423 gtk_combo_box_set_active_iter (combo_box
, &iter
);
431 gtr_combo_box_new_enum (const char * text_1
, ...)
435 GtkListStore
* store
;
438 va_start (vl
, text_1
);
440 store
= gtk_list_store_new (2, G_TYPE_INT
, G_TYPE_STRING
);
445 const int val
= va_arg (vl
, int);
446 gtk_list_store_insert_with_values (store
, NULL
, INT_MAX
, 0, val
, 1, text
, -1);
447 text
= va_arg (vl
, const char *);
449 while (text
!= NULL
);
451 w
= gtk_combo_box_new_with_model (GTK_TREE_MODEL (store
));
452 r
= gtk_cell_renderer_text_new ();
453 gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (w
), r
, TRUE
);
454 gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (w
), r
, "text", 1, NULL
);
457 g_object_unref (store
);
462 gtr_combo_box_get_active_enum (GtkComboBox
* combo_box
)
467 if (gtk_combo_box_get_active_iter (combo_box
, &iter
))
468 gtk_tree_model_get (gtk_combo_box_get_model (combo_box
), &iter
, 0, &value
, -1);
474 gtr_priority_combo_new (void)
476 return gtr_combo_box_new_enum (_("High"), TR_PRI_HIGH
,
477 _("Normal"), TR_PRI_NORMAL
,
478 _("Low"), TR_PRI_LOW
,
486 #define GTR_CHILD_HIDDEN "gtr-child-hidden"
489 gtr_widget_set_visible (GtkWidget
* w
, gboolean b
)
491 /* toggle the transient children, too */
492 if (GTK_IS_WINDOW (w
))
495 GList
* windows
= gtk_window_list_toplevels ();
496 GtkWindow
* window
= GTK_WINDOW (w
);
498 for (l
=windows
; l
!=NULL
; l
=l
->next
)
500 if (!GTK_IS_WINDOW (l
->data
))
502 if (gtk_window_get_transient_for (GTK_WINDOW (l
->data
)) != window
)
504 if (gtk_widget_get_visible (GTK_WIDGET (l
->data
)) == b
)
507 if (b
&& g_object_get_data (G_OBJECT (l
->data
), GTR_CHILD_HIDDEN
) != NULL
)
509 g_object_steal_data (G_OBJECT (l
->data
), GTR_CHILD_HIDDEN
);
510 gtr_widget_set_visible (GTK_WIDGET (l
->data
), TRUE
);
514 g_object_set_data (G_OBJECT (l
->data
), GTR_CHILD_HIDDEN
, GINT_TO_POINTER (1));
515 gtr_widget_set_visible (GTK_WIDGET (l
->data
), FALSE
);
519 g_list_free (windows
);
522 gtk_widget_set_visible (w
, b
);
526 gtr_dialog_set_content (GtkDialog
* dialog
, GtkWidget
* content
)
528 GtkWidget
* vbox
= gtk_dialog_get_content_area (dialog
);
529 gtk_box_pack_start (GTK_BOX (vbox
), content
, TRUE
, TRUE
, 0);
530 gtk_widget_show_all (content
);
538 gtr_http_failure_dialog (GtkWidget
* parent
, const char * url
, long response_code
)
540 GtkWindow
* window
= getWindow (parent
);
542 GtkWidget
* w
= gtk_message_dialog_new (window
, 0,
545 _("Error opening \"%s\""), url
);
547 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (w
),
548 _("Server returned \"%1$ld %2$s\""),
550 tr_webGetResponseStr (response_code
));
552 g_signal_connect_swapped (w
, "response", G_CALLBACK (gtk_widget_destroy
), w
);
557 gtr_unrecognized_url_dialog (GtkWidget
* parent
, const char * url
)
559 const char * xt
= "xt=urn:btih";
561 GtkWindow
* window
= getWindow (parent
);
563 GString
* gstr
= g_string_new (NULL
);
565 GtkWidget
* w
= gtk_message_dialog_new (window
, 0,
568 "%s", _("Unrecognized URL"));
570 g_string_append_printf (gstr
, _("Transmission doesn't know how to use \"%s\""), url
);
572 if (gtr_is_magnet_link (url
) && (strstr (url
, xt
) == NULL
))
574 g_string_append_printf (gstr
, "\n \n");
575 g_string_append_printf (gstr
, _("This magnet link appears to be intended for something other than BitTorrent. BitTorrent magnet links have a section containing \"%s\"."), xt
);
578 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (w
), "%s", gstr
->str
);
579 g_signal_connect_swapped (w
, "response", G_CALLBACK (gtk_widget_destroy
), w
);
581 g_string_free (gstr
, TRUE
);
589 gtr_paste_clipboard_url_into_entry (GtkWidget
* e
)
595 g_strstrip (gtk_clipboard_wait_for_text (gtk_clipboard_get (GDK_SELECTION_PRIMARY
))),
596 g_strstrip (gtk_clipboard_wait_for_text (gtk_clipboard_get (GDK_SELECTION_CLIPBOARD
)))
599 for (i
=0; i
<G_N_ELEMENTS (text
); ++i
)
602 if (s
&& (gtr_is_supported_url (s
) || gtr_is_magnet_link (s
) || gtr_is_hex_hashcode (s
)))
604 gtk_entry_set_text (GTK_ENTRY (e
), s
);
609 for (i
=0; i
<G_N_ELEMENTS (text
); ++i
)
618 gtr_label_set_text (GtkLabel
* lb
, const char * newstr
)
620 const char * oldstr
= gtk_label_get_text (lb
);
622 if (g_strcmp0 (oldstr
, newstr
))
623 gtk_label_set_text (lb
, newstr
);
630 struct freespace_label_data
638 static void on_freespace_label_core_destroyed (gpointer gdata
, GObject
* dead_core
);
639 static void on_freespace_label_destroyed (gpointer gdata
, GObject
* dead_label
);
642 freespace_label_data_free (gpointer gdata
)
644 struct freespace_label_data
* data
= gdata
;
646 if (data
->core
!= NULL
)
647 g_object_weak_unref (G_OBJECT(data
->core
), on_freespace_label_core_destroyed
, data
);
649 if (data
->label
!= NULL
)
650 g_object_weak_ref (G_OBJECT(data
->label
), on_freespace_label_destroyed
, data
);
652 g_source_remove (data
->timer_id
);
657 static TR_DEFINE_QUARK (freespace_label_data
, freespace_label_data
)
660 on_freespace_label_core_destroyed (gpointer gdata
, GObject
* dead_core G_GNUC_UNUSED
)
662 struct freespace_label_data
* data
= gdata
;
664 freespace_label_data_free (data
);
668 on_freespace_label_destroyed (gpointer gdata
, GObject
* dead_label G_GNUC_UNUSED
)
670 struct freespace_label_data
* data
= gdata
;
672 freespace_label_data_free (data
);
676 on_freespace_timer (gpointer gdata
)
681 tr_session
* session
;
682 struct freespace_label_data
* data
= gdata
;
684 session
= gtr_core_session (data
->core
);
685 bytes
= tr_sessionGetDirFreeSpace (session
, data
->dir
);
689 g_snprintf (text
, sizeof(text
), _("Error"));
694 tr_strlsize (size
, bytes
, sizeof(size
));
695 g_snprintf (text
, sizeof(text
), _("%s free"), size
);
698 g_snprintf (markup
, sizeof(markup
), "<i>%s</i>", text
);
699 gtk_label_set_markup (data
->label
, markup
);
701 return G_SOURCE_CONTINUE
;
705 gtr_freespace_label_new (struct _TrCore
* core
, const char * dir
)
707 struct freespace_label_data
* data
;
709 data
= g_new0 (struct freespace_label_data
, 1);
710 data
->timer_id
= g_timeout_add_seconds (3, on_freespace_timer
, data
);
712 data
->label
= GTK_LABEL (gtk_label_new (NULL
));
713 data
->dir
= g_strdup (dir
);
715 /* when either the core or the label is destroyed, stop updating */
716 g_object_weak_ref (G_OBJECT(core
), on_freespace_label_core_destroyed
, data
);
717 g_object_weak_ref (G_OBJECT(data
->label
), on_freespace_label_destroyed
, data
);
719 g_object_set_qdata (G_OBJECT(data
->label
), freespace_label_data_quark (), data
);
720 on_freespace_timer (data
);
721 return GTK_WIDGET (data
->label
);
725 gtr_freespace_label_set_dir (GtkWidget
* label
, const char * dir
)
727 struct freespace_label_data
* data
;
729 data
= g_object_get_qdata (G_OBJECT(label
), freespace_label_data_quark ());
732 data
->dir
= g_strdup (dir
);
733 on_freespace_timer (data
);