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

Last change on this file since 9071 was 9071, checked in by charles, 13 years ago

(trunk gtk) #2389: Sort by Time Left broken in the GTK client

  • Property svn:keywords set to Date Rev Author Id
File size: 41.5 KB
Line 
1/******************************************************************************
2 * $Id: tr-core.c 9071 2009-09-08 18:59:49Z 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
864/* invoked remotely via dbus. */
865gboolean
866tr_core_add_metainfo( TrCore      * core,
867                      const char  * base64_metainfo,
868                      gboolean    * setme_success,
869                      GError     ** gerr UNUSED )
870{
871    tr_session * session = tr_core_session( core );
872
873    if( !session )
874    {
875        *setme_success = FALSE;
876    }
877    else
878    {
879        int err;
880        int file_length;
881        tr_ctor * ctor;
882        char * file_contents;
883        gboolean do_prompt = pref_flag_get( PREF_KEY_OPTIONS_PROMPT );
884
885        ctor = tr_ctorNew( session );
886        tr_core_apply_defaults( ctor );
887
888        file_contents = tr_base64_decode( base64_metainfo, -1, &file_length );
889        err = tr_ctorSetMetainfo( ctor, (const uint8_t*)file_contents, file_length );
890
891        if( !err )
892            err = add_ctor( core, ctor, do_prompt, TRUE );
893
894        tr_free( file_contents );
895        tr_core_torrents_added( core );
896        *setme_success = TRUE;
897    }
898
899    return TRUE;
900}
901
902static void
903add_filename( TrCore      * core,
904              const char  * filename,
905              gboolean      doStart,
906              gboolean      doPrompt,
907              gboolean      doNotify )
908{
909    tr_session * session = tr_core_session( core );
910    if( filename && session )
911    {
912        int err;
913        tr_ctor * ctor = tr_ctorNew( session );
914        tr_core_apply_defaults( ctor );
915        tr_ctorSetPaused( ctor, TR_FORCE, !doStart );
916        tr_ctorSetMetainfoFromFile( ctor, filename );
917
918        err = add_ctor( core, ctor, doPrompt, doNotify );
919        if( err == TR_PARSE_ERR )
920            tr_core_errsig( core, TR_PARSE_ERR, filename );
921    }
922}
923
924gboolean
925tr_core_present_window( TrCore      * core UNUSED,
926                        gboolean *         success,
927                        GError     ** err  UNUSED )
928{
929    action_activate( "present-main-window" );
930    *success = TRUE;
931    return TRUE;
932}
933
934void
935tr_core_add_list( TrCore       * core,
936                  GSList       * torrentFiles,
937                  pref_flag_t    start,
938                  pref_flag_t    prompt,
939                  gboolean       doNotify )
940{
941    const gboolean doStart = pref_flag_eval( start, PREF_KEY_START );
942    const gboolean doPrompt = pref_flag_eval( prompt, PREF_KEY_OPTIONS_PROMPT );
943    GSList * l;
944
945    for( l = torrentFiles; l != NULL; l = l->next )
946        add_filename( core, l->data, doStart, doPrompt, doNotify );
947
948    tr_core_torrents_added( core );
949    freestrlist( torrentFiles );
950}
951
952void
953tr_core_torrents_added( TrCore * self )
954{
955    tr_core_update( self );
956    tr_core_errsig( self, TR_CORE_ERR_NO_MORE_TORRENTS, NULL );
957}
958
959static gboolean
960findTorrentInModel( TrCore *      core,
961                    int           id,
962                    GtkTreeIter * setme )
963{
964    int            match = 0;
965    GtkTreeIter    iter;
966    GtkTreeModel * model = tr_core_model( core );
967
968    if( gtk_tree_model_iter_children( model, &iter, NULL ) ) do
969        {
970            tr_torrent * tor;
971            gtk_tree_model_get( model, &iter, MC_TORRENT_RAW, &tor, -1 );
972            match = tr_torrentId( tor ) == id;
973        }
974        while( !match && gtk_tree_model_iter_next( model, &iter ) );
975
976    if( match )
977        *setme = iter;
978
979    return match;
980}
981
982void
983tr_core_torrent_destroyed( TrCore * core,
984                           int      id )
985{
986    GtkTreeIter iter;
987
988    if( findTorrentInModel( core, id, &iter ) )
989    {
990        TrTorrent * gtor;
991        GtkTreeModel * model = tr_core_model( core );
992        gtk_tree_model_get( model, &iter, MC_TORRENT, &gtor, -1 );
993        tr_torrent_clear( gtor );
994        gtk_list_store_remove( GTK_LIST_STORE( model ), &iter );
995        g_object_unref( G_OBJECT( gtor ) );
996    }
997}
998
999void
1000tr_core_remove_torrent( TrCore *    core,
1001                        TrTorrent * gtor,
1002                        int         deleteFiles )
1003{
1004    const tr_torrent * tor = tr_torrent_handle( gtor );
1005
1006    if( tor )
1007    {
1008        int         id = tr_torrentId( tor );
1009        GtkTreeIter iter;
1010        if( findTorrentInModel( core, id, &iter ) )
1011        {
1012            GtkTreeModel * model = tr_core_model( core );
1013
1014            /* remove from the gui */
1015            gtk_list_store_remove( GTK_LIST_STORE( model ), &iter );
1016
1017            /* maybe delete the downloaded files */
1018            if( deleteFiles )
1019                tr_torrent_delete_files( gtor );
1020
1021            /* remove the torrent */
1022            tr_torrent_set_remove_flag( gtor, TRUE );
1023            g_object_unref( G_OBJECT( gtor ) );
1024        }
1025    }
1026}
1027
1028/***
1029****
1030***/
1031
1032static gboolean
1033update_foreach( GtkTreeModel * model,
1034                GtkTreePath  * path UNUSED,
1035                GtkTreeIter  * iter,
1036                gpointer       data UNUSED )
1037{
1038    int oldActivity, newActivity;
1039    double oldUpSpeed, newUpSpeed;
1040    double oldDownSpeed, newDownSpeed;
1041    const tr_stat * st;
1042    TrTorrent * gtor;
1043
1044    /* get the old states */
1045    gtk_tree_model_get( model, iter,
1046                        MC_TORRENT, &gtor,
1047                        MC_ACTIVITY, &oldActivity,
1048                        MC_SPEED_UP, &oldUpSpeed,
1049                        MC_SPEED_DOWN, &oldDownSpeed,
1050                        -1 );
1051
1052    /* get the new states */
1053    st = tr_torrentStat( tr_torrent_handle( gtor ) );
1054    newActivity = st->activity;
1055    newUpSpeed = st->pieceUploadSpeed;
1056    newDownSpeed = st->pieceDownloadSpeed;
1057
1058    /* updating the model triggers off resort/refresh,
1059       so don't do it unless something's actually changed... */
1060    if( ( newActivity != oldActivity ) ||
1061        ( (int)(newUpSpeed*10.0) != (int)(oldUpSpeed*10.0) ) ||
1062        ( (int)(newDownSpeed*10.0) != (int)(oldDownSpeed*10.0) ) )
1063    {
1064        gtk_list_store_set( GTK_LIST_STORE( model ), iter,
1065                            MC_ACTIVITY, newActivity,
1066                            MC_SPEED_UP, newUpSpeed,
1067                            MC_SPEED_DOWN, newDownSpeed,
1068                            -1 );
1069    }
1070
1071    /* cleanup */
1072    g_object_unref( gtor );
1073    return FALSE;
1074}
1075
1076void
1077tr_core_update( TrCore * self )
1078{
1079    int               column;
1080    GtkSortType       order;
1081    GtkTreeSortable * sortable;
1082    GtkTreeModel *    model = tr_core_model( self );
1083
1084    /* pause sorting */
1085    sortable = GTK_TREE_SORTABLE( model );
1086    gtk_tree_sortable_get_sort_column_id( sortable, &column, &order );
1087    gtk_tree_sortable_set_sort_column_id(
1088        sortable, GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, order );
1089
1090    /* refresh the model */
1091    gtk_tree_model_foreach( model, update_foreach, NULL );
1092
1093    /* resume sorting */
1094    gtk_tree_sortable_set_sort_column_id( sortable, column, order );
1095
1096    /* maybe inhibit hibernation */
1097    maybeInhibitHibernation( self );
1098}
1099
1100void
1101tr_core_quit( TrCore * core )
1102{
1103    g_signal_emit( core, TR_CORE_GET_CLASS( core )->quitsig, 0 );
1104}
1105
1106/**
1107***  Hibernate
1108**/
1109
1110#ifdef HAVE_DBUS_GLIB
1111
1112static DBusGProxy*
1113get_hibernation_inhibit_proxy( void )
1114{
1115    GError *          error = NULL;
1116    DBusGConnection * conn;
1117
1118    conn = dbus_g_bus_get( DBUS_BUS_SESSION, &error );
1119    if( error )
1120    {
1121        g_warning ( "DBUS cannot connect : %s", error->message );
1122        g_error_free ( error );
1123        return NULL;
1124    }
1125
1126    return dbus_g_proxy_new_for_name (
1127               conn,
1128               "org.freedesktop.PowerManagement",
1129               "/org/freedesktop/PowerManagement/Inhibit",
1130               "org.freedesktop.PowerManagement.Inhibit" );
1131}
1132
1133static gboolean
1134gtr_inhibit_hibernation( guint * cookie )
1135{
1136    gboolean     success = FALSE;
1137    DBusGProxy * proxy = get_hibernation_inhibit_proxy( );
1138
1139    if( proxy )
1140    {
1141        GError *     error = NULL;
1142        const char * application = _( "Transmission Bittorrent Client" );
1143        const char * reason = _( "BitTorrent Activity" );
1144        success = dbus_g_proxy_call( proxy, "Inhibit", &error,
1145                                     G_TYPE_STRING, application,
1146                                     G_TYPE_STRING, reason,
1147                                     G_TYPE_INVALID,
1148                                     G_TYPE_UINT, cookie,
1149                                     G_TYPE_INVALID );
1150        if( success )
1151            tr_inf( "%s", _( "Disallowing desktop hibernation" ) );
1152        else
1153        {
1154            tr_err( _( "Couldn't disable desktop hibernation: %s" ),
1155                    error->message );
1156            g_error_free( error );
1157        }
1158
1159        g_object_unref( G_OBJECT( proxy ) );
1160    }
1161
1162    return success != 0;
1163}
1164
1165static void
1166gtr_uninhibit_hibernation( guint inhibit_cookie )
1167{
1168    DBusGProxy * proxy = get_hibernation_inhibit_proxy( );
1169
1170    if( proxy )
1171    {
1172        GError * error = NULL;
1173        gboolean success = dbus_g_proxy_call( proxy, "UnInhibit", &error,
1174                                              G_TYPE_UINT, inhibit_cookie,
1175                                              G_TYPE_INVALID,
1176                                              G_TYPE_INVALID );
1177        if( success )
1178            tr_inf( "%s", _( "Allowing desktop hibernation" ) );
1179        else
1180        {
1181            g_warning( "Couldn't uninhibit the system from suspending: %s.",
1182                       error->message );
1183            g_error_free( error );
1184        }
1185
1186        g_object_unref( G_OBJECT( proxy ) );
1187    }
1188}
1189
1190#endif
1191
1192static void
1193tr_core_set_hibernation_allowed( TrCore * core,
1194                                 gboolean allowed )
1195{
1196#ifdef HAVE_DBUS_GLIB
1197    g_return_if_fail( core );
1198    g_return_if_fail( core->priv );
1199
1200    core->priv->inhibit_allowed = allowed != 0;
1201
1202    if( allowed && core->priv->have_inhibit_cookie )
1203    {
1204        gtr_uninhibit_hibernation( core->priv->inhibit_cookie );
1205        core->priv->have_inhibit_cookie = FALSE;
1206    }
1207
1208    if( !allowed
1209      && !core->priv->have_inhibit_cookie
1210      && !core->priv->dbus_error )
1211    {
1212        if( gtr_inhibit_hibernation( &core->priv->inhibit_cookie ) )
1213            core->priv->have_inhibit_cookie = TRUE;
1214        else
1215            core->priv->dbus_error = TRUE;
1216    }
1217#endif
1218}
1219
1220static void
1221maybeInhibitHibernation( TrCore * core )
1222{
1223    gboolean inhibit = pref_flag_get( PREF_KEY_INHIBIT_HIBERNATION );
1224
1225    /* always allow hibernation when all the torrents are paused */
1226    if( inhibit ) {
1227        tr_session * session = tr_core_session( core );
1228
1229        if( tr_sessionGetActiveTorrentCount( session ) == 0 )
1230            inhibit = FALSE;
1231    }
1232
1233    tr_core_set_hibernation_allowed( core, !inhibit );
1234}
1235
1236/**
1237***  Prefs
1238**/
1239
1240static void
1241commitPrefsChange( TrCore *     core,
1242                   const char * key )
1243{
1244    g_signal_emit( core, TR_CORE_GET_CLASS( core )->prefsig, 0, key );
1245    pref_save( tr_core_session( core ) );
1246}
1247
1248void
1249tr_core_set_pref( TrCore *     self,
1250                  const char * key,
1251                  const char * newval )
1252{
1253    const char * oldval = pref_string_get( key );
1254
1255    if( tr_strcmp( oldval, newval ) )
1256    {
1257        pref_string_set( key, newval );
1258        commitPrefsChange( self, key );
1259    }
1260}
1261
1262void
1263tr_core_set_pref_bool( TrCore *     self,
1264                       const char * key,
1265                       gboolean     newval )
1266{
1267    const gboolean oldval = pref_flag_get( key );
1268
1269    if( oldval != newval )
1270    {
1271        pref_flag_set( key, newval );
1272        commitPrefsChange( self, key );
1273    }
1274}
1275
1276void
1277tr_core_set_pref_int( TrCore *     self,
1278                      const char * key,
1279                      int          newval )
1280{
1281    const int oldval = pref_int_get( key );
1282
1283    if( oldval != newval )
1284    {
1285        pref_int_set( key, newval );
1286        commitPrefsChange( self, key );
1287    }
1288}
1289
1290void
1291tr_core_set_pref_double( TrCore *     self,
1292                         const char * key,
1293                         double       newval )
1294{
1295    const double oldval = pref_double_get( key );
1296
1297    if( oldval != newval )
1298    {
1299        pref_double_set( key, newval );
1300        commitPrefsChange( self, key );
1301    }
1302}
1303
1304/***
1305****
1306****  RPC Interface
1307****
1308***/
1309
1310/* #define DEBUG_RPC */
1311
1312static int nextTag = 1;
1313
1314typedef void ( server_response_func )( TrCore * core, tr_benc * response, gpointer user_data );
1315
1316struct pending_request_data
1317{
1318    TrCore * core;
1319    server_response_func * responseFunc;
1320    gpointer responseFuncUserData;
1321};
1322
1323static GHashTable * pendingRequests = NULL;
1324
1325static gboolean
1326readResponseIdle( void * vresponse )
1327{
1328    GByteArray * response;
1329    tr_benc top;
1330    int64_t intVal;
1331    int tag;
1332    struct pending_request_data * data;
1333
1334    response = vresponse;
1335    tr_jsonParse( NULL, response->data, response->len, &top, NULL );
1336    tr_bencDictFindInt( &top, "tag", &intVal );
1337    tag = (int)intVal;
1338
1339    data = g_hash_table_lookup( pendingRequests, &tag );
1340    if( data && data->responseFunc )
1341        (*data->responseFunc)(data->core, &top, data->responseFuncUserData );
1342
1343    tr_bencFree( &top );
1344    g_hash_table_remove( pendingRequests, &tag );
1345    g_byte_array_free( response, TRUE );
1346    return FALSE;
1347}
1348
1349static void
1350readResponse( tr_session  * session UNUSED,
1351              const char  * response,
1352              size_t        response_len,
1353              void        * unused UNUSED )
1354{
1355    GByteArray * bytes = g_byte_array_new( );
1356#ifdef DEBUG_RPC
1357    g_message( "response: [%*.*s]", (int)response_len, (int)response_len, response );
1358#endif
1359    g_byte_array_append( bytes, (const uint8_t*)response, response_len );
1360    gtr_idle_add( readResponseIdle, bytes );
1361}
1362
1363static void
1364sendRequest( TrCore * core, const char * json, int tag,
1365             server_response_func * responseFunc, void * responseFuncUserData )
1366{
1367    tr_session * session = tr_core_session( core );
1368
1369    if( pendingRequests == NULL )
1370    {
1371        pendingRequests = g_hash_table_new_full( g_int_hash, g_int_equal, g_free, g_free );
1372    }
1373
1374    if( session == NULL )
1375    {
1376        g_error( "GTK+ client doesn't support connections to remote servers yet." );
1377    }
1378    else
1379    {
1380        /* remember this request */
1381        struct pending_request_data * data;
1382        data = g_new0( struct pending_request_data, 1 );
1383        data->core = core;
1384        data->responseFunc = responseFunc;
1385        data->responseFuncUserData = responseFuncUserData;
1386        g_hash_table_insert( pendingRequests, g_memdup( &tag, sizeof( int ) ), data );
1387
1388        /* make the request */
1389#ifdef DEBUG_RPC
1390        g_message( "request: [%s]", json );
1391#endif
1392        tr_rpc_request_exec_json( session, json, strlen( json ), readResponse, GINT_TO_POINTER(tag) );
1393    }
1394}
1395
1396/***
1397****  Sending a test-port request via RPC
1398***/
1399
1400static void
1401portTestResponseFunc( TrCore * core, tr_benc * response, gpointer userData UNUSED )
1402{
1403    tr_benc * args;
1404    tr_bool isOpen = FALSE;
1405
1406    if( tr_bencDictFindDict( response, "arguments", &args ) )
1407        tr_bencDictFindBool( args, "port-is-open", &isOpen );
1408
1409    emitPortTested( core, isOpen );
1410}
1411
1412void
1413tr_core_port_test( TrCore * core )
1414{
1415    char buf[128];
1416    const int tag = nextTag++;
1417    g_snprintf( buf, sizeof( buf ), "{ \"method\": \"port-test\", \"tag\": %d }", tag );
1418    sendRequest( core, buf, tag, portTestResponseFunc, NULL );
1419}
1420
1421/***
1422****  Updating a blocklist via RPC
1423***/
1424
1425static void
1426blocklistResponseFunc( TrCore * core, tr_benc * response, gpointer userData UNUSED )
1427{
1428    tr_benc * args;
1429    int64_t ruleCount = 0;
1430
1431    if( tr_bencDictFindDict( response, "arguments", &args ) )
1432        tr_bencDictFindInt( args, "blocklist-size", &ruleCount );
1433
1434    if( ruleCount > 0 )
1435        pref_int_set( "blocklist-date", time( NULL ) );
1436
1437    emitBlocklistUpdated( core, ruleCount );
1438}
1439
1440void
1441tr_core_blocklist_update( TrCore * core )
1442{
1443    char buf[128];
1444    const int tag = nextTag++;
1445    g_snprintf( buf, sizeof( buf ), "{ \"method\": \"blocklist-update\", \"tag\": %d }", tag );
1446    sendRequest( core, buf, tag, blocklistResponseFunc, NULL );
1447}
1448
1449/***
1450****
1451***/
1452
1453void
1454tr_core_exec_json( TrCore * core, const char * json )
1455{
1456    const int tag = nextTag++;
1457    sendRequest( core, json, tag, NULL, NULL );
1458}
1459
1460void
1461tr_core_exec( TrCore * core, const tr_benc * top )
1462{
1463    char * json = tr_bencToStr( top, TR_FMT_JSON_LEAN, NULL );
1464    tr_core_exec_json( core, json );
1465    tr_free( json );
1466}
Note: See TracBrowser for help on using the repository browser.