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