source: trunk/libtransmission/torrent.c @ 5908

Last change on this file since 5908 was 5908, checked in by charles, 14 years ago

(libt) more janitorial work on cleaning up tr_session*() and tr_torrent*() functions: session stats, torrent count, and manual update.

  • Property svn:keywords set to Date Rev Author Id
File size: 38.9 KB
Line 
1/******************************************************************************
2 * $Id: torrent.c 5908 2008-05-22 20:44:41Z charles $
3 *
4 * Copyright (c) 2005-2008 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 <sys/types.h> /* stat */
26#include <sys/stat.h> /* stat */
27#include <unistd.h> /* stat */
28
29#include <assert.h>
30#include <limits.h> /* INT_MAX */
31#include <string.h> /* memcmp */
32#include <stdlib.h> /* qsort */
33
34#include "transmission.h"
35#include "bencode.h"
36#include "completion.h"
37#include "crypto.h" /* for tr_sha1 */
38#include "resume.h"
39#include "fdlimit.h" /* tr_fdFileClose */
40#include "metainfo.h"
41#include "peer-mgr.h"
42#include "ratecontrol.h"
43#include "torrent.h"
44#include "tracker.h"
45#include "trevent.h"
46#include "utils.h"
47#include "verify.h"
48
49#define MAX_BLOCK_SIZE (1024*16)
50
51/***
52****
53***/
54
55int
56tr_torrentId( const tr_torrent * tor )
57{
58    return tor->uniqueId;
59}
60
61tr_torrent*
62tr_torrentFindFromId( tr_handle * handle, int id )
63{
64    tr_torrent * tor = NULL;
65
66    while(( tor = tr_torrentNext( handle, tor )))
67        if( tor->uniqueId == id )
68            return tor;
69
70    return NULL;
71}
72
73tr_torrent*
74tr_torrentFindFromHashString( tr_handle * handle, const char * str )
75{
76    tr_torrent * tor = NULL;
77
78    while(( tor = tr_torrentNext( handle, tor )))
79        if( !strcmp( str, tor->info.hashString ) )
80            return tor;
81
82    return NULL;
83}
84
85int
86tr_torrentExists( const tr_handle * handle,
87                  const uint8_t   * torrentHash )
88{
89    return tr_torrentFindFromHash( (tr_handle*)handle, torrentHash ) != NULL;
90}
91
92tr_torrent*
93tr_torrentFindFromHash( tr_handle      * handle,
94                        const uint8_t  * torrentHash )
95{
96    tr_torrent * tor = NULL;
97
98    while(( tor = tr_torrentNext( handle, tor )))
99        if( !memcmp( tor->info.hash, torrentHash, SHA_DIGEST_LENGTH ) )
100            return tor;
101
102    return NULL;
103}
104
105tr_torrent*
106tr_torrentFindFromObfuscatedHash( tr_handle      * handle,
107                                  const uint8_t  * obfuscatedTorrentHash )
108{
109    tr_torrent * tor = NULL;
110
111    while(( tor = tr_torrentNext( handle, tor )))
112        if( !memcmp( tor->obfuscatedHash, obfuscatedTorrentHash, SHA_DIGEST_LENGTH ) )
113            return tor;
114
115    return NULL;
116}
117
118/***
119****  LOCKS
120***/
121
122void
123tr_torrentLock( const tr_torrent * tor )
124{
125    tr_globalLock( tor->handle );
126}
127
128void
129tr_torrentUnlock( const tr_torrent * tor )
130{
131    tr_globalUnlock( tor->handle );
132}
133
134/***
135****  PER-TORRENT UL / DL SPEEDS
136***/
137
138void
139tr_torrentSetSpeedMode( tr_torrent   * tor,
140                        int            up_or_down,
141                        tr_speedlimit  mode )
142{
143    tr_speedlimit * limit = up_or_down==TR_UP
144        ? &tor->uploadLimitMode
145        : &tor->downloadLimitMode;
146    *limit = mode;
147}
148
149tr_speedlimit
150tr_torrentGetSpeedMode( const tr_torrent * tor,
151                        int                up_or_down)
152{
153    return up_or_down==TR_UP ? tor->uploadLimitMode
154                             : tor->downloadLimitMode;
155}
156
157void
158tr_torrentSetSpeedLimit( tr_torrent   * tor,
159                         int            up_or_down,
160                         int            single_KiB_sec )
161{
162    tr_ratecontrol * rc = up_or_down==TR_UP ? tor->upload : tor->download;
163    tr_rcSetLimit( rc, single_KiB_sec );
164}
165
166int
167tr_torrentGetSpeedLimit( const tr_torrent  * tor,
168                         int                 up_or_down )
169{
170    tr_ratecontrol * rc = up_or_down==TR_UP ? tor->upload : tor->download;
171    return tr_rcGetLimit( rc );
172}
173
174/***
175****
176***/
177
178static void
179onTrackerResponse( void * tracker UNUSED, void * vevent, void * user_data )
180{
181    tr_torrent * tor = user_data;
182    tr_tracker_event * event = vevent;
183
184    switch( event->messageType )
185    {
186        case TR_TRACKER_PEERS: {
187            size_t i, n;
188            tr_pex * pex = tr_peerMgrCompactToPex( event->compact,
189                                                   event->compactLen,
190                                                   NULL, &n );
191            if( event->allAreSeeds )
192                tr_tordbg( tor, "Got %d seeds from tracker", (int)n );
193            else
194                tr_torinf( tor, _( "Got %d peers from tracker" ), (int)n );
195
196            for( i=0; i<n; ++i ) {
197                if( event->allAreSeeds )
198                    pex[i].flags |= ADDED_F_SEED_FLAG;
199                tr_peerMgrAddPex( tor->handle->peerMgr, tor->info.hash,
200                                  TR_PEER_FROM_TRACKER, pex+i );
201            }
202
203            tr_free( pex );
204            break;
205        }
206
207        case TR_TRACKER_WARNING:
208            tr_torerr( tor, _( "Tracker warning: \"%s\"" ), event->text );
209            tor->error = TR_ERROR_TC_WARNING;
210            tr_strlcpy( tor->errorString, event->text, sizeof(tor->errorString) );
211            break;
212
213        case TR_TRACKER_ERROR:
214            tr_torerr( tor, _( "Tracker error: \"%s\"" ), event->text );
215            tor->error = TR_ERROR_TC_ERROR;
216            tr_strlcpy( tor->errorString, event->text, sizeof(tor->errorString) );
217            break;
218
219        case TR_TRACKER_ERROR_CLEAR:
220            tor->error = 0;
221            tor->errorString[0] = '\0';
222            break;
223    }
224}
225
226/***
227****
228****  TORRENT INSTANTIATION
229****
230***/
231
232static int
233getBytePiece( const tr_info * info, uint64_t byteOffset )
234{
235    assert( info != NULL );
236    assert( info->pieceSize != 0 );
237
238    return byteOffset / info->pieceSize;
239}
240
241static void
242initFilePieces ( tr_info * info, tr_file_index_t fileIndex )
243{
244    tr_file * file = &info->files[fileIndex];
245    uint64_t firstByte, lastByte;
246
247    assert( info != NULL );
248    assert( fileIndex < info->fileCount );
249
250    file = &info->files[fileIndex];
251    firstByte = file->offset;
252    lastByte = firstByte + (file->length ? file->length-1 : 0);
253    file->firstPiece = getBytePiece( info, firstByte );
254    file->lastPiece = getBytePiece( info, lastByte );
255}
256
257static int
258pieceHasFile( tr_piece_index_t piece, const tr_file * file )
259{
260    return ( file->firstPiece <= piece ) && ( piece <= file->lastPiece );
261}
262
263static tr_priority_t
264calculatePiecePriority ( const tr_torrent * tor,
265                         tr_piece_index_t   piece,
266                         int                fileHint )
267{
268    tr_file_index_t i;
269    int priority = TR_PRI_LOW;
270
271    /* find the first file that has data in this piece */
272    if( fileHint >= 0 ) {
273        i = fileHint;
274        while( i>0 && pieceHasFile( piece, &tor->info.files[i-1] ) )
275            --i;
276    } else {
277        for( i=0; i<tor->info.fileCount; ++i )
278            if( pieceHasFile( piece, &tor->info.files[i] ) )
279                break;
280    }
281
282    /* the piece's priority is the max of the priorities
283     * of all the files in that piece */
284    for( ; i<tor->info.fileCount; ++i )
285    {
286        const tr_file * file = &tor->info.files[i];
287
288        if( !pieceHasFile( piece, file ) )
289            break;
290
291        priority = MAX( priority, file->priority );
292
293        /* when dealing with multimedia files, getting the first and
294           last pieces can sometimes allow you to preview it a bit
295           before it's fully downloaded... */
296        if ( file->priority >= TR_PRI_NORMAL )
297            if ( file->firstPiece == piece || file->lastPiece == piece )
298                priority = TR_PRI_HIGH;
299    }
300
301    return priority;
302}
303
304static void
305tr_torrentInitFilePieces( tr_torrent * tor )
306{
307    tr_file_index_t ff;
308    tr_piece_index_t pp;
309    uint64_t offset = 0;
310
311    assert( tor != NULL );
312
313    for( ff=0; ff<tor->info.fileCount; ++ff ) {
314      tor->info.files[ff].offset = offset;
315      offset += tor->info.files[ff].length;
316      initFilePieces( &tor->info, ff );
317    }
318
319    for( pp=0; pp<tor->info.pieceCount; ++pp )
320        tor->info.pieces[pp].priority = calculatePiecePriority( tor, pp, -1 );
321}
322
323int
324tr_torrentPromoteTracker( tr_torrent * tor, int pos )
325{
326    int i;
327    int tier;
328
329    assert( tor != NULL );
330    assert( ( 0 <= pos ) && ( pos < tor->info.trackerCount ) );
331
332    /* the tier of the tracker we're promoting */
333    tier = tor->info.trackers[pos].tier;
334
335    /* find the index of that tier's first tracker */
336    for( i=0; i<tor->info.trackerCount; ++i )
337        if( tor->info.trackers[i].tier == tier )
338            break;
339
340    assert( i < tor->info.trackerCount );
341
342    /* promote the tracker at `pos' to the front of the tier */
343    if( i != pos ) {
344        const tr_tracker_info tmp = tor->info.trackers[i];
345        tor->info.trackers[i] = tor->info.trackers[pos];
346        tor->info.trackers[pos] = tmp;
347    }
348
349    /* return the new position of the tracker that started out at [pos] */
350    return i;
351}
352
353struct RandomTracker
354{
355    tr_tracker_info tracker;
356    int random_value;
357};
358
359/* the tiers will be sorted from lowest to highest,
360 * and trackers are randomized within the tiers */
361static int
362compareRandomTracker( const void * va, const void * vb )
363{
364    const struct RandomTracker * a = va;
365    const struct RandomTracker * b = vb;
366
367    if( a->tracker.tier != b->tracker.tier )
368        return a->tracker.tier - b->tracker.tier;
369
370   return a->random_value - b->random_value;
371}
372   
373static void
374randomizeTiers( tr_info * info )
375{
376    int i;
377    const int n = info->trackerCount;
378    struct RandomTracker * r = tr_new0( struct RandomTracker, n );
379    for( i=0; i<n; ++i ) {
380        r[i].tracker = info->trackers[i];
381        r[i].random_value = tr_rand( INT_MAX );
382    }
383    qsort( r, n, sizeof( struct RandomTracker ), compareRandomTracker );
384    for( i=0; i<n; ++i )
385        info->trackers[i] = r[i].tracker;
386    tr_free( r );
387}
388
389static void torrentStart( tr_torrent * tor, int reloadProgress );
390
391static void
392torrentRealInit( tr_handle     * h,
393                 tr_torrent    * tor,
394                 const tr_ctor * ctor )
395{
396    int doStart;
397    uint64_t loaded;
398    uint64_t t;
399    tr_info * info = &tor->info;
400    static int nextUniqueId = 1;
401   
402    tr_globalLock( h );
403
404    tor->handle   = h;
405    tor->uniqueId = nextUniqueId++;
406
407    randomizeTiers( info );
408
409    /**
410     * Decide on a block size.  constraints:
411     * (1) most clients decline requests over 16 KiB
412     * (2) pieceSize must be a multiple of block size
413     */
414    tor->blockSize = info->pieceSize;
415    while( tor->blockSize > MAX_BLOCK_SIZE )
416        tor->blockSize /= 2;
417
418    tor->lastPieceSize = info->totalSize % info->pieceSize;
419
420    if( !tor->lastPieceSize )
421         tor->lastPieceSize = info->pieceSize;
422
423    tor->lastBlockSize = info->totalSize % tor->blockSize;
424
425    if( !tor->lastBlockSize )
426         tor->lastBlockSize = tor->blockSize;
427
428    tor->blockCount =
429        ( info->totalSize + tor->blockSize - 1 ) / tor->blockSize;
430
431    tor->blockCountInPiece =
432        info->pieceSize / tor->blockSize;
433
434    tor->blockCountInLastPiece =
435        ( tor->lastPieceSize + tor->blockSize - 1 ) / tor->blockSize;
436
437    /* check our work */
438    assert( ( info->pieceSize % tor->blockSize ) == 0 );
439    t = info->pieceCount - 1;
440    t *= info->pieceSize;
441    t += tor->lastPieceSize;
442    assert( t == info->totalSize );
443    t = tor->blockCount - 1;
444    t *= tor->blockSize;
445    t += tor->lastBlockSize;
446    assert( t == info->totalSize );
447    t = info->pieceCount - 1;
448    t *= tor->blockCountInPiece;
449    t += tor->blockCountInLastPiece;
450    assert( t == (uint64_t)tor->blockCount );
451
452    tor->completion = tr_cpInit( tor );
453
454    tr_torrentInitFilePieces( tor );
455
456    tor->upload         = tr_rcInit();
457    tor->download       = tr_rcInit();
458    tor->swarmSpeed     = tr_rcInit();
459
460    tr_sha1( tor->obfuscatedHash, "req2", 4,
461                                  info->hash, SHA_DIGEST_LENGTH,
462                                  NULL );
463
464    tr_peerMgrAddTorrent( h->peerMgr, tor );
465
466    if( !h->isPortSet )
467        tr_sessionSetPublicPort( h, TR_DEFAULT_PORT );
468
469    assert( !tor->downloadedCur );
470    assert( !tor->uploadedCur );
471
472    tor->error   = TR_OK;
473
474    tor->checkedPieces = tr_bitfieldNew( tor->info.pieceCount );
475    tr_torrentUncheck( tor );
476    loaded = tr_torrentLoadResume( tor, ~0, ctor );
477   
478    doStart = tor->isRunning;
479    tor->isRunning = 0;
480
481    if( !(loaded & TR_FR_SPEEDLIMIT ) ) {
482        tr_torrentSetSpeedLimit( tor, TR_UP,
483                tr_sessionGetSpeedLimit( tor->handle, TR_UP ) );
484        tr_torrentSetSpeedLimit( tor, TR_DOWN,
485                tr_sessionGetSpeedLimit( tor->handle, TR_DOWN ) );
486    }
487
488    tor->cpStatus = tr_cpGetStatus( tor->completion );
489
490    tor->tracker = tr_trackerNew( tor );
491    tor->trackerSubscription = tr_trackerSubscribe( tor->tracker, onTrackerResponse, tor );
492
493    {
494        tr_torrent * it = NULL;
495        tr_torrent * last = NULL;
496        while(( it = tr_torrentNext( h, it )))
497            last = it;
498        if( !last )
499            h->torrentList = tor;
500        else
501            last->next = tor;
502        ++h->torrentCount;
503    }
504
505    tr_globalUnlock( h );
506
507    /* maybe save our own copy of the metainfo */
508    if( tr_ctorGetSave( ctor ) ) {
509        const tr_benc * val;
510        if( !tr_ctorGetMetainfo( ctor, &val ) ) {
511            const char * filename = tor->info.torrent;
512            tr_bencSaveFile( filename, val );
513            tr_sessionSetTorrentFile( tor->handle, tor->info.hashString, filename );
514        }
515    }
516
517    tr_metainfoMigrate( h, &tor->info );
518
519    if( doStart )
520        torrentStart( tor, FALSE );
521}
522
523int
524tr_torrentParse( const tr_handle  * handle,
525                 const tr_ctor    * ctor,
526                 tr_info          * setmeInfo )
527{
528    int err = 0;
529    int doFree;
530    tr_info tmp;
531    const tr_benc * metainfo;
532
533    if( setmeInfo == NULL )
534        setmeInfo = &tmp;
535    memset( setmeInfo, 0, sizeof( tr_info ) );
536
537    if( !err && tr_ctorGetMetainfo( ctor, &metainfo ) )
538        return TR_EINVALID;
539
540    err = tr_metainfoParse( handle, setmeInfo, metainfo );
541    doFree = !err && ( setmeInfo == &tmp );
542
543    if( !err && tr_torrentExists( handle, setmeInfo->hash ) )
544        err = TR_EDUPLICATE;
545
546    if( doFree )
547        tr_metainfoFree( setmeInfo );
548
549    return err;
550}
551
552tr_torrent *
553tr_torrentNew( tr_handle      * handle,
554               const tr_ctor  * ctor,
555               int            * setmeError )
556{
557    int err;
558    tr_info tmpInfo;
559    tr_torrent * tor = NULL;
560
561    err = tr_torrentParse( handle, ctor, &tmpInfo );
562    if( !err ) {
563        tor = tr_new0( tr_torrent, 1 );
564        tor->info = tmpInfo;
565        torrentRealInit( handle, tor, ctor );
566    } else if( setmeError ) {
567        *setmeError = err;
568    }
569
570    return tor;
571}
572
573/**
574***
575**/
576
577void
578tr_torrentSetDownloadDir( tr_torrent * tor, const char * path )
579{
580    if( !path || !tor->downloadDir || strcmp( path, tor->downloadDir ) )
581    {
582        tr_free( tor->downloadDir );
583        tor->downloadDir = tr_strdup( path );
584        tr_torrentSaveResume( tor );
585    }
586}
587
588const char*
589tr_torrentGetDownloadDir( const tr_torrent * tor )
590{
591    return tor->downloadDir;
592}
593
594void
595tr_torrentChangeMyPort( tr_torrent * tor )
596{
597    if( tor->tracker )
598        tr_trackerChangeMyPort( tor->tracker );
599}
600
601int
602tr_torrentIsPrivate( const tr_torrent * tor )
603{
604    return tor
605        && tor->info.isPrivate;
606}
607
608int
609tr_torrentAllowsPex( const tr_torrent * tor )
610{
611    return tor
612        && tor->handle->isPexEnabled
613        && !tr_torrentIsPrivate( tor );
614}
615
616static void
617tr_torrentManualUpdateImpl( void * vtor )
618{
619    tr_torrent * tor = vtor;
620    if( tor->isRunning )
621        tr_trackerReannounce( tor->tracker );
622}
623void
624tr_torrentManualUpdate( tr_torrent * tor )
625{
626    tr_runInEventThread( tor->handle, tr_torrentManualUpdateImpl, tor );
627}
628int
629tr_torrentCanManualUpdate( const tr_torrent * tor )
630{
631    return ( tor != NULL )
632        && ( tor->isRunning )
633        && ( tr_trackerCanManualAnnounce( tor->tracker ) );
634}
635
636/* rcRate's averaging code can make it appear that we're
637 * still sending bytes after a torrent stops or all the
638 * peers disconnect, so short-circuit that appearance here */
639void
640tr_torrentGetRates( const tr_torrent * tor,
641                    float            * toClient,
642                    float            * toPeer)
643{
644    const int showSpeed = tor->isRunning
645        && tr_peerMgrHasConnections( tor->handle->peerMgr, tor->info.hash );
646
647    if( toClient )
648        *toClient = showSpeed ? tr_rcRate( tor->download ) : 0.0;
649    if( toPeer )
650        *toPeer = showSpeed ? tr_rcRate( tor->upload ) : 0.0;
651}
652const tr_info *
653tr_torrentInfo( const tr_torrent * tor )
654{
655    return tor ? &tor->info : NULL;
656}
657
658const tr_stat *
659tr_torrentStatCached( tr_torrent * tor )
660{
661    const time_t now = time( NULL );
662
663    return tor && ( now == tor->lastStatTime ) ? &tor->stats
664                                               : tr_torrentStat( tor );
665}
666
667tr_torrent_status
668tr_torrentGetStatus( tr_torrent * tor )
669{
670    tr_torrentRecheckCompleteness( tor );
671
672    if( tor->verifyState == TR_VERIFY_NOW )
673        return TR_STATUS_CHECK;
674    if( tor->verifyState == TR_VERIFY_WAIT )
675        return TR_STATUS_CHECK_WAIT;
676    if( !tor->isRunning )
677        return TR_STATUS_STOPPED;
678    if( tor->cpStatus == TR_CP_INCOMPLETE )
679        return TR_STATUS_DOWNLOAD;
680
681    return TR_STATUS_SEED;
682}
683
684const tr_stat *
685tr_torrentStat( tr_torrent * tor )
686{
687    tr_stat * s;
688    struct tr_tracker * tc;
689    const tr_tracker_info * ti;
690
691    if( !tor )
692        return NULL;
693
694    tr_torrentLock( tor );
695
696    tor->lastStatTime = time( NULL );
697
698    s = &tor->stats;
699    s->id = tor->uniqueId;
700    s->status = tr_torrentGetStatus( tor );
701    s->error  = tor->error;
702    memcpy( s->errorString, tor->errorString,
703            sizeof( s->errorString ) );
704
705    tc = tor->tracker;
706    ti = tr_trackerGetAddress( tor->tracker );
707    s->announceURL = ti ? ti->announce : NULL;
708    s->scrapeURL   = ti ? ti->scrape   : NULL;
709    tr_trackerStat( tc, &s->trackerStat );
710    tr_trackerGetCounts( tc, &s->completedFromTracker,
711                             &s->leechers, 
712                             &s->seeders );
713
714    tr_peerMgrTorrentStats( tor->handle->peerMgr,
715                            tor->info.hash,
716                            &s->peersKnown,
717                            &s->peersConnected,
718                            &s->peersSendingToUs,
719                            &s->peersGettingFromUs,
720                             s->peersFrom );
721
722    s->manualAnnounceTime = tr_trackerGetManualAnnounceTime( tor->tracker );
723
724    s->percentComplete = tr_cpPercentComplete ( tor->completion );
725
726    s->percentDone = tr_cpPercentDone( tor->completion );
727    s->leftUntilDone = tr_cpLeftUntilDone( tor->completion );
728    s->sizeWhenDone = tr_cpSizeWhenDone( tor->completion );
729
730    s->recheckProgress =
731        1.0 - (tr_torrentCountUncheckedPieces( tor ) / (double) tor->info.pieceCount);
732
733    tr_torrentGetRates( tor, &s->rateDownload, &s->rateUpload );
734
735    s->swarmSpeed = tr_rcRate( tor->swarmSpeed );
736   
737    s->startDate = tor->startDate;
738    s->activityDate = tor->activityDate;
739
740    s->corruptEver     = tor->corruptCur    + tor->corruptPrev;
741    s->downloadedEver  = tor->downloadedCur + tor->downloadedPrev;
742    s->uploadedEver    = tor->uploadedCur   + tor->uploadedPrev;
743    s->haveValid       = tr_cpHaveValid( tor->completion );
744    s->haveUnchecked   = tr_cpHaveTotal( tor->completion ) - s->haveValid;
745
746
747    {
748        tr_piece_index_t i;
749        tr_bitfield * peerPieces = tr_peerMgrGetAvailable( tor->handle->peerMgr,
750                                                           tor->info.hash );
751        s->desiredAvailable = 0;
752        for( i=0; i<tor->info.pieceCount; ++i )
753            if( !tor->info.pieces[i].dnd && tr_bitfieldHas( peerPieces, i ) )
754                s->desiredAvailable += tr_cpMissingBlocksInPiece( tor->completion, i );
755        s->desiredAvailable *= tor->blockSize;
756        tr_bitfieldFree( peerPieces );
757    }
758
759    if( s->leftUntilDone > s->desiredAvailable )
760        s->eta = TR_ETA_NOT_AVAIL;
761    else if( s->rateDownload < 0.1 )
762        s->eta = TR_ETA_UNKNOWN;
763    else
764        s->eta = s->leftUntilDone / s->rateDownload / 1024.0;
765
766    s->ratio = tr_getRatio( s->uploadedEver,
767                            s->downloadedEver ? s->downloadedEver : s->haveValid );
768   
769    tr_torrentUnlock( tor );
770
771    return s;
772}
773
774/***
775****
776***/
777
778static uint64_t
779fileBytesCompleted ( const tr_torrent * tor, tr_file_index_t fileIndex )
780{
781    const tr_file * file     =  &tor->info.files[fileIndex];
782    const tr_block_index_t firstBlock       =  file->offset / tor->blockSize;
783    const uint64_t firstBlockOffset =  file->offset % tor->blockSize;
784    const uint64_t lastOffset       =  file->length ? (file->length-1) : 0;
785    const tr_block_index_t lastBlock        = (file->offset + lastOffset) / tor->blockSize;
786    const uint64_t lastBlockOffset  = (file->offset + lastOffset) % tor->blockSize;
787    uint64_t haveBytes = 0;
788
789    assert( tor != NULL );
790    assert( fileIndex < tor->info.fileCount );
791    assert( file->offset + file->length <= tor->info.totalSize );
792    assert( ( firstBlock < tor->blockCount ) || (!file->length && file->offset==tor->info.totalSize) );
793    assert( ( lastBlock < tor->blockCount ) || (!file->length && file->offset==tor->info.totalSize) );
794    assert( firstBlock <= lastBlock );
795    assert( tr_torBlockPiece( tor, firstBlock ) == file->firstPiece );
796    assert( tr_torBlockPiece( tor, lastBlock ) == file->lastPiece );
797
798    if( firstBlock == lastBlock )
799    {
800        if( tr_cpBlockIsComplete( tor->completion, firstBlock ) )
801            haveBytes += lastBlockOffset + 1 - firstBlockOffset;
802    }
803    else
804    {
805        tr_block_index_t i;
806
807        if( tr_cpBlockIsComplete( tor->completion, firstBlock ) )
808            haveBytes += tor->blockSize - firstBlockOffset;
809
810        for( i=firstBlock+1; i<lastBlock; ++i )
811            if( tr_cpBlockIsComplete( tor->completion, i ) )
812               haveBytes += tor->blockSize;
813
814        if( tr_cpBlockIsComplete( tor->completion, lastBlock ) )
815            haveBytes += lastBlockOffset + 1;
816    }
817
818    return haveBytes;
819}
820
821tr_file_stat *
822tr_torrentFiles( const tr_torrent * tor, tr_file_index_t * fileCount )
823{
824    tr_file_index_t i;
825    const tr_file_index_t n = tor->info.fileCount;
826    tr_file_stat * files = tr_new0( tr_file_stat, n );
827    tr_file_stat * walk = files;
828
829    for( i=0; i<n; ++i, ++walk ) {
830        const uint64_t b = fileBytesCompleted( tor, i );
831        walk->bytesCompleted = b;
832        walk->progress = tr_getRatio( b, tor->info.files[i].length );
833    }
834
835    if( fileCount )
836        *fileCount = n;
837
838    return files;
839}
840
841void
842tr_torrentFilesFree( tr_file_stat * files, tr_file_index_t fileCount UNUSED )
843{
844    tr_free( files );
845}
846
847/***
848****
849***/
850
851tr_peer_stat *
852tr_torrentPeers( const tr_torrent * tor, int * peerCount )
853{
854    tr_peer_stat * ret = NULL;
855
856    if( tor != NULL )
857        ret = tr_peerMgrPeerStats( tor->handle->peerMgr,
858                                   tor->info.hash, peerCount );
859
860    return ret;
861}
862
863void
864tr_torrentPeersFree( tr_peer_stat * peers, int peerCount UNUSED )
865{
866    tr_free( peers );
867}
868
869void tr_torrentAvailability( const tr_torrent * tor, int8_t * tab, int size )
870{
871    return tr_peerMgrTorrentAvailability( tor->handle->peerMgr,
872                                          tor->info.hash,
873                                          tab, size );
874}
875
876void
877tr_torrentAmountFinished( const tr_torrent * tor, float * tab, int size )
878{
879    tr_torrentLock( tor );
880    tr_cpGetAmountDone( tor->completion, tab, size );
881    tr_torrentUnlock( tor );
882}
883
884void
885tr_torrentResetTransferStats( tr_torrent * tor )
886{
887    tr_torrentLock( tor );
888
889    tor->downloadedPrev += tor->downloadedCur;
890    tor->downloadedCur   = 0;
891    tor->uploadedPrev   += tor->uploadedCur;
892    tor->uploadedCur     = 0;
893    tor->corruptPrev    += tor->corruptCur;
894    tor->corruptCur      = 0;
895
896    tr_torrentUnlock( tor );
897}
898
899
900void
901tr_torrentSetHasPiece( tr_torrent * tor, tr_piece_index_t pieceIndex, int has )
902{
903    tr_torrentLock( tor );
904
905    assert( tor != NULL );
906    assert( pieceIndex < tor->info.pieceCount );
907
908    if( has )
909        tr_cpPieceAdd( tor->completion, pieceIndex );
910    else
911        tr_cpPieceRem( tor->completion, pieceIndex );
912
913    tr_torrentUnlock( tor );
914}
915
916void
917tr_torrentRemoveSaved( tr_torrent * tor )
918{
919    tr_metainfoRemoveSaved( tor->handle, &tor->info );
920
921    tr_torrentRemoveResume( tor );
922}
923
924/***
925****
926***/
927
928static void
929freeTorrent( tr_torrent * tor )
930{
931    tr_torrent * t;
932    tr_handle * h = tor->handle;
933    tr_info * inf = &tor->info;
934
935    assert( tor != NULL );
936    assert( !tor->isRunning );
937
938    tr_globalLock( h );
939
940    tr_peerMgrRemoveTorrent( h->peerMgr, tor->info.hash );
941
942    tr_cpClose( tor->completion );
943
944    tr_rcClose( tor->upload );
945    tr_rcClose( tor->download );
946    tr_rcClose( tor->swarmSpeed );
947
948    tr_trackerUnsubscribe( tor->tracker, tor->trackerSubscription );
949    tr_trackerFree( tor->tracker );
950    tor->tracker = NULL;
951
952    tr_bitfieldFree( tor->checkedPieces );
953
954    tr_free( tor->downloadDir );
955
956    if( tor == h->torrentList )
957        h->torrentList = tor->next;
958    else for( t=h->torrentList; t!=NULL; t=t->next ) {
959        if( t->next == tor ) {
960            t->next = tor->next;
961            break;
962        }
963    }
964
965    assert( h->torrentCount >= 1 );
966    h->torrentCount--;
967
968    tr_metainfoFree( inf );
969    tr_free( tor );
970
971    tr_globalUnlock( h );
972}
973
974/**
975***  Start/Stop Callback
976**/
977
978static void
979fireActiveChange( tr_torrent * tor, int isRunning )
980{
981    assert( tor != NULL );
982
983    if( tor->active_func != NULL )
984        (tor->active_func)( tor, isRunning, tor->active_func_user_data );
985}
986
987void
988tr_torrentSetActiveCallback( tr_torrent             * tor,
989                             tr_torrent_active_func   func,
990                             void                   * user_data )
991{
992    assert( tor != NULL );
993    tor->active_func = func;
994    tor->active_func_user_data = user_data;
995}
996
997void
998tr_torrentClearActiveCallback( tr_torrent * torrent )
999{
1000    tr_torrentSetActiveCallback( torrent, NULL, NULL );
1001}
1002
1003
1004static void
1005checkAndStartImpl( void * vtor )
1006{
1007    tr_torrent * tor = vtor;
1008
1009    tr_globalLock( tor->handle );
1010
1011    tor->isRunning  = 1;
1012    fireActiveChange( tor, tor->isRunning );
1013    *tor->errorString = '\0';
1014    tr_torrentResetTransferStats( tor );
1015    tor->cpStatus = tr_cpGetStatus( tor->completion );
1016    tr_torrentSaveResume( tor );
1017    tor->startDate = tr_date( );
1018    tr_trackerStart( tor->tracker );
1019    tr_peerMgrStartTorrent( tor->handle->peerMgr, tor->info.hash );
1020
1021    tr_globalUnlock( tor->handle );
1022}
1023
1024static void
1025checkAndStartCB( tr_torrent * tor )
1026{
1027    tr_runInEventThread( tor->handle, checkAndStartImpl, tor );
1028}
1029
1030static void
1031torrentStart( tr_torrent * tor, int reloadProgress )
1032{
1033    tr_globalLock( tor->handle );
1034
1035    if( !tor->isRunning )
1036    {
1037        if( reloadProgress )
1038            tr_torrentLoadResume( tor, TR_FR_PROGRESS, NULL );
1039        tor->isRunning = 1;
1040        tr_verifyAdd( tor, checkAndStartCB );
1041    }
1042
1043    tr_globalUnlock( tor->handle );
1044}
1045
1046void
1047tr_torrentStart( tr_torrent * tor )
1048{
1049    torrentStart( tor, TRUE );
1050}
1051
1052static void
1053torrentRecheckDoneImpl( void * vtor )
1054{
1055    tr_torrentRecheckCompleteness( vtor );
1056}
1057static void
1058torrentRecheckDoneCB( tr_torrent * tor )
1059{
1060    tr_runInEventThread( tor->handle, torrentRecheckDoneImpl, tor );
1061}
1062void
1063tr_torrentVerify( tr_torrent * tor )
1064{
1065    tr_verifyRemove( tor );
1066
1067    tr_globalLock( tor->handle );
1068
1069    tr_torrentUncheck( tor );
1070    tr_verifyAdd( tor, torrentRecheckDoneCB );
1071
1072    tr_globalUnlock( tor->handle );
1073}
1074
1075
1076static void
1077stopTorrent( void * vtor )
1078{
1079    tr_file_index_t i;
1080
1081    tr_torrent * tor = vtor;
1082    tr_verifyRemove( tor );
1083    tr_peerMgrStopTorrent( tor->handle->peerMgr, tor->info.hash );
1084    tr_trackerStop( tor->tracker );
1085    fireActiveChange( tor, 0 );
1086
1087    for( i=0; i<tor->info.fileCount; ++i )
1088    {
1089        char path[MAX_PATH_LENGTH];
1090        const tr_file * file = &tor->info.files[i];
1091        tr_buildPath( path, sizeof(path), tor->downloadDir, file->name, NULL );
1092        tr_fdFileClose( path );
1093    }
1094}
1095
1096void
1097tr_torrentStop( tr_torrent * tor )
1098{
1099    tr_globalLock( tor->handle );
1100
1101    tor->isRunning = 0;
1102    if( !tor->isDeleting )
1103        tr_torrentSaveResume( tor );
1104    tr_runInEventThread( tor->handle, stopTorrent, tor );
1105
1106    tr_globalUnlock( tor->handle );
1107}
1108
1109static void
1110closeTorrent( void * vtor )
1111{
1112    tr_torrent * tor = vtor;
1113    tr_torrentSaveResume( tor );
1114    tor->isRunning = 0;
1115    stopTorrent( tor );
1116    if( tor->isDeleting )
1117        tr_torrentRemoveSaved( tor );
1118    freeTorrent( tor );
1119}
1120
1121void
1122tr_torrentFree( tr_torrent * tor )
1123{
1124    if( tor != NULL )
1125    {
1126        tr_handle * handle = tor->handle;
1127        tr_globalLock( handle );
1128
1129        tr_torrentClearStatusCallback( tor );
1130        tr_runInEventThread( handle, closeTorrent, tor );
1131
1132        tr_globalUnlock( handle );
1133    }
1134}
1135
1136void
1137tr_torrentRemove( tr_torrent * tor )
1138{
1139    tor->isDeleting = 1;
1140    tr_torrentFree( tor );
1141}
1142
1143
1144/**
1145***  Completeness
1146**/
1147
1148static const char *
1149getCompletionString( int type )
1150{
1151    switch( type )
1152    {
1153        /* Translators: this is a minor point that's safe to skip over, but FYI:
1154           "Complete" and "Done" are specific, different terms in Transmission:
1155           "Complete" means we've downloaded every file in the torrent.
1156           "Done" means we're done downloading the files we wanted, but NOT all that exist */
1157        case TR_CP_DONE:     return _( "Done" );
1158        case TR_CP_COMPLETE: return _( "Complete" );
1159        default:             return _( "Incomplete" );
1160    }
1161}
1162
1163static void
1164fireStatusChange( tr_torrent * tor, cp_status_t status )
1165{
1166    assert( tor != NULL );
1167    assert( status==TR_CP_INCOMPLETE || status==TR_CP_DONE || status==TR_CP_COMPLETE );
1168
1169    if( tor->status_func != NULL )
1170        (tor->status_func)( tor, status, tor->status_func_user_data );
1171}
1172
1173void
1174tr_torrentSetStatusCallback( tr_torrent             * tor,
1175                             tr_torrent_status_func   func,
1176                             void                   * user_data )
1177{
1178    assert( tor != NULL );
1179    tor->status_func = func;
1180    tor->status_func_user_data = user_data;
1181}
1182
1183void
1184tr_torrentClearStatusCallback( tr_torrent * torrent )
1185{
1186    tr_torrentSetStatusCallback( torrent, NULL, NULL );
1187}
1188
1189void
1190tr_torrentRecheckCompleteness( tr_torrent * tor )
1191{
1192    cp_status_t cpStatus;
1193
1194    tr_torrentLock( tor );
1195
1196    cpStatus = tr_cpGetStatus( tor->completion );
1197
1198    if( cpStatus != tor->cpStatus )
1199    {
1200        const int recentChange = tor->downloadedCur != 0;
1201
1202        if( recentChange )
1203        {
1204            tr_torinf( tor, _( "State changed from \"%1$s\" to \"%2$s\"" ),
1205                            getCompletionString( tor->cpStatus ),
1206                            getCompletionString( cpStatus ) );
1207        }
1208
1209        tor->cpStatus = cpStatus;
1210        fireStatusChange( tor, cpStatus );
1211
1212        if( recentChange && ( cpStatus == TR_CP_COMPLETE ) )
1213            tr_trackerCompleted( tor->tracker );
1214
1215        tr_torrentSaveResume( tor );
1216    }
1217
1218    tr_torrentUnlock( tor );
1219}
1220
1221int
1222tr_torrentIsSeed( const tr_torrent * tor )
1223{
1224    return tor->cpStatus==TR_CP_COMPLETE || tor->cpStatus==TR_CP_DONE;
1225}
1226
1227/**
1228***  File priorities
1229**/
1230
1231void
1232tr_torrentInitFilePriority( tr_torrent      * tor,
1233                            tr_file_index_t   fileIndex,
1234                            tr_priority_t     priority )
1235{
1236    tr_piece_index_t i;
1237    tr_file * file;
1238
1239    assert( tor != NULL );
1240    assert( fileIndex < tor->info.fileCount );
1241    assert( priority==TR_PRI_LOW || priority==TR_PRI_NORMAL || priority==TR_PRI_HIGH );
1242
1243    file = &tor->info.files[fileIndex];
1244    file->priority = priority;
1245    for( i=file->firstPiece; i<=file->lastPiece; ++i )
1246      tor->info.pieces[i].priority = calculatePiecePriority( tor, i, fileIndex );
1247}
1248
1249void
1250tr_torrentSetFilePriorities( tr_torrent       * tor,
1251                             tr_file_index_t  * files,
1252                             tr_file_index_t    fileCount,
1253                             tr_priority_t      priority )
1254{
1255    tr_file_index_t i;
1256    tr_torrentLock( tor );
1257
1258    for( i=0; i<fileCount; ++i )
1259        tr_torrentInitFilePriority( tor, files[i], priority );
1260
1261    tr_torrentSaveResume( tor );
1262    tr_torrentUnlock( tor );
1263}
1264
1265tr_priority_t
1266tr_torrentGetFilePriority( const tr_torrent *  tor, tr_file_index_t file )
1267{
1268    tr_priority_t ret;
1269
1270    tr_torrentLock( tor );
1271    assert( tor != NULL );
1272    assert( file < tor->info.fileCount );
1273    ret = tor->info.files[file].priority;
1274    tr_torrentUnlock( tor );
1275
1276    return ret;
1277}
1278
1279tr_priority_t*
1280tr_torrentGetFilePriorities( const tr_torrent * tor )
1281{
1282    tr_file_index_t i;
1283    tr_priority_t * p;
1284
1285    tr_torrentLock( tor );
1286    p = tr_new0( tr_priority_t, tor->info.fileCount );
1287    for( i=0; i<tor->info.fileCount; ++i )
1288        p[i] = tor->info.files[i].priority;
1289    tr_torrentUnlock( tor );
1290
1291    return p;
1292}
1293
1294/**
1295***  File DND
1296**/
1297
1298int
1299tr_torrentGetFileDL( const tr_torrent * tor,
1300                     tr_file_index_t    file )
1301{
1302    int doDownload;
1303    tr_torrentLock( tor );
1304
1305    assert( file < tor->info.fileCount );
1306    doDownload = !tor->info.files[file].dnd;
1307
1308    tr_torrentUnlock( tor );
1309    return doDownload != 0;
1310}
1311
1312static void
1313setFileDND( tr_torrent      * tor,
1314            tr_file_index_t   fileIndex,
1315            int               doDownload )
1316{
1317    tr_file * file;
1318    const int dnd = !doDownload;
1319    tr_piece_index_t firstPiece, firstPieceDND;
1320    tr_piece_index_t lastPiece, lastPieceDND;
1321    tr_file_index_t i;
1322
1323    file = &tor->info.files[fileIndex];
1324    file->dnd = dnd;
1325    firstPiece = file->firstPiece;
1326    lastPiece = file->lastPiece;
1327
1328    /* can't set the first piece to DND unless
1329       every file using that piece is DND */
1330    firstPieceDND = dnd;
1331    if( fileIndex > 0 ) {
1332        for( i=fileIndex-1; firstPieceDND; --i ) {
1333            if( tor->info.files[i].lastPiece != firstPiece )
1334                break;
1335            firstPieceDND = tor->info.files[i].dnd;
1336            if( !i )
1337                break;
1338        }
1339    }
1340
1341    /* can't set the last piece to DND unless
1342       every file using that piece is DND */
1343    lastPieceDND = dnd;
1344    for( i=fileIndex+1; lastPieceDND && i<tor->info.fileCount; ++i ) {
1345        if( tor->info.files[i].firstPiece != lastPiece )
1346            break;
1347        lastPieceDND = tor->info.files[i].dnd;
1348    }
1349
1350    if( firstPiece == lastPiece )
1351    {
1352        tor->info.pieces[firstPiece].dnd = firstPieceDND && lastPieceDND;
1353    }
1354    else
1355    {
1356        tr_piece_index_t pp;
1357        tor->info.pieces[firstPiece].dnd = firstPieceDND;
1358        tor->info.pieces[lastPiece].dnd = lastPieceDND;
1359        for( pp=firstPiece+1; pp<lastPiece; ++pp )
1360            tor->info.pieces[pp].dnd = dnd;
1361    }
1362}
1363
1364void
1365tr_torrentInitFileDLs ( tr_torrent       * tor,
1366                        tr_file_index_t  * files,
1367                        tr_file_index_t    fileCount,
1368                        int                doDownload )
1369{
1370    tr_file_index_t i;
1371    tr_torrentLock( tor );
1372
1373    for( i=0; i<fileCount; ++i )
1374        setFileDND( tor, files[i], doDownload );
1375    tr_cpInvalidateDND ( tor->completion );
1376
1377    tr_torrentUnlock( tor );
1378}
1379
1380void
1381tr_torrentSetFileDLs ( tr_torrent      * tor,
1382                       tr_file_index_t * files,
1383                       tr_file_index_t   fileCount,
1384                       int               doDownload )
1385{
1386    tr_torrentLock( tor );
1387    tr_torrentInitFileDLs( tor, files, fileCount, doDownload );
1388    tr_torrentSaveResume( tor );
1389    tr_torrentUnlock( tor );
1390}
1391
1392/***
1393****
1394***/
1395
1396void
1397tr_torrentSetPeerLimit( tr_torrent  * tor,
1398                        uint16_t      maxConnectedPeers )
1399{
1400    tor->maxConnectedPeers = maxConnectedPeers;
1401}
1402
1403uint16_t
1404tr_torrentGetPeerLimit( const tr_torrent  * tor )
1405{
1406    return tor->maxConnectedPeers;
1407}
1408
1409/***
1410****
1411***/
1412
1413tr_block_index_t
1414_tr_block( const tr_torrent  * tor,
1415           tr_piece_index_t    index,
1416           uint32_t            offset )
1417{
1418    const tr_info * inf = &tor->info;
1419    tr_block_index_t ret;
1420    ret = index;
1421    ret *= ( inf->pieceSize / tor->blockSize );
1422    ret += offset / tor->blockSize;
1423    return ret;
1424}
1425
1426int
1427tr_torrentReqIsValid( const tr_torrent * tor,
1428                      tr_piece_index_t   index,
1429                      uint32_t           offset,
1430                      uint32_t           length )
1431{
1432    int err = 0;
1433
1434    if( index >= tor->info.pieceCount )
1435        err = 1;
1436    else if ( offset >= tr_torPieceCountBytes( tor, index ) )
1437        err = 2;
1438    else if( length > MAX_BLOCK_SIZE )
1439        err = 3;
1440    else if( tr_pieceOffset( tor, index, offset, length ) > tor->info.totalSize )
1441        err = 4;
1442
1443    return !err;
1444}
1445
1446
1447uint64_t
1448tr_pieceOffset( const tr_torrent * tor,
1449                tr_piece_index_t   index,
1450                uint32_t           offset,
1451                uint32_t           length )
1452{
1453    uint64_t ret;
1454    ret = tor->info.pieceSize;
1455    ret *= index;
1456    ret += offset;
1457    ret += length;
1458    return ret;
1459}
1460
1461/***
1462****
1463***/
1464
1465int
1466tr_torrentIsPieceChecked( const tr_torrent * tor, tr_piece_index_t piece )
1467{
1468    return tr_bitfieldHas( tor->checkedPieces, piece );
1469}
1470
1471void
1472tr_torrentSetPieceChecked( tr_torrent * tor, tr_piece_index_t piece, int isChecked )
1473{
1474    if( isChecked )
1475        tr_bitfieldAdd( tor->checkedPieces, piece );
1476    else
1477        tr_bitfieldRem( tor->checkedPieces, piece );
1478}
1479
1480void
1481tr_torrentSetFileChecked( tr_torrent * tor, tr_file_index_t fileIndex, int isChecked )
1482{
1483    const tr_file * file = &tor->info.files[fileIndex];
1484    const tr_piece_index_t begin = file->firstPiece;
1485    const tr_piece_index_t end = file->lastPiece + 1;
1486
1487    if( isChecked )
1488        tr_bitfieldAddRange ( tor->checkedPieces, begin, end );
1489    else
1490        tr_bitfieldRemRange ( tor->checkedPieces, begin, end );
1491}
1492
1493int
1494tr_torrentIsFileChecked( const tr_torrent * tor, tr_file_index_t fileIndex )
1495{
1496    const tr_file * file = &tor->info.files[fileIndex];
1497    const tr_piece_index_t begin = file->firstPiece;
1498    const tr_piece_index_t end = file->lastPiece + 1;
1499    tr_piece_index_t i;
1500    int isChecked = TRUE;
1501
1502    for( i=begin; isChecked && i<end; ++i )
1503        if( !tr_torrentIsPieceChecked( tor, i ) )
1504            isChecked = FALSE;
1505
1506    return isChecked;
1507}
1508
1509void
1510tr_torrentUncheck( tr_torrent * tor )
1511{
1512    tr_bitfieldRemRange ( tor->checkedPieces, 0, tor->info.pieceCount );
1513}
1514
1515int
1516tr_torrentCountUncheckedPieces( const tr_torrent * tor )
1517{
1518    return tor->info.pieceCount - tr_bitfieldCountTrueBits( tor->checkedPieces );
1519}
1520
1521time_t*
1522tr_torrentGetMTimes( const tr_torrent * tor, int * setme_n )
1523{
1524    int i;
1525    const int n = tor->info.fileCount;
1526    time_t * m = tr_new0( time_t, n );
1527
1528    for( i=0; i<n; ++i ) {
1529        char fname[MAX_PATH_LENGTH];
1530        struct stat sb;
1531        tr_buildPath( fname, sizeof(fname),
1532                      tor->downloadDir, tor->info.files[i].name, NULL );
1533        if ( !stat( fname, &sb ) ) {
1534#ifdef SYS_DARWIN
1535            m[i] = sb.st_mtimespec.tv_sec;
1536#else
1537            m[i] = sb.st_mtime;
1538#endif
1539        }
1540    }
1541
1542    *setme_n = n;
1543    return m;
1544}
Note: See TracBrowser for help on using the repository browser.