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-files-model.c | 449 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 449 insertions(+) create mode 100644 src/trg-files-model.c (limited to 'src/trg-files-model.c') 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 +#include +#include +#include + +#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); +} -- cgit v1.2.3