source: trunk/libtransmission/fastresume.h @ 100

Last change on this file since 100 was 100, checked in by joshe, 16 years ago

Merge the version 1 extensible resume file format,
as well as compatability code to read the existing version 0 files.
Note that older versions of transmission won't understand the version 1 format
and a hash check will be done on all the files.

Save uploaded and downloaded totals in the resume file.

File size: 10.4 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/***********************************************************************
24 * Fast resume
25 ***********************************************************************
26 * The format of the resume file is a 4 byte format version (currently 1),
27 * followed by several variable-sized blocks of data.  Each block is
28 * preceded by a 1 byte ID and a 4 byte length.  The currently recognized
29 * IDs are defined below by the FR_ID_* macros.  The length does not include
30 * the 5 bytes for the ID and length.
31 *
32 * The name of the resume file is "resume.<hash>".
33 *
34 * All values are stored in the native endianness. Moving a
35 * libtransmission resume file from an architecture to another will not
36 * work, although it will not hurt either (the version will be wrong,
37 * so the resume file will not be read).
38 **********************************************************************/
39
40/* progress data:
41 *  - 4 bytes * number of files: mtimes of files
42 *  - 1 bit * number of blocks: whether we have the block or not
43 *  - 4 bytes * number of pieces (byte aligned): the pieces that have
44 *    been completed or started in each slot
45 */
46#define FR_ID_PROGRESS          0x01
47/* number of bytes downloaded */
48#define FR_ID_DOWNLOADED        0x02
49/* number of bytes uploaded */
50#define FR_ID_UPLOADED          0x03
51
52/* macros for the length of various pieces of the progress data */
53#define FR_MTIME_LEN( t ) \
54  ( 4 * (t)->info.fileCount )
55#define FR_BLOCK_BITFIELD_LEN( t ) \
56  ( ( (t)->blockCount + 7 ) / 8 )
57#define FR_SLOTPIECE_LEN( t ) \
58  ( 4 * (t)->info.pieceCount )
59#define FR_PROGRESS_LEN( t ) \
60  ( FR_MTIME_LEN( t ) + FR_BLOCK_BITFIELD_LEN( t ) + FR_SLOTPIECE_LEN( t ) )
61
62static char * fastResumeFileName( tr_io_t * io )
63{
64    char * ret, * p;
65    int i;
66
67    asprintf( &ret, "%s/resume.%40d", tr_getPrefsDirectory(), 0 );
68
69    p = &ret[ strlen( ret ) - 2 * SHA_DIGEST_LENGTH ];
70    for( i = 0; i < SHA_DIGEST_LENGTH; i++ )
71    {
72        sprintf( p, "%02x", io->tor->info.hash[i] );
73        p += 2;
74    }
75
76    return ret;
77}
78
79static int fastResumeMTimes( tr_io_t * io, int * tab )
80{
81    tr_torrent_t * tor = io->tor;
82    tr_info_t    * inf = &tor->info;
83
84    int           i;
85    char        * path;
86    struct stat   sb;
87
88    for( i = 0; i < inf->fileCount; i++ )
89    {
90        asprintf( &path, "%s/%s", tor->destination, inf->files[i].name );
91        if( stat( path, &sb ) )
92        {
93            tr_err( "Could not stat '%s'", path );
94            free( path );
95            return 1;
96        }
97        if( ( sb.st_mode & S_IFMT ) != S_IFREG )
98        {
99            tr_err( "Wrong st_mode for '%s'", path );
100            free( path );
101            return 1;
102        }
103        free( path );
104
105#ifdef SYS_DARWIN
106        tab[i] = ( sb.st_mtimespec.tv_sec & 0x7FFFFFFF );
107#else
108        tab[i] = ( sb.st_mtime & 0x7FFFFFFF );
109#endif
110    }
111
112    return 0;
113}
114
115static inline void fastResumeWriteData( uint8_t id, void * data, uint32_t size,
116                                        uint32_t count, FILE * file )
117{
118    uint32_t  datalen = size * count;
119
120    fwrite( &id, 1, 1, file );
121    fwrite( &datalen, 4, 1, file );
122    fwrite( data, size, count, file );
123}
124
125static void fastResumeSave( tr_io_t * io )
126{
127    tr_torrent_t * tor = io->tor;
128   
129    FILE    * file;
130    int       version = 1;
131    char    * path;
132    uint8_t * buf;
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    fastResumeWriteData( FR_ID_DOWNLOADED, &tor->downloaded, 8, 1, file );
170    fastResumeWriteData( FR_ID_UPLOADED, &tor->uploaded, 8, 1, file );
171
172    fclose( file );
173
174    tr_dbg( "Resume file '%s' written", path );
175    free( path );
176}
177
178static int fastResumeLoadProgress( tr_io_t * io, FILE * file )
179{
180    tr_torrent_t * tor = io->tor;
181    tr_info_t    * inf = &tor->info;
182
183    int     * fileMTimes;
184    int       i, j;
185    uint8_t * buf;
186    size_t    len;
187
188    len = FR_PROGRESS_LEN( tor );
189    buf = calloc( len, 1 );
190    if( len != fread( buf, 1, len, file ) )
191    {
192        tr_inf( "Could not read from resume file" );
193        free( buf );
194        return 1;
195    }
196
197    /* Compare file mtimes */
198    fileMTimes = malloc( FR_MTIME_LEN( tor ) );
199    if( fastResumeMTimes( io, fileMTimes ) )
200    {
201        free( buf );
202        free( fileMTimes );
203        return 1;
204    }
205    if( memcmp( fileMTimes, buf, FR_MTIME_LEN( tor ) ) )
206    {
207        tr_inf( "File mtimes don't match" );
208        free( buf );
209        free( fileMTimes );
210        return 1;
211    }
212    free( fileMTimes );
213
214    /* Copy the bitfield for blocks and fill blockHave */
215    tr_cpBlockBitfieldSet( tor->completion, buf + FR_MTIME_LEN( tor ) );
216
217    /* Copy the 'slotPiece' table */
218    memcpy( io->slotPiece, buf + FR_MTIME_LEN( tor ) +
219            FR_BLOCK_BITFIELD_LEN( tor ), FR_SLOTPIECE_LEN( tor ) );
220
221    free( buf );
222
223    /* Update io->pieceSlot, io->slotsUsed, and tor->bitfield */
224    io->slotsUsed = 0;
225    for( i = 0; i < inf->pieceCount; i++ )
226    {
227        io->pieceSlot[i] = -1;
228        for( j = 0; j < inf->pieceCount; j++ )
229        {
230            if( io->slotPiece[j] == i )
231            {
232                // tr_dbg( "Has piece %d in slot %d", i, j );
233                io->pieceSlot[i] = j;
234                io->slotsUsed = MAX( io->slotsUsed, j + 1 );
235                break;
236            }
237        }
238    }
239    // tr_dbg( "Slot used: %d", io->slotsUsed );
240
241    return 0;
242}
243
244static int fastResumeLoadOld( tr_io_t * io, FILE * file )
245{
246    tr_torrent_t * tor = io->tor;
247   
248    int size;
249
250    /* Check the size */
251    size = 4 + FR_PROGRESS_LEN( tor );
252    fseek( file, 0, SEEK_END );
253    if( ftell( file ) != size )
254    {
255        tr_inf( "Wrong size for resume file (%d bytes, %d expected)",
256                ftell( file ), size );
257        fclose( file );
258        return 1;
259    }
260    fseek( file, 0, SEEK_SET );
261
262    /* load progress information */
263    if( fastResumeLoadProgress( io, file ) )
264    {
265        fclose( file );
266        return 1;
267    }
268
269    fclose( file );
270
271    tr_inf( "Fast resuming successful (version 0)" );
272   
273    return 0;
274}
275
276static int fastResumeLoad( tr_io_t * io )
277{
278    tr_torrent_t * tor = io->tor;
279   
280    FILE    * file;
281    int       version = 0;
282    char    * path;
283    uint8_t   id;
284    uint32_t  len;
285    int       ret;
286
287    /* Open resume file */
288    path = fastResumeFileName( io );
289    if( !( file = fopen( path, "r" ) ) )
290    {
291        tr_inf( "Could not open '%s' for reading", path );
292        free( path );
293        return 1;
294    }
295    tr_dbg( "Resume file '%s' loaded", path );
296    free( path );
297
298    /* Check format version */
299    fread( &version, 4, 1, file );
300    if( 0 == version )
301    {
302        return fastResumeLoadOld( io, file );
303    }
304    if( 1 != version )
305    {
306        tr_inf( "Resume file has version %d, not supported", version );
307        fclose( file );
308        return 1;
309    }
310
311    ret = 1;
312    /* read each block of data */
313    while( 1 == fread( &id, 1, 1, file ) && 1 == fread( &len, 4, 1, file ) )
314    {
315        switch( id )
316        {
317            case FR_ID_PROGRESS:
318                /* read progress data */
319                if( (uint32_t)FR_PROGRESS_LEN( tor ) == len )
320                {
321                    if( fastResumeLoadProgress( io, file ) )
322                    {
323                        fclose( file );
324                        return 1;
325                    }
326                    ret = 0;
327                    continue;
328                }
329                break;
330
331            case FR_ID_DOWNLOADED:
332                /* read download total */
333                if( 8 == len)
334                {
335                    if( 1 != fread( &tor->downloaded, 8, 1, file ) )
336                    {
337                      fclose( file );
338                      return 1;
339                    }
340                    continue;
341                }
342                break;
343
344            case FR_ID_UPLOADED:
345                /* read upload total */
346                if( 8 == len)
347                {
348                    if( 1 != fread( &tor->uploaded, 8, 1, file ) )
349                    {
350                      fclose( file );
351                      return 1;
352                    }
353                    continue;
354                }
355                break;
356
357            default:
358                break;
359        }
360
361        /* if we didn't read the data, seek past it */
362        tr_inf( "Skipping resume data type %02x, %u bytes", id, len );
363        fseek( file, len, SEEK_CUR );
364    }
365
366    fclose( file );
367
368    if( !ret )
369    {
370        tr_inf( "Fast resuming successful" );
371    }
372   
373    return ret;
374}
Note: See TracBrowser for help on using the repository browser.