source: trunk/wx/torrent-list.cc @ 6335

Last change on this file since 6335 was 6335, checked in by charles, 13 years ago

missed a few `snprintf' calls in r6334

  • Property svn:keywords set to Date Rev Author Id
File size: 20.7 KB
Line 
1/*
2 * Xmission - a cross-platform bittorrent client
3 * Copyright (C) 2007 Charles Kerr <charles@rebelbase.com>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; version 2 of the License.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
17 *
18 * $Id: torrent-list.cc 6335 2008-07-15 18:24:18Z charles $
19 */
20
21#include <wx/intl.h>
22#include <torrent-list.h>
23
24namespace
25{
26    typedef std::vector<tr_torrent*> torrents_t;
27
28    enum
29    {
30        COL_POSITION,
31        COL_PERCENT_DONE,
32        COL_DOWNLOAD_SPEED,
33        COL_ETA,
34        COL_HASH,
35        COL_NAME,
36        COL_PEERS,
37        COL_RATIO,
38        COL_RECEIVED,
39        COL_REMAINING,
40        COL_SEEDS,
41        COL_SENT,
42        COL_SIZE,
43        COL_STATE,
44        COL_STATUS,
45        COL_TOTAL,
46        COL_UPLOAD_SPEED,
47        N_COLS
48    };
49
50    const wxString columnKeys[N_COLS] =
51    {
52        _T("position"),
53        _T("done"),
54        _T("download-speed"),
55        _T("eta"),
56        _T("hash"),
57        _T("name"),
58        _T("peers"),
59        _T("ratio"),
60        _T("received"),
61        _T("remaining"),
62        _T("seeds"),
63        _T("sent"),
64        _T("size"),
65        _T("state"),
66        _T("status"),
67        _T("total"),
68        _T("upload-speed")
69    };
70
71    int getTorrentColumn( const wxString& key )
72    {
73        typedef std::map<wxString,int> string2key_t;
74        static string2key_t columns;
75
76        if( columns.empty() )
77        {
78            columns[_T("position")]        = COL_POSITION;
79            columns[_T("done")]            = COL_PERCENT_DONE;
80            columns[_T("download-speed")]  = COL_DOWNLOAD_SPEED;
81            columns[_T("eta")]             = COL_ETA;
82            columns[_T("hash")]            = COL_HASH;
83            columns[_T("name")]            = COL_NAME;
84            columns[_T("peers")]           = COL_PEERS;
85            columns[_T("ratio")]           = COL_RATIO;
86            columns[_T("received")]        = COL_RECEIVED;
87            columns[_T("remaining")]       = COL_REMAINING;
88            columns[_T("seeds")]           = COL_SEEDS; 
89            columns[_T("sent")]            = COL_SENT; 
90            columns[_T("size")]            = COL_SIZE; 
91            columns[_T("state")]           = COL_STATE; 
92            columns[_T("status")]          = COL_STATUS; 
93            columns[_T("total")]           = COL_TOTAL; 
94            columns[_T("upload-speed")]    = COL_UPLOAD_SPEED;
95        }
96
97        int i = -1;
98        string2key_t::const_iterator it = columns.find( key );
99        if( it != columns.end() )
100            i = it->second;
101
102        return i;
103    }
104
105    typedef std::vector<int> int_v;
106
107    int_v getTorrentColumns( wxConfig * config )
108    {
109        const wxString key = _T("torrent-list-columns");
110        wxString columnStr;
111        if( !config->Read( key, &columnStr, _T("name|download-speed|upload-speed|eta|peers|size|done|status|seeds") ) )
112            config->Write( key, columnStr );
113
114        int_v cols;
115        while( !columnStr.IsEmpty() )
116        {
117            const wxString key = columnStr.BeforeFirst(_T('|'));
118            columnStr.Remove( 0, key.Len() + 1 );
119            cols.push_back( getTorrentColumn( key ) );
120        }
121        return cols;
122    }
123
124    int bestDecimal( double num ) {
125        if ( num < 10 ) return 2;
126        if ( num < 100 ) return 1;
127        return 0;
128    }
129
130    wxString toWxStr( const std::string& s )
131    {
132        return wxString( s.c_str(), wxConvUTF8 );
133    }
134
135    wxString toWxStr( const char * s )
136    {
137        return wxString( s, wxConvUTF8 );
138    }
139
140    wxString getReadableSize( uint64_t size )
141    {
142        int i;
143        static const char *sizestrs[] = { "B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB" };
144        for ( i=0; size>>10; ++i )
145            size = size >> 10;
146        char buf[512];
147        tr_snprintf( buf, sizeof(buf), "%.*f %s", bestDecimal(size), (double)size, sizestrs[i] );
148        return toWxStr( buf );
149    }
150
151    wxString getReadableSize( float f )
152    {
153        return getReadableSize( (uint64_t)f );
154    }
155
156    wxString getReadableSpeed( float kib_sec )
157    {
158        wxString xstr = getReadableSize(1024*kib_sec);
159        xstr += _T("/s");
160        return xstr;
161    }
162
163    wxString getReadableTime( int i /*seconds*/ )  /*FIXME*/
164    {
165        const int s = i % 60; i /= 60;
166        const int m = i % 60; i /= 60;
167        const int h = i;
168        return wxString::Format( _T("%d:%02d:%02d"), h, m, s );
169    }
170}
171
172enum
173{
174    TORRENT_LIST_CTRL = 1000
175};
176
177BEGIN_EVENT_TABLE(TorrentListCtrl, wxListCtrl)
178    EVT_LIST_COL_CLICK( TORRENT_LIST_CTRL, TorrentListCtrl::OnSort )
179    EVT_LIST_ITEM_SELECTED( TORRENT_LIST_CTRL, TorrentListCtrl::OnItemSelected )
180    EVT_LIST_ITEM_DESELECTED( TORRENT_LIST_CTRL, TorrentListCtrl::OnItemDeselected )
181END_EVENT_TABLE()
182
183
184
185TorrentListCtrl :: TorrentListCtrl( tr_handle       * handle,
186                                    wxConfig        * config,
187                                    wxWindow        * parent,
188                                    const wxPoint   & pos,
189                                    const wxSize    & size):
190    wxListCtrl( parent, TORRENT_LIST_CTRL, pos, size, wxLC_REPORT|wxLC_HRULES ),
191    myHandle( handle ),
192    myConfig( config )
193{
194    wxString sortColStr;
195    myConfig->Read( _T("torrent-sort-column"), &sortColStr, columnKeys[COL_NAME] );
196    prevSortCol = getTorrentColumn( sortColStr );
197    bool descending;
198    myConfig->Read( _T("torrent-sort-is-descending"), &descending, FALSE );
199    if( descending )
200        prevSortCol = -prevSortCol;
201    Rebuild ();
202}
203
204TorrentListCtrl :: ~TorrentListCtrl()
205{
206}
207
208void
209TorrentListCtrl :: SetCell( int item, int column, const wxString& xstr )
210{
211    wxListItem i;
212    i.SetId( item );
213    i.SetColumn( column );
214    i.SetMask( wxLIST_MASK_TEXT );
215    GetItem( i );
216    if( i.GetText() != xstr )
217        SetItem( item, column, xstr );
218}
219
220void
221TorrentListCtrl :: RefreshTorrent( tr_torrent   * tor,
222                                   int            myTorrents_index,
223                                   const int_v  & cols )
224{
225    int row = -1;
226    int col = 0;
227    char buf[512];
228    std::string str;
229    const tr_stat * s = getStat( tor );
230    const tr_info * info = tr_torrentInfo( tor );
231
232    for( int_v::const_iterator it(cols.begin()), end(cols.end()); it!=end; ++it )
233    {
234        wxString xstr;
235
236        switch( *it )
237        {
238            case COL_POSITION:
239                tr_snprintf( buf, sizeof(buf), "%d", 666 );
240                xstr = toWxStr( buf );
241                break;
242
243            case COL_PERCENT_DONE:
244                tr_snprintf( buf, sizeof(buf), "%d%%", (int)(s->percentDone*100.0) );
245                xstr = toWxStr( buf );
246                break;
247
248            case COL_DOWNLOAD_SPEED:
249                if( s->rateDownload > 0.01 )
250                    xstr = getReadableSpeed( s->rateDownload );
251                else
252                    xstr.Clear( );
253                break;
254
255            case COL_ETA:
256                if( (int)(s->percentDone*100) >= 100 )
257                    xstr.Clear ();
258                else if( s->eta < 0 )
259                    xstr = toWxStr( "\xE2\x88\x9E" ); /* infinity, in utf-8 */
260                else
261                    xstr = getReadableTime( s->eta );
262                break;
263               
264            case COL_HASH:
265                xstr = toWxStr( info->hashString );
266                break;
267
268            case COL_NAME:
269                xstr = toWxStr( info->name );
270                break;
271
272            case COL_PEERS:
273                xstr = wxString::Format( _("%d (%d)"), s->peersConnected, s->leechers );
274                break;
275
276            case COL_RATIO:
277                xstr = wxString::Format( _T("%%%d"), (int)(s->uploadedEver / (double)s->downloadedEver) );
278                break;
279
280            case COL_RECEIVED:
281                xstr = getReadableSize( s->downloadedEver );
282                break;
283
284            case COL_REMAINING:
285                xstr = getReadableSize( s->leftUntilDone );
286                break;
287
288            case COL_SEEDS:
289                if( s->seeders > 0 )
290                    xstr = wxString::Format( _T("%d"), s->seeders );
291                else
292                    xstr.Clear ();
293                break;
294
295            case COL_SENT:
296                xstr = getReadableSize( s->uploadedEver );
297                break;
298
299            case COL_SIZE:
300                xstr = getReadableSize( info->totalSize );
301                break;
302
303            case COL_STATE: /* FIXME: divine the meaning of these two columns */
304            case COL_STATUS:
305                switch( s->status ) {
306                    case TR_STATUS_STOPPED:     xstr = _("Stopped"); break;
307                    case TR_STATUS_CHECK:       xstr = wxString::Format ( _("Checking Files (%.0f)"), s->recheckProgress );  break;
308                    case TR_STATUS_CHECK_WAIT:  xstr = _("Waiting to Check"); break;
309                    case TR_STATUS_DOWNLOAD:    xstr = _("Downloading"); break;
310                    case TR_STATUS_SEED:        xstr = _("Seeding"); break;
311                    default: assert( 0 );
312                }
313                break;
314
315            case COL_TOTAL:
316                xstr = _T("Fixme");
317                break;
318
319            case COL_UPLOAD_SPEED:
320                if( s->rateUpload > 0.01 )
321                    xstr = getReadableSpeed( s->rateUpload );
322                else
323                    xstr.Clear( );
324                break;
325
326            default:
327                xstr = _T("Fixme");
328        }
329
330        if( col )
331            SetCell( row, col++, xstr );
332        else {
333            // first column... find the right row to put the info in.
334            // if the torrent's in the list already, update that row.
335            // otherwise, add a new row.
336            if( row < 0 ) {
337                str2int_t::const_iterator it = myHashToItem.find( info->hashString );
338                if( it != myHashToItem.end() ) {
339                    row = it->second;
340                }
341            }
342            if( row >= 0 ) {
343                SetCell( row, col++, xstr );
344            }
345            else {
346                row = InsertItem( GetItemCount(), xstr );
347                col = 1;
348                myHashToItem[info->hashString] = row;
349                SetItemData( row, myTorrents_index );
350            }
351        }
352    }
353}
354
355/***
356****
357***/
358
359void
360TorrentListCtrl :: OnSort( wxListEvent& event )
361{
362    const int_v  cols = getTorrentColumns( myConfig );
363    const int key = cols[ event.GetColumn() ];
364    Sort( key );
365}
366
367void
368TorrentListCtrl :: OnItemSelected( wxListEvent& WXUNUSED(event) )
369{
370    std::set<tr_torrent*> sel;
371    long item = -1;
372    for ( ;; ) {
373        item = GetNextItem(item, wxLIST_NEXT_ALL, wxLIST_STATE_SELECTED);
374        if ( item == -1 )
375            break;
376        sel.insert( myTorrents[GetItemData(item)] );
377    }
378    fire_selection_changed( sel );
379}
380
381void
382TorrentListCtrl :: OnItemDeselected( wxListEvent& event )
383{
384    OnItemSelected( event );
385}
386
387/***
388****
389***/
390
391static TorrentListCtrl * uglyHack = NULL;
392
393int wxCALLBACK
394TorrentListCtrl :: Compare( long item1, long item2, long sortData )
395{
396    TorrentListCtrl * self = uglyHack;
397    tr_torrent * a = self->myTorrents[item1];
398    tr_torrent * b = self->myTorrents[item2];
399    const tr_info* ia = tr_torrentInfo( a );
400    const tr_info* ib = tr_torrentInfo( b );
401    const tr_stat* sa = self->getStat( a );
402    const tr_stat* sb = self->getStat( b );
403    int ret = 0;
404
405    switch( abs(sortData) )
406    {
407        case COL_POSITION:
408            ret = item1 - item2;
409            break;
410
411        case COL_PERCENT_DONE:
412            if( sa->percentDone < sb->percentDone )
413                ret = -1;
414            else if( sa->percentDone > sb->percentDone )
415                ret =  1;
416            else
417                ret = 0;
418            break;
419
420        case COL_DOWNLOAD_SPEED:
421            if( sa->rateDownload < sb->rateDownload )
422                ret = -1;
423            else if( sa->rateDownload > sb->rateDownload )
424                ret =  1;
425            else
426                ret = 0;
427            break;
428
429        case COL_ETA:
430            ret = sa->eta - sb->eta;
431            break;
432           
433        case COL_HASH:
434            ret = strcmp( ia->hashString, ib->hashString );
435            break;
436
437        case COL_NAME:
438            ret = strcmp( ia->name, ib->name );
439            break;
440
441        case COL_PEERS:
442            /* FIXME: this is all peers, not just leechers
443            tr_snprintf( buf, sizeof(buf), "%d (%d)", s->peersTotal, s->peersConnected );
444            xstr = toWxStr( buf );*/
445            break;
446
447        case COL_RATIO: {
448            const double ra = sa->uploadedEver / (double)(sa->downloadedEver + 0.01);
449            const double rb = sb->uploadedEver / (double)(sb->downloadedEver + 0.01);
450            if( ra < rb )
451                ret = -1;
452            else if( ra > rb )
453                ret = 1;
454            else
455                ret = 0;
456            break;
457        }
458
459        case COL_RECEIVED:
460            if( sa->downloadedEver < sb->downloadedEver )
461                ret = -1;
462            else if( sa->downloadedEver > sb->downloadedEver )
463                ret = 1;
464            else
465                ret = 0;
466            break;
467
468        case COL_REMAINING:
469            if( sa->leftUntilDone < sb->leftUntilDone )
470                ret = -1;
471            else if( sa->leftUntilDone > sb->leftUntilDone )
472                ret = 1;
473            else
474                ret = 0;
475            break;
476
477        case COL_SEEDS:
478            /*tr_snprintf( buf, sizeof(buf), "%d", s->seeders );
479            xstr = toWxStr( buf );*/
480            break;
481
482        case COL_SENT:
483            if( sa->uploadedEver < sb->uploadedEver )
484                ret = -1;
485            else if( sa->uploadedEver > sb->uploadedEver )
486                ret = 1;
487            else
488                ret = 0;
489            break;
490
491        case COL_SIZE:
492            if( ia->totalSize < ib->totalSize ) ret = -1;
493            else if( ia->totalSize > ib->totalSize ) ret = 1;
494            else ret = 0;
495            break;
496
497        case COL_STATE: /* FIXME */
498        case COL_STATUS:
499            ret = sa->status - sb->status;
500            break;
501
502        case COL_TOTAL:
503            /*xstr = _T("Fixme");*/
504            break;
505
506        case COL_UPLOAD_SPEED:
507            if( sa->rateUpload < sb->rateUpload )
508                ret = -1;
509            else if( sa->rateUpload > sb->rateUpload )
510                ret = 1;
511            else
512                ret = 0;
513            break;
514
515        default:
516            abort ();
517    }
518
519    if( sortData < 0 )
520       ret = -ret;
521
522    return ret;
523}
524
525void
526TorrentListCtrl :: Sort( int column )
527{
528    if( column == prevSortCol )
529        column = -column;
530    prevSortCol = column;
531    Resort ();
532}
533
534bool
535TorrentListCtrl :: IsSorted( ) const
536{
537    bool is_sorted = true;
538    long prevItem=-1, curItem=-1;
539
540    uglyHack = const_cast<TorrentListCtrl*>(this);
541    while( is_sorted )
542    {
543        prevItem = curItem;
544        curItem = GetNextItem( curItem, wxLIST_NEXT_ALL, wxLIST_STATE_DONTCARE );
545        if ( curItem == -1 )
546            break;
547        if( prevItem>=0 && curItem>=0 )
548            if( Compare( prevItem, curItem, prevSortCol ) > 0 )
549                is_sorted = false;
550    }
551    uglyHack = 0;
552
553    return is_sorted;
554}
555
556void
557TorrentListCtrl :: Resort( )
558{
559    myConfig->Write( _T("torrent-sort-column"), columnKeys[abs(prevSortCol)] );
560    myConfig->Write( _T("torrent-sort-is-descending"), prevSortCol < 0 );
561
562    if( !IsSorted ( ) )
563    {
564        uglyHack = this;
565        SortItems( Compare, prevSortCol );
566
567        const int n = GetItemCount ();
568        str2int_t tmp;
569        for( int i=0; i<n; ++i ) {
570            int idx = GetItemData( i );
571            const tr_info * info = tr_torrentInfo( myTorrents[idx] );
572            tmp[info->hashString] = i;
573        }
574        myHashToItem.swap( tmp );
575        uglyHack = NULL;
576    }
577}
578
579/***
580****
581***/
582
583void
584TorrentListCtrl :: Refresh ()
585{
586    const int_v  cols = getTorrentColumns( myConfig );
587    const int rowCount = GetItemCount();
588    for( int row=0; row<rowCount; ++row )
589    {
590        int array_index = GetItemData( row );
591        tr_torrent * tor = myTorrents[array_index];
592        RefreshTorrent( tor, array_index, cols );
593    }
594}
595
596void
597TorrentListCtrl :: Repopulate ()
598{
599    DeleteAllItems();
600    myHashToItem.clear ();
601
602    const int_v cols = getTorrentColumns( myConfig );
603    int i = 0;
604    for( torrents_t::const_iterator it(myTorrents.begin()),
605                                   end(myTorrents.end()); it!=end; ++it )
606        RefreshTorrent( *it, i++, cols );
607
608    Resort( );
609}
610
611void
612TorrentListCtrl :: Rebuild()
613{
614    ClearAll( );
615    myHashToItem.clear ();
616
617    int i = 0;
618    const int_v  cols = getTorrentColumns( myConfig );
619    for( int_v ::const_iterator it(cols.begin()), end(cols.end()); it!=end; ++it )
620    {
621        int format = wxLIST_FORMAT_LEFT;
622        int width = -1;
623        wxString h;
624
625        switch( *it )
626        {
627            case COL_POSITION:        h = _("#"); format = wxLIST_FORMAT_CENTRE; break;
628            case COL_PERCENT_DONE:    h = _("Done"); width = 50; format = wxLIST_FORMAT_RIGHT; break;
629            case COL_DOWNLOAD_SPEED:  h = _("Down"); width = 80; format = wxLIST_FORMAT_RIGHT; break;
630            case COL_ETA:             h = _("ETA"); format = wxLIST_FORMAT_RIGHT; break;
631            case COL_HASH:            h = _("Checksum"); break;
632            case COL_NAME:            h = _("Name"); width = 500; break;
633            case COL_PEERS:           h = _("Peers"); format = wxLIST_FORMAT_RIGHT; break;
634            case COL_RATIO:           h = _("Ratio"); format = wxLIST_FORMAT_RIGHT; break;
635            case COL_RECEIVED:        h = _("Received"); format = wxLIST_FORMAT_RIGHT; break;
636            case COL_REMAINING:       h = _("Remaining"); format = wxLIST_FORMAT_RIGHT; break;
637            case COL_SEEDS:           h = _("Seeds"); format = wxLIST_FORMAT_RIGHT; break;
638            case COL_SENT:            h = _("Sent"); format = wxLIST_FORMAT_RIGHT; break;
639            case COL_SIZE:            h = _("Size");  format = wxLIST_FORMAT_RIGHT; break;
640            case COL_STATE:           h = _("State"); width = 120; break;
641            case COL_STATUS:          h = _("Status"); width = 120; break;
642            case COL_TOTAL:           h = _("Total"); break;
643            case COL_UPLOAD_SPEED:    h = _("Up"); width = 80; format = wxLIST_FORMAT_RIGHT;break;
644            default:                  h = _("Error"); break;
645        }
646
647        InsertColumn( i++, h, format, width );
648    }
649
650    Repopulate( );
651}
652
653typedef std::set<tr_torrent*> torrent_set;
654
655void
656TorrentListCtrl :: Assign( const torrents_t& torrents )
657{
658    torrent_set prev, cur, removed;
659    torrents_v added;
660    prev.insert( myTorrents.begin(), myTorrents.end() );
661    cur.insert( torrents.begin(), torrents.end() );
662    std::set_difference (prev.begin(), prev.end(),
663                         cur.begin(), cur.end(), inserter(removed, removed.begin()));
664    std::set_difference (cur.begin(), cur.end(),
665                         prev.begin(), prev.end(), inserter(added, added.begin()));
666    Remove( removed );
667    Add( added );
668    Refresh( );
669    Resort( );
670}
671
672void
673TorrentListCtrl :: Add( const torrents_v& add )
674{
675    const int_v  cols = getTorrentColumns( myConfig );
676    int i = myTorrents.size();
677    myTorrents.insert( myTorrents.end(), add.begin(), add.end() );
678    for( torrents_v::const_iterator it(add.begin()), end(add.end()); it!=end; ++it )
679        RefreshTorrent( *it, i++, cols );
680}
681
682void
683TorrentListCtrl :: Remove( const torrent_set& remove )
684{
685    torrents_v vtmp;
686    str2int_t htmp;
687
688    for( int item=0; item<GetItemCount(); )
689    {
690        tr_torrent * tor = myTorrents[GetItemData(item)];
691        const tr_info * info = tr_torrentInfo( tor );
692
693        if( remove.count( tor ) )
694        {
695            DeleteItem( item );
696            continue;
697        }
698
699        vtmp.push_back( tor );
700        SetItemData( item, vtmp.size()-1 );
701        htmp[ info->hashString ] = item;
702        ++item;
703    }
704
705    myHashToItem.swap( htmp );
706    myTorrents.swap( vtmp );
707}
708
709/***
710****
711***/
712
713const tr_stat*
714TorrentListCtrl :: getStat( tr_torrent * tor )
715{
716    const tr_info * info = tr_torrentInfo( tor );
717    const time_t now = time( 0 );
718    TorStat& ts = myHashToStat[ info->hashString ];
719    if( ts.time < now ) {
720        ts.time = now;
721        ts.stat = tr_torrentStat( tor );
722    }
723    return ts.stat;
724}
725
726/***
727****
728***/
729
730void
731TorrentListCtrl :: SelectAll( )
732{
733    for( int i=0, n=GetItemCount(); i<n; ++i )
734        SetItemState( i, ~0, wxLIST_STATE_SELECTED );
735}
736
737void
738TorrentListCtrl :: DeselectAll( )
739{
740    for( int i=0, n=GetItemCount(); i<n; ++i )
741        SetItemState( i, 0, wxLIST_STATE_SELECTED );
742}
743
Note: See TracBrowser for help on using the repository browser.