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

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

(trunk gtk) use G_SOURCE_REMOVE where appropriate

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