765 lines
18 KiB
C++
765 lines
18 KiB
C++
/*
|
|
* This file Copyright (C) 2008-2014 Mnemosyne LLC
|
|
*
|
|
* It may be used under the GNU GPL versions 2 or 3
|
|
* or any future license endorsed by Mnemosyne LLC.
|
|
*
|
|
*/
|
|
|
|
#include <ctype.h> /* isxdigit() */
|
|
#include <errno.h>
|
|
#include <limits.h> /* INT_MAX */
|
|
#include <stdarg.h>
|
|
#include <string.h> /* strchr(), strrchr(), strlen(), strstr() */
|
|
|
|
#include <gtk/gtk.h>
|
|
#include <glib/gi18n.h>
|
|
#include <gio/gio.h> /* g_file_trash() */
|
|
|
|
#include <libtransmission/transmission.h> /* TR_RATIO_NA, TR_RATIO_INF */
|
|
#include <libtransmission/error.h>
|
|
#include <libtransmission/utils.h> /* tr_strratio() */
|
|
#include <libtransmission/web.h> /* tr_webResponseStr() */
|
|
#include <libtransmission/version.h> /* SHORT_VERSION_STRING */
|
|
|
|
#include "conf.h"
|
|
#include "hig.h"
|
|
#include "tr-core.h"
|
|
#include "tr-prefs.h"
|
|
#include "util.h"
|
|
|
|
/***
|
|
**** UNITS
|
|
***/
|
|
|
|
int const mem_K = 1024;
|
|
char const* mem_K_str = N_("KiB");
|
|
char const* mem_M_str = N_("MiB");
|
|
char const* mem_G_str = N_("GiB");
|
|
char const* mem_T_str = N_("TiB");
|
|
|
|
int const disk_K = 1000;
|
|
char const* disk_K_str = N_("kB");
|
|
char const* disk_M_str = N_("MB");
|
|
char const* disk_G_str = N_("GB");
|
|
char const* disk_T_str = N_("TB");
|
|
|
|
int const speed_K = 1000;
|
|
char const* speed_K_str = N_("kB/s");
|
|
char const* speed_M_str = N_("MB/s");
|
|
char const* speed_G_str = N_("GB/s");
|
|
char const* speed_T_str = N_("TB/s");
|
|
|
|
/***
|
|
****
|
|
***/
|
|
|
|
char const* gtr_get_unicode_string(int i)
|
|
{
|
|
switch (i)
|
|
{
|
|
case GTR_UNICODE_UP:
|
|
return "\xE2\x96\xB4";
|
|
|
|
case GTR_UNICODE_DOWN:
|
|
return "\xE2\x96\xBE";
|
|
|
|
case GTR_UNICODE_INF:
|
|
return "\xE2\x88\x9E";
|
|
|
|
case GTR_UNICODE_BULLET:
|
|
return "\xE2\x88\x99";
|
|
|
|
default:
|
|
return "err";
|
|
}
|
|
}
|
|
|
|
char* tr_strlratio(char* buf, double ratio, size_t buflen)
|
|
{
|
|
return tr_strratio(buf, buflen, ratio, gtr_get_unicode_string(GTR_UNICODE_INF));
|
|
}
|
|
|
|
char* tr_strlpercent(char* buf, double x, size_t buflen)
|
|
{
|
|
return tr_strpercent(buf, x, buflen);
|
|
}
|
|
|
|
char* tr_strlsize(char* buf, guint64 bytes, size_t buflen)
|
|
{
|
|
if (bytes == 0)
|
|
{
|
|
g_strlcpy(buf, Q_("None"), buflen);
|
|
}
|
|
else
|
|
{
|
|
tr_formatter_size_B(buf, bytes, buflen);
|
|
}
|
|
|
|
return buf;
|
|
}
|
|
|
|
char* tr_strltime(char* buf, time_t seconds, size_t buflen)
|
|
{
|
|
char d[128];
|
|
char h[128];
|
|
char m[128];
|
|
char s[128];
|
|
|
|
if (seconds < 0)
|
|
{
|
|
seconds = 0;
|
|
}
|
|
|
|
int const days = (int)(seconds / 86400);
|
|
int const hours = (seconds % 86400) / 3600;
|
|
int const minutes = (seconds % 3600) / 60;
|
|
seconds = (seconds % 3600) % 60;
|
|
|
|
g_snprintf(d, sizeof(d), ngettext("%'d day", "%'d days", days), days);
|
|
g_snprintf(h, sizeof(h), ngettext("%'d hour", "%'d hours", hours), hours);
|
|
g_snprintf(m, sizeof(m), ngettext("%'d minute", "%'d minutes", minutes), minutes);
|
|
g_snprintf(s, sizeof(s), ngettext("%'d second", "%'d seconds", (int)seconds), (int)seconds);
|
|
|
|
if (days != 0)
|
|
{
|
|
if (days >= 4 || hours == 0)
|
|
{
|
|
g_strlcpy(buf, d, buflen);
|
|
}
|
|
else
|
|
{
|
|
g_snprintf(buf, buflen, "%s, %s", d, h);
|
|
}
|
|
}
|
|
else if (hours != 0)
|
|
{
|
|
if (hours >= 4 || minutes == 0)
|
|
{
|
|
g_strlcpy(buf, h, buflen);
|
|
}
|
|
else
|
|
{
|
|
g_snprintf(buf, buflen, "%s, %s", h, m);
|
|
}
|
|
}
|
|
else if (minutes != 0)
|
|
{
|
|
if (minutes >= 4 || seconds == 0)
|
|
{
|
|
g_strlcpy(buf, m, buflen);
|
|
}
|
|
else
|
|
{
|
|
g_snprintf(buf, buflen, "%s, %s", m, s);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
g_strlcpy(buf, s, buflen);
|
|
}
|
|
|
|
return buf;
|
|
}
|
|
|
|
/* pattern-matching text; ie, legaltorrents.com */
|
|
void gtr_get_host_from_url(char* buf, size_t buflen, char const* url)
|
|
{
|
|
char host[1024];
|
|
char const* pch;
|
|
|
|
if ((pch = strstr(url, "://")) != nullptr)
|
|
{
|
|
size_t const hostlen = strcspn(pch + 3, ":/");
|
|
size_t const copylen = MIN(hostlen, sizeof(host) - 1);
|
|
memcpy(host, pch + 3, copylen);
|
|
host[copylen] = '\0';
|
|
}
|
|
else
|
|
{
|
|
*host = '\0';
|
|
}
|
|
|
|
if (tr_addressIsIP(host))
|
|
{
|
|
g_strlcpy(buf, url, buflen);
|
|
}
|
|
else
|
|
{
|
|
char const* first_dot = strchr(host, '.');
|
|
char const* last_dot = strrchr(host, '.');
|
|
|
|
if (first_dot != nullptr && last_dot != nullptr && first_dot != last_dot)
|
|
{
|
|
g_strlcpy(buf, first_dot + 1, buflen);
|
|
}
|
|
else
|
|
{
|
|
g_strlcpy(buf, host, buflen);
|
|
}
|
|
}
|
|
}
|
|
|
|
static gboolean gtr_is_supported_url(char const* str)
|
|
{
|
|
return str != nullptr &&
|
|
(g_str_has_prefix(str, "ftp://") || g_str_has_prefix(str, "http://") || g_str_has_prefix(str, "https://"));
|
|
}
|
|
|
|
gboolean gtr_is_magnet_link(char const* str)
|
|
{
|
|
return str != nullptr && g_str_has_prefix(str, "magnet:?");
|
|
}
|
|
|
|
gboolean gtr_is_hex_hashcode(char const* str)
|
|
{
|
|
if (str == nullptr || strlen(str) != 40)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
for (int i = 0; i < 40; ++i)
|
|
{
|
|
if (!isxdigit(str[i]))
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static GtkWindow* getWindow(GtkWidget* w)
|
|
{
|
|
if (w == nullptr)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
if (GTK_IS_WINDOW(w))
|
|
{
|
|
return GTK_WINDOW(w);
|
|
}
|
|
|
|
return GTK_WINDOW(gtk_widget_get_ancestor(w, GTK_TYPE_WINDOW));
|
|
}
|
|
|
|
void gtr_add_torrent_error_dialog(GtkWidget* child, int err, tr_torrent* duplicate_torrent, char const* filename)
|
|
{
|
|
char* secondary;
|
|
GtkWidget* w;
|
|
GtkWindow* win = getWindow(child);
|
|
|
|
if (err == TR_PARSE_ERR)
|
|
{
|
|
secondary = g_strdup_printf(_("The torrent file \"%s\" contains invalid data."), filename);
|
|
}
|
|
else if (err == TR_PARSE_DUPLICATE)
|
|
{
|
|
secondary = g_strdup_printf(
|
|
_("The torrent file \"%s\" is already in use by \"%s.\""),
|
|
filename,
|
|
tr_torrentName(duplicate_torrent));
|
|
}
|
|
else
|
|
{
|
|
secondary = g_strdup_printf(_("The torrent file \"%s\" encountered an unknown error."), filename);
|
|
}
|
|
|
|
w = gtk_message_dialog_new(
|
|
win,
|
|
GTK_DIALOG_DESTROY_WITH_PARENT,
|
|
GTK_MESSAGE_ERROR,
|
|
GTK_BUTTONS_CLOSE,
|
|
"%s",
|
|
_("Error opening torrent"));
|
|
gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(w), "%s", secondary);
|
|
g_signal_connect_swapped(w, "response", G_CALLBACK(gtk_widget_destroy), w);
|
|
gtk_widget_show_all(w);
|
|
g_free(secondary);
|
|
}
|
|
|
|
typedef void (*PopupFunc)(GtkWidget*, GdkEventButton*);
|
|
|
|
/* pop up the context menu if a user right-clicks.
|
|
if the row they right-click on isn't selected, select it. */
|
|
|
|
gboolean on_tree_view_button_pressed(GtkWidget* view, GdkEventButton* event, gpointer func)
|
|
{
|
|
GtkTreeView* tv = GTK_TREE_VIEW(view);
|
|
|
|
if (event->type == GDK_BUTTON_PRESS && event->button == 3)
|
|
{
|
|
GtkTreePath* path;
|
|
GtkTreeSelection* selection = gtk_tree_view_get_selection(tv);
|
|
|
|
if (gtk_tree_view_get_path_at_pos(tv, (gint)event->x, (gint)event->y, &path, nullptr, nullptr, nullptr))
|
|
{
|
|
if (!gtk_tree_selection_path_is_selected(selection, path))
|
|
{
|
|
gtk_tree_selection_unselect_all(selection);
|
|
gtk_tree_selection_select_path(selection, path);
|
|
}
|
|
|
|
gtk_tree_path_free(path);
|
|
}
|
|
|
|
if (func != nullptr)
|
|
{
|
|
(*(PopupFunc)func)(view, event);
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
/* if the user clicked in an empty area of the list,
|
|
* clear all the selections. */
|
|
gboolean on_tree_view_button_released(GtkWidget* view, GdkEventButton* event, gpointer user_data)
|
|
{
|
|
TR_UNUSED(user_data);
|
|
|
|
GtkTreeView* tv = GTK_TREE_VIEW(view);
|
|
|
|
if (!gtk_tree_view_get_path_at_pos(tv, (gint)event->x, (gint)event->y, nullptr, nullptr, nullptr, nullptr))
|
|
{
|
|
GtkTreeSelection* selection = gtk_tree_view_get_selection(tv);
|
|
gtk_tree_selection_unselect_all(selection);
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
bool gtr_file_trash_or_remove(char const* filename, tr_error** error)
|
|
{
|
|
GFile* file;
|
|
gboolean trashed = FALSE;
|
|
bool result = true;
|
|
|
|
g_return_val_if_fail(filename && *filename, false);
|
|
|
|
file = g_file_new_for_path(filename);
|
|
|
|
if (gtr_pref_flag_get(TR_KEY_trash_can_enabled))
|
|
{
|
|
GError* err = nullptr;
|
|
trashed = g_file_trash(file, nullptr, &err);
|
|
|
|
if (err != nullptr)
|
|
{
|
|
g_message("Unable to trash file \"%s\": %s", filename, err->message);
|
|
tr_error_set_literal(error, err->code, err->message);
|
|
g_clear_error(&err);
|
|
}
|
|
}
|
|
|
|
if (!trashed)
|
|
{
|
|
GError* err = nullptr;
|
|
g_file_delete(file, nullptr, &err);
|
|
|
|
if (err != nullptr)
|
|
{
|
|
g_message("Unable to delete file \"%s\": %s", filename, err->message);
|
|
tr_error_clear(error);
|
|
tr_error_set_literal(error, err->code, err->message);
|
|
g_clear_error(&err);
|
|
result = false;
|
|
}
|
|
}
|
|
|
|
g_object_unref(G_OBJECT(file));
|
|
return result;
|
|
}
|
|
|
|
char const* gtr_get_help_uri(void)
|
|
{
|
|
static char const* uri = nullptr;
|
|
|
|
if (uri == nullptr)
|
|
{
|
|
uri = g_strdup_printf("https://transmissionbt.com/help/gtk/%d.%dx", MAJOR_VERSION, MINOR_VERSION / 10);
|
|
}
|
|
|
|
return uri;
|
|
}
|
|
|
|
void gtr_open_file(char const* path)
|
|
{
|
|
GFile* file = g_file_new_for_path(path);
|
|
gchar* uri = g_file_get_uri(file);
|
|
gtr_open_uri(uri);
|
|
g_free(uri);
|
|
g_object_unref(file);
|
|
}
|
|
|
|
void gtr_open_uri(char const* uri)
|
|
{
|
|
if (uri != nullptr)
|
|
{
|
|
gboolean opened = FALSE;
|
|
|
|
if (!opened)
|
|
{
|
|
#if GTK_CHECK_VERSION(3, 22, 0)
|
|
opened = gtk_show_uri_on_window(nullptr, uri, GDK_CURRENT_TIME, nullptr);
|
|
#else
|
|
opened = gtk_show_uri(nullptr, uri, GDK_CURRENT_TIME, nullptr);
|
|
#endif
|
|
}
|
|
|
|
if (!opened)
|
|
{
|
|
opened = g_app_info_launch_default_for_uri(uri, nullptr, nullptr);
|
|
}
|
|
|
|
if (!opened)
|
|
{
|
|
char* argv[] = { (char*)"xdg-open", (char*)uri, nullptr };
|
|
opened = g_spawn_async(nullptr, argv, nullptr, G_SPAWN_SEARCH_PATH, nullptr, nullptr, nullptr, nullptr);
|
|
}
|
|
|
|
if (!opened)
|
|
{
|
|
g_message("Unable to open \"%s\"", uri);
|
|
}
|
|
}
|
|
}
|
|
|
|
/***
|
|
****
|
|
***/
|
|
|
|
void gtr_combo_box_set_active_enum(GtkComboBox* combo_box, int value)
|
|
{
|
|
int i;
|
|
int currentValue;
|
|
int const column = 0;
|
|
GtkTreeIter iter;
|
|
GtkTreeModel* model = gtk_combo_box_get_model(combo_box);
|
|
|
|
/* do the value and current value match? */
|
|
if (gtk_combo_box_get_active_iter(combo_box, &iter))
|
|
{
|
|
gtk_tree_model_get(model, &iter, column, ¤tValue, -1);
|
|
|
|
if (currentValue == value)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* find the one to select */
|
|
i = 0;
|
|
|
|
while (gtk_tree_model_iter_nth_child(model, &iter, nullptr, i))
|
|
{
|
|
gtk_tree_model_get(model, &iter, column, ¤tValue, -1);
|
|
|
|
if (currentValue == value)
|
|
{
|
|
gtk_combo_box_set_active_iter(combo_box, &iter);
|
|
return;
|
|
}
|
|
|
|
++i;
|
|
}
|
|
}
|
|
|
|
GtkWidget* gtr_combo_box_new_enum(char const* text_1, ...)
|
|
{
|
|
GtkWidget* w;
|
|
GtkCellRenderer* r;
|
|
GtkListStore* store;
|
|
char const* text;
|
|
|
|
store = gtk_list_store_new(2, G_TYPE_INT, G_TYPE_STRING);
|
|
|
|
text = text_1;
|
|
|
|
if (text != nullptr)
|
|
{
|
|
va_list vl;
|
|
|
|
va_start(vl, text_1);
|
|
|
|
do
|
|
{
|
|
int const val = va_arg(vl, int);
|
|
gtk_list_store_insert_with_values(store, nullptr, INT_MAX, 0, val, 1, text, -1);
|
|
text = va_arg(vl, char const*);
|
|
} while (text != nullptr);
|
|
|
|
va_end(vl);
|
|
}
|
|
|
|
w = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store));
|
|
r = gtk_cell_renderer_text_new();
|
|
gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(w), r, TRUE);
|
|
gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(w), r, "text", 1, nullptr);
|
|
|
|
/* cleanup */
|
|
g_object_unref(store);
|
|
return w;
|
|
}
|
|
|
|
int gtr_combo_box_get_active_enum(GtkComboBox* combo_box)
|
|
{
|
|
int value = 0;
|
|
GtkTreeIter iter;
|
|
|
|
if (gtk_combo_box_get_active_iter(combo_box, &iter))
|
|
{
|
|
gtk_tree_model_get(gtk_combo_box_get_model(combo_box), &iter, 0, &value, -1);
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
GtkWidget* gtr_priority_combo_new(void)
|
|
{
|
|
return gtr_combo_box_new_enum(
|
|
TR_ARG_TUPLE(_("High"), TR_PRI_HIGH),
|
|
TR_ARG_TUPLE(_("Normal"), TR_PRI_NORMAL),
|
|
TR_ARG_TUPLE(_("Low"), TR_PRI_LOW),
|
|
nullptr);
|
|
}
|
|
|
|
/***
|
|
****
|
|
***/
|
|
|
|
#define GTR_CHILD_HIDDEN "gtr-child-hidden"
|
|
|
|
void gtr_widget_set_visible(GtkWidget* w, gboolean b)
|
|
{
|
|
/* toggle the transient children, too */
|
|
if (GTK_IS_WINDOW(w))
|
|
{
|
|
GList* windows = gtk_window_list_toplevels();
|
|
GtkWindow const* const window = GTK_WINDOW(w);
|
|
|
|
for (GList* l = windows; l != nullptr; l = l->next)
|
|
{
|
|
if (!GTK_IS_WINDOW(l->data))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (gtk_window_get_transient_for(GTK_WINDOW(l->data)) != window)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (gtk_widget_get_visible(GTK_WIDGET(l->data)) == b)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (b && g_object_get_data(G_OBJECT(l->data), GTR_CHILD_HIDDEN) != nullptr)
|
|
{
|
|
g_object_steal_data(G_OBJECT(l->data), GTR_CHILD_HIDDEN);
|
|
gtr_widget_set_visible(GTK_WIDGET(l->data), TRUE);
|
|
}
|
|
else if (!b)
|
|
{
|
|
g_object_set_data(G_OBJECT(l->data), GTR_CHILD_HIDDEN, GINT_TO_POINTER(1));
|
|
gtr_widget_set_visible(GTK_WIDGET(l->data), FALSE);
|
|
}
|
|
}
|
|
|
|
g_list_free(windows);
|
|
}
|
|
|
|
gtk_widget_set_visible(w, b);
|
|
}
|
|
|
|
void gtr_dialog_set_content(GtkDialog* dialog, GtkWidget* content)
|
|
{
|
|
GtkWidget* vbox = gtk_dialog_get_content_area(dialog);
|
|
gtk_box_pack_start(GTK_BOX(vbox), content, TRUE, TRUE, 0);
|
|
gtk_widget_show_all(content);
|
|
}
|
|
|
|
/***
|
|
****
|
|
***/
|
|
|
|
void gtr_unrecognized_url_dialog(GtkWidget* parent, char const* url)
|
|
{
|
|
char const* xt = "xt=urn:btih";
|
|
|
|
GtkWindow* window = getWindow(parent);
|
|
|
|
GString* gstr = g_string_new(nullptr);
|
|
|
|
GtkWidget* w = gtk_message_dialog_new(window, {}, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, "%s", _("Unrecognized URL"));
|
|
|
|
g_string_append_printf(gstr, _("Transmission doesn't know how to use \"%s\""), url);
|
|
|
|
if (gtr_is_magnet_link(url) && strstr(url, xt) == nullptr)
|
|
{
|
|
g_string_append_printf(gstr, "\n \n");
|
|
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);
|
|
}
|
|
|
|
gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(w), "%s", gstr->str);
|
|
g_signal_connect_swapped(w, "response", G_CALLBACK(gtk_widget_destroy), w);
|
|
gtk_widget_show(w);
|
|
g_string_free(gstr, TRUE);
|
|
}
|
|
|
|
/***
|
|
****
|
|
***/
|
|
|
|
void gtr_paste_clipboard_url_into_entry(GtkWidget* e)
|
|
{
|
|
char* text[] = {
|
|
g_strstrip(gtk_clipboard_wait_for_text(gtk_clipboard_get(GDK_SELECTION_PRIMARY))),
|
|
g_strstrip(gtk_clipboard_wait_for_text(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD))),
|
|
};
|
|
|
|
for (size_t i = 0; i < G_N_ELEMENTS(text); ++i)
|
|
{
|
|
char const* const s = text[i];
|
|
|
|
if (s != nullptr && (gtr_is_supported_url(s) || gtr_is_magnet_link(s) || gtr_is_hex_hashcode(s)))
|
|
{
|
|
gtk_entry_set_text(GTK_ENTRY(e), s);
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (size_t i = 0; i < G_N_ELEMENTS(text); ++i)
|
|
{
|
|
g_free(text[i]);
|
|
}
|
|
}
|
|
|
|
/***
|
|
****
|
|
***/
|
|
|
|
void gtr_label_set_text(GtkLabel* lb, char const* newstr)
|
|
{
|
|
char const* oldstr = gtk_label_get_text(lb);
|
|
|
|
if (g_strcmp0(oldstr, newstr) != 0)
|
|
{
|
|
gtk_label_set_text(lb, newstr);
|
|
}
|
|
}
|
|
|
|
/***
|
|
****
|
|
***/
|
|
|
|
struct freespace_label_data
|
|
{
|
|
guint timer_id;
|
|
TrCore* core;
|
|
GtkLabel* label;
|
|
char* dir;
|
|
};
|
|
|
|
static void on_freespace_label_core_destroyed(gpointer gdata, GObject* dead_core);
|
|
static void on_freespace_label_destroyed(gpointer gdata, GObject* dead_label);
|
|
|
|
static void freespace_label_data_free(gpointer gdata)
|
|
{
|
|
auto* data = static_cast<freespace_label_data*>(gdata);
|
|
|
|
if (data->core != nullptr)
|
|
{
|
|
g_object_weak_unref(G_OBJECT(data->core), on_freespace_label_core_destroyed, data);
|
|
}
|
|
|
|
if (data->label != nullptr)
|
|
{
|
|
g_object_weak_ref(G_OBJECT(data->label), on_freespace_label_destroyed, data);
|
|
}
|
|
|
|
g_source_remove(data->timer_id);
|
|
g_free(data->dir);
|
|
g_free(data);
|
|
}
|
|
|
|
static TR_DEFINE_QUARK(freespace_label_data, freespace_label_data)
|
|
|
|
static void on_freespace_label_core_destroyed(gpointer gdata, GObject* dead_core G_GNUC_UNUSED)
|
|
{
|
|
auto* data = static_cast<freespace_label_data*>(gdata);
|
|
data->core = nullptr;
|
|
freespace_label_data_free(data);
|
|
}
|
|
|
|
static void on_freespace_label_destroyed(gpointer gdata, GObject* dead_label G_GNUC_UNUSED)
|
|
{
|
|
auto* data = static_cast<freespace_label_data*>(gdata);
|
|
data->label = nullptr;
|
|
freespace_label_data_free(data);
|
|
}
|
|
|
|
static gboolean on_freespace_timer(gpointer gdata)
|
|
{
|
|
char text[128];
|
|
char markup[128];
|
|
int64_t bytes;
|
|
tr_session* session;
|
|
auto* data = static_cast<freespace_label_data*>(gdata);
|
|
|
|
session = gtr_core_session(data->core);
|
|
bytes = tr_sessionGetDirFreeSpace(session, data->dir);
|
|
|
|
if (bytes < 0)
|
|
{
|
|
g_snprintf(text, sizeof(text), _("Error"));
|
|
}
|
|
else
|
|
{
|
|
char size[128];
|
|
tr_strlsize(size, bytes, sizeof(size));
|
|
g_snprintf(text, sizeof(text), _("%s free"), size);
|
|
}
|
|
|
|
g_snprintf(markup, sizeof(markup), "<i>%s</i>", text);
|
|
gtk_label_set_markup(data->label, markup);
|
|
|
|
return G_SOURCE_CONTINUE;
|
|
}
|
|
|
|
GtkWidget* gtr_freespace_label_new(struct _TrCore* core, char const* dir)
|
|
{
|
|
struct freespace_label_data* data;
|
|
|
|
data = g_new0(struct freespace_label_data, 1);
|
|
data->timer_id = g_timeout_add_seconds(3, on_freespace_timer, data);
|
|
data->core = core;
|
|
data->label = GTK_LABEL(gtk_label_new(nullptr));
|
|
data->dir = g_strdup(dir);
|
|
|
|
/* when either the core or the label is destroyed, stop updating */
|
|
g_object_weak_ref(G_OBJECT(core), on_freespace_label_core_destroyed, data);
|
|
g_object_weak_ref(G_OBJECT(data->label), on_freespace_label_destroyed, data);
|
|
|
|
g_object_set_qdata(G_OBJECT(data->label), freespace_label_data_quark(), data);
|
|
on_freespace_timer(data);
|
|
return GTK_WIDGET(data->label);
|
|
}
|
|
|
|
void gtr_freespace_label_set_dir(GtkWidget* label, char const* dir)
|
|
{
|
|
auto* data = static_cast<freespace_label_data*>(g_object_get_qdata(G_OBJECT(label), freespace_label_data_quark()));
|
|
|
|
tr_free(data->dir);
|
|
data->dir = g_strdup(dir);
|
|
on_freespace_timer(data);
|
|
}
|