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

Last change on this file since 7390 was 7390, checked in by charles, 10 years ago

(trunk gtk) fix prefs oopsie reported by RolCol?

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