summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGravatar Alan F <ajf@eth0.org.uk>2014-03-10 09:10:24 +0000
committerGravatar Alan F <ajf@eth0.org.uk>2014-03-10 09:10:24 +0000
commit1a5447c0138fd25e3f6d78f78593f5b70be5614a (patch)
tree9468304a4688a63788363bcf2457b1767aa513a9 /src
parentdd0c7d8317dd199b6dd9d612ebb863c4da6cf242 (diff)
parent118b702bec50d9872699357a379413f69e9b176e (diff)
Merge branch 'rss-viewer'. This is the biggest feature I've attempted to add for quite a long time. Quite a lot of refactoring of the HTTP client and threadpool has been done (to make it not send session tokens etc), uploads (was too much code duplication), and the add dialog was done to implement this. Don't expect this to be some fully automatable RSS client, use something like flexget for that, this is just a simple view on RSS feeds. It uses the rss-glib library, which you will probably need to install yourself to optionally use this feature. There are probably bugs still, so report them.
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