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

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

(trunk gtk) in torrent-cell-renderer and tr-core, use the _parent_class field generated by G_DEFINE_TYPE.

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