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

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

(1.4x gtk) #1585: use g_timeout_add_seconds() where appropriate to group timers together for fewer scheduled wakeups

  • Property svn:keywords set to Date Rev Author Id
File size: 36.4 KB
Line 
1/******************************************************************************
2 * $Id: tr-core.c 7463 2008-12-22 05:39:02Z 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 = gtr_timeout_add_seconds( 1, watchFolderIdle, 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, PREF_KEY_MAX_PEERS_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, PREF_KEY_MAX_PEERS_GLOBAL, NULL );
687    prefsChanged( core, PREF_KEY_INHIBIT_HIBERNATION, NULL );
688    g_signal_connect( core, "prefs-changed", G_CALLBACK(
689                          prefsChanged ), NULL );
690
691    return core;
692}
693
694void
695tr_core_close( TrCore * core )
696{
697    tr_session * session = tr_core_session( core );
698
699    if( session )
700    {
701        core->priv->session = NULL;
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( PREF_KEY_MAX_PEERS_PER_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        int       err;
861        tr_ctor * ctor = tr_ctorNew( session );
862        tr_core_apply_defaults( ctor );
863        tr_ctorSetPaused( ctor, TR_FORCE, !doStart );
864        if( tr_ctorSetMetainfoFromFile( ctor, filename ) )
865        {
866            tr_core_errsig( core, TR_EINVALID, filename );
867            tr_ctorFree( ctor );
868        }
869        else if( ( err = tr_torrentParse( session, ctor, NULL ) ) )
870        {
871            /* don't complain about .torrent files in the watch directory
872               that have already been added... that gets annoying, and we
873               don't want to nag about cleaning up the watch dir */
874            const gboolean quiet = ( err == TR_EDUPLICATE )
875                                && ( core->priv->adding_from_watch_dir );
876            if( !quiet )
877                tr_core_errsig( core, err, filename );
878
879            tr_ctorFree( ctor );
880        }
881        else if( doPrompt )
882            g_signal_emit( core, TR_CORE_GET_CLASS(
883                               core )->promptsig, 0, ctor );
884        else
885        {
886            tr_torrent * tor = tr_torrentNew( session, ctor, &err );
887            if( err )
888                tr_core_errsig( core, err, filename );
889            else
890                tr_core_add_torrent( core, tr_torrent_new_preexisting( tor ) );
891        }
892    }
893}
894
895gboolean
896tr_core_add_file( TrCore *          core,
897                  const char *      filename,
898                  gboolean *        success,
899                  GError     ** err UNUSED )
900{
901    add_filename( core, filename,
902                 pref_flag_get( PREF_KEY_START ),
903                 pref_flag_get( PREF_KEY_OPTIONS_PROMPT ) );
904    *success = TRUE;
905    return TRUE;
906}
907
908gboolean
909tr_core_present_window( TrCore      * core UNUSED,
910                        gboolean *         success,
911                        GError     ** err  UNUSED )
912{
913    action_activate( "present-main-window" );
914    *success = TRUE;
915    return TRUE;
916}
917
918void
919tr_core_add_list( TrCore *    core,
920                  GSList *    torrentFiles,
921                  pref_flag_t start,
922                  pref_flag_t prompt )
923{
924    const gboolean doStart = pref_flag_eval( start, PREF_KEY_START );
925    const gboolean doPrompt = pref_flag_eval( prompt, PREF_KEY_OPTIONS_PROMPT );
926    GSList * l;
927
928    for( l = torrentFiles; l != NULL; l = l->next )
929        add_filename( core, l->data, doStart, doPrompt );
930
931    tr_core_torrents_added( core );
932    freestrlist( torrentFiles );
933}
934
935void
936tr_core_torrents_added( TrCore * self )
937{
938    tr_core_update( self );
939    tr_core_errsig( self, TR_CORE_ERR_NO_MORE_TORRENTS, NULL );
940}
941
942static gboolean
943findTorrentInModel( TrCore *      core,
944                    int           id,
945                    GtkTreeIter * setme )
946{
947    int            match = 0;
948    GtkTreeIter    iter;
949    GtkTreeModel * model = tr_core_model( core );
950
951    if( gtk_tree_model_iter_children( model, &iter, NULL ) ) do
952        {
953            tr_torrent * tor;
954            gtk_tree_model_get( model, &iter, MC_TORRENT_RAW, &tor, -1 );
955            match = tr_torrentId( tor ) == id;
956        }
957        while( !match && gtk_tree_model_iter_next( model, &iter ) );
958
959    if( match )
960        *setme = iter;
961
962    return match;
963}
964
965void
966tr_core_torrent_destroyed( TrCore * core,
967                           int      id )
968{
969    GtkTreeIter iter;
970
971    if( findTorrentInModel( core, id, &iter ) )
972    {
973        TrTorrent *    gtor;
974        GtkTreeModel * model = tr_core_model( core );
975        gtk_tree_model_get( model, &iter, MC_TORRENT, &gtor, -1 );
976        tr_torrent_clear( gtor );
977        gtk_list_store_remove( GTK_LIST_STORE( model ), &iter );
978        g_object_unref( G_OBJECT( gtor ) );
979    }
980}
981
982void
983tr_core_remove_torrent( TrCore *    core,
984                        TrTorrent * gtor,
985                        int         deleteFiles )
986{
987    const tr_torrent * tor = tr_torrent_handle( gtor );
988
989    if( tor )
990    {
991        int         id = tr_torrentId( tor );
992        GtkTreeIter iter;
993        if( findTorrentInModel( core, id, &iter ) )
994        {
995            GtkTreeModel * model = tr_core_model( core );
996
997            /* remove from the gui */
998            gtk_list_store_remove( GTK_LIST_STORE( model ), &iter );
999
1000            /* maybe delete the downloaded files */
1001            if( deleteFiles )
1002                tr_torrent_delete_files( gtor );
1003
1004            /* remove the torrent */
1005            tr_torrent_set_remove_flag( gtor, TRUE );
1006            g_object_unref( G_OBJECT( gtor ) );
1007        }
1008    }
1009}
1010
1011/***
1012****
1013***/
1014
1015static gboolean
1016update_foreach( GtkTreeModel *      model,
1017                GtkTreePath  * path UNUSED,
1018                GtkTreeIter *       iter,
1019                gpointer       data UNUSED )
1020{
1021    int         oldActivity;
1022    int         newActivity;
1023    TrTorrent * gtor;
1024
1025    /* maybe update the status column in the model */
1026    gtk_tree_model_get( model, iter,
1027                        MC_TORRENT, &gtor,
1028                        MC_ACTIVITY, &oldActivity,
1029                        -1 );
1030    newActivity = tr_torrentGetActivity( tr_torrent_handle( gtor ) );
1031    if( newActivity != oldActivity )
1032        gtk_list_store_set( GTK_LIST_STORE( model ), iter,
1033                            MC_ACTIVITY, newActivity,
1034                            -1 );
1035
1036    /* cleanup */
1037    g_object_unref( gtor );
1038    return FALSE;
1039}
1040
1041void
1042tr_core_update( TrCore * self )
1043{
1044    int               column;
1045    GtkSortType       order;
1046    GtkTreeSortable * sortable;
1047    GtkTreeModel *    model = tr_core_model( self );
1048
1049    /* pause sorting */
1050    sortable = GTK_TREE_SORTABLE( model );
1051    gtk_tree_sortable_get_sort_column_id( sortable, &column, &order );
1052    gtk_tree_sortable_set_sort_column_id(
1053        sortable, GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, order );
1054
1055    /* refresh the model */
1056    gtk_tree_model_foreach( model, update_foreach, NULL );
1057
1058    /* resume sorting */
1059    gtk_tree_sortable_set_sort_column_id( sortable, column, order );
1060
1061    /* maybe inhibit hibernation */
1062    maybeInhibitHibernation( self );
1063}
1064
1065void
1066tr_core_quit( TrCore * core )
1067{
1068    g_signal_emit( core, TR_CORE_GET_CLASS( core )->quitsig, 0 );
1069}
1070
1071/**
1072***  Hibernate
1073**/
1074
1075#ifdef HAVE_DBUS_GLIB
1076
1077static DBusGProxy*
1078get_hibernation_inhibit_proxy( void )
1079{
1080    GError *          error = NULL;
1081    DBusGConnection * conn;
1082
1083    conn = dbus_g_bus_get( DBUS_BUS_SESSION, &error );
1084    if( error )
1085    {
1086        g_warning ( "DBUS cannot connect : %s", error->message );
1087        g_error_free ( error );
1088        return NULL;
1089    }
1090
1091    return dbus_g_proxy_new_for_name (
1092               conn,
1093               "org.freedesktop.PowerManagement",
1094               "/org/freedesktop/PowerManagement/Inhibit",
1095               "org.freedesktop.PowerManagement.Inhibit" );
1096}
1097
1098static gboolean
1099gtr_inhibit_hibernation( guint * cookie )
1100{
1101    gboolean     success = FALSE;
1102    DBusGProxy * proxy = get_hibernation_inhibit_proxy( );
1103
1104    if( proxy )
1105    {
1106        GError *     error = NULL;
1107        const char * application = _( "Transmission Bittorrent Client" );
1108        const char * reason = _( "BitTorrent Activity" );
1109        success = dbus_g_proxy_call( proxy, "Inhibit", &error,
1110                                     G_TYPE_STRING, application,
1111                                     G_TYPE_STRING, reason,
1112                                     G_TYPE_INVALID,
1113                                     G_TYPE_UINT, cookie,
1114                                     G_TYPE_INVALID );
1115        if( success )
1116            tr_inf( _( "Disallowing desktop hibernation" ) );
1117        else
1118        {
1119            tr_err( _(
1120                        "Couldn't disable desktop hibernation: %s" ),
1121                    error->message );
1122            g_error_free( error );
1123        }
1124
1125        g_object_unref( G_OBJECT( proxy ) );
1126    }
1127
1128    return success != 0;
1129}
1130
1131static void
1132gtr_uninhibit_hibernation( guint inhibit_cookie )
1133{
1134    DBusGProxy * proxy = get_hibernation_inhibit_proxy( );
1135
1136    if( proxy )
1137    {
1138        GError * error = NULL;
1139        gboolean success = dbus_g_proxy_call( proxy, "UnInhibit", &error,
1140                                              G_TYPE_UINT, inhibit_cookie,
1141                                              G_TYPE_INVALID,
1142                                              G_TYPE_INVALID );
1143        if( success )
1144            tr_inf( _( "Allowing desktop hibernation" ) );
1145        else
1146        {
1147            g_warning( "Couldn't uninhibit the system from suspending: %s.",
1148                       error->message );
1149            g_error_free( error );
1150        }
1151
1152        g_object_unref( G_OBJECT( proxy ) );
1153    }
1154}
1155
1156#endif
1157
1158static void
1159tr_core_set_hibernation_allowed( TrCore * core,
1160                                 gboolean allowed )
1161{
1162#ifdef HAVE_DBUS_GLIB
1163    g_return_if_fail( core );
1164    g_return_if_fail( core->priv );
1165
1166    core->priv->inhibit_allowed = allowed != 0;
1167
1168    if( allowed && core->priv->have_inhibit_cookie )
1169    {
1170        gtr_uninhibit_hibernation( core->priv->inhibit_cookie );
1171        core->priv->have_inhibit_cookie = FALSE;
1172    }
1173
1174    if( !allowed
1175      && !core->priv->have_inhibit_cookie
1176      && !core->priv->dbus_error )
1177    {
1178        if( gtr_inhibit_hibernation( &core->priv->inhibit_cookie ) )
1179            core->priv->have_inhibit_cookie = TRUE;
1180        else
1181            core->priv->dbus_error = TRUE;
1182    }
1183#endif
1184}
1185
1186static void
1187maybeInhibitHibernation( TrCore * core )
1188{
1189    gboolean inhibit = pref_flag_get( PREF_KEY_INHIBIT_HIBERNATION );
1190
1191    /* always allow hibernation when all the torrents are paused */
1192    if( inhibit ) {
1193        gboolean active = FALSE;
1194        tr_session *  session = tr_core_session( core );
1195        tr_torrent * tor = NULL;
1196        while(( tor = tr_torrentNext( session, tor )))
1197            if(( active = ( tr_torrentGetActivity( tor ) != TR_STATUS_STOPPED )))
1198                break;
1199        if( !active )
1200            inhibit = FALSE;
1201    }
1202
1203    tr_core_set_hibernation_allowed( core, !inhibit );
1204}
1205
1206/**
1207***  Prefs
1208**/
1209
1210static void
1211commitPrefsChange( TrCore *     core,
1212                   const char * key )
1213{
1214    g_signal_emit( core, TR_CORE_GET_CLASS( core )->prefsig, 0, key );
1215    pref_save( );
1216}
1217
1218void
1219tr_core_set_pref( TrCore *     self,
1220                  const char * key,
1221                  const char * newval )
1222{
1223    const char * oldval = pref_string_get( key );
1224
1225    if( tr_strcmp( oldval, newval ) )
1226    {
1227        pref_string_set( key, newval );
1228        commitPrefsChange( self, key );
1229    }
1230}
1231
1232void
1233tr_core_set_pref_bool( TrCore *     self,
1234                       const char * key,
1235                       gboolean     newval )
1236{
1237    const gboolean oldval = pref_flag_get( key );
1238
1239    if( oldval != newval )
1240    {
1241        pref_flag_set( key, newval );
1242        commitPrefsChange( self, key );
1243    }
1244}
1245
1246void
1247tr_core_set_pref_int( TrCore *     self,
1248                      const char * key,
1249                      int          newval )
1250{
1251    const int oldval = pref_int_get( key );
1252
1253    if( oldval != newval )
1254    {
1255        pref_int_set( key, newval );
1256        commitPrefsChange( self, key );
1257    }
1258}
1259
Note: See TracBrowser for help on using the repository browser.