Update 2005-11-17

This commit is contained in:
Eric Petit 2006-01-12 18:29:20 +00:00
parent 8adc2d7338
commit d2cc6ce7c6
36 changed files with 1001 additions and 459 deletions

View File

@ -26,3 +26,6 @@ Jeremy Messenger
Martin Stadtmueller
+ Icon tweaking
John Blitch
+ Contextual menu patch

View File

@ -33,6 +33,8 @@ if $(OS) = MACOSX
macosx/ProgressCell.h
macosx/ProgressCell.m
macosx/main.m
macosx/TorrentTableView.h
macosx/TorrentTableView.m
macosx/Transmission.xcodeproj/project.pbxproj
macosx/Transmission_Prefix.pch
macosx/Utils.h ;

View File

@ -2,7 +2,8 @@ SubDir TOP libtransmission ;
LIBTRANSMISSION_SRC =
transmission.c bencode.c net.c tracker.c peer.c inout.c
metainfo.c sha1.c utils.c upload.c fdlimit.c clients.c ;
metainfo.c sha1.c utils.c upload.c fdlimit.c clients.c
completion.c ;
Library libtransmission.a : $(LIBTRANSMISSION_SRC) ;
ObjectDefines $(LIBTRANSMISSION_SRC) : __TRANSMISSION__ ;

View File

@ -0,0 +1,253 @@
/******************************************************************************
* Copyright (c) 2005 Eric Petit
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*****************************************************************************/
#include "transmission.h"
tr_completion_t * tr_cpInit( tr_torrent_t * tor )
{
tr_completion_t * cp;
int i;
cp = calloc( 1, sizeof( tr_completion_t ) );
cp->tor = tor;
cp->blockBitfield = calloc( 1, ( tor->blockCount + 7 ) / 8 );
cp->blockDownloaders = calloc( 1, tor->blockCount );
cp->pieceBitfield = calloc( 1, ( tor->info.pieceCount + 7 ) / 8 );
cp->missingBlocks = calloc( 1, tor->info.pieceCount * sizeof( int ) );
for( i = 0; i < tor->info.pieceCount; i++ )
{
cp->missingBlocks[i] = tr_pieceCountBlocks( i );
}
return cp;
}
void tr_cpClose( tr_completion_t * cp )
{
free( cp->blockBitfield );
free( cp->blockDownloaders );
free( cp->pieceBitfield );
free( cp->missingBlocks );
free( cp );
}
float tr_cpCompletionAsFloat( tr_completion_t * cp )
{
return (float) cp->blockCount / (float) cp->tor->blockCount;
}
uint64_t tr_cpLeftBytes( tr_completion_t * cp )
{
tr_torrent_t * tor = cp->tor;
uint64_t left;
left = (uint64_t) ( cp->tor->blockCount - cp->blockCount ) *
(uint64_t) tor->blockSize;
if( !tr_bitfieldHas( cp->blockBitfield, cp->tor->blockCount - 1 ) &&
tor->info.totalSize % tor->blockSize )
{
left += tor->info.totalSize % tor->blockSize;
left -= tor->blockSize;
}
return left;
}
/* Pieces */
int tr_cpPieceIsComplete( tr_completion_t * cp, int piece )
{
return tr_bitfieldHas( cp->pieceBitfield, piece );
}
uint8_t * tr_cpPieceBitfield( tr_completion_t * cp )
{
return cp->pieceBitfield;
}
void tr_cpPieceAdd( tr_completion_t * cp, int piece )
{
tr_torrent_t * tor = cp->tor;
int startBlock, endBlock, i;
startBlock = tr_pieceStartBlock( piece );
endBlock = startBlock + tr_pieceCountBlocks( piece );
for( i = startBlock; i < endBlock; i++ )
{
tr_cpBlockAdd( cp, i );
}
tr_bitfieldAdd( cp->pieceBitfield, piece );
}
/* Blocks */
void tr_cpDownloaderAdd( tr_completion_t * cp, int block )
{
tr_torrent_t * tor = cp->tor;
if( !cp->blockDownloaders[block] && !tr_cpBlockIsComplete( cp, block ) )
{
cp->missingBlocks[tr_blockPiece(block)]--;
}
(cp->blockDownloaders[block])++;
}
void tr_cpDownloaderRem( tr_completion_t * cp, int block )
{
tr_torrent_t * tor = cp->tor;
(cp->blockDownloaders[block])--;
if( !cp->blockDownloaders[block] && !tr_cpBlockIsComplete( cp, block ) )
{
cp->missingBlocks[tr_blockPiece(block)]++;
}
}
int tr_cpBlockIsComplete( tr_completion_t * cp, int block )
{
return tr_bitfieldHas( cp->blockBitfield, block );
}
void tr_cpBlockAdd( tr_completion_t * cp, int block )
{
tr_torrent_t * tor = cp->tor;
if( !tr_cpBlockIsComplete( cp, block ) )
{
(cp->blockCount)++;
if( !cp->blockDownloaders[block] )
{
(cp->missingBlocks[tr_blockPiece(block)])--;
}
}
tr_bitfieldAdd( cp->blockBitfield, block );
}
void tr_cpBlockRem( tr_completion_t * cp, int block )
{
tr_torrent_t * tor = cp->tor;
if( tr_cpBlockIsComplete( cp, block ) )
{
(cp->blockCount)--;
if( !cp->blockDownloaders[block] )
{
(cp->missingBlocks[tr_blockPiece(block)])++;
}
}
tr_bitfieldRem( cp->blockBitfield, block );
}
uint8_t * tr_cpBlockBitfield( tr_completion_t * cp )
{
return cp->blockBitfield;
}
void tr_cpBlockBitfieldSet( tr_completion_t * cp, uint8_t * bitfield )
{
tr_torrent_t * tor = cp->tor;
int i, j;
int startBlock, endBlock;
int pieceComplete;
for( i = 0; i < cp->tor->info.pieceCount; i++ )
{
startBlock = tr_pieceStartBlock( i );
endBlock = startBlock + tr_pieceCountBlocks( i );
pieceComplete = 1;
for( j = startBlock; j < endBlock; j++ )
{
if( tr_bitfieldHas( bitfield, j ) )
{
tr_cpBlockAdd( cp, j );
}
else
{
pieceComplete = 0;
}
}
if( pieceComplete )
{
tr_cpPieceAdd( cp, i );
}
}
}
int tr_cpMissingBlockInPiece( tr_completion_t * cp, int piece )
{
tr_torrent_t * tor = cp->tor;
int start, count, end, i;
start = tr_pieceStartBlock( piece );
count = tr_pieceCountBlocks( piece );
end = start + count;
for( i = start; i < end; i++ )
{
if( tr_cpBlockIsComplete( cp, i ) || cp->blockDownloaders[i] )
{
continue;
}
return i;
}
return -1;
}
int tr_cpMostMissingBlockInPiece( tr_completion_t * cp, int piece,
int * downloaders )
{
tr_torrent_t * tor = cp->tor;
int start, count, end, i;
int * pool, poolSize, min, ret;
start = tr_pieceStartBlock( piece );
count = tr_pieceCountBlocks( piece );
end = start + count;
pool = malloc( count * sizeof( int ) );
poolSize = 0;
min = 255;
for( i = start; i < end; i++ )
{
if( tr_cpBlockIsComplete( cp, i ) || cp->blockDownloaders[i] > min )
{
continue;
}
if( cp->blockDownloaders[i] < min )
{
min = cp->blockDownloaders[i];
poolSize = 0;
}
if( cp->blockDownloaders[i] <= min )
{
pool[poolSize++] = i;
}
}
if( poolSize < 1 )
{
return -1;
}
ret = pool[0];
free( pool );
*downloaders = min;
return ret;
}

View File

@ -0,0 +1,64 @@
/******************************************************************************
* Copyright (c) 2005 Eric Petit
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*****************************************************************************/
struct tr_completion_s
{
tr_torrent_t * tor;
uint8_t * blockBitfield;
uint8_t * blockDownloaders;
int blockCount;
uint8_t * pieceBitfield;
int * missingBlocks;
};
tr_completion_t * tr_cpInit( tr_torrent_t * );
void tr_cpClose( tr_completion_t * );
/* General */
float tr_cpCompletionAsFloat( tr_completion_t * );
static inline int tr_cpIsSeeding( tr_completion_t * cp )
{
return ( cp->blockCount == cp->tor->blockCount );
}
uint64_t tr_cpLeftBytes( tr_completion_t * );
/* Pieces */
int tr_cpPieceIsComplete( tr_completion_t *, int piece );
uint8_t * tr_cpPieceBitfield( tr_completion_t * );
void tr_cpPieceAdd( tr_completion_t *, int piece );
/* Blocks */
void tr_cpDownloaderAdd( tr_completion_t *, int block );
void tr_cpDownloaderRem( tr_completion_t *, int block );
int tr_cpBlockIsComplete( tr_completion_t *, int block );
void tr_cpBlockAdd( tr_completion_t *, int block );
void tr_cpBlockRem( tr_completion_t *, int block );
uint8_t * tr_cpBlockBitfield( tr_completion_t * );
void tr_cpBlockBitfieldSet( tr_completion_t *, uint8_t * );
/* Missing = we don't have it and we are not getting it from any peer yet */
static inline int tr_cpMissingBlocksForPiece( tr_completion_t * cp, int piece )
{
return cp->missingBlocks[piece];
}
int tr_cpMissingBlockInPiece( tr_completion_t *, int piece );
int tr_cpMostMissingBlockInPiece( tr_completion_t *, int piece,
int * downloaders );

View File

@ -101,7 +101,6 @@ static void fastResumeSave( tr_io_t * io )
int version = 0;
char * path;
int * fileMTimes;
int i;
uint8_t * blockBitfield;
/* Get file sizes */
@ -130,16 +129,8 @@ static void fastResumeSave( tr_io_t * io )
free( fileMTimes );
/* Build and write the bitfield for blocks */
blockBitfield = calloc( ( tor->blockCount + 7 ) / 8, 1 );
for( i = 0; i < tor->blockCount; i++ )
{
if( tor->blockHave[i] < 0 )
{
tr_bitfieldAdd( blockBitfield, i );
}
}
blockBitfield = tr_cpBlockBitfield( tor->completion );
fwrite( blockBitfield, ( tor->blockCount + 7 ) / 8, 1, file );
free( blockBitfield );
/* Write the 'slotPiece' table */
fwrite( io->slotPiece, 4, inf->pieceCount, file );
@ -222,15 +213,7 @@ static int fastResumeLoad( tr_io_t * io )
/* Load the bitfield for blocks and fill blockHave */
blockBitfield = calloc( ( tor->blockCount + 7 ) / 8, 1 );
fread( blockBitfield, ( tor->blockCount + 7 ) / 8, 1, file );
tor->blockHaveCount = 0;
for( i = 0; i < tor->blockCount; i++ )
{
if( tr_bitfieldHas( blockBitfield, i ) )
{
tor->blockHave[i] = -1;
(tor->blockHaveCount)++;
}
}
tr_cpBlockBitfieldSet( tor->completion, blockBitfield );
free( blockBitfield );
/* Load the 'slotPiece' table */
@ -253,21 +236,6 @@ static int fastResumeLoad( tr_io_t * io )
break;
}
}
for( j = tr_pieceStartBlock( i );
j < tr_pieceStartBlock( i ) + tr_pieceCountBlocks( i );
j++ )
{
if( tor->blockHave[j] > -1 )
{
break;
}
}
if( j >= tr_pieceStartBlock( i ) + tr_pieceCountBlocks( i ) )
{
// tr_dbg( "Piece %d is complete", i );
tr_bitfieldAdd( tor->bitfield, i );
}
}
// tr_dbg( "Slot used: %d", io->slotsUsed );

View File

@ -34,6 +34,7 @@ typedef struct tr_openFile_s
#define STATUS_INVALID 1
#define STATUS_UNUSED 2
#define STATUS_USED 4
#define STATUS_CLOSING 8
int status;
uint64_t date;
@ -111,6 +112,15 @@ FILE * tr_fdFileOpen( tr_fd_t * f, char * path )
if( f->open[i].status > STATUS_INVALID &&
!strcmp( path, f->open[i].path ) )
{
if( f->open[i].status & STATUS_CLOSING )
{
/* Wait until the file is closed */
tr_lockUnlock( f->lock );
tr_wait( 10 );
tr_lockLock( f->lock );
i = -1;
continue;
}
winner = i;
goto done;
}
@ -134,7 +144,7 @@ FILE * tr_fdFileOpen( tr_fd_t * f, char * path )
for( i = 0; i < TR_MAX_OPEN_FILES; i++ )
{
if( f->open[i].status & STATUS_USED )
if( !( f->open[i].status & STATUS_UNUSED ) )
{
continue;
}
@ -147,8 +157,14 @@ FILE * tr_fdFileOpen( tr_fd_t * f, char * path )
if( winner >= 0 )
{
/* Close the file: we mark it as closing then release the
lock while doing so, because fclose may take same time
and we don't want to block other threads */
tr_dbg( "Closing %s", f->open[winner].path );
f->open[winner].status = STATUS_CLOSING;
tr_lockUnlock( f->lock );
fclose( f->open[winner].file );
tr_lockLock( f->lock );
goto open;
}

View File

@ -136,7 +136,7 @@ int tr_ioWrite( tr_io_t * io, int index, int begin, int length,
endBlock = startBlock + tr_pieceCountBlocks( index );
for( i = startBlock; i < endBlock; i++ )
{
if( tor->blockHave[i] >= 0 )
if( !tr_cpBlockIsComplete( tor->completion, i ) )
{
/* The piece is not complete */
return 0;
@ -159,15 +159,14 @@ int tr_ioWrite( tr_io_t * io, int index, int begin, int length,
/* We will need to reload the whole piece */
for( i = startBlock; i < endBlock; i++ )
{
tor->blockHave[i] = 0;
tor->blockHaveCount -= 1;
tr_cpBlockRem( tor->completion, i );
}
}
else
{
tr_inf( "Piece %d (slot %d): hash OK", index,
io->pieceSlot[index] );
tr_bitfieldAdd( tor->bitfield, index );
tr_cpPieceAdd( tor->completion, index );
}
return 0;
@ -266,7 +265,6 @@ static int checkFiles( tr_io_t * io )
int i;
uint8_t * buf;
uint8_t hash[SHA_DIGEST_LENGTH];
int startBlock, endBlock;
io->pieceSlot = malloc( inf->pieceCount * sizeof( int ) );
io->slotPiece = malloc( inf->pieceCount * sizeof( int ) );
@ -281,9 +279,6 @@ static int checkFiles( tr_io_t * io )
/* Yet we don't have anything */
memset( io->pieceSlot, 0xFF, inf->pieceCount * sizeof( int ) );
memset( io->slotPiece, 0xFF, inf->pieceCount * sizeof( int ) );
memset( tor->bitfield, 0, ( inf->pieceCount + 7 ) / 8 );
memset( tor->blockHave, 0, tor->blockCount );
tor->blockHaveCount = 0;
/* Check pieces */
io->slotsUsed = 0;
@ -304,18 +299,10 @@ static int checkFiles( tr_io_t * io )
{
if( !memcmp( hash, &inf->pieces[20*j], SHA_DIGEST_LENGTH ) )
{
int k;
io->pieceSlot[j] = i;
io->slotPiece[i] = j;
tr_bitfieldAdd( tor->bitfield, j );
startBlock = tr_pieceStartBlock( j );
endBlock = startBlock + tr_pieceCountBlocks( j );
for( k = startBlock; k < endBlock; k++ )
{
tor->blockHave[k] = -1;
tor->blockHaveCount++;
}
tr_cpPieceAdd( tor->completion, j );
break;
}
}
@ -332,16 +319,8 @@ static int checkFiles( tr_io_t * io )
{
io->pieceSlot[inf->pieceCount - 1] = i;
io->slotPiece[i] = inf->pieceCount - 1;
tr_bitfieldAdd( tor->bitfield, inf->pieceCount - 1 );
startBlock = tr_pieceStartBlock( inf->pieceCount - 1 );
endBlock = startBlock +
tr_pieceCountBlocks( inf->pieceCount - 1 );
for( j = startBlock; j < endBlock; j++ )
{
tor->blockHave[j] = -1;
tor->blockHaveCount++;
}
tr_cpPieceAdd( tor->completion, inf->pieceCount - 1 );
}
}
free( buf );
@ -410,7 +389,9 @@ static int readOrWriteBytes( tr_io_t * io, uint64_t offset, int size,
while( size > 0 )
{
asprintf( &path, "%s/%s", tor->destination, inf->files[i].name );
tr_lockUnlock( tor->lock );
file = tr_fdFileOpen( tor->fdlimit, path );
tr_lockLock( tor->lock );
free( path );
if( !file )
@ -421,14 +402,17 @@ static int readOrWriteBytes( tr_io_t * io, uint64_t offset, int size,
willRead = MIN( inf->files[i].length - posInFile,
(uint64_t) size );
tr_lockUnlock( tor->lock );
if( fseeko( file, posInFile, SEEK_SET ) )
{
tr_lockLock( tor->lock );
return 1;
}
if( write )
{
if( fwrite( buf, willRead, 1, file ) != 1 )
{
tr_lockLock( tor->lock );
return 1;
}
}
@ -436,10 +420,11 @@ static int readOrWriteBytes( tr_io_t * io, uint64_t offset, int size,
{
if( fread( buf, willRead, 1, file ) != 1 )
{
tr_lockLock( tor->lock );
return 1;
}
}
tr_lockLock( tor->lock );
tr_fdFileRelease( tor->fdlimit, file );
/* 'willRead' less bytes to do */

View File

@ -107,6 +107,7 @@
#define TR_MAX_PEER_COUNT 60
typedef struct tr_torrent_s tr_torrent_t;
typedef struct tr_completion_s tr_completion_t;
#include "bencode.h"
#include "metainfo.h"
@ -129,6 +130,7 @@ struct tr_torrent_s
char error[128];
char * id;
char * key;
/* An escaped string used to include the hash in HTTP queries */
char hashString[3*SHA_DIGEST_LENGTH+1];
@ -142,12 +144,15 @@ struct tr_torrent_s
int blockSize;
int blockCount;
#if 0
/* Status for each block
-1 = we have it
n = we are downloading it from n peers */
char * blockHave;
int blockHaveCount;
uint8_t * bitfield;
#endif
tr_completion_t * completion;
volatile char die;
tr_thread_t thread;
@ -170,6 +175,7 @@ struct tr_torrent_s
};
#include "utils.h"
#include "completion.h"
struct tr_handle_s
{
@ -182,6 +188,7 @@ struct tr_handle_s
int bindPort;
char id[21];
char key[21];
char prefsDirectory[256];
};

View File

@ -169,10 +169,7 @@ void tr_peerRem( tr_torrent_t * tor, int i )
r = &peer->inRequests[j];
block = tr_block( r->index,r->begin );
if( tor->blockHave[block] > 0 )
{
(tor->blockHave[block])--;
}
tr_cpDownloaderRem( tor->completion, block );
}
if( !peer->amChoking )
{
@ -271,77 +268,48 @@ void tr_peerPulse( tr_torrent_t * tor )
{
peer = tor->peers[i];
/* Connect */
if( ( peer->status & PEER_STATUS_IDLE ) &&
!tr_fdSocketWillCreate( tor->fdlimit, 0 ) )
if( peer->status < PEER_STATUS_HANDSHAKE )
{
peer->socket = tr_netOpen( peer->addr, peer->port );
if( peer->socket < 0 )
{
peer_dbg( "connection failed" );
goto dropPeer;
}
peer->status = PEER_STATUS_CONNECTING;
i++;
continue;
}
/* Try to send handshake */
if( peer->status & PEER_STATUS_CONNECTING )
/* Try to read */
for( ;; )
{
uint8_t buf[68];
tr_info_t * inf = &tor->info;
buf[0] = 19;
memcpy( &buf[1], "BitTorrent protocol", 19 );
memset( &buf[20], 0, 8 );
memcpy( &buf[28], inf->hash, 20 );
memcpy( &buf[48], tor->id, 20 );
ret = tr_netSend( peer->socket, buf, 68 );
if( peer->size < 1 )
{
peer->size = 1024;
peer->buf = malloc( peer->size );
}
else if( peer->pos >= peer->size )
{
peer->size *= 2;
peer->buf = realloc( peer->buf, peer->size );
}
ret = tr_netRecv( peer->socket, &peer->buf[peer->pos],
peer->size - peer->pos );
if( ret & TR_NET_CLOSE )
{
peer_dbg( "connection closed" );
goto dropPeer;
}
else if( !( ret & TR_NET_BLOCK ) )
else if( ret & TR_NET_BLOCK )
{
peer_dbg( "SEND handshake" );
peer->status = PEER_STATUS_HANDSHAKE;
break;
}
peer->date = tr_date();
peer->pos += ret;
if( parseBuf( tor, peer, ret ) )
{
goto dropPeer;
}
}
/* Try to read */
if( peer->status >= PEER_STATUS_HANDSHAKE )
if( peer->status < PEER_STATUS_CONNECTED )
{
for( ;; )
{
if( peer->size < 1 )
{
peer->size = 1024;
peer->buf = malloc( peer->size );
}
else if( peer->pos >= peer->size )
{
peer->size *= 2;
peer->buf = realloc( peer->buf, peer->size );
}
ret = tr_netRecv( peer->socket, &peer->buf[peer->pos],
peer->size - peer->pos );
if( ret & TR_NET_CLOSE )
{
peer_dbg( "connection closed" );
goto dropPeer;
}
else if( ret & TR_NET_BLOCK )
{
break;
}
peer->date = tr_date();
peer->pos += ret;
if( parseBuf( tor, peer, ret ) )
{
goto dropPeer;
}
}
i++;
continue;
}
/* Try to write */
@ -393,31 +361,28 @@ writeBegin:
}
writeEnd:
/* Connected peers: ask for a block whenever possible */
if( peer->status & PEER_STATUS_CONNECTED )
/* Ask for a block whenever possible */
if( !tr_cpIsSeeding( tor->completion ) &&
!peer->amInterested && tor->peerCount > TR_MAX_PEER_COUNT - 2 )
{
if( tor->blockHaveCount < tor->blockCount &&
!peer->amInterested && tor->peerCount > TR_MAX_PEER_COUNT - 2 )
{
/* This peer is no use to us, and it seems there are
more */
peer_dbg( "not interesting" );
tr_peerRem( tor, i );
continue;
}
/* This peer is no use to us, and it seems there are
more */
peer_dbg( "not interesting" );
tr_peerRem( tor, i );
continue;
}
if( peer->amInterested && !peer->peerChoking )
if( peer->amInterested && !peer->peerChoking )
{
int block;
while( peer->inRequestCount < OUR_REQUEST_COUNT )
{
int block;
while( peer->inRequestCount < OUR_REQUEST_COUNT )
block = chooseBlock( tor, peer );
if( block < 0 )
{
block = chooseBlock( tor, peer );
if( block < 0 )
{
break;
}
sendRequest( tor, peer, block );
break;
}
sendRequest( tor, peer, block );
}
}

View File

@ -215,7 +215,7 @@ static void sendBitfield( tr_torrent_t * tor, tr_peer_t * peer )
TR_HTONL( 1 + bitfieldSize, p );
p[4] = 5;
memcpy( &p[5], tor->bitfield, bitfieldSize );
memcpy( &p[5], tr_cpPieceBitfield( tor->completion ), bitfieldSize );
peer_dbg( "SEND bitfield" );
}
@ -257,8 +257,7 @@ static void sendRequest( tr_torrent_t * tor, tr_peer_t * peer, int block )
TR_HTONL( r->begin, p + 9 );
TR_HTONL( r->length, p + 13 );
/* Remember that we have one more uploader for this block */
(tor->blockHave[block])++;
tr_cpDownloaderAdd( tor->completion, block );
peer_dbg( "SEND request %d/%d (%d bytes)",
r->index, r->begin, r->length );

View File

@ -51,10 +51,7 @@ static inline int parseChoke( tr_torrent_t * tor, tr_peer_t * peer,
for( i = 0; i < peer->inRequestCount; i++ )
{
r = &peer->inRequests[i];
if( tor->blockHave[tr_block(r->index,r->begin)] > 0 )
{
tor->blockHave[tr_block(r->index,r->begin)]--;
}
tr_cpDownloaderRem( tor->completion, tr_block(r->index,r->begin) );
}
peer->inRequestCount = 0;
}
@ -245,10 +242,8 @@ static inline int parsePiece( tr_torrent_t * tor, tr_peer_t * peer,
for( j = 0; j < i; j++ )
{
r = &peer->inRequests[j];
if( tor->blockHave[tr_block(r->index,r->begin)] > 0 )
{
tor->blockHave[tr_block(r->index,r->begin)]--;
}
tr_cpDownloaderRem( tor->completion,
tr_block(r->index,r->begin) );
}
suckyClient = 1;
peer->inRequestCount -= i;
@ -274,7 +269,7 @@ static inline int parsePiece( tr_torrent_t * tor, tr_peer_t * peer,
}
block = tr_block( r->index, r->begin );
if( tor->blockHave[block] < 0 )
if( tr_cpBlockIsComplete( tor->completion, block ) )
{
peer_dbg( "have this block already" );
(peer->inRequestCount)--;
@ -283,13 +278,13 @@ static inline int parsePiece( tr_torrent_t * tor, tr_peer_t * peer,
return 0;
}
tor->blockHave[block] = -1;
tor->blockHaveCount += 1;
tr_cpBlockAdd( tor->completion, block );
tr_ioWrite( tor->io, index, begin, len - 9, &p[8] );
tr_cpDownloaderRem( tor->completion, block );
sendCancel( tor, block );
if( tr_bitfieldHas( tor->bitfield, index ) )
if( tr_cpPieceIsComplete( tor->completion, index ) )
{
tr_peer_t * otherPeer;

View File

@ -163,6 +163,46 @@ static int checkPeer( tr_torrent_t * tor, int i )
}
}
/* Connect */
if( ( peer->status & PEER_STATUS_IDLE ) &&
!tr_fdSocketWillCreate( tor->fdlimit, 0 ) )
{
peer->socket = tr_netOpen( peer->addr, peer->port );
if( peer->socket < 0 )
{
peer_dbg( "connection failed" );
tr_fdSocketClosed( tor->fdlimit, 0 );
return 1;
}
peer->status = PEER_STATUS_CONNECTING;
}
/* Try to send handshake */
if( peer->status & PEER_STATUS_CONNECTING )
{
uint8_t buf[68];
tr_info_t * inf = &tor->info;
int ret;
buf[0] = 19;
memcpy( &buf[1], "BitTorrent protocol", 19 );
memset( &buf[20], 0, 8 );
memcpy( &buf[28], inf->hash, 20 );
memcpy( &buf[48], tor->id, 20 );
ret = tr_netSend( peer->socket, buf, 68 );
if( ret & TR_NET_CLOSE )
{
peer_dbg( "connection closed" );
return 1;
}
else if( !( ret & TR_NET_BLOCK ) )
{
peer_dbg( "SEND handshake" );
peer->status = PEER_STATUS_HANDSHAKE;
}
}
return 0;
}
@ -178,6 +218,7 @@ static int isInteresting( tr_torrent_t * tor, tr_peer_t * peer )
int i;
int bitfieldSize = ( inf->pieceCount + 7 ) / 8;
uint8_t * bitfield = tr_cpPieceBitfield( tor->completion );
if( !peer->bitfield )
{
@ -187,7 +228,7 @@ static int isInteresting( tr_torrent_t * tor, tr_peer_t * peer )
for( i = 0; i < bitfieldSize; i++ )
{
if( ( peer->bitfield[i] & ~(tor->bitfield[i]) ) & 0xFF )
if( ( peer->bitfield[i] & ~(bitfield[i]) ) & 0xFF )
{
return 1;
}
@ -222,8 +263,7 @@ static inline int chooseBlock( tr_torrent_t * tor, tr_peer_t * peer )
{
tr_info_t * inf = &tor->info;
int i, j;
int startBlock, endBlock, countBlocks;
int i;
int missingBlocks, minMissing;
int poolSize, * pool;
int block, minDownloading;
@ -234,40 +274,17 @@ static inline int chooseBlock( tr_torrent_t * tor, tr_peer_t * peer )
minMissing = tor->blockCount + 1;
for( i = 0; i < inf->pieceCount; i++ )
{
missingBlocks = tr_cpMissingBlocksForPiece( tor->completion, i );
if( missingBlocks < 1 )
{
/* We already have or are downloading all blocks */
continue;
}
if( !tr_bitfieldHas( peer->bitfield, i ) )
{
/* The peer doesn't have this piece */
continue;
}
if( tr_bitfieldHas( tor->bitfield, i ) )
{
/* We already have it */
continue;
}
/* Count how many blocks from this piece are missing */
startBlock = tr_pieceStartBlock( i );
countBlocks = tr_pieceCountBlocks( i );
endBlock = startBlock + countBlocks;
missingBlocks = countBlocks;
for( j = startBlock; j < endBlock; j++ )
{
/* TODO: optimize */
if( tor->blockHave[j] )
{
missingBlocks--;
}
if( missingBlocks > minMissing )
{
break;
}
}
if( missingBlocks < 1 )
{
/* We are already downloading all blocks */
continue;
}
/* We are interested in this piece, remember it */
if( missingBlocks < minMissing )
@ -331,44 +348,43 @@ static inline int chooseBlock( tr_torrent_t * tor, tr_peer_t * peer )
free( pool2 );
/* Pick a block in this piece */
startBlock = tr_pieceStartBlock( piece );
endBlock = startBlock + tr_pieceCountBlocks( piece );
for( i = startBlock; i < endBlock; i++ )
{
if( !tor->blockHave[i] )
{
block = i;
goto check;
}
}
/* Shouldn't happen */
return -1;
block = tr_cpMissingBlockInPiece( tor->completion, piece );
goto check;
}
free( pool );
/* "End game" mode */
block = -1;
minDownloading = TR_MAX_PEER_COUNT + 1;
for( i = 0; i < tor->blockCount; i++ )
minDownloading = 255;
block = -1;
for( i = 0; i < inf->pieceCount; i++ )
{
/* TODO: optimize */
if( tr_bitfieldHas( peer->bitfield, tr_blockPiece( i ) ) &&
tor->blockHave[i] >= 0 && tor->blockHave[i] < minDownloading )
int downloaders, block2;
if( !tr_bitfieldHas( peer->bitfield, i ) )
{
block = i;
minDownloading = tor->blockHave[i];
/* The peer doesn't have this piece */
continue;
}
if( tr_cpPieceIsComplete( tor->completion, i ) )
{
/* We already have it */
continue;
}
block2 = tr_cpMostMissingBlockInPiece( tor->completion, i, &downloaders );
if( block2 > -1 && downloaders < minDownloading )
{
block = block2;
minDownloading = downloaders;
}
}
check:
if( block < 0 )
{
/* Shouldn't happen */
return -1;
}
check:
for( i = 0; i < peer->inRequestCount; i++ )
{
tr_request_t * r;

View File

@ -245,17 +245,28 @@ static void sendQuery( tr_tracker_t * tc )
else
event = "";
left = (uint64_t) ( tor->blockCount - tor->blockHaveCount ) *
(uint64_t) tor->blockSize;
left = MIN( left, inf->totalSize );
left = tr_cpLeftBytes( tor->completion );
ret = snprintf( (char *) tc->buf, tc->size,
"GET %s?info_hash=%s&peer_id=%s&port=%d&uploaded=%lld&"
"downloaded=%lld&left=%lld&compact=1&numwant=50%s "
"HTTP/1.1\r\nHost: %s\r\nConnection: close\r\n\r\n",
inf->trackerAnnounce, tor->hashString, tc->id,
tor->bindPort, tor->uploaded[9], tor->downloaded[9],
left, event, inf->trackerAddress );
"GET %s?"
"info_hash=%s&"
"peer_id=%s&"
"port=%d&"
"uploaded=%lld&"
"downloaded=%lld&"
"left=%lld&"
"compact=1&"
"numwant=50&"
"key=%s"
"%s "
"HTTP/1.1\r\n"
"Host: %s\r\n"
"User-Agent: Transmission/%d.%d\r\n"
"Connection: close\r\n\r\n",
inf->trackerAnnounce, tor->hashString, tc->id,
tor->bindPort, tor->uploaded[9], tor->downloaded[9],
left, tor->key, event, inf->trackerAddress,
VERSION_MAJOR, VERSION_MINOR );
ret = tr_netSend( tc->socket, tc->buf, ret );
if( ret & TR_NET_CLOSE )
@ -381,7 +392,7 @@ static void recvAnswer( tr_tracker_t * tc )
{
tc->leechers = beFoo->val.i;
}
if( tc->seeders + tc->seeders >= 50 )
if( tc->seeders + tc->leechers >= 50 )
{
tc->hasManyPeers = 1;
}

View File

@ -51,6 +51,13 @@ tr_handle_t * tr_init()
h->id[i] = ( r < 26 ) ? ( 'a' + r ) : ( '0' + r - 26 ) ;
}
/* Random key */
for( i = 0; i < 20; i++ )
{
r = tr_rand( 36 );
h->key[i] = ( r < 26 ) ? ( 'a' + r ) : ( '0' + r - 26 ) ;
}
/* Don't exit when writing on a broken socket */
signal( SIGPIPE, SIG_IGN );
@ -164,6 +171,7 @@ int tr_torrentInit( tr_handle_t * h, const char * path )
tor->status = TR_STATUS_PAUSE;
tor->id = h->id;
tor->key = h->key;
/* Guess scrape URL */
s1 = strchr( inf->trackerAnnounce, '/' );
@ -191,8 +199,7 @@ int tr_torrentInit( tr_handle_t * h, const char * path )
tor->blockSize = MIN( inf->pieceSize, 1 << 14 );
tor->blockCount = ( inf->totalSize + tor->blockSize - 1 ) /
tor->blockSize;
tor->blockHave = calloc( tor->blockCount, 1 );
tor->bitfield = calloc( ( inf->pieceCount + 7 ) / 8, 1 );
tor->completion = tr_cpInit( tor );
tr_lockInit( &tor->lock );
@ -330,8 +337,6 @@ int tr_torrentStat( tr_handle_t * h, tr_stat_t ** stat )
tor = h->torrents[i];
inf = &tor->info;
tr_lockLock( tor->lock );
if( ( tor->status & TR_STATUS_STOPPED ) ||
( ( tor->status & TR_STATUS_STOPPING ) &&
tr_date() > tor->stopDate + 60000 ) )
@ -340,6 +345,8 @@ int tr_torrentStat( tr_handle_t * h, tr_stat_t ** stat )
tor->status = TR_STATUS_PAUSE;
}
tr_lockLock( tor->lock );
memcpy( &s[i].info, &tor->info, sizeof( tr_info_t ) );
s[i].status = tor->status;
memcpy( s[i].error, tor->error, sizeof( s[i].error ) );
@ -364,8 +371,7 @@ int tr_torrentStat( tr_handle_t * h, tr_stat_t ** stat )
}
}
s[i].progress = (float) tor->blockHaveCount / (float) tor->blockCount;
s[i].progress = tr_cpCompletionAsFloat( tor->completion );
s[i].rateDownload = rateDownload( tor );
s[i].rateUpload = rateUpload( tor );
@ -375,8 +381,8 @@ int tr_torrentStat( tr_handle_t * h, tr_stat_t ** stat )
}
else
{
s[i].eta = (float) (tor->blockCount - tor->blockHaveCount ) *
(float) tor->blockSize / s[i].rateDownload / 1024.0;
s[i].eta = (float) ( 1.0 - s[i].progress ) *
(float) inf->totalSize / s[i].rateDownload / 1024.0;
if( s[i].eta > 99 * 3600 + 59 * 60 + 59 )
{
s[i].eta = -1;
@ -387,7 +393,7 @@ int tr_torrentStat( tr_handle_t * h, tr_stat_t ** stat )
{
piece = j * inf->pieceCount / 120;
if( tr_bitfieldHas( tor->bitfield, piece ) )
if( tr_cpPieceIsComplete( tor->completion, piece ) )
{
s[i].pieces[j] = -1;
continue;
@ -430,14 +436,13 @@ void tr_torrentClose( tr_handle_t * h, int t )
if( tor->status & ( TR_STATUS_STOPPING | TR_STATUS_STOPPED ) )
{
/* Join the thread first */
tr_lockLock( tor->lock );
torrentReallyStop( h, t );
tr_lockUnlock( tor->lock );
}
h->torrentCount--;
tr_lockClose( tor->lock );
tr_cpClose( tor->completion );
if( tor->destination )
{
@ -445,8 +450,6 @@ void tr_torrentClose( tr_handle_t * h, int t )
}
free( inf->pieces );
free( inf->files );
free( tor->blockHave );
free( tor->bitfield );
free( tor );
memmove( &h->torrents[t], &h->torrents[t+1],
@ -476,19 +479,19 @@ static void downloadLoop( void * _tor )
signal( SIGINT, SIG_IGN );
#endif
tr_lockLock( tor->lock );
tor->io = tr_ioInit( tor );
tor->status = ( tor->blockHaveCount < tor->blockCount ) ?
TR_STATUS_DOWNLOAD : TR_STATUS_SEED;
tor->status = tr_cpIsSeeding( tor->completion ) ?
TR_STATUS_SEED : TR_STATUS_DOWNLOAD;
while( !tor->die )
{
date1 = tr_date();
tr_lockLock( tor->lock );
/* Are we finished ? */
if( ( tor->status & TR_STATUS_DOWNLOAD ) &&
tor->blockHaveCount >= tor->blockCount )
tr_cpIsSeeding( tor->completion ) )
{
/* Done */
tor->status = TR_STATUS_SEED;
@ -501,8 +504,6 @@ static void downloadLoop( void * _tor )
/* Try to get new peers or to send a message to the tracker */
tr_trackerPulse( tor->tracker );
tr_lockUnlock( tor->lock );
if( tor->status & TR_STATUS_STOPPED )
{
break;
@ -512,10 +513,14 @@ static void downloadLoop( void * _tor )
date2 = tr_date();
if( date2 < date1 + 20 )
{
tr_lockUnlock( tor->lock );
tr_wait( date1 + 20 - date2 );
tr_lockLock( tor->lock );
}
}
tr_lockUnlock( tor->lock );
tr_ioClose( tor->io );
tor->status = TR_STATUS_STOPPED;

View File

@ -75,6 +75,11 @@ static inline void tr_bitfieldAdd( uint8_t * bitfield, int piece )
bitfield[ piece / 8 ] |= ( 1 << ( 7 - ( piece % 8 ) ) );
}
static inline void tr_bitfieldRem( uint8_t * bitfield, int piece )
{
bitfield[ piece / 8 ] &= ~( 1 << ( 7 - ( piece % 8 ) ) );
}
#define tr_blockPiece(a) _tr_blockPiece(tor,a)
static inline int _tr_blockPiece( tr_torrent_t * tor, int block )
{

View File

@ -27,13 +27,15 @@
#include <transmission.h>
#include "PrefsController.h"
@class TorrentTableView;
@interface Controller : NSObject
{
tr_handle_t * fHandle;
int fCount;
tr_stat_t * fStat;
int fResumeOnWake[TR_MAX_TORRENT_COUNT];
NSToolbar * fToolbar;
IBOutlet PrefsController * fPrefsController;
@ -41,9 +43,10 @@
IBOutlet NSMenuItem * fAdvancedBarItem;
IBOutlet NSWindow * fWindow;
IBOutlet NSTableView * fTableView;
IBOutlet TorrentTableView * fTableView;
IBOutlet NSTextField * fTotalDLField;
IBOutlet NSTextField * fTotalULField;
IBOutlet NSMenu * fContextMenu;
IBOutlet NSPanel * fInfoPanel;
IBOutlet NSTextField * fInfoTitle;
@ -70,12 +73,20 @@
- (void) resumeTorrent: (id) sender;
- (void) resumeTorrentWithIndex: (int) index;
- (void) removeTorrent: (id) sender;
- (void) removeTorrentDeleteFile: (id) sender;
- (void) removeTorrentDeleteData: (id) sender;
- (void) removeTorrentDeleteBoth: (id) sender;
- (void) removeTorrentWithIndex: (int) idx
deleteTorrent: (BOOL) deleteTorrent
deleteData: (BOOL) deleteData;
- (void) showInfo: (id) sender;
- (void) updateUI: (NSTimer *) timer;
- (void) sleepCallBack: (natural_t) messageType argument:
(void *) messageArgument;
- (NSMenu *) menuForIndex: (int) idx;
- (void) showMainWindow: (id) sender;
- (void) linkHomepage: (id) sender;
- (void) linkForums: (id) sender;

View File

@ -25,12 +25,20 @@
#include "NameCell.h"
#include "ProgressCell.h"
#include "Utils.h"
#include "TorrentTableView.h"
#define TOOLBAR_OPEN @"Toolbar Open"
#define TOOLBAR_REMOVE @"Toolbar Remove"
#define TOOLBAR_PREFS @"Toolbar Preferences"
#define TOOLBAR_INFO @"Toolbar Info"
#define CONTEXT_PAUSE 1
#define CONTEXT_REMOVE 2
#define CONTEXT_REMOVE_TORRENT 3
#define CONTEXT_REMOVE_DATA 4
#define CONTEXT_REMOVE_BOTH 5
#define CONTEXT_INFO 6
static void sleepCallBack( void * controller, io_service_t y,
natural_t messageType, void * messageArgument )
{
@ -82,9 +90,6 @@ static void sleepCallBack( void * controller, io_service_t y,
[fWindow setToolbar: fToolbar];
[fWindow setDelegate: self];
[fTableView setDataSource: self];
[fTableView setDelegate: self];
NSTableColumn * tableColumn;
NameCell * nameCell;
ProgressCell * progressCell;
@ -92,7 +97,6 @@ static void sleepCallBack( void * controller, io_service_t y,
nameCell = [[NameCell alloc] init];
progressCell = [[ProgressCell alloc] init];
tableColumn = [fTableView tableColumnWithIdentifier: @"Name"];
[nameCell setController: self];
[tableColumn setDataCell: nameCell];
[tableColumn setMinWidth: 10.0];
[tableColumn setMaxWidth: 3000.0];
@ -396,10 +400,103 @@ static void sleepCallBack( void * controller, io_service_t y,
[self updateUI: NULL];
}
- (void) removeTorrentWithIndex: (int) idx
deleteTorrent: (BOOL) deleteTorrent
deleteData: (BOOL) deleteData
{
BOOL torrentWarning = ![[NSUserDefaults standardUserDefaults]
boolForKey:@"SkipTorrentDeletionWarning"];
BOOL dataWarning = ![[NSUserDefaults standardUserDefaults]
boolForKey:@"SkipDataDeletionWarning"];
if ( ( torrentWarning && deleteTorrent ) || (dataWarning && deleteData ) )
{
NSAlert *alert = [[[NSAlert alloc] init] autorelease];
[alert addButtonWithTitle:@"No"];
[alert addButtonWithTitle:@"Yes"];
[alert setAlertStyle:NSWarningAlertStyle];
[alert setMessageText:@"Are you sure you want to remove and delete?"];
if ( (deleteTorrent && torrentWarning) &&
!( dataWarning && deleteData ) )
{
/* delete torrent warning YES, delete data warning NO */
[alert setInformativeText:@"If you choose yes, the .torrent file will "
"be deleted. This cannot be undone."];
}
else if( (deleteData && dataWarning) &&
!( torrentWarning && deleteTorrent ) )
{
/* delete torrent warning NO, delete data warning YES */
[alert setInformativeText:@"If you choose yes, the downloaded data will "
"be deleted. This cannot be undone."];
}
else
{
/* delete torrent warning YES, delete data warning YES */
[alert setInformativeText:@"If you choose yes, both downloaded data and "
"torrent file will be deleted. This cannot be undone."];
}
if ( [alert runModal] == NSAlertFirstButtonReturn )
return;
}
if ( deleteData )
{
tr_file_t * files = fStat[idx].info.files;
int i;
for ( i = 0; i < fStat[idx].info.fileCount; i++ )
{
if ( -1 == remove([[NSString stringWithFormat:@"%s/%s",
fStat[idx].folder,
files[i].name]
cString]) )
{
NSLog(@"remove(%s) failed, errno = %i",
files[i].name,
errno);
}
}
/* in some cases, we should remove fStat[idx].folder also. When? */
}
if ( deleteTorrent )
{
if ( -1 == remove( fStat[idx].info.torrent ) )
{
NSLog(@"remove(%s) failed, errno = %i",
fStat[idx].info.torrent,
errno);
}
}
tr_torrentClose( fHandle, idx );
[self updateUI: NULL];
}
- (void) removeTorrent: (id) sender
{
tr_torrentClose( fHandle, [fTableView selectedRow] );
[self updateUI: NULL];
[self removeTorrentWithIndex: [fTableView selectedRow] deleteTorrent: NO deleteData: NO ];
}
- (void) removeTorrentDeleteFile: (id) sender
{
[self removeTorrentWithIndex: [fTableView selectedRow] deleteTorrent: YES deleteData: NO];
}
- (void) removeTorrentDeleteData: (id) sender
{
[self removeTorrentWithIndex: [fTableView selectedRow] deleteTorrent: NO deleteData: YES];
}
- (void) removeTorrentDeleteBoth: (id) sender
{
[self removeTorrentWithIndex: [fTableView selectedRow] deleteTorrent: YES deleteData: YES];
}
- (void) showInfo: (id) sender
@ -425,7 +522,7 @@ static void sleepCallBack( void * controller, io_service_t y,
free( fStat );
}
fCount = tr_torrentStat( fHandle, &fStat );
[fTableView reloadData];
[fTableView updateUI: fStat];
/* Update the global DL/UL rates */
tr_torrentRates( fHandle, &dl, &ul );
@ -448,6 +545,43 @@ static void sleepCallBack( void * controller, io_service_t y,
[self updateToolbar];
}
- (NSMenu *) menuForIndex: (int) idx
{
if ( idx < 0 || idx >= fCount )
{
return nil;
}
int status = fStat[idx].status;
NSMenuItem *pauseItem = [fContextMenu itemWithTag: CONTEXT_PAUSE];
NSMenuItem *removeItem = [fContextMenu itemAtIndex: 1];
[pauseItem setTarget: self];
if ( status & TR_STATUS_CHECK ||
status & TR_STATUS_DOWNLOAD ||
status & TR_STATUS_SEED )
{
/* we can stop */
[removeItem setEnabled: NO];
[pauseItem setTitle: @"Pause"];
[pauseItem setAction: @selector(stopTorrent:)];
[pauseItem setEnabled: YES];
} else {
/* we are stopped */
[removeItem setEnabled: YES];
[pauseItem setTitle: @"Resume"];
[pauseItem setAction: @selector(resumeTorrent:)];
/* don't allow resuming if we aren't in PAUSE */
if ( !(status & TR_STATUS_PAUSE) )
[pauseItem setEnabled: NO];
}
return fContextMenu;
}
- (int) numberOfRowsInTableView: (NSTableView *) t
{
return fCount;
@ -464,7 +598,7 @@ static void sleepCallBack( void * controller, io_service_t y,
{
if( [[tableColumn identifier] isEqualToString: @"Name"] )
{
[(NameCell *) cell setStat: &fStat[rowIndex] index: rowIndex];
[(NameCell *) cell setStat: &fStat[rowIndex]];
}
else if( [[tableColumn identifier] isEqualToString: @"Progress"] )
{
@ -518,7 +652,7 @@ static void sleepCallBack( void * controller, io_service_t y,
}
/* Update info window */
[fInfoTitle setStringValue: [NSString stringWithCString:
[fInfoTitle setStringValue: [NSString stringWithUTF8String:
fStat[row].info.name]];
[fInfoTracker setStringValue: [NSString stringWithFormat:
@"%s:%d", fStat[row].info.trackerAddress, fStat[row].info.trackerPort]];

View File

@ -7,6 +7,9 @@
linkHomepage = id;
openShowSheet = id;
removeTorrent = id;
removeTorrentDeleteBoth = id;
removeTorrentDeleteData = id;
removeTorrentDeleteFile = id;
resumeTorrent = id;
showInfo = id;
showMainWindow = id;
@ -16,6 +19,7 @@
LANGUAGE = ObjC;
OUTLETS = {
fAdvancedBarItem = NSMenuItem;
fContextMenu = NSMenu;
fInfoAnnounce = NSTextField;
fInfoDownloaded = NSTextField;
fInfoFolder = NSTextField;
@ -49,6 +53,12 @@
fWindow = NSWindow;
};
SUPERCLASS = NSObject;
},
{
CLASS = TorrentTableView;
LANGUAGE = ObjC;
OUTLETS = {fController = Controller; };
SUPERCLASS = NSTableView;
}
);
IBVersion = 1;

View File

@ -3,24 +3,24 @@
<plist version="1.0">
<dict>
<key>IBDocumentLocation</key>
<string>204 84 361 432 0 0 1440 878 </string>
<string>188 334 361 432 0 0 1280 832 </string>
<key>IBEditorPositions</key>
<dict>
<key>29</key>
<string>105 768 371 44 0 0 1280 832 </string>
<key>456</key>
<string>174 512 147 87 0 0 1280 832 </string>
</dict>
<key>IBFramework Version</key>
<string>439.0</string>
<string>443.0</string>
<key>IBOldestOS</key>
<integer>3</integer>
<key>IBOpenObjects</key>
<array>
<integer>456</integer>
<integer>21</integer>
<integer>29</integer>
<integer>273</integer>
<integer>343</integer>
</array>
<key>IBSystem Version</key>
<string>8C46</string>
<string>8F46</string>
</dict>
</plist>

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -29,15 +29,12 @@
@interface NameCell : NSCell
{
Controller * fController;
tr_stat_t * fStat;
int fIndex;
NSRect fPauseRect;
NSRect fRevealRect;
NSPoint fClickPoint;
NSString * fNameString;
NSString * fSizeString;
NSString * fTimeString;
NSString * fPeersString;
}
- (void) setController: (Controller *) controller;
- (void) setStat: (tr_stat_t *) stat index: (int) index;
- (void) setStat: (tr_stat_t *) stat;
@end
#endif

View File

@ -25,209 +25,98 @@
@implementation NameCell
- (void) setController: (Controller *) controller
- (void) setStat: (tr_stat_t *) stat
{
fController = controller;
}
fNameString = [NSString stringWithUTF8String: stat->info.name];
fSizeString = [NSString stringWithFormat: @" (%@)",
stringForFileSize( stat->info.totalSize )];
fTimeString = @"";
fPeersString = @"";
- (void) setStat: (tr_stat_t *) stat index: (int) idx
{
fStat = stat;
fIndex = idx;
if( stat->status & TR_STATUS_PAUSE )
{
fTimeString = [NSString stringWithFormat:
@"Paused (%.2f %%)", 100 * stat->progress];
}
else if( stat->status & TR_STATUS_CHECK )
{
fTimeString = [NSString stringWithFormat:
@"Checking existing files (%.2f %%)", 100 * stat->progress];
}
else if( stat->status & TR_STATUS_DOWNLOAD )
{
if( stat->eta < 0 )
{
fTimeString = [NSString stringWithFormat:
@"Finishing in --:--:-- (%.2f %%)", 100 * stat->progress];
}
else
{
fTimeString = [NSString stringWithFormat:
@"Finishing in %02d:%02d:%02d (%.2f %%)",
stat->eta / 3600, ( stat->eta / 60 ) % 60,
stat->eta % 60, 100 * stat->progress];
}
fPeersString = [NSString stringWithFormat:
@"Downloading from %d of %d peer%s",
stat->peersUploading, stat->peersTotal,
( stat->peersTotal == 1 ) ? "" : "s"];
}
else if( stat->status & TR_STATUS_SEED )
{
fTimeString = [NSString stringWithFormat:
@"Seeding, uploading to %d of %d peer%s",
stat->peersDownloading, stat->peersTotal,
( stat->peersTotal == 1 ) ? "" : "s"];
}
else if( stat->status & TR_STATUS_STOPPING )
{
fTimeString = @"Stopping...";
}
if( ( stat->status & ( TR_STATUS_DOWNLOAD | TR_STATUS_SEED ) ) &&
( stat->status & TR_TRACKER_ERROR ) )
{
fPeersString = [NSString stringWithFormat: @"%@%@",
@"Error: ", [NSString stringWithUTF8String: stat->error]];
}
}
- (void) drawWithFrame: (NSRect) cellFrame inView: (NSView *) view
{
NSString * string;
NSPoint pen;
NSMutableDictionary * attributes;
if( ![view lockFocusIfCanDraw] )
{
return;
}
NSString * nameString = NULL, * timeString = @"", * peersString = @"";
NSMutableDictionary * attributes;
pen = cellFrame.origin;
attributes = [NSMutableDictionary dictionaryWithCapacity: 1];
NSPoint pen = cellFrame.origin;
NSString * sizeString = [NSString stringWithFormat: @" (%@)",
stringForFileSize( fStat->info.totalSize )];
nameString = [NSString stringWithFormat: @"%@%@",
stringFittingInWidth( fStat->info.name, cellFrame.size.width -
35 - widthForString( sizeString, 12 ), 12 ),
sizeString];
if( fStat->status & TR_STATUS_PAUSE )
{
timeString = [NSString stringWithFormat:
@"Paused (%.2f %%)", 100 * fStat->progress];
peersString = @"";
}
else if( fStat->status & TR_STATUS_CHECK )
{
timeString = [NSString stringWithFormat:
@"Checking existing files (%.2f %%)", 100 * fStat->progress];
peersString = @"";
}
else if( fStat->status & TR_STATUS_DOWNLOAD )
{
if( fStat->eta < 0 )
{
timeString = [NSString stringWithFormat:
@"Finishing in --:--:-- (%.2f %%)", 100 * fStat->progress];
}
else
{
timeString = [NSString stringWithFormat:
@"Finishing in %02d:%02d:%02d (%.2f %%)",
fStat->eta / 3600, ( fStat->eta / 60 ) % 60,
fStat->eta % 60, 100 * fStat->progress];
}
peersString = [NSString stringWithFormat:
@"Downloading from %d of %d peer%s",
fStat->peersUploading, fStat->peersTotal,
( fStat->peersTotal == 1 ) ? "" : "s"];
}
else if( fStat->status & TR_STATUS_SEED )
{
timeString = [NSString stringWithFormat:
@"Seeding, uploading to %d of %d peer%s",
fStat->peersDownloading, fStat->peersTotal,
( fStat->peersTotal == 1 ) ? "" : "s"];
peersString = @"";
}
else if( fStat->status & TR_STATUS_STOPPING )
{
timeString = @"Stopping...";
peersString = @"";
}
if( ( fStat->status & ( TR_STATUS_DOWNLOAD | TR_STATUS_SEED ) ) &&
( fStat->status & TR_TRACKER_ERROR ) )
{
peersString = [NSString stringWithFormat: @"%@%@",
@"Error: ", stringFittingInWidth( fStat->error,
cellFrame.size.width - 40 -
widthForString( @"Error: ", 10 ), 10 )];
}
[attributes setObject: [NSFont messageFontOfSize:12.0]
forKey: NSFontAttributeName];
pen.x += 5; pen.y += 5;
[nameString drawAtPoint: pen withAttributes: attributes];
string = [NSString stringWithFormat: @"%@%@",
stringFittingInWidth( fNameString, cellFrame.size.width -
35 - widthForString( fSizeString, 12 ), 12 ), fSizeString];
[string drawAtPoint: pen withAttributes: attributes];
[attributes setObject: [NSFont messageFontOfSize:10.0]
forKey: NSFontAttributeName];
pen.x += 5; pen.y += 20;
[timeString drawAtPoint: pen withAttributes: attributes];
[fTimeString drawAtPoint: pen withAttributes: attributes];
pen.x += 0; pen.y += 15;
[peersString drawAtPoint: pen withAttributes: attributes];
/* "Pause" button */
fPauseRect = NSMakeRect( cellFrame.origin.x + cellFrame.size.width - 19,
cellFrame.origin.y + cellFrame.size.height - 38,
14, 14 );
NSImage * pauseImage = NULL;
if( fStat->status & TR_STATUS_PAUSE )
{
if( NSPointInRect( fClickPoint, fPauseRect ) )
{
pauseImage = [NSImage imageNamed: @"ResumeOn.png"];
}
else
{
pauseImage = [NSImage imageNamed: @"ResumeOff.png"];
}
}
else if( fStat->status &
( TR_STATUS_CHECK | TR_STATUS_DOWNLOAD | TR_STATUS_SEED ) )
{
if( NSPointInRect( fClickPoint, fPauseRect ) )
{
pauseImage = [NSImage imageNamed: @"PauseOn.png"];
}
else
{
pauseImage = [NSImage imageNamed: @"PauseOff.png"];
}
}
if( pauseImage )
{
pen.x = fPauseRect.origin.x;
pen.y = fPauseRect.origin.y + 14;
[pauseImage compositeToPoint: pen operation: NSCompositeSourceOver];
}
/* "Reveal in Finder" button */
fRevealRect = NSMakeRect( cellFrame.origin.x + cellFrame.size.width - 19,
cellFrame.origin.y + cellFrame.size.height - 19,
14, 14 );
NSImage * revealImage;
if( NSPointInRect( fClickPoint, fRevealRect ) )
{
revealImage = [NSImage imageNamed: @"RevealOn.png"];
}
else
{
revealImage = [NSImage imageNamed: @"RevealOff.png"];
}
pen.x = fRevealRect.origin.x;
pen.y = fRevealRect.origin.y + 14;
[revealImage compositeToPoint: pen operation: NSCompositeSourceOver];
string = stringFittingInWidth( fPeersString,
cellFrame.size.width - 40, 10 );
[string drawAtPoint: pen withAttributes: attributes];
[view unlockFocus];
}
/* Track mouse as long as button is down */
- (BOOL) startTrackingAt: (NSPoint) start inView: (NSView *) v
{
fClickPoint = start;
return YES;
}
- (BOOL) continueTracking: (NSPoint) last at: (NSPoint) current
inView: (NSView *) v
{
fClickPoint = current;
return YES;
}
- (void) stopTracking: (NSPoint) last at:(NSPoint) stop
inView: (NSView *) v mouseIsUp: (BOOL) flag
{
if( flag )
{
if( NSPointInRect( stop, fRevealRect ) )
{
/* Reveal in Finder */
NSString * string = [NSString stringWithFormat:
@"tell application \"Finder\"\nactivate\nreveal (POSIX file \"%s/%s\")\nend tell",
fStat->folder, fStat->info.name];
NSAppleScript * appleScript;
appleScript = [[NSAppleScript alloc] initWithSource: string];
NSDictionary * error;
if( ![appleScript executeAndReturnError: &error] )
{
printf( "Reveal in Finder: AppleScript failed\n" );
}
[appleScript release];
}
else if( NSPointInRect( stop, fPauseRect ) )
{
/* Pause, resume */
if( fStat->status & TR_STATUS_PAUSE )
{
[fController resumeTorrentWithIndex: fIndex];
}
else if( fStat->status & ( TR_STATUS_CHECK |
TR_STATUS_DOWNLOAD | TR_STATUS_SEED ) )
{
[fController stopTorrentWithIndex: fIndex];
}
}
}
fClickPoint = NSMakePoint(0,0);
}
@end

17
macosx/TorrentTableView.h Normal file
View File

@ -0,0 +1,17 @@
#include <Cocoa/Cocoa.h>
#include <transmission.h>
@class Controller;
@interface TorrentTableView : NSTableView
{
IBOutlet Controller * fController;
tr_stat_t * fStat;
NSPoint fClickPoint;
}
- (void) updateUI: (tr_stat_t *) stat;
@end

180
macosx/TorrentTableView.m Normal file
View File

@ -0,0 +1,180 @@
#include "TorrentTableView.h"
#include "Controller.h"
@implementation TorrentTableView
- (void) updateUI: (tr_stat_t *) stat
{
fStat = stat;
[self reloadData];
}
- (void) revealInFinder: (int) row
{
NSString * string;
NSAppleScript * appleScript;
NSDictionary * error;
string = [NSString stringWithFormat: @"tell application "
"\"Finder\"\nactivate\nreveal (POSIX file \"%@/%@\")\nend tell",
[NSString stringWithUTF8String: fStat[row].folder],
[NSString stringWithUTF8String: fStat[row].info.name]];
appleScript = [[NSAppleScript alloc] initWithSource: string];
if( ![appleScript executeAndReturnError: &error] )
{
printf( "Reveal in Finder: AppleScript failed\n" );
}
[appleScript release];
}
- (void) pauseOrResume: (int) row
{
if( fStat[row].status & TR_STATUS_PAUSE )
{
[fController resumeTorrentWithIndex: row];
}
else if( fStat[row].status & ( TR_STATUS_CHECK |
TR_STATUS_DOWNLOAD | TR_STATUS_SEED ) )
{
[fController stopTorrentWithIndex: row];
}
}
- (void) mouseDown: (NSEvent *) e
{
fClickPoint = [self convertPoint: [e locationInWindow] fromView: NULL];
[self display];
}
- (NSRect) pauseRectForRow: (int) row
{
int col;
NSRect cellRect, rect;
col = [self columnWithIdentifier: @"Name"];
cellRect = [self frameOfCellAtColumn: col row: row];
rect = NSMakeRect( cellRect.origin.x + cellRect.size.width - 19,
cellRect.origin.y + cellRect.size.height - 38,
14, 14 );
return rect;
}
- (NSRect) revealRectForRow: (int) row
{
int col;
NSRect cellRect, rect;
col = [self columnWithIdentifier: @"Name"];
cellRect = [self frameOfCellAtColumn: col row: row];
rect = NSMakeRect( cellRect.origin.x + cellRect.size.width - 19,
cellRect.origin.y + cellRect.size.height - 19,
14, 14 );
return rect;
}
- (BOOL) pointInPauseRect: (NSPoint) point
{
return NSPointInRect( point, [self pauseRectForRow:
[self rowAtPoint: point]] );
}
- (BOOL) pointInRevealRect: (NSPoint) point
{
return NSPointInRect( point, [self revealRectForRow:
[self rowAtPoint: point]] );
}
- (void) mouseUp: (NSEvent *) e
{
NSPoint point;
int row, col;
point = [self convertPoint: [e locationInWindow] fromView: NULL];
row = [self rowAtPoint: point];
col = [self columnAtPoint: point];
if( row < 0 )
{
[self deselectAll: NULL];
}
else if( [self pointInPauseRect: point] )
{
[self pauseOrResume: row];
}
else if( [self pointInRevealRect: point] )
{
[self revealInFinder: row];
[self display];
}
else
{
[self selectRowIndexes: [NSIndexSet indexSetWithIndex: row]
byExtendingSelection: NO];
}
fClickPoint = NSMakePoint( 0, 0 );
}
- (NSMenu *) menuForEvent: (NSEvent *) e
{
NSPoint point;
int row;
point = [self convertPoint: [e locationInWindow] fromView: NULL];
row = [self rowAtPoint: point];
if( row < 0 )
{
return NULL;
}
[self selectRowIndexes: [NSIndexSet indexSetWithIndex: row]
byExtendingSelection: NO];
return [fController menuForIndex: row];
}
- (void) drawRect: (NSRect) r
{
int i;
NSRect rect;
NSPoint point;
NSImage * image;
[super drawRect: r];
for( i = 0; i < [self numberOfRows]; i++ )
{
rect = [self pauseRectForRow: i];
image = NULL;
if( fStat[i].status & TR_STATUS_PAUSE )
{
image = NSPointInRect( fClickPoint, rect ) ?
[NSImage imageNamed: @"ResumeOn.png"] :
[NSImage imageNamed: @"ResumeOff.png"];
}
else if( fStat[i].status &
( TR_STATUS_CHECK | TR_STATUS_DOWNLOAD | TR_STATUS_SEED ) )
{
image = NSPointInRect( fClickPoint, rect ) ?
[NSImage imageNamed: @"PauseOn.png"] :
[NSImage imageNamed: @"PauseOff.png"];
}
if( image )
{
point = NSMakePoint( rect.origin.x, rect.origin.y + 14 );
[image compositeToPoint: point operation: NSCompositeSourceOver];
}
rect = [self revealRectForRow: i];
image = NSPointInRect( fClickPoint, rect ) ?
[NSImage imageNamed: @"RevealOn.png"] :
[NSImage imageNamed: @"RevealOff.png"];
point = NSMakePoint( rect.origin.x, rect.origin.y + 14 );
[image compositeToPoint: point operation: NSCompositeSourceOver];
}
}
@end

View File

@ -12,6 +12,7 @@
4D096C13089FB4E20091B166 /* ProgressCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D096C11089FB4E20091B166 /* ProgressCell.m */; };
4D118E1A08CB46B20033958F /* PrefsController.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D118E1908CB46B20033958F /* PrefsController.m */; };
4D2784370905709500687951 /* Transmission.icns in Resources */ = {isa = PBXBuildFile; fileRef = 4D2784360905709500687951 /* Transmission.icns */; };
4D364DA0091FBB2C00377D12 /* TorrentTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D364D9F091FBB2C00377D12 /* TorrentTableView.m */; };
4D3EA0AA08AE13C600EA10C2 /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4D3EA0A908AE13C600EA10C2 /* IOKit.framework */; };
4D6DAAC6090CE00500F43C22 /* RevealOff.png in Resources */ = {isa = PBXBuildFile; fileRef = 4D6DAAC4090CE00500F43C22 /* RevealOff.png */; };
4D6DAAC7090CE00500F43C22 /* RevealOn.png in Resources */ = {isa = PBXBuildFile; fileRef = 4D6DAAC5090CE00500F43C22 /* RevealOn.png */; };
@ -76,6 +77,8 @@
4D118E1808CB46B20033958F /* PrefsController.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = PrefsController.h; sourceTree = "<group>"; };
4D118E1908CB46B20033958F /* PrefsController.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = PrefsController.m; sourceTree = "<group>"; };
4D2784360905709500687951 /* Transmission.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; name = Transmission.icns; path = Images/Transmission.icns; sourceTree = "<group>"; };
4D364D9E091FBB2C00377D12 /* TorrentTableView.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = TorrentTableView.h; sourceTree = "<group>"; };
4D364D9F091FBB2C00377D12 /* TorrentTableView.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = TorrentTableView.m; sourceTree = "<group>"; };
4D3EA0A908AE13C600EA10C2 /* IOKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOKit.framework; path = /System/Library/Frameworks/IOKit.framework; sourceTree = "<absolute>"; };
4D6DAAC4090CE00500F43C22 /* RevealOff.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = RevealOff.png; path = Images/RevealOff.png; sourceTree = "<group>"; };
4D6DAAC5090CE00500F43C22 /* RevealOn.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = RevealOn.png; path = Images/RevealOn.png; sourceTree = "<group>"; };
@ -120,6 +123,8 @@
4DF0C5AA0899190500DD8943 /* Controller.h */,
4D118E1808CB46B20033958F /* PrefsController.h */,
4D118E1908CB46B20033958F /* PrefsController.m */,
4D364D9E091FBB2C00377D12 /* TorrentTableView.h */,
4D364D9F091FBB2C00377D12 /* TorrentTableView.m */,
);
name = Classes;
sourceTree = "<group>";
@ -293,6 +298,7 @@
4D096C12089FB4E20091B166 /* NameCell.m in Sources */,
4D096C13089FB4E20091B166 /* ProgressCell.m in Sources */,
4D118E1A08CB46B20033958F /* PrefsController.m in Sources */,
4D364DA0091FBB2C00377D12 /* TorrentTableView.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@ -50,25 +50,24 @@ static float widthForString( NSString * string, float fontSize )
return [string sizeWithAttributes: attributes].width;
}
static NSString * stringFittingInWidth( char * string, float width,
#define NS_ELLIPSIS [NSString stringWithUTF8String: "\xE2\x80\xA6"]
static NSString * stringFittingInWidth( NSString * oldString, float width,
float fontSize )
{
NSString * nsString = NULL;
char * foo = strdup( string );
int i;
NSString * newString = NULL;
unsigned i;
for( i = strlen( string ); i > 0; i-- )
for( i = 0; i < [oldString length]; i++ )
{
foo[i] = '\0';
nsString = [NSString stringWithFormat: @"%s%@",
foo, ( i - strlen( string ) ? [NSString
stringWithUTF8String:"\xE2\x80\xA6"] : @"" )];
newString = [NSString stringWithFormat: @"%@%@",
[oldString substringToIndex: [oldString length] - i],
i ? NS_ELLIPSIS : @""];
if( widthForString( nsString, fontSize ) <= width )
if( widthForString( newString, fontSize ) <= width )
{
break;
}
}
free( foo );
return nsString;
return newString;
}

View File

@ -27,6 +27,10 @@
#include <getopt.h>
#include <signal.h>
#include <transmission.h>
#ifdef SYS_BEOS
#include <kernel/OS.h>
#define usleep snooze
#endif
#define USAGE \
"Usage: %s [options] file.torrent [options]\n\n" \