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

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

(trunk gtk) simplify the gtk+ client's watchdir code

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