diff --git a/AUTHORS b/AUTHORS index d3cd5c63d..d13a8b9cd 100644 --- a/AUTHORS +++ b/AUTHORS @@ -26,3 +26,6 @@ Jeremy Messenger Martin Stadtmueller + Icon tweaking + +John Blitch + + Contextual menu patch diff --git a/Jamfile b/Jamfile index 5269f698b..725876389 100644 --- a/Jamfile +++ b/Jamfile @@ -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 ; diff --git a/libtransmission/Jamfile b/libtransmission/Jamfile index d21df5c75..fa3fbcbbf 100644 --- a/libtransmission/Jamfile +++ b/libtransmission/Jamfile @@ -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__ ; diff --git a/libtransmission/completion.c b/libtransmission/completion.c new file mode 100644 index 000000000..2ac69a48a --- /dev/null +++ b/libtransmission/completion.c @@ -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; +} + diff --git a/libtransmission/completion.h b/libtransmission/completion.h new file mode 100644 index 000000000..4afee7e34 --- /dev/null +++ b/libtransmission/completion.h @@ -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 ); diff --git a/libtransmission/fastresume.h b/libtransmission/fastresume.h index 4c240b4a2..b54e10331 100644 --- a/libtransmission/fastresume.h +++ b/libtransmission/fastresume.h @@ -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 ); diff --git a/libtransmission/fdlimit.c b/libtransmission/fdlimit.c index 7cac63267..53e413721 100644 --- a/libtransmission/fdlimit.c +++ b/libtransmission/fdlimit.c @@ -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; } diff --git a/libtransmission/inout.c b/libtransmission/inout.c index 9deb71c1f..c6a0db579 100644 --- a/libtransmission/inout.c +++ b/libtransmission/inout.c @@ -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 */ diff --git a/libtransmission/internal.h b/libtransmission/internal.h index 86e265d24..2049e89b6 100644 --- a/libtransmission/internal.h +++ b/libtransmission/internal.h @@ -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]; }; diff --git a/libtransmission/peer.c b/libtransmission/peer.c index 87c4ab31b..b4b479863 100644 --- a/libtransmission/peer.c +++ b/libtransmission/peer.c @@ -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 ); } } diff --git a/libtransmission/peermessages.h b/libtransmission/peermessages.h index d0710aacd..066e573bb 100644 --- a/libtransmission/peermessages.h +++ b/libtransmission/peermessages.h @@ -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 ); diff --git a/libtransmission/peerparse.h b/libtransmission/peerparse.h index 76085aca1..96170f7dc 100644 --- a/libtransmission/peerparse.h +++ b/libtransmission/peerparse.h @@ -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; diff --git a/libtransmission/peerutils.h b/libtransmission/peerutils.h index e1d2c0d1f..74f180582 100644 --- a/libtransmission/peerutils.h +++ b/libtransmission/peerutils.h @@ -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; diff --git a/libtransmission/tracker.c b/libtransmission/tracker.c index 132e082b9..480e6121c 100644 --- a/libtransmission/tracker.c +++ b/libtransmission/tracker.c @@ -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; } diff --git a/libtransmission/transmission.c b/libtransmission/transmission.c index caaa59a92..d71e91f2f 100644 --- a/libtransmission/transmission.c +++ b/libtransmission/transmission.c @@ -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; diff --git a/libtransmission/utils.h b/libtransmission/utils.h index e99aafda1..047bec3ea 100644 --- a/libtransmission/utils.h +++ b/libtransmission/utils.h @@ -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 ) { diff --git a/macosx/Controller.h b/macosx/Controller.h index 43f1718ed..cb860a9a5 100644 --- a/macosx/Controller.h +++ b/macosx/Controller.h @@ -27,13 +27,15 @@ #include #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; diff --git a/macosx/Controller.m b/macosx/Controller.m index 06975d0b3..8dee298b5 100644 --- a/macosx/Controller.m +++ b/macosx/Controller.m @@ -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]]; diff --git a/macosx/English.lproj/MainMenu.nib/classes.nib b/macosx/English.lproj/MainMenu.nib/classes.nib index 481d84c75..6e0c54add 100644 --- a/macosx/English.lproj/MainMenu.nib/classes.nib +++ b/macosx/English.lproj/MainMenu.nib/classes.nib @@ -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; diff --git a/macosx/English.lproj/MainMenu.nib/info.nib b/macosx/English.lproj/MainMenu.nib/info.nib index 141b856cb..c032b1016 100644 --- a/macosx/English.lproj/MainMenu.nib/info.nib +++ b/macosx/English.lproj/MainMenu.nib/info.nib @@ -3,24 +3,24 @@ IBDocumentLocation - 204 84 361 432 0 0 1440 878 + 188 334 361 432 0 0 1280 832 IBEditorPositions 29 105 768 371 44 0 0 1280 832 + 456 + 174 512 147 87 0 0 1280 832 IBFramework Version - 439.0 + 443.0 IBOldestOS 3 IBOpenObjects + 456 21 - 29 - 273 - 343 IBSystem Version - 8C46 + 8F46 diff --git a/macosx/English.lproj/MainMenu.nib/keyedobjects.nib b/macosx/English.lproj/MainMenu.nib/keyedobjects.nib index 232682329..a1a3fa44c 100644 Binary files a/macosx/English.lproj/MainMenu.nib/keyedobjects.nib and b/macosx/English.lproj/MainMenu.nib/keyedobjects.nib differ diff --git a/macosx/Images/Info.tiff b/macosx/Images/Info.tiff deleted file mode 100644 index d38cbf19a..000000000 Binary files a/macosx/Images/Info.tiff and /dev/null differ diff --git a/macosx/Images/Open.tiff b/macosx/Images/Open.tiff deleted file mode 100644 index 16689c732..000000000 Binary files a/macosx/Images/Open.tiff and /dev/null differ diff --git a/macosx/Images/Progress.tiff b/macosx/Images/Progress.tiff deleted file mode 100644 index 1726c6748..000000000 Binary files a/macosx/Images/Progress.tiff and /dev/null differ diff --git a/macosx/Images/Remove.tiff b/macosx/Images/Remove.tiff deleted file mode 100644 index 0bab7024a..000000000 Binary files a/macosx/Images/Remove.tiff and /dev/null differ diff --git a/macosx/Images/Resume.tiff b/macosx/Images/Resume.tiff deleted file mode 100644 index 2c2500ede..000000000 Binary files a/macosx/Images/Resume.tiff and /dev/null differ diff --git a/macosx/Images/RevealOff.tiff b/macosx/Images/RevealOff.tiff deleted file mode 100644 index 38caf2f92..000000000 Binary files a/macosx/Images/RevealOff.tiff and /dev/null differ diff --git a/macosx/Images/RevealOn.tiff b/macosx/Images/RevealOn.tiff deleted file mode 100644 index 3c406a650..000000000 Binary files a/macosx/Images/RevealOn.tiff and /dev/null differ diff --git a/macosx/Images/Stop.tiff b/macosx/Images/Stop.tiff deleted file mode 100644 index 86c8f1b1d..000000000 Binary files a/macosx/Images/Stop.tiff and /dev/null differ diff --git a/macosx/NameCell.h b/macosx/NameCell.h index 5d4773e98..fafd61d77 100644 --- a/macosx/NameCell.h +++ b/macosx/NameCell.h @@ -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 diff --git a/macosx/NameCell.m b/macosx/NameCell.m index 1ef9f8989..39ee72cfb 100644 --- a/macosx/NameCell.m +++ b/macosx/NameCell.m @@ -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 diff --git a/macosx/TorrentTableView.h b/macosx/TorrentTableView.h new file mode 100644 index 000000000..d9af14948 --- /dev/null +++ b/macosx/TorrentTableView.h @@ -0,0 +1,17 @@ +#include +#include + +@class Controller; + +@interface TorrentTableView : NSTableView + +{ + IBOutlet Controller * fController; + + tr_stat_t * fStat; + NSPoint fClickPoint; +} + +- (void) updateUI: (tr_stat_t *) stat; + +@end diff --git a/macosx/TorrentTableView.m b/macosx/TorrentTableView.m new file mode 100644 index 000000000..8d0003043 --- /dev/null +++ b/macosx/TorrentTableView.m @@ -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 diff --git a/macosx/Transmission.xcodeproj/project.pbxproj b/macosx/Transmission.xcodeproj/project.pbxproj index 61535f1d6..18472f376 100644 --- a/macosx/Transmission.xcodeproj/project.pbxproj +++ b/macosx/Transmission.xcodeproj/project.pbxproj @@ -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 = ""; }; 4D118E1908CB46B20033958F /* PrefsController.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = PrefsController.m; sourceTree = ""; }; 4D2784360905709500687951 /* Transmission.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; name = Transmission.icns; path = Images/Transmission.icns; sourceTree = ""; }; + 4D364D9E091FBB2C00377D12 /* TorrentTableView.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = TorrentTableView.h; sourceTree = ""; }; + 4D364D9F091FBB2C00377D12 /* TorrentTableView.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = TorrentTableView.m; sourceTree = ""; }; 4D3EA0A908AE13C600EA10C2 /* IOKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOKit.framework; path = /System/Library/Frameworks/IOKit.framework; sourceTree = ""; }; 4D6DAAC4090CE00500F43C22 /* RevealOff.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = RevealOff.png; path = Images/RevealOff.png; sourceTree = ""; }; 4D6DAAC5090CE00500F43C22 /* RevealOn.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = RevealOn.png; path = Images/RevealOn.png; sourceTree = ""; }; @@ -120,6 +123,8 @@ 4DF0C5AA0899190500DD8943 /* Controller.h */, 4D118E1808CB46B20033958F /* PrefsController.h */, 4D118E1908CB46B20033958F /* PrefsController.m */, + 4D364D9E091FBB2C00377D12 /* TorrentTableView.h */, + 4D364D9F091FBB2C00377D12 /* TorrentTableView.m */, ); name = Classes; sourceTree = ""; @@ -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; }; diff --git a/macosx/Utils.h b/macosx/Utils.h index a7212df7d..3a4ddedd7 100644 --- a/macosx/Utils.h +++ b/macosx/Utils.h @@ -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; } diff --git a/transmissioncli.c b/transmissioncli.c index 5f01b197e..6eabb1e2f 100644 --- a/transmissioncli.c +++ b/transmissioncli.c @@ -27,6 +27,10 @@ #include #include #include +#ifdef SYS_BEOS +#include +#define usleep snooze +#endif #define USAGE \ "Usage: %s [options] file.torrent [options]\n\n" \