/* * This file Copyright (C) 2009-2010 Mnemosyne LLC * * This file is licensed by the GPL version 2. Works owned by the * Transmission project are granted a special exemption to clause 2(b) * so that the bulk of its code can remain under the MIT license. * This exemption does not extend to derived works not owned by * the Transmission project. * * $Id:$ */ #include #include /* struct evbuffer */ #include /* remove() */ #include "transmission.h" #include "bencode.h" #include "crypto.h" #include "magnet.h" #include "metainfo.h" #include "resume.h" #include "torrent.h" #include "torrent-magnet.h" #include "utils.h" #include "web.h" #define dbgmsg( tor, ... ) \ do { \ if( tr_deepLoggingIsActive( ) ) \ tr_deepLog( __FILE__, __LINE__, tor->info.name, __VA_ARGS__ ); \ } while( 0 ) /*** **** ***/ enum { /* don't ask for the same metadata piece more than this often */ MIN_REPEAT_INTERVAL_SECS = 3 }; struct metadata_node { time_t requestedAt; int piece; }; struct tr_incomplete_metadata { uint8_t * metadata; int metadata_size; int pieceCount; /** sorted from least to most recently requested */ struct metadata_node * piecesNeeded; int piecesNeededCount; }; static void incompleteMetadataFree( struct tr_incomplete_metadata * m ) { tr_free( m->metadata ); tr_free( m->piecesNeeded ); tr_free( m ); } void tr_torrentSetMetadataSizeHint( tr_torrent * tor, int size ) { if( !tr_torrentHasMetadata( tor ) ) { if( tor->incompleteMetadata == NULL ) { int i; struct tr_incomplete_metadata * m; int n = ( size + ( METADATA_PIECE_SIZE - 1 ) ) / METADATA_PIECE_SIZE; dbgmsg( tor, "there are %d pieces", n ); m = tr_new( struct tr_incomplete_metadata, 1 ); m->pieceCount = n; m->metadata = tr_new( uint8_t, size ); m->metadata_size = size; m->piecesNeededCount = n; m->piecesNeeded = tr_new( struct metadata_node, n ); for( i=0; ipiecesNeeded[i].piece = i; m->piecesNeeded[i].requestedAt = 0; } tor->incompleteMetadata = m; } } } void* tr_torrentGetMetadataPiece( const tr_torrent * tor, int piece, int * len ) { char * ret = NULL; assert( tr_isTorrent( tor ) ); assert( piece >= 0 ); assert( len != NULL ); if( tor->infoDictLength > 0 ) { FILE * fp = fopen( tor->info.torrent, "rb" ); if( fp != NULL ) { const int o = piece * METADATA_PIECE_SIZE; if( !fseek( fp, tor->infoDictOffset + o, SEEK_SET ) ) { const int l = o + METADATA_PIECE_SIZE <= tor->infoDictLength ? METADATA_PIECE_SIZE : tor->infoDictLength - o; if( 0incompleteMetadata; if( m == NULL ) return; /* does this data pass the smell test? */ if( offset + len > m->metadata_size ) return; /* do we need this piece? */ for( i=0; ipiecesNeededCount; ++i ) if( m->piecesNeeded[i].piece == piece ) break; if( i==m->piecesNeededCount ) return; memcpy( m->metadata + offset, data, len ); tr_removeElementFromArray( m->piecesNeeded, i, sizeof( struct metadata_node ), m->piecesNeededCount-- ); dbgmsg( tor, "saving metainfo piece %d... %d remain", piece, m->piecesNeededCount ); /* are we done? */ if( m->piecesNeededCount == 0 ) { tr_bool success = FALSE; tr_bool checksumPassed = FALSE; tr_bool metainfoParsed = FALSE; uint8_t sha1[SHA_DIGEST_LENGTH]; /* we've got a complete set of metainfo... see if it passes the checksum test */ dbgmsg( tor, "metainfo piece %d was the last one", piece ); tr_sha1( sha1, m->metadata, m->metadata_size, NULL ); if(( checksumPassed = !memcmp( sha1, tor->info.hash, SHA_DIGEST_LENGTH ))) { /* checksum passed; now try to parse it as benc */ tr_benc infoDict; const int err = tr_bencLoad( m->metadata, m->metadata_size, &infoDict, NULL ); dbgmsg( tor, "err is %d", err ); if(( metainfoParsed = !err )) { /* yay we have bencoded metainfo... merge it into our .torrent file */ tr_benc newMetainfo; char * path = tr_strdup( tor->info.torrent ); if( !tr_bencLoadFile( &newMetainfo, TR_FMT_BENC, path ) ) { tr_bool hasInfo; tr_benc * tmp; /* remove any old .torrent and .resume files */ remove( path ); tr_torrentRemoveResume( tor ); dbgmsg( tor, "Saving completed metadata to \"%s\"", path ); assert( !tr_bencDictFindDict( &newMetainfo, "info", &tmp ) ); tr_bencMergeDicts( tr_bencDictAddDict( &newMetainfo, "info", 0 ), &infoDict ); success = tr_metainfoParse( tor->session, &newMetainfo, &tor->info, &hasInfo, &tor->infoDictOffset, &tor->infoDictLength ); assert( hasInfo ); assert( success ); /* save the new .torrent file */ tr_bencToFile( &newMetainfo, TR_FMT_BENC, tor->info.torrent ); tr_sessionSetTorrentFile( tor->session, tor->info.hashString, tor->info.torrent ); tr_torrentGotNewInfoDict( tor ); tr_torrentSetDirty( tor ); tr_bencFree( &newMetainfo ); } tr_bencFree( &infoDict ); tr_free( path ); } } if( success ) { incompleteMetadataFree( tor->incompleteMetadata ); tor->incompleteMetadata = NULL; } else /* drat. */ { const int n = m->pieceCount; for( i=0; ipiecesNeeded[i].piece = i; m->piecesNeeded[i].requestedAt = 0; } m->piecesNeededCount = n; dbgmsg( tor, "metadata error; trying again. %d pieces left", n ); tr_err( "magnet status: checksum passed %d, metainfo parsed %d", (int)checksumPassed, (int)metainfoParsed ); } } } tr_bool tr_torrentGetNextMetadataRequest( tr_torrent * tor, time_t now, int * setme_piece ) { tr_bool have_request = FALSE; struct tr_incomplete_metadata * m; assert( tr_isTorrent( tor ) ); m = tor->incompleteMetadata; if( ( m != NULL ) && ( m->piecesNeededCount > 0 ) && ( m->piecesNeeded[0].requestedAt + MIN_REPEAT_INTERVAL_SECS < now ) ) { int i; const int piece = m->piecesNeeded[0].piece; tr_removeElementFromArray( m->piecesNeeded, 0, sizeof( struct metadata_node ), m->piecesNeededCount-- ); i = m->piecesNeededCount++; m->piecesNeeded[i].piece = piece; m->piecesNeeded[i].requestedAt = now; dbgmsg( tor, "next piece to request: %d", piece ); *setme_piece = piece; have_request = TRUE; } return have_request; } float tr_torrentGetMetadataPercent( const tr_torrent * tor ) { float ret; if( tr_torrentHasMetadata( tor ) ) ret = 1.0; else { const struct tr_incomplete_metadata * m = tor->incompleteMetadata; if( m == NULL ) ret = 0.0; else ret = (m->pieceCount - m->piecesNeededCount) / (float)m->pieceCount; } return ret; } char* tr_torrentGetMagnetLink( const tr_torrent * tor ) { int i; char * ret; struct evbuffer * s; assert( tr_isTorrent( tor ) ); s = evbuffer_new( ); evbuffer_add_printf( s, "magnet:?xt=urn:btih:%s", tor->info.hashString ); evbuffer_add_printf( s, "%s", "&dn=" ); tr_http_escape( s, tr_torrentName( tor ), -1, TRUE ); for( i=0; iinfo.trackerCount; ++i ) { evbuffer_add_printf( s, "%s", "&tr=" ); tr_http_escape( s, tor->info.trackers[i].announce, -1, TRUE ); } ret = tr_strndup( EVBUFFER_DATA( s ), EVBUFFER_LENGTH( s ) ); evbuffer_free( s ); return ret; }