source: trunk/libtransmission/torrent-magnet.c @ 11599

Last change on this file since 11599 was 11599, checked in by charles, 11 years ago

(trunk) Join the 21st century and use only 1 space at the end sentences. This commit is nearly as important as the semi-annual ones that remove trailing spaces from the ends of lines of code... :)

  • Property svn:keywords set to Date Rev Author Id
File size: 10.9 KB
Line 
1/*
2 * This file Copyright (C) 2009-2010 Mnemosyne LLC
3 *
4 * This file is licensed by the GPL version 2. Works owned by the
5 * Transmission project are granted a special exemption to clause 2(b)
6 * so that the bulk of its code can remain under the MIT license.
7 * This exemption does not extend to derived works not owned by
8 * the Transmission project.
9 *
10 * $Id: torrent-magnet.c 11599 2010-12-27 19:18:17Z charles $
11 */
12
13#include <assert.h>
14#include <stdio.h> /* remove() */
15
16#include <event2/buffer.h>
17
18#include "transmission.h"
19#include "bencode.h"
20#include "crypto.h"
21#include "magnet.h"
22#include "metainfo.h"
23#include "resume.h"
24#include "torrent.h"
25#include "torrent-magnet.h"
26#include "utils.h"
27#include "web.h"
28
29#define dbgmsg( tor, ... ) \
30    do { \
31        if( tr_deepLoggingIsActive( ) ) \
32            tr_deepLog( __FILE__, __LINE__, tor->info.name, __VA_ARGS__ ); \
33    } while( 0 )
34
35/***
36****
37***/
38
39enum
40{
41    /* don't ask for the same metadata piece more than this often */
42    MIN_REPEAT_INTERVAL_SECS = 3
43};
44
45struct metadata_node
46{
47    time_t requestedAt;
48    int piece;
49};
50
51struct tr_incomplete_metadata
52{
53    uint8_t * metadata;
54    int metadata_size;
55    int pieceCount;
56
57    /** sorted from least to most recently requested */
58    struct metadata_node * piecesNeeded;
59    int piecesNeededCount;
60};
61
62static void
63incompleteMetadataFree( struct tr_incomplete_metadata * m )
64{
65    tr_free( m->metadata );
66    tr_free( m->piecesNeeded );
67    tr_free( m );
68}
69
70void
71tr_torrentSetMetadataSizeHint( tr_torrent * tor, int size )
72{
73    if( !tr_torrentHasMetadata( tor ) )
74    {
75        if( tor->incompleteMetadata == NULL )
76        {
77            int i;
78            struct tr_incomplete_metadata * m;
79            const int n = ( size + ( METADATA_PIECE_SIZE - 1 ) ) / METADATA_PIECE_SIZE;
80            dbgmsg( tor, "metadata is %d bytes in %d pieces", size, n );
81
82            m = tr_new( struct tr_incomplete_metadata, 1 );
83            m->pieceCount = n;
84            m->metadata = tr_new( uint8_t, size );
85            m->metadata_size = size;
86            m->piecesNeededCount = n;
87            m->piecesNeeded = tr_new( struct metadata_node, n );
88
89            for( i=0; i<n; ++i ) {
90                m->piecesNeeded[i].piece = i;
91                m->piecesNeeded[i].requestedAt = 0;
92            }
93
94            tor->incompleteMetadata = m;
95        }
96    }
97}
98
99static int
100findInfoDictOffset( const tr_torrent * tor )
101{
102    size_t fileLen;
103    uint8_t * fileContents;
104    int offset = 0;
105
106    /* load the file, and find the info dict's offset inside the file */
107    if(( fileContents = tr_loadFile( tor->info.torrent, &fileLen )))
108    {
109        tr_benc top;
110
111        if( !tr_bencParse( fileContents, fileContents + fileLen, &top, NULL ) )
112        {
113            tr_benc * infoDict;
114
115            if( tr_bencDictFindDict( &top, "info", &infoDict ) )
116            {
117                int infoLen;
118                char * infoContents = tr_bencToStr( infoDict, TR_FMT_BENC, &infoLen );
119                const uint8_t * i = (const uint8_t*) tr_memmem( (char*)fileContents, fileLen, infoContents, infoLen );
120                offset = i != NULL ? i - fileContents : 0;
121                tr_free( infoContents );
122            }
123
124            tr_bencFree( &top );
125        }
126
127        tr_free( fileContents );
128    }
129
130    return offset;
131}
132
133static void
134ensureInfoDictOffsetIsCached( tr_torrent * tor )
135{
136    assert( tr_torrentHasMetadata( tor ) );
137
138    if( !tor->infoDictOffsetIsCached )
139    {
140        tor->infoDictOffset = findInfoDictOffset( tor );
141        tor->infoDictOffsetIsCached = TRUE;
142    }
143}
144
145void*
146tr_torrentGetMetadataPiece( tr_torrent * tor, int piece, int * len )
147{
148    char * ret = NULL;
149
150    assert( tr_isTorrent( tor ) );
151    assert( piece >= 0 );
152    assert( len != NULL );
153
154    if( tr_torrentHasMetadata( tor ) )
155    {
156        FILE * fp;
157
158        ensureInfoDictOffsetIsCached( tor );
159
160        assert( tor->infoDictLength > 0 );
161        assert( tor->infoDictOffset >= 0 );
162
163        fp = fopen( tor->info.torrent, "rb" );
164        if( fp != NULL )
165        {
166            const int o = piece  * METADATA_PIECE_SIZE;
167
168            if( !fseek( fp, tor->infoDictOffset + o, SEEK_SET ) )
169            {
170                const int l = o + METADATA_PIECE_SIZE <= tor->infoDictLength
171                            ? METADATA_PIECE_SIZE
172                            : tor->infoDictLength - o;
173
174                if( 0<l && l<=METADATA_PIECE_SIZE )
175                {
176                    char * buf = tr_new( char, l );
177                    const int n = fread( buf, 1, l, fp );
178                    if( n == l )
179                    {
180                        *len = l;
181                        ret = buf;
182                        buf = NULL;
183                    }
184
185                    tr_free( buf );
186                }
187            }
188
189            fclose( fp );
190        }
191    }
192
193    return ret;
194}
195
196void
197tr_torrentSetMetadataPiece( tr_torrent  * tor, int piece, const void  * data, int len )
198{
199    int i;
200    struct tr_incomplete_metadata * m;
201    const int offset = piece * METADATA_PIECE_SIZE;
202
203    assert( tr_isTorrent( tor ) );
204
205    dbgmsg( tor, "got metadata piece %d", piece );
206
207    /* are we set up to download metadata? */
208    m = tor->incompleteMetadata;
209    if( m == NULL )
210        return;
211
212    /* does this data pass the smell test? */
213    if( offset + len > m->metadata_size )
214        return;
215
216    /* do we need this piece? */
217    for( i=0; i<m->piecesNeededCount; ++i )
218        if( m->piecesNeeded[i].piece == piece )
219            break;
220    if( i==m->piecesNeededCount )
221        return;
222
223    memcpy( m->metadata + offset, data, len );
224
225    tr_removeElementFromArray( m->piecesNeeded, i,
226                               sizeof( struct metadata_node ),
227                               m->piecesNeededCount-- );
228
229    dbgmsg( tor, "saving metainfo piece %d... %d remain", piece, m->piecesNeededCount );
230
231    /* are we done? */
232    if( m->piecesNeededCount == 0 )
233    {
234        tr_bool success = FALSE;
235        tr_bool checksumPassed = FALSE;
236        tr_bool metainfoParsed = FALSE;
237        uint8_t sha1[SHA_DIGEST_LENGTH];
238
239        /* we've got a complete set of metainfo... see if it passes the checksum test */
240        dbgmsg( tor, "metainfo piece %d was the last one", piece );
241        tr_sha1( sha1, m->metadata, m->metadata_size, NULL );
242        if(( checksumPassed = !memcmp( sha1, tor->info.hash, SHA_DIGEST_LENGTH )))
243        {
244            /* checksum passed; now try to parse it as benc */
245            tr_benc infoDict;
246            const int err = tr_bencLoad( m->metadata, m->metadata_size, &infoDict, NULL );
247            dbgmsg( tor, "err is %d", err );
248            if(( metainfoParsed = !err ))
249            {
250                /* yay we have bencoded metainfo... merge it into our .torrent file */
251                tr_benc newMetainfo;
252                char * path = tr_strdup( tor->info.torrent );
253
254                if( !tr_bencLoadFile( &newMetainfo, TR_FMT_BENC, path ) )
255                {
256                    tr_bool hasInfo;
257                    tr_info info;
258                    int infoDictLength;
259
260                    /* remove any old .torrent and .resume files */
261                    remove( path );
262                    tr_torrentRemoveResume( tor );
263
264                    dbgmsg( tor, "Saving completed metadata to \"%s\"", path );
265                    tr_bencMergeDicts( tr_bencDictAddDict( &newMetainfo, "info", 0 ), &infoDict );
266
267                    memset( &info, 0, sizeof( tr_info ) );
268                    success = tr_metainfoParse( tor->session, &newMetainfo, &info, &hasInfo, &infoDictLength );
269
270                    if( success && !tr_getBlockSize( info.pieceSize ) )
271                    {
272                        tr_torrentSetLocalError( tor, "%s", _( "Magnet torrent's metadata is not usable" ) );
273                        success = FALSE;
274                    }
275
276                    if( success )
277                    {
278                        /* keep the new info */
279                        tor->info = info;
280                        tor->infoDictLength = infoDictLength;
281
282                        /* save the new .torrent file */
283                        tr_bencToFile( &newMetainfo, TR_FMT_BENC, tor->info.torrent );
284                        tr_sessionSetTorrentFile( tor->session, tor->info.hashString, tor->info.torrent );
285                        tr_torrentGotNewInfoDict( tor );
286                        tr_torrentSetDirty( tor );
287                    }
288
289                    tr_bencFree( &newMetainfo );
290                }
291
292                tr_bencFree( &infoDict );
293                tr_free( path );
294            }
295        }
296
297        if( success )
298        {
299            incompleteMetadataFree( tor->incompleteMetadata );
300            tor->incompleteMetadata = NULL;
301        }
302        else /* drat. */
303        {
304            const int n = m->pieceCount;
305            for( i=0; i<n; ++i )
306            {
307                m->piecesNeeded[i].piece = i;
308                m->piecesNeeded[i].requestedAt = 0;
309            }
310            m->piecesNeededCount = n;
311            dbgmsg( tor, "metadata error; trying again. %d pieces left", n );
312
313            tr_err( "magnet status: checksum passed %d, metainfo parsed %d",
314                    (int)checksumPassed, (int)metainfoParsed );
315        }
316    }
317}
318
319tr_bool
320tr_torrentGetNextMetadataRequest( tr_torrent * tor, time_t now, int * setme_piece )
321{
322    tr_bool have_request = FALSE;
323    struct tr_incomplete_metadata * m;
324
325    assert( tr_isTorrent( tor ) );
326
327    m = tor->incompleteMetadata;
328
329    if( ( m != NULL )
330        && ( m->piecesNeededCount > 0 )
331        && ( m->piecesNeeded[0].requestedAt + MIN_REPEAT_INTERVAL_SECS < now ) )
332    {
333        int i;
334        const int piece = m->piecesNeeded[0].piece;
335
336        tr_removeElementFromArray( m->piecesNeeded, 0,
337                                   sizeof( struct metadata_node ),
338                                   m->piecesNeededCount-- );
339
340        i = m->piecesNeededCount++;
341        m->piecesNeeded[i].piece = piece;
342        m->piecesNeeded[i].requestedAt = now;
343
344        dbgmsg( tor, "next piece to request: %d", piece );
345        *setme_piece = piece;
346        have_request = TRUE;
347    }
348
349    return have_request;
350}
351
352double
353tr_torrentGetMetadataPercent( const tr_torrent * tor )
354{
355    double ret;
356
357    if( tr_torrentHasMetadata( tor ) )
358        ret = 1.0;
359    else {
360        const struct tr_incomplete_metadata * m = tor->incompleteMetadata;
361        if( m == NULL )
362            ret = 0.0;
363        else
364            ret = (m->pieceCount - m->piecesNeededCount) / (double)m->pieceCount;
365    }
366
367    return ret;
368}
369
370char*
371tr_torrentGetMagnetLink( const tr_torrent * tor )
372{
373    int i;
374    const char * name;
375    struct evbuffer * s;
376
377    assert( tr_isTorrent( tor ) );
378
379    s = evbuffer_new( );
380    evbuffer_add_printf( s, "magnet:?xt=urn:btih:%s", tor->info.hashString );
381    name = tr_torrentName( tor );
382    if( name && *name )
383    {
384        evbuffer_add_printf( s, "%s", "&dn=" );
385        tr_http_escape( s, tr_torrentName( tor ), -1, TRUE );
386    }
387    for( i=0; i<tor->info.trackerCount; ++i )
388    {
389        evbuffer_add_printf( s, "%s", "&tr=" );
390        tr_http_escape( s, tor->info.trackers[i].announce, -1, TRUE );
391    }
392
393    return evbuffer_free_to_str( s );
394}
Note: See TracBrowser for help on using the repository browser.