source: trunk/libtransmission/transmission.c @ 92

Last change on this file since 92 was 92, checked in by titer, 16 years ago

Force rates to 0 when paused, and download rate to 0 when seeding.
Increased OS X UI update interval to 1 second.

File size: 20.2 KB
Line 
1/******************************************************************************
2 * Copyright (c) 2005 Eric Petit
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a
5 * copy of this software and associated documentation files (the "Software"),
6 * to deal in the Software without restriction, including without limitation
7 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 * and/or sell copies of the Software, and to permit persons to whom the
9 * Software is furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20 * DEALINGS IN THE SOFTWARE.
21 *****************************************************************************/
22
23#include "transmission.h"
24
25/***********************************************************************
26 * Local prototypes
27 **********************************************************************/
28static void torrentReallyStop( tr_handle_t * h, int t );
29static void  downloadLoop( void * );
30static void  acceptLoop( void * );
31static void acceptStop( tr_handle_t * h );
32
33/***********************************************************************
34 * tr_init
35 ***********************************************************************
36 * Allocates a tr_handle_t structure and initializes a few things
37 **********************************************************************/
38tr_handle_t * tr_init()
39{
40    tr_handle_t * h;
41    int           i, r;
42
43    h = calloc( sizeof( tr_handle_t ), 1 );
44
45    /* Generate a peer id : "-TRxxyy-" + 12 random alphanumeric
46       characters, where xx is the major version number and yy the
47       minor version number (Azureus-style) */
48    sprintf( h->id, "-TR%02d%02d-", VERSION_MAJOR, VERSION_MINOR );
49    for( i = 8; i < 20; i++ )
50    {
51        r        = tr_rand( 36 );
52        h->id[i] = ( r < 26 ) ? ( 'a' + r ) : ( '0' + r - 26 ) ;
53    }
54
55    /* Random key */
56    for( i = 0; i < 20; i++ )
57    {
58        r         = tr_rand( 36 );
59        h->key[i] = ( r < 26 ) ? ( 'a' + r ) : ( '0' + r - 26 ) ;
60    }
61
62    /* Don't exit when writing on a broken socket */
63    signal( SIGPIPE, SIG_IGN );
64
65    /* Initialize rate and file descripts controls */
66    h->upload   = tr_rcInit();
67    h->download = tr_rcInit();
68    h->fdlimit  = tr_fdInit();
69    h->choking  = tr_chokingInit( h );
70
71    h->bindPort = TR_DEFAULT_PORT;
72    h->bindSocket = -1;
73
74#ifndef BEOS_NETSERVER
75    /* BeOS net_server seems to be unable to set incoming connections to
76       non-blocking. Too bad. */
77    if( !tr_fdSocketWillCreate( h->fdlimit, 0 ) )
78    {
79        /* XXX should handle failure here in a better way */
80        h->bindSocket = tr_netBind( h->bindPort );
81    }
82#endif
83
84
85    h->acceptDie = 0;
86    tr_lockInit( &h->acceptLock );
87    tr_threadCreate( &h->acceptThread, acceptLoop, h );
88
89    return h;
90}
91
92/***********************************************************************
93 * tr_setBindPort
94 ***********************************************************************
95 *
96 **********************************************************************/
97void tr_setBindPort( tr_handle_t * h, int port )
98{
99    int ii, sock;
100
101    if( h->bindPort == port )
102      return;
103
104#ifndef BEOS_NETSERVER
105    /* BeOS net_server seems to be unable to set incoming connections to
106       non-blocking. Too bad. */
107    if( !tr_fdSocketWillCreate( h->fdlimit, 0 ) )
108    {
109        /* XXX should handle failure here in a better way */
110        sock = tr_netBind( port );
111    }
112#else
113    return;
114#endif
115
116    tr_lockLock( &h->acceptLock );
117
118    h->bindPort = port;
119
120    for( ii = 0; ii < h->torrentCount; ii++ )
121    {
122        tr_lockLock( &h->torrents[ii]->lock );
123        if( NULL != h->torrents[ii]->tracker )
124        {
125            tr_trackerChangePort( h->torrents[ii]->tracker, port );
126        }
127        tr_lockUnlock( &h->torrents[ii]->lock );
128    }
129
130    if( h->bindSocket > -1 )
131    {
132        tr_netClose( h->bindSocket );
133        tr_fdSocketClosed( h->fdlimit, 0 );
134    }
135
136    h->bindSocket = sock;
137
138    tr_lockUnlock( &h->acceptLock );
139}
140
141/***********************************************************************
142 * tr_setUploadLimit
143 ***********************************************************************
144 *
145 **********************************************************************/
146void tr_setUploadLimit( tr_handle_t * h, int limit )
147{
148    tr_rcSetLimit( h->upload, limit );
149    tr_chokingSetLimit( h->choking, limit );
150}
151
152/***********************************************************************
153 * tr_torrentRates
154 ***********************************************************************
155 *
156 **********************************************************************/
157void tr_torrentRates( tr_handle_t * h, float * dl, float * ul )
158{
159    tr_torrent_t * tor;
160    int i;
161
162    *dl = 0.0;
163    *ul = 0.0;
164    for( i = 0; i < h->torrentCount; i++ )
165    {
166        tor = h->torrents[i];
167        tr_lockLock( &tor->lock );
168        if( tor->status & TR_STATUS_DOWNLOAD )
169            *dl += tr_rcRate( tor->download );
170        *ul += tr_rcRate( tor->upload );
171        tr_lockUnlock( &tor->lock );
172    }
173}
174
175/***********************************************************************
176 * tr_torrentInit
177 ***********************************************************************
178 * Allocates a tr_torrent_t structure, then relies on tr_metainfoParse
179 * to fill it.
180 **********************************************************************/
181int tr_torrentInit( tr_handle_t * h, const char * path )
182{
183    tr_torrent_t  * tor;
184    tr_info_t     * inf;
185    int             i;
186    char          * s1, * s2;
187
188    if( h->torrentCount >= TR_MAX_TORRENT_COUNT )
189    {
190        tr_err( "Maximum number of torrents reached" );
191        return 1;
192    }
193
194    tor = calloc( sizeof( tr_torrent_t ), 1 );
195    inf = &tor->info;
196
197    /* Parse torrent file */
198    if( tr_metainfoParse( inf, path ) )
199    {
200        free( tor );
201        return 1;
202    }
203
204    /* Make sure this torrent is not already open */
205    for( i = 0; i < h->torrentCount; i++ )
206    {
207        if( !memcmp( tor->info.hash, h->torrents[i]->info.hash,
208                     SHA_DIGEST_LENGTH ) )
209        {
210            tr_err( "Torrent already open" );
211            free( tor );
212            return 1;
213        }
214    }
215
216    tor->status = TR_STATUS_PAUSE;
217    tor->id     = h->id;
218    tor->key    = h->key;
219        tor->finished = 0;
220
221
222    /* Guess scrape URL */
223    s1 = strchr( inf->trackerAnnounce, '/' );
224    while( ( s2 = strchr( s1 + 1, '/' ) ) )
225    {
226        s1 = s2;
227    }
228    s1++;
229    if( !strncmp( s1, "announce", 8 ) )
230    {
231        int pre  = (long) s1 - (long) inf->trackerAnnounce;
232        int post = strlen( inf->trackerAnnounce ) - pre - 8;
233        memcpy( tor->scrape, inf->trackerAnnounce, pre );
234        sprintf( &tor->scrape[pre], "scrape" );
235        memcpy( &tor->scrape[pre+6], &inf->trackerAnnounce[pre+8], post );
236    }
237
238    /* Escaped info hash for HTTP queries */
239    for( i = 0; i < SHA_DIGEST_LENGTH; i++ )
240    {
241        sprintf( &tor->hashString[3*i], "%%%02x", inf->hash[i] );
242    }
243
244    /* Block size: usually 16 ko, or less if we have to */
245    tor->blockSize  = MIN( inf->pieceSize, 1 << 14 );
246    tor->blockCount = ( inf->totalSize + tor->blockSize - 1 ) /
247                        tor->blockSize;
248    tor->completion = tr_cpInit( tor );
249
250    tr_lockInit( &tor->lock );
251
252    tor->globalUpload   = h->upload;
253    tor->globalDownload = h->download;
254    tor->fdlimit        = h->fdlimit;
255    tor->upload         = tr_rcInit();
256    tor->download       = tr_rcInit();
257 
258    /* We have a new torrent */
259    tr_lockLock( &h->acceptLock );
260    h->torrents[h->torrentCount] = tor;
261    (h->torrentCount)++;
262    tr_lockUnlock( &h->acceptLock );
263
264    return 0;
265}
266
267/***********************************************************************
268 * tr_torrentScrape
269 ***********************************************************************
270 * Allocates a tr_torrent_t structure, then relies on tr_metainfoParse
271 * to fill it.
272 **********************************************************************/
273int tr_torrentScrape( tr_handle_t * h, int t, int * s, int * l )
274{
275    return tr_trackerScrape( h->torrents[t], s, l );
276}
277
278void tr_torrentSetFolder( tr_handle_t * h, int t, const char * path )
279{
280    tr_torrent_t * tor = h->torrents[t];
281
282    tor->destination = strdup( path );
283}
284
285char * tr_torrentGetFolder( tr_handle_t * h, int t )
286{
287    tr_torrent_t * tor = h->torrents[t];
288
289    return tor->destination;
290}
291
292void tr_torrentStart( tr_handle_t * h, int t )
293{
294    tr_torrent_t * tor = h->torrents[t];
295
296    if( tor->status & ( TR_STATUS_STOPPING | TR_STATUS_STOPPED ) )
297    {
298        /* Join the thread first */
299        torrentReallyStop( h, t );
300    }
301
302    tor->status   = TR_STATUS_CHECK;
303    tor->tracker  = tr_trackerInit( h, tor );
304
305    tor->date = tr_date();
306    tor->die = 0;
307    tr_threadCreate( &tor->thread, downloadLoop, tor );
308}
309
310void tr_torrentStop( tr_handle_t * h, int t )
311{
312    tr_torrent_t * tor = h->torrents[t];
313
314    tr_lockLock( &tor->lock );
315    tr_trackerStopped( tor->tracker );
316    tr_rcReset( tor->download );
317    tr_rcReset( tor->upload );
318    tor->status = TR_STATUS_STOPPING;
319    tor->stopDate = tr_date();
320    tr_lockUnlock( &tor->lock );
321}
322
323/***********************************************************************
324 * torrentReallyStop
325 ***********************************************************************
326 * Joins the download thread and frees/closes everything related to it.
327 **********************************************************************/
328static void torrentReallyStop( tr_handle_t * h, int t )
329{
330    tr_torrent_t * tor = h->torrents[t];
331
332    tor->die = 1;
333    tr_threadJoin( &tor->thread );
334    tr_dbg( "Thread joined" );
335
336    tr_trackerClose( tor->tracker );
337
338    while( tor->peerCount > 0 )
339    {
340        tr_peerRem( tor, 0 );
341    }
342
343    tor->downloaded = 0;
344    tor->uploaded   = 0;
345}
346
347/***********************************************************************
348 * tr_torrentCount
349 ***********************************************************************
350 *
351 **********************************************************************/
352int tr_torrentCount( tr_handle_t * h )
353{
354    return h->torrentCount;
355}
356
357int tr_getFinished( tr_handle_t * h, int i)
358{
359        return h->torrents[i]->finished;
360}
361void tr_setFinished( tr_handle_t * h, int i, int val)
362{
363        h->torrents[i]->finished = val;
364}
365
366int tr_torrentStat( tr_handle_t * h, tr_stat_t ** stat )
367{
368    tr_stat_t * s;
369    tr_torrent_t * tor;
370    tr_info_t * inf;
371    int i, j, k, piece;
372
373    if( h->torrentCount < 1 )
374    {
375        *stat = NULL;
376        return 0;
377    }
378
379    s = malloc( h->torrentCount * sizeof( tr_stat_t ) );
380
381    for( i = 0; i < h->torrentCount; i++ )
382    {
383        tor = h->torrents[i];
384        inf = &tor->info;
385
386        if( ( tor->status & TR_STATUS_STOPPED ) ||
387            ( ( tor->status & TR_STATUS_STOPPING ) &&
388              tr_date() > tor->stopDate + 60000 ) )
389        {
390            torrentReallyStop( h, i );
391            tor->status = TR_STATUS_PAUSE;
392        }
393
394        tr_lockLock( &tor->lock );
395
396        memcpy( &s[i].info, &tor->info, sizeof( tr_info_t ) );
397        s[i].status = tor->status;
398        memcpy( s[i].error, tor->error, sizeof( s[i].error ) );
399
400        s[i].peersTotal       = 0;
401        s[i].peersUploading   = 0;
402        s[i].peersDownloading = 0;
403
404        for( j = 0; j < tor->peerCount; j++ )
405        {
406            if( tr_peerIsConnected( tor->peers[j] ) )
407            {
408                (s[i].peersTotal)++;
409                if( tr_peerIsUploading( tor->peers[j] ) )
410                {
411                    (s[i].peersUploading)++;
412                }
413                if( tr_peerIsDownloading( tor->peers[j] ) )
414                {
415                    (s[i].peersDownloading)++;
416                }
417            }
418        }
419
420        s[i].progress     = tr_cpCompletionAsFloat( tor->completion );
421        if( tor->status & TR_STATUS_DOWNLOAD )
422            s[i].rateDownload = tr_rcRate( tor->download );
423        else
424            /* tr_rcRate() doesn't make the difference between 'piece'
425               messages and other messages, which causes a non-zero
426               download rate even tough we are not downloading. So we
427               force it to zero not to confuse the user. */
428            s[i].rateDownload = 0.0;
429        s[i].rateUpload = tr_rcRate( tor->upload );
430       
431        s[i].seeders      = tr_trackerSeeders(tor);
432                s[i].leechers     = tr_trackerLeechers(tor);
433
434        if( s[i].rateDownload < 0.1 )
435        {
436            s[i].eta = -1;
437        }
438        else
439        {
440            s[i].eta = (float) ( 1.0 - s[i].progress ) *
441                (float) inf->totalSize / s[i].rateDownload / 1024.0;
442            if( s[i].eta > 99 * 3600 + 59 * 60 + 59 )
443            {
444                s[i].eta = -1;
445            }
446        }
447
448        for( j = 0; j < 120; j++ )
449        {
450            piece = j * inf->pieceCount / 120;
451
452            if( tr_cpPieceIsComplete( tor->completion, piece ) )
453            {
454                s[i].pieces[j] = -1;
455                continue;
456            }
457
458            s[i].pieces[j] = 0;
459           
460            for( k = 0; k < tor->peerCount; k++ )
461            {
462                if( tr_peerBitfield( tor->peers[k] ) &&
463                    tr_bitfieldHas( tr_peerBitfield( tor->peers[k] ), piece ) )
464                {
465                    (s[i].pieces[j])++;
466                }
467            }
468        }
469
470        s[i].downloaded = tor->downloaded;
471        s[i].uploaded   = tor->uploaded;
472
473        s[i].folder = tor->destination;
474
475        tr_lockUnlock( &tor->lock );
476    }
477
478    *stat = s;
479    return h->torrentCount;
480}
481
482/***********************************************************************
483 * tr_torrentClose
484 ***********************************************************************
485 * Frees memory allocated by tr_torrentInit.
486 **********************************************************************/
487void tr_torrentClose( tr_handle_t * h, int t )
488{
489    tr_torrent_t * tor = h->torrents[t];
490    tr_info_t    * inf = &tor->info;
491
492    if( tor->status & ( TR_STATUS_STOPPING | TR_STATUS_STOPPED ) )
493    {
494        /* Join the thread first */
495        torrentReallyStop( h, t );
496    }
497
498    tr_lockLock( &h->acceptLock );
499
500    h->torrentCount--;
501
502    tr_lockClose( &tor->lock );
503    tr_cpClose( tor->completion );
504
505    tr_rcClose( tor->upload );
506    tr_rcClose( tor->download );
507
508    if( tor->destination )
509    {
510        free( tor->destination );
511    }
512    free( inf->pieces );
513    free( inf->files );
514    free( tor );
515
516    memmove( &h->torrents[t], &h->torrents[t+1],
517             ( h->torrentCount - t ) * sizeof( void * ) );
518
519    tr_lockUnlock( &h->acceptLock );
520}
521
522void tr_close( tr_handle_t * h )
523{
524    acceptStop( h );
525    tr_chokingClose( h->choking );
526    tr_fdClose( h->fdlimit );
527    tr_rcClose( h->upload );
528    tr_rcClose( h->download );
529    free( h );
530}
531
532/***********************************************************************
533 * downloadLoop
534 **********************************************************************/
535static void downloadLoop( void * _tor )
536{
537    tr_torrent_t * tor = _tor;
538    uint64_t       date1, date2;
539
540    tr_dbg( "Thread started" );
541
542#ifdef SYS_BEOS
543    /* This is required because on BeOS, SIGINT is sent to each thread,
544       which kills them not nicely */
545    signal( SIGINT, SIG_IGN );
546#endif
547
548    tr_lockLock( &tor->lock );
549
550    tr_cpReset( tor->completion );
551    tor->io     = tr_ioInit( tor );
552    tor->status = tr_cpIsSeeding( tor->completion ) ?
553                      TR_STATUS_SEED : TR_STATUS_DOWNLOAD;
554
555    while( !tor->die )
556    {
557        date1 = tr_date();
558
559        /* Are we finished ? */
560        if( ( tor->status & TR_STATUS_DOWNLOAD ) &&
561            tr_cpIsSeeding( tor->completion ) )
562        {
563            /* Done */
564            tor->status = TR_STATUS_SEED;
565                        tor->finished = 1;
566            tr_trackerCompleted( tor->tracker );
567        }
568
569        /* Receive/send messages */
570        tr_peerPulse( tor );
571
572        /* Try to get new peers or to send a message to the tracker */
573        tr_trackerPulse( tor->tracker );
574
575        if( tor->status & TR_STATUS_STOPPED )
576        {
577            break;
578        }
579
580        /* Wait up to 20 ms */
581        date2 = tr_date();
582        if( date2 < date1 + 20 )
583        {
584            tr_lockUnlock( &tor->lock );
585            tr_wait( date1 + 20 - date2 );
586            tr_lockLock( &tor->lock );
587        }
588    }
589
590    tr_lockUnlock( &tor->lock );
591
592    tr_ioClose( tor->io );
593
594    tor->status = TR_STATUS_STOPPED;
595
596    tr_dbg( "Thread exited" );
597}
598
599/***********************************************************************
600 * acceptLoop
601 **********************************************************************/
602static void acceptLoop( void * _h )
603{
604    tr_handle_t * h = _h;
605    uint64_t      date1, date2, lastchoke = 0;
606    int           ii, jj;
607    uint8_t     * hash;
608
609    tr_dbg( "Accept thread started" );
610
611#ifdef SYS_BEOS
612    /* This is required because on BeOS, SIGINT is sent to each thread,
613       which kills them not nicely */
614    signal( SIGINT, SIG_IGN );
615#endif
616
617    tr_lockLock( &h->acceptLock );
618
619    while( !h->acceptDie )
620    {
621        date1 = tr_date();
622
623        /* Check for incoming connections */
624        if( h->bindSocket > -1 &&
625            h->acceptPeerCount < TR_MAX_PEER_COUNT &&
626            !tr_fdSocketWillCreate( h->fdlimit, 0 ) )
627        {
628            int            s;
629            struct in_addr addr;
630            in_port_t      port;
631            s = tr_netAccept( h->bindSocket, &addr, &port );
632            if( s > -1 )
633            {
634                h->acceptPeers[h->acceptPeerCount++] = tr_peerInit( addr, port, s );
635            }
636            else
637            {
638                tr_fdSocketClosed( h->fdlimit, 0 );
639            }
640        }
641
642        for( ii = 0; ii < h->acceptPeerCount; )
643        {
644            if( tr_peerRead( NULL, h->acceptPeers[ii] ) )
645            {
646                tr_peerDestroy( h->fdlimit, h->acceptPeers[ii] );
647                goto removePeer;
648            }
649            if( NULL != ( hash = tr_peerHash( h->acceptPeers[ii] ) ) )
650            {
651                for( jj = 0; jj < h->torrentCount; jj++ )
652                {
653                    tr_lockLock( &h->torrents[jj]->lock );
654                    if( 0 == memcmp( h->torrents[jj]->info.hash, hash,
655                                     SHA_DIGEST_LENGTH ) )
656                    {
657                      tr_peerAttach( h->torrents[jj], h->acceptPeers[ii] );
658                      tr_lockUnlock( &h->torrents[jj]->lock );
659                      goto removePeer;
660                    }
661                    tr_lockUnlock( &h->torrents[jj]->lock );
662                }
663                tr_peerDestroy( h->fdlimit, h->acceptPeers[ii] );
664                goto removePeer;
665            }
666            ii++;
667            continue;
668           removePeer:
669            h->acceptPeerCount--;
670            memmove( &h->acceptPeers[ii], &h->acceptPeers[ii+1],
671                     ( h->acceptPeerCount - ii ) * sizeof( tr_peer_t * ) );
672        }
673
674        if( date1 > lastchoke + 2000 )
675        {
676            tr_chokingPulse( h->choking );
677            lastchoke = date1;
678        }
679
680        /* Wait up to 20 ms */
681        date2 = tr_date();
682        if( date2 < date1 + 20 )
683        {
684            tr_lockUnlock( &h->acceptLock );
685            tr_wait( date1 + 20 - date2 );
686            tr_lockLock( &h->acceptLock );
687        }
688    }
689
690    tr_lockUnlock( &h->acceptLock );
691
692    tr_dbg( "Accept thread exited" );
693}
694
695/***********************************************************************
696 * acceptStop
697 ***********************************************************************
698 * Joins the accept thread and frees/closes everything related to it.
699 **********************************************************************/
700static void acceptStop( tr_handle_t * h )
701{
702    int ii;
703
704    h->acceptDie = 1;
705    tr_threadJoin( &h->acceptThread );
706    tr_lockClose( &h->acceptLock );
707    tr_dbg( "Accept thread joined" );
708
709    for( ii = 0; ii < h->acceptPeerCount; ii++ )
710    {
711        tr_peerDestroy( h->fdlimit, h->acceptPeers[ii] );
712    }
713
714    if( h->bindSocket > -1 )
715    {
716        tr_netClose( h->bindSocket );
717        tr_fdSocketClosed( h->fdlimit, 0 );
718    }
719}
Note: See TracBrowser for help on using the repository browser.