summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGravatar Alan Fitton <ajf@eth0.org.uk>2011-04-08 14:24:25 +0000
committerGravatar Alan Fitton <ajf@eth0.org.uk>2011-04-08 14:24:25 +0000
commit070688006ac7cea1639f38c0bb270c191f7dc38f (patch)
tree258fbf88a6f28a86bef83553e2430e950d972793 /src
parentf74c398577f66488cf5c420f5df318ab801117cc (diff)
a torrent add dialog based off Transmission GTK (different bencode+action implementation) which allows you to set file priorities, torrent priority, directory in advance. probably needs a little more work.
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am4
-rw-r--r--src/bencode.c294
-rw-r--r--src/bencode.h66
-rw-r--r--src/protocol-constants.h9
-rw-r--r--src/requests.c3
-rw-r--r--src/transmission-remote-gtk.schemas65
-rw-r--r--src/trg-cell-renderer-priority.c9
-rw-r--r--src/trg-file-parser.c186
-rw-r--r--src/trg-file-parser.h15
-rw-r--r--src/trg-files-model.c8
-rw-r--r--src/trg-files-model.h4
-rw-r--r--src/trg-files-tree-view.c78
-rw-r--r--src/trg-general-panel.c10
-rw-r--r--src/trg-main-window.c107
-rw-r--r--src/trg-preferences-dialog.c24
-rw-r--r--src/trg-preferences.c27
-rw-r--r--src/trg-preferences.h5
-rw-r--r--src/trg-state-selector.c6
-rw-r--r--src/trg-torrent-add-dialog.c1057
-rw-r--r--src/trg-torrent-add-dialog.h58
-rw-r--r--src/trg-torrent-graph.c5
-rw-r--r--src/trg-torrent-tree-view.c5
-rw-r--r--src/util.c18
-rw-r--r--src/util.h2
24 files changed, 1901 insertions, 164 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
index a022cd6..18efe16 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -69,6 +69,10 @@ transmission_remote_gtk_SOURCES = main.c \
trg-torrent-move-dialog.c \
trg-stats-dialog.c \
trg-torrent-graph.c \
+ trg-torrent-add-dialog.c \
+ trg-file-parser.c \
+ bencode.c \
+ trg-preferences.c \
$(NULL)
transmission_remote_gtk_LDFLAGS = -lm -lcurl $(jsonglib_LIBS) $(gtk_LIBS) $(gthread_LIBS) $(GEOIP_LIBS) $(gconf_LIBS) $(gio_LIBS) $(unique_LIBS) $(notify_LIBS) $(libproxy_LIBS)
diff --git a/src/bencode.c b/src/bencode.c
new file mode 100644
index 0000000..0a66cdb
--- /dev/null
+++ b/src/bencode.c
@@ -0,0 +1,294 @@
+/*
+ * C implementation of a bencode decoder.
+ * This is the format defined by BitTorrent:
+ * http://wiki.theory.org/BitTorrentSpecification#bencoding
+ *
+ * The only external requirements are a few [standard] function calls and
+ * the gint64 type. Any sane system should provide all of these things.
+ *
+ * See the bencode.h header file for usage information.
+ *
+ * This is released into the public domain:
+ * http://en.wikipedia.org/wiki/Public_Domain
+ *
+ * Written by:
+ * Mike Frysinger <vapier@gmail.com>
+ * And improvements from:
+ * Gilles Chanteperdrix <gilles.chanteperdrix@xenomai.org>
+ */
+
+/*
+ * This implementation isn't optimized at all as I wrote it to support a bogus
+ * system. I have no real interest in this format. Feel free to send me
+ * patches (so long as you don't copyright them and you release your changes
+ * into the public domain as well).
+ */
+
+#include <stdlib.h> /* malloc() realloc() free() strtoll() */
+#include <string.h> /* memset() */
+#include <ctype.h>
+
+#include <glib.h>
+
+#include "bencode.h"
+
+static be_node *be_alloc(be_type type)
+{
+ be_node *ret = calloc(1, sizeof(be_node));
+ if (ret)
+ ret->type = type;
+ return ret;
+}
+
+static gint64 _be_decode_int(const char **data, gint64 * data_len)
+{
+ char *endp;
+ gint64 ret = strtoll(*data, &endp, 10);
+ *data_len -= (endp - *data);
+ *data = endp;
+ return ret;
+}
+
+gint64 be_str_len(be_node * node)
+{
+ gint64 ret = 0;
+ if (node->val.s)
+ memcpy(&ret, node->val.s - sizeof(ret), sizeof(ret));
+ return ret;
+}
+
+static char *_be_decode_str(const char **data, gint64 * data_len)
+{
+ gint64 sllen = _be_decode_int(data, data_len);
+ long slen = sllen;
+ unsigned long len;
+ char *ret = NULL;
+
+ /* slen is signed, so negative values get rejected */
+ if (sllen < 0)
+ return ret;
+
+ /* reject attempts to allocate large values that overflow the
+ * size_t type which is used with malloc()
+ */
+ if (sizeof(gint64) != sizeof(long))
+ if (sllen != slen)
+ return ret;
+
+ /* make sure we have enough data left */
+ if (sllen > *data_len - 1)
+ return ret;
+
+ /* switch from signed to unsigned so we don't overflow below */
+ len = slen;
+
+ if (**data == ':') {
+ char *_ret = malloc(sizeof(sllen) + len + 1);
+ memcpy(_ret, &sllen, sizeof(sllen));
+ ret = _ret + sizeof(sllen);
+ memcpy(ret, *data + 1, len);
+ ret[len] = '\0';
+ *data += len + 1;
+ *data_len -= len + 1;
+ }
+ return ret;
+}
+
+static be_node *_be_decode(const char **data, gint64 * data_len)
+{
+ be_node *ret = NULL;
+ char dc;
+
+ if (!*data_len)
+ return ret;
+
+ dc = **data;
+ if (dc == 'l') {
+ unsigned int i = 0;
+
+ ret = be_alloc(BE_LIST);
+
+ --(*data_len);
+ ++(*data);
+ while (**data != 'e') {
+ ret->val.l =
+ realloc(ret->val.l, (i + 2) * sizeof(*ret->val.l));
+ ret->val.l[i] = _be_decode(data, data_len);
+ if (!ret->val.l[i])
+ break;
+ ++i;
+ }
+ --(*data_len);
+ ++(*data);
+
+ ret->val.l[i] = NULL;
+
+ return ret;
+ } else if (dc == 'd') {
+ unsigned int i = 0;
+
+ ret = be_alloc(BE_DICT);
+
+ --(*data_len);
+ ++(*data);
+ while (**data != 'e') {
+ ret->val.d =
+ realloc(ret->val.d, (i + 2) * sizeof(*ret->val.d));
+ ret->val.d[i].key = _be_decode_str(data, data_len);
+ ret->val.d[i].val = _be_decode(data, data_len);
+ if (!ret->val.l[i])
+ break;
+ ++i;
+ }
+ --(*data_len);
+ ++(*data);
+
+ ret->val.d[i].val = NULL;
+
+ return ret;
+ } else if (dc == 'i') {
+ ret = be_alloc(BE_INT);
+
+ --(*data_len);
+ ++(*data);
+ ret->val.i = _be_decode_int(data, data_len);
+ if (**data != 'e')
+ return NULL;
+ --(*data_len);
+ ++(*data);
+
+ return ret;
+ } else if (isdigit(dc)) {
+ ret = be_alloc(BE_STR);
+
+ ret->val.s = _be_decode_str(data, data_len);
+ return ret;
+ }
+
+ return ret;
+}
+
+be_node *be_decoden(const char *data, gint64 len)
+{
+ return _be_decode(&data, &len);
+}
+
+be_node *be_decode(const char *data)
+{
+ return be_decoden(data, strlen(data));
+}
+
+static inline void _be_free_str(char *str)
+{
+ if (str)
+ free(str - sizeof(gint64));
+}
+
+int be_validate_node(be_node * node, int type)
+{
+ if (!node || node->type != type)
+ return 1;
+ else
+ return 0;
+}
+
+void be_free(be_node * node)
+{
+ switch (node->type) {
+ case BE_STR:
+ _be_free_str(node->val.s);
+ break;
+
+ case BE_INT:
+ break;
+
+ case BE_LIST:{
+ unsigned int i;
+ for (i = 0; node->val.l[i]; ++i)
+ be_free(node->val.l[i]);
+ free(node->val.l);
+ break;
+ }
+
+ case BE_DICT:{
+ unsigned int i;
+ for (i = 0; node->val.d[i].val; ++i) {
+ _be_free_str(node->val.d[i].key);
+ be_free(node->val.d[i].val);
+ }
+ free(node->val.d);
+ break;
+ }
+ }
+ free(node);
+}
+
+be_node *be_dict_find(be_node * node, char *key, int type)
+{
+ int i;
+ for (i = 0; node->val.d[i].val; ++i) {
+ if (!strcmp(node->val.d[i].key, key)) {
+ be_node *cn = node->val.d[i].val;
+ if (type < 0 || cn->type == type)
+ return node->val.d[i].val;
+ }
+ }
+ return NULL;
+}
+
+#ifdef BE_DEBUG
+#include <stdio.h>
+#include <stdint.h>
+
+static void _be_dump_indent(ssize_t indent)
+{
+ while (indent-- > 0)
+ printf(" ");
+}
+
+static void _be_dump(be_node * node, ssize_t indent)
+{
+ size_t i;
+
+ _be_dump_indent(indent);
+ indent = abs(indent);
+
+ switch (node->type) {
+ case BE_STR:
+ printf("str = %s (len = %lli)\n", node->val.s, be_str_len(node));
+ break;
+
+ case BE_INT:
+ printf("int = %lli\n", node->val.i);
+ break;
+
+ case BE_LIST:
+ puts("list [");
+
+ for (i = 0; node->val.l[i]; ++i)
+ _be_dump(node->val.l[i], indent + 1);
+
+ _be_dump_indent(indent);
+ puts("]");
+ break;
+
+ case BE_DICT:
+ puts("dict {");
+
+ for (i = 0; node->val.d[i].val; ++i) {
+ _be_dump_indent(indent + 1);
+ printf("%s => ", node->val.d[i].key);
+ _be_dump(node->val.d[i].val, -(indent + 1));
+ }
+
+ _be_dump_indent(indent);
+ puts("}");
+ break;
+ }
+}
+
+void be_dump(be_node * node)
+{
+ _be_dump(node, 0);
+}
+#endif
diff --git a/src/bencode.h b/src/bencode.h
new file mode 100644
index 0000000..71760e0
--- /dev/null
+++ b/src/bencode.h
@@ -0,0 +1,66 @@
+/*
+ * C implementation of a bencode decoder.
+ * This is the format defined by BitTorrent:
+ * http://wiki.theory.org/BitTorrentSpecification#bencoding
+ *
+ * The only external requirements are a few [standard] function calls and
+ * the gint64 type. Any sane system should provide all of these things.
+ *
+ * This is released into the public domain.
+ * Written by Mike Frysinger <vapier@gmail.com>.
+ */
+
+/* USAGE:
+ * - pass the string full of the bencoded data to be_decode()
+ * - parse the resulting tree however you like
+ * - call be_free() on the tree to release resources
+ */
+
+#ifndef _BENCODE_H
+#define _BENCODE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+ typedef enum {
+ BE_STR,
+ BE_INT,
+ BE_LIST,
+ BE_DICT
+ } be_type;
+
+ struct be_dict;
+ struct be_node;
+
+/*
+ * XXX: the "val" field of be_dict and be_node can be confusing ...
+ */
+
+ typedef struct be_dict {
+ char *key;
+ struct be_node *val;
+ } be_dict;
+
+ typedef struct be_node {
+ be_type type;
+ union {
+ char *s;
+ gint64 i;
+ struct be_node **l;
+ struct be_dict *d;
+ } val;
+ } be_node;
+
+ extern gint64 be_str_len(be_node * node);
+ extern be_node *be_decode(const char *bencode);
+ extern be_node *be_decoden(const char *bencode, gint64 bencode_len);
+ extern void be_free(be_node * node);
+ extern void be_dump(be_node * node);
+ extern be_node *be_dict_find(be_node * node, char *key, int type);
+ extern int be_validate_node(be_node * node, int type);
+
+#ifdef __cplusplus
+}
+#endif
+#endif
diff --git a/src/protocol-constants.h b/src/protocol-constants.h
index 98d1d72..5299f3b 100644
--- a/src/protocol-constants.h
+++ b/src/protocol-constants.h
@@ -70,6 +70,7 @@
#define FIELD_SEED_RATIO_LIMIT "seedRatioLimit"
#define FIELD_PEER_LIMIT "peer-limit"
#define FIELD_DOWNLOAD_DIR "downloadDir"
+#define FIELD_FILE_DOWNLOAD_DIR "download-dir"
#define FIELD_FILES_WANTED "files-wanted"
#define FIELD_FILES_UNWANTED "files-unwanted"
@@ -115,7 +116,13 @@ enum {
STATUS_DOWNLOADING = 4,
STATUS_SEEDING = 8,
STATUS_PAUSED = 16
-} TorrentState;
+};
+
+enum {
+ TR_PRI_LOW = -1,
+ TR_PRI_NORMAL = 0, /* since NORMAL is 0, memset initializes nicely */
+ TR_PRI_HIGH = 1
+};
#define TFILE_LENGTH "length"
#define TFILE_BYTES_COMPLETED "bytesCompleted"
diff --git a/src/requests.c b/src/requests.c
index 99bdd75..9c252c7 100644
--- a/src/requests.c
+++ b/src/requests.c
@@ -164,12 +164,13 @@ JsonNode *torrent_add_url(const gchar * url, gboolean paused)
return root;
}
-JsonNode *torrent_add(gchar * filename, gboolean paused G_GNUC_UNUSED)
+JsonNode *torrent_add(gchar * filename, gboolean paused)
{
JsonNode *root = base_request(METHOD_TORRENT_ADD);
JsonObject *args = node_get_arguments(root);
gchar *encodedFile = base64encode(filename);
json_object_set_string_member(args, PARAM_METAINFO, encodedFile);
+ json_object_set_boolean_member(args, PARAM_PAUSED, paused);
g_free(encodedFile);
return root;
}
diff --git a/src/transmission-remote-gtk.schemas b/src/transmission-remote-gtk.schemas
index 4849f83..11415a8 100644
--- a/src/transmission-remote-gtk.schemas
+++ b/src/transmission-remote-gtk.schemas
@@ -2,6 +2,19 @@
<schemalist>
<schema>
+ <key>/schemas/apps/transmission-remote-gtk/add-dialog</key>
+ <applyto>/apps/transmission-remote-gtk/add-dialog</applyto>
+ <owner>transmission-remote-gtk</owner>
+ <type>bool</type>
+ <default>1</default>
+
+ <locale name="C">
+ <short>Show add dialog</short>
+ <long>Show add dialog</long>
+ </locale>
+ </schema>
+
+ <schema>
<key>/schemas/apps/transmission-remote-gtk/auto-connect</key>
<applyto>/apps/transmission-remote-gtk/auto-connect</applyto>
<owner>transmission-remote-gtk</owner>
@@ -106,6 +119,45 @@
</schema>
<schema>
+ <key>/schemas/apps/transmission-remote-gtk/add-options-dialog</key>
+ <applyto>/apps/transmission-remote-gtk/add-options-dialog</applyto>
+ <owner>transmission-remote-gtk</owner>
+ <type>bool</type>
+ <default>1</default>
+
+ <locale name="C">
+ <short>Options dialog</short>
+ <long>Options dialog</long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/transmission-remote-gtk/start-paused</key>
+ <applyto>/apps/transmission-remote-gtk/start-paused</applyto>
+ <owner>transmission-remote-gtk</owner>
+ <type>bool</type>
+ <default>0</default>
+
+ <locale name="C">
+ <short>Start paused</short>
+ <long>Start paused</long>
+ </locale>
+ </schema>
+
+ <schema>
+ <key>/schemas/apps/transmission-remote-gtk/show-graph</key>
+ <applyto>/apps/transmission-remote-gtk/show-graph</applyto>
+ <owner>transmission-remote-gtk</owner>
+ <type>bool</type>
+ <default>0</default>
+
+ <locale name="C">
+ <short>Show graph</short>
+ <long>Show graph</long>
+ </locale>
+ </schema>
+
+ <schema>
<key>/schemas/apps/transmission-remote-gtk/update-interval</key>
<applyto>/apps/transmission-remote-gtk/update-interval</applyto>
<owner>transmission-remote-gtk</owner>
@@ -171,6 +223,19 @@
</schema>
<schema>
+ <key>/schemas/apps/transmission-remote-gtk/last-torrent-dir</key>
+ <applyto>/apps/transmission-remote-gtk/last-torrent-dir</applyto>
+ <owner>transmission-remote-gtk</owner>
+ <type>string</type>
+ <default></default>
+
+ <locale name="C">
+ <short>Last torrent directory</short>
+ <long>Last torrent directory</long>
+ </locale>
+ </schema>
+
+ <schema>
<key>/schemas/apps/transmission-remote-gtk/username</key>
<applyto>/apps/transmission-remote-gtk/username</applyto>
<owner>transmission-remote-gtk</owner>
diff --git a/src/trg-cell-renderer-priority.c b/src/trg-cell-renderer-priority.c
index eb16580..14d4763 100644
--- a/src/trg-cell-renderer-priority.c
+++ b/src/trg-cell-renderer-priority.c
@@ -22,6 +22,7 @@
#include <gtk/gtk.h>
#include "trg-cell-renderer-priority.h"
+#include "protocol-constants.h"
#include "trg-files-model.h"
#include "util.h"
@@ -68,9 +69,9 @@ trg_cell_renderer_priority_set_property(GObject * object,
if (property_id == PROP_PRIORITY_VALUE) {
priv->priority_value = g_value_get_int64(value);
- if (priv->priority_value == T_PRIORITY_LOW) {
+ if (priv->priority_value == TR_PRI_LOW) {
g_object_set(object, "text", _("Low"), NULL);
- } else if (priv->priority_value == T_PRIORITY_HIGH) {
+ } else if (priv->priority_value == TR_PRI_HIGH) {
g_object_set(object, "text", _("High"), NULL);
} else {
g_object_set(object, "text", _("Normal"), NULL);
@@ -93,8 +94,8 @@ trg_cell_renderer_priority_class_init(TrgCellRendererPriorityClass * klass)
g_param_spec_int64
("priority-value",
"Priority Value",
- "Priority Value", T_PRIORITY_LOW,
- T_PRIORITY_HIGH, T_PRIORITY_NORMAL,
+ "Priority Value", TR_PRI_LOW,
+ TR_PRI_HIGH, TR_PRI_NORMAL,
G_PARAM_READWRITE |
G_PARAM_STATIC_NAME |
G_PARAM_STATIC_NICK |
diff --git a/src/trg-file-parser.c b/src/trg-file-parser.c
new file mode 100644
index 0000000..ec22d1d
--- /dev/null
+++ b/src/trg-file-parser.c
@@ -0,0 +1,186 @@
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+
+#include <glib.h>
+
+#include "bencode.h"
+#include "trg-file-parser.h"
+
+#define my_print_errno(x) printf("%s: error (%d) %s\n", __func__, errno, x);
+
+static trg_torrent_file_node
+ *trg_torrent_file_node_insert(trg_torrent_file_node * top,
+ be_node * file_node, guint index,
+ gint64 * total_length)
+{
+ int i;
+ trg_torrent_file_node *path_el_parent = top;
+ be_node *file_length_node = be_dict_find(file_node, "length", BE_INT);
+ be_node *file_path_node = be_dict_find(file_node, "path", BE_LIST);
+
+ if (!file_path_node || !file_length_node)
+ return NULL;
+
+ /* Iterate over the path list which contains each file/directory
+ * component of the path in order.
+ */
+ for (i = 0;;) {
+ be_node *path_el_node = file_path_node->val.l[i];
+
+ trg_torrent_file_node *target_node = NULL;
+ GList *li;
+
+ /* Does this element exist already? */
+ for (li = path_el_parent->children; li != NULL;
+ li = g_list_next(li)) {
+ trg_torrent_file_node *x = (trg_torrent_file_node *) li->data;
+ if (!g_strcmp0(x->name, path_el_node->val.s)) {
+ target_node = x;
+ break;
+ }
+ }
+
+ if (!target_node) {
+ /* Create a new node and add it as a child of the parent from the
+ * last iteration. */
+ target_node = g_new0(trg_torrent_file_node, 1);
+ target_node->name = g_strdup(path_el_node->val.s);
+ path_el_parent->children =
+ g_list_append(path_el_parent->children, target_node);
+ }
+
+ path_el_parent = target_node;
+
+ /* Is this the last component of the path (the file)? */
+ if (!file_path_node->val.l[++i]) {
+ *total_length += (target_node->length =
+ (gint64) (file_length_node->val.i));
+ target_node->index = index;
+ return target_node;
+ }
+ }
+}
+
+static void trg_torrent_file_node_free(trg_torrent_file_node * node)
+{
+ GList *li;
+ for (li = node->children; li != NULL; li = g_list_next(li))
+ trg_torrent_file_node_free((trg_torrent_file_node *) li->data);
+ g_list_free(node->children);
+ g_free(node->name);
+ g_free(node);
+}
+
+void trg_torrent_file_free(trg_torrent_file * t)
+{
+ trg_torrent_file_node_free(t->top_node);
+ g_free(t->name);
+ g_free(t);
+}
+
+static trg_torrent_file_node *trg_parse_torrent_file_nodes(be_node *
+ info_node,
+ gint64 *
+ total_length)
+{
+ be_node *files_node = be_dict_find(info_node, "files", BE_LIST);
+ trg_torrent_file_node *top_node = g_new0(trg_torrent_file_node, 1);
+ int i;
+
+ /* Probably means single file mode. */
+ if (!files_node)
+ return NULL;
+
+ for (i = 0; files_node->val.l[i]; ++i) {
+ be_node *file_node = files_node->val.l[i];
+
+ if (be_validate_node(file_node, BE_DICT) ||
+ !trg_torrent_file_node_insert(top_node, file_node, i,
+ total_length)) {
+ /* Unexpected format. Throw away everything, file indexes need to
+ * be correct. */
+ trg_torrent_file_node_free(top_node);
+ return NULL;
+ }
+ }
+
+ return top_node;
+}
+
+trg_torrent_file *trg_parse_torrent_file(char *filename)
+{
+ int fd;
+ struct stat sb;
+ void *addr;
+ be_node *top_node, *info_node, *name_node;
+ trg_torrent_file *ret = NULL;
+
+ fd = open(filename, O_RDONLY);
+ if (fd < 0) {
+ my_print_errno("opening file");
+ return NULL;
+ }
+
+ if (fstat(fd, &sb) == -1) {
+ my_print_errno("on fstat");
+ close(fd);
+ return NULL;
+ }
+
+ addr = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
+ if (addr == MAP_FAILED) {
+ my_print_errno("on mmap");
+ close(fd);
+ return NULL;
+ }
+
+ top_node = be_decoden((char *) addr, sb.st_size);
+ munmap(addr, sb.st_size);
+ close(fd);
+
+ if (!top_node) {
+ return NULL;
+ } else if (be_validate_node(top_node, BE_DICT)) {
+ goto out;
+ }
+
+ info_node = be_dict_find(top_node, "info", BE_DICT);
+ if (!info_node)
+ goto out;
+
+ name_node = be_dict_find(info_node, "name", BE_STR);
+ if (!name_node)
+ goto out;
+
+ ret = g_new0(trg_torrent_file, 1);
+ ret->name = g_strdup(name_node->val.s);
+
+ ret->top_node =
+ trg_parse_torrent_file_nodes(info_node, &(ret->total_length));
+ if (!ret->top_node) {
+ trg_torrent_file_node *file_node;
+ be_node *length_node = be_dict_find(info_node, "length", BE_INT);
+
+ if (!length_node) {
+ g_free(ret);
+ ret = NULL;
+ goto out;
+ }
+
+ file_node = g_new0(trg_torrent_file_node, 1);
+ file_node->length = ret->total_length =
+ (gint64) (length_node->val.i);
+ file_node->name = g_strdup(ret->name);
+ ret->top_node = file_node;
+ }
+
+ out:
+ be_free(top_node);
+ return ret;
+}
diff --git a/src/trg-file-parser.h b/src/trg-file-parser.h
new file mode 100644
index 0000000..0958b9d
--- /dev/null
+++ b/src/trg-file-parser.h
@@ -0,0 +1,15 @@
+typedef struct {
+ char *name;
+ gint64 length;
+ GList *children;
+ guint index;
+} trg_torrent_file_node;
+
+typedef struct {
+ char *name;
+ trg_torrent_file_node *top_node;
+ gint64 total_length;
+} trg_torrent_file;
+
+void trg_torrent_file_free(trg_torrent_file * t);
+trg_torrent_file *trg_parse_torrent_file(char *filename);
diff --git a/src/trg-files-model.c b/src/trg-files-model.c
index 3051f55..084997d 100644
--- a/src/trg-files-model.c
+++ b/src/trg-files-model.c
@@ -44,14 +44,12 @@ static void trg_files_model_iter_new(TrgFilesModel * model,
GtkTreeIter * iter, JsonObject * file,
int id)
{
- char size[32];
-
gtk_list_store_append(GTK_LIST_STORE(model), iter);
- trg_strlsize(size, file_get_length(file));
gtk_list_store_set(GTK_LIST_STORE(model), iter,
FILESCOL_NAME, file_get_name(file),
- FILESCOL_SIZE, size, FILESCOL_ID, id, -1);
+ FILESCOL_SIZE, file_get_length(file),
+ FILESCOL_ID, id, -1);
}
void trg_files_model_set_accept(TrgFilesModel * model, gboolean accept)
@@ -100,7 +98,7 @@ static void trg_files_model_init(TrgFilesModel * self)
column_types[FILESCOL_ICON] = G_TYPE_STRING;
column_types[FILESCOL_NAME] = G_TYPE_STRING;
- column_types[FILESCOL_SIZE] = G_TYPE_STRING;
+ column_types[FILESCOL_SIZE] = G_TYPE_INT64;
column_types[FILESCOL_PROGRESS] = G_TYPE_DOUBLE;
column_types[FILESCOL_ID] = G_TYPE_INT;
column_types[FILESCOL_WANTED] = G_TYPE_BOOLEAN;
diff --git a/src/trg-files-model.h b/src/trg-files-model.h
index c92ddd0..76dccf7 100644
--- a/src/trg-files-model.h
+++ b/src/trg-files-model.h
@@ -60,10 +60,6 @@ G_END_DECLS enum {
FILESCOL_COLUMNS
};
-#define T_PRIORITY_LOW -1
-#define T_PRIORITY_NORMAL 0
-#define T_PRIORITY_HIGH 1
-
void
trg_files_model_update(TrgFilesModel * model, gint64 updateSerial,
JsonObject * t, gboolean first);
diff --git a/src/trg-files-tree-view.c b/src/trg-files-tree-view.c
index dab7611..2b5c812 100644
--- a/src/trg-files-tree-view.c
+++ b/src/trg-files-tree-view.c
@@ -28,6 +28,7 @@
#include "trg-cell-renderer-priority.h"
#include "trg-main-window.h"
#include "requests.h"
+#include "util.h"
#include "json.h"
#include "dispatch.h"
#include "protocol-constants.h"
@@ -65,44 +66,18 @@ static void set_wanted_foreachfunc(GtkTreeModel * model,
TRUE, FILESCOL_ICON, GTK_STOCK_FILE, -1);
}
-static void set_low_foreachfunc(GtkTreeModel * model,
- GtkTreePath * path G_GNUC_UNUSED,
- GtkTreeIter * iter,
- gpointer data G_GNUC_UNUSED)
+static void set_priority_foreachfunc(GtkTreeModel * model,
+ GtkTreePath * path G_GNUC_UNUSED,
+ GtkTreeIter * iter, gpointer data)
{
GValue value = { 0 };
g_value_init(&value, G_TYPE_INT64);
- g_value_set_int64(&value, T_PRIORITY_LOW);
+ g_value_set_int64(&value, (gint64) GPOINTER_TO_INT(data));
gtk_list_store_set_value(GTK_LIST_STORE(model), iter,
FILESCOL_PRIORITY, &value);
}
-static void set_normal_foreachfunc(GtkTreeModel * model,
- GtkTreePath * path G_GNUC_UNUSED,
- GtkTreeIter * iter,
- gpointer data G_GNUC_UNUSED)
-{
- gtk_list_store_set(GTK_LIST_STORE(model), iter, FILESCOL_PRIORITY,
- T_PRIORITY_NORMAL, -1);
-}
-
-static void set_high_foreachfunc(GtkTreeModel * model,
- GtkTreePath * path G_GNUC_UNUSED,
- GtkTreeIter * iter,
- gpointer data G_GNUC_UNUSED)
-{
- gtk_list_store_set(GTK_LIST_STORE(model), iter, FILESCOL_PRIORITY,
- T_PRIORITY_HIGH, -1);
-}
-
-static void add_file_id_to_array(JsonObject * args, gchar * key,
- gint index)
-{
- JsonArray *array = json_object_get_array_member(args, key);
- json_array_add_int_element(array, index);
-}
-
static void send_updated_file_prefs_foreachfunc(GtkTreeModel * model,
GtkTreePath *
path G_GNUC_UNUSED,
@@ -121,21 +96,14 @@ static void send_updated_file_prefs_foreachfunc(GtkTreeModel * model,
else
add_file_id_to_array(args, FIELD_FILES_WANTED, id);
- if (priority == T_PRIORITY_LOW)
+ if (priority == TR_PRI_LOW)
add_file_id_to_array(args, FIELD_FILES_PRIORITY_LOW, id);
- else if (priority == T_PRIORITY_HIGH)
+ else if (priority == TR_PRI_HIGH)
add_file_id_to_array(args, FIELD_FILES_PRIORITY_HIGH, id);
else
add_file_id_to_array(args, FIELD_FILES_PRIORITY_NORMAL, id);
}
-static void remove_array_if_empty(JsonObject * args, gchar * key)
-{
- JsonArray *array = json_object_get_array_member(args, key);
- if (json_array_get_length(array) == 0)
- json_object_remove_member(args, key);
-}
-
static void
on_files_update(JsonObject * response, int status, gpointer data)
{
@@ -166,27 +134,10 @@ static void send_updated_file_prefs(TrgFilesTreeView * tv)
req = torrent_set(targetIdArray);
args = node_get_arguments(req);
- json_object_set_array_member(args, FIELD_FILES_WANTED,
- json_array_new());
- json_object_set_array_member(args, FIELD_FILES_UNWANTED,
- json_array_new());
- json_object_set_array_member(args, FIELD_FILES_PRIORITY_HIGH,
- json_array_new());
- json_object_set_array_member(args, FIELD_FILES_PRIORITY_NORMAL,
- json_array_new());
- json_object_set_array_member(args, FIELD_FILES_PRIORITY_LOW,
- json_array_new());
-
gtk_tree_selection_selected_foreach(selection,
send_updated_file_prefs_foreachfunc,
args);
- remove_array_if_empty(args, FIELD_FILES_WANTED);
- remove_array_if_empty(args, FIELD_FILES_UNWANTED);
- remove_array_if_empty(args, FIELD_FILES_PRIORITY_HIGH);
- remove_array_if_empty(args, FIELD_FILES_PRIORITY_NORMAL);
- remove_array_if_empty(args, FIELD_FILES_PRIORITY_LOW);
-
trg_files_model_set_accept(TRG_FILES_MODEL(model), FALSE);
dispatch_async(priv->client, req, on_files_update, tv);
@@ -197,8 +148,9 @@ static void set_low(GtkWidget * w G_GNUC_UNUSED, gpointer data)
TrgFilesTreeView *tv = TRG_FILES_TREE_VIEW(data);
GtkTreeSelection *selection =
gtk_tree_view_get_selection(GTK_TREE_VIEW(data));
- gtk_tree_selection_selected_foreach(selection, set_low_foreachfunc,
- NULL);
+ gtk_tree_selection_selected_foreach(selection,
+ set_priority_foreachfunc,
+ GINT_TO_POINTER(TR_PRI_LOW));
send_updated_file_prefs(tv);
}
@@ -208,7 +160,8 @@ static void set_normal(GtkWidget * w G_GNUC_UNUSED, gpointer data)
GtkTreeSelection *selection =
gtk_tree_view_get_selection(GTK_TREE_VIEW(data));
gtk_tree_selection_selected_foreach(selection,
- set_normal_foreachfunc, NULL);
+ set_priority_foreachfunc,
+ GINT_TO_POINTER(TR_PRI_NORMAL));
send_updated_file_prefs(tv);
}
@@ -218,7 +171,8 @@ static void set_high(GtkWidget * w G_GNUC_UNUSED, gpointer data)
GtkTreeSelection *selection =
gtk_tree_view_get_selection(GTK_TREE_VIEW(data));
gtk_tree_selection_selected_foreach(selection,
- set_high_foreachfunc, NULL);
+ set_priority_foreachfunc,
+ GINT_TO_POINTER(TR_PRI_HIGH));
send_updated_file_prefs(tv);
}
@@ -362,8 +316,8 @@ static void trg_files_tree_view_init(TrgFilesTreeView * self)
trg_tree_view_add_pixbuf_text_column(TRG_TREE_VIEW(self),
FILESCOL_ICON, FILESCOL_NAME,
_("Name"), -1);
- trg_tree_view_add_column(TRG_TREE_VIEW(self), _("Size"),
- FILESCOL_SIZE);
+ trg_tree_view_add_size_column(TRG_TREE_VIEW(self), _("Size"),
+ FILESCOL_SIZE, -1);
trg_tree_view_add_prog_column(TRG_TREE_VIEW(self), _("Progress"),
FILESCOL_PROGRESS, -1);
trg_files_tree_view_add_wanted_column(TRG_TREE_VIEW(self), _("Wanted"),
diff --git a/src/trg-general-panel.c b/src/trg-general-panel.c
index 91274bf..ec60cf9 100644
--- a/src/trg-general-panel.c
+++ b/src/trg-general-panel.c
@@ -134,9 +134,7 @@ void trg_general_panel_update(TrgGeneralPanel * panel, JsonObject * t,
gtk_label_set_text(GTK_LABEL(priv->gen_downloaded_label), buf);
if (uploaded > 0 && downloaded > 0) {
- trg_strlratio(buf,
- (double) uploaded /
- (double) downloaded);
+ trg_strlratio(buf, (double) uploaded / (double) downloaded);
gtk_label_set_text(GTK_LABEL(priv->gen_ratio_label), buf);
} else {
gtk_label_set_text(GTK_LABEL(priv->gen_ratio_label), _("N/A"));
@@ -165,8 +163,10 @@ void trg_general_panel_update(TrgGeneralPanel * panel, JsonObject * t,
gtk_label_set_markup(GTK_LABEL(priv->gen_error_label), markup);
g_free(markup);
- markup = g_markup_printf_escaped("<span font_weight=\"bold\" fgcolor=\"red\">%s</span>",
- _("Error"));
+ markup =
+ g_markup_printf_escaped
+ ("<span font_weight=\"bold\" fgcolor=\"red\">%s</span>",
+ _("Error"));
gtk_label_set_markup(keyLabel, markup);
g_free(markup);
} else {
diff --git a/src/trg-main-window.c b/src/trg-main-window.c
index 4e21cf7..8080038 100644
--- a/src/trg-main-window.c
+++ b/src/trg-main-window.c
@@ -60,6 +60,7 @@
#include "trg-torrent-move-dialog.h"
#include "trg-torrent-props-dialog.h"
#include "trg-torrent-add-url-dialog.h"
+#include "trg-torrent-add-dialog.h"
#include "trg-toolbar.h"
#include "trg-menu-bar.h"
#include "trg-status-bar.h"
@@ -84,7 +85,6 @@ static void torrent_tv_onRowActivated(GtkTreeView * treeview,
GtkTreePath * path,
GtkTreeViewColumn * col,
gpointer userdata);
-static gpointer add_files_threadfunc(gpointer data);
static void add_url_cb(GtkWidget * w, gpointer data);
static void add_cb(GtkWidget * w, gpointer data);
static void disconnect_cb(GtkWidget * w, gpointer data);
@@ -370,40 +370,6 @@ torrent_tv_onRowActivated(GtkTreeView * treeview,
open_props_cb(GTK_WIDGET(treeview), userdata);
}
-/* 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;
- trg_client *client;
- gpointer cb_data;
-};
-
-static gpointer add_files_threadfunc(gpointer data)
-{
- JsonObject *response;
- JsonNode *request;
- struct add_torrent_threadfunc_args *args;
- gint status;
- GSList *li;
-
- args = (struct add_torrent_threadfunc_args *) data;
-
- for (li = args->list; li != NULL; li = g_slist_next(li)) {
- request = torrent_add((gchar *) li->data, FALSE);
- g_free(li->data);
- response = dispatch(args->client, request, &status);
- on_generic_interactive_action(response, status, args->cb_data);
- }
-
- g_slist_free(args->list);
- g_free(args);
-
- return NULL;
-}
-
static void add_url_cb(GtkWidget * w G_GNUC_UNUSED, gpointer data)
{
TrgMainWindow *win = TRG_MAIN_WINDOW(data);
@@ -416,47 +382,33 @@ static void add_url_cb(GtkWidget * w G_GNUC_UNUSED, gpointer data)
static void add_cb(GtkWidget * w G_GNUC_UNUSED, gpointer data)
{
- TrgMainWindowPrivate *priv;
- GtkWidget *dialog;
- GtkFileFilter *filter;
+ TrgMainWindowPrivate *priv = TRG_MAIN_WINDOW_GET_PRIVATE(data);
+ /*TrgTorrentAddDialog *dialog;
+ GtkFileFilter *filter; */
- priv = TRG_MAIN_WINDOW_GET_PRIVATE(data);
+ trg_torrent_add_dialog(TRG_MAIN_WINDOW(data), priv->client);
- dialog = gtk_file_chooser_dialog_new("Open File",
- GTK_WINDOW(data),
- GTK_FILE_CHOOSER_ACTION_OPEN,
- GTK_STOCK_CANCEL,
- GTK_RESPONSE_CANCEL,
- GTK_STOCK_OPEN,
- GTK_RESPONSE_ACCEPT, NULL);
- gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE);
-
- filter = gtk_file_filter_new();
- gtk_file_filter_add_pattern(filter, "*.torrent");
- gtk_file_filter_set_name(filter, _("BitTorrent Metadata"));
- gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter);
-
- if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
- GThread *thread;
- GError *error = NULL;
- struct add_torrent_threadfunc_args *args;
-
- args = g_new(struct add_torrent_threadfunc_args, 1);
- args->list =
- gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog));
- args->cb_data = data;
- args->client = priv->client;
-
- thread =
- g_thread_create(add_files_threadfunc, args, FALSE, &error);
- if (error != NULL) {
- g_printf("thread creation error: %s\n", error->message);
- g_error_free(error);
- g_free(args);
- }
- }
+ /*dialog = trg_torrent_add_dialog_new(TRG_MAIN_WINDOW(data), priv->client, NULL, TRUE);
+ gtk_dialog_run(GTK_DIALOG(dialog)); */
- gtk_widget_destroy(dialog);
+ /*dialog = gtk_file_chooser_dialog_new("Open File",
+ GTK_WINDOW(data),
+ GTK_FILE_CHOOSER_ACTION_OPEN,
+ GTK_STOCK_CANCEL,
+ GTK_RESPONSE_CANCEL,
+ GTK_STOCK_OPEN,
+ GTK_RESPONSE_ACCEPT, NULL);
+ gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE);
+
+ filter = gtk_file_filter_new();
+ gtk_file_filter_add_pattern(filter, "*.torrent");
+ gtk_file_filter_set_name(filter, _("BitTorrent Metadata"));
+ gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog), filter);
+
+ if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
+ }
+
+ gtk_widget_destroy(dialog); */
}
static void pause_cb(GtkWidget * w G_GNUC_UNUSED, gpointer data)
@@ -474,7 +426,7 @@ gboolean trg_add_from_filename(TrgMainWindow * win, gchar * fileName)
TrgMainWindowPrivate *priv = TRG_MAIN_WINDOW_GET_PRIVATE(win);
if (g_file_test(fileName, G_FILE_TEST_EXISTS) == TRUE) {
- JsonNode *torrentAddReq = torrent_add(fileName, FALSE);
+ JsonNode *torrentAddReq = torrent_add(fileName, pref_get_start_paused(priv->client->gconf));
dispatch_async(priv->client, torrentAddReq,
on_generic_interactive_action, win);
return TRUE;
@@ -1779,6 +1731,7 @@ static GObject *trg_main_window_constructor(GType type,
gtk_icon_theme_load_icon(theme, PACKAGE_NAME, 48,
GTK_ICON_LOOKUP_USE_BUILTIN, NULL);
+ notify_init(PACKAGE_NAME);
if (priv->icon)
gtk_window_set_default_icon(priv->icon);
@@ -1874,9 +1827,9 @@ static GObject *trg_main_window_constructor(GType type,
gtk_paned_pack1(GTK_PANED(priv->vpaned), priv->hpaned, TRUE, TRUE);
priv->stateSelector = trg_state_selector_new(priv->client);
- priv->stateSelectorScroller = my_scrolledwin_new(GTK_WIDGET(priv->stateSelector));
- gtk_paned_pack1(GTK_PANED(priv->hpaned),
- priv->stateSelectorScroller,
+ priv->stateSelectorScroller =
+ my_scrolledwin_new(GTK_WIDGET(priv->stateSelector));
+ gtk_paned_pack1(GTK_PANED(priv->hpaned), priv->stateSelectorScroller,
FALSE, FALSE);
gtk_paned_pack2(GTK_PANED(priv->hpaned),
diff --git a/src/trg-preferences-dialog.c b/src/trg-preferences-dialog.c
index c20c2cf..fb71881 100644
--- a/src/trg-preferences-dialog.c
+++ b/src/trg-preferences-dialog.c
@@ -305,6 +305,26 @@ static GtkWidget *trg_prefs_desktopPage(GConfClient * gconf,
return t;
}
+static GtkWidget *trg_prefs_behaviorPage(GConfClient *gconf)
+{
+ GtkWidget *w, *t;
+ gint row = 0;
+
+ t = hig_workarea_create();
+
+ hig_workarea_add_section_title(t, &row, _("Torrents"));
+
+ w = new_check_button(gconf, _("Start paused"),
+ TRG_GCONF_KEY_START_PAUSED);
+ hig_workarea_add_wide_control(t, &row, w);
+
+ w = new_check_button(gconf, _("Options dialog on add"),
+ TRG_GCONF_KEY_ADD_OPTIONS_DIALOG);
+ hig_workarea_add_wide_control(t, &row, w);
+
+ return t;
+}
+
static GtkWidget *trg_prefs_serverPage(GConfClient * gconf,
trg_client * client)
{
@@ -386,6 +406,10 @@ static GObject *trg_preferences_dialog_constructor(GType type,
trg_prefs_desktopPage(priv->gconf, priv->win),
gtk_label_new(_("Desktop")));
+ gtk_notebook_append_page(GTK_NOTEBOOK(notebook),
+ trg_prefs_behaviorPage(priv->gconf),
+ gtk_label_new(_("Behavior")));
+
gtk_container_set_border_width(GTK_CONTAINER(notebook), GUI_PAD);
gtk_box_pack_start(GTK_BOX(GTK_DIALOG(object)->vbox), notebook,
diff --git a/src/trg-preferences.c b/src/trg-preferences.c
new file mode 100644
index 0000000..ca52be4
--- /dev/null
+++ b/src/trg-preferences.c
@@ -0,0 +1,27 @@
+/*
+ * transmission-remote-gtk - Transmission RPC client for GTK
+ * Copyright (C) 2010 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 <glib.h>
+#include <gconf/gconf-client.h>
+
+#include "trg-preferences.h"
+
+gboolean pref_get_start_paused(GConfClient *gcc) {
+ return gconf_client_get_bool(gcc, TRG_GCONF_KEY_START_PAUSED, NULL);
+}
diff --git a/src/trg-preferences.h b/src/trg-preferences.h
index e4a047d..e781dc6 100644
--- a/src/trg-preferences.h
+++ b/src/trg-preferences.h
@@ -37,5 +37,10 @@
#define TRG_GCONF_KEY_SYSTEM_TRAY_MINIMISE "/apps/transmission-remote-gtk/system-tray-minimise"
#define TRG_GCONF_KEY_FILTER_TRACKERS "/apps/transmission-remote-gtk/filter-trackers"
#define TRG_GCONF_KEY_FILTER_DIRS "/apps/transmission-remote-gtk/filter-dirs"
+#define TRG_GCONF_KEY_LAST_TORRENT_DIR "/apps/transmission-remote-gtk/last-torrent-dir"
+#define TRG_GCONF_KEY_ADD_OPTIONS_DIALOG "/apps/transmission-remote-gtk/add-options-dialog"
+#define TRG_GCONF_KEY_START_PAUSED "/apps/transmission-remote-gtk/start-paused"
+
+gboolean pref_get_start_paused(GConfClient *gcc);
#endif /* TRG_PREFERENCES_H_ */
diff --git a/src/trg-state-selector.c b/src/trg-state-selector.c
index b6f3462..80fadde 100644
--- a/src/trg-state-selector.c
+++ b/src/trg-state-selector.c
@@ -250,8 +250,8 @@ void trg_state_selector_update(TrgStateSelector * s)
} else {
gtk_list_store_insert(GTK_LIST_STORE(model), &iter,
9 +
- g_hash_table_size(priv->
- trackers));
+ g_hash_table_size
+ (priv->trackers));
gtk_list_store_set(GTK_LIST_STORE(model), &iter,
STATE_SELECTOR_ICON,
GTK_STOCK_NETWORK,
@@ -441,7 +441,7 @@ TrgStateSelector *trg_state_selector_new(trg_client * client)
priv->client = client;
priv->showDirs =
gconf_client_get_bool(client->gconf,
- TRG_GCONF_KEY_FILTER_DIRS, NULL);
+ TRG_GCONF_KEY_FILTER_DIRS, NULL);
priv->showTrackers =
gconf_client_get_bool_or_true(client->gconf,
TRG_GCONF_KEY_FILTER_TRACKERS);
diff --git a/src/trg-torrent-add-dialog.c b/src/trg-torrent-add-dialog.c
new file mode 100644
index 0000000..dd1fac0
--- /dev/null
+++ b/src/trg-torrent-add-dialog.c
@@ -0,0 +1,1057 @@
+/*
+ * transmission-remote-gtk - Transmission RPC client for GTK
+ * Copyright (C) 2011 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.
+ */
+
+/* Most of the UI code was taken from open-dialog.c and files-list.c
+ * in Transmission, adapted to fit in with different torrent file parser
+ * and JSON dispatch.
+ */
+
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+#include <json-glib/json-glib.h>
+#include <glib/gprintf.h>
+#include <gconf/gconf-client.h>
+
+#include "hig.h"
+#include "util.h"
+#include "trg-client.h"
+#include "trg-main-window.h"
+#include "trg-file-parser.h"
+#include "trg-torrent-add-dialog.h"
+#include "trg-cell-renderer-size.h"
+#include "trg-preferences.h"
+#include "requests.h"
+#include "json.h"
+#include "dispatch.h"
+#include "protocol-constants.h"
+
+enum {
+ PROP_0,
+ PROP_FILENAME,
+ PROP_PARENT,
+ PROP_SHOW_OPTIONS,
+ PROP_CLIENT
+};
+
+enum {
+ FC_ICON,
+ FC_INDEX,
+ FC_LABEL,
+ FC_SIZE,
+ FC_PRIORITY,
+ FC_ENABLED,
+ N_FILE_COLS
+};
+
+enum {
+ NOT_SET = 1000,
+ MIXED = 1001
+};
+
+#define TR_COLUMN_ID_KEY "tr-id-key"
+
+G_DEFINE_TYPE(TrgTorrentAddDialog, trg_torrent_add_dialog, GTK_TYPE_DIALOG)
+#define TRG_TORRENT_ADD_DIALOG_GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), TRG_TYPE_TORRENT_ADD_DIALOG, TrgTorrentAddDialogPrivate))
+typedef struct _TrgTorrentAddDialogPrivate
+ TrgTorrentAddDialogPrivate;
+
+struct _TrgTorrentAddDialogPrivate {
+ trg_client *client;
+ TrgMainWindow *parent;
+ gint show_options;
+ GSList *filenames;
+ GtkWidget *source_chooser;
+ GtkWidget *dest_combo;
+ GtkWidget *priority_combo;
+ GtkWidget *file_list;
+ GtkTreeStore *store;
+ GtkWidget *paused_check;
+};
+
+static void
+trg_torrent_add_dialog_set_property(GObject * object,
+ guint prop_id,
+ const GValue * value,
+ GParamSpec * pspec G_GNUC_UNUSED)
+{
+ TrgTorrentAddDialogPrivate *priv =
+ TRG_TORRENT_ADD_DIALOG_GET_PRIVATE(object);
+
+ switch (prop_id) {
+ case PROP_FILENAME:
+ priv->filenames = g_value_get_pointer(value);
+ break;
+ case PROP_SHOW_OPTIONS:
+ priv->show_options = g_value_get_int(value);
+ break;
+ case PROP_PARENT:
+ priv->parent = g_value_get_object(value);
+ break;
+ case PROP_CLIENT:
+ priv->client = g_value_get_pointer(value);
+ break;
+ }
+}
+
+static void
+trg_torrent_add_dialog_get_property(GObject * object,
+ guint prop_id,
+ GValue * value,
+ GParamSpec * pspec G_GNUC_UNUSED)
+{
+ TrgTorrentAddDialogPrivate *priv =
+ TRG_TORRENT_ADD_DIALOG_GET_PRIVATE(object);
+
+ switch (prop_id) {
+ case PROP_FILENAME:
+ g_value_set_pointer(value, priv->filenames);
+ break;
+ case PROP_SHOW_OPTIONS:
+ g_value_set_int(value, priv->show_options);
+ break;
+ case PROP_PARENT:
+ g_value_set_object(value, priv->parent);
+ break;
+ }
+}
+
+/* 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;
+ trg_client *client;
+ gpointer cb_data;
+ gboolean paused;
+ gchar *dir;
+ gint priority;
+ gboolean extraArgs;
+};
+
+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 != NULL; li = g_slist_next(li)) {
+ JsonNode *request =
+ torrent_add((gchar *) li->data, files_thread_data->paused);
+ JsonObject *args = node_get_arguments(request);
+ JsonObject *response;
+ gint status;
+
+ 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, &status);
+ on_generic_interactive_action(response, status,
+ files_thread_data->cb_data);
+ }
+
+ 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;
+}
+
+static gchar *trg_destination_folder_get(GtkComboBox * box)
+{
+ return gtk_combo_box_get_active_text(box);
+}
+
+static GtkWidget *trg_destination_folder_new(trg_client * client)
+{
+ GtkWidget *combo = gtk_combo_box_entry_new_text();
+ gtk_combo_box_append_text(GTK_COMBO_BOX(combo),
+ json_object_get_string_member
+ (client->session, SGET_DOWNLOAD_DIR));
+ gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
+ return combo;
+}
+
+static void launch_add_thread(struct add_torrent_threadfunc_args *args)
+{
+ GError *error = NULL;
+ g_thread_create(add_files_threadfunc, args, FALSE, &error);
+
+ if (error != NULL) {
+ g_printf("thread creation error: %s\n", 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;
+ gint priority, index, wanted;
+
+ gtk_tree_model_get(model, iter, FC_PRIORITY, &priority,
+ FC_ENABLED, &wanted, FC_INDEX, &index, -1);
+
+ if (gtk_tree_model_iter_has_child(model, iter))
+ 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);
+
+ return FALSE;
+}
+
+static void
+trg_torrent_add_response_cb(GtkDialog * dlg, gint res_id, gpointer data)
+{
+ TrgTorrentAddDialogPrivate *priv =
+ TRG_TORRENT_ADD_DIALOG_GET_PRIVATE(dlg);
+
+ if (res_id == GTK_RESPONSE_ACCEPT) {
+ gboolean paused =
+ gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON
+ (priv->paused_check));
+ gint priority =
+ gtk_combo_box_get_active(GTK_COMBO_BOX(priv->priority_combo)) -
+ 1;
+ gchar *dir =
+ trg_destination_folder_get(GTK_COMBO_BOX(priv->dest_combo));
+
+ if (g_slist_length(priv->filenames) == 1) {
+ JsonNode *req =
+ torrent_add((gchar *) priv->filenames->data, paused);
+ 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);
+ } else {
+ struct add_torrent_threadfunc_args *args =
+ g_new(struct add_torrent_threadfunc_args, 1);
+ args->list = priv->filenames;
+ args->cb_data = data;
+ args->client = priv->client;
+ args->dir = g_strdup(dir);
+ args->priority = priority;
+ args->paused = paused;
+ args->extraArgs = TRUE;
+
+ launch_add_thread(args);
+ }
+ } else {
+ g_str_slist_free(priv->filenames);
+ }
+
+ gtk_widget_destroy(GTK_WIDGET(dlg));
+}
+
+static void
+renderPriority(GtkTreeViewColumn * column G_GNUC_UNUSED,
+ GtkCellRenderer * renderer,
+ GtkTreeModel * model,
+ GtkTreeIter * iter, gpointer data G_GNUC_UNUSED)
+{
+ int priority;
+ const char *text;
+ gtk_tree_model_get(model, iter, FC_PRIORITY, &priority, -1);
+ switch (priority) {
+ case TR_PRI_HIGH:
+ text = _("High");
+ break;
+ case TR_PRI_NORMAL:
+ text = _("Normal");
+ break;
+ case TR_PRI_LOW:
+ text = _("Low");
+ break;
+ default:
+ text = _("Mixed");
+ break;
+ }
+ g_object_set(renderer, "text", text, NULL);
+}
+
+static void
+renderDownload(GtkTreeViewColumn * column G_GNUC_UNUSED,
+ GtkCellRenderer * renderer,
+ GtkTreeModel * model,
+ GtkTreeIter * iter, gpointer data G_GNUC_UNUSED)
+{
+ gint enabled;
+ gtk_tree_model_get(model, iter, FC_ENABLED, &enabled, -1);
+ g_object_set(renderer, "inconsistent", (enabled == MIXED),
+ "active", (enabled == TRUE), NULL);
+}
+
+struct SubtreeForeachData {
+ gint column;
+ gint new_value;
+ GtkTreePath *path;
+};
+
+static gboolean
+setSubtreeForeach(GtkTreeModel * model,
+ GtkTreePath * path, GtkTreeIter * iter, gpointer gdata)
+{
+ /*const gboolean is_file = !gtk_tree_model_iter_has_child(model, iter); */
+
+ struct SubtreeForeachData *data = gdata;
+
+ if (!gtk_tree_path_compare(path, data->path)
+ || gtk_tree_path_is_descendant(path, data->path)) {
+ gtk_tree_store_set(GTK_TREE_STORE(model), iter, data->column,
+ data->new_value, -1);
+ }
+
+ return FALSE; /* keep walking */
+}
+
+static void
+setSubtree(GtkTreeModel * model, GtkTreePath * path, GtkTreeIter * iter,
+ gint column, gint new_value)
+{
+ gint result = new_value;
+ GtkTreeIter back_iter = *iter;
+
+ if (gtk_tree_model_iter_has_child(model, iter)) {
+ struct SubtreeForeachData tmp;
+ GtkTreeIter parent;
+
+ tmp.column = column;
+ tmp.new_value = new_value;
+ tmp.path = path;
+ gtk_tree_model_foreach(model, setSubtreeForeach, &tmp);
+
+ gtk_tree_model_iter_parent(model, &parent, iter);
+ } else {
+ gtk_tree_store_set(GTK_TREE_STORE(model), &back_iter, column, new_value,
+ -1);
+ }
+
+ while (1) {
+ GtkTreeIter tmp_iter;
+ gint n_children, i;
+
+ if (!gtk_tree_model_iter_parent(model, &tmp_iter, &back_iter))
+ break;
+
+ n_children =
+ gtk_tree_model_iter_n_children(model, &tmp_iter);
+
+ for (i = 0; i < n_children; i++) {
+ GtkTreeIter child;
+ gint current_value;
+
+ if (!gtk_tree_model_iter_nth_child(model, &child, &tmp_iter, i))
+ continue;
+
+ gtk_tree_model_get(model, &child, column, &current_value, -1);
+ if (current_value != new_value) {
+ result = MIXED;
+ break;
+ }
+ }
+
+ gtk_tree_store_set(GTK_TREE_STORE(model), &tmp_iter, column,
+ result, -1);
+
+ back_iter = tmp_iter;
+ }
+}
+
+static gboolean
+onViewPathToggled(GtkTreeView * view,
+ GtkTreeViewColumn * col,
+ GtkTreePath * path, gpointer data)
+{
+ int cid;
+ gboolean handled = FALSE;
+
+ if (!col || !path)
+ return FALSE;
+
+ cid =
+ GPOINTER_TO_INT(g_object_get_data
+ (G_OBJECT(col), TR_COLUMN_ID_KEY));
+ if ((cid == FC_PRIORITY) || (cid == FC_ENABLED)) {
+ GtkTreeIter iter;
+ /*GArray *indices = getActiveFilesForPath(view, path); */
+ GtkTreeModel *model = gtk_tree_view_get_model(view);
+
+ gtk_tree_model_get_iter(model, &iter, path);
+
+ if (cid == FC_PRIORITY) {
+ int priority;
+ gtk_tree_model_get(model, &iter, FC_PRIORITY, &priority, -1);
+ switch (priority) {
+ case TR_PRI_NORMAL:
+ priority = TR_PRI_HIGH;
+ break;
+ case TR_PRI_HIGH:
+ priority = TR_PRI_LOW;
+ break;
+ default:
+ priority = TR_PRI_NORMAL;
+ break;
+ }
+ setSubtree(model, path, &iter, FC_PRIORITY, priority);
+ } else {
+ int enabled;
+ gtk_tree_model_get(model, &iter, FC_ENABLED, &enabled, -1);
+ enabled = !enabled;
+
+ setSubtree(model, path, &iter, FC_ENABLED, enabled);
+ }
+
+ handled = TRUE;
+ }
+
+ return handled;
+}
+
+static gboolean
+getAndSelectEventPath(GtkTreeView * treeview,
+ GdkEventButton * event,
+ GtkTreeViewColumn ** col, GtkTreePath ** path)
+{
+ GtkTreeSelection *sel;
+
+ if (gtk_tree_view_get_path_at_pos(treeview,
+ event->x, event->y,
+ path, col, NULL, NULL)) {
+ sel = gtk_tree_view_get_selection(treeview);
+ if (!gtk_tree_selection_path_is_selected(sel, *path)) {
+ gtk_tree_selection_unselect_all(sel);
+ gtk_tree_selection_select_path(sel, *path);
+ }
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean
+onViewButtonPressed(GtkWidget * w, GdkEventButton * event, gpointer gdata)
+{
+ GtkTreeViewColumn *col = NULL;
+ GtkTreePath *path = NULL;
+ gboolean handled = FALSE;
+ GtkTreeView *treeview = GTK_TREE_VIEW(w);
+
+ if (event->type == GDK_BUTTON_PRESS && event->button == 1
+ && !(event->state & (GDK_SHIFT_MASK | GDK_CONTROL_MASK))
+ && getAndSelectEventPath(treeview, event, &col, &path)) {
+ handled = onViewPathToggled(treeview, col, path, NULL);
+ }
+
+ gtk_tree_path_free(path);
+ return handled;
+}
+
+GtkWidget *gtr_file_list_new(GtkTreeStore ** store)
+{
+ int size;
+ int width;
+ GtkWidget *view;
+ GtkWidget *scroll;
+ GtkCellRenderer *rend;
+ GtkTreeSelection *sel;
+ GtkTreeViewColumn *col;
+ GtkTreeView *tree_view;
+ const char *title;
+ PangoLayout *pango_layout;
+ PangoContext *pango_context;
+ PangoFontDescription *pango_font_description;
+
+ /* create the view */
+ view = gtk_tree_view_new();
+ tree_view = GTK_TREE_VIEW(view);
+ gtk_tree_view_set_rules_hint(tree_view, TRUE);
+ gtk_container_set_border_width(GTK_CONTAINER(view), GUI_PAD_BIG);
+ g_signal_connect(view, "button-press-event",
+ G_CALLBACK(onViewButtonPressed), view);
+
+ pango_context = gtk_widget_create_pango_context(view);
+ pango_font_description =
+ pango_font_description_copy(pango_context_get_font_description
+ (pango_context));
+ size = pango_font_description_get_size(pango_font_description);
+ pango_font_description_set_size(pango_font_description, size * 0.8);
+ g_object_unref(G_OBJECT(pango_context));
+
+ /* set up view */
+ sel = gtk_tree_view_get_selection(tree_view);
+ gtk_tree_selection_set_mode(sel, GTK_SELECTION_MULTIPLE);
+ gtk_tree_view_expand_all(tree_view);
+ gtk_tree_view_set_search_column(tree_view, FC_LABEL);
+
+ /* add file column */
+ col = GTK_TREE_VIEW_COLUMN(g_object_new(GTK_TYPE_TREE_VIEW_COLUMN,
+ "expand", TRUE,
+ "title", _("Name"), NULL));
+ gtk_tree_view_column_set_resizable(col, TRUE);
+ rend = gtk_cell_renderer_pixbuf_new();
+ gtk_tree_view_column_pack_start(col, rend, FALSE);
+ gtk_tree_view_column_add_attribute(col, rend, "stock-id", FC_ICON);
+
+ /* add text renderer */
+ rend = gtk_cell_renderer_text_new();
+ g_object_set(rend, "ellipsize", PANGO_ELLIPSIZE_END, "font-desc",
+ pango_font_description, NULL);
+ gtk_tree_view_column_pack_start(col, rend, TRUE);
+ gtk_tree_view_column_set_attributes(col, rend, "text", FC_LABEL, NULL);
+ gtk_tree_view_column_set_sort_column_id(col, FC_LABEL);
+ gtk_tree_view_append_column(tree_view, col);
+
+ /* add "size" column */
+
+ title = _("Size");
+ rend = trg_cell_renderer_size_new();
+ g_object_set(rend, "alignment", PANGO_ALIGN_RIGHT,
+ "font-desc", pango_font_description,
+ "xpad", GUI_PAD, "xalign", 1.0f, "yalign", 0.5f, NULL);
+ col = gtk_tree_view_column_new_with_attributes(title, rend, NULL);
+ gtk_tree_view_column_set_sizing(col, GTK_TREE_VIEW_COLUMN_GROW_ONLY);
+ gtk_tree_view_column_set_sort_column_id(col, FC_SIZE);
+ gtk_tree_view_column_set_attributes(col, rend, "size-value", FC_SIZE,
+ NULL);
+ gtk_tree_view_append_column(tree_view, col);
+
+ /* add "enabled" column */
+ title = _("Download");
+ pango_layout = gtk_widget_create_pango_layout(view, title);
+ pango_layout_get_pixel_size(pango_layout, &width, NULL);
+ width += 30; /* room for the sort indicator */
+ g_object_unref(G_OBJECT(pango_layout));
+ rend = gtk_cell_renderer_toggle_new();
+ col = gtk_tree_view_column_new_with_attributes(title, rend, NULL);
+ g_object_set_data(G_OBJECT(col), TR_COLUMN_ID_KEY,
+ GINT_TO_POINTER(FC_ENABLED));
+ gtk_tree_view_column_set_fixed_width(col, width);
+ gtk_tree_view_column_set_sizing(col, GTK_TREE_VIEW_COLUMN_FIXED);
+ gtk_tree_view_column_set_cell_data_func(col, rend, renderDownload,
+ NULL, NULL);
+ gtk_tree_view_column_set_sort_column_id(col, FC_ENABLED);
+ gtk_tree_view_append_column(tree_view, col);
+
+ /* add priority column */
+ title = _("Priority");
+ pango_layout = gtk_widget_create_pango_layout(view, title);
+ pango_layout_get_pixel_size(pango_layout, &width, NULL);
+ width += 30; /* room for the sort indicator */
+ g_object_unref(G_OBJECT(pango_layout));
+ rend = gtk_cell_renderer_text_new();
+ g_object_set(rend, "xalign", (gfloat) 0.5, "yalign", (gfloat) 0.5,
+ NULL);
+ col = gtk_tree_view_column_new_with_attributes(title, rend, NULL);
+ g_object_set_data(G_OBJECT(col), TR_COLUMN_ID_KEY,
+ GINT_TO_POINTER(FC_PRIORITY));
+ gtk_tree_view_column_set_fixed_width(col, width);
+ gtk_tree_view_column_set_sizing(col, GTK_TREE_VIEW_COLUMN_FIXED);
+ gtk_tree_view_column_set_sort_column_id(col, FC_PRIORITY);
+ gtk_tree_view_column_set_cell_data_func(col, rend, renderPriority,
+ NULL, NULL);
+ gtk_tree_view_append_column(tree_view, col);
+
+ *store = gtk_tree_store_new(N_FILE_COLS, G_TYPE_STRING, /* icon */
+ G_TYPE_UINT, /* index */
+ G_TYPE_STRING, /* label */
+ G_TYPE_INT64, /* size */
+ G_TYPE_INT, /* priority */
+ G_TYPE_INT); /* dl enabled */
+
+ gtk_tree_view_set_model(tree_view, GTK_TREE_MODEL(*store));
+
+ /* create the scrolled window and stick the view in it */
+ scroll = gtk_scrolled_window_new(NULL, NULL);
+ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scroll),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC);
+ gtk_scrolled_window_set_shadow_type(GTK_SCROLLED_WINDOW(scroll),
+ GTK_SHADOW_IN);
+ gtk_container_add(GTK_CONTAINER(scroll), view);
+ gtk_widget_set_size_request(scroll, -1, 200);
+
+ pango_font_description_free(pango_font_description);
+ return scroll;
+}
+
+static GtkWidget *gtr_dialog_get_content_area(GtkDialog * dialog)
+{
+#if GTK_CHECK_VERSION( 2,14,0 )
+ return gtk_dialog_get_content_area(dialog);
+#else
+ return dialog->vbox;
+#endif
+}
+
+static void gtr_dialog_set_content(GtkDialog * dialog, GtkWidget * content)
+{
+ GtkWidget *vbox = gtr_dialog_get_content_area(dialog);
+ gtk_box_pack_start(GTK_BOX(vbox), content, TRUE, TRUE, 0);
+ gtk_widget_show_all(content);
+}
+
+GtkWidget *gtr_combo_box_new_enum(const char *text_1, ...)
+{
+ GtkWidget *w;
+ GtkCellRenderer *r;
+ GtkListStore *store;
+ va_list vl;
+ const char *text;
+ va_start(vl, text_1);
+
+ store = gtk_list_store_new(2, G_TYPE_INT, G_TYPE_STRING);
+
+ text = text_1;
+ if (text != NULL)
+ do {
+ const int val = va_arg(vl, int);
+ gtk_list_store_insert_with_values(store, NULL, INT_MAX, 0, val,
+ 1, text, -1);
+ text = va_arg(vl, const char *);
+ }
+ while (text != NULL);
+
+ w = gtk_combo_box_new_with_model(GTK_TREE_MODEL(store));
+ r = gtk_cell_renderer_text_new();
+ gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(w), r, TRUE);
+ gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(w), r, "text", 1, NULL);
+
+ /* cleanup */
+ g_object_unref(store);
+ return w;
+}
+
+GtkWidget *gtr_priority_combo_new(void)
+{
+ return gtr_combo_box_new_enum(_("Low"), TR_PRI_LOW,
+ _("Normal"), TR_PRI_NORMAL,
+ _("High"), TR_PRI_HIGH, NULL);
+}
+
+static void addTorrentFilters(GtkFileChooser * chooser)
+{
+ GtkFileFilter *filter;
+
+ filter = gtk_file_filter_new();
+ gtk_file_filter_set_name(filter, _("Torrent files"));
+ gtk_file_filter_add_pattern(filter, "*.torrent");
+ gtk_file_chooser_add_filter(chooser, filter);
+
+ filter = gtk_file_filter_new();
+ gtk_file_filter_set_name(filter, _("All files"));
+ gtk_file_filter_add_pattern(filter, "*");
+ gtk_file_chooser_add_filter(chooser, filter);
+}
+
+static void store_add_node(GtkTreeStore * store, GtkTreeIter * parent,
+ trg_torrent_file_node * node)
+{
+ GtkTreeIter child;
+ GList *li;
+
+ if (node->name) {
+ gtk_tree_store_append(store, &child, parent);
+ gtk_tree_store_set(store, &child, FC_LABEL, node->name, -1);
+ gtk_tree_store_set(store, &child, FC_ICON,
+ node->children ? GTK_STOCK_DIRECTORY :
+ GTK_STOCK_FILE, -1);
+ if (!node->children) {
+ gtk_tree_store_set(store, &child, FC_INDEX, node->index, -1);
+ gtk_tree_store_set(store, &child, FC_SIZE, node->length, -1);
+ gtk_tree_store_set(store, &child, FC_PRIORITY, 0, -1);
+ gtk_tree_store_set(store, &child, FC_ENABLED, 1, -1);
+ }
+ }
+
+ for (li = node->children; li != NULL; li = g_list_next(li))
+ store_add_node(store, node->name ? &child : NULL,
+ (trg_torrent_file_node *) li->data);
+}
+
+static void trg_torrent_add_dialog_set_filenames(TrgTorrentAddDialog * d,
+ GSList * filenames)
+{
+ TrgTorrentAddDialogPrivate *priv =
+ TRG_TORRENT_ADD_DIALOG_GET_PRIVATE(d);
+ GtkButton *chooser = GTK_BUTTON(priv->source_chooser);
+ gint nfiles = filenames ? g_slist_length(filenames) : 0;
+
+ gtk_tree_store_clear(priv->store);
+
+ if (nfiles == 1) {
+ gchar *file_name = (gchar *) filenames->data;
+ gchar *file_name_base = g_path_get_basename(file_name);
+ trg_torrent_file *tor_data = trg_parse_torrent_file(file_name);
+ store_add_node(priv->store, NULL, tor_data->top_node);
+ trg_torrent_file_free(tor_data);
+ gtk_button_set_label(chooser, file_name_base);
+ g_free(file_name_base);
+ gtk_widget_set_sensitive(priv->file_list, TRUE);
+ } else {
+ gtk_widget_set_sensitive(priv->file_list, FALSE);
+ if (nfiles < 1) {
+ gtk_button_set_label(chooser, _("(None)"));
+ } else {
+ gtk_button_set_label(chooser, _("(Multiple)"));
+ }
+ }
+
+ priv->filenames = filenames;
+}
+
+static void trg_torrent_add_dialog_generic_save_dir(GtkFileChooser * c,
+ GConfClient * gcc)
+{
+ gchar *cwd = gtk_file_chooser_get_current_folder(c);
+
+ if (cwd) {
+ gconf_client_set_string(gcc, TRG_GCONF_KEY_LAST_TORRENT_DIR, cwd,
+ NULL);
+ g_free(cwd);
+ }
+}
+
+static GtkWidget *trg_torrent_add_dialog_generic(GtkWindow * parent,
+ GConfClient * gcc)
+{
+ GtkWidget *w = gtk_file_chooser_dialog_new(_("Add a Torrent"), parent,
+ GTK_FILE_CHOOSER_ACTION_OPEN,
+ GTK_STOCK_CANCEL,
+ GTK_RESPONSE_CANCEL,
+ GTK_STOCK_ADD,
+ GTK_RESPONSE_ACCEPT,
+ NULL);
+ gchar *dir =
+ gconf_client_get_string(gcc, TRG_GCONF_KEY_LAST_TORRENT_DIR, NULL);
+ if (dir) {
+ gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(w), dir);
+ g_free(dir);
+ }
+ addTorrentFilters(GTK_FILE_CHOOSER(w));
+ gtk_dialog_set_alternative_button_order(GTK_DIALOG(w),
+ GTK_RESPONSE_ACCEPT,
+ GTK_RESPONSE_CANCEL, -1);
+ gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(w), TRUE);
+ return w;
+}
+
+static void trg_torrent_add_dialog_source_click_cb(GtkWidget * w,
+ gpointer data)
+{
+ TrgTorrentAddDialogPrivate *priv =
+ TRG_TORRENT_ADD_DIALOG_GET_PRIVATE(data);
+ GtkWidget *d = trg_torrent_add_dialog_generic(GTK_WINDOW(data),
+ priv->client->gconf);
+
+ if (gtk_dialog_run(GTK_DIALOG(d)) == GTK_RESPONSE_ACCEPT) {
+ if (priv->filenames)
+ g_str_slist_free(priv->filenames);
+
+ priv->filenames =
+ gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(d));
+
+ trg_torrent_add_dialog_generic_save_dir(GTK_FILE_CHOOSER(d),
+ priv->client->gconf);
+ trg_torrent_add_dialog_set_filenames(TRG_TORRENT_ADD_DIALOG(data),
+ priv->filenames);
+ }
+
+ gtk_widget_destroy(GTK_WIDGET(d));
+}
+
+static GObject *trg_torrent_add_dialog_constructor(GType type,
+ guint
+ n_construct_properties,
+ GObjectConstructParam
+ * construct_params)
+{
+ GObject *obj = G_OBJECT_CLASS
+ (trg_torrent_add_dialog_parent_class)->constructor(type,
+ n_construct_properties,
+ construct_params);
+ TrgTorrentAddDialogPrivate *priv =
+ TRG_TORRENT_ADD_DIALOG_GET_PRIVATE(obj);
+
+ GtkWidget *t, *l;
+ gint row = 0;
+ gint col = 0;
+
+ /* window */
+ gtk_window_set_title(GTK_WINDOW(obj), _("Add Torrent"));
+ gtk_window_set_transient_for(GTK_WINDOW(obj),
+ GTK_WINDOW(priv->parent));
+ gtk_window_set_destroy_with_parent(GTK_WINDOW(obj), TRUE);
+
+ /* buttons */
+ gtk_dialog_add_button(GTK_DIALOG(obj), GTK_STOCK_CANCEL,
+ GTK_RESPONSE_CANCEL);
+ gtk_dialog_add_button(GTK_DIALOG(obj), GTK_STOCK_OPEN,
+ GTK_RESPONSE_ACCEPT);
+ gtk_dialog_set_alternative_button_order(GTK_DIALOG(obj),
+ GTK_RESPONSE_ACCEPT,
+ GTK_RESPONSE_CANCEL, -1);
+ gtk_dialog_set_default_response(GTK_DIALOG(obj), GTK_RESPONSE_ACCEPT);
+
+ /* workspace */
+ t = gtk_table_new(6, 2, FALSE);
+ gtk_container_set_border_width(GTK_CONTAINER(t), GUI_PAD_BIG);
+ gtk_table_set_row_spacings(GTK_TABLE(t), GUI_PAD);
+ gtk_table_set_col_spacings(GTK_TABLE(t), GUI_PAD_BIG);
+
+ priv->file_list = gtr_file_list_new(&priv->store);
+ gtk_widget_set_sensitive(priv->file_list, FALSE);
+ priv->paused_check =
+ gtk_check_button_new_with_mnemonic(_("Start _paused"));
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(priv->paused_check),
+ pref_get_start_paused(priv->client->
+ gconf));
+
+ priv->priority_combo = gtr_priority_combo_new();
+ gtk_combo_box_set_active(GTK_COMBO_BOX(priv->priority_combo), 1);
+
+ l = gtk_label_new_with_mnemonic(_("_Torrent file:"));
+ gtk_misc_set_alignment(GTK_MISC(l), 0.0f, 0.5f);
+ gtk_table_attach(GTK_TABLE(t), l, col, col + 1, row, row + 1, GTK_FILL,
+ 0, 0, 0);
+ ++col;
+
+ 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);
+
+ gtk_table_attach(GTK_TABLE(t), priv->source_chooser, col, col + 1, row,
+ row + 1, ~0, 0, 0, 0);
+ gtk_label_set_mnemonic_widget(GTK_LABEL(l), priv->source_chooser);
+ g_signal_connect(priv->source_chooser, "clicked",
+ G_CALLBACK(trg_torrent_add_dialog_source_click_cb),
+ obj);
+
+ ++row;
+ col = 0;
+ l = gtk_label_new_with_mnemonic(_("_Destination folder:"));
+ gtk_misc_set_alignment(GTK_MISC(l), 0.0f, 0.5f);
+ gtk_table_attach(GTK_TABLE(t), l, col, col + 1, row, row + 1, GTK_FILL,
+ 0, 0, 0);
+ ++col;
+ priv->dest_combo = trg_destination_folder_new(priv->client);
+
+ /*if( !gtk_file_chooser_set_current_folder( GTK_FILE_CHOOSER( w ),
+ data->downloadDir ) )
+ g_warning( "couldn't select '%s'", data->downloadDir );
+ list = get_recent_destinations( );
+ for( walk = list; walk; walk = walk->next )
+ gtk_file_chooser_add_shortcut_folder( GTK_FILE_CHOOSER( w ), walk->data, NULL );
+ g_slist_free( list ); */
+ gtk_table_attach(GTK_TABLE(t), priv->dest_combo, col, col + 1, row,
+ row + 1, ~0, 0, 0, 0);
+ gtk_label_set_mnemonic_widget(GTK_LABEL(l), priv->dest_combo);
+
+ ++row;
+ col = 0;
+ gtk_widget_set_size_request(priv->file_list, 466u, 300u);
+ gtk_table_attach_defaults(GTK_TABLE(t), priv->file_list, col, col + 2,
+ row, row + 1);
+
+ ++row;
+ col = 0;
+ l = gtk_label_new_with_mnemonic(_("Torrent _priority:"));
+ gtk_misc_set_alignment(GTK_MISC(l), 0.0f, 0.5f);
+ gtk_table_attach(GTK_TABLE(t), l, col, col + 1, row, row + 1, ~0, 0, 0,
+ 0);
+ ++col;
+ gtk_table_attach(GTK_TABLE(t), priv->priority_combo, col, col + 1, row,
+ row + 1, ~0, 0, 0, 0);
+ gtk_label_set_mnemonic_widget(GTK_LABEL(l), priv->priority_combo);
+
+ ++row;
+ col = 0;
+ gtk_table_attach(GTK_TABLE(t), priv->paused_check, col, col + 2, row,
+ row + 1, GTK_FILL, 0, 0, 0);
+
+ gtr_dialog_set_content(GTK_DIALOG(obj), t);
+
+ g_signal_connect(G_OBJECT(obj),
+ "response",
+ G_CALLBACK(trg_torrent_add_response_cb),
+ priv->parent);
+
+ return obj;
+}
+
+static void
+trg_torrent_add_dialog_class_init(TrgTorrentAddDialogClass * klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+ g_type_class_add_private(klass, sizeof(TrgTorrentAddDialogPrivate));
+
+ object_class->set_property = trg_torrent_add_dialog_set_property;
+ object_class->get_property = trg_torrent_add_dialog_get_property;
+ object_class->constructor = trg_torrent_add_dialog_constructor;
+
+ g_object_class_install_property(object_class,
+ PROP_FILENAME,
+ g_param_spec_pointer("filenames",
+ "filenames",
+ "filenames",
+ 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",
+ "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_SHOW_OPTIONS,
+ g_param_spec_int("show-options",
+ "show-options",
+ "show-options", 0, 1,
+ 1,
+ 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", "parent",
+ "parent",
+ 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_torrent_add_dialog_init(TrgTorrentAddDialog * self)
+{
+}
+
+TrgTorrentAddDialog *trg_torrent_add_dialog_new(TrgMainWindow * parent,
+ trg_client * client,
+ GSList * filenames,
+ gboolean showOptions)
+{
+ return g_object_new(TRG_TYPE_TORRENT_ADD_DIALOG,
+ "filenames", filenames,
+ "show-options", showOptions ? 1 : 0,
+ "parent", parent, "client", client, NULL);
+}
+
+void trg_torrent_add_dialog(TrgMainWindow * win, trg_client * client)
+{
+ GtkWidget *w;
+ GtkWidget *c;
+
+ w = trg_torrent_add_dialog_generic(GTK_WINDOW(win), client->gconf);
+
+ c = gtk_check_button_new_with_mnemonic(_("Show _options dialog"));
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(c),
+ gconf_client_get_bool_or_true(client->
+ gconf,
+ TRG_GCONF_KEY_ADD_OPTIONS_DIALOG));
+ gtk_file_chooser_set_extra_widget(GTK_FILE_CHOOSER(w), c);
+
+ if (gtk_dialog_run(GTK_DIALOG(w)) == GTK_RESPONSE_ACCEPT) {
+ GtkFileChooser *chooser = GTK_FILE_CHOOSER(w);
+ GtkToggleButton *tb =
+ GTK_TOGGLE_BUTTON(gtk_file_chooser_get_extra_widget(chooser));
+ gboolean showOptions = gtk_toggle_button_get_active(tb);
+ GSList *l = gtk_file_chooser_get_filenames(chooser);
+
+ trg_torrent_add_dialog_generic_save_dir(GTK_FILE_CHOOSER(w),
+ client->gconf);
+
+ if (showOptions) {
+ TrgTorrentAddDialog *dialog =
+ trg_torrent_add_dialog_new(win, client, l, showOptions);
+
+ gtk_widget_show_all(GTK_WIDGET(dialog));
+ } else {
+ struct add_torrent_threadfunc_args *args =
+ g_new(struct add_torrent_threadfunc_args, 1);
+ args->list = l;
+ args->cb_data = win;
+ args->client = client;
+ args->paused = pref_get_start_paused(client->gconf);
+ args->extraArgs = FALSE;
+
+ launch_add_thread(args);
+ }
+ }
+
+ gtk_widget_destroy(GTK_WIDGET(w));
+}
diff --git a/src/trg-torrent-add-dialog.h b/src/trg-torrent-add-dialog.h
new file mode 100644
index 0000000..ab3d6cb
--- /dev/null
+++ b/src/trg-torrent-add-dialog.h
@@ -0,0 +1,58 @@
+/*
+ * transmission-remote-gtk - Transmission RPC client for GTK
+ * Copyright (C) 2011 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_TORRENT_ADD_DIALOG_H_
+#define TRG_TORRENT_ADD_DIALOG_H_
+
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+#include "trg-client.h"
+#include "trg-main-window.h"
+
+G_BEGIN_DECLS
+#define TRG_TYPE_TORRENT_ADD_DIALOG trg_torrent_add_dialog_get_type()
+#define TRG_TORRENT_ADD_DIALOG(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST ((obj), TRG_TYPE_TORRENT_ADD_DIALOG, TrgTorrentAddDialog))
+#define TRG_TORRENT_ADD_DIALOG_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST ((klass), TRG_TYPE_TORRENT_ADD_DIALOG, TrgTorrentAddDialogClass))
+#define TRG_IS_TORRENT_ADD_DIALOG(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE ((obj), TRG_TYPE_TORRENT_ADD_DIALOG))
+#define TRG_IS_TORRENT_ADD_DIALOG_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE ((klass), TRG_TYPE_TORRENT_ADD_DIALOG))
+#define TRG_TORRENT_ADD_DIALOG_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS ((obj), TRG_TYPE_TORRENT_ADD_DIALOG, TrgTorrentAddDialogClass))
+ typedef struct {
+ GtkDialog parent;
+} TrgTorrentAddDialog;
+
+typedef struct {
+ GtkDialogClass parent_class;
+} TrgTorrentAddDialogClass;
+
+GType trg_torrent_add_dialog_get_type(void);
+
+TrgTorrentAddDialog *trg_torrent_add_dialog_new(TrgMainWindow * win,
+ trg_client * client,
+ GSList * filenames,
+ gint showOptions);
+void trg_torrent_add_dialog(TrgMainWindow * win, trg_client * client);
+
+G_END_DECLS
+#endif /* TRG_TORRENT_ADD_DIALOG_H_ */
diff --git a/src/trg-torrent-graph.c b/src/trg-torrent-graph.c
index b5ce339..c5e4d4b 100644
--- a/src/trg-torrent-graph.c
+++ b/src/trg-torrent-graph.c
@@ -17,7 +17,8 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-/* This graph drawing code was taken from gnome-system-monitor (load-graph.cpp)
+/*
+ * This graph drawing code was taken from gnome-system-monitor (load-graph.cpp)
* Converted the class from C++ to GObject, substituted out some STL (C++)
* functions, and removed the unecessary parts for memory/cpu.
*/
@@ -61,7 +62,7 @@ struct _TrgTorrentGraphPrivate {
double graph_delx;
guint graph_buffer_offset;
- GdkColor colors[2];
+ GdkColor colors[GRAPH_NUM_LINES];
float data_block[GRAPH_NUM_POINTS * GRAPH_NUM_LINES];
GList *points;
diff --git a/src/trg-torrent-tree-view.c b/src/trg-torrent-tree-view.c
index 7571adb..f6a99c6 100644
--- a/src/trg-torrent-tree-view.c
+++ b/src/trg-torrent-tree-view.c
@@ -61,6 +61,9 @@ static void trg_torrent_tree_view_init(TrgTorrentTreeView * tv)
TORRENT_COLUMN_DOWNLOADED, -1);
trg_tree_view_add_ratio_column(TRG_TREE_VIEW(tv), _("Ratio"),
TORRENT_COLUMN_RATIO, -1);
+ gtk_tree_view_set_search_column(GTK_TREE_VIEW(tv),
+ TORRENT_COLUMN_NAME);
+
}
gint get_first_selected(trg_client * client, TrgTorrentTreeView * view,
@@ -86,6 +89,8 @@ gint get_first_selected(trg_client * client, TrgTorrentTreeView * view,
TORRENT_COLUMN_UPDATESERIAL, &updateSerial,
-1);
+ /* This is about to be removed and won't have valid JSON pointed
+ * to by the model. */
if (updateSerial < client->updateSerial)
id = -1;
}
diff --git a/src/util.c b/src/util.c
index 6cb6e11..cd7e9f2 100644
--- a/src/util.c
+++ b/src/util.c
@@ -35,6 +35,24 @@
#include "util.h"
#include "dispatch.h"
+void add_file_id_to_array(JsonObject * args, gchar * key, gint index)
+{
+ JsonArray *array;
+ if (json_object_has_member(args, key)) {
+ array = json_object_get_array_member(args, key);
+ } else {
+ array = json_array_new();
+ json_object_set_array_member(args, key, array);
+ }
+ json_array_add_int_element(array, index);
+}
+
+void g_str_slist_free(GSList * list)
+{
+ g_slist_foreach( list, (GFunc)g_free, NULL );
+ g_slist_free(list);
+}
+
GRegex *trg_uri_host_regex_new(void)
{
return g_regex_new("^[^:/?#]+:?//([^/?#]*)", G_REGEX_OPTIMIZE, 0,
diff --git a/src/util.h b/src/util.h
index a3b2053..73938b5 100644
--- a/src/util.h
+++ b/src/util.h
@@ -37,6 +37,8 @@
#define MEGABYTE_FACTOR ( 1024.0 * 1024.0 )
#define GIGABYTE_FACTOR ( 1024.0 * 1024.0 * 1024.0 )
+void add_file_id_to_array(JsonObject * args, gchar * key, gint index);
+void g_str_slist_free(GSList * list);
GRegex *trg_uri_host_regex_new(void);
gchar *trg_gregex_get_first(GRegex * rx, const gchar * uri);
gboolean gconf_client_get_bool_or_true(GConfClient * gconf, gchar * key);