source: branches/2.1x/gtk/tr-core.c @ 12089

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

(2.1x gtk) #4080 "Toggling speed limit mode from the web interface doesn't affect GUI" -- fixed.

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