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

Last change on this file since 9130 was 9130, checked in by charles, 12 years ago

(trunk gtk) rewrite the 'create torrent' dialog

  • Property svn:keywords set to Date Rev Author Id
File size: 41.7 KB
Line 
1/******************************************************************************
2 * $Id: tr-core.c 9130 2009-09-17 01:28:45Z charles $
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
42#include "conf.h"
43#include "notify.h"
44#include "tr-core.h"
45#ifdef HAVE_DBUS_GLIB
46 #include "tr-core-dbus.h"
47#endif
48#include "tr-prefs.h"
49#include "tr-torrent.h"
50#include "util.h"
51#include "actions.h"
52
53static void     maybeInhibitHibernation( TrCore * core );
54
55static gboolean our_instance_adds_remote_torrents = FALSE;
56
57struct TrCorePrivate
58{
59#ifdef HAVE_GIO
60    GFileMonitor *  monitor;
61    gulong          monitor_tag;
62    char *          monitor_path;
63    GSList *        monitor_files;
64    guint           monitor_idle_tag;
65#endif
66    gboolean        adding_from_watch_dir;
67    gboolean        inhibit_allowed;
68    gboolean        have_inhibit_cookie;
69    gboolean        dbus_error;
70    guint           inhibit_cookie;
71    GtkTreeModel *  model;
72    tr_session *    session;
73};
74
75static int
76isDisposed( const TrCore * core )
77{
78    return !core || !core->priv;
79}
80
81static void
82tr_core_dispose( GObject * obj )
83{
84    TrCore * core = TR_CORE( obj );
85
86    if( !isDisposed( core ) )
87    {
88        GObjectClass * parent;
89
90        core->priv = NULL;
91
92        parent = g_type_class_peek( g_type_parent( TR_CORE_TYPE ) );
93        parent->dispose( obj );
94    }
95}
96
97static void
98tr_core_class_init( gpointer              g_class,
99                    gpointer g_class_data UNUSED )
100{
101    GObjectClass * gobject_class;
102    TrCoreClass *  cc;
103
104    g_type_class_add_private( g_class, sizeof( struct TrCorePrivate ) );
105
106    gobject_class = G_OBJECT_CLASS( g_class );
107    gobject_class->dispose = tr_core_dispose;
108
109    cc = TR_CORE_CLASS( g_class );
110
111    cc->blocklistSignal = g_signal_new( "blocklist-updated",          /* name */
112                                        G_TYPE_FROM_CLASS( g_class ), /* applies to TrCore */
113                                        G_SIGNAL_RUN_FIRST,           /* when to invoke */
114                                        0, NULL, NULL,                /* accumulator */
115                                        g_cclosure_marshal_VOID__INT, /* marshaler */
116                                        G_TYPE_NONE,                  /* return type */
117                                        1, G_TYPE_INT );              /* signal arguments */
118
119    cc->portSignal = g_signal_new( "port-tested",
120                                   G_TYPE_FROM_CLASS( g_class ),
121                                   G_SIGNAL_RUN_LAST,
122                                   0, NULL, NULL,
123                                   g_cclosure_marshal_VOID__BOOLEAN,
124                                   G_TYPE_NONE,
125                                   1, G_TYPE_BOOLEAN );
126
127    cc->errsig = g_signal_new( "error",
128                               G_TYPE_FROM_CLASS( g_class ),
129                               G_SIGNAL_RUN_LAST,
130                               0, NULL, NULL,
131                               g_cclosure_marshal_VOID__UINT_POINTER,
132                               G_TYPE_NONE,
133                               2, G_TYPE_UINT, G_TYPE_POINTER );
134
135    cc->promptsig = g_signal_new( "add-torrent-prompt",
136                                  G_TYPE_FROM_CLASS( g_class ),
137                                  G_SIGNAL_RUN_LAST,
138                                  0, NULL, NULL,
139                                  g_cclosure_marshal_VOID__POINTER,
140                                  G_TYPE_NONE,
141                                  1, G_TYPE_POINTER );
142
143    cc->quitsig = g_signal_new( "quit",
144                                G_TYPE_FROM_CLASS( g_class ),
145                                G_SIGNAL_RUN_LAST,
146                                0, NULL, NULL,
147                                g_cclosure_marshal_VOID__VOID,
148                                G_TYPE_NONE,
149                                0 );
150
151    cc->prefsig = g_signal_new( "prefs-changed",
152                                G_TYPE_FROM_CLASS( g_class ),
153                                G_SIGNAL_RUN_LAST,
154                                0, NULL, NULL,
155                                g_cclosure_marshal_VOID__STRING,
156                                G_TYPE_NONE,
157                                1, G_TYPE_STRING );
158
159#ifdef HAVE_DBUS_GLIB
160    {
161        DBusGConnection * bus = dbus_g_bus_get( DBUS_BUS_SESSION, NULL );
162        DBusGProxy *      bus_proxy = NULL;
163        if( bus )
164            bus_proxy =
165                dbus_g_proxy_new_for_name( bus, "org.freedesktop.DBus",
166                                           "/org/freedesktop/DBus",
167                                           "org.freedesktop.DBus" );
168        if( bus_proxy )
169        {
170            int result = 0;
171            dbus_g_proxy_call( bus_proxy, "RequestName", NULL,
172                               G_TYPE_STRING,
173                               "com.transmissionbt.Transmission",
174                               G_TYPE_UINT, 0,
175                               G_TYPE_INVALID,
176                               G_TYPE_UINT, &result,
177                               G_TYPE_INVALID );
178            if( ( our_instance_adds_remote_torrents = result == 1 ) )
179                dbus_g_object_type_install_info(
180                    TR_CORE_TYPE,
181                    &
182                    dbus_glib_tr_core_object_info );
183        }
184    }
185#endif
186}
187
188/***
189****  SORTING
190***/
191
192static gboolean
193isValidETA( int t )
194{
195    return ( t != TR_ETA_NOT_AVAIL ) && ( t != TR_ETA_UNKNOWN );
196}
197
198static int
199compareETA( int a, int b )
200{
201    const gboolean a_valid = isValidETA( a );
202    const gboolean b_valid = isValidETA( b );
203
204    if( !a_valid && !b_valid ) return 0;
205    if( !a_valid ) return -1;
206    if( !b_valid ) return 1;
207    return a < b ? 1 : -1;
208}
209
210static int
211compareDouble( double a, double b )
212{
213    if( a < b ) return -1;
214    if( a > b ) return 1;
215    return 0;
216}
217
218static int
219compareRatio( double a, double b )
220{
221    if( (int)a == TR_RATIO_INF && (int)b == TR_RATIO_INF ) return 0;
222    if( (int)a == TR_RATIO_INF ) return 1;
223    if( (int)b == TR_RATIO_INF ) return -1;
224    return compareDouble( a, b );
225}
226
227static int
228compareTime( time_t a, time_t b )
229{
230    if( a < b ) return -1;
231    if( a > b ) return 1;
232    return 0;
233}
234
235static int
236compareByRatio( GtkTreeModel  * model,
237                GtkTreeIter   * a,
238                GtkTreeIter   * b,
239                gpointer        user_data UNUSED )
240{
241    tr_torrent *ta, *tb;
242    const tr_stat *sa, *sb;
243
244    gtk_tree_model_get( model, a, MC_TORRENT_RAW, &ta, -1 );
245    gtk_tree_model_get( model, b, MC_TORRENT_RAW, &tb, -1 );
246
247    sa = tr_torrentStatCached( ta );
248    sb = tr_torrentStatCached( tb );
249
250    return compareRatio( sa->ratio, sb->ratio );
251}
252
253static int
254compareByActivity( GtkTreeModel * model,
255                   GtkTreeIter  * a,
256                   GtkTreeIter  * b,
257                   gpointer       user_data UNUSED )
258{
259    int i;
260    tr_torrent *ta, *tb;
261    const tr_stat *sa, *sb;
262    double aUp, aDown, bUp, bDown;
263
264    gtk_tree_model_get( model, a, MC_SPEED_UP, &aUp,
265                                  MC_SPEED_DOWN, &aDown,
266                                  MC_TORRENT_RAW, &ta,
267                                  -1 );
268    gtk_tree_model_get( model, b, MC_SPEED_UP, &bUp,
269                                  MC_SPEED_DOWN, &bDown,
270                                  MC_TORRENT_RAW, &tb,
271                                  -1 );
272
273    if(( i = compareDouble( aUp+aDown, bUp+bDown )))
274        return i;
275
276    sa = tr_torrentStatCached( ta );
277    sb = tr_torrentStatCached( tb );
278    if( sa->uploadedEver != sb->uploadedEver )
279        return sa->uploadedEver < sa->uploadedEver ? -1 : 1;
280
281    return 0;
282}
283
284static int
285compareByName( GtkTreeModel *             model,
286               GtkTreeIter *              a,
287               GtkTreeIter *              b,
288               gpointer         user_data UNUSED )
289{
290    int   ret;
291    char *ca, *cb;
292
293    gtk_tree_model_get( model, a, MC_NAME_COLLATED, &ca, -1 );
294    gtk_tree_model_get( model, b, MC_NAME_COLLATED, &cb, -1 );
295    ret = strcmp( ca, cb );
296    g_free( cb );
297    g_free( ca );
298    return ret;
299}
300
301static int
302compareByAge( GtkTreeModel * model,
303              GtkTreeIter  * a,
304              GtkTreeIter  * b,
305              gpointer       user_data UNUSED )
306{
307    tr_torrent *ta, *tb;
308
309    gtk_tree_model_get( model, a, MC_TORRENT_RAW, &ta, -1 );
310    gtk_tree_model_get( model, b, MC_TORRENT_RAW, &tb, -1 );
311    return compareTime( tr_torrentStatCached( ta )->addedDate,
312                        tr_torrentStatCached( tb )->addedDate );
313}
314
315static int
316compareBySize( GtkTreeModel * model,
317               GtkTreeIter  * a,
318               GtkTreeIter  * b,
319               gpointer       user_data UNUSED )
320{
321    tr_torrent *t;
322    const tr_info *ia, *ib;
323
324    gtk_tree_model_get( model, a, MC_TORRENT_RAW, &t, -1 );
325    ia = tr_torrentInfo( t );
326    gtk_tree_model_get( model, b, MC_TORRENT_RAW, &t, -1 );
327    ib = tr_torrentInfo( t );
328
329    if( ia->totalSize < ib->totalSize ) return 1;
330    if( ia->totalSize > ib->totalSize ) return -1;
331    return 0;
332}
333
334static int
335compareByProgress( GtkTreeModel *             model,
336                   GtkTreeIter *              a,
337                   GtkTreeIter *              b,
338                   gpointer         user_data UNUSED )
339{
340    int ret;
341    tr_torrent * t;
342    const tr_stat *sa, *sb;
343
344    gtk_tree_model_get( model, a, MC_TORRENT_RAW, &t, -1 );
345    sa = tr_torrentStatCached( t );
346    gtk_tree_model_get( model, b, MC_TORRENT_RAW, &t, -1 );
347    sb = tr_torrentStatCached( t );
348    ret = compareDouble( sa->percentDone, sb->percentDone );
349    if( !ret )
350        ret = compareRatio( sa->ratio, sb->ratio );
351    return ret;
352}
353
354static int
355compareByETA( GtkTreeModel * model,
356              GtkTreeIter  * a,
357              GtkTreeIter  * b,
358              gpointer       user_data UNUSED )
359{
360    tr_torrent *ta, *tb;
361
362    gtk_tree_model_get( model, a, MC_TORRENT_RAW, &ta, -1 );
363    gtk_tree_model_get( model, b, MC_TORRENT_RAW, &tb, -1 );
364
365    return compareETA( tr_torrentStatCached( ta )->eta,
366                       tr_torrentStatCached( tb )->eta );
367}
368
369static int
370compareByState( GtkTreeModel * model,
371                GtkTreeIter *  a,
372                GtkTreeIter *  b,
373                gpointer       user_data )
374{
375    int sa, sb, ret;
376
377    /* first by state */
378    gtk_tree_model_get( model, a, MC_ACTIVITY, &sa, -1 );
379    gtk_tree_model_get( model, b, MC_ACTIVITY, &sb, -1 );
380    ret = sa - sb;
381
382    /* second by progress */
383    if( !ret )
384        ret = compareByProgress( model, a, b, user_data );
385
386    return ret;
387}
388
389static int
390compareByTracker( GtkTreeModel * model,
391                  GtkTreeIter  * a,
392                  GtkTreeIter  * b,
393                  gpointer       user_data UNUSED )
394{
395    const tr_torrent *ta, *tb;
396
397    gtk_tree_model_get( model, a, MC_TORRENT_RAW, &ta, -1 );
398    gtk_tree_model_get( model, b, MC_TORRENT_RAW, &tb, -1 );
399    return strcmp( tr_torrentInfo( ta )->trackers[0].announce,
400                   tr_torrentInfo( tb )->trackers[0].announce );
401}
402
403static void
404setSort( TrCore *     core,
405         const char * mode,
406         gboolean     isReversed  )
407{
408    const int              col = MC_TORRENT_RAW;
409    GtkTreeIterCompareFunc sort_func;
410    GtkSortType            type =
411        isReversed ? GTK_SORT_ASCENDING : GTK_SORT_DESCENDING;
412    GtkTreeSortable *      sortable =
413        GTK_TREE_SORTABLE( tr_core_model( core ) );
414
415    if( !strcmp( mode, "sort-by-activity" ) )
416        sort_func = compareByActivity;
417    else if( !strcmp( mode, "sort-by-age" ) )
418        sort_func = compareByAge;
419    else if( !strcmp( mode, "sort-by-progress" ) )
420        sort_func = compareByProgress;
421    else if( !strcmp( mode, "sort-by-time-left" ) )
422        sort_func = compareByETA;
423    else if( !strcmp( mode, "sort-by-ratio" ) )
424        sort_func = compareByRatio;
425    else if( !strcmp( mode, "sort-by-state" ) )
426        sort_func = compareByState;
427    else if( !strcmp( mode, "sort-by-tracker" ) )
428        sort_func = compareByTracker;
429    else if( !strcmp( mode, "sort-by-size" ) )
430        sort_func = compareBySize;
431    else {
432        sort_func = compareByName;
433        type = isReversed ? GTK_SORT_DESCENDING : GTK_SORT_ASCENDING;
434    }
435
436    gtk_tree_sortable_set_sort_func( sortable, col, sort_func, NULL, NULL );
437    gtk_tree_sortable_set_sort_column_id( sortable, col, type );
438}
439
440static void
441tr_core_apply_defaults( tr_ctor * ctor )
442{
443    if( tr_ctorGetPaused( ctor, TR_FORCE, NULL ) )
444        tr_ctorSetPaused( ctor, TR_FORCE, !pref_flag_get( PREF_KEY_START ) );
445
446    if( tr_ctorGetDeleteSource( ctor, NULL ) )
447        tr_ctorSetDeleteSource( ctor,
448                               pref_flag_get( PREF_KEY_TRASH_ORIGINAL ) );
449
450    if( tr_ctorGetPeerLimit( ctor, TR_FORCE, NULL ) )
451        tr_ctorSetPeerLimit( ctor, TR_FORCE,
452                             pref_int_get( TR_PREFS_KEY_PEER_LIMIT_TORRENT ) );
453
454    if( tr_ctorGetDownloadDir( ctor, TR_FORCE, NULL ) )
455    {
456        const char * path = pref_string_get( TR_PREFS_KEY_DOWNLOAD_DIR );
457        tr_ctorSetDownloadDir( ctor, TR_FORCE, path );
458    }
459}
460
461static int
462tr_strcmp( const void * a,
463           const void * b )
464{
465    if( a && b ) return strcmp( a, b );
466    if( a ) return 1;
467    if( b ) return -1;
468    return 0;
469}
470
471#ifdef HAVE_GIO
472static gboolean
473watchFolderIdle( gpointer gcore )
474{
475    TrCore * core = TR_CORE( gcore );
476
477    core->priv->adding_from_watch_dir = TRUE;
478    tr_core_add_list_defaults( core, core->priv->monitor_files, TRUE );
479    core->priv->adding_from_watch_dir = FALSE;
480
481    /* cleanup */
482    core->priv->monitor_files = NULL;
483    core->priv->monitor_idle_tag = 0;
484    return FALSE;
485}
486
487static void
488maybeAddTorrent( TrCore *     core,
489                 const char * filename )
490{
491    const gboolean isTorrent = g_str_has_suffix( filename, ".torrent" );
492
493    if( isTorrent )
494    {
495        struct TrCorePrivate * p = core->priv;
496
497        if( !g_slist_find_custom( p->monitor_files, filename,
498                                  (GCompareFunc)strcmp ) )
499            p->monitor_files =
500                g_slist_append( p->monitor_files, g_strdup( filename ) );
501        if( !p->monitor_idle_tag )
502            p->monitor_idle_tag = gtr_timeout_add_seconds( 1, watchFolderIdle, core );
503    }
504}
505
506static void
507watchFolderChanged( GFileMonitor       * monitor    UNUSED,
508                    GFile *                         file,
509                    GFile              * other_type UNUSED,
510                    GFileMonitorEvent               event_type,
511                    gpointer                        core )
512{
513    if( event_type == G_FILE_MONITOR_EVENT_CREATED )
514    {
515        char * filename = g_file_get_path( file );
516        maybeAddTorrent( core, filename );
517        g_free( filename );
518    }
519}
520
521static void
522scanWatchDir( TrCore * core )
523{
524    const gboolean isEnabled = pref_flag_get( PREF_KEY_DIR_WATCH_ENABLED );
525
526    if( isEnabled )
527    {
528        const char * basename;
529        const char * dirname = pref_string_get( PREF_KEY_DIR_WATCH );
530        GDir * dir = g_dir_open( dirname, 0, NULL );
531
532        while(( basename = g_dir_read_name( dir )))
533        {
534            char * filename = g_build_filename( dirname, basename, NULL );
535            maybeAddTorrent( core, filename );
536            g_free( filename );
537        }
538
539        g_dir_close( dir );
540    }
541}
542
543static void
544updateWatchDir( TrCore * core )
545{
546    const char *           filename = pref_string_get( PREF_KEY_DIR_WATCH );
547    const gboolean         isEnabled = pref_flag_get(
548        PREF_KEY_DIR_WATCH_ENABLED );
549    struct TrCorePrivate * p = TR_CORE( core )->priv;
550
551    if( p->monitor && ( !isEnabled || tr_strcmp( filename, p->monitor_path ) ) )
552    {
553        g_signal_handler_disconnect( p->monitor, p->monitor_tag );
554        g_free( p->monitor_path );
555        g_file_monitor_cancel( p->monitor );
556        g_object_unref( G_OBJECT( p->monitor ) );
557        p->monitor_path = NULL;
558        p->monitor = NULL;
559        p->monitor_tag = 0;
560    }
561
562    if( isEnabled && !p->monitor )
563    {
564        GFile *        file = g_file_new_for_path( filename );
565        GFileMonitor * m = g_file_monitor_directory( file, 0, NULL, NULL );
566        scanWatchDir( core );
567        p->monitor = m;
568        p->monitor_path = g_strdup( filename );
569        p->monitor_tag = g_signal_connect( m, "changed",
570                                           G_CALLBACK(
571                                               watchFolderChanged ), core );
572    }
573}
574
575#endif
576
577static void
578prefsChanged( TrCore *      core,
579              const char *  key,
580              gpointer data UNUSED )
581{
582    if( !strcmp( key, PREF_KEY_SORT_MODE )
583      || !strcmp( key, PREF_KEY_SORT_REVERSED ) )
584    {
585        const char * mode = pref_string_get( PREF_KEY_SORT_MODE );
586        gboolean     isReversed = pref_flag_get( PREF_KEY_SORT_REVERSED );
587        setSort( core, mode, isReversed );
588    }
589    else if( !strcmp( key, TR_PREFS_KEY_PEER_LIMIT_GLOBAL ) )
590    {
591        const uint16_t val = pref_int_get( key );
592        tr_sessionSetPeerLimit( tr_core_session( core ), val );
593    }
594    else if( !strcmp( key, TR_PREFS_KEY_PEER_LIMIT_TORRENT ) )
595    {
596        const uint16_t val = pref_int_get( key );
597        tr_sessionSetPeerLimitPerTorrent( tr_core_session( core ), val );
598    }
599    else if( !strcmp( key, PREF_KEY_INHIBIT_HIBERNATION ) )
600    {
601        maybeInhibitHibernation( core );
602    }
603#ifdef HAVE_GIO
604    else if( !strcmp( key, PREF_KEY_DIR_WATCH )
605           || !strcmp( key, PREF_KEY_DIR_WATCH_ENABLED ) )
606    {
607        updateWatchDir( core );
608    }
609#endif
610}
611
612static void
613tr_core_init( GTypeInstance *  instance,
614              gpointer g_class UNUSED )
615{
616    GtkListStore * store;
617    struct TrCorePrivate * p;
618    TrCore * self = (TrCore *) instance;
619
620    /* column types for the model used to store torrent information */
621    /* keep this in sync with the enum near the bottom of tr_core.h */
622    GType types[] = { G_TYPE_STRING,    /* name */
623                      G_TYPE_STRING,    /* collated name */
624                      TR_TORRENT_TYPE,  /* TrTorrent object */
625                      G_TYPE_POINTER,   /* tr_torrent* */
626                      G_TYPE_DOUBLE,    /* tr_stat.pieceUploadSpeed */
627                      G_TYPE_DOUBLE,    /* tr_stat.pieceDownloadSpeed */
628                      G_TYPE_INT };     /* tr_stat.status */
629
630    p = self->priv = G_TYPE_INSTANCE_GET_PRIVATE( self,
631                                                  TR_CORE_TYPE,
632                                                  struct TrCorePrivate );
633
634    /* create the model used to store torrent data */
635    g_assert( G_N_ELEMENTS( types ) == MC_ROW_COUNT );
636    store = gtk_list_store_newv( MC_ROW_COUNT, types );
637
638    p->model    = GTK_TREE_MODEL( store );
639
640#ifdef HAVE_DBUS_GLIB
641    if( our_instance_adds_remote_torrents )
642    {
643        DBusGConnection * bus = dbus_g_bus_get( DBUS_BUS_SESSION, NULL );
644        if( bus )
645            dbus_g_connection_register_g_object(
646                 bus,
647                "/com/transmissionbt/Transmission",
648                G_OBJECT( self ) );
649    }
650#endif
651}
652
653GType
654tr_core_get_type( void )
655{
656    static GType type = 0;
657
658    if( !type )
659    {
660        static const GTypeInfo info =
661        {
662            sizeof( TrCoreClass ),
663            NULL,                 /* base_init */
664            NULL,                 /* base_finalize */
665            tr_core_class_init,   /* class_init */
666            NULL,                 /* class_finalize */
667            NULL,                 /* class_data */
668            sizeof( TrCore ),
669            0,                    /* n_preallocs */
670            tr_core_init,         /* instance_init */
671            NULL,
672        };
673        type = g_type_register_static( G_TYPE_OBJECT, "TrCore", &info, 0 );
674    }
675
676    return type;
677}
678
679/**
680***
681**/
682
683TrCore *
684tr_core_new( tr_session * session )
685{
686    TrCore * core = TR_CORE( g_object_new( TR_CORE_TYPE, NULL ) );
687
688    core->priv->session  = session;
689
690    /* init from prefs & listen to pref changes */
691    prefsChanged( core, PREF_KEY_SORT_MODE, NULL );
692    prefsChanged( core, PREF_KEY_SORT_REVERSED, NULL );
693    prefsChanged( core, PREF_KEY_DIR_WATCH_ENABLED, NULL );
694    prefsChanged( core, TR_PREFS_KEY_PEER_LIMIT_GLOBAL, NULL );
695    prefsChanged( core, PREF_KEY_INHIBIT_HIBERNATION, NULL );
696    g_signal_connect( core, "prefs-changed", G_CALLBACK( prefsChanged ), NULL );
697
698    return core;
699}
700
701void
702tr_core_close( TrCore * core )
703{
704    tr_session * session = tr_core_session( core );
705
706    if( session )
707    {
708        core->priv->session = NULL;
709        pref_save( session );
710        tr_sessionClose( session );
711    }
712}
713
714GtkTreeModel *
715tr_core_model( TrCore * core )
716{
717    return isDisposed( core ) ? NULL : core->priv->model;
718}
719
720tr_session *
721tr_core_session( TrCore * core )
722{
723    return isDisposed( core ) ? NULL : core->priv->session;
724}
725
726static char*
727doCollate( const char * in )
728{
729    const char * end = in + strlen( in );
730    char *       casefold;
731    char *       ret;
732
733    while( in < end )
734    {
735        const gunichar ch = g_utf8_get_char( in );
736        if( !g_unichar_isalnum ( ch ) ) /* eat everything before the first alnum
737                                          */
738            in += g_unichar_to_utf8( ch, NULL );
739        else
740            break;
741    }
742
743    if( in == end )
744        return g_strdup ( "" );
745
746    casefold = g_utf8_casefold( in, end - in );
747    ret = g_utf8_collate_key( casefold, -1 );
748    g_free( casefold );
749
750    return ret;
751}
752
753void
754tr_core_add_torrent( TrCore     * self,
755                     TrTorrent  * gtor,
756                     gboolean     doNotify )
757{
758    const tr_info * inf = tr_torrent_info( gtor );
759    const tr_stat * st = tr_torrent_stat( gtor );
760    tr_torrent *    tor = tr_torrent_handle( gtor );
761    char *          collated = doCollate( inf->name );
762    GtkListStore *  store = GTK_LIST_STORE( tr_core_model( self ) );
763    GtkTreeIter     unused;
764
765    gtk_list_store_insert_with_values( store, &unused, 0,
766                                       MC_NAME,          inf->name,
767                                       MC_NAME_COLLATED, collated,
768                                       MC_TORRENT,       gtor,
769                                       MC_TORRENT_RAW,   tor,
770                                       MC_SPEED_UP,      st->pieceUploadSpeed,
771                                       MC_SPEED_DOWN,    st->pieceDownloadSpeed,
772                                       MC_ACTIVITY,      st->activity,
773                                       -1 );
774
775    if( doNotify )
776        tr_notify_added( inf->name );
777
778    /* cleanup */
779    g_object_unref( G_OBJECT( gtor ) );
780    g_free( collated );
781}
782
783int
784tr_core_load( TrCore * self,
785              gboolean forcePaused )
786{
787    int           i;
788    int           count = 0;
789    tr_torrent ** torrents;
790    tr_ctor *     ctor;
791
792    ctor = tr_ctorNew( tr_core_session( self ) );
793    if( forcePaused )
794        tr_ctorSetPaused( ctor, TR_FORCE, TRUE );
795    tr_ctorSetPeerLimit( ctor, TR_FALLBACK,
796                         pref_int_get( TR_PREFS_KEY_PEER_LIMIT_TORRENT ) );
797
798    torrents = tr_sessionLoadTorrents ( tr_core_session( self ), ctor, &count );
799    for( i = 0; i < count; ++i )
800        tr_core_add_torrent( self, tr_torrent_new_preexisting( torrents[i] ), FALSE );
801
802    tr_free( torrents );
803    tr_ctorFree( ctor );
804
805    return count;
806}
807
808static void
809emitBlocklistUpdated( TrCore * core, int ruleCount )
810{
811    g_signal_emit( core, TR_CORE_GET_CLASS( core )->blocklistSignal, 0, ruleCount );
812}
813
814static void
815emitPortTested( TrCore * core, gboolean isOpen )
816{
817    g_signal_emit( core, TR_CORE_GET_CLASS( core )->portSignal, 0, isOpen );
818}
819
820static void
821tr_core_errsig( TrCore *         core,
822                enum tr_core_err type,
823                const char *     msg )
824{
825    g_signal_emit( core, TR_CORE_GET_CLASS( core )->errsig, 0, type, msg );
826}
827
828static int
829add_ctor( TrCore * core, tr_ctor * ctor, gboolean doPrompt, gboolean doNotify )
830{
831    tr_info inf;
832    int err = tr_torrentParse( ctor, &inf );
833
834    switch( err )
835    {
836        case TR_PARSE_ERR:
837            break;
838
839        case TR_PARSE_DUPLICATE:
840            /* don't complain about .torrent files in the watch directory
841             * that have already been added... that gets annoying and we
842             * don't want to be nagging users to clean up their watch dirs */
843            if( !tr_ctorGetSourceFile(ctor) || !core->priv->adding_from_watch_dir )
844                tr_core_errsig( core, err, inf.name );
845            tr_metainfoFree( &inf );
846            break;
847
848        default:
849            if( doPrompt )
850                g_signal_emit( core, TR_CORE_GET_CLASS( core )->promptsig, 0, ctor );
851            else {
852                tr_session * session = tr_core_session( core );
853                TrTorrent * gtor = tr_torrent_new_ctor( session, ctor, &err );
854                if( !err )
855                    tr_core_add_torrent( core, gtor, doNotify );
856            }
857            tr_metainfoFree( &inf );
858            break;
859    }
860
861    return err;
862}
863
864void
865tr_core_add_ctor( TrCore * core, tr_ctor * ctor )
866{
867    const gboolean doStart = pref_flag_get( PREF_KEY_START );
868    const gboolean doPrompt = pref_flag_get( PREF_KEY_OPTIONS_PROMPT );
869    tr_core_apply_defaults( ctor );
870    add_ctor( core, ctor, doStart, doPrompt );
871}
872
873/* invoked remotely via dbus. */
874gboolean
875tr_core_add_metainfo( TrCore      * core,
876                      const char  * base64_metainfo,
877                      gboolean    * setme_success,
878                      GError     ** gerr UNUSED )
879{
880    tr_session * session = tr_core_session( core );
881
882    if( !session )
883    {
884        *setme_success = FALSE;
885    }
886    else
887    {
888        int err;
889        int file_length;
890        tr_ctor * ctor;
891        char * file_contents;
892        gboolean do_prompt = pref_flag_get( PREF_KEY_OPTIONS_PROMPT );
893
894        ctor = tr_ctorNew( session );
895        tr_core_apply_defaults( ctor );
896
897        file_contents = tr_base64_decode( base64_metainfo, -1, &file_length );
898        err = tr_ctorSetMetainfo( ctor, (const uint8_t*)file_contents, file_length );
899
900        if( !err )
901            err = add_ctor( core, ctor, do_prompt, TRUE );
902
903        tr_free( file_contents );
904        tr_core_torrents_added( core );
905        *setme_success = TRUE;
906    }
907
908    return TRUE;
909}
910
911static void
912add_filename( TrCore      * core,
913              const char  * filename,
914              gboolean      doStart,
915              gboolean      doPrompt,
916              gboolean      doNotify )
917{
918    tr_session * session = tr_core_session( core );
919    if( filename && session )
920    {
921        int err;
922        tr_ctor * ctor = tr_ctorNew( session );
923        tr_core_apply_defaults( ctor );
924        tr_ctorSetPaused( ctor, TR_FORCE, !doStart );
925        tr_ctorSetMetainfoFromFile( ctor, filename );
926
927        err = add_ctor( core, ctor, doPrompt, doNotify );
928        if( err == TR_PARSE_ERR )
929            tr_core_errsig( core, TR_PARSE_ERR, filename );
930    }
931}
932
933gboolean
934tr_core_present_window( TrCore      * core UNUSED,
935                        gboolean *         success,
936                        GError     ** err  UNUSED )
937{
938    action_activate( "present-main-window" );
939    *success = TRUE;
940    return TRUE;
941}
942
943void
944tr_core_add_list( TrCore       * core,
945                  GSList       * torrentFiles,
946                  pref_flag_t    start,
947                  pref_flag_t    prompt,
948                  gboolean       doNotify )
949{
950    const gboolean doStart = pref_flag_eval( start, PREF_KEY_START );
951    const gboolean doPrompt = pref_flag_eval( prompt, PREF_KEY_OPTIONS_PROMPT );
952    GSList * l;
953
954    for( l = torrentFiles; l != NULL; l = l->next )
955        add_filename( core, l->data, doStart, doPrompt, doNotify );
956
957    tr_core_torrents_added( core );
958    freestrlist( torrentFiles );
959}
960
961void
962tr_core_torrents_added( TrCore * self )
963{
964    tr_core_update( self );
965    tr_core_errsig( self, TR_CORE_ERR_NO_MORE_TORRENTS, NULL );
966}
967
968static gboolean
969findTorrentInModel( TrCore *      core,
970                    int           id,
971                    GtkTreeIter * setme )
972{
973    int            match = 0;
974    GtkTreeIter    iter;
975    GtkTreeModel * model = tr_core_model( core );
976
977    if( gtk_tree_model_iter_children( model, &iter, NULL ) ) do
978        {
979            tr_torrent * tor;
980            gtk_tree_model_get( model, &iter, MC_TORRENT_RAW, &tor, -1 );
981            match = tr_torrentId( tor ) == id;
982        }
983        while( !match && gtk_tree_model_iter_next( model, &iter ) );
984
985    if( match )
986        *setme = iter;
987
988    return match;
989}
990
991void
992tr_core_torrent_destroyed( TrCore * core,
993                           int      id )
994{
995    GtkTreeIter iter;
996
997    if( findTorrentInModel( core, id, &iter ) )
998    {
999        TrTorrent * gtor;
1000        GtkTreeModel * model = tr_core_model( core );
1001        gtk_tree_model_get( model, &iter, MC_TORRENT, &gtor, -1 );
1002        tr_torrent_clear( gtor );
1003        gtk_list_store_remove( GTK_LIST_STORE( model ), &iter );
1004        g_object_unref( G_OBJECT( gtor ) );
1005    }
1006}
1007
1008void
1009tr_core_remove_torrent( TrCore *    core,
1010                        TrTorrent * gtor,
1011                        int         deleteFiles )
1012{
1013    const tr_torrent * tor = tr_torrent_handle( gtor );
1014
1015    if( tor )
1016    {
1017        int         id = tr_torrentId( tor );
1018        GtkTreeIter iter;
1019        if( findTorrentInModel( core, id, &iter ) )
1020        {
1021            GtkTreeModel * model = tr_core_model( core );
1022
1023            /* remove from the gui */
1024            gtk_list_store_remove( GTK_LIST_STORE( model ), &iter );
1025
1026            /* maybe delete the downloaded files */
1027            if( deleteFiles )
1028                tr_torrent_delete_files( gtor );
1029
1030            /* remove the torrent */
1031            tr_torrent_set_remove_flag( gtor, TRUE );
1032            g_object_unref( G_OBJECT( gtor ) );
1033        }
1034    }
1035}
1036
1037/***
1038****
1039***/
1040
1041static gboolean
1042update_foreach( GtkTreeModel * model,
1043                GtkTreePath  * path UNUSED,
1044                GtkTreeIter  * iter,
1045                gpointer       data UNUSED )
1046{
1047    int oldActivity, newActivity;
1048    double oldUpSpeed, newUpSpeed;
1049    double oldDownSpeed, newDownSpeed;
1050    const tr_stat * st;
1051    TrTorrent * gtor;
1052
1053    /* get the old states */
1054    gtk_tree_model_get( model, iter,
1055                        MC_TORRENT, &gtor,
1056                        MC_ACTIVITY, &oldActivity,
1057                        MC_SPEED_UP, &oldUpSpeed,
1058                        MC_SPEED_DOWN, &oldDownSpeed,
1059                        -1 );
1060
1061    /* get the new states */
1062    st = tr_torrentStat( tr_torrent_handle( gtor ) );
1063    newActivity = st->activity;
1064    newUpSpeed = st->pieceUploadSpeed;
1065    newDownSpeed = st->pieceDownloadSpeed;
1066
1067    /* updating the model triggers off resort/refresh,
1068       so don't do it unless something's actually changed... */
1069    if( ( newActivity != oldActivity ) ||
1070        ( (int)(newUpSpeed*10.0) != (int)(oldUpSpeed*10.0) ) ||
1071        ( (int)(newDownSpeed*10.0) != (int)(oldDownSpeed*10.0) ) )
1072    {
1073        gtk_list_store_set( GTK_LIST_STORE( model ), iter,
1074                            MC_ACTIVITY, newActivity,
1075                            MC_SPEED_UP, newUpSpeed,
1076                            MC_SPEED_DOWN, newDownSpeed,
1077                            -1 );
1078    }
1079
1080    /* cleanup */
1081    g_object_unref( gtor );
1082    return FALSE;
1083}
1084
1085void
1086tr_core_update( TrCore * self )
1087{
1088    int               column;
1089    GtkSortType       order;
1090    GtkTreeSortable * sortable;
1091    GtkTreeModel *    model = tr_core_model( self );
1092
1093    /* pause sorting */
1094    sortable = GTK_TREE_SORTABLE( model );
1095    gtk_tree_sortable_get_sort_column_id( sortable, &column, &order );
1096    gtk_tree_sortable_set_sort_column_id(
1097        sortable, GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, order );
1098
1099    /* refresh the model */
1100    gtk_tree_model_foreach( model, update_foreach, NULL );
1101
1102    /* resume sorting */
1103    gtk_tree_sortable_set_sort_column_id( sortable, column, order );
1104
1105    /* maybe inhibit hibernation */
1106    maybeInhibitHibernation( self );
1107}
1108
1109void
1110tr_core_quit( TrCore * core )
1111{
1112    g_signal_emit( core, TR_CORE_GET_CLASS( core )->quitsig, 0 );
1113}
1114
1115/**
1116***  Hibernate
1117**/
1118
1119#ifdef HAVE_DBUS_GLIB
1120
1121static DBusGProxy*
1122get_hibernation_inhibit_proxy( void )
1123{
1124    GError *          error = NULL;
1125    DBusGConnection * conn;
1126
1127    conn = dbus_g_bus_get( DBUS_BUS_SESSION, &error );
1128    if( error )
1129    {
1130        g_warning ( "DBUS cannot connect : %s", error->message );
1131        g_error_free ( error );
1132        return NULL;
1133    }
1134
1135    return dbus_g_proxy_new_for_name (
1136               conn,
1137               "org.freedesktop.PowerManagement",
1138               "/org/freedesktop/PowerManagement/Inhibit",
1139               "org.freedesktop.PowerManagement.Inhibit" );
1140}
1141
1142static gboolean
1143gtr_inhibit_hibernation( guint * cookie )
1144{
1145    gboolean     success = FALSE;
1146    DBusGProxy * proxy = get_hibernation_inhibit_proxy( );
1147
1148    if( proxy )
1149    {
1150        GError *     error = NULL;
1151        const char * application = _( "Transmission Bittorrent Client" );
1152        const char * reason = _( "BitTorrent Activity" );
1153        success = dbus_g_proxy_call( proxy, "Inhibit", &error,
1154                                     G_TYPE_STRING, application,
1155                                     G_TYPE_STRING, reason,
1156                                     G_TYPE_INVALID,
1157                                     G_TYPE_UINT, cookie,
1158                                     G_TYPE_INVALID );
1159        if( success )
1160            tr_inf( "%s", _( "Disallowing desktop hibernation" ) );
1161        else
1162        {
1163            tr_err( _( "Couldn't disable desktop hibernation: %s" ),
1164                    error->message );
1165            g_error_free( error );
1166        }
1167
1168        g_object_unref( G_OBJECT( proxy ) );
1169    }
1170
1171    return success != 0;
1172}
1173
1174static void
1175gtr_uninhibit_hibernation( guint inhibit_cookie )
1176{
1177    DBusGProxy * proxy = get_hibernation_inhibit_proxy( );
1178
1179    if( proxy )
1180    {
1181        GError * error = NULL;
1182        gboolean success = dbus_g_proxy_call( proxy, "UnInhibit", &error,
1183                                              G_TYPE_UINT, inhibit_cookie,
1184                                              G_TYPE_INVALID,
1185                                              G_TYPE_INVALID );
1186        if( success )
1187            tr_inf( "%s", _( "Allowing desktop hibernation" ) );
1188        else
1189        {
1190            g_warning( "Couldn't uninhibit the system from suspending: %s.",
1191                       error->message );
1192            g_error_free( error );
1193        }
1194
1195        g_object_unref( G_OBJECT( proxy ) );
1196    }
1197}
1198
1199#endif
1200
1201static void
1202tr_core_set_hibernation_allowed( TrCore * core,
1203                                 gboolean allowed )
1204{
1205#ifdef HAVE_DBUS_GLIB
1206    g_return_if_fail( core );
1207    g_return_if_fail( core->priv );
1208
1209    core->priv->inhibit_allowed = allowed != 0;
1210
1211    if( allowed && core->priv->have_inhibit_cookie )
1212    {
1213        gtr_uninhibit_hibernation( core->priv->inhibit_cookie );
1214        core->priv->have_inhibit_cookie = FALSE;
1215    }
1216
1217    if( !allowed
1218      && !core->priv->have_inhibit_cookie
1219      && !core->priv->dbus_error )
1220    {
1221        if( gtr_inhibit_hibernation( &core->priv->inhibit_cookie ) )
1222            core->priv->have_inhibit_cookie = TRUE;
1223        else
1224            core->priv->dbus_error = TRUE;
1225    }
1226#endif
1227}
1228
1229static void
1230maybeInhibitHibernation( TrCore * core )
1231{
1232    gboolean inhibit = pref_flag_get( PREF_KEY_INHIBIT_HIBERNATION );
1233
1234    /* always allow hibernation when all the torrents are paused */
1235    if( inhibit ) {
1236        tr_session * session = tr_core_session( core );
1237
1238        if( tr_sessionGetActiveTorrentCount( session ) == 0 )
1239            inhibit = FALSE;
1240    }
1241
1242    tr_core_set_hibernation_allowed( core, !inhibit );
1243}
1244
1245/**
1246***  Prefs
1247**/
1248
1249static void
1250commitPrefsChange( TrCore *     core,
1251                   const char * key )
1252{
1253    g_signal_emit( core, TR_CORE_GET_CLASS( core )->prefsig, 0, key );
1254    pref_save( tr_core_session( core ) );
1255}
1256
1257void
1258tr_core_set_pref( TrCore *     self,
1259                  const char * key,
1260                  const char * newval )
1261{
1262    const char * oldval = pref_string_get( key );
1263
1264    if( tr_strcmp( oldval, newval ) )
1265    {
1266        pref_string_set( key, newval );
1267        commitPrefsChange( self, key );
1268    }
1269}
1270
1271void
1272tr_core_set_pref_bool( TrCore *     self,
1273                       const char * key,
1274                       gboolean     newval )
1275{
1276    const gboolean oldval = pref_flag_get( key );
1277
1278    if( oldval != newval )
1279    {
1280        pref_flag_set( key, newval );
1281        commitPrefsChange( self, key );
1282    }
1283}
1284
1285void
1286tr_core_set_pref_int( TrCore *     self,
1287                      const char * key,
1288                      int          newval )
1289{
1290    const int oldval = pref_int_get( key );
1291
1292    if( oldval != newval )
1293    {
1294        pref_int_set( key, newval );
1295        commitPrefsChange( self, key );
1296    }
1297}
1298
1299void
1300tr_core_set_pref_double( TrCore *     self,
1301                         const char * key,
1302                         double       newval )
1303{
1304    const double oldval = pref_double_get( key );
1305
1306    if( oldval != newval )
1307    {
1308        pref_double_set( key, newval );
1309        commitPrefsChange( self, key );
1310    }
1311}
1312
1313/***
1314****
1315****  RPC Interface
1316****
1317***/
1318
1319/* #define DEBUG_RPC */
1320
1321static int nextTag = 1;
1322
1323typedef void ( server_response_func )( TrCore * core, tr_benc * response, gpointer user_data );
1324
1325struct pending_request_data
1326{
1327    TrCore * core;
1328    server_response_func * responseFunc;
1329    gpointer responseFuncUserData;
1330};
1331
1332static GHashTable * pendingRequests = NULL;
1333
1334static gboolean
1335readResponseIdle( void * vresponse )
1336{
1337    GByteArray * response;
1338    tr_benc top;
1339    int64_t intVal;
1340    int tag;
1341    struct pending_request_data * data;
1342
1343    response = vresponse;
1344    tr_jsonParse( NULL, response->data, response->len, &top, NULL );
1345    tr_bencDictFindInt( &top, "tag", &intVal );
1346    tag = (int)intVal;
1347
1348    data = g_hash_table_lookup( pendingRequests, &tag );
1349    if( data && data->responseFunc )
1350        (*data->responseFunc)(data->core, &top, data->responseFuncUserData );
1351
1352    tr_bencFree( &top );
1353    g_hash_table_remove( pendingRequests, &tag );
1354    g_byte_array_free( response, TRUE );
1355    return FALSE;
1356}
1357
1358static void
1359readResponse( tr_session  * session UNUSED,
1360              const char  * response,
1361              size_t        response_len,
1362              void        * unused UNUSED )
1363{
1364    GByteArray * bytes = g_byte_array_new( );
1365#ifdef DEBUG_RPC
1366    g_message( "response: [%*.*s]", (int)response_len, (int)response_len, response );
1367#endif
1368    g_byte_array_append( bytes, (const uint8_t*)response, response_len );
1369    gtr_idle_add( readResponseIdle, bytes );
1370}
1371
1372static void
1373sendRequest( TrCore * core, const char * json, int tag,
1374             server_response_func * responseFunc, void * responseFuncUserData )
1375{
1376    tr_session * session = tr_core_session( core );
1377
1378    if( pendingRequests == NULL )
1379    {
1380        pendingRequests = g_hash_table_new_full( g_int_hash, g_int_equal, g_free, g_free );
1381    }
1382
1383    if( session == NULL )
1384    {
1385        g_error( "GTK+ client doesn't support connections to remote servers yet." );
1386    }
1387    else
1388    {
1389        /* remember this request */
1390        struct pending_request_data * data;
1391        data = g_new0( struct pending_request_data, 1 );
1392        data->core = core;
1393        data->responseFunc = responseFunc;
1394        data->responseFuncUserData = responseFuncUserData;
1395        g_hash_table_insert( pendingRequests, g_memdup( &tag, sizeof( int ) ), data );
1396
1397        /* make the request */
1398#ifdef DEBUG_RPC
1399        g_message( "request: [%s]", json );
1400#endif
1401        tr_rpc_request_exec_json( session, json, strlen( json ), readResponse, GINT_TO_POINTER(tag) );
1402    }
1403}
1404
1405/***
1406****  Sending a test-port request via RPC
1407***/
1408
1409static void
1410portTestResponseFunc( TrCore * core, tr_benc * response, gpointer userData UNUSED )
1411{
1412    tr_benc * args;
1413    tr_bool isOpen = FALSE;
1414
1415    if( tr_bencDictFindDict( response, "arguments", &args ) )
1416        tr_bencDictFindBool( args, "port-is-open", &isOpen );
1417
1418    emitPortTested( core, isOpen );
1419}
1420
1421void
1422tr_core_port_test( TrCore * core )
1423{
1424    char buf[128];
1425    const int tag = nextTag++;
1426    g_snprintf( buf, sizeof( buf ), "{ \"method\": \"port-test\", \"tag\": %d }", tag );
1427    sendRequest( core, buf, tag, portTestResponseFunc, NULL );
1428}
1429
1430/***
1431****  Updating a blocklist via RPC
1432***/
1433
1434static void
1435blocklistResponseFunc( TrCore * core, tr_benc * response, gpointer userData UNUSED )
1436{
1437    tr_benc * args;
1438    int64_t ruleCount = 0;
1439
1440    if( tr_bencDictFindDict( response, "arguments", &args ) )
1441        tr_bencDictFindInt( args, "blocklist-size", &ruleCount );
1442
1443    if( ruleCount > 0 )
1444        pref_int_set( "blocklist-date", time( NULL ) );
1445
1446    emitBlocklistUpdated( core, ruleCount );
1447}
1448
1449void
1450tr_core_blocklist_update( TrCore * core )
1451{
1452    char buf[128];
1453    const int tag = nextTag++;
1454    g_snprintf( buf, sizeof( buf ), "{ \"method\": \"blocklist-update\", \"tag\": %d }", tag );
1455    sendRequest( core, buf, tag, blocklistResponseFunc, NULL );
1456}
1457
1458/***
1459****
1460***/
1461
1462void
1463tr_core_exec_json( TrCore * core, const char * json )
1464{
1465    const int tag = nextTag++;
1466    sendRequest( core, json, tag, NULL, NULL );
1467}
1468
1469void
1470tr_core_exec( TrCore * core, const tr_benc * top )
1471{
1472    char * json = tr_bencToStr( top, TR_FMT_JSON_LEAN, NULL );
1473    tr_core_exec_json( core, json );
1474    tr_free( json );
1475}
Note: See TracBrowser for help on using the repository browser.