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

Last change on this file since 12973 was 12973, checked in by jordan, 11 years ago

(trunk gtk) #5204 "Transmission GTK client crashes on start" -- another possible fix ;)

  • Property svn:keywords set to Date Rev Author Id
File size: 51.2 KB
Line 
1/******************************************************************************
2 * $Id: tr-core.c 12973 2011-10-11 20:32:15Z jordan $
3 *
4 * Copyright (c) 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 <math.h> /* pow() */
26#include <string.h> /* strcmp, strlen */
27
28#include <gtk/gtk.h>
29#include <glib/gi18n.h>
30#include <gio/gio.h>
31
32#include <event2/buffer.h>
33
34#include <libtransmission/transmission.h>
35#include <libtransmission/bencode.h>
36#include <libtransmission/rpcimpl.h>
37#include <libtransmission/json.h>
38#include <libtransmission/utils.h> /* tr_free */
39
40#include "actions.h"
41#include "conf.h"
42#include "notify.h"
43#include "tr-core.h"
44#include "tr-prefs.h"
45#include "util.h"
46
47/***
48****
49***/
50
51enum
52{
53  ADD_ERROR_SIGNAL,
54  ADD_PROMPT_SIGNAL,
55  BLOCKLIST_SIGNAL,
56  BUSY_SIGNAL,
57  PORT_SIGNAL,
58  PREFS_SIGNAL,
59
60  LAST_SIGNAL
61};
62
63static guint signals[LAST_SIGNAL] = { 0 };
64
65static void core_maybe_inhibit_hibernation( TrCore * core );
66
67struct TrCorePrivate
68{
69    GFileMonitor * monitor;
70    gulong         monitor_tag;
71    GFile        * monitor_dir;
72    GSList       * monitor_files;
73    gulong         monitor_idle_tag;
74
75    gboolean       adding_from_watch_dir;
76    gboolean       inhibit_allowed;
77    gboolean       have_inhibit_cookie;
78    gboolean       dbus_error;
79    guint          inhibit_cookie;
80    gint           busy_count;
81    GtkTreeModel * raw_model;
82    GtkTreeModel * sorted_model;
83    tr_session   * session;
84    GStringChunk * string_chunk;
85};
86
87static int
88core_is_disposed( const TrCore * core )
89{
90    return !core || !core->priv->sorted_model;
91}
92
93G_DEFINE_TYPE (TrCore, tr_core, G_TYPE_OBJECT)
94
95static void
96core_dispose( GObject * o )
97{
98    TrCore * core = TR_CORE( o );
99
100    if( core->priv->sorted_model != NULL )
101    {
102        g_object_unref( core->priv->sorted_model );
103        core->priv->sorted_model = NULL;
104        core->priv->raw_model = NULL;
105    }
106
107    G_OBJECT_CLASS( tr_core_parent_class )->dispose( o );
108}
109
110static void
111core_finalize( GObject * o )
112{
113    TrCore * core = TR_CORE( o );
114
115    g_string_chunk_free( core->priv->string_chunk );
116
117    G_OBJECT_CLASS( tr_core_parent_class )->finalize( o );
118}
119
120static void
121tr_core_class_init( TrCoreClass * core_class )
122{
123    GObjectClass * gobject_class;
124    GType core_type = G_TYPE_FROM_CLASS( core_class );
125
126    g_type_class_add_private( core_class, sizeof( struct TrCorePrivate ) );
127
128    gobject_class = G_OBJECT_CLASS( core_class );
129    gobject_class->dispose = core_dispose;
130    gobject_class->finalize = core_finalize;
131
132    signals[ADD_ERROR_SIGNAL] =
133        g_signal_new( "add-error", core_type,
134                      G_SIGNAL_RUN_LAST,
135                      G_STRUCT_OFFSET(TrCoreClass, add_error),
136                      NULL, NULL,
137                      g_cclosure_marshal_VOID__UINT_POINTER,
138                      G_TYPE_NONE,
139                      2, G_TYPE_UINT, G_TYPE_POINTER );
140
141    signals[ADD_PROMPT_SIGNAL] =
142        g_signal_new( "add-prompt", core_type,
143                      G_SIGNAL_RUN_LAST,
144                      G_STRUCT_OFFSET(TrCoreClass, add_prompt),
145                      NULL, NULL,
146                      g_cclosure_marshal_VOID__POINTER,
147                      G_TYPE_NONE,
148                      1, G_TYPE_POINTER );
149
150    signals[BUSY_SIGNAL] =
151        g_signal_new( "busy", core_type,
152                      G_SIGNAL_RUN_FIRST,
153                      G_STRUCT_OFFSET(TrCoreClass, busy),
154                      NULL, NULL,
155                      g_cclosure_marshal_VOID__BOOLEAN,
156                      G_TYPE_NONE,
157                      1, G_TYPE_BOOLEAN );
158
159    signals[BLOCKLIST_SIGNAL] =
160        g_signal_new( "blocklist-updated", core_type,
161                      G_SIGNAL_RUN_FIRST,
162                      G_STRUCT_OFFSET(TrCoreClass, blocklist_updated),
163                      NULL, NULL,
164                      g_cclosure_marshal_VOID__INT,
165                      G_TYPE_NONE,
166                      1, G_TYPE_INT );
167
168    signals[PORT_SIGNAL] =
169        g_signal_new( "port-tested", core_type,
170                      G_SIGNAL_RUN_LAST,
171                      G_STRUCT_OFFSET(TrCoreClass, port_tested),
172                      NULL, NULL,
173                      g_cclosure_marshal_VOID__BOOLEAN,
174                      G_TYPE_NONE,
175                      1, G_TYPE_BOOLEAN );
176
177    signals[PREFS_SIGNAL] =
178        g_signal_new( "prefs-changed", core_type,
179                      G_SIGNAL_RUN_LAST,
180                      G_STRUCT_OFFSET(TrCoreClass, prefs_changed),
181                      NULL, NULL,
182                      g_cclosure_marshal_VOID__STRING,
183                      G_TYPE_NONE,
184                      1, G_TYPE_STRING );
185}
186
187static void
188tr_core_init( TrCore * core )
189{
190    GtkListStore * store;
191    struct TrCorePrivate * p;
192
193    /* column types for the model used to store torrent information */
194    /* keep this in sync with the enum near the bottom of tr_core.h */
195    GType types[] = { G_TYPE_POINTER,   /* collated name */
196                      G_TYPE_POINTER,   /* tr_torrent* */
197                      G_TYPE_INT,       /* torrent id */
198                      G_TYPE_DOUBLE,    /* tr_stat.pieceUploadSpeed_KBps */
199                      G_TYPE_DOUBLE,    /* tr_stat.pieceDownloadSpeed_KBps */
200                      G_TYPE_DOUBLE,    /* tr_stat.recheckProgress */
201                      G_TYPE_BOOLEAN,   /* filter.c:ACTIVITY_FILTER_ACTIVE */
202                      G_TYPE_INT,       /* tr_stat.activity */
203                      G_TYPE_UCHAR,     /* tr_stat.finished */
204                      G_TYPE_CHAR,      /* tr_priority_t */
205                      G_TYPE_INT,       /* tr_stat.queuePosition */
206                      G_TYPE_UINT,      /* build_torrent_trackers_hash() */
207                      G_TYPE_INT,       /* MC_ERROR */
208                      G_TYPE_INT };     /* MC_ACTIVE_PEER_COUNT */
209
210    p = core->priv = G_TYPE_INSTANCE_GET_PRIVATE( core,
211                                                  TR_CORE_TYPE,
212                                                  struct TrCorePrivate );
213
214    /* create the model used to store torrent data */
215    g_assert( G_N_ELEMENTS( types ) == MC_ROW_COUNT );
216    store = gtk_list_store_newv( MC_ROW_COUNT, types );
217
218    p->raw_model = GTK_TREE_MODEL( store );
219    p->sorted_model = gtk_tree_model_sort_new_with_model( p->raw_model );
220    p->string_chunk = g_string_chunk_new( 2048 );
221    g_object_unref( p->raw_model );
222}
223
224
225
226/***
227****  EMIT SIGNALS
228***/
229
230static inline void
231core_emit_blocklist_udpated( TrCore * core, int ruleCount )
232{
233    g_signal_emit( core, signals[BLOCKLIST_SIGNAL], 0, ruleCount );
234}
235
236static inline void
237core_emit_port_tested( TrCore * core, gboolean is_open )
238{
239    g_signal_emit( core, signals[PORT_SIGNAL], 0, is_open );
240}
241
242static inline void
243core_emit_err( TrCore * core, enum tr_core_err type, const char * msg )
244{
245    g_signal_emit( core, signals[ADD_ERROR_SIGNAL], 0, type, msg );
246}
247
248static inline void
249core_emit_busy( TrCore * core, gboolean is_busy )
250{
251    g_signal_emit( core, signals[BUSY_SIGNAL], 0, is_busy );
252}
253
254void
255gtr_core_pref_changed( TrCore * core, const char * key )
256{
257    g_signal_emit( core, signals[PREFS_SIGNAL], 0, key );
258}
259
260/***
261****
262***/
263
264static GtkTreeModel *
265core_raw_model( TrCore * core )
266{
267    return core_is_disposed( core ) ? NULL : core->priv->raw_model;
268}
269
270GtkTreeModel *
271gtr_core_model( TrCore * core )
272{
273    return core_is_disposed( core ) ? NULL : core->priv->sorted_model;
274}
275
276tr_session *
277gtr_core_session( TrCore * core )
278{
279    return core_is_disposed( core ) ? NULL : core->priv->session;
280}
281
282/***
283****  BUSY
284***/
285
286static bool
287core_is_busy( TrCore * core )
288{
289    return core->priv->busy_count > 0;
290}
291
292static void
293core_add_to_busy( TrCore * core, int addMe )
294{
295    const bool wasBusy = core_is_busy( core );
296
297    core->priv->busy_count += addMe;
298
299    if( wasBusy != core_is_busy( core ) )
300        core_emit_busy( core, core_is_busy( core ) );
301}
302
303static void core_inc_busy( TrCore * core ) { core_add_to_busy( core, 1 ); }
304static void core_dec_busy( TrCore * core ) { core_add_to_busy( core, -1 ); }
305
306/***
307****
308****  SORTING THE MODEL
309****
310***/
311
312static gboolean
313is_valid_eta( int t )
314{
315    return ( t != TR_ETA_NOT_AVAIL ) && ( t != TR_ETA_UNKNOWN );
316}
317
318static int
319compare_eta( int a, int b )
320{
321    const gboolean a_valid = is_valid_eta( a );
322    const gboolean b_valid = is_valid_eta( b );
323
324    if( !a_valid && !b_valid ) return 0;
325    if( !a_valid ) return -1;
326    if( !b_valid ) return 1;
327    return a < b ? 1 : -1;
328}
329
330static int
331compare_double( double a, double b )
332{
333    if( a < b ) return -1;
334    if( a > b ) return 1;
335    return 0;
336}
337
338static int
339compare_uint64( uint64_t a, uint64_t b )
340{
341    if( a < b ) return -1;
342    if( a > b ) return 1;
343    return 0;
344}
345
346static int
347compare_int( int a, int b )
348{
349    if( a < b ) return -1;
350    if( a > b ) return 1;
351    return 0;
352}
353
354static int
355compare_ratio( double a, double b )
356{
357    if( (int)a == TR_RATIO_INF && (int)b == TR_RATIO_INF ) return 0;
358    if( (int)a == TR_RATIO_INF ) return 1;
359    if( (int)b == TR_RATIO_INF ) return -1;
360    return compare_double( a, b );
361}
362
363static int
364compare_time( time_t a, time_t b )
365{
366    if( a < b ) return -1;
367    if( a > b ) return 1;
368    return 0;
369}
370
371static int
372compare_by_name( GtkTreeModel * m, GtkTreeIter * a, GtkTreeIter * b, gpointer user_data UNUSED )
373{
374    const char *ca, *cb;
375    gtk_tree_model_get( m, a, MC_NAME_COLLATED, &ca, -1 );
376    gtk_tree_model_get( m, b, MC_NAME_COLLATED, &cb, -1 );
377    return tr_strcmp0( ca, cb );
378}
379
380static int
381compare_by_queue( GtkTreeModel * m, GtkTreeIter * a, GtkTreeIter * b, gpointer user_data UNUSED )
382{
383    tr_torrent *ta, *tb;
384    const tr_stat *sa, *sb;
385
386    gtk_tree_model_get( m, a, MC_TORRENT, &ta, -1 );
387    sa = tr_torrentStatCached( ta );
388    gtk_tree_model_get( m, b, MC_TORRENT, &tb, -1 );
389    sb = tr_torrentStatCached( tb );
390
391    return sb->queuePosition - sa->queuePosition;
392}
393
394static int
395compare_by_ratio( GtkTreeModel* m, GtkTreeIter * a, GtkTreeIter * b, gpointer user_data )
396{
397    int ret = 0;
398    tr_torrent *ta, *tb;
399    const tr_stat *sa, *sb;
400
401    gtk_tree_model_get( m, a, MC_TORRENT, &ta, -1 );
402    sa = tr_torrentStatCached( ta );
403    gtk_tree_model_get( m, b, MC_TORRENT, &tb, -1 );
404    sb = tr_torrentStatCached( tb );
405
406    if( !ret ) ret = compare_ratio( sa->ratio, sb->ratio );
407    if( !ret ) ret = compare_by_queue( m, a, b, user_data );
408    return ret;
409}
410
411static int
412compare_by_activity( GtkTreeModel * m, GtkTreeIter * a, GtkTreeIter * b, gpointer user_data )
413{
414    int ret = 0;
415    tr_torrent *ta, *tb;
416    const tr_stat *sa, *sb;
417    double aUp, aDown, bUp, bDown;
418
419    gtk_tree_model_get( m, a, MC_SPEED_UP, &aUp,
420                              MC_SPEED_DOWN, &aDown,
421                              MC_TORRENT, &ta,
422                              -1 );
423    gtk_tree_model_get( m, b, MC_SPEED_UP, &bUp,
424                              MC_SPEED_DOWN, &bDown,
425                              MC_TORRENT, &tb,
426                              -1 );
427    sa = tr_torrentStatCached( ta );
428    sb = tr_torrentStatCached( tb );
429
430    if( !ret ) ret = compare_double( aUp+aDown, bUp+bDown );
431    if( !ret ) ret = compare_uint64( sa->uploadedEver, sb->uploadedEver );
432    if( !ret ) ret = compare_by_queue( m, a, b, user_data );
433    return ret;
434}
435
436static int
437compare_by_age( GtkTreeModel * m, GtkTreeIter * a, GtkTreeIter * b, gpointer u )
438{
439    int ret = 0;
440    tr_torrent *ta, *tb;
441
442    gtk_tree_model_get( m, a, MC_TORRENT, &ta, -1 );
443    gtk_tree_model_get( m, b, MC_TORRENT, &tb, -1 );
444
445    if( !ret ) ret = compare_time( tr_torrentStatCached( ta )->addedDate,
446                                   tr_torrentStatCached( tb )->addedDate );
447    if( !ret ) ret = compare_by_name( m, a, b, u );
448    return ret;
449}
450
451static int
452compare_by_size( GtkTreeModel * m, GtkTreeIter * a, GtkTreeIter * b, gpointer u )
453{
454    int ret = 0;
455    tr_torrent *t;
456    const tr_info *ia, *ib;
457
458    gtk_tree_model_get( m, a, MC_TORRENT, &t, -1 );
459    ia = tr_torrentInfo( t );
460    gtk_tree_model_get( m, b, MC_TORRENT, &t, -1 );
461    ib = tr_torrentInfo( t );
462
463    if( !ret ) ret = compare_uint64( ia->totalSize, ib->totalSize );
464    if( !ret ) ret = compare_by_name( m, a, b, u );
465    return ret;
466}
467
468static int
469compare_by_progress( GtkTreeModel * m, GtkTreeIter * a, GtkTreeIter * b, gpointer u )
470{
471    int ret = 0;
472    tr_torrent * t;
473    const tr_stat *sa, *sb;
474
475    gtk_tree_model_get( m, a, MC_TORRENT, &t, -1 );
476    sa = tr_torrentStatCached( t );
477    gtk_tree_model_get( m, b, MC_TORRENT, &t, -1 );
478    sb = tr_torrentStatCached( t );
479
480    if( !ret ) ret = compare_double( sa->percentComplete, sb->percentComplete );
481    if( !ret ) ret = compare_double( sa->seedRatioPercentDone, sb->seedRatioPercentDone );
482    if( !ret ) ret = compare_by_ratio( m, a, b, u );
483    return ret;
484}
485
486static int
487compare_by_eta( GtkTreeModel * m, GtkTreeIter  * a, GtkTreeIter  * b, gpointer u )
488{
489    int ret = 0;
490    tr_torrent *ta, *tb;
491
492    gtk_tree_model_get( m, a, MC_TORRENT, &ta, -1 );
493    gtk_tree_model_get( m, b, MC_TORRENT, &tb, -1 );
494
495    if( !ret ) ret = compare_eta( tr_torrentStatCached( ta )->eta,
496                                  tr_torrentStatCached( tb )->eta );
497    if( !ret ) ret = compare_by_name( m, a, b, u );
498    return ret;
499}
500
501static int
502compare_by_state( GtkTreeModel * m, GtkTreeIter * a, GtkTreeIter * b, gpointer u )
503{
504    int ret = 0;
505    int sa, sb;
506    tr_torrent *ta, *tb;
507
508    gtk_tree_model_get( m, a, MC_ACTIVITY, &sa, MC_TORRENT, &ta, -1 );
509    gtk_tree_model_get( m, b, MC_ACTIVITY, &sb, MC_TORRENT, &tb, -1 );
510
511    if( !ret ) ret = compare_int( sa, sb );
512    if( !ret ) ret = compare_by_queue( m, a, b, u );
513    return ret;
514}
515
516static void
517core_set_sort_mode( TrCore * core, const char * mode, gboolean is_reversed )
518{
519    const int col = MC_TORRENT;
520    GtkTreeIterCompareFunc sort_func;
521    GtkSortType type = is_reversed ? GTK_SORT_ASCENDING : GTK_SORT_DESCENDING;
522    GtkTreeSortable * sortable = GTK_TREE_SORTABLE( gtr_core_model( core ) );
523
524    if( !strcmp( mode, "sort-by-activity" ) )
525        sort_func = compare_by_activity;
526    else if( !strcmp( mode, "sort-by-age" ) )
527        sort_func = compare_by_age;
528    else if( !strcmp( mode, "sort-by-progress" ) )
529        sort_func = compare_by_progress;
530    else if( !strcmp( mode, "sort-by-queue" ) )
531        sort_func = compare_by_queue;
532    else if( !strcmp( mode, "sort-by-time-left" ) )
533        sort_func = compare_by_eta;
534    else if( !strcmp( mode, "sort-by-ratio" ) )
535        sort_func = compare_by_ratio;
536    else if( !strcmp( mode, "sort-by-state" ) )
537        sort_func = compare_by_state;
538    else if( !strcmp( mode, "sort-by-size" ) )
539        sort_func = compare_by_size;
540    else {
541        sort_func = compare_by_name;
542        type = is_reversed ? GTK_SORT_DESCENDING : GTK_SORT_ASCENDING;
543    }
544
545    gtk_tree_sortable_set_sort_func( sortable, col, sort_func, NULL, NULL );
546    gtk_tree_sortable_set_sort_column_id( sortable, col, type );
547}
548
549/***
550****
551****  WATCHDIR
552****
553***/
554
555static time_t
556get_file_mtime( GFile * file )
557{
558    time_t mtime;
559    GFileInfo * info = g_file_query_info( file, G_FILE_ATTRIBUTE_TIME_MODIFIED, 0, NULL, NULL );
560    mtime = g_file_info_get_attribute_uint64( info, G_FILE_ATTRIBUTE_TIME_MODIFIED );
561    g_object_unref( G_OBJECT( info ) );
562    return mtime;
563}
564
565static void
566rename_torrent_and_unref_file( GFile * file )
567{
568    GFileInfo * info = g_file_query_info( file, G_FILE_ATTRIBUTE_STANDARD_EDIT_NAME, 0, NULL, NULL );
569    const char * old_name = g_file_info_get_attribute_string( info, G_FILE_ATTRIBUTE_STANDARD_EDIT_NAME );
570    char * new_name = g_strdup_printf( "%s.added", old_name );
571    GFile * new_file = g_file_set_display_name( file, new_name, NULL, NULL );
572    g_object_unref( G_OBJECT( new_file ) );
573    g_free( new_name );
574    g_object_unref( G_OBJECT( info ) );
575    g_object_unref( G_OBJECT( file ) );
576}
577
578static gboolean
579core_watchdir_idle( gpointer gcore )
580{
581    GSList * l;
582    GSList * changing = NULL;
583    GSList * unchanging = NULL;
584    TrCore * core = TR_CORE( gcore );
585    const time_t now = tr_time( );
586    struct TrCorePrivate * p = core->priv;
587
588    /* separate the files into two lists: changing and unchanging */
589    for( l=p->monitor_files; l!=NULL; l=l->next )
590    {
591        GFile * file = l->data;
592        const time_t mtime = get_file_mtime( file );
593        if( mtime + 2 >= now )
594            changing = g_slist_prepend( changing, file );
595        else
596            unchanging = g_slist_prepend( unchanging, file );
597    }
598
599    /* add the files that have stopped changing */
600    if( unchanging != NULL )
601    {
602        const gboolean do_start = gtr_pref_flag_get( TR_PREFS_KEY_START );
603        const gboolean do_prompt = gtr_pref_flag_get( PREF_KEY_OPTIONS_PROMPT );
604
605        core->priv->adding_from_watch_dir = TRUE;
606        gtr_core_add_files( core, unchanging, do_start, do_prompt, TRUE );
607        g_slist_foreach( unchanging, (GFunc)rename_torrent_and_unref_file, NULL );
608        g_slist_free( unchanging );
609        core->priv->adding_from_watch_dir = FALSE;
610    }
611
612    /* keep monitoring the ones that are still changing */
613    g_slist_free( p->monitor_files );
614    p->monitor_files = changing;
615
616    /* if monitor_files is nonempty, keep checking every second */
617    if( core->priv->monitor_files )
618        return TRUE;
619    core->priv->monitor_idle_tag = 0;
620    return FALSE;
621
622}
623
624/* If this file is a torrent, add it to our list */
625static void
626core_watchdir_monitor_file( TrCore * core, GFile * file )
627{
628    char * filename = g_file_get_path( file );
629    const gboolean is_torrent = g_str_has_suffix( filename, ".torrent" );
630
631    if( is_torrent )
632    {
633        GSList * l;
634        struct TrCorePrivate * p = core->priv;
635
636        /* if we're not already watching this file, start watching it now */
637        for( l=p->monitor_files; l!=NULL; l=l->next )
638            if( g_file_equal( file, l->data ) )
639                break;
640        if( l == NULL ) {
641            g_object_ref( file );
642            p->monitor_files = g_slist_prepend( p->monitor_files, file );
643            if( p->monitor_idle_tag == 0 )
644                p->monitor_idle_tag = gdk_threads_add_timeout_seconds( 1, core_watchdir_idle, core );
645        }
646    }
647
648    g_free( filename );
649}
650
651/* GFileMonitor noticed a file was created */
652static void
653on_file_changed_in_watchdir( GFileMonitor       * monitor UNUSED,
654                             GFile              * file,
655                             GFile              * other_type UNUSED,
656                             GFileMonitorEvent    event_type,
657                             gpointer             core )
658{
659    if( event_type == G_FILE_MONITOR_EVENT_CREATED )
660        core_watchdir_monitor_file( core, file );
661}
662
663/* walk through the pre-existing files in the watchdir */
664static void
665core_watchdir_scan( TrCore * core )
666{
667    const char * dirname = gtr_pref_string_get( PREF_KEY_DIR_WATCH );
668    GDir * dir = g_dir_open( dirname, 0, NULL );
669
670    if( dir != NULL )
671    {
672        const char * name;
673        while(( name = g_dir_read_name( dir )))
674        {
675            char * filename = g_build_filename( dirname, name, NULL );
676            GFile * file = g_file_new_for_path( filename );
677            core_watchdir_monitor_file( core, file );
678            g_object_unref( file );
679            g_free( filename );
680        }
681
682        g_dir_close( dir );
683    }
684}
685
686static void
687core_watchdir_update( TrCore * core )
688{
689    const gboolean is_enabled = gtr_pref_flag_get( PREF_KEY_DIR_WATCH_ENABLED );
690    GFile * dir = g_file_new_for_path( gtr_pref_string_get( PREF_KEY_DIR_WATCH ) );
691    struct TrCorePrivate * p = core->priv;
692
693    if( p->monitor && ( !is_enabled || !g_file_equal( dir, p->monitor_dir ) ) )
694    {
695        g_signal_handler_disconnect( p->monitor, p->monitor_tag );
696        g_file_monitor_cancel( p->monitor );
697        g_object_unref( p->monitor );
698        g_object_unref( p->monitor_dir );
699
700        p->monitor_dir = NULL;
701        p->monitor = NULL;
702        p->monitor_tag = 0;
703    }
704
705    if( is_enabled && !p->monitor )
706    {
707        GFileMonitor * m = g_file_monitor_directory( dir, 0, NULL, NULL );
708        core_watchdir_scan( core );
709
710        g_object_ref( dir );
711        p->monitor = m;
712        p->monitor_dir = dir;
713        p->monitor_tag = g_signal_connect( m, "changed",
714                                           G_CALLBACK( on_file_changed_in_watchdir ), core );
715    }
716
717    g_object_unref( dir );
718}
719
720/***
721****
722***/
723
724static void
725on_pref_changed( TrCore * core, const char * key, gpointer data UNUSED )
726{
727    if( !strcmp( key, PREF_KEY_SORT_MODE )
728      || !strcmp( key, PREF_KEY_SORT_REVERSED ) )
729    {
730        const char * mode = gtr_pref_string_get( PREF_KEY_SORT_MODE );
731        gboolean is_reversed = gtr_pref_flag_get( PREF_KEY_SORT_REVERSED );
732        core_set_sort_mode( core, mode, is_reversed );
733    }
734    else if( !strcmp( key, TR_PREFS_KEY_PEER_LIMIT_GLOBAL ) )
735    {
736        tr_sessionSetPeerLimit( gtr_core_session( core ),
737                                gtr_pref_int_get( key ) );
738    }
739    else if( !strcmp( key, TR_PREFS_KEY_PEER_LIMIT_TORRENT ) )
740    {
741        tr_sessionSetPeerLimitPerTorrent( gtr_core_session( core ),
742                                          gtr_pref_int_get( key ) );
743    }
744    else if( !strcmp( key, PREF_KEY_INHIBIT_HIBERNATION ) )
745    {
746        core_maybe_inhibit_hibernation( core );
747    }
748    else if( !strcmp( key, PREF_KEY_DIR_WATCH )
749           || !strcmp( key, PREF_KEY_DIR_WATCH_ENABLED ) )
750    {
751        core_watchdir_update( core );
752    }
753}
754
755/**
756***
757**/
758
759TrCore *
760gtr_core_new( tr_session * session )
761{
762    TrCore * core = TR_CORE( g_object_new( TR_CORE_TYPE, NULL ) );
763
764    core->priv->session = session;
765
766    /* init from prefs & listen to pref changes */
767    on_pref_changed( core, PREF_KEY_SORT_MODE, NULL );
768    on_pref_changed( core, PREF_KEY_SORT_REVERSED, NULL );
769    on_pref_changed( core, PREF_KEY_DIR_WATCH_ENABLED, NULL );
770    on_pref_changed( core, TR_PREFS_KEY_PEER_LIMIT_GLOBAL, NULL );
771    on_pref_changed( core, PREF_KEY_INHIBIT_HIBERNATION, NULL );
772    g_signal_connect( core, "prefs-changed", G_CALLBACK( on_pref_changed ), NULL );
773
774    return core;
775}
776
777void
778gtr_core_close( TrCore * core )
779{
780    tr_session * session = gtr_core_session( core );
781
782    if( session )
783    {
784        core->priv->session = NULL;
785        gtr_pref_save( session );
786        tr_sessionClose( session );
787    }
788}
789
790/***
791****  COMPLETENESS CALLBACK
792***/
793
794struct notify_callback_data
795{
796    TrCore * core;
797    int torrent_id;
798};
799
800static gboolean
801on_torrent_completeness_changed_idle( gpointer gdata )
802{
803    struct notify_callback_data * data = gdata;
804    gtr_notify_torrent_completed( data->core, data->torrent_id );
805    g_object_unref( G_OBJECT( data->core ) );
806    g_free( data );
807    return FALSE;
808}
809
810/* this is called in the libtransmission thread, *NOT* the GTK+ thread,
811   so delegate to the GTK+ thread before calling notify's dbus code... */
812static void
813on_torrent_completeness_changed( tr_torrent       * tor,
814                                 tr_completeness    completeness,
815                                 bool               was_running,
816                                 void             * gcore )
817{
818    if( was_running && ( completeness != TR_LEECH ) && ( tr_torrentStat( tor )->sizeWhenDone != 0 ) )
819    {
820        struct notify_callback_data * data = g_new( struct notify_callback_data, 1 );
821        data->core = gcore;
822        data->torrent_id = tr_torrentId( tor );
823        g_object_ref( G_OBJECT( data->core ) );
824        gdk_threads_add_idle( on_torrent_completeness_changed_idle, data );
825    }
826}
827
828/***
829****  METADATA CALLBACK
830***/
831
832static const char*
833get_collated_name( TrCore * core, const tr_torrent * tor )
834{
835    char buf[2048];
836    const char * name = tr_torrentName( tor );
837    char * down = g_utf8_strdown( name ? name : "", -1 );
838    const tr_info * inf = tr_torrentInfo( tor );
839    g_snprintf( buf, sizeof( buf ), "%s\t%s", down, inf->hashString );
840    g_free( down );
841    return g_string_chunk_insert_const( core->priv->string_chunk, buf );
842}
843
844struct metadata_callback_data
845{
846    TrCore * core;
847    int torrent_id;
848};
849
850static gboolean
851find_row_from_torrent_id( GtkTreeModel * model, int id, GtkTreeIter * setme )
852{
853    GtkTreeIter iter;
854    gboolean match = FALSE;
855
856    if( gtk_tree_model_iter_children( model, &iter, NULL ) ) do
857    {
858        int row_id;
859        gtk_tree_model_get( model, &iter, MC_TORRENT_ID, &row_id, -1 );
860        match = id == row_id;
861    }
862    while( !match && gtk_tree_model_iter_next( model, &iter ) );
863
864    if( match )
865        *setme = iter;
866
867    return match;
868}
869
870static gboolean
871on_torrent_metadata_changed_idle( gpointer gdata )
872{
873    struct notify_callback_data * data = gdata;
874    tr_session * session = gtr_core_session( data->core );
875    tr_torrent * tor = tr_torrentFindFromId( session, data->torrent_id );
876
877    /* update the torrent's collated name */
878    if( tor != NULL ) {
879        GtkTreeIter iter;
880        GtkTreeModel * model = core_raw_model( data->core );
881        if( find_row_from_torrent_id( model, data->torrent_id, &iter ) ) {
882            const char * collated = get_collated_name( data->core, tor );
883            GtkListStore * store = GTK_LIST_STORE( model );
884            gtk_list_store_set( store, &iter, MC_NAME_COLLATED, collated, -1 );
885        }
886    }
887
888    /* cleanup */
889    g_object_unref( G_OBJECT( data->core ) );
890    g_free( data );
891    return FALSE;
892}
893
894/* this is called in the libtransmission thread, *NOT* the GTK+ thread,
895   so delegate to the GTK+ thread before changing our list store... */
896static void
897on_torrent_metadata_changed( tr_torrent * tor, void * gcore )
898{
899    struct notify_callback_data * data = g_new( struct notify_callback_data, 1 );
900    data->core = gcore;
901    data->torrent_id = tr_torrentId( tor );
902    g_object_ref( G_OBJECT( data->core ) );
903    gdk_threads_add_idle( on_torrent_metadata_changed_idle, data );
904}
905
906/***
907****
908****  ADDING TORRENTS
909****
910***/
911
912static unsigned int
913build_torrent_trackers_hash( tr_torrent * tor )
914{
915    int i;
916    const char * pch;
917    uint64_t hash = 0;
918    const tr_info * const inf = tr_torrentInfo( tor );
919
920    for( i=0; i<inf->trackerCount; ++i )
921        for( pch=inf->trackers[i].announce; *pch; ++pch )
922            hash = (hash<<4) ^ (hash>>28) ^ *pch;
923
924    return hash;
925}
926
927static gboolean
928is_torrent_active( const tr_stat * st )
929{
930    return ( st->peersSendingToUs > 0 )
931        || ( st->peersGettingFromUs > 0 )
932        || ( st->activity == TR_STATUS_CHECK );
933}
934
935void
936gtr_core_add_torrent( TrCore * core, tr_torrent * tor, gboolean do_notify )
937{
938    if( tor != NULL )
939    {
940        GtkTreeIter unused;
941        const tr_stat * st = tr_torrentStat( tor );
942        const char * collated = get_collated_name( core, tor );
943        const unsigned int trackers_hash = build_torrent_trackers_hash( tor );
944        GtkListStore * store = GTK_LIST_STORE( core_raw_model( core ) );
945
946        gtk_list_store_insert_with_values( store, &unused, 0,
947            MC_NAME_COLLATED,     collated,
948            MC_TORRENT,           tor,
949            MC_TORRENT_ID,        tr_torrentId( tor ),
950            MC_SPEED_UP,          st->pieceUploadSpeed_KBps,
951            MC_SPEED_DOWN,        st->pieceDownloadSpeed_KBps,
952            MC_RECHECK_PROGRESS,  st->recheckProgress,
953            MC_ACTIVE,            is_torrent_active( st ),
954            MC_ACTIVITY,          st->activity,
955            MC_FINISHED,          st->finished,
956            MC_PRIORITY,          tr_torrentGetPriority( tor ),
957            MC_QUEUE_POSITION,    st->queuePosition,
958            MC_TRACKERS,          trackers_hash,
959            -1 );
960
961        if( do_notify )
962            gtr_notify_torrent_added( tr_torrentName( tor ) );
963
964        tr_torrentSetMetadataCallback( tor, on_torrent_metadata_changed, core );
965        tr_torrentSetCompletenessCallback( tor, on_torrent_completeness_changed, core );
966    }
967}
968
969static tr_torrent *
970core_create_new_torrent( TrCore * core, tr_ctor * ctor )
971{
972    int errcode = 0;
973    tr_torrent * tor;
974    bool do_trash = false;
975    tr_session * session = gtr_core_session( core );
976
977    /* let the gtk client handle the removal, since libT
978     * doesn't have any concept of the glib trash API */
979    tr_ctorGetDeleteSource( ctor, &do_trash );
980    tr_ctorSetDeleteSource( ctor, FALSE );
981    tor = tr_torrentNew( ctor, &errcode );
982
983    if( tor && do_trash )
984    {
985        const char * config = tr_sessionGetConfigDir( session );
986        const char * source = tr_ctorGetSourceFile( ctor );
987        const int is_internal = source && ( strstr( source, config ) == source );
988
989        /* #1294: don't delete the .torrent file if it's our internal copy */
990        if( !is_internal )
991            gtr_file_trash_or_remove( source );
992    }
993
994    return tor;
995}
996
997static int
998core_add_ctor( TrCore * core, tr_ctor * ctor,
999               gboolean do_prompt, gboolean do_notify )
1000{
1001    tr_info inf;
1002    int err = tr_torrentParse( ctor, &inf );
1003
1004    switch( err )
1005    {
1006        case TR_PARSE_ERR:
1007            break;
1008
1009        case TR_PARSE_DUPLICATE:
1010            /* don't complain about .torrent files in the watch directory
1011             * that have already been added... that gets annoying and we
1012             * don't want to be nagging users to clean up their watch dirs */
1013            if( !tr_ctorGetSourceFile(ctor) || !core->priv->adding_from_watch_dir )
1014                core_emit_err( core, err, inf.name );
1015            tr_metainfoFree( &inf );
1016            tr_ctorFree( ctor );
1017            break;
1018
1019        default:
1020            if( do_prompt )
1021                g_signal_emit( core, signals[ADD_PROMPT_SIGNAL], 0, ctor );
1022            else {
1023                gtr_core_add_torrent( core, core_create_new_torrent( core, ctor ), do_notify );
1024                tr_ctorFree( ctor );
1025            }
1026            tr_metainfoFree( &inf );
1027            break;
1028    }
1029
1030    return err;
1031}
1032
1033static void
1034core_apply_defaults( tr_ctor * ctor )
1035{
1036    if( tr_ctorGetPaused( ctor, TR_FORCE, NULL ) )
1037        tr_ctorSetPaused( ctor, TR_FORCE, !gtr_pref_flag_get( TR_PREFS_KEY_START ) );
1038
1039    if( tr_ctorGetDeleteSource( ctor, NULL ) )
1040        tr_ctorSetDeleteSource( ctor,
1041                               gtr_pref_flag_get( TR_PREFS_KEY_TRASH_ORIGINAL ) );
1042
1043    if( tr_ctorGetPeerLimit( ctor, TR_FORCE, NULL ) )
1044        tr_ctorSetPeerLimit( ctor, TR_FORCE,
1045                             gtr_pref_int_get( TR_PREFS_KEY_PEER_LIMIT_TORRENT ) );
1046
1047    if( tr_ctorGetDownloadDir( ctor, TR_FORCE, NULL ) )
1048        tr_ctorSetDownloadDir( ctor, TR_FORCE,
1049                               gtr_pref_string_get( TR_PREFS_KEY_DOWNLOAD_DIR ) );
1050}
1051
1052void
1053gtr_core_add_ctor( TrCore * core, tr_ctor * ctor )
1054{
1055    const gboolean do_notify = FALSE;
1056    const gboolean do_prompt = gtr_pref_flag_get( PREF_KEY_OPTIONS_PROMPT );
1057    core_apply_defaults( ctor );
1058    core_add_ctor( core, ctor, do_prompt, do_notify );
1059}
1060
1061/***
1062****
1063***/
1064
1065struct add_from_url_data
1066{
1067    TrCore * core;
1068    tr_ctor * ctor;
1069    bool do_prompt;
1070    bool do_notify;
1071};
1072
1073static void
1074add_file_async_callback( GObject * file, GAsyncResult * result, gpointer gdata )
1075{
1076    gsize length;
1077    char * contents;
1078    GError * error = NULL;
1079    struct add_from_url_data * data = gdata;
1080
1081    if( !g_file_load_contents_finish( G_FILE( file ), result, &contents, &length, NULL, &error ) )
1082    {
1083        g_message( _( "Couldn't read \"%s\": %s" ), g_file_get_parse_name( G_FILE( file ) ), error->message );
1084        g_error_free( error );
1085    }
1086    else if( !tr_ctorSetMetainfo( data->ctor, (const uint8_t*)contents, length ) )
1087    {
1088        core_add_ctor( data->core, data->ctor, data->do_prompt, data->do_notify );
1089    }
1090    else
1091    {
1092        tr_ctorFree( data->ctor );
1093    }
1094
1095    core_dec_busy( data->core );
1096    g_free( data );
1097}
1098
1099static bool
1100add_file( TrCore      * core,
1101          GFile       * file,
1102          gboolean      do_start,
1103          gboolean      do_prompt,
1104          gboolean      do_notify )
1105{
1106    bool handled = false;
1107    tr_session * session = gtr_core_session( core );
1108
1109    if( session != NULL )
1110    {
1111        tr_ctor * ctor;
1112        bool tried = false;
1113        bool loaded = false;
1114
1115        ctor = tr_ctorNew( session );
1116        core_apply_defaults( ctor );
1117        tr_ctorSetPaused( ctor, TR_FORCE, !do_start );
1118
1119        /* local files... */
1120        if( !tried ) {
1121            char * str = g_file_get_path( file );
1122            if(( tried = g_file_test( str, G_FILE_TEST_EXISTS )))
1123               loaded = !tr_ctorSetMetainfoFromFile( ctor, str );
1124            g_free( str );
1125        }
1126
1127        /* magnet links... */
1128        if( !tried && g_file_has_uri_scheme( file, "magnet" ) ) {
1129            /* GFile mangles the original string with /// so we have to un-mangle */
1130            char * str = g_file_get_parse_name( file );
1131            char * magnet = g_strdup_printf( "magnet:%s", strchr( str, '?' ) );
1132            tried = true;
1133            loaded = !tr_ctorSetMetainfoFromMagnetLink( ctor, magnet );
1134            g_free( magnet );
1135            g_free( str );
1136        }
1137
1138        /* hashcodes that we can turn into magnet links... */
1139        if( !tried ) {
1140            char * str = g_file_get_basename( file );
1141            if( gtr_is_hex_hashcode( str ) ) {
1142                char * magnet = g_strdup_printf( "magnet:?xt=urn:btih:%s", str );
1143                tried = true;
1144                loaded = !tr_ctorSetMetainfoFromMagnetLink( ctor, magnet );
1145                g_free( magnet );
1146            }
1147            g_free( str );
1148        }
1149
1150        /* if we were able to load the metainfo, add the torrent */
1151        if( loaded )
1152        {
1153            handled = true;
1154            core_add_ctor( core, ctor, do_prompt, do_notify );
1155        }
1156        else if( g_file_has_uri_scheme( file, "http" ) ||
1157                 g_file_has_uri_scheme( file, "https" ) ||
1158                 g_file_has_uri_scheme( file, "ftp" ) )
1159        {
1160            struct add_from_url_data * data;
1161
1162            data = g_new0( struct add_from_url_data, 1 );
1163            data->core = core;
1164            data->ctor = ctor;
1165            data->do_prompt = do_prompt;
1166            data->do_notify = do_notify;
1167
1168            handled = true;
1169            core_inc_busy( core );
1170            g_file_load_contents_async( file, NULL, add_file_async_callback, data );
1171        }
1172        else
1173        {
1174            tr_ctorFree( ctor );
1175            g_message( _( "Skipping unknown torrent \"%s\"" ), g_file_get_parse_name( file ) );
1176        }
1177    }
1178
1179    return handled;
1180}
1181
1182bool
1183gtr_core_add_from_url( TrCore * core, const char * uri )
1184{
1185    bool handled;
1186    const bool do_start = gtr_pref_flag_get( TR_PREFS_KEY_START );
1187    const bool do_prompt = gtr_pref_flag_get( PREF_KEY_OPTIONS_PROMPT );
1188    const bool do_notify = false;
1189
1190    GFile * file = g_file_new_for_uri( uri );
1191    handled = add_file( core, file, do_start, do_prompt, do_notify );
1192    g_object_unref( file );
1193    gtr_core_torrents_added( core );
1194
1195    return handled;
1196}
1197
1198void
1199gtr_core_add_files( TrCore     * core,
1200                    GSList     * files,
1201                    gboolean     do_start,
1202                    gboolean     do_prompt,
1203                    gboolean     do_notify )
1204{
1205    GSList * l;
1206
1207    for( l=files; l!=NULL; l=l->next )
1208        add_file( core, l->data, do_start, do_prompt, do_notify );
1209
1210    gtr_core_torrents_added( core );
1211}
1212
1213void
1214gtr_core_torrents_added( TrCore * self )
1215{
1216    gtr_core_update( self );
1217    core_emit_err( self, TR_CORE_ERR_NO_MORE_TORRENTS, NULL );
1218}
1219
1220void
1221gtr_core_remove_torrent( TrCore * core, int id, gboolean delete_local_data )
1222{
1223    tr_torrent * tor = gtr_core_find_torrent( core, id );
1224
1225    if( tor != NULL )
1226    {
1227        /* remove from the gui */
1228        GtkTreeIter iter;
1229        GtkTreeModel * model = core_raw_model( core );
1230        if( find_row_from_torrent_id( model, id, &iter ) )
1231            gtk_list_store_remove( GTK_LIST_STORE( model ), &iter );
1232
1233        /* remove the torrent */
1234        tr_torrentRemove( tor, delete_local_data, gtr_file_trash_or_remove );
1235    }
1236}
1237
1238void
1239gtr_core_load( TrCore * self, gboolean forcePaused )
1240{
1241    int i;
1242    tr_ctor * ctor;
1243    tr_torrent ** torrents;
1244    int count = 0;
1245
1246    ctor = tr_ctorNew( gtr_core_session( self ) );
1247    if( forcePaused )
1248        tr_ctorSetPaused( ctor, TR_FORCE, TRUE );
1249    tr_ctorSetPeerLimit( ctor, TR_FALLBACK,
1250                         gtr_pref_int_get( TR_PREFS_KEY_PEER_LIMIT_TORRENT ) );
1251
1252    torrents = tr_sessionLoadTorrents ( gtr_core_session( self ), ctor, &count );
1253    for( i=0; i<count; ++i )
1254        gtr_core_add_torrent( self, torrents[i], FALSE );
1255
1256    tr_free( torrents );
1257    tr_ctorFree( ctor );
1258}
1259
1260void
1261gtr_core_clear( TrCore * self )
1262{
1263    gtk_list_store_clear( GTK_LIST_STORE( core_raw_model( self ) ) );
1264}
1265
1266/***
1267****
1268***/
1269
1270static int
1271gtr_compare_double( const double a, const double b, int decimal_places )
1272{
1273    const int64_t ia = (int64_t)(a * pow( 10, decimal_places ) );
1274    const int64_t ib = (int64_t)(b * pow( 10, decimal_places ) );
1275    if( ia < ib ) return -1;
1276    if( ia > ib ) return  1;
1277    return 0;
1278}
1279
1280static void
1281update_foreach( GtkTreeModel * model, GtkTreeIter * iter )
1282{
1283    int oldActivity, newActivity;
1284    int oldActivePeerCount, newActivePeerCount;
1285    int oldError, newError;
1286    bool oldFinished, newFinished;
1287    int oldQueuePosition, newQueuePosition;
1288    tr_priority_t oldPriority, newPriority;
1289    unsigned int oldTrackers, newTrackers;
1290    double oldUpSpeed, newUpSpeed;
1291    double oldDownSpeed, newDownSpeed;
1292    double oldRecheckProgress, newRecheckProgress;
1293    gboolean oldActive, newActive;
1294    const tr_stat * st;
1295    tr_torrent * tor;
1296
1297    /* get the old states */
1298    gtk_tree_model_get( model, iter,
1299                        MC_TORRENT,  &tor,
1300                        MC_ACTIVE, &oldActive,
1301                        MC_ACTIVE_PEER_COUNT, &oldActivePeerCount,
1302                        MC_ERROR, &oldError,
1303                        MC_ACTIVITY, &oldActivity,
1304                        MC_FINISHED, &oldFinished,
1305                        MC_PRIORITY, &oldPriority,
1306                        MC_QUEUE_POSITION, &oldQueuePosition,
1307                        MC_TRACKERS, &oldTrackers,
1308                        MC_SPEED_UP, &oldUpSpeed,
1309                        MC_RECHECK_PROGRESS, &oldRecheckProgress,
1310                        MC_SPEED_DOWN, &oldDownSpeed,
1311                        -1 );
1312
1313    /* get the new states */
1314    st = tr_torrentStat( tor );
1315    newActive = is_torrent_active( st );
1316    newActivity = st->activity;
1317    newFinished = st->finished;
1318    newPriority = tr_torrentGetPriority( tor );
1319    newQueuePosition = st->queuePosition;
1320    newTrackers = build_torrent_trackers_hash( tor );
1321    newUpSpeed = st->pieceUploadSpeed_KBps;
1322    newDownSpeed = st->pieceDownloadSpeed_KBps;
1323    newRecheckProgress = st->recheckProgress;
1324    newActivePeerCount = st->peersSendingToUs + st->peersGettingFromUs + st->webseedsSendingToUs;
1325    newError = st->error;
1326
1327    /* updating the model triggers off resort/refresh,
1328       so don't do it unless something's actually changed... */
1329    if( ( newActive != oldActive )
1330        || ( newActivity  != oldActivity )
1331        || ( newFinished != oldFinished )
1332        || ( newPriority != oldPriority )
1333        || ( newQueuePosition != oldQueuePosition )
1334        || ( newError != oldError )
1335        || ( newActivePeerCount != oldActivePeerCount )
1336        || ( newTrackers != oldTrackers )
1337        || gtr_compare_double( newUpSpeed, oldUpSpeed, 2 )
1338        || gtr_compare_double( newDownSpeed, oldDownSpeed, 2 )
1339        || gtr_compare_double( newRecheckProgress, oldRecheckProgress, 2 ) )
1340    {
1341        gtk_list_store_set( GTK_LIST_STORE( model ), iter,
1342                            MC_ACTIVE, newActive,
1343                            MC_ACTIVE_PEER_COUNT, newActivePeerCount,
1344                            MC_ERROR, newError,
1345                            MC_ACTIVITY, newActivity,
1346                            MC_FINISHED, newFinished,
1347                            MC_PRIORITY, newPriority,
1348                            MC_QUEUE_POSITION, newQueuePosition,
1349                            MC_TRACKERS, newTrackers,
1350                            MC_SPEED_UP, newUpSpeed,
1351                            MC_SPEED_DOWN, newDownSpeed,
1352                            MC_RECHECK_PROGRESS, newRecheckProgress,
1353                            -1 );
1354    }
1355}
1356
1357void
1358gtr_core_update( TrCore * core )
1359{
1360    GtkTreeIter iter;
1361    GtkTreeModel * model;
1362
1363    /* update the model */
1364    model = core_raw_model( core );
1365    if( gtk_tree_model_iter_nth_child( model, &iter, NULL, 0 ) ) do
1366        update_foreach( model, &iter );
1367    while( gtk_tree_model_iter_next( model, &iter ) );
1368
1369    /* update hibernation */
1370    core_maybe_inhibit_hibernation( core );
1371}
1372
1373/**
1374***  Hibernate
1375**/
1376
1377#define SESSION_MANAGER_SERVICE_NAME  "org.gnome.SessionManager"
1378#define SESSION_MANAGER_INTERFACE     "org.gnome.SessionManager"
1379#define SESSION_MANAGER_OBJECT_PATH   "/org/gnome/SessionManager"
1380
1381static gboolean
1382gtr_inhibit_hibernation( guint * cookie )
1383{
1384    gboolean success;
1385    GVariant * response;
1386    GDBusConnection * connection;
1387    GError * err = NULL;
1388    const char * application = "Transmission BitTorrent Client";
1389    const char * reason = "BitTorrent Activity";
1390    const int toplevel_xid = 0;
1391    const int flags = 4; /* Inhibit suspending the session or computer */
1392
1393    connection = g_bus_get_sync( G_BUS_TYPE_SESSION, NULL, &err );
1394
1395    response = g_dbus_connection_call_sync( connection,
1396                                            SESSION_MANAGER_SERVICE_NAME,
1397                                            SESSION_MANAGER_OBJECT_PATH,
1398                                            SESSION_MANAGER_INTERFACE,
1399                                            "Inhibit",
1400                                            g_variant_new( "(susu)", application, toplevel_xid, reason, flags ),
1401                                            NULL, G_DBUS_CALL_FLAGS_NONE,
1402                                            1000, NULL, &err );
1403
1404    if( response != NULL )
1405        *cookie = g_variant_get_uint32( g_variant_get_child_value( response, 0 ) );
1406
1407    success = ( response != NULL ) && ( err == NULL );
1408
1409    /* logging */
1410    if( success )
1411        tr_inf( "%s", _( "Inhibiting desktop hibernation" ) );
1412    else {
1413        tr_err( _( "Couldn't inhibit desktop hibernation: %s" ), err->message );
1414        g_error_free( err );
1415    }
1416
1417    /* cleanup */
1418    if( response != NULL )
1419        g_variant_unref( response );
1420    if( connection != NULL )
1421        g_object_unref( connection );
1422
1423    return success;
1424}
1425
1426static void
1427gtr_uninhibit_hibernation( guint inhibit_cookie )
1428{
1429    GVariant * response;
1430    GDBusConnection * connection;
1431    GError * err = NULL;
1432
1433    connection = g_bus_get_sync( G_BUS_TYPE_SESSION, NULL, &err );
1434
1435    response = g_dbus_connection_call_sync( connection,
1436                                            SESSION_MANAGER_SERVICE_NAME,
1437                                            SESSION_MANAGER_OBJECT_PATH,
1438                                            SESSION_MANAGER_INTERFACE,
1439                                            "Uninhibit",
1440                                            g_variant_new( "(u)", inhibit_cookie ),
1441                                            NULL, G_DBUS_CALL_FLAGS_NONE,
1442                                            1000, NULL, &err );
1443
1444    /* logging */
1445    if( err == NULL )
1446        tr_inf( "%s", _( "Allowing desktop hibernation" ) );
1447    else {
1448        g_warning( "Couldn't uninhibit desktop hibernation: %s.", err->message );
1449        g_error_free( err );
1450    }
1451
1452    /* cleanup */
1453    g_variant_unref( response );
1454    g_object_unref( connection );
1455}
1456
1457static void
1458gtr_core_set_hibernation_allowed( TrCore * core, gboolean allowed )
1459{
1460    g_return_if_fail( core );
1461    g_return_if_fail( core->priv );
1462
1463    core->priv->inhibit_allowed = allowed != 0;
1464
1465    if( allowed && core->priv->have_inhibit_cookie )
1466    {
1467        gtr_uninhibit_hibernation( core->priv->inhibit_cookie );
1468        core->priv->have_inhibit_cookie = FALSE;
1469    }
1470
1471    if( !allowed
1472      && !core->priv->have_inhibit_cookie
1473      && !core->priv->dbus_error )
1474    {
1475        if( gtr_inhibit_hibernation( &core->priv->inhibit_cookie ) )
1476            core->priv->have_inhibit_cookie = TRUE;
1477        else
1478            core->priv->dbus_error = TRUE;
1479    }
1480}
1481
1482static void
1483core_maybe_inhibit_hibernation( TrCore * core )
1484{
1485    /* hibernation is allowed if EITHER
1486     * (a) the "inhibit" pref is turned off OR
1487     * (b) there aren't any active torrents */
1488    const gboolean hibernation_allowed = !gtr_pref_flag_get( PREF_KEY_INHIBIT_HIBERNATION )
1489                                      || !gtr_core_get_active_torrent_count( core );
1490    gtr_core_set_hibernation_allowed( core, hibernation_allowed );
1491}
1492
1493/**
1494***  Prefs
1495**/
1496
1497static void
1498core_commit_prefs_change( TrCore * core, const char * key )
1499{
1500    gtr_core_pref_changed( core, key );
1501    gtr_pref_save( gtr_core_session( core ) );
1502}
1503
1504void
1505gtr_core_set_pref( TrCore * self, const char * key, const char * newval )
1506{
1507    if( tr_strcmp0( newval, gtr_pref_string_get( key ) ) )
1508    {
1509        gtr_pref_string_set( key, newval );
1510        core_commit_prefs_change( self, key );
1511    }
1512}
1513
1514void
1515gtr_core_set_pref_bool( TrCore * self, const char * key, gboolean newval )
1516{
1517    if( newval != gtr_pref_flag_get( key ) )
1518    {
1519        gtr_pref_flag_set( key, newval );
1520        core_commit_prefs_change( self, key );
1521    }
1522}
1523
1524void
1525gtr_core_set_pref_int( TrCore * self, const char * key, int newval )
1526{
1527    if( newval != gtr_pref_int_get( key ) )
1528    {
1529        gtr_pref_int_set( key, newval );
1530        core_commit_prefs_change( self, key );
1531    }
1532}
1533
1534void
1535gtr_core_set_pref_double( TrCore * self, const char * key, double newval )
1536{
1537    if( gtr_compare_double( newval, gtr_pref_double_get( key ), 4 ) )
1538    {
1539        gtr_pref_double_set( key, newval );
1540        core_commit_prefs_change( self, key );
1541    }
1542}
1543
1544/***
1545****
1546****  RPC Interface
1547****
1548***/
1549
1550/* #define DEBUG_RPC */
1551
1552static int nextTag = 1;
1553
1554typedef void ( server_response_func )( TrCore * core, tr_benc * response, gpointer user_data );
1555
1556struct pending_request_data
1557{
1558    TrCore * core;
1559    server_response_func * response_func;
1560    gpointer response_func_user_data;
1561};
1562
1563static GHashTable * pendingRequests = NULL;
1564
1565static gboolean
1566core_read_rpc_response_idle( void * vresponse )
1567{
1568    tr_benc top;
1569    int64_t intVal;
1570    struct evbuffer * response = vresponse;
1571
1572    tr_jsonParse( NULL, evbuffer_pullup( response, -1 ), evbuffer_get_length( response ), &top, NULL );
1573
1574    if( tr_bencDictFindInt( &top, "tag", &intVal ) )
1575    {
1576        const int tag = (int)intVal;
1577        struct pending_request_data * data = g_hash_table_lookup( pendingRequests, &tag );
1578        if( data ) {
1579            if( data->response_func )
1580                (*data->response_func)(data->core, &top, data->response_func_user_data );
1581            g_hash_table_remove( pendingRequests, &tag );
1582        }
1583    }
1584
1585    tr_bencFree( &top );
1586    evbuffer_free( response );
1587    return FALSE;
1588}
1589
1590static void
1591core_read_rpc_response( tr_session       * session UNUSED,
1592                        struct evbuffer  * response,
1593                        void             * unused UNUSED )
1594{
1595    struct evbuffer * buf = evbuffer_new( );
1596    evbuffer_add_buffer( buf, response );
1597    gdk_threads_add_idle( core_read_rpc_response_idle, buf );
1598}
1599
1600static void
1601core_send_rpc_request( TrCore * core, const char * json, int tag,
1602                       server_response_func * response_func,
1603                       void * response_func_user_data )
1604{
1605    tr_session * session = gtr_core_session( core );
1606
1607    if( pendingRequests == NULL )
1608    {
1609        pendingRequests = g_hash_table_new_full( g_int_hash, g_int_equal, g_free, g_free );
1610    }
1611
1612    if( session == NULL )
1613    {
1614        g_error( "GTK+ client doesn't support connections to remote servers yet." );
1615    }
1616    else
1617    {
1618        /* remember this request */
1619        struct pending_request_data * data;
1620        data = g_new0( struct pending_request_data, 1 );
1621        data->core = core;
1622        data->response_func = response_func;
1623        data->response_func_user_data = response_func_user_data;
1624        g_hash_table_insert( pendingRequests, g_memdup( &tag, sizeof( int ) ), data );
1625
1626        /* make the request */
1627#ifdef DEBUG_RPC
1628        g_message( "request: [%s]", json );
1629#endif
1630        tr_rpc_request_exec_json( session, json, strlen( json ), core_read_rpc_response, GINT_TO_POINTER(tag) );
1631    }
1632}
1633
1634/***
1635****  Sending a test-port request via RPC
1636***/
1637
1638static void
1639on_port_test_response( TrCore * core, tr_benc * response, gpointer u UNUSED )
1640{
1641    tr_benc * args;
1642    bool is_open = FALSE;
1643
1644    if( tr_bencDictFindDict( response, "arguments", &args ) )
1645        tr_bencDictFindBool( args, "port-is-open", &is_open );
1646
1647    core_emit_port_tested( core, is_open );
1648}
1649
1650void
1651gtr_core_port_test( TrCore * core )
1652{
1653    char buf[64];
1654    const int tag = nextTag++;
1655    g_snprintf( buf, sizeof( buf ), "{ \"method\": \"port-test\", \"tag\": %d }", tag );
1656    core_send_rpc_request( core, buf, tag, on_port_test_response, NULL );
1657}
1658
1659/***
1660****  Updating a blocklist via RPC
1661***/
1662
1663static void
1664on_blocklist_response( TrCore * core, tr_benc * response, gpointer data UNUSED )
1665{
1666    tr_benc * args;
1667    int64_t ruleCount = -1;
1668
1669    if( tr_bencDictFindDict( response, "arguments", &args ) )
1670        tr_bencDictFindInt( args, "blocklist-size", &ruleCount );
1671
1672    if( ruleCount > 0 )
1673        gtr_pref_int_set( "blocklist-date", tr_time( ) );
1674
1675    core_emit_blocklist_udpated( core, ruleCount );
1676}
1677
1678void
1679gtr_core_blocklist_update( TrCore * core )
1680{
1681    char buf[64];
1682    const int tag = nextTag++;
1683    g_snprintf( buf, sizeof( buf ), "{ \"method\": \"blocklist-update\", \"tag\": %d }", tag );
1684    core_send_rpc_request( core, buf, tag, on_blocklist_response, NULL );
1685}
1686
1687/***
1688****
1689***/
1690
1691void
1692gtr_core_exec_json( TrCore * core, const char * json )
1693{
1694    const int tag = nextTag++;
1695    core_send_rpc_request( core, json, tag, NULL, NULL );
1696}
1697
1698void
1699gtr_core_exec( TrCore * core, const tr_benc * top )
1700{
1701    char * json = tr_bencToStr( top, TR_FMT_JSON_LEAN, NULL );
1702    gtr_core_exec_json( core, json );
1703    tr_free( json );
1704}
1705
1706/***
1707****
1708***/
1709
1710size_t
1711gtr_core_get_torrent_count( TrCore * core )
1712{
1713    return gtk_tree_model_iter_n_children( core_raw_model( core ), NULL );
1714}
1715
1716size_t
1717gtr_core_get_active_torrent_count( TrCore * core )
1718{
1719    GtkTreeIter iter;
1720    size_t activeCount = 0;
1721    GtkTreeModel * model = core_raw_model( core );
1722
1723    if( gtk_tree_model_iter_nth_child( model, &iter, NULL, 0 ) ) do
1724    {
1725        int activity;
1726        gtk_tree_model_get( model, &iter, MC_ACTIVITY, &activity, -1 );
1727
1728        if( activity != TR_STATUS_STOPPED )
1729            ++activeCount;
1730    }
1731    while( gtk_tree_model_iter_next( model, &iter ) );
1732
1733    return activeCount;
1734}
1735
1736tr_torrent *
1737gtr_core_find_torrent( TrCore * core, int id )
1738{
1739    tr_session * session;
1740    tr_torrent * tor = NULL;
1741
1742    if(( session = gtr_core_session( core )))
1743        tor = tr_torrentFindFromId( session, id );
1744
1745    return tor;
1746}
1747
1748void
1749gtr_core_open_folder( TrCore * core, int torrent_id )
1750{
1751    const tr_torrent * tor = gtr_core_find_torrent( core, torrent_id );
1752
1753    if( tor != NULL )
1754    {
1755        const gboolean single = tr_torrentInfo( tor )->fileCount == 1;
1756        const char * currentDir = tr_torrentGetCurrentDir( tor );
1757        if( single )
1758            gtr_open_file( currentDir );
1759        else {
1760            char * path = g_build_filename( currentDir, tr_torrentName( tor ), NULL );
1761            gtr_open_file( path );
1762            g_free( path );
1763        }
1764    }
1765}
Note: See TracBrowser for help on using the repository browser.