diff options
Diffstat (limited to 'src/trg-torrent-model.c')
-rw-r--r-- | src/trg-torrent-model.c | 851 |
1 files changed, 851 insertions, 0 deletions
diff --git a/src/trg-torrent-model.c b/src/trg-torrent-model.c new file mode 100644 index 0000000..b1e98e5 --- /dev/null +++ b/src/trg-torrent-model.c @@ -0,0 +1,851 @@ +/* + * 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 <gtk/gtk.h> +#include <json-glib/json-glib.h> +#include <glib/gi18n.h> + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "torrent.h" +#include "json.h" +#include "trg-torrent-model.h" +#include "protocol-constants.h" +#include "trg-model.h" +#include "util.h" + +/* An extension of TrgModel (which is an extension of GtkListStore) which + * updates from a JSON torrent-get response. It handles a number of different + * update modes. + * 1) The first update. + * 2) A full update. + * 3) An active-only update. + * 4) Individual torrent updates. + * + * Other stuff it does. + * 1) Populates a stats struct with speeds/state counts as it works through the + * response. + * 2) Emits signals if something is added or removed. This is used by the state + * selector so it doesn't have to refresh itself on every update. + * 3) Added or completed signals, for libnotify notifications. + * 4) Maintains the torrent hash table (by ID). + * (and provide a lookup function which outputs an iter and/or JSON object.) + * 5) If the download directory is new/changed, create a short version if there + * is one (duplicate it not) for the state selector to filter/populate against. + * 6) Shorten the tracker announce URL. + */ + +enum { + TMODEL_TORRENT_COMPLETED, + TMODEL_UPDATE, + TMODEL_TORRENT_ADDED, + TMODEL_STATE_CHANGED, + TMODEL_SIGNAL_COUNT +}; + +#define PROP_REMOVE_IN_PROGRESS "remove-in-progress" + +static guint signals[TMODEL_SIGNAL_COUNT] = { 0 }; + +G_DEFINE_TYPE(TrgTorrentModel, trg_torrent_model, GTK_TYPE_LIST_STORE) +#define TRG_TORRENT_MODEL_GET_PRIVATE(o) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((o), TRG_TYPE_TORRENT_MODEL, TrgTorrentModelPrivate)) +typedef struct _TrgTorrentModelPrivate TrgTorrentModelPrivate; + +struct _TrgTorrentModelPrivate { + GHashTable *ht; + GRegex *urlHostRegex; + trg_torrent_model_update_stats stats; +}; + +static void trg_torrent_model_dispose(GObject * object) +{ + TrgTorrentModelPrivate *priv = TRG_TORRENT_MODEL_GET_PRIVATE(object); + g_hash_table_destroy(priv->ht); + G_OBJECT_CLASS(trg_torrent_model_parent_class)->dispose(object); +} + +static void +update_torrent_iter(TrgTorrentModel * model, TrgClient * tc, gint64 rpcv, + gint64 serial, GtkTreeIter * iter, JsonObject * t, + trg_torrent_model_update_stats * stats, + guint * whatsChanged); + +static void trg_torrent_model_class_init(TrgTorrentModelClass * klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS(klass); + + g_type_class_add_private(klass, sizeof(TrgTorrentModelPrivate)); + object_class->dispose = trg_torrent_model_dispose; + + signals[TMODEL_TORRENT_COMPLETED] = g_signal_new("torrent-completed", + G_TYPE_FROM_CLASS + (object_class), + G_SIGNAL_RUN_LAST | + G_SIGNAL_ACTION, + G_STRUCT_OFFSET + (TrgTorrentModelClass, + torrent_completed), + NULL, NULL, + g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, 1, + G_TYPE_POINTER); + + signals[TMODEL_UPDATE] = g_signal_new("update", + G_TYPE_FROM_CLASS + (object_class), + G_SIGNAL_RUN_LAST | + G_SIGNAL_ACTION, + G_STRUCT_OFFSET + (TrgTorrentModelClass, + update), + NULL, NULL, + g_cclosure_marshal_VOID__VOID, + G_TYPE_NONE, 0); + + signals[TMODEL_TORRENT_ADDED] = g_signal_new("torrent-added", + G_TYPE_FROM_CLASS + (object_class), + G_SIGNAL_RUN_LAST | + G_SIGNAL_ACTION, + G_STRUCT_OFFSET + (TrgTorrentModelClass, + torrent_added), NULL, + NULL, + g_cclosure_marshal_VOID__POINTER, + G_TYPE_NONE, 1, + G_TYPE_POINTER); + + signals[TMODEL_STATE_CHANGED] = g_signal_new("torrents-state-change", + G_TYPE_FROM_CLASS + (object_class), + G_SIGNAL_RUN_LAST | + G_SIGNAL_ACTION, + G_STRUCT_OFFSET + (TrgTorrentModelClass, + torrent_removed), NULL, + NULL, + g_cclosure_marshal_VOID__UINT, + G_TYPE_NONE, 1, + G_TYPE_UINT); +} + +trg_torrent_model_update_stats *trg_torrent_model_get_stats(TrgTorrentModel + * model) +{ + TrgTorrentModelPrivate *priv = TRG_TORRENT_MODEL_GET_PRIVATE(model); + return &(priv->stats); +} + +static void +trg_torrent_model_count_peers(TrgTorrentModel * model, + GtkTreeIter * iter, JsonObject * t) +{ + GList *trackersList = + json_array_get_elements(torrent_get_tracker_stats(t)); + gint64 seeders = 0; + gint64 leechers = 0; + gint64 downloads = 0; + GList *li; + + for (li = trackersList; li; li = g_list_next(li)) { + JsonObject *tracker = json_node_get_object((JsonNode *) li->data); + + seeders += tracker_stats_get_seeder_count(tracker); + leechers += tracker_stats_get_leecher_count(tracker); + downloads += tracker_stats_get_download_count(tracker); + } + + g_list_free(trackersList); + + gtk_list_store_set(GTK_LIST_STORE(model), iter, TORRENT_COLUMN_SEEDS, + seeders, TORRENT_COLUMN_LEECHERS, leechers, + TORRENT_COLUMN_DOWNLOADS, downloads, -1); +} + +static void trg_torrent_model_ref_free(gpointer data) +{ + GtkTreeRowReference *rr = (GtkTreeRowReference *) data; + GtkTreeModel *model = gtk_tree_row_reference_get_model(rr); + GtkTreePath *path = gtk_tree_row_reference_get_path(rr); + if (path) { + GtkTreeIter iter; + JsonObject *json; + if (gtk_tree_model_get_iter(model, &iter, path)) { + gtk_tree_model_get(model, &iter, TORRENT_COLUMN_JSON, &json, + -1); + json_object_unref(json); + g_object_set_data(G_OBJECT(model), PROP_REMOVE_IN_PROGRESS, + GINT_TO_POINTER(TRUE)); + gtk_list_store_remove(GTK_LIST_STORE(model), &iter); + g_object_set_data(G_OBJECT(model), PROP_REMOVE_IN_PROGRESS, + GINT_TO_POINTER(FALSE)); + } + + gtk_tree_path_free(path); + } + + gtk_tree_row_reference_free(rr); +} + +static void trg_torrent_model_init(TrgTorrentModel * self) +{ + TrgTorrentModelPrivate *priv = TRG_TORRENT_MODEL_GET_PRIVATE(self); + + GType column_types[TORRENT_COLUMN_COLUMNS]; + + column_types[TORRENT_COLUMN_ICON] = G_TYPE_STRING; + column_types[TORRENT_COLUMN_NAME] = G_TYPE_STRING; + column_types[TORRENT_COLUMN_ERROR] = G_TYPE_INT64; + column_types[TORRENT_COLUMN_SIZEWHENDONE] = G_TYPE_INT64; + column_types[TORRENT_COLUMN_TOTALSIZE] = G_TYPE_INT64; + column_types[TORRENT_COLUMN_HAVE_UNCHECKED] = G_TYPE_INT64; + column_types[TORRENT_COLUMN_PERCENTDONE] = G_TYPE_DOUBLE; + column_types[TORRENT_COLUMN_METADATAPERCENTCOMPLETE] = G_TYPE_DOUBLE; + column_types[TORRENT_COLUMN_STATUS] = G_TYPE_STRING; + column_types[TORRENT_COLUMN_SEEDS] = G_TYPE_INT64; + column_types[TORRENT_COLUMN_LEECHERS] = G_TYPE_INT64; + column_types[TORRENT_COLUMN_DOWNLOADS] = G_TYPE_INT64; + column_types[TORRENT_COLUMN_DOWNSPEED] = G_TYPE_INT64; + column_types[TORRENT_COLUMN_ADDED] = G_TYPE_INT64; + column_types[TORRENT_COLUMN_UPSPEED] = G_TYPE_INT64; + column_types[TORRENT_COLUMN_ETA] = G_TYPE_INT64; + column_types[TORRENT_COLUMN_UPLOADED] = G_TYPE_INT64; + column_types[TORRENT_COLUMN_DOWNLOADED] = G_TYPE_INT64; + column_types[TORRENT_COLUMN_HAVE_VALID] = G_TYPE_INT64; + column_types[TORRENT_COLUMN_RATIO] = G_TYPE_DOUBLE; + column_types[TORRENT_COLUMN_ID] = G_TYPE_INT64; + column_types[TORRENT_COLUMN_JSON] = G_TYPE_POINTER; + column_types[TORRENT_COLUMN_UPDATESERIAL] = G_TYPE_INT64; + column_types[TORRENT_COLUMN_FLAGS] = G_TYPE_INT; + column_types[TORRENT_COLUMN_DOWNLOADDIR] = G_TYPE_STRING; + column_types[TORRENT_COLUMN_DOWNLOADDIR_SHORT] = G_TYPE_STRING; + column_types[TORRENT_COLUMN_BANDWIDTH_PRIORITY] = G_TYPE_INT64; + column_types[TORRENT_COLUMN_DONE_DATE] = G_TYPE_INT64; + column_types[TORRENT_COLUMN_FROMPEX] = G_TYPE_INT64; + column_types[TORRENT_COLUMN_FROMDHT] = G_TYPE_INT64; + column_types[TORRENT_COLUMN_FROMTRACKERS] = G_TYPE_INT64; + column_types[TORRENT_COLUMN_FROMLTEP] = G_TYPE_INT64; + column_types[TORRENT_COLUMN_FROMRESUME] = G_TYPE_INT64; + column_types[TORRENT_COLUMN_FROMINCOMING] = G_TYPE_INT64; + column_types[TORRENT_COLUMN_PEER_SOURCES] = G_TYPE_STRING; + column_types[TORRENT_COLUMN_SEED_RATIO_LIMIT] = G_TYPE_DOUBLE; + column_types[TORRENT_COLUMN_SEED_RATIO_MODE] = G_TYPE_INT64; + column_types[TORRENT_COLUMN_PEERS_CONNECTED] = G_TYPE_INT64; + column_types[TORRENT_COLUMN_PEERS_FROM_US] = G_TYPE_INT64; + column_types[TORRENT_COLUMN_WEB_SEEDS_TO_US] = G_TYPE_INT64; + column_types[TORRENT_COLUMN_PEERS_TO_US] = G_TYPE_INT64; + column_types[TORRENT_COLUMN_TRACKERHOST] = G_TYPE_STRING; + column_types[TORRENT_COLUMN_QUEUE_POSITION] = G_TYPE_INT64; + column_types[TORRENT_COLUMN_LASTACTIVE] = G_TYPE_INT64; + column_types[TORRENT_COLUMN_FILECOUNT] = G_TYPE_UINT; + + gtk_list_store_set_column_types(GTK_LIST_STORE(self), + TORRENT_COLUMN_COLUMNS, column_types); + + priv->ht = g_hash_table_new_full(g_int64_hash, g_int64_equal, + (GDestroyNotify) g_free, + trg_torrent_model_ref_free); + + g_object_set_data(G_OBJECT(self), PROP_REMOVE_IN_PROGRESS, + GINT_TO_POINTER(FALSE)); + + priv->urlHostRegex = trg_uri_host_regex_new(); +} + +gboolean trg_torrent_model_is_remove_in_progress(TrgTorrentModel * model) +{ + return (gboolean) GPOINTER_TO_INT(g_object_get_data + (G_OBJECT(model), + PROP_REMOVE_IN_PROGRESS)); +} + +static gboolean +trg_torrent_model_reload_dir_aliases_foreachfunc(GtkTreeModel * model, + GtkTreePath * + path G_GNUC_UNUSED, + GtkTreeIter * iter, + gpointer gdata) +{ + gchar *downloadDir, *shortDownloadDir; + + gtk_tree_model_get(model, iter, TORRENT_COLUMN_DOWNLOADDIR, + &downloadDir, -1); + + shortDownloadDir = + shorten_download_dir((TrgClient *) gdata, downloadDir); + + gtk_list_store_set(GTK_LIST_STORE(model), iter, + TORRENT_COLUMN_DOWNLOADDIR_SHORT, shortDownloadDir, + -1); + + g_free(downloadDir); + g_free(shortDownloadDir); + + return FALSE; +} + +void +trg_torrent_model_reload_dir_aliases(TrgClient * tc, GtkTreeModel * model) +{ + gtk_tree_model_foreach(model, + trg_torrent_model_reload_dir_aliases_foreachfunc, + tc); + g_signal_emit(model, signals[TMODEL_STATE_CHANGED], 0, + TORRENT_UPDATE_PATH_CHANGE); +} + +static gboolean +trg_torrent_model_stats_scan_foreachfunc(GtkTreeModel * + model, + GtkTreePath * + path + G_GNUC_UNUSED, + GtkTreeIter * iter, + gpointer gdata) +{ + trg_torrent_model_update_stats *stats = + (trg_torrent_model_update_stats *) gdata; + guint flags; + + gtk_tree_model_get(model, iter, TORRENT_COLUMN_FLAGS, &flags, -1); + + if (flags & TORRENT_FLAG_SEEDING) + stats->seeding++; + else if (flags & TORRENT_FLAG_DOWNLOADING) + stats->down++; + else if (flags & TORRENT_FLAG_PAUSED) + stats->paused++; + + if (flags & TORRENT_FLAG_ERROR) + stats->error++; + + if (flags & TORRENT_FLAG_COMPLETE) + stats->complete++; + else + stats->incomplete++; + + if (flags & TORRENT_FLAG_CHECKING) + stats->checking++; + + if (flags & TORRENT_FLAG_ACTIVE) + stats->active++; + + if (flags & TORRENT_FLAG_SEEDING_WAIT) + stats->seed_wait++; + + if (flags & TORRENT_FLAG_DOWNLOADING_WAIT) + stats->down_wait++; + + stats->count++; + + return FALSE; +} + +void trg_torrent_model_remove_all(TrgTorrentModel * model) +{ + TrgTorrentModelPrivate *priv = TRG_TORRENT_MODEL_GET_PRIVATE(model); + g_hash_table_remove_all(priv->ht); + gtk_list_store_clear(GTK_LIST_STORE(model)); +} + +gchar *shorten_download_dir(TrgClient * tc, const gchar * downloadDir) +{ + TrgPrefs *prefs = trg_client_get_prefs(tc); + JsonArray *labels = + trg_prefs_get_array(prefs, TRG_PREFS_KEY_DESTINATIONS, + TRG_PREFS_CONNECTION); + JsonObject *session = trg_client_get_session(tc); + const gchar *defaultDownloadDir = session_get_download_dir(session); + gchar *shortDownloadDir = NULL; + + if (labels) { + GList *labelsList = json_array_get_elements(labels); + if (labelsList) { + GList *li; + for (li = labelsList; li; li = g_list_next(li)) { + JsonObject *labelObj = json_node_get_object((JsonNode *) + li->data); + const gchar *labelDir = + json_object_get_string_member(labelObj, + TRG_PREFS_KEY_DESTINATIONS_SUBKEY_DIR); + if (!g_strcmp0(downloadDir, labelDir)) { + const gchar *labelLabel = + json_object_get_string_member(labelObj, + TRG_PREFS_SUBKEY_LABEL); + shortDownloadDir = g_strdup(labelLabel); + break; + } + } + g_list_free(labelsList); + } + } + + if (shortDownloadDir) { + return shortDownloadDir; + } else { + if (!g_strcmp0(defaultDownloadDir, downloadDir)) + return g_strdup(_("Default")); + + if (g_str_has_prefix(downloadDir, defaultDownloadDir)) { + int offset = strlen(defaultDownloadDir); + if (*(downloadDir + offset) == '/') + offset++; + + if (offset < strlen(downloadDir)) + return g_strdup(downloadDir + offset); + } + } + + return g_strdup(downloadDir); +} + +static inline void +update_torrent_iter(TrgTorrentModel * model, + TrgClient * tc, gint64 rpcv, + gint64 serial, GtkTreeIter * iter, + JsonObject * t, + trg_torrent_model_update_stats * + stats, guint * whatsChanged) +{ + TrgTorrentModelPrivate *priv = TRG_TORRENT_MODEL_GET_PRIVATE(model); + GtkListStore *ls = GTK_LIST_STORE(model); + guint lastFlags, newFlags; + JsonObject *lastJson, *pf; + JsonArray *trackerStats; + gchar *statusString, *statusIcon, *downloadDir; + gint64 downRate, upRate, haveValid, uploaded, downloaded, id, status, + lpd; + guint fileCount; + gchar *firstTrackerHost = NULL; + gchar *peerSources = NULL; + gchar *lastDownloadDir = NULL; + + downRate = torrent_get_rate_down(t); + stats->downRateTotal += downRate; + + upRate = torrent_get_rate_up(t); + stats->upRateTotal += upRate; + + uploaded = torrent_get_uploaded(t); + downloaded = torrent_get_downloaded(t); + haveValid = torrent_get_have_valid(t); + + downloadDir = (gchar *) torrent_get_download_dir(t); + rm_trailing_slashes(downloadDir); + + id = torrent_get_id(t); + status = torrent_get_status(t); + fileCount = json_array_get_length(torrent_get_files(t)); + newFlags = + torrent_get_flags(t, rpcv, status, fileCount, downRate, upRate); + statusString = torrent_get_status_string(rpcv, status, newFlags); + statusIcon = torrent_get_status_icon(rpcv, newFlags); + pf = torrent_get_peersfrom(t); + trackerStats = torrent_get_tracker_stats(t); + + gtk_tree_model_get(GTK_TREE_MODEL(model), iter, TORRENT_COLUMN_FLAGS, + &lastFlags, TORRENT_COLUMN_JSON, &lastJson, + TORRENT_COLUMN_DOWNLOADDIR, &lastDownloadDir, -1); + + json_object_ref(t); + + if (json_array_get_length(trackerStats) > 0) { + JsonObject *firstTracker = + json_array_get_object_element(trackerStats, + 0); + firstTrackerHost = trg_gregex_get_first(priv->urlHostRegex, + tracker_stats_get_host + (firstTracker)); + } + + lpd = peerfrom_get_lpd(pf); + if (newFlags & TORRENT_FLAG_ACTIVE) { + if (lpd >= 0) { + peerSources = + g_strdup_printf("%" G_GINT64_FORMAT " / %" G_GINT64_FORMAT + " / %" G_GINT64_FORMAT " / %" + G_GINT64_FORMAT " / %" G_GINT64_FORMAT + " / %" G_GINT64_FORMAT " / %" + G_GINT64_FORMAT, peerfrom_get_trackers(pf), + peerfrom_get_incoming(pf), + peerfrom_get_ltep(pf), + peerfrom_get_dht(pf), peerfrom_get_pex(pf), + lpd, peerfrom_get_resume(pf)); + } else { + peerSources = + g_strdup_printf("%" G_GINT64_FORMAT " / %" G_GINT64_FORMAT + " / %" G_GINT64_FORMAT " / %" + G_GINT64_FORMAT " / %" G_GINT64_FORMAT + " / N/A / %" G_GINT64_FORMAT, + peerfrom_get_trackers(pf), + peerfrom_get_incoming(pf), + peerfrom_get_ltep(pf), + peerfrom_get_dht(pf), peerfrom_get_pex(pf), + peerfrom_get_resume(pf)); + } + } +#ifdef DEBUG + gtk_list_store_set(ls, iter, TORRENT_COLUMN_ICON, statusIcon, -1); + gtk_list_store_set(ls, iter, + TORRENT_COLUMN_NAME, torrent_get_name(t), -1); + gtk_list_store_set(ls, iter, + TORRENT_COLUMN_SIZEWHENDONE, + torrent_get_size_when_done(t), -1); + gtk_list_store_set(ls, iter, TORRENT_COLUMN_PERCENTDONE, + (newFlags & TORRENT_FLAG_CHECKING) ? + torrent_get_recheck_progress(t) + : torrent_get_percent_done(t), -1); + gtk_list_store_set(ls, iter, TORRENT_COLUMN_STATUS, statusString, -1); + gtk_list_store_set(ls, iter, TORRENT_COLUMN_DOWNSPEED, downRate, -1); + gtk_list_store_set(ls, iter, TORRENT_COLUMN_FLAGS, newFlags, -1); + gtk_list_store_set(ls, iter, TORRENT_COLUMN_UPSPEED, upRate, -1); + gtk_list_store_set(ls, iter, TORRENT_COLUMN_ETA, torrent_get_eta(t), + -1); + gtk_list_store_set(ls, iter, TORRENT_COLUMN_UPLOADED, uploaded, -1); + gtk_list_store_set(ls, iter, TORRENT_COLUMN_DOWNLOADED, downloaded, + -1); + gtk_list_store_set(ls, iter, TORRENT_COLUMN_RATIO, uploaded > 0 + && downloaded > + 0 ? (double) uploaded / (double) downloaded : 0, + -1); + gtk_list_store_set(ls, iter, TORRENT_COLUMN_ID, id, -1); + gtk_list_store_set(ls, iter, TORRENT_COLUMN_JSON, t, -1); + gtk_list_store_set(ls, iter, TORRENT_COLUMN_UPDATESERIAL, serial, -1); + gtk_list_store_set(ls, iter, + TORRENT_COLUMN_ADDED, torrent_get_added_date(t), + -1); + gtk_list_store_set(ls, iter, TORRENT_COLUMN_DOWNLOADDIR, downloadDir, + -1); + gtk_list_store_set(ls, iter, TORRENT_COLUMN_BANDWIDTH_PRIORITY, + torrent_get_bandwidth_priority(t), -1); + gtk_list_store_set(ls, iter, TORRENT_COLUMN_TOTALSIZE, + torrent_get_total_size(t), -1); + gtk_list_store_set(ls, iter, TORRENT_COLUMN_HAVE_UNCHECKED, + torrent_get_have_unchecked(t), -1); + gtk_list_store_set(ls, iter, TORRENT_COLUMN_METADATAPERCENTCOMPLETE, + torrent_get_metadata_percent_complete(t), -1); + gtk_list_store_set(ls, iter, TORRENT_COLUMN_PEERS_TO_US, + torrent_get_peers_sending_to_us(t), -1); + gtk_list_store_set(ls, iter, TORRENT_COLUMN_PEERS_FROM_US, + torrent_get_peers_getting_from_us(t), -1); + gtk_list_store_set(ls, iter, TORRENT_COLUMN_WEB_SEEDS_TO_US, + torrent_get_web_seeds_sending_to_us(t), -1); + gtk_list_store_set(ls, iter, TORRENT_COLUMN_SEED_RATIO_LIMIT, + torrent_get_seed_ratio_limit(t), -1); + gtk_list_store_set(ls, iter, TORRENT_COLUMN_SEED_RATIO_MODE, + torrent_get_seed_ratio_mode(t), -1); + gtk_list_store_set(ls, iter, TORRENT_COLUMN_FILECOUNT, fileCount, -1); + gtk_list_store_set(ls, iter, TORRENT_COLUMN_HAVE_VALID, haveValid, -1); +#else + gtk_list_store_set(ls, iter, TORRENT_COLUMN_ICON, statusIcon, + TORRENT_COLUMN_ADDED, torrent_get_added_date(t), + TORRENT_COLUMN_FILECOUNT, + fileCount, + TORRENT_COLUMN_DONE_DATE, torrent_get_done_date(t), + TORRENT_COLUMN_NAME, torrent_get_name(t), + TORRENT_COLUMN_ERROR, torrent_get_error(t), + TORRENT_COLUMN_SIZEWHENDONE, + torrent_get_size_when_done(t), + TORRENT_COLUMN_PERCENTDONE, + (newFlags & TORRENT_FLAG_CHECKING) ? + torrent_get_recheck_progress(t) + : torrent_get_percent_done(t), + TORRENT_COLUMN_METADATAPERCENTCOMPLETE, + torrent_get_metadata_percent_complete(t), + TORRENT_COLUMN_STATUS, statusString, + TORRENT_COLUMN_DOWNSPEED, downRate, + TORRENT_COLUMN_FLAGS, newFlags, + TORRENT_COLUMN_UPSPEED, upRate, TORRENT_COLUMN_ETA, + torrent_get_eta(t), TORRENT_COLUMN_UPLOADED, + uploaded, TORRENT_COLUMN_DOWNLOADED, downloaded, + TORRENT_COLUMN_TOTALSIZE, torrent_get_total_size(t), + TORRENT_COLUMN_HAVE_UNCHECKED, + torrent_get_have_unchecked(t), + TORRENT_COLUMN_HAVE_VALID, haveValid, + TORRENT_COLUMN_FROMPEX, peerfrom_get_pex(pf), + TORRENT_COLUMN_FROMDHT, peerfrom_get_dht(pf), + TORRENT_COLUMN_FROMTRACKERS, + peerfrom_get_trackers(pf), TORRENT_COLUMN_FROMLTEP, + peerfrom_get_ltep(pf), TORRENT_COLUMN_FROMRESUME, + peerfrom_get_resume(pf), + TORRENT_COLUMN_FROMINCOMING, + peerfrom_get_incoming(pf), + TORRENT_COLUMN_PEER_SOURCES, peerSources, + TORRENT_COLUMN_PEERS_CONNECTED, + torrent_get_peers_connected(t), + TORRENT_COLUMN_PEERS_TO_US, + torrent_get_peers_sending_to_us(t), + TORRENT_COLUMN_PEERS_FROM_US, + torrent_get_peers_getting_from_us(t), + TORRENT_COLUMN_WEB_SEEDS_TO_US, + torrent_get_web_seeds_sending_to_us(t), + TORRENT_COLUMN_QUEUE_POSITION, + torrent_get_queue_position(t), + TORRENT_COLUMN_SEED_RATIO_LIMIT, + torrent_get_seed_ratio_limit(t), + TORRENT_COLUMN_SEED_RATIO_MODE, + torrent_get_seed_ratio_mode(t), + TORRENT_COLUMN_LASTACTIVE, + torrent_get_activity_date(t), TORRENT_COLUMN_RATIO, + uploaded > 0 + && haveValid > + 0 ? (double) uploaded / (double) haveValid : 0, + TORRENT_COLUMN_DOWNLOADDIR, downloadDir, + TORRENT_COLUMN_BANDWIDTH_PRIORITY, + torrent_get_bandwidth_priority(t), + TORRENT_COLUMN_ID, id, TORRENT_COLUMN_JSON, t, + TORRENT_COLUMN_TRACKERHOST, + firstTrackerHost ? firstTrackerHost : "", + TORRENT_COLUMN_UPDATESERIAL, serial, -1); +#endif + + if (!lastDownloadDir || g_strcmp0(downloadDir, lastDownloadDir)) { + gchar *shortDownloadDir = shorten_download_dir(tc, downloadDir); + gtk_list_store_set(ls, iter, TORRENT_COLUMN_DOWNLOADDIR_SHORT, + shortDownloadDir, -1); + g_free(shortDownloadDir); + *whatsChanged |= TORRENT_UPDATE_PATH_CHANGE; + } + + if (lastJson) + json_object_unref(lastJson); + + if ((lastFlags & TORRENT_FLAG_DOWNLOADING) + && (!(newFlags & TORRENT_FLAG_DOWNLOADING)) + && (newFlags & TORRENT_FLAG_COMPLETE)) + g_signal_emit(model, signals[TMODEL_TORRENT_COMPLETED], 0, iter); + + if (lastFlags != newFlags) + *whatsChanged |= TORRENT_UPDATE_STATE_CHANGE; + + trg_torrent_model_count_peers(model, iter, t); + + if (firstTrackerHost) + g_free(firstTrackerHost); + + if (peerSources) + g_free(peerSources); + + g_free(lastDownloadDir); + g_free(statusString); + g_free(statusIcon); +} + +TrgTorrentModel *trg_torrent_model_new(void) +{ + return g_object_new(TRG_TYPE_TORRENT_MODEL, NULL); +} + +struct TrgModelRemoveData { + GList *toRemove; + gint64 currentSerial; +}; + +GHashTable *get_torrent_table(TrgTorrentModel * model) +{ + TrgTorrentModelPrivate *priv = TRG_TORRENT_MODEL_GET_PRIVATE(model); + return priv->ht; +} + +gboolean +trg_model_find_removed_foreachfunc(GtkTreeModel * model, + GtkTreePath * + path G_GNUC_UNUSED, + GtkTreeIter * iter, gpointer gdata) +{ + struct TrgModelRemoveData *args = (struct TrgModelRemoveData *) gdata; + gint64 rowSerial; + + gtk_tree_model_get(model, iter, TORRENT_COLUMN_UPDATESERIAL, + &rowSerial, -1); + + if (rowSerial != args->currentSerial) { + gint64 *id = g_new(gint64, 1); + gtk_tree_model_get(model, iter, TORRENT_COLUMN_ID, id, -1); + args->toRemove = g_list_append(args->toRemove, id); + } + + return FALSE; +} + +GList *trg_torrent_model_find_removed(GtkTreeModel * model, + gint64 currentSerial) +{ + struct TrgModelRemoveData args; + args.toRemove = NULL; + args.currentSerial = currentSerial; + + gtk_tree_model_foreach(GTK_TREE_MODEL(model), + trg_model_find_removed_foreachfunc, &args); + + return args.toRemove; +} + +gboolean +get_torrent_data(GHashTable * table, gint64 id, JsonObject ** t, + GtkTreeIter * out_iter) +{ + gpointer result = g_hash_table_lookup(table, &id); + gboolean found = FALSE; + + if (result) { + GtkTreeRowReference *rr = (GtkTreeRowReference *) result; + GtkTreePath *path = gtk_tree_row_reference_get_path(rr); + GtkTreeIter iter; + if (path) { + GtkTreeModel *model = gtk_tree_row_reference_get_model(rr); + gtk_tree_model_get_iter(model, &iter, path); + if (out_iter) + *out_iter = iter; + if (t) + gtk_tree_model_get(model, &iter, TORRENT_COLUMN_JSON, t, + -1); + found = TRUE; + gtk_tree_path_free(path); + } + } + + return found; +} + +static void +trg_torrent_model_stat_counts_clear(trg_torrent_model_update_stats * stats) +{ + stats->count = stats->down = stats->error = stats->paused = + stats->seeding = stats->complete = stats->incomplete = + stats->active = stats->checking = stats->seed_wait = + stats->down_wait = 0; +} + +trg_torrent_model_update_stats *trg_torrent_model_update(TrgTorrentModel * + model, + TrgClient * tc, + JsonObject * + response, + gint mode) +{ + TrgTorrentModelPrivate *priv = TRG_TORRENT_MODEL_GET_PRIVATE(model); + + GList *torrentList; + JsonObject *args, *t; + GList *li; + gint64 id; + gint64 serial = trg_client_get_serial(tc); + JsonArray *removedTorrents; + GtkTreeIter iter; + GtkTreePath *path; + GtkTreeRowReference *rr; + gpointer *result; + guint whatsChanged = 0; + + gint64 rpcv = trg_client_get_rpc_version(tc); + + args = get_arguments(response); + torrentList = json_array_get_elements(get_torrents(args)); + + priv->stats.downRateTotal = 0; + priv->stats.upRateTotal = 0; + + for (li = torrentList; li; li = g_list_next(li)) { + t = json_node_get_object((JsonNode *) li->data); + id = torrent_get_id(t); + + result = + mode == TORRENT_GET_MODE_FIRST ? NULL : + g_hash_table_lookup(priv->ht, &id); + + if (!result) { + gint64 *idCopy; + gtk_list_store_append(GTK_LIST_STORE(model), &iter); + whatsChanged |= TORRENT_UPDATE_ADDREMOVE; + + update_torrent_iter(model, tc, rpcv, serial, + &iter, t, &(priv->stats), &whatsChanged); + + path = gtk_tree_model_get_path(GTK_TREE_MODEL(model), &iter); + rr = gtk_tree_row_reference_new(GTK_TREE_MODEL(model), path); + idCopy = g_new(gint64, 1); + *idCopy = id; + g_hash_table_insert(priv->ht, idCopy, rr); + gtk_tree_path_free(path); + + if (mode != TORRENT_GET_MODE_FIRST) + g_signal_emit(model, signals[TMODEL_TORRENT_ADDED], 0, + &iter); + } else { + path = gtk_tree_row_reference_get_path((GtkTreeRowReference *) + result); + if (path) { + if (gtk_tree_model_get_iter(GTK_TREE_MODEL(model), &iter, + path)) { + update_torrent_iter(model, tc, rpcv, + serial, &iter, + t, &(priv->stats), &whatsChanged); + } + gtk_tree_path_free(path); + } + } + } + + g_list_free(torrentList); + + if (mode == TORRENT_GET_MODE_UPDATE) { + GList *hitlist = + trg_torrent_model_find_removed(GTK_TREE_MODEL(model), serial); + if (hitlist) { + for (li = hitlist; li; li = g_list_next(li)) { + g_hash_table_remove(priv->ht, li->data); + g_free(li->data); + } + whatsChanged |= TORRENT_UPDATE_ADDREMOVE; + g_list_free(hitlist); + } + } else if (mode > TORRENT_GET_MODE_FIRST) { + removedTorrents = get_torrents_removed(args); + if (removedTorrents) { + GList *hitlist = json_array_get_elements(removedTorrents); + for (li = hitlist; li; li = g_list_next(li)) { + id = json_node_get_int((JsonNode *) li->data); + g_hash_table_remove(priv->ht, &id); + whatsChanged |= TORRENT_UPDATE_ADDREMOVE; + } + g_list_free(hitlist); + } + } + + if (whatsChanged != 0) { + if ((whatsChanged & TORRENT_UPDATE_ADDREMOVE) + || (whatsChanged & TORRENT_UPDATE_STATE_CHANGE)) { + trg_torrent_model_stat_counts_clear(&priv->stats); + gtk_tree_model_foreach(GTK_TREE_MODEL(model), + trg_torrent_model_stats_scan_foreachfunc, + &(priv->stats)); + } + g_signal_emit(model, signals[TMODEL_STATE_CHANGED], 0, + whatsChanged); + } + + g_signal_emit(model, signals[TMODEL_UPDATE], 0); + + return &(priv->stats); +} |