From d2cc6ce7c6b10efbe36215884ff432c4490d7d25 Mon Sep 17 00:00:00 2001 From: Eric Petit Date: Thu, 12 Jan 2006 18:29:20 +0000 Subject: [PATCH] Update 2005-11-17 --- AUTHORS | 3 + Jamfile | 2 + libtransmission/Jamfile | 3 +- libtransmission/completion.c | 253 ++++++++++++++++++ libtransmission/completion.h | 64 +++++ libtransmission/fastresume.h | 36 +-- libtransmission/fdlimit.c | 18 +- libtransmission/inout.c | 39 +-- libtransmission/internal.h | 7 + libtransmission/peer.c | 127 ++++----- libtransmission/peermessages.h | 5 +- libtransmission/peerparse.h | 19 +- libtransmission/peerutils.h | 124 +++++---- libtransmission/tracker.c | 31 ++- libtransmission/transmission.c | 47 ++-- libtransmission/utils.h | 5 + macosx/Controller.h | 15 +- macosx/Controller.m | 152 ++++++++++- macosx/English.lproj/MainMenu.nib/classes.nib | 10 + macosx/English.lproj/MainMenu.nib/info.nib | 12 +- .../MainMenu.nib/keyedobjects.nib | Bin 35917 -> 38124 bytes macosx/Images/Info.tiff | Bin 19380 -> 0 bytes macosx/Images/Open.tiff | Bin 19220 -> 0 bytes macosx/Images/Progress.tiff | Bin 26220 -> 0 bytes macosx/Images/Remove.tiff | Bin 19496 -> 0 bytes macosx/Images/Resume.tiff | Bin 19220 -> 0 bytes macosx/Images/RevealOff.tiff | Bin 528 -> 0 bytes macosx/Images/RevealOn.tiff | Bin 478 -> 0 bytes macosx/Images/Stop.tiff | Bin 18956 -> 0 bytes macosx/NameCell.h | 13 +- macosx/NameCell.m | 245 +++++------------ macosx/TorrentTableView.h | 17 ++ macosx/TorrentTableView.m | 180 +++++++++++++ macosx/Transmission.xcodeproj/project.pbxproj | 6 + macosx/Utils.h | 23 +- transmissioncli.c | 4 + 36 files changed, 1001 insertions(+), 459 deletions(-) create mode 100644 libtransmission/completion.c create mode 100644 libtransmission/completion.h delete mode 100644 macosx/Images/Info.tiff delete mode 100644 macosx/Images/Open.tiff delete mode 100644 macosx/Images/Progress.tiff delete mode 100644 macosx/Images/Remove.tiff delete mode 100644 macosx/Images/Resume.tiff delete mode 100644 macosx/Images/RevealOff.tiff delete mode 100644 macosx/Images/RevealOn.tiff delete mode 100644 macosx/Images/Stop.tiff create mode 100644 macosx/TorrentTableView.h create mode 100644 macosx/TorrentTableView.m 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 232682329afb7eba5255e4764528dae0ef359a86..a1a3fa44cba418a5c8f22675bdd933dca20dd09e 100644 GIT binary patch literal 38124 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 PfBEIP|MLHDe&+uH6(%KO literal 35917 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 diff --git a/macosx/Images/Info.tiff b/macosx/Images/Info.tiff deleted file mode 100644 index d38cbf19a4bdae83d5b36ffa8495248dec413b0a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19380 zcmeG^30PCd)^n4P1Pq%3f*@QFfwn?I!WuDz9sUP`Y{jK5A-RD_HnRw5)qoUmtKd?R zwhG8oUrQ}&Yq9OKhbIzPObIzG_ z&z#I;NJ;`P0s!d10XzW|SVG{9Kgh%f3d@K|nM9N!9pZqH^>{M~QT1gKEZZ9RM7bdL zdOXoWU&eWkHqg*#B4YsLfOmgc3Op0>Eam0=TSTu73#1W^)7BTn-0>g%>IX!hB4F=3u!J z`6TaKpMBwtN<@>qUk*%SB`Kq@JW2c<6_z$5Uj@2sR0RHINx^CcRTzQ^!H;iVMi7y(N#327OEsljYJ_w@va3q3aw_6w^l0= zLGuI#hX#jmd_{s#k*~kMD8x53AeiMV6bpky!oXm5PDr4)1*-27E0pxe+N)E%#?UQ_ zhT$MLN+DIK(xAmLUI05ZAT%hL9U2(M>{|!9chjdx3NdL`v_yu<)lgO*hpi{>ThY74 z)PY)pyv9Um9ONdeBp7@*aQEiEW&AVgQF(qZ7` z4K^*9h_9!9KBJ)4q+$bjv|$|7=|xJ6iTj9FCBz{4xSp^JX_Ar#A4Zvs2rk56BqhN( zr;^OZM6oJ`j0pKkfl7_xJxua8bb-erICz9;T!~z*5y*vDLNt$<7KHu+j4cch`-(Wh z!tQAyCrAiq31MiEAi$r+!TdwQm`}Ae;V4oFwJ?xtAjj^>5+dMy(-TtkU0~1Bg$Xl! z$Z;p^tt74s?uR(`_d3>3dF;WaJYyF<%$|>@h=j&*R%%sJA`=h^nV1yAjj4vOtiOJs zMMADvp^^zSJc&$@ixDvjSm;NGed%ZA(0756nr-d09r5Hcmih=%eb@1t2y&@(>q9A)27M- zoSu9guN&J0*~+BoY)pkO=uh&1`w|`~CKtlRLV4~akBszK-w+Rci^V-UIVyc>N(`!$ z!qsOqB_lF1Aqw^IWilg_N-2g$DP&5m22(Ma(dp4>N@7AI)nAi8LY)*k5-yz2t&ekwmEBS(u0AV?{i;lj7ISOsRr< zN`5JDjU|>?Z}Ce>idU#4a};pVSqfLv!k9QcY^HDCe|wDt-+1y9;)|>bm7$F}Tz`Y? zbNhA&Q8IxXb_pr<6c*-#GL~pCS%gX@DC%J?z(FV&Y-kW|03N@u9L@xaF69No3gL2C)8L{QUiG{3!WB>r z7AqCxnsS9hkHJyk!If`AkuWIJV9_w9!OXCqXZ0VGEek_u#v$t0`~LrUPd}>Reqt~l z+@LWOe|JEJ9caJ+lqM8NVT|~zIZq$8j6nx_&2Y>y#uxMdQ_RVP^PmK}&0lcF>PI!B z>kQ9V|AzU>VBkLQ=U-*c8bCdx4-JPO+!TiN=9$BfTAP#Jo%JdOFbjW8A{Q%O>&|4J zZQ|;cmPUwXYSkJ{gdOig)_m!>HfjofKV;GX6+*jEFJa~Xvd;s)cZF2w5_h6+`^xrVS52U8a zB%hInCJs;X_%6r4Ws=vYfDtJPa9su8_v^_W^;s2*<;R9!vz~2Z3vR#7hF|#-6~g=; zMq+_fjWO{(ox!qD4gA4-Pn-UCpWw!xKBlJ=YZWG98CN_8>*HxP4Y^}qEeGS$kCI_V z9S3V@s-mIezG-I9GD=?+4I09uKKQ;X&w|*y{gK$$EhJ9u#e;YpBAqAkyKZ*fBvVPL z zFoZO}K^Q=dpGiQf25AE49*(=F+sN-@e`vx5S@Tw%C%A{{J_Pg!lRRNkvchN zrVv8sPfmWl4 z!{nF>##j8+1^5XeJ+VbMF^<7&5@fk3{P?r=C&M!@=`mU9uq(ZBapU*KB&l<;UKpxC zs!12*_QF{SG1v|()Fi0m(~}Z&;K?wYA>DxPMQom@P|b;uN^%XBvhG$M{>-Br4O`Mg zm{_2dYGBcPHrz_NKr zD@z+ID;qm2D=RzvVP)6d#P(SjbhQ9mI#dZIBOZWci;!)Ru5(azQrBK297<`15Em2} zzsOKIiW!wgH@6r8r=#AL2q2LSl{NsOAY>ASWJaUX&BzQ6th6Ol+(-JGMNAQl@|ac3 zrrK52ZH*k~X`i|~hZCSGIUYqDzap*k;%%Wi(4l_YLa*qR>7tm{4Vs|Q-(+0E?rbmJ z^R4#szhb>tHN5fB>b)mA?*9Gbec%1?OQv{s*;^ZT>~FhrFF5Yy+=B8on?5=4{ncM> z0f_{qr4TaFsAhqL2=4wPDNur09;3|I#Z`8=1iMp@cXB*)E~-jaM5Wma)d9E1Q*i-k zUV*LOKp{4a7R6))X|PL15eAmPyGMks(||!H`m_b%;O>#1ue0)kXH4Fd?|5#;RaeKB zosZTxPL0_ww<~-(YTB>XKV6uA{oLcl8H&dH$)CLU>Dqfj{}1w>q#gK&@Qb5&Yc^+J zeCP1KiD$BAS)SFhZ`HSL-F0=I{iegJ>izC(m{ayW8L{?=x@dCRlF?Cno{WC^)ejqs zR=u@m=l6l9%g*m_YF@DWOuR6>bNsRP+{;%lFS+LB<+^7>&Bep6qsRZUWE_gUyhMFU zajKd;WB<<&`169+oVs~0@Nd_5$X5R%K7N1g1oe#YRlBdZ-TK$O%(}N%mp099BC*Mf z%Mb7F0>O9F%HMvcXt_9dv1jLuUGnhz7aF=i*RgXCF2poHPPy=8#Q0^(@GrCN|M|t0 z2a+e7ca)3IRH;v2*uMQtbMgC+|Nh{~Pt&_VlJ=FCN$ct=ex5t^i_hq2=oXhrrCYpi zz4vbB`*g=!J6GP&PRtO_9&8cIr+e{4xd*Igd)`)d zfmf{44`DmoPMPOFy`MLN+1-5zBWuG0Kued1G&63%%p|S}L8oC-M}9M|Gy}ZUK&TDtLXDj!)ZffAc{x zZ+#PsL8nrS!*$6w$PY8GX~J}jSm7IYPv^7Oe%QvIaO79bCpFcDwIPY2Ug6ZMtG3c# z53m|ZMN;Si78Eei)yC_m9h9?0Q{SCaQNd{ebdt8tSLRtGI^;@GfOu4hxM1-U8d1P^!yDqmH)K+_= zq*tv-Q?9Gu-dMu|x@<=}Wl`x`-AG!iPGdzun@QStZ#dl_=`$-Vm0RYU*XR;+&AnqT z8u4SWYcW5|p;V`Ho@P$+tvjN8*Zcmb0tag7&8f@k?mwBo{=+Z5=w+n%)&;auTh^HJ zi#o;}*X87>kxd5<-40%PDQ_e&Yk$D_wl-oX)3q(5#*wT$n&(Ij_nKOg7Z8g+3K)B( zSe{&O!D5h$Np)N?W%tPy4oG{ow`C$QYu!V*S;Jd2Kt^l%`4?V-q?_j(d*1SL7TUkyl2lM^D6oh|1F;l-+rWVBMze z(;MoOwr9@!wjH#L{3u7(2q@?_Nw@3J9$hGI97~>lBX<4;e(8B9!3m9yHnnm3TJN=~0p;{;SI%2BI^Vtm z?X)mMcFpl+>nOU?CHvd2C0%CCa;UShTmAOgxh3xk0OCZ#zm__i#P{tOYg2awq;G0ocE^#Iowk#gWZhTsNPffQ8k+o~(SZSUB?~ z>nfgI{i%gZ8tmxQ@=LVh_;B*kq^ZkSMgn`=d$I9*kMF%?wdr_#E~SIcp>VE8>}~!$ zI8rGabIjbgW1^3n&)r#`#Rn&q-gR@=MNe21v6K?QkEe}$M2TCKllz0ay&L)LTpjYN z1LLG8*`lF%dGWoeB_Y*gb%=CJXno0HR+sC6YR$3;)q~fj--yt42ABgI%RQ(Z$xgQc zqr&DMe0y4u?sG~_G1rk_&zk8;7g;u(cZ&IFA)TLI%&0msKkg*`mAy00xK-Vp80XmK zMrI{1n$^zSTbb{!V~r%&t^=+7a(j?%Q_^I^i#v0^PQ*^BKvhywMeqVol)w1$5#C!V zBHFDE@5L|d2Q8^i9#swS>!QWfa{l=-8`|eD(cQ5>PYkXJaCj@VkeJR-z72sxPPD%f)JxFpVFi(pI*AB4u%wm6f| zW|XC5hj&!jAhnyvmGLPyQP);))V)KtcE7$D`zLTuOP#qC9Et($cA~v5;$=Ijt}zV> zF2dhVPFwNfrmqiLA$gx`mfiknW*NQAihN||h$((CFI7?y#3yzsi_K_N2D`P7U%gzs zBu6&IY?ZsxrsmiRhTFj($6oEoS@vDli{<7Yeez|{&Z>zz0J7*ofHKd+{Rqdb_OKi2 zM99J&A8m_op^;iDod8BAi zk@)S-m7_ZEO}~2T>zu5Mk=Hk-?>VeAi_gk`%M!g6zM)pisQU(#6|S)oo|G^3PpXMe ze;qIwBT95C5K$-^3G5gp$I|J=K0ZLFu>g;Q#Q;3`?5FW|jRjo(D!~SygS#9Y0JY#w z)@@f5M)Wb2m9Tfra?7UIZ=i)}(e9n^N6I z9>1AK*D>h!?Gwl?g=90JO+s`YjW_N5N_3=|FSedS57=BC8}&%J&i%o}xU4HUTR0mpEIIZ?R5Z?G)7jZMVoDN*0l=+uu&1P#$vSYj(PFOu*-H&&3 z0WNbHvSoqNhY0y+PYZd9`ObNgGb7QW7dF}iu0E4A`MaC19Qaf=Z|k{-`%j`MfN6fM Z{{L*@Ye2Om;+qhj(4e*Co-%~t^}i9E*Yp4Y diff --git a/macosx/Images/Open.tiff b/macosx/Images/Open.tiff deleted file mode 100644 index 16689c73211a870a63959aa273485b772ae4e415..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19220 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 V%<0~f-DU#@5nKS2{Irq%D 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 diff --git a/macosx/Images/Resume.tiff b/macosx/Images/Resume.tiff deleted file mode 100644 index 2c2500ede16e628429c369864e2ed24db2551f60..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19220 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 SLcVDbsR`DM_LLzEum1&fc!RG1 diff --git a/macosx/Images/RevealOff.tiff b/macosx/Images/RevealOff.tiff deleted file mode 100644 index 38caf2f92882b100c16e8eb437874033fde7ac0c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 528 zcmebEWzb?^VDxHWPzbP45s_vVa5yGlc*jVTqw(N_jTQ?S_{9a-9=mKTDLG;}LF)0M z#{xw@q1}xqHW;z+WHa-+CQO>hFe@iA#4r5Z$;U_k+t;6$?Uy{Yuts%>xT?_(k!>tC zGh7)rF1V@JwJ$p?MwRQ~2kCg#a4z;!ry~!CPiE)ZbUKAAo~e_8MaWo`^Kp`0w1dd& zyN}+M?2Xr-Fhk&wgQe*O`L7=`wV8E}32lrhjYLf6cX}TRt`FX3=BFvy@6LDV(o3eGr*Ut)cIV9I(+PI&xx@9zOY%hwH)r+Im+xkJ zFT1+*dVbQ(yW8%oXrDg)d6CiE{cYcM-it`tZF~Lv&TqvUKRfT1pk)6~KmQ!z_Z0tQ zrI6Lc@_U(w3di>S0p2X<9R*alnbzr5&tfpyu-IUyO+ghC10N#;12Y3KFd6uO1S1lg z3Cv~zivD0^hKjQSd2CQN6Ob*)0F(#nWnkc76ora|bcn&knLHWAVQfYxMkxlM2_W@M m42;rnHV2~&R4-75Q3J{RAcQzjAJ7&k`17RVbn-3pYAG~?4(C)HzPbREB{BLSe z`W0=X1#9*Rot@Q@y?MHB16w>d-?FBWZRVBlk9U|?nd1`PurkYGe& zGlAJGK+$82%usPwAdd~oW&*MW8G!OYy$lQ-jG|C+kPb1JIFl!%IE>9Gz$nE4Gy$ZZ m@f@QxoPCW^2C5e%qk&|85JDWN4`>S%a{XywU{DuefYJbHl8Au- diff --git a/macosx/Images/Stop.tiff b/macosx/Images/Stop.tiff deleted file mode 100644 index 86c8f1b1d8efe9f764d3d77d5dbe2eef443c640d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18956 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 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" \