source: trunk/gtk/tr-core.c @ 13797

Last change on this file since 13797 was 13797, checked in by jordan, 8 years ago

(gtk) silence a g_object_unref() warning when appending '.added' to a .torrent file fails

  • Property svn:keywords set to Date Rev Author Id
File size: 49.8 KB
Line 
1/******************************************************************************
2 * $Id: tr-core.c 13797 2013-01-17 00:45:31Z jordan $
3 *
4 * Copyright (c) Transmission authors and contributors
5 *
6 * Permission is hereby granted, free of charge, to any person obtaining a
7 * copy of this software and associated documentation files (the "Software"),
8 * to deal in the Software without restriction, including without limitation
9 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
10 * and/or sell copies of the Software, and to permit persons to whom the
11 * Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
15 *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 * DEALINGS IN THE SOFTWARE.
23 *****************************************************************************/
24
25#include <math.h> /* pow () */
26#include <string.h> /* strcmp, strlen */
27
28#include <gtk/gtk.h>
29#include <glib/gi18n.h>
30#include <gio/gio.h>
31
32#include <event2/buffer.h>
33
34#include <libtransmission/transmission.h>
35#include <libtransmission/rpcimpl.h>
36#include <libtransmission/utils.h> /* tr_free */
37#include <libtransmission/variant.h>
38
39#include "actions.h"
40#include "conf.h"
41#include "notify.h"
42#include "tr-core.h"
43#include "tr-prefs.h"
44#include "util.h"
45
46/***
47****
48***/
49
50enum
51{
52  ADD_ERROR_SIGNAL,
53  ADD_PROMPT_SIGNAL,
54  BLOCKLIST_SIGNAL,
55  BUSY_SIGNAL,
56  PORT_SIGNAL,
57  PREFS_SIGNAL,
58
59  LAST_SIGNAL
60};
61
62static guint signals[LAST_SIGNAL] = { 0 };
63
64static void core_maybe_inhibit_hibernation (TrCore * core);
65
66struct TrCorePrivate
67{
68  GFileMonitor * monitor;
69  gulong         monitor_tag;
70  GFile        * monitor_dir;
71  GSList       * monitor_files;
72  gulong         monitor_idle_tag;
73
74  gboolean       adding_from_watch_dir;
75  gboolean       inhibit_allowed;
76  gboolean       have_inhibit_cookie;
77  gboolean       dbus_error;
78  guint          inhibit_cookie;
79  gint           busy_count;
80  GtkTreeModel * raw_model;
81  GtkTreeModel * sorted_model;
82  tr_session   * session;
83  GStringChunk * string_chunk;
84};
85
86static int
87core_is_disposed (const TrCore * core)
88{
89  return !core || !core->priv->sorted_model;
90}
91
92G_DEFINE_TYPE (TrCore, tr_core, G_TYPE_OBJECT)
93
94static void
95core_dispose (GObject * o)
96{
97  TrCore * core = TR_CORE (o);
98
99  if (core->priv->sorted_model != NULL)
100    {
101      g_object_unref (core->priv->sorted_model);
102      core->priv->sorted_model = NULL;
103      core->priv->raw_model = NULL;
104    }
105
106  G_OBJECT_CLASS (tr_core_parent_class)->dispose (o);
107}
108
109static void
110core_finalize (GObject * o)
111{
112  TrCore * core = TR_CORE (o);
113
114  g_string_chunk_free (core->priv->string_chunk);
115
116  G_OBJECT_CLASS (tr_core_parent_class)->finalize (o);
117}
118
119static void
120tr_core_class_init (TrCoreClass * core_class)
121{
122  GObjectClass * gobject_class;
123  GType core_type = G_TYPE_FROM_CLASS (core_class);
124
125  g_type_class_add_private (core_class, sizeof (struct TrCorePrivate));
126
127  gobject_class = G_OBJECT_CLASS (core_class);
128  gobject_class->dispose = core_dispose;
129  gobject_class->finalize = core_finalize;
130
131  signals[ADD_ERROR_SIGNAL] =
132    g_signal_new ("add-error", core_type,
133                  G_SIGNAL_RUN_LAST,
134                  G_STRUCT_OFFSET (TrCoreClass, add_error),
135                  NULL, NULL,
136                  g_cclosure_marshal_VOID__UINT_POINTER,
137                  G_TYPE_NONE,
138                  2, G_TYPE_UINT, G_TYPE_POINTER);
139
140  signals[ADD_PROMPT_SIGNAL] =
141    g_signal_new ("add-prompt", core_type,
142                  G_SIGNAL_RUN_LAST,
143                  G_STRUCT_OFFSET (TrCoreClass, add_prompt),
144                  NULL, NULL,
145                  g_cclosure_marshal_VOID__POINTER,
146                  G_TYPE_NONE,
147                  1, G_TYPE_POINTER);
148
149  signals[BUSY_SIGNAL] =
150    g_signal_new ("busy", core_type,
151                  G_SIGNAL_RUN_FIRST,
152                  G_STRUCT_OFFSET (TrCoreClass, busy),
153                  NULL, NULL,
154                  g_cclosure_marshal_VOID__BOOLEAN,
155                  G_TYPE_NONE,
156                  1, G_TYPE_BOOLEAN);
157
158  signals[BLOCKLIST_SIGNAL] =
159    g_signal_new ("blocklist-updated", core_type,
160                  G_SIGNAL_RUN_FIRST,
161                  G_STRUCT_OFFSET (TrCoreClass, blocklist_updated),
162                  NULL, NULL,
163                  g_cclosure_marshal_VOID__INT,
164                  G_TYPE_NONE,
165                  1, G_TYPE_INT);
166
167  signals[PORT_SIGNAL] =
168    g_signal_new ("port-tested", core_type,
169                  G_SIGNAL_RUN_LAST,
170                  G_STRUCT_OFFSET (TrCoreClass, port_tested),
171                  NULL, NULL,
172                  g_cclosure_marshal_VOID__BOOLEAN,
173                  G_TYPE_NONE,
174                  1, G_TYPE_BOOLEAN);
175
176  signals[PREFS_SIGNAL] =
177    g_signal_new ("prefs-changed", core_type,
178                  G_SIGNAL_RUN_LAST,
179                  G_STRUCT_OFFSET (TrCoreClass, prefs_changed),
180                  NULL, NULL,
181                  g_cclosure_marshal_VOID__INT,
182                  G_TYPE_NONE,
183                  1, G_TYPE_INT);
184}
185
186static void
187tr_core_init (TrCore * core)
188{
189  GtkListStore * store;
190  struct TrCorePrivate * p;
191
192  /* column types for the model used to store torrent information */
193  /* keep this in sync with the enum near the bottom of tr_core.h */
194  GType types[] = { G_TYPE_POINTER,   /* collated name */
195                    G_TYPE_POINTER,   /* tr_torrent* */
196                    G_TYPE_INT,       /* torrent id */
197                    G_TYPE_DOUBLE,    /* tr_stat.pieceUploadSpeed_KBps */
198                    G_TYPE_DOUBLE,    /* tr_stat.pieceDownloadSpeed_KBps */
199                    G_TYPE_DOUBLE,    /* tr_stat.recheckProgress */
200                    G_TYPE_BOOLEAN,   /* filter.c:ACTIVITY_FILTER_ACTIVE */
201                    G_TYPE_INT,       /* tr_stat.activity */
202                    G_TYPE_UCHAR,     /* tr_stat.finished */
203                    G_TYPE_CHAR,      /* tr_priority_t */
204                    G_TYPE_INT,       /* tr_stat.queuePosition */
205                    G_TYPE_UINT,      /* build_torrent_trackers_hash () */
206                    G_TYPE_INT,       /* MC_ERROR */
207                    G_TYPE_INT };     /* MC_ACTIVE_PEER_COUNT */
208
209  p = core->priv = G_TYPE_INSTANCE_GET_PRIVATE (core,
210                                                TR_CORE_TYPE,
211                                                struct TrCorePrivate);
212
213  /* create the model used to store torrent data */
214  g_assert (G_N_ELEMENTS (types) == MC_ROW_COUNT);
215  store = gtk_list_store_newv (MC_ROW_COUNT, types);
216
217  p->raw_model = GTK_TREE_MODEL (store);
218  p->sorted_model = gtk_tree_model_sort_new_with_model (p->raw_model);
219  p->string_chunk = g_string_chunk_new (2048);
220  g_object_unref (p->raw_model);
221}
222
223
224
225/***
226****  EMIT SIGNALS
227***/
228
229static inline void
230core_emit_blocklist_udpated (TrCore * core, int ruleCount)
231{
232  g_signal_emit (core, signals[BLOCKLIST_SIGNAL], 0, ruleCount);
233}
234
235static inline void
236core_emit_port_tested (TrCore * core, gboolean is_open)
237{
238  g_signal_emit (core, signals[PORT_SIGNAL], 0, is_open);
239}
240
241static inline void
242core_emit_err (TrCore * core, enum tr_core_err type, const char * msg)
243{
244  g_signal_emit (core, signals[ADD_ERROR_SIGNAL], 0, type, msg);
245}
246
247static inline void
248core_emit_busy (TrCore * core, gboolean is_busy)
249{
250  g_signal_emit (core, signals[BUSY_SIGNAL], 0, is_busy);
251}
252
253void
254gtr_core_pref_changed (TrCore * core, const tr_quark key)
255{
256  g_signal_emit (core, signals[PREFS_SIGNAL], 0, key);
257}
258
259/***
260****
261***/
262
263static GtkTreeModel *
264core_raw_model (TrCore * core)
265{
266  return core_is_disposed (core) ? NULL : core->priv->raw_model;
267}
268
269GtkTreeModel *
270gtr_core_model (TrCore * core)
271{
272  return core_is_disposed (core) ? NULL : core->priv->sorted_model;
273}
274
275tr_session *
276gtr_core_session (TrCore * core)
277{
278  return core_is_disposed (core) ? NULL : core->priv->session;
279}
280
281/***
282****  BUSY
283***/
284
285static bool
286core_is_busy (TrCore * core)
287{
288  return core->priv->busy_count > 0;
289}
290
291static void
292core_add_to_busy (TrCore * core, int addMe)
293{
294  const bool wasBusy = core_is_busy (core);
295
296  core->priv->busy_count += addMe;
297
298  if (wasBusy != core_is_busy (core))
299      core_emit_busy (core, core_is_busy (core));
300}
301
302static void core_inc_busy (TrCore * core) { core_add_to_busy (core, 1); }
303static void core_dec_busy (TrCore * core) { core_add_to_busy (core, -1); }
304
305/***
306****
307****  SORTING THE MODEL
308****
309***/
310
311static gboolean
312is_valid_eta (int t)
313{
314  return (t != TR_ETA_NOT_AVAIL) && (t != TR_ETA_UNKNOWN);
315}
316
317static int
318compare_eta (int a, int b)
319{
320  int ret;
321
322  const gboolean a_valid = is_valid_eta (a);
323  const gboolean b_valid = is_valid_eta (b);
324
325  if (!a_valid && !b_valid)
326    ret = 0;
327  else if (!a_valid)
328    ret = -1;
329  else if (!b_valid)
330    ret = 1;
331  else
332    ret = a < b ? 1 : -1;
333
334  return ret;
335}
336
337static int
338compare_double (double a, double b)
339{
340  int ret;
341
342  if (a < b)
343    ret = -1;
344  else if (a > b)
345    ret = 1;
346  else
347    ret = 0;
348
349  return ret;
350}
351
352static int
353compare_uint64 (uint64_t a, uint64_t b)
354{
355  int ret;
356
357  if (a < b)
358    ret = -1;
359  else if (a > b)
360    ret = 1;
361  else
362    ret = 0;
363
364  return ret;
365}
366
367static int
368compare_int (int a, int b)
369{
370  int ret;
371
372  if (a < b)
373    ret = -1;
374  else if (a > b)
375    ret = 1;
376  else
377    ret = 0;
378
379  return ret;
380}
381
382static int
383compare_ratio (double a, double b)
384{
385  int ret;
386
387  if ((int)a == TR_RATIO_INF && (int)b == TR_RATIO_INF)
388    ret = 0;
389  else if ((int)a == TR_RATIO_INF)
390    ret = 1;
391  else if ((int)b == TR_RATIO_INF)
392    ret = -1;
393  else
394    ret = compare_double (a, b);
395
396  return ret;
397}
398
399static int
400compare_time (time_t a, time_t b)
401{
402  int ret;
403
404  if (a < b)
405    ret = -1;
406  else if (a > b)
407    ret = 1;
408  else
409    ret = 0;
410
411  return ret;
412}
413
414static int
415compare_by_name (GtkTreeModel * m,
416                 GtkTreeIter  * a,
417                 GtkTreeIter  * b,
418                 gpointer       user_data UNUSED)
419{
420  const char *ca, *cb;
421  gtk_tree_model_get (m, a, MC_NAME_COLLATED, &ca, -1);
422  gtk_tree_model_get (m, b, MC_NAME_COLLATED, &cb, -1);
423  return tr_strcmp0 (ca, cb);
424}
425
426static int
427compare_by_queue (GtkTreeModel * m,
428                  GtkTreeIter  * a,
429                  GtkTreeIter  * b,
430                  gpointer       user_data UNUSED)
431{
432  tr_torrent *ta, *tb;
433  const tr_stat *sa, *sb;
434
435  gtk_tree_model_get (m, a, MC_TORRENT, &ta, -1);
436  sa = tr_torrentStatCached (ta);
437  gtk_tree_model_get (m, b, MC_TORRENT, &tb, -1);
438  sb = tr_torrentStatCached (tb);
439
440  return sb->queuePosition - sa->queuePosition;
441}
442
443static int
444compare_by_ratio (GtkTreeModel* m, GtkTreeIter * a, GtkTreeIter * b, gpointer user_data)
445{
446  int ret = 0;
447  tr_torrent *ta, *tb;
448  const tr_stat *sa, *sb;
449
450  gtk_tree_model_get (m, a, MC_TORRENT, &ta, -1);
451  sa = tr_torrentStatCached (ta);
452  gtk_tree_model_get (m, b, MC_TORRENT, &tb, -1);
453  sb = tr_torrentStatCached (tb);
454
455  if (!ret)
456    ret = compare_ratio (sa->ratio, sb->ratio);
457  if (!ret)
458    ret = compare_by_queue (m, a, b, user_data);
459  return ret;
460}
461
462static int
463compare_by_activity (GtkTreeModel * m,
464                     GtkTreeIter  * a,
465                     GtkTreeIter  * b,
466                     gpointer       user_data)
467{
468  int ret = 0;
469  tr_torrent *ta, *tb;
470  const tr_stat *sa, *sb;
471  double aUp, aDown, bUp, bDown;
472
473  gtk_tree_model_get (m, a, MC_SPEED_UP, &aUp,
474                            MC_SPEED_DOWN, &aDown,
475                            MC_TORRENT, &ta,
476                            -1);
477  gtk_tree_model_get (m, b, MC_SPEED_UP, &bUp,
478                            MC_SPEED_DOWN, &bDown,
479                            MC_TORRENT, &tb,
480                            -1);
481  sa = tr_torrentStatCached (ta);
482  sb = tr_torrentStatCached (tb);
483
484  if (!ret)
485    ret = compare_double (aUp+aDown, bUp+bDown);
486  if (!ret)
487    ret = compare_uint64 (sa->uploadedEver, sb->uploadedEver);
488  if (!ret)
489    ret = compare_by_queue (m, a, b, user_data);
490
491  return ret;
492}
493
494static int
495compare_by_age (GtkTreeModel * m,
496                GtkTreeIter  * a,
497                GtkTreeIter  * b,
498                gpointer       u)
499{
500  int ret = 0;
501  tr_torrent *ta, *tb;
502
503  gtk_tree_model_get (m, a, MC_TORRENT, &ta, -1);
504  gtk_tree_model_get (m, b, MC_TORRENT, &tb, -1);
505
506  if (!ret)
507    ret = compare_time (tr_torrentStatCached (ta)->addedDate,
508                        tr_torrentStatCached (tb)->addedDate);
509
510  if (!ret)
511    ret = compare_by_name (m, a, b, u);
512
513  return ret;
514}
515
516static int
517compare_by_size (GtkTreeModel * m,
518                 GtkTreeIter  * a,
519                 GtkTreeIter  * b,
520                 gpointer       u)
521{
522  int ret = 0;
523  tr_torrent *t;
524  const tr_info *ia, *ib;
525
526  gtk_tree_model_get (m, a, MC_TORRENT, &t, -1);
527  ia = tr_torrentInfo (t);
528  gtk_tree_model_get (m, b, MC_TORRENT, &t, -1);
529  ib = tr_torrentInfo (t);
530
531  if (!ret)
532    ret = compare_uint64 (ia->totalSize, ib->totalSize);
533  if (!ret)
534    ret = compare_by_name (m, a, b, u);
535
536  return ret;
537}
538
539static int
540compare_by_progress (GtkTreeModel * m,
541                     GtkTreeIter  * a,
542                     GtkTreeIter  * b,
543                     gpointer       u)
544{
545  int ret = 0;
546  tr_torrent * t;
547  const tr_stat *sa, *sb;
548
549  gtk_tree_model_get (m, a, MC_TORRENT, &t, -1);
550  sa = tr_torrentStatCached (t);
551  gtk_tree_model_get (m, b, MC_TORRENT, &t, -1);
552  sb = tr_torrentStatCached (t);
553
554  if (!ret)
555    ret = compare_double (sa->percentComplete, sb->percentComplete);
556  if (!ret)
557    ret = compare_double (sa->seedRatioPercentDone, sb->seedRatioPercentDone);
558  if (!ret)
559    ret = compare_by_ratio (m, a, b, u);
560
561  return ret;
562}
563
564static int
565compare_by_eta (GtkTreeModel * m,
566                GtkTreeIter  * a,
567                GtkTreeIter  * b,
568                gpointer       u)
569{
570  int ret = 0;
571  tr_torrent *ta, *tb;
572
573  gtk_tree_model_get (m, a, MC_TORRENT, &ta, -1);
574  gtk_tree_model_get (m, b, MC_TORRENT, &tb, -1);
575
576  if (!ret)
577    ret = compare_eta (tr_torrentStatCached (ta)->eta,
578                       tr_torrentStatCached (tb)->eta);
579
580  if (!ret)
581    ret = compare_by_name (m, a, b, u);
582
583  return ret;
584}
585
586static int
587compare_by_state (GtkTreeModel * m,
588                  GtkTreeIter  * a,
589                  GtkTreeIter  * b,
590                  gpointer       u)
591{
592  int ret = 0;
593  int sa, sb;
594  tr_torrent *ta, *tb;
595
596  gtk_tree_model_get (m, a, MC_ACTIVITY, &sa, MC_TORRENT, &ta, -1);
597  gtk_tree_model_get (m, b, MC_ACTIVITY, &sb, MC_TORRENT, &tb, -1);
598
599  if (!ret)
600    ret = compare_int (sa, sb);
601  if (!ret)
602    ret = compare_by_queue (m, a, b, u);
603
604  return ret;
605}
606
607static void
608core_set_sort_mode (TrCore * core, const char * mode, gboolean is_reversed)
609{
610  const int col = MC_TORRENT;
611  GtkTreeIterCompareFunc sort_func;
612  GtkSortType type = is_reversed ? GTK_SORT_ASCENDING : GTK_SORT_DESCENDING;
613  GtkTreeSortable * sortable = GTK_TREE_SORTABLE (gtr_core_model (core));
614
615  if (!strcmp (mode, "sort-by-activity"))
616    sort_func = compare_by_activity;
617  else if (!strcmp (mode, "sort-by-age"))
618    sort_func = compare_by_age;
619  else if (!strcmp (mode, "sort-by-progress"))
620    sort_func = compare_by_progress;
621  else if (!strcmp (mode, "sort-by-queue"))
622    sort_func = compare_by_queue;
623  else if (!strcmp (mode, "sort-by-time-left"))
624    sort_func = compare_by_eta;
625  else if (!strcmp (mode, "sort-by-ratio"))
626    sort_func = compare_by_ratio;
627  else if (!strcmp (mode, "sort-by-state"))
628    sort_func = compare_by_state;
629  else if (!strcmp (mode, "sort-by-size"))
630    sort_func = compare_by_size;
631  else {
632    sort_func = compare_by_name;
633    type = is_reversed ? GTK_SORT_DESCENDING : GTK_SORT_ASCENDING;
634  }
635
636  gtk_tree_sortable_set_sort_func (sortable, col, sort_func, NULL, NULL);
637  gtk_tree_sortable_set_sort_column_id (sortable, col, type);
638}
639
640/***
641****
642****  WATCHDIR
643****
644***/
645
646static time_t
647get_file_mtime (GFile * file)
648{
649  time_t mtime;
650  GFileInfo * info = g_file_query_info (file, G_FILE_ATTRIBUTE_TIME_MODIFIED, 0, NULL, NULL);
651  mtime = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
652  g_object_unref (G_OBJECT (info));
653  return mtime;
654}
655
656static void
657rename_torrent_and_unref_file (GFile * file)
658{
659  GError * error = NULL;
660  GFileInfo * info = g_file_query_info (file, G_FILE_ATTRIBUTE_STANDARD_EDIT_NAME, 0, NULL, NULL);
661  const char * old_name = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_EDIT_NAME);
662  char * new_name = g_strdup_printf ("%s.added", old_name);
663  GFile * new_file = g_file_set_display_name (file, new_name, NULL, &error);
664  if (error != NULL)
665    g_message ("Unable to rename \"%s\" as \"%s\": %s", old_name, new_name, error->message);
666  if (new_file != NULL)
667    g_object_unref (G_OBJECT (new_file));
668  g_free (new_name);
669  g_object_unref (G_OBJECT (info));
670  g_object_unref (G_OBJECT (file));
671  g_clear_error (&error);
672}
673
674static gboolean
675core_watchdir_idle (gpointer gcore)
676{
677  GSList * l;
678  GSList * changing = NULL;
679  GSList * unchanging = NULL;
680  TrCore * core = TR_CORE (gcore);
681  const time_t now = tr_time ();
682  struct TrCorePrivate * p = core->priv;
683
684  /* separate the files into two lists: changing and unchanging */
685  for (l=p->monitor_files; l!=NULL; l=l->next)
686    {
687      GFile * file = l->data;
688      const time_t mtime = get_file_mtime (file);
689      if (mtime + 2 >= now)
690        changing = g_slist_prepend (changing, file);
691      else
692        unchanging = g_slist_prepend (unchanging, file);
693    }
694
695  /* add the files that have stopped changing */
696  if (unchanging != NULL)
697    {
698      const gboolean do_start = gtr_pref_flag_get (TR_KEY_start_added_torrents);
699      const gboolean do_prompt = gtr_pref_flag_get (TR_KEY_show_options_window);
700
701      core->priv->adding_from_watch_dir = TRUE;
702      gtr_core_add_files (core, unchanging, do_start, do_prompt, TRUE);
703      g_slist_foreach (unchanging, (GFunc)rename_torrent_and_unref_file, NULL);
704      g_slist_free (unchanging);
705      core->priv->adding_from_watch_dir = FALSE;
706    }
707
708  /* keep monitoring the ones that are still changing */
709  g_slist_free (p->monitor_files);
710  p->monitor_files = changing;
711
712  /* if monitor_files is nonempty, keep checking every second */
713  if (core->priv->monitor_files)
714    return G_SOURCE_CONTINUE;
715  core->priv->monitor_idle_tag = 0;
716  return G_SOURCE_REMOVE;
717}
718
719/* If this file is a torrent, add it to our list */
720static void
721core_watchdir_monitor_file (TrCore * core, GFile * file)
722{
723  char * filename = g_file_get_path (file);
724  const gboolean is_torrent = g_str_has_suffix (filename, ".torrent");
725
726  if (is_torrent)
727    {
728      GSList * l;
729      struct TrCorePrivate * p = core->priv;
730
731      /* if we're not already watching this file, start watching it now */
732      for (l=p->monitor_files; l!=NULL; l=l->next)
733        if (g_file_equal (file, l->data))
734          break;
735
736      if (l == NULL)
737        {
738          g_object_ref (file);
739          p->monitor_files = g_slist_prepend (p->monitor_files, file);
740          if (p->monitor_idle_tag == 0)
741            p->monitor_idle_tag = gdk_threads_add_timeout_seconds (1, core_watchdir_idle, core);
742        }
743    }
744
745  g_free (filename);
746}
747
748/* GFileMonitor noticed a file was created */
749static void
750on_file_changed_in_watchdir (GFileMonitor       * monitor UNUSED,
751                             GFile              * file,
752                             GFile              * other_type UNUSED,
753                             GFileMonitorEvent    event_type,
754                             gpointer             core)
755{
756  if (event_type == G_FILE_MONITOR_EVENT_CREATED)
757    core_watchdir_monitor_file (core, file);
758}
759
760/* walk through the pre-existing files in the watchdir */
761static void
762core_watchdir_scan (TrCore * core)
763{
764  const char * dirname = gtr_pref_string_get (TR_KEY_watch_dir);
765  GDir * dir = g_dir_open (dirname, 0, NULL);
766
767  if (dir != NULL)
768    {
769      const char * name;
770      while ((name = g_dir_read_name (dir)))
771        {
772          char * filename = g_build_filename (dirname, name, NULL);
773          GFile * file = g_file_new_for_path (filename);
774          core_watchdir_monitor_file (core, file);
775          g_object_unref (file);
776          g_free (filename);
777        }
778
779      g_dir_close (dir);
780    }
781}
782
783static void
784core_watchdir_update (TrCore * core)
785{
786  const gboolean is_enabled = gtr_pref_flag_get (TR_KEY_watch_dir_enabled);
787  GFile * dir = g_file_new_for_path (gtr_pref_string_get (TR_KEY_watch_dir));
788  struct TrCorePrivate * p = core->priv;
789
790  if (p->monitor && (!is_enabled || !g_file_equal (dir, p->monitor_dir)))
791    {
792      g_signal_handler_disconnect (p->monitor, p->monitor_tag);
793      g_file_monitor_cancel (p->monitor);
794      g_object_unref (p->monitor);
795      g_object_unref (p->monitor_dir);
796
797      p->monitor_dir = NULL;
798      p->monitor = NULL;
799      p->monitor_tag = 0;
800    }
801
802    if (is_enabled && !p->monitor)
803    {
804      GFileMonitor * m = g_file_monitor_directory (dir, 0, NULL, NULL);
805      core_watchdir_scan (core);
806
807      g_object_ref (dir);
808      p->monitor = m;
809      p->monitor_dir = dir;
810      p->monitor_tag = g_signal_connect (m, "changed",
811                                         G_CALLBACK (on_file_changed_in_watchdir), core);
812    }
813
814  g_object_unref (dir);
815}
816
817/***
818****
819***/
820
821static void
822on_pref_changed (TrCore * core, const tr_quark key, gpointer data UNUSED)
823{
824  switch (key)
825    {
826      case TR_KEY_sort_mode:
827      case TR_KEY_sort_reversed:
828        {
829          const char * mode = gtr_pref_string_get (TR_KEY_sort_mode);
830          const gboolean is_reversed = gtr_pref_flag_get (TR_KEY_sort_reversed);
831          core_set_sort_mode (core, mode, is_reversed);
832          break;
833        }
834
835      case TR_KEY_peer_limit_global:
836        tr_sessionSetPeerLimit (gtr_core_session (core), gtr_pref_int_get (key));
837        break;
838
839      case TR_KEY_peer_limit_per_torrent:
840        tr_sessionSetPeerLimitPerTorrent (gtr_core_session (core), gtr_pref_int_get (key));
841        break;
842
843      case TR_KEY_inhibit_desktop_hibernation:
844        core_maybe_inhibit_hibernation (core);
845        break;
846
847      case TR_KEY_watch_dir:
848      case TR_KEY_watch_dir_enabled:
849        core_watchdir_update (core);
850        break;
851
852      default:
853        break;
854    }
855}
856
857/**
858***
859**/
860
861TrCore *
862gtr_core_new (tr_session * session)
863{
864  TrCore * core = TR_CORE (g_object_new (TR_CORE_TYPE, NULL));
865
866  core->priv->session = session;
867
868  /* init from prefs & listen to pref changes */
869  on_pref_changed (core, TR_KEY_sort_mode, NULL);
870  on_pref_changed (core, TR_KEY_sort_reversed, NULL);
871  on_pref_changed (core, TR_KEY_watch_dir_enabled, NULL);
872  on_pref_changed (core, TR_KEY_peer_limit_global, NULL);
873  on_pref_changed (core, TR_KEY_inhibit_desktop_hibernation, NULL);
874  g_signal_connect (core, "prefs-changed", G_CALLBACK (on_pref_changed), NULL);
875
876  return core;
877}
878
879tr_session *
880gtr_core_close (TrCore * core)
881{
882  tr_session * session = gtr_core_session (core);
883
884  if (session)
885    {
886      core->priv->session = NULL;
887      gtr_pref_save (session);
888    }
889
890  return session;
891}
892
893/***
894****  COMPLETENESS CALLBACK
895***/
896
897struct notify_callback_data
898{
899  TrCore * core;
900  int torrent_id;
901};
902
903static gboolean
904on_torrent_completeness_changed_idle (gpointer gdata)
905{
906  struct notify_callback_data * data = gdata;
907  gtr_notify_torrent_completed (data->core, data->torrent_id);
908  g_object_unref (G_OBJECT (data->core));
909  g_free (data);
910  return FALSE;
911}
912
913/* this is called in the libtransmission thread, *NOT* the GTK+ thread,
914   so delegate to the GTK+ thread before calling notify's dbus code... */
915static void
916on_torrent_completeness_changed (tr_torrent       * tor,
917                                 tr_completeness    completeness,
918                                 bool               was_running,
919                                 void             * gcore)
920{
921  if (was_running && (completeness != TR_LEECH) && (tr_torrentStat (tor)->sizeWhenDone != 0))
922    {
923      struct notify_callback_data * data = g_new (struct notify_callback_data, 1);
924      data->core = gcore;
925      data->torrent_id = tr_torrentId (tor);
926      g_object_ref (G_OBJECT (data->core));
927      gdk_threads_add_idle (on_torrent_completeness_changed_idle, data);
928    }
929}
930
931/***
932****  METADATA CALLBACK
933***/
934
935static const char*
936get_collated_name (TrCore * core, const tr_torrent * tor)
937{
938  char buf[2048];
939  const char * name = tr_torrentName (tor);
940  char * down = g_utf8_strdown (name ? name : "", -1);
941  const tr_info * inf = tr_torrentInfo (tor);
942  g_snprintf (buf, sizeof (buf), "%s\t%s", down, inf->hashString);
943  g_free (down);
944  return g_string_chunk_insert_const (core->priv->string_chunk, buf);
945}
946
947struct metadata_callback_data
948{
949  TrCore * core;
950  int torrent_id;
951};
952
953static gboolean
954find_row_from_torrent_id (GtkTreeModel * model, int id, GtkTreeIter * setme)
955{
956  GtkTreeIter iter;
957  gboolean match = FALSE;
958
959  if (gtk_tree_model_iter_children (model, &iter, NULL)) do
960    {
961      int row_id;
962      gtk_tree_model_get (model, &iter, MC_TORRENT_ID, &row_id, -1);
963      match = id == row_id;
964    }
965  while (!match && gtk_tree_model_iter_next (model, &iter));
966
967  if (match)
968    *setme = iter;
969
970  return match;
971}
972
973static gboolean
974on_torrent_metadata_changed_idle (gpointer gdata)
975{
976  struct notify_callback_data * data = gdata;
977  tr_session * session = gtr_core_session (data->core);
978  tr_torrent * tor = tr_torrentFindFromId (session, data->torrent_id);
979
980  /* update the torrent's collated name */
981  if (tor != NULL)
982    {
983      GtkTreeIter iter;
984      GtkTreeModel * model = core_raw_model (data->core);
985      if (find_row_from_torrent_id (model, data->torrent_id, &iter))
986        {
987          const char * collated = get_collated_name (data->core, tor);
988          GtkListStore * store = GTK_LIST_STORE (model);
989          gtk_list_store_set (store, &iter, MC_NAME_COLLATED, collated, -1);
990        }
991    }
992
993  /* cleanup */
994  g_object_unref (G_OBJECT (data->core));
995  g_free (data);
996  return FALSE;
997}
998
999/* this is called in the libtransmission thread, *NOT* the GTK+ thread,
1000   so delegate to the GTK+ thread before changing our list store... */
1001static void
1002on_torrent_metadata_changed (tr_torrent * tor, void * gcore)
1003{
1004  struct notify_callback_data * data = g_new (struct notify_callback_data, 1);
1005  data->core = gcore;
1006  data->torrent_id = tr_torrentId (tor);
1007  g_object_ref (G_OBJECT (data->core));
1008  gdk_threads_add_idle (on_torrent_metadata_changed_idle, data);
1009}
1010
1011/***
1012****
1013****  ADDING TORRENTS
1014****
1015***/
1016
1017static unsigned int
1018build_torrent_trackers_hash (tr_torrent * tor)
1019{
1020  unsigned int i;
1021  const char * pch;
1022  uint64_t hash = 0;
1023  const tr_info * const inf = tr_torrentInfo (tor);
1024
1025  for (i=0; i<inf->trackerCount; ++i)
1026    for (pch=inf->trackers[i].announce; *pch; ++pch)
1027      hash = (hash<<4) ^ (hash>>28) ^ *pch;
1028
1029  return hash;
1030}
1031
1032static gboolean
1033is_torrent_active (const tr_stat * st)
1034{
1035  return (st->peersSendingToUs > 0)
1036      || (st->peersGettingFromUs > 0)
1037      || (st->activity == TR_STATUS_CHECK);
1038}
1039
1040void
1041gtr_core_add_torrent (TrCore * core, tr_torrent * tor, gboolean do_notify)
1042{
1043  if (tor != NULL)
1044    {
1045      GtkTreeIter unused;
1046      const tr_stat * st = tr_torrentStat (tor);
1047      const char * collated = get_collated_name (core, tor);
1048      const unsigned int trackers_hash = build_torrent_trackers_hash (tor);
1049      GtkListStore * store = GTK_LIST_STORE (core_raw_model (core));
1050
1051      gtk_list_store_insert_with_values (store, &unused, 0,
1052        MC_NAME_COLLATED,     collated,
1053        MC_TORRENT,           tor,
1054        MC_TORRENT_ID,        tr_torrentId (tor),
1055        MC_SPEED_UP,          st->pieceUploadSpeed_KBps,
1056        MC_SPEED_DOWN,        st->pieceDownloadSpeed_KBps,
1057        MC_RECHECK_PROGRESS,  st->recheckProgress,
1058        MC_ACTIVE,            is_torrent_active (st),
1059        MC_ACTIVITY,          st->activity,
1060        MC_FINISHED,          st->finished,
1061        MC_PRIORITY,          tr_torrentGetPriority (tor),
1062        MC_QUEUE_POSITION,    st->queuePosition,
1063        MC_TRACKERS,          trackers_hash,
1064        -1);
1065
1066      if (do_notify)
1067        gtr_notify_torrent_added (tr_torrentName (tor));
1068
1069      tr_torrentSetMetadataCallback (tor, on_torrent_metadata_changed, core);
1070      tr_torrentSetCompletenessCallback (tor, on_torrent_completeness_changed, core);
1071    }
1072}
1073
1074static tr_torrent *
1075core_create_new_torrent (TrCore * core, tr_ctor * ctor)
1076{
1077  int errcode = 0;
1078  tr_torrent * tor;
1079  bool do_trash = false;
1080  tr_session * session = gtr_core_session (core);
1081
1082  /* let the gtk client handle the removal, since libT
1083   * doesn't have any concept of the glib trash API */
1084  tr_ctorGetDeleteSource (ctor, &do_trash);
1085  tr_ctorSetDeleteSource (ctor, FALSE);
1086  tor = tr_torrentNew (ctor, &errcode);
1087
1088  if (tor && do_trash)
1089    {
1090      const char * config = tr_sessionGetConfigDir (session);
1091      const char * source = tr_ctorGetSourceFile (ctor);
1092
1093      if (source != NULL)
1094        {
1095          /* #1294: don't delete the .torrent file if it's our internal copy */
1096          const int is_internal = (strstr (source, config) == source);
1097          if (!is_internal)
1098              gtr_file_trash_or_remove (source);
1099        }
1100    }
1101
1102    return tor;
1103}
1104
1105static int
1106core_add_ctor (TrCore * core, tr_ctor * ctor,
1107               gboolean do_prompt, gboolean do_notify)
1108{
1109  tr_info inf;
1110  int err = tr_torrentParse (ctor, &inf);
1111
1112  switch (err)
1113    {
1114      case TR_PARSE_ERR:
1115        break;
1116
1117      case TR_PARSE_DUPLICATE:
1118        /* don't complain about .torrent files in the watch directory
1119         * that have already been added... that gets annoying and we
1120         * don't want to be nagging users to clean up their watch dirs */
1121        if (!tr_ctorGetSourceFile (ctor) || !core->priv->adding_from_watch_dir)
1122          core_emit_err (core, err, inf.name);
1123        tr_metainfoFree (&inf);
1124        tr_ctorFree (ctor);
1125        break;
1126
1127      default:
1128        if (do_prompt)
1129          {
1130            g_signal_emit (core, signals[ADD_PROMPT_SIGNAL], 0, ctor);
1131          }
1132        else
1133          {
1134            gtr_core_add_torrent (core, core_create_new_torrent (core, ctor), do_notify);
1135            tr_ctorFree (ctor);
1136          }
1137        tr_metainfoFree (&inf);
1138        break;
1139    }
1140
1141  return err;
1142}
1143
1144static void
1145core_apply_defaults (tr_ctor * ctor)
1146{
1147  if (tr_ctorGetPaused (ctor, TR_FORCE, NULL))
1148    tr_ctorSetPaused (ctor, TR_FORCE, !gtr_pref_flag_get (TR_KEY_start_added_torrents));
1149
1150  if (tr_ctorGetDeleteSource (ctor, NULL))
1151    tr_ctorSetDeleteSource (ctor, gtr_pref_flag_get (TR_KEY_trash_original_torrent_files));
1152
1153  if (tr_ctorGetPeerLimit (ctor, TR_FORCE, NULL))
1154    tr_ctorSetPeerLimit (ctor, TR_FORCE, gtr_pref_int_get (TR_KEY_peer_limit_per_torrent));
1155
1156  if (tr_ctorGetDownloadDir (ctor, TR_FORCE, NULL))
1157    tr_ctorSetDownloadDir (ctor, TR_FORCE, gtr_pref_string_get (TR_KEY_download_dir));
1158}
1159
1160void
1161gtr_core_add_ctor (TrCore * core, tr_ctor * ctor)
1162{
1163  const gboolean do_notify = FALSE;
1164  const gboolean do_prompt = gtr_pref_flag_get (TR_KEY_show_options_window);
1165  core_apply_defaults (ctor);
1166  core_add_ctor (core, ctor, do_prompt, do_notify);
1167}
1168
1169/***
1170****
1171***/
1172
1173struct add_from_url_data
1174{
1175  TrCore * core;
1176  tr_ctor * ctor;
1177  bool do_prompt;
1178  bool do_notify;
1179};
1180
1181static void
1182add_file_async_callback (GObject * file, GAsyncResult * result, gpointer gdata)
1183{
1184  gsize length;
1185  char * contents;
1186  GError * error = NULL;
1187  struct add_from_url_data * data = gdata;
1188
1189  if (!g_file_load_contents_finish (G_FILE (file), result, &contents, &length, NULL, &error))
1190    {
1191      g_message (_("Couldn't read \"%s\": %s"), g_file_get_parse_name (G_FILE (file)), error->message);
1192      g_error_free (error);
1193    }
1194  else if (!tr_ctorSetMetainfo (data->ctor, (const uint8_t*)contents, length))
1195    {
1196      core_add_ctor (data->core, data->ctor, data->do_prompt, data->do_notify);
1197    }
1198  else
1199    {
1200      tr_ctorFree (data->ctor);
1201    }
1202
1203  core_dec_busy (data->core);
1204  g_free (data);
1205}
1206
1207static bool
1208add_file (TrCore      * core,
1209          GFile       * file,
1210          gboolean      do_start,
1211          gboolean      do_prompt,
1212          gboolean      do_notify)
1213{
1214  bool handled = false;
1215  tr_session * session = gtr_core_session (core);
1216
1217  if (session != NULL)
1218    {
1219      tr_ctor * ctor;
1220      bool tried = false;
1221      bool loaded = false;
1222
1223      ctor = tr_ctorNew (session);
1224      core_apply_defaults (ctor);
1225      tr_ctorSetPaused (ctor, TR_FORCE, !do_start);
1226
1227      /* local files... */
1228      if (!tried)
1229        {
1230          char * str = g_file_get_path (file);
1231          if ((tried = g_file_test (str, G_FILE_TEST_EXISTS)))
1232             loaded = !tr_ctorSetMetainfoFromFile (ctor, str);
1233          g_free (str);
1234        }
1235
1236      /* magnet links... */
1237      if (!tried && g_file_has_uri_scheme (file, "magnet"))
1238        {
1239          /* GFile mangles the original string with /// so we have to un-mangle */
1240          char * str = g_file_get_parse_name (file);
1241          char * magnet = g_strdup_printf ("magnet:%s", strchr (str, '?'));
1242          tried = true;
1243          loaded = !tr_ctorSetMetainfoFromMagnetLink (ctor, magnet);
1244          g_free (magnet);
1245          g_free (str);
1246        }
1247
1248      /* hashcodes that we can turn into magnet links... */
1249      if (!tried)
1250        {
1251          char * str = g_file_get_basename (file);
1252          if (gtr_is_hex_hashcode (str))
1253            {
1254              char * magnet = g_strdup_printf ("magnet:?xt=urn:btih:%s", str);
1255              tried = true;
1256              loaded = !tr_ctorSetMetainfoFromMagnetLink (ctor, magnet);
1257              g_free (magnet);
1258            }
1259          g_free (str);
1260        }
1261
1262      /* if we were able to load the metainfo, add the torrent */
1263      if (loaded)
1264        {
1265          handled = true;
1266          core_add_ctor (core, ctor, do_prompt, do_notify);
1267        }
1268      else if (g_file_has_uri_scheme (file, "http") ||
1269               g_file_has_uri_scheme (file, "https") ||
1270               g_file_has_uri_scheme (file, "ftp"))
1271        {
1272          struct add_from_url_data * data;
1273
1274          data = g_new0 (struct add_from_url_data, 1);
1275          data->core = core;
1276          data->ctor = ctor;
1277          data->do_prompt = do_prompt;
1278          data->do_notify = do_notify;
1279
1280          handled = true;
1281          core_inc_busy (core);
1282          g_file_load_contents_async (file, NULL, add_file_async_callback, data);
1283        }
1284      else
1285        {
1286          tr_ctorFree (ctor);
1287          g_message (_("Skipping unknown torrent \"%s\""), g_file_get_parse_name (file));
1288        }
1289    }
1290
1291  return handled;
1292}
1293
1294bool
1295gtr_core_add_from_url (TrCore * core, const char * uri)
1296{
1297  bool handled;
1298  const bool do_start = gtr_pref_flag_get (TR_KEY_start_added_torrents);
1299  const bool do_prompt = gtr_pref_flag_get (TR_KEY_show_options_window);
1300  const bool do_notify = false;
1301
1302  GFile * file = g_file_new_for_uri (uri);
1303  handled = add_file (core, file, do_start, do_prompt, do_notify);
1304  g_object_unref (file);
1305  gtr_core_torrents_added (core);
1306
1307  return handled;
1308}
1309
1310void
1311gtr_core_add_files (TrCore     * core,
1312                    GSList     * files,
1313                    gboolean     do_start,
1314                    gboolean     do_prompt,
1315                    gboolean     do_notify)
1316{
1317  GSList * l;
1318
1319  for (l=files; l!=NULL; l=l->next)
1320    add_file (core, l->data, do_start, do_prompt, do_notify);
1321
1322  gtr_core_torrents_added (core);
1323}
1324
1325void
1326gtr_core_torrents_added (TrCore * self)
1327{
1328  gtr_core_update (self);
1329  core_emit_err (self, TR_CORE_ERR_NO_MORE_TORRENTS, NULL);
1330}
1331
1332void
1333gtr_core_remove_torrent (TrCore * core, int id, gboolean delete_local_data)
1334{
1335  tr_torrent * tor = gtr_core_find_torrent (core, id);
1336
1337  if (tor != NULL)
1338    {
1339      /* remove from the gui */
1340      GtkTreeIter iter;
1341      GtkTreeModel * model = core_raw_model (core);
1342      if (find_row_from_torrent_id (model, id, &iter))
1343        gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
1344
1345      /* remove the torrent */
1346      tr_torrentRemove (tor, delete_local_data, gtr_file_trash_or_remove);
1347    }
1348}
1349
1350void
1351gtr_core_load (TrCore * self, gboolean forcePaused)
1352{
1353  int i;
1354  tr_ctor * ctor;
1355  tr_torrent ** torrents;
1356  int count = 0;
1357
1358  ctor = tr_ctorNew (gtr_core_session (self));
1359  if (forcePaused)
1360    tr_ctorSetPaused (ctor, TR_FORCE, TRUE);
1361  tr_ctorSetPeerLimit (ctor, TR_FALLBACK,
1362                       gtr_pref_int_get (TR_KEY_peer_limit_per_torrent));
1363
1364  torrents = tr_sessionLoadTorrents (gtr_core_session (self), ctor, &count);
1365  for (i=0; i<count; ++i)
1366    gtr_core_add_torrent (self, torrents[i], FALSE);
1367
1368  tr_free (torrents);
1369  tr_ctorFree (ctor);
1370}
1371
1372void
1373gtr_core_clear (TrCore * self)
1374{
1375  gtk_list_store_clear (GTK_LIST_STORE (core_raw_model (self)));
1376}
1377
1378/***
1379****
1380***/
1381
1382static int
1383gtr_compare_double (const double a, const double b, int decimal_places)
1384{
1385  int ret;
1386  const int64_t ia = (int64_t)(a * pow (10, decimal_places));
1387  const int64_t ib = (int64_t)(b * pow (10, decimal_places));
1388
1389  if (ia < ib)
1390    ret = -1;
1391  else if (ia > ib)
1392    ret = 1;
1393  else
1394    ret = 0;
1395
1396  return ret;
1397}
1398
1399static void
1400update_foreach (GtkTreeModel * model, GtkTreeIter * iter)
1401{
1402  int oldActivity, newActivity;
1403  int oldActivePeerCount, newActivePeerCount;
1404  int oldError, newError;
1405  bool oldFinished, newFinished;
1406  int oldQueuePosition, newQueuePosition;
1407  tr_priority_t oldPriority, newPriority;
1408  unsigned int oldTrackers, newTrackers;
1409  double oldUpSpeed, newUpSpeed;
1410  double oldDownSpeed, newDownSpeed;
1411  double oldRecheckProgress, newRecheckProgress;
1412  gboolean oldActive, newActive;
1413  const tr_stat * st;
1414  tr_torrent * tor;
1415
1416  /* get the old states */
1417  gtk_tree_model_get (model, iter,
1418                      MC_TORRENT,  &tor,
1419                      MC_ACTIVE, &oldActive,
1420                      MC_ACTIVE_PEER_COUNT, &oldActivePeerCount,
1421                      MC_ERROR, &oldError,
1422                      MC_ACTIVITY, &oldActivity,
1423                      MC_FINISHED, &oldFinished,
1424                      MC_PRIORITY, &oldPriority,
1425                      MC_QUEUE_POSITION, &oldQueuePosition,
1426                      MC_TRACKERS, &oldTrackers,
1427                      MC_SPEED_UP, &oldUpSpeed,
1428                      MC_RECHECK_PROGRESS, &oldRecheckProgress,
1429                      MC_SPEED_DOWN, &oldDownSpeed,
1430                      -1);
1431
1432  /* get the new states */
1433  st = tr_torrentStat (tor);
1434  newActive = is_torrent_active (st);
1435  newActivity = st->activity;
1436  newFinished = st->finished;
1437  newPriority = tr_torrentGetPriority (tor);
1438  newQueuePosition = st->queuePosition;
1439  newTrackers = build_torrent_trackers_hash (tor);
1440  newUpSpeed = st->pieceUploadSpeed_KBps;
1441  newDownSpeed = st->pieceDownloadSpeed_KBps;
1442  newRecheckProgress = st->recheckProgress;
1443  newActivePeerCount = st->peersSendingToUs + st->peersGettingFromUs + st->webseedsSendingToUs;
1444  newError = st->error;
1445
1446  /* updating the model triggers off resort/refresh,
1447     so don't do it unless something's actually changed... */
1448  if ((newActive != oldActive)
1449        || (newActivity  != oldActivity)
1450        || (newFinished != oldFinished)
1451        || (newPriority != oldPriority)
1452        || (newQueuePosition != oldQueuePosition)
1453        || (newError != oldError)
1454        || (newActivePeerCount != oldActivePeerCount)
1455        || (newTrackers != oldTrackers)
1456        || gtr_compare_double (newUpSpeed, oldUpSpeed, 2)
1457        || gtr_compare_double (newDownSpeed, oldDownSpeed, 2)
1458        || gtr_compare_double (newRecheckProgress, oldRecheckProgress, 2))
1459    {
1460      gtk_list_store_set (GTK_LIST_STORE (model), iter,
1461                          MC_ACTIVE, newActive,
1462                          MC_ACTIVE_PEER_COUNT, newActivePeerCount,
1463                          MC_ERROR, newError,
1464                          MC_ACTIVITY, newActivity,
1465                          MC_FINISHED, newFinished,
1466                          MC_PRIORITY, newPriority,
1467                          MC_QUEUE_POSITION, newQueuePosition,
1468                          MC_TRACKERS, newTrackers,
1469                          MC_SPEED_UP, newUpSpeed,
1470                          MC_SPEED_DOWN, newDownSpeed,
1471                          MC_RECHECK_PROGRESS, newRecheckProgress,
1472                          -1);
1473    }
1474}
1475
1476void
1477gtr_core_update (TrCore * core)
1478{
1479  GtkTreeIter iter;
1480  GtkTreeModel * model;
1481
1482  /* update the model */
1483  model = core_raw_model (core);
1484  if (gtk_tree_model_iter_nth_child (model, &iter, NULL, 0)) do
1485    update_foreach (model, &iter);
1486  while (gtk_tree_model_iter_next (model, &iter));
1487
1488  /* update hibernation */
1489  core_maybe_inhibit_hibernation (core);
1490}
1491
1492/**
1493***  Hibernate
1494**/
1495
1496#define SESSION_MANAGER_SERVICE_NAME  "org.gnome.SessionManager"
1497#define SESSION_MANAGER_INTERFACE     "org.gnome.SessionManager"
1498#define SESSION_MANAGER_OBJECT_PATH   "/org/gnome/SessionManager"
1499
1500static gboolean
1501gtr_inhibit_hibernation (guint * cookie)
1502{
1503  gboolean success;
1504  GVariant * response;
1505  GDBusConnection * connection;
1506  GError * err = NULL;
1507  const char * application = "Transmission BitTorrent Client";
1508  const char * reason = "BitTorrent Activity";
1509  const int toplevel_xid = 0;
1510  const int flags = 4; /* Inhibit suspending the session or computer */
1511
1512  connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &err);
1513
1514  response = g_dbus_connection_call_sync (connection,
1515                                          SESSION_MANAGER_SERVICE_NAME,
1516                                          SESSION_MANAGER_OBJECT_PATH,
1517                                          SESSION_MANAGER_INTERFACE,
1518                                          "Inhibit",
1519                                          g_variant_new ("(susu)", application, toplevel_xid, reason, flags),
1520                                          NULL, G_DBUS_CALL_FLAGS_NONE,
1521                                          1000, NULL, &err);
1522
1523  if (response != NULL)
1524    *cookie = g_variant_get_uint32 (g_variant_get_child_value (response, 0));
1525
1526  success = (response != NULL) && (err == NULL);
1527
1528  /* logging */
1529  if (success)
1530    {
1531      tr_inf ("%s", _("Inhibiting desktop hibernation"));
1532    }
1533  else
1534    {
1535      tr_err (_("Couldn't inhibit desktop hibernation: %s"), err->message);
1536      g_error_free (err);
1537    }
1538
1539  /* cleanup */
1540  if (response != NULL)
1541    g_variant_unref (response);
1542  if (connection != NULL)
1543    g_object_unref (connection);
1544
1545  return success;
1546}
1547
1548static void
1549gtr_uninhibit_hibernation (guint inhibit_cookie)
1550{
1551  GVariant * response;
1552  GDBusConnection * connection;
1553  GError * err = NULL;
1554
1555  connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &err);
1556
1557  response = g_dbus_connection_call_sync (connection,
1558                                          SESSION_MANAGER_SERVICE_NAME,
1559                                          SESSION_MANAGER_OBJECT_PATH,
1560                                          SESSION_MANAGER_INTERFACE,
1561                                          "Uninhibit",
1562                                          g_variant_new ("(u)", inhibit_cookie),
1563                                          NULL, G_DBUS_CALL_FLAGS_NONE,
1564                                          1000, NULL, &err);
1565
1566  /* logging */
1567  if (err == NULL)
1568    {
1569      tr_inf ("%s", _("Allowing desktop hibernation"));
1570    }
1571  else
1572    {
1573      g_warning ("Couldn't uninhibit desktop hibernation: %s.", err->message);
1574      g_error_free (err);
1575    }
1576
1577  /* cleanup */
1578  g_variant_unref (response);
1579  g_object_unref (connection);
1580}
1581
1582static void
1583gtr_core_set_hibernation_allowed (TrCore * core, gboolean allowed)
1584{
1585  g_return_if_fail (core);
1586  g_return_if_fail (core->priv);
1587
1588  core->priv->inhibit_allowed = allowed != 0;
1589
1590  if (allowed && core->priv->have_inhibit_cookie)
1591    {
1592      gtr_uninhibit_hibernation (core->priv->inhibit_cookie);
1593      core->priv->have_inhibit_cookie = FALSE;
1594    }
1595
1596  if (!allowed && !core->priv->have_inhibit_cookie
1597               && !core->priv->dbus_error)
1598    {
1599      if (gtr_inhibit_hibernation (&core->priv->inhibit_cookie))
1600        core->priv->have_inhibit_cookie = TRUE;
1601      else
1602        core->priv->dbus_error = TRUE;
1603    }
1604}
1605
1606static void
1607core_maybe_inhibit_hibernation (TrCore * core)
1608{
1609  /* hibernation is allowed if EITHER
1610   * (a) the "inhibit" pref is turned off OR
1611   * (b) there aren't any active torrents */
1612  const gboolean hibernation_allowed = !gtr_pref_flag_get (TR_KEY_inhibit_desktop_hibernation)
1613                                    || !gtr_core_get_active_torrent_count (core);
1614  gtr_core_set_hibernation_allowed (core, hibernation_allowed);
1615}
1616
1617/**
1618***  Prefs
1619**/
1620
1621static void
1622core_commit_prefs_change (TrCore * core, const tr_quark key)
1623{
1624  gtr_core_pref_changed (core, key);
1625  gtr_pref_save (gtr_core_session (core));
1626}
1627
1628void
1629gtr_core_set_pref (TrCore * self, const tr_quark key, const char * newval)
1630{
1631  if (tr_strcmp0 (newval, gtr_pref_string_get (key)))
1632    {
1633      gtr_pref_string_set (key, newval);
1634      core_commit_prefs_change (self, key);
1635    }
1636}
1637
1638void
1639gtr_core_set_pref_bool (TrCore * self, const tr_quark key, gboolean newval)
1640{
1641  if (newval != gtr_pref_flag_get (key))
1642    {
1643      gtr_pref_flag_set (key, newval);
1644      core_commit_prefs_change (self, key);
1645    }
1646}
1647
1648void
1649gtr_core_set_pref_int (TrCore * self, const tr_quark key, int newval)
1650{
1651  if (newval != gtr_pref_int_get (key))
1652    {
1653      gtr_pref_int_set (key, newval);
1654      core_commit_prefs_change (self, key);
1655    }
1656}
1657
1658void
1659gtr_core_set_pref_double (TrCore * self, const tr_quark key, double newval)
1660{
1661  if (gtr_compare_double (newval, gtr_pref_double_get (key), 4))
1662    {
1663      gtr_pref_double_set (key, newval);
1664      core_commit_prefs_change (self, key);
1665    }
1666}
1667
1668/***
1669****
1670****  RPC Interface
1671****
1672***/
1673
1674/* #define DEBUG_RPC */
1675
1676static int nextTag = 1;
1677
1678typedef void (server_response_func)(TrCore * core, tr_variant * response, gpointer user_data);
1679
1680struct pending_request_data
1681{
1682  TrCore * core;
1683  server_response_func * response_func;
1684  gpointer response_func_user_data;
1685};
1686
1687static GHashTable * pendingRequests = NULL;
1688
1689static gboolean
1690core_read_rpc_response_idle (void * vresponse)
1691{
1692  tr_variant top;
1693  int64_t intVal;
1694  struct evbuffer * response = vresponse;
1695
1696  tr_variantFromJson (&top, evbuffer_pullup (response, -1), evbuffer_get_length (response));
1697
1698  if (tr_variantDictFindInt (&top, TR_KEY_tag, &intVal))
1699    {
1700      const int tag = (int)intVal;
1701      struct pending_request_data * data = g_hash_table_lookup (pendingRequests, &tag);
1702      if (data)
1703        {
1704          if (data->response_func)
1705            (*data->response_func)(data->core, &top, data->response_func_user_data);
1706          g_hash_table_remove (pendingRequests, &tag);
1707        }
1708    }
1709
1710  tr_variantFree (&top);
1711  evbuffer_free (response);
1712  return FALSE;
1713}
1714
1715static void
1716core_read_rpc_response (tr_session       * session UNUSED,
1717                        struct evbuffer  * response,
1718                        void             * unused UNUSED)
1719{
1720  struct evbuffer * buf = evbuffer_new ();
1721  evbuffer_add_buffer (buf, response);
1722  gdk_threads_add_idle (core_read_rpc_response_idle, buf);
1723}
1724
1725static void
1726core_send_rpc_request (TrCore * core, const char * json, int tag,
1727                       server_response_func * response_func,
1728                       void * response_func_user_data)
1729{
1730  tr_session * session = gtr_core_session (core);
1731
1732  if (pendingRequests == NULL)
1733    {
1734      pendingRequests = g_hash_table_new_full (g_int_hash, g_int_equal, g_free, g_free);
1735    }
1736
1737  if (session == NULL)
1738    {
1739      g_error ("GTK+ client doesn't support connections to remote servers yet.");
1740    }
1741  else
1742    {
1743      /* remember this request */
1744      struct pending_request_data * data;
1745      data = g_new0 (struct pending_request_data, 1);
1746      data->core = core;
1747      data->response_func = response_func;
1748      data->response_func_user_data = response_func_user_data;
1749      g_hash_table_insert (pendingRequests, g_memdup (&tag, sizeof (int)), data);
1750
1751      /* make the request */
1752#ifdef DEBUG_RPC
1753      g_message ("request: [%s]", json);
1754#endif
1755      tr_rpc_request_exec_json (session, json, strlen (json), core_read_rpc_response, GINT_TO_POINTER (tag));
1756    }
1757}
1758
1759/***
1760****  Sending a test-port request via RPC
1761***/
1762
1763static void
1764on_port_test_response (TrCore * core, tr_variant * response, gpointer u UNUSED)
1765{
1766  tr_variant * args;
1767  bool is_open = FALSE;
1768
1769  if (tr_variantDictFindDict (response, TR_KEY_arguments, &args))
1770    tr_variantDictFindBool (args, TR_KEY_port_is_open, &is_open);
1771
1772  core_emit_port_tested (core, is_open);
1773}
1774
1775void
1776gtr_core_port_test (TrCore * core)
1777{
1778  char buf[64];
1779  const int tag = nextTag++;
1780  g_snprintf (buf, sizeof (buf), "{ \"method\": \"port-test\", \"tag\": %d }", tag);
1781  core_send_rpc_request (core, buf, tag, on_port_test_response, NULL);
1782}
1783
1784/***
1785****  Updating a blocklist via RPC
1786***/
1787
1788static void
1789on_blocklist_response (TrCore * core, tr_variant * response, gpointer data UNUSED)
1790{
1791  tr_variant * args;
1792  int64_t ruleCount = -1;
1793
1794  if (tr_variantDictFindDict (response, TR_KEY_arguments, &args))
1795    tr_variantDictFindInt (args, TR_KEY_blocklist_size, &ruleCount);
1796
1797  if (ruleCount > 0)
1798    gtr_pref_int_set (TR_KEY_blocklist_date, tr_time ());
1799
1800  core_emit_blocklist_udpated (core, ruleCount);
1801}
1802
1803void
1804gtr_core_blocklist_update (TrCore * core)
1805{
1806  char buf[64];
1807  const int tag = nextTag++;
1808  g_snprintf (buf, sizeof (buf), "{ \"method\": \"blocklist-update\", \"tag\": %d }", tag);
1809  core_send_rpc_request (core, buf, tag, on_blocklist_response, NULL);
1810}
1811
1812/***
1813****
1814***/
1815
1816void
1817gtr_core_exec_json (TrCore * core, const char * json)
1818{
1819  const int tag = nextTag++;
1820  core_send_rpc_request (core, json, tag, NULL, NULL);
1821}
1822
1823void
1824gtr_core_exec (TrCore * core, const tr_variant * top)
1825{
1826  char * json = tr_variantToStr (top, TR_VARIANT_FMT_JSON_LEAN, NULL);
1827  gtr_core_exec_json (core, json);
1828  tr_free (json);
1829}
1830
1831/***
1832****
1833***/
1834
1835size_t
1836gtr_core_get_torrent_count (TrCore * core)
1837{
1838  return gtk_tree_model_iter_n_children (core_raw_model (core), NULL);
1839}
1840
1841size_t
1842gtr_core_get_active_torrent_count (TrCore * core)
1843{
1844  GtkTreeIter iter;
1845  size_t activeCount = 0;
1846  GtkTreeModel * model = core_raw_model (core);
1847
1848  if (gtk_tree_model_iter_nth_child (model, &iter, NULL, 0)) do
1849    {
1850      int activity;
1851      gtk_tree_model_get (model, &iter, MC_ACTIVITY, &activity, -1);
1852
1853      if (activity != TR_STATUS_STOPPED)
1854        ++activeCount;
1855    }
1856  while (gtk_tree_model_iter_next (model, &iter));
1857
1858  return activeCount;
1859}
1860
1861tr_torrent *
1862gtr_core_find_torrent (TrCore * core, int id)
1863{
1864  tr_session * session;
1865  tr_torrent * tor = NULL;
1866
1867  if ((session = gtr_core_session (core)))
1868    tor = tr_torrentFindFromId (session, id);
1869
1870  return tor;
1871}
1872
1873void
1874gtr_core_open_folder (TrCore * core, int torrent_id)
1875{
1876  const tr_torrent * tor = gtr_core_find_torrent (core, torrent_id);
1877
1878  if (tor != NULL)
1879    {
1880      const gboolean single = tr_torrentInfo (tor)->fileCount == 1;
1881      const char * currentDir = tr_torrentGetCurrentDir (tor);
1882
1883      if (single)
1884        {
1885          gtr_open_file (currentDir);
1886        }
1887      else
1888        {
1889          char * path = g_build_filename (currentDir, tr_torrentName (tor), NULL);
1890          gtr_open_file (path);
1891          g_free (path);
1892        }
1893    }
1894}
Note: See TracBrowser for help on using the repository browser.