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

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

(trunk) fix gtk bug of not remembering users' settings for per-torrent peer limits

  • Property svn:keywords set to Date Rev Author Id
File size: 37.0 KB
Line 
1/******************************************************************************
2 * $Id: tr-core.c 7393 2008-12-14 22:57:08Z 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, TR_PREFS_KEY_PEER_LIMIT_TORRENT ) )
592    {
593        const uint16_t val = pref_int_get( key );
594        tr_sessionSetPeerLimitPerTorrent( tr_core_session( core ), val );
595    }
596    else if( !strcmp( key, PREF_KEY_INHIBIT_HIBERNATION ) )
597    {
598        maybeInhibitHibernation( core );
599    }
600#ifdef HAVE_GIO
601    else if( !strcmp( key, PREF_KEY_DIR_WATCH )
602           || !strcmp( key, PREF_KEY_DIR_WATCH_ENABLED ) )
603    {
604        updateWatchDir( core );
605    }
606#endif
607}
608
609static void
610tr_core_init( GTypeInstance *  instance,
611              gpointer g_class UNUSED )
612{
613    TrCore *               self = (TrCore *) instance;
614    GtkListStore *         store;
615    struct TrCorePrivate * p;
616
617    /* column types for the model used to store torrent information */
618    /* keep this in sync with the enum near the bottom of tr_core.h */
619    GType                  types[] = {
620        G_TYPE_STRING,    /* name */
621        G_TYPE_STRING,    /* collated name */
622        TR_TORRENT_TYPE,  /* TrTorrent object */
623        G_TYPE_POINTER,   /* tr_torrent* */
624        G_TYPE_INT        /* tr_stat()->status */
625    };
626
627    p = self->priv = G_TYPE_INSTANCE_GET_PRIVATE( self,
628                                                  TR_CORE_TYPE,
629                                                  struct TrCorePrivate );
630
631    /* create the model used to store torrent data */
632    g_assert( ALEN( types ) == MC_ROW_COUNT );
633    store = gtk_list_store_newv( MC_ROW_COUNT, types );
634
635    p->model    = GTK_TREE_MODEL( store );
636
637#ifdef HAVE_DBUS_GLIB
638    if( our_instance_adds_remote_torrents )
639    {
640        DBusGConnection * bus = dbus_g_bus_get( DBUS_BUS_SESSION, NULL );
641        if( bus )
642            dbus_g_connection_register_g_object(
643                 bus,
644                "/com/transmissionbt/Transmission",
645                G_OBJECT( self ) );
646    }
647#endif
648}
649
650GType
651tr_core_get_type( void )
652{
653    static GType type = 0;
654
655    if( !type )
656    {
657        static const GTypeInfo info =
658        {
659            sizeof( TrCoreClass ),
660            NULL,                 /* base_init */
661            NULL,                 /* base_finalize */
662            tr_core_class_init,   /* class_init */
663            NULL,                 /* class_finalize */
664            NULL,                 /* class_data */
665            sizeof( TrCore ),
666            0,                    /* n_preallocs */
667            tr_core_init,         /* instance_init */
668            NULL,
669        };
670        type = g_type_register_static( G_TYPE_OBJECT, "TrCore", &info, 0 );
671    }
672
673    return type;
674}
675
676/**
677***
678**/
679
680TrCore *
681tr_core_new( tr_session * session )
682{
683    TrCore * core = TR_CORE( g_object_new( TR_CORE_TYPE, NULL ) );
684
685    core->priv->session  = session;
686
687    /* init from prefs & listen to pref changes */
688    prefsChanged( core, PREF_KEY_SORT_MODE, NULL );
689    prefsChanged( core, PREF_KEY_SORT_REVERSED, NULL );
690    prefsChanged( core, PREF_KEY_DIR_WATCH_ENABLED, NULL );
691    prefsChanged( core, TR_PREFS_KEY_PEER_LIMIT_GLOBAL, NULL );
692    prefsChanged( core, PREF_KEY_INHIBIT_HIBERNATION, NULL );
693    g_signal_connect( core, "prefs-changed", G_CALLBACK( prefsChanged ), NULL );
694
695    return core;
696}
697
698void
699tr_core_close( TrCore * core )
700{
701    tr_session * session = tr_core_session( core );
702
703    if( session )
704    {
705        core->priv->session = NULL;
706        pref_save( session );
707        tr_sessionClose( session );
708    }
709}
710
711GtkTreeModel *
712tr_core_model( TrCore * core )
713{
714    return isDisposed( core ) ? NULL : core->priv->model;
715}
716
717tr_session *
718tr_core_session( TrCore * core )
719{
720    return isDisposed( core ) ? NULL : core->priv->session;
721}
722
723static gboolean
724statsForeach( GtkTreeModel * model,
725              GtkTreePath  * path UNUSED,
726              GtkTreeIter  * iter,
727              gpointer       gstats )
728{
729    tr_torrent *        tor;
730    struct core_stats * stats = gstats;
731    int                 activity;
732
733    gtk_tree_model_get( model, iter, MC_TORRENT_RAW, &tor, -1 );
734    activity = tr_torrentGetActivity( tor );
735
736    if( activity == TR_STATUS_DOWNLOAD )
737        ++stats->downloadCount;
738    else if( activity == TR_STATUS_SEED )
739        ++stats->seedingCount;
740
741    return FALSE;
742}
743
744void
745tr_core_get_stats( const TrCore *      core,
746                   struct core_stats * setme )
747{
748    memset( setme, 0, sizeof( struct core_stats ) );
749
750    if( !isDisposed( core ) )
751    {
752        tr_session * session = core->priv->session;
753
754        setme->clientDownloadSpeed = tr_sessionGetPieceSpeed( session, TR_DOWN );
755
756        setme->clientUploadSpeed = tr_sessionGetPieceSpeed( session, TR_UP );
757
758        gtk_tree_model_foreach( core->priv->model, statsForeach, setme );
759    }
760}
761
762static char*
763doCollate( const char * in )
764{
765    const char * end = in + strlen( in );
766    char *       casefold;
767    char *       ret;
768
769    while( in < end )
770    {
771        const gunichar ch = g_utf8_get_char( in );
772        if( !g_unichar_isalnum ( ch ) ) /* eat everything before the first alnum
773                                          */
774            in += g_unichar_to_utf8( ch, NULL );
775        else
776            break;
777    }
778
779    if( in == end )
780        return g_strdup ( "" );
781
782    casefold = g_utf8_casefold( in, end - in );
783    ret = g_utf8_collate_key( casefold, -1 );
784    g_free( casefold );
785
786    return ret;
787}
788
789void
790tr_core_add_torrent( TrCore *    self,
791                     TrTorrent * gtor )
792{
793    const tr_info * inf = tr_torrent_info( gtor );
794    const tr_stat * torStat = tr_torrent_stat( gtor );
795    tr_torrent *    tor = tr_torrent_handle( gtor );
796    char *          collated = doCollate( inf->name );
797    GtkListStore *  store = GTK_LIST_STORE( tr_core_model( self ) );
798    GtkTreeIter     unused;
799
800    gtk_list_store_insert_with_values( store, &unused, 0,
801                                       MC_NAME,          inf->name,
802                                       MC_NAME_COLLATED, collated,
803                                       MC_TORRENT,       gtor,
804                                       MC_TORRENT_RAW,   tor,
805                                       MC_ACTIVITY,      torStat->activity,
806                                       -1 );
807
808    /* cleanup */
809    g_object_unref( G_OBJECT( gtor ) );
810    g_free( collated );
811}
812
813int
814tr_core_load( TrCore * self,
815              gboolean forcePaused )
816{
817    int           i;
818    int           count = 0;
819    tr_torrent ** torrents;
820    tr_ctor *     ctor;
821
822    ctor = tr_ctorNew( tr_core_session( self ) );
823    if( forcePaused )
824        tr_ctorSetPaused( ctor, TR_FORCE, TRUE );
825    tr_ctorSetPeerLimit( ctor, TR_FALLBACK,
826                         pref_int_get( TR_PREFS_KEY_PEER_LIMIT_TORRENT ) );
827
828    torrents = tr_sessionLoadTorrents ( tr_core_session( self ), ctor, &count );
829    for( i = 0; i < count; ++i )
830        tr_core_add_torrent( self, tr_torrent_new_preexisting( torrents[i] ) );
831
832    tr_free( torrents );
833    tr_ctorFree( ctor );
834
835    return count;
836}
837
838void
839tr_core_blocksig( TrCore *     core,
840                  gboolean     isDone,
841                  const char * status )
842{
843    g_signal_emit( core, TR_CORE_GET_CLASS(
844                       core )->blocksig, 0, isDone, status );
845}
846
847static void
848tr_core_errsig( TrCore *         core,
849                enum tr_core_err type,
850                const char *     msg )
851{
852    g_signal_emit( core, TR_CORE_GET_CLASS( core )->errsig, 0, type, msg );
853}
854
855static void
856add_filename( TrCore *     core,
857              const char * filename,
858              gboolean     doStart,
859              gboolean     doPrompt )
860{
861    tr_session * session = tr_core_session( core );
862
863    if( filename && session )
864    {
865        tr_ctor * ctor;
866
867        ctor = tr_ctorNew( session );
868        tr_core_apply_defaults( ctor );
869        tr_ctorSetPaused( ctor, TR_FORCE, !doStart );
870
871        if( tr_ctorSetMetainfoFromFile( ctor, filename ) )
872        {
873            tr_core_errsig( core, TR_EINVALID, filename );
874            tr_ctorFree( ctor );
875        }
876        else
877        {
878            tr_info inf;
879            int err = tr_torrentParse( session, ctor, &inf );
880
881            switch( err )
882            {
883                case TR_EINVALID:
884                    tr_core_errsig( core, err, filename );
885                    break;
886
887                case TR_EDUPLICATE:
888                    /* don't complain about .torrent files in the watch directory
889                     * that have already been added... that gets annoying and we
890                     * don't want to be naggign users to clean up their watch dirs */
891                    if( !core->priv->adding_from_watch_dir )
892                        tr_core_errsig( core, err, inf.name );
893                    tr_metainfoFree( &inf );
894                    break;
895
896                default:
897                    if( doPrompt )
898                        g_signal_emit( core, TR_CORE_GET_CLASS( core )->promptsig, 0, ctor );
899                    else {
900                        tr_torrent * tor = tr_torrentNew( session, ctor, &err );
901                        if( err )
902                            tr_core_errsig( core, err, filename );
903                        else
904                            tr_core_add_torrent( core, tr_torrent_new_preexisting( tor ) );
905                    }
906                    tr_metainfoFree( &inf );
907                    break;
908            }
909        }
910    }
911}
912
913gboolean
914tr_core_add_file( TrCore *          core,
915                  const char *      filename,
916                  gboolean *        success,
917                  GError     ** err UNUSED )
918{
919    add_filename( core, filename,
920                  pref_flag_get( PREF_KEY_START ),
921                  pref_flag_get( PREF_KEY_OPTIONS_PROMPT ) );
922    *success = TRUE;
923    return TRUE;
924}
925
926gboolean
927tr_core_present_window( TrCore      * core UNUSED,
928                        gboolean *         success,
929                        GError     ** err  UNUSED )
930{
931    action_activate( "present-main-window" );
932    *success = TRUE;
933    return TRUE;
934}
935
936void
937tr_core_add_list( TrCore *    core,
938                  GSList *    torrentFiles,
939                  pref_flag_t start,
940                  pref_flag_t prompt )
941{
942    const gboolean doStart = pref_flag_eval( start, PREF_KEY_START );
943    const gboolean doPrompt = pref_flag_eval( prompt, PREF_KEY_OPTIONS_PROMPT );
944    GSList * l;
945
946    for( l = torrentFiles; l != NULL; l = l->next )
947        add_filename( core, l->data, doStart, doPrompt );
948
949    tr_core_torrents_added( core );
950    freestrlist( torrentFiles );
951}
952
953void
954tr_core_torrents_added( TrCore * self )
955{
956    tr_core_update( self );
957    tr_core_errsig( self, TR_CORE_ERR_NO_MORE_TORRENTS, NULL );
958}
959
960static gboolean
961findTorrentInModel( TrCore *      core,
962                    int           id,
963                    GtkTreeIter * setme )
964{
965    int            match = 0;
966    GtkTreeIter    iter;
967    GtkTreeModel * model = tr_core_model( core );
968
969    if( gtk_tree_model_iter_children( model, &iter, NULL ) ) do
970        {
971            tr_torrent * tor;
972            gtk_tree_model_get( model, &iter, MC_TORRENT_RAW, &tor, -1 );
973            match = tr_torrentId( tor ) == id;
974        }
975        while( !match && gtk_tree_model_iter_next( model, &iter ) );
976
977    if( match )
978        *setme = iter;
979
980    return match;
981}
982
983void
984tr_core_torrent_destroyed( TrCore * core,
985                           int      id )
986{
987    GtkTreeIter iter;
988
989    if( findTorrentInModel( core, id, &iter ) )
990    {
991        TrTorrent *    gtor;
992        GtkTreeModel * model = tr_core_model( core );
993        gtk_tree_model_get( model, &iter, MC_TORRENT, &gtor, -1 );
994        tr_torrent_clear( gtor );
995        gtk_list_store_remove( GTK_LIST_STORE( model ), &iter );
996        g_object_unref( G_OBJECT( gtor ) );
997    }
998}
999
1000void
1001tr_core_remove_torrent( TrCore *    core,
1002                        TrTorrent * gtor,
1003                        int         deleteFiles )
1004{
1005    const tr_torrent * tor = tr_torrent_handle( gtor );
1006
1007    if( tor )
1008    {
1009        int         id = tr_torrentId( tor );
1010        GtkTreeIter iter;
1011        if( findTorrentInModel( core, id, &iter ) )
1012        {
1013            GtkTreeModel * model = tr_core_model( core );
1014
1015            /* remove from the gui */
1016            gtk_list_store_remove( GTK_LIST_STORE( model ), &iter );
1017
1018            /* maybe delete the downloaded files */
1019            if( deleteFiles )
1020                tr_torrent_delete_files( gtor );
1021
1022            /* remove the torrent */
1023            tr_torrent_set_remove_flag( gtor, TRUE );
1024            g_object_unref( G_OBJECT( gtor ) );
1025        }
1026    }
1027}
1028
1029/***
1030****
1031***/
1032
1033static gboolean
1034update_foreach( GtkTreeModel *      model,
1035                GtkTreePath  * path UNUSED,
1036                GtkTreeIter *       iter,
1037                gpointer       data UNUSED )
1038{
1039    int         oldActivity;
1040    int         newActivity;
1041    TrTorrent * gtor;
1042
1043    /* maybe update the status column in the model */
1044    gtk_tree_model_get( model, iter,
1045                        MC_TORRENT, &gtor,
1046                        MC_ACTIVITY, &oldActivity,
1047                        -1 );
1048    newActivity = tr_torrentGetActivity( tr_torrent_handle( gtor ) );
1049    if( newActivity != oldActivity )
1050        gtk_list_store_set( GTK_LIST_STORE( model ), iter,
1051                            MC_ACTIVITY, newActivity,
1052                            -1 );
1053
1054    /* cleanup */
1055    g_object_unref( gtor );
1056    return FALSE;
1057}
1058
1059void
1060tr_core_update( TrCore * self )
1061{
1062    int               column;
1063    GtkSortType       order;
1064    GtkTreeSortable * sortable;
1065    GtkTreeModel *    model = tr_core_model( self );
1066
1067    /* pause sorting */
1068    sortable = GTK_TREE_SORTABLE( model );
1069    gtk_tree_sortable_get_sort_column_id( sortable, &column, &order );
1070    gtk_tree_sortable_set_sort_column_id(
1071        sortable, GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, order );
1072
1073    /* refresh the model */
1074    gtk_tree_model_foreach( model, update_foreach, NULL );
1075
1076    /* resume sorting */
1077    gtk_tree_sortable_set_sort_column_id( sortable, column, order );
1078
1079    /* maybe inhibit hibernation */
1080    maybeInhibitHibernation( self );
1081}
1082
1083void
1084tr_core_quit( TrCore * core )
1085{
1086    g_signal_emit( core, TR_CORE_GET_CLASS( core )->quitsig, 0 );
1087}
1088
1089/**
1090***  Hibernate
1091**/
1092
1093#ifdef HAVE_DBUS_GLIB
1094
1095static DBusGProxy*
1096get_hibernation_inhibit_proxy( void )
1097{
1098    GError *          error = NULL;
1099    DBusGConnection * conn;
1100
1101    conn = dbus_g_bus_get( DBUS_BUS_SESSION, &error );
1102    if( error )
1103    {
1104        g_warning ( "DBUS cannot connect : %s", error->message );
1105        g_error_free ( error );
1106        return NULL;
1107    }
1108
1109    return dbus_g_proxy_new_for_name (
1110               conn,
1111               "org.freedesktop.PowerManagement",
1112               "/org/freedesktop/PowerManagement/Inhibit",
1113               "org.freedesktop.PowerManagement.Inhibit" );
1114}
1115
1116static gboolean
1117gtr_inhibit_hibernation( guint * cookie )
1118{
1119    gboolean     success = FALSE;
1120    DBusGProxy * proxy = get_hibernation_inhibit_proxy( );
1121
1122    if( proxy )
1123    {
1124        GError *     error = NULL;
1125        const char * application = _( "Transmission Bittorrent Client" );
1126        const char * reason = _( "BitTorrent Activity" );
1127        success = dbus_g_proxy_call( proxy, "Inhibit", &error,
1128                                     G_TYPE_STRING, application,
1129                                     G_TYPE_STRING, reason,
1130                                     G_TYPE_INVALID,
1131                                     G_TYPE_UINT, cookie,
1132                                     G_TYPE_INVALID );
1133        if( success )
1134            tr_inf( _( "Disallowing desktop hibernation" ) );
1135        else
1136        {
1137            tr_err( _(
1138                        "Couldn't disable desktop hibernation: %s" ),
1139                    error->message );
1140            g_error_free( error );
1141        }
1142
1143        g_object_unref( G_OBJECT( proxy ) );
1144    }
1145
1146    return success != 0;
1147}
1148
1149static void
1150gtr_uninhibit_hibernation( guint inhibit_cookie )
1151{
1152    DBusGProxy * proxy = get_hibernation_inhibit_proxy( );
1153
1154    if( proxy )
1155    {
1156        GError * error = NULL;
1157        gboolean success = dbus_g_proxy_call( proxy, "UnInhibit", &error,
1158                                              G_TYPE_UINT, inhibit_cookie,
1159                                              G_TYPE_INVALID,
1160                                              G_TYPE_INVALID );
1161        if( success )
1162            tr_inf( _( "Allowing desktop hibernation" ) );
1163        else
1164        {
1165            g_warning( "Couldn't uninhibit the system from suspending: %s.",
1166                       error->message );
1167            g_error_free( error );
1168        }
1169
1170        g_object_unref( G_OBJECT( proxy ) );
1171    }
1172}
1173
1174#endif
1175
1176static void
1177tr_core_set_hibernation_allowed( TrCore * core,
1178                                 gboolean allowed )
1179{
1180#ifdef HAVE_DBUS_GLIB
1181    g_return_if_fail( core );
1182    g_return_if_fail( core->priv );
1183
1184    core->priv->inhibit_allowed = allowed != 0;
1185
1186    if( allowed && core->priv->have_inhibit_cookie )
1187    {
1188        gtr_uninhibit_hibernation( core->priv->inhibit_cookie );
1189        core->priv->have_inhibit_cookie = FALSE;
1190    }
1191
1192    if( !allowed
1193      && !core->priv->have_inhibit_cookie
1194      && !core->priv->dbus_error )
1195    {
1196        if( gtr_inhibit_hibernation( &core->priv->inhibit_cookie ) )
1197            core->priv->have_inhibit_cookie = TRUE;
1198        else
1199            core->priv->dbus_error = TRUE;
1200    }
1201#endif
1202}
1203
1204static void
1205maybeInhibitHibernation( TrCore * core )
1206{
1207    gboolean inhibit = pref_flag_get( PREF_KEY_INHIBIT_HIBERNATION );
1208
1209    /* always allow hibernation when all the torrents are paused */
1210    if( inhibit ) {
1211        gboolean active = FALSE;
1212        tr_session *  session = tr_core_session( core );
1213        tr_torrent * tor = NULL;
1214        while(( tor = tr_torrentNext( session, tor )))
1215            if(( active = ( tr_torrentGetActivity( tor ) != TR_STATUS_STOPPED )))
1216                break;
1217        if( !active )
1218            inhibit = FALSE;
1219    }
1220
1221    tr_core_set_hibernation_allowed( core, !inhibit );
1222}
1223
1224/**
1225***  Prefs
1226**/
1227
1228static void
1229commitPrefsChange( TrCore *     core,
1230                   const char * key )
1231{
1232    g_signal_emit( core, TR_CORE_GET_CLASS( core )->prefsig, 0, key );
1233    pref_save( tr_core_session( core ) );
1234}
1235
1236void
1237tr_core_set_pref( TrCore *     self,
1238                  const char * key,
1239                  const char * newval )
1240{
1241    const char * oldval = pref_string_get( key );
1242
1243    if( tr_strcmp( oldval, newval ) )
1244    {
1245        pref_string_set( key, newval );
1246        commitPrefsChange( self, key );
1247    }
1248}
1249
1250void
1251tr_core_set_pref_bool( TrCore *     self,
1252                       const char * key,
1253                       gboolean     newval )
1254{
1255    const gboolean oldval = pref_flag_get( key );
1256
1257    if( oldval != newval )
1258    {
1259        pref_flag_set( key, newval );
1260        commitPrefsChange( self, key );
1261    }
1262}
1263
1264void
1265tr_core_set_pref_int( TrCore *     self,
1266                      const char * key,
1267                      int          newval )
1268{
1269    const int oldval = pref_int_get( key );
1270
1271    if( oldval != newval )
1272    {
1273        pref_int_set( key, newval );
1274        commitPrefsChange( self, key );
1275    }
1276}
1277
Note: See TracBrowser for help on using the repository browser.