source: trunk/libtransmission/fastresume.c @ 5658

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

string folding

  • Property svn:keywords set to Date Rev Author Id
File size: 20.0 KB
Line 
1/******************************************************************************
2 * $Id: fastresume.c 5658 2008-04-21 15:22:54Z 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/***********************************************************************
26 * Fast resume
27 ***********************************************************************
28 * The format of the resume file is a 4 byte format version (currently 1),
29 * followed by several variable-sized blocks of data.  Each block is
30 * preceded by a 1 byte ID and a 4 byte length.  The currently recognized
31 * IDs are defined below by the FR_ID_* macros.  The length does not include
32 * the 5 bytes for the ID and length.
33 *
34 * The name of the resume file is "resume.<hash>-<tag>", although
35 * older files with a name of "resume.<hash>" will be recognized if
36 * the former doesn't exist.
37 *
38 * All values are stored in the native endianness. Moving a
39 * libtransmission resume file from an architecture to another will not
40 * work, although it will not hurt either (the version will be wrong,
41 * so the resume file will not be read).
42 **********************************************************************/
43
44#include <assert.h>
45#include <errno.h>
46#include <stdio.h>
47#include <stdlib.h> /* calloc */
48#include <string.h> /* strcpy, memset, memcmp */
49
50#include <sys/types.h>
51#include <sys/stat.h> /* stat */
52#include <unistd.h> /* unlink */
53
54#include "transmission.h"
55#include "completion.h"
56#include "fastresume.h"
57#include "peer-mgr.h"
58#include "platform.h"
59#include "resume.h" /* TR_FR_ bitwise enum */
60#include "torrent.h"
61#include "utils.h"
62
63/* time_t can be 32 or 64 bits... for consistency we'll hardwire 64 */ 
64typedef uint64_t tr_time_t; 
65
66enum
67{
68    /* number of bytes downloaded */
69    FR_ID_DOWNLOADED = 2,
70
71    /* number of bytes uploaded */
72    FR_ID_UPLOADED = 3,
73
74    /* progress data:
75     *  - 4 bytes * number of files: mtimes of files
76     *  - 1 bit * number of blocks: whether we have the block or not */
77    FR_ID_PROGRESS = 5,
78
79    /* dnd and priority
80     * char * number of files: l,n,h for low, normal, high priority
81     * char * number of files: t,f for DND flags */
82    FR_ID_PRIORITY = 6,
83
84    /* transfer speeds
85     * uint32_t: dl speed rate to use when the mode is single
86     * uint32_t: dl's tr_speedlimit
87     * uint32_t: ul speed rate to use when the mode is single
88     * uint32_t: ul's tr_speedlimit
89     */
90    FR_ID_SPEED = 8,
91
92    /* active
93     * char: 't' if running, 'f' if paused
94     */
95    FR_ID_RUN = 9,
96
97    /* number of corrupt bytes downloaded */
98    FR_ID_CORRUPT = 10,
99
100    /* IPs and ports of connectable peers */
101    FR_ID_PEERS = 11,
102
103    /* destination of the torrent: zero-terminated string */
104    FR_ID_DESTINATION = 12,
105
106    /* pex flag
107     * 't' if pex is enabled, 'f' if disabled */
108    FR_ID_PEX = 13,
109
110    /* max connected peers -- uint16_t */
111    FR_ID_MAX_PEERS = 14
112};
113
114
115/* macros for the length of various pieces of the progress data */
116#define FR_MTIME_LEN( t ) \
117  ( sizeof(tr_time_t) * (t)->info.fileCount )
118#define FR_BLOCK_BITFIELD_LEN( t ) \
119  ( ( (t)->blockCount + 7u ) / 8u )
120#define FR_PROGRESS_LEN( t ) \
121  ( FR_MTIME_LEN( t ) + FR_BLOCK_BITFIELD_LEN( t ) )
122#define FR_SPEED_LEN (2 * (sizeof(uint16_t) + sizeof(uint8_t) ) )
123
124#if 0
125static void
126fastResumeFileName( char * buf, size_t buflen, const tr_torrent * tor, int tag )
127{
128    const char * cacheDir = tr_getResumeDir( tor->handle );
129    const char * hash = tor->info.hashString;
130
131    if( !tag )
132    {
133        tr_buildPath( buf, buflen, cacheDir, hash, NULL );
134    }
135    else
136    {
137        char base[1024];
138        snprintf( base, sizeof(base), "%s-%s", hash, tor->handle->tag );
139        tr_buildPath( buf, buflen, cacheDir, base, NULL );
140    }
141}
142#endif
143
144static tr_time_t*
145getMTimes( const tr_torrent * tor, int * setme_n )
146{
147    int i;
148    const int n = tor->info.fileCount;
149    tr_time_t * m = calloc( n, sizeof(tr_time_t) );
150
151    for( i=0; i<n; ++i ) {
152        char fname[MAX_PATH_LENGTH];
153        struct stat sb;
154        tr_buildPath( fname, sizeof(fname),
155                      tor->destination, tor->info.files[i].name, NULL );
156        if ( !stat( fname, &sb ) && S_ISREG( sb.st_mode ) ) {
157#ifdef SYS_DARWIN
158            m[i] = sb.st_mtimespec.tv_sec;
159#else
160            m[i] = sb.st_mtime;
161#endif
162        }
163    }
164
165    *setme_n = n;
166    return m;
167}
168
169#if 0
170static void
171fastResumeWriteData( uint8_t       id,
172                     const void  * data,
173                     uint32_t      size,
174                     uint32_t      count,
175                     FILE        * file )
176{
177    uint32_t  datalen = size * count;
178
179    fwrite( &id, 1, 1, file );
180    fwrite( &datalen, 4, 1, file );
181    fwrite( data, size, count, file );
182}
183
184void
185tr_fastResumeSave( const tr_torrent * tor )
186{
187    char      path[MAX_PATH_LENGTH];
188    FILE    * file;
189    const int version = 1;
190    uint64_t  total;
191
192    fastResumeFileName( path, sizeof path, tor, 1 );
193    file = fopen( path, "wb+" );
194    if( !file ) {
195        tr_torerr( tor, _( "Couldn't open \"%1$s\": %2$s" ), path, tr_strerror( errno ) );
196        return;
197    }
198   
199    /* Write format version */
200    fwrite( &version, 4, 1, file );
201
202    if( TRUE ) /* FR_ID_DESTINATION */
203    {
204        const char * d = tor->destination ? tor->destination : "";
205        const int byteCount = strlen( d ) + 1;
206        fastResumeWriteData( FR_ID_DESTINATION, d, 1, byteCount, file );
207    }
208
209    /* Write progress data */
210    if (1) {
211        int i, n;
212        tr_time_t * mtimes;
213        uint8_t * buf = malloc( FR_PROGRESS_LEN( tor ) );
214        uint8_t * walk = buf;
215        const tr_bitfield * bitfield;
216
217        /* mtimes */
218        mtimes = getMTimes( tor, &n );
219        for( i=0; i<n; ++i )
220            if( !tr_torrentIsFileChecked( tor, i ) )
221                mtimes[i] = ~(tr_time_t)0; /* force a recheck next time */
222        memcpy( walk, mtimes, n*sizeof(tr_time_t) );
223        walk += n * sizeof(tr_time_t);
224
225        /* completion bitfield */
226        bitfield = tr_cpBlockBitfield( tor->completion );
227        assert( (unsigned)FR_BLOCK_BITFIELD_LEN( tor ) == bitfield->len );
228        memcpy( walk, bitfield->bits, bitfield->len );
229        walk += bitfield->len;
230
231        /* write it */
232        assert( walk-buf == (int)FR_PROGRESS_LEN( tor ) );
233        fastResumeWriteData( FR_ID_PROGRESS, buf, 1, walk-buf, file );
234
235        /* cleanup */
236        free( mtimes );
237        free( buf );
238    }
239
240
241    /* Write the priorities and DND flags */
242    if( TRUE )
243    {
244        int i;
245        const int n = tor->info.fileCount;
246        char * buf = tr_new0( char, n*2 );
247        char * walk = buf;
248
249        /* priorities */
250        for( i=0; i<n; ++i ) {
251            char ch;
252            const int priority = tor->info.files[i].priority;
253            switch( priority ) {
254               case TR_PRI_LOW:   ch = 'l'; break; /* low */
255               case TR_PRI_HIGH:  ch = 'h'; break; /* high */
256               default:           ch = 'n'; break; /* normal */
257            };
258            *walk++ = ch;
259        }
260
261        /* dnd flags */
262        for( i=0; i<n; ++i )
263            *walk++ = tor->info.files[i].dnd ? 't' : 'f';
264
265        /* write it */
266        assert( walk - buf == 2*n );
267        fastResumeWriteData( FR_ID_PRIORITY, buf, 1, walk-buf, file );
268
269        /* cleanup */
270        tr_free( buf );
271    }
272
273
274    /* Write the torrent ul/dl speed caps */
275    if( TRUE )
276    {
277        const int len = FR_SPEED_LEN;
278        char * buf = tr_new0( char, len );
279        char * walk = buf;
280        uint16_t i16;
281        uint8_t i8;
282
283        i16 = (uint16_t) tr_torrentGetSpeedLimit( tor, TR_DOWN );
284        memcpy( walk, &i16, 2 ); walk += 2;
285        i8 = (uint8_t) tr_torrentGetSpeedMode( tor, TR_DOWN );
286        memcpy( walk, &i8, 1 ); walk += 1;
287        i16 = (uint16_t) tr_torrentGetSpeedLimit( tor, TR_UP );
288        memcpy( walk, &i16, 2 ); walk += 2;
289        i8 = (uint8_t) tr_torrentGetSpeedMode( tor, TR_UP );
290        memcpy( walk, &i8, 1 ); walk += 1;
291
292        assert( walk - buf == len );
293        fastResumeWriteData( FR_ID_SPEED, buf, 1, walk-buf, file );
294        tr_free( buf );
295    }
296
297    if( TRUE ) /* FR_ID_RUN */
298    {
299        const char is_running = tor->isRunning ? 't' : 'f';
300        fastResumeWriteData( FR_ID_RUN, &is_running, 1, 1, file );
301    }
302
303    /* Write download and upload totals */
304
305    total = tor->downloadedCur + tor->downloadedPrev;
306    fastResumeWriteData( FR_ID_DOWNLOADED, &total, 8, 1, file );
307
308    total = tor->uploadedCur + tor->uploadedPrev;
309    fastResumeWriteData( FR_ID_UPLOADED, &total, 8, 1, file );
310
311    total = tor->corruptCur + tor->corruptPrev;
312    fastResumeWriteData( FR_ID_CORRUPT, &total, 8, 1, file );
313
314    fastResumeWriteData( FR_ID_MAX_PEERS,
315                         &tor->maxConnectedPeers,
316                         sizeof(uint16_t), 1, file );
317
318    if( !tor->info.isPrivate )
319    {
320        tr_pex * pex;
321        const int count = tr_peerMgrGetPeers( tor->handle->peerMgr,
322                                              tor->info.hash,
323                                              &pex );
324        if( count > 0 )
325            fastResumeWriteData( FR_ID_PEERS, pex, sizeof(tr_pex), count, file );
326        tr_free( pex );
327    }
328
329    fclose( file );
330}
331#endif
332
333/***
334****
335***/
336
337static uint64_t
338internalIdToPublicBitfield( uint8_t id )
339{
340    uint64_t ret = 0;
341
342    switch( id )
343    {
344        case FR_ID_DOWNLOADED:     ret = TR_FR_DOWNLOADED;    break;
345        case FR_ID_UPLOADED:       ret = TR_FR_UPLOADED;      break;
346        case FR_ID_PROGRESS:       ret = TR_FR_PROGRESS;      break;
347        case FR_ID_PRIORITY:       ret = TR_FR_PRIORITY;      break;
348        case FR_ID_SPEED:          ret = TR_FR_SPEEDLIMIT;    break;
349        case FR_ID_RUN:            ret = TR_FR_RUN;           break;
350        case FR_ID_CORRUPT:        ret = TR_FR_CORRUPT;       break;
351        case FR_ID_PEERS:          ret = TR_FR_PEERS;         break;
352        case FR_ID_DESTINATION:    ret = TR_FR_DESTINATION;   break;
353        case FR_ID_MAX_PEERS:      ret = TR_FR_MAX_PEERS;     break;
354    }
355
356    return ret;
357}
358
359static void
360readBytes( void * target, const uint8_t ** source, size_t byteCount )
361{
362    memcpy( target, *source, byteCount );
363    *source += byteCount;
364}
365
366static uint64_t
367parseDownloaded( tr_torrent * tor, const uint8_t * buf, uint32_t len )
368{
369    if( len != sizeof(uint64_t) )
370        return 0;
371    readBytes( &tor->downloadedPrev, &buf, sizeof(uint64_t) );
372    return TR_FR_DOWNLOADED;
373}
374
375static uint64_t
376parseUploaded( tr_torrent * tor, const uint8_t * buf, uint32_t len )
377{
378    if( len != sizeof(uint64_t) )
379        return 0;
380    readBytes( &tor->uploadedPrev, &buf, sizeof(uint64_t) );
381    return TR_FR_UPLOADED;
382}
383
384static uint64_t
385parseCorrupt( tr_torrent * tor, const uint8_t * buf, uint32_t len )
386{
387    if( len != sizeof(uint64_t) )
388        return 0;
389    readBytes( &tor->corruptPrev, &buf, sizeof(uint64_t) );
390    return TR_FR_CORRUPT;
391}
392
393static uint64_t
394parseConnections( tr_torrent * tor, const uint8_t * buf, uint32_t len )
395{
396    if( len != sizeof(uint16_t) )
397        return 0;
398    readBytes( &tor->maxConnectedPeers, &buf, sizeof(uint16_t) );
399    return TR_FR_MAX_PEERS;
400}
401
402static uint64_t
403parseProgress( tr_torrent     * tor,
404               const uint8_t  * buf,
405               uint32_t         len )
406{
407    uint64_t ret = 0;
408   
409    if( len == FR_PROGRESS_LEN( tor ) )
410    {
411        int i;
412        int n;
413        tr_bitfield bitfield;
414
415        /* compare file mtimes */
416        tr_time_t * curMTimes = getMTimes( tor, &n );
417        const uint8_t * walk = buf;
418        tr_time_t mtime;
419        for( i=0; i<n; ++i ) {
420            readBytes( &mtime, &walk, sizeof(tr_time_t) );
421            if ( curMTimes[i] == mtime )
422                tr_torrentSetFileChecked( tor, i, TRUE );
423            else {
424                tr_torrentSetFileChecked( tor, i, FALSE );
425                tr_tordbg( tor, "Torrent needs to be verified" );
426            }
427        }
428        free( curMTimes );
429
430        /* get the completion bitfield */
431        memset( &bitfield, 0, sizeof bitfield );
432        bitfield.len = FR_BLOCK_BITFIELD_LEN( tor );
433        bitfield.bits = (uint8_t*) walk;
434        if( !tr_cpBlockBitfieldSet( tor->completion, &bitfield ) )
435            ret = TR_FR_PROGRESS;
436        else {
437            tr_torrentUncheck( tor );
438            tr_tordbg( tor, "Torrent needs to be verified" );
439        }
440    }
441
442    /* the files whose mtimes are wrong,
443       remove from completion pending a recheck... */
444    {
445        tr_piece_index_t i;
446        for( i=0; i<tor->info.pieceCount; ++i )
447            if( !tr_torrentIsPieceChecked( tor, i ) )
448                tr_cpPieceRem( tor->completion, i );
449    }
450
451    return ret;
452}
453
454static uint64_t
455parsePriorities( tr_torrent * tor, const uint8_t * buf, uint32_t len )
456{
457    uint64_t ret = 0;
458
459    if( len == (uint32_t)(2 * tor->info.fileCount) )
460    {
461        const size_t n = tor->info.fileCount;
462        const size_t len = 2 * n;
463        tr_file_index_t *dnd = NULL, dndCount = 0;
464        tr_file_index_t *dl = NULL, dlCount = 0;
465        size_t i;
466        const uint8_t * walk = buf;
467
468        /* set file priorities */
469        for( i=0; i<n; ++i ) {
470           tr_priority_t priority;
471           const char ch = *walk++;
472           switch( ch ) {
473               case 'l': priority = TR_PRI_LOW; break;
474               case 'h': priority = TR_PRI_HIGH; break;
475               default:  priority = TR_PRI_NORMAL; break;
476           }
477           tr_torrentInitFilePriority( tor, i, priority );
478        }
479
480        /* set the dnd flags */
481        dl = tr_new( tr_file_index_t, len );
482        dnd = tr_new( tr_file_index_t, len );
483        for( i=0; i<n; ++i )
484            if( *walk++ == 't' ) /* 't' means the DND flag is true */
485                dnd[dndCount++] = i;
486            else
487                dl[dlCount++] = i;
488
489        if( dndCount )
490            tr_torrentInitFileDLs ( tor, dnd, dndCount, FALSE );
491        if( dlCount )
492            tr_torrentInitFileDLs ( tor, dl, dlCount, TRUE );
493
494        tr_free( dnd );
495        tr_free( dl );
496
497        ret = TR_FR_PRIORITY;
498    }
499
500    return ret;
501}
502
503static uint64_t
504parseSpeedLimit( tr_torrent * tor, const uint8_t * buf, uint32_t len )
505{
506    uint64_t ret = 0;
507
508    if( len == FR_SPEED_LEN )
509    {
510        uint8_t i8;
511        uint16_t i16;
512
513        readBytes( &i16, &buf, sizeof(i16) );
514        tr_torrentSetSpeedLimit( tor, TR_DOWN, i16 );
515        readBytes( &i8, &buf, sizeof(i8) );
516        tr_torrentSetSpeedMode( tor, TR_DOWN, (tr_speedlimit)i8 );
517        readBytes( &i16, &buf, sizeof(i16) );
518        tr_torrentSetSpeedLimit( tor, TR_UP, i16 );
519        readBytes( &i8, &buf, sizeof(i8) );
520        tr_torrentSetSpeedMode( tor, TR_UP, (tr_speedlimit)i8 );
521
522        ret = TR_FR_SPEEDLIMIT;
523    }
524
525    return ret;
526}
527
528static uint64_t
529parseRun( tr_torrent * tor, const uint8_t * buf, uint32_t len )
530{
531    if( len != 1 )
532        return 0;
533    tor->isRunning = *buf=='t';
534    return TR_FR_RUN;
535}
536
537static uint64_t
538parsePeers( tr_torrent * tor, const uint8_t * buf, uint32_t len )
539{
540    uint64_t ret = 0;
541
542    if( !tor->info.isPrivate )
543    {
544        int i;
545        const int count = len / sizeof(tr_pex);
546
547        for( i=0; i<count; ++i )
548        {
549            tr_pex pex;
550            readBytes( &pex, &buf, sizeof( tr_pex ) );
551            tr_peerMgrAddPex( tor->handle->peerMgr, tor->info.hash, TR_PEER_FROM_CACHE, &pex );
552        }
553
554        tr_tordbg( tor, "Loaded %d peers from resume file", count );
555        ret = TR_FR_PEERS;
556    }
557
558    return ret;
559}
560
561static uint64_t
562parseDestination( tr_torrent * tor, const uint8_t * buf, uint32_t len )
563{
564    uint64_t ret = 0;
565
566    if( buf && *buf && len ) {
567        tr_free( tor->destination );
568        tor->destination = tr_strndup( (char*)buf, len );
569        ret = TR_FR_DESTINATION;
570    }
571
572    return ret;
573}
574
575static uint64_t
576parseVersion1( tr_torrent * tor, const uint8_t * buf, const uint8_t * end,
577               uint64_t fieldsToLoad )
578{
579    uint64_t ret = 0;
580
581    while( end-buf >= 5 )
582    {
583        uint8_t id;
584        uint32_t len;
585        readBytes( &id, &buf, sizeof(id) );
586        readBytes( &len, &buf, sizeof(len) );
587
588        if( fieldsToLoad & internalIdToPublicBitfield( id ) ) switch( id )
589        {
590            case FR_ID_DOWNLOADED:   ret |= parseDownloaded( tor, buf, len ); break;
591            case FR_ID_UPLOADED:     ret |= parseUploaded( tor, buf, len ); break;
592            case FR_ID_PROGRESS:     ret |= parseProgress( tor, buf, len ); break;
593            case FR_ID_PRIORITY:     ret |= parsePriorities( tor, buf, len ); break;
594            case FR_ID_SPEED:        ret |= parseSpeedLimit( tor, buf, len ); break;
595            case FR_ID_RUN:          ret |= parseRun( tor, buf, len ); break;
596            case FR_ID_CORRUPT:      ret |= parseCorrupt( tor, buf, len ); break;
597            case FR_ID_PEERS:        ret |= parsePeers( tor, buf, len ); break;
598            case FR_ID_MAX_PEERS:    ret |= parseConnections( tor, buf, len ); break;
599            case FR_ID_DESTINATION:  ret |= parseDestination( tor, buf, len ); break;
600            default:                 tr_tordbg( tor, "Skipping unknown resume code %d", (int)id ); break;
601        }
602
603        buf += len;
604    }
605
606    return ret;
607}
608
609static uint8_t* 
610loadResumeFile( const tr_torrent * tor, size_t * len )
611{
612    uint8_t * ret = NULL;
613    char path[MAX_PATH_LENGTH];
614    const char * cacheDir = tr_getResumeDir( tor->handle );
615    const char * hash = tor->info.hashString;
616
617    if( !ret && tor->handle->tag )
618    {
619        char base[1024];
620        snprintf( base, sizeof(base), "%s-%s", hash, tor->handle->tag );
621        tr_buildPath( path, sizeof(path), cacheDir, base, NULL );
622        ret = tr_loadFile( path, len );
623    }
624
625    if( !ret )
626    {
627        tr_buildPath( path, sizeof(path), cacheDir, hash, NULL );
628        ret = tr_loadFile( path, len );
629    }
630
631    return ret;
632}
633
634static uint64_t
635fastResumeLoadImpl ( tr_torrent   * tor,
636                     uint64_t       fieldsToLoad )
637{
638    uint64_t ret = 0;
639    size_t size = 0;
640    uint8_t * buf = loadResumeFile( tor, &size );
641
642    if( !buf )
643        /* %s is the torrent name */
644        tr_torinf( tor, _( "Couldn't read resume file" ) );
645    else {
646        const uint8_t * walk = buf;
647        const uint8_t * end = walk + size;
648        if( end - walk >= 4 ) {
649            uint32_t version;
650            readBytes( &version, &walk, sizeof(version) );
651            if( version == 1 )
652                ret |= parseVersion1 ( tor, walk, end, fieldsToLoad );
653            else
654                /* %s is the torrent name */
655                tr_torinf( tor, _( "Couldn't read resume file" ) );
656        }
657
658        tr_free( buf );
659    }
660
661    return ret;
662}
663
664uint64_t
665tr_fastResumeLoad( tr_torrent     * tor,
666                   uint64_t         fieldsToLoad )
667{
668    return fastResumeLoadImpl( tor, fieldsToLoad );
669}
670
671void
672tr_fastResumeRemove( const tr_torrent * tor )
673{
674    char path[MAX_PATH_LENGTH];
675    const char * cacheDir = tr_getResumeDir( tor->handle );
676    const char * hash = tor->info.hashString;
677
678    if( tor->handle->tag )
679    {
680        char base[1024];
681        snprintf( base, sizeof(base), "%s-%s", hash, tor->handle->tag );
682        tr_buildPath( path, sizeof(path), cacheDir, base, NULL );
683        unlink( path );
684    }
685    else
686    {
687        tr_buildPath( path, sizeof(path), cacheDir, hash, NULL );
688        unlink( path );
689    }
690}
Note: See TracBrowser for help on using the repository browser.