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

Last change on this file since 10084 was 10084, checked in by charles, 12 years ago

(trunk) #2802, #2716, #2717 -- remember magnet links and their settings between sessions, and allow their trackers to be modified

File size: 8.8 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:$
11 */
12
13#include <assert.h>
14#include <event.h> /* struct evbuffer */
15
16#include "transmission.h"
17#include "bencode.h"
18#include "crypto.h"
19#include "magnet.h"
20#include "metainfo.h"
21#include "torrent.h"
22#include "torrent-magnet.h"
23#include "utils.h"
24#include "web.h"
25
26#define dbgmsg( tor, ... ) \
27    do { \
28        if( tr_deepLoggingIsActive( ) ) \
29            tr_deepLog( __FILE__, __LINE__, tor->info.name, __VA_ARGS__ ); \
30    } while( 0 )
31
32/***
33****
34***/
35
36enum
37{
38    /* don't ask for the same metadata piece more than this often */
39    MIN_REPEAT_INTERVAL_SECS = 3
40};
41
42struct metadata_node
43{
44    time_t requestedAt;
45    int piece;
46};
47
48struct tr_incomplete_metadata
49{
50    uint8_t * metadata;
51    int metadata_size;
52    int pieceCount;
53
54    /** sorted from least to most recently requested */
55    struct metadata_node * piecesNeeded;
56    int piecesNeededCount;
57};
58
59static void
60incompleteMetadataFree( struct tr_incomplete_metadata * m )
61{
62    tr_free( m->metadata );
63    tr_free( m->piecesNeeded );
64    tr_free( m );
65}
66
67void
68tr_torrentSetMetadataSizeHint( tr_torrent * tor, int size )
69{
70    if( !tr_torrentHasMetadata( tor ) )
71    {
72        if( tor->incompleteMetadata == NULL )
73        {
74            int i;
75            struct tr_incomplete_metadata * m;
76            int n = ( size + ( METADATA_PIECE_SIZE - 1 ) ) / METADATA_PIECE_SIZE;
77            dbgmsg( tor, "there are %d pieces", n );
78
79            m = tr_new( struct tr_incomplete_metadata, 1 );
80            m->pieceCount = n;
81            m->metadata = tr_new( uint8_t, size );
82            m->metadata_size = size;
83            m->piecesNeededCount = n;
84            m->piecesNeeded = tr_new( struct metadata_node, n );
85
86            for( i=0; i<n; ++i ) {
87                m->piecesNeeded[i].piece = i;
88                m->piecesNeeded[i].requestedAt = 0;
89            }
90
91            tor->incompleteMetadata = m;
92        }
93    }
94}
95
96void*
97tr_torrentGetMetadataPiece( const tr_torrent * tor, int piece, int * len )
98{
99    char * ret = NULL;
100
101    assert( tr_isTorrent( tor ) );
102    assert( piece >= 0 );
103    assert( len != NULL );
104
105    if( tor->infoDictLength > 0 )
106    {
107        FILE * fp = fopen( tor->info.torrent, "rb" );
108        if( fp != NULL )
109        {
110            const int o = piece  * METADATA_PIECE_SIZE;
111
112            if( !fseek( fp, tor->infoDictOffset + o, SEEK_SET ) )
113            {
114                const int l = o + METADATA_PIECE_SIZE <= tor->infoDictLength
115                            ? METADATA_PIECE_SIZE
116                            : tor->infoDictLength - o;
117
118                if( 0<l && l<=METADATA_PIECE_SIZE )
119                {
120                    char * buf = tr_new( char, l );
121                    const int n = fread( buf, 1, l, fp );
122                    if( n == l )
123                    {
124                        *len = l;
125                        ret = buf;
126                        buf = NULL;
127                    }
128
129                    tr_free( buf );
130                }
131            }
132
133            fclose( fp );
134        }
135    }
136
137    return ret;
138}
139
140void
141tr_torrentSetMetadataPiece( tr_torrent  * tor, int piece, const void  * data, int len )
142{
143    int i;
144    struct tr_incomplete_metadata * m;
145    const int offset = piece * METADATA_PIECE_SIZE;
146
147    assert( tr_isTorrent( tor ) );
148
149    dbgmsg( tor, "got metadata piece %d", piece );
150
151    /* are we set up to download metadata? */
152    m = tor->incompleteMetadata;
153    if( m == NULL )
154        return;
155
156    /* does this data pass the smell test? */
157    if( offset + len > m->metadata_size )
158        return;
159
160    /* do we need this piece? */
161    for( i=0; i<m->piecesNeededCount; ++i )
162        if( m->piecesNeeded[i].piece == piece )
163            break;
164    if( i==m->piecesNeededCount )
165        return;
166
167    memcpy( m->metadata + offset, data, len );
168
169    tr_removeElementFromArray( m->piecesNeeded, i,
170                               sizeof( struct metadata_node ),
171                               m->piecesNeededCount-- );
172
173    dbgmsg( tor, "saving metainfo piece %d... %d remain", piece, m->piecesNeededCount );
174
175    /* are we done? */
176    if( m->piecesNeededCount == 0 )
177    {
178        tr_bool success = FALSE;
179        tr_bool checksumPassed = FALSE;
180        tr_bool metainfoParsed = FALSE;
181        uint8_t sha1[SHA_DIGEST_LENGTH];
182
183        /* we've got a complete set of metainfo... see if it passes the checksum test */
184        dbgmsg( tor, "metainfo piece %d was the last one", piece );
185        tr_sha1( sha1, m->metadata, m->metadata_size, NULL );
186        if(( checksumPassed = !memcmp( sha1, tor->info.hash, SHA_DIGEST_LENGTH )))
187        {
188            /* checksum passed; now try to parse it as benc */
189            tr_benc infoDict;
190            const int err = tr_bencLoad( m->metadata, m->metadata_size, &infoDict, NULL );
191            dbgmsg( tor, "err is %d", err );
192            if(( metainfoParsed = !err ))
193            {
194                /* yay we have bencoded metainfo... merge it into our .torrnet file */
195                tr_benc newMetainfo;
196                const char * path = tor->info.torrent;
197                if( !tr_bencLoadFile( &newMetainfo, TR_FMT_BENC, path ) )
198                {
199                    tr_bool hasInfo;
200                    tr_benc * tmp;
201
202                    dbgmsg( tor, "Saving completed metadata to \"%s\"", path );
203                    assert( !tr_bencDictFindDict( &newMetainfo, "info", &tmp ) );
204                    tr_bencMergeDicts( tr_bencDictAddDict( &newMetainfo, "info", 0 ), &infoDict );
205                    tr_bencToFile( &newMetainfo, TR_FMT_BENC, path );
206
207                    success = tr_metainfoParse( tor->session, &newMetainfo, &tor->info,
208                                                &hasInfo, &tor->infoDictOffset, &tor->infoDictLength );
209
210                    assert( hasInfo );
211                    assert( success );
212
213                    tr_torrentGotNewInfoDict( tor );
214                    tr_torrentSetDirty( tor );
215
216                    tr_bencFree( &newMetainfo );
217                }
218       
219                tr_bencFree( &infoDict );
220            }
221        }
222
223        if( success )
224        {
225            incompleteMetadataFree( tor->incompleteMetadata );
226            tor->incompleteMetadata = NULL;
227        }
228        else /* drat. */
229        {
230            const int n = m->pieceCount;
231            for( i=0; i<n; ++i )
232            {
233                m->piecesNeeded[i].piece = i;
234                m->piecesNeeded[i].requestedAt = 0;
235            }
236            m->piecesNeededCount = n;
237            dbgmsg( tor, "metadata error; trying again. %d pieces left", n );
238
239            tr_err( "magnet status: checksum passed %d, metainfo parsed %d",
240                    (int)checksumPassed, (int)metainfoParsed );
241        }
242    }
243}
244
245tr_bool
246tr_torrentGetNextMetadataRequest( tr_torrent * tor, time_t now, int * setme_piece )
247{
248    tr_bool have_request = FALSE;
249    struct tr_incomplete_metadata * m;
250
251    assert( tr_isTorrent( tor ) );
252
253    m = tor->incompleteMetadata;
254
255    if( ( m != NULL )
256        && ( m->piecesNeededCount > 0 )
257        && ( m->piecesNeeded[0].requestedAt + MIN_REPEAT_INTERVAL_SECS < now ) )
258    {
259        int i;
260        const int piece = m->piecesNeeded[0].piece;
261
262        tr_removeElementFromArray( m->piecesNeeded, 0,
263                                   sizeof( struct metadata_node ),
264                                   m->piecesNeededCount-- );
265
266        i = m->piecesNeededCount++;
267        m->piecesNeeded[i].piece = piece;
268        m->piecesNeeded[i].requestedAt = now;
269
270        dbgmsg( tor, "next piece to request: %d", piece );
271        *setme_piece = piece;
272        have_request = TRUE;
273    }
274
275    return have_request;
276}
277
278float
279tr_torrentGetMetadataPercent( const tr_torrent * tor )
280{
281    float ret;
282
283    if( tr_torrentHasMetadata( tor ) )
284        ret = 1.0;
285    else {
286        const struct tr_incomplete_metadata * m = tor->incompleteMetadata;
287        if( m == NULL )
288            ret = 0.0;
289        else
290            ret = (m->pieceCount - m->piecesNeededCount) / (float)m->pieceCount;
291    }
292
293    return ret;
294}
295
296char*
297tr_torrentGetMagnetLink( const tr_torrent * tor )
298{
299    int i;
300    char * ret;
301    struct evbuffer * s;
302
303    assert( tr_isTorrent( tor ) );
304
305    s = evbuffer_new( );
306    evbuffer_add_printf( s, "magnet:?xt=urn:btih:%s", tor->info.hashString );
307    evbuffer_add_printf( s, "%s", "&dn=" );
308    tr_http_escape( s, tr_torrentName( tor ), -1, TRUE );
309    for( i=0; i<tor->info.trackerCount; ++i )
310    {
311        evbuffer_add_printf( s, "%s", "&tr=" );
312        tr_http_escape( s, tor->info.trackers[i].announce, -1, TRUE );
313    }
314
315    ret = tr_strndup( EVBUFFER_DATA( s ), EVBUFFER_LENGTH( s ) );
316    evbuffer_free( s );
317    return ret;
318}
Note: See TracBrowser for help on using the repository browser.