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

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

janitorial: tr_handle -> tr_session

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