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

Last change on this file since 11742 was 11742, checked in by jordan, 11 years ago

(trunk gtk) include the torrent hashcode in the model's collated name.

This simplifies sorting by name by merging the primary and secondary keys (case-insensitive name, and hash string) into a single key.

  • Property svn:keywords set to Date Rev Author Id
File size: 50.5 KB
Line 
1/******************************************************************************
2 * $Id: tr-core.c 11742 2011-01-21 18:30:08Z 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 <string.h> /* strcmp, strlen */
26
27#include <gtk/gtk.h>
28#include <glib/gi18n.h>
29#ifdef HAVE_GIO
30 #include <gio/gio.h>
31#endif
32#ifdef HAVE_DBUS_GLIB
33 #include <dbus/dbus-glib.h>
34#endif
35
36#include <event2/buffer.h>
37
38#include <libtransmission/transmission.h>
39#include <libtransmission/bencode.h>
40#include <libtransmission/rpcimpl.h>
41#include <libtransmission/json.h>
42#include <libtransmission/utils.h> /* tr_free */
43#include <libtransmission/web.h>
44
45#include "conf.h"
46#include "notify.h"
47#include "tr-core.h"
48#ifdef HAVE_DBUS_GLIB
49 #include "tr-core-dbus.h"
50#endif
51#include "tr-prefs.h"
52#include "tr-torrent.h"
53#include "util.h"
54#include "actions.h"
55
56/***
57****
58***/
59
60enum
61{
62  ADD_ERROR_SIGNAL,
63  ADD_PROMPT_SIGNAL,
64  BLOCKLIST_SIGNAL,
65  BUSY_SIGNAL,
66  PORT_SIGNAL,
67  PREFS_SIGNAL,
68
69  LAST_SIGNAL
70};
71
72static guint core_signals[LAST_SIGNAL] = { 0 };
73
74static void maybeInhibitHibernation( TrCore * core );
75
76static gboolean our_instance_adds_remote_torrents = FALSE;
77
78struct TrCorePrivate
79{
80#ifdef HAVE_GIO
81    GFileMonitor *  monitor;
82    gulong          monitor_tag;
83    char *          monitor_path;
84    GSList *        monitor_files;
85    guint           monitor_idle_tag;
86#endif
87    gboolean        adding_from_watch_dir;
88    gboolean        inhibit_allowed;
89    gboolean        have_inhibit_cookie;
90    gboolean        dbus_error;
91    guint           inhibit_cookie;
92    gint            busy_count;
93    GtkTreeModel *  model;
94    tr_session *    session;
95};
96
97static int
98isDisposed( const TrCore * core )
99{
100    return !core || !core->priv;
101}
102
103static void
104tr_core_dispose( GObject * obj )
105{
106    TrCore * core = TR_CORE( obj );
107
108    if( !isDisposed( core ) )
109    {
110        GObjectClass * parent;
111
112        core->priv = NULL;
113
114        parent = g_type_class_peek( g_type_parent( TR_CORE_TYPE ) );
115        parent->dispose( obj );
116    }
117}
118
119static void
120tr_core_class_init( gpointer              g_class,
121                    gpointer g_class_data UNUSED )
122{
123    GObjectClass * gobject_class;
124
125    g_type_class_add_private( g_class, sizeof( struct TrCorePrivate ) );
126
127    gobject_class = G_OBJECT_CLASS( g_class );
128    gobject_class->dispose = tr_core_dispose;
129
130    core_signals[ADD_ERROR_SIGNAL] = g_signal_new(
131        "add-error",
132        G_TYPE_FROM_CLASS( g_class ),
133        G_SIGNAL_RUN_LAST,
134        G_STRUCT_OFFSET(TrCoreClass, add_error),
135        NULL, NULL,
136        g_cclosure_marshal_VOID__UINT_POINTER,
137        G_TYPE_NONE,
138        2, G_TYPE_UINT, G_TYPE_POINTER );
139
140    core_signals[ADD_PROMPT_SIGNAL] = g_signal_new(
141        "add-prompt",
142        G_TYPE_FROM_CLASS( g_class ),
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    core_signals[BUSY_SIGNAL] = g_signal_new(
151        "busy",                             /* signal name */
152        G_TYPE_FROM_CLASS( g_class ),       /* applies to TrCore */
153        G_SIGNAL_RUN_FIRST,                 /* when to invoke */
154        G_STRUCT_OFFSET(TrCoreClass, busy), /* class_offset */
155        NULL, NULL,                         /* accumulator */
156        g_cclosure_marshal_VOID__BOOLEAN    /* marshaler */,
157        G_TYPE_NONE,                        /* return type */
158        1, G_TYPE_BOOLEAN );                /* signal arguments */
159
160    core_signals[BLOCKLIST_SIGNAL] = g_signal_new(
161        "blocklist-updated",                          /* signal name */
162        G_TYPE_FROM_CLASS( g_class ),                     /* applies to TrCore */
163        G_SIGNAL_RUN_FIRST,                               /* when to invoke */
164        G_STRUCT_OFFSET(TrCoreClass, blocklist_updated),  /* class_offset */
165        NULL, NULL,                                       /* accumulator */
166        g_cclosure_marshal_VOID__INT,                     /* marshaler */
167        G_TYPE_NONE,                                      /* return type */
168        1, G_TYPE_INT );                                  /* signal arguments */
169
170    core_signals[PORT_SIGNAL] = g_signal_new(
171        "port-tested",
172        G_TYPE_FROM_CLASS( g_class ),
173        G_SIGNAL_RUN_LAST,
174        G_STRUCT_OFFSET(TrCoreClass, port_tested),
175        NULL, NULL,
176        g_cclosure_marshal_VOID__BOOLEAN,
177        G_TYPE_NONE,
178        1, G_TYPE_BOOLEAN );
179
180    core_signals[PREFS_SIGNAL] = g_signal_new(
181        "prefs-changed",
182        G_TYPE_FROM_CLASS( g_class ),
183        G_SIGNAL_RUN_LAST,
184        G_STRUCT_OFFSET(TrCoreClass, prefs_changed),
185        NULL, NULL,
186        g_cclosure_marshal_VOID__STRING,
187        G_TYPE_NONE,
188        1, G_TYPE_STRING );
189
190#ifdef HAVE_DBUS_GLIB
191    {
192        DBusGConnection * bus = dbus_g_bus_get( DBUS_BUS_SESSION, NULL );
193        DBusGProxy *      bus_proxy = NULL;
194        if( bus )
195            bus_proxy =
196                dbus_g_proxy_new_for_name( bus, "org.freedesktop.DBus",
197                                           "/org/freedesktop/DBus",
198                                           "org.freedesktop.DBus" );
199        if( bus_proxy )
200        {
201            int result = 0;
202            dbus_g_proxy_call( bus_proxy, "RequestName", NULL,
203                               G_TYPE_STRING,
204                               "com.transmissionbt.Transmission",
205                               G_TYPE_UINT, 0,
206                               G_TYPE_INVALID,
207                               G_TYPE_UINT, &result,
208                               G_TYPE_INVALID );
209            if( ( our_instance_adds_remote_torrents = result == 1 ) )
210                dbus_g_object_type_install_info(
211                    TR_CORE_TYPE,
212                    &
213                    dbus_glib_tr_core_object_info );
214        }
215    }
216#endif
217}
218
219/***
220****
221***/
222
223static tr_bool
224coreIsBusy( TrCore * core )
225{
226    return core->priv->busy_count > 0;
227}
228
229static void
230emitBusy( TrCore * core )
231{
232    g_signal_emit( core, core_signals[BUSY_SIGNAL], 0, coreIsBusy( core ) );
233}
234
235static void
236coreAddToBusy( TrCore * core, int addMe )
237{
238    const tr_bool wasBusy = coreIsBusy( core );
239
240    core->priv->busy_count += addMe;
241
242    if( wasBusy != coreIsBusy( core ) )
243        emitBusy( core );
244}
245
246static void coreIncBusy( TrCore * core ) { coreAddToBusy( core, 1 ); }
247static void coreDecBusy( TrCore * core ) { coreAddToBusy( core, -1 ); }
248
249/***
250****  SORTING
251***/
252
253static gboolean
254isValidETA( int t )
255{
256    return ( t != TR_ETA_NOT_AVAIL ) && ( t != TR_ETA_UNKNOWN );
257}
258
259static int
260compareETA( int a, int b )
261{
262    const gboolean a_valid = isValidETA( a );
263    const gboolean b_valid = isValidETA( b );
264
265    if( !a_valid && !b_valid ) return 0;
266    if( !a_valid ) return -1;
267    if( !b_valid ) return 1;
268    return a < b ? 1 : -1;
269}
270
271static int
272compareDouble( double a, double b )
273{
274    if( a < b ) return -1;
275    if( a > b ) return 1;
276    return 0;
277}
278
279static int
280compareUint64( uint64_t a, uint64_t b )
281{
282    if( a < b ) return -1;
283    if( a > b ) return 1;
284    return 0;
285}
286
287static int
288compareInt_( int a, int b )
289{
290    if( a < b ) return -1;
291    if( a > b ) return 1;
292    return 0;
293}
294
295static int
296compareRatio( double a, double b )
297{
298    if( (int)a == TR_RATIO_INF && (int)b == TR_RATIO_INF ) return 0;
299    if( (int)a == TR_RATIO_INF ) return 1;
300    if( (int)b == TR_RATIO_INF ) return -1;
301    return compareDouble( a, b );
302}
303
304static int
305compareTime( time_t a, time_t b )
306{
307    if( a < b ) return -1;
308    if( a > b ) return 1;
309    return 0;
310}
311
312static int
313compareByName( GtkTreeModel * m, GtkTreeIter * a, GtkTreeIter * b, gpointer user_data UNUSED )
314{
315    int ret = 0;
316
317    if( !ret ) {
318        char *ca, *cb;
319        gtk_tree_model_get( m, a, MC_NAME_COLLATED, &ca, -1 );
320        gtk_tree_model_get( m, b, MC_NAME_COLLATED, &cb, -1 );
321        ret = gtr_strcmp0( ca, cb );
322        g_free( cb );
323        g_free( ca );
324    }
325
326    return ret;
327}
328
329static int
330compareByRatio( GtkTreeModel* m, GtkTreeIter * a, GtkTreeIter * b, gpointer user_data )
331{
332    int ret = 0;
333    tr_torrent *ta, *tb;
334    const tr_stat *sa, *sb;
335
336    gtk_tree_model_get( m, a, MC_TORRENT_RAW, &ta, -1 );
337    sa = tr_torrentStatCached( ta );
338    gtk_tree_model_get( m, b, MC_TORRENT_RAW, &tb, -1 );
339    sb = tr_torrentStatCached( tb );
340
341    if( !ret ) ret = compareRatio( sa->ratio, sb->ratio );
342    if( !ret ) ret = compareByName( m, a, b, user_data );
343    return ret;
344}
345
346static int
347compareByActivity( GtkTreeModel * m, GtkTreeIter * a, GtkTreeIter * b, gpointer user_data )
348{
349    int ret = 0;
350    tr_torrent *ta, *tb;
351    const tr_stat *sa, *sb;
352    double aUp, aDown, bUp, bDown;
353
354    gtk_tree_model_get( m, a, MC_SPEED_UP, &aUp,
355                              MC_SPEED_DOWN, &aDown,
356                              MC_TORRENT_RAW, &ta,
357                              -1 );
358    gtk_tree_model_get( m, b, MC_SPEED_UP, &bUp,
359                              MC_SPEED_DOWN, &bDown,
360                              MC_TORRENT_RAW, &tb,
361                              -1 );
362    sa = tr_torrentStatCached( ta );
363    sb = tr_torrentStatCached( tb );
364
365    if( !ret ) ret = compareDouble( aUp+aDown, bUp+bDown );
366    if( !ret ) ret = compareUint64( sa->uploadedEver, sb->uploadedEver );
367    if( !ret ) ret = compareByName( m, a, b, user_data );
368    return ret;
369}
370
371static int
372compareByAge( GtkTreeModel * m, GtkTreeIter * a, GtkTreeIter * b, gpointer user_data )
373{
374    int ret = 0;
375    tr_torrent *ta, *tb;
376
377    gtk_tree_model_get( m, a, MC_TORRENT_RAW, &ta, -1 );
378    gtk_tree_model_get( m, b, MC_TORRENT_RAW, &tb, -1 );
379
380    if( !ret ) ret = compareTime( tr_torrentStatCached( ta )->addedDate, tr_torrentStatCached( tb )->addedDate );
381    if( !ret ) ret = compareByName( m, a, b, user_data );
382    return ret;
383}
384
385static int
386compareBySize( GtkTreeModel * m, GtkTreeIter * a, GtkTreeIter * b, gpointer user_data )
387{
388    int ret = 0;
389    tr_torrent *t;
390    const tr_info *ia, *ib;
391
392    gtk_tree_model_get( m, a, MC_TORRENT_RAW, &t, -1 );
393    ia = tr_torrentInfo( t );
394    gtk_tree_model_get( m, b, MC_TORRENT_RAW, &t, -1 );
395    ib = tr_torrentInfo( t );
396
397    if( !ret ) ret = compareUint64( ia->totalSize, ib->totalSize );
398    if( !ret ) ret = compareByName( m, a, b, user_data );
399    return ret;
400}
401
402static int
403compareByProgress( GtkTreeModel * m, GtkTreeIter * a, GtkTreeIter * b, gpointer user_data )
404{
405    int ret = 0;
406    tr_torrent * t;
407    const tr_stat *sa, *sb;
408
409    gtk_tree_model_get( m, a, MC_TORRENT_RAW, &t, -1 );
410    sa = tr_torrentStatCached( t );
411    gtk_tree_model_get( m, b, MC_TORRENT_RAW, &t, -1 );
412    sb = tr_torrentStatCached( t );
413
414    if( !ret ) ret = compareDouble( sa->percentComplete, sb->percentComplete );
415    if( !ret ) ret = compareDouble( sa->seedRatioPercentDone, sb->seedRatioPercentDone );
416    if( !ret ) ret = compareByRatio( m, a, b, user_data );
417    return ret;
418}
419
420static int
421compareByETA( GtkTreeModel * m, GtkTreeIter  * a, GtkTreeIter  * b, gpointer user_data )
422{
423    int ret = 0;
424    tr_torrent *ta, *tb;
425
426    gtk_tree_model_get( m, a, MC_TORRENT_RAW, &ta, -1 );
427    gtk_tree_model_get( m, b, MC_TORRENT_RAW, &tb, -1 );
428
429    if( !ret ) ret = compareETA( tr_torrentStatCached( ta )->eta, tr_torrentStatCached( tb )->eta );
430    if( !ret ) ret = compareByName( m, a, b, user_data );
431    return ret;
432}
433
434static int
435compareByState( GtkTreeModel * m, GtkTreeIter * a, GtkTreeIter * b, gpointer user_data )
436{
437    int ret = 0;
438    int sa, sb;
439
440    gtk_tree_model_get( m, a, MC_ACTIVITY, &sa, -1 );
441    gtk_tree_model_get( m, b, MC_ACTIVITY, &sb, -1 );
442
443    if( !ret ) ret = compareInt_( sa, sb );
444    if( !ret ) ret = compareByProgress( m, a, b, user_data );
445    return ret;
446}
447
448static void
449setSort( TrCore *     core,
450         const char * mode,
451         gboolean     isReversed  )
452{
453    const int              col = MC_TORRENT_RAW;
454    GtkTreeIterCompareFunc sort_func;
455    GtkSortType            type =
456        isReversed ? GTK_SORT_ASCENDING : GTK_SORT_DESCENDING;
457    GtkTreeSortable *      sortable =
458        GTK_TREE_SORTABLE( tr_core_model( core ) );
459
460    if( !strcmp( mode, "sort-by-activity" ) )
461        sort_func = compareByActivity;
462    else if( !strcmp( mode, "sort-by-age" ) )
463        sort_func = compareByAge;
464    else if( !strcmp( mode, "sort-by-progress" ) )
465        sort_func = compareByProgress;
466    else if( !strcmp( mode, "sort-by-time-left" ) )
467        sort_func = compareByETA;
468    else if( !strcmp( mode, "sort-by-ratio" ) )
469        sort_func = compareByRatio;
470    else if( !strcmp( mode, "sort-by-state" ) )
471        sort_func = compareByState;
472    else if( !strcmp( mode, "sort-by-size" ) )
473        sort_func = compareBySize;
474    else {
475        sort_func = compareByName;
476        type = isReversed ? GTK_SORT_DESCENDING : GTK_SORT_ASCENDING;
477    }
478
479    gtk_tree_sortable_set_sort_func( sortable, col, sort_func, NULL, NULL );
480    gtk_tree_sortable_set_sort_column_id( sortable, col, type );
481}
482
483static void
484tr_core_apply_defaults( tr_ctor * ctor )
485{
486    if( tr_ctorGetPaused( ctor, TR_FORCE, NULL ) )
487        tr_ctorSetPaused( ctor, TR_FORCE, !gtr_pref_flag_get( TR_PREFS_KEY_START ) );
488
489    if( tr_ctorGetDeleteSource( ctor, NULL ) )
490        tr_ctorSetDeleteSource( ctor,
491                               gtr_pref_flag_get( TR_PREFS_KEY_TRASH_ORIGINAL ) );
492
493    if( tr_ctorGetPeerLimit( ctor, TR_FORCE, NULL ) )
494        tr_ctorSetPeerLimit( ctor, TR_FORCE,
495                             gtr_pref_int_get( TR_PREFS_KEY_PEER_LIMIT_TORRENT ) );
496
497    if( tr_ctorGetDownloadDir( ctor, TR_FORCE, NULL ) )
498    {
499        const char * path = gtr_pref_string_get( TR_PREFS_KEY_DOWNLOAD_DIR );
500        tr_ctorSetDownloadDir( ctor, TR_FORCE, path );
501    }
502}
503
504static char *
505torrentTrackerString( tr_torrent * tor )
506{
507    int i;
508    GString * str = g_string_new( NULL );
509    const tr_info * inf = tr_torrentInfo( tor );
510
511    for( i = 0; i < inf->trackerCount; ++i )
512    {
513        const tr_tracker_info * t = &inf->trackers[i];
514        g_string_append( str, t->announce );
515    }
516
517    return g_string_free( str, FALSE );
518}
519
520static gboolean
521isTorrentActive( const tr_stat * st )
522{
523    return ( st->peersSendingToUs > 0 )
524        || ( st->peersGettingFromUs > 0 )
525        || ( st->activity == TR_STATUS_CHECK );
526}
527
528#ifdef HAVE_GIO
529
530struct watchdir_file
531{
532    char * filename;
533    time_t mtime;
534};
535
536static int
537compare_watchdir_file_to_filename( const void * a, const void * filename )
538{
539    return strcmp( ((const struct watchdir_file*)a)->filename, filename );
540}
541
542static void
543watchdir_file_update_mtime( struct watchdir_file * file )
544{
545    GFile * gfile = g_file_new_for_path( file->filename );
546    GFileInfo * info = g_file_query_info( gfile, G_FILE_ATTRIBUTE_TIME_MODIFIED, 0, NULL, NULL );
547
548    file->mtime = g_file_info_get_attribute_uint64( info, G_FILE_ATTRIBUTE_TIME_MODIFIED );
549
550    g_object_unref( G_OBJECT( info ) );
551    g_object_unref( G_OBJECT( gfile ) );
552}
553
554static struct watchdir_file*
555watchdir_file_new( const char * filename )
556{
557    struct watchdir_file * f;
558
559    f = g_new( struct watchdir_file, 1 );
560    f->filename = g_strdup( filename );
561    watchdir_file_update_mtime( f );
562
563    return f;
564}
565
566static void
567watchdir_file_free( struct watchdir_file * f )
568{
569    g_free( f->filename );
570    g_free( f );
571}
572
573static gboolean
574watchFolderIdle( gpointer gcore )
575{
576    GSList * l;
577    GSList * addme = NULL;
578    GSList * monitor_files = NULL;
579    TrCore * core = TR_CORE( gcore );
580    const time_t now = time( NULL );
581    struct TrCorePrivate * p = core->priv;
582
583    /* of the monitor_files, make a list of those that haven't
584     * changed lately, since they should be ready to add */
585    for( l=p->monitor_files; l!=NULL; l=l->next ) {
586        struct watchdir_file * f = l->data;
587        watchdir_file_update_mtime( f );
588        if( f->mtime + 2 >= now )
589            monitor_files = g_slist_prepend( monitor_files, f );
590        else {
591            addme = g_slist_prepend( addme, g_strdup( f->filename ) );
592            watchdir_file_free( f );
593        }
594    }
595
596    /* add the torrents from that list */
597    core->priv->adding_from_watch_dir = TRUE;
598    tr_core_add_list_defaults( core, addme, TRUE );
599    core->priv->adding_from_watch_dir = FALSE;
600
601    /* update the monitor_files list */
602    g_slist_free( p->monitor_files );
603    p->monitor_files = monitor_files;
604
605    /* if monitor_files is nonempty, keep checking every second */
606    if( core->priv->monitor_files )
607        return TRUE;
608    core->priv->monitor_idle_tag = 0;
609    return FALSE;
610
611}
612
613static void
614maybeAddTorrent( TrCore * core, const char * filename )
615{
616    const gboolean isTorrent = g_str_has_suffix( filename, ".torrent" );
617
618    if( isTorrent )
619    {
620        struct TrCorePrivate * p = core->priv;
621
622        if( !g_slist_find_custom( p->monitor_files, filename, (GCompareFunc)compare_watchdir_file_to_filename ) )
623            p->monitor_files = g_slist_append( p->monitor_files, watchdir_file_new( filename ) );
624
625        if( !p->monitor_idle_tag )
626            p->monitor_idle_tag = gtr_timeout_add_seconds( 1, watchFolderIdle, core );
627    }
628}
629
630static void
631watchFolderChanged( GFileMonitor       * monitor    UNUSED,
632                    GFile *                         file,
633                    GFile              * other_type UNUSED,
634                    GFileMonitorEvent               event_type,
635                    gpointer                        core )
636{
637    if( event_type == G_FILE_MONITOR_EVENT_CREATED )
638    {
639        char * filename = g_file_get_path( file );
640        maybeAddTorrent( core, filename );
641        g_free( filename );
642    }
643}
644
645static void
646scanWatchDir( TrCore * core )
647{
648    const gboolean isEnabled = gtr_pref_flag_get( PREF_KEY_DIR_WATCH_ENABLED );
649
650    if( isEnabled )
651    {
652        const char * dirname = gtr_pref_string_get( PREF_KEY_DIR_WATCH );
653        GDir * dir = g_dir_open( dirname, 0, NULL );
654
655        if( dir != NULL )
656        {
657            const char * basename;
658            while(( basename = g_dir_read_name( dir )))
659            {
660                char * filename = g_build_filename( dirname, basename, NULL );
661                maybeAddTorrent( core, filename );
662                g_free( filename );
663            }
664
665            g_dir_close( dir );
666        }
667    }
668}
669
670static void
671updateWatchDir( TrCore * core )
672{
673    const char *           filename = gtr_pref_string_get( PREF_KEY_DIR_WATCH );
674    const gboolean         isEnabled = gtr_pref_flag_get(
675        PREF_KEY_DIR_WATCH_ENABLED );
676    struct TrCorePrivate * p = TR_CORE( core )->priv;
677
678    if( p->monitor && ( !isEnabled || gtr_strcmp0( filename, p->monitor_path ) ) )
679    {
680        g_signal_handler_disconnect( p->monitor, p->monitor_tag );
681        g_free( p->monitor_path );
682        g_file_monitor_cancel( p->monitor );
683        g_object_unref( G_OBJECT( p->monitor ) );
684        p->monitor_path = NULL;
685        p->monitor = NULL;
686        p->monitor_tag = 0;
687    }
688
689    if( isEnabled && !p->monitor )
690    {
691        GFile *        file = g_file_new_for_path( filename );
692        GFileMonitor * m = g_file_monitor_directory( file, 0, NULL, NULL );
693        scanWatchDir( core );
694        p->monitor = m;
695        p->monitor_path = g_strdup( filename );
696        p->monitor_tag = g_signal_connect( m, "changed",
697                                           G_CALLBACK(
698                                               watchFolderChanged ), core );
699    }
700}
701
702#endif
703
704static void
705prefsChanged( TrCore *      core,
706              const char *  key,
707              gpointer data UNUSED )
708{
709    if( !strcmp( key, PREF_KEY_SORT_MODE )
710      || !strcmp( key, PREF_KEY_SORT_REVERSED ) )
711    {
712        const char * mode = gtr_pref_string_get( PREF_KEY_SORT_MODE );
713        gboolean     isReversed = gtr_pref_flag_get( PREF_KEY_SORT_REVERSED );
714        setSort( core, mode, isReversed );
715    }
716    else if( !strcmp( key, TR_PREFS_KEY_PEER_LIMIT_GLOBAL ) )
717    {
718        const uint16_t val = gtr_pref_int_get( key );
719        tr_sessionSetPeerLimit( tr_core_session( core ), val );
720    }
721    else if( !strcmp( key, TR_PREFS_KEY_PEER_LIMIT_TORRENT ) )
722    {
723        const uint16_t val = gtr_pref_int_get( key );
724        tr_sessionSetPeerLimitPerTorrent( tr_core_session( core ), val );
725    }
726    else if( !strcmp( key, PREF_KEY_INHIBIT_HIBERNATION ) )
727    {
728        maybeInhibitHibernation( core );
729    }
730#ifdef HAVE_GIO
731    else if( !strcmp( key, PREF_KEY_DIR_WATCH )
732           || !strcmp( key, PREF_KEY_DIR_WATCH_ENABLED ) )
733    {
734        updateWatchDir( core );
735    }
736#endif
737}
738
739static void
740tr_core_init( GTypeInstance *  instance,
741              gpointer g_class UNUSED )
742{
743    GtkListStore * store;
744    struct TrCorePrivate * p;
745    TrCore * self = (TrCore *) instance;
746
747    /* column types for the model used to store torrent information */
748    /* keep this in sync with the enum near the bottom of tr_core.h */
749    GType types[] = { G_TYPE_STRING,    /* name */
750                      G_TYPE_STRING,    /* collated name */
751                      TR_TORRENT_TYPE,  /* TrTorrent object */
752                      G_TYPE_POINTER,   /* tr_torrent* */
753                      G_TYPE_DOUBLE,    /* tr_stat.pieceUploadSpeed_KBps */
754                      G_TYPE_DOUBLE,    /* tr_stat.pieceDownloadSpeed_KBps */
755                      G_TYPE_BOOLEAN,   /* filter.c:ACTIVITY_FILTER_ACTIVE */
756                      G_TYPE_INT,       /* tr_stat.activity */
757                      G_TYPE_UCHAR,     /* tr_stat.finished */
758                      G_TYPE_CHAR,      /* tr_priority_t */
759                      G_TYPE_STRING,    /* concatenated trackers string */
760                      G_TYPE_INT,       /* MC_ERROR */
761                      G_TYPE_INT };     /* MC_ACTIVE_PEER_COUNT */
762
763    p = self->priv = G_TYPE_INSTANCE_GET_PRIVATE( self,
764                                                  TR_CORE_TYPE,
765                                                  struct TrCorePrivate );
766
767    /* create the model used to store torrent data */
768    g_assert( G_N_ELEMENTS( types ) == MC_ROW_COUNT );
769    store = gtk_list_store_newv( MC_ROW_COUNT, types );
770
771    p->model    = GTK_TREE_MODEL( store );
772
773#ifdef HAVE_DBUS_GLIB
774    if( our_instance_adds_remote_torrents )
775    {
776        DBusGConnection * bus = dbus_g_bus_get( DBUS_BUS_SESSION, NULL );
777        if( bus )
778            dbus_g_connection_register_g_object(
779                 bus,
780                "/com/transmissionbt/Transmission",
781                G_OBJECT( self ) );
782    }
783#endif
784}
785
786GType
787tr_core_get_type( void )
788{
789    static GType type = 0;
790
791    if( !type )
792    {
793        static const GTypeInfo info =
794        {
795            sizeof( TrCoreClass ),
796            NULL,                 /* base_init */
797            NULL,                 /* base_finalize */
798            tr_core_class_init,   /* class_init */
799            NULL,                 /* class_finalize */
800            NULL,                 /* class_data */
801            sizeof( TrCore ),
802            0,                    /* n_preallocs */
803            tr_core_init,         /* instance_init */
804            NULL,
805        };
806        type = g_type_register_static( G_TYPE_OBJECT, "TrCore", &info, 0 );
807    }
808
809    return type;
810}
811
812/**
813***
814**/
815
816TrCore *
817tr_core_new( tr_session * session )
818{
819    TrCore * core = TR_CORE( g_object_new( TR_CORE_TYPE, NULL ) );
820
821    core->priv->session  = session;
822
823    /* init from prefs & listen to pref changes */
824    prefsChanged( core, PREF_KEY_SORT_MODE, NULL );
825    prefsChanged( core, PREF_KEY_SORT_REVERSED, NULL );
826    prefsChanged( core, PREF_KEY_DIR_WATCH_ENABLED, NULL );
827    prefsChanged( core, TR_PREFS_KEY_PEER_LIMIT_GLOBAL, NULL );
828    prefsChanged( core, PREF_KEY_INHIBIT_HIBERNATION, NULL );
829    g_signal_connect( core, "prefs-changed", G_CALLBACK( prefsChanged ), NULL );
830
831    return core;
832}
833
834void
835tr_core_close( TrCore * core )
836{
837    tr_session * session = tr_core_session( core );
838
839    if( session )
840    {
841        core->priv->session = NULL;
842        gtr_pref_save( session );
843        tr_sessionClose( session );
844    }
845}
846
847GtkTreeModel *
848tr_core_model( TrCore * core )
849{
850    return isDisposed( core ) ? NULL : core->priv->model;
851}
852
853tr_session *
854tr_core_session( TrCore * core )
855{
856    return isDisposed( core ) ? NULL : core->priv->session;
857}
858
859static char*
860get_collated_name( const tr_info * inf )
861{
862    char * down = g_utf8_strdown( inf->name ? inf->name : "", -1 );
863    char * collated = g_strdup_printf( "%s\t%s", down, inf->hashString );
864    g_free( down );
865    return collated;
866}
867
868void
869tr_core_add_torrent( TrCore     * self,
870                     TrTorrent  * gtor,
871                     gboolean     doNotify )
872{
873    const tr_info * inf = tr_torrent_info( gtor );
874    const tr_stat * st = tr_torrent_stat( gtor );
875    tr_torrent * tor = tr_torrent_handle( gtor );
876    char *  collated = get_collated_name( inf );
877    char *  trackers = torrentTrackerString( tor );
878    GtkListStore *  store = GTK_LIST_STORE( tr_core_model( self ) );
879    GtkTreeIter  unused;
880
881    gtk_list_store_insert_with_values( store, &unused, 0,
882                                       MC_NAME,          inf->name,
883                                       MC_NAME_COLLATED, collated,
884                                       MC_TORRENT,       gtor,
885                                       MC_TORRENT_RAW,   tor,
886                                       MC_SPEED_UP,      st->pieceUploadSpeed_KBps,
887                                       MC_SPEED_DOWN,    st->pieceDownloadSpeed_KBps,
888                                       MC_ACTIVE,        isTorrentActive( st ),
889                                       MC_ACTIVITY,      st->activity,
890                                       MC_FINISHED,      st->finished,
891                                       MC_PRIORITY,      tr_torrentGetPriority( tor ),
892                                       MC_TRACKERS,      trackers,
893                                       -1 );
894
895    if( doNotify )
896        gtr_notify_added( inf->name );
897
898    /* cleanup */
899    g_object_unref( G_OBJECT( gtor ) );
900    g_free( collated );
901    g_free( trackers );
902}
903
904void
905tr_core_load( TrCore * self, gboolean forcePaused )
906{
907    int i;
908    tr_ctor * ctor;
909    tr_torrent ** torrents;
910    int count = 0;
911
912    ctor = tr_ctorNew( tr_core_session( self ) );
913    if( forcePaused )
914        tr_ctorSetPaused( ctor, TR_FORCE, TRUE );
915    tr_ctorSetPeerLimit( ctor, TR_FALLBACK,
916                         gtr_pref_int_get( TR_PREFS_KEY_PEER_LIMIT_TORRENT ) );
917
918    torrents = tr_sessionLoadTorrents ( tr_core_session( self ), ctor, &count );
919    for( i=0; i<count; ++i )
920        tr_core_add_torrent( self, tr_torrent_new_preexisting( torrents[i] ), FALSE );
921
922    tr_free( torrents );
923    tr_ctorFree( ctor );
924}
925
926/***
927****
928***/
929
930static void
931emitBlocklistUpdated( TrCore * core, int ruleCount )
932{
933    g_signal_emit( core, core_signals[BLOCKLIST_SIGNAL], 0, ruleCount );
934}
935
936static void
937emitPortTested( TrCore * core, gboolean isOpen )
938{
939    g_signal_emit( core, core_signals[PORT_SIGNAL], 0, isOpen );
940}
941
942static void
943tr_core_errsig( TrCore * core, enum tr_core_err type, const char * msg )
944{
945    g_signal_emit( core, core_signals[ADD_ERROR_SIGNAL], 0, type, msg );
946}
947
948static int
949add_ctor( TrCore * core, tr_ctor * ctor, gboolean doPrompt, gboolean doNotify )
950{
951    tr_info inf;
952    int err = tr_torrentParse( ctor, &inf );
953
954    switch( err )
955    {
956        case TR_PARSE_ERR:
957            break;
958
959        case TR_PARSE_DUPLICATE:
960            /* don't complain about .torrent files in the watch directory
961             * that have already been added... that gets annoying and we
962             * don't want to be nagging users to clean up their watch dirs */
963            if( !tr_ctorGetSourceFile(ctor) || !core->priv->adding_from_watch_dir )
964                tr_core_errsig( core, err, inf.name );
965            tr_metainfoFree( &inf );
966            break;
967
968        default:
969            if( doPrompt )
970                g_signal_emit( core, core_signals[ADD_PROMPT_SIGNAL], 0, ctor );
971            else {
972                tr_session * session = tr_core_session( core );
973                TrTorrent * gtor = tr_torrent_new_ctor( session, ctor, &err );
974                if( !err )
975                    tr_core_add_torrent( core, gtor, doNotify );
976            }
977            tr_metainfoFree( &inf );
978            break;
979    }
980
981    return err;
982}
983
984void
985tr_core_add_ctor( TrCore * core, tr_ctor * ctor )
986{
987    const gboolean doPrompt = gtr_pref_flag_get( PREF_KEY_OPTIONS_PROMPT );
988    const gboolean doNotify = FALSE;
989    tr_core_apply_defaults( ctor );
990    add_ctor( core, ctor, doPrompt, doNotify );
991}
992
993/* invoked remotely via dbus. */
994gboolean
995tr_core_add_metainfo( TrCore      * core,
996                      const char  * payload,
997                      gboolean    * setme_handled,
998                      GError     ** gerr UNUSED )
999{
1000    tr_session * session = tr_core_session( core );
1001
1002    if( !session )
1003    {
1004        *setme_handled = FALSE;
1005    }
1006    else if( gtr_is_supported_url( payload ) || gtr_is_magnet_link( payload ) )
1007    {
1008        tr_core_add_from_url( core, payload );
1009        *setme_handled = TRUE;
1010    }
1011    else /* base64-encoded metainfo */
1012    {
1013        int file_length;
1014        tr_ctor * ctor;
1015        char * file_contents;
1016        gboolean do_prompt = gtr_pref_flag_get( PREF_KEY_OPTIONS_PROMPT );
1017
1018        ctor = tr_ctorNew( session );
1019        tr_core_apply_defaults( ctor );
1020
1021        file_contents = tr_base64_decode( payload, -1, &file_length );
1022        tr_ctorSetMetainfo( ctor, (const uint8_t*)file_contents, file_length );
1023        add_ctor( core, ctor, do_prompt, TRUE );
1024
1025        tr_free( file_contents );
1026        tr_core_torrents_added( core );
1027        *setme_handled = TRUE;
1028    }
1029
1030    return TRUE;
1031}
1032
1033/***
1034****
1035***/
1036
1037struct url_dialog_data
1038{
1039    TrCore * core;
1040    tr_ctor * ctor;
1041    char * url;
1042    long response_code;
1043};
1044
1045static gboolean
1046onURLDoneIdle( gpointer vdata )
1047{
1048    struct url_dialog_data * data = vdata;
1049
1050    if( data->response_code != 200 )
1051    {
1052        gtr_http_failure_dialog( NULL, data->url, data->response_code );
1053    }
1054    else
1055    {
1056        const gboolean doPrompt = gtr_pref_flag_get( PREF_KEY_OPTIONS_PROMPT );
1057        const gboolean doNotify = FALSE;
1058        const int err = add_ctor( data->core, data->ctor, doPrompt, doNotify );
1059
1060        if( err == TR_PARSE_ERR )
1061            tr_core_errsig( data->core, TR_PARSE_ERR, data->url );
1062
1063        tr_core_torrents_added( data->core );
1064    }
1065
1066    /* cleanup */
1067    coreDecBusy( data->core );
1068    g_free( data->url );
1069    g_free( data );
1070    return FALSE;
1071}
1072
1073static void
1074onURLDone( tr_session   * session,
1075           long           response_code,
1076           const void   * response,
1077           size_t         response_byte_count,
1078           void         * vdata )
1079{
1080    struct url_dialog_data * data = vdata;
1081
1082    data->response_code = response_code;
1083    data->ctor = tr_ctorNew( session );
1084    tr_core_apply_defaults( data->ctor );
1085    tr_ctorSetMetainfo( data->ctor, response, response_byte_count );
1086
1087    gtr_idle_add( onURLDoneIdle, data );
1088}
1089
1090void
1091tr_core_add_from_url( TrCore * core, const char * url )
1092{
1093    tr_session * session = tr_core_session( core );
1094    const gboolean is_magnet_link = gtr_is_magnet_link( url );
1095
1096    if( is_magnet_link || gtr_is_hex_hashcode( url ) )
1097    {
1098        int err;
1099        char * tmp = NULL;
1100        tr_ctor * ctor = tr_ctorNew( session );
1101
1102        if( gtr_is_hex_hashcode( url ) )
1103            url = tmp = g_strdup_printf( "magnet:?xt=urn:btih:%s", url );
1104
1105        err = tr_ctorSetMetainfoFromMagnetLink( ctor, url );
1106
1107        if( !err )
1108            tr_core_add_ctor( core, ctor );
1109        else {
1110            gtr_unrecognized_url_dialog( NULL, url );
1111            tr_ctorFree( ctor );
1112        }
1113
1114        g_free( tmp );
1115    }
1116    else
1117    {
1118        struct url_dialog_data * data = g_new( struct url_dialog_data, 1 );
1119        data->core = core;
1120        data->url = g_strdup( url );
1121        coreIncBusy( data->core );
1122        tr_webRun( session, url, NULL, onURLDone, data );
1123    }
1124}
1125
1126/***
1127****
1128***/
1129
1130static void
1131add_filename( TrCore      * core,
1132              const char  * filename,
1133              gboolean      doStart,
1134              gboolean      doPrompt,
1135              gboolean      doNotify )
1136{
1137    tr_session * session = tr_core_session( core );
1138
1139    if( session == NULL )
1140        return;
1141
1142    if( gtr_is_supported_url( filename ) || gtr_is_magnet_link( filename ) )
1143    {
1144        tr_core_add_from_url( core, filename );
1145    }
1146    else if( g_file_test( filename, G_FILE_TEST_EXISTS ) )
1147    {
1148        int err;
1149
1150        tr_ctor * ctor = tr_ctorNew( session );
1151        tr_ctorSetMetainfoFromFile( ctor, filename );
1152        tr_core_apply_defaults( ctor );
1153        tr_ctorSetPaused( ctor, TR_FORCE, !doStart );
1154
1155        err = add_ctor( core, ctor, doPrompt, doNotify );
1156        if( err == TR_PARSE_ERR )
1157            tr_core_errsig( core, TR_PARSE_ERR, filename );
1158    }
1159    else if( gtr_is_hex_hashcode( filename ) )
1160    {
1161        tr_core_add_from_url( core, filename );
1162    }
1163}
1164
1165gboolean
1166tr_core_present_window( TrCore      * core UNUSED,
1167                        gboolean *         success,
1168                        GError     ** err  UNUSED )
1169{
1170    /* Setting the toggle-main-window GtkCheckMenuItem to
1171       make sure its state is correctly set */
1172    gtr_action_set_toggled( "toggle-main-window", TRUE);
1173
1174    *success = TRUE;
1175    return TRUE;
1176}
1177
1178void
1179tr_core_add_list( TrCore       * core,
1180                  GSList       * torrentFiles,
1181                  gboolean       doStart,
1182                  gboolean       doPrompt,
1183                  gboolean       doNotify )
1184{
1185    GSList * l;
1186
1187    for( l=torrentFiles; l!=NULL; l=l->next )
1188    {
1189        char * filename = l->data;
1190        add_filename( core, filename, doStart, doPrompt, doNotify );
1191        g_free( filename );
1192    }
1193
1194    tr_core_torrents_added( core );
1195
1196    g_slist_free( torrentFiles );
1197}
1198
1199void
1200tr_core_add_list_defaults( TrCore * core, GSList * torrentFiles, gboolean doNotify )
1201{
1202    const gboolean doStart = gtr_pref_flag_get( TR_PREFS_KEY_START );
1203    const gboolean doPrompt = gtr_pref_flag_get( PREF_KEY_OPTIONS_PROMPT );
1204
1205    tr_core_add_list( core, torrentFiles, doStart, doPrompt, doNotify );
1206}
1207
1208void
1209tr_core_torrents_added( TrCore * self )
1210{
1211    tr_core_update( self );
1212    tr_core_errsig( self, TR_CORE_ERR_NO_MORE_TORRENTS, NULL );
1213}
1214
1215static gboolean
1216findTorrentInModel( TrCore *      core,
1217                    int           id,
1218                    GtkTreeIter * setme )
1219{
1220    int            match = 0;
1221    GtkTreeIter    iter;
1222    GtkTreeModel * model = tr_core_model( core );
1223
1224    if( gtk_tree_model_iter_children( model, &iter, NULL ) ) do
1225        {
1226            tr_torrent * tor;
1227            gtk_tree_model_get( model, &iter, MC_TORRENT_RAW, &tor, -1 );
1228            match = tr_torrentId( tor ) == id;
1229        }
1230        while( !match && gtk_tree_model_iter_next( model, &iter ) );
1231
1232    if( match )
1233        *setme = iter;
1234
1235    return match;
1236}
1237
1238void
1239tr_core_remove_torrent( TrCore * core, TrTorrent * gtor, gboolean deleteFiles )
1240{
1241    const tr_torrent * tor = tr_torrent_handle( gtor );
1242
1243    if( tor != NULL )
1244        tr_core_remove_torrent_from_id( core, tr_torrentId( tor ), deleteFiles );
1245}
1246
1247void
1248tr_core_remove_torrent_from_id( TrCore * core, int id, gboolean deleteFiles )
1249{
1250    GtkTreeIter iter;
1251
1252    if( findTorrentInModel( core, id, &iter ) )
1253    {
1254        TrTorrent * gtor = NULL;
1255        tr_torrent * tor = NULL;
1256        GtkTreeModel * model = tr_core_model( core );
1257
1258        gtk_tree_model_get( model, &iter, MC_TORRENT, &gtor,
1259                                          MC_TORRENT_RAW, &tor,
1260                                          -1 );
1261
1262        /* remove from the gui */
1263        gtk_list_store_remove( GTK_LIST_STORE( model ), &iter );
1264
1265        /* remove the torrent */
1266        tr_torrent_set_delete_local_data_flag( gtor, deleteFiles );
1267        tr_torrent_set_remove_flag( gtor, TRUE );
1268        gtr_warn_if_fail( G_OBJECT( gtor )->ref_count == 1 );
1269        g_object_unref( G_OBJECT( gtor ) ); /* remove the last refcount */
1270    }
1271}
1272
1273/***
1274****
1275***/
1276
1277static gboolean
1278update_foreach( GtkTreeModel * model,
1279                GtkTreePath  * path UNUSED,
1280                GtkTreeIter  * iter,
1281                gpointer       data UNUSED )
1282{
1283    int oldActivity, newActivity;
1284    int oldActivePeerCount, newActivePeerCount;
1285    int oldError, newError;
1286    tr_bool oldFinished, newFinished;
1287    tr_priority_t oldPriority, newPriority;
1288    char * oldCollatedName, * newCollatedName;
1289    char * oldTrackers, * newTrackers;
1290    double oldUpSpeed, newUpSpeed;
1291    double oldDownSpeed, newDownSpeed;
1292    gboolean oldActive, newActive;
1293    const tr_stat * st;
1294    TrTorrent * gtor;
1295    tr_torrent * tor;
1296
1297    /* get the old states */
1298    gtk_tree_model_get( model, iter,
1299                        MC_TORRENT, &gtor,
1300                        MC_NAME_COLLATED, &oldCollatedName,
1301                        MC_ACTIVE, &oldActive,
1302                        MC_ACTIVE_PEER_COUNT, &oldActivePeerCount,
1303                        MC_ERROR, &oldError,
1304                        MC_ACTIVITY, &oldActivity,
1305                        MC_FINISHED, &oldFinished,
1306                        MC_PRIORITY, &oldPriority,
1307                        MC_TRACKERS, &oldTrackers,
1308                        MC_SPEED_UP, &oldUpSpeed,
1309                        MC_SPEED_DOWN, &oldDownSpeed,
1310                        -1 );
1311
1312    /* get the new states */
1313    tor = tr_torrent_handle( gtor );
1314    st = tr_torrentStat( tor );
1315    newActive = isTorrentActive( st );
1316    newActivity = st->activity;
1317    newFinished = st->finished;
1318    newPriority = tr_torrentGetPriority( tor );
1319    newTrackers = torrentTrackerString( tor );
1320    newUpSpeed = st->pieceUploadSpeed_KBps;
1321    newDownSpeed = st->pieceDownloadSpeed_KBps;
1322    newActivePeerCount = st->peersSendingToUs + st->peersGettingFromUs + st->webseedsSendingToUs;
1323    newError = st->error;
1324    newCollatedName = get_collated_name( tr_torrent_info( gtor ) );
1325
1326    /* updating the model triggers off resort/refresh,
1327       so don't do it unless something's actually changed... */
1328    if( ( newActive != oldActive )
1329        || ( newActivity  != oldActivity )
1330        || ( newFinished != oldFinished )
1331        || ( newPriority != oldPriority )
1332        || ( newError != oldError )
1333        || ( newActivePeerCount != oldActivePeerCount )
1334        || gtr_strcmp0( oldTrackers, newTrackers )
1335        || gtr_strcmp0( oldCollatedName, newCollatedName )
1336        || gtr_compare_double( newUpSpeed, oldUpSpeed, 3 )
1337        || gtr_compare_double( newDownSpeed, oldDownSpeed, 3 ) )
1338    {
1339        gtk_list_store_set( GTK_LIST_STORE( model ), iter,
1340                            MC_ACTIVE, newActive,
1341                            MC_ACTIVE_PEER_COUNT, newActivePeerCount,
1342                            MC_ERROR, newError,
1343                            MC_ACTIVITY, newActivity,
1344                            MC_NAME_COLLATED, newCollatedName,
1345                            MC_FINISHED, newFinished,
1346                            MC_PRIORITY, newPriority,
1347                            MC_TRACKERS, newTrackers,
1348                            MC_SPEED_UP, newUpSpeed,
1349                            MC_SPEED_DOWN, newDownSpeed,
1350                            -1 );
1351    }
1352
1353    /* cleanup */
1354    g_object_unref( gtor );
1355    g_free( newCollatedName );
1356    g_free( oldCollatedName );
1357    g_free( newTrackers );
1358    g_free( oldTrackers );
1359    return FALSE;
1360}
1361
1362void
1363tr_core_update( TrCore * self )
1364{
1365    int               column;
1366    GtkSortType       order;
1367    GtkTreeSortable * sortable;
1368    GtkTreeModel *    model = tr_core_model( self );
1369
1370    /* pause sorting */
1371    sortable = GTK_TREE_SORTABLE( model );
1372    gtk_tree_sortable_get_sort_column_id( sortable, &column, &order );
1373    gtk_tree_sortable_set_sort_column_id(
1374        sortable, GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, order );
1375
1376    /* refresh the model */
1377    gtk_tree_model_foreach( model, update_foreach, NULL );
1378
1379    /* resume sorting */
1380    gtk_tree_sortable_set_sort_column_id( sortable, column, order );
1381
1382    /* maybe inhibit hibernation */
1383    maybeInhibitHibernation( self );
1384}
1385
1386/**
1387***  Hibernate
1388**/
1389
1390#ifdef HAVE_DBUS_GLIB
1391
1392static DBusGProxy*
1393get_hibernation_inhibit_proxy( void )
1394{
1395    DBusGConnection * conn;
1396    GError * error = NULL;
1397    const char * name = "org.gnome.SessionManager";
1398    const char * path = "/org/gnome/SessionManager";
1399    const char * interface = "org.gnome.SessionManager";
1400
1401    conn = dbus_g_bus_get( DBUS_BUS_SESSION, &error );
1402    if( error )
1403    {
1404        g_warning ( "DBUS cannot connect : %s", error->message );
1405        g_error_free ( error );
1406        return NULL;
1407    }
1408
1409    return dbus_g_proxy_new_for_name ( conn, name, path, interface );
1410}
1411
1412static gboolean
1413gtr_inhibit_hibernation( guint * cookie )
1414{
1415    gboolean     success = FALSE;
1416    DBusGProxy * proxy = get_hibernation_inhibit_proxy( );
1417
1418    if( proxy )
1419    {
1420        GError * error = NULL;
1421        const int toplevel_xid = 0;
1422        const char * application = _( "Transmission Bittorrent Client" );
1423        const char * reason = _( "BitTorrent Activity" );
1424        const int flags = 4; /* Inhibit suspending the session or computer */
1425
1426        success = dbus_g_proxy_call( proxy, "Inhibit", &error,
1427                                     G_TYPE_STRING, application,
1428                                     G_TYPE_UINT, toplevel_xid,
1429                                     G_TYPE_STRING, reason,
1430                                     G_TYPE_UINT, flags,
1431                                     G_TYPE_INVALID, /* sentinel - end of input args */
1432                                     G_TYPE_UINT, cookie,
1433                                     G_TYPE_INVALID /* senitnel - end of output args */ );
1434
1435        if( success )
1436            tr_inf( "%s", _( "Disallowing desktop hibernation" ) );
1437        else
1438        {
1439            tr_err( _( "Couldn't disable desktop hibernation: %s" ),
1440                    error->message );
1441            g_error_free( error );
1442        }
1443
1444        g_object_unref( G_OBJECT( proxy ) );
1445    }
1446
1447    return success != 0;
1448}
1449
1450static void
1451gtr_uninhibit_hibernation( guint inhibit_cookie )
1452{
1453    DBusGProxy * proxy = get_hibernation_inhibit_proxy( );
1454
1455    if( proxy )
1456    {
1457        GError * error = NULL;
1458        gboolean success = dbus_g_proxy_call( proxy, "Uninhibit", &error,
1459                                              G_TYPE_UINT, inhibit_cookie,
1460                                              G_TYPE_INVALID,
1461                                              G_TYPE_INVALID );
1462        if( success )
1463            tr_inf( "%s", _( "Allowing desktop hibernation" ) );
1464        else
1465        {
1466            g_warning( "Couldn't uninhibit the system from suspending: %s.",
1467                       error->message );
1468            g_error_free( error );
1469        }
1470
1471        g_object_unref( G_OBJECT( proxy ) );
1472    }
1473}
1474
1475#endif
1476
1477static void
1478tr_core_set_hibernation_allowed( TrCore * core,
1479                                 gboolean allowed )
1480{
1481#ifdef HAVE_DBUS_GLIB
1482    g_return_if_fail( core );
1483    g_return_if_fail( core->priv );
1484
1485    core->priv->inhibit_allowed = allowed != 0;
1486
1487    if( allowed && core->priv->have_inhibit_cookie )
1488    {
1489        gtr_uninhibit_hibernation( core->priv->inhibit_cookie );
1490        core->priv->have_inhibit_cookie = FALSE;
1491    }
1492
1493    if( !allowed
1494      && !core->priv->have_inhibit_cookie
1495      && !core->priv->dbus_error )
1496    {
1497        if( gtr_inhibit_hibernation( &core->priv->inhibit_cookie ) )
1498            core->priv->have_inhibit_cookie = TRUE;
1499        else
1500            core->priv->dbus_error = TRUE;
1501    }
1502#endif
1503}
1504
1505static void
1506maybeInhibitHibernation( TrCore * core )
1507{
1508    /* hibernation is allowed if EITHER
1509     * (a) the "inhibit" pref is turned off OR
1510     * (b) there aren't any active torrents */
1511    const gboolean hibernation_allowed = !gtr_pref_flag_get( PREF_KEY_INHIBIT_HIBERNATION )
1512                                      || !tr_core_get_active_torrent_count( core );
1513    tr_core_set_hibernation_allowed( core, hibernation_allowed );
1514}
1515
1516/**
1517***  Prefs
1518**/
1519
1520static void
1521commitPrefsChange( TrCore * core, const char * key )
1522{
1523    g_signal_emit( core, core_signals[PREFS_SIGNAL], 0, key );
1524    gtr_pref_save( tr_core_session( core ) );
1525}
1526
1527void
1528tr_core_set_pref( TrCore * self, const char * key, const char * newval )
1529{
1530    const char * oldval = gtr_pref_string_get( key );
1531
1532    if( gtr_strcmp0( oldval, newval ) )
1533    {
1534        gtr_pref_string_set( key, newval );
1535        commitPrefsChange( self, key );
1536    }
1537}
1538
1539void
1540tr_core_set_pref_bool( TrCore *     self,
1541                       const char * key,
1542                       gboolean     newval )
1543{
1544    const gboolean oldval = gtr_pref_flag_get( key );
1545
1546    if( oldval != newval )
1547    {
1548        gtr_pref_flag_set( key, newval );
1549        commitPrefsChange( self, key );
1550    }
1551}
1552
1553void
1554tr_core_set_pref_int( TrCore *     self,
1555                      const char * key,
1556                      int          newval )
1557{
1558    const int oldval = gtr_pref_int_get( key );
1559
1560    if( oldval != newval )
1561    {
1562        gtr_pref_int_set( key, newval );
1563        commitPrefsChange( self, key );
1564    }
1565}
1566
1567void
1568tr_core_set_pref_double( TrCore *     self,
1569                         const char * key,
1570                         double       newval )
1571{
1572    const double oldval = gtr_pref_double_get( key );
1573
1574    if( gtr_compare_double( oldval, newval, 4 ) )
1575    {
1576        gtr_pref_double_set( key, newval );
1577        commitPrefsChange( self, key );
1578    }
1579}
1580
1581/***
1582****
1583****  RPC Interface
1584****
1585***/
1586
1587/* #define DEBUG_RPC */
1588
1589static int nextTag = 1;
1590
1591typedef void ( server_response_func )( TrCore * core, tr_benc * response, gpointer user_data );
1592
1593struct pending_request_data
1594{
1595    TrCore * core;
1596    server_response_func * responseFunc;
1597    gpointer responseFuncUserData;
1598};
1599
1600static GHashTable * pendingRequests = NULL;
1601
1602static gboolean
1603readResponseIdle( void * vresponse )
1604{
1605    tr_benc top;
1606    int64_t intVal;
1607    struct evbuffer * response = vresponse;
1608
1609    tr_jsonParse( NULL, evbuffer_pullup( response, -1 ), evbuffer_get_length( response ), &top, NULL );
1610
1611    if( tr_bencDictFindInt( &top, "tag", &intVal ) )
1612    {
1613        const int tag = (int)intVal;
1614        struct pending_request_data * data = g_hash_table_lookup( pendingRequests, &tag );
1615        if( data ) {
1616            if( data->responseFunc )
1617                (*data->responseFunc)(data->core, &top, data->responseFuncUserData );
1618            g_hash_table_remove( pendingRequests, &tag );
1619        }
1620    }
1621
1622    tr_bencFree( &top );
1623    evbuffer_free( response );
1624    return FALSE;
1625}
1626
1627static void
1628readResponse( tr_session  * session UNUSED,
1629              struct evbuffer * response,
1630              void        * unused UNUSED )
1631{
1632    struct evbuffer * buf = evbuffer_new( );
1633    evbuffer_add_buffer( buf, response );
1634    gtr_idle_add( readResponseIdle, buf );
1635}
1636
1637static void
1638sendRequest( TrCore * core, const char * json, int tag,
1639             server_response_func * responseFunc, void * responseFuncUserData )
1640{
1641    tr_session * session = tr_core_session( core );
1642
1643    if( pendingRequests == NULL )
1644    {
1645        pendingRequests = g_hash_table_new_full( g_int_hash, g_int_equal, g_free, g_free );
1646    }
1647
1648    if( session == NULL )
1649    {
1650        g_error( "GTK+ client doesn't support connections to remote servers yet." );
1651    }
1652    else
1653    {
1654        /* remember this request */
1655        struct pending_request_data * data;
1656        data = g_new0( struct pending_request_data, 1 );
1657        data->core = core;
1658        data->responseFunc = responseFunc;
1659        data->responseFuncUserData = responseFuncUserData;
1660        g_hash_table_insert( pendingRequests, g_memdup( &tag, sizeof( int ) ), data );
1661
1662        /* make the request */
1663#ifdef DEBUG_RPC
1664        g_message( "request: [%s]", json );
1665#endif
1666        tr_rpc_request_exec_json( session, json, strlen( json ), readResponse, GINT_TO_POINTER(tag) );
1667    }
1668}
1669
1670/***
1671****  Sending a test-port request via RPC
1672***/
1673
1674static void
1675portTestResponseFunc( TrCore * core, tr_benc * response, gpointer userData UNUSED )
1676{
1677    tr_benc * args;
1678    tr_bool isOpen = FALSE;
1679
1680    if( tr_bencDictFindDict( response, "arguments", &args ) )
1681        tr_bencDictFindBool( args, "port-is-open", &isOpen );
1682
1683    emitPortTested( core, isOpen );
1684}
1685
1686void
1687tr_core_port_test( TrCore * core )
1688{
1689    char buf[128];
1690    const int tag = nextTag++;
1691    g_snprintf( buf, sizeof( buf ), "{ \"method\": \"port-test\", \"tag\": %d }", tag );
1692    sendRequest( core, buf, tag, portTestResponseFunc, NULL );
1693}
1694
1695/***
1696****  Updating a blocklist via RPC
1697***/
1698
1699static void
1700blocklistResponseFunc( TrCore * core, tr_benc * response, gpointer userData UNUSED )
1701{
1702    tr_benc * args;
1703    int64_t ruleCount = -1;
1704
1705    if( tr_bencDictFindDict( response, "arguments", &args ) )
1706        tr_bencDictFindInt( args, "blocklist-size", &ruleCount );
1707
1708    if( ruleCount > 0 )
1709        gtr_pref_int_set( "blocklist-date", time( NULL ) );
1710
1711    emitBlocklistUpdated( core, ruleCount );
1712}
1713
1714void
1715tr_core_blocklist_update( TrCore * core )
1716{
1717    char buf[128];
1718    const int tag = nextTag++;
1719    g_snprintf( buf, sizeof( buf ), "{ \"method\": \"blocklist-update\", \"tag\": %d }", tag );
1720    sendRequest( core, buf, tag, blocklistResponseFunc, NULL );
1721}
1722
1723/***
1724****
1725***/
1726
1727void
1728tr_core_exec_json( TrCore * core, const char * json )
1729{
1730    const int tag = nextTag++;
1731    sendRequest( core, json, tag, NULL, NULL );
1732}
1733
1734void
1735tr_core_exec( TrCore * core, const tr_benc * top )
1736{
1737    char * json = tr_bencToStr( top, TR_FMT_JSON_LEAN, NULL );
1738    tr_core_exec_json( core, json );
1739    tr_free( json );
1740}
1741
1742/***
1743****
1744***/
1745
1746void
1747tr_core_torrent_changed( TrCore * core, int id )
1748{
1749    GtkTreeIter iter;
1750    GtkTreeModel * model = tr_core_model( core );
1751
1752    if( gtk_tree_model_get_iter_first( model, &iter ) ) do
1753    {
1754        tr_torrent * tor;
1755        gtk_tree_model_get( model, &iter, MC_TORRENT_RAW, &tor, -1 );
1756        if( tr_torrentId( tor ) == id )
1757        {
1758            GtkTreePath * path = gtk_tree_model_get_path( model, &iter );
1759            gtk_tree_model_row_changed( model, path, &iter );
1760            gtk_tree_path_free( path );
1761            break;
1762        }
1763    }
1764    while( gtk_tree_model_iter_next( model, &iter ) );
1765}
1766
1767size_t
1768tr_core_get_torrent_count( TrCore * core )
1769{
1770    return gtk_tree_model_iter_n_children( tr_core_model( core ), NULL );
1771}
1772
1773size_t
1774tr_core_get_active_torrent_count( TrCore * core )
1775{
1776    GtkTreeIter iter;
1777    GtkTreeModel * model = tr_core_model( core );
1778    size_t activeCount = 0;
1779
1780    if( gtk_tree_model_get_iter_first( model, &iter ) ) do
1781    {
1782        int activity;
1783        gtk_tree_model_get( model, &iter, MC_ACTIVITY, &activity, -1 );
1784
1785        if( activity != TR_STATUS_STOPPED )
1786            ++activeCount;
1787    }
1788    while( gtk_tree_model_iter_next( model, &iter ) );
1789
1790    return activeCount;
1791}
1792
Note: See TracBrowser for help on using the repository browser.