source: trunk/gtk/tr_core.c @ 3942

Last change on this file since 3942 was 3942, checked in by charles, 15 years ago

fix bug in gtk client that caused torrent changes to show up too slowly in the GUI

  • Property svn:keywords set to Date Rev Author Id
File size: 19.3 KB
Line 
1/******************************************************************************
2 * $Id: tr_core.c 3942 2007-11-23 15:36:31Z charles $
3 *
4 * Copyright (c) 2007 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 <inttypes.h>
26#include <stdio.h>
27#include <string.h>
28
29#include <gtk/gtk.h>
30#include <glib/gi18n.h>
31
32#include <libtransmission/transmission.h>
33#include <libtransmission/utils.h>
34
35#include "conf.h"
36#include "tr_core.h"
37#include "tr_prefs.h"
38#include "tr_torrent.h"
39#include "util.h"
40
41static void
42tr_core_marshal_err( GClosure * closure, GValue * ret SHUTUP, guint count,
43                     const GValue * vals, gpointer hint SHUTUP,
44                     gpointer marshal )
45{
46    typedef void (*TRMarshalErr)
47        ( gpointer, enum tr_core_err, const char *, gpointer );
48    TRMarshalErr     callback;
49    GCClosure      * cclosure = (GCClosure*) closure;
50    enum tr_core_err errcode;
51    const char     * errstr;
52    gpointer         inst, gdata;
53
54    g_return_if_fail( 3 == count );
55
56    inst    = g_value_peek_pointer( vals );
57    errcode = g_value_get_int( vals + 1 );
58    errstr  = g_value_get_string( vals + 2 );
59    gdata   = closure->data;
60
61    callback = (TRMarshalErr) ( NULL == marshal ?
62                                cclosure->callback : marshal );
63    callback( inst, errcode, errstr, gdata );
64}
65
66static void
67tr_core_marshal_prompt( GClosure * closure, GValue * ret SHUTUP, guint count,
68                        const GValue * vals, gpointer hint SHUTUP,
69                        gpointer marshal )
70{
71    typedef void (*TRMarshalPrompt)
72        ( gpointer, GList *, enum tr_torrent_action, gboolean, gpointer );
73    TRMarshalPrompt        callback;
74    GCClosure            * cclosure = (GCClosure*) closure;
75    GList                * paths;
76    enum tr_torrent_action action;
77    gboolean               paused;
78    gpointer               inst, gdata;
79
80    g_return_if_fail( 4 == count );
81
82    inst    = g_value_peek_pointer( vals );
83    paths   = g_value_get_pointer( vals + 1 );
84    action  = g_value_get_int( vals + 2 );
85    paused  = g_value_get_boolean( vals + 3 );
86    gdata   = closure->data;
87
88    callback = (TRMarshalPrompt) ( NULL == marshal ?
89                                   cclosure->callback : marshal );
90    callback( inst, paths, action, paused, gdata );
91}
92
93static void
94tr_core_marshal_data( GClosure * closure, GValue * ret SHUTUP, guint count,
95                      const GValue * vals, gpointer hint SHUTUP,
96                      gpointer marshal )
97{
98    typedef void (*TRMarshalPrompt)
99        ( gpointer, uint8_t *, size_t, gboolean, gpointer );
100    TRMarshalPrompt        callback;
101    GCClosure            * cclosure = (GCClosure*) closure;
102    uint8_t              * data;
103    size_t                 size;
104    gboolean               paused;
105    gpointer               inst, gdata;
106
107    g_return_if_fail( 4 == count );
108
109    inst    = g_value_peek_pointer( vals );
110    data    = (uint8_t *) g_value_get_string( vals + 1 );
111    size    = g_value_get_uint( vals + 2 );
112    paused  = g_value_get_boolean( vals + 3 );
113    gdata   = closure->data;
114
115    callback = (TRMarshalPrompt) ( NULL == marshal ?
116                                   cclosure->callback : marshal );
117    callback( inst, data, size, paused, gdata );
118}
119
120static void
121tr_core_dispose( GObject * obj )
122{
123    TrCore       * self = (TrCore *) obj;
124    GObjectClass * parent;
125
126    if( self->disposed )
127        return;
128
129    self->disposed = TRUE;
130    pref_save( NULL );
131    parent = g_type_class_peek( g_type_parent( TR_CORE_TYPE ) );
132    parent->dispose( obj );
133}
134
135
136static void
137tr_core_class_init( gpointer g_class, gpointer g_class_data SHUTUP )
138{
139    GObjectClass * gobject_class;
140    TrCoreClass  * core_class;
141
142    gobject_class = G_OBJECT_CLASS( g_class );
143    gobject_class->dispose = tr_core_dispose;
144
145    core_class = TR_CORE_CLASS( g_class );
146    core_class->errsig = g_signal_new( "error", G_TYPE_FROM_CLASS( g_class ),
147                                       G_SIGNAL_RUN_LAST, 0, NULL, NULL,
148                                       tr_core_marshal_err, G_TYPE_NONE,
149                                       2, G_TYPE_INT, G_TYPE_STRING );
150    core_class->promptsig = g_signal_new( "directory-prompt",
151                                          G_TYPE_FROM_CLASS( g_class ),
152                                          G_SIGNAL_RUN_LAST, 0, NULL, NULL,
153                                          tr_core_marshal_prompt, G_TYPE_NONE,
154                                          3, G_TYPE_POINTER, G_TYPE_INT,
155                                          G_TYPE_BOOLEAN );
156    core_class->promptdatasig = g_signal_new( "directory-prompt-data",
157                                              G_TYPE_FROM_CLASS( g_class ),
158                                              G_SIGNAL_RUN_LAST, 0, NULL, NULL,
159                                              tr_core_marshal_data,
160                                              G_TYPE_NONE, 3, G_TYPE_STRING,
161                                              G_TYPE_UINT, G_TYPE_BOOLEAN );
162    core_class->quitsig = g_signal_new( "quit", G_TYPE_FROM_CLASS( g_class ),
163                                        G_SIGNAL_RUN_LAST, 0, NULL, NULL,
164                                        g_cclosure_marshal_VOID__VOID,
165                                        G_TYPE_NONE, 0 );
166    core_class->prefsig = g_signal_new( "prefs-changed",
167                                        G_TYPE_FROM_CLASS( g_class ),
168                                        G_SIGNAL_RUN_LAST, 0, NULL, NULL,
169                                        g_cclosure_marshal_VOID__STRING,
170                                        G_TYPE_NONE, 1, G_TYPE_STRING );
171}
172
173static int
174compareProgress( GtkTreeModel   * model,
175                 GtkTreeIter    * a,
176                 GtkTreeIter    * b,
177                 gpointer         user_data UNUSED )
178{
179    int ia, ib;
180    gfloat rateUpA, rateUpB;
181    gfloat rateDownA, rateDownB;
182    gfloat percentDoneA, percentDoneB;
183    guint64 uploadedEverA, uploadedEverB;
184
185    gtk_tree_model_get( model, a, MC_PROG_D, &percentDoneA,
186                                  MC_DRATE, &rateDownA,
187                                  MC_URATE, &rateUpA,
188                                  MC_UP, &uploadedEverA,
189                                  -1 );
190    gtk_tree_model_get( model, b, MC_PROG_D, &percentDoneB,
191                                  MC_DRATE, &rateDownB,
192                                  MC_URATE, &rateUpB,
193                                  MC_UP, &uploadedEverB,
194                                  -1 );
195
196    ia = (int)( rateUpA + rateDownA );
197    ib = (int)( rateUpB + rateDownB );
198    if( ia != ib )
199        return ia - ib;
200
201    ia = (int)( 100.0 * percentDoneA );
202    ib = (int)( 100.0 * percentDoneB );
203    if( ia != ib )
204        return ia - ib;
205
206    if( uploadedEverA != uploadedEverB )
207        return uploadedEverA < uploadedEverB ? -1 : 1;
208
209    return 0;
210}
211
212#define STR_REVERSE "reverse-"
213#define STR_PROGRESS "progress"
214#define STR_NAME "name"
215
216static void
217onSortColumnChanged( GtkTreeSortable * sortable, gpointer unused UNUSED )
218{
219    int column;
220    GtkSortType order;
221    if( gtk_tree_sortable_get_sort_column_id( sortable, &column, &order ) )
222    {
223        GString * gstr = g_string_new( NULL );
224        switch( column ) {
225            case MC_PROG_D: g_string_assign( gstr, STR_PROGRESS ); break;
226            default: g_string_assign( gstr, STR_NAME ); break;
227        }
228        if( order == GTK_SORT_DESCENDING )
229            g_string_prepend( gstr, STR_REVERSE );
230        pref_string_set( PREF_KEY_SORT_COLUMN, gstr->str );
231        g_string_free( gstr, TRUE );
232    }
233}
234
235void
236tr_core_set_sort_column_from_prefs( TrCore * core )
237{
238    char * val = pref_string_get( PREF_KEY_SORT_COLUMN );
239    char * freeme = val;
240    gint column;
241    GtkSortType order = GTK_SORT_ASCENDING;
242    if( g_str_has_prefix( val, STR_REVERSE ) ) {
243        order = GTK_SORT_DESCENDING;
244        val += strlen( STR_REVERSE );
245    }
246    if( !strcmp( val, STR_PROGRESS ) )
247        column = MC_PROG_D;
248    else /* default */
249        column = MC_NAME;
250    gtk_tree_sortable_set_sort_column_id ( GTK_TREE_SORTABLE( core->model ), column, order );
251    g_free( freeme );
252}
253
254static void
255tr_core_init( GTypeInstance * instance, gpointer g_class SHUTUP )
256{
257    TrCore * self = (TrCore *) instance;
258    GtkListStore * store;
259
260    /* column types for the model used to store torrent information */
261    /* keep this in sync with the enum near the bottom of tr_core.h */
262    GType types[] =
263    {
264        /* info->name, info->totalSize, info->hashString, status, */
265        G_TYPE_STRING, G_TYPE_UINT64,   G_TYPE_STRING,    G_TYPE_INT,
266        /* error,   errorString,   percentComplete, percentDone,  rateDownload, rateUpload, */
267        G_TYPE_INT, G_TYPE_STRING, G_TYPE_FLOAT,    G_TYPE_FLOAT, G_TYPE_FLOAT, G_TYPE_FLOAT,
268        /* eta,     peersConnected, peersUploading, peersDownloading, seeders, */
269        G_TYPE_INT, G_TYPE_INT,     G_TYPE_INT,     G_TYPE_INT,       G_TYPE_INT,
270        /* leechers, completedFromTracker, downloaded,    uploaded */
271        G_TYPE_INT,  G_TYPE_INT,           G_TYPE_UINT64, G_TYPE_UINT64,
272        /* ratio,      left,          TrTorrent object, ID for IPC */
273        G_TYPE_FLOAT,  G_TYPE_UINT64, TR_TORRENT_TYPE,  G_TYPE_INT,
274    };
275
276#ifdef REFDBG
277    fprintf( stderr, "core    %p init\n", self );
278#endif
279
280    /* create the model used to store torrent data */
281    g_assert( ALEN( types ) == MC_ROW_COUNT );
282    store = gtk_list_store_newv( MC_ROW_COUNT, types );
283    g_signal_connect( store, "sort-column-changed", G_CALLBACK(onSortColumnChanged), NULL );
284
285    gtk_tree_sortable_set_sort_func( GTK_TREE_SORTABLE(store),
286                                     MC_PROG_D,
287                                     compareProgress,
288                                     NULL, NULL );
289
290    self->model    = GTK_TREE_MODEL( store );
291    self->handle   = tr_init( "gtk" );
292    self->nextid   = 1;
293    self->quitting = FALSE;
294    self->disposed = FALSE;
295}
296
297GType
298tr_core_get_type( void )
299{
300    static GType type = 0;
301
302    if( 0 == type )
303    {
304        static const GTypeInfo info =
305        {
306            sizeof( TrCoreClass ),
307            NULL,                       /* base_init */
308            NULL,                       /* base_finalize */
309            tr_core_class_init,         /* class_init */
310            NULL,                       /* class_finalize */
311            NULL,                       /* class_data */
312            sizeof( TrCore ),
313            0,                          /* n_preallocs */
314            tr_core_init,               /* instance_init */
315            NULL,
316        };
317        type = g_type_register_static( G_TYPE_OBJECT, "TrCore", &info, 0 );
318    }
319
320    return type;
321}
322
323/**
324***
325**/
326
327TrCore *
328tr_core_new( void )
329{
330    return g_object_new( TR_CORE_TYPE, NULL );
331}
332
333GtkTreeModel *
334tr_core_model( TrCore * self )
335{
336    g_return_val_if_fail (TR_IS_CORE(self), NULL);
337
338    return self->disposed ? NULL : self->model;
339}
340
341tr_handle *
342tr_core_handle( TrCore * self )
343{
344    g_return_val_if_fail (TR_IS_CORE(self), NULL);
345
346    return self->disposed ? NULL : self->handle;
347}
348
349static void
350tr_core_insert( TrCore * self, TrTorrent * tor )
351{
352    GtkTreeIter iter;
353    const tr_info * inf;
354
355    gtk_list_store_append( GTK_LIST_STORE( self->model ), &iter );
356    inf = tr_torrent_info( tor );
357    gtk_list_store_set( GTK_LIST_STORE( self->model ), &iter,
358                        MC_NAME,    inf->name,
359                        MC_SIZE,    inf->totalSize,
360                        MC_HASH,    inf->hashString,
361                        MC_TORRENT, tor,
362                        MC_ID,      self->nextid,
363                        -1);
364    g_object_unref( tor );
365    self->nextid++;
366}
367
368int
369tr_core_load( TrCore * self, gboolean paused )
370{
371    int i;
372    int count = 0;
373    tr_torrent ** torrents;
374    char * path;
375
376    TR_IS_CORE( self );
377
378    path = getdownloaddir( );
379
380    torrents = tr_loadTorrents ( self->handle, path, paused, &count );
381    for( i=0; i<count; ++i )
382        tr_core_insert( self, tr_torrent_new_preexisting( torrents[i] ) );
383    tr_free( torrents );
384
385    g_free( path );
386    return count;
387}
388
389gboolean
390tr_core_add( TrCore * self, const char * path, enum tr_torrent_action act,
391             gboolean paused )
392{
393    GList * list;
394    int     ret;
395
396    TR_IS_CORE( self );
397
398    list = g_list_append( NULL, (void *) path );
399    ret  = tr_core_add_list( self, list, act, paused );
400    g_list_free( list );
401
402    return 1 == ret;
403}
404
405static void
406tr_core_errsig( TrCore * self, enum tr_core_err type, const char * msg )
407{
408    TrCoreClass * class;
409
410    class = g_type_class_peek( TR_CORE_TYPE );
411    g_signal_emit( self, class->errsig, 0, type, msg );
412}
413
414gboolean
415tr_core_add_dir( TrCore * self, const char * path, const char * dir,
416                 enum tr_torrent_action act, gboolean paused )
417{
418    TrTorrent * tor;
419    char      * errstr;
420
421    TR_IS_CORE( self );
422
423    errstr = NULL;
424    tor = tr_torrent_new( self->handle, path, dir, act, paused, &errstr );
425    if( NULL == tor )
426    {
427        tr_core_errsig( self, TR_CORE_ERR_ADD_TORRENT, errstr );
428        g_free( errstr );
429        return FALSE;
430    }
431    g_assert( NULL == errstr );
432
433    tr_core_insert( self, tor );
434
435    return TRUE;
436}
437
438int
439tr_core_add_list( TrCore * self, GList * paths, enum tr_torrent_action act,
440                  gboolean paused )
441{
442    char * dir;
443    int count;
444
445    TR_IS_CORE( self );
446
447    if( pref_flag_get( PREF_KEY_DIR_ASK ) )
448    {
449        TrCoreClass * class = g_type_class_peek( TR_CORE_TYPE );
450        g_signal_emit( self, class->promptsig, 0, paths, act, paused );
451        return 0;
452    }
453
454    dir = getdownloaddir();
455    count = 0;
456    for( ; paths; paths=paths->next )
457        if( tr_core_add_dir( self, paths->data, dir, act, paused ) )
458            count++;
459
460    g_free( dir );
461    return count;
462}
463
464gboolean
465tr_core_add_data( TrCore * self, uint8_t * data, size_t size, gboolean paused )
466{
467    gboolean ret;
468    char * path;
469    TR_IS_CORE( self );
470
471    if( pref_flag_get( PREF_KEY_DIR_ASK ) )
472    {
473        TrCoreClass * class = g_type_class_peek( TR_CORE_TYPE );
474        g_signal_emit( self, class->promptdatasig, 0, data, size, paused );
475        return FALSE;
476    }
477
478    path = getdownloaddir( );
479    ret = tr_core_add_data_dir( self, data, size, path, paused );
480    g_free( path );
481    return ret;
482}
483
484gboolean
485tr_core_add_data_dir( TrCore * self, uint8_t * data, size_t size,
486                      const char * dir, gboolean paused )
487{
488    TrTorrent * tor;
489    char      * errstr = NULL;
490
491    TR_IS_CORE( self );
492
493    tor = tr_torrent_new_with_data( self->handle, data, size, dir,
494                                    paused, &errstr );
495    if( NULL == tor )
496    {
497        tr_core_errsig( self, TR_CORE_ERR_ADD_TORRENT, errstr );
498        g_free( errstr );
499        return FALSE;
500    }
501    g_assert( NULL == errstr );
502
503    tr_core_insert( self, tor );
504
505    return TRUE;
506}
507
508void
509tr_core_torrents_added( TrCore * self )
510{
511    TR_IS_CORE( self );
512
513    tr_core_update( self );
514    tr_core_errsig( self, TR_CORE_ERR_NO_MORE_TORRENTS, NULL );
515}
516
517void
518tr_core_delete_torrent( TrCore * self, GtkTreeIter * iter )
519{
520    TrTorrent * tor;
521
522    TR_IS_CORE( self );
523
524    gtk_tree_model_get( self->model, iter, MC_TORRENT, &tor, -1 );
525    gtk_list_store_remove( GTK_LIST_STORE( self->model ), iter );
526    tr_torrentRemoveSaved( tr_torrent_handle( tor ) );
527
528    tr_torrent_sever( tor );
529}
530
531static gboolean
532update_foreach( GtkTreeModel * model,
533                GtkTreePath  * path UNUSED,
534                GtkTreeIter  * iter,
535                gpointer       data UNUSED)
536{
537    TrTorrent * tor;
538    const tr_stat * st;
539
540    gtk_tree_model_get( model, iter, MC_TORRENT, &tor, -1 );
541    st = tr_torrent_stat( tor );
542    tr_torrent_check_seeding_cap ( tor );
543    g_object_unref( tor );
544
545    gtk_list_store_set( GTK_LIST_STORE( model ), iter,
546                        MC_STAT,        st->status,
547                        MC_ERR,         st->error,
548                        MC_TERR,        st->errorString,
549                        MC_PROG_C,      st->percentComplete,
550                        MC_PROG_D,      st->percentDone,
551                        MC_DRATE,       st->rateDownload,
552                        MC_URATE,       st->rateUpload,
553                        MC_ETA,         st->eta,
554                        MC_PEERS,       st->peersConnected,
555                        MC_UPEERS,      st->peersGettingFromUs,
556                        MC_DPEERS,      st->peersSendingToUs,
557                        MC_SEED,        st->seeders,
558                        MC_LEECH,       st->leechers,
559                        MC_DONE,        st->completedFromTracker,
560                        MC_DOWN,        st->downloadedEver,
561                        MC_UP,          st->uploadedEver,
562                        MC_RATIO,       st->ratio,
563                        MC_LEFT,        st->leftUntilDone,
564                        -1 );
565
566    return FALSE;
567}
568
569void
570tr_core_update( TrCore * self )
571{
572    int column;
573    GtkSortType order;
574    GtkTreeSortable * sortable;
575
576    /* pause sorting */
577    sortable = GTK_TREE_SORTABLE( self->model );
578    gtk_tree_sortable_get_sort_column_id( sortable, &column, &order );
579    gtk_tree_sortable_set_sort_column_id( sortable, GTK_TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID, order );
580
581    /* refresh the model */
582    gtk_tree_model_foreach( self->model, update_foreach, NULL );
583
584    /* resume sorting */
585    gtk_tree_sortable_set_sort_column_id( sortable, column, order );
586}
587
588void
589tr_core_quit( TrCore * self )
590{
591    TrCoreClass * class;
592
593    TR_IS_CORE( self );
594
595    class = g_type_class_peek( TR_CORE_TYPE );
596    g_signal_emit( self, class->quitsig, 0 );
597}
598
599/**
600***  Prefs
601**/
602
603static void
604commitPrefsChange( TrCore * self, const char * key )
605{
606    TrCoreClass * class = g_type_class_peek( TR_CORE_TYPE );
607    pref_save( NULL );
608    g_signal_emit( self, class->prefsig, 0, key );
609}
610
611void
612tr_core_set_pref( TrCore * self, const char * key, const char * newval )
613{
614    char * oldval = pref_string_get( key );
615    if( tr_strcmp( oldval, newval ) )
616    {
617        pref_string_set( key, newval );
618        commitPrefsChange( self, key );
619    }
620    g_free( oldval );
621}
622
623void
624tr_core_set_pref_bool( TrCore * self, const char * key, gboolean newval )
625{
626    const gboolean oldval = pref_flag_get( key );
627    if( oldval != newval )
628    {
629        pref_flag_set( key, newval );
630        commitPrefsChange( self, key );
631    }
632}
633
634void
635tr_core_set_pref_int( TrCore * self, const char * key, int newval )
636{
637    const int oldval = pref_int_get( key );
638    if( oldval != newval )
639    {
640        pref_int_set( key, newval );
641        commitPrefsChange( self, key );
642    }
643}
Note: See TracBrowser for help on using the repository browser.