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

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

(gtk) silence a couple of potential GFileInfo console warnings

  • Property svn:keywords set to Date Rev Author Id
File size: 50.0 KB
Line 
1/******************************************************************************
2 * $Id: tr-core.c 13811 2013-01-20 04:10:47Z 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  GFileInfo * info;
650  time_t mtime = 0;
651
652  info = g_file_query_info (file, G_FILE_ATTRIBUTE_TIME_MODIFIED, 0, NULL, NULL);
653  if (info != NULL)
654    {
655      mtime = g_file_info_get_attribute_uint64 (info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
656      g_object_unref (G_OBJECT (info));
657    }
658
659  return mtime;
660}
661
662static void
663rename_torrent_and_unref_file (GFile * file)
664{
665  GFileInfo * info;
666
667  info = g_file_query_info (file, G_FILE_ATTRIBUTE_STANDARD_EDIT_NAME, 0, NULL, NULL);
668  if (info != NULL)
669    {
670      GError * error;
671      const char * old_name;
672      char * new_name;
673      GFile * new_file;
674
675      old_name = g_file_info_get_attribute_string (info, G_FILE_ATTRIBUTE_STANDARD_EDIT_NAME);
676      new_name = g_strdup_printf ("%s.added", old_name);
677      new_file = g_file_set_display_name (file, new_name, NULL, &error);
678
679      if (error != NULL)
680        {
681          g_message ("Unable to rename \"%s\" as \"%s\": %s", old_name, new_name, error->message);
682          g_error_free (error);
683        }
684
685      if (new_file != NULL)
686        g_object_unref (G_OBJECT (new_file));
687      g_free (new_name);
688      g_object_unref (G_OBJECT(info));
689    }
690
691  g_object_unref (G_OBJECT(file));
692}
693
694static gboolean
695core_watchdir_idle (gpointer gcore)
696{
697  GSList * l;
698  GSList * changing = NULL;
699  GSList * unchanging = NULL;
700  TrCore * core = TR_CORE (gcore);
701  const time_t now = tr_time ();
702  struct TrCorePrivate * p = core->priv;
703
704  /* separate the files into two lists: changing and unchanging */
705  for (l=p->monitor_files; l!=NULL; l=l->next)
706    {
707      GFile * file = l->data;
708      const time_t mtime = get_file_mtime (file);
709      if (mtime + 2 >= now)
710        changing = g_slist_prepend (changing, file);
711      else
712        unchanging = g_slist_prepend (unchanging, file);
713    }
714
715  /* add the files that have stopped changing */
716  if (unchanging != NULL)
717    {
718      const gboolean do_start = gtr_pref_flag_get (TR_KEY_start_added_torrents);
719      const gboolean do_prompt = gtr_pref_flag_get (TR_KEY_show_options_window);
720
721      core->priv->adding_from_watch_dir = TRUE;
722      gtr_core_add_files (core, unchanging, do_start, do_prompt, TRUE);
723      g_slist_foreach (unchanging, (GFunc)rename_torrent_and_unref_file, NULL);
724      g_slist_free (unchanging);
725      core->priv->adding_from_watch_dir = FALSE;
726    }
727
728  /* keep monitoring the ones that are still changing */
729  g_slist_free (p->monitor_files);
730  p->monitor_files = changing;
731
732  /* if monitor_files is nonempty, keep checking every second */
733  if (core->priv->monitor_files)
734    return G_SOURCE_CONTINUE;
735  core->priv->monitor_idle_tag = 0;
736  return G_SOURCE_REMOVE;
737}
738
739/* If this file is a torrent, add it to our list */
740static void
741core_watchdir_monitor_file (TrCore * core, GFile * file)
742{
743  char * filename = g_file_get_path (file);
744  const gboolean is_torrent = g_str_has_suffix (filename, ".torrent");
745
746  if (is_torrent)
747    {
748      GSList * l;
749      struct TrCorePrivate * p = core->priv;
750
751      /* if we're not already watching this file, start watching it now */
752      for (l=p->monitor_files; l!=NULL; l=l->next)
753        if (g_file_equal (file, l->data))
754          break;
755
756      if (l == NULL)
757        {
758          g_object_ref (file);
759          p->monitor_files = g_slist_prepend (p->monitor_files, file);
760          if (p->monitor_idle_tag == 0)
761            p->monitor_idle_tag = gdk_threads_add_timeout_seconds (1, core_watchdir_idle, core);
762        }
763    }
764
765  g_free (filename);
766}
767
768/* GFileMonitor noticed a file was created */
769static void
770on_file_changed_in_watchdir (GFileMonitor       * monitor UNUSED,
771                             GFile              * file,
772                             GFile              * other_type UNUSED,
773                             GFileMonitorEvent    event_type,
774                             gpointer             core)
775{
776  if (event_type == G_FILE_MONITOR_EVENT_CREATED)
777    core_watchdir_monitor_file (core, file);
778}
779
780/* walk through the pre-existing files in the watchdir */
781static void
782core_watchdir_scan (TrCore * core)
783{
784  const char * dirname = gtr_pref_string_get (TR_KEY_watch_dir);
785  GDir * dir = g_dir_open (dirname, 0, NULL);
786
787  if (dir != NULL)
788    {
789      const char * name;
790      while ((name = g_dir_read_name (dir)))
791        {
792          char * filename = g_build_filename (dirname, name, NULL);
793          GFile * file = g_file_new_for_path (filename);
794          core_watchdir_monitor_file (core, file);
795          g_object_unref (file);
796          g_free (filename);
797        }
798
799      g_dir_close (dir);
800    }
801}
802
803static void
804core_watchdir_update (TrCore * core)
805{
806  const gboolean is_enabled = gtr_pref_flag_get (TR_KEY_watch_dir_enabled);
807  GFile * dir = g_file_new_for_path (gtr_pref_string_get (TR_KEY_watch_dir));
808  struct TrCorePrivate * p = core->priv;
809
810  if (p->monitor && (!is_enabled || !g_file_equal (dir, p->monitor_dir)))
811    {
812      g_signal_handler_disconnect (p->monitor, p->monitor_tag);
813      g_file_monitor_cancel (p->monitor);
814      g_object_unref (p->monitor);
815      g_object_unref (p->monitor_dir);
816
817      p->monitor_dir = NULL;
818      p->monitor = NULL;
819      p->monitor_tag = 0;
820    }
821
822    if (is_enabled && !p->monitor)
823    {
824      GFileMonitor * m = g_file_monitor_directory (dir, 0, NULL, NULL);
825      core_watchdir_scan (core);
826
827      g_object_ref (dir);
828      p->monitor = m;
829      p->monitor_dir = dir;
830      p->monitor_tag = g_signal_connect (m, "changed",
831                                         G_CALLBACK (on_file_changed_in_watchdir), core);
832    }
833
834  g_object_unref (dir);
835}
836
837/***
838****
839***/
840
841static void
842on_pref_changed (TrCore * core, const tr_quark key, gpointer data UNUSED)
843{
844  switch (key)
845    {
846      case TR_KEY_sort_mode:
847      case TR_KEY_sort_reversed:
848        {
849          const char * mode = gtr_pref_string_get (TR_KEY_sort_mode);
850          const gboolean is_reversed = gtr_pref_flag_get (TR_KEY_sort_reversed);
851          core_set_sort_mode (core, mode, is_reversed);
852          break;
853        }
854
855      case TR_KEY_peer_limit_global:
856        tr_sessionSetPeerLimit (gtr_core_session (core), gtr_pref_int_get (key));
857        break;
858
859      case TR_KEY_peer_limit_per_torrent:
860        tr_sessionSetPeerLimitPerTorrent (gtr_core_session (core), gtr_pref_int_get (key));
861        break;
862
863      case TR_KEY_inhibit_desktop_hibernation:
864        core_maybe_inhibit_hibernation (core);
865        break;
866
867      case TR_KEY_watch_dir:
868      case TR_KEY_watch_dir_enabled:
869        core_watchdir_update (core);
870        break;
871
872      default:
873        break;
874    }
875}
876
877/**
878***
879**/
880
881TrCore *
882gtr_core_new (tr_session * session)
883{
884  TrCore * core = TR_CORE (g_object_new (TR_CORE_TYPE, NULL));
885
886  core->priv->session = session;
887
888  /* init from prefs & listen to pref changes */
889  on_pref_changed (core, TR_KEY_sort_mode, NULL);
890  on_pref_changed (core, TR_KEY_sort_reversed, NULL);
891  on_pref_changed (core, TR_KEY_watch_dir_enabled, NULL);
892  on_pref_changed (core, TR_KEY_peer_limit_global, NULL);
893  on_pref_changed (core, TR_KEY_inhibit_desktop_hibernation, NULL);
894  g_signal_connect (core, "prefs-changed", G_CALLBACK (on_pref_changed), NULL);
895
896  return core;
897}
898
899tr_session *
900gtr_core_close (TrCore * core)
901{
902  tr_session * session = gtr_core_session (core);
903
904  if (session)
905    {
906      core->priv->session = NULL;
907      gtr_pref_save (session);
908    }
909
910  return session;
911}
912
913/***
914****  COMPLETENESS CALLBACK
915***/
916
917struct notify_callback_data
918{
919  TrCore * core;
920  int torrent_id;
921};
922
923static gboolean
924on_torrent_completeness_changed_idle (gpointer gdata)
925{
926  struct notify_callback_data * data = gdata;
927  gtr_notify_torrent_completed (data->core, data->torrent_id);
928  g_object_unref (G_OBJECT (data->core));
929  g_free (data);
930  return FALSE;
931}
932
933/* this is called in the libtransmission thread, *NOT* the GTK+ thread,
934   so delegate to the GTK+ thread before calling notify's dbus code... */
935static void
936on_torrent_completeness_changed (tr_torrent       * tor,
937                                 tr_completeness    completeness,
938                                 bool               was_running,
939                                 void             * gcore)
940{
941  if (was_running && (completeness != TR_LEECH) && (tr_torrentStat (tor)->sizeWhenDone != 0))
942    {
943      struct notify_callback_data * data = g_new (struct notify_callback_data, 1);
944      data->core = gcore;
945      data->torrent_id = tr_torrentId (tor);
946      g_object_ref (G_OBJECT (data->core));
947      gdk_threads_add_idle (on_torrent_completeness_changed_idle, data);
948    }
949}
950
951/***
952****  METADATA CALLBACK
953***/
954
955static const char*
956get_collated_name (TrCore * core, const tr_torrent * tor)
957{
958  char buf[2048];
959  const char * name = tr_torrentName (tor);
960  char * down = g_utf8_strdown (name ? name : "", -1);
961  const tr_info * inf = tr_torrentInfo (tor);
962  g_snprintf (buf, sizeof (buf), "%s\t%s", down, inf->hashString);
963  g_free (down);
964  return g_string_chunk_insert_const (core->priv->string_chunk, buf);
965}
966
967struct metadata_callback_data
968{
969  TrCore * core;
970  int torrent_id;
971};
972
973static gboolean
974find_row_from_torrent_id (GtkTreeModel * model, int id, GtkTreeIter * setme)
975{
976  GtkTreeIter iter;
977  gboolean match = FALSE;
978
979  if (gtk_tree_model_iter_children (model, &iter, NULL)) do
980    {
981      int row_id;
982      gtk_tree_model_get (model, &iter, MC_TORRENT_ID, &row_id, -1);
983      match = id == row_id;
984    }
985  while (!match && gtk_tree_model_iter_next (model, &iter));
986
987  if (match)
988    *setme = iter;
989
990  return match;
991}
992
993static gboolean
994on_torrent_metadata_changed_idle (gpointer gdata)
995{
996  struct notify_callback_data * data = gdata;
997  tr_session * session = gtr_core_session (data->core);
998  tr_torrent * tor = tr_torrentFindFromId (session, data->torrent_id);
999
1000  /* update the torrent's collated name */
1001  if (tor != NULL)
1002    {
1003      GtkTreeIter iter;
1004      GtkTreeModel * model = core_raw_model (data->core);
1005      if (find_row_from_torrent_id (model, data->torrent_id, &iter))
1006        {
1007          const char * collated = get_collated_name (data->core, tor);
1008          GtkListStore * store = GTK_LIST_STORE (model);
1009          gtk_list_store_set (store, &iter, MC_NAME_COLLATED, collated, -1);
1010        }
1011    }
1012
1013  /* cleanup */
1014  g_object_unref (G_OBJECT (data->core));
1015  g_free (data);
1016  return FALSE;
1017}
1018
1019/* this is called in the libtransmission thread, *NOT* the GTK+ thread,
1020   so delegate to the GTK+ thread before changing our list store... */
1021static void
1022on_torrent_metadata_changed (tr_torrent * tor, void * gcore)
1023{
1024  struct notify_callback_data * data = g_new (struct notify_callback_data, 1);
1025  data->core = gcore;
1026  data->torrent_id = tr_torrentId (tor);
1027  g_object_ref (G_OBJECT (data->core));
1028  gdk_threads_add_idle (on_torrent_metadata_changed_idle, data);
1029}
1030
1031/***
1032****
1033****  ADDING TORRENTS
1034****
1035***/
1036
1037static unsigned int
1038build_torrent_trackers_hash (tr_torrent * tor)
1039{
1040  unsigned int i;
1041  const char * pch;
1042  uint64_t hash = 0;
1043  const tr_info * const inf = tr_torrentInfo (tor);
1044
1045  for (i=0; i<inf->trackerCount; ++i)
1046    for (pch=inf->trackers[i].announce; *pch; ++pch)
1047      hash = (hash<<4) ^ (hash>>28) ^ *pch;
1048
1049  return hash;
1050}
1051
1052static gboolean
1053is_torrent_active (const tr_stat * st)
1054{
1055  return (st->peersSendingToUs > 0)
1056      || (st->peersGettingFromUs > 0)
1057      || (st->activity == TR_STATUS_CHECK);
1058}
1059
1060void
1061gtr_core_add_torrent (TrCore * core, tr_torrent * tor, gboolean do_notify)
1062{
1063  if (tor != NULL)
1064    {
1065      GtkTreeIter unused;
1066      const tr_stat * st = tr_torrentStat (tor);
1067      const char * collated = get_collated_name (core, tor);
1068      const unsigned int trackers_hash = build_torrent_trackers_hash (tor);
1069      GtkListStore * store = GTK_LIST_STORE (core_raw_model (core));
1070
1071      gtk_list_store_insert_with_values (store, &unused, 0,
1072        MC_NAME_COLLATED,     collated,
1073        MC_TORRENT,           tor,
1074        MC_TORRENT_ID,        tr_torrentId (tor),
1075        MC_SPEED_UP,          st->pieceUploadSpeed_KBps,
1076        MC_SPEED_DOWN,        st->pieceDownloadSpeed_KBps,
1077        MC_RECHECK_PROGRESS,  st->recheckProgress,
1078        MC_ACTIVE,            is_torrent_active (st),
1079        MC_ACTIVITY,          st->activity,
1080        MC_FINISHED,          st->finished,
1081        MC_PRIORITY,          tr_torrentGetPriority (tor),
1082        MC_QUEUE_POSITION,    st->queuePosition,
1083        MC_TRACKERS,          trackers_hash,
1084        -1);
1085
1086      if (do_notify)
1087        gtr_notify_torrent_added (tr_torrentName (tor));
1088
1089      tr_torrentSetMetadataCallback (tor, on_torrent_metadata_changed, core);
1090      tr_torrentSetCompletenessCallback (tor, on_torrent_completeness_changed, core);
1091    }
1092}
1093
1094static tr_torrent *
1095core_create_new_torrent (TrCore * core, tr_ctor * ctor)
1096{
1097  int errcode = 0;
1098  tr_torrent * tor;
1099  bool do_trash = false;
1100  tr_session * session = gtr_core_session (core);
1101
1102  /* let the gtk client handle the removal, since libT
1103   * doesn't have any concept of the glib trash API */
1104  tr_ctorGetDeleteSource (ctor, &do_trash);
1105  tr_ctorSetDeleteSource (ctor, FALSE);
1106  tor = tr_torrentNew (ctor, &errcode);
1107
1108  if (tor && do_trash)
1109    {
1110      const char * config = tr_sessionGetConfigDir (session);
1111      const char * source = tr_ctorGetSourceFile (ctor);
1112
1113      if (source != NULL)
1114        {
1115          /* #1294: don't delete the .torrent file if it's our internal copy */
1116          const int is_internal = (strstr (source, config) == source);
1117          if (!is_internal)
1118              gtr_file_trash_or_remove (source);
1119        }
1120    }
1121
1122    return tor;
1123}
1124
1125static int
1126core_add_ctor (TrCore * core, tr_ctor * ctor,
1127               gboolean do_prompt, gboolean do_notify)
1128{
1129  tr_info inf;
1130  int err = tr_torrentParse (ctor, &inf);
1131
1132  switch (err)
1133    {
1134      case TR_PARSE_ERR:
1135        break;
1136
1137      case TR_PARSE_DUPLICATE:
1138        /* don't complain about .torrent files in the watch directory
1139         * that have already been added... that gets annoying and we
1140         * don't want to be nagging users to clean up their watch dirs */
1141        if (!tr_ctorGetSourceFile (ctor) || !core->priv->adding_from_watch_dir)
1142          core_emit_err (core, err, inf.name);
1143        tr_metainfoFree (&inf);
1144        tr_ctorFree (ctor);
1145        break;
1146
1147      default:
1148        if (do_prompt)
1149          {
1150            g_signal_emit (core, signals[ADD_PROMPT_SIGNAL], 0, ctor);
1151          }
1152        else
1153          {
1154            gtr_core_add_torrent (core, core_create_new_torrent (core, ctor), do_notify);
1155            tr_ctorFree (ctor);
1156          }
1157        tr_metainfoFree (&inf);
1158        break;
1159    }
1160
1161  return err;
1162}
1163
1164static void
1165core_apply_defaults (tr_ctor * ctor)
1166{
1167  if (tr_ctorGetPaused (ctor, TR_FORCE, NULL))
1168    tr_ctorSetPaused (ctor, TR_FORCE, !gtr_pref_flag_get (TR_KEY_start_added_torrents));
1169
1170  if (tr_ctorGetDeleteSource (ctor, NULL))
1171    tr_ctorSetDeleteSource (ctor, gtr_pref_flag_get (TR_KEY_trash_original_torrent_files));
1172
1173  if (tr_ctorGetPeerLimit (ctor, TR_FORCE, NULL))
1174    tr_ctorSetPeerLimit (ctor, TR_FORCE, gtr_pref_int_get (TR_KEY_peer_limit_per_torrent));
1175
1176  if (tr_ctorGetDownloadDir (ctor, TR_FORCE, NULL))
1177    tr_ctorSetDownloadDir (ctor, TR_FORCE, gtr_pref_string_get (TR_KEY_download_dir));
1178}
1179
1180void
1181gtr_core_add_ctor (TrCore * core, tr_ctor * ctor)
1182{
1183  const gboolean do_notify = FALSE;
1184  const gboolean do_prompt = gtr_pref_flag_get (TR_KEY_show_options_window);
1185  core_apply_defaults (ctor);
1186  core_add_ctor (core, ctor, do_prompt, do_notify);
1187}
1188
1189/***
1190****
1191***/
1192
1193struct add_from_url_data
1194{
1195  TrCore * core;
1196  tr_ctor * ctor;
1197  bool do_prompt;
1198  bool do_notify;
1199};
1200
1201static void
1202add_file_async_callback (GObject * file, GAsyncResult * result, gpointer gdata)
1203{
1204  gsize length;
1205  char * contents;
1206  GError * error = NULL;
1207  struct add_from_url_data * data = gdata;
1208
1209  if (!g_file_load_contents_finish (G_FILE (file), result, &contents, &length, NULL, &error))
1210    {
1211      g_message (_("Couldn't read \"%s\": %s"), g_file_get_parse_name (G_FILE (file)), error->message);
1212      g_error_free (error);
1213    }
1214  else if (!tr_ctorSetMetainfo (data->ctor, (const uint8_t*)contents, length))
1215    {
1216      core_add_ctor (data->core, data->ctor, data->do_prompt, data->do_notify);
1217    }
1218  else
1219    {
1220      tr_ctorFree (data->ctor);
1221    }
1222
1223  core_dec_busy (data->core);
1224  g_free (data);
1225}
1226
1227static bool
1228add_file (TrCore      * core,
1229          GFile       * file,
1230          gboolean      do_start,
1231          gboolean      do_prompt,
1232          gboolean      do_notify)
1233{
1234  bool handled = false;
1235  tr_session * session = gtr_core_session (core);
1236
1237  if (session != NULL)
1238    {
1239      tr_ctor * ctor;
1240      bool tried = false;
1241      bool loaded = false;
1242
1243      ctor = tr_ctorNew (session);
1244      core_apply_defaults (ctor);
1245      tr_ctorSetPaused (ctor, TR_FORCE, !do_start);
1246
1247      /* local files... */
1248      if (!tried)
1249        {
1250          char * str = g_file_get_path (file);
1251          if ((tried = g_file_test (str, G_FILE_TEST_EXISTS)))
1252             loaded = !tr_ctorSetMetainfoFromFile (ctor, str);
1253          g_free (str);
1254        }
1255
1256      /* magnet links... */
1257      if (!tried && g_file_has_uri_scheme (file, "magnet"))
1258        {
1259          /* GFile mangles the original string with /// so we have to un-mangle */
1260          char * str = g_file_get_parse_name (file);
1261          char * magnet = g_strdup_printf ("magnet:%s", strchr (str, '?'));
1262          tried = true;
1263          loaded = !tr_ctorSetMetainfoFromMagnetLink (ctor, magnet);
1264          g_free (magnet);
1265          g_free (str);
1266        }
1267
1268      /* hashcodes that we can turn into magnet links... */
1269      if (!tried)
1270        {
1271          char * str = g_file_get_basename (file);
1272          if (gtr_is_hex_hashcode (str))
1273            {
1274              char * magnet = g_strdup_printf ("magnet:?xt=urn:btih:%s", str);
1275              tried = true;
1276              loaded = !tr_ctorSetMetainfoFromMagnetLink (ctor, magnet);
1277              g_free (magnet);
1278            }
1279          g_free (str);
1280        }
1281
1282      /* if we were able to load the metainfo, add the torrent */
1283      if (loaded)
1284        {
1285          handled = true;
1286          core_add_ctor (core, ctor, do_prompt, do_notify);
1287        }
1288      else if (g_file_has_uri_scheme (file, "http") ||
1289               g_file_has_uri_scheme (file, "https") ||
1290               g_file_has_uri_scheme (file, "ftp"))
1291        {
1292          struct add_from_url_data * data;
1293
1294          data = g_new0 (struct add_from_url_data, 1);
1295          data->core = core;
1296          data->ctor = ctor;
1297          data->do_prompt = do_prompt;
1298          data->do_notify = do_notify;
1299
1300          handled = true;
1301          core_inc_busy (core);
1302          g_file_load_contents_async (file, NULL, add_file_async_callback, data);
1303        }
1304      else
1305        {
1306          tr_ctorFree (ctor);
1307          g_message (_("Skipping unknown torrent \"%s\""), g_file_get_parse_name (file));
1308        }
1309    }
1310
1311  return handled;
1312}
1313
1314bool
1315gtr_core_add_from_url (TrCore * core, const char * uri)
1316{
1317  bool handled;
1318  const bool do_start = gtr_pref_flag_get (TR_KEY_start_added_torrents);
1319  const bool do_prompt = gtr_pref_flag_get (TR_KEY_show_options_window);
1320  const bool do_notify = false;
1321
1322  GFile * file = g_file_new_for_uri (uri);
1323  handled = add_file (core, file, do_start, do_prompt, do_notify);
1324  g_object_unref (file);
1325  gtr_core_torrents_added (core);
1326
1327  return handled;
1328}
1329
1330void
1331gtr_core_add_files (TrCore     * core,
1332                    GSList     * files,
1333                    gboolean     do_start,
1334                    gboolean     do_prompt,
1335                    gboolean     do_notify)
1336{
1337  GSList * l;
1338
1339  for (l=files; l!=NULL; l=l->next)
1340    add_file (core, l->data, do_start, do_prompt, do_notify);
1341
1342  gtr_core_torrents_added (core);
1343}
1344
1345void
1346gtr_core_torrents_added (TrCore * self)
1347{
1348  gtr_core_update (self);
1349  core_emit_err (self, TR_CORE_ERR_NO_MORE_TORRENTS, NULL);
1350}
1351
1352void
1353gtr_core_remove_torrent (TrCore * core, int id, gboolean delete_local_data)
1354{
1355  tr_torrent * tor = gtr_core_find_torrent (core, id);
1356
1357  if (tor != NULL)
1358    {
1359      /* remove from the gui */
1360      GtkTreeIter iter;
1361      GtkTreeModel * model = core_raw_model (core);
1362      if (find_row_from_torrent_id (model, id, &iter))
1363        gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
1364
1365      /* remove the torrent */
1366      tr_torrentRemove (tor, delete_local_data, gtr_file_trash_or_remove);
1367    }
1368}
1369
1370void
1371gtr_core_load (TrCore * self, gboolean forcePaused)
1372{
1373  int i;
1374  tr_ctor * ctor;
1375  tr_torrent ** torrents;
1376  int count = 0;
1377
1378  ctor = tr_ctorNew (gtr_core_session (self));
1379  if (forcePaused)
1380    tr_ctorSetPaused (ctor, TR_FORCE, TRUE);
1381  tr_ctorSetPeerLimit (ctor, TR_FALLBACK,
1382                       gtr_pref_int_get (TR_KEY_peer_limit_per_torrent));
1383
1384  torrents = tr_sessionLoadTorrents (gtr_core_session (self), ctor, &count);
1385  for (i=0; i<count; ++i)
1386    gtr_core_add_torrent (self, torrents[i], FALSE);
1387
1388  tr_free (torrents);
1389  tr_ctorFree (ctor);
1390}
1391
1392void
1393gtr_core_clear (TrCore * self)
1394{
1395  gtk_list_store_clear (GTK_LIST_STORE (core_raw_model (self)));
1396}
1397
1398/***
1399****
1400***/
1401
1402static int
1403gtr_compare_double (const double a, const double b, int decimal_places)
1404{
1405  int ret;
1406  const int64_t ia = (int64_t)(a * pow (10, decimal_places));
1407  const int64_t ib = (int64_t)(b * pow (10, decimal_places));
1408
1409  if (ia < ib)
1410    ret = -1;
1411  else if (ia > ib)
1412    ret = 1;
1413  else
1414    ret = 0;
1415
1416  return ret;
1417}
1418
1419static void
1420update_foreach (GtkTreeModel * model, GtkTreeIter * iter)
1421{
1422  int oldActivity, newActivity;
1423  int oldActivePeerCount, newActivePeerCount;
1424  int oldError, newError;
1425  bool oldFinished, newFinished;
1426  int oldQueuePosition, newQueuePosition;
1427  tr_priority_t oldPriority, newPriority;
1428  unsigned int oldTrackers, newTrackers;
1429  double oldUpSpeed, newUpSpeed;
1430  double oldDownSpeed, newDownSpeed;
1431  double oldRecheckProgress, newRecheckProgress;
1432  gboolean oldActive, newActive;
1433  const tr_stat * st;
1434  tr_torrent * tor;
1435
1436  /* get the old states */
1437  gtk_tree_model_get (model, iter,
1438                      MC_TORRENT,  &tor,
1439                      MC_ACTIVE, &oldActive,
1440                      MC_ACTIVE_PEER_COUNT, &oldActivePeerCount,
1441                      MC_ERROR, &oldError,
1442                      MC_ACTIVITY, &oldActivity,
1443                      MC_FINISHED, &oldFinished,
1444                      MC_PRIORITY, &oldPriority,
1445                      MC_QUEUE_POSITION, &oldQueuePosition,
1446                      MC_TRACKERS, &oldTrackers,
1447                      MC_SPEED_UP, &oldUpSpeed,
1448                      MC_RECHECK_PROGRESS, &oldRecheckProgress,
1449                      MC_SPEED_DOWN, &oldDownSpeed,
1450                      -1);
1451
1452  /* get the new states */
1453  st = tr_torrentStat (tor);
1454  newActive = is_torrent_active (st);
1455  newActivity = st->activity;
1456  newFinished = st->finished;
1457  newPriority = tr_torrentGetPriority (tor);
1458  newQueuePosition = st->queuePosition;
1459  newTrackers = build_torrent_trackers_hash (tor);
1460  newUpSpeed = st->pieceUploadSpeed_KBps;
1461  newDownSpeed = st->pieceDownloadSpeed_KBps;
1462  newRecheckProgress = st->recheckProgress;
1463  newActivePeerCount = st->peersSendingToUs + st->peersGettingFromUs + st->webseedsSendingToUs;
1464  newError = st->error;
1465
1466  /* updating the model triggers off resort/refresh,
1467     so don't do it unless something's actually changed... */
1468  if ((newActive != oldActive)
1469        || (newActivity  != oldActivity)
1470        || (newFinished != oldFinished)
1471        || (newPriority != oldPriority)
1472        || (newQueuePosition != oldQueuePosition)
1473        || (newError != oldError)
1474        || (newActivePeerCount != oldActivePeerCount)
1475        || (newTrackers != oldTrackers)
1476        || gtr_compare_double (newUpSpeed, oldUpSpeed, 2)
1477        || gtr_compare_double (newDownSpeed, oldDownSpeed, 2)
1478        || gtr_compare_double (newRecheckProgress, oldRecheckProgress, 2))
1479    {
1480      gtk_list_store_set (GTK_LIST_STORE (model), iter,
1481                          MC_ACTIVE, newActive,
1482                          MC_ACTIVE_PEER_COUNT, newActivePeerCount,
1483                          MC_ERROR, newError,
1484                          MC_ACTIVITY, newActivity,
1485                          MC_FINISHED, newFinished,
1486                          MC_PRIORITY, newPriority,
1487                          MC_QUEUE_POSITION, newQueuePosition,
1488                          MC_TRACKERS, newTrackers,
1489                          MC_SPEED_UP, newUpSpeed,
1490                          MC_SPEED_DOWN, newDownSpeed,
1491                          MC_RECHECK_PROGRESS, newRecheckProgress,
1492                          -1);
1493    }
1494}
1495
1496void
1497gtr_core_update (TrCore * core)
1498{
1499  GtkTreeIter iter;
1500  GtkTreeModel * model;
1501
1502  /* update the model */
1503  model = core_raw_model (core);
1504  if (gtk_tree_model_iter_nth_child (model, &iter, NULL, 0)) do
1505    update_foreach (model, &iter);
1506  while (gtk_tree_model_iter_next (model, &iter));
1507
1508  /* update hibernation */
1509  core_maybe_inhibit_hibernation (core);
1510}
1511
1512/**
1513***  Hibernate
1514**/
1515
1516#define SESSION_MANAGER_SERVICE_NAME  "org.gnome.SessionManager"
1517#define SESSION_MANAGER_INTERFACE     "org.gnome.SessionManager"
1518#define SESSION_MANAGER_OBJECT_PATH   "/org/gnome/SessionManager"
1519
1520static gboolean
1521gtr_inhibit_hibernation (guint * cookie)
1522{
1523  gboolean success;
1524  GVariant * response;
1525  GDBusConnection * connection;
1526  GError * err = NULL;
1527  const char * application = "Transmission BitTorrent Client";
1528  const char * reason = "BitTorrent Activity";
1529  const int toplevel_xid = 0;
1530  const int flags = 4; /* Inhibit suspending the session or computer */
1531
1532  connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &err);
1533
1534  response = g_dbus_connection_call_sync (connection,
1535                                          SESSION_MANAGER_SERVICE_NAME,
1536                                          SESSION_MANAGER_OBJECT_PATH,
1537                                          SESSION_MANAGER_INTERFACE,
1538                                          "Inhibit",
1539                                          g_variant_new ("(susu)", application, toplevel_xid, reason, flags),
1540                                          NULL, G_DBUS_CALL_FLAGS_NONE,
1541                                          1000, NULL, &err);
1542
1543  if (response != NULL)
1544    *cookie = g_variant_get_uint32 (g_variant_get_child_value (response, 0));
1545
1546  success = (response != NULL) && (err == NULL);
1547
1548  /* logging */
1549  if (success)
1550    {
1551      tr_inf ("%s", _("Inhibiting desktop hibernation"));
1552    }
1553  else
1554    {
1555      tr_err (_("Couldn't inhibit desktop hibernation: %s"), err->message);
1556      g_error_free (err);
1557    }
1558
1559  /* cleanup */
1560  if (response != NULL)
1561    g_variant_unref (response);
1562  if (connection != NULL)
1563    g_object_unref (connection);
1564
1565  return success;
1566}
1567
1568static void
1569gtr_uninhibit_hibernation (guint inhibit_cookie)
1570{
1571  GVariant * response;
1572  GDBusConnection * connection;
1573  GError * err = NULL;
1574
1575  connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &err);
1576
1577  response = g_dbus_connection_call_sync (connection,
1578                                          SESSION_MANAGER_SERVICE_NAME,
1579                                          SESSION_MANAGER_OBJECT_PATH,
1580                                          SESSION_MANAGER_INTERFACE,
1581                                          "Uninhibit",
1582                                          g_variant_new ("(u)", inhibit_cookie),
1583                                          NULL, G_DBUS_CALL_FLAGS_NONE,
1584                                          1000, NULL, &err);
1585
1586  /* logging */
1587  if (err == NULL)
1588    {
1589      tr_inf ("%s", _("Allowing desktop hibernation"));
1590    }
1591  else
1592    {
1593      g_warning ("Couldn't uninhibit desktop hibernation: %s.", err->message);
1594      g_error_free (err);
1595    }
1596
1597  /* cleanup */
1598  g_variant_unref (response);
1599  g_object_unref (connection);
1600}
1601
1602static void
1603gtr_core_set_hibernation_allowed (TrCore * core, gboolean allowed)
1604{
1605  g_return_if_fail (core);
1606  g_return_if_fail (core->priv);
1607
1608  core->priv->inhibit_allowed = allowed != 0;
1609
1610  if (allowed && core->priv->have_inhibit_cookie)
1611    {
1612      gtr_uninhibit_hibernation (core->priv->inhibit_cookie);
1613      core->priv->have_inhibit_cookie = FALSE;
1614    }
1615
1616  if (!allowed && !core->priv->have_inhibit_cookie
1617               && !core->priv->dbus_error)
1618    {
1619      if (gtr_inhibit_hibernation (&core->priv->inhibit_cookie))
1620        core->priv->have_inhibit_cookie = TRUE;
1621      else
1622        core->priv->dbus_error = TRUE;
1623    }
1624}
1625
1626static void
1627core_maybe_inhibit_hibernation (TrCore * core)
1628{
1629  /* hibernation is allowed if EITHER
1630   * (a) the "inhibit" pref is turned off OR
1631   * (b) there aren't any active torrents */
1632  const gboolean hibernation_allowed = !gtr_pref_flag_get (TR_KEY_inhibit_desktop_hibernation)
1633                                    || !gtr_core_get_active_torrent_count (core);
1634  gtr_core_set_hibernation_allowed (core, hibernation_allowed);
1635}
1636
1637/**
1638***  Prefs
1639**/
1640
1641static void
1642core_commit_prefs_change (TrCore * core, const tr_quark key)
1643{
1644  gtr_core_pref_changed (core, key);
1645  gtr_pref_save (gtr_core_session (core));
1646}
1647
1648void
1649gtr_core_set_pref (TrCore * self, const tr_quark key, const char * newval)
1650{
1651  if (tr_strcmp0 (newval, gtr_pref_string_get (key)))
1652    {
1653      gtr_pref_string_set (key, newval);
1654      core_commit_prefs_change (self, key);
1655    }
1656}
1657
1658void
1659gtr_core_set_pref_bool (TrCore * self, const tr_quark key, gboolean newval)
1660{
1661  if (newval != gtr_pref_flag_get (key))
1662    {
1663      gtr_pref_flag_set (key, newval);
1664      core_commit_prefs_change (self, key);
1665    }
1666}
1667
1668void
1669gtr_core_set_pref_int (TrCore * self, const tr_quark key, int newval)
1670{
1671  if (newval != gtr_pref_int_get (key))
1672    {
1673      gtr_pref_int_set (key, newval);
1674      core_commit_prefs_change (self, key);
1675    }
1676}
1677
1678void
1679gtr_core_set_pref_double (TrCore * self, const tr_quark key, double newval)
1680{
1681  if (gtr_compare_double (newval, gtr_pref_double_get (key), 4))
1682    {
1683      gtr_pref_double_set (key, newval);
1684      core_commit_prefs_change (self, key);
1685    }
1686}
1687
1688/***
1689****
1690****  RPC Interface
1691****
1692***/
1693
1694/* #define DEBUG_RPC */
1695
1696static int nextTag = 1;
1697
1698typedef void (server_response_func)(TrCore * core, tr_variant * response, gpointer user_data);
1699
1700struct pending_request_data
1701{
1702  TrCore * core;
1703  server_response_func * response_func;
1704  gpointer response_func_user_data;
1705};
1706
1707static GHashTable * pendingRequests = NULL;
1708
1709static gboolean
1710core_read_rpc_response_idle (void * vresponse)
1711{
1712  tr_variant top;
1713  int64_t intVal;
1714  struct evbuffer * response = vresponse;
1715
1716  tr_variantFromJson (&top, evbuffer_pullup (response, -1), evbuffer_get_length (response));
1717
1718  if (tr_variantDictFindInt (&top, TR_KEY_tag, &intVal))
1719    {
1720      const int tag = (int)intVal;
1721      struct pending_request_data * data = g_hash_table_lookup (pendingRequests, &tag);
1722      if (data)
1723        {
1724          if (data->response_func)
1725            (*data->response_func)(data->core, &top, data->response_func_user_data);
1726          g_hash_table_remove (pendingRequests, &tag);
1727        }
1728    }
1729
1730  tr_variantFree (&top);
1731  evbuffer_free (response);
1732  return FALSE;
1733}
1734
1735static void
1736core_read_rpc_response (tr_session       * session UNUSED,
1737                        struct evbuffer  * response,
1738                        void             * unused UNUSED)
1739{
1740  struct evbuffer * buf = evbuffer_new ();
1741  evbuffer_add_buffer (buf, response);
1742  gdk_threads_add_idle (core_read_rpc_response_idle, buf);
1743}
1744
1745static void
1746core_send_rpc_request (TrCore * core, const char * json, int tag,
1747                       server_response_func * response_func,
1748                       void * response_func_user_data)
1749{
1750  tr_session * session = gtr_core_session (core);
1751
1752  if (pendingRequests == NULL)
1753    {
1754      pendingRequests = g_hash_table_new_full (g_int_hash, g_int_equal, g_free, g_free);
1755    }
1756
1757  if (session == NULL)
1758    {
1759      g_error ("GTK+ client doesn't support connections to remote servers yet.");
1760    }
1761  else
1762    {
1763      /* remember this request */
1764      struct pending_request_data * data;
1765      data = g_new0 (struct pending_request_data, 1);
1766      data->core = core;
1767      data->response_func = response_func;
1768      data->response_func_user_data = response_func_user_data;
1769      g_hash_table_insert (pendingRequests, g_memdup (&tag, sizeof (int)), data);
1770
1771      /* make the request */
1772#ifdef DEBUG_RPC
1773      g_message ("request: [%s]", json);
1774#endif
1775      tr_rpc_request_exec_json (session, json, strlen (json), core_read_rpc_response, GINT_TO_POINTER (tag));
1776    }
1777}
1778
1779/***
1780****  Sending a test-port request via RPC
1781***/
1782
1783static void
1784on_port_test_response (TrCore * core, tr_variant * response, gpointer u UNUSED)
1785{
1786  tr_variant * args;
1787  bool is_open = FALSE;
1788
1789  if (tr_variantDictFindDict (response, TR_KEY_arguments, &args))
1790    tr_variantDictFindBool (args, TR_KEY_port_is_open, &is_open);
1791
1792  core_emit_port_tested (core, is_open);
1793}
1794
1795void
1796gtr_core_port_test (TrCore * core)
1797{
1798  char buf[64];
1799  const int tag = nextTag++;
1800  g_snprintf (buf, sizeof (buf), "{ \"method\": \"port-test\", \"tag\": %d }", tag);
1801  core_send_rpc_request (core, buf, tag, on_port_test_response, NULL);
1802}
1803
1804/***
1805****  Updating a blocklist via RPC
1806***/
1807
1808static void
1809on_blocklist_response (TrCore * core, tr_variant * response, gpointer data UNUSED)
1810{
1811  tr_variant * args;
1812  int64_t ruleCount = -1;
1813
1814  if (tr_variantDictFindDict (response, TR_KEY_arguments, &args))
1815    tr_variantDictFindInt (args, TR_KEY_blocklist_size, &ruleCount);
1816
1817  if (ruleCount > 0)
1818    gtr_pref_int_set (TR_KEY_blocklist_date, tr_time ());
1819
1820  core_emit_blocklist_udpated (core, ruleCount);
1821}
1822
1823void
1824gtr_core_blocklist_update (TrCore * core)
1825{
1826  char buf[64];
1827  const int tag = nextTag++;
1828  g_snprintf (buf, sizeof (buf), "{ \"method\": \"blocklist-update\", \"tag\": %d }", tag);
1829  core_send_rpc_request (core, buf, tag, on_blocklist_response, NULL);
1830}
1831
1832/***
1833****
1834***/
1835
1836void
1837gtr_core_exec_json (TrCore * core, const char * json)
1838{
1839  const int tag = nextTag++;
1840  core_send_rpc_request (core, json, tag, NULL, NULL);
1841}
1842
1843void
1844gtr_core_exec (TrCore * core, const tr_variant * top)
1845{
1846  char * json = tr_variantToStr (top, TR_VARIANT_FMT_JSON_LEAN, NULL);
1847  gtr_core_exec_json (core, json);
1848  tr_free (json);
1849}
1850
1851/***
1852****
1853***/
1854
1855size_t
1856gtr_core_get_torrent_count (TrCore * core)
1857{
1858  return gtk_tree_model_iter_n_children (core_raw_model (core), NULL);
1859}
1860
1861size_t
1862gtr_core_get_active_torrent_count (TrCore * core)
1863{
1864  GtkTreeIter iter;
1865  size_t activeCount = 0;
1866  GtkTreeModel * model = core_raw_model (core);
1867
1868  if (gtk_tree_model_iter_nth_child (model, &iter, NULL, 0)) do
1869    {
1870      int activity;
1871      gtk_tree_model_get (model, &iter, MC_ACTIVITY, &activity, -1);
1872
1873      if (activity != TR_STATUS_STOPPED)
1874        ++activeCount;
1875    }
1876  while (gtk_tree_model_iter_next (model, &iter));
1877
1878  return activeCount;
1879}
1880
1881tr_torrent *
1882gtr_core_find_torrent (TrCore * core, int id)
1883{
1884  tr_session * session;
1885  tr_torrent * tor = NULL;
1886
1887  if ((session = gtr_core_session (core)))
1888    tor = tr_torrentFindFromId (session, id);
1889
1890  return tor;
1891}
1892
1893void
1894gtr_core_open_folder (TrCore * core, int torrent_id)
1895{
1896  const tr_torrent * tor = gtr_core_find_torrent (core, torrent_id);
1897
1898  if (tor != NULL)
1899    {
1900      const gboolean single = tr_torrentInfo (tor)->fileCount == 1;
1901      const char * currentDir = tr_torrentGetCurrentDir (tor);
1902
1903      if (single)
1904        {
1905          gtr_open_file (currentDir);
1906        }
1907      else
1908        {
1909          char * path = g_build_filename (currentDir, tr_torrentName (tor), NULL);
1910          gtr_open_file (path);
1911          g_free (path);
1912        }
1913    }
1914}
Note: See TracBrowser for help on using the repository browser.