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

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

(trunk gtk) fix #2292: wrong speed in bottom bar

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