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

Last change on this file since 12676 was 12676, checked in by jordan, 10 years ago

(trunk gtk) remove some unnecessary #includes

  • Property svn:keywords set to Date Rev Author Id
File size: 50.5 KB
Line 
1/******************************************************************************
2 * $Id: tr-core.c 12676 2011-08-13 14:19:40Z 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    guint          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 gboolean
566core_watchdir_idle( gpointer gcore )
567{
568    GSList * l;
569    GSList * changing = NULL;
570    GSList * unchanging = NULL;
571    TrCore * core = TR_CORE( gcore );
572    const time_t now = tr_time( );
573    struct TrCorePrivate * p = core->priv;
574
575    /* separate the files into two lists: changing and unchanging */
576    for( l=p->monitor_files; l!=NULL; l=l->next )
577    {
578        GFile * file = l->data;
579        const time_t mtime = get_file_mtime( file );
580        if( mtime + 2 >= now )
581            changing = g_slist_prepend( changing, file );
582        else
583            unchanging = g_slist_prepend( unchanging, file );
584    }
585
586    /* add the files that have stopped changing */
587    if( unchanging != NULL )
588    {
589        const gboolean do_start = gtr_pref_flag_get( TR_PREFS_KEY_START );
590        const gboolean do_prompt = gtr_pref_flag_get( PREF_KEY_OPTIONS_PROMPT );
591
592        core->priv->adding_from_watch_dir = TRUE;
593        gtr_core_add_files( core, unchanging, do_start, do_prompt, TRUE );
594        g_slist_foreach( unchanging, (GFunc)g_object_unref, NULL );
595        g_slist_free( unchanging );
596        core->priv->adding_from_watch_dir = FALSE;
597    }
598
599    /* keep monitoring the ones that are still changing */
600    g_slist_free( p->monitor_files );
601    p->monitor_files = changing;
602
603    /* if monitor_files is nonempty, keep checking every second */
604    if( core->priv->monitor_files )
605        return TRUE;
606    core->priv->monitor_idle_tag = 0;
607    return FALSE;
608
609}
610
611/* If this file is a torrent, add it to our list */
612static void
613core_watchdir_monitor_file( TrCore * core, GFile * file )
614{
615    char * filename = g_file_get_path( file );
616    const gboolean is_torrent = g_str_has_suffix( filename, ".torrent" );
617
618    if( is_torrent )
619    {
620        GSList * l;
621        struct TrCorePrivate * p = core->priv;
622
623        /* if we're not already watching this file, start watching it now */
624        for( l=p->monitor_files; l!=NULL; l=l->next )
625            if( g_file_equal( file, l->data ) )
626                break;
627        if( l == NULL ) {
628            g_object_ref( file );
629            p->monitor_files = g_slist_prepend( p->monitor_files, file );
630            if( p->monitor_idle_tag == 0 )
631                p->monitor_idle_tag = gdk_threads_add_timeout_seconds( 1, core_watchdir_idle, core );
632        }
633    }
634
635    g_free( filename );
636}
637
638/* GFileMonitor noticed a file was created */
639static void
640on_file_changed_in_watchdir( GFileMonitor       * monitor UNUSED,
641                             GFile              * file,
642                             GFile              * other_type UNUSED,
643                             GFileMonitorEvent    event_type,
644                             gpointer             core )
645{
646    if( event_type == G_FILE_MONITOR_EVENT_CREATED )
647        core_watchdir_monitor_file( core, file );
648}
649
650/* walk through the pre-existing files in the watchdir */
651static void
652core_watchdir_scan( TrCore * core )
653{
654    const char * dirname = gtr_pref_string_get( PREF_KEY_DIR_WATCH );
655    GDir * dir = g_dir_open( dirname, 0, NULL );
656
657    if( dir != NULL )
658    {
659        const char * name;
660        while(( name = g_dir_read_name( dir )))
661        {
662            char * filename = g_build_filename( dirname, name, NULL );
663            GFile * file = g_file_new_for_path( filename );
664            core_watchdir_monitor_file( core, file );
665            g_object_unref( file );
666            g_free( filename );
667        }
668
669        g_dir_close( dir );
670    }
671}
672
673static void
674core_watchdir_update( TrCore * core )
675{
676    const gboolean is_enabled = gtr_pref_flag_get( PREF_KEY_DIR_WATCH_ENABLED );
677    GFile * dir = g_file_new_for_path( gtr_pref_string_get( PREF_KEY_DIR_WATCH ) );
678    struct TrCorePrivate * p = core->priv;
679
680    if( p->monitor && ( !is_enabled || !g_file_equal( dir, p->monitor_dir ) ) )
681    {
682        g_signal_handler_disconnect( p->monitor, p->monitor_tag );
683        g_file_monitor_cancel( p->monitor );
684        g_object_unref( p->monitor );
685        g_object_unref( p->monitor_dir );
686
687        p->monitor_dir = NULL;
688        p->monitor = NULL;
689        p->monitor_tag = 0;
690    }
691
692    if( is_enabled && !p->monitor )
693    {
694        GFileMonitor * m = g_file_monitor_directory( dir, 0, NULL, NULL );
695        core_watchdir_scan( core );
696
697        g_object_ref( dir );
698        p->monitor = m;
699        p->monitor_dir = dir;
700        p->monitor_tag = g_signal_connect( m, "changed",
701                                           G_CALLBACK( on_file_changed_in_watchdir ), core );
702    }
703
704    g_object_unref( dir );
705}
706
707/***
708****
709***/
710
711static void
712on_pref_changed( TrCore * core, const char * key, gpointer data UNUSED )
713{
714    if( !strcmp( key, PREF_KEY_SORT_MODE )
715      || !strcmp( key, PREF_KEY_SORT_REVERSED ) )
716    {
717        const char * mode = gtr_pref_string_get( PREF_KEY_SORT_MODE );
718        gboolean is_reversed = gtr_pref_flag_get( PREF_KEY_SORT_REVERSED );
719        core_set_sort_mode( core, mode, is_reversed );
720    }
721    else if( !strcmp( key, TR_PREFS_KEY_PEER_LIMIT_GLOBAL ) )
722    {
723        tr_sessionSetPeerLimit( gtr_core_session( core ),
724                                gtr_pref_int_get( key ) );
725    }
726    else if( !strcmp( key, TR_PREFS_KEY_PEER_LIMIT_TORRENT ) )
727    {
728        tr_sessionSetPeerLimitPerTorrent( gtr_core_session( core ),
729                                          gtr_pref_int_get( key ) );
730    }
731    else if( !strcmp( key, PREF_KEY_INHIBIT_HIBERNATION ) )
732    {
733        core_maybe_inhibit_hibernation( core );
734    }
735    else if( !strcmp( key, PREF_KEY_DIR_WATCH )
736           || !strcmp( key, PREF_KEY_DIR_WATCH_ENABLED ) )
737    {
738        core_watchdir_update( core );
739    }
740}
741
742/**
743***
744**/
745
746TrCore *
747gtr_core_new( tr_session * session )
748{
749    TrCore * core = TR_CORE( g_object_new( TR_CORE_TYPE, NULL ) );
750
751    core->priv->session = session;
752
753    /* init from prefs & listen to pref changes */
754    on_pref_changed( core, PREF_KEY_SORT_MODE, NULL );
755    on_pref_changed( core, PREF_KEY_SORT_REVERSED, NULL );
756    on_pref_changed( core, PREF_KEY_DIR_WATCH_ENABLED, NULL );
757    on_pref_changed( core, TR_PREFS_KEY_PEER_LIMIT_GLOBAL, NULL );
758    on_pref_changed( core, PREF_KEY_INHIBIT_HIBERNATION, NULL );
759    g_signal_connect( core, "prefs-changed", G_CALLBACK( on_pref_changed ), NULL );
760
761    return core;
762}
763
764void
765gtr_core_close( TrCore * core )
766{
767    tr_session * session = gtr_core_session( core );
768
769    if( session )
770    {
771        core->priv->session = NULL;
772        gtr_pref_save( session );
773        tr_sessionClose( session );
774    }
775}
776
777/***
778****  COMPLETENESS CALLBACK
779***/
780
781struct notify_callback_data
782{
783    TrCore * core;
784    int torrent_id;
785};
786
787static gboolean
788on_torrent_completeness_changed_idle( gpointer gdata )
789{
790    struct notify_callback_data * data = gdata;
791    gtr_notify_torrent_completed( data->core, data->torrent_id );
792    g_object_unref( G_OBJECT( data->core ) );
793    g_free( data );
794    return FALSE;
795}
796
797/* this is called in the libtransmission thread, *NOT* the GTK+ thread,
798   so delegate to the GTK+ thread before calling notify's dbus code... */
799static void
800on_torrent_completeness_changed( tr_torrent       * tor,
801                                 tr_completeness    completeness,
802                                 bool               was_running,
803                                 void             * gcore )
804{
805    if( was_running && ( completeness != TR_LEECH ) && ( tr_torrentStat( tor )->sizeWhenDone != 0 ) )
806    {
807        struct notify_callback_data * data = g_new( struct notify_callback_data, 1 );
808        data->core = gcore;
809        data->torrent_id = tr_torrentId( tor );
810        g_object_ref( G_OBJECT( data->core ) );
811        gdk_threads_add_idle( on_torrent_completeness_changed_idle, data );
812    }
813}
814
815/***
816****  METADATA CALLBACK
817***/
818
819static const char*
820get_collated_name( TrCore * core, const tr_torrent * tor )
821{
822    char buf[2048];
823    const char * name = tr_torrentName( tor );
824    char * down = g_utf8_strdown( name ? name : "", -1 );
825    const tr_info * inf = tr_torrentInfo( tor );
826    g_snprintf( buf, sizeof( buf ), "%s\t%s", down, inf->hashString );
827    g_free( down );
828    return g_string_chunk_insert_const( core->priv->string_chunk, buf );
829}
830
831struct metadata_callback_data
832{
833    TrCore * core;
834    int torrent_id;
835};
836
837static gboolean
838find_row_from_torrent_id( GtkTreeModel * model, int id, GtkTreeIter * setme )
839{
840    GtkTreeIter iter;
841    gboolean match = FALSE;
842
843    if( gtk_tree_model_iter_children( model, &iter, NULL ) ) do
844    {
845        int row_id;
846        gtk_tree_model_get( model, &iter, MC_TORRENT_ID, &row_id, -1 );
847        match = id == row_id;
848    }
849    while( !match && gtk_tree_model_iter_next( model, &iter ) );
850
851    if( match )
852        *setme = iter;
853
854    return match;
855}
856
857static gboolean
858on_torrent_metadata_changed_idle( gpointer gdata )
859{
860    struct notify_callback_data * data = gdata;
861    tr_session * session = gtr_core_session( data->core );
862    tr_torrent * tor = tr_torrentFindFromId( session, data->torrent_id );
863
864    /* update the torrent's collated name */
865    if( tor != NULL ) {
866        GtkTreeIter iter;
867        GtkTreeModel * model = core_raw_model( data->core );
868        if( find_row_from_torrent_id( model, data->torrent_id, &iter ) ) {
869            const char * collated = get_collated_name( data->core, tor );
870            GtkListStore * store = GTK_LIST_STORE( model );
871            gtk_list_store_set( store, &iter, MC_NAME_COLLATED, collated, -1 );
872        }
873    }
874
875    /* cleanup */
876    g_object_unref( G_OBJECT( data->core ) );
877    g_free( data );
878    return FALSE;
879}
880
881/* this is called in the libtransmission thread, *NOT* the GTK+ thread,
882   so delegate to the GTK+ thread before changing our list store... */
883static void
884on_torrent_metadata_changed( tr_torrent * tor, void * gcore )
885{
886    struct notify_callback_data * data = g_new( struct notify_callback_data, 1 );
887    data->core = gcore;
888    data->torrent_id = tr_torrentId( tor );
889    g_object_ref( G_OBJECT( data->core ) );
890    gdk_threads_add_idle( on_torrent_metadata_changed_idle, data );
891}
892
893/***
894****
895****  ADDING TORRENTS
896****
897***/
898
899static unsigned int
900build_torrent_trackers_hash( tr_torrent * tor )
901{
902    int i;
903    const char * pch;
904    uint64_t hash = 0;
905    const tr_info * const inf = tr_torrentInfo( tor );
906
907    for( i=0; i<inf->trackerCount; ++i )
908        for( pch=inf->trackers[i].announce; *pch; ++pch )
909            hash = (hash<<4) ^ (hash>>28) ^ *pch;
910
911    return hash;
912}
913
914static gboolean
915is_torrent_active( const tr_stat * st )
916{
917    return ( st->peersSendingToUs > 0 )
918        || ( st->peersGettingFromUs > 0 )
919        || ( st->activity == TR_STATUS_CHECK );
920}
921
922void
923gtr_core_add_torrent( TrCore * core, tr_torrent * tor, gboolean do_notify )
924{
925    if( tor != NULL )
926    {
927        GtkTreeIter unused;
928        const tr_stat * st = tr_torrentStat( tor );
929        const char * collated = get_collated_name( core, tor );
930        const unsigned int trackers_hash = build_torrent_trackers_hash( tor );
931        GtkListStore * store = GTK_LIST_STORE( core_raw_model( core ) );
932
933        gtk_list_store_insert_with_values( store, &unused, 0,
934            MC_NAME_COLLATED,     collated,
935            MC_TORRENT,           tor,
936            MC_TORRENT_ID,        tr_torrentId( tor ),
937            MC_SPEED_UP,          st->pieceUploadSpeed_KBps,
938            MC_SPEED_DOWN,        st->pieceDownloadSpeed_KBps,
939            MC_RECHECK_PROGRESS,  st->recheckProgress,
940            MC_ACTIVE,            is_torrent_active( st ),
941            MC_ACTIVITY,          st->activity,
942            MC_FINISHED,          st->finished,
943            MC_PRIORITY,          tr_torrentGetPriority( tor ),
944            MC_QUEUE_POSITION,    st->queuePosition,
945            MC_TRACKERS,          trackers_hash,
946            -1 );
947
948        if( do_notify )
949            gtr_notify_torrent_added( tr_torrentName( tor ) );
950
951        tr_torrentSetMetadataCallback( tor, on_torrent_metadata_changed, core );
952        tr_torrentSetCompletenessCallback( tor, on_torrent_completeness_changed, core );
953    }
954}
955
956static tr_torrent *
957core_create_new_torrent( TrCore * core, tr_ctor * ctor )
958{
959    int errcode = 0;
960    tr_torrent * tor;
961    bool do_trash = false;
962    tr_session * session = gtr_core_session( core );
963
964    /* let the gtk client handle the removal, since libT
965     * doesn't have any concept of the glib trash API */
966    tr_ctorGetDeleteSource( ctor, &do_trash );
967    tr_ctorSetDeleteSource( ctor, FALSE );
968    tor = tr_torrentNew( ctor, &errcode );
969
970    if( tor && do_trash )
971    {
972        const char * config = tr_sessionGetConfigDir( session );
973        const char * source = tr_ctorGetSourceFile( ctor );
974        const int is_internal = source && ( strstr( source, config ) == source );
975
976        /* #1294: don't delete the .torrent file if it's our internal copy */
977        if( !is_internal )
978            gtr_file_trash_or_remove( source );
979    }
980
981    return tor;
982}
983
984static int
985core_add_ctor( TrCore * core, tr_ctor * ctor,
986               gboolean do_prompt, gboolean do_notify )
987{
988    tr_info inf;
989    int err = tr_torrentParse( ctor, &inf );
990
991    switch( err )
992    {
993        case TR_PARSE_ERR:
994            break;
995
996        case TR_PARSE_DUPLICATE:
997            /* don't complain about .torrent files in the watch directory
998             * that have already been added... that gets annoying and we
999             * don't want to be nagging users to clean up their watch dirs */
1000            if( !tr_ctorGetSourceFile(ctor) || !core->priv->adding_from_watch_dir )
1001                core_emit_err( core, err, inf.name );
1002            tr_metainfoFree( &inf );
1003            tr_ctorFree( ctor );
1004            break;
1005
1006        default:
1007            if( do_prompt )
1008                g_signal_emit( core, signals[ADD_PROMPT_SIGNAL], 0, ctor );
1009            else {
1010                gtr_core_add_torrent( core, core_create_new_torrent( core, ctor ), do_notify );
1011                tr_ctorFree( ctor );
1012            }
1013            tr_metainfoFree( &inf );
1014            break;
1015    }
1016
1017    return err;
1018}
1019
1020static void
1021core_apply_defaults( tr_ctor * ctor )
1022{
1023    if( tr_ctorGetPaused( ctor, TR_FORCE, NULL ) )
1024        tr_ctorSetPaused( ctor, TR_FORCE, !gtr_pref_flag_get( TR_PREFS_KEY_START ) );
1025
1026    if( tr_ctorGetDeleteSource( ctor, NULL ) )
1027        tr_ctorSetDeleteSource( ctor,
1028                               gtr_pref_flag_get( TR_PREFS_KEY_TRASH_ORIGINAL ) );
1029
1030    if( tr_ctorGetPeerLimit( ctor, TR_FORCE, NULL ) )
1031        tr_ctorSetPeerLimit( ctor, TR_FORCE,
1032                             gtr_pref_int_get( TR_PREFS_KEY_PEER_LIMIT_TORRENT ) );
1033
1034    if( tr_ctorGetDownloadDir( ctor, TR_FORCE, NULL ) )
1035        tr_ctorSetDownloadDir( ctor, TR_FORCE,
1036                               gtr_pref_string_get( TR_PREFS_KEY_DOWNLOAD_DIR ) );
1037}
1038
1039void
1040gtr_core_add_ctor( TrCore * core, tr_ctor * ctor )
1041{
1042    const gboolean do_notify = FALSE;
1043    const gboolean do_prompt = gtr_pref_flag_get( PREF_KEY_OPTIONS_PROMPT );
1044    core_apply_defaults( ctor );
1045    core_add_ctor( core, ctor, do_prompt, do_notify );
1046}
1047
1048/***
1049****
1050***/
1051
1052struct add_from_url_data
1053{
1054    TrCore * core;
1055    tr_ctor * ctor;
1056    bool do_prompt;
1057    bool do_notify;
1058};
1059
1060static void
1061add_file_async_callback( GObject * file, GAsyncResult * result, gpointer gdata )
1062{
1063    gsize length;
1064    char * contents;
1065    GError * error = NULL;
1066    struct add_from_url_data * data = gdata;
1067
1068    if( !g_file_load_contents_finish( G_FILE( file ), result, &contents, &length, NULL, &error ) )
1069    {
1070        g_message( _( "Couldn't read \"%s\": %s" ), g_file_get_parse_name( G_FILE( file ) ), error->message );
1071        g_error_free( error );
1072    }
1073    else if( !tr_ctorSetMetainfo( data->ctor, (const uint8_t*)contents, length ) )
1074    {
1075        core_add_ctor( data->core, data->ctor, data->do_prompt, data->do_notify );
1076    }
1077    else
1078    {
1079        tr_ctorFree( data->ctor );
1080    }
1081
1082    core_dec_busy( data->core );
1083    g_free( data );
1084}
1085
1086static bool
1087add_file( TrCore      * core,
1088          GFile       * file,
1089          gboolean      do_start,
1090          gboolean      do_prompt,
1091          gboolean      do_notify )
1092{
1093    bool handled = false;
1094    tr_session * session = gtr_core_session( core );
1095
1096    if( session != NULL )
1097    {
1098        tr_ctor * ctor;
1099        bool tried = false;
1100        bool loaded = false;
1101
1102        ctor = tr_ctorNew( session );
1103        core_apply_defaults( ctor );
1104        tr_ctorSetPaused( ctor, TR_FORCE, !do_start );
1105
1106        /* local files... */
1107        if( !tried ) {
1108            char * str = g_file_get_path( file );
1109            if(( tried = g_file_test( str, G_FILE_TEST_EXISTS )))
1110               loaded = !tr_ctorSetMetainfoFromFile( ctor, str );
1111            g_free( str );
1112        }
1113
1114        /* magnet links... */
1115        if( !tried && g_file_has_uri_scheme( file, "magnet" ) ) {
1116            /* GFile mangles the original string with /// so we have to un-mangle */
1117            char * str = g_file_get_parse_name( file );
1118            char * magnet = g_strdup_printf( "magnet:%s", strchr( str, '?' ) );
1119            tried = true;
1120            loaded = !tr_ctorSetMetainfoFromMagnetLink( ctor, magnet );
1121            g_free( magnet );
1122            g_free( str );
1123        }
1124
1125        /* hashcodes that we can turn into magnet links... */
1126        if( !tried ) {
1127            char * str = g_file_get_basename( file );
1128            if( gtr_is_hex_hashcode( str ) ) {
1129                char * magnet = g_strdup_printf( "magnet:?xt=urn:btih:%s", str );
1130                tried = true;
1131                loaded = !tr_ctorSetMetainfoFromMagnetLink( ctor, magnet );
1132                g_free( magnet );
1133            }
1134            g_free( str );
1135        }
1136
1137        /* if we were able to load the metainfo, add the torrent */
1138        if( loaded )
1139        {
1140            handled = true;
1141            core_add_ctor( core, ctor, do_prompt, do_notify );
1142        }
1143        else if( g_file_has_uri_scheme( file, "http" ) ||
1144                 g_file_has_uri_scheme( file, "https" ) ||
1145                 g_file_has_uri_scheme( file, "ftp" ) )
1146        {
1147            struct add_from_url_data * data;
1148
1149            data = g_new0( struct add_from_url_data, 1 );
1150            data->core = core;
1151            data->ctor = ctor;
1152            data->do_prompt = do_prompt;
1153            data->do_notify = do_notify;
1154
1155            handled = true;
1156            core_inc_busy( core );
1157            g_file_load_contents_async( file, NULL, add_file_async_callback, data );
1158        }
1159        else
1160        {
1161            tr_ctorFree( ctor );
1162            g_message( _( "Skipping unknown torrent \"%s\"" ), g_file_get_parse_name( file ) );
1163        }
1164    }
1165
1166    return handled;
1167}
1168
1169bool
1170gtr_core_add_from_url( TrCore * core, const char * uri )
1171{
1172    bool handled;
1173    const bool do_start = gtr_pref_flag_get( TR_PREFS_KEY_START );
1174    const bool do_prompt = gtr_pref_flag_get( PREF_KEY_OPTIONS_PROMPT );
1175    const bool do_notify = false;
1176
1177    GFile * file = g_file_new_for_uri( uri );
1178    handled = add_file( core, file, do_start, do_prompt, do_notify );
1179    g_object_unref( file );
1180    gtr_core_torrents_added( core );
1181
1182    return handled;
1183}
1184
1185void
1186gtr_core_add_files( TrCore     * core,
1187                    GSList     * files,
1188                    gboolean     do_start,
1189                    gboolean     do_prompt,
1190                    gboolean     do_notify )
1191{
1192    GSList * l;
1193
1194    for( l=files; l!=NULL; l=l->next )
1195        add_file( core, l->data, do_start, do_prompt, do_notify );
1196
1197    gtr_core_torrents_added( core );
1198}
1199
1200void
1201gtr_core_torrents_added( TrCore * self )
1202{
1203    gtr_core_update( self );
1204    core_emit_err( self, TR_CORE_ERR_NO_MORE_TORRENTS, NULL );
1205}
1206
1207void
1208gtr_core_remove_torrent( TrCore * core, int id, gboolean delete_local_data )
1209{
1210    tr_torrent * tor = gtr_core_find_torrent( core, id );
1211
1212    if( tor != NULL )
1213    {
1214        /* remove from the gui */
1215        GtkTreeIter iter;
1216        GtkTreeModel * model = core_raw_model( core );
1217        if( find_row_from_torrent_id( model, id, &iter ) )
1218            gtk_list_store_remove( GTK_LIST_STORE( model ), &iter );
1219
1220        /* remove the torrent */
1221        tr_torrentRemove( tor, delete_local_data, gtr_file_trash_or_remove );
1222    }
1223}
1224
1225void
1226gtr_core_load( TrCore * self, gboolean forcePaused )
1227{
1228    int i;
1229    tr_ctor * ctor;
1230    tr_torrent ** torrents;
1231    int count = 0;
1232
1233    ctor = tr_ctorNew( gtr_core_session( self ) );
1234    if( forcePaused )
1235        tr_ctorSetPaused( ctor, TR_FORCE, TRUE );
1236    tr_ctorSetPeerLimit( ctor, TR_FALLBACK,
1237                         gtr_pref_int_get( TR_PREFS_KEY_PEER_LIMIT_TORRENT ) );
1238
1239    torrents = tr_sessionLoadTorrents ( gtr_core_session( self ), ctor, &count );
1240    for( i=0; i<count; ++i )
1241        gtr_core_add_torrent( self, torrents[i], FALSE );
1242
1243    tr_free( torrents );
1244    tr_ctorFree( ctor );
1245}
1246
1247void
1248gtr_core_clear( TrCore * self )
1249{
1250    gtk_list_store_clear( GTK_LIST_STORE( core_raw_model( self ) ) );
1251}
1252
1253/***
1254****
1255***/
1256
1257static int
1258gtr_compare_double( const double a, const double b, int decimal_places )
1259{
1260    const int64_t ia = (int64_t)(a * pow( 10, decimal_places ) );
1261    const int64_t ib = (int64_t)(b * pow( 10, decimal_places ) );
1262    if( ia < ib ) return -1;
1263    if( ia > ib ) return  1;
1264    return 0;
1265}
1266
1267static void
1268update_foreach( GtkTreeModel * model, GtkTreeIter * iter )
1269{
1270    int oldActivity, newActivity;
1271    int oldActivePeerCount, newActivePeerCount;
1272    int oldError, newError;
1273    bool oldFinished, newFinished;
1274    int oldQueuePosition, newQueuePosition;
1275    tr_priority_t oldPriority, newPriority;
1276    unsigned int oldTrackers, newTrackers;
1277    double oldUpSpeed, newUpSpeed;
1278    double oldDownSpeed, newDownSpeed;
1279    double oldRecheckProgress, newRecheckProgress;
1280    gboolean oldActive, newActive;
1281    const tr_stat * st;
1282    tr_torrent * tor;
1283
1284    /* get the old states */
1285    gtk_tree_model_get( model, iter,
1286                        MC_TORRENT,  &tor,
1287                        MC_ACTIVE, &oldActive,
1288                        MC_ACTIVE_PEER_COUNT, &oldActivePeerCount,
1289                        MC_ERROR, &oldError,
1290                        MC_ACTIVITY, &oldActivity,
1291                        MC_FINISHED, &oldFinished,
1292                        MC_PRIORITY, &oldPriority,
1293                        MC_QUEUE_POSITION, &oldQueuePosition,
1294                        MC_TRACKERS, &oldTrackers,
1295                        MC_SPEED_UP, &oldUpSpeed,
1296                        MC_RECHECK_PROGRESS, &oldRecheckProgress,
1297                        MC_SPEED_DOWN, &oldDownSpeed,
1298                        -1 );
1299
1300    /* get the new states */
1301    st = tr_torrentStat( tor );
1302    newActive = is_torrent_active( st );
1303    newActivity = st->activity;
1304    newFinished = st->finished;
1305    newPriority = tr_torrentGetPriority( tor );
1306    newQueuePosition = st->queuePosition;
1307    newTrackers = build_torrent_trackers_hash( tor );
1308    newUpSpeed = st->pieceUploadSpeed_KBps;
1309    newDownSpeed = st->pieceDownloadSpeed_KBps;
1310    newRecheckProgress = st->recheckProgress;
1311    newActivePeerCount = st->peersSendingToUs + st->peersGettingFromUs + st->webseedsSendingToUs;
1312    newError = st->error;
1313
1314    /* updating the model triggers off resort/refresh,
1315       so don't do it unless something's actually changed... */
1316    if( ( newActive != oldActive )
1317        || ( newActivity  != oldActivity )
1318        || ( newFinished != oldFinished )
1319        || ( newPriority != oldPriority )
1320        || ( newQueuePosition != oldQueuePosition )
1321        || ( newError != oldError )
1322        || ( newActivePeerCount != oldActivePeerCount )
1323        || ( newTrackers != oldTrackers )
1324        || gtr_compare_double( newUpSpeed, oldUpSpeed, 2 )
1325        || gtr_compare_double( newDownSpeed, oldDownSpeed, 2 )
1326        || gtr_compare_double( newRecheckProgress, oldRecheckProgress, 2 ) )
1327    {
1328        gtk_list_store_set( GTK_LIST_STORE( model ), iter,
1329                            MC_ACTIVE, newActive,
1330                            MC_ACTIVE_PEER_COUNT, newActivePeerCount,
1331                            MC_ERROR, newError,
1332                            MC_ACTIVITY, newActivity,
1333                            MC_FINISHED, newFinished,
1334                            MC_PRIORITY, newPriority,
1335                            MC_QUEUE_POSITION, newQueuePosition,
1336                            MC_TRACKERS, newTrackers,
1337                            MC_SPEED_UP, newUpSpeed,
1338                            MC_SPEED_DOWN, newDownSpeed,
1339                            MC_RECHECK_PROGRESS, newRecheckProgress,
1340                            -1 );
1341    }
1342}
1343
1344void
1345gtr_core_update( TrCore * core )
1346{
1347    GtkTreeIter iter;
1348    GtkTreeModel * model;
1349
1350    /* update the model */
1351    model = core_raw_model( core );
1352    if( gtk_tree_model_iter_nth_child( model, &iter, NULL, 0 ) ) do
1353        update_foreach( model, &iter );
1354    while( gtk_tree_model_iter_next( model, &iter ) );
1355
1356    /* update hibernation */
1357    core_maybe_inhibit_hibernation( core );
1358}
1359
1360/**
1361***  Hibernate
1362**/
1363
1364#define SESSION_MANAGER_SERVICE_NAME  "org.gnome.SessionManager"
1365#define SESSION_MANAGER_INTERFACE     "org.gnome.SessionManager"
1366#define SESSION_MANAGER_OBJECT_PATH   "/org/gnome/SessionManager"
1367
1368static gboolean
1369gtr_inhibit_hibernation( guint * cookie )
1370{
1371    gboolean success;
1372    GVariant * response;
1373    GDBusConnection * connection;
1374    GError * err = NULL;
1375    const char * application = "Transmission BitTorrent Client";
1376    const char * reason = "BitTorrent Activity";
1377    const int toplevel_xid = 0;
1378    const int flags = 4; /* Inhibit suspending the session or computer */
1379
1380    connection = g_bus_get_sync( G_BUS_TYPE_SESSION, NULL, &err );
1381
1382    response = g_dbus_connection_call_sync( connection,
1383                                            SESSION_MANAGER_SERVICE_NAME,
1384                                            SESSION_MANAGER_OBJECT_PATH,
1385                                            SESSION_MANAGER_INTERFACE,
1386                                            "Inhibit",
1387                                            g_variant_new( "(susu)", application, toplevel_xid, reason, flags ),
1388                                            NULL, G_DBUS_CALL_FLAGS_NONE,
1389                                            1000, NULL, &err );
1390
1391    *cookie = g_variant_get_uint32( g_variant_get_child_value( response, 0 ) );
1392
1393    success = err == NULL;
1394
1395    /* logging */
1396    if( success )
1397        tr_inf( "%s", _( "Inhibiting desktop hibernation" ) );
1398    else {
1399        tr_err( _( "Couldn't inhibit desktop hibernation: %s" ), err->message );
1400        g_error_free( err );
1401    }
1402
1403    /* cleanup */
1404    g_variant_unref( response );
1405    g_object_unref( connection );
1406    return success;
1407}
1408
1409static void
1410gtr_uninhibit_hibernation( guint inhibit_cookie )
1411{
1412    GVariant * response;
1413    GDBusConnection * connection;
1414    GError * err = NULL;
1415
1416    connection = g_bus_get_sync( G_BUS_TYPE_SESSION, NULL, &err );
1417
1418    response = g_dbus_connection_call_sync( connection,
1419                                            SESSION_MANAGER_SERVICE_NAME,
1420                                            SESSION_MANAGER_OBJECT_PATH,
1421                                            SESSION_MANAGER_INTERFACE,
1422                                            "Uninhibit",
1423                                            g_variant_new( "(u)", inhibit_cookie ),
1424                                            NULL, G_DBUS_CALL_FLAGS_NONE,
1425                                            1000, NULL, &err );
1426
1427    /* logging */
1428    if( err == NULL )
1429        tr_inf( "%s", _( "Allowing desktop hibernation" ) );
1430    else {
1431        g_warning( "Couldn't uninhibit desktop hibernation: %s.", err->message );
1432        g_error_free( err );
1433    }
1434
1435    /* cleanup */
1436    g_variant_unref( response );
1437    g_object_unref( connection );
1438}
1439
1440static void
1441gtr_core_set_hibernation_allowed( TrCore * core, gboolean allowed )
1442{
1443    g_return_if_fail( core );
1444    g_return_if_fail( core->priv );
1445
1446    core->priv->inhibit_allowed = allowed != 0;
1447
1448    if( allowed && core->priv->have_inhibit_cookie )
1449    {
1450        gtr_uninhibit_hibernation( core->priv->inhibit_cookie );
1451        core->priv->have_inhibit_cookie = FALSE;
1452    }
1453
1454    if( !allowed
1455      && !core->priv->have_inhibit_cookie
1456      && !core->priv->dbus_error )
1457    {
1458        if( gtr_inhibit_hibernation( &core->priv->inhibit_cookie ) )
1459            core->priv->have_inhibit_cookie = TRUE;
1460        else
1461            core->priv->dbus_error = TRUE;
1462    }
1463}
1464
1465static void
1466core_maybe_inhibit_hibernation( TrCore * core )
1467{
1468    /* hibernation is allowed if EITHER
1469     * (a) the "inhibit" pref is turned off OR
1470     * (b) there aren't any active torrents */
1471    const gboolean hibernation_allowed = !gtr_pref_flag_get( PREF_KEY_INHIBIT_HIBERNATION )
1472                                      || !gtr_core_get_active_torrent_count( core );
1473    gtr_core_set_hibernation_allowed( core, hibernation_allowed );
1474}
1475
1476/**
1477***  Prefs
1478**/
1479
1480static void
1481core_commit_prefs_change( TrCore * core, const char * key )
1482{
1483    gtr_core_pref_changed( core, key );
1484    gtr_pref_save( gtr_core_session( core ) );
1485}
1486
1487void
1488gtr_core_set_pref( TrCore * self, const char * key, const char * newval )
1489{
1490    if( tr_strcmp0( newval, gtr_pref_string_get( key ) ) )
1491    {
1492        gtr_pref_string_set( key, newval );
1493        core_commit_prefs_change( self, key );
1494    }
1495}
1496
1497void
1498gtr_core_set_pref_bool( TrCore * self, const char * key, gboolean newval )
1499{
1500    if( newval != gtr_pref_flag_get( key ) )
1501    {
1502        gtr_pref_flag_set( key, newval );
1503        core_commit_prefs_change( self, key );
1504    }
1505}
1506
1507void
1508gtr_core_set_pref_int( TrCore * self, const char * key, int newval )
1509{
1510    if( newval != gtr_pref_int_get( key ) )
1511    {
1512        gtr_pref_int_set( key, newval );
1513        core_commit_prefs_change( self, key );
1514    }
1515}
1516
1517void
1518gtr_core_set_pref_double( TrCore * self, const char * key, double newval )
1519{
1520    if( gtr_compare_double( newval, gtr_pref_double_get( key ), 4 ) )
1521    {
1522        gtr_pref_double_set( key, newval );
1523        core_commit_prefs_change( self, key );
1524    }
1525}
1526
1527/***
1528****
1529****  RPC Interface
1530****
1531***/
1532
1533/* #define DEBUG_RPC */
1534
1535static int nextTag = 1;
1536
1537typedef void ( server_response_func )( TrCore * core, tr_benc * response, gpointer user_data );
1538
1539struct pending_request_data
1540{
1541    TrCore * core;
1542    server_response_func * response_func;
1543    gpointer response_func_user_data;
1544};
1545
1546static GHashTable * pendingRequests = NULL;
1547
1548static gboolean
1549core_read_rpc_response_idle( void * vresponse )
1550{
1551    tr_benc top;
1552    int64_t intVal;
1553    struct evbuffer * response = vresponse;
1554
1555    tr_jsonParse( NULL, evbuffer_pullup( response, -1 ), evbuffer_get_length( response ), &top, NULL );
1556
1557    if( tr_bencDictFindInt( &top, "tag", &intVal ) )
1558    {
1559        const int tag = (int)intVal;
1560        struct pending_request_data * data = g_hash_table_lookup( pendingRequests, &tag );
1561        if( data ) {
1562            if( data->response_func )
1563                (*data->response_func)(data->core, &top, data->response_func_user_data );
1564            g_hash_table_remove( pendingRequests, &tag );
1565        }
1566    }
1567
1568    tr_bencFree( &top );
1569    evbuffer_free( response );
1570    return FALSE;
1571}
1572
1573static void
1574core_read_rpc_response( tr_session       * session UNUSED,
1575                        struct evbuffer  * response,
1576                        void             * unused UNUSED )
1577{
1578    struct evbuffer * buf = evbuffer_new( );
1579    evbuffer_add_buffer( buf, response );
1580    gdk_threads_add_idle( core_read_rpc_response_idle, buf );
1581}
1582
1583static void
1584core_send_rpc_request( TrCore * core, const char * json, int tag,
1585                       server_response_func * response_func,
1586                       void * response_func_user_data )
1587{
1588    tr_session * session = gtr_core_session( core );
1589
1590    if( pendingRequests == NULL )
1591    {
1592        pendingRequests = g_hash_table_new_full( g_int_hash, g_int_equal, g_free, g_free );
1593    }
1594
1595    if( session == NULL )
1596    {
1597        g_error( "GTK+ client doesn't support connections to remote servers yet." );
1598    }
1599    else
1600    {
1601        /* remember this request */
1602        struct pending_request_data * data;
1603        data = g_new0( struct pending_request_data, 1 );
1604        data->core = core;
1605        data->response_func = response_func;
1606        data->response_func_user_data = response_func_user_data;
1607        g_hash_table_insert( pendingRequests, g_memdup( &tag, sizeof( int ) ), data );
1608
1609        /* make the request */
1610#ifdef DEBUG_RPC
1611        g_message( "request: [%s]", json );
1612#endif
1613        tr_rpc_request_exec_json( session, json, strlen( json ), core_read_rpc_response, GINT_TO_POINTER(tag) );
1614    }
1615}
1616
1617/***
1618****  Sending a test-port request via RPC
1619***/
1620
1621static void
1622on_port_test_response( TrCore * core, tr_benc * response, gpointer u UNUSED )
1623{
1624    tr_benc * args;
1625    bool is_open = FALSE;
1626
1627    if( tr_bencDictFindDict( response, "arguments", &args ) )
1628        tr_bencDictFindBool( args, "port-is-open", &is_open );
1629
1630    core_emit_port_tested( core, is_open );
1631}
1632
1633void
1634gtr_core_port_test( TrCore * core )
1635{
1636    char buf[64];
1637    const int tag = nextTag++;
1638    g_snprintf( buf, sizeof( buf ), "{ \"method\": \"port-test\", \"tag\": %d }", tag );
1639    core_send_rpc_request( core, buf, tag, on_port_test_response, NULL );
1640}
1641
1642/***
1643****  Updating a blocklist via RPC
1644***/
1645
1646static void
1647on_blocklist_response( TrCore * core, tr_benc * response, gpointer data UNUSED )
1648{
1649    tr_benc * args;
1650    int64_t ruleCount = -1;
1651
1652    if( tr_bencDictFindDict( response, "arguments", &args ) )
1653        tr_bencDictFindInt( args, "blocklist-size", &ruleCount );
1654
1655    if( ruleCount > 0 )
1656        gtr_pref_int_set( "blocklist-date", tr_time( ) );
1657
1658    core_emit_blocklist_udpated( core, ruleCount );
1659}
1660
1661void
1662gtr_core_blocklist_update( TrCore * core )
1663{
1664    char buf[64];
1665    const int tag = nextTag++;
1666    g_snprintf( buf, sizeof( buf ), "{ \"method\": \"blocklist-update\", \"tag\": %d }", tag );
1667    core_send_rpc_request( core, buf, tag, on_blocklist_response, NULL );
1668}
1669
1670/***
1671****
1672***/
1673
1674void
1675gtr_core_exec_json( TrCore * core, const char * json )
1676{
1677    const int tag = nextTag++;
1678    core_send_rpc_request( core, json, tag, NULL, NULL );
1679}
1680
1681void
1682gtr_core_exec( TrCore * core, const tr_benc * top )
1683{
1684    char * json = tr_bencToStr( top, TR_FMT_JSON_LEAN, NULL );
1685    gtr_core_exec_json( core, json );
1686    tr_free( json );
1687}
1688
1689/***
1690****
1691***/
1692
1693size_t
1694gtr_core_get_torrent_count( TrCore * core )
1695{
1696    return gtk_tree_model_iter_n_children( core_raw_model( core ), NULL );
1697}
1698
1699size_t
1700gtr_core_get_active_torrent_count( TrCore * core )
1701{
1702    GtkTreeIter iter;
1703    size_t activeCount = 0;
1704    GtkTreeModel * model = core_raw_model( core );
1705
1706    if( gtk_tree_model_iter_nth_child( model, &iter, NULL, 0 ) ) do
1707    {
1708        int activity;
1709        gtk_tree_model_get( model, &iter, MC_ACTIVITY, &activity, -1 );
1710
1711        if( activity != TR_STATUS_STOPPED )
1712            ++activeCount;
1713    }
1714    while( gtk_tree_model_iter_next( model, &iter ) );
1715
1716    return activeCount;
1717}
1718
1719tr_torrent *
1720gtr_core_find_torrent( TrCore * core, int id )
1721{
1722    tr_session * session;
1723    tr_torrent * tor = NULL;
1724
1725    if(( session = gtr_core_session( core )))
1726        tor = tr_torrentFindFromId( session, id );
1727
1728    return tor;
1729}
1730
1731void
1732gtr_core_open_folder( TrCore * core, int torrent_id )
1733{
1734    const tr_torrent * tor = gtr_core_find_torrent( core, torrent_id );
1735
1736    if( tor != NULL )
1737    {
1738        const gboolean single = tr_torrentInfo( tor )->fileCount == 1;
1739        const char * currentDir = tr_torrentGetCurrentDir( tor );
1740        if( single )
1741            gtr_open_file( currentDir );
1742        else {
1743            char * path = g_build_filename( currentDir, tr_torrentName( tor ), NULL );
1744            gtr_open_file( path );
1745            g_free( path );
1746        }
1747    }
1748}
Note: See TracBrowser for help on using the repository browser.