source: trunk/libtransmission/fastresume.h @ 1125

Last change on this file since 1125 was 1074, checked in by titer, 15 years ago

Handle torrents with empty folders (fixes #12)

  • Property svn:keywords set to Date Rev Author Id
File size: 11.3 KB
Line 
1/******************************************************************************
2 * $Id: fastresume.h 1074 2006-11-10 21:30:32Z livings124 $
3 *
4 * Copyright (c) 2005-2006 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>".
35 *
36 * All values are stored in the native endianness. Moving a
37 * libtransmission resume file from an architecture to another will not
38 * work, although it will not hurt either (the version will be wrong,
39 * so the resume file will not be read).
40 **********************************************************************/
41
42/* progress data:
43 *  - 4 bytes * number of files: mtimes of files
44 *  - 1 bit * number of blocks: whether we have the block or not
45 *  - 4 bytes * number of pieces (byte aligned): the pieces that have
46 *    been completed or started in each slot
47 */
48#define FR_ID_PROGRESS          0x01
49/* number of bytes downloaded */
50#define FR_ID_DOWNLOADED        0x02
51/* number of bytes uploaded */
52#define FR_ID_UPLOADED          0x03
53/* IPs and ports of connectable peers */
54#define FR_ID_PEERS             0x04
55
56/* macros for the length of various pieces of the progress data */
57#define FR_MTIME_LEN( t ) \
58  ( 4 * (t)->info.fileCount )
59#define FR_BLOCK_BITFIELD_LEN( t ) \
60  ( ( (t)->blockCount + 7 ) / 8 )
61#define FR_SLOTPIECE_LEN( t ) \
62  ( 4 * (t)->info.pieceCount )
63#define FR_PROGRESS_LEN( t ) \
64  ( FR_MTIME_LEN( t ) + FR_BLOCK_BITFIELD_LEN( t ) + FR_SLOTPIECE_LEN( t ) )
65
66static char * fastResumeFileName( tr_io_t * io )
67{
68    char * ret;
69
70    asprintf( &ret, "%s/resume.%s", tr_getCacheDirectory(),
71              io->tor->info.hashString );
72
73    return ret;
74}
75
76static int fastResumeMTimes( tr_io_t * io, int * tab )
77{
78    tr_torrent_t * tor = io->tor;
79    tr_info_t    * inf = &tor->info;
80
81    int           i;
82    char        * path;
83    struct stat   sb;
84
85    for( i = 0; i < inf->fileCount; i++ )
86    {
87        asprintf( &path, "%s/%s", tor->destination, inf->files[i].name );
88        if( stat( path, &sb ) )
89        {
90            tr_err( "Could not stat '%s'", path );
91            free( path );
92            return 1;
93        }
94        if( ( sb.st_mode & S_IFMT ) == S_IFREG )
95        {
96#ifdef SYS_DARWIN
97            tab[i] = ( sb.st_mtimespec.tv_sec & 0x7FFFFFFF );
98#else
99            tab[i] = ( sb.st_mtime & 0x7FFFFFFF );
100#endif
101        }
102        else
103        {
104            /* Empty folder */
105            tab[i] = 0;
106        }
107        free( path );
108    }
109
110    return 0;
111}
112
113static inline void fastResumeWriteData( uint8_t id, void * data, uint32_t size,
114                                        uint32_t count, FILE * file )
115{
116    uint32_t  datalen = size * count;
117
118    fwrite( &id, 1, 1, file );
119    fwrite( &datalen, 4, 1, file );
120    fwrite( data, size, count, file );
121}
122
123static void fastResumeSave( tr_io_t * io )
124{
125    tr_torrent_t * tor = io->tor;
126   
127    FILE    * file;
128    int       version = 1;
129    char    * path;
130    uint8_t * buf;
131    uint64_t  total;
132    int       size;
133
134    buf = malloc( FR_PROGRESS_LEN( tor ) );
135
136    /* Get file sizes */
137    if( fastResumeMTimes( io, (int*)buf ) )
138    {
139        free( buf );
140        return;
141    }
142
143    /* Create/overwrite the resume file */
144    path = fastResumeFileName( io );
145    if( !( file = fopen( path, "w" ) ) )
146    {
147        tr_err( "Could not open '%s' for writing", path );
148        free( buf );
149        free( path );
150        return;
151    }
152   
153    /* Write format version */
154    fwrite( &version, 4, 1, file );
155
156    /* Build and copy the bitfield for blocks */
157    memcpy(buf + FR_MTIME_LEN( tor ), tr_cpBlockBitfield( tor->completion ),
158           FR_BLOCK_BITFIELD_LEN( tor ) );
159
160    /* Copy the 'slotPiece' table */
161    memcpy(buf + FR_MTIME_LEN( tor ) + FR_BLOCK_BITFIELD_LEN( tor ),
162           io->slotPiece, FR_SLOTPIECE_LEN( tor ) );
163
164    /* Write progress data */
165    fastResumeWriteData( FR_ID_PROGRESS, buf, 1, FR_PROGRESS_LEN( tor ), file );
166    free( buf );
167
168    /* Write download and upload totals */
169    total = tor->downloadedCur + tor->downloadedPrev;
170    fastResumeWriteData( FR_ID_DOWNLOADED, &total, 8, 1, file );
171    total = tor->uploadedCur + tor->uploadedPrev;
172    fastResumeWriteData( FR_ID_UPLOADED, &total, 8, 1, file );
173
174    /* Write IPs and ports of connectable peers, if any */
175    if( ( size = tr_peerGetConnectable( tor, &buf ) ) > 0 )
176    {
177        fastResumeWriteData( FR_ID_PEERS, buf, size, 1, file );
178        free( buf );
179    }
180
181    fclose( file );
182
183    tr_dbg( "Resume file '%s' written", path );
184    free( path );
185}
186
187static int fastResumeLoadProgress( tr_io_t * io, FILE * file )
188{
189    tr_torrent_t * tor = io->tor;
190    tr_info_t    * inf = &tor->info;
191
192    int     * fileMTimes;
193    int       i, j;
194    uint8_t * buf;
195    size_t    len;
196
197    len = FR_PROGRESS_LEN( tor );
198    buf = calloc( len, 1 );
199    if( len != fread( buf, 1, len, file ) )
200    {
201        tr_inf( "Could not read from resume file" );
202        free( buf );
203        return 1;
204    }
205
206    /* Compare file mtimes */
207    fileMTimes = malloc( FR_MTIME_LEN( tor ) );
208    if( fastResumeMTimes( io, fileMTimes ) )
209    {
210        free( buf );
211        free( fileMTimes );
212        return 1;
213    }
214    if( memcmp( fileMTimes, buf, FR_MTIME_LEN( tor ) ) )
215    {
216        tr_inf( "File mtimes don't match" );
217        free( buf );
218        free( fileMTimes );
219        return 1;
220    }
221    free( fileMTimes );
222
223    /* Copy the bitfield for blocks and fill blockHave */
224    tr_cpBlockBitfieldSet( tor->completion, buf + FR_MTIME_LEN( tor ) );
225
226    /* Copy the 'slotPiece' table */
227    memcpy( io->slotPiece, buf + FR_MTIME_LEN( tor ) +
228            FR_BLOCK_BITFIELD_LEN( tor ), FR_SLOTPIECE_LEN( tor ) );
229
230    free( buf );
231
232    /* Update io->pieceSlot, io->slotsUsed, and tor->bitfield */
233    io->slotsUsed = 0;
234    for( i = 0; i < inf->pieceCount; i++ )
235    {
236        io->pieceSlot[i] = -1;
237        for( j = 0; j < inf->pieceCount; j++ )
238        {
239            if( io->slotPiece[j] == i )
240            {
241                // tr_dbg( "Has piece %d in slot %d", i, j );
242                io->pieceSlot[i] = j;
243                io->slotsUsed = MAX( io->slotsUsed, j + 1 );
244                break;
245            }
246        }
247    }
248    // tr_dbg( "Slot used: %d", io->slotsUsed );
249
250    return 0;
251}
252
253static int fastResumeLoadOld( tr_io_t * io, FILE * file )
254{
255    tr_torrent_t * tor = io->tor;
256   
257    int size;
258
259    /* Check the size */
260    size = 4 + FR_PROGRESS_LEN( tor );
261    fseek( file, 0, SEEK_END );
262    if( ftell( file ) != size )
263    {
264        tr_inf( "Wrong size for resume file (%d bytes, %d expected)",
265                (int)ftell( file ), size );
266        fclose( file );
267        return 1;
268    }
269
270    /* load progress information */
271    fseek( file, 4, SEEK_SET );
272    if( fastResumeLoadProgress( io, file ) )
273    {
274        fclose( file );
275        return 1;
276    }
277
278    fclose( file );
279
280    tr_inf( "Fast resuming successful (version 0)" );
281   
282    return 0;
283}
284
285static int fastResumeLoad( tr_io_t * io )
286{
287    tr_torrent_t * tor = io->tor;
288   
289    FILE    * file;
290    int       version = 0;
291    char    * path;
292    uint8_t   id;
293    uint32_t  len;
294    int       ret;
295
296    /* Open resume file */
297    path = fastResumeFileName( io );
298    if( !( file = fopen( path, "r" ) ) )
299    {
300        tr_inf( "Could not open '%s' for reading", path );
301        free( path );
302        return 1;
303    }
304    tr_dbg( "Resume file '%s' loaded", path );
305    free( path );
306
307    /* Check format version */
308    fread( &version, 4, 1, file );
309    if( 0 == version )
310    {
311        return fastResumeLoadOld( io, file );
312    }
313    if( 1 != version )
314    {
315        tr_inf( "Resume file has version %d, not supported", version );
316        fclose( file );
317        return 1;
318    }
319
320    ret = 1;
321    /* read each block of data */
322    while( 1 == fread( &id, 1, 1, file ) && 1 == fread( &len, 4, 1, file ) )
323    {
324        switch( id )
325        {
326            case FR_ID_PROGRESS:
327                /* read progress data */
328                if( (uint32_t)FR_PROGRESS_LEN( tor ) == len )
329                {
330                    if( fastResumeLoadProgress( io, file ) )
331                    {
332                        if( feof( file ) || ferror( file ) )
333                        {
334                            fclose( file );
335                            return 1;
336                        }
337                    }
338                    else
339                    {
340                        ret = 0;
341                    }
342                    continue;
343                }
344                break;
345
346            case FR_ID_DOWNLOADED:
347                /* read download total */
348                if( 8 == len)
349                {
350                    if( 1 != fread( &tor->downloadedPrev, 8, 1, file ) )
351                    {
352                        fclose( file );
353                        return 1;
354                    }
355                    continue;
356                }
357                break;
358
359            case FR_ID_UPLOADED:
360                /* read upload total */
361                if( 8 == len)
362                {
363                    if( 1 != fread( &tor->uploadedPrev, 8, 1, file ) )
364                    {
365                        fclose( file );
366                        return 1;
367                    }
368                    continue;
369                }
370                break;
371
372            case FR_ID_PEERS:
373            {
374                uint8_t * buf = malloc( len );
375                if( 1 != fread( buf, len, 1, file ) )
376                {
377                    free( buf );
378                    fclose( file );
379                    return 1;
380                }
381                tr_peerAddCompactMany( tor, buf, len );
382                free( buf );
383                continue;
384            }
385
386            default:
387                break;
388        }
389
390        /* if we didn't read the data, seek past it */
391        tr_inf( "Skipping resume data type %02x, %u bytes", id, len );
392        fseek( file, len, SEEK_CUR );
393    }
394
395    fclose( file );
396
397    if( !ret )
398    {
399        tr_inf( "Fast resuming successful" );
400    }
401   
402    return ret;
403}
Note: See TracBrowser for help on using the repository browser.