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

Last change on this file since 5994 was 5994, checked in by charles, 14 years ago

#981: add tr_stat.dateAdded to libT; add "sort by age" to gtk+ client

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