source: branches/1.4x/gtk/tr-core.c @ 7395

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

(1.4x gtk) fix some preferences bugs revealed by Rolcol's testing out changes in trunk

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