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

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

(gtk) fix watch directory FMR

  • Property svn:keywords set to Date Rev Author Id
File size: 30.6 KB
Line 
1/******************************************************************************
2 * $Id: tr-core.c 6165 2008-06-13 00:46:27Z 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( );
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        const char * path = pref_string_get( PREF_KEY_DOWNLOAD_DIR );
390        tr_ctorSetDownloadDir( ctor, TR_FORCE, path );
391    }
392}
393
394#ifdef HAVE_GIO
395static gboolean
396watchFolderIdle( gpointer gcore )
397{
398    TrCore * core = TR_CORE( gcore );
399    tr_core_add_list_defaults( core, core->priv->monitor_files );
400
401    /* cleanup */
402    core->priv->monitor_files = NULL;
403    core->priv->monitor_idle_tag = 0;
404    return FALSE;
405}
406
407static void
408maybeAddTorrent( TrCore * core, const char * filename )
409{
410    const gboolean isTorrent = g_str_has_suffix( filename, ".torrent" );
411
412    if( isTorrent )
413    {
414        struct TrCorePrivate * p = core->priv;
415
416        if( !g_slist_find_custom( p->monitor_files, filename, (GCompareFunc)strcmp ) )
417            p->monitor_files = g_slist_append( p->monitor_files, g_strdup( filename ) );
418        if( !p->monitor_idle_tag )
419            p->monitor_idle_tag = g_timeout_add( 1000, watchFolderIdle, core );
420    }
421}
422
423static void
424watchFolderChanged( GFileMonitor       * monitor UNUSED,
425                    GFile              * file,
426                    GFile              * other_type UNUSED,
427                    GFileMonitorEvent    event_type,
428                    gpointer             core )
429{
430    if( event_type == G_FILE_MONITOR_EVENT_CREATED )
431    {
432        char * filename = g_file_get_path( file );
433        maybeAddTorrent( core, filename );
434        g_free( filename );
435    }
436}
437
438static void
439scanWatchDir( TrCore * core )
440{
441    const gboolean isEnabled = pref_flag_get( PREF_KEY_DIR_WATCH_ENABLED );
442    if( isEnabled )
443    {
444        const char * dirname = pref_string_get( PREF_KEY_DIR_WATCH );
445        GDir * dir = g_dir_open( dirname, 0, NULL );
446        const char * basename;
447        while(( basename = g_dir_read_name( dir ))) {
448            char * filename = g_build_filename( dirname, basename, NULL );
449            maybeAddTorrent( core, filename );
450            g_free( filename );
451        }
452    }
453}
454
455static void
456updateWatchDir( TrCore * core )
457{
458    const char * filename = pref_string_get( PREF_KEY_DIR_WATCH );
459    const gboolean isEnabled = pref_flag_get( PREF_KEY_DIR_WATCH_ENABLED );
460    struct TrCorePrivate * p = TR_CORE( core )->priv;
461
462    if( p->monitor && ( !isEnabled || tr_strcmp( filename, p->monitor_path ) ) )
463    {
464        g_signal_handler_disconnect( p->monitor, p->monitor_tag );
465        g_free( p->monitor_path );
466        g_file_monitor_cancel( p->monitor );
467        g_object_unref( G_OBJECT( p->monitor ) );
468        p->monitor_path = NULL;
469        p->monitor = NULL;
470        p->monitor_tag = 0;
471    }
472
473    if( isEnabled && !p->monitor )
474    {
475        GFile * file = g_file_new_for_path( filename );
476        GFileMonitor * m = g_file_monitor_directory( file, 0, NULL, NULL );
477        scanWatchDir( core );
478        p->monitor = m;
479        p->monitor_path = g_strdup( filename );
480        p->monitor_tag = g_signal_connect( m, "changed",
481                                           G_CALLBACK( watchFolderChanged ), core );
482    }
483
484}
485#endif
486
487static void
488prefsChanged( TrCore * core, const char * key, gpointer data UNUSED )
489{
490    if( !strcmp( key, PREF_KEY_SORT_MODE ) ||
491        !strcmp( key, PREF_KEY_SORT_REVERSED ) )
492    {
493        const char * mode = pref_string_get( PREF_KEY_SORT_MODE );
494        gboolean isReversed = pref_flag_get( PREF_KEY_SORT_REVERSED );
495        setSort( core, mode, isReversed );
496    }
497    else if( !strcmp( key, PREF_KEY_MAX_PEERS_GLOBAL ) )
498    {
499        const uint16_t val = pref_int_get( key );
500        tr_sessionSetPeerLimit( tr_core_handle( core ), val );
501    }
502    else if( !strcmp( key, PREF_KEY_ALLOW_HIBERNATION ) )
503    {
504        tr_core_set_hibernation_allowed( core, pref_flag_get( key ) );
505    }
506#ifdef HAVE_GIO
507    else if( !strcmp( key, PREF_KEY_DIR_WATCH ) ||
508             !strcmp( key, PREF_KEY_DIR_WATCH_ENABLED ) )
509    {
510        updateWatchDir( core );
511    }
512#endif
513}
514
515static void
516tr_core_init( GTypeInstance * instance, gpointer g_class UNUSED )
517{
518    TrCore * self = (TrCore *) instance;
519    GtkListStore * store;
520    struct TrCorePrivate * p;
521
522    /* column types for the model used to store torrent information */
523    /* keep this in sync with the enum near the bottom of tr_core.h */
524    GType types[] = {
525        G_TYPE_STRING,    /* name */
526        G_TYPE_STRING,    /* collated name */
527        TR_TORRENT_TYPE,  /* TrTorrent object */
528        G_TYPE_POINTER,   /* tr_torrent* */
529        G_TYPE_INT        /* tr_stat()->status */
530    };
531
532    p = self->priv = G_TYPE_INSTANCE_GET_PRIVATE( self,
533                                                  TR_CORE_TYPE,
534                                                  struct TrCorePrivate );
535
536    /* create the model used to store torrent data */
537    g_assert( ALEN( types ) == MC_ROW_COUNT );
538    store = gtk_list_store_newv( MC_ROW_COUNT, types );
539
540    p->model    = GTK_TREE_MODEL( store );
541
542#ifdef HAVE_DBUS_GLIB
543    if( our_instance_adds_remote_torrents )
544    {
545        DBusGConnection * bus = dbus_g_bus_get( DBUS_BUS_SESSION, NULL );
546        if( bus )
547            dbus_g_connection_register_g_object( bus,
548                                                 "/com/transmissionbt/Transmission",
549                                                 G_OBJECT( self ));
550    }
551#endif
552
553}
554
555GType
556tr_core_get_type( void )
557{
558    static GType type = 0;
559
560    if( !type )
561    {
562        static const GTypeInfo info =
563        {
564            sizeof( TrCoreClass ),
565            NULL,                       /* base_init */
566            NULL,                       /* base_finalize */
567            tr_core_class_init,         /* class_init */
568            NULL,                       /* class_finalize */
569            NULL,                       /* class_data */
570            sizeof( TrCore ),
571            0,                          /* n_preallocs */
572            tr_core_init,               /* instance_init */
573            NULL,
574        };
575        type = g_type_register_static( G_TYPE_OBJECT, "TrCore", &info, 0 );
576    }
577
578    return type;
579}
580
581/**
582***
583**/
584
585TrCore *
586tr_core_new( tr_handle * h )
587{
588    TrCore * core = TR_CORE( g_object_new( TR_CORE_TYPE, NULL ) );
589    core->priv->handle   = h;
590
591    /* init from prefs & listen to pref changes */
592    prefsChanged( core, PREF_KEY_SORT_MODE, NULL );
593    prefsChanged( core, PREF_KEY_SORT_REVERSED, NULL );
594    prefsChanged( core, PREF_KEY_DIR_WATCH_ENABLED, NULL );
595    prefsChanged( core, PREF_KEY_MAX_PEERS_GLOBAL, NULL );
596    prefsChanged( core, PREF_KEY_ALLOW_HIBERNATION, NULL );
597    g_signal_connect( core, "prefs-changed", G_CALLBACK(prefsChanged), NULL );
598
599    return core;
600}
601
602void
603tr_core_close( TrCore * core )
604{
605    tr_handle * handle = tr_core_handle( core );
606    if( handle )
607    {
608        core->priv->handle = NULL;
609        tr_sessionClose( handle ); 
610    }
611}
612
613GtkTreeModel *
614tr_core_model( TrCore * core )
615{
616    return isDisposed( core ) ? NULL : core->priv->model;
617}
618
619tr_handle *
620tr_core_handle( TrCore * core )
621{
622    return isDisposed( core ) ? NULL : core->priv->handle;
623}
624
625static gboolean
626statsForeach( GtkTreeModel * model,
627              GtkTreePath  * path UNUSED,
628              GtkTreeIter  * iter,
629              gpointer       gstats )
630{
631    tr_torrent * tor;
632    struct core_stats * stats = gstats;
633    int status;
634
635    gtk_tree_model_get( model, iter, MC_TORRENT_RAW, &tor, -1 );
636    status = tr_torrentGetStatus( tor );
637
638    if( status == TR_STATUS_DOWNLOAD )
639        ++stats->downloadCount;
640    else if( status == TR_STATUS_SEED )
641        ++stats->seedingCount;
642
643    return FALSE;
644}
645
646void
647tr_core_get_stats( const TrCore       * core,
648                   struct core_stats  * setme )
649{
650    memset( setme, 0, sizeof( struct core_stats ) );
651
652    if( !isDisposed( core ) )
653    {
654        tr_sessionGetSpeed( core->priv->handle,
655                            &setme->clientDownloadSpeed,
656                            &setme->clientUploadSpeed );
657
658        gtk_tree_model_foreach( core->priv->model,
659                                statsForeach,
660                                setme );
661    }
662}
663
664static char*
665doCollate( const char * in )
666{
667    const char * end = in + strlen( in );
668    char * casefold;
669    char * ret;
670
671    while( in < end ) {
672        const gunichar ch = g_utf8_get_char( in );
673        if (!g_unichar_isalnum (ch)) /* eat everything before the first alnum */
674            in += g_unichar_to_utf8( ch, NULL );
675        else
676            break;
677    }
678
679    if ( in == end )
680        return g_strdup ("");
681
682    casefold = g_utf8_casefold( in, end-in );
683    ret = g_utf8_collate_key( casefold, -1 );
684    g_free( casefold );
685
686    return ret;
687}
688
689void
690tr_core_add_torrent( TrCore * self, TrTorrent * gtor )
691{
692    const tr_info * inf = tr_torrent_info( gtor );
693    const tr_stat * torStat = tr_torrent_stat( gtor );
694    tr_torrent * tor = tr_torrent_handle( gtor );
695    char * collated = doCollate( inf->name );
696    GtkListStore * store = GTK_LIST_STORE( tr_core_model( self ) );
697    GtkTreeIter unused;
698
699    gtk_list_store_insert_with_values( store, &unused, 0, 
700                                       MC_NAME,          inf->name,
701                                       MC_NAME_COLLATED, collated,
702                                       MC_TORRENT,       gtor,
703                                       MC_TORRENT_RAW,   tor,
704                                       MC_STATUS,        torStat->status,
705                                       -1);
706
707    /* cleanup */
708    g_object_unref( G_OBJECT( gtor ) );
709    g_free( collated );
710}
711
712int
713tr_core_load( TrCore * self, gboolean forcePaused )
714{
715    int i;
716    int count = 0;
717    tr_torrent ** torrents;
718    tr_ctor * ctor;
719
720    ctor = tr_ctorNew( tr_core_handle( self ) );
721    if( forcePaused )
722        tr_ctorSetPaused( ctor, TR_FORCE, TRUE );
723    tr_ctorSetPeerLimit( ctor, TR_FALLBACK,
724                         pref_int_get( PREF_KEY_MAX_PEERS_PER_TORRENT ) );
725
726    torrents = tr_sessionLoadTorrents ( tr_core_handle( self ), ctor, &count );
727    for( i=0; i<count; ++i )
728        tr_core_add_torrent( self, tr_torrent_new_preexisting( torrents[i] ) );
729
730    tr_free( torrents );
731    tr_ctorFree( ctor );
732
733    return count;
734}
735
736static void
737tr_core_errsig( TrCore * core, enum tr_core_err type, const char * msg )
738{
739    g_signal_emit( core, TR_CORE_GET_CLASS(core)->errsig, 0, type, msg );
740}
741
742void
743tr_core_add_ctor( TrCore * self, tr_ctor * ctor )
744{
745    TrTorrent * tor;
746    char      * errstr = NULL;
747
748    tr_core_apply_defaults( ctor );
749
750    if(( tor = tr_torrent_new_ctor( tr_core_handle( self ), ctor, &errstr )))
751        tr_core_add_torrent( self, tor );
752    else{ 
753        tr_core_errsig( self, TR_CORE_ERR_ADD_TORRENT, errstr );
754        g_free( errstr );
755    }
756
757    /* cleanup */
758    tr_ctorFree( ctor );
759}
760
761static void
762add_filename( TrCore       * core,
763              const char   * filename,
764              gboolean       doStart,
765              gboolean       doPrompt )
766{
767    tr_handle * handle = tr_core_handle( core );
768
769    if( filename && handle )
770    {
771        tr_ctor * ctor = tr_ctorNew( handle );
772        tr_core_apply_defaults( ctor );
773        tr_ctorSetPaused( ctor, TR_FORCE, !doStart );
774        if( tr_ctorSetMetainfoFromFile( ctor, filename ) )
775            tr_ctorFree( ctor );
776        else if( tr_torrentParse( handle, ctor, NULL ) )
777            tr_ctorFree( ctor );
778        else if( doPrompt )
779            g_signal_emit( core, TR_CORE_GET_CLASS(core)->promptsig, 0, ctor );
780        else
781            tr_core_add_ctor( core, ctor );
782    }
783}
784
785gboolean
786tr_core_add_file( TrCore      * core,
787                  const char  * filename,
788                  gboolean    * success,
789                  GError     ** err UNUSED )
790{
791    add_filename( core, filename,
792                  pref_flag_get( PREF_KEY_START ),
793                  pref_flag_get( PREF_KEY_OPTIONS_PROMPT ) );
794    *success = TRUE;
795    return TRUE;
796}
797
798void
799tr_core_add_list( TrCore      * core,
800                  GSList      * torrentFiles,
801                  pref_flag_t   start,
802                  pref_flag_t   prompt )
803{
804    const gboolean doStart = pref_flag_eval( start, PREF_KEY_START );
805    const gboolean doPrompt = pref_flag_eval( prompt,PREF_KEY_OPTIONS_PROMPT );
806    GSList * l;
807    for( l=torrentFiles; l!=NULL; l=l->next )
808        add_filename( core, l->data, doStart, doPrompt );
809    freestrlist( torrentFiles );
810}
811
812void
813tr_core_torrents_added( TrCore * self )
814{
815    tr_core_update( self );
816    tr_core_errsig( self, TR_CORE_ERR_NO_MORE_TORRENTS, NULL );
817}
818
819static gboolean
820findTorrentInModel( TrCore * core, const TrTorrent * gtor, GtkTreeIter * setme )
821{
822    int match = 0;
823    GtkTreeIter iter;
824    GtkTreeModel * model = tr_core_model( core );
825
826    if( gtk_tree_model_iter_children( model, &iter, NULL ) ) do
827    {
828        TrTorrent * tmp;
829        gtk_tree_model_get( model, &iter, MC_TORRENT, &tmp, -1 );
830        match = tmp == gtor;
831        g_object_unref( G_OBJECT( tmp ) );
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_remove_torrent( TrCore * self, TrTorrent * gtor, int deleteFiles )
843{
844    GtkTreeIter iter;
845    GtkTreeModel * model = tr_core_model( self );
846
847    /* remove from the gui */
848    if( findTorrentInModel( self, gtor, &iter ) )
849        gtk_list_store_remove( GTK_LIST_STORE( model ), &iter );
850
851    /* maybe delete the downloaded files */
852    if( deleteFiles )
853        tr_torrent_delete_files( gtor );
854
855    /* remove the torrent */
856    tr_torrent_set_remove_flag( gtor, TRUE );
857    g_object_unref( G_OBJECT( gtor ) );
858}
859
860
861/***
862****
863***/
864
865static gboolean
866update_foreach( GtkTreeModel * model,
867                GtkTreePath  * path UNUSED,
868                GtkTreeIter  * iter,
869                gpointer       data UNUSED )
870{
871    int oldStatus;
872    int newStatus;
873    TrTorrent * gtor;
874
875    /* maybe update the status column in the model */
876    gtk_tree_model_get( model, iter,
877                        MC_TORRENT, &gtor,
878                        MC_STATUS, &oldStatus,
879                        -1 );
880    newStatus = tr_torrentGetStatus( tr_torrent_handle( gtor ) );
881    if( newStatus != oldStatus )
882        gtk_list_store_set( GTK_LIST_STORE( model ), iter,
883                            MC_STATUS, newStatus,
884                            -1 );
885
886    /* cleanup */
887    g_object_unref( gtor );
888    return FALSE;
889}
890
891void
892tr_core_update( TrCore * self )
893{
894    int column;
895    GtkSortType order;
896    GtkTreeSortable * sortable;
897    GtkTreeModel * model = tr_core_model( self );
898
899    /* pause sorting */
900    sortable = GTK_TREE_SORTABLE( model );
901    gtk_tree_sortable_get_sort_column_id( sortable, &column, &order );
902    gtk_tree_sortable_set_sort_column_id( sortable, GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, order );
903
904    /* refresh the model */
905    gtk_tree_model_foreach( model, update_foreach, NULL );
906
907    /* resume sorting */
908    gtk_tree_sortable_set_sort_column_id( sortable, column, order );
909}
910
911void
912tr_core_quit( TrCore * core )
913{
914    g_signal_emit( core, TR_CORE_GET_CLASS(core)->quitsig, 0 );
915}
916
917/**
918***  Hibernate
919**/
920
921#ifdef HAVE_DBUS_GLIB
922
923static DBusGProxy*
924get_hibernation_inhibit_proxy( void )
925{
926    GError * error = NULL;
927    DBusGConnection * conn;
928
929    conn = dbus_g_bus_get( DBUS_BUS_SESSION, &error );
930    if( error )
931    {
932        g_warning ("DBUS cannot connect : %s", error->message);
933        g_error_free (error);
934        return NULL;
935    }
936
937    return dbus_g_proxy_new_for_name (conn,
938               "org.freedesktop.PowerManagement",
939               "/org/freedesktop/PowerManagement/Inhibit",
940               "org.freedesktop.PowerManagement.Inhibit" );
941}
942
943static gboolean
944gtr_inhibit_hibernation( guint * cookie )
945{
946    gboolean success = FALSE;
947    DBusGProxy * proxy = get_hibernation_inhibit_proxy( );
948    if( proxy )
949    {
950        GError * error = NULL;
951        const char * application = _( "Transmission Bittorrent Client" );
952        const char * reason = _( "BitTorrent Activity" );
953        success = dbus_g_proxy_call( proxy, "Inhibit", &error,
954                                     G_TYPE_STRING, application,
955                                     G_TYPE_STRING, reason,
956                                     G_TYPE_INVALID,
957                                     G_TYPE_UINT, cookie,
958                                     G_TYPE_INVALID );
959        if( success )
960            tr_inf( _( "Disallowing desktop hibernation" ) );
961        else {
962            tr_err( _( "Couldn't disable desktop hibernation: %s" ), error->message );
963            g_error_free( error );
964        }
965
966        g_object_unref( G_OBJECT( proxy ) );
967    }
968
969    return success != 0;
970}
971
972static void
973gtr_uninhibit_hibernation( guint inhibit_cookie )
974{
975    DBusGProxy * proxy = get_hibernation_inhibit_proxy( );
976    if( proxy )
977    {
978        GError * error = NULL;
979        gboolean success = dbus_g_proxy_call( proxy, "UnInhibit", &error,
980                                              G_TYPE_UINT, inhibit_cookie,
981                                              G_TYPE_INVALID,
982                                              G_TYPE_INVALID );
983        if( success )
984            tr_inf( _( "Allowing desktop hibernation" ) );
985        else {
986            g_warning( "Couldn't uninhibit the system from suspending: %s.", error->message );
987            g_error_free( error );
988        }
989
990        g_object_unref( G_OBJECT( proxy ) );
991    }
992}
993
994#endif
995
996
997void
998tr_core_set_hibernation_allowed( TrCore * core, gboolean allowed )
999{
1000#ifdef HAVE_DBUS_GLIB
1001    g_return_if_fail( core );
1002    g_return_if_fail( core->priv );
1003
1004    core->priv->inhibit_allowed = allowed != 0;
1005
1006    if( allowed && core->priv->have_inhibit_cookie )
1007    {
1008        gtr_uninhibit_hibernation( core->priv->inhibit_cookie );
1009        core->priv->have_inhibit_cookie = FALSE;
1010    }
1011
1012    if( !allowed && !core->priv->have_inhibit_cookie )
1013    {
1014        core->priv->have_inhibit_cookie = gtr_inhibit_hibernation( &core->priv->inhibit_cookie );
1015    }
1016#endif
1017}
1018
1019/**
1020***  Prefs
1021**/
1022
1023static void
1024commitPrefsChange( TrCore * core, const char * key )
1025{
1026    pref_save( );
1027    g_signal_emit( core, TR_CORE_GET_CLASS(core)->prefsig, 0, key );
1028}
1029
1030void
1031tr_core_set_pref( TrCore * self, const char * key, const char * newval )
1032{
1033    const char * oldval = pref_string_get( key );
1034    if( tr_strcmp( oldval, newval ) )
1035    {
1036        pref_string_set( key, newval );
1037        commitPrefsChange( self, key );
1038    }
1039}
1040
1041void
1042tr_core_set_pref_bool( TrCore * self, const char * key, gboolean newval )
1043{
1044    const gboolean oldval = pref_flag_get( key );
1045    if( oldval != newval )
1046    {
1047        pref_flag_set( key, newval );
1048        commitPrefsChange( self, key );
1049    }
1050}
1051
1052void
1053tr_core_set_pref_int( TrCore * self, const char * key, int newval )
1054{
1055    const int oldval = pref_int_get( key );
1056    if( oldval != newval )
1057    {
1058        pref_int_set( key, newval );
1059        commitPrefsChange( self, key );
1060    }
1061}
Note: See TracBrowser for help on using the repository browser.