summaryrefslogtreecommitdiff
path: root/src/trg-files-model.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/trg-files-model.c')
-rw-r--r--src/trg-files-model.c449
1 files changed, 449 insertions, 0 deletions
diff --git a/src/trg-files-model.c b/src/trg-files-model.c
new file mode 100644
index 0000000..bb1b46b
--- /dev/null
+++ b/src/trg-files-model.c
@@ -0,0 +1,449 @@
+/*
+ * 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 <string.h>
+#include <limits.h>
+#include <gtk/gtk.h>
+#include <json-glib/json-glib.h>
+
+#include "protocol-constants.h"
+#include "trg-files-model-common.h"
+#include "trg-files-tree-view-common.h"
+#include "trg-files-tree.h"
+#include "trg-files-model.h"
+#include "trg-client.h"
+#include "torrent.h"
+#include "util.h"
+
+#include "trg-files-model.h"
+
+G_DEFINE_TYPE(TrgFilesModel, trg_files_model, GTK_TYPE_TREE_STORE)
+#define TRG_FILES_MODEL_GET_PRIVATE(o) \
+ (G_TYPE_INSTANCE_GET_PRIVATE ((o), TRG_TYPE_FILES_MODEL, TrgFilesModelPrivate))
+typedef struct _TrgFilesModelPrivate TrgFilesModelPrivate;
+
+struct _TrgFilesModelPrivate {
+ gint64 torrentId;
+ guint n_items;
+ gboolean accept;
+};
+
+/* Push a given increment to a treemodel node and its parents.
+ * Used for updating bytescompleted.
+ * This is only used for user interaction, the initial population is done
+ * in a simple tree before for performance.
+ * */
+
+static void trg_files_update_parent_progress(GtkTreeModel * model,
+ GtkTreeIter * iter,
+ gint64 increment)
+{
+ GtkTreeIter back_iter = *iter;
+
+ if (increment < 1)
+ return;
+
+ while (1) {
+ GtkTreeIter tmp_iter;
+ gint64 lastCompleted, newCompleted, length;
+
+ if (!gtk_tree_model_iter_parent(model, &tmp_iter, &back_iter))
+ break;
+
+ gtk_tree_model_get(model, &tmp_iter, FILESCOL_BYTESCOMPLETED,
+ &lastCompleted, FILESCOL_SIZE, &length, -1);
+ newCompleted = lastCompleted + increment;
+
+ gtk_tree_store_set(GTK_TREE_STORE(model), &tmp_iter,
+ FILESCOL_PROGRESS, file_get_progress(length,
+ newCompleted),
+ FILESCOL_BYTESCOMPLETED, newCompleted, -1);
+
+ back_iter = tmp_iter;
+ }
+}
+
+/* Update the bytesCompleted and size for a nodes parents, and also figure out
+ * if a priority/enabled change requires updating the parents (needs to iterate
+ * over the other nodes at its level).
+ *
+ * It's faster doing it in here than when it's in the model.
+ */
+static void trg_files_tree_update_ancestors(trg_files_tree_node * node)
+{
+ trg_files_tree_node *back_iter = node;
+ gint pri_result = node->priority;
+ gint enabled_result = node->enabled;
+
+ while ((back_iter = back_iter->parent)) {
+ GList *li;
+ for (li = back_iter->children; li; li = g_list_next(li)) {
+ trg_files_tree_node *back_node =
+ (trg_files_tree_node *) li->data;
+ gint common_result = 0;
+
+ if (back_node->priority != pri_result)
+ common_result = pri_result = TR_PRI_MIXED;
+
+ if (back_node->enabled != enabled_result)
+ common_result = enabled_result = TR_PRI_MIXED;
+
+ if (common_result == TR_PRI_MIXED)
+ break;
+ }
+
+ back_iter->bytesCompleted += node->bytesCompleted;
+ back_iter->length += node->length;
+ back_iter->priority = pri_result;
+ back_iter->enabled = enabled_result;
+ }
+}
+
+static void
+store_add_node(GtkTreeStore * store, GtkTreeIter * parent,
+ trg_files_tree_node * node)
+{
+ GtkTreeIter child;
+ GList *li;
+
+ if (node->name) {
+ gdouble progress =
+ file_get_progress(node->length, node->bytesCompleted);
+ gtk_tree_store_insert_with_values(store, &child, parent, INT_MAX,
+ FILESCOL_WANTED, node->enabled,
+ FILESCOL_PROGRESS, progress,
+ FILESCOL_SIZE, node->length,
+ FILESCOL_ID, node->index,
+ FILESCOL_PRIORITY,
+ node->priority, FILESCOL_NAME,
+ node->name, -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);
+}
+
+static trg_files_tree_node *trg_file_parser_node_insert(trg_files_tree_node
+ * top,
+ trg_files_tree_node
+ * last,
+ JsonObject * file,
+ gint index,
+ JsonArray *
+ enabled,
+ JsonArray *
+ priorities)
+{
+ gchar **path = g_strsplit(file_get_name(file), "/", -1);
+ trg_files_tree_node *lastIter = last;
+ GList *parentList = NULL;
+ gchar *path_el;
+ GList *li;
+ int i;
+
+ /* Build up a list of pointers to each parent trg_files_tree_node
+ * reversing the order as it iterates over its parent.
+ */
+ if (lastIter)
+ while ((lastIter = lastIter->parent))
+ parentList = g_list_prepend(parentList, lastIter);
+
+ li = parentList;
+ lastIter = top;
+
+ /* Iterate over the path list which contains each file/directory
+ * component of the path in order.
+ */
+ for (i = 0; (path_el = path[i]); i++) {
+ gboolean isFile = !path[i + 1];
+ trg_files_tree_node *target_node = NULL;
+
+ /* No point checking for files. If there is a last parents iterator
+ * check it for a shortcut. I'm assuming that these come in order of
+ * directory at least to give performance a boost.
+ */
+ if (li && !isFile) {
+ trg_files_tree_node *lastPathNode =
+ (trg_files_tree_node *) li->data;
+
+ if (!g_strcmp0(lastPathNode->name, path[i])) {
+ target_node = lastPathNode;
+ li = g_list_next(li);
+ } else {
+ /* No need to check any further. */
+ li = NULL;
+ }
+ }
+
+ if (!target_node && lastIter && lastIter->childrenHash && !isFile)
+ target_node = g_hash_table_lookup(lastIter->childrenHash, path_el);
+
+ /* Node needs creating */
+
+ if (!target_node) {
+ target_node = g_new0(trg_files_tree_node, 1);
+ target_node->name = g_strdup(path[i]);
+ target_node->parent = lastIter;
+ trg_files_tree_node_add_child(lastIter, target_node);
+ }
+
+ lastIter = target_node;
+
+ /* Files have more properties set here than for files.
+ * Directories are updated from here too, by trg_files_tree_update_ancestors
+ * working up the parents.
+ */
+ if (isFile) {
+ target_node->length = file_get_length(file);
+ target_node->bytesCompleted = file_get_bytes_completed(file);
+ target_node->index = index;
+ target_node->enabled =
+ (gint) json_array_get_int_element(enabled, index);
+ target_node->priority =
+ (gint) json_array_get_int_element(priorities, index);
+
+ trg_files_tree_update_ancestors(target_node);
+ } else {
+ target_node->index = -1;
+ }
+ }
+
+ g_list_free(parentList);
+ g_strfreev(path);
+
+ return lastIter;
+}
+
+void trg_files_model_set_accept(TrgFilesModel * model, gboolean accept)
+{
+ TrgFilesModelPrivate *priv = TRG_FILES_MODEL_GET_PRIVATE(model);
+ priv->accept = accept;
+}
+
+static void
+trg_files_model_iter_update(TrgFilesModel * model,
+ GtkTreeIter * filesIter,
+ JsonObject * file,
+ JsonArray * wantedArray,
+ JsonArray * prioritiesArray, gint id)
+{
+ TrgFilesModelPrivate *priv = TRG_FILES_MODEL_GET_PRIVATE(model);
+ gint64 fileLength = file_get_length(file);
+ gint64 fileCompleted = file_get_bytes_completed(file);
+ gint64 lastCompleted;
+
+ gint wanted = (gint) json_array_get_int_element(wantedArray, id);
+ gint priority = (gint) json_array_get_int_element(prioritiesArray, id);
+ gdouble progress = file_get_progress(fileLength, fileCompleted);
+
+ gtk_tree_model_get(GTK_TREE_MODEL(model), filesIter,
+ FILESCOL_BYTESCOMPLETED, &lastCompleted, -1);
+
+ gtk_tree_store_set(GTK_TREE_STORE(model), filesIter, FILESCOL_PROGRESS,
+ progress, FILESCOL_BYTESCOMPLETED, fileCompleted,
+ -1);
+
+ trg_files_update_parent_progress(GTK_TREE_MODEL(model), filesIter,
+ fileCompleted - lastCompleted);
+
+ if (priv->accept)
+ gtk_tree_store_set(GTK_TREE_STORE(model), filesIter,
+ FILESCOL_WANTED, wanted, FILESCOL_PRIORITY,
+ priority, -1);
+}
+
+static void trg_files_model_class_init(TrgFilesModelClass * klass)
+{
+ g_type_class_add_private(klass, sizeof(TrgFilesModelPrivate));
+}
+
+static void trg_files_model_init(TrgFilesModel * self)
+{
+ TrgFilesModelPrivate *priv = TRG_FILES_MODEL_GET_PRIVATE(self);
+ GType column_types[FILESCOL_COLUMNS];
+
+ priv->accept = TRUE;
+
+ column_types[FILESCOL_NAME] = 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_INT;
+ column_types[FILESCOL_PRIORITY] = G_TYPE_INT;
+ column_types[FILESCOL_BYTESCOMPLETED] = G_TYPE_INT64;
+
+ gtk_tree_store_set_column_types(GTK_TREE_STORE(self), FILESCOL_COLUMNS,
+ column_types);
+}
+
+struct MinorUpdateData {
+ GList *filesList;
+ JsonArray *priorities;
+ JsonArray *wanted;
+};
+
+gboolean
+trg_files_model_update_foreach(GtkListStore * model,
+ GtkTreePath * path G_GNUC_UNUSED,
+ GtkTreeIter * iter, gpointer data)
+{
+ struct MinorUpdateData *mud = (struct MinorUpdateData *) data;
+ JsonObject *file;
+ gint id;
+
+ gtk_tree_model_get(GTK_TREE_MODEL(model), iter, FILESCOL_ID, &id, -1);
+
+ if (id >= 0) {
+ file = json_node_get_object(g_list_nth_data(mud->filesList, id));
+ trg_files_model_iter_update(TRG_FILES_MODEL(model), iter,
+ file, mud->wanted, mud->priorities,
+ id);
+ }
+
+ return FALSE;
+}
+
+struct FirstUpdateThreadData {
+ TrgFilesModel *model;
+ GtkTreeView *tree_view;
+ JsonArray *files;
+ JsonArray *priorities;
+ JsonArray *wanted;
+ guint n_items;
+ trg_files_tree_node *top_node;
+ gint64 torrent_id;
+ GList *filesList;
+ gboolean idle_add;
+};
+
+static gboolean trg_files_model_applytree_idlefunc(gpointer data)
+{
+ struct FirstUpdateThreadData *args =
+ (struct FirstUpdateThreadData *) data;
+ TrgFilesModelPrivate *priv = TRG_FILES_MODEL_GET_PRIVATE(args->model);
+
+ if (args->torrent_id == priv->torrentId) {
+ store_add_node(GTK_TREE_STORE(args->model), NULL, args->top_node);
+ gtk_tree_view_expand_all(args->tree_view);
+ priv->n_items = args->n_items;
+ priv->accept = TRUE;
+ }
+
+ trg_files_tree_node_free(args->top_node);
+ g_free(data);
+
+ return FALSE;
+}
+
+static gpointer trg_files_model_buildtree_threadfunc(gpointer data)
+{
+ struct FirstUpdateThreadData *args =
+ (struct FirstUpdateThreadData *) data;
+ trg_files_tree_node *lastNode = NULL;
+ GList *li;
+
+ args->top_node = g_new0(trg_files_tree_node, 1);
+
+ for (li = args->filesList; li; li = g_list_next(li)) {
+ JsonObject *file = json_node_get_object((JsonNode *) li->data);
+
+ lastNode =
+ trg_file_parser_node_insert(args->top_node, lastNode,
+ file, args->n_items++,
+ args->wanted, args->priorities);
+ }
+
+ g_list_free(args->filesList);
+ json_array_unref(args->files);
+
+ if (args->idle_add)
+ g_idle_add(trg_files_model_applytree_idlefunc, data);
+
+ return NULL;
+}
+
+void
+trg_files_model_update(TrgFilesModel * model, GtkTreeView * tv,
+ gint64 updateSerial, JsonObject * t, gint mode)
+{
+ TrgFilesModelPrivate *priv = TRG_FILES_MODEL_GET_PRIVATE(model);
+ JsonArray *files = torrent_get_files(t);
+ GList *filesList = json_array_get_elements(files);
+ guint filesListLength = g_list_length(filesList);
+ JsonArray *priorities = torrent_get_priorities(t);
+ JsonArray *wanted = torrent_get_wanted(t);
+ priv->torrentId = torrent_get_id(t);
+
+ /* It's quicker to build this up with simple data structures before
+ * putting it into GTK models.
+ */
+ if (mode == TORRENT_GET_MODE_FIRST || priv->n_items != filesListLength) {
+ struct FirstUpdateThreadData *futd =
+ g_new0(struct FirstUpdateThreadData, 1);
+
+ gtk_tree_store_clear(GTK_TREE_STORE(model));
+ json_array_ref(files);
+
+ futd->tree_view = tv;
+ futd->files = files;
+ futd->priorities = priorities;
+ futd->wanted = wanted;
+ futd->filesList = filesList;
+ futd->torrent_id = priv->torrentId;
+ futd->model = model;
+ futd->idle_add =
+ filesListLength > TRG_FILES_MODEL_CREATE_THREAD_IF_GT;
+
+ /* If this update has more than a given number of files, build up the
+ * simple tree in a thread, then g_idle_add a function which
+ * adds the contents of this prebuilt tree.
+ *
+ * If less than or equal to, I don't think it's worth spawning threads
+ * for. Just do it in the main loop.
+ */
+ if (futd->idle_add) {
+ g_thread_create(trg_files_model_buildtree_threadfunc, futd,
+ FALSE, NULL);
+ } else {
+ trg_files_model_buildtree_threadfunc(futd);
+ trg_files_model_applytree_idlefunc(futd);
+ }
+ } else {
+ struct MinorUpdateData mud;
+ mud.priorities = priorities;
+ mud.wanted = wanted;
+ mud.filesList = filesList;
+ gtk_tree_model_foreach(GTK_TREE_MODEL(model),
+ (GtkTreeModelForeachFunc)
+ trg_files_model_update_foreach, &mud);
+ g_list_free(filesList);
+ }
+}
+
+gint64 trg_files_model_get_torrent_id(TrgFilesModel * model)
+{
+ TrgFilesModelPrivate *priv = TRG_FILES_MODEL_GET_PRIVATE(model);
+ return priv->torrentId;
+}
+
+TrgFilesModel *trg_files_model_new(void)
+{
+ return g_object_new(TRG_TYPE_FILES_MODEL, NULL);
+}