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

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

(trunk gtk) first cut at using GApplication. This lets glib replace hundreds of lines of homegrown code. Whee!

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