source: branches/1.5x/gtk/tr-core.c @ 8205

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

(1.5x gtk) minor backports:
(1) remove dead code
(2) minor improvements to the filterbar buttons
(3) various minor formatting changes to reduce the diffs between 1.52 and trunk

  • Property svn:keywords set to Date Rev Author Id
File size: 35.9 KB
Line 
1/******************************************************************************
2 * $Id: tr-core.c 8205 2009-04-10 17:44:17Z 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/utils.h> /* tr_free */
38
39#include "conf.h"
40#include "tr-core.h"
41#ifdef HAVE_DBUS_GLIB
42 #include "tr-core-dbus.h"
43#endif
44#include "tr-prefs.h"
45#include "tr-torrent.h"
46#include "util.h"
47#include "actions.h"
48
49static void     maybeInhibitHibernation( TrCore * core );
50
51static gboolean our_instance_adds_remote_torrents = FALSE;
52
53struct TrCorePrivate
54{
55#ifdef HAVE_GIO
56    GFileMonitor *  monitor;
57    gulong          monitor_tag;
58    char *          monitor_path;
59    GSList *        monitor_files;
60    guint           monitor_idle_tag;
61#endif
62    gboolean        adding_from_watch_dir;
63    gboolean        inhibit_allowed;
64    gboolean        have_inhibit_cookie;
65    gboolean        dbus_error;
66    guint           inhibit_cookie;
67    GtkTreeModel *  model;
68    tr_session *    session;
69};
70
71static void
72tr_core_marshal_err( GClosure *     closure,
73                     GValue * ret   UNUSED,
74                     guint          count,
75                     const GValue * vals,
76                     gpointer hint  UNUSED,
77                     gpointer       marshal )
78{
79    typedef void ( *TRMarshalErr )
80                                ( gpointer, enum tr_core_err, const char *,
81                                gpointer );
82    TRMarshalErr     callback;
83    GCClosure *      cclosure = (GCClosure*) closure;
84    enum tr_core_err errcode;
85    const char *     errstr;
86    gpointer         inst, gdata;
87
88    g_return_if_fail( count == 3 );
89
90    inst    = g_value_peek_pointer( vals );
91    errcode = g_value_get_int( vals + 1 );
92    errstr  = g_value_get_string( vals + 2 );
93    gdata   = closure->data;
94
95    callback = (TRMarshalErr)( marshal ? marshal : cclosure->callback );
96    callback( inst, errcode, errstr, gdata );
97}
98
99static void
100tr_core_marshal_blocklist( GClosure *     closure,
101                           GValue * ret   UNUSED,
102                           guint          count,
103                           const GValue * vals,
104                           gpointer hint  UNUSED,
105                           gpointer       marshal )
106{
107    typedef void ( *TRMarshalErr )
108                                ( gpointer, enum tr_core_err, const char *,
109                                gpointer );
110    TRMarshalErr callback;
111    GCClosure *  cclosure = (GCClosure*) closure;
112    gboolean     flag;
113    const char * str;
114    gpointer     inst, gdata;
115
116    g_return_if_fail( count == 3 );
117
118    inst    = g_value_peek_pointer( vals );
119    flag    = g_value_get_boolean( vals + 1 );
120    str     = g_value_get_string( vals + 2 );
121    gdata   = closure->data;
122
123    callback = (TRMarshalErr)( marshal ? marshal : cclosure->callback );
124    callback( inst, flag, str, gdata );
125}
126
127static int
128isDisposed( const TrCore * core )
129{
130    return !core || !core->priv;
131}
132
133static void
134tr_core_dispose( GObject * obj )
135{
136    TrCore * core = TR_CORE( obj );
137
138    if( !isDisposed( core ) )
139    {
140        GObjectClass * parent;
141
142        core->priv = NULL;
143
144        parent = g_type_class_peek( g_type_parent( TR_CORE_TYPE ) );
145        parent->dispose( obj );
146    }
147}
148
149static void
150tr_core_class_init( gpointer              g_class,
151                    gpointer g_class_data UNUSED )
152{
153    GObjectClass * gobject_class;
154    TrCoreClass *  core_class;
155
156    g_type_class_add_private( g_class, sizeof( struct TrCorePrivate ) );
157
158    gobject_class = G_OBJECT_CLASS( g_class );
159    gobject_class->dispose = tr_core_dispose;
160
161
162    core_class = TR_CORE_CLASS( g_class );
163
164    core_class->blocksig = g_signal_new( "blocklist-status",
165                                         G_TYPE_FROM_CLASS( g_class ),
166                                         G_SIGNAL_RUN_LAST, 0, NULL, NULL,
167                                         tr_core_marshal_blocklist,
168                                         G_TYPE_NONE,
169                                         2, G_TYPE_BOOLEAN, G_TYPE_STRING );
170
171    core_class->errsig = g_signal_new( "error",
172                                       G_TYPE_FROM_CLASS( g_class ),
173                                       G_SIGNAL_RUN_LAST, 0, NULL, NULL,
174                                       tr_core_marshal_err, G_TYPE_NONE,
175                                       2, G_TYPE_INT, G_TYPE_STRING );
176
177    core_class->promptsig = g_signal_new( "add-torrent-prompt",
178                                          G_TYPE_FROM_CLASS( g_class ),
179                                          G_SIGNAL_RUN_LAST, 0, NULL, NULL,
180                                          g_cclosure_marshal_VOID__POINTER,
181                                          G_TYPE_NONE,
182                                          1, G_TYPE_POINTER );
183
184    core_class->quitsig = g_signal_new( "quit",
185                                        G_TYPE_FROM_CLASS( g_class ),
186                                        G_SIGNAL_RUN_LAST, 0, NULL, NULL,
187                                        g_cclosure_marshal_VOID__VOID,
188                                        G_TYPE_NONE, 0 );
189
190    core_class->prefsig = g_signal_new( "prefs-changed",
191                                        G_TYPE_FROM_CLASS( g_class ),
192                                        G_SIGNAL_RUN_LAST, 0, NULL, NULL,
193                                        g_cclosure_marshal_VOID__STRING,
194                                        G_TYPE_NONE, 1, G_TYPE_STRING );
195
196#ifdef HAVE_DBUS_GLIB
197    {
198        DBusGConnection * bus = dbus_g_bus_get( DBUS_BUS_SESSION, NULL );
199        DBusGProxy *      bus_proxy = NULL;
200        if( bus )
201            bus_proxy =
202                dbus_g_proxy_new_for_name( bus, "org.freedesktop.DBus",
203                                           "/org/freedesktop/DBus",
204                                           "org.freedesktop.DBus" );
205        if( bus_proxy )
206        {
207            int result = 0;
208            dbus_g_proxy_call( bus_proxy, "RequestName", NULL,
209                               G_TYPE_STRING,
210                               "com.transmissionbt.Transmission",
211                               G_TYPE_UINT, 0,
212                               G_TYPE_INVALID,
213                               G_TYPE_UINT, &result,
214                               G_TYPE_INVALID );
215            if( ( our_instance_adds_remote_torrents = result == 1 ) )
216                dbus_g_object_type_install_info(
217                    TR_CORE_TYPE,
218                    &
219                    dbus_glib_tr_core_object_info );
220        }
221    }
222#endif
223}
224
225/***
226****  SORTING
227***/
228
229static int
230compareDouble( double a, double b )
231{
232    if( a < b ) return -1;
233    if( a > b ) return 1;
234    return 0;
235}
236
237static int
238compareRatio( double a, double b )
239{
240    if( (int)a == TR_RATIO_INF && (int)b == TR_RATIO_INF ) return 0;
241    if( (int)a == TR_RATIO_INF ) return 1;
242    if( (int)b == TR_RATIO_INF ) return -1;
243    return compareDouble( a, b );
244}
245
246static int
247compareTime( time_t a, time_t b )
248{
249    if( a < b ) return -1;
250    if( a > b ) return 1;
251    return 0;
252}
253
254static int
255compareByRatio( GtkTreeModel * model,
256                GtkTreeIter  * a,
257                GtkTreeIter  * b,
258                gpointer       user_data UNUSED )
259{
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    return compareRatio( sa->ratio, sb->ratio );
270}
271
272static int
273compareByActivity( GtkTreeModel * model,
274                   GtkTreeIter  * a,
275                   GtkTreeIter  * b,
276                   gpointer       user_data UNUSED )
277{
278    int            i;
279    tr_torrent *   ta, *tb;
280    const tr_stat *sa, *sb;
281
282    gtk_tree_model_get( model, a, MC_TORRENT_RAW, &ta, -1 );
283    gtk_tree_model_get( model, b, MC_TORRENT_RAW, &tb, -1 );
284
285    sa = tr_torrentStatCached( ta );
286    sb = tr_torrentStatCached( tb );
287
288    if( ( i = compareDouble( sa->pieceUploadSpeed + sa->pieceDownloadSpeed,
289                             sb->pieceUploadSpeed + sb->pieceDownloadSpeed ) ) )
290        return i;
291
292    if( sa->uploadedEver != sb->uploadedEver )
293        return sa->uploadedEver < sa->uploadedEver ? -1 : 1;
294
295    return 0;
296}
297
298static int
299compareByName( GtkTreeModel *             model,
300               GtkTreeIter *              a,
301               GtkTreeIter *              b,
302               gpointer         user_data UNUSED )
303{
304    int   ret;
305    char *ca, *cb;
306
307    gtk_tree_model_get( model, a, MC_NAME_COLLATED, &ca, -1 );
308    gtk_tree_model_get( model, b, MC_NAME_COLLATED, &cb, -1 );
309    ret = strcmp( ca, cb );
310    g_free( cb );
311    g_free( ca );
312    return ret;
313}
314
315static int
316compareByAge( GtkTreeModel *             model,
317              GtkTreeIter *              a,
318              GtkTreeIter *              b,
319              gpointer         user_data UNUSED )
320{
321    tr_torrent *ta, *tb;
322
323    gtk_tree_model_get( model, a, MC_TORRENT_RAW, &ta, -1 );
324    gtk_tree_model_get( model, b, MC_TORRENT_RAW, &tb, -1 );
325    return compareTime( tr_torrentStatCached( ta )->addedDate,
326                        tr_torrentStatCached( tb )->addedDate );
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 *   ta, *tb;
337    const tr_stat *sa, *sb;
338
339    gtk_tree_model_get( model, a, MC_TORRENT_RAW, &ta, -1 );
340    gtk_tree_model_get( model, b, MC_TORRENT_RAW, &tb, -1 );
341    sa = tr_torrentStatCached( ta );
342    sb = tr_torrentStatCached( tb );
343    ret = compareDouble( sa->percentDone, sb->percentDone );
344    if( !ret )
345        ret = compareRatio( sa->ratio, sb->ratio );
346    return ret;
347}
348
349static int
350compareByState( GtkTreeModel * model,
351                GtkTreeIter *  a,
352                GtkTreeIter *  b,
353                gpointer       user_data )
354{
355    int sa, sb, ret;
356
357    /* first by state */
358    gtk_tree_model_get( model, a, MC_ACTIVITY, &sa, -1 );
359    gtk_tree_model_get( model, b, MC_ACTIVITY, &sb, -1 );
360    ret = sa - sb;
361
362    /* second by progress */
363    if( !ret )
364        ret = compareByProgress( model, a, b, user_data );
365
366    return ret;
367}
368
369static int
370compareByTracker( GtkTreeModel * model,
371                  GtkTreeIter  * a,
372                  GtkTreeIter  * b,
373                  gpointer       user_data UNUSED )
374{
375    const tr_torrent *ta, *tb;
376
377    gtk_tree_model_get( model, a, MC_TORRENT_RAW, &ta, -1 );
378    gtk_tree_model_get( model, b, MC_TORRENT_RAW, &tb, -1 );
379    return strcmp( tr_torrentInfo( ta )->trackers[0].announce,
380                   tr_torrentInfo( tb )->trackers[0].announce );
381}
382
383static void
384setSort( TrCore *     core,
385         const char * mode,
386         gboolean     isReversed  )
387{
388    const int              col = MC_TORRENT_RAW;
389    GtkTreeIterCompareFunc sort_func;
390    GtkSortType            type =
391        isReversed ? GTK_SORT_ASCENDING : GTK_SORT_DESCENDING;
392    GtkTreeSortable *      sortable =
393        GTK_TREE_SORTABLE( tr_core_model( core ) );
394
395    if( !strcmp( mode, "sort-by-activity" ) )
396        sort_func = compareByActivity;
397    else if( !strcmp( mode, "sort-by-age" ) )
398        sort_func = compareByAge;
399    else if( !strcmp( mode, "sort-by-progress" ) )
400        sort_func = compareByProgress;
401    else if( !strcmp( mode, "sort-by-ratio" ) )
402        sort_func = compareByRatio;
403    else if( !strcmp( mode, "sort-by-state" ) )
404        sort_func = compareByState;
405    else if( !strcmp( mode, "sort-by-tracker" ) )
406        sort_func = compareByTracker;
407    else
408    {
409        sort_func = compareByName;
410        type = isReversed ? GTK_SORT_DESCENDING : GTK_SORT_ASCENDING;
411    }
412
413    gtk_tree_sortable_set_sort_func( sortable, col, sort_func, NULL, NULL );
414    gtk_tree_sortable_set_sort_column_id( sortable, col, type );
415}
416
417static void
418tr_core_apply_defaults( tr_ctor * ctor )
419{
420    if( tr_ctorGetPaused( ctor, TR_FORCE, NULL ) )
421        tr_ctorSetPaused( ctor, TR_FORCE, !pref_flag_get( PREF_KEY_START ) );
422
423    if( tr_ctorGetDeleteSource( ctor, NULL ) )
424        tr_ctorSetDeleteSource( ctor,
425                               pref_flag_get( PREF_KEY_TRASH_ORIGINAL ) );
426
427    if( tr_ctorGetPeerLimit( ctor, TR_FORCE, NULL ) )
428        tr_ctorSetPeerLimit( ctor, TR_FORCE,
429                             pref_int_get( TR_PREFS_KEY_PEER_LIMIT_TORRENT ) );
430
431    if( tr_ctorGetDownloadDir( ctor, TR_FORCE, NULL ) )
432    {
433        const char * path = pref_string_get( TR_PREFS_KEY_DOWNLOAD_DIR );
434        tr_ctorSetDownloadDir( ctor, TR_FORCE, path );
435    }
436}
437
438static int
439tr_strcmp( const void * a,
440           const void * b )
441{
442    if( a && b ) return strcmp( a, b );
443    if( a ) return 1;
444    if( b ) return -1;
445    return 0;
446}
447
448#ifdef HAVE_GIO
449static gboolean
450watchFolderIdle( gpointer gcore )
451{
452    TrCore * core = TR_CORE( gcore );
453
454    core->priv->adding_from_watch_dir = TRUE;
455    tr_core_add_list_defaults( core, core->priv->monitor_files );
456    core->priv->adding_from_watch_dir = FALSE;
457
458    /* cleanup */
459    core->priv->monitor_files = NULL;
460    core->priv->monitor_idle_tag = 0;
461    return FALSE;
462}
463
464static void
465maybeAddTorrent( TrCore *     core,
466                 const char * filename )
467{
468    const gboolean isTorrent = g_str_has_suffix( filename, ".torrent" );
469
470    if( isTorrent )
471    {
472        struct TrCorePrivate * p = core->priv;
473
474        if( !g_slist_find_custom( p->monitor_files, filename,
475                                  (GCompareFunc)strcmp ) )
476            p->monitor_files =
477                g_slist_append( p->monitor_files, g_strdup( filename ) );
478        if( !p->monitor_idle_tag )
479            p->monitor_idle_tag = gtr_timeout_add_seconds( 1, watchFolderIdle, core );
480    }
481}
482
483static void
484watchFolderChanged( GFileMonitor       * monitor    UNUSED,
485                    GFile *                         file,
486                    GFile              * other_type UNUSED,
487                    GFileMonitorEvent               event_type,
488                    gpointer                        core )
489{
490    if( event_type == G_FILE_MONITOR_EVENT_CREATED )
491    {
492        char * filename = g_file_get_path( file );
493        maybeAddTorrent( core, filename );
494        g_free( filename );
495    }
496}
497
498static void
499scanWatchDir( TrCore * core )
500{
501    const gboolean isEnabled = pref_flag_get( PREF_KEY_DIR_WATCH_ENABLED );
502
503    if( isEnabled )
504    {
505        const char * dirname = pref_string_get( PREF_KEY_DIR_WATCH );
506        GDir *       dir = g_dir_open( dirname, 0, NULL );
507        const char * basename;
508        while( ( basename = g_dir_read_name( dir ) ) )
509        {
510            char * filename = g_build_filename( dirname, basename, NULL );
511            maybeAddTorrent( core, filename );
512            g_free( filename );
513        }
514    }
515}
516
517static void
518updateWatchDir( TrCore * core )
519{
520    const char *           filename = pref_string_get( PREF_KEY_DIR_WATCH );
521    const gboolean         isEnabled = pref_flag_get(
522        PREF_KEY_DIR_WATCH_ENABLED );
523    struct TrCorePrivate * p = TR_CORE( core )->priv;
524
525    if( p->monitor && ( !isEnabled || tr_strcmp( filename, p->monitor_path ) ) )
526    {
527        g_signal_handler_disconnect( p->monitor, p->monitor_tag );
528        g_free( p->monitor_path );
529        g_file_monitor_cancel( p->monitor );
530        g_object_unref( G_OBJECT( p->monitor ) );
531        p->monitor_path = NULL;
532        p->monitor = NULL;
533        p->monitor_tag = 0;
534    }
535
536    if( isEnabled && !p->monitor )
537    {
538        GFile *        file = g_file_new_for_path( filename );
539        GFileMonitor * m = g_file_monitor_directory( file, 0, NULL, NULL );
540        scanWatchDir( core );
541        p->monitor = m;
542        p->monitor_path = g_strdup( filename );
543        p->monitor_tag = g_signal_connect( m, "changed",
544                                           G_CALLBACK(
545                                               watchFolderChanged ), core );
546    }
547}
548
549#endif
550
551static void
552prefsChanged( TrCore *      core,
553              const char *  key,
554              gpointer data UNUSED )
555{
556    if( !strcmp( key, PREF_KEY_SORT_MODE )
557      || !strcmp( key, PREF_KEY_SORT_REVERSED ) )
558    {
559        const char * mode = pref_string_get( PREF_KEY_SORT_MODE );
560        gboolean     isReversed = pref_flag_get( PREF_KEY_SORT_REVERSED );
561        setSort( core, mode, isReversed );
562    }
563    else if( !strcmp( key, TR_PREFS_KEY_PEER_LIMIT_GLOBAL ) )
564    {
565        const uint16_t val = pref_int_get( key );
566        tr_sessionSetPeerLimit( tr_core_session( core ), val );
567    }
568    else if( !strcmp( key, TR_PREFS_KEY_PEER_LIMIT_TORRENT ) )
569    {
570        const uint16_t val = pref_int_get( key );
571        tr_sessionSetPeerLimitPerTorrent( tr_core_session( core ), val );
572    }
573    else if( !strcmp( key, PREF_KEY_INHIBIT_HIBERNATION ) )
574    {
575        maybeInhibitHibernation( core );
576    }
577#ifdef HAVE_GIO
578    else if( !strcmp( key, PREF_KEY_DIR_WATCH )
579           || !strcmp( key, PREF_KEY_DIR_WATCH_ENABLED ) )
580    {
581        updateWatchDir( core );
582    }
583#endif
584}
585
586static void
587tr_core_init( GTypeInstance *  instance,
588              gpointer g_class UNUSED )
589{
590    TrCore *               self = (TrCore *) instance;
591    GtkListStore *         store;
592    struct TrCorePrivate * p;
593
594    /* column types for the model used to store torrent information */
595    /* keep this in sync with the enum near the bottom of tr_core.h */
596    GType                  types[] = {
597        G_TYPE_STRING,    /* name */
598        G_TYPE_STRING,    /* collated name */
599        TR_TORRENT_TYPE,  /* TrTorrent object */
600        G_TYPE_POINTER,   /* tr_torrent* */
601        G_TYPE_INT        /* tr_stat()->status */
602    };
603
604    p = self->priv = G_TYPE_INSTANCE_GET_PRIVATE( self,
605                                                  TR_CORE_TYPE,
606                                                  struct TrCorePrivate );
607
608    /* create the model used to store torrent data */
609    g_assert( ALEN( types ) == MC_ROW_COUNT );
610    store = gtk_list_store_newv( MC_ROW_COUNT, types );
611
612    p->model    = GTK_TREE_MODEL( store );
613
614#ifdef HAVE_DBUS_GLIB
615    if( our_instance_adds_remote_torrents )
616    {
617        DBusGConnection * bus = dbus_g_bus_get( DBUS_BUS_SESSION, NULL );
618        if( bus )
619            dbus_g_connection_register_g_object(
620                 bus,
621                "/com/transmissionbt/Transmission",
622                G_OBJECT( self ) );
623    }
624#endif
625}
626
627GType
628tr_core_get_type( void )
629{
630    static GType type = 0;
631
632    if( !type )
633    {
634        static const GTypeInfo info =
635        {
636            sizeof( TrCoreClass ),
637            NULL,                 /* base_init */
638            NULL,                 /* base_finalize */
639            tr_core_class_init,   /* class_init */
640            NULL,                 /* class_finalize */
641            NULL,                 /* class_data */
642            sizeof( TrCore ),
643            0,                    /* n_preallocs */
644            tr_core_init,         /* instance_init */
645            NULL,
646        };
647        type = g_type_register_static( G_TYPE_OBJECT, "TrCore", &info, 0 );
648    }
649
650    return type;
651}
652
653/**
654***
655**/
656
657TrCore *
658tr_core_new( tr_session * session )
659{
660    TrCore * core = TR_CORE( g_object_new( TR_CORE_TYPE, NULL ) );
661
662    core->priv->session  = session;
663
664    /* init from prefs & listen to pref changes */
665    prefsChanged( core, PREF_KEY_SORT_MODE, NULL );
666    prefsChanged( core, PREF_KEY_SORT_REVERSED, NULL );
667    prefsChanged( core, PREF_KEY_DIR_WATCH_ENABLED, NULL );
668    prefsChanged( core, TR_PREFS_KEY_PEER_LIMIT_GLOBAL, NULL );
669    prefsChanged( core, PREF_KEY_INHIBIT_HIBERNATION, NULL );
670    g_signal_connect( core, "prefs-changed", G_CALLBACK( prefsChanged ), NULL );
671
672    return core;
673}
674
675void
676tr_core_close( TrCore * core )
677{
678    tr_session * session = tr_core_session( core );
679
680    if( session )
681    {
682        core->priv->session = NULL;
683        pref_save( session );
684        tr_sessionClose( session );
685    }
686}
687
688GtkTreeModel *
689tr_core_model( TrCore * core )
690{
691    return isDisposed( core ) ? NULL : core->priv->model;
692}
693
694tr_session *
695tr_core_session( TrCore * core )
696{
697    return isDisposed( core ) ? NULL : core->priv->session;
698}
699
700static gboolean
701statsForeach( GtkTreeModel * model,
702              GtkTreePath  * path UNUSED,
703              GtkTreeIter  * iter,
704              gpointer       gstats )
705{
706    tr_torrent *        tor;
707    struct core_stats * stats = gstats;
708    int                 activity;
709
710    gtk_tree_model_get( model, iter, MC_TORRENT_RAW, &tor, -1 );
711    activity = tr_torrentGetActivity( tor );
712
713    if( activity == TR_STATUS_DOWNLOAD )
714        ++stats->downloadCount;
715    else if( activity == TR_STATUS_SEED )
716        ++stats->seedingCount;
717
718    return FALSE;
719}
720
721void
722tr_core_get_stats( const TrCore *      core,
723                   struct core_stats * setme )
724{
725    memset( setme, 0, sizeof( struct core_stats ) );
726
727    if( !isDisposed( core ) )
728    {
729        tr_session * session = core->priv->session;
730
731        setme->clientDownloadSpeed = tr_sessionGetPieceSpeed( session, TR_DOWN );
732
733        setme->clientUploadSpeed = tr_sessionGetPieceSpeed( session, TR_UP );
734
735        gtk_tree_model_foreach( core->priv->model, statsForeach, setme );
736    }
737}
738
739static char*
740doCollate( const char * in )
741{
742    const char * end = in + strlen( in );
743    char *       casefold;
744    char *       ret;
745
746    while( in < end )
747    {
748        const gunichar ch = g_utf8_get_char( in );
749        if( !g_unichar_isalnum ( ch ) ) /* eat everything before the first alnum
750                                          */
751            in += g_unichar_to_utf8( ch, NULL );
752        else
753            break;
754    }
755
756    if( in == end )
757        return g_strdup ( "" );
758
759    casefold = g_utf8_casefold( in, end - in );
760    ret = g_utf8_collate_key( casefold, -1 );
761    g_free( casefold );
762
763    return ret;
764}
765
766void
767tr_core_add_torrent( TrCore *    self,
768                     TrTorrent * gtor )
769{
770    const tr_info * inf = tr_torrent_info( gtor );
771    const tr_stat * torStat = tr_torrent_stat( gtor );
772    tr_torrent *    tor = tr_torrent_handle( gtor );
773    char *          collated = doCollate( inf->name );
774    GtkListStore *  store = GTK_LIST_STORE( tr_core_model( self ) );
775    GtkTreeIter     unused;
776
777    gtk_list_store_insert_with_values( store, &unused, 0,
778                                       MC_NAME,          inf->name,
779                                       MC_NAME_COLLATED, collated,
780                                       MC_TORRENT,       gtor,
781                                       MC_TORRENT_RAW,   tor,
782                                       MC_ACTIVITY,      torStat->activity,
783                                       -1 );
784
785    /* cleanup */
786    g_object_unref( G_OBJECT( gtor ) );
787    g_free( collated );
788}
789
790int
791tr_core_load( TrCore * self,
792              gboolean forcePaused )
793{
794    int           i;
795    int           count = 0;
796    tr_torrent ** torrents;
797    tr_ctor *     ctor;
798
799    ctor = tr_ctorNew( tr_core_session( self ) );
800    if( forcePaused )
801        tr_ctorSetPaused( ctor, TR_FORCE, TRUE );
802    tr_ctorSetPeerLimit( ctor, TR_FALLBACK,
803                         pref_int_get( TR_PREFS_KEY_PEER_LIMIT_TORRENT ) );
804
805    torrents = tr_sessionLoadTorrents ( tr_core_session( self ), ctor, &count );
806    for( i = 0; i < count; ++i )
807        tr_core_add_torrent( self, tr_torrent_new_preexisting( torrents[i] ) );
808
809    tr_free( torrents );
810    tr_ctorFree( ctor );
811
812    return count;
813}
814
815void
816tr_core_blocksig( TrCore *     core,
817                  gboolean     isDone,
818                  const char * status )
819{
820    g_signal_emit( core, TR_CORE_GET_CLASS(
821                       core )->blocksig, 0, isDone, status );
822}
823
824static void
825tr_core_errsig( TrCore *         core,
826                enum tr_core_err type,
827                const char *     msg )
828{
829    g_signal_emit( core, TR_CORE_GET_CLASS( core )->errsig, 0, type, msg );
830}
831
832static void
833add_filename( TrCore *     core,
834              const char * filename,
835              gboolean     doStart,
836              gboolean     doPrompt )
837{
838    tr_session * session = tr_core_session( core );
839
840    if( filename && session )
841    {
842        tr_ctor * ctor;
843
844        ctor = tr_ctorNew( session );
845        tr_core_apply_defaults( ctor );
846        tr_ctorSetPaused( ctor, TR_FORCE, !doStart );
847
848        if( tr_ctorSetMetainfoFromFile( ctor, filename ) )
849        {
850            tr_core_errsig( core, TR_EINVALID, filename );
851            tr_ctorFree( ctor );
852        }
853        else
854        {
855            tr_info inf;
856            int err = tr_torrentParse( session, ctor, &inf );
857
858            switch( err )
859            {
860                case TR_EINVALID:
861                    tr_core_errsig( core, err, filename );
862                    break;
863
864                case TR_EDUPLICATE:
865                    /* don't complain about .torrent files in the watch directory
866                     * that have already been added... that gets annoying and we
867                     * don't want to be naggign users to clean up their watch dirs */
868                    if( !core->priv->adding_from_watch_dir )
869                        tr_core_errsig( core, err, inf.name );
870                    tr_metainfoFree( &inf );
871                    break;
872
873                default:
874                    if( doPrompt )
875                        g_signal_emit( core, TR_CORE_GET_CLASS( core )->promptsig, 0, ctor );
876                    else {
877                        TrTorrent * gtor = tr_torrent_new_ctor( session, ctor, &err );
878                        if( err )
879                            tr_core_errsig( core, err, filename );
880                        else
881                            tr_core_add_torrent( core, gtor );
882                    }
883                    tr_metainfoFree( &inf );
884                    break;
885            }
886        }
887    }
888}
889
890gboolean
891tr_core_add_file( TrCore *          core,
892                  const char *      filename,
893                  gboolean *        success,
894                  GError     ** err UNUSED )
895{
896    add_filename( core, filename,
897                  pref_flag_get( PREF_KEY_START ),
898                  pref_flag_get( PREF_KEY_OPTIONS_PROMPT ) );
899    *success = TRUE;
900    return TRUE;
901}
902
903gboolean
904tr_core_present_window( TrCore      * core UNUSED,
905                        gboolean *         success,
906                        GError     ** err  UNUSED )
907{
908    action_activate( "present-main-window" );
909    *success = TRUE;
910    return TRUE;
911}
912
913void
914tr_core_add_list( TrCore *    core,
915                  GSList *    torrentFiles,
916                  pref_flag_t start,
917                  pref_flag_t prompt )
918{
919    const gboolean doStart = pref_flag_eval( start, PREF_KEY_START );
920    const gboolean doPrompt = pref_flag_eval( prompt, PREF_KEY_OPTIONS_PROMPT );
921    GSList * l;
922
923    for( l = torrentFiles; l != NULL; l = l->next )
924        add_filename( core, l->data, doStart, doPrompt );
925
926    tr_core_torrents_added( core );
927    freestrlist( torrentFiles );
928}
929
930void
931tr_core_torrents_added( TrCore * self )
932{
933    tr_core_update( self );
934    tr_core_errsig( self, TR_CORE_ERR_NO_MORE_TORRENTS, NULL );
935}
936
937static gboolean
938findTorrentInModel( TrCore *      core,
939                    int           id,
940                    GtkTreeIter * setme )
941{
942    int            match = 0;
943    GtkTreeIter    iter;
944    GtkTreeModel * model = tr_core_model( core );
945
946    if( gtk_tree_model_iter_children( model, &iter, NULL ) ) do
947        {
948            tr_torrent * tor;
949            gtk_tree_model_get( model, &iter, MC_TORRENT_RAW, &tor, -1 );
950            match = tr_torrentId( tor ) == id;
951        }
952        while( !match && gtk_tree_model_iter_next( model, &iter ) );
953
954    if( match )
955        *setme = iter;
956
957    return match;
958}
959
960void
961tr_core_torrent_destroyed( TrCore * core,
962                           int      id )
963{
964    GtkTreeIter iter;
965
966    if( findTorrentInModel( core, id, &iter ) )
967    {
968        TrTorrent * gtor;
969        GtkTreeModel * model = tr_core_model( core );
970        gtk_tree_model_get( model, &iter, MC_TORRENT, &gtor, -1 );
971        tr_torrent_clear( gtor );
972        gtk_list_store_remove( GTK_LIST_STORE( model ), &iter );
973        g_object_unref( G_OBJECT( gtor ) );
974    }
975}
976
977void
978tr_core_remove_torrent( TrCore *    core,
979                        TrTorrent * gtor,
980                        int         deleteFiles )
981{
982    const tr_torrent * tor = tr_torrent_handle( gtor );
983
984    if( tor )
985    {
986        int         id = tr_torrentId( tor );
987        GtkTreeIter iter;
988        if( findTorrentInModel( core, id, &iter ) )
989        {
990            GtkTreeModel * model = tr_core_model( core );
991
992            /* remove from the gui */
993            gtk_list_store_remove( GTK_LIST_STORE( model ), &iter );
994
995            /* maybe delete the downloaded files */
996            if( deleteFiles )
997                tr_torrent_delete_files( gtor );
998
999            /* remove the torrent */
1000            tr_torrent_set_remove_flag( gtor, TRUE );
1001            g_object_unref( G_OBJECT( gtor ) );
1002        }
1003    }
1004}
1005
1006/***
1007****
1008***/
1009
1010static gboolean
1011update_foreach( GtkTreeModel *      model,
1012                GtkTreePath  * path UNUSED,
1013                GtkTreeIter *       iter,
1014                gpointer       data UNUSED )
1015{
1016    int         oldActivity;
1017    int         newActivity;
1018    TrTorrent * gtor;
1019
1020    /* maybe update the status column in the model */
1021    gtk_tree_model_get( model, iter,
1022                        MC_TORRENT, &gtor,
1023                        MC_ACTIVITY, &oldActivity,
1024                        -1 );
1025    newActivity = tr_torrentGetActivity( tr_torrent_handle( gtor ) );
1026    if( newActivity != oldActivity )
1027        gtk_list_store_set( GTK_LIST_STORE( model ), iter,
1028                            MC_ACTIVITY, newActivity,
1029                            -1 );
1030
1031    /* cleanup */
1032    g_object_unref( gtor );
1033    return FALSE;
1034}
1035
1036void
1037tr_core_update( TrCore * self )
1038{
1039    int               column;
1040    GtkSortType       order;
1041    GtkTreeSortable * sortable;
1042    GtkTreeModel *    model = tr_core_model( self );
1043
1044    /* pause sorting */
1045    sortable = GTK_TREE_SORTABLE( model );
1046    gtk_tree_sortable_get_sort_column_id( sortable, &column, &order );
1047    gtk_tree_sortable_set_sort_column_id(
1048        sortable, GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, order );
1049
1050    /* refresh the model */
1051    gtk_tree_model_foreach( model, update_foreach, NULL );
1052
1053    /* resume sorting */
1054    gtk_tree_sortable_set_sort_column_id( sortable, column, order );
1055
1056    /* maybe inhibit hibernation */
1057    maybeInhibitHibernation( self );
1058}
1059
1060void
1061tr_core_quit( TrCore * core )
1062{
1063    g_signal_emit( core, TR_CORE_GET_CLASS( core )->quitsig, 0 );
1064}
1065
1066/**
1067***  Hibernate
1068**/
1069
1070#ifdef HAVE_DBUS_GLIB
1071
1072static DBusGProxy*
1073get_hibernation_inhibit_proxy( void )
1074{
1075    GError *          error = NULL;
1076    DBusGConnection * conn;
1077
1078    conn = dbus_g_bus_get( DBUS_BUS_SESSION, &error );
1079    if( error )
1080    {
1081        g_warning ( "DBUS cannot connect : %s", error->message );
1082        g_error_free ( error );
1083        return NULL;
1084    }
1085
1086    return dbus_g_proxy_new_for_name (
1087               conn,
1088               "org.freedesktop.PowerManagement",
1089               "/org/freedesktop/PowerManagement/Inhibit",
1090               "org.freedesktop.PowerManagement.Inhibit" );
1091}
1092
1093static gboolean
1094gtr_inhibit_hibernation( guint * cookie )
1095{
1096    gboolean     success = FALSE;
1097    DBusGProxy * proxy = get_hibernation_inhibit_proxy( );
1098
1099    if( proxy )
1100    {
1101        GError *     error = NULL;
1102        const char * application = _( "Transmission Bittorrent Client" );
1103        const char * reason = _( "BitTorrent Activity" );
1104        success = dbus_g_proxy_call( proxy, "Inhibit", &error,
1105                                     G_TYPE_STRING, application,
1106                                     G_TYPE_STRING, reason,
1107                                     G_TYPE_INVALID,
1108                                     G_TYPE_UINT, cookie,
1109                                     G_TYPE_INVALID );
1110        if( success )
1111            tr_inf( _( "Disallowing desktop hibernation" ) );
1112        else
1113        {
1114            tr_err( _(
1115                        "Couldn't disable desktop hibernation: %s" ),
1116                    error->message );
1117            g_error_free( error );
1118        }
1119
1120        g_object_unref( G_OBJECT( proxy ) );
1121    }
1122
1123    return success != 0;
1124}
1125
1126static void
1127gtr_uninhibit_hibernation( guint inhibit_cookie )
1128{
1129    DBusGProxy * proxy = get_hibernation_inhibit_proxy( );
1130
1131    if( proxy )
1132    {
1133        GError * error = NULL;
1134        gboolean success = dbus_g_proxy_call( proxy, "UnInhibit", &error,
1135                                              G_TYPE_UINT, inhibit_cookie,
1136                                              G_TYPE_INVALID,
1137                                              G_TYPE_INVALID );
1138        if( success )
1139            tr_inf( _( "Allowing desktop hibernation" ) );
1140        else
1141        {
1142            g_warning( "Couldn't uninhibit the system from suspending: %s.",
1143                       error->message );
1144            g_error_free( error );
1145        }
1146
1147        g_object_unref( G_OBJECT( proxy ) );
1148    }
1149}
1150
1151#endif
1152
1153static void
1154tr_core_set_hibernation_allowed( TrCore * core,
1155                                 gboolean allowed )
1156{
1157#ifdef HAVE_DBUS_GLIB
1158    g_return_if_fail( core );
1159    g_return_if_fail( core->priv );
1160
1161    core->priv->inhibit_allowed = allowed != 0;
1162
1163    if( allowed && core->priv->have_inhibit_cookie )
1164    {
1165        gtr_uninhibit_hibernation( core->priv->inhibit_cookie );
1166        core->priv->have_inhibit_cookie = FALSE;
1167    }
1168
1169    if( !allowed
1170      && !core->priv->have_inhibit_cookie
1171      && !core->priv->dbus_error )
1172    {
1173        if( gtr_inhibit_hibernation( &core->priv->inhibit_cookie ) )
1174            core->priv->have_inhibit_cookie = TRUE;
1175        else
1176            core->priv->dbus_error = TRUE;
1177    }
1178#endif
1179}
1180
1181static void
1182maybeInhibitHibernation( TrCore * core )
1183{
1184    gboolean inhibit = pref_flag_get( PREF_KEY_INHIBIT_HIBERNATION );
1185
1186    /* always allow hibernation when all the torrents are paused */
1187    if( inhibit ) {
1188        tr_session * session = tr_core_session( core );
1189
1190        if( tr_sessionGetActiveTorrentCount( session ) == 0 )
1191            inhibit = FALSE;
1192    }
1193
1194    tr_core_set_hibernation_allowed( core, !inhibit );
1195}
1196
1197/**
1198***  Prefs
1199**/
1200
1201static void
1202commitPrefsChange( TrCore *     core,
1203                   const char * key )
1204{
1205    g_signal_emit( core, TR_CORE_GET_CLASS( core )->prefsig, 0, key );
1206    pref_save( tr_core_session( core ) );
1207}
1208
1209void
1210tr_core_set_pref( TrCore *     self,
1211                  const char * key,
1212                  const char * newval )
1213{
1214    const char * oldval = pref_string_get( key );
1215
1216    if( tr_strcmp( oldval, newval ) )
1217    {
1218        pref_string_set( key, newval );
1219        commitPrefsChange( self, key );
1220    }
1221}
1222
1223void
1224tr_core_set_pref_bool( TrCore *     self,
1225                       const char * key,
1226                       gboolean     newval )
1227{
1228    const gboolean oldval = pref_flag_get( key );
1229
1230    if( oldval != newval )
1231    {
1232        pref_flag_set( key, newval );
1233        commitPrefsChange( self, key );
1234    }
1235}
1236
1237void
1238tr_core_set_pref_int( TrCore *     self,
1239                      const char * key,
1240                      int          newval )
1241{
1242    const int oldval = pref_int_get( key );
1243
1244    if( oldval != newval )
1245    {
1246        pref_int_set( key, newval );
1247        commitPrefsChange( self, key );
1248    }
1249}
1250
Note: See TracBrowser for help on using the repository browser.