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

Last change on this file since 6559 was 6559, checked in by muks, 13 years ago

Don't show an error when transmission is run twice

Instead, present the main window. This commit also auto-generates
the dbus bindings.

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