source: trunk/libtransmission/transmission.c @ 210

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

Adds download rate limit

File size: 20.0 KB
Line 
1/******************************************************************************
2 * Copyright (c) 2005-2006 Transmission authors and contributors
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_torrent_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 = -1;
72    h->bindSocket = -1;
73
74    h->acceptDie = 0;
75    tr_lockInit( &h->acceptLock );
76    tr_threadCreate( &h->acceptThread, acceptLoop, h );
77
78    return h;
79}
80
81/***********************************************************************
82 * tr_setBindPort
83 ***********************************************************************
84 *
85 **********************************************************************/
86void tr_setBindPort( tr_handle_t * h, int port )
87{
88    int sock = -1;
89    tr_torrent_t * tor;
90
91    if( h->bindPort == port )
92      return;
93
94#ifndef BEOS_NETSERVER
95    /* BeOS net_server seems to be unable to set incoming connections to
96       non-blocking. Too bad. */
97    if( !tr_fdSocketWillCreate( h->fdlimit, 0 ) )
98    {
99        /* XXX should handle failure here in a better way */
100        sock = tr_netBind( port );
101    }
102#else
103    return;
104#endif
105
106    tr_lockLock( &h->acceptLock );
107
108    h->bindPort = port;
109
110    for( tor = h->torrentList; tor; tor = tor->next )
111    {
112        tr_lockLock( &tor->lock );
113        if( NULL != tor->tracker )
114        {
115            tr_trackerChangePort( tor->tracker, port );
116        }
117        tr_lockUnlock( &tor->lock );
118    }
119
120    if( h->bindSocket > -1 )
121    {
122        tr_netClose( h->bindSocket );
123        tr_fdSocketClosed( h->fdlimit, 0 );
124    }
125
126    h->bindSocket = sock;
127
128    tr_lockUnlock( &h->acceptLock );
129}
130
131/***********************************************************************
132 * tr_setUploadLimit
133 ***********************************************************************
134 *
135 **********************************************************************/
136void tr_setUploadLimit( tr_handle_t * h, int limit )
137{
138    tr_rcSetLimit( h->upload, limit );
139    tr_chokingSetLimit( h->choking, limit );
140}
141
142/***********************************************************************
143 * tr_setDownloadLimit
144 ***********************************************************************
145 *
146 **********************************************************************/
147void tr_setDownloadLimit( tr_handle_t * h, int limit )
148{
149    tr_rcSetLimit( h->download, 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
161    *dl = 0.0;
162    *ul = 0.0;
163    for( tor = h->torrentList; tor; tor = tor->next )
164    {
165        tr_lockLock( &tor->lock );
166        if( tor->status & TR_STATUS_DOWNLOAD )
167            *dl += tr_rcRate( tor->download );
168        *ul += tr_rcRate( tor->upload );
169        tr_lockUnlock( &tor->lock );
170    }
171}
172
173/***********************************************************************
174 * tr_torrentInit
175 ***********************************************************************
176 * Allocates a tr_torrent_t structure, then relies on tr_metainfoParse
177 * to fill it.
178 **********************************************************************/
179tr_torrent_t * tr_torrentInit( tr_handle_t * h, const char * path,
180                               int * error )
181{
182    tr_torrent_t  * tor, * tor_tmp;
183    tr_info_t     * inf;
184    int             i;
185    char          * s1, * s2;
186
187    tor = calloc( sizeof( tr_torrent_t ), 1 );
188    inf = &tor->info;
189
190    /* Parse torrent file */
191    if( tr_metainfoParse( inf, path ) )
192    {
193        *error = TR_EINVALID;
194        free( tor );
195        return NULL;
196    }
197
198    /* Make sure this torrent is not already open */
199    for( tor_tmp = h->torrentList; tor_tmp; tor_tmp = tor_tmp->next )
200    {
201        if( !memcmp( tor->info.hash, tor_tmp->info.hash,
202                     SHA_DIGEST_LENGTH ) )
203        {
204            *error = TR_EDUPLICATE;
205            free( tor );
206            return NULL;
207        }
208    }
209
210    tor->status = TR_STATUS_PAUSE;
211    tor->id     = h->id;
212    tor->key    = h->key;
213    tor->bindPort = &h->bindPort;
214        tor->finished = 0;
215
216
217    /* Guess scrape URL */
218    s1 = strchr( inf->trackerAnnounce, '/' );
219    while( ( s2 = strchr( s1 + 1, '/' ) ) )
220    {
221        s1 = s2;
222    }
223    s1++;
224    if( !strncmp( s1, "announce", 8 ) )
225    {
226        int pre  = (long) s1 - (long) inf->trackerAnnounce;
227        int post = strlen( inf->trackerAnnounce ) - pre - 8;
228        memcpy( tor->scrape, inf->trackerAnnounce, pre );
229        sprintf( &tor->scrape[pre], "scrape" );
230        memcpy( &tor->scrape[pre+6], &inf->trackerAnnounce[pre+8], post );
231    }
232
233    /* Escaped info hash for HTTP queries */
234    for( i = 0; i < SHA_DIGEST_LENGTH; i++ )
235    {
236        sprintf( &tor->hashString[3*i], "%%%02x", inf->hash[i] );
237    }
238
239    /* Block size: usually 16 ko, or less if we have to */
240    tor->blockSize  = MIN( inf->pieceSize, 1 << 14 );
241    tor->blockCount = ( inf->totalSize + tor->blockSize - 1 ) /
242                        tor->blockSize;
243    tor->completion = tr_cpInit( tor );
244
245    tr_lockInit( &tor->lock );
246
247    tor->globalUpload   = h->upload;
248    tor->globalDownload = h->download;
249    tor->fdlimit        = h->fdlimit;
250    tor->upload         = tr_rcInit();
251    tor->download       = tr_rcInit();
252 
253    /* We have a new torrent */
254    tr_lockLock( &h->acceptLock );
255    tor->prev = NULL;
256    tor->next = h->torrentList;
257    if( tor->next )
258    {
259        tor->next->prev = tor;
260    }
261    h->torrentList = tor;
262    (h->torrentCount)++;
263    tr_lockUnlock( &h->acceptLock );
264
265    if( 0 > h->bindPort )
266    {
267        tr_setBindPort( h, TR_DEFAULT_PORT );
268    }
269
270    return tor;
271}
272
273tr_info_t * tr_torrentInfo( tr_torrent_t * tor )
274{
275    return &tor->info;
276}
277
278/***********************************************************************
279 * tr_torrentScrape
280 **********************************************************************/
281int tr_torrentScrape( tr_torrent_t * tor, int * s, int * l )
282{
283    return tr_trackerScrape( tor, s, l );
284}
285
286void tr_torrentSetFolder( tr_torrent_t * tor, const char * path )
287{
288    tor->destination = strdup( path );
289}
290
291char * tr_torrentGetFolder( tr_torrent_t * tor )
292{
293    return tor->destination;
294}
295
296void tr_torrentStart( tr_torrent_t * tor )
297{
298    if( tor->status & ( TR_STATUS_STOPPING | TR_STATUS_STOPPED ) )
299    {
300        /* Join the thread first */
301        torrentReallyStop( tor );
302    }
303
304    tor->status  = TR_STATUS_CHECK;
305    tor->tracker = tr_trackerInit( tor );
306
307    tor->date = tr_date();
308    tor->die = 0;
309    tr_threadCreate( &tor->thread, downloadLoop, tor );
310}
311
312void tr_torrentStop( tr_torrent_t * tor )
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_torrent_t * tor )
329{
330    tor->die = 1;
331    tr_threadJoin( &tor->thread );
332    tr_dbg( "Thread joined" );
333
334    tr_trackerClose( tor->tracker );
335
336    while( tor->peerCount > 0 )
337    {
338        tr_peerRem( tor, 0 );
339    }
340}
341
342/***********************************************************************
343 * tr_torrentCount
344 ***********************************************************************
345 *
346 **********************************************************************/
347int tr_torrentCount( tr_handle_t * h )
348{
349    return h->torrentCount;
350}
351
352void tr_torrentIterate( tr_handle_t * h, tr_callback_t func, void * d )
353{
354    tr_torrent_t * tor;
355
356    for( tor = h->torrentList; tor; tor = tor->next )
357    {
358        func( tor, d );
359    }
360}
361
362int tr_getFinished( tr_torrent_t * tor )
363{
364    if( tor->finished )
365    {
366        tor->finished = 0;
367        return 1;
368    }
369    return 0;
370}
371
372tr_stat_t * tr_torrentStat( tr_torrent_t * tor )
373{
374    tr_stat_t * s;
375    tr_info_t * inf = &tor->info;
376    int i;
377
378    tor->statCur = ( tor->statCur + 1 ) % 2;
379    s = &tor->stats[tor->statCur];
380
381    if( ( tor->status & TR_STATUS_STOPPED ) ||
382        ( ( tor->status & TR_STATUS_STOPPING ) &&
383          tr_date() > tor->stopDate + 60000 ) )
384    {
385        torrentReallyStop( tor );
386        tor->status = TR_STATUS_PAUSE;
387    }
388
389    tr_lockLock( &tor->lock );
390
391    s->status = tor->status;
392    memcpy( s->trackerError, tor->trackerError,
393            sizeof( s->trackerError ) );
394
395    s->peersTotal       = 0;
396    s->peersUploading   = 0;
397    s->peersDownloading = 0;
398
399    for( i = 0; i < tor->peerCount; i++ )
400    {
401        if( tr_peerIsConnected( tor->peers[i] ) )
402        {
403            (s->peersTotal)++;
404            if( tr_peerIsUploading( tor->peers[i] ) )
405            {
406                (s->peersUploading)++;
407            }
408            if( tr_peerIsDownloading( tor->peers[i] ) )
409            {
410                (s->peersDownloading)++;
411            }
412        }
413    }
414
415    s->progress = tr_cpCompletionAsFloat( tor->completion );
416    if( tor->status & TR_STATUS_DOWNLOAD )
417        s->rateDownload = tr_rcRate( tor->download );
418    else
419        /* tr_rcRate() doesn't make the difference between 'piece'
420           messages and other messages, which causes a non-zero
421           download rate even tough we are not downloading. So we
422           force it to zero not to confuse the user. */
423        s->rateDownload = 0.0;
424    s->rateUpload = tr_rcRate( tor->upload );
425   
426    s->seeders    = tr_trackerSeeders(tor);
427        s->leechers       = tr_trackerLeechers(tor);
428
429    if( s->rateDownload < 0.1 )
430    {
431        s->eta = -1;
432    }
433    else
434    {
435        s->eta = (float) ( 1.0 - s->progress ) *
436            (float) inf->totalSize / s->rateDownload / 1024.0;
437        if( s->eta > 99 * 3600 + 59 * 60 + 59 )
438        {
439            s->eta = -1;
440        }
441    }
442
443    s->downloaded = tor->downloaded;
444    s->uploaded   = tor->uploaded;
445
446    tr_lockUnlock( &tor->lock );
447
448    return s;
449}
450
451void tr_torrentAvailability( tr_torrent_t * tor, int8_t * tab, int size )
452{
453    int i, j, piece;
454
455    tr_lockLock( &tor->lock );
456    for( i = 0; i < size; i++ )
457    {
458        piece = i * tor->info.pieceCount / size;
459
460        if( tr_cpPieceIsComplete( tor->completion, piece ) )
461        {
462            tab[i] = -1;
463            continue;
464        }
465
466        tab[i] = 0;
467        for( j = 0; j < tor->peerCount; j++ )
468        {
469            if( tr_peerBitfield( tor->peers[j] ) &&
470                tr_bitfieldHas( tr_peerBitfield( tor->peers[j] ), piece ) )
471            {
472                (tab[i])++;
473            }
474        }
475    }
476    tr_lockUnlock( &tor->lock );
477}
478
479/***********************************************************************
480 * tr_torrentClose
481 ***********************************************************************
482 * Frees memory allocated by tr_torrentInit.
483 **********************************************************************/
484void tr_torrentClose( tr_handle_t * h, tr_torrent_t * tor )
485{
486    tr_info_t * inf = &tor->info;
487
488    if( tor->status & ( TR_STATUS_STOPPING | TR_STATUS_STOPPED ) )
489    {
490        /* Join the thread first */
491        torrentReallyStop( tor );
492    }
493
494    tr_lockLock( &h->acceptLock );
495
496    h->torrentCount--;
497
498    tr_lockClose( &tor->lock );
499    tr_cpClose( tor->completion );
500
501    tr_rcClose( tor->upload );
502    tr_rcClose( tor->download );
503
504    if( tor->destination )
505    {
506        free( tor->destination );
507    }
508    free( inf->pieces );
509    free( inf->files );
510
511    if( tor->prev )
512    {
513        tor->prev->next = tor->next;
514    }
515    else
516    {
517        h->torrentList = tor->next;
518    }
519    if( tor->next )
520    {
521        tor->next->prev = tor->prev;
522    }
523    free( tor );
524
525    tr_lockUnlock( &h->acceptLock );
526}
527
528void tr_close( tr_handle_t * h )
529{
530    acceptStop( h );
531    tr_chokingClose( h->choking );
532    tr_fdClose( h->fdlimit );
533    tr_rcClose( h->upload );
534    tr_rcClose( h->download );
535    free( h );
536}
537
538/***********************************************************************
539 * downloadLoop
540 **********************************************************************/
541static void downloadLoop( void * _tor )
542{
543    tr_torrent_t * tor = _tor;
544    uint64_t       date1, date2;
545
546    tr_dbg( "Thread started" );
547
548#ifdef SYS_BEOS
549    /* This is required because on BeOS, SIGINT is sent to each thread,
550       which kills them not nicely */
551    signal( SIGINT, SIG_IGN );
552#endif
553
554    tr_lockLock( &tor->lock );
555
556    tr_cpReset( tor->completion );
557    tor->io     = tr_ioInit( tor );
558    tor->status = tr_cpIsSeeding( tor->completion ) ?
559                      TR_STATUS_SEED : TR_STATUS_DOWNLOAD;
560
561    while( !tor->die )
562    {
563        date1 = tr_date();
564
565        /* Are we finished ? */
566        if( ( tor->status & TR_STATUS_DOWNLOAD ) &&
567            tr_cpIsSeeding( tor->completion ) )
568        {
569            /* Done */
570            tor->status = TR_STATUS_SEED;
571                        tor->finished = 1;
572            tr_trackerCompleted( tor->tracker );
573            tr_ioSaveResume( tor->io );
574        }
575
576        /* Receive/send messages */
577        tr_peerPulse( tor );
578
579        /* Try to get new peers or to send a message to the tracker */
580        tr_trackerPulse( tor->tracker );
581
582        if( tor->status & TR_STATUS_STOPPED )
583        {
584            break;
585        }
586
587        /* Wait up to 20 ms */
588        date2 = tr_date();
589        if( date2 < date1 + 20 )
590        {
591            tr_lockUnlock( &tor->lock );
592            tr_wait( date1 + 20 - date2 );
593            tr_lockLock( &tor->lock );
594        }
595    }
596
597    tr_lockUnlock( &tor->lock );
598
599    tr_ioClose( tor->io );
600
601    tor->status = TR_STATUS_STOPPED;
602
603    tr_dbg( "Thread exited" );
604}
605
606/***********************************************************************
607 * acceptLoop
608 **********************************************************************/
609static void acceptLoop( void * _h )
610{
611    tr_handle_t * h = _h;
612    uint64_t      date1, date2, lastchoke = 0;
613    int           ii;
614    uint8_t     * hash;
615    tr_torrent_t * tor;
616
617    tr_dbg( "Accept thread started" );
618
619#ifdef SYS_BEOS
620    /* This is required because on BeOS, SIGINT is sent to each thread,
621       which kills them not nicely */
622    signal( SIGINT, SIG_IGN );
623#endif
624
625    tr_lockLock( &h->acceptLock );
626
627    while( !h->acceptDie )
628    {
629        date1 = tr_date();
630
631        /* Check for incoming connections */
632        if( h->bindSocket > -1 &&
633            h->acceptPeerCount < TR_MAX_PEER_COUNT &&
634            !tr_fdSocketWillCreate( h->fdlimit, 0 ) )
635        {
636            int            s;
637            struct in_addr addr;
638            in_port_t      port;
639            s = tr_netAccept( h->bindSocket, &addr, &port );
640            if( s > -1 )
641            {
642                h->acceptPeers[h->acceptPeerCount++] = tr_peerInit( addr, port, s );
643            }
644            else
645            {
646                tr_fdSocketClosed( h->fdlimit, 0 );
647            }
648        }
649
650        for( ii = 0; ii < h->acceptPeerCount; )
651        {
652            if( tr_peerRead( NULL, h->acceptPeers[ii] ) )
653            {
654                tr_peerDestroy( h->fdlimit, h->acceptPeers[ii] );
655                goto removePeer;
656            }
657            if( NULL != ( hash = tr_peerHash( h->acceptPeers[ii] ) ) )
658            {
659                for( tor = h->torrentList; tor; tor = tor->next )
660                {
661                    tr_lockLock( &tor->lock );
662                    if( 0 == memcmp( tor->info.hash, hash,
663                                     SHA_DIGEST_LENGTH ) )
664                    {
665                      tr_peerAttach( tor, h->acceptPeers[ii] );
666                      tr_lockUnlock( &tor->lock );
667                      goto removePeer;
668                    }
669                    tr_lockUnlock( &tor->lock );
670                }
671                tr_peerDestroy( h->fdlimit, h->acceptPeers[ii] );
672                goto removePeer;
673            }
674            if( date1 > tr_peerDate( h->acceptPeers[ii] ) + 10000 )
675            {
676                /* Give them 10 seconds to send the handshake */
677                tr_peerDestroy( h->fdlimit, h->acceptPeers[ii] );
678                goto removePeer;
679            }
680            ii++;
681            continue;
682           removePeer:
683            h->acceptPeerCount--;
684            memmove( &h->acceptPeers[ii], &h->acceptPeers[ii+1],
685                     ( h->acceptPeerCount - ii ) * sizeof( tr_peer_t * ) );
686        }
687
688        if( date1 > lastchoke + 2000 )
689        {
690            tr_chokingPulse( h->choking );
691            lastchoke = date1;
692        }
693
694        /* Wait up to 20 ms */
695        date2 = tr_date();
696        if( date2 < date1 + 20 )
697        {
698            tr_lockUnlock( &h->acceptLock );
699            tr_wait( date1 + 20 - date2 );
700            tr_lockLock( &h->acceptLock );
701        }
702    }
703
704    tr_lockUnlock( &h->acceptLock );
705
706    tr_dbg( "Accept thread exited" );
707}
708
709/***********************************************************************
710 * acceptStop
711 ***********************************************************************
712 * Joins the accept thread and frees/closes everything related to it.
713 **********************************************************************/
714static void acceptStop( tr_handle_t * h )
715{
716    int ii;
717
718    h->acceptDie = 1;
719    tr_threadJoin( &h->acceptThread );
720    tr_lockClose( &h->acceptLock );
721    tr_dbg( "Accept thread joined" );
722
723    for( ii = 0; ii < h->acceptPeerCount; ii++ )
724    {
725        tr_peerDestroy( h->fdlimit, h->acceptPeers[ii] );
726    }
727
728    if( h->bindSocket > -1 )
729    {
730        tr_netClose( h->bindSocket );
731        tr_fdSocketClosed( h->fdlimit, 0 );
732    }
733}
Note: See TracBrowser for help on using the repository browser.