From 834a3f2151dd8738a1f878489f6207664c4af5aa Mon Sep 17 00:00:00 2001 From: Benjamin Barenblat Date: Sun, 14 Jul 2013 17:14:53 -0700 Subject: Imported Upstream version 1.1.1 --- src/trg-client.c | 706 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 706 insertions(+) create mode 100644 src/trg-client.c (limited to 'src/trg-client.c') diff --git a/src/trg-client.c b/src/trg-client.c new file mode 100644 index 0000000..f20677a --- /dev/null +++ b/src/trg-client.c @@ -0,0 +1,706 @@ +/* + * 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" + +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_LIBPROXY +#include +#endif + +#include +#include + +#include "json.h" +#include "trg-prefs.h" +#include "protocol-constants.h" +#include "util.h" +#include "requests.h" +#include "trg-client.h" + +/* This class manages/does quite a few things, and is passed around a lot. It: + * + * 1) Holds/inits the single TrgPrefs object for managing configuration. + * 2) Manages a thread pool for making requests + * (each thread has its own CURL client in thread local storage) + * 3) Holds current connection details needed by CURL clients. + * (session ID, username, password, URL, ssl, proxy) + * 4) Holds a hash table for looking up a torrent by its ID. + * 5) Dispatches synchronous/asyncrhonous requests and tracks failures. + * 6) Holds connection state, an update serial, and provides signals for + * connect/disconnect. + * 7) Provides a mutex for locking updates. + * 8) Holds the latest session object sent in a session-get response. + */ + +G_DEFINE_TYPE(TrgClient, trg_client, G_TYPE_OBJECT) +enum { + TC_SESSION_UPDATED, TC_SIGNAL_COUNT +}; + +static guint signals[TC_SIGNAL_COUNT] = { 0 }; + +struct _TrgClientPrivate { + char *session_id; + gint connid; + guint failCount; + guint retries; + guint timeout; + gint64 updateSerial; + JsonObject *session; + gboolean ssl; + gdouble version; + char *url; + char *username; + char *password; + char *proxy; + GHashTable *torrentTable; + GThreadPool *pool; + TrgPrefs *prefs; + GPrivate *tlsKey; + gint configSerial; + GMutex *configMutex; + gboolean seedRatioLimited; + gdouble seedRatioLimit; +}; + +static void dispatch_async_threadfunc(trg_request * reqrsp, + TrgClient * tc); + +static void +trg_client_get_property(GObject * object, guint property_id, + GValue * value, GParamSpec * pspec) +{ + switch (property_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); + break; + } +} + +static void +trg_client_set_property(GObject * object, guint property_id, + const GValue * value, GParamSpec * pspec) +{ + switch (property_id) { + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec); + break; + } +} + +static void trg_client_dispose(GObject * object) +{ + G_OBJECT_CLASS(trg_client_parent_class)->dispose(object); +} + +static void trg_client_class_init(TrgClientClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + g_type_class_add_private(klass, sizeof(TrgClientPrivate)); + + object_class->get_property = trg_client_get_property; + object_class->set_property = trg_client_set_property; + object_class->dispose = trg_client_dispose; + + signals[TC_SESSION_UPDATED] = g_signal_new("session-updated", + G_TYPE_FROM_CLASS + (object_class), + G_SIGNAL_RUN_LAST | + G_SIGNAL_ACTION, + G_STRUCT_OFFSET + (TrgClientClass, + session_updated), NULL, + NULL, + g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, 1, + G_TYPE_POINTER); +} + +static void trg_client_init(TrgClient * self) +{ + self->priv = + G_TYPE_INSTANCE_GET_PRIVATE(self, TRG_TYPE_CLIENT, + TrgClientPrivate); +} + +TrgClient *trg_client_new(void) +{ + TrgClient *tc = g_object_new(TRG_TYPE_CLIENT, NULL); + TrgClientPrivate *priv = tc->priv; + TrgPrefs *prefs = priv->prefs = trg_prefs_new(); + + trg_prefs_load(prefs); + + priv->configMutex = g_mutex_new(); + priv->tlsKey = g_private_new(NULL); + priv->seedRatioLimited = FALSE; + priv->seedRatioLimit = 0.00; + + priv->pool = g_thread_pool_new((GFunc) dispatch_async_threadfunc, tc, + DISPATCH_POOL_SIZE, TRUE, NULL); + + tr_formatter_size_init(disk_K, _(disk_K_str), _(disk_M_str), + _(disk_G_str), _(disk_T_str)); + tr_formatter_speed_init(speed_K, _(speed_K_str), _(speed_M_str), + _(speed_G_str), _(speed_T_str)); + + return tc; +} + +const gchar *trg_client_get_version_string(TrgClient * tc) +{ + return session_get_version_string(tc->priv->session); +} + +gdouble trg_client_get_version(TrgClient * tc) +{ + return tc->priv->version; +} + +gint64 trg_client_get_rpc_version(TrgClient * tc) +{ + TrgClientPrivate *priv = tc->priv; + return session_get_rpc_version(priv->session); +} + +void trg_client_inc_connid(TrgClient * tc) +{ + TrgClientPrivate *priv = tc->priv; + g_atomic_int_inc(&priv->connid); +} + +void trg_client_set_session(TrgClient * tc, JsonObject * session) +{ + TrgClientPrivate *priv = tc->priv; + + if (priv->session) + json_object_unref(priv->session); + else + priv->version = session_get_version(session); + + priv->session = session; + json_object_ref(session); + + priv->seedRatioLimit = session_get_seed_ratio_limit(session); + priv->seedRatioLimited = session_get_seed_ratio_limited(session); + + g_signal_emit(tc, signals[TC_SESSION_UPDATED], 0, session); +} + +TrgPrefs *trg_client_get_prefs(TrgClient * tc) +{ + return tc->priv->prefs; +} + +int trg_client_populate_with_settings(TrgClient * tc) +{ + TrgClientPrivate *priv = tc->priv; + TrgPrefs *prefs = priv->prefs; + + gint port; + gchar *host, *path; +#ifdef HAVE_LIBPROXY + pxProxyFactory *pf = NULL; +#endif + + g_mutex_lock(priv->configMutex); + + trg_prefs_set_connection(prefs, trg_prefs_get_profile(prefs)); + + g_free(priv->url); + priv->url = NULL; + + g_free(priv->username); + priv->username = NULL; + + g_free(priv->password); + priv->password = NULL; + + port = + trg_prefs_get_int(prefs, TRG_PREFS_KEY_PORT, TRG_PREFS_CONNECTION); + + host = trg_prefs_get_string(prefs, TRG_PREFS_KEY_HOSTNAME, + TRG_PREFS_CONNECTION); + path = trg_prefs_get_string(prefs, TRG_PREFS_KEY_RPC_URL_PATH, TRG_PREFS_CONNECTION); + + if (!host || strlen(host) < 1) { + g_free(host); + g_mutex_unlock(priv->configMutex); + return TRG_NO_HOSTNAME_SET; + } +#ifndef CURL_NO_SSL + priv->ssl = trg_prefs_get_bool(prefs, TRG_PREFS_KEY_SSL, + TRG_PREFS_CONNECTION); +#else + priv->ssl = FALSE; +#endif + + + priv->url = g_strdup_printf("%s://%s:%d%s", + priv->ssl ? HTTPS_URI_PREFIX : + HTTP_URI_PREFIX, host, port, path); + g_free(host); + g_free(path); + + priv->username = trg_prefs_get_string(prefs, TRG_PREFS_KEY_USERNAME, + TRG_PREFS_CONNECTION); + + priv->password = trg_prefs_get_string(prefs, TRG_PREFS_KEY_PASSWORD, + TRG_PREFS_CONNECTION); + + g_free(priv->proxy); + priv->proxy = NULL; + +#ifdef HAVE_LIBPROXY + if ((pf = px_proxy_factory_new())) { + char **proxies = px_proxy_factory_get_proxies(pf, priv->url); + int i; + + for (i = 0; proxies[i]; i++) { + if (g_str_has_prefix(proxies[i], HTTP_URI_PREFIX) + || g_str_has_prefix(proxies[i], HTTPS_URI_PREFIX)) { + g_free(priv->proxy); + priv->proxy = proxies[i]; + } else { + g_free(proxies[i]); + } + } + + g_free(proxies); + px_proxy_factory_free(pf); + } +#endif + + priv->configSerial++; + g_mutex_unlock(priv->configMutex); + return 0; +} + +gchar *trg_client_get_password(TrgClient * tc) +{ + return tc->priv->password; +} + +gchar *trg_client_get_username(TrgClient * tc) +{ + return tc->priv->username; +} + +gchar *trg_client_get_url(TrgClient * tc) +{ + return tc->priv->url; +} + +gchar *trg_client_get_session_id(TrgClient * tc) +{ + TrgClientPrivate *priv = tc->priv; + return priv->session_id ? g_strdup(priv->session_id) : NULL; +} + +void trg_client_set_session_id(TrgClient * tc, gchar * session_id) +{ + TrgClientPrivate *priv = tc->priv; + + g_mutex_lock(priv->configMutex); + + if (priv->session_id) + g_free(priv->session_id); + + priv->session_id = session_id; + + g_mutex_unlock(priv->configMutex); +} + +void trg_client_status_change(TrgClient * tc, gboolean connected) +{ + TrgClientPrivate *priv = tc->priv; + + if (!connected) { + if (priv->session) { + json_object_unref(priv->session); + priv->session = NULL; + } + g_mutex_lock(priv->configMutex); + trg_prefs_set_connection(priv->prefs, NULL); + g_mutex_unlock(priv->configMutex); + } +} + +JsonObject *trg_client_get_session(TrgClient * tc) +{ + return tc->priv->session; +} + +void +trg_client_thread_pool_push(TrgClient * tc, gpointer data, GError ** err) +{ + g_thread_pool_push(tc->priv->pool, data, err); +} + +void trg_client_inc_serial(TrgClient * tc) +{ + TrgClientPrivate *priv = tc->priv; + priv->updateSerial++; +} + +gint64 trg_client_get_serial(TrgClient * tc) +{ + return tc->priv->updateSerial; +} + +#ifndef CURL_NO_SSL +gboolean trg_client_get_ssl(TrgClient * tc) +{ + return tc->priv->ssl; +} +#endif + +gchar *trg_client_get_proxy(TrgClient * tc) +{ + return tc->priv->proxy; +} + +void trg_client_set_torrent_table(TrgClient * tc, GHashTable * table) +{ + TrgClientPrivate *priv = tc->priv; + priv->torrentTable = table; +} + +GHashTable *trg_client_get_torrent_table(TrgClient * tc) +{ + TrgClientPrivate *priv = tc->priv; + return priv->torrentTable; +} + +gboolean trg_client_is_connected(TrgClient * tc) +{ + TrgClientPrivate *priv = tc->priv; + return priv->session != NULL; +} + +void trg_client_configlock(TrgClient * tc) +{ + g_mutex_lock(tc->priv->configMutex); +} + +guint trg_client_get_failcount(TrgClient * tc) +{ + return tc->priv->failCount; +} + +guint trg_client_inc_failcount(TrgClient * tc) +{ + TrgClientPrivate *priv = tc->priv; + return ++(priv->failCount); +} + +void trg_client_reset_failcount(TrgClient * tc) +{ + tc->priv->failCount = 0; +} + +void trg_client_configunlock(TrgClient * tc) +{ + g_mutex_unlock(tc->priv->configMutex); +} + +/* formerly http.c */ + +void trg_response_free(trg_response * response) +{ + if (response->obj) + json_object_unref(response->obj); + g_free(response); +} + +static size_t +http_receive_callback(void *ptr, size_t size, size_t nmemb, void *data) +{ + size_t realsize = size * nmemb; + trg_response *mem = (trg_response *) data; + + mem->raw = g_realloc(mem->raw, mem->size + realsize + 1); + if (mem->raw) { + memcpy(&(mem->raw[mem->size]), ptr, realsize); + mem->size += realsize; + mem->raw[mem->size] = 0; + } + + return realsize; +} + +static size_t +header_callback(void *ptr, size_t size, size_t nmemb, void *data) +{ + char *header = (char *) (ptr); + TrgClient *tc = TRG_CLIENT(data); + gchar *session_id; + + if (g_str_has_prefix(header, X_TRANSMISSION_SESSION_ID_HEADER_PREFIX)) { + char *nl; + + session_id = g_strdup(header); + nl = strrchr(session_id, '\r'); + if (nl) + *nl = '\0'; + + trg_client_set_session_id(tc, session_id); + } + + 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)) + 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; + + if (!threadLocalStorage) { + tls = trg_tls_new(tc); + g_private_set(priv->tlsKey, tls); + } else { + tls = (trg_tls *) threadLocalStorage; + } + + g_mutex_lock(priv->configMutex); + + if (priv->configSerial > tls->serial) + trg_tls_update(tc, tls, priv->configSerial); + + 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(tls->curl, CURLOPT_TIMEOUT, + (long) trg_prefs_get_int(prefs, TRG_PREFS_KEY_TIMEOUT, + TRG_PREFS_CONNECTION)); + + g_mutex_unlock(priv->configMutex); + + 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); + + 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); + else if (httpCode != HTTP_OK) + response->status = (-httpCode) - 100; + } + + return response->status; +} + +int trg_http_perform(TrgClient * tc, gchar * reqstr, trg_response * reqrsp) +{ + return trg_http_perform_inner(tc, reqstr, reqrsp, TRUE); +} + +/* formerly dispatch.c */ + +trg_response *dispatch(TrgClient * tc, JsonNode * req) +{ + gchar *serialized = trg_serialize(req); + json_node_free(req); +#ifdef DEBUG + if (g_getenv("TRG_SHOW_OUTGOING")) + g_debug("=>(OUTgoing)=>: %s", serialized); +#endif + return dispatch_str(tc, serialized); +} + +trg_response *dispatch_str(TrgClient * tc, gchar * req) +{ + trg_response *response = g_new0(trg_response, 1); + GError *decode_error = NULL; + JsonNode *result; + + trg_http_perform(tc, req, response); + g_free(req); + + if (response->status == CURLE_OK) + response->obj = trg_deserialize(response, &decode_error); + + g_free(response->raw); + response->raw = NULL; + + if (response->status != CURLE_OK) + return response; + + if (decode_error) { + g_error("JSON decoding error: %s", decode_error->message); + g_error_free(decode_error); + response->status = FAIL_JSON_DECODE; + return response; + } + + result = json_object_get_member(response->obj, FIELD_RESULT); + if (!result || g_strcmp0(json_node_get_string(result), FIELD_SUCCESS)) + response->status = FAIL_RESPONSE_UNSUCCESSFUL; + + return response; +} + +static void dispatch_async_threadfunc(trg_request * req, TrgClient * tc) +{ + TrgClientPrivate *priv = tc->priv; + + trg_response *rsp; + + if (req->str) + rsp = dispatch_str(tc, req->str); + else + rsp = dispatch(tc, req->node); + + rsp->cb_data = req->cb_data; + + if (req->callback && req->connid == g_atomic_int_get(&priv->connid)) + g_idle_add(req->callback, rsp); + else + trg_response_free(rsp); + + g_free(req); +} + +static gboolean +dispatch_async_common(TrgClient * tc, + trg_request * trg_req, + GSourceFunc callback, gpointer data) +{ + TrgClientPrivate *priv = tc->priv; + GError *error = NULL; + + trg_req->callback = callback; + trg_req->cb_data = data; + trg_req->connid = g_atomic_int_get(&priv->connid); + + trg_client_thread_pool_push(tc, trg_req, &error); + if (error) { + g_error("thread creation error: %s\n", error->message); + g_error_free(error); + g_free(trg_req); + return FALSE; + } else { + return TRUE; + } +} + +gboolean +dispatch_async(TrgClient * tc, JsonNode * req, + GSourceFunc callback, gpointer data) +{ + trg_request *trg_req = g_new0(trg_request, 1); + trg_req->node = req; + + return dispatch_async_common(tc, trg_req, callback, data); +} + +gboolean +dispatch_async_str(TrgClient * tc, gchar * req, + GSourceFunc callback, gpointer data) +{ + trg_request *trg_req = g_new0(trg_request, 1); + trg_req->str = req; + + return dispatch_async_common(tc, trg_req, callback, data); +} + +gboolean trg_client_update_session(TrgClient * tc, GSourceFunc callback, + gpointer data) +{ + return dispatch_async(tc, session_get(), callback, data); +} + +gdouble trg_client_get_seed_ratio_limit(TrgClient * tc) +{ + return tc->priv->seedRatioLimit; +} + +gboolean trg_client_get_seed_ratio_limited(TrgClient * tc) +{ + return tc->priv->seedRatioLimited; +} -- cgit v1.2.3