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