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

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

(trunk, gtk/qt) use Monsoon's strings for encryption preferences and Deluge's system tray tooltip.

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