source: branches/new_api/libtransmission/transmission.c @ 137

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

Adds a permanent handle for every torrent.
Removed the limit on the number of open torrents.

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