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

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

(trunk gtk) fix r9625 oops on adding torrents by their raw 40 character hex hashcode from the commandline in the GTK+ client

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