summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am8
-rw-r--r--src/requests.c17
-rw-r--r--src/requests.h5
-rw-r--r--src/torrent-cell-renderer.c2
-rw-r--r--src/trg-client.c195
-rw-r--r--src/trg-client.h8
-rw-r--r--src/trg-destination-combo.c2
-rw-r--r--src/trg-file-parser.c56
-rw-r--r--src/trg-files-tree-view.c2
-rw-r--r--src/trg-main-window.c106
-rw-r--r--src/trg-main-window.h3
-rw-r--r--src/trg-menu-bar.c31
-rw-r--r--src/trg-persistent-tree-view.c25
-rw-r--r--src/trg-persistent-tree-view.h2
-rw-r--r--src/trg-preferences-dialog.c61
-rw-r--r--src/trg-preferences-dialog.h1
-rw-r--r--src/trg-prefs.c6
-rw-r--r--src/trg-prefs.h5
-rw-r--r--src/trg-rss-cell-renderer.c429
-rw-r--r--src/trg-rss-cell-renderer.h55
-rw-r--r--src/trg-rss-model.c259
-rw-r--r--src/trg-rss-model.h86
-rw-r--r--src/trg-rss-window.c368
-rw-r--r--src/trg-rss-window.h60
-rw-r--r--src/trg-toolbar.h1
-rw-r--r--src/trg-torrent-add-dialog.c214
-rw-r--r--src/trg-torrent-add-dialog.h22
-rw-r--r--src/trg-torrent-add-url-dialog.c2
-rw-r--r--src/trg-torrent-move-dialog.c2
-rw-r--r--src/trg-torrent-props-dialog.c2
-rw-r--r--src/trg-trackers-tree-view.c2
-rw-r--r--src/upload.c103
-rw-r--r--src/upload.h30
-rw-r--r--src/util.c2
-rw-r--r--src/util.h1
35 files changed, 1895 insertions, 278 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index e43e66f..7cb7801 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -44,7 +44,7 @@ desktop_DATA = transmission-remote-gtk.desktop
endif
bin_PROGRAMS = transmission-remote-gtk
-INCLUDES = -std=c99 -Wall -I.. -Wno-overflow -DTRGLICENSE=\""$(trglicense)"\" $(libcurl_CFLAGS) $(jsonglib_CFLAGS) $(gthread_CFLAGS) $(gtk_CFLAGS) $(gio_CFLAGS) $(notify_CFLAGS) $(libproxy_CFLAGS) $(libappindicator_CFLAGS)
+INCLUDES = -std=c99 -Wall -I.. -Wno-overflow -DTRGLICENSE=\""$(trglicense)"\" $(libcurl_CFLAGS) $(jsonglib_CFLAGS) $(gthread_CFLAGS) $(gtk_CFLAGS) $(gio_CFLAGS) $(notify_CFLAGS) $(libproxy_CFLAGS) $(libappindicator_CFLAGS) $(rssglib_CFLAGS)
transmission_remote_gtk_SOURCES = \
trg-cell-renderer-speed.c \
@@ -105,9 +105,13 @@ transmission_remote_gtk_SOURCES = \
trg-client.c \
trg-main-window.c \
main.c \
+ trg-rss-model.c \
+ trg-rss-window.c \
+ trg-rss-cell-renderer.c \
+ upload.c \
$(NULL)
-transmission_remote_gtk_LDFLAGS = -lm $(jsonglib_LIBS) $(gtk_LIBS) $(gthread_LIBS) $(GEOIP_LIBS) $(gio_LIBS) $(notify_LIBS) $(libproxy_LIBS) $(libcurl_LIBS) $(libappindicator_LIBS)
+transmission_remote_gtk_LDFLAGS = -lm $(jsonglib_LIBS) $(gtk_LIBS) $(gthread_LIBS) $(GEOIP_LIBS) $(gio_LIBS) $(notify_LIBS) $(libproxy_LIBS) $(libcurl_LIBS) $(libappindicator_LIBS) $(rssglib_LIBS)
if WIN32
.rc.o:
diff --git a/src/requests.c b/src/requests.c
index 95be1d7..43a8561 100644
--- a/src/requests.c
+++ b/src/requests.c
@@ -227,7 +227,22 @@ JsonNode *torrent_add_url(const gchar * url, gboolean paused)
return root;
}
-JsonNode *torrent_add(gchar * target, gint flags)
+JsonNode *torrent_add_from_response(trg_response *response, gint flags) {
+ JsonNode *root = base_request(METHOD_TORRENT_ADD);
+ JsonObject *args = node_get_arguments(root);
+ gchar *encoded = g_base64_encode((guchar *)response->raw, response->size);
+
+ json_object_set_string_member(args, PARAM_METAINFO,
+ encoded);
+ g_free(encoded);
+
+ json_object_set_boolean_member(args, PARAM_PAUSED,
+ (flags & TORRENT_ADD_FLAG_PAUSED));
+
+ return root;
+}
+
+JsonNode *torrent_add_from_file(gchar * target, gint flags)
{
JsonNode *root;
JsonObject *args;
diff --git a/src/requests.h b/src/requests.h
index 5ea9b2a..7430fda 100644
--- a/src/requests.h
+++ b/src/requests.h
@@ -23,6 +23,8 @@
#include <glib-object.h>
#include <json-glib/json-glib.h>
+#include "trg-client.h"
+
JsonNode *generic_request(gchar * method, JsonArray * array);
JsonNode *session_set(void);
@@ -34,7 +36,8 @@ JsonNode *torrent_start(JsonArray * array);
JsonNode *torrent_verify(JsonArray * array);
JsonNode *torrent_reannounce(JsonArray * array);
JsonNode *torrent_remove(JsonArray * array, int removeData);
-JsonNode *torrent_add(gchar * filename, gint flags);
+JsonNode *torrent_add_from_response(trg_response *response, gint flags);
+JsonNode *torrent_add_from_file(gchar * filename, gint flags);
JsonNode *torrent_add_url(const gchar * url, gboolean paused);
JsonNode *torrent_set_location(JsonArray * array, gchar * location,
gboolean move);
diff --git a/src/torrent-cell-renderer.c b/src/torrent-cell-renderer.c
index 654d7bd..5c6abd2 100644
--- a/src/torrent-cell-renderer.c
+++ b/src/torrent-cell-renderer.c
@@ -445,8 +445,6 @@ get_size_compact(TorrentCellRenderer * cell,
g_object_unref(icon);
}
-#define MAX3(a,b,c) MAX(a,MAX(b,c))
-
static void
get_size_full(TorrentCellRenderer * cell,
GtkWidget * widget, gint * width, gint * height)
diff --git a/src/trg-client.c b/src/trg-client.c
index 1ffde9b..f03e893 100644
--- a/src/trg-client.c
+++ b/src/trg-client.c
@@ -82,6 +82,7 @@ struct _TrgClientPrivate {
TrgPrefs *prefs;
GPrivate *tlsKey;
gint configSerial;
+ guint http_class;
GMutex *configMutex;
gboolean seedRatioLimited;
gdouble seedRatioLimit;
@@ -441,9 +442,15 @@ void trg_client_configunlock(TrgClient * tc)
void trg_response_free(trg_response * response)
{
- if (response->obj)
- json_object_unref(response->obj);
- g_free(response);
+ if (response) {
+ if (response->obj)
+ json_object_unref(response->obj);
+
+ if (response->raw)
+ g_free(response->raw);
+
+ g_free(response);
+ }
}
static size_t
@@ -483,61 +490,20 @@ header_callback(void *ptr, size_t size, size_t nmemb, void *data)
return (nmemb * size);
}
-static void trg_tls_update(TrgClient * tc, trg_tls * tls, gint serial)
-{
- gchar *proxy;
-
- curl_easy_setopt(tls->curl, CURLOPT_PASSWORD,
- trg_client_get_password(tc));
- curl_easy_setopt(tls->curl, CURLOPT_USERNAME,
- trg_client_get_username(tc));
- curl_easy_setopt(tls->curl, CURLOPT_URL, trg_client_get_url(tc));
-
-#ifndef CURL_NO_SSL
- if (trg_client_get_ssl(tc) && !trg_client_get_ssl_validate(tc)) {
-
- curl_easy_setopt(tls->curl, CURLOPT_SSL_VERIFYHOST, 0);
- curl_easy_setopt(tls->curl, CURLOPT_SSL_VERIFYPEER, 0);
- }
-#endif
-
- proxy = trg_client_get_proxy(tc);
- if (proxy) {
- curl_easy_setopt(tls->curl, CURLOPT_PROXYTYPE, CURLPROXY_HTTP);
- curl_easy_setopt(tls->curl, CURLOPT_PROXY, proxy);
- }
-
- tls->serial = serial;
-}
-
trg_tls *trg_tls_new(TrgClient * tc)
{
trg_tls *tls = g_new0(trg_tls, 1);
tls->curl = curl_easy_init();
- curl_easy_setopt(tls->curl, CURLOPT_USERAGENT, PACKAGE_NAME);
- curl_easy_setopt(tls->curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
- curl_easy_setopt(tls->curl, CURLOPT_WRITEFUNCTION,
- &http_receive_callback);
- curl_easy_setopt(tls->curl, CURLOPT_HEADERFUNCTION, &header_callback);
- curl_easy_setopt(tls->curl, CURLOPT_WRITEHEADER, (void *) tc);
-
tls->serial = -1;
return tls;
}
-static int
-trg_http_perform_inner(TrgClient * tc, gchar * reqstr,
- trg_response * response, gboolean recurse)
-{
- TrgClientPrivate *priv = tc->priv;
- TrgPrefs *prefs = trg_client_get_prefs(tc);
- gpointer threadLocalStorage = g_private_get(priv->tlsKey);
- trg_tls *tls;
- long httpCode = 0;
- gchar *session_id;
- struct curl_slist *headers = NULL;
+static trg_tls *get_tls(TrgClient *tc) {
+ TrgClientPrivate *priv = tc->priv;
+ gpointer threadLocalStorage = g_private_get(priv->tlsKey);
+ trg_tls *tls;
if (!threadLocalStorage) {
tls = trg_tls_new(tc);
@@ -546,37 +512,104 @@ trg_http_perform_inner(TrgClient * tc, gchar * reqstr,
tls = (trg_tls *) threadLocalStorage;
}
+ return tls;
+}
+
+static CURL* get_curl(TrgClient *tc, guint http_class)
+{
+ TrgClientPrivate *priv = tc->priv;
+ TrgPrefs *prefs = trg_client_get_prefs(tc);
+ trg_tls *tls = get_tls(tc);
+ CURL *curl = tls->curl;
+
g_mutex_lock(priv->configMutex);
- if (priv->configSerial > tls->serial)
- trg_tls_update(tc, tls, priv->configSerial);
+ if (priv->configSerial > tls->serial || http_class != priv->http_class) {
+ gchar *proxy;
+
+ curl_easy_reset(curl);
+
+ curl_easy_setopt(curl, CURLOPT_USERAGENT, PACKAGE_NAME);
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION,
+ &http_receive_callback);
+ //curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
+
+ if (http_class == HTTP_CLASS_TRANSMISSION) {
+ curl_easy_setopt(curl, CURLOPT_WRITEHEADER, (void *) tc);
+ curl_easy_setopt(curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
+ curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, &header_callback);
+ curl_easy_setopt(curl, CURLOPT_PASSWORD,
+ trg_client_get_password(tc));
+ curl_easy_setopt(curl, CURLOPT_USERNAME,
+ trg_client_get_username(tc));
+ curl_easy_setopt(curl, CURLOPT_URL, trg_client_get_url(tc));
+ }
+
+ #ifndef CURL_NO_SSL
+ if (trg_client_get_ssl(tc) && !trg_client_get_ssl_validate(tc)) {
- session_id = trg_client_get_session_id(tc);
- if (session_id) {
- headers = curl_slist_append(NULL, session_id);
- curl_easy_setopt(tls->curl, CURLOPT_HTTPHEADER, headers);
+ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0);
+ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
+ }
+ #endif
+
+ proxy = trg_client_get_proxy(tc);
+ if (proxy) {
+ curl_easy_setopt(curl, CURLOPT_PROXYTYPE, CURLPROXY_HTTP);
+ curl_easy_setopt(curl, CURLOPT_PROXY, proxy);
+ }
+
+ tls->serial = priv->configSerial;
+ priv->http_class = http_class;
}
- curl_easy_setopt(tls->curl, CURLOPT_TIMEOUT,
- (long) trg_prefs_get_int(prefs, TRG_PREFS_KEY_TIMEOUT,
- TRG_PREFS_CONNECTION));
+ if (http_class == HTTP_CLASS_TRANSMISSION)
+ curl_easy_setopt(curl, CURLOPT_URL, trg_client_get_url(tc));
+
+ curl_easy_setopt(curl, CURLOPT_TIMEOUT,
+ (long) trg_prefs_get_int(prefs, TRG_PREFS_KEY_TIMEOUT,
+ TRG_PREFS_CONNECTION));
g_mutex_unlock(priv->configMutex);
+ /* Headers are set on each use, then freed, so make sure invalid headers aren't still around. */
+ curl_easy_setopt(curl, CURLOPT_HTTPHEADER, NULL);
+
+ return curl;
+
+}
+
+static int
+trg_http_perform_inner(TrgClient * tc, gchar * reqstr,
+ trg_response * response, gboolean recurse)
+{
+ CURL* curl = get_curl(tc, HTTP_CLASS_TRANSMISSION);
+
+ struct curl_slist *headers = NULL;
+ gchar *session_id = NULL;
+ long httpCode = 0;
+
response->size = 0;
response->raw = NULL;
- curl_easy_setopt(tls->curl, CURLOPT_POSTFIELDS, reqstr);
- curl_easy_setopt(tls->curl, CURLOPT_WRITEDATA, (void *) response);
- response->status = curl_easy_perform(tls->curl);
+ curl_easy_setopt(curl, CURLOPT_POSTFIELDS, reqstr);
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *) response);
+
+ session_id = trg_client_get_session_id(tc);
+ if (session_id) {
+ headers = curl_slist_append(NULL, session_id);
+ curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
+ }
+
+ response->status = curl_easy_perform(curl);
+
+ curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpCode);
if (session_id) {
g_free(session_id);
curl_slist_free_all(headers);
}
- curl_easy_getinfo(tls->curl, CURLINFO_RESPONSE_CODE, &httpCode);
-
if (response->status == CURLE_OK) {
if (httpCode == HTTP_CONFLICT && recurse == TRUE)
return trg_http_perform_inner(tc, reqstr, response, FALSE);
@@ -600,11 +633,38 @@ trg_response *dispatch(TrgClient * tc, JsonNode * req)
json_node_free(req);
#ifdef DEBUG
if (g_getenv("TRG_SHOW_OUTGOING"))
- g_debug("=>(OUTgoing)=>: %s", serialized);
+ g_message("=>(OUTgoing)=>: %s", serialized);
#endif
return dispatch_str(tc, serialized);
}
+trg_response *dispatch_public_http(TrgClient *tc, trg_request *req) {
+ trg_response *response = g_new0(trg_response, 1);
+
+ CURL* curl = get_curl(tc, HTTP_CLASS_PUBLIC);
+
+ long httpCode = 0;
+
+ response->size = 0;
+ response->raw = NULL;
+
+ curl_easy_setopt(curl, CURLOPT_URL, req->url);
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *) response);
+
+ response->status = curl_easy_perform(curl);
+
+ g_free(req->url);
+ req->url = NULL;
+
+ curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &httpCode);
+
+ if (response->status == CURLE_OK && httpCode != HTTP_OK) {
+ response->status = (-httpCode) - 100;
+ }
+
+ return response;
+}
+
trg_response *dispatch_str(TrgClient * tc, gchar * req)
{
trg_response *response = g_new0(trg_response, 1);
@@ -645,6 +705,8 @@ static void dispatch_async_threadfunc(trg_request * req, TrgClient * tc)
if (req->str)
rsp = dispatch_str(tc, req->str);
+ else if (req->url)
+ rsp = dispatch_public_http(tc, req);
else
rsp = dispatch(tc, req->node);
@@ -701,6 +763,13 @@ dispatch_async_str(TrgClient * tc, gchar * req,
return dispatch_async_common(tc, trg_req, callback, data);
}
+gboolean async_http_request(TrgClient *tc, gchar *url, GSourceFunc callback, gpointer data) {
+ trg_request *trg_req = g_new0(trg_request, 1);
+ trg_req->url = g_strdup(url);
+
+ return dispatch_async_common(tc, trg_req, callback, data);
+}
+
gboolean trg_client_update_session(TrgClient * tc, GSourceFunc callback,
gpointer data)
{
diff --git a/src/trg-client.h b/src/trg-client.h
index a0bee33..ce98a4e 100644
--- a/src/trg-client.h
+++ b/src/trg-client.h
@@ -57,6 +57,9 @@
#define FAIL_RESPONSE_UNSUCCESSFUL -3
#define DISPATCH_POOL_SIZE 3
+#define HTTP_CLASS_TRANSMISSION 0
+#define HTTP_CLASS_PUBLIC 1
+
typedef struct {
int status;
int size;
@@ -69,6 +72,7 @@ typedef struct {
gint connid;
JsonNode *node;
gchar *str;
+ gchar *url;
GSourceFunc callback;
gpointer cb_data;
} trg_request;
@@ -109,6 +113,7 @@ typedef struct {
* We lock updating (and checking for updates) with priv->configMutex
*/
int serial;
+ guint client_class;
CURL *curl;
} trg_tls;
@@ -121,8 +126,11 @@ int trg_http_perform(TrgClient * client, gchar * reqstr,
/* stuff that used to be in dispatch.c */
trg_response *dispatch(TrgClient * client, JsonNode * req);
trg_response *dispatch_str(TrgClient * client, gchar * req);
+trg_response *dispatch_public_http(TrgClient *tc, trg_request *req);
gboolean dispatch_async(TrgClient * client, JsonNode * req,
GSourceFunc callback, gpointer data);
+gboolean async_http_request(TrgClient *tc, gchar *url, GSourceFunc callback, gpointer data);
+
/* end dispatch.c*/
GType trg_client_get_type(void);
diff --git a/src/trg-destination-combo.c b/src/trg-destination-combo.c
index 099b6a5..f7fc2f0 100644
--- a/src/trg-destination-combo.c
+++ b/src/trg-destination-combo.c
@@ -397,7 +397,7 @@ gchar *trg_destination_combo_get_dir(TrgDestinationCombo * combo)
if (type == DEST_LABEL) {
gtk_tree_model_get(model, &iter, DEST_COLUMN_DIR, &value, -1);
- return value;
+ return g_strdup(value);
}
}
diff --git a/src/trg-file-parser.c b/src/trg-file-parser.c
index fbfb6aa..ff4ac62 100644
--- a/src/trg-file-parser.c
+++ b/src/trg-file-parser.c
@@ -134,32 +134,11 @@ static trg_files_tree_node *trg_parse_torrent_file_nodes(be_node *
return top_node;
}
-trg_torrent_file *trg_parse_torrent_file(const gchar * filename)
-{
- GError *error = NULL;
- GMappedFile *mf;
- be_node *top_node, *info_node, *name_node;
- trg_torrent_file *ret = NULL;
-
- if (!g_file_test(filename, G_FILE_TEST_IS_REGULAR)) {
- g_message("%s does not exist", filename);
- return NULL;
- }
-
- mf = g_mapped_file_new(filename, FALSE, &error);
+trg_torrent_file *trg_parse_torrent_data(const gchar *data, gsize length) {
+ trg_torrent_file *ret = NULL;
+ be_node *top_node, *info_node, *name_node;
- if (error) {
- g_error("%s", error->message);
- g_error_free(error);
- g_mapped_file_unref(mf);
- return NULL;
- } else {
- top_node =
- be_decoden(g_mapped_file_get_contents(mf),
- g_mapped_file_get_length(mf));
- }
-
- g_mapped_file_unref(mf);
+ top_node = be_decoden(data, length);
if (!top_node) {
return NULL;
@@ -199,3 +178,30 @@ trg_torrent_file *trg_parse_torrent_file(const gchar * filename)
be_free(top_node);
return ret;
}
+
+trg_torrent_file *trg_parse_torrent_file(const gchar * filename)
+{
+ GError *error = NULL;
+ trg_torrent_file *ret = NULL;
+ GMappedFile *mf;
+
+ if (!g_file_test(filename, G_FILE_TEST_IS_REGULAR)) {
+ g_message("%s does not exist", filename);
+ return NULL;
+ }
+
+ mf = g_mapped_file_new(filename, FALSE, &error);
+
+ if (error) {
+ g_error("%s", error->message);
+ g_error_free(error);
+ g_mapped_file_unref(mf);
+ return NULL;
+ } else {
+ ret = trg_parse_torrent_data(g_mapped_file_get_contents(mf), g_mapped_file_get_length(mf));
+ }
+
+ g_mapped_file_unref(mf);
+
+ return ret;
+}
diff --git a/src/trg-files-tree-view.c b/src/trg-files-tree-view.c
index 1c93ad6..ae9e071 100644
--- a/src/trg-files-tree-view.c
+++ b/src/trg-files-tree-view.c
@@ -93,7 +93,7 @@ static gboolean on_files_update(gpointer data)
response->cb_data = priv->win;
- return on_generic_interactive_action(data);
+ return on_generic_interactive_action_response(data);
}
static void send_updated_file_prefs(TrgFilesTreeView * tv)
diff --git a/src/trg-main-window.c b/src/trg-main-window.c
index 906a384..4cf6047 100644
--- a/src/trg-main-window.c
+++ b/src/trg-main-window.c
@@ -17,9 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#ifdef HAVE_CONFIG_H
#include "config.h"
-#endif
#include <stdlib.h>
#include <string.h>
@@ -73,8 +71,12 @@
#include "trg-menu-bar.h"
#include "trg-status-bar.h"
#include "trg-stats-dialog.h"
+#ifdef HAVE_RSSGLIB
+#include "trg-rss-window.h"
+#endif
#include "trg-remote-prefs-dialog.h"
#include "trg-preferences-dialog.h"
+#include "upload.h"
/* The rather large main window class, which glues everything together. */
@@ -479,7 +481,7 @@ static void pause_cb(GtkWidget * w G_GNUC_UNUSED, TrgMainWindow * win)
dispatch_async(priv->client,
torrent_pause(build_json_id_array
(priv->torrentTreeView)),
- on_generic_interactive_action, win);
+ on_generic_interactive_action_response, win);
}
static void pause_all_cb(GtkWidget * w G_GNUC_UNUSED, TrgMainWindow * win)
@@ -488,7 +490,7 @@ static void pause_all_cb(GtkWidget * w G_GNUC_UNUSED, TrgMainWindow * win)
if (trg_client_is_connected(priv->client))
dispatch_async(priv->client, torrent_pause(NULL),
- on_generic_interactive_action, win);
+ on_generic_interactive_action_response, win);
}
gint trg_add_from_filename(TrgMainWindow * win, gchar ** uris)
@@ -504,10 +506,14 @@ gint trg_add_from_filename(TrgMainWindow * win, gchar ** uris)
return EXIT_SUCCESS;
}
- if (uris)
- for (i = 0; uris[i]; i++)
- if (uris[i])
+ if (uris) {
+ for (i = 0; uris[i]; i++) {
+ if (is_minimised_arg(uris[i]))
+ g_free(uris[i]);
+ else if (uris[i])
filesList = g_slist_append(filesList, uris[i]);
+ }
+ }
g_free(uris);
@@ -517,20 +523,20 @@ gint trg_add_from_filename(TrgMainWindow * win, gchar ** uris)
if (trg_prefs_get_bool(prefs, TRG_PREFS_KEY_ADD_OPTIONS_DIALOG,
TRG_PREFS_GLOBAL)) {
TrgTorrentAddDialog *dialog =
- trg_torrent_add_dialog_new(win, client,
+ trg_torrent_add_dialog_new_from_filenames(win, client,
filesList);
gtk_widget_show_all(GTK_WIDGET(dialog));
} else {
- struct add_torrent_threadfunc_args *args =
- g_new0(struct add_torrent_threadfunc_args, 1);
- args->list = filesList;
- args->cb_data = win;
- args->client = client;
- args->extraArgs = FALSE;
- args->flags = trg_prefs_get_add_flags(prefs);
-
- launch_add_thread(args);
+ trg_upload *upload = g_new0(trg_upload, 1);
+
+ upload->list = filesList;
+ upload->main_window = win;
+ upload->client = client;
+ upload->extra_args = FALSE;
+ upload->flags = trg_prefs_get_add_flags(prefs);
+
+ trg_do_upload(upload);
}
return EXIT_SUCCESS;
@@ -542,7 +548,7 @@ static void resume_all_cb(GtkWidget * w G_GNUC_UNUSED, TrgMainWindow * win)
if (trg_client_is_connected(priv->client))
dispatch_async(priv->client, torrent_start(NULL),
- on_generic_interactive_action, win);
+ on_generic_interactive_action_response, win);
}
static void resume_cb(GtkWidget * w G_GNUC_UNUSED, TrgMainWindow * win)
@@ -553,7 +559,7 @@ static void resume_cb(GtkWidget * w G_GNUC_UNUSED, TrgMainWindow * win)
dispatch_async(priv->client,
torrent_start(build_json_id_array
(priv->torrentTreeView)),
- on_generic_interactive_action, win);
+ on_generic_interactive_action_response, win);
}
static void disconnect_cb(GtkWidget * w G_GNUC_UNUSED, gpointer data)
@@ -712,7 +718,7 @@ static void reannounce_cb(GtkWidget * w G_GNUC_UNUSED, TrgMainWindow * win)
dispatch_async(priv->client,
torrent_reannounce(build_json_id_array
(priv->torrentTreeView)),
- on_generic_interactive_action, win);
+ on_generic_interactive_action_response, win);
}
static void verify_cb(GtkWidget * w G_GNUC_UNUSED, TrgMainWindow * win)
@@ -723,7 +729,7 @@ static void verify_cb(GtkWidget * w G_GNUC_UNUSED, TrgMainWindow * win)
dispatch_async(priv->client,
torrent_verify(build_json_id_array
(priv->torrentTreeView)),
- on_generic_interactive_action, win);
+ on_generic_interactive_action_response, win);
}
static void start_now_cb(GtkWidget * w G_GNUC_UNUSED, TrgMainWindow * win)
@@ -734,7 +740,7 @@ static void start_now_cb(GtkWidget * w G_GNUC_UNUSED, TrgMainWindow * win)
dispatch_async(priv->client,
torrent_start_now(build_json_id_array
(priv->torrentTreeView)),
- on_generic_interactive_action, win);
+ on_generic_interactive_action_response, win);
}
static void up_queue_cb(GtkWidget * w G_GNUC_UNUSED, TrgMainWindow * win)
@@ -745,7 +751,7 @@ static void up_queue_cb(GtkWidget * w G_GNUC_UNUSED, TrgMainWindow * win)
dispatch_async(priv->client,
torrent_queue_move_up(build_json_id_array
(priv->torrentTreeView)),
- on_generic_interactive_action, win);
+ on_generic_interactive_action_response, win);
}
static void top_queue_cb(GtkWidget * w G_GNUC_UNUSED, TrgMainWindow * win)
@@ -756,7 +762,7 @@ static void top_queue_cb(GtkWidget * w G_GNUC_UNUSED, TrgMainWindow * win)
dispatch_async(priv->client,
torrent_queue_move_top(build_json_id_array
(priv->torrentTreeView)),
- on_generic_interactive_action, win);
+ on_generic_interactive_action_response, win);
}
static void
@@ -768,7 +774,7 @@ bottom_queue_cb(GtkWidget * w G_GNUC_UNUSED, TrgMainWindow * win)
dispatch_async(priv->client,
torrent_queue_move_bottom(build_json_id_array
(priv->torrentTreeView)),
- on_generic_interactive_action, win);
+ on_generic_interactive_action_response, win);
}
static void down_queue_cb(GtkWidget * w G_GNUC_UNUSED, TrgMainWindow * win)
@@ -779,7 +785,7 @@ static void down_queue_cb(GtkWidget * w G_GNUC_UNUSED, TrgMainWindow * win)
dispatch_async(priv->client,
torrent_queue_move_down(build_json_id_array
(priv->torrentTreeView)),
- on_generic_interactive_action, win);
+ on_generic_interactive_action_response, win);
}
static gint
@@ -881,7 +887,7 @@ static void remove_cb(GtkWidget * w G_GNUC_UNUSED, TrgMainWindow * win)
_("<big><b>Remove %d torrents?</b></big>"),
GTK_STOCK_REMOVE) == GTK_RESPONSE_ACCEPT)
dispatch_async(priv->client, torrent_remove(ids, FALSE),
- on_generic_interactive_action, win);
+ on_generic_interactive_action_response, win);
else
json_array_unref(ids);
}
@@ -924,6 +930,22 @@ static void view_stats_toggled_cb(GtkWidget * w, gpointer data)
}
}
+#ifdef HAVE_RSSGLIB
+static void view_rss_toggled_cb(GtkWidget * w, gpointer data)
+{
+ TrgMainWindow *win = TRG_MAIN_WINDOW(data);
+ TrgMainWindowPrivate *priv = win->priv;
+
+ if (trg_client_is_connected(priv->client)) {
+ TrgRssWindow *rss =
+ trg_rss_window_get_instance(TRG_MAIN_WINDOW(data), priv->client);
+
+ gtk_widget_show_all(GTK_WIDGET(rss));
+ gtk_window_present(GTK_WINDOW(rss));
+ }
+}
+#endif
+
static void
view_states_toggled_cb(GtkCheckMenuItem * w, TrgMainWindow * win)
{
@@ -1570,13 +1592,10 @@ gboolean on_delete_complete(gpointer data)
trg_client_update_session(priv->client, on_session_get,
response->cb_data);
- return on_generic_interactive_action(data);
+ return on_generic_interactive_action_response(data);
}
-gboolean on_generic_interactive_action(gpointer data)
-{
- trg_response *response = (trg_response *) data;
- TrgMainWindow *win = TRG_MAIN_WINDOW(response->cb_data);
+void on_generic_interactive_action(TrgMainWindow *win, trg_response *response) {
TrgMainWindowPrivate *priv = win->priv;
TrgClient *tc = priv->client;
@@ -1596,6 +1615,15 @@ gboolean on_generic_interactive_action(gpointer data)
}
trg_response_free(response);
+}
+
+gboolean on_generic_interactive_action_response(gpointer data)
+{
+ trg_response *response = (trg_response *) data;
+ TrgMainWindow *win = TRG_MAIN_WINDOW(response->cb_data);
+
+ on_generic_interactive_action(win, response);
+
return FALSE;
}
@@ -1738,6 +1766,9 @@ static TrgMenuBar *trg_main_window_menu_bar_new(TrgMainWindow * win)
#if TRG_WITH_GRAPH
*b_show_graph,
#endif
+#ifdef HAVE_RSSGLIB
+ *b_view_rss,
+#endif
*b_start_now;
TrgMenuBar *menuBar;
@@ -1766,6 +1797,9 @@ static TrgMenuBar *trg_main_window_menu_bar_new(TrgMainWindow * win)
#if TRG_WITH_GRAPH
"show-graph", &b_show_graph,
#endif
+#ifdef HAVE_RSSGLIB
+ "view-rss-button", &b_view_rss,
+#endif
"up-queue", &b_up_queue, "down-queue", &b_down_queue,
"top-queue", &b_top_queue, "bottom-queue",
&b_bottom_queue, "start-now", &b_start_now, NULL);
@@ -1810,6 +1844,10 @@ static TrgMenuBar *trg_main_window_menu_bar_new(TrgMainWindow * win)
G_CALLBACK(view_states_toggled_cb), win);
g_signal_connect(b_view_stats, "activate",
G_CALLBACK(view_stats_toggled_cb), win);
+#ifdef HAVE_RSSGLIB
+ g_signal_connect(b_view_rss, "activate",
+ G_CALLBACK(view_rss_toggled_cb), win);
+#endif
#if TRG_WITH_GRAPH
g_signal_connect(b_show_graph, "toggled",
G_CALLBACK(trg_main_window_toggle_graph_cb), win);
@@ -1928,7 +1966,7 @@ static void set_limit_cb(GtkWidget * w, TrgMainWindow * win)
json_object_set_boolean_member(args, enabledKey, speed >= 0);
if (limitIds)
- dispatch_async(priv->client, req, on_generic_interactive_action,
+ dispatch_async(priv->client, req, on_generic_interactive_action_response,
win);
else
dispatch_async(priv->client, req, on_session_set, win);
@@ -1953,7 +1991,7 @@ static void set_priority_cb(GtkWidget * w, TrgMainWindow * win)
json_object_set_int_member(args, FIELD_BANDWIDTH_PRIORITY, priority);
- dispatch_async(priv->client, req, on_generic_interactive_action, win);
+ dispatch_async(priv->client, req, on_generic_interactive_action_response, win);
}
static GtkWidget *limit_item_new(TrgMainWindow * win, GtkWidget * menu,
diff --git a/src/trg-main-window.h b/src/trg-main-window.h
index 3c6ace5..3fb69af 100644
--- a/src/trg-main-window.h
+++ b/src/trg-main-window.h
@@ -63,7 +63,8 @@ GType trg_main_window_get_type(void);
gint trg_add_from_filename(TrgMainWindow * win, gchar ** uris);
gboolean on_session_set(gpointer data);
gboolean on_delete_complete(gpointer data);
-gboolean on_generic_interactive_action(gpointer data);
+void on_generic_interactive_action(TrgMainWindow *win, trg_response *response);
+gboolean on_generic_interactive_action_response(gpointer data);
void auto_connect_if_required(TrgMainWindow * win);
void trg_main_window_set_start_args(TrgMainWindow * win, gchar ** args);
TrgMainWindow *trg_main_window_new(TrgClient * tc, gboolean minonstart);
diff --git a/src/trg-menu-bar.c b/src/trg-menu-bar.c
index df96240..37c6c2c 100644
--- a/src/trg-menu-bar.c
+++ b/src/trg-menu-bar.c
@@ -17,6 +17,8 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
+
#include <glib/gi18n.h>
#include <gtk/gtk.h>
#include <gdk/gdkkeysyms.h>
@@ -49,6 +51,9 @@ enum {
PROP_LOCAL_PREFS_BUTTON,
PROP_ABOUT_BUTTON,
PROP_VIEW_STATS_BUTTON,
+#ifdef HAVE_RSSGLIB
+ PROP_VIEW_RSS_BUTTON,
+#endif
PROP_VIEW_STATES_BUTTON,
PROP_VIEW_NOTEBOOK_BUTTON,
PROP_QUIT,
@@ -96,6 +101,9 @@ struct _TrgMenuBarPrivate {
GtkWidget *mb_view_states;
GtkWidget *mb_view_notebook;
GtkWidget *mb_view_stats;
+#ifdef HAVE_RSSGLIB
+ GtkWidget *mb_view_rss;
+#endif
GtkWidget *mb_about;
GtkWidget *mb_quit;
GtkWidget *mb_directory_filters;
@@ -140,6 +148,9 @@ void trg_menu_bar_connected_change(TrgMenuBar * mb, gboolean connected)
gtk_widget_set_sensitive(priv->mb_disconnect, connected);
gtk_widget_set_sensitive(priv->mb_remote_prefs, connected);
gtk_widget_set_sensitive(priv->mb_view_stats, connected);
+#ifdef HAVE_RSSGLIB
+ gtk_widget_set_sensitive(priv->mb_view_rss, connected);
+#endif
gtk_widget_set_sensitive(priv->mb_resume_all, connected);
gtk_widget_set_sensitive(priv->mb_pause_all, connected);
}
@@ -273,6 +284,11 @@ trg_menu_bar_get_property(GObject * object, guint property_id,
case PROP_VIEW_STATS_BUTTON:
g_value_set_object(value, priv->mb_view_stats);
break;
+#ifdef HAVE_RSSGLIB
+ case PROP_VIEW_RSS_BUTTON:
+ g_value_set_object(value, priv->mb_view_rss);
+ break;
+#endif
case PROP_QUIT:
g_value_set_object(value, priv->mb_quit);
break;
@@ -542,6 +558,14 @@ static GtkWidget *trg_menu_bar_view_menu_new(TrgMenuBar * mb)
gtk_widget_set_sensitive(priv->mb_view_stats, FALSE);
gtk_menu_shell_append(GTK_MENU_SHELL(viewMenu), priv->mb_view_stats);
+#ifdef HAVE_RSSGLIB
+ priv->mb_view_rss =
+ gtk_menu_item_new_with_mnemonic(_("_RSS"));
+ //trg_menu_bar_accel_add(mb, priv->mb_view_rss, GDK_F7, 0);
+ gtk_widget_set_sensitive(priv->mb_view_rss, FALSE);
+ gtk_menu_shell_append(GTK_MENU_SHELL(viewMenu), priv->mb_view_rss);
+#endif
+
return view;
}
@@ -578,7 +602,7 @@ trg_menu_bar_file_connect_item_new(TrgMainWindow * win,
{
GtkWidget *item = gtk_check_menu_item_new_with_label(text);
- gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item), checked);
+ gtk_check_menu_item_set_active( GTK_CHECK_MENU_ITEM(item), checked);
g_object_set_data(G_OBJECT(item), "profile", profile);
gtk_check_menu_item_set_draw_as_radio(GTK_CHECK_MENU_ITEM(item), TRUE);
@@ -870,6 +894,11 @@ static void trg_menu_bar_class_init(TrgMenuBarClass * klass)
trg_menu_bar_install_widget_prop(object_class, PROP_VIEW_STATS_BUTTON,
"view-stats-button",
"View stats button");
+#ifdef HAVE_RSSGLIB
+ trg_menu_bar_install_widget_prop(object_class, PROP_VIEW_RSS_BUTTON,
+ "view-rss-button",
+ "View rss button");
+#endif
trg_menu_bar_install_widget_prop(object_class, PROP_VIEW_STATES_BUTTON,
"view-states-button",
"View states Button");
diff --git a/src/trg-persistent-tree-view.c b/src/trg-persistent-tree-view.c
index e116fdf..c608acb 100644
--- a/src/trg-persistent-tree-view.c
+++ b/src/trg-persistent-tree-view.c
@@ -37,7 +37,7 @@ typedef struct _TrgPersistentTreeViewPrivate
TrgPersistentTreeViewPrivate;
enum {
- PROP_0, PROP_PREFS, PROP_KEY, PROP_MODEL
+ PROP_0, PROP_PREFS, PROP_KEY, PROP_MODEL, PROP_CONF_FLAGS
};
struct _TrgPersistentTreeViewPrivate {
@@ -52,6 +52,7 @@ struct _TrgPersistentTreeViewPrivate {
trg_pref_widget_desc *wd;
GtkTreeModel *model;
trg_persistent_tree_view_column *addSelect;
+ gint conf_flags;
};
static void selection_changed(TrgPersistentTreeView * ptv,
@@ -322,6 +323,9 @@ trg_persistent_tree_view_set_property(GObject * object,
case PROP_MODEL:
priv->model = g_value_get_object(value);
break;
+ case PROP_CONF_FLAGS:
+ priv->conf_flags = g_value_get_int(value);
+ break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
break;
@@ -411,7 +415,7 @@ static GObject *trg_persistent_tree_view_constructor(GType type,
gtk_box_pack_start(GTK_BOX(object), hbox, FALSE, FALSE, 4);
priv->wd = trg_pref_widget_desc_new(GTK_WIDGET(priv->tv), priv->key,
- TRG_PREFS_PROFILE);
+ priv->conf_flags);
priv->wd->widget = GTK_WIDGET(object);
priv->wd->saveFunc = &trg_persistent_tree_view_save;
priv->wd->refreshFunc = &trg_persistent_tree_view_refresh;
@@ -462,6 +466,19 @@ trg_persistent_tree_view_class_init(TrgPersistentTreeViewClass * klass)
G_PARAM_STATIC_BLURB));
g_object_class_install_property(object_class,
+ PROP_CONF_FLAGS,
+ g_param_spec_int("conf-flags",
+ "Conf Flags",
+ "Conf Flags",
+ INT_MIN,
+ INT_MAX,
+ TRG_PREFS_PROFILE,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NAME |
+ G_PARAM_STATIC_NICK |
+ G_PARAM_STATIC_BLURB));
+
+ g_object_class_install_property(object_class,
PROP_MODEL,
g_param_spec_object("persistent-model",
"Persistent Model",
@@ -483,12 +500,12 @@ static void trg_persistent_tree_view_init(TrgPersistentTreeView * self)
TrgPersistentTreeView *trg_persistent_tree_view_new(TrgPrefs * prefs,
GtkListStore * model,
- const gchar * key)
+ const gchar * key, gint conf_flags)
{
GObject *obj =
g_object_new(TRG_TYPE_PERSISTENT_TREE_VIEW, "prefs", prefs,
"conf-key", key, "persistent-model",
- model,
+ model, "conf-flags", conf_flags,
NULL);
return TRG_PERSISTENT_TREE_VIEW(obj);
diff --git a/src/trg-persistent-tree-view.h b/src/trg-persistent-tree-view.h
index a5a7c8f..0d16fb6 100644
--- a/src/trg-persistent-tree-view.h
+++ b/src/trg-persistent-tree-view.h
@@ -57,7 +57,7 @@ typedef struct {
TrgPersistentTreeView *trg_persistent_tree_view_new(TrgPrefs * prefs,
GtkListStore * model,
- const gchar * key);
+ const gchar * key, gint conf_flags);
trg_pref_widget_desc
* trg_persistent_tree_view_get_widget_desc(TrgPersistentTreeView *
diff --git a/src/trg-preferences-dialog.c b/src/trg-preferences-dialog.c
index b9327c6..53e4fe9 100644
--- a/src/trg-preferences-dialog.c
+++ b/src/trg-preferences-dialog.c
@@ -17,9 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#ifdef HAVE_CONFIG_H
#include "config.h"
-#endif
#include <glib.h>
#include <glib/gi18n.h>
@@ -60,6 +58,7 @@ struct _TrgPreferencesDialogPrivate {
GtkWidget *profileNameEntry;
GtkWidget *fullUpdateCheck;
GList *widgets;
+ GtkWidget *notebook;
};
static GObject *instance = NULL;
@@ -601,7 +600,7 @@ static GtkWidget *trg_prefs_openExecPage(TrgPreferencesDialog * dlg)
model = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING);
ptv = trg_persistent_tree_view_new(priv->prefs, model,
- TRG_PREFS_KEY_EXEC_COMMANDS);
+ TRG_PREFS_KEY_EXEC_COMMANDS, TRG_PREFS_PROFILE);
trg_persistent_tree_view_set_add_select(ptv,
trg_persistent_tree_view_add_column
(ptv, 0,
@@ -621,6 +620,46 @@ static GtkWidget *trg_prefs_openExecPage(TrgPreferencesDialog * dlg)
return t;
}
+#ifdef HAVE_RSSGLIB
+static GtkWidget *trg_prefs_rss_page(TrgPreferencesDialog * dlg) {
+ TrgPreferencesDialogPrivate *priv =
+ TRG_PREFERENCES_DIALOG_GET_PRIVATE(dlg);
+ GtkWidget *t;
+ guint row = 0;
+ TrgPersistentTreeView *ptv;
+ trg_pref_widget_desc *wd;
+ GtkListStore *model;
+
+ t = hig_workarea_create();
+
+ hig_workarea_add_section_title(t, &row,
+ _("RSS Feeds"));
+
+ model = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING);
+
+ ptv = trg_persistent_tree_view_new(priv->prefs, model,
+ TRG_PREFS_KEY_RSS, TRG_PREFS_GLOBAL);
+ trg_persistent_tree_view_set_add_select(ptv,
+ trg_persistent_tree_view_add_column
+ (ptv, 0,
+ TRG_PREFS_RSS_SUBKEY_ID,
+ _("Name")));
+ trg_persistent_tree_view_add_column(ptv, 1,
+ TRG_PREFS_RSS_SUBKEY_URL,
+ _("URL"));
+
+ wd = trg_persistent_tree_view_get_widget_desc(ptv);
+ trg_pref_widget_refresh(dlg, wd);
+ priv->widgets = g_list_append(priv->widgets, wd);
+
+ gtk_table_attach(GTK_TABLE(t), GTK_WIDGET(ptv), 1, 2, row, row + 1,
+ GTK_EXPAND | GTK_SHRINK | GTK_FILL,
+ GTK_FILL | GTK_EXPAND | GTK_SHRINK, 0, 0);
+
+ return t;
+}
+#endif
+
static GtkWidget *trg_prefs_dirsPage(TrgPreferencesDialog * dlg)
{
TrgPreferencesDialogPrivate *priv =
@@ -639,7 +678,7 @@ static GtkWidget *trg_prefs_dirsPage(TrgPreferencesDialog * dlg)
model = gtk_list_store_new(2, G_TYPE_STRING, G_TYPE_STRING);
ptv = trg_persistent_tree_view_new(priv->prefs, model,
- TRG_PREFS_KEY_DESTINATIONS);
+ TRG_PREFS_KEY_DESTINATIONS, TRG_PREFS_GLOBAL);
trg_persistent_tree_view_set_add_select(ptv,
trg_persistent_tree_view_add_column
(ptv, 0,
@@ -890,7 +929,7 @@ static GObject *trg_preferences_dialog_constructor(GType type,
g_signal_connect(G_OBJECT(object), "response",
G_CALLBACK(trg_preferences_response_cb), NULL);
- notebook = gtk_notebook_new();
+ notebook = priv->notebook = gtk_notebook_new();
gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
trg_prefs_serverPage(TRG_PREFERENCES_DIALOG
@@ -917,6 +956,13 @@ static GObject *trg_preferences_dialog_constructor(GType type,
(object)),
gtk_label_new(_("Directories")));
+#ifdef HAVE_RSSGLIB
+ gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
+ trg_prefs_rss_page(TRG_PREFERENCES_DIALOG
+ (object)),
+ gtk_label_new(_("RSS Feeds")));
+#endif
+
gtk_container_set_border_width(GTK_CONTAINER(notebook), GUI_PAD);
gtk_box_pack_start(GTK_BOX(contentvbox), notebook, TRUE, TRUE, 0);
@@ -924,6 +970,11 @@ static GObject *trg_preferences_dialog_constructor(GType type,
return object;
}
+void trg_preferences_dialog_set_page(TrgPreferencesDialog *pref_dlg, guint page) {
+ TrgPreferencesDialogPrivate *priv = TRG_PREFERENCES_DIALOG_GET_PRIVATE(pref_dlg);
+ gtk_notebook_set_current_page(GTK_NOTEBOOK(priv->notebook), page);
+}
+
static void trg_preferences_dialog_init(TrgPreferencesDialog * pref_dlg)
{
}
diff --git a/src/trg-preferences-dialog.h b/src/trg-preferences-dialog.h
index f5e9825..e163964 100644
--- a/src/trg-preferences-dialog.h
+++ b/src/trg-preferences-dialog.h
@@ -60,6 +60,7 @@ GtkWidget *trg_preferences_dialog_get_instance(TrgMainWindow * win,
TrgClient * client);
trg_pref_widget_desc *trg_pref_widget_desc_new(GtkWidget * w, gchar * key,
int flags);
+void trg_preferences_dialog_set_page(TrgPreferencesDialog *pref_dlg, guint page);
G_END_DECLS
#endif /* TRG_PREFERENCES_WINDOW_H_ */
diff --git a/src/trg-prefs.c b/src/trg-prefs.c
index b559479..811c575 100644
--- a/src/trg-prefs.c
+++ b/src/trg-prefs.c
@@ -421,6 +421,12 @@ JsonArray *trg_prefs_get_profiles(TrgPrefs * p)
TRG_PREFS_KEY_PROFILES);
}
+JsonArray *trg_prefs_get_rss(TrgPrefs *p) {
+ TrgPrefsPrivate *priv = p->priv;
+ return json_object_get_array_member(priv->userObj,
+ TRG_PREFS_KEY_RSS);
+}
+
void
trg_prefs_set_double(TrgPrefs * p, const gchar * key, gdouble value,
int flags)
diff --git a/src/trg-prefs.h b/src/trg-prefs.h
index 9dda148..4245505 100644
--- a/src/trg-prefs.h
+++ b/src/trg-prefs.h
@@ -33,6 +33,7 @@
#define TRG_PREFS_KEY_RPC_URL_PATH "rpc-url-path"
#define TRG_PREFS_KEY_PROFILE_ID "profile-id"
#define TRG_PREFS_KEY_PROFILES "profiles"
+#define TRG_PREFS_KEY_RSS "rss"
#define TRG_PREFS_KEY_PROFILE_NAME "profile-name"
#define TRG_PREFS_KEY_HOSTNAME "hostname"
#define TRG_PREFS_KEY_PORT "port"
@@ -78,6 +79,9 @@
#define TRG_PREFS_KEY_EXEC_COMMANDS "exec-commands"
#define TRG_PREFS_KEY_EXEC_COMMANDS_SUBKEY_CMD "cmd"
#define TRG_PREFS_KEY_DESTINATIONS "destinations"
+#define TRG_PREFS_KEY_RSS "rss"
+#define TRG_PREFS_RSS_SUBKEY_ID "id"
+#define TRG_PREFS_RSS_SUBKEY_URL "url"
#define TRG_PREFS_KEY_LAST_MOVE_DESTINATION "last-move-destination"
#define TRG_PREFS_KEY_LAST_ADD_DESTINATION "last-add-destination"
#define TRG_PREFS_KEY_DESTINATIONS_SUBKEY_DIR "dir"
@@ -139,6 +143,7 @@ gboolean trg_prefs_get_bool(TrgPrefs * p, const gchar * key, int flags);
JsonObject *trg_prefs_get_profile(TrgPrefs * p);
JsonObject *trg_prefs_get_connection(TrgPrefs * p);
JsonArray *trg_prefs_get_profiles(TrgPrefs * p);
+JsonArray *trg_prefs_get_rss(TrgPrefs *p);
void trg_prefs_set_connection(TrgPrefs * p, JsonObject * profile);
gint trg_prefs_get_profile_id(TrgPrefs * p);
void trg_prefs_del_profile(TrgPrefs * p, JsonObject * profile);
diff --git a/src/trg-rss-cell-renderer.c b/src/trg-rss-cell-renderer.c
new file mode 100644
index 0000000..4765f2c
--- /dev/null
+++ b/src/trg-rss-cell-renderer.c
@@ -0,0 +1,429 @@
+#include "config.h"
+
+#ifdef HAVE_RSSGLIB
+
+#include <gtk/gtk.h>
+#include <gdk/gdk.h>
+#include <glib/gi18n.h>
+
+#include "icons.h"
+#include "hig.h"
+#include "util.h"
+#include "trg-rss-cell-renderer.h"
+
+enum {
+ PROP_TITLE = 1,
+ PROP_FEED,
+ PROP_PUBLISHED,
+ PROP_UPLOADED
+};
+
+#define SMALL_SCALE 0.9
+#define COMPACT_ICON_SIZE GTK_ICON_SIZE_MENU
+#define FULL_ICON_SIZE GTK_ICON_SIZE_DND
+
+#define FOREGROUND_COLOR_KEY "foreground-rgba"
+typedef GdkRGBA GtrColor;
+typedef cairo_t GtrDrawable;
+typedef GtkRequisition GtrRequisition;
+
+struct TrgRssCellRendererPrivate {
+ GtkCellRenderer *text_renderer;
+ GtkCellRenderer *icon_renderer;
+ GString *gstr1;
+ GString *gstr2;
+ gchar *title;
+ gchar *published;
+ gchar *feed;
+ gboolean uploaded;
+};
+
+static void
+trg_rss_cell_renderer_render(GtkCellRenderer * cell,
+ GtrDrawable * window, GtkWidget * widget,
+ const GdkRectangle * background_area,
+ const GdkRectangle * cell_area,
+ GtkCellRendererState flags);
+
+static void
+gtr_cell_renderer_render(GtkCellRenderer * renderer,
+ GtrDrawable * drawable,
+ GtkWidget * widget,
+ const GdkRectangle * area,
+ GtkCellRendererState flags)
+{
+ gtk_cell_renderer_render(renderer, drawable, widget, area, area,
+ flags);
+}
+
+static void trg_rss_cell_renderer_set_property(GObject * object,
+ guint property_id,
+ const GValue * v,
+ GParamSpec * pspec)
+{
+ TrgRssCellRenderer *self = TRG_RSS_CELL_RENDERER(object);
+ struct TrgRssCellRendererPrivate *p = self->priv;
+
+ switch (property_id) {
+ case PROP_TITLE:
+ g_free(p->title);
+ p->title = g_value_dup_string(v);
+ break;
+ case PROP_PUBLISHED:
+ g_free(p->published);
+ p->published = g_value_dup_string(v);
+ break;
+ case PROP_FEED:
+ g_free(p->feed);
+ p->feed = g_value_dup_string(v);
+ break;
+ case PROP_UPLOADED:
+ p->uploaded = g_value_get_boolean(v);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+trg_rss_cell_renderer_get_property(GObject * object,
+ guint property_id,
+ GValue * v, GParamSpec * pspec)
+{
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+}
+
+G_DEFINE_TYPE(TrgRssCellRenderer, trg_rss_cell_renderer,
+ GTK_TYPE_CELL_RENDERER)
+
+static void trg_rss_cell_renderer_dispose(GObject * o)
+{
+ TrgRssCellRenderer *r = TRG_RSS_CELL_RENDERER(o);
+
+ if (r && r->priv) {
+ struct TrgRssCellRendererPrivate *priv = r->priv;
+
+ g_string_free(priv->gstr1, TRUE);
+ g_free(priv->feed);
+ g_free(priv->published);
+ g_free(priv->title);
+ g_object_unref(G_OBJECT(priv->text_renderer));
+ g_object_unref(G_OBJECT(priv->icon_renderer));
+ r->priv = NULL;
+ }
+
+ G_OBJECT_CLASS(trg_rss_cell_renderer_parent_class)->dispose(o);
+}
+
+static GdkPixbuf *get_icon(TrgRssCellRenderer * r, GtkIconSize icon_size,
+ GtkWidget * for_widget)
+{
+ const char *mime_type = "file";
+
+ return gtr_get_mime_type_icon(mime_type, icon_size, for_widget);
+}
+
+static void
+trg_rss_cell_renderer_get_size(GtkCellRenderer * cell, GtkWidget * widget,
+ const GdkRectangle * cell_area,
+ gint * x_offset,
+ gint * y_offset,
+ gint * width, gint * height)
+{
+ TrgRssCellRenderer *self = TRG_RSS_CELL_RENDERER(cell);
+
+ if (self) {
+ struct TrgRssCellRendererPrivate *p = self->priv;
+ int xpad, ypad;
+ int h, w;
+ GtkRequisition icon_size;
+ GtkRequisition name_size;
+ GtkRequisition stat_size;
+ GtkRequisition prog_size;
+ GdkPixbuf *icon;
+
+ icon = get_icon(self, FULL_ICON_SIZE, widget);
+
+ gtk_cell_renderer_get_padding(cell, &xpad, &ypad);
+
+ /* get the idealized cell dimensions */
+ g_object_set(p->icon_renderer, "pixbuf", icon, NULL);
+ gtk_cell_renderer_get_preferred_size(p->icon_renderer, widget, NULL,
+ &icon_size);
+ g_object_set(p->text_renderer, "text", p->title,
+ "weight", PANGO_WEIGHT_BOLD, "scale", 1.0, "ellipsize",
+ PANGO_ELLIPSIZE_NONE, NULL);
+ gtk_cell_renderer_get_preferred_size(p->text_renderer, widget, NULL,
+ &name_size);
+ g_object_set(p->text_renderer, "text", p->feed, "weight",
+ PANGO_WEIGHT_NORMAL, "scale", SMALL_SCALE, NULL);
+ gtk_cell_renderer_get_preferred_size(p->text_renderer, widget, NULL,
+ &prog_size);
+ g_object_set(p->text_renderer, "text", p->published, NULL);
+ gtk_cell_renderer_get_preferred_size(p->text_renderer, widget, NULL,
+ &stat_size);
+
+ /**
+ *** LAYOUT
+ **/
+
+ if (width != NULL)
+ *width = w =
+ xpad * 2 + icon_size.width + GUI_PAD + MAX3(name_size.width,
+ prog_size.width,
+ stat_size.width);
+ if (height != NULL)
+ *height = h =
+ ypad * 2 + name_size.height + prog_size.height +
+ GUI_PAD_SMALL + stat_size.height;
+
+ /* cleanup */
+ g_object_unref(icon);
+
+ if (x_offset)
+ *x_offset = cell_area ? cell_area->x : 0;
+
+ if (y_offset) {
+ int xpad, ypad;
+ gtk_cell_renderer_get_padding(cell, &xpad, &ypad);
+ *y_offset =
+ cell_area ? (int) ((cell_area->height - (ypad * 2 + h)) /
+ 2.0) : 0;
+ }
+ }
+}
+
+static void
+trg_rss_cell_renderer_class_init(TrgRssCellRendererClass * klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
+ GtkCellRendererClass *cell_class = GTK_CELL_RENDERER_CLASS(klass);
+
+ g_type_class_add_private(klass,
+ sizeof(struct TrgRssCellRendererPrivate));
+
+ cell_class->render = trg_rss_cell_renderer_render;
+ cell_class->get_size = trg_rss_cell_renderer_get_size;
+ gobject_class->set_property = trg_rss_cell_renderer_set_property;
+ gobject_class->get_property = trg_rss_cell_renderer_get_property;
+ gobject_class->dispose = trg_rss_cell_renderer_dispose;
+
+ g_object_class_install_property(gobject_class,
+ PROP_TITLE,
+ g_param_spec_string("title",
+ "title",
+ "Title",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NAME
+ |
+ G_PARAM_STATIC_NICK
+ |
+ G_PARAM_STATIC_BLURB));
+
+ g_object_class_install_property(gobject_class,
+ PROP_PUBLISHED,
+ g_param_spec_string("published",
+ "published",
+ "Published",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NAME
+ |
+ G_PARAM_STATIC_NICK
+ |
+ G_PARAM_STATIC_BLURB));
+
+ g_object_class_install_property(gobject_class,
+ PROP_FEED,
+ g_param_spec_string("feed",
+ "feed",
+ "Feed",
+ NULL,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NAME
+ |
+ G_PARAM_STATIC_NICK
+ |
+ G_PARAM_STATIC_BLURB));
+
+ g_object_class_install_property(gobject_class,
+ PROP_UPLOADED,
+ g_param_spec_boolean("uploaded",
+ "uploaded",
+ "Uploaded",
+ FALSE,
+ G_PARAM_READWRITE |
+ G_PARAM_STATIC_NAME
+ |
+ G_PARAM_STATIC_NICK
+ |
+ G_PARAM_STATIC_BLURB));
+
+ /*g_object_class_install_property(gobject_class, P_BAR_HEIGHT,
+ g_param_spec_int("bar-height", NULL,
+ "Bar Height",
+ 1, INT_MAX,
+ DEFAULT_BAR_HEIGHT,
+ G_PARAM_READWRITE));*/
+
+}
+
+static void trg_rss_cell_renderer_init(TrgRssCellRenderer * self)
+{
+ struct TrgRssCellRendererPrivate *p;
+
+ p = self->priv = G_TYPE_INSTANCE_GET_PRIVATE(self,
+ TRG_RSS_CELL_RENDERER_TYPE,
+ struct
+ TrgRssCellRendererPrivate);
+
+ p->gstr1 = g_string_new(NULL);
+ p->gstr2 = g_string_new(NULL);
+ p->text_renderer = gtk_cell_renderer_text_new();
+ g_object_set(p->text_renderer, "xpad", 0, "ypad", 0, NULL);
+ p->icon_renderer = gtk_cell_renderer_pixbuf_new();
+ g_object_ref_sink(p->text_renderer);
+ g_object_ref_sink(p->icon_renderer);
+}
+
+
+GtkCellRenderer *trg_rss_cell_renderer_new(void)
+{
+ return (GtkCellRenderer *) g_object_new(TRG_RSS_CELL_RENDERER_TYPE,
+ NULL);
+}
+
+static void
+get_text_color(TrgRssCellRenderer * r, GtkWidget * widget,
+ GtrColor * setme)
+{
+ struct TrgRssCellRendererPrivate *p = r->priv;
+
+ if (p->uploaded)
+ gtk_style_context_get_color(gtk_widget_get_style_context(widget),
+ GTK_STATE_FLAG_INSENSITIVE, setme);
+ else
+ gtk_style_context_get_color(gtk_widget_get_style_context(widget),
+ GTK_STATE_FLAG_NORMAL, setme);
+}
+
+static void
+trg_rss_cell_renderer_render(GtkCellRenderer * cell,
+ GtrDrawable * window, GtkWidget * widget,
+ const GdkRectangle * background_area,
+ const GdkRectangle * cell_area,
+ GtkCellRendererState flags)
+{
+ TrgRssCellRenderer *self = TRG_RSS_CELL_RENDERER(cell);
+ int xpad, ypad;
+ GtkRequisition size;
+ GdkRectangle fill_area;
+ GdkRectangle icon_area;
+ GdkRectangle name_area;
+ GdkRectangle stat_area;
+ GdkRectangle prog_area;
+ GdkRectangle prct_area;
+ GdkPixbuf *icon;
+ GtrColor text_color;
+ struct TrgRssCellRendererPrivate *p;
+
+ if (!self)
+ return;
+
+ p = self->priv;
+
+ icon = get_icon(self, FULL_ICON_SIZE, widget);
+ gtk_cell_renderer_get_padding(GTK_CELL_RENDERER(cell), &xpad, &ypad);
+ get_text_color(self, widget, &text_color);
+
+ /* get the idealized cell dimensions */
+ g_object_set(p->icon_renderer, "pixbuf", icon, NULL);
+ gtk_cell_renderer_get_preferred_size(p->icon_renderer, widget, NULL,
+ &size);
+ icon_area.width = size.width;
+ icon_area.height = size.height;
+ g_object_set(p->text_renderer, "text", p->title,
+ "weight", PANGO_WEIGHT_BOLD, "ellipsize",
+ PANGO_ELLIPSIZE_NONE, "scale", 1.0, NULL);
+ gtk_cell_renderer_get_preferred_size(p->text_renderer, widget, NULL,
+ &size);
+ name_area.width = size.width;
+ name_area.height = size.height;
+ g_object_set(p->text_renderer, "text", p->feed, "weight",
+ PANGO_WEIGHT_NORMAL, "scale", SMALL_SCALE, NULL);
+ gtk_cell_renderer_get_preferred_size(p->text_renderer, widget, NULL,
+ &size);
+ prog_area.width = size.width;
+ prog_area.height = size.height;
+ g_object_set(p->text_renderer, "text", p->published, NULL);
+ gtk_cell_renderer_get_preferred_size(p->text_renderer, widget, NULL,
+ &size);
+ stat_area.width = size.width;
+ stat_area.height = size.height;
+
+ /**
+ *** LAYOUT
+ **/
+
+ fill_area = *background_area;
+ fill_area.x += xpad;
+ fill_area.y += ypad;
+ fill_area.width -= xpad * 2;
+ fill_area.height -= ypad * 2;
+
+ /* icon */
+ icon_area.x = fill_area.x;
+ icon_area.y = fill_area.y + (fill_area.height - icon_area.height) / 2;
+
+ /* name */
+ name_area.x = icon_area.x + icon_area.width + GUI_PAD;
+ name_area.y = fill_area.y;
+ name_area.width =
+ fill_area.width - GUI_PAD - icon_area.width - GUI_PAD_SMALL;
+
+ /* prog */
+ prog_area.x = name_area.x;
+ prog_area.y = name_area.y + name_area.height;
+ prog_area.width = name_area.width;
+
+ /* progressbar */
+ prct_area.x = prog_area.x;
+ prct_area.y = prog_area.y + prog_area.height + GUI_PAD_SMALL;
+ prct_area.width = prog_area.width;
+ prct_area.height = 0;
+
+ /* status */
+ stat_area.x = prct_area.x;
+ stat_area.y = prct_area.y + prct_area.height + GUI_PAD_SMALL;
+ stat_area.width = prct_area.width;
+
+ /**
+ *** RENDER
+ **/
+
+ g_object_set(p->icon_renderer, "pixbuf", icon, "sensitive", TRUE,
+ NULL);
+ gtr_cell_renderer_render(p->icon_renderer, window, widget, &icon_area,
+ flags);
+ g_object_set(p->text_renderer, "text", p->title,
+ "scale", 1.0, FOREGROUND_COLOR_KEY, &text_color,
+ "ellipsize", PANGO_ELLIPSIZE_END, "weight",
+ PANGO_WEIGHT_BOLD, NULL);
+ gtr_cell_renderer_render(p->text_renderer, window, widget, &name_area,
+ flags);
+ g_object_set(p->text_renderer, "text", p->feed, "scale",
+ SMALL_SCALE, "weight", PANGO_WEIGHT_NORMAL, NULL);
+ gtr_cell_renderer_render(p->text_renderer, window, widget, &prog_area,
+ flags);
+ g_object_set(p->text_renderer, "text", p->published,
+ FOREGROUND_COLOR_KEY, &text_color, NULL);
+ gtr_cell_renderer_render(p->text_renderer, window, widget, &stat_area,
+ flags);
+
+ /* cleanup */
+ g_object_unref(icon);
+}
+
+#endif
diff --git a/src/trg-rss-cell-renderer.h b/src/trg-rss-cell-renderer.h
new file mode 100644
index 0000000..a255d90
--- /dev/null
+++ b/src/trg-rss-cell-renderer.h
@@ -0,0 +1,55 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef TRG_RSS_CELL_RENDERER_H
+#define TRG_RSS_CELL_RENDERER_H
+
+#include "config.h"
+
+#ifdef HAVE_RSSGLIB
+
+#include <gtk/gtk.h>
+
+#define TRG_RSS_CELL_RENDERER_TYPE ( trg_rss_cell_renderer_get_type( ) )
+
+#define TRG_RSS_CELL_RENDERER( o ) \
+ ( G_TYPE_CHECK_INSTANCE_CAST( ( o ), \
+ TRG_RSS_CELL_RENDERER_TYPE, \
+ TrgRssCellRenderer ) )
+
+typedef struct TrgRssCellRenderer TrgRssCellRenderer;
+
+typedef struct TrgRssCellRendererClass TrgRssCellRendererClass;
+
+struct TrgRssCellRenderer {
+ GtkCellRenderer parent;
+ struct TrgRssCellRendererPrivate *priv;
+};
+
+struct TrgRssCellRendererClass {
+ GtkCellRendererClass parent;
+};
+
+GType trg_rss_cell_renderer_get_type(void) G_GNUC_CONST;
+
+GtkCellRenderer *trg_rss_cell_renderer_new(void);
+
+#endif
+
+#endif /* TRG_RSS_CELL_RENDERER_H */
diff --git a/src/trg-rss-model.c b/src/trg-rss-model.c
new file mode 100644
index 0000000..f898f34
--- /dev/null
+++ b/src/trg-rss-model.c
@@ -0,0 +1,259 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+
+#ifdef HAVE_RSSGLIB
+
+#include <gtk/gtk.h>
+#include <json-glib/json-glib.h>
+#include <rss-glib/rss-glib.h>
+
+#include "config.h"
+#include "torrent.h"
+#include "trg-client.h"
+#include "trg-model.h"
+#include "trg-rss-model.h"
+
+enum {
+ PROP_0, PROP_CLIENT
+};
+
+enum {
+ SIGNAL_GET_ERROR, SIGNAL_PARSE_ERROR, SIGNAL_COUNT
+};
+
+static guint signals[SIGNAL_COUNT] = { 0 };
+
+G_DEFINE_TYPE(TrgRssModel, trg_rss_model, GTK_TYPE_LIST_STORE)
+#define TRG_RSS_MODEL_GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), TRG_TYPE_RSS_MODEL, TrgRssModelPrivate))
+typedef struct _TrgRssModelPrivate TrgRssModelPrivate;
+
+struct _TrgRssModelPrivate {
+ TrgClient *client;
+ GHashTable *table;
+ guint index;
+};
+
+typedef struct {
+ TrgRssModel *model;
+ gchar *feed_id;
+ gchar *feed_url;
+ GError *error;
+ trg_response *response;
+} feed_update;
+
+static void feed_update_free(feed_update *update) {
+ if (update->error)
+ g_error_free(update->error);
+
+ g_free(update->feed_id);
+ g_free(update->feed_url);
+
+ g_free(update);
+}
+
+static gboolean on_rss_receive(gpointer data) {
+ trg_response *response = (trg_response *) data;
+ feed_update *update = (feed_update*) response->cb_data;
+ TrgRssModel *model = update->model;
+ TrgRssModelPrivate *priv = TRG_RSS_MODEL_GET_PRIVATE(model);
+
+ if (response->status == CURLE_OK) {
+ RssParser* parser = rss_parser_new();
+ GError *error = NULL;
+ if (rss_parser_load_from_data(parser, response->raw,
+ response->size, &error)) {
+ RssDocument *doc = rss_parser_get_document(parser);
+ GtkTreeIter iter;
+ GList *list, *tmp;
+ gchar *title;
+
+ list = rss_document_get_items(doc);
+
+ for (tmp = list; tmp != NULL; tmp = tmp->next) {
+ RssItem *item = (RssItem*) tmp->data;
+ const gchar *guid = rss_item_get_guid(item);
+ if (g_hash_table_lookup(priv->table, guid) != (void*) 1) {
+ gtk_list_store_append(GTK_LIST_STORE(model), &iter);
+ gtk_list_store_set(GTK_LIST_STORE(model), &iter, RSSCOL_ID,
+ guid, RSSCOL_TITLE, rss_item_get_title(item),
+ RSSCOL_LINK, rss_item_get_link(item), RSSCOL_FEED,
+ update->feed_id, RSSCOL_PUBDATE,
+ rss_item_get_pub_date(item), -1);
+ g_hash_table_insert(priv->table, g_strdup(guid), (void*) 1);
+ }
+ }
+
+ g_list_free(list);
+ g_object_unref(doc);
+ g_object_unref(parser);
+ } else {
+ rss_parse_error perror;
+ perror.error = error;
+ perror.feed_id = update->feed_id;
+
+ g_signal_emit(model, signals[SIGNAL_PARSE_ERROR], 0,
+ &perror);
+
+ g_message("parse error: %s", error->message);
+ g_error_free(error);
+ }
+ } else {
+ rss_get_error get_error;
+ get_error.error_code = response->status;
+ get_error.feed_id = update->feed_id;
+
+ g_signal_emit(model, signals[SIGNAL_GET_ERROR], 0,
+ &get_error);
+ }
+
+ trg_response_free(response);
+ feed_update_free(update);
+
+ return FALSE;
+}
+
+void trg_rss_model_update(TrgRssModel * model) {
+ TrgRssModelPrivate *priv = TRG_RSS_MODEL_GET_PRIVATE(model);
+ TrgPrefs *prefs = trg_client_get_prefs(priv->client);
+ JsonArray *feeds = trg_prefs_get_rss(prefs);
+ GList *li;
+
+ if (!feeds)
+ return;
+
+ for (li = json_array_get_elements(feeds); li != NULL;
+ li = g_list_next(li)) {
+ JsonObject *feed = json_node_get_object((JsonNode *) li->data);
+ const gchar *url = json_object_get_string_member(feed, "url");
+ const gchar *id = json_object_get_string_member(feed, "id");
+
+ if (!url || !id)
+ continue;
+
+ feed_update *update = g_new0(feed_update, 1);
+ update->feed_url = g_strdup(url);
+ update->feed_id = g_strdup(id);
+ update->model = model;
+
+ async_http_request(priv->client, update->feed_url, on_rss_receive,
+ update);
+ }
+
+ /*trg_model_remove_removed(GTK_LIST_STORE(model),
+ RSSCOL_UPDATESERIAL, updateSerial);*/
+}
+
+static void trg_rss_model_set_property(GObject * object, guint prop_id,
+ const GValue * value, GParamSpec * pspec G_GNUC_UNUSED) {
+ TrgRssModelPrivate *priv = TRG_RSS_MODEL_GET_PRIVATE(object);
+
+ switch (prop_id) {
+ case PROP_CLIENT:
+ priv->client = g_value_get_pointer(value);
+ break;
+ }
+}
+
+static void trg_rss_model_get_property(GObject * object, guint prop_id,
+ GValue * value, GParamSpec * pspec G_GNUC_UNUSED) {
+}
+
+static GObject *trg_rss_model_constructor(GType type,
+ guint n_construct_properties, GObjectConstructParam * construct_params) {
+ GObject *obj = G_OBJECT_CLASS
+ (trg_rss_model_parent_class)->constructor(type,
+ n_construct_properties, construct_params);
+ TrgRssModelPrivate *priv = TRG_RSS_MODEL_GET_PRIVATE(obj);
+
+ priv->table = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
+
+ return obj;
+}
+
+static void trg_rss_model_dispose(GObject * object) {
+ TrgRssModelPrivate *priv = TRG_RSS_MODEL_GET_PRIVATE(object);
+ g_hash_table_destroy(priv->table);
+ G_OBJECT_CLASS(trg_rss_model_parent_class)->dispose(object);
+}
+
+static void trg_rss_model_class_init(TrgRssModelClass * klass) {
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+ g_type_class_add_private(klass, sizeof(TrgRssModelPrivate));
+
+ object_class->set_property = trg_rss_model_set_property;
+ object_class->get_property = trg_rss_model_get_property;
+ object_class->constructor = trg_rss_model_constructor;
+ object_class->dispose = trg_rss_model_dispose;
+
+ g_object_class_install_property(object_class, PROP_CLIENT,
+ g_param_spec_pointer("client", "client", "client",
+ G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY
+ | G_PARAM_STATIC_NAME | G_PARAM_STATIC_NICK
+ | G_PARAM_STATIC_BLURB));
+
+ signals[SIGNAL_GET_ERROR] = g_signal_new("get-error",
+ G_TYPE_FROM_CLASS
+ (object_class),
+ G_SIGNAL_RUN_LAST |
+ G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET
+ (TrgRssModelClass,
+ get_error),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE, 1,
+ G_TYPE_POINTER);
+
+ signals[SIGNAL_PARSE_ERROR] = g_signal_new("parse-error",
+ G_TYPE_FROM_CLASS
+ (object_class),
+ G_SIGNAL_RUN_LAST |
+ G_SIGNAL_ACTION,
+ G_STRUCT_OFFSET
+ (TrgRssModelClass,
+ parse_error),
+ NULL, NULL,
+ g_cclosure_marshal_VOID__POINTER,
+ G_TYPE_NONE, 1,
+ G_TYPE_POINTER);
+}
+
+static void trg_rss_model_init(TrgRssModel * self) {
+ GType column_types[RSSCOL_COLUMNS];
+
+ column_types[RSSCOL_ID] = G_TYPE_STRING;
+ column_types[RSSCOL_TITLE] = G_TYPE_STRING;
+ column_types[RSSCOL_LINK] = G_TYPE_STRING;
+ column_types[RSSCOL_FEED] = G_TYPE_STRING;
+ column_types[RSSCOL_PUBDATE] = G_TYPE_STRING;
+ column_types[RSSCOL_UPLOADED] = G_TYPE_BOOLEAN;
+
+ gtk_list_store_set_column_types(GTK_LIST_STORE(self), RSSCOL_COLUMNS,
+ column_types);
+}
+
+TrgRssModel *trg_rss_model_new(TrgClient *client) {
+ return g_object_new(TRG_TYPE_RSS_MODEL, "client", client, NULL);
+}
+
+#endif
diff --git a/src/trg-rss-model.h b/src/trg-rss-model.h
new file mode 100644
index 0000000..206ee41
--- /dev/null
+++ b/src/trg-rss-model.h
@@ -0,0 +1,86 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef TRG_RSS_MODEL_H_
+#define TRG_RSS_MODEL_H_
+
+#include "config.h"
+
+#ifdef HAVE_RSSGLIB
+
+#include <glib-object.h>
+#include <json-glib/json-glib.h>
+
+#include "trg-client.h"
+#include "trg-model.h"
+
+G_BEGIN_DECLS
+#define TRG_TYPE_RSS_MODEL trg_rss_model_get_type()
+#define TRG_RSS_MODEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), TRG_TYPE_RSS_MODEL, TrgRssModel))
+#define TRG_RSS_MODEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), TRG_TYPE_RSS_MODEL, TrgRssModelClass))
+#define TRG_IS_RSS_MODEL(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TRG_TYPE_RSS_MODEL))
+#define TRG_IS_RSS_MODEL_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), TRG_TYPE_RSS_MODEL))
+#define TRG_RSS_MODEL_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TRG_TYPE_RSS_MODEL, TrgRssModelClass))
+ typedef struct {
+ GtkListStore parent;
+} TrgRssModel;
+
+typedef struct {
+ gchar *feed_id;
+ gint error_code;
+} rss_get_error;
+
+typedef struct {
+ GError *error;
+ gchar *feed_id;
+} rss_parse_error;
+
+typedef struct {
+ GtkListStoreClass parent_class;
+ void (*get_error) (TrgRssModel * model,
+ rss_get_error *error);
+ void (*parse_error) (TrgRssModel * model,
+ rss_parse_error *error);
+} TrgRssModelClass;
+
+GType trg_rss_model_get_type(void);
+
+TrgRssModel *trg_rss_model_new(TrgClient *client);
+
+G_END_DECLS
+void trg_rss_model_update(TrgRssModel * model);
+
+enum {
+ RSSCOL_ID,
+ RSSCOL_TITLE,
+ RSSCOL_LINK,
+ RSSCOL_FEED,
+ RSSCOL_PUBDATE,
+ RSSCOL_UPLOADED,
+ RSSCOL_COLUMNS
+};
+
+#endif
+
+#endif /* TRG_RSS_MODEL_H_ */
diff --git a/src/trg-rss-window.c b/src/trg-rss-window.c
new file mode 100644
index 0000000..442cce9
--- /dev/null
+++ b/src/trg-rss-window.c
@@ -0,0 +1,368 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include "config.h"
+
+#ifdef HAVE_RSSGLIB
+
+#include <gtk/gtk.h>
+#include <glib/gi18n.h>
+#include <rss-glib/rss-glib.h>
+
+#include "trg-rss-window.h"
+#include "trg-rss-model.h"
+#include "trg-rss-cell-renderer.h"
+#include "trg-torrent-add-dialog.h"
+#include "trg-preferences-dialog.h"
+#include "trg-client.h"
+#include "upload.h"
+#include "util.h"
+
+G_DEFINE_TYPE(TrgRssWindow, trg_rss_window,
+ GTK_TYPE_WINDOW)
+#define TRG_RSS_WINDOW_GET_PRIVATE(o) \
+(G_TYPE_INSTANCE_GET_PRIVATE ((o), TRG_TYPE_RSS_WINDOW, TrgRssWindowPrivate))
+enum {
+ PROP_0, PROP_PARENT, PROP_CLIENT
+};
+
+typedef struct _TrgRssWindowPrivate TrgRssWindowPrivate;
+
+struct _TrgRssWindowPrivate {
+ TrgMainWindow *parent;
+ TrgClient *client;
+ GtkTreeView *tree_view;
+ TrgRssModel *tree_model;
+};
+
+static GObject *instance = NULL;
+
+static void
+trg_rss_window_get_property(GObject * object,
+ guint property_id,
+ GValue * value, GParamSpec * pspec)
+{
+ TrgRssWindowPrivate *priv =
+ TRG_RSS_WINDOW_GET_PRIVATE(object);
+ switch (property_id) {
+ case PROP_CLIENT:
+ g_value_set_pointer(value, priv->client);
+ break;
+ case PROP_PARENT:
+ g_value_set_object(value, priv->parent);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ break;
+ }
+}
+
+static void
+trg_rss_window_set_property(GObject * object,
+ guint property_id,
+ const GValue * value,
+ GParamSpec * pspec)
+{
+ TrgRssWindowPrivate *priv =
+ TRG_RSS_WINDOW_GET_PRIVATE(object);
+ switch (property_id) {
+ case PROP_PARENT:
+ priv->parent = g_value_get_object(value);
+ break;
+ case PROP_CLIENT:
+ priv->client = g_value_get_pointer(value);
+ break;
+ default:
+ G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);
+ break;
+ }
+}
+
+static gboolean upload_complete_searchfunc(GtkTreeModel *model,
+ GtkTreePath *path,
+ GtkTreeIter *iter,
+ gpointer data) {
+ trg_upload *upload = (trg_upload*)data;
+ gchar *item_guid = NULL;
+ gboolean stop = FALSE;
+
+ gtk_tree_model_get(model, iter, RSSCOL_ID, &item_guid, -1);
+
+ if (!g_strcmp0(item_guid, upload->uid)) {
+ gtk_list_store_set(GTK_LIST_STORE(model), iter, RSSCOL_UPLOADED, TRUE, -1);
+ stop = TRUE;
+ }
+
+ g_free(item_guid);
+
+ return stop;
+}
+
+static gboolean on_upload_complete(gpointer data) {
+ trg_response *response = (trg_response*)data;
+ trg_upload *upload = (trg_upload*)response->cb_data;
+ TrgRssWindowPrivate *priv = TRG_RSS_WINDOW_GET_PRIVATE(upload->cb_data);
+
+ if (response->status == CURLE_OK)
+ gtk_tree_model_foreach(GTK_TREE_MODEL(priv->tree_model), upload_complete_searchfunc, upload);
+
+ return FALSE;
+}
+
+static gboolean on_torrent_receive(gpointer data) {
+ trg_response *response = (trg_response *) data;
+ trg_upload *upload = (trg_upload*)response->cb_data;
+ TrgRssWindowPrivate *priv = TRG_RSS_WINDOW_GET_PRIVATE(upload->cb_data);
+ TrgClient *client = priv->client;
+ TrgPrefs *prefs = trg_client_get_prefs(client);
+ TrgMainWindow *main_win = priv->parent;
+
+ upload->upload_response = response;
+
+ if (response->status == CURLE_OK) {
+ if (trg_prefs_get_bool(prefs, TRG_PREFS_KEY_ADD_OPTIONS_DIALOG,
+ TRG_PREFS_GLOBAL)) {
+ TrgTorrentAddDialog *dialog =
+ trg_torrent_add_dialog_new_from_upload(main_win, client,
+ upload);
+ gtk_widget_show_all(GTK_WIDGET(dialog));
+ } else {
+ trg_do_upload(upload);
+ }
+ } else {
+ trg_error_dialog(GTK_WINDOW(main_win), response);
+ trg_upload_free(upload);
+ }
+
+ return FALSE;
+}
+
+static void
+rss_item_activated(GtkTreeView * treeview,
+ GtkTreePath * path,
+ GtkTreeViewColumn *
+ col G_GNUC_UNUSED, gpointer userdata)
+{
+ TrgRssWindow *win = TRG_RSS_WINDOW(userdata);
+ TrgRssWindowPrivate *priv = TRG_RSS_WINDOW_GET_PRIVATE(win);
+ TrgClient *client = priv->client;
+ TrgPrefs *prefs = trg_client_get_prefs(client);
+ GtkTreeModel *model = gtk_tree_view_get_model(treeview);
+ trg_upload *upload = g_new0(trg_upload, 1);
+ GtkTreeIter iter;
+ gchar *link, *uid;
+
+ //upload->upload_response = response;
+ upload->main_window = priv->parent;
+ upload->client = client;
+ upload->extra_args = FALSE;
+ upload->flags = trg_prefs_get_add_flags(prefs);
+ upload->callback = on_upload_complete;
+ upload->cb_data = win;
+
+ gtk_tree_model_get_iter(model, &iter, path);
+
+ gtk_tree_model_get(model, &iter, RSSCOL_LINK, &link, RSSCOL_ID, &uid, -1);
+
+ upload->uid = uid;
+
+ async_http_request(priv->client, link, on_torrent_receive, upload);
+
+ g_free(link);
+}
+
+static void trg_rss_on_get_error(TrgRssModel *model, rss_get_error *error, gpointer data) {
+ GtkWindow *win = GTK_WINDOW(data);
+ gchar *msg;
+ if (error->error_code <= -100) {
+ msg = g_strdup_printf(_("Request failed with HTTP code %d"), -(error->error_code + 100));
+ } else {
+ msg = g_strdup(curl_easy_strerror(error->error_code));
+ }
+ GtkWidget *dialog = gtk_message_dialog_new(win,
+ GTK_DIALOG_MODAL,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_OK,
+ msg);
+ g_free(msg);
+ gtk_window_set_title(GTK_WINDOW(dialog), _("Error"));
+ gtk_dialog_run(GTK_DIALOG(dialog));
+ gtk_widget_destroy(dialog);
+}
+
+static void trg_rss_on_parse_error(TrgRssModel *model, rss_parse_error *error, gpointer data) {
+ GtkWindow *win = GTK_WINDOW(data);
+ gchar *msg = g_strdup_printf(_("Error parsing RSS feed \"%s\": %s"), error->feed_id, error->error->message);
+ GtkWidget *dialog = gtk_message_dialog_new(win,
+ GTK_DIALOG_MODAL,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_OK,
+ msg);
+ g_free(msg);
+ gtk_window_set_title(GTK_WINDOW(dialog), _("Error"));
+ gtk_dialog_run(GTK_DIALOG(dialog));
+ gtk_widget_destroy(dialog);
+}
+
+static void on_configure(GtkWidget *widget, gpointer data) {
+ TrgRssWindowPrivate *priv = TRG_RSS_WINDOW_GET_PRIVATE(data);
+ GtkWidget *dlg = trg_preferences_dialog_get_instance(priv->parent, priv->client);
+ gtk_widget_show_all(dlg);
+ trg_preferences_dialog_set_page(TRG_PREFERENCES_DIALOG(dlg), 5);
+}
+
+static void on_refresh(GtkWidget *widget, gpointer data) {
+ TrgRssWindowPrivate *priv = TRG_RSS_WINDOW_GET_PRIVATE(data);
+ trg_rss_model_update(priv->tree_model);
+}
+
+static GObject *trg_rss_window_constructor(GType type,
+ guint
+ n_construct_properties,
+ GObjectConstructParam *
+ construct_params)
+{
+ GObject *object;
+ TrgRssWindowPrivate *priv;
+ GtkWidget *vbox;
+ GtkToolItem *item;
+ GtkWidget *toolbar;
+
+ object = G_OBJECT_CLASS
+ (trg_rss_window_parent_class)->constructor(type,
+ n_construct_properties,
+ construct_params);
+ priv = TRG_RSS_WINDOW_GET_PRIVATE(object);
+
+ priv->tree_model = trg_rss_model_new(priv->client);
+
+ g_signal_connect(priv->tree_model, "get-error",
+ G_CALLBACK(trg_rss_on_get_error), object);
+ g_signal_connect(priv->tree_model, "parse-error",
+ G_CALLBACK(trg_rss_on_parse_error), object);
+
+ trg_rss_model_update(priv->tree_model);
+
+ priv->tree_view = GTK_TREE_VIEW(gtk_tree_view_new());
+ gtk_tree_view_set_headers_visible(priv->tree_view, FALSE);
+ gtk_tree_view_set_model(priv->tree_view, GTK_TREE_MODEL(priv->tree_model));
+
+ gtk_tree_view_insert_column_with_attributes(priv->tree_view, -1, NULL, trg_rss_cell_renderer_new(), "title", RSSCOL_TITLE, "feed", RSSCOL_FEED, "published", RSSCOL_PUBDATE, "uploaded", RSSCOL_UPLOADED, NULL);
+
+ g_signal_connect(priv->tree_view, "row-activated",
+ G_CALLBACK(rss_item_activated), object);
+
+ gtk_window_set_title(GTK_WINDOW(object), _("RSS Feeds"));
+
+ toolbar = gtk_toolbar_new();
+
+ item = gtk_tool_button_new_from_stock(GTK_STOCK_REFRESH);
+ gtk_widget_set_sensitive(GTK_WIDGET(item), TRUE);
+ gtk_tool_item_set_tooltip_text(item, "Refresh");
+ g_signal_connect(G_OBJECT(item), "clicked", G_CALLBACK(on_refresh), object);
+ gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, 0);
+
+ item = gtk_tool_button_new_from_stock(GTK_STOCK_PREFERENCES);
+ gtk_widget_set_sensitive(GTK_WIDGET(item), TRUE);
+ gtk_tool_item_set_tooltip_text(item, "Configure Feeds");
+ g_signal_connect(G_OBJECT(item), "clicked", G_CALLBACK(on_configure), object);
+ gtk_toolbar_insert(GTK_TOOLBAR(toolbar), item, 0);
+
+ vbox = trg_vbox_new(FALSE, 0);
+
+ gtk_box_pack_start(GTK_BOX(vbox), GTK_WIDGET(toolbar),
+ FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(vbox), my_scrolledwin_new(GTK_WIDGET(priv->tree_view)),
+ TRUE, TRUE, 0);
+
+ gtk_container_add(GTK_CONTAINER(object), vbox);
+
+ /*g_signal_connect(object, "response",
+ G_CALLBACK(trg_rss_window_response_cb), NULL);*/
+
+ gtk_widget_set_size_request(GTK_WIDGET(object), 500, 300);
+
+ return object;
+}
+
+static void trg_rss_window_dispose(GObject * object)
+{
+ instance = NULL;
+ G_OBJECT_CLASS(trg_rss_window_parent_class)->dispose(object);
+}
+
+static void
+trg_rss_window_class_init(TrgRssWindowClass * klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+ g_type_class_add_private(klass, sizeof(TrgRssWindowPrivate));
+
+ object_class->constructor = trg_rss_window_constructor;
+ object_class->get_property = trg_rss_window_get_property;
+ object_class->set_property = trg_rss_window_set_property;
+ object_class->dispose = trg_rss_window_dispose;
+
+ g_object_class_install_property(object_class,
+ PROP_CLIENT,
+ g_param_spec_pointer("trg-client",
+ "TClient",
+ "Client",
+ G_PARAM_READWRITE
+ |
+ G_PARAM_CONSTRUCT_ONLY
+ |
+ G_PARAM_STATIC_NAME
+ |
+ G_PARAM_STATIC_NICK
+ |
+ G_PARAM_STATIC_BLURB));
+
+ g_object_class_install_property(object_class,
+ PROP_PARENT,
+ g_param_spec_object("parent-window",
+ "Parent window",
+ "Parent window",
+ TRG_TYPE_MAIN_WINDOW,
+ G_PARAM_READWRITE |
+ G_PARAM_CONSTRUCT_ONLY
+ |
+ G_PARAM_STATIC_NAME
+ |
+ G_PARAM_STATIC_NICK
+ |
+ G_PARAM_STATIC_BLURB));
+}
+
+static void
+trg_rss_window_init(TrgRssWindow * self G_GNUC_UNUSED)
+{
+}
+
+TrgRssWindow *trg_rss_window_get_instance(TrgMainWindow *parent, TrgClient *client)
+{
+ if (instance == NULL) {
+ instance =
+ g_object_new(TRG_TYPE_RSS_WINDOW, "parent-window", parent, "trg-client", client, NULL);
+ }
+
+ return TRG_RSS_WINDOW(instance);
+}
+
+#endif
diff --git a/src/trg-rss-window.h b/src/trg-rss-window.h
new file mode 100644
index 0000000..5ce8690
--- /dev/null
+++ b/src/trg-rss-window.h
@@ -0,0 +1,60 @@
+/*
+ * transmission-remote-gtk - A GTK RPC client to Transmission
+ * Copyright (C) 2011-2013 Alan Fitton
+
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef TRG_RSS_WINDOW_H_
+#define TRG_RSS_WINDOW_H_
+
+#include "config.h"
+
+#ifdef HAVE_RSSGLIB
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+#include "trg-main-window.h"
+
+G_BEGIN_DECLS
+#define TRG_TYPE_RSS_WINDOW trg_rss_window_get_type()
+#define TRG_RSS_WINDOW(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), TRG_TYPE_RSS_WINDOW, TrgRssWindow))
+#define TRG_RSS_WINDOW_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), TRG_TYPE_RSS_WINDOW, TrgRssWindowClass))
+#define TRG_IS_RSS_WINDOW(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TRG_TYPE_RSS_WINDOW))
+#define TRG_IS_RSS_WINDOW_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), TRG_TYPE_RSS_WINDOW))
+#define TRG_RSS_WINDOW_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TRG_TYPE_RSS_WINDOW, TrgRssWindowClass))
+ typedef struct {
+ GtkWindow parent;
+} TrgRssWindow;
+
+typedef struct {
+ GtkWindowClass parent_class;
+} TrgRssWindowClass;
+
+GType trg_rss_window_get_type(void);
+
+TrgRssWindow *trg_rss_window_get_instance(TrgMainWindow *parent, TrgClient *client);
+
+G_END_DECLS
+
+#endif
+
+#endif /* TRG_RSS_WINDOW_H_ */
diff --git a/src/trg-toolbar.h b/src/trg-toolbar.h
index 7d12b2d..6887a16 100644
--- a/src/trg-toolbar.h
+++ b/src/trg-toolbar.h
@@ -17,7 +17,6 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-
#ifndef TRG_TOOLBAR_H_
#define TRG_TOOLBAR_H_
diff --git a/src/trg-torrent-add-dialog.c b/src/trg-torrent-add-dialog.c
index 517797e..aa63166 100644
--- a/src/trg-torrent-add-dialog.c
+++ b/src/trg-torrent-add-dialog.c
@@ -45,9 +45,10 @@
#include "torrent.h"
#include "json.h"
#include "protocol-constants.h"
+#include "upload.h"
enum {
- PROP_0, PROP_FILENAME, PROP_PARENT, PROP_CLIENT
+ PROP_0, PROP_FILENAME, PROP_PARENT, PROP_CLIENT, PROP_UPLOAD
};
enum {
@@ -63,6 +64,7 @@ struct _TrgTorrentAddDialogPrivate {
TrgClient *client;
TrgMainWindow *parent;
GSList *filenames;
+ trg_upload *upload;
GtkWidget *source_chooser;
GtkWidget *dest_combo;
GtkWidget *priority_combo;
@@ -70,6 +72,7 @@ struct _TrgTorrentAddDialogPrivate {
GtkTreeStore *store;
GtkWidget *paused_check;
GtkWidget *delete_check;
+ guint n_files;
};
#define MAGNET_MAX_LINK_WIDTH 75
@@ -90,6 +93,9 @@ static void trg_torrent_add_dialog_set_property(GObject * object,
case PROP_PARENT:
priv->parent = g_value_get_object(value);
break;
+ case PROP_UPLOAD:
+ priv->upload = g_value_get_pointer(value);
+ break;
case PROP_CLIENT:
priv->client = g_value_get_pointer(value);
break;
@@ -115,72 +121,13 @@ trg_torrent_add_dialog_get_property(GObject * object,
}
}
-static void
-add_set_common_args(JsonObject * args, gint priority, gchar * dir)
-{
- json_object_set_string_member(args, FIELD_FILE_DOWNLOAD_DIR, dir);
- json_object_set_int_member(args, FIELD_BANDWIDTH_PRIORITY,
- (gint64) priority);
-}
-
-static gpointer add_files_threadfunc(gpointer data)
-{
- struct add_torrent_threadfunc_args *files_thread_data =
- (struct add_torrent_threadfunc_args *) data;
-
- GSList *li;
-
- for (li = files_thread_data->list; li; li = g_slist_next(li)) {
- gchar *fileName = (gchar *) li->data;
- JsonNode *request =
- torrent_add(fileName, files_thread_data->flags);
- JsonObject *args;
- trg_response *response;
-
- if (!request)
- continue;
-
- args = node_get_arguments(request);
-
- if (files_thread_data->extraArgs)
- add_set_common_args(args, files_thread_data->priority,
- files_thread_data->dir);
-
- response = dispatch(files_thread_data->client, request);
- response->cb_data = files_thread_data->cb_data;
- g_idle_add(on_generic_interactive_action, response);
- }
-
- g_str_slist_free(files_thread_data->list);
-
- if (files_thread_data->extraArgs)
- g_free(files_thread_data->dir);
-
- g_free(files_thread_data);
-
- return NULL;
-}
-
-void launch_add_thread(struct add_torrent_threadfunc_args *args)
-{
- GError *error = NULL;
- g_thread_create(add_files_threadfunc, args, FALSE, &error);
-
- if (error) {
- g_error("thread creation error: %s", error->message);
- g_error_free(error);
- g_str_slist_free(args->list);
- g_free(args);
- }
-}
-
static gboolean
add_file_indexes_foreachfunc(GtkTreeModel * model,
GtkTreePath *
path G_GNUC_UNUSED,
GtkTreeIter * iter, gpointer data)
{
- JsonObject *args = (JsonObject *) data;
+ trg_upload *upload = (trg_upload *) data;
gint priority, index, wanted;
gtk_tree_model_get(model, iter, FC_PRIORITY, &priority, FC_ENABLED,
@@ -189,17 +136,8 @@ add_file_indexes_foreachfunc(GtkTreeModel * model,
if (gtk_tree_model_iter_has_child(model, iter) || index < 0)
return FALSE;
- if (wanted)
- add_file_id_to_array(args, FIELD_FILES_WANTED, index);
- else
- add_file_id_to_array(args, FIELD_FILES_UNWANTED, index);
-
- if (priority == TR_PRI_LOW)
- add_file_id_to_array(args, FIELD_FILES_PRIORITY_LOW, index);
- else if (priority == TR_PRI_HIGH)
- add_file_id_to_array(args, FIELD_FILES_PRIORITY_HIGH, index);
- else
- add_file_id_to_array(args, FIELD_FILES_PRIORITY_NORMAL, index);
+ upload->file_wanted[index] = wanted;
+ upload->file_priorities[index] = priority;
return FALSE;
}
@@ -225,38 +163,33 @@ trg_torrent_add_response_cb(GtkDialog * dlg, gint res_id, gpointer data)
gchar *dir =
trg_destination_combo_get_dir(TRG_DESTINATION_COMBO
(priv->dest_combo));
+ trg_upload *upload;
- if (g_slist_length(priv->filenames) == 1) {
- JsonNode *req =
- torrent_add((gchar *) priv->filenames->data, flags);
- if (req) {
- JsonObject *args = node_get_arguments(req);
- gtk_tree_model_foreach(GTK_TREE_MODEL(priv->store),
- add_file_indexes_foreachfunc, args);
- add_set_common_args(args, priority, dir);
- dispatch_async(priv->client, req,
- on_generic_interactive_action,
- priv->parent);
- }
- g_str_slist_free(priv->filenames);
+ if (priv->upload) {
+ upload = priv->upload;
} else {
- struct add_torrent_threadfunc_args *args =
- g_new(struct add_torrent_threadfunc_args, 1);
- args->list = priv->filenames;
- args->cb_data = priv->parent;
- args->client = priv->client;
- args->dir = g_strdup(dir);
- args->priority = priority;
- args->flags = flags;
- args->extraArgs = TRUE;
-
- launch_add_thread(args);
+ upload = g_new0(trg_upload, 1);
+ upload->list = priv->filenames;
}
+ upload->main_window = priv->parent;
+ upload->client = priv->client;
+ upload->dir = dir;
+ upload->priority = priority;
+ upload->flags = flags;
+ upload->extra_args = TRUE;
+
+ upload->n_files = priv->n_files;
+ upload->file_priorities = g_new0(gint, priv->n_files);
+ upload->file_wanted = g_new0(gint, priv->n_files);
+
+ gtk_tree_model_foreach(GTK_TREE_MODEL(priv->store),
+ add_file_indexes_foreachfunc, upload);
+
+ trg_do_upload(upload);
+
trg_destination_combo_save_selection(TRG_DESTINATION_COMBO
(priv->dest_combo));
-
- g_free(dir);
} else {
g_str_slist_free(priv->filenames);
}
@@ -465,7 +398,7 @@ static void addTorrentFilters(GtkFileChooser * chooser)
static void
store_add_node(GtkTreeStore * store, GtkTreeIter * parent,
- trg_files_tree_node * node)
+ trg_files_tree_node * node, guint *n_files)
{
GtkTreeIter child;
GList *li;
@@ -476,11 +409,12 @@ store_add_node(GtkTreeStore * store, GtkTreeIter * parent,
1, FC_INDEX, node->index,
FC_PRIORITY, TR_PRI_NORMAL,
FC_SIZE, node->length, -1);
+ *n_files = *n_files + 1;
}
for (li = node->children; li; li = g_list_next(li))
store_add_node(store, node->name ? &child : NULL,
- (trg_files_tree_node *) li->data);
+ (trg_files_tree_node *) li->data, n_files);
}
static void torrent_not_parsed_warning(GtkWindow * parent)
@@ -511,6 +445,28 @@ static void torrent_not_found_error(GtkWindow * parent, gchar * file)
}
static void
+trg_torrent_add_dialog_set_upload(TrgTorrentAddDialog *d, trg_upload *upload) {
+ TrgTorrentAddDialogPrivate *priv =
+ TRG_TORRENT_ADD_DIALOG_GET_PRIVATE(d);
+ GtkButton *chooser = GTK_BUTTON(priv->source_chooser);
+ trg_torrent_file *tor_data = NULL;
+
+ if (upload->uid)
+ gtk_button_set_label(chooser, upload->uid);
+
+ tor_data = trg_parse_torrent_data(upload->upload_response->raw, upload->upload_response->size);
+
+ if (!tor_data) {
+ torrent_not_parsed_warning(GTK_WINDOW(priv->parent));
+ } else {
+ store_add_node(priv->store, NULL, tor_data->top_node, &priv->n_files);
+ trg_torrent_file_free(tor_data);
+ }
+
+ gtk_widget_set_sensitive(priv->file_list, tor_data != NULL);
+}
+
+static void
trg_torrent_add_dialog_set_filenames(TrgTorrentAddDialog * d,
GSList * filenames)
{
@@ -521,6 +477,11 @@ trg_torrent_add_dialog_set_filenames(TrgTorrentAddDialog * d,
gtk_tree_store_clear(priv->store);
+ if (priv->upload) {
+ trg_upload_free(priv->upload);
+ priv->upload = NULL;
+ }
+
if (nfiles == 1) {
gchar *file_name = (gchar *) filenames->data;
if (is_url(file_name) || is_magnet(file_name)) {
@@ -556,7 +517,7 @@ trg_torrent_add_dialog_set_filenames(TrgTorrentAddDialog * d,
if (!tor_data) {
torrent_not_parsed_warning(GTK_WINDOW(priv->parent));
} else {
- store_add_node(priv->store, NULL, tor_data->top_node);
+ store_add_node(priv->store, NULL, tor_data->top_node, &priv->n_files);
trg_torrent_file_free(tor_data);
}
} else {
@@ -784,8 +745,12 @@ static GObject *trg_torrent_add_dialog_constructor(GType type,
priv->source_chooser = gtk_button_new();
gtk_button_set_alignment(GTK_BUTTON(priv->source_chooser), 0.0f, 0.5f);
- trg_torrent_add_dialog_set_filenames(TRG_TORRENT_ADD_DIALOG(obj),
- priv->filenames);
+
+ if (priv->filenames)
+ trg_torrent_add_dialog_set_filenames(TRG_TORRENT_ADD_DIALOG(obj),
+ priv->filenames);
+ else if (priv->upload)
+ trg_torrent_add_dialog_set_upload(TRG_TORRENT_ADD_DIALOG(obj), priv->upload);
gtk_table_attach(GTK_TABLE(t), priv->source_chooser, 1, 2, 0,
1, ~0, 0, 0, 0);
@@ -868,6 +833,21 @@ trg_torrent_add_dialog_class_init(TrgTorrentAddDialogClass * klass)
G_PARAM_STATIC_BLURB));
g_object_class_install_property(object_class,
+ PROP_UPLOAD,
+ g_param_spec_pointer("upload",
+ "upload",
+ "upload",
+ G_PARAM_READWRITE
+ |
+ G_PARAM_CONSTRUCT_ONLY
+ |
+ G_PARAM_STATIC_NAME
+ |
+ G_PARAM_STATIC_NICK
+ |
+ G_PARAM_STATIC_BLURB));
+
+ g_object_class_install_property(object_class,
PROP_CLIENT,
g_param_spec_pointer("client",
"client",
@@ -902,7 +882,7 @@ static void trg_torrent_add_dialog_init(TrgTorrentAddDialog * self)
{
}
-TrgTorrentAddDialog *trg_torrent_add_dialog_new(TrgMainWindow * parent,
+TrgTorrentAddDialog *trg_torrent_add_dialog_new_from_filenames(TrgMainWindow * parent,
TrgClient * client,
GSList * filenames)
{
@@ -911,6 +891,15 @@ TrgTorrentAddDialog *trg_torrent_add_dialog_new(TrgMainWindow * parent,
NULL);
}
+TrgTorrentAddDialog *trg_torrent_add_dialog_new_from_upload(TrgMainWindow * parent,
+ TrgClient * client,
+ trg_upload *upload)
+{
+ return g_object_new(TRG_TYPE_TORRENT_ADD_DIALOG, "upload",
+ upload, "parent", parent, "client", client,
+ NULL);
+}
+
void trg_torrent_add_dialog(TrgMainWindow * win, TrgClient * client)
{
GtkWidget *w;
@@ -937,22 +926,21 @@ void trg_torrent_add_dialog(TrgMainWindow * win, TrgClient * client)
prefs);
if (showOptions) {
- TrgTorrentAddDialog *dialog = trg_torrent_add_dialog_new(win,
+ TrgTorrentAddDialog *dialog = trg_torrent_add_dialog_new_from_filenames(win,
client,
l);
gtk_widget_show_all(GTK_WIDGET(dialog));
} else {
- struct add_torrent_threadfunc_args *args =
- g_new0(struct add_torrent_threadfunc_args, 1);
+ trg_upload *upload = g_new0(trg_upload, 1);
- args->list = l;
- args->cb_data = win;
- args->client = client;
- args->extraArgs = FALSE;
- args->flags = trg_prefs_get_add_flags(prefs);
+ upload->list = l;
+ upload->main_window = win;
+ upload->client = client;
+ upload->extra_args = FALSE;
+ upload->flags = trg_prefs_get_add_flags(prefs);
- launch_add_thread(args);
+ trg_do_upload(upload);
}
}
diff --git a/src/trg-torrent-add-dialog.h b/src/trg-torrent-add-dialog.h
index 0f45e2c..eebea45 100644
--- a/src/trg-torrent-add-dialog.h
+++ b/src/trg-torrent-add-dialog.h
@@ -24,6 +24,7 @@
#include <gtk/gtk.h>
#include "trg-client.h"
+#include "upload.h"
#include "trg-main-window.h"
G_BEGIN_DECLS
@@ -46,28 +47,15 @@ typedef struct {
GtkDialogClass parent_class;
} TrgTorrentAddDialogClass;
-/* Use synchronous dispatch() in our dedicated thread function.
- * This means torrents are added in sequence, instead of dispatch_async()
- * working concurrently for each upload.
- */
-
-struct add_torrent_threadfunc_args {
- GSList *list;
- TrgClient *client;
- gpointer cb_data;
- guint flags;
- gchar *dir;
- gint priority;
- gboolean extraArgs;
-};
-
GType trg_torrent_add_dialog_get_type(void);
-TrgTorrentAddDialog *trg_torrent_add_dialog_new(TrgMainWindow * win,
+TrgTorrentAddDialog *trg_torrent_add_dialog_new_from_filenames(TrgMainWindow * parent,
TrgClient * client,
GSList * filenames);
+TrgTorrentAddDialog *trg_torrent_add_dialog_new_from_upload(TrgMainWindow * parent,
+ TrgClient * client,
+ trg_upload *upload);
void trg_torrent_add_dialog(TrgMainWindow * win, TrgClient * client);
-void launch_add_thread(struct add_torrent_threadfunc_args *args);
G_END_DECLS
#endif /* TRG_TORRENT_ADD_DIALOG_H_ */
diff --git a/src/trg-torrent-add-url-dialog.c b/src/trg-torrent-add-url-dialog.c
index 7f6335e..8ed15d8 100644
--- a/src/trg-torrent-add-url-dialog.c
+++ b/src/trg-torrent-add-url-dialog.c
@@ -90,7 +90,7 @@ trg_torrent_add_url_response_cb(TrgTorrentAddUrlDialog * dlg, gint res_id,
gtk_toggle_button_get_active
(GTK_TOGGLE_BUTTON(priv->startCheck)));
dispatch_async(priv->client, request,
- on_generic_interactive_action, data);
+ on_generic_interactive_action_response, data);
}
gtk_widget_destroy(GTK_WIDGET(dlg));
diff --git a/src/trg-torrent-move-dialog.c b/src/trg-torrent-move-dialog.c
index 7669545..99a11fb 100644
--- a/src/trg-torrent-move-dialog.c
+++ b/src/trg-torrent-move-dialog.c
@@ -69,7 +69,7 @@ trg_torrent_move_response_cb(GtkDialog * dlg, gint res_id, gpointer data)
trg_destination_combo_save_selection(TRG_DESTINATION_COMBO
(priv->location_combo));
dispatch_async(priv->client, request,
- on_generic_interactive_action, data);
+ on_generic_interactive_action_response, data);
} else {
json_array_unref(priv->ids);
}
diff --git a/src/trg-torrent-props-dialog.c b/src/trg-torrent-props-dialog.c
index 90ef4e3..71a9c71 100644
--- a/src/trg-torrent-props-dialog.c
+++ b/src/trg-torrent-props-dialog.c
@@ -172,7 +172,7 @@ static void trg_torrent_props_response_cb(GtkDialog * dialog, gint res_id,
trg_json_widgets_save(priv->widgets, args);
trg_json_widget_desc_list_free(priv->widgets);
- dispatch_async(priv->client, request, on_generic_interactive_action,
+ dispatch_async(priv->client, request, on_generic_interactive_action_response,
priv->parent);
}
diff --git a/src/trg-trackers-tree-view.c b/src/trg-trackers-tree-view.c
index 7ce05b7..54948f3 100644
--- a/src/trg-trackers-tree-view.c
+++ b/src/trg-trackers-tree-view.c
@@ -66,7 +66,7 @@ static gboolean on_trackers_update(gpointer data)
trg_trackers_model_set_accept(TRG_TRACKERS_MODEL(model), TRUE);
response->cb_data = priv->win;
- return on_generic_interactive_action(data);
+ return on_generic_interactive_action_response(data);
}
void
diff --git a/src/upload.c b/src/upload.c
new file mode 100644
index 0000000..2f16c51
--- /dev/null
+++ b/src/upload.c
@@ -0,0 +1,103 @@
+#include "protocol-constants.h"
+#include "requests.h"
+#include "trg-client.h"
+#include "util.h"
+#include "trg-main-window.h"
+#include "json.h"
+#include "upload.h"
+
+static gboolean upload_complete_callback(gpointer data);
+static void next_upload(trg_upload *upload);
+
+static void
+add_set_common_args(JsonObject * args, gint priority, gchar * dir)
+{
+ json_object_set_string_member(args, FIELD_FILE_DOWNLOAD_DIR, dir);
+ json_object_set_int_member(args, FIELD_BANDWIDTH_PRIORITY,
+ (gint64) priority);
+}
+
+void trg_upload_free(trg_upload *upload) {
+ g_str_slist_free(upload->list);
+ g_free(upload->dir);
+ g_free(upload->uid);
+ g_free(upload->file_wanted);
+ g_free(upload->file_priorities);
+ trg_response_free(upload->upload_response);
+ g_free(upload);
+}
+
+static void add_priorities(JsonObject *args, gint* priorities, gint n_files)
+{
+ gint i;
+ for (i = 0; i < n_files; i++) {
+ gint priority = priorities[i];
+ if (priority == TR_PRI_LOW)
+ add_file_id_to_array(args, FIELD_FILES_PRIORITY_LOW, i);
+ else if (priority == TR_PRI_HIGH)
+ add_file_id_to_array(args, FIELD_FILES_PRIORITY_HIGH, i);
+ else
+ add_file_id_to_array(args, FIELD_FILES_PRIORITY_NORMAL, i);
+ }
+}
+
+static void add_wanteds(JsonObject *args, gint* wanteds, gint n_files) {
+ gint i;
+ for (i = 0; i < n_files; i++) {
+ if (wanteds[i])
+ add_file_id_to_array(args, FIELD_FILES_WANTED, i);
+ else
+ add_file_id_to_array(args, FIELD_FILES_UNWANTED, i);
+ }
+}
+
+static void next_upload(trg_upload *upload) {
+ JsonNode *req = NULL;
+
+ if (upload->upload_response && upload->progress_index < 1)
+ req = torrent_add_from_response(upload->upload_response, upload->flags);
+ else if (upload->list && upload->progress_index < g_slist_length(upload->list))
+ req = torrent_add_from_file((gchar*)g_slist_nth_data(upload->list, upload->progress_index), upload->flags);
+
+ if (req) {
+ JsonObject *args = node_get_arguments(req);
+
+ if (upload->extra_args)
+ add_set_common_args(args, upload->priority, upload->dir);
+
+ if (upload->file_wanted)
+ add_wanteds(args, upload->file_wanted, upload->n_files);
+
+ if (upload->file_priorities)
+ add_priorities(args, upload->file_priorities, upload->n_files);
+
+ upload->progress_index++;
+ dispatch_async(upload->client, req, upload_complete_callback, upload);
+ } else {
+ trg_upload_free(upload);
+ }
+}
+
+static gboolean upload_complete_callback(gpointer data) {
+ trg_response *response = (trg_response*)data;
+ trg_upload *upload = (trg_upload*)response->cb_data;
+
+ if (upload->callback)
+ upload->callback(data);
+
+ /* the callback we're delegating to will destroy the response */
+
+ if (upload->main_window)
+ on_generic_interactive_action(upload->main_window, response);
+ else
+ trg_response_free(response);
+
+ next_upload(upload);
+
+ return FALSE;
+}
+
+void trg_do_upload(trg_upload *upload)
+{
+ next_upload(upload);
+}
diff --git a/src/upload.h b/src/upload.h
new file mode 100644
index 0000000..5dc17e2
--- /dev/null
+++ b/src/upload.h
@@ -0,0 +1,30 @@
+#ifndef UPLOAD_H_
+#define UPLOAD_H_
+
+#include <glib.h>
+
+#include "trg-client.h"
+#include "trg-main-window.h"
+
+typedef struct {
+ GSList *list; // list of filenames
+ trg_response *upload_response; // OR: a HTTP response containing a torrent
+ TrgClient *client;
+ gpointer cb_data;
+ TrgMainWindow *main_window; // a parent window to attach any error dialogs to
+ guint flags;
+ gchar *dir;
+ gint priority;
+ gint* file_priorities;
+ gint* file_wanted;
+ guint n_files;
+ gboolean extra_args;
+ guint progress_index;
+ GSourceFunc callback;
+ gchar *uid;
+} trg_upload;
+
+void trg_upload_free(trg_upload *upload);
+void trg_do_upload(trg_upload *upload);
+
+#endif
diff --git a/src/util.c b/src/util.c
index 5ef7768..8dcbb65 100644
--- a/src/util.c
+++ b/src/util.c
@@ -340,7 +340,7 @@ gchar *make_error_message(JsonObject * response, int status)
{
if (status == FAIL_JSON_DECODE) {
return g_strdup(_("JSON decoding error."));
- } else if (status == FAIL_RESPONSE_UNSUCCESSFUL) {
+ } else if (response && status == FAIL_RESPONSE_UNSUCCESSFUL) {
const gchar *resultStr =
json_object_get_string_member(response, "result");
if (resultStr == NULL)
diff --git a/src/util.h b/src/util.h
index 351f565..8134952 100644
--- a/src/util.h
+++ b/src/util.h
@@ -32,6 +32,7 @@
#define trg_strlpercent(a, b) tr_strlpercent(a, b, sizeof(a))
#define trg_strlsize(a, b) tr_formatter_size_B(a, b, sizeof(a))
#define trg_strlratio(a, b) tr_strlratio(a, b, sizeof(a))
+#define MAX3(a,b,c) MAX(a,MAX(b,c))
#define TR_RATIO_NA -1
#define TR_RATIO_INF -2