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