commit a6aa88477617ee0c570a3229a220682338834165 Author: Eric Petit Date: Thu Jan 12 17:43:21 2006 +0000 Import from 2005-10-26 diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 000000000..d3cd5c63d --- /dev/null +++ b/AUTHORS @@ -0,0 +1,28 @@ +Authors: + +Eric Petit + + About everything until now + + +Thanks to: + +vi@nwr.jp + + Free SHA1 implementation (sha1.[ch]) + +Mike Matas + + OS X toolbar icons + +Michael, Omar and Adrien + + Beta testing + +Various people + + Writing http://wiki.theory.org/BitTorrentSpecification + +Ahmad M. Afuni + + NetBSD patch + +Jeremy Messenger + + FreeBSD patch + +Martin Stadtmueller + + Icon tweaking diff --git a/Jamfile b/Jamfile new file mode 100644 index 000000000..85a3e37f0 --- /dev/null +++ b/Jamfile @@ -0,0 +1,46 @@ +SubDir TOP ; + +Main transmissioncli : transmissioncli.c ; +LinkLibraries transmissioncli : libtransmission.a ; +ObjectHdrs transmissioncli.c : $(TOP)/libtransmission ; + +if $(OS) = MACOSX +{ + OSXInfoPlist macosx/Info.plist : macosx/Info.plist.in ; + OSXBundle Transmission.app : libtransmission.a + macosx/Controller.h + macosx/Controller.m + macosx/English.lproj/InfoPlist.strings + macosx/English.lproj/MainMenu.nib + macosx/English.lproj/MainMenu.nib/classes.nib + macosx/English.lproj/MainMenu.nib/info.nib + macosx/English.lproj/MainMenu.nib/keyedobjects.nib + macosx/Images/Info.tiff + macosx/Images/Open.tiff + macosx/Images/Progress.tiff + macosx/Images/Remove.tiff + macosx/Images/Resume.tiff + macosx/Images/RevealOff.tiff + macosx/Images/RevealOn.tiff + macosx/Images/Stop.tiff + macosx/Images/Transmission.icns + macosx/Images/TransmissionDocument.icns + macosx/Info.plist + macosx/NameCell.h + macosx/NameCell.m + macosx/PrefsController.h + macosx/PrefsController.m + macosx/ProgressCell.h + macosx/ProgressCell.m + macosx/main.m + macosx/Transmission.xcodeproj/project.pbxproj + macosx/Transmission_Prefix.pch + macosx/Utils.h ; + + OSXPackage Transmission-$(VERSION_STRING)-OSX.dmg : + Transmission.app ; + NotFile package ; + Depends package : Transmission-$(VERSION_STRING)-OSX.dmg ; +} + +SubInclude TOP libtransmission ; diff --git a/Jamrules b/Jamrules new file mode 100644 index 000000000..1633988a5 --- /dev/null +++ b/Jamrules @@ -0,0 +1,85 @@ +include config.jam ; + +if ! $(DEFINES) +{ + Exit "Please run ./configure first." ; +} + +VERSION_MAJOR = 0 ; +VERSION_MINOR = 3 ; +VERSION_STRING = $(VERSION_MAJOR).$(VERSION_MINOR) ; + +DEFINES += VERSION_MAJOR=$(VERSION_MAJOR) + VERSION_MINOR=$(VERSION_MINOR) + VERSION_STRING=\\\"$(VERSION_STRING)\\\" ; +CCFLAGS = -g -Wall -W ; +OPTIM = -O3 ; +RM = rm -Rf ; + +if $(OS) = MACOSX +{ + # Build universal binaries + CCFLAGS += -isysroot /Developer/SDKs/MacOSX10.4u.sdk + -arch ppc -arch i386 ; + LINKFLAGS += -Wl,-syslibroot,/Developer/SDKs/MacOSX10.4u.sdk + -arch ppc -arch i386 ; + + # Use libtool to build static libraries (ar does not handle + # universal binaries) + RANLIB = ; + NOARUPDATE = 1 ; + actions Archive + { + libtool -static $(>) -o $(<) ; + } + + rule OSXInfoPlist + { + Depends $(1) : $(2) ; + Clean clean : $(1) ; + } + actions OSXInfoPlist + { + sed "s/%%VERSION%%/$(VERSION_STRING)/" < $(2) > $(1) + } + + rule OSXBundle + { + Depends exe : $(1) ; + Depends $(1) : $(2) ; + Clean clean : $(1) macosx/build ; + } + actions OSXBundle + { + $(RM) $(1) && ( cd macosx && xcodebuild ) && \ + mv macosx/build/Debug/Transmission.app $(1) + } + + rule OSXPackage + { + Depends $(1) : $(2) ; + Clean clean : $(1) ; + DoOSXPackage $(1) ; + } + actions DoOSXPackage + { + TMP="Transmission $(VERSION_STRING)" + rm -f $(1) "$TMP" && mkdir "$TMP" && + mkdir "$TMP/Transmission.app" && + ditto Transmission.app "$TMP/Transmission.app" && + ditto AUTHORS "$TMP/AUTHORS.txt" && + ditto LICENSE "$TMP/LICENSE.txt" && + ditto NEWS "$TMP/NEWS.txt" && + ( echo "[InternetShortcut]"; \ + echo "URL=http://transmission.m0k.org/" ) > \ + "$TMP/Homepage.url" && + ( echo "[InternetShortcut]"; \ + echo "URL=http://transmission.m0k.org/forum/" ) > \ + "$TMP/Forums.url" && + ( echo "[InternetShortcut]"; \ + echo "URL=http://transmission.m0k.org/contribute.php" ) > \ + "$TMP/Contribute.url" && + hdiutil create -format UDZO -srcfolder "$TMP" $(1) && + rm -rf "$TMP" + } +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 000000000..848d253fe --- /dev/null +++ b/LICENSE @@ -0,0 +1,23 @@ +The Transmission binaries and source code are distributed under the MIT +license. + +----- +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 SIMON TATHAM 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. +----- diff --git a/NEWS b/NEWS new file mode 100644 index 000000000..2cb4e818f --- /dev/null +++ b/NEWS @@ -0,0 +1,22 @@ +NEWS file for Transmission + +0.3 (2005/10/19) + - Fixed "Sometimes sends incorrect messages and looses peers" + - Fixed "Crashes with many torrents or torrents with many files" + - Enhancements in the "End game" mode + - Is nicer to the trackers + - Asks for the rarest pieces first + - OS X: Universal binary for PPC and x86 + - OS X: Fixed "Progress increases every time I pause then resume" + - OS X: Fixed "Sometimes crashes at exit" + - OS X: Cleaner icon + - OS X: Show all sizes in human readable form + - OS X: Keep downloading in the background when the window is closed + - Miscellaneus bugfixes and internal enhancements + +0.2 (2005/09/22) + - Bugfixes + - OS X: Users can now choose where the downloads are sent + +0.1 (2005/09/15) + - First version diff --git a/README b/README new file mode 100644 index 000000000..80340bd06 --- /dev/null +++ b/README @@ -0,0 +1,6 @@ +README for Transmission +======================= + +This is Transmission, a free BitTorrent client. For more information +(including build instructions), please consult the website: +http://transmission.m0k.org/ diff --git a/configure b/configure new file mode 100755 index 000000000..82b8a42fc --- /dev/null +++ b/configure @@ -0,0 +1,85 @@ +#! /bin/sh + +# For > 2 GB files +DEFINES="_FILE_OFFSET_BITS=64 _LARGEFILE_SOURCE" + +# For asprintf +DEFINES="$DEFINES _GNU_SOURCE" + +# System-specific flags +SYSTEM=`uname -s` +case $SYSTEM in + BeOS) + DEFINES="$DEFINES SYS_BEOS" + + RELEASE=`uname -r` + case $RELEASE in + 6.0|5.0.4) # Zeta or R5 / BONE beta 7 + SYSTEM="$SYSTEM / BONE" + LINKLIBS="$LINKLIBS -lbind -lsocket" + ;; + 5.0*) # R5 / net_server + SYSTEM="$SYSTEM / net_server" + DEFINES="$DEFINES BEOS_NETSERVER" + LINKLIBS="$LINKLIBS -lnet" + ;; + *) + echo "Unsupported BeOS version" + exit 1 ;; + esac + ;; + + Darwin) + DEFINES="$DEFINES SYS_DARWIN" + LINKLIBS="$LINKLIBS -lpthread" + ;; + + FreeBSD) + DEFINES="$DEFINES SYS_FREEBSD" + LINKLIBS="$LINKLIBS -pthread" + ;; + + NetBSD) + DEFINES="$DEFINES SYS_NETBSD" + LINKLIBS="$LINKLIBS -lpthread" + ;; + + Linux) + DEFINES="$DEFINES SYS_LINUX" + LINKLIBS="$LINKLIBS -lpthread" + ;; + + *) + echo "Unsupported operating system" + exit 1 ;; +esac +echo "System: $SYSTEM" + +# Check for OpenSSL +cat > testconf.c << EOF +#include +#include +int main() +{ + SHA1( 0, 0, 0 ); +} +EOF +if cc -o testconf testconf.c -lcrypto > /dev/null 2>&1 +then + echo "OpenSSL: yes" + DEFINES="$DEFINES HAVE_OPENSSL" + LINKLIBS="$LINKLIBS -lcrypto" +else + echo "OpenSSL: no, using built-in SHA1 implementation" +fi +rm -f testconf.c testconf + +# Generate config.jam +rm -f config.jam +cat << EOF > config.jam +DEFINES = $DEFINES ; +LINKLIBS = $LINKLIBS ; +EOF + +echo +echo "To build Transmission, run 'jam'." diff --git a/libtransmission/Jamfile b/libtransmission/Jamfile new file mode 100644 index 000000000..d21df5c75 --- /dev/null +++ b/libtransmission/Jamfile @@ -0,0 +1,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 ; + +Library libtransmission.a : $(LIBTRANSMISSION_SRC) ; +ObjectDefines $(LIBTRANSMISSION_SRC) : __TRANSMISSION__ ; diff --git a/libtransmission/bencode.c b/libtransmission/bencode.c new file mode 100644 index 000000000..3ee15ae4c --- /dev/null +++ b/libtransmission/bencode.c @@ -0,0 +1,205 @@ +/****************************************************************************** + * 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" + +#define LIST_SIZE 20 + +int _tr_bencLoad( char * buf, benc_val_t * val, char ** end ) +{ + char * p, * foo; + + if( !end ) + { + /* So we only have to check once */ + end = &foo; + } + + val->begin = buf; + + if( buf[0] == 'i' ) + { + /* Integer: i1242e */ + val->type = TYPE_INT; + val->val.i = strtoll( &buf[1], &p, 10 ); + + if( p == &buf[1] || p[0] != 'e' ) + { + return 1; + } + + val->end = p + 1; + } + else if( buf[0] == 'l' || buf[0] == 'd' ) + { + /* List: le + Dict: de + A dictionary is just a special kind of list with an even + count of items, and where even items are strings. */ + char * cur; + char is_dict; + char str_expected; + + is_dict = ( buf[0] == 'd' ); + val->type = is_dict ? TYPE_DICT : TYPE_LIST; + val->val.l.alloc = LIST_SIZE; + val->val.l.count = 0; + val->val.l.vals = malloc( LIST_SIZE * sizeof( benc_val_t ) ); + cur = &buf[1]; + str_expected = 1; + while( cur[0] != 'e' ) + { + if( val->val.l.count == val->val.l.alloc ) + { + /* We need a bigger boat */ + val->val.l.alloc += LIST_SIZE; + val->val.l.vals = realloc( val->val.l.vals, + val->val.l.alloc * sizeof( benc_val_t ) ); + } + if( tr_bencLoad( cur, &val->val.l.vals[val->val.l.count], &p ) ) + { + return 1; + } + if( is_dict && str_expected && + val->val.l.vals[val->val.l.count].type != TYPE_STR ) + { + return 1; + } + str_expected = !str_expected; + + val->val.l.count++; + cur = p; + } + + if( is_dict && ( val->val.l.count & 1 ) ) + { + return 1; + } + + val->end = cur + 1; + } + else + { + /* String: 12:whateverword */ + val->type = TYPE_STR; + val->val.s.i = strtol( buf, &p, 10 ); + + if( p == buf || p[0] != ':' ) + { + return 1; + } + + val->val.s.s = malloc( val->val.s.i + 1 ); + val->val.s.s[val->val.s.i] = 0; + memcpy( val->val.s.s, p + 1, val->val.s.i ); + + val->end = p + 1 + val->val.s.i; + } + + *end = val->end; + + return 0; +} + +static void __bencPrint( benc_val_t * val, int space ) +{ + int i; + + for( i = 0; i < space; i++ ) + { + fprintf( stderr, " " ); + } + + switch( val->type ) + { + case TYPE_INT: + fprintf( stderr, "int: %lld\n", val->val.i ); + break; + + case TYPE_STR: + fprintf( stderr, "%s\n", val->val.s.s ); + break; + + case TYPE_LIST: + fprintf( stderr, "list\n" ); + for( i = 0; i < val->val.l.count; i++ ) + __bencPrint( &val->val.l.vals[i], space + 1 ); + break; + + case TYPE_DICT: + fprintf( stderr, "dict\n" ); + for( i = 0; i < val->val.l.count; i++ ) + __bencPrint( &val->val.l.vals[i], space + 1 ); + break; + } +} + +void tr_bencPrint( benc_val_t * val ) +{ + __bencPrint( val, 0 ); +} + +void tr_bencFree( benc_val_t * val ) +{ + int i; + + switch( val->type ) + { + case TYPE_INT: + break; + + case TYPE_STR: + if( val->val.s.s ) + { + free( val->val.s.s ); + } + break; + + case TYPE_LIST: + case TYPE_DICT: + for( i = 0; i < val->val.l.count; i++ ) + { + tr_bencFree( &val->val.l.vals[i] ); + } + free( val->val.l.vals ); + break; + } +} + +benc_val_t * tr_bencDictFind( benc_val_t * val, char * key ) +{ + int i; + if( val->type != TYPE_DICT ) + { + return NULL; + } + + for( i = 0; i < val->val.l.count; i += 2 ) + { + if( !strcmp( val->val.l.vals[i].val.s.s, key ) ) + { + return &val->val.l.vals[i+1]; + } + } + + return NULL; +} diff --git a/libtransmission/bencode.h b/libtransmission/bencode.h new file mode 100644 index 000000000..32e053658 --- /dev/null +++ b/libtransmission/bencode.h @@ -0,0 +1,58 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef TR_BENCODE_H +#define TR_BENCODE_H 1 + +typedef struct benc_val_s +{ + char * begin; + char * end; +#define TYPE_INT 1 +#define TYPE_STR 2 +#define TYPE_LIST 4 +#define TYPE_DICT 8 + char type; + union + { + int64_t i; + struct + { + int i; + char * s; + } s; + struct + { + int alloc; + int count; + struct benc_val_s * vals; + } l; + } val; +} benc_val_t; + +#define tr_bencLoad(b,v,e) _tr_bencLoad((char*)(b),v,(char**)e) +int _tr_bencLoad( char * buf, benc_val_t * val, char ** end ); +void tr_bencPrint( benc_val_t * val ); +void tr_bencFree( benc_val_t * val ); +benc_val_t * tr_bencDictFind( benc_val_t * val, char * key ); + +#endif diff --git a/libtransmission/clients.c b/libtransmission/clients.c new file mode 100644 index 000000000..6f544e52e --- /dev/null +++ b/libtransmission/clients.c @@ -0,0 +1,88 @@ +/****************************************************************************** + * 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" + +char * tr_clientForId( uint8_t * id ) +{ + char * ret = NULL; + + if( id[0] == '-' && id[7] == '-' ) + { + if( !memcmp( &id[1], "TR", 2 ) ) + { + asprintf( &ret, "Transmission %d.%d", + ( id[3] - '0' ) * 10 + ( id[4] - '0' ), + ( id[5] - '0' ) * 10 + ( id[6] - '0' ) ); + } + else if( !memcmp( &id[1], "AZ", 2 ) ) + { + asprintf( &ret, "Azureus %c.%c.%c.%c", + id[3], id[4], id[5], id[6] ); + } + else if( !memcmp( &id[1], "TS", 2 ) ) + { + asprintf( &ret, "TorrentStorm (%c%c%c%c)", + id[3], id[4], id[5], id[6] ); + } + else if( !memcmp( &id[1], "BC", 2 ) ) + { + asprintf( &ret, "BitComet %d.%c%c", + ( id[3] - '0' ) * 10 + ( id[4] - '0' ), + id[5], id[6] ); + } + else if( !memcmp( &id[1], "SZ", 2 ) ) + { + asprintf( &ret, "Shareaza %c.%c.%c.%c", + id[3], id[4], id[5], id[6] ); + } + } + else if( !memcmp( &id[4], "----", 4 ) ) + { + if( id[0] == 'T' ) + { + asprintf( &ret, "BitTornado (%c%c%c)", id[1], id[2], id[3] ); + } + else if( id[0] == 'A' ) + { + asprintf( &ret, "ABC (%c%c%c)", id[1], id[2], id[3] ); + } + } + else if( id[0] == 'M' && id[2] == '-' && + id[4] == '-' && id[6] == '-' && + id[7] == '-' ) + { + asprintf( &ret, "BitTorrent %c.%c.%c", id[1], id[3], id[5] ); + } + else if( !memcmp( id, "exbc", 4 ) ) + { + asprintf( &ret, "BitComet %d.%02d", id[4], id[5] ); + } + + if( !ret ) + { + asprintf( &ret, "Unknown client (%c%c%c%c%c%c%c%c)", + id[0], id[1], id[2], id[3], id[4], id[5], id[6], id[7] ); + } + + return ret; +} diff --git a/libtransmission/clients.h b/libtransmission/clients.h new file mode 100644 index 000000000..af6e9afad --- /dev/null +++ b/libtransmission/clients.h @@ -0,0 +1,23 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +char * tr_clientForId( uint8_t * ); diff --git a/libtransmission/fastresume.h b/libtransmission/fastresume.h new file mode 100644 index 000000000..8b869fa3f --- /dev/null +++ b/libtransmission/fastresume.h @@ -0,0 +1,288 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +/*********************************************************************** + * Fast resume + *********************************************************************** + * Format of the resume file: + * - 4 bytes: format version (currently 0) + * - 4 bytes * number of files: mtimes of files + * - 1 bit * number of blocks: whether we have the block or not + * - 4 bytes * number of pieces (byte aligned): the pieces that have + * been completed or started in each slot + * + * The resume file is located in ~/.transmission/. Its name is + * "resume.". + * + * All values are stored in the native endianness. Moving a + * libtransmission resume file from an architecture to another will not + * work, although it will not hurt either (the mtimes will be wrong, + * so the files will be scanned). + **********************************************************************/ + +static char * fastResumeFolderName() +{ + char * ret; + asprintf( &ret, "%s/.transmission", getenv( "HOME" ) ); + return ret; +} + +static char * fastResumeFileName( tr_io_t * io ) +{ + char * ret, * p; + int i; + + p = fastResumeFolderName(); + asprintf( &ret, "%s/resume.%40d", p, 0 ); + free( p ); + + p = &ret[ strlen( ret ) - 2 * SHA_DIGEST_LENGTH ]; + for( i = 0; i < SHA_DIGEST_LENGTH; i++ ) + { + sprintf( p, "%02x", io->tor->info.hash[i] ); + p += 2; + } + + return ret; +} + +static int fastResumeMTimes( tr_io_t * io, int * tab ) +{ + tr_torrent_t * tor = io->tor; + tr_info_t * inf = &tor->info; + + int i; + char * path; + struct stat sb; + + for( i = 0; i < inf->fileCount; i++ ) + { + asprintf( &path, "%s/%s", tor->destination, inf->files[i].name ); + if( stat( path, &sb ) ) + { + tr_err( "Could not stat '%s'", path ); + free( path ); + return 1; + } + if( ( sb.st_mode & S_IFMT ) != S_IFREG ) + { + tr_err( "Wrong st_mode for '%s'", path ); + free( path ); + return 1; + } + free( path ); + +#ifdef SYS_DARWIN + tab[i] = ( sb.st_mtimespec.tv_sec & 0x7FFFFFFF ); +#else + tab[i] = ( sb.st_mtime & 0x7FFFFFFF ); +#endif + } + + return 0; +} + +static void fastResumeSave( tr_io_t * io ) +{ + tr_torrent_t * tor = io->tor; + tr_info_t * inf = &tor->info; + + FILE * file; + int version = 0; + char * path; + int * fileMTimes; + int i; + uint8_t * blockBitfield; + + /* Get file sizes */ + fileMTimes = malloc( inf->fileCount * 4 ); + if( fastResumeMTimes( io, fileMTimes ) ) + { + free( fileMTimes ); + return; + } + + /* Create folder if missing */ + path = fastResumeFolderName(); + mkdir( path, 0755 ); + free( path ); + + /* Create/overwrite the resume file */ + path = fastResumeFileName( io ); + if( !( file = fopen( path, "w" ) ) ) + { + tr_err( "Could not open '%s' for writing", path ); + free( fileMTimes ); + free( path ); + return; + } + free( path ); + + /* Write format version */ + fwrite( &version, 4, 1, file ); + + /* Write file mtimes */ + fwrite( fileMTimes, 4, inf->fileCount, file ); + 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 ); + } + } + fwrite( blockBitfield, ( tor->blockCount + 7 ) / 8, 1, file ); + free( blockBitfield ); + + /* Write the 'slotPiece' table */ + fwrite( io->slotPiece, 4, inf->pieceCount, file ); + + fclose( file ); +} + +static int fastResumeLoad( tr_io_t * io ) +{ + tr_torrent_t * tor = io->tor; + tr_info_t * inf = &tor->info; + + FILE * file; + int version = 0; + char * path; + int * fileMTimes1, * fileMTimes2; + int i, j; + uint8_t * blockBitfield; + + int size; + + /* Open resume file */ + path = fastResumeFileName( io ); + if( !( file = fopen( path, "r" ) ) ) + { + tr_inf( "Could not open '%s' for reading", path ); + free( path ); + return 1; + } + free( path ); + + /* Check the size */ + size = 4 + 4 * inf->fileCount + 4 * inf->pieceCount + + ( tor->blockCount + 7 ) / 8; + fseek( file, 0, SEEK_END ); + if( ftell( file ) != size ) + { + tr_inf( "Wrong size for resume file (%d bytes, %d expected)", + ftell( file ), size ); + fclose( file ); + return 1; + } + fseek( file, 0, SEEK_SET ); + + /* Check format version */ + fread( &version, 4, 1, file ); + if( version != 0 ) + { + tr_inf( "Resume file has version %d, not supported", + version ); + fclose( file ); + return 1; + } + + /* Compare file mtimes */ + fileMTimes1 = malloc( inf->fileCount * 4 ); + if( fastResumeMTimes( io, fileMTimes1 ) ) + { + free( fileMTimes1 ); + fclose( file ); + return 1; + } + fileMTimes2 = malloc( inf->fileCount * 4 ); + fread( fileMTimes2, 4, inf->fileCount, file ); + if( memcmp( fileMTimes1, fileMTimes2, inf->fileCount * 4 ) ) + { + tr_inf( "File mtimes don't match" ); + free( fileMTimes1 ); + free( fileMTimes2 ); + fclose( file ); + return 1; + } + free( fileMTimes1 ); + free( fileMTimes2 ); + + /* 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)++; + } + } + free( blockBitfield ); + + /* Load the 'slotPiece' table */ + fread( io->slotPiece, 4, inf->pieceCount, file ); + + fclose( file ); + + /* Update io->pieceSlot, io->slotsUsed, and tor->bitfield */ + io->slotsUsed = 0; + for( i = 0; i < inf->pieceCount; i++ ) + { + io->pieceSlot[i] = -1; + for( j = 0; j < inf->pieceCount; j++ ) + { + if( io->slotPiece[j] == i ) + { + // tr_dbg( "Has piece %d in slot %d", i, j ); + io->pieceSlot[i] = j; + io->slotsUsed = MAX( io->slotsUsed, j + 1 ); + 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 ); + + tr_inf( "Fast resuming successful" ); + + return 0; +} diff --git a/libtransmission/fdlimit.c b/libtransmission/fdlimit.c new file mode 100644 index 000000000..7cac63267 --- /dev/null +++ b/libtransmission/fdlimit.c @@ -0,0 +1,279 @@ +/****************************************************************************** + * 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" + +#define TR_MAX_OPEN_FILES 16 /* That is, real files, not sockets */ +#define TR_RESERVED_FDS 16 /* Number of sockets reserved for + connections to trackers */ + +typedef struct tr_openFile_s +{ + char path[MAX_PATH_LENGTH]; + FILE * file; + +#define STATUS_INVALID 1 +#define STATUS_UNUSED 2 +#define STATUS_USED 4 + int status; + + uint64_t date; + +} tr_openFile_t; + +struct tr_fd_s +{ + tr_lock_t lock; + + int reserved; + + int normal; + int normalMax; + + tr_openFile_t open[TR_MAX_OPEN_FILES]; +}; + +/*********************************************************************** + * tr_fdInit + **********************************************************************/ +tr_fd_t * tr_fdInit() +{ + tr_fd_t * f; + int i, j, s[4096]; + + f = calloc( sizeof( tr_fd_t ), 1 ); + + /* Init lock */ + tr_lockInit( &f->lock ); + + /* Detect the maximum number of open files or sockets */ + for( i = 0; i < 4096; i++ ) + { + if( ( s[i] = socket( AF_INET, SOCK_STREAM, 0 ) ) < 0 ) + { + break; + } + } + for( j = 0; j < i; j++ ) + { + tr_netClose( s[j] ); + } + + tr_dbg( "%d usable file descriptors", i ); + + f->reserved = 0; + f->normal = 0; + + f->normalMax = i - TR_RESERVED_FDS - 10; + /* To be safe, in case the UI needs to write a preferences file + or something */ + + for( i = 0; i < TR_MAX_OPEN_FILES; i++ ) + { + f->open[i].status = STATUS_INVALID; + } + + return f; +} + +/*********************************************************************** + * tr_fdFileOpen + **********************************************************************/ +FILE * tr_fdFileOpen( tr_fd_t * f, char * path ) +{ + int i, winner; + uint64_t date; + + tr_lockLock( f->lock ); + + /* Is it already open? */ + for( i = 0; i < TR_MAX_OPEN_FILES; i++ ) + { + if( f->open[i].status > STATUS_INVALID && + !strcmp( path, f->open[i].path ) ) + { + winner = i; + goto done; + } + } + + /* Can we open one more file? */ + for( i = 0; i < TR_MAX_OPEN_FILES; i++ ) + { + if( f->open[i].status & STATUS_INVALID ) + { + winner = i; + goto open; + } + } + + for( ;; ) + { + /* Close the oldest currently unused file */ + date = tr_date() + 1; + winner = -1; + + for( i = 0; i < TR_MAX_OPEN_FILES; i++ ) + { + if( f->open[i].status & STATUS_USED ) + { + continue; + } + if( f->open[i].date < date ) + { + winner = i; + date = f->open[i].date; + } + } + + if( winner >= 0 ) + { + tr_dbg( "Closing %s", f->open[winner].path ); + fclose( f->open[winner].file ); + goto open; + } + + /* All used! Wait a bit and try again */ + tr_lockUnlock( f->lock ); + tr_wait( 10 ); + tr_lockLock( f->lock ); + } + +open: + tr_dbg( "Opening %s", path ); + snprintf( f->open[winner].path, MAX_PATH_LENGTH, "%s", path ); + f->open[winner].file = fopen( path, "r+" ); + +done: + f->open[winner].status = STATUS_USED; + f->open[winner].date = tr_date(); + tr_lockUnlock( f->lock ); + + return f->open[winner].file; +} + +/*********************************************************************** + * tr_fdFileRelease + **********************************************************************/ +void tr_fdFileRelease( tr_fd_t * f, FILE * file ) +{ + int i; + tr_lockLock( f->lock ); + + for( i = 0; i < TR_MAX_OPEN_FILES; i++ ) + { + if( f->open[i].file == file ) + { + f->open[i].status = STATUS_UNUSED; + break; + } + } + + tr_lockUnlock( f->lock ); +} + +/*********************************************************************** + * tr_fdFileClose + **********************************************************************/ +void tr_fdFileClose( tr_fd_t * f, char * path ) +{ + int i; + + tr_lockLock( f->lock ); + + /* Is it already open? */ + for( i = 0; i < TR_MAX_OPEN_FILES; i++ ) + { + if( f->open[i].status & STATUS_INVALID ) + { + continue; + } + if( !strcmp( path, f->open[i].path ) ) + { + tr_dbg( "Closing %s", path ); + fclose( f->open[i].file ); + f->open[i].status = STATUS_INVALID; + break; + } + } + + tr_lockUnlock( f->lock ); +} + +int tr_fdSocketWillCreate( tr_fd_t * f, int reserved ) +{ + int ret; + + tr_lockLock( f->lock ); + + if( reserved ) + { + if( f->reserved < TR_RESERVED_FDS ) + { + ret = 0; + (f->reserved)++; + } + else + { + ret = 1; + } + } + else + { + if( f->normal < f->normalMax ) + { + ret = 0; + (f->normal)++; + } + else + { + ret = 1; + } + } + + tr_lockUnlock( f->lock ); + + return ret; +} + +void tr_fdSocketClosed( tr_fd_t * f, int reserved ) +{ + tr_lockLock( f->lock ); + + if( reserved ) + { + (f->reserved)--; + } + else + { + (f->normal)--; + } + + tr_lockUnlock( f->lock ); +} + +void tr_fdClose( tr_fd_t * f ) +{ + tr_lockClose( f->lock ); + free( f ); +} + diff --git a/libtransmission/fdlimit.h b/libtransmission/fdlimit.h new file mode 100644 index 000000000..435c5afc3 --- /dev/null +++ b/libtransmission/fdlimit.h @@ -0,0 +1,34 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +typedef struct tr_fd_s tr_fd_t; + +tr_fd_t * tr_fdInit(); + +FILE * tr_fdFileOpen ( tr_fd_t *, char * ); +void tr_fdFileRelease ( tr_fd_t *, FILE * ); +void tr_fdFileClose ( tr_fd_t *, char * ); + +int tr_fdSocketWillCreate ( tr_fd_t *, int ); +void tr_fdSocketClosed ( tr_fd_t *, int ); + +void tr_fdClose ( tr_fd_t * ); diff --git a/libtransmission/inout.c b/libtransmission/inout.c new file mode 100644 index 000000000..9deb71c1f --- /dev/null +++ b/libtransmission/inout.c @@ -0,0 +1,601 @@ +/****************************************************************************** + * 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" + +#ifdef SYS_BEOS +# define fseeko _fseek +#endif + +struct tr_io_s +{ + tr_torrent_t * tor; + + /* Position of pieces + -1 = we haven't started to download this piece yet + n = we have started or completed the piece in slot n */ + int * pieceSlot; + + /* Pieces in slot + -1 = unused slot + n = piece n */ + int * slotPiece; + + int slotsUsed; +}; + +#include "fastresume.h" + +/*********************************************************************** + * Local prototypes + **********************************************************************/ +static int createFiles( tr_io_t * ); +static int checkFiles( tr_io_t * ); +static void closeFiles( tr_io_t * ); +static int readOrWriteBytes( tr_io_t *, uint64_t, int, uint8_t *, int ); +static int readOrWriteSlot( tr_io_t * io, int slot, uint8_t * buf, + int * size, int write ); +static void findSlotForPiece( tr_io_t *, int ); + +#define readBytes(io,o,s,b) readOrWriteBytes(io,o,s,b,0) +#define writeBytes(io,o,s,b) readOrWriteBytes(io,o,s,b,1) + +#define readSlot(io,sl,b,s) readOrWriteSlot(io,sl,b,s,0) +#define writeSlot(io,sl,b,s) readOrWriteSlot(io,sl,b,s,1) + +/*********************************************************************** + * tr_ioInit + *********************************************************************** + * Open all files we are going to write to + **********************************************************************/ +tr_io_t * tr_ioInit( tr_torrent_t * tor ) +{ + tr_io_t * io; + + io = malloc( sizeof( tr_io_t ) ); + io->tor = tor; + + if( createFiles( io ) || checkFiles( io ) ) + { + free( io ); + return NULL; + } + + return io; +} + +/*********************************************************************** + * tr_ioRead + *********************************************************************** + * + **********************************************************************/ +int tr_ioRead( tr_io_t * io, int index, int begin, int length, + uint8_t * buf ) +{ + uint64_t offset; + tr_info_t * inf = &io->tor->info; + + offset = (uint64_t) io->pieceSlot[index] * + (uint64_t) inf->pieceSize + (uint64_t) begin; + + return readBytes( io, offset, length, buf ); +} + +/*********************************************************************** + * tr_ioWrite + *********************************************************************** + * + **********************************************************************/ +int tr_ioWrite( tr_io_t * io, int index, int begin, int length, + uint8_t * buf ) +{ + tr_torrent_t * tor = io->tor; + tr_info_t * inf = &io->tor->info; + uint64_t offset; + int i; + uint8_t hash[SHA_DIGEST_LENGTH]; + uint8_t * pieceBuf; + int pieceSize; + int startBlock, endBlock; + + if( io->pieceSlot[index] < 0 ) + { + findSlotForPiece( io, index ); + tr_inf( "Piece %d: starting in slot %d", index, + io->pieceSlot[index] ); + } + + offset = (uint64_t) io->pieceSlot[index] * + (uint64_t) inf->pieceSize + (uint64_t) begin; + + if( writeBytes( io, offset, length, buf ) ) + { + return 1; + } + + startBlock = tr_pieceStartBlock( index ); + endBlock = startBlock + tr_pieceCountBlocks( index ); + for( i = startBlock; i < endBlock; i++ ) + { + if( tor->blockHave[i] >= 0 ) + { + /* The piece is not complete */ + return 0; + } + } + + /* The piece is complete, check the hash */ + pieceSize = tr_pieceSize( index ); + pieceBuf = malloc( pieceSize ); + readBytes( io, (uint64_t) io->pieceSlot[index] * + (uint64_t) inf->pieceSize, pieceSize, pieceBuf ); + SHA1( pieceBuf, pieceSize, hash ); + free( pieceBuf ); + + if( memcmp( hash, &inf->pieces[20*index], SHA_DIGEST_LENGTH ) ) + { + tr_inf( "Piece %d (slot %d): hash FAILED", index, + io->pieceSlot[index] ); + + /* We will need to reload the whole piece */ + for( i = startBlock; i < endBlock; i++ ) + { + tor->blockHave[i] = 0; + tor->blockHaveCount -= 1; + } + } + else + { + tr_inf( "Piece %d (slot %d): hash OK", index, + io->pieceSlot[index] ); + tr_bitfieldAdd( tor->bitfield, index ); + } + + return 0; +} + +void tr_ioClose( tr_io_t * io ) +{ + closeFiles( io ); + + fastResumeSave( io ); + + free( io->pieceSlot ); + free( io->slotPiece ); + free( io ); +} + +/*********************************************************************** + * createFiles + *********************************************************************** + * Make sure the existing folders/files have correct types and + * permissions, and create missing folders and files + **********************************************************************/ +static int createFiles( tr_io_t * io ) +{ + tr_torrent_t * tor = io->tor; + tr_info_t * inf = &tor->info; + + int i; + char * path, * p; + struct stat sb; + FILE * file; + + tr_dbg( "Creating files..." ); + + for( i = 0; i < inf->fileCount; i++ ) + { + asprintf( &path, "%s/%s", tor->destination, inf->files[i].name ); + + /* Create folders */ + p = path; + while( ( p = strchr( p, '/' ) ) ) + { + *p = '\0'; + if( stat( path, &sb ) ) + { + /* Folder doesn't exist yet */ + mkdir( path, 0755 ); + } + else if( ( sb.st_mode & S_IFMT ) != S_IFDIR ) + { + /* Node exists but isn't a folder */ + printf( "Remove %s, it's in the way.\n", path ); + free( path ); + return 1; + } + *p = '/'; + p++; + } + + if( stat( path, &sb ) ) + { + /* File doesn't exist yet */ + if( !( file = fopen( path, "w" ) ) ) + { + tr_err( "Could not create `%s' (%s)", path, + strerror( errno ) ); + free( path ); + return 1; + } + fclose( file ); + } + else if( ( sb.st_mode & S_IFMT ) != S_IFREG ) + { + /* Node exists but isn't a file */ + printf( "Remove %s, it's in the way.\n", path ); + free( path ); + return 1; + } + + free( path ); + } + + return 0; +} + +/*********************************************************************** + * checkFiles + *********************************************************************** + * Look for pieces + **********************************************************************/ +static int checkFiles( tr_io_t * io ) +{ + tr_torrent_t * tor = io->tor; + tr_info_t * inf = &tor->info; + + 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 ) ); + + if( !fastResumeLoad( io ) ) + { + return 0; + } + + tr_dbg( "Checking pieces..." ); + + /* 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; + buf = malloc( inf->pieceSize ); + for( i = 0; i < inf->pieceCount; i++ ) + { + int size, j; + + if( readSlot( io, i, buf, &size ) ) + { + break; + } + + io->slotsUsed = i + 1; + SHA1( buf, size, hash ); + + for( j = i; j < inf->pieceCount - 1; j++ ) + { + 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++; + } + break; + } + } + + if( io->slotPiece[i] > -1 ) + { + continue; + } + + /* Special case for the last piece */ + SHA1( buf, tr_pieceSize( inf->pieceCount - 1 ), hash ); + if( !memcmp( hash, &inf->pieces[20 * (inf->pieceCount - 1)], + SHA_DIGEST_LENGTH ) ) + { + 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++; + } + } + } + free( buf ); + + return 0; +} + +/*********************************************************************** + * closeFiles + **********************************************************************/ +static void closeFiles( tr_io_t * io ) +{ + tr_torrent_t * tor = io->tor; + tr_info_t * inf = &tor->info; + + int i; + char * path; + + for( i = 0; i < inf->fileCount; i++ ) + { + asprintf( &path, "%s/%s", tor->destination, inf->files[i].name ); + tr_fdFileClose( tor->fdlimit, path ); + free( path ); + } +} + +/*********************************************************************** + * readOrWriteBytes + *********************************************************************** + * + **********************************************************************/ +static int readOrWriteBytes( tr_io_t * io, uint64_t offset, int size, + uint8_t * buf, int write ) +{ + tr_torrent_t * tor = io->tor; + tr_info_t * inf = &tor->info; + + int piece = offset / inf->pieceSize; + int begin = offset % inf->pieceSize; + + int i; + uint64_t foo; + uint64_t posInFile = 0; + int willRead; + char * path; + FILE * file; + + /* We can't ever read or write more than a piece at a time */ + if( tr_pieceSize( piece ) < begin + size ) + { + return 1; + } + + /* Find which file we shall start reading/writing in */ + foo = 0; + for( i = 0; i < inf->fileCount; i++ ) + { + if( offset < foo + inf->files[i].length ) + { + posInFile = offset - foo; + break; + } + foo += inf->files[i].length; + } + + while( size > 0 ) + { + asprintf( &path, "%s/%s", tor->destination, inf->files[i].name ); + file = tr_fdFileOpen( tor->fdlimit, path ); + free( path ); + + if( !file ) + { + return 1; + } + + willRead = MIN( inf->files[i].length - posInFile, + (uint64_t) size ); + + if( fseeko( file, posInFile, SEEK_SET ) ) + { + return 1; + } + if( write ) + { + if( fwrite( buf, willRead, 1, file ) != 1 ) + { + return 1; + } + } + else + { + if( fread( buf, willRead, 1, file ) != 1 ) + { + return 1; + } + } + + tr_fdFileRelease( tor->fdlimit, file ); + + /* 'willRead' less bytes to do */ + size -= willRead; + buf += willRead; + + /* Go to the beginning of the next file */ + i += 1; + posInFile = 0; + } + + return 0; +} + +/*********************************************************************** + * readSlot + *********************************************************************** + * + **********************************************************************/ +static int readOrWriteSlot( tr_io_t * io, int slot, uint8_t * buf, + int * size, int write ) +{ + tr_torrent_t * tor = io->tor; + tr_info_t * inf = &tor->info; + + uint64_t offset = (uint64_t) slot * (uint64_t) inf->pieceSize; + + *size = 0; + if( slot == inf->pieceCount - 1 ) + { + *size = inf->totalSize % inf->pieceSize; + } + if( !*size ) + { + *size = inf->pieceSize; + } + + return readOrWriteBytes( io, offset, *size, buf, write ); +} + +static void invertSlots( tr_io_t * io, int slot1, int slot2 ) +{ + tr_torrent_t * tor = io->tor; + tr_info_t * inf = &tor->info; + + uint8_t * buf1, * buf2; + int piece1, piece2, foo; + + buf1 = calloc( inf->pieceSize, 1 ); + buf2 = calloc( inf->pieceSize, 1 ); + + readSlot( io, slot1, buf1, &foo ); + readSlot( io, slot2, buf2, &foo ); + + writeSlot( io, slot1, buf2, &foo ); + writeSlot( io, slot2, buf1, &foo ); + + free( buf1 ); + free( buf2 ); + + piece1 = io->slotPiece[slot1]; + piece2 = io->slotPiece[slot2]; + io->slotPiece[slot1] = piece2; + io->slotPiece[slot2] = piece1; + if( piece1 >= 0 ) + { + io->pieceSlot[piece1] = slot2; + } + if( piece2 >= 0 ) + { + io->pieceSlot[piece2] = slot1; + } +} + +static void reorderPieces( tr_io_t * io ) +{ + tr_torrent_t * tor = io->tor; + tr_info_t * inf = &tor->info; + + int i, didInvert; + + /* Try to move pieces to their final places */ + do + { + didInvert = 0; + + for( i = 0; i < inf->pieceCount; i++ ) + { + if( io->pieceSlot[i] < 0 ) + { + /* We haven't started this piece yet */ + continue; + } + if( io->pieceSlot[i] == i ) + { + /* Already in place */ + continue; + } + if( i >= io->slotsUsed ) + { + /* File is not big enough yet */ + continue; + } + + /* Move piece i into slot i */ + tr_inf( "invert %d and %d", io->pieceSlot[i], i ); + invertSlots( io, i, io->pieceSlot[i] ); + didInvert = 1; + } + } while( didInvert ); +} + +static void findSlotForPiece( tr_io_t * io, int piece ) +{ + int i; +#if 0 + tr_torrent_t * tor = io->tor; + tr_info_t * inf = &tor->info; + + tr_dbg( "Entering findSlotForPiece" ); + + for( i = 0; i < inf->pieceCount; i++ ) + printf( "%02d ", io->slotPiece[i] ); + printf( "\n" ); + for( i = 0; i < inf->pieceCount; i++ ) + printf( "%02d ", io->pieceSlot[i] ); + printf( "\n" ); +#endif + + /* Look for an empty slot somewhere */ + for( i = 0; i < io->slotsUsed; i++ ) + { + if( io->slotPiece[i] < 0 ) + { + io->pieceSlot[piece] = i; + io->slotPiece[i] = piece; + goto reorder; + } + } + + /* No empty slot, extend the file */ + io->pieceSlot[piece] = io->slotsUsed; + io->slotPiece[io->slotsUsed] = piece; + (io->slotsUsed)++; + + reorder: + reorderPieces( io ); + +#if 0 + for( i = 0; i < inf->pieceCount; i++ ) + printf( "%02d ", io->slotPiece[i] ); + printf( "\n" ); + for( i = 0; i < inf->pieceCount; i++ ) + printf( "%02d ", io->pieceSlot[i] ); + printf( "\n" ); + + printf( "Leaving findSlotForPiece\n" ); +#endif +} diff --git a/libtransmission/inout.h b/libtransmission/inout.h new file mode 100644 index 000000000..5de6fdfe4 --- /dev/null +++ b/libtransmission/inout.h @@ -0,0 +1,33 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef TR_IO_H +#define TR_IO_H 1 + +typedef struct tr_io_s tr_io_t; + +tr_io_t * tr_ioInit ( tr_torrent_t * ); +int tr_ioRead ( tr_io_t *, int, int, int, uint8_t * ); +int tr_ioWrite ( tr_io_t *, int, int, int, uint8_t * ); +void tr_ioClose ( tr_io_t * ); + +#endif diff --git a/libtransmission/internal.h b/libtransmission/internal.h new file mode 100644 index 000000000..7c63b45e7 --- /dev/null +++ b/libtransmission/internal.h @@ -0,0 +1,184 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef TR_INTERNAL_H +#define TR_INTERNAL_H 1 + +/* Standard headers used here and there. + That is probably ugly to put them all here, but it is sooo + convenient */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef BEOS_NETSERVER +# define in_port_t uint16_t +#else +# include +#endif + +/* We use OpenSSL whenever possible, since it is likely to be more + optimized and it is ok to use it with a MIT-licensed application. + Otherwise, we use the included implementation by vi@nwr.jp. */ +#ifdef HAVE_OPENSSL +# undef SHA_DIGEST_LENGTH +# include +#else +# include "sha1.h" +# define SHA1(p,i,h) \ + { \ + sha1_state_s pms; \ + sha1_init( &pms ); \ + sha1_update( &pms, (sha1_byte_t *) p, i ); \ + sha1_finish( &pms, (sha1_byte_t *) h ); \ + } +#endif + +/* Convenient macros to perform uint32_t endian conversions with + char pointers */ +#define TR_NTOHL(p,a) (a) = ntohl(*((uint32_t*)(p))) +#define TR_HTONL(a,p) *((uint32_t*)(p)) = htonl((a)) + +/* Multithreading support: native threads on BeOS, pthreads elsewhere */ +#ifdef SYS_BEOS +# include +# define tr_thread_t thread_id +# define tr_threadCreate(pt,f,d) *(pt) = spawn_thread((void*)f,"",10,d); \ + resume_thread(*(pt)); +# define tr_threadJoin(t) { long e; wait_for_thread(t,&e); } +# define tr_lock_t sem_id +# define tr_lockInit(pl) *(pl) = create_sem(1,"") +# define tr_lockLock(l) acquire_sem(l) +# define tr_lockUnlock(l) release_sem(l) +# define tr_lockClose(l) delete_sem(l) +#else +# include +# define tr_thread_t pthread_t +# define tr_threadCreate(pt,f,d) pthread_create(pt,NULL,(void*)f,d) +# define tr_threadJoin(t) pthread_join(t,NULL) +# define tr_lock_t pthread_mutex_t +# define tr_lockInit(pl) pthread_mutex_init(pl,NULL) +# define tr_lockLock(l) pthread_mutex_lock(&l) +# define tr_lockUnlock(l) pthread_mutex_unlock(&l) +# define tr_lockClose(l) pthread_mutex_destroy(&l) +#endif + +/* Sometimes the system defines MAX/MIN, sometimes not. In the latter + case, define those here since we will use them */ +#ifndef MAX +#define MAX(a,b) ((a)>(b)?(a):(b)) +#endif +#ifndef MIN +#define MIN(a,b) ((a)>(b)?(b):(a)) +#endif + +#define TR_MAX_PEER_COUNT 60 + +typedef struct tr_torrent_s tr_torrent_t; + +#include "bencode.h" +#include "metainfo.h" +#include "tracker.h" +#include "peer.h" +#include "net.h" +#include "inout.h" +#include "upload.h" +#include "fdlimit.h" +#include "clients.h" + +struct tr_torrent_s +{ + tr_info_t info; + + tr_upload_t * upload; + tr_fd_t * fdlimit; + + int status; + char error[128]; + + char * id; + + /* An escaped string used to include the hash in HTTP queries */ + char hashString[3*SHA_DIGEST_LENGTH+1]; + + char scrape[MAX_PATH_LENGTH]; + + /* Where to download */ + char * destination; + + /* How many bytes we ask for per request */ + int blockSize; + int blockCount; + + /* Status for each block + -1 = we have it + n = we are downloading it from n peers */ + char * blockHave; + int blockHaveCount; + uint8_t * bitfield; + + volatile char die; + tr_thread_t thread; + tr_lock_t lock; + + tr_tracker_t * tracker; + tr_io_t * io; + + int bindSocket; + int bindPort; + int peerCount; + tr_peer_t * peers[TR_MAX_PEER_COUNT]; + + uint64_t dates[10]; + uint64_t downloaded[10]; + uint64_t uploaded[10]; +}; + +#include "utils.h" + +struct tr_handle_s +{ + int torrentCount; + tr_torrent_t * torrents[TR_MAX_TORRENT_COUNT]; + + tr_upload_t * upload; + tr_fd_t * fdlimit; + + int bindPort; + + char id[21]; +}; + +#endif diff --git a/libtransmission/metainfo.c b/libtransmission/metainfo.c new file mode 100644 index 000000000..a0b76c9ac --- /dev/null +++ b/libtransmission/metainfo.c @@ -0,0 +1,282 @@ +/****************************************************************************** + * 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" + +/*********************************************************************** + * Local prototypes + **********************************************************************/ +static void strcatUTF8( char *, char * ); + +/*********************************************************************** + * tr_metainfoParse + *********************************************************************** + * + **********************************************************************/ +int tr_metainfoParse( tr_info_t * inf, const char * path ) +{ + FILE * file; + char * buf; + benc_val_t meta, * beInfo, * list, * val; + char * s, * s2, * s3; + int i; + struct stat sb; + + snprintf( inf->torrent, MAX_PATH_LENGTH, path ); + + if( stat( path, &sb ) ) + { + fprintf( stderr, "Could not stat file (%s)\n", path ); + return 1; + } + if( ( sb.st_mode & S_IFMT ) != S_IFREG ) + { + fprintf( stderr, "Not a regular file (%s)\n", path ); + return 1; + } + if( sb.st_size > 2097152 ) + { + tr_err( "Torrent file is too big (%d bytes)", sb.st_size ); + return 1; + } + + /* Load the torrent file into our buffer */ + file = fopen( path, "rb" ); + if( !file ) + { + fprintf( stderr, "Could not open file (%s)\n", path ); + return 1; + } + buf = malloc( sb.st_size ); + fseek( file, 0, SEEK_SET ); + if( fread( buf, sb.st_size, 1, file ) != 1 ) + { + fprintf( stderr, "Read error (%s)\n", path ); + free( buf ); + fclose( file ); + return 1; + } + fclose( file ); + + /* Parse bencoded infos */ + if( tr_bencLoad( buf, &meta, NULL ) ) + { + fprintf( stderr, "Error while parsing bencoded data\n" ); + free( buf ); + return 1; + } + + /* Get info hash */ + if( !( beInfo = tr_bencDictFind( &meta, "info" ) ) ) + { + fprintf( stderr, "Could not find \"info\" dictionary\n" ); + tr_bencFree( &meta ); + free( buf ); + return 1; + } + SHA1( (uint8_t *) beInfo->begin, + (long) beInfo->end - (long) beInfo->begin, inf->hash ); + + /* No that we got the hash, we won't need this anymore */ + free( buf ); + + if( !( val = tr_bencDictFind( &meta, "announce" ) ) ) + { + fprintf( stderr, "No \"announce\" entry\n" ); + tr_bencFree( &meta ); + return 1; + } + + /* Skip spaces */ + s3 = val->val.s.s; + while( *s3 && *s3 == ' ' ) + { + s3++; + } + + /* Parse announce URL */ + if( strncmp( s3, "http://", 7 ) ) + { + fprintf( stderr, "Invalid announce URL (%s)\n", + inf->trackerAddress ); + tr_bencFree( &meta ); + return 1; + } + s = strchr( s3 + 7, ':' ); + s2 = strchr( s3 + 7, '/' ); + if( s && s < s2 ) + { + memcpy( inf->trackerAddress, s3 + 7, + (long) s - (long) s3 - 7 ); + inf->trackerPort = atoi( s + 1 ); + } + else if( s2 ) + { + memcpy( inf->trackerAddress, s3 + 7, + (long) s2 - (long) s3 - 7 ); + inf->trackerPort = 80; + } + else + { + fprintf( stderr, "Invalid announce URL (%s)\n", + inf->trackerAddress ); + tr_bencFree( &meta ); + return 1; + } + snprintf( inf->trackerAnnounce, MAX_PATH_LENGTH, s2 ); + + /* Piece length */ + if( !( val = tr_bencDictFind( beInfo, "piece length" ) ) ) + { + fprintf( stderr, "No \"piece length\" entry\n" ); + tr_bencFree( &meta ); + return 1; + } + inf->pieceSize = val->val.i; + + /* Hashes */ + val = tr_bencDictFind( beInfo, "pieces" ); + if( val->val.s.i % SHA_DIGEST_LENGTH ) + { + fprintf( stderr, "Invalid \"piece\" string (size is %d)\n", + val->val.s.i ); + return 1; + } + inf->pieceCount = val->val.s.i / SHA_DIGEST_LENGTH; + inf->pieces = (uint8_t *) val->val.s.s; /* Ugly, but avoids a memcpy */ + val->val.s.s = NULL; + + /* TODO add more tests so we don't crash on weird files */ + + inf->totalSize = 0; + if( ( list = tr_bencDictFind( beInfo, "files" ) ) ) + { + /* Multi-file mode */ + int j; + + val = tr_bencDictFind( beInfo, "name" ); + strcatUTF8( inf->name, val->val.s.s ); + + inf->fileCount = list->val.l.count; + inf->files = calloc( inf->fileCount * sizeof( tr_file_t ), 1 ); + + for( i = 0; i < list->val.l.count; i++ ) + { + val = tr_bencDictFind( &list->val.l.vals[i], "path" ); + strcatUTF8( inf->files[i].name, inf->name ); + for( j = 0; j < val->val.l.count; j++ ) + { + strcatUTF8( inf->files[i].name, "/" ); + strcatUTF8( inf->files[i].name, + val->val.l.vals[j].val.s.s ); + } + val = tr_bencDictFind( &list->val.l.vals[i], "length" ); + inf->files[i].length = val->val.i; + inf->totalSize += val->val.i; + } + + } + else + { + /* Single-file mode */ + inf->fileCount = 1; + inf->files = calloc( sizeof( tr_file_t ), 1 ); + + val = tr_bencDictFind( beInfo, "name" ); + strcatUTF8( inf->files[0].name, val->val.s.s ); + strcatUTF8( inf->name, val->val.s.s ); + + val = tr_bencDictFind( beInfo, "length" ); + inf->files[0].length = val->val.i; + inf->totalSize += val->val.i; + } + + if( (uint64_t) inf->pieceCount != + ( inf->totalSize + inf->pieceSize - 1 ) / inf->pieceSize ) + { + fprintf( stderr, "Size of hashes and files don't match\n" ); + tr_bencFree( &meta ); + return 1; + } + + tr_bencFree( &meta ); + return 0; +} + +/*********************************************************************** + * strcatUTF8 + *********************************************************************** + * According to the official specification, all strings in the torrent + * file are supposed to be UTF-8 encoded. However, there are + * non-compliant torrents around... If we encounter an invalid UTF-8 + * character, we assume it is ISO 8859-1 and convert it to UTF-8. + **********************************************************************/ +static void strcatUTF8( char * s, char * append ) +{ + char * p; + + /* Go to the end of the destination string */ + while( s[0] ) + { + s++; + } + + /* Now start appending, converting on the fly if necessary */ + for( p = append; p[0]; ) + { + if( !( p[0] & 0x80 ) ) + { + /* ASCII character */ + *(s++) = *(p++); + continue; + } + + if( ( p[0] & 0xE0 ) == 0xC0 && ( p[1] & 0xC0 ) == 0x80 ) + { + /* 2-bytes UTF-8 character */ + *(s++) = *(p++); *(s++) = *(p++); + continue; + } + + if( ( p[0] & 0xF0 ) == 0xE0 && ( p[1] & 0xC0 ) == 0x80 && + ( p[2] & 0xC0 ) == 0x80 ) + { + /* 3-bytes UTF-8 character */ + *(s++) = *(p++); *(s++) = *(p++); + *(s++) = *(p++); + continue; + } + + if( ( p[0] & 0xF8 ) == 0xF0 && ( p[1] & 0xC0 ) == 0x80 && + ( p[2] & 0xC0 ) == 0x80 && ( p[3] & 0xC0 ) == 0x80 ) + { + /* 4-bytes UTF-8 character */ + *(s++) = *(p++); *(s++) = *(p++); + *(s++) = *(p++); *(s++) = *(p++); + continue; + } + + /* ISO 8859-1 -> UTF-8 conversion */ + *(s++) = 0xC0 | ( ( *p & 0xFF ) >> 6 ); + *(s++) = 0x80 | ( *(p++) & 0x3F ); + } +} diff --git a/libtransmission/metainfo.h b/libtransmission/metainfo.h new file mode 100644 index 000000000..e0c31c9d1 --- /dev/null +++ b/libtransmission/metainfo.h @@ -0,0 +1,28 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef TR_METAINFO_H +#define TR_METAINFO_H 1 + +int tr_metainfoParse( tr_info_t *, const char * ); + +#endif diff --git a/libtransmission/net.c b/libtransmission/net.c new file mode 100644 index 000000000..7d3e9634c --- /dev/null +++ b/libtransmission/net.c @@ -0,0 +1,225 @@ +/****************************************************************************** + * 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" + +static int makeSocketNonBlocking( int s ) +{ + int flags; + +#ifdef SYS_BEOS + flags = 1; + if( setsockopt( s, SOL_SOCKET, SO_NONBLOCK, + &flags, sizeof( int ) ) < 0 ) +#else + if( ( flags = fcntl( s, F_GETFL, 0 ) ) < 0 || + fcntl( s, F_SETFL, flags | O_NONBLOCK ) < 0 ) +#endif + { + tr_err( "Could not set socket to non-blocking mode (%s)", + strerror( errno ) ); + tr_netClose( s ); + return -1; + } + + return s; +} + +static int createSocket() +{ + int s; + + s = socket( AF_INET, SOCK_STREAM, 0 ); + if( s < 0 ) + { + tr_err( "Could not create socket (%s)", strerror( errno ) ); + return -1; + } + + return makeSocketNonBlocking( s ); +} + +int tr_netResolve( char * address, struct in_addr * addr ) +{ + struct hostent * host; + + addr->s_addr = inet_addr( address ); + if( addr->s_addr != 0xFFFFFFFF ) + { + return 0; + } + + if( !( host = gethostbyname( address ) ) ) + { + tr_err( "Could not resolve (%s)", address ); + return -1; + } + memcpy( addr, host->h_addr, host->h_length ); + + return 0; +} + +int tr_netOpen( struct in_addr addr, in_port_t port ) +{ + int s; + struct sockaddr_in sock; + + s = createSocket(); + if( s < 0 ) + { + return -1; + } + + memset( &sock, 0, sizeof( sock ) ); + sock.sin_family = AF_INET; + sock.sin_addr.s_addr = addr.s_addr; + sock.sin_port = port; + + if( connect( s, (struct sockaddr *) &sock, + sizeof( struct sockaddr_in ) ) < 0 && + errno != EINPROGRESS ) + { + tr_err( "Could not connect socket (%s)", strerror( errno ) ); + tr_netClose( s ); + return -1; + } + + return s; +} + +int tr_netBind( int * port ) +{ + int s, i; + struct sockaddr_in sock; + int minPort, maxPort; + + s = createSocket(); + if( s < 0 ) + { + return -1; + } + + minPort = *port; + maxPort = minPort + 1000; + maxPort = MIN( maxPort, 65535 ); + + for( i = minPort; i <= maxPort; i++ ) + { + memset( &sock, 0, sizeof( sock ) ); + sock.sin_family = AF_INET; + sock.sin_addr.s_addr = INADDR_ANY; + sock.sin_port = htons( i ); + + if( !bind( s, (struct sockaddr *) &sock, + sizeof( struct sockaddr_in ) ) ) + { + break; + } + } + + if( i > maxPort ) + { + tr_netClose( s ); + tr_err( "Could not bind any port from %d to %d", + minPort, maxPort ); + return -1; + } + + tr_inf( "Binded port %d", i ); + *port = i; + listen( s, 5 ); + + return s; +} + +int tr_netAccept( int s, struct in_addr * addr, in_port_t * port ) +{ + int t; + unsigned len; + struct sockaddr_in sock; + + len = sizeof( sock ); + t = accept( s, (struct sockaddr *) &sock, &len ); + + if( t < 0 ) + { + return -1; + } + + *addr = sock.sin_addr; + *port = sock.sin_port; + + return makeSocketNonBlocking( t ); +} + +int tr_netSend( int s, uint8_t * buf, int size ) +{ + int ret; + + ret = send( s, buf, size, 0 ); + if( ret < 0 ) + { + if( errno == ENOTCONN || errno == EAGAIN || errno == EWOULDBLOCK ) + { + ret = TR_NET_BLOCK; + } + else + { + ret = TR_NET_CLOSE; + } + } + + return ret; +} + +int tr_netRecv( int s, uint8_t * buf, int size ) +{ + int ret; + + ret = recv( s, buf, size, 0 ); + if( ret < 0 ) + { + if( errno == EAGAIN || errno == EWOULDBLOCK ) + { + ret = TR_NET_BLOCK; + } + else + { + ret = TR_NET_CLOSE; + } + } + if( !ret ) + { + ret = TR_NET_CLOSE; + } + + return ret; +} + +void tr_netClose( int s ) +{ +#ifdef BEOS_NETSERVER + closesocket( s ); +#else + close( s ); +#endif +} diff --git a/libtransmission/net.h b/libtransmission/net.h new file mode 100644 index 000000000..84548cafa --- /dev/null +++ b/libtransmission/net.h @@ -0,0 +1,32 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +int tr_netResolve ( char *, struct in_addr * ); +int tr_netOpen ( struct in_addr addr, in_port_t port ); +int tr_netBind ( int * ); +int tr_netAccept ( int s, struct in_addr *, in_port_t * ); +void tr_netClose ( int s ); + +#define TR_NET_BLOCK 0x80000000 +#define TR_NET_CLOSE 0x40000000 +int tr_netSend ( int s, uint8_t * buf, int size ); +int tr_netRecv ( int s, uint8_t * buf, int size ); diff --git a/libtransmission/peer.c b/libtransmission/peer.c new file mode 100644 index 000000000..95760f911 --- /dev/null +++ b/libtransmission/peer.c @@ -0,0 +1,464 @@ +/****************************************************************************** + * 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" + +#define MAX_REQUEST_COUNT 32 +#define OUR_REQUEST_COUNT 8 /* TODO: we should detect if we are on a + high-speed network and adapt */ + +typedef struct tr_request_s +{ + int index; + int begin; + int length; + +} tr_request_t; + +struct tr_peer_s +{ + struct in_addr addr; + in_port_t port; + +#define PEER_STATUS_IDLE 1 /* Need to connect */ +#define PEER_STATUS_CONNECTING 2 /* Trying to send handshake */ +#define PEER_STATUS_HANDSHAKE 4 /* Waiting for peer's handshake */ +#define PEER_STATUS_CONNECTED 8 /* Got peer's handshake */ + int status; + int socket; + uint64_t date; + uint64_t keepAlive; + + char amChoking; + char amInterested; + char peerChoking; + char peerInterested; + + uint8_t id[20]; + + uint8_t * bitfield; + + uint8_t * buf; + int size; + int pos; + + uint8_t * outMessages; + int outMessagesSize; + int outMessagesPos; + uint8_t outBlock[13+16384]; + int outBlockSize; + int outBlockLoaded; + int outBlockSending; + + int inRequestCount; + tr_request_t inRequests[OUR_REQUEST_COUNT]; + int inIndex; + int inBegin; + int inLength; + uint64_t inTotal; + + int outRequestCount; + tr_request_t outRequests[MAX_REQUEST_COUNT]; + uint64_t outTotal; + uint64_t outDate; + int outSlow; +}; + +#define peer_dbg( a... ) __peer_dbg( peer, ## a ) +static void __peer_dbg( tr_peer_t * peer, char * msg, ... ) +{ + char string[256]; + va_list args; + + va_start( args, msg ); + sprintf( string, "%08x:%04x ", + (uint32_t) peer->addr.s_addr, peer->port ); + vsnprintf( &string[14], sizeof( string ) - 14, msg, args ); + va_end( args ); + + tr_dbg( "%s", string ); +} + +#include "peermessages.h" +#include "peerutils.h" + +/*********************************************************************** + * tr_peerAddOld + *********************************************************************** + * Tries to add a peer given its IP and port (received from a tracker + * which doesn't support the "compact" extension). + **********************************************************************/ +void tr_peerAddOld( tr_torrent_t * tor, char * ip, int port ) +{ + struct in_addr addr; + + if( tr_netResolve( ip, &addr ) ) + { + return; + } + + addWithAddr( tor, addr, htons( port ) ); +} + +/*********************************************************************** + * tr_peerAddCompact + *********************************************************************** + * Tries to add a peer. If 's' is a negative value, will use 'addr' and + * 'port' to connect to the peer. Otherwise, use the already connected + * socket 's'. + **********************************************************************/ +void tr_peerAddCompact( tr_torrent_t * tor, struct in_addr addr, + in_port_t port, int s ) +{ + tr_peer_t * peer; + + if( s < 0 ) + { + addWithAddr( tor, addr, port ); + return; + } + + if( !( peer = peerInit( tor ) ) ) + { + tr_netClose( s ); + tr_fdSocketClosed( tor->fdlimit, 0 ); + return; + } + + peer->socket = s; + peer->addr = addr; + peer->port = port; + peer->status = PEER_STATUS_CONNECTING; +} + +/*********************************************************************** + * tr_peerRem + *********************************************************************** + * Frees and closes everything related to the peer at index 'i', and + * removes it from the peers list. + **********************************************************************/ +void tr_peerRem( tr_torrent_t * tor, int i ) +{ + tr_peer_t * peer = tor->peers[i]; + int j; + + for( j = 0; j < peer->inRequestCount; j++ ) + { + tr_request_t * r; + int block; + + r = &peer->inRequests[j]; + block = tr_block( r->index,r->begin ); + if( tor->blockHave[block] > 0 ) + { + (tor->blockHave[block])--; + } + } + if( !peer->amChoking ) + { + tr_uploadChoked( tor->upload ); + } + if( peer->bitfield ) + { + free( peer->bitfield ); + } + if( peer->buf ) + { + free( peer->buf ); + } + if( peer->outMessages ) + { + free( peer->outMessages ); + } + if( peer->status > PEER_STATUS_IDLE ) + { + tr_netClose( peer->socket ); + tr_fdSocketClosed( tor->fdlimit, 0 ); + } + free( peer ); + tor->peerCount--; + memmove( &tor->peers[i], &tor->peers[i+1], + ( tor->peerCount - i ) * sizeof( tr_peer_t * ) ); +} + +/*********************************************************************** + * tr_peerPulse + *********************************************************************** + * + **********************************************************************/ +void tr_peerPulse( tr_torrent_t * tor ) +{ + int i, ret, size; + uint8_t * p; + tr_peer_t * peer; + + tor->dates[9] = tr_date(); + if( tor->dates[9] > tor->dates[8] + 1000 ) + { + memmove( &tor->downloaded[0], &tor->downloaded[1], + 9 * sizeof( uint64_t ) ); + memmove( &tor->uploaded[0], &tor->uploaded[1], + 9 * sizeof( uint64_t ) ); + memmove( &tor->dates[0], &tor->dates[1], + 9 * sizeof( uint64_t ) ); + + for( i = 0; i < tor->peerCount; ) + { + if( checkPeer( tor, i ) ) + { + tr_peerRem( tor, i ); + continue; + } + i++; + } + } + + /* Check for incoming connections */ + if( tor->bindSocket > -1 && + tor->peerCount < TR_MAX_PEER_COUNT && + !tr_fdSocketWillCreate( tor->fdlimit, 0 ) ) + { + int s; + struct in_addr addr; + in_port_t port; + s = tr_netAccept( tor->bindSocket, &addr, &port ); + if( s > -1 ) + { + tr_peerAddCompact( tor, addr, port, s ); + } + else + { + tr_fdSocketClosed( tor->fdlimit, 0 ); + } + } + + /* Shuffle peers */ + if( tor->peerCount > 1 ) + { + peer = tor->peers[0]; + memmove( &tor->peers[0], &tor->peers[1], + ( tor->peerCount - 1 ) * sizeof( void * ) ); + tor->peers[tor->peerCount - 1] = peer; + } + + /* Handle peers */ + for( i = 0; i < tor->peerCount; ) + { + peer = tor->peers[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" ); + goto dropPeer; + } + 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; + + 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" ); + goto dropPeer; + } + else if( !( ret & TR_NET_BLOCK ) ) + { + peer_dbg( "SEND handshake" ); + peer->status = PEER_STATUS_HANDSHAKE; + } + } + + /* Try to read */ + if( peer->status >= PEER_STATUS_HANDSHAKE ) + { + 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( parseMessage( tor, peer, ret ) ) + { + goto dropPeer; + } + } + } + + /* Try to write */ +writeBegin: + + /* Send all smaller messages regardless of the upload cap */ + while( ( p = messagesPending( peer, &size ) ) ) + { + ret = tr_netSend( peer->socket, p, size ); + if( ret & TR_NET_CLOSE ) + { + goto dropPeer; + } + else if( ret & TR_NET_BLOCK ) + { + goto writeEnd; + } + messagesSent( peer, ret ); + } + + /* Send pieces if we can */ + while( ( p = blockPending( tor, peer, &size ) ) ) + { + if( !tr_uploadCanUpload( tor->upload ) ) + { + break; + } + + ret = tr_netSend( peer->socket, p, size ); + if( ret & TR_NET_CLOSE ) + { + goto dropPeer; + } + else if( ret & TR_NET_BLOCK ) + { + break; + } + + blockSent( peer, ret ); + tr_uploadUploaded( tor->upload, ret ); + + tor->uploaded[9] += ret; + peer->outTotal += ret; + peer->outDate = tr_date(); + + /* In case this block is done, you may have messages + pending. Send them before we start the next block */ + goto writeBegin; + } +writeEnd: + + /* Connected peers: ask for a block whenever possible */ + if( peer->status & PEER_STATUS_CONNECTED ) + { + 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; + } + + if( peer->amInterested && !peer->peerChoking ) + { + int block; + while( peer->inRequestCount < OUR_REQUEST_COUNT ) + { + block = chooseBlock( tor, peer ); + if( block < 0 ) + { + break; + } + sendRequest( tor, peer, block ); + } + } + } + + i++; + continue; + +dropPeer: + tr_peerRem( tor, i ); + } +} + +/*********************************************************************** + * tr_peerIsConnected + *********************************************************************** + * + **********************************************************************/ +int tr_peerIsConnected( tr_peer_t * peer ) +{ + return peer->status & PEER_STATUS_CONNECTED; +} + +/*********************************************************************** + * tr_peerIsUploading + *********************************************************************** + * + **********************************************************************/ +int tr_peerIsUploading( tr_peer_t * peer ) +{ + return ( peer->inRequestCount > 0 ); +} + +/*********************************************************************** + * tr_peerIsDownloading + *********************************************************************** + * + **********************************************************************/ +int tr_peerIsDownloading( tr_peer_t * peer ) +{ + return peer->outBlockSending; +} + +/*********************************************************************** + * tr_peerBitfield + *********************************************************************** + * + **********************************************************************/ +uint8_t * tr_peerBitfield( tr_peer_t * peer ) +{ + return peer->bitfield; +} diff --git a/libtransmission/peer.h b/libtransmission/peer.h new file mode 100644 index 000000000..bbbae6bd2 --- /dev/null +++ b/libtransmission/peer.h @@ -0,0 +1,38 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef TR_PEER_H +#define TR_PEER_H 1 + +typedef struct tr_peer_s tr_peer_t; + +void tr_peerAddOld ( tr_torrent_t *, char *, int ); +void tr_peerAddCompact ( tr_torrent_t *, struct in_addr, + in_port_t, int ); +void tr_peerRem ( tr_torrent_t *, int ); +void tr_peerPulse ( tr_torrent_t * ); +int tr_peerIsConnected ( tr_peer_t * ); +int tr_peerIsUploading ( tr_peer_t * ); +int tr_peerIsDownloading ( tr_peer_t * ); +uint8_t * tr_peerBitfield ( tr_peer_t * ); + +#endif diff --git a/libtransmission/peermessages.h b/libtransmission/peermessages.h new file mode 100644 index 000000000..d740a36c0 --- /dev/null +++ b/libtransmission/peermessages.h @@ -0,0 +1,306 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +static uint8_t * messagesPending( tr_peer_t * peer, int * size ) +{ + if( peer->outBlockSending || peer->outMessagesPos < 1 ) + { + return NULL; + } + + *size = MIN( peer->outMessagesPos, 1024 ); + + return peer->outMessages; +} + +static void messagesSent( tr_peer_t * peer, int size ) +{ + peer->outMessagesPos -= size; + memmove( peer->outMessages, &peer->outMessages[size], + peer->outMessagesPos ); +} + +static uint8_t * blockPending( tr_torrent_t * tor, tr_peer_t * peer, + int * size ) +{ + if( !peer->outBlockLoaded ) + { + uint8_t * p; + tr_request_t * r; + + if( peer->outRequestCount < 1 ) + { + /* No piece to send */ + return NULL; + } + + /* We need to load the block for the next request */ + r = &peer->outRequests[0]; + p = (uint8_t *) peer->outBlock; + + TR_HTONL( 9 + r->length, p ); + p[4] = 7; + TR_HTONL( r->index, p + 5 ); + TR_HTONL( r->begin, p + 9 ); + + tr_ioRead( tor->io, r->index, r->begin, r->length, &p[13] ); + + peer_dbg( "SEND piece %d/%d (%d bytes)", + r->index, r->begin, r->length ); + + peer->outBlockSize = 13 + r->length; + peer->outBlockLoaded = 1; + + (peer->outRequestCount)--; + memmove( &peer->outRequests[0], &peer->outRequests[1], + peer->outRequestCount * sizeof( tr_request_t ) ); + } + + *size = MIN( 1024, peer->outBlockSize ); + + return (uint8_t *) peer->outBlock; +} + +static void blockSent( tr_peer_t * peer, int size ) +{ + peer->outBlockSize -= size; + memmove( peer->outBlock, &peer->outBlock[size], peer->outBlockSize ); + + if( peer->outBlockSize > 0 ) + { + /* We can't send messages until we are done sending the block */ + peer->outBlockSending = 1; + } + else + { + /* Block fully sent */ + peer->outBlockSending = 0; + peer->outBlockLoaded = 0; + } +} + +static uint8_t * getPointerForSize( tr_peer_t * peer, int size ) +{ + uint8_t * p; + + if( peer->outMessagesPos + size > peer->outMessagesSize ) + { + peer->outMessagesSize = peer->outMessagesPos + size; + peer->outMessages = realloc( peer->outMessages, + peer->outMessagesSize ); + } + + p = &peer->outMessages[peer->outMessagesPos]; + peer->outMessagesPos += size; + + return p; +} + +/*********************************************************************** + * sendKeepAlive + *********************************************************************** + * + **********************************************************************/ +static void sendKeepAlive( tr_peer_t * peer ) +{ + uint8_t * p; + + p = getPointerForSize( peer, 4 ); + + TR_HTONL( 0, p ); + + peer_dbg( "SEND keep-alive" ); +} + + +/*********************************************************************** + * sendChoke + *********************************************************************** + * + **********************************************************************/ +static void sendChoke( tr_peer_t * peer, int yes ) +{ + uint8_t * p; + + p = getPointerForSize( peer, 5 ); + + TR_HTONL( 1, p ); + p[4] = yes ? 0 : 1; + + peer->amChoking = yes; + + if( yes ) + { + /* Drop all pending requests */ + peer->outRequestCount = 0; + } + + peer_dbg( "SEND %schoke", yes ? "" : "un" ); +} + +/*********************************************************************** + * sendInterest + *********************************************************************** + * + **********************************************************************/ +static void sendInterest( tr_peer_t * peer, int yes ) +{ + uint8_t * p; + + p = getPointerForSize( peer, 5 ); + + TR_HTONL( 1, p ); + p[4] = yes ? 2 : 3; + + peer->amInterested = yes; + + peer_dbg( "SEND %sinterested", yes ? "" : "un" ); +} + +/*********************************************************************** + * sendHave + *********************************************************************** + * + **********************************************************************/ +static void sendHave( tr_peer_t * peer, int piece ) +{ + uint8_t * p; + + p = getPointerForSize( peer, 9 ); + + TR_HTONL( 5, &p[0] ); + p[4] = 4; + TR_HTONL( piece, &p[5] ); + + peer_dbg( "SEND have %d", piece ); +} + +/*********************************************************************** + * sendBitfield + *********************************************************************** + * Builds a 'bitfield' message: + * - size = 5 + X (4 bytes) + * - id = 5 (1 byte) + * - bitfield (X bytes) + **********************************************************************/ +static void sendBitfield( tr_torrent_t * tor, tr_peer_t * peer ) +{ + uint8_t * p; + int bitfieldSize = ( tor->info.pieceCount + 7 ) / 8; + + p = getPointerForSize( peer, 5 + bitfieldSize ); + + TR_HTONL( 1 + bitfieldSize, p ); + p[4] = 5; + memcpy( &p[5], tor->bitfield, bitfieldSize ); + + peer_dbg( "SEND bitfield" ); +} + +/*********************************************************************** + * sendRequest + *********************************************************************** + * + **********************************************************************/ +static void sendRequest( tr_torrent_t * tor, tr_peer_t * peer, int block ) +{ + tr_info_t * inf = &tor->info; + tr_request_t * r; + uint8_t * p; + + /* Get the piece the block is a part of, its position in the piece + and its size */ + r = &peer->inRequests[peer->inRequestCount]; + r->index = block / ( inf->pieceSize / tor->blockSize ); + r->begin = ( block % ( inf->pieceSize / tor->blockSize ) ) * + tor->blockSize; + r->length = tor->blockSize; + if( block == tor->blockCount - 1 ) + { + int lastSize = inf->totalSize % tor->blockSize; + if( lastSize ) + { + r->length = lastSize; + } + } + (peer->inRequestCount)++; + + /* Build the "ask" message */ + p = getPointerForSize( peer, 17 ); + + TR_HTONL( 13, p ); + p[4] = 6; + TR_HTONL( r->index, p + 5 ); + 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])++; + + peer_dbg( "SEND request %d/%d (%d bytes)", + r->index, r->begin, r->length ); +} + +/*********************************************************************** + * sendCancel + *********************************************************************** + * + **********************************************************************/ +static void sendCancel( tr_torrent_t * tor, int block ) +{ + int i, j; + uint8_t * p; + tr_peer_t * peer; + tr_request_t * r; + + for( i = 0; i < tor->peerCount; i++ ) + { + peer = tor->peers[i]; + + for( j = 1; j < peer->inRequestCount; j++ ) + { + r = &peer->inRequests[j]; + + if( block != tr_block( r->index, r->begin ) ) + { + continue; + } + + p = getPointerForSize( peer, 17 ); + + /* Build the "cancel" message */ + TR_HTONL( 13, p ); + p[4] = 8; + TR_HTONL( r->index, p + 5 ); + TR_HTONL( r->begin, p + 9 ); + TR_HTONL( r->length, p + 13 ); + + peer_dbg( "SEND cancel %d/%d (%d bytes)", + r->index, r->begin, r->length ); + + (peer->inRequestCount)--; + memmove( &peer->inRequests[j], &peer->inRequests[j+1], + ( peer->inRequestCount - j ) * sizeof( tr_request_t ) ); + break; + } + } +} diff --git a/libtransmission/peerutils.h b/libtransmission/peerutils.h new file mode 100644 index 000000000..d7319214c --- /dev/null +++ b/libtransmission/peerutils.h @@ -0,0 +1,827 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +static void updateInterest( tr_torrent_t * tor, tr_peer_t * peer ); + +/*********************************************************************** + * peerInit + *********************************************************************** + * Returns NULL if we reached the maximum authorized number of peers. + * Otherwise, allocates a new tr_peer_t, add it to the peers list and + * returns a pointer to it. + **********************************************************************/ +static tr_peer_t * peerInit( tr_torrent_t * tor ) +{ + tr_peer_t * peer; + + if( tor->peerCount >= TR_MAX_PEER_COUNT ) + { + return NULL; + } + + peer = calloc( sizeof( tr_peer_t ), 1 ); + peer->amChoking = 1; + peer->peerChoking = 1; + peer->date = tr_date(); + peer->keepAlive = peer->date; + + tor->peers[tor->peerCount++] = peer; + return peer; +} + +static int peerCmp( tr_peer_t * peer1, tr_peer_t * peer2 ) +{ + /* Wait until we got the peers' ids */ + if( peer1->status < PEER_STATUS_CONNECTED || + peer2->status < PEER_STATUS_CONNECTED ) + { + return 1; + } + + return memcmp( peer1->id, peer2->id, 20 ); +} + +/*********************************************************************** + * addWithAddr + *********************************************************************** + * Does nothing if we already have a peer matching 'addr' and 'port'. + * Otherwise adds such a new peer. + **********************************************************************/ +static void addWithAddr( tr_torrent_t * tor, struct in_addr addr, + in_port_t port ) +{ + int i; + tr_peer_t * peer; + + for( i = 0; i < tor->peerCount; i++ ) + { + peer = tor->peers[i]; + if( peer->addr.s_addr == addr.s_addr && + peer->port == port ) + { + /* We are already connected to this peer */ + return; + } + } + + if( !( peer = peerInit( tor ) ) ) + { + return; + } + + peer->addr = addr; + peer->port = port; + peer->status = PEER_STATUS_IDLE; +} + +static int checkPeer( tr_torrent_t * tor, int i ) +{ + tr_peer_t * peer = tor->peers[i]; + + if( peer->status < PEER_STATUS_CONNECTED && + tr_date() > peer->date + 8000 ) + { + /* If it has been too long, don't wait for the socket + to timeout - forget about it now */ + peer_dbg( "connection timeout" ); + return 1; + } + + /* Drop peers who haven't even sent a keep-alive within the + last 3 minutes */ + if( tr_date() > peer->date + 180000 ) + { + peer_dbg( "read timeout" ); + return 1; + } + + /* Drop peers which are supposed to upload but actually + haven't sent anything within the last minute */ + if( peer->inRequestCount && tr_date() > peer->date + 60000 ) + { + peer_dbg( "bad uploader" ); + return 1; + } + +#if 0 + /* Choke unchoked peers we are not sending anything to */ + if( !peer->amChoking && tr_date() > peer->outDate + 10000 ) + { + peer_dbg( "not worth the unchoke" ); + if( sendChoke( peer, 1 ) ) + { + goto dropPeer; + } + peer->outSlow = 1; + tr_uploadChoked( tor->upload ); + } +#endif + + if( peer->status & PEER_STATUS_CONNECTED ) + { + /* Send keep-alive every 2 minutes */ + if( tr_date() > peer->keepAlive + 120000 ) + { + sendKeepAlive( peer ); + peer->keepAlive = tr_date(); + } + + /* Choke or unchoke some people */ + /* TODO: prefer people who upload to us */ + if( !peer->amChoking && !peer->peerInterested ) + { + /* He doesn't need us */ + sendChoke( peer, 1 ); + tr_uploadChoked( tor->upload ); + } + if( peer->amChoking && peer->peerInterested && + !peer->outSlow && tr_uploadCanUnchoke( tor->upload ) ) + { + sendChoke( peer, 0 ); + tr_uploadUnchoked( tor->upload ); + } + } + + return 0; +} + +static int parseMessage( tr_torrent_t * tor, tr_peer_t * peer, + int newBytes ) +{ + tr_info_t * inf = &tor->info; + + int i, j; + int len; + char id; + uint8_t * p = peer->buf; + uint8_t * end = &p[peer->pos]; + + for( ;; ) + { + if( peer->pos < 4 ) + { + break; + } + + if( peer->status & PEER_STATUS_HANDSHAKE ) + { + char * client; + + if( p[0] != 19 || memcmp( &p[1], "Bit", 3 ) ) + { + /* Don't wait until we get 68 bytes, this is wrong + already */ + peer_dbg( "GET handshake, invalid" ); + tr_netSend( peer->socket, (uint8_t *) "Nice try...\r\n", 13 ); + return 1; + } + + if( peer->pos < 68 ) + { + break; + } + + if( memcmp( &p[4], "Torrent protocol", 16 ) ) + { + peer_dbg( "GET handshake, invalid" ); + return 1; + } + + if( memcmp( &p[28], inf->hash, 20 ) ) + { + peer_dbg( "GET handshake, wrong torrent hash" ); + return 1; + } + + if( !memcmp( &p[48], tor->id, 20 ) ) + { + /* We are connected to ourselves... */ + peer_dbg( "GET handshake, that is us" ); + return 1; + } + + peer->status = PEER_STATUS_CONNECTED; + memcpy( peer->id, &p[48], 20 ); + p += 68; + peer->pos -= 68; + + for( i = 0; i < tor->peerCount; i++ ) + { + if( tor->peers[i] == peer ) + { + continue; + } + if( !peerCmp( peer, tor->peers[i] ) ) + { + peer_dbg( "GET handshake, duplicate" ); + return 1; + } + } + + client = tr_clientForId( (uint8_t *) peer->id ); + peer_dbg( "GET handshake, ok (%s)", client ); + free( client ); + + sendBitfield( tor, peer ); + + continue; + } + + /* Get payload size */ + TR_NTOHL( p, len ); + p += 4; + + if( len > 9 + tor->blockSize ) + { + /* This shouldn't happen. Forget about that peer */ + peer_dbg( "message too large" ); + return 1; + } + + if( !len ) + { + /* keep-alive */ + peer_dbg( "GET keep-alive" ); + peer->pos -= 4; + continue; + } + + /* That's a piece coming */ + if( p < end && *p == 7 ) + { + /* XXX */ + tor->downloaded[9] += newBytes; + peer->inTotal += newBytes; + newBytes = 0; + } + + if( &p[len] > end ) + { + /* We do not have the entire message */ + p -= 4; + break; + } + + /* Remaining data after this message */ + peer->pos -= 4 + len; + + /* Type of the message */ + id = *(p++); + + switch( id ) + { + case 0: /* choke */ + { + tr_request_t * r; + + if( len != 1 ) + { + peer_dbg( "GET choke, invalid" ); + return 1; + } + + peer_dbg( "GET choke" ); + peer->peerChoking = 1; + + 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)]--; + } + } + peer->inRequestCount = 0; + + break; + } + case 1: /* unchoke */ + if( len != 1 ) + { + peer_dbg( "GET unchoke, invalid" ); + return 1; + } + peer_dbg( "GET unchoke" ); + peer->peerChoking = 0; + break; + case 2: /* interested */ + if( len != 1 ) + { + peer_dbg( "GET interested, invalid" ); + return 1; + } + peer_dbg( "GET interested" ); + peer->peerInterested = 1; + break; + case 3: /* uninterested */ + if( len != 1 ) + { + peer_dbg( "GET uninterested, invalid" ); + return 1; + } + peer_dbg( "GET uninterested" ); + peer->peerInterested = 0; + break; + case 4: /* have */ + { + uint32_t piece; + if( len != 5 ) + { + peer_dbg( "GET have, invalid" ); + return 1; + } + TR_NTOHL( p, piece ); + if( !peer->bitfield ) + { + peer->bitfield = calloc( ( inf->pieceCount + 7 ) / 8, 1 ); + } + tr_bitfieldAdd( peer->bitfield, piece ); + + updateInterest( tor, peer ); + + peer_dbg( "GET have %d", piece ); + break; + } + case 5: /* bitfield */ + { + int bitfieldSize; + + bitfieldSize = ( inf->pieceCount + 7 ) / 8; + + if( len != 1 + bitfieldSize ) + { + peer_dbg( "GET bitfield, wrong size" ); + return 1; + } + + /* Make sure the spare bits are unset */ + if( ( inf->pieceCount & 0x7 ) ) + { + uint8_t lastByte; + + lastByte = p[bitfieldSize-1]; + lastByte <<= inf->pieceCount & 0x7; + lastByte &= 0xFF; + + if( lastByte ) + { + peer_dbg( "GET bitfield, spare bits set" ); + return 1; + } + } + + if( !peer->bitfield ) + { + peer->bitfield = malloc( bitfieldSize ); + } + memcpy( peer->bitfield, p, bitfieldSize ); + + updateInterest( tor, peer ); + + peer_dbg( "GET bitfield, ok" ); + break; + } + case 6: /* request */ + { + int index, begin, length; + + if( peer->amChoking ) + { + /* Didn't he get it? */ + sendChoke( peer, 1 ); + break; + } + + TR_NTOHL( p, index ); + TR_NTOHL( &p[4], begin ); + TR_NTOHL( &p[8], length ); + + peer_dbg( "GET request %d/%d (%d bytes)", + index, begin, length ); + + /* TODO sanity checks (do we have the piece, etc) */ + + if( length > 16384 ) + { + /* Sorry mate */ + return 1; + } + + if( peer->outRequestCount < MAX_REQUEST_COUNT ) + { + tr_request_t * r; + + r = &peer->outRequests[peer->outRequestCount]; + r->index = index; + r->begin = begin; + r->length = length; + + (peer->outRequestCount)++; + } + else + { + tr_err( "Too many requests" ); + return 1; + } + break; + } + case 7: /* piece */ + { + int index, begin; + int block; + tr_request_t * r; + + TR_NTOHL( p, index ); + TR_NTOHL( &p[4], begin ); + + peer_dbg( "GET piece %d/%d (%d bytes)", + index, begin, len - 9 ); + + if( peer->inRequestCount < 1 ) + { + /* Our "cancel" was probably late */ + peer_dbg( "not expecting a block" ); + break; + } + + r = &peer->inRequests[0]; + if( index != r->index || begin != r->begin ) + { + int suckyClient; + + /* Either our "cancel" was late, or this is a sucky + client that cannot deal with multiple requests */ + suckyClient = 0; + for( i = 0; i < peer->inRequestCount; i++ ) + { + r = &peer->inRequests[i]; + + if( index != r->index || begin != r->begin ) + { + continue; + } + + /* Sucky client, he dropped the previous requests */ + peer_dbg( "block was expected later" ); + 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)]--; + } + } + suckyClient = 1; + peer->inRequestCount -= i; + memmove( &peer->inRequests[0], &peer->inRequests[i], + peer->inRequestCount * sizeof( tr_request_t ) ); + r = &peer->inRequests[0]; + break; + } + + if( !suckyClient ) + { + r = &peer->inRequests[0]; + peer_dbg( "wrong block (expecting %d/%d)", + r->index, r->begin ); + break; + } + } + + if( len - 9 != r->length ) + { + peer_dbg( "wrong size (expecting %d)", r->length ); + return 1; + } + + block = tr_block( r->index, r->begin ); + if( tor->blockHave[block] < 0 ) + { + peer_dbg( "have this block already" ); + (peer->inRequestCount)--; + memmove( &peer->inRequests[0], &peer->inRequests[1], + peer->inRequestCount * sizeof( tr_request_t ) ); + break; + } + + tor->blockHave[block] = -1; + tor->blockHaveCount += 1; + tr_ioWrite( tor->io, index, begin, len - 9, &p[8] ); + + sendCancel( tor, block ); + + if( tr_bitfieldHas( tor->bitfield, index ) ) + { + tr_peer_t * otherPeer; + + for( i = 0; i < tor->peerCount; i++ ) + { + otherPeer = tor->peers[i]; + + if( otherPeer->status < PEER_STATUS_CONNECTED ) + { + continue; + } + + sendHave( otherPeer, index ); + updateInterest( tor, otherPeer ); + } + } + + (peer->inRequestCount)--; + memmove( &peer->inRequests[0], &peer->inRequests[1], + peer->inRequestCount * sizeof( tr_request_t ) ); + break; + } + case 8: /* cancel */ + { + int index, begin, length; + int i; + tr_request_t * r; + + TR_NTOHL( p, index ); + TR_NTOHL( &p[4], begin ); + TR_NTOHL( &p[8], length ); + + peer_dbg( "GET cancel %d/%d (%d bytes)", + index, begin, length ); + + for( i = 0; i < peer->outRequestCount; i++ ) + { + r = &peer->outRequests[i]; + if( r->index == index && r->begin == begin && + r->length == length ) + { + (peer->outRequestCount)--; + memmove( &r[0], &r[1], sizeof( tr_request_t ) * + ( peer->outRequestCount - i ) ); + break; + } + } + + break; + } + case 9: + { + in_port_t port; + + if( len != 3 ) + { + peer_dbg( "GET port, invalid" ); + return 1; + } + + port = *( (in_port_t *) p ); + peer_dbg( "GET port %d", ntohs( port ) ); + + break; + } + default: + { + peer_dbg( "Unknown message '%d'", id ); + return 1; + } + } + + p += len - 1; + } + + memmove( peer->buf, p, peer->pos ); + + return 0; +} + +/*********************************************************************** + * isInteresting + *********************************************************************** + * Returns 1 if 'peer' has at least one piece that we haven't completed, + * or 0 otherwise. + **********************************************************************/ +static int isInteresting( tr_torrent_t * tor, tr_peer_t * peer ) +{ + tr_info_t * inf = &tor->info; + + int i; + int bitfieldSize = ( inf->pieceCount + 7 ) / 8; + + if( !peer->bitfield ) + { + /* We don't know what this peer has */ + return 0; + } + + for( i = 0; i < bitfieldSize; i++ ) + { + if( ( peer->bitfield[i] & ~(tor->bitfield[i]) ) & 0xFF ) + { + return 1; + } + } + + return 0; +} +static void updateInterest( tr_torrent_t * tor, tr_peer_t * peer ) +{ + int interested = isInteresting( tor, peer ); + + if( interested && !peer->amInterested ) + { + sendInterest( peer, 1 ); + } + if( !interested && peer->amInterested ) + { + sendInterest( peer, 0 ); + } +} + +/*********************************************************************** + * chooseBlock + *********************************************************************** + * At this point, we know the peer has at least one block we have an + * interest in. If he has more than one, we choose which one we are + * going to ask first. + * Our main goal is to complete pieces, so we look the pieces which are + * missing less blocks. + **********************************************************************/ +static int chooseBlock( tr_torrent_t * tor, tr_peer_t * peer ) +{ + tr_info_t * inf = &tor->info; + + int i, j; + int startBlock, endBlock, countBlocks; + int missingBlocks, minMissing; + int poolSize, * pool; + int block, minDownloading; + + /* Choose a piece */ + pool = malloc( inf->pieceCount * sizeof( int ) ); + poolSize = 0; + minMissing = tor->blockCount + 1; + for( i = 0; i < inf->pieceCount; i++ ) + { + 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 ) + { + minMissing = missingBlocks; + poolSize = 0; + } + if( missingBlocks <= minMissing ) + { + pool[poolSize++] = i; + } + } + + if( poolSize ) + { + /* All pieces in 'pool' have 'minMissing' missing blocks. Find + the rarest ones. */ + uint8_t * bitfield; + int piece; + int min, foo, j; + int * pool2; + int pool2Size; + + pool2 = malloc( poolSize * sizeof( int ) ); + pool2Size = 0; + min = TR_MAX_PEER_COUNT + 1; + for( i = 0; i < poolSize; i++ ) + { + foo = 0; + for( j = 0; j < tor->peerCount; j++ ) + { + bitfield = tor->peers[j]->bitfield; + if( bitfield && tr_bitfieldHas( bitfield, pool[i] ) ) + { + foo++; + } + } + if( foo < min ) + { + min = foo; + pool2Size = 0; + } + if( foo <= min ) + { + pool2[pool2Size++] = pool[i]; + } + } + free( pool ); + + if( pool2Size < 1 ) + { + /* Shouldn't happen */ + free( pool2 ); + return -1; + } + + /* All pieces in pool2 have the same number of missing blocks, + and are availabme from the same number of peers. Pick a + random one */ + piece = pool2[tr_rand(pool2Size)]; + 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; + } + + free( pool ); + + /* "End game" mode */ + block = -1; + minDownloading = TR_MAX_PEER_COUNT + 1; + for( i = 0; i < tor->blockCount; i++ ) + { + /* TODO: optimize */ + if( tor->blockHave[i] > 0 && tor->blockHave[i] < minDownloading ) + { + block = i; + minDownloading = tor->blockHave[i]; + } + } + + if( block < 0 ) + { + /* Shouldn't happen */ + return -1; + } + +check: + for( i = 0; i < peer->inRequestCount; i++ ) + { + tr_request_t * r; + r = &peer->inRequests[i]; + if( tr_block( r->index, r->begin ) == block ) + { + /* We are already asking this peer for this block */ + return -1; + } + } + + return block; +} diff --git a/libtransmission/sha1.c b/libtransmission/sha1.c new file mode 100644 index 000000000..d9d3b37bf --- /dev/null +++ b/libtransmission/sha1.c @@ -0,0 +1,235 @@ +/* + sha1.c: Implementation of SHA-1 Secure Hash Algorithm-1 + + Based upon: NIST FIPS180-1 Secure Hash Algorithm-1 + http://www.itl.nist.gov/fipspubs/fip180-1.htm + + Non-official Japanese Translation by HIRATA Yasuyuki: + http://yasu.asuka.net/translations/SHA-1.html + + Copyright (C) 2002 vi@nwr.jp. All rights reserved. + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgement in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as beging the original software. + 3. This notice may not be removed or altered from any source distribution. + + Note: + The copyright notice above is copied from md5.h by L. Peter Deutsch + . Thank him since I'm not a good speaker of English. :) + */ +#include +#include "sha1.h" + +#define INLINE inline +/* + * Packing bytes to a word + * + * Should not assume p is aligned to word boundary + */ +static INLINE sha1_word_t packup(sha1_byte_t *p) +{ + /* Portable, but slow */ + return p[0] << 24 | p[1] << 16 | p[2] << 8 | p[3] << 0; +} + +/* + * Unpacking a word to bytes + * + * Should not assume p is aligned to word boundary + */ +static void unpackup(sha1_byte_t *p, sha1_word_t q) +{ + p[0] = (q >> 24) & 0xff; + p[1] = (q >> 16) & 0xff; + p[2] = (q >> 8) & 0xff; + p[3] = (q >> 0) & 0xff; +} + +/* + * Processing a block + */ +static inline void sha1_update_now(sha1_state_s *pms, sha1_byte_t *bp) +{ + sha1_word_t tmp, a, b, c, d, e, w[16+16]; + int i, s; + + /* pack 64 bytes into 16 words */ + for(i = 0; i < 16; i++) { + w[i] = packup(bp + i * sizeof(sha1_word_t)); + } + memcpy(w + 16, w + 0, sizeof(sha1_word_t) * 16); + + a = pms->sha1_h[0], b = pms->sha1_h[1], c = pms->sha1_h[2], d = pms->sha1_h[3], e = pms->sha1_h[4]; + +#define rot(x,n) (((x) << n) | ((x) >> (32-n))) +#define f0(b, c, d) ((b&c)|(~b&d)) +#define f1(b, c, d) (b^c^d) +#define f2(b, c, d) ((b&c)|(b&d)|(c&d)) +#define f3(b, c, d) (b^c^d) +#define k0 0x5a827999 +#define k1 0x6ed9eba1 +#define k2 0x8f1bbcdc +#define k3 0xca62c1d6 + + /* t=0-15 */ + s = 0; + for(i = 0; i < 16; i++) { + tmp = rot(a, 5) + f0(b, c, d) + e + w[s] + k0; + e = d; d = c; c = rot(b, 30); b = a; a = tmp; + s = (s + 1) % 16; + } + + /* t=16-19 */ + for(i = 16; i < 20; i++) { + w[s] = rot(w[s+13] ^ w[s+8] ^ w[s+2] ^ w[s], 1); + w[s+16] = w[s]; + tmp = rot(a, 5) + f0(b, c, d) + e + w[s] + k0; + e = d; d = c; c = rot(b, 30); b = a; a = tmp; + s = (s + 1) % 16; + } + + /* t=20-39 */ + for(i = 0; i < 20; i++) { + w[s] = rot(w[s+13] ^ w[s+8] ^ w[s+2] ^ w[s], 1); + w[s+16] = w[s]; + tmp = rot(a, 5) + f1(b, c, d) + e + w[s] + k1; + e = d; d = c; c = rot(b, 30); b = a; a = tmp; + s = (s + 1) % 16; + } + + /* t=40-59 */ + for(i = 0; i < 20; i++) { + w[s] = rot(w[s+13] ^ w[s+8] ^ w[s+2] ^ w[s], 1); + w[s+16] = w[s]; + tmp = rot(a, 5) + f2(b, c, d) + e + w[s] + k2; + e = d; d = c; c = rot(b, 30); b = a; a = tmp; + s = (s + 1) % 16; + } + + /* t=60-79 */ + for(i = 0; i < 20; i++) { + w[s] = rot(w[s+13] ^ w[s+8] ^ w[s+2] ^ w[s], 1); + w[s+16] = w[s]; + tmp = rot(a, 5) + f3(b, c, d) + e + w[s] + k3; + e = d; d = c; c = rot(b, 30); b = a; a = tmp; + s = (s + 1) % 16; + } + + pms->sha1_h[0] += a, pms->sha1_h[1] += b, pms->sha1_h[2] += c, pms->sha1_h[3] += d, pms->sha1_h[4] += e; +} + +/* + * Increment sha1_size1, sha1_size2 field of sha1_state_s + */ +static INLINE void incr(sha1_state_s *pms, int v) +{ + sha1_word_t q; + + q = pms->sha1_size1 + v * BITS; + if(q < pms->sha1_size1) { + pms->sha1_size2++; + } + pms->sha1_size1 = q; +} + +/* + * Initialize sha1_state_s as FIPS specifies + */ +void sha1_init(sha1_state_s *pms) +{ + memset(pms, 0, sizeof(*pms)); + pms->sha1_h[0] = 0x67452301; /* Initialize H[0]-H[4] */ + pms->sha1_h[1] = 0xEFCDAB89; + pms->sha1_h[2] = 0x98BADCFE; + pms->sha1_h[3] = 0x10325476; + pms->sha1_h[4] = 0xC3D2E1F0; +} + +/* + * Fill block and update output when needed + */ +void sha1_update(sha1_state_s *pms, sha1_byte_t *bufp, int length) +{ + /* Is the buffer partially filled? */ + if(pms->sha1_count != 0) { + if(pms->sha1_count + length >= (signed) sizeof(pms->sha1_buf)) { /* buffer is filled enough */ + int fil = sizeof(pms->sha1_buf) - pms->sha1_count; /* length to copy */ + + memcpy(pms->sha1_buf + pms->sha1_count, bufp, fil); + sha1_update_now(pms, pms->sha1_buf); + length -= fil; + bufp += fil; + pms->sha1_count = 0; + incr(pms, fil); + } else { + memcpy(pms->sha1_buf + pms->sha1_count, bufp, length); + pms->sha1_count += length; + incr(pms, length); + return; + } + } + + /* Loop to update state */ + for(;;) { + if(length < (signed) sizeof(pms->sha1_buf)) { /* Short to fill up the buffer */ + if(length) { + memcpy(pms->sha1_buf, bufp, length); + } + pms->sha1_count = length; + incr(pms, length); + break; + } + sha1_update_now(pms, bufp); + length -= sizeof(pms->sha1_buf); + bufp += sizeof(pms->sha1_buf); + incr(pms, sizeof(pms->sha1_buf)); + } +} + +void sha1_finish(sha1_state_s *pms, sha1_byte_t output[SHA1_OUTPUT_SIZE]) +{ + int i; + sha1_byte_t buf[1]; + + /* fill a bit */ + buf[0] = 0x80; + sha1_update(pms, buf, 1); + + /* Decrement sha1_size1, sha1_size2 */ + if((pms->sha1_size1 -= BITS) == 0) { + pms->sha1_size2--; + } + + /* fill zeros */ + if(pms->sha1_count > (signed) (sizeof(pms->sha1_buf) - 2 * sizeof(sha1_word_t))) { + memset(pms->sha1_buf + pms->sha1_count, 0, sizeof(pms->sha1_buf) - pms->sha1_count); + sha1_update_now(pms, pms->sha1_buf); + pms->sha1_count = 0; + } + memset(pms->sha1_buf + pms->sha1_count, 0, + sizeof(pms->sha1_buf) - pms->sha1_count - sizeof(sha1_word_t) * 2); + + /* fill last length */ + unpackup(pms->sha1_buf + sizeof(pms->sha1_buf) - sizeof(sha1_word_t) * 2, pms->sha1_size2); + unpackup(pms->sha1_buf + sizeof(pms->sha1_buf) - sizeof(sha1_word_t) * 1, pms->sha1_size1); + + /* final update */ + sha1_update_now(pms, pms->sha1_buf); + + /* move hash value to output byte array */ + for(i = 0; i < (signed) (sizeof(pms->sha1_h)/sizeof(sha1_word_t)); i++) { + unpackup(output + i * sizeof(sha1_word_t), pms->sha1_h[i]); + } +} diff --git a/libtransmission/sha1.h b/libtransmission/sha1.h new file mode 100644 index 000000000..bae61e0d3 --- /dev/null +++ b/libtransmission/sha1.h @@ -0,0 +1,68 @@ +/* + sha1.h: Implementation of SHA-1 Secure Hash Algorithm-1 + + Based upon: NIST FIPS180-1 Secure Hash Algorithm-1 + http://www.itl.nist.gov/fipspubs/fip180-1.htm + + Non-official Japanese Translation by HIRATA Yasuyuki: + http://yasu.asuka.net/translations/SHA-1.html + + Copyright (C) 2002 vi@nwr.jp. All rights reserved. + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgement in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as beging the original software. + 3. This notice may not be removed or altered from any source distribution. + + Note: + The copyright notice above is copied from md5.h by L. Petet Deutsch + . Thank him since I'm not a good speaker of English. :) + */ +#ifndef SHA1_H +#define SHA1_H + +typedef unsigned int sha1_word_t; /* 32bits unsigned integer */ +typedef unsigned char sha1_byte_t; /* 8bits unsigned integer */ +#define BITS 8 + +/* Define the state of SHA-1 algorithm */ +typedef struct { + sha1_byte_t sha1_buf[64]; /* 512 bits */ + int sha1_count; /* How many bytes are used */ + sha1_word_t sha1_size1; /* Length counter Lower Word */ + sha1_word_t sha1_size2; /* Length counter Upper Word */ + sha1_word_t sha1_h[5]; /* Hash output */ +} sha1_state_s; +#define SHA1_OUTPUT_SIZE 20 /* in bytes */ + +/* External Functions */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* Initialize SHA-1 algorithm */ +void sha1_init(sha1_state_s *pms); + +/* Append a string to SHA-1 algorithm */ +void sha1_update(sha1_state_s *pms, sha1_byte_t *input_buffer, int length); + +/* Finish the SHA-1 algorithm and return the hash */ +void sha1_finish(sha1_state_s *pms, sha1_byte_t output[SHA1_OUTPUT_SIZE]); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/libtransmission/tracker.c b/libtransmission/tracker.c new file mode 100644 index 000000000..132e082b9 --- /dev/null +++ b/libtransmission/tracker.c @@ -0,0 +1,597 @@ +/****************************************************************************** + * 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" + +struct tr_tracker_s +{ + tr_torrent_t * tor; + + char * id; + + char started; + char completed; + char stopped; + + int interval; + int seeders; + int leechers; + int hasManyPeers; + + uint64_t dateTry; + uint64_t dateOk; + +#define TC_STATUS_IDLE 1 +#define TC_STATUS_CONNECT 2 +#define TC_STATUS_RECV 4 + char status; + + int socket; + uint8_t * buf; + int size; + int pos; +}; + +static void sendQuery ( tr_tracker_t * tc ); +static void recvAnswer ( tr_tracker_t * tc ); + +tr_tracker_t * tr_trackerInit( tr_handle_t * h, tr_torrent_t * tor ) +{ + tr_tracker_t * tc; + + tc = calloc( 1, sizeof( tr_tracker_t ) ); + tc->tor = tor; + tc->id = h->id; + + tc->started = 1; + + tc->seeders = -1; + tc->leechers = -1; + + tc->status = TC_STATUS_IDLE; + tc->size = 1024; + tc->buf = malloc( tc->size ); + + return tc; +} + +static int shouldConnect( tr_tracker_t * tc ) +{ + uint64_t now = tr_date(); + + /* In any case, always wait 5 seconds between two requests */ + if( now < tc->dateTry + 5000 ) + { + return 0; + } + + /* Do we need to send an event? */ + if( tc->started || tc->completed || tc->stopped ) + { + return 1; + } + + /* Should we try and get more peers? */ + if( now > tc->dateOk + 1000 * tc->interval ) + { + return 1; + } + + /* If there is quite a lot of people on this torrent, stress + the tracker a bit until we get a decent number of peers */ + if( tc->hasManyPeers ) + { + if( tc->tor->peerCount < 5 && now > tc->dateOk + 10000 ) + { + return 1; + } + if( tc->tor->peerCount < 10 && now > tc->dateOk + 20000 ) + { + return 1; + } + if( tc->tor->peerCount < 15 && now > tc->dateOk + 30000 ) + { + return 1; + } + } + + return 0; +} + +int tr_trackerPulse( tr_tracker_t * tc ) +{ + tr_torrent_t * tor = tc->tor; + tr_info_t * inf = &tor->info; + uint64_t now = tr_date(); + + if( ( tc->status & TC_STATUS_IDLE ) && shouldConnect( tc ) ) + { + struct in_addr addr; + + if( tr_fdSocketWillCreate( tor->fdlimit, 1 ) ) + { + return 0; + } + + if( tr_netResolve( inf->trackerAddress, &addr ) ) + { + tr_fdSocketClosed( tor->fdlimit, 1 ); + return 0; + } + + tc->socket = tr_netOpen( addr, htons( inf->trackerPort ) ); + if( tc->socket < 0 ) + { + tr_fdSocketClosed( tor->fdlimit, 1 ); + return 0; + } + + tr_inf( "Tracker: connecting to %s:%d (%s)", + inf->trackerAddress, inf->trackerPort, + tc->started ? "sending 'started'" : + ( tc->completed ? "sending 'completed'" : + ( tc->stopped ? "sending 'stopped'" : + "getting peers" ) ) ); + + tc->status = TC_STATUS_CONNECT; + tc->dateTry = tr_date(); + } + + if( tc->status & TC_STATUS_CONNECT ) + { + /* We are connecting to the tracker. Try to send the query */ + sendQuery( tc ); + } + + if( tc->status & TC_STATUS_RECV ) + { + /* Try to get something */ + recvAnswer( tc ); + } + + if( tc->status > TC_STATUS_IDLE && now > tc->dateTry + 60000 ) + { + /* Give up if the request wasn't successful within 60 seconds */ + tr_inf( "Tracker: timeout reached (60 s)" ); + + tr_netClose( tc->socket ); + tr_fdSocketClosed( tor->fdlimit, 1 ); + + tc->status = TC_STATUS_IDLE; + tc->dateTry = tr_date(); + } + + return 0; +} + +void tr_trackerCompleted( tr_tracker_t * tc ) +{ + tc->started = 0; + tc->completed = 1; + tc->stopped = 0; +} + +void tr_trackerStopped( tr_tracker_t * tc ) +{ + tr_torrent_t * tor = tc->tor; + + if( tc->status > TC_STATUS_CONNECT ) + { + /* If we are already sendy a query at the moment, we need to + reconnect */ + tr_netClose( tc->socket ); + tr_fdSocketClosed( tor->fdlimit, 1 ); + tc->status = TC_STATUS_IDLE; + } + + tc->started = 0; + tc->completed = 0; + tc->stopped = 1; + + /* Even if we have connected recently, reconnect right now */ + if( tc->status & TC_STATUS_IDLE ) + { + tc->dateTry = 0; + } +} + +void tr_trackerClose( tr_tracker_t * tc ) +{ + tr_torrent_t * tor = tc->tor; + + if( tc->status > TC_STATUS_IDLE ) + { + tr_netClose( tc->socket ); + tr_fdSocketClosed( tor->fdlimit, 1 ); + } + free( tc->buf ); + free( tc ); +} + +static void sendQuery( tr_tracker_t * tc ) +{ + tr_torrent_t * tor = tc->tor; + tr_info_t * inf = &tor->info; + + char * event; + uint64_t left; + int ret; + + if( tc->started ) + event = "&event=started"; + else if( tc->completed ) + event = "&event=completed"; + else if( tc->stopped ) + event = "&event=stopped"; + else + event = ""; + + left = (uint64_t) ( tor->blockCount - tor->blockHaveCount ) * + (uint64_t) tor->blockSize; + left = MIN( left, inf->totalSize ); + + 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 ); + + ret = tr_netSend( tc->socket, tc->buf, ret ); + if( ret & TR_NET_CLOSE ) + { + tr_inf( "Tracker: connection failed" ); + tr_netClose( tc->socket ); + tr_fdSocketClosed( tor->fdlimit, 1 ); + tc->status = TC_STATUS_IDLE; + tc->dateTry = tr_date(); + } + else if( !( ret & TR_NET_BLOCK ) ) + { + // printf( "Tracker: sent %s", tc->buf ); + tc->status = TC_STATUS_RECV; + tc->pos = 0; + } +} + +static void recvAnswer( tr_tracker_t * tc ) +{ + tr_torrent_t * tor = tc->tor; + int ret; + int i; + benc_val_t beAll; + benc_val_t * bePeers, * beFoo; + + if( tc->pos == tc->size ) + { + tc->size *= 2; + tc->buf = realloc( tc->buf, tc->size ); + } + + ret = tr_netRecv( tc->socket, &tc->buf[tc->pos], + tc->size - tc->pos ); + + if( ret & TR_NET_BLOCK ) + { + return; + } + if( !( ret & TR_NET_CLOSE ) ) + { + // printf( "got %d bytes\n", ret ); + tc->pos += ret; + return; + } + + tr_netClose( tc->socket ); + tr_fdSocketClosed( tor->fdlimit, 1 ); + // printf( "connection closed, got total %d bytes\n", tc->pos ); + + tc->status = TC_STATUS_IDLE; + tc->dateTry = tr_date(); + + if( tc->pos < 1 ) + { + /* We got nothing */ + return; + } + + /* Find the beginning of the dictionary */ + for( i = 0; i < tc->pos - 18; i++ ) + { + /* Hem */ + if( !memcmp( &tc->buf[i], "d8:interval", 11 ) || + !memcmp( &tc->buf[i], "d8:complete", 11 ) || + !memcmp( &tc->buf[i], "d14:failure reason", 18 ) ) + { + break; + } + } + + if( i >= tc->pos - 18 ) + { + tr_err( "Tracker: no dictionary in answer" ); + // printf( "%s\n", tc->buf ); + return; + } + + if( tr_bencLoad( &tc->buf[i], &beAll, NULL ) ) + { + tr_err( "Tracker: error parsing bencoded data" ); + return; + } + + // tr_bencPrint( &beAll ); + + if( ( bePeers = tr_bencDictFind( &beAll, "failure reason" ) ) ) + { + tr_err( "Tracker: %s", bePeers->val.s.s ); + tor->status |= TR_TRACKER_ERROR; + snprintf( tor->error, sizeof( tor->error ), + bePeers->val.s.s ); + goto cleanup; + } + + tor->status &= ~TR_TRACKER_ERROR; + + if( !tc->interval ) + { + /* Get the tracker interval, ignore it if it is not between + 10 sec and 5 mins */ + if( !( beFoo = tr_bencDictFind( &beAll, "interval" ) ) || + !( beFoo->type & TYPE_INT ) ) + { + tr_err( "Tracker: no 'interval' field" ); + goto cleanup; + } + + tc->interval = beFoo->val.i; + tc->interval = MIN( tc->interval, 300 ); + tc->interval = MAX( 10, tc->interval ); + + tr_inf( "Tracker: interval = %d seconds", tc->interval ); + } + + if( ( beFoo = tr_bencDictFind( &beAll, "complete" ) ) && + ( beFoo->type & TYPE_INT ) ) + { + tc->seeders = beFoo->val.i; + } + if( ( beFoo = tr_bencDictFind( &beAll, "incomplete" ) ) && + ( beFoo->type & TYPE_INT ) ) + { + tc->leechers = beFoo->val.i; + } + if( tc->seeders + tc->seeders >= 50 ) + { + tc->hasManyPeers = 1; + } + + if( !( bePeers = tr_bencDictFind( &beAll, "peers" ) ) ) + { + tr_err( "Tracker: no \"peers\" field" ); + goto cleanup; + } + + if( bePeers->type & TYPE_LIST ) + { + char * ip; + int port; + + /* Original protocol */ + tr_inf( "Tracker: got %d peers", bePeers->val.l.count ); + + for( i = 0; i < bePeers->val.l.count; i++ ) + { + beFoo = tr_bencDictFind( &bePeers->val.l.vals[i], "ip" ); + if( !beFoo ) + continue; + ip = beFoo->val.s.s; + beFoo = tr_bencDictFind( &bePeers->val.l.vals[i], "port" ); + if( !beFoo ) + continue; + port = beFoo->val.i; + + tr_peerAddOld( tor, ip, port ); + } + + if( bePeers->val.l.count >= 50 ) + { + tc->hasManyPeers = 1; + } + } + else if( bePeers->type & TYPE_STR ) + { + struct in_addr addr; + in_port_t port; + + /* "Compact" extension */ + if( bePeers->val.s.i % 6 ) + { + tr_err( "Tracker: \"peers\" of size %d", + bePeers->val.s.i ); + tr_lockUnlock( tor->lock ); + goto cleanup; + } + + tr_inf( "Tracker: got %d peers", bePeers->val.s.i / 6 ); + for( i = 0; i < bePeers->val.s.i / 6; i++ ) + { + memcpy( &addr, &bePeers->val.s.s[6*i], 4 ); + memcpy( &port, &bePeers->val.s.s[6*i+4], 2 ); + + tr_peerAddCompact( tor, addr, port, -1 ); + } + + if( bePeers->val.s.i / 6 >= 50 ) + { + tc->hasManyPeers = 1; + } + } + + /* Success */ + tc->started = 0; + tc->completed = 0; + tc->dateOk = tr_date(); + + if( tc->stopped ) + { + tor->status = TR_STATUS_STOPPED; + tc->stopped = 0; + } + +cleanup: + tr_bencFree( &beAll ); +} + +int tr_trackerScrape( tr_torrent_t * tor, int * seeders, int * leechers ) +{ + tr_info_t * inf = &tor->info; + + int s, i, ret; + uint8_t buf[1024]; + benc_val_t scrape, * val1, * val2; + struct in_addr addr; + uint64_t date; + int pos, len; + + if( !tor->scrape[0] ) + { + /* scrape not supported */ + return 1; + } + + if( tr_netResolve( inf->trackerAddress, &addr ) ) + { + return 0; + } + s = tr_netOpen( addr, htons( inf->trackerPort ) ); + if( s < 0 ) + { + return 1; + } + + len = snprintf( (char *) buf, sizeof( buf ), + "GET %s?info_hash=%s HTTP/1.1\r\n" + "Host: %s\r\n" + "Connection: close\r\n\r\n", + tor->scrape, tor->hashString, + inf->trackerAddress ); + + for( date = tr_date();; ) + { + ret = tr_netSend( s, buf, len ); + if( ret & TR_NET_CLOSE ) + { + fprintf( stderr, "Could not connect to tracker\n" ); + tr_netClose( s ); + return 1; + } + else if( ret & TR_NET_BLOCK ) + { + if( tr_date() > date + 10000 ) + { + fprintf( stderr, "Could not connect to tracker\n" ); + tr_netClose( s ); + return 1; + } + } + else + { + break; + } + tr_wait( 10 ); + } + + pos = 0; + for( date = tr_date();; ) + { + ret = tr_netRecv( s, &buf[pos], sizeof( buf ) - pos ); + if( ret & TR_NET_CLOSE ) + { + break; + } + else if( ret & TR_NET_BLOCK ) + { + if( tr_date() > date + 10000 ) + { + fprintf( stderr, "Could not read from tracker\n" ); + tr_netClose( s ); + return 1; + } + } + else + { + pos += ret; + } + tr_wait( 10 ); + } + + if( pos < 1 ) + { + fprintf( stderr, "Could not read from tracker\n" ); + tr_netClose( s ); + return 1; + } + + for( i = 0; i < ret - 8; i++ ) + { + if( !memcmp( &buf[i], "d5:files", 8 ) ) + { + break; + } + } + if( i >= ret - 8 ) + { + return 1; + } + if( tr_bencLoad( &buf[i], &scrape, NULL ) ) + { + return 1; + } + + val1 = tr_bencDictFind( &scrape, "files" ); + if( !val1 ) + { + return 1; + } + val1 = &val1->val.l.vals[1]; + if( !val1 ) + { + return 1; + } + val2 = tr_bencDictFind( val1, "complete" ); + if( !val2 ) + { + return 1; + } + *seeders = val2->val.i; + val2 = tr_bencDictFind( val1, "incomplete" ); + if( !val2 ) + { + return 1; + } + *leechers = val2->val.i; + tr_bencFree( &scrape ); + + return 0; +} diff --git a/libtransmission/tracker.h b/libtransmission/tracker.h new file mode 100644 index 000000000..2da90210d --- /dev/null +++ b/libtransmission/tracker.h @@ -0,0 +1,36 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef TR_TRACKER_H +#define TR_TRACKER_H 1 + +typedef struct tr_tracker_s tr_tracker_t; + +tr_tracker_t * tr_trackerInit ( tr_handle_t *, tr_torrent_t * ); +int tr_trackerPulse ( tr_tracker_t * ); +void tr_trackerCompleted ( tr_tracker_t * ); +void tr_trackerStopped ( tr_tracker_t * ); +void tr_trackerClose ( tr_tracker_t * ); + +int tr_trackerScrape ( tr_torrent_t *, int *, int * ); + +#endif diff --git a/libtransmission/transmission.c b/libtransmission/transmission.c new file mode 100644 index 000000000..2404d4fd7 --- /dev/null +++ b/libtransmission/transmission.c @@ -0,0 +1,541 @@ +/****************************************************************************** + * 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" + +/*********************************************************************** + * Local prototypes + **********************************************************************/ +static void downloadLoop( void * ); +static float rateDownload( tr_torrent_t * ); +static float rateUpload( tr_torrent_t * ); + +/*********************************************************************** + * tr_init + *********************************************************************** + * Allocates a tr_handle_t structure and initializes a few things + **********************************************************************/ +tr_handle_t * tr_init() +{ + tr_handle_t * h; + int i, r; + + h = calloc( sizeof( tr_handle_t ), 1 ); + + /* Generate a peer id : "-TRxxyy-" + 12 random alphanumeric + characters, where xx is the major version number and yy the + minor version number (Azureus-style) */ + sprintf( h->id, "-TR%02d%02d-", VERSION_MAJOR, VERSION_MINOR ); + for( i = 8; i < 20; i++ ) + { + r = tr_rand( 36 ); + h->id[i] = ( r < 26 ) ? ( 'a' + r ) : ( '0' + r - 26 ) ; + } + + /* Don't exit when writing on a broken socket */ + signal( SIGPIPE, SIG_IGN ); + + /* Initialize rate and file descripts controls */ + h->upload = tr_uploadInit(); + h->fdlimit = tr_fdInit(); + + h->bindPort = 9090; + + return h; +} + +/*********************************************************************** + * tr_setBindPort + *********************************************************************** + * + **********************************************************************/ +void tr_setBindPort( tr_handle_t * h, int port ) +{ + /* FIXME multithread safety */ + h->bindPort = port; +} + +/*********************************************************************** + * tr_setUploadLimit + *********************************************************************** + * + **********************************************************************/ +void tr_setUploadLimit( tr_handle_t * h, int limit ) +{ + tr_uploadSetLimit( h->upload, limit ); +} + +/*********************************************************************** + * tr_torrentRates + *********************************************************************** + * + **********************************************************************/ +void tr_torrentRates( tr_handle_t * h, float * dl, float * ul ) +{ + int i; + tr_torrent_t * tor; + + *dl = 0.0; + *ul = 0.0; + + for( i = 0; i < h->torrentCount; i++ ) + { + tor = h->torrents[i]; + tr_lockLock( tor->lock ); + *dl += rateDownload( tor ); + *ul += rateUpload( tor ); + tr_lockUnlock( tor->lock ); + } +} + +/*********************************************************************** + * tr_torrentInit + *********************************************************************** + * Allocates a tr_torrent_t structure, then relies on tr_metainfoParse + * to fill it. + **********************************************************************/ +int tr_torrentInit( tr_handle_t * h, const char * path ) +{ + tr_torrent_t * tor; + tr_info_t * inf; + int i; + char * s1, * s2; + + if( h->torrentCount >= TR_MAX_TORRENT_COUNT ) + { + tr_err( "Maximum number of torrents reached" ); + return 1; + } + + tor = calloc( sizeof( tr_torrent_t ), 1 ); + inf = &tor->info; + + /* Parse torrent file */ + if( tr_metainfoParse( inf, path ) ) + { + free( tor ); + return 1; + } + + /* Make sure this torrent is not already open */ + for( i = 0; i < h->torrentCount; i++ ) + { + if( !memcmp( tor->info.hash, h->torrents[i]->info.hash, + SHA_DIGEST_LENGTH ) ) + { + tr_err( "Torrent already open" ); + free( tor ); + return 1; + } + } + + tor->status = TR_STATUS_PAUSE; + tor->id = h->id; + + /* Guess scrape URL */ + s1 = strchr( inf->trackerAnnounce, '/' ); + while( ( s2 = strchr( s1 + 1, '/' ) ) ) + { + s1 = s2; + } + s1++; + if( !strncmp( s1, "announce", 8 ) ) + { + int pre = (long) s1 - (long) inf->trackerAnnounce; + int post = strlen( inf->trackerAnnounce ) - pre - 8; + memcpy( tor->scrape, inf->trackerAnnounce, pre ); + sprintf( &tor->scrape[pre], "scrape" ); + memcpy( &tor->scrape[pre+6], &inf->trackerAnnounce[pre+8], post ); + } + + /* Escaped info hash for HTTP queries */ + for( i = 0; i < SHA_DIGEST_LENGTH; i++ ) + { + sprintf( &tor->hashString[3*i], "%%%02x", inf->hash[i] ); + } + + /* Block size: usually 16 ko, or less if we have to */ + 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 ); + + tr_lockInit( &tor->lock ); + + tor->upload = h->upload; + tor->fdlimit = h->fdlimit; + + /* We have a new torrent */ + h->torrents[h->torrentCount] = tor; + (h->torrentCount)++; + + return 0; +} + +/*********************************************************************** + * tr_torrentScrape + *********************************************************************** + * Allocates a tr_torrent_t structure, then relies on tr_metainfoParse + * to fill it. + **********************************************************************/ +int tr_torrentScrape( tr_handle_t * h, int t, int * s, int * l ) +{ + return tr_trackerScrape( h->torrents[t], s, l ); +} + +void tr_torrentSetFolder( tr_handle_t * h, int t, const char * path ) +{ + tr_torrent_t * tor = h->torrents[t]; + + tor->destination = strdup( path ); +} + +char * tr_torrentGetFolder( tr_handle_t * h, int t ) +{ + tr_torrent_t * tor = h->torrents[t]; + + return tor->destination; +} + +void tr_torrentStart( tr_handle_t * h, int t ) +{ + tr_torrent_t * tor = h->torrents[t]; + uint64_t now; + int i; + + tor->status = TR_STATUS_CHECK; + tor->tracker = tr_trackerInit( h, tor ); + tor->bindPort = h->bindPort; +#ifndef BEOS_NETSERVER + /* BeOS net_server seems to be unable to set incoming connections to + non-blocking. Too bad. */ + if( !tr_fdSocketWillCreate( h->fdlimit, 0 ) ) + { + tor->bindSocket = tr_netBind( &tor->bindPort ); + } +#endif + + now = tr_date(); + for( i = 0; i < 10; i++ ) + { + tor->dates[i] = now; + } + + tor->die = 0; + tr_threadCreate( &tor->thread, downloadLoop, tor ); +} + +void tr_torrentStop( tr_handle_t * h, int t ) +{ + tr_torrent_t * tor = h->torrents[t]; + + tr_lockLock( tor->lock ); + tr_trackerStopped( tor->tracker ); + tor->status = TR_STATUS_STOPPING; + tr_lockUnlock( tor->lock ); +} + +/*********************************************************************** + * torrentReallyStop + *********************************************************************** + * Joins the download thread and frees/closes everything related to it. + **********************************************************************/ +static void torrentReallyStop( tr_handle_t * h, int t ) +{ + tr_torrent_t * tor = h->torrents[t]; + + tor->die = 1; + tr_threadJoin( tor->thread ); + tr_dbg( "Thread joined" ); + + tr_trackerClose( tor->tracker ); + + while( tor->peerCount > 0 ) + { + tr_peerRem( tor, 0 ); + } + if( tor->bindSocket > -1 ) + { + tr_netClose( tor->bindSocket ); + tr_fdSocketClosed( h->fdlimit, 0 ); + } + + memset( tor->downloaded, 0, sizeof( tor->downloaded ) ); + memset( tor->uploaded, 0, sizeof( tor->uploaded ) ); +} + +/*********************************************************************** + * tr_torrentCount + *********************************************************************** + * + **********************************************************************/ +int tr_torrentCount( tr_handle_t * h ) +{ + return h->torrentCount; +} + +int tr_torrentStat( tr_handle_t * h, tr_stat_t ** stat ) +{ + tr_stat_t * s; + tr_torrent_t * tor; + tr_info_t * inf; + int i, j, k, piece; + + if( h->torrentCount < 1 ) + { + *stat = NULL; + return 0; + } + + s = malloc( h->torrentCount * sizeof( tr_stat_t ) ); + + for( i = 0; i < h->torrentCount; i++ ) + { + tor = h->torrents[i]; + inf = &tor->info; + + tr_lockLock( tor->lock ); + + if( tor->status & TR_STATUS_STOPPED ) + { + torrentReallyStop( h, i ); + tor->status = TR_STATUS_PAUSE; + } + + 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 ) ); + + s[i].peersTotal = 0; + s[i].peersUploading = 0; + s[i].peersDownloading = 0; + + for( j = 0; j < tor->peerCount; j++ ) + { + if( tr_peerIsConnected( tor->peers[j] ) ) + { + (s[i].peersTotal)++; + if( tr_peerIsUploading( tor->peers[j] ) ) + { + (s[i].peersUploading)++; + } + if( tr_peerIsDownloading( tor->peers[j] ) ) + { + (s[i].peersDownloading)++; + } + } + } + + s[i].progress = (float) tor->blockHaveCount / (float) tor->blockCount; + + s[i].rateDownload = rateDownload( tor ); + s[i].rateUpload = rateUpload( tor ); + + if( s[i].rateDownload < 0.1 ) + { + s[i].eta = -1; + } + else + { + s[i].eta = (float) (tor->blockCount - tor->blockHaveCount ) * + (float) tor->blockSize / s[i].rateDownload / 1024.0; + if( s[i].eta > 99 * 3600 + 59 * 60 + 59 ) + { + s[i].eta = -1; + } + } + + for( j = 0; j < 120; j++ ) + { + piece = j * inf->pieceCount / 120; + + if( tr_bitfieldHas( tor->bitfield, piece ) ) + { + s[i].pieces[j] = -1; + continue; + } + + s[i].pieces[j] = 0; + + for( k = 0; k < tor->peerCount; k++ ) + { + if( tr_peerBitfield( tor->peers[k] ) && + tr_bitfieldHas( tr_peerBitfield( tor->peers[k] ), piece ) ) + { + (s[i].pieces[j])++; + } + } + } + + s[i].downloaded = tor->downloaded[9]; + s[i].uploaded = tor->uploaded[9]; + + s[i].folder = tor->destination; + + tr_lockUnlock( tor->lock ); + } + + *stat = s; + return h->torrentCount; +} + +/*********************************************************************** + * tr_torrentClose + *********************************************************************** + * Frees memory allocated by tr_torrentInit. + **********************************************************************/ +void tr_torrentClose( tr_handle_t * h, int t ) +{ + tr_torrent_t * tor = h->torrents[t]; + tr_info_t * inf = &tor->info; + + 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 ); + + if( tor->destination ) + { + free( tor->destination ); + } + free( inf->pieces ); + free( inf->files ); + free( tor->blockHave ); + free( tor->bitfield ); + free( tor ); + + memmove( &h->torrents[t], &h->torrents[t+1], + ( h->torrentCount - t ) * sizeof( void * ) ); +} + +void tr_close( tr_handle_t * h ) +{ + tr_fdClose( h->fdlimit ); + tr_uploadClose( h->upload ); + free( h ); +} + +/*********************************************************************** + * downloadLoop + **********************************************************************/ +static void downloadLoop( void * _tor ) +{ + tr_torrent_t * tor = _tor; + uint64_t date1, date2; + + tr_dbg( "Thread started" ); + +#ifdef SYS_BEOS + /* This is required because on BeOS, SIGINT is sent to each thread, + which kills them not nicely */ + signal( SIGINT, SIG_IGN ); +#endif + + tor->io = tr_ioInit( tor ); + tor->status = ( tor->blockHaveCount < tor->blockCount ) ? + TR_STATUS_DOWNLOAD : TR_STATUS_SEED; + + while( !tor->die ) + { + date1 = tr_date(); + + tr_lockLock( tor->lock ); + + /* Are we finished ? */ + if( ( tor->status & TR_STATUS_DOWNLOAD ) && + tor->blockHaveCount >= tor->blockCount ) + { + /* Done */ + tor->status = TR_STATUS_SEED; + tr_trackerCompleted( tor->tracker ); + } + + /* Receive/send messages */ + if( !( tor->status & TR_STATUS_STOPPING ) ) + { + tr_peerPulse( 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; + } + + /* Wait up to 20 ms */ + date2 = tr_date(); + if( date2 < date1 + 20 ) + { + tr_wait( date1 + 20 - date2 ); + } + } + + tr_ioClose( tor->io ); + + tor->status = TR_STATUS_STOPPED; + + tr_dbg( "Thread exited" ); +} + +/*********************************************************************** + * rateDownload, rateUpload + **********************************************************************/ +static float rateGeneric( uint64_t * dates, uint64_t * counts ) +{ + float ret; + int i; + + ret = 0.0; + for( i = 0; i < 9; i++ ) + { + if( dates[i+1] == dates[i] ) + { + continue; + } + ret += (float) ( i + 1 ) * 1000.0 / 1024.0 * + (float) ( counts[i+1] - counts[i] ) / + (float) ( dates[i+1] - dates[i] ); + } + ret *= 1000.0 / 1024.0 / 45.0; + + return ret; +} +static float rateDownload( tr_torrent_t * tor ) +{ + return rateGeneric( tor->dates, tor->downloaded ); +} +static float rateUpload( tr_torrent_t * tor ) +{ + return rateGeneric( tor->dates, tor->uploaded ); +} diff --git a/libtransmission/transmission.h b/libtransmission/transmission.h new file mode 100644 index 000000000..cd246a7fb --- /dev/null +++ b/libtransmission/transmission.h @@ -0,0 +1,210 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef TR_TRANSMISSION_H +#define TR_TRANSMISSION_H 1 + +#include + +#define SHA_DIGEST_LENGTH 20 +#define MAX_PATH_LENGTH 1024 +#define TR_MAX_TORRENT_COUNT 50 + +/*********************************************************************** + * tr_init + *********************************************************************** + * Initializes a libtransmission instance. Returns a obscure handle to + * be passed to all functions below. + **********************************************************************/ +typedef struct tr_handle_s tr_handle_t; + +tr_handle_t * tr_init(); + +/*********************************************************************** + * tr_setBindPort + *********************************************************************** + * Sets a "start" port: everytime we start a torrent, we try to bind + * this port, then the next one and so on until we are successful. + **********************************************************************/ +void tr_setBindPort( tr_handle_t *, int ); + +/*********************************************************************** + * tr_setUploadLimit + *********************************************************************** + * Sets the total upload rate limit in KB/s + **********************************************************************/ +void tr_setUploadLimit( tr_handle_t *, int ); + +/*********************************************************************** + * tr_torrentRates + *********************************************************************** + * Gets the total download and upload rates + **********************************************************************/ +void tr_torrentRates( tr_handle_t *, float *, float * ); + +/*********************************************************************** + * tr_torrentInit + *********************************************************************** + * Opens and parses torrent file at 'path'. If the file exists and is a + * valid torrent file, returns 0 and adds it to the list of torrents + * (but doesn't start it). Returns a non-zero value otherwise. + **********************************************************************/ +int tr_torrentInit( tr_handle_t *, const char * path ); + +/*********************************************************************** + * tr_torrentScrape + *********************************************************************** + * Asks the tracker for the count of seeders and leechers. Returns 0 + * and fills 's' and 'l' if successful. Otherwise returns 1 if the + * tracker doesn't support the scrape protocol, is unreachable or + * replied with some error. tr_torrentScrape may block up to 20 seconds + * before returning. + **********************************************************************/ +int tr_torrentScrape( tr_handle_t *, int, int * s, int * l ); + +/*********************************************************************** + * tr_torrentStart + *********************************************************************** + * Starts downloading. The download is launched in a seperate thread, + * therefore tr_torrentStart returns immediately. + **********************************************************************/ +void tr_torrentSetFolder( tr_handle_t *, int, const char * ); +char * tr_torrentGetFolder( tr_handle_t *, int ); +void tr_torrentStart( tr_handle_t *, int ); + +/*********************************************************************** + * tr_torrentStop + *********************************************************************** + * Stops downloading and notices the tracker that we are leaving. The + * thread keeps running while doing so. + * The thread will eventually be joined, either: + * - by tr_torrentStat when the tracker has been successfully noticed, + * - by tr_torrentStat if the tracker could not be noticed within 60s, + * - by tr_torrentClose if you choose to remove the torrent without + * waiting any further. + **********************************************************************/ +void tr_torrentStop( tr_handle_t *, int ); + +/*********************************************************************** + * tr_torrentStat + *********************************************************************** + * Allocates an array of tr_stat_t structures, containing information + * about the current status of all open torrents (see the contents + * of tr_stat_s below). Returns the count of open torrents and sets + * (*s) to the address of the array, or NULL if no torrent is open. + * In the former case, the array belongs to the caller who is + * responsible of freeing it. + * The interface should call this function every 0.5 second or so in + * order to update itself. + **********************************************************************/ +typedef struct tr_stat_s tr_stat_t; + +int tr_torrentCount( tr_handle_t * h ); +int tr_torrentStat( tr_handle_t *, tr_stat_t ** s ); + +/*********************************************************************** + * tr_torrentClose + *********************************************************************** + * Frees memory allocated by tr_torrentInit. If the torrent was running, + * you must call tr_torrentStop() before closing it. + **********************************************************************/ +void tr_torrentClose( tr_handle_t *, int ); + +/*********************************************************************** + * tr_close + *********************************************************************** + * Frees memory allocated by tr_init. + **********************************************************************/ +void tr_close( tr_handle_t * ); + + +/*********************************************************************** + * tr_stat_s + **********************************************************************/ +typedef struct tr_file_s +{ + uint64_t length; /* Length of the file, in bytes */ + char name[MAX_PATH_LENGTH]; /* Path to the file */ +} +tr_file_t; + +typedef struct tr_info_s +{ + /* Path to torrent */ + char torrent[MAX_PATH_LENGTH]; + + /* General info */ + uint8_t hash[SHA_DIGEST_LENGTH]; + char name[MAX_PATH_LENGTH]; + + /* Tracker info */ + char trackerAddress[256]; + int trackerPort; + char trackerAnnounce[MAX_PATH_LENGTH]; + + /* Pieces info */ + int pieceSize; + int pieceCount; + uint64_t totalSize; + uint8_t * pieces; + + /* Files info */ + int fileCount; + tr_file_t * files; +} +tr_info_t; + +struct tr_stat_s +{ + tr_info_t info; + +#define TR_STATUS_CHECK 0x001 /* Checking files */ +#define TR_STATUS_DOWNLOAD 0x002 /* Downloading */ +#define TR_STATUS_SEED 0x004 /* Seeding */ +#define TR_STATUS_STOPPING 0x008 /* Sending 'stopped' to the tracker */ +#define TR_STATUS_STOPPED 0x010 /* Sent 'stopped' but thread still + running (for internal use only) */ +#define TR_STATUS_PAUSE 0x020 /* Paused */ +#define TR_TRACKER_ERROR 0x100 + int status; + char error[128]; + + float progress; + float rateDownload; + float rateUpload; + int eta; + int peersTotal; + int peersUploading; + int peersDownloading; + char pieces[120]; + + uint64_t downloaded; + uint64_t uploaded; + + char * folder; +}; + +#ifdef __TRANSMISSION__ +# include "internal.h" +#endif + +#endif diff --git a/libtransmission/upload.c b/libtransmission/upload.c new file mode 100644 index 000000000..de1b4bb79 --- /dev/null +++ b/libtransmission/upload.c @@ -0,0 +1,136 @@ +/****************************************************************************** + * 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" + +#define FOO 10 + +struct tr_upload_s +{ + tr_lock_t lock; + int limit; /* Max upload rate in KB/s */ + int count; /* Number of peers currently unchoked */ + uint64_t dates[FOO]; /* The last times we uploaded something */ + int sizes[FOO]; /* How many bytes we uploaded */ +}; + +tr_upload_t * tr_uploadInit() +{ + tr_upload_t * u; + + u = calloc( sizeof( tr_upload_t ), 1 ); + tr_lockInit( &u->lock ); + + return u; +} + +void tr_uploadSetLimit( tr_upload_t * u, int limit ) +{ + tr_lockLock( u->lock ); + u->limit = limit; + tr_lockUnlock( u->lock ); +} + +int tr_uploadCanUnchoke( tr_upload_t * u ) +{ + int ret; + + tr_lockLock( u->lock ); + if( u->limit < 0 ) + { + /* Infinite number of slots */ + ret = 1; + } + else + { + /* One slot per 2 KB/s */ + ret = ( u->count < ( u->limit + 1 ) / 2 ); + } + tr_lockUnlock( u->lock ); + + return ret; +} + +void tr_uploadChoked( tr_upload_t * u ) +{ + tr_lockLock( u->lock ); + (u->count)--; + tr_lockUnlock( u->lock ); +} + +void tr_uploadUnchoked( tr_upload_t * u ) +{ + tr_lockLock( u->lock ); + (u->count)++; + tr_lockUnlock( u->lock ); +} + +int tr_uploadCanUpload( tr_upload_t * u ) +{ + int ret, i, size; + uint64_t now; + + tr_lockLock( u->lock ); + if( u->limit < 0 ) + { + /* No limit */ + ret = 1; + } + else + { + ret = 0; + size = 0; + now = tr_date(); + + /* Check the last four times we sent something and decide if + we must wait */ + for( i = 0; i < FOO; i++ ) + { + size += u->sizes[i]; + if( (uint64_t) size < 1024ULL * + u->limit * ( now - u->dates[i] ) / 1000 ) + { + ret = 1; + break; + } + } + } + tr_lockUnlock( u->lock ); + + return ret; +} + +void tr_uploadUploaded( tr_upload_t * u, int size ) +{ + tr_lockLock( u->lock ); + memmove( &u->dates[1], &u->dates[0], (FOO-1) * sizeof( uint64_t ) ); + memmove( &u->sizes[1], &u->sizes[0], (FOO-1) * sizeof( int ) ); + u->dates[0] = tr_date(); + u->sizes[0] = size; + tr_lockUnlock( u->lock ); +} + +void tr_uploadClose( tr_upload_t * u ) +{ + tr_lockClose( u->lock ); + free( u ); +} diff --git a/libtransmission/upload.h b/libtransmission/upload.h new file mode 100644 index 000000000..21c10b905 --- /dev/null +++ b/libtransmission/upload.h @@ -0,0 +1,32 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +typedef struct tr_upload_s tr_upload_t; + +tr_upload_t * tr_uploadInit(); +void tr_uploadSetLimit( tr_upload_t *, int ); +int tr_uploadCanUnchoke( tr_upload_t * ); +void tr_uploadChoked( tr_upload_t * ); +void tr_uploadUnchoked( tr_upload_t * ); +int tr_uploadCanUpload( tr_upload_t * ); +void tr_uploadUploaded( tr_upload_t *, int ); +void tr_uploadClose( tr_upload_t * ); diff --git a/libtransmission/utils.c b/libtransmission/utils.c new file mode 100644 index 000000000..35c2db108 --- /dev/null +++ b/libtransmission/utils.c @@ -0,0 +1,63 @@ +/****************************************************************************** + * 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" + +void tr_msg( int level, char * msg, ... ) +{ + char string[256]; + va_list args; + static int verboseLevel = 0; + + if( !verboseLevel ) + { + char * env; + env = getenv( "TR_DEBUG" ); + verboseLevel = env ? atoi( env ) : -1; + verboseLevel = verboseLevel ? verboseLevel : -1; + } + + if( verboseLevel < 1 && level > TR_MSG_ERR ) + { + return; + } + if( verboseLevel < 2 && level > TR_MSG_INF ) + { + return; + } + + va_start( args, msg ); + vsnprintf( string, sizeof( string ), msg, args ); + va_end( args ); + fprintf( stderr, "%s\n", string ); +} + +int tr_rand( int sup ) +{ + static int init = 0; + if( !init ) + { + srand( tr_date() ); + init = 1; + } + return rand() % sup; +} diff --git a/libtransmission/utils.h b/libtransmission/utils.h new file mode 100644 index 000000000..e99aafda1 --- /dev/null +++ b/libtransmission/utils.h @@ -0,0 +1,147 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +#ifndef TR_UTILS_H +#define TR_UTILS_H 1 + +#define TR_MSG_ERR 1 +#define TR_MSG_INF 2 +#define TR_MSG_DBG 4 +#define tr_err( a... ) tr_msg( TR_MSG_ERR, ## a ) +#define tr_inf( a... ) tr_msg( TR_MSG_INF, ## a ) +#define tr_dbg( a... ) tr_msg( TR_MSG_DBG, ## a ) +void tr_msg ( int level, char * msg, ... ); + +int tr_rand ( int ); + +/*********************************************************************** + * tr_date + *********************************************************************** + * Returns the current date in milliseconds + **********************************************************************/ +static inline uint64_t tr_date() +{ + struct timeval tv; + gettimeofday( &tv, NULL ); + return( (uint64_t) tv.tv_sec * 1000 + (uint64_t) tv.tv_usec / 1000 ); +} + +/*********************************************************************** + * tr_wait + *********************************************************************** + * Wait 'delay' milliseconds + **********************************************************************/ +static inline void tr_wait( uint64_t delay ) +{ +#ifdef SYS_BEOS + snooze( 1000 * delay ); +#else + usleep( 1000 * delay ); +#endif +} + +/*********************************************************************** + * tr_bitfieldHas + **********************************************************************/ +static inline int tr_bitfieldHas( uint8_t * bitfield, int piece ) +{ + return ( bitfield[ piece / 8 ] & ( 1 << ( 7 - ( piece % 8 ) ) ) ); +} + +/*********************************************************************** + * tr_bitfieldAdd + **********************************************************************/ +static inline void tr_bitfieldAdd( 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 ) +{ + tr_info_t * inf = &tor->info; + return block / ( inf->pieceSize / tor->blockSize ); +} + +#define tr_blockSize(a) _tr_blockSize(tor,a) +static inline int _tr_blockSize( tr_torrent_t * tor, int block ) +{ + tr_info_t * inf = &tor->info; + int dummy; + + if( block != tor->blockCount - 1 || + !( dummy = inf->totalSize % tor->blockSize ) ) + { + return tor->blockSize; + } + + return dummy; +} + +#define tr_blockPosInPiece(a) _tr_blockPosInPiece(tor,a) +static inline int _tr_blockPosInPiece( tr_torrent_t * tor, int block ) +{ + tr_info_t * inf = &tor->info; + return tor->blockSize * + ( block % ( inf->pieceSize / tor->blockSize ) ); +} + +#define tr_pieceCountBlocks(a) _tr_pieceCountBlocks(tor,a) +static inline int _tr_pieceCountBlocks( tr_torrent_t * tor, int piece ) +{ + tr_info_t * inf = &tor->info; + if( piece < inf->pieceCount - 1 || + !( tor->blockCount % ( inf->pieceSize / tor->blockSize ) ) ) + { + return inf->pieceSize / tor->blockSize; + } + return tor->blockCount % ( inf->pieceSize / tor->blockSize ); +} + +#define tr_pieceStartBlock(a) _tr_pieceStartBlock(tor,a) +static inline int _tr_pieceStartBlock( tr_torrent_t * tor, int piece ) +{ + tr_info_t * inf = &tor->info; + return piece * ( inf->pieceSize / tor->blockSize ); +} + +#define tr_pieceSize(a) _tr_pieceSize(tor,a) +static inline int _tr_pieceSize( tr_torrent_t * tor, int piece ) +{ + tr_info_t * inf = &tor->info; + if( piece < inf->pieceCount - 1 || + !( inf->totalSize % inf->pieceSize ) ) + { + return inf->pieceSize; + } + return inf->totalSize % inf->pieceSize; +} + +#define tr_block(a,b) _tr_block(tor,a,b) +static inline int _tr_block( tr_torrent_t * tor, int index, int begin ) +{ + tr_info_t * inf = &tor->info; + return index * ( inf->pieceSize / tor->blockSize ) + + begin / tor->blockSize; +} + +#endif diff --git a/macosx/Controller.h b/macosx/Controller.h new file mode 100644 index 000000000..8f675fbbb --- /dev/null +++ b/macosx/Controller.h @@ -0,0 +1,78 @@ +/****************************************************************************** + * 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 +#include +#include "PrefsController.h" + +@interface Controller : NSObject +{ + tr_handle_t * fHandle; + int fCount; + tr_stat_t * fStat; + int fResumeOnWake[TR_MAX_TORRENT_COUNT]; + + NSToolbar * fToolbar; + + IBOutlet PrefsController * fPrefsController; + + IBOutlet NSMenuItem * fAdvancedBarItem; + + IBOutlet NSWindow * fWindow; + IBOutlet NSTableView * fTableView; + IBOutlet NSTextField * fTotalDLField; + IBOutlet NSTextField * fTotalULField; + + IBOutlet NSPanel * fInfoPanel; + IBOutlet NSTextField * fInfoTitle; + IBOutlet NSTextField * fInfoTracker; + IBOutlet NSTextField * fInfoAnnounce; + IBOutlet NSTextField * fInfoSize; + IBOutlet NSTextField * fInfoPieces; + IBOutlet NSTextField * fInfoPieceSize; + IBOutlet NSTextField * fInfoFolder; + IBOutlet NSTextField * fInfoDownloaded; + IBOutlet NSTextField * fInfoUploaded; + + io_connect_t fRootPort; + NSArray * fFilenames; + NSTimer * fTimer; +} + +- (void) advancedChanged: (id) sender; +- (void) openShowSheet: (id) sender; +- (void) openSheetClosed: (NSOpenPanel *) s returnCode: (int) code + contextInfo: (void *) info; +- (void) stopTorrent: (id) sender; +- (void) resumeTorrent: (id) sender; +- (void) removeTorrent: (id) sender; +- (void) showInfo: (id) sender; + +- (void) updateUI: (NSTimer *) timer; +- (void) sleepCallBack: (natural_t) messageType argument: + (void *) messageArgument; + +- (void) showMainWindow: (id) sender; +- (void) linkHomepage: (id) sender; +- (void) linkForums: (id) sender; + +@end diff --git a/macosx/Controller.m b/macosx/Controller.m new file mode 100644 index 000000000..762b7510e --- /dev/null +++ b/macosx/Controller.m @@ -0,0 +1,705 @@ +/****************************************************************************** + * 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 + +#include "Controller.h" +#include "NameCell.h" +#include "ProgressCell.h" +#include "Utils.h" + +#define TOOLBAR_OPEN @"Toolbar Open" +#define TOOLBAR_RESUME @"Toolbar Resume" +#define TOOLBAR_STOP @"Toolbar Stop" +#define TOOLBAR_REMOVE @"Toolbar Remove" +#define TOOLBAR_INFO @"Toolbar Info" + +static void sleepCallBack( void * controller, io_service_t y, + natural_t messageType, void * messageArgument ) +{ + Controller * c = controller; + [c sleepCallBack: messageType argument: messageArgument]; +} + +@implementation Controller + +- (void) enableToolbarItem: (NSString *) ident flag: (BOOL) e +{ + NSArray * array = [fToolbar items]; + NSToolbarItem * item; + + if( [ident isEqualToString: TOOLBAR_OPEN] ) + { + item = [array objectAtIndex: 0]; + [item setAction: e ? @selector( openShowSheet: ) : NULL]; + } + else if( [ident isEqualToString: TOOLBAR_RESUME] ) + { + item = [array objectAtIndex: 1]; + [item setAction: e ? @selector( resumeTorrent: ) : NULL]; + } + else if( [ident isEqualToString: TOOLBAR_STOP] ) + { + item = [array objectAtIndex: 2]; + [item setAction: e ? @selector( stopTorrent: ) : NULL]; + } + else if( [ident isEqualToString: TOOLBAR_REMOVE] ) + { + item = [array objectAtIndex: 3]; + [item setAction: e ? @selector( removeTorrent: ) : NULL]; + } + else if( [ident isEqualToString: TOOLBAR_INFO] ) + { + item = [array objectAtIndex: 5]; + [item setAction: e ? @selector( showInfo: ) : NULL]; + } +} + +- (void) updateToolbar +{ + int row = [fTableView selectedRow]; + + [self enableToolbarItem: TOOLBAR_RESUME flag: NO]; + [self enableToolbarItem: TOOLBAR_STOP flag: NO]; + [self enableToolbarItem: TOOLBAR_REMOVE flag: NO]; + + if( row < 0 ) + { + return; + } + + if( fStat[row].status & + ( TR_STATUS_CHECK | TR_STATUS_DOWNLOAD | TR_STATUS_SEED ) ) + { + [self enableToolbarItem: TOOLBAR_STOP flag: YES]; + } + else + { + [self enableToolbarItem: TOOLBAR_RESUME flag: YES]; + [self enableToolbarItem: TOOLBAR_REMOVE flag: YES]; + } +} + +- (void) awakeFromNib +{ + fHandle = tr_init(); + + [fPrefsController setHandle: fHandle]; + + [fWindow setContentMinSize: NSMakeSize( 400, 120 )]; + + /* Check or uncheck menu item in respect to current preferences */ + [fAdvancedBarItem setState: [[NSUserDefaults standardUserDefaults] + boolForKey:@"UseAdvancedBar"] ? NSOnState : NSOffState]; + + fToolbar = [[NSToolbar alloc] initWithIdentifier: @"Transmission Toolbar"]; + [fToolbar setDelegate: self]; + [fToolbar setAllowsUserCustomization: YES]; + [fToolbar setAutosavesConfiguration: YES]; + [fWindow setToolbar: fToolbar]; + [fWindow setDelegate: self]; + + [self enableToolbarItem: TOOLBAR_OPEN flag: YES]; + [self enableToolbarItem: TOOLBAR_RESUME flag: NO]; + [self enableToolbarItem: TOOLBAR_STOP flag: NO]; + [self enableToolbarItem: TOOLBAR_REMOVE flag: NO]; + [self enableToolbarItem: TOOLBAR_INFO flag: YES]; + + [fTableView setDataSource: self]; + [fTableView setDelegate: self]; + + NSTableColumn * tableColumn; + NameCell * nameCell; + ProgressCell * progressCell; + + nameCell = [[NameCell alloc] init]; + progressCell = [[ProgressCell alloc] init]; + tableColumn = [fTableView tableColumnWithIdentifier: @"Name"]; + [tableColumn setDataCell: nameCell]; + [tableColumn setMinWidth: 10.0]; + [tableColumn setMaxWidth: 3000.0]; + + tableColumn = [fTableView tableColumnWithIdentifier: @"Progress"]; + [tableColumn setDataCell: progressCell]; + [tableColumn setMinWidth: 134.0]; + [tableColumn setMaxWidth: 134.0]; + + [fTableView sizeToFit]; + + [fTableView registerForDraggedTypes: [NSArray arrayWithObjects: + NSFilenamesPboardType, NULL]]; + + IONotificationPortRef notify; + io_object_t anIterator; + + /* Register for sleep notifications */ + fRootPort = IORegisterForSystemPower( self, ¬ify, sleepCallBack, + &anIterator); + if( !fRootPort ) + { + printf( "Could not IORegisterForSystemPower\n" ); + } + else + { + CFRunLoopAddSource( CFRunLoopGetCurrent(), + IONotificationPortGetRunLoopSource( notify ), + kCFRunLoopCommonModes ); + } + + NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults]; + + NSArray * history = [defaults arrayForKey: @"History"]; + if( history ) + { + unsigned i; + NSDictionary * dic; + NSString * torrentPath, * downloadFolder, * paused; + + for( i = 0; i < [history count]; i++ ) + { + dic = [history objectAtIndex: i]; + + torrentPath = [dic objectForKey: @"TorrentPath"]; + downloadFolder = [dic objectForKey: @"DownloadFolder"]; + paused = [dic objectForKey: @"Paused"]; + + if( !torrentPath || !downloadFolder || !paused ) + { + continue; + } + + if( tr_torrentInit( fHandle, [torrentPath UTF8String] ) ) + { + continue; + } + + tr_torrentSetFolder( fHandle, tr_torrentCount( fHandle ) - 1, + [downloadFolder UTF8String] ); + + if( [paused isEqualToString: @"NO"] ) + { + tr_torrentStart( fHandle, tr_torrentCount( fHandle ) - 1 ); + } + } + } + + /* Update the interface every 500 ms */ + fCount = 0; + fStat = NULL; + fTimer = [NSTimer scheduledTimerWithTimeInterval: 0.5 target: self + selector: @selector( updateUI: ) userInfo: NULL repeats: YES]; + [[NSRunLoop currentRunLoop] addTimer: fTimer + forMode: NSModalPanelRunLoopMode]; +} + +- (BOOL) windowShouldClose: (id) sender +{ + [fWindow orderOut: NULL]; + return NO; +} + +- (BOOL) applicationShouldHandleReopen: (NSApplication *) app + hasVisibleWindows: (BOOL) flag +{ + [self showMainWindow: NULL]; + return NO; +} + +- (NSApplicationTerminateReply) applicationShouldTerminate: + (NSApplication *) app +{ + NSMutableArray * history = [NSMutableArray + arrayWithCapacity: TR_MAX_TORRENT_COUNT]; + int i; + + /* Stop updating the interface */ + [fTimer invalidate]; + + /* Save history and stop running torrents */ + for( i = 0; i < fCount; i++ ) + { + [history addObject: [NSDictionary dictionaryWithObjectsAndKeys: + [NSString stringWithUTF8String: fStat[i].info.torrent], + @"TorrentPath", + [NSString stringWithUTF8String: tr_torrentGetFolder( fHandle, i )], + @"DownloadFolder", + ( fStat[i].status & ( TR_STATUS_CHECK | TR_STATUS_DOWNLOAD | + TR_STATUS_SEED ) ) ? @"NO" : @"YES", + @"Paused", + NULL]]; + + if( fStat[i].status & ( TR_STATUS_CHECK | + TR_STATUS_DOWNLOAD | TR_STATUS_SEED ) ) + { + tr_torrentStop( fHandle, i ); + } + } + + /* Wait for torrents to stop (5 seconds timeout) */ + NSDate * start = [NSDate date]; + while( fCount > 0 ) + { + while( [[NSDate date] timeIntervalSinceDate: start] < 5 ) + { + fCount = tr_torrentStat( fHandle, &fStat ); + if( fStat[0].status & TR_STATUS_PAUSE ) + { + break; + } + usleep( 500000 ); + } + tr_torrentClose( fHandle, 0 ); + fCount = tr_torrentStat( fHandle, &fStat ); + } + + tr_close( fHandle ); + + [[NSUserDefaults standardUserDefaults] + setObject: history forKey: @"History"]; + + return NSTerminateNow; +} + +- (void) folderChoiceClosed: (NSOpenPanel *) s returnCode: (int) code + contextInfo: (void *) info +{ + if( code != NSOKButton ) + { + tr_torrentClose( fHandle, tr_torrentCount( fHandle ) - 1 ); + [NSApp stopModal]; + return; + } + + tr_torrentSetFolder( fHandle, tr_torrentCount( fHandle ) - 1, + [[[s filenames] objectAtIndex: 0] UTF8String] ); + tr_torrentStart( fHandle, tr_torrentCount( fHandle ) - 1 ); + [NSApp stopModal]; +} + + +- (void) application: (NSApplication *) sender + openFiles: (NSArray *) filenames +{ + unsigned i; + NSUserDefaults * defaults; + NSString * downloadChoice, * downloadFolder, * torrentPath; + + defaults = [NSUserDefaults standardUserDefaults]; + downloadChoice = [defaults stringForKey: @"DownloadChoice"]; + downloadFolder = [defaults stringForKey: @"DownloadFolder"]; + + for( i = 0; i < [filenames count]; i++ ) + { + torrentPath = [filenames objectAtIndex: i]; + + if( tr_torrentInit( fHandle, [torrentPath UTF8String] ) ) + { + continue; + } + + if( [downloadChoice isEqualToString: @"Constant"] ) + { + tr_torrentSetFolder( fHandle, tr_torrentCount( fHandle ) - 1, + [downloadFolder UTF8String] ); + tr_torrentStart( fHandle, tr_torrentCount( fHandle ) - 1 ); + continue; + } + + if( [downloadChoice isEqualToString: @"Torrent"] ) + { + tr_torrentSetFolder( fHandle, tr_torrentCount( fHandle ) - 1, + [[torrentPath stringByDeletingLastPathComponent] UTF8String] ); + tr_torrentStart( fHandle, tr_torrentCount( fHandle ) - 1 ); + continue; + } + + NSOpenPanel * panel; + NSString * message; + + panel = [NSOpenPanel openPanel]; + message = [NSString stringWithFormat: + @"Select the download folder for %@", + [torrentPath lastPathComponent]]; + + [panel setPrompt: @"Select"]; + [panel setMessage: message]; + [panel setAllowsMultipleSelection: NO]; + [panel setCanChooseFiles: NO]; + [panel setCanChooseDirectories: YES]; + + [panel beginSheetForDirectory: NULL file: NULL types: NULL + modalForWindow: fWindow modalDelegate: self didEndSelector: + @selector( folderChoiceClosed:returnCode:contextInfo: ) + contextInfo: NULL]; + [NSApp runModalForWindow: panel]; + } + + [self updateUI: NULL]; +} + +- (void) advancedChanged: (id) sender +{ + NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults]; + if( [fAdvancedBarItem state] == NSOnState ) + { + [fAdvancedBarItem setState: NSOffState]; + [defaults setObject:@"NO" forKey:@"UseAdvancedBar"]; + } + else + { + [fAdvancedBarItem setState: NSOnState]; + [defaults setObject:@"YES" forKey:@"UseAdvancedBar"]; + } +} + +- (void) openShowSheet: (id) sender +{ + NSOpenPanel * panel; + NSArray * fileTypes; + + panel = [NSOpenPanel openPanel]; + fileTypes = [NSArray arrayWithObject: @"torrent"]; + + [panel setAllowsMultipleSelection: YES]; + [panel setCanChooseFiles: YES]; + [panel setCanChooseDirectories: NO]; + + [panel beginSheetForDirectory: NULL file: NULL types: fileTypes + modalForWindow: fWindow modalDelegate: self didEndSelector: + @selector( openSheetClosed:returnCode:contextInfo: ) + contextInfo: NULL]; +} + +- (void) cantFindAName: (id) sender +{ + [self application: NSApp openFiles: fFilenames]; + [fFilenames release]; +} + +- (void) openSheetClosed: (NSOpenPanel *) s returnCode: (int) code + contextInfo: (void *) info +{ + if( code != NSOKButton ) + { + return; + } + + fFilenames = [[s filenames] retain]; + + [self performSelectorOnMainThread: @selector(cantFindAName:) + withObject: NULL waitUntilDone: NO]; +} + +- (void) resumeTorrent: (id) sender +{ + tr_torrentStart( fHandle, [fTableView selectedRow] ); + [self updateToolbar]; +} + +- (void) stopTorrent: (id) sender +{ + tr_torrentStop( fHandle, [fTableView selectedRow] ); + [self updateToolbar]; +} + +- (void) removeTorrent: (id) sender +{ + tr_torrentClose( fHandle, [fTableView selectedRow] ); + [self updateUI: NULL]; +} + +- (void) showInfo: (id) sender +{ + if( [fInfoPanel isVisible] ) + { + [fInfoPanel close]; + } + else + { + [fInfoPanel orderFront: sender]; + } +} + +- (void) updateUI: (NSTimer *) t +{ + float dl, ul; + int row; + + /* Update the NSTableView */ + if( fStat ) + { + free( fStat ); + } + fCount = tr_torrentStat( fHandle, &fStat ); + [fTableView reloadData]; + + /* Update the global DL/UL rates */ + tr_torrentRates( fHandle, &dl, &ul ); + [fTotalDLField setStringValue: [NSString stringWithFormat: + @"Total DL: %.2f KB/s", dl]]; + [fTotalULField setStringValue: [NSString stringWithFormat: + @"Total UL: %.2f KB/s", ul]]; + + /* Update DL/UL totals in the Info panel */ + row = [fTableView selectedRow]; + if( row > -1 ) + { + [fInfoDownloaded setStringValue: + stringForFileSize( fStat[row].downloaded )]; + [fInfoUploaded setStringValue: + stringForFileSize( fStat[row].uploaded )]; + } + + /* Must we do this? Can't remember */ + [self updateToolbar]; +} + +- (int) numberOfRowsInTableView: (NSTableView *) t +{ + return fCount; +} + +- (id) tableView: (NSTableView *) t objectValueForTableColumn: + (NSTableColumn *) tableColumn row: (int) rowIndex +{ + return NULL; +} + +- (void) tableView: (NSTableView *) t willDisplayCell: (id) cell + forTableColumn: (NSTableColumn *) tableColumn row: (int) rowIndex +{ + if( [[tableColumn identifier] isEqualToString: @"Name"] ) + { + [(NameCell *) cell setStat: &fStat[rowIndex]]; + } + else if( [[tableColumn identifier] isEqualToString: @"Progress"] ) + { + [(ProgressCell *) cell setStat: &fStat[rowIndex]]; + } +} + +- (BOOL) tableView: (NSTableView *) t acceptDrop: + (id ) info row: (int) row dropOperation: + (NSTableViewDropOperation) operation +{ + NSPasteboard * pasteboard; + + pasteboard = [info draggingPasteboard]; + if( ![[pasteboard types] containsObject: NSFilenamesPboardType] ) + { + return NO; + } + + [self application: NSApp openFiles: + [pasteboard propertyListForType: NSFilenamesPboardType]]; + + return YES; +} + +- (NSDragOperation) tableView: (NSTableView *) t validateDrop: + (id ) info proposedRow: (int) row + proposedDropOperation: (NSTableViewDropOperation) operation +{ + return NSDragOperationGeneric; +} + +- (void) tableViewSelectionDidChange: (NSNotification *) n +{ + int row = [fTableView selectedRow]; + + [self updateToolbar]; + + if( row < 0 ) + { + [fInfoTitle setStringValue: @"No torrent selected"]; + [fInfoTracker setStringValue: @""]; + [fInfoAnnounce setStringValue: @""]; + [fInfoSize setStringValue: @""]; + [fInfoPieces setStringValue: @""]; + [fInfoPieceSize setStringValue: @""]; + [fInfoFolder setStringValue: @""]; + [fInfoDownloaded setStringValue: @""]; + [fInfoUploaded setStringValue: @""]; + return; + } + + /* Update info window */ + [fInfoTitle setStringValue: [NSString stringWithCString: + fStat[row].info.name]]; + [fInfoTracker setStringValue: [NSString stringWithFormat: + @"%s:%d", fStat[row].info.trackerAddress, fStat[row].info.trackerPort]]; + [fInfoAnnounce setStringValue: [NSString stringWithCString: + fStat[row].info.trackerAnnounce]]; + [fInfoSize setStringValue: + stringForFileSize( fStat[row].info.totalSize )]; + [fInfoPieces setStringValue: [NSString stringWithFormat: @"%d", + fStat[row].info.pieceCount]]; + [fInfoPieceSize setStringValue: + stringForFileSize( fStat[row].info.pieceSize )]; + [fInfoFolder setStringValue: [[NSString stringWithUTF8String: + tr_torrentGetFolder( fHandle, row )] lastPathComponent]]; +} + +- (NSToolbarItem *) toolbar: (NSToolbar *) t itemForItemIdentifier: + (NSString *) ident willBeInsertedIntoToolbar: (BOOL) flag +{ + NSToolbarItem * item; + item = [[NSToolbarItem alloc] initWithItemIdentifier: ident]; + + [item setTarget: self]; + + if( [ident isEqualToString: TOOLBAR_OPEN] ) + { + [item setLabel: @"Open"]; + [item setToolTip: @"Open a torrent"]; + [item setImage: [NSImage imageNamed: @"Open.tiff"]]; + } + else if( [ident isEqualToString: TOOLBAR_RESUME] ) + { + [item setLabel: @"Resume"]; + [item setToolTip: @"Resume download"]; + [item setImage: [NSImage imageNamed: @"Resume.tiff"]]; + } + else if( [ident isEqualToString: TOOLBAR_STOP] ) + { + [item setLabel: @"Stop"]; + [item setToolTip: @"Stop download"]; + [item setImage: [NSImage imageNamed: @"Stop.tiff"]]; + } + else if( [ident isEqualToString: TOOLBAR_REMOVE] ) + { + [item setLabel: @"Remove"]; + [item setToolTip: @"Remove torrent from list"]; + [item setImage: [NSImage imageNamed: @"Remove.tiff"]]; + } + else if( [ident isEqualToString: TOOLBAR_INFO] ) + { + [item setLabel: @"Info"]; + [item setToolTip: @"Information"]; + [item setImage: [NSImage imageNamed: @"Info.tiff"]]; + } + else + { + [item release]; + return NULL; + } + + return item; +} + +- (NSArray *) toolbarAllowedItemIdentifiers: (NSToolbar *) t +{ + return [NSArray arrayWithObjects: + TOOLBAR_OPEN, TOOLBAR_RESUME, TOOLBAR_STOP, TOOLBAR_REMOVE, + NSToolbarFlexibleSpaceItemIdentifier, TOOLBAR_INFO, NULL]; +} + +- (NSArray *) toolbarDefaultItemIdentifiers: (NSToolbar *) t +{ + return [self toolbarAllowedItemIdentifiers: t]; +} + +- (void) sleepCallBack: (natural_t) messageType argument: + (void *) messageArgument +{ + int i; + + switch( messageType ) + { + case kIOMessageSystemWillSleep: + /* Close all connections before going to sleep and remember + we should resume when we wake up */ + for( i = 0; i < fCount; i++ ) + { + if( fStat[i].status & ( TR_STATUS_CHECK | + TR_STATUS_DOWNLOAD | TR_STATUS_SEED ) ) + { + tr_torrentStop( fHandle, i ); + fResumeOnWake[i] = 1; + } + else + { + fResumeOnWake[i] = 0; + } + } + + /* TODO: wait a few seconds to let the torrents + stop properly */ + + IOAllowPowerChange( fRootPort, (long) messageArgument ); + break; + + case kIOMessageCanSystemSleep: + /* Do not prevent idle sleep */ + /* TODO: prevent it unless there are all paused? */ + IOAllowPowerChange( fRootPort, (long) messageArgument ); + break; + + case kIOMessageSystemHasPoweredOn: + /* Resume download after we wake up */ + for( i = 0; i < fCount; i++ ) + { + if( fResumeOnWake[i] ) + { + tr_torrentStart( fHandle, i ); + } + } + [self updateToolbar]; + break; + } +} + +- (NSRect) windowWillUseStandardFrame: (NSWindow *) w + defaultFrame: (NSRect) defaultFrame +{ + NSRect rectWin, rectView; + float foo; + + rectWin = [fWindow frame]; + rectView = [[fWindow contentView] frame]; + foo = 68.0 + MAX( 1, tr_torrentCount( fHandle ) ) * 62.0 - + rectView.size.height; + + rectWin.size.height += foo; + rectWin.origin.y -= foo; + + return rectWin; +} + +- (void) showMainWindow: (id) sender +{ + [fWindow makeKeyAndOrderFront: NULL]; +} + +- (void) linkHomepage: (id) sender +{ + [[NSWorkspace sharedWorkspace] openURL: [NSURL + URLWithString:@"http://transmission.m0k.org/"]]; +} + +- (void) linkForums: (id) sender +{ + [[NSWorkspace sharedWorkspace] openURL: [NSURL + URLWithString:@"http://transmission.m0k.org/forum/"]]; +} + +@end diff --git a/macosx/English.lproj/InfoPlist.strings b/macosx/English.lproj/InfoPlist.strings new file mode 100644 index 000000000..3aa7860a1 --- /dev/null +++ b/macosx/English.lproj/InfoPlist.strings @@ -0,0 +1,4 @@ +/* Localized versions of Info.plist keys */ + +CFBundleName = "Transmission"; +NSHumanReadableCopyright = "Copyright 2005 Eric Petit"; diff --git a/macosx/English.lproj/MainMenu.nib/classes.nib b/macosx/English.lproj/MainMenu.nib/classes.nib new file mode 100644 index 000000000..481d84c75 --- /dev/null +++ b/macosx/English.lproj/MainMenu.nib/classes.nib @@ -0,0 +1,55 @@ +{ + IBClasses = ( + { + ACTIONS = { + advancedChanged = id; + linkForums = id; + linkHomepage = id; + openShowSheet = id; + removeTorrent = id; + resumeTorrent = id; + showInfo = id; + showMainWindow = id; + stopTorrent = id; + }; + CLASS = Controller; + LANGUAGE = ObjC; + OUTLETS = { + fAdvancedBarItem = NSMenuItem; + fInfoAnnounce = NSTextField; + fInfoDownloaded = NSTextField; + fInfoFolder = NSTextField; + fInfoPanel = NSPanel; + fInfoPieceSize = NSTextField; + fInfoPieces = NSTextField; + fInfoSize = NSTextField; + fInfoTitle = NSTextField; + fInfoTracker = NSTextField; + fInfoUploaded = NSTextField; + fPrefsController = PrefsController; + fTableView = NSTableView; + fTotalDLField = NSTextField; + fTotalULField = NSTextField; + fWindow = NSWindow; + }; + SUPERCLASS = NSObject; + }, + {CLASS = FirstResponder; LANGUAGE = ObjC; SUPERCLASS = NSObject; }, + { + ACTIONS = {cancel = id; check = id; ratio = id; save = id; show = id; }; + CLASS = PrefsController; + LANGUAGE = ObjC; + OUTLETS = { + fFolderMatrix = NSMatrix; + fFolderPopUp = NSPopUpButton; + fPortField = NSTextField; + fPrefsWindow = NSWindow; + fUploadCheck = NSButton; + fUploadField = NSTextField; + fWindow = NSWindow; + }; + SUPERCLASS = NSObject; + } + ); + IBVersion = 1; +} \ No newline at end of file diff --git a/macosx/English.lproj/MainMenu.nib/info.nib b/macosx/English.lproj/MainMenu.nib/info.nib new file mode 100644 index 000000000..141b856cb --- /dev/null +++ b/macosx/English.lproj/MainMenu.nib/info.nib @@ -0,0 +1,26 @@ + + + + + IBDocumentLocation + 204 84 361 432 0 0 1440 878 + IBEditorPositions + + 29 + 105 768 371 44 0 0 1280 832 + + IBFramework Version + 439.0 + IBOldestOS + 3 + IBOpenObjects + + 21 + 29 + 273 + 343 + + IBSystem Version + 8C46 + + diff --git a/macosx/English.lproj/MainMenu.nib/keyedobjects.nib b/macosx/English.lproj/MainMenu.nib/keyedobjects.nib new file mode 100644 index 000000000..8b31fb0bc Binary files /dev/null and b/macosx/English.lproj/MainMenu.nib/keyedobjects.nib differ diff --git a/macosx/Images/Info.tiff b/macosx/Images/Info.tiff new file mode 100644 index 000000000..d38cbf19a Binary files /dev/null and b/macosx/Images/Info.tiff differ diff --git a/macosx/Images/Open.tiff b/macosx/Images/Open.tiff new file mode 100644 index 000000000..16689c732 Binary files /dev/null and b/macosx/Images/Open.tiff differ diff --git a/macosx/Images/Progress.tiff b/macosx/Images/Progress.tiff new file mode 100644 index 000000000..1726c6748 Binary files /dev/null and b/macosx/Images/Progress.tiff differ diff --git a/macosx/Images/Remove.tiff b/macosx/Images/Remove.tiff new file mode 100644 index 000000000..0bab7024a Binary files /dev/null and b/macosx/Images/Remove.tiff differ diff --git a/macosx/Images/Resume.tiff b/macosx/Images/Resume.tiff new file mode 100644 index 000000000..2c2500ede Binary files /dev/null and b/macosx/Images/Resume.tiff differ diff --git a/macosx/Images/RevealOff.tiff b/macosx/Images/RevealOff.tiff new file mode 100644 index 000000000..38caf2f92 Binary files /dev/null and b/macosx/Images/RevealOff.tiff differ diff --git a/macosx/Images/RevealOn.tiff b/macosx/Images/RevealOn.tiff new file mode 100644 index 000000000..3c406a650 Binary files /dev/null and b/macosx/Images/RevealOn.tiff differ diff --git a/macosx/Images/Stop.tiff b/macosx/Images/Stop.tiff new file mode 100644 index 000000000..86c8f1b1d Binary files /dev/null and b/macosx/Images/Stop.tiff differ diff --git a/macosx/Images/Transmission.icns b/macosx/Images/Transmission.icns new file mode 100644 index 000000000..99f623e0c Binary files /dev/null and b/macosx/Images/Transmission.icns differ diff --git a/macosx/Images/TransmissionDocument.icns b/macosx/Images/TransmissionDocument.icns new file mode 100644 index 000000000..328208297 Binary files /dev/null and b/macosx/Images/TransmissionDocument.icns differ diff --git a/macosx/Info.plist.in b/macosx/Info.plist.in new file mode 100644 index 000000000..07976f635 --- /dev/null +++ b/macosx/Info.plist.in @@ -0,0 +1,45 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleDocumentTypes + + + CFBundleTypeExtensions + + torrent + + CFBundleTypeIconFile + TransmissionDocument + CFBundleTypeName + BitTorrent Document + CFBundleTypeRole + Viewer + LSTypeIsPackage + + NSPersistentStoreTypeKey + XML + + + CFBundleExecutable + Transmission + CFBundleIconFile + Transmission + CFBundleIdentifier + org.m0k.transmission + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + APPL + CFBundleSignature + TR## + CFBundleVersion + %%VERSION%% + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/macosx/NameCell.h b/macosx/NameCell.h new file mode 100644 index 000000000..895f6942f --- /dev/null +++ b/macosx/NameCell.h @@ -0,0 +1,33 @@ +/****************************************************************************** + * 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 +#include + +@interface NameCell : NSCell +{ + tr_stat_t * fStat; + NSRect fRevealRect; + NSPoint fClickPoint; +} +- (void) setStat: (tr_stat_t *) stat; +@end diff --git a/macosx/NameCell.m b/macosx/NameCell.m new file mode 100644 index 000000000..c22d16d3a --- /dev/null +++ b/macosx/NameCell.m @@ -0,0 +1,167 @@ +/****************************************************************************** + * 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 "NameCell.h" +#include "Utils.h" + +@implementation NameCell + +- (void) setStat: (tr_stat_t *) stat; +{ + fStat = stat; +} + +- (void) drawWithFrame: (NSRect) cellFrame inView: (NSView *) view +{ + if( ![view lockFocusIfCanDraw] ) + { + return; + } + + NSString * nameString = NULL, * timeString = @"", * peersString = @""; + NSMutableDictionary * attributes; + 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 - + 10 - 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 - 15 - + widthForString( @"Error: ", 10 ), 10 )]; + } + + [attributes setObject: [NSFont messageFontOfSize:12.0] + forKey: NSFontAttributeName]; + + pen.x += 5; pen.y += 5; + [nameString drawAtPoint: pen withAttributes: attributes]; + + [attributes setObject: [NSFont messageFontOfSize:10.0] + forKey: NSFontAttributeName]; + + pen.x += 5; pen.y += 20; + [timeString drawAtPoint: pen withAttributes: attributes]; + + pen.x += 0; pen.y += 15; + [peersString drawAtPoint: pen withAttributes: attributes]; + + /* "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.tiff"]; + } + else + { + revealImage = [NSImage imageNamed: @"RevealOff.tiff"]; + } + pen.x = fRevealRect.origin.x; + pen.y = fRevealRect.origin.y + 14; + [revealImage compositeToPoint: pen operation: NSCompositeSourceOver]; + + [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 && NSPointInRect( stop, fRevealRect ) ) + { + /* Reveal in Finder */ + [[NSWorkspace sharedWorkspace] openFile: + [NSString stringWithUTF8String: fStat->folder]]; + } + fClickPoint = NSMakePoint(0,0); +} + +@end diff --git a/macosx/PrefsController.h b/macosx/PrefsController.h new file mode 100644 index 000000000..fbbc7427c --- /dev/null +++ b/macosx/PrefsController.h @@ -0,0 +1,49 @@ +/****************************************************************************** + * 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 +#include + +@interface PrefsController : NSObject + +{ + tr_handle_t * fHandle; + + IBOutlet NSWindow * fWindow; + IBOutlet NSWindow * fPrefsWindow; + IBOutlet NSMatrix * fFolderMatrix; + IBOutlet NSPopUpButton * fFolderPopUp; + IBOutlet NSTextField * fPortField; + IBOutlet NSButton * fUploadCheck; + IBOutlet NSTextField * fUploadField; + + NSString * fDownloadFolder; +} + +- (void) setHandle: (tr_handle_t *) handle; +- (void) show: (id) sender; +- (void) ratio: (id) sender; +- (void) check: (id) sender; +- (void) cancel: (id) sender; +- (void) save: (id) sender; + +@end diff --git a/macosx/PrefsController.m b/macosx/PrefsController.m new file mode 100644 index 000000000..d451f132a --- /dev/null +++ b/macosx/PrefsController.m @@ -0,0 +1,374 @@ +/****************************************************************************** + * 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 "PrefsController.h" + +@interface PrefsController (Private) + +- (void) folderSheetShow: (id) sender; +- (void) folderSheetClosed: (NSOpenPanel *) s returnCode: (int) code + contextInfo: (void *) info; +- (void) loadSettings; +- (void) saveSettings; +- (void) updatePopUp; + +@end + +@implementation PrefsController + +/*********************************************************************** + * setHandle + *********************************************************************** + * + **********************************************************************/ +- (void) setHandle: (tr_handle_t *) handle +{ + NSUserDefaults * defaults; + NSDictionary * appDefaults; + + fHandle = handle; + + /* Register defaults settings: + - Simple bar + - Always download to Desktop + - Port 9090 + - 20 KB/s upload limit */ + NSString * desktopPath + = [NSString stringWithFormat: @"%@/Desktop", + NSHomeDirectory()]; + + defaults = [NSUserDefaults standardUserDefaults]; + appDefaults = [NSDictionary dictionaryWithObjectsAndKeys: + @"NO", @"UseAdvancedBar", + @"Constant", @"DownloadChoice", + desktopPath, @"DownloadFolder", + @"9090", @"BindPort", + @"20", @"UploadLimit", + NULL]; + [defaults registerDefaults: appDefaults]; + + /* Apply settings */ + tr_setBindPort( fHandle, [defaults integerForKey: @"BindPort"] ); + tr_setUploadLimit( fHandle, [defaults integerForKey: @"UploadLimit"] ); +} + +/*********************************************************************** + * show + *********************************************************************** + * + **********************************************************************/ +- (void) show: (id) sender +{ + NSRect mainFrame; + NSRect prefsFrame; + NSRect screenRect; + NSPoint point; + + [self loadSettings]; + + /* Place the window */ + mainFrame = [fWindow frame]; + prefsFrame = [fPrefsWindow frame]; + screenRect = [[NSScreen mainScreen] visibleFrame]; + point.x = mainFrame.origin.x + mainFrame.size.width / 2 - + prefsFrame.size.width / 2; + point.y = mainFrame.origin.y + mainFrame.size.height - 30; + + /* Make sure it is in the screen */ + if( point.x < screenRect.origin.x ) + { + point.x = screenRect.origin.x; + } + if( point.x + prefsFrame.size.width > + screenRect.origin.x + screenRect.size.width ) + { + point.x = screenRect.origin.x + + screenRect.size.width - prefsFrame.size.width; + } + if( point.y - prefsFrame.size.height < screenRect.origin.y ) + { + point.y = screenRect.origin.y + prefsFrame.size.height; + } + + [fPrefsWindow setFrameTopLeftPoint: point]; + [fPrefsWindow makeKeyAndOrderFront: NULL]; +} + +/*********************************************************************** + * ratio + *********************************************************************** + * + **********************************************************************/ +- (void) ratio: (id) sender +{ + [fFolderPopUp setEnabled: ![fFolderMatrix selectedRow]]; +} + +/*********************************************************************** + * check + *********************************************************************** + * + **********************************************************************/ +- (void) check: (id) sender +{ + if( [fUploadCheck state] == NSOnState ) + { + [fUploadField setEnabled: YES]; + } + else + { + [fUploadField setEnabled: NO]; + [fUploadField setStringValue: @""]; + } +} + +/*********************************************************************** + * cancel + *********************************************************************** + * Discards changes and closes the Preferences window + **********************************************************************/ +- (void) cancel: (id) sender +{ + [fDownloadFolder release]; + [fPrefsWindow close]; +} + +/*********************************************************************** + * save + *********************************************************************** + * Checks the user-defined options. If they are correct, saves settings + * and closes the Preferences window. Otherwise corrects them and leaves + * the window open + **********************************************************************/ +- (void) save: (id) sender +{ + int bindPort; + int uploadLimit; + + /* Bind port */ + bindPort = [fPortField intValue]; + bindPort = MAX( 1, bindPort ); + bindPort = MIN( bindPort, 65535 ); + + if( ![[fPortField stringValue] isEqualToString: + [NSString stringWithFormat: @"%d", bindPort]] ) + { + [fPortField setIntValue: bindPort]; + return; + } + + /* Upload limit */ + if( [fUploadCheck state] == NSOnState ) + { + uploadLimit = [fUploadField intValue]; + uploadLimit = MAX( 0, uploadLimit ); + + if( ![[fUploadField stringValue] isEqualToString: + [NSString stringWithFormat: @"%d", uploadLimit]] ) + { + [fUploadField setIntValue: uploadLimit]; + return; + } + } + + [self saveSettings]; + [self cancel: NULL]; +} + +@end /* @implementation PrefsController */ + +@implementation PrefsController (Private) + +- (void) folderSheetShow: (id) sender +{ + NSOpenPanel * panel; + + panel = [NSOpenPanel openPanel]; + + [panel setPrompt: @"Select"]; + [panel setAllowsMultipleSelection: NO]; + [panel setCanChooseFiles: NO]; + [panel setCanChooseDirectories: YES]; + + [panel beginSheetForDirectory: NULL file: NULL types: NULL + modalForWindow: fPrefsWindow modalDelegate: self didEndSelector: + @selector( folderSheetClosed:returnCode:contextInfo: ) + contextInfo: NULL]; +} + +- (void) folderSheetClosed: (NSOpenPanel *) s returnCode: (int) code + contextInfo: (void *) info +{ + [fFolderPopUp selectItemAtIndex: 0]; + + if( code != NSOKButton ) + { + return; + } + + [fDownloadFolder release]; + fDownloadFolder = [[s filenames] objectAtIndex: 0]; + [fDownloadFolder retain]; + + [self updatePopUp]; +} + +/*********************************************************************** + * loadSettings + *********************************************************************** + * Update the interface with the current settings + **********************************************************************/ +- (void) loadSettings +{ + NSUserDefaults * defaults; + NSString * downloadChoice; + int uploadLimit; + + /* Fill with current settings */ + defaults = [NSUserDefaults standardUserDefaults]; + + /* Download folder selection */ + downloadChoice = [defaults stringForKey: @"DownloadChoice"]; + fDownloadFolder = [defaults stringForKey: @"DownloadFolder"]; + [fDownloadFolder retain]; + + if( [downloadChoice isEqualToString: @"Constant"] ) + { + [fFolderMatrix selectCellAtRow: 0 column: 0]; + } + else if( [downloadChoice isEqualToString: @"Torrent"] ) + { + [fFolderMatrix selectCellAtRow: 1 column: 0]; + } + else + { + [fFolderMatrix selectCellAtRow: 2 column: 0]; + } + [self ratio: NULL]; + [self updatePopUp]; + + [fPortField setIntValue: [defaults integerForKey: @"BindPort"]]; + + uploadLimit = [defaults integerForKey: @"UploadLimit"]; + if( uploadLimit < 0 ) + { + [fUploadCheck setState: NSOffState]; + } + else + { + [fUploadCheck setState: NSOnState]; + [fUploadField setIntValue: uploadLimit]; + } + [self check: NULL]; +} + +/*********************************************************************** + * saveSettings + *********************************************************************** + * + **********************************************************************/ +- (void) saveSettings +{ + NSUserDefaults * defaults; + int bindPort; + int uploadLimit; + + defaults = [NSUserDefaults standardUserDefaults]; + + /* Download folder */ + switch( [fFolderMatrix selectedRow] ) + { + case 0: + [defaults setObject: @"Constant" forKey: @"DownloadChoice"]; + break; + case 1: + [defaults setObject: @"Torrent" forKey: @"DownloadChoice"]; + break; + case 2: + [defaults setObject: @"Ask" forKey: @"DownloadChoice"]; + break; + } + [defaults setObject: fDownloadFolder forKey: @"DownloadFolder"]; + + /* Bind port */ + bindPort = [fPortField intValue]; + tr_setBindPort( fHandle, bindPort ); + [defaults setObject: [NSString stringWithFormat: @"%d", bindPort] + forKey: @"BindPort"]; + + /* Upload limit */ + if( [fUploadCheck state] == NSOnState ) + { + uploadLimit = [fUploadField intValue]; + } + else + { + uploadLimit = -1; + } + tr_setUploadLimit( fHandle, uploadLimit ); + [defaults setObject: [NSString stringWithFormat: @"%d", uploadLimit] + forKey: @"UploadLimit"]; +} + +/*********************************************************************** + * updatePopUp + *********************************************************************** + * Uses fDownloadFolder to update the displayed folder name and icon + **********************************************************************/ +- (void) updatePopUp +{ + NSMenuItem * menuItem; + NSImage * image32, * image16; + + /* Set up the pop up */ + [fFolderPopUp removeAllItems]; + [fFolderPopUp addItemWithTitle: @""]; + [[fFolderPopUp menu] addItem: [NSMenuItem separatorItem]]; + [fFolderPopUp addItemWithTitle: @"Other..."]; + + menuItem = (NSMenuItem *) [fFolderPopUp lastItem]; + [menuItem setTarget: self]; + [menuItem setAction: @selector( folderSheetShow: )]; + + /* Get the icon for the folder */ + image32 = [[NSWorkspace sharedWorkspace] iconForFile: + fDownloadFolder]; + image16 = [[NSImage alloc] initWithSize: NSMakeSize(16,16)]; + + /* 32x32 -> 16x16 scaling */ + [image16 lockFocus]; + [[NSGraphicsContext currentContext] + setImageInterpolation: NSImageInterpolationHigh]; + [image32 drawInRect: NSMakeRect(0,0,16,16) + fromRect: NSMakeRect(0,0,32,32) operation: NSCompositeCopy + fraction: 1.0]; + [image16 unlockFocus]; + + /* Update the menu item */ + menuItem = (NSMenuItem *) [fFolderPopUp itemAtIndex: 0]; + [menuItem setTitle: [fDownloadFolder lastPathComponent]]; + [menuItem setImage: image16]; + + [image16 release]; +} + +@end /* @implementation PrefsController (Private) */ diff --git a/macosx/ProgressCell.h b/macosx/ProgressCell.h new file mode 100644 index 000000000..bd25b19a9 --- /dev/null +++ b/macosx/ProgressCell.h @@ -0,0 +1,43 @@ +/****************************************************************************** + * 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 +#include + +@interface ProgressCell : NSCell +{ + tr_stat_t * fStat; + + NSString * fProgressString; + NSString * fDlString; + NSString * fUlString; + + NSBitmapImageRep * fBgBmp; + NSImage * fImg; + NSBitmapImageRep * fBmp; +} +- (id) init; +- (void) setStat: (tr_stat_t *) stat; +- (void) buildSimpleBar; +- (void) buildAdvancedBar; +- (void) drawWithFrame: (NSRect) cellFrame inView: (NSView *) view; +@end diff --git a/macosx/ProgressCell.m b/macosx/ProgressCell.m new file mode 100644 index 000000000..182ff43c8 --- /dev/null +++ b/macosx/ProgressCell.m @@ -0,0 +1,257 @@ +/****************************************************************************** + * 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 "ProgressCell.h" + +#if 0 +/* Coefficients for the "3D effect" */ +static float kBarCoeffs[] = + { 0.49, 0.73, 0.84, 0.85, 0.84, 0.79, 0.78, + 0.82, 0.86, 0.91, 0.93, 0.95, 0.96, 0.71 }; +#endif + +/* 255, 100, 80 */ +static uint32_t kRed[] = + { 0x7C3127FF, 0xBA493AFF, 0xD65443FF, 0xD85544FF, 0xD65443FF, + 0xC94F3FFF, 0xC64E3EFF, 0xD15241FF, 0xDB5644FF, 0xE85B48FF, + 0xED5D4AFF, 0xF25F4CFF, 0xF4604CFF, 0xB54738FF }; + +/* 160, 220, 255 */ +static uint32_t kBlue1[] = + { 0x4E6B7CFF, 0x74A0BAFF, 0x86B8D6FF, 0x88BBD8FF, 0x86B8D6FF, + 0x7EADC9FF, 0x7CABC6FF, 0x83B4D1FF, 0x89BDDBFF, 0x91C8E8FF, + 0x94CCEDFF, 0x98D1F2FF, 0x99D3F4FF, 0x719CB5FF }; + +/* 120, 190, 255 */ +static uint32_t kBlue2[] = + { 0x3A5D7CFF, 0x578ABAFF, 0x649FD6FF, 0x66A1D8FF, 0x649FD6FF, + 0x5E96C9FF, 0x5D94C6FF, 0x629BD1FF, 0x67A3DBFF, 0x6DACE8FF, + 0x6FB0EDFF, 0x72B4F2FF, 0x73B6F4FF, 0x5586B5FF }; + +/* 80, 160, 255 */ +static uint32_t kBlue3[] = + { 0x274E7CFF, 0x3A74BAFF, 0x4386D6FF, 0x4488D8FF, 0x4386D6FF, + 0x3F7EC9FF, 0x3E7CC6FF, 0x4183D1FF, 0x4489DBFF, 0x4891E8FF, + 0x4A94EDFF, 0x4C98F2FF, 0x4C99F4FF, 0x3871B5FF }; + +/* 30, 70, 180 */ +static uint32_t kBlue4[] = + { 0x0E2258FF, 0x153383FF, 0x193A97FF, 0x193B99FF, 0x193A97FF, + 0x17378EFF, 0x17368CFF, 0x183993FF, 0x193C9AFF, 0x1B3FA3FF, + 0x1B41A7FF, 0x1C42ABFF, 0x1C43ACFF, 0x15317FFF }; + +/* 130, 130, 130 */ +static uint32_t kGray[] = + { 0x3F3F3FFF, 0x5E5E5EFF, 0x6D6D6DFF, 0x6E6E6EFF, 0x6D6D6DFF, + 0x666666FF, 0x656565FF, 0x6A6A6AFF, 0x6F6F6FFF, 0x767676FF, + 0x787878FF, 0x7B7B7BFF, 0x7C7C7CFF, 0x5C5C5CFF }; + +/* 0, 255, 0 */ +static uint32_t kGreen[] = + { 0x007C00FF, 0x00BA00FF, 0x00D600FF, 0x00D800FF, 0x00D600FF, + 0x00C900FF, 0x00C600FF, 0x00D100FF, 0x00DB00FF, 0x00E800FF, + 0x00ED00FF, 0x00F200FF, 0x00F400FF, 0x00B500FF }; + +@implementation ProgressCell + +- (id) init +{ + NSImage * bgImg; + NSSize size; + + self = [super init]; + + /* Have a NSBitmapImageRep ready to draw the progression bar */ + bgImg = [NSImage imageNamed: @"Progress.tiff"]; + fBgBmp = [[bgImg representations] objectAtIndex: 0]; + size = [bgImg size]; + fImg = [[NSImage alloc] initWithSize: size]; + fBmp = [[NSBitmapImageRep alloc] + initWithBitmapDataPlanes: NULL pixelsWide: size.width + pixelsHigh: size.height bitsPerSample: 8 samplesPerPixel: 4 + hasAlpha: YES isPlanar: NO + colorSpaceName: NSCalibratedRGBColorSpace + bytesPerRow: 0 bitsPerPixel: 0]; + + [fImg addRepresentation: fBmp]; + + return self; +} + +- (void) setStat: (tr_stat_t *) stat +{ + int i; + + fStat = stat; + + fProgressString = [NSString stringWithFormat: + @"%.2f %%", 100.0 * fStat->progress]; + fDlString = [NSString stringWithFormat: + @"DL: %.2f KB/s", fStat->rateDownload]; + fUlString = [NSString stringWithFormat: + @"UL: %.2f KB/s", fStat->rateUpload]; + + for( i = 0; i < [fImg size].height; i++ ) + { + memcpy( [fBmp bitmapData] + i * [fBmp bytesPerRow], + [fBgBmp bitmapData] + i * [fBgBmp bytesPerRow], + [fImg size].width * 4 ); + } + + if( [[NSUserDefaults standardUserDefaults] + boolForKey:@"UseAdvancedBar"]) + { + [self buildAdvancedBar]; + } + else + { + [self buildSimpleBar]; + } +} + +- (void) buildSimpleBar +{ + int h, w; + uint32_t * p; + + for( h = 0; h < 14; h++ ) + { + p = (uint32_t *) ( [fBmp bitmapData] + + h * [fBmp bytesPerRow] ) + 2; + + for( w = 0; w < 120; w++ ) + { + *p = kBlue2[h]; + + if( w >= (int) ( fStat->progress * 120 ) ) + { + break; + } + + p++; + } + } +} + +- (void) buildAdvancedBar +{ + int h, w; + uint32_t * p; + + if( fStat->status & TR_STATUS_SEED ) + { + for( h = 0; h < 2; h++ ) + { + p = (uint32_t *) ( [fBmp bitmapData] + + h * [fBmp bytesPerRow] ) + 2; + } + } + + for( h = 0; h < 14; h++ ) + { + p = (uint32_t *) ( [fBmp bitmapData] + + h * [fBmp bytesPerRow] ) + 2; + + for( w = 0; w < 120; w++ ) + { + if( fStat->status & TR_STATUS_SEED ) + { + *p = kGreen[h]; + } + else + { + /* Download is not finished yet */ + if( h < 2 ) + { + /* First two lines: dark blue to show progression */ + *p = kBlue4[h]; + + if( w >= (int) ( fStat->progress * 120 ) ) + { + break; + } + } + else + { + /* Lines 2 to X: blue or grey depending on whether + we have the piece or not */ + if( fStat->pieces[w] < 0 ) + { + *p = kGray[h]; + } + else if( fStat->pieces[w] < 1 ) + { + *p = kRed[h]; + } + else if( fStat->pieces[w] < 2 ) + { + *p = kBlue1[h]; + } + else if( fStat->pieces[w] < 3 ) + { + *p = kBlue2[h]; + } + else + { + *p = kBlue3[h]; + } + } + } + + p++; + } + } +} + +- (void) drawWithFrame: (NSRect) cellFrame inView: (NSView *) view +{ + if( ![view lockFocusIfCanDraw] ) + { + return; + } + + NSMutableDictionary * attributes; + NSPoint pen = cellFrame.origin; + + attributes = [NSMutableDictionary dictionaryWithCapacity: 1]; + [attributes setObject: [NSFont messageFontOfSize:12.0] + forKey: NSFontAttributeName]; + + pen.x += 5; pen.y += 5; + + pen.y += [fImg size].height; + [fImg compositeToPoint: pen operation: NSCompositeSourceOver]; + pen.y -= [fImg size].height; + + [attributes setObject: [NSFont messageFontOfSize:10.0] + forKey: NSFontAttributeName]; + + pen.x += 5; pen.y += 20; + [fDlString drawAtPoint: pen withAttributes: attributes]; + + pen.x += 0; pen.y += 15; + [fUlString drawAtPoint: pen withAttributes: attributes]; + + [view unlockFocus]; +} + +@end diff --git a/macosx/Transmission.xcodeproj/project.pbxproj b/macosx/Transmission.xcodeproj/project.pbxproj new file mode 100644 index 000000000..cf0abcbfb --- /dev/null +++ b/macosx/Transmission.xcodeproj/project.pbxproj @@ -0,0 +1,386 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 42; + objects = { + +/* Begin PBXBuildFile section */ + 4D043A7F090AE979009FEDA8 /* TransmissionDocument.icns in Resources */ = {isa = PBXBuildFile; fileRef = 4D043A7E090AE979009FEDA8 /* TransmissionDocument.icns */; }; + 4D096C12089FB4E20091B166 /* NameCell.m in Sources */ = {isa = PBXBuildFile; fileRef = 4D096C0F089FB4E20091B166 /* NameCell.m */; }; + 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 */; }; + 4D3EA0AA08AE13C600EA10C2 /* IOKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4D3EA0A908AE13C600EA10C2 /* IOKit.framework */; }; + 4D6DAAC6090CE00500F43C22 /* RevealOff.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 4D6DAAC4090CE00500F43C22 /* RevealOff.tiff */; }; + 4D6DAAC7090CE00500F43C22 /* RevealOn.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 4D6DAAC5090CE00500F43C22 /* RevealOn.tiff */; }; + 4D813EB508AA43AC00191DB4 /* Progress.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 4D813EB408AA43AC00191DB4 /* Progress.tiff */; }; + 4DF0C5AB0899190500DD8943 /* Controller.m in Sources */ = {isa = PBXBuildFile; fileRef = 4DF0C5A90899190500DD8943 /* Controller.m */; }; + 4DF0C5AE08991C1600DD8943 /* libtransmission.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4DF0C5AD08991C1600DD8943 /* libtransmission.a */; }; + 4DF7500C08A103AD007B0D70 /* Open.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 4DF7500708A103AD007B0D70 /* Open.tiff */; }; + 4DF7500D08A103AD007B0D70 /* Info.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 4DF7500808A103AD007B0D70 /* Info.tiff */; }; + 4DF7500E08A103AD007B0D70 /* Remove.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 4DF7500908A103AD007B0D70 /* Remove.tiff */; }; + 4DF7500F08A103AD007B0D70 /* Resume.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 4DF7500A08A103AD007B0D70 /* Resume.tiff */; }; + 4DF7501008A103AD007B0D70 /* Stop.tiff in Resources */ = {isa = PBXBuildFile; fileRef = 4DF7500B08A103AD007B0D70 /* Stop.tiff */; }; + 8D11072A0486CEB800E47090 /* MainMenu.nib in Resources */ = {isa = PBXBuildFile; fileRef = 29B97318FDCFA39411CA2CEA /* MainMenu.nib */; }; + 8D11072B0486CEB800E47090 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */; }; + 8D11072D0486CEB800E47090 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 29B97316FDCFA39411CA2CEA /* main.m */; settings = {ATTRIBUTES = (); }; }; + 8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */; }; +/* End PBXBuildFile section */ + +/* Begin PBXBuildStyle section */ + 4A9504CCFFE6A4B311CA0CBA /* Debug */ = { + isa = PBXBuildStyle; + buildSettings = { + COPY_PHASE_STRIP = NO; + GCC_DYNAMIC_NO_PIC = NO; + GCC_ENABLE_FIX_AND_CONTINUE = YES; + GCC_GENERATE_DEBUGGING_SYMBOLS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + PREBINDING = NO; + ZERO_LINK = YES; + }; + name = Debug; + }; + 4A9504CDFFE6A4B311CA0CBA /* Release */ = { + isa = PBXBuildStyle; + buildSettings = { + COPY_PHASE_STRIP = YES; + GCC_ENABLE_FIX_AND_CONTINUE = NO; + GCC_GENERATE_DEBUGGING_SYMBOLS = NO; + PREBINDING = NO; + ZERO_LINK = NO; + }; + name = Release; + }; +/* End PBXBuildStyle section */ + +/* Begin PBXFileReference section */ + 089C165DFE840E0CC02AAC07 /* English */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = English; path = English.lproj/InfoPlist.strings; sourceTree = ""; }; + 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = /System/Library/Frameworks/Cocoa.framework; sourceTree = ""; }; + 13E42FB307B3F0F600E4EEF1 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = /System/Library/Frameworks/CoreData.framework; sourceTree = ""; }; + 29B97316FDCFA39411CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; + 29B97319FDCFA39411CA2CEA /* English */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = English; path = English.lproj/MainMenu.nib; sourceTree = ""; }; + 29B97324FDCFA39411CA2CEA /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = ""; }; + 29B97325FDCFA39411CA2CEA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = ""; }; + 32CA4F630368D1EE00C91783 /* Transmission_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Transmission_Prefix.pch; sourceTree = ""; }; + 4D043A7E090AE979009FEDA8 /* TransmissionDocument.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; name = TransmissionDocument.icns; path = Images/TransmissionDocument.icns; sourceTree = ""; }; + 4D096C0E089FB4E20091B166 /* NameCell.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = NameCell.h; sourceTree = ""; }; + 4D096C0F089FB4E20091B166 /* NameCell.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = NameCell.m; sourceTree = ""; }; + 4D096C10089FB4E20091B166 /* ProgressCell.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = ProgressCell.h; sourceTree = ""; }; + 4D096C11089FB4E20091B166 /* ProgressCell.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = ProgressCell.m; sourceTree = ""; }; + 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 = ""; }; + 4D3EA0A908AE13C600EA10C2 /* IOKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOKit.framework; path = /System/Library/Frameworks/IOKit.framework; sourceTree = ""; }; + 4D6DAAC4090CE00500F43C22 /* RevealOff.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; name = RevealOff.tiff; path = Images/RevealOff.tiff; sourceTree = ""; }; + 4D6DAAC5090CE00500F43C22 /* RevealOn.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; name = RevealOn.tiff; path = Images/RevealOn.tiff; sourceTree = ""; }; + 4D813EB408AA43AC00191DB4 /* Progress.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; name = Progress.tiff; path = Images/Progress.tiff; sourceTree = ""; }; + 4DF0C5A90899190500DD8943 /* Controller.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = Controller.m; sourceTree = ""; }; + 4DF0C5AA0899190500DD8943 /* Controller.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Controller.h; sourceTree = ""; }; + 4DF0C5AD08991C1600DD8943 /* libtransmission.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libtransmission.a; path = ../libtransmission/libtransmission.a; sourceTree = SOURCE_ROOT; }; + 4DF7500708A103AD007B0D70 /* Open.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; name = Open.tiff; path = Images/Open.tiff; sourceTree = ""; }; + 4DF7500808A103AD007B0D70 /* Info.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; name = Info.tiff; path = Images/Info.tiff; sourceTree = ""; }; + 4DF7500908A103AD007B0D70 /* Remove.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; name = Remove.tiff; path = Images/Remove.tiff; sourceTree = ""; }; + 4DF7500A08A103AD007B0D70 /* Resume.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; name = Resume.tiff; path = Images/Resume.tiff; sourceTree = ""; }; + 4DF7500B08A103AD007B0D70 /* Stop.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; name = Stop.tiff; path = Images/Stop.tiff; sourceTree = ""; }; + 8D1107310486CEB800E47090 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; + 8D1107320486CEB800E47090 /* Transmission.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Transmission.app; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 8D11072E0486CEB800E47090 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 8D11072F0486CEB800E47090 /* Cocoa.framework in Frameworks */, + 4DF0C5AE08991C1600DD8943 /* libtransmission.a in Frameworks */, + 4D3EA0AA08AE13C600EA10C2 /* IOKit.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 080E96DDFE201D6D7F000001 /* Classes */ = { + isa = PBXGroup; + children = ( + 4D096C0E089FB4E20091B166 /* NameCell.h */, + 4D096C0F089FB4E20091B166 /* NameCell.m */, + 4D096C10089FB4E20091B166 /* ProgressCell.h */, + 4D096C11089FB4E20091B166 /* ProgressCell.m */, + 4DF0C5A90899190500DD8943 /* Controller.m */, + 4DF0C5AA0899190500DD8943 /* Controller.h */, + 4D118E1808CB46B20033958F /* PrefsController.h */, + 4D118E1908CB46B20033958F /* PrefsController.m */, + ); + name = Classes; + sourceTree = ""; + }; + 1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */ = { + isa = PBXGroup; + children = ( + 1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */, + ); + name = "Linked Frameworks"; + sourceTree = ""; + }; + 1058C7A2FEA54F0111CA2CBB /* Other Frameworks */ = { + isa = PBXGroup; + children = ( + 4D3EA0A908AE13C600EA10C2 /* IOKit.framework */, + 29B97324FDCFA39411CA2CEA /* AppKit.framework */, + 13E42FB307B3F0F600E4EEF1 /* CoreData.framework */, + 29B97325FDCFA39411CA2CEA /* Foundation.framework */, + ); + name = "Other Frameworks"; + sourceTree = ""; + }; + 19C28FACFE9D520D11CA2CBB /* Products */ = { + isa = PBXGroup; + children = ( + 8D1107320486CEB800E47090 /* Transmission.app */, + ); + name = Products; + sourceTree = ""; + }; + 29B97314FDCFA39411CA2CEA /* Transmission */ = { + isa = PBXGroup; + children = ( + 4DF0C5AD08991C1600DD8943 /* libtransmission.a */, + 080E96DDFE201D6D7F000001 /* Classes */, + 29B97315FDCFA39411CA2CEA /* Other Sources */, + 29B97317FDCFA39411CA2CEA /* Resources */, + 29B97323FDCFA39411CA2CEA /* Frameworks */, + 19C28FACFE9D520D11CA2CBB /* Products */, + ); + name = Transmission; + sourceTree = ""; + }; + 29B97315FDCFA39411CA2CEA /* Other Sources */ = { + isa = PBXGroup; + children = ( + 32CA4F630368D1EE00C91783 /* Transmission_Prefix.pch */, + 29B97316FDCFA39411CA2CEA /* main.m */, + ); + name = "Other Sources"; + sourceTree = ""; + }; + 29B97317FDCFA39411CA2CEA /* Resources */ = { + isa = PBXGroup; + children = ( + 4D2784360905709500687951 /* Transmission.icns */, + 4D043A7E090AE979009FEDA8 /* TransmissionDocument.icns */, + 4DF7500808A103AD007B0D70 /* Info.tiff */, + 4D813EB408AA43AC00191DB4 /* Progress.tiff */, + 4DF7500708A103AD007B0D70 /* Open.tiff */, + 4DF7500908A103AD007B0D70 /* Remove.tiff */, + 4DF7500A08A103AD007B0D70 /* Resume.tiff */, + 4DF7500B08A103AD007B0D70 /* Stop.tiff */, + 4D6DAAC4090CE00500F43C22 /* RevealOff.tiff */, + 4D6DAAC5090CE00500F43C22 /* RevealOn.tiff */, + 8D1107310486CEB800E47090 /* Info.plist */, + 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */, + 29B97318FDCFA39411CA2CEA /* MainMenu.nib */, + ); + name = Resources; + sourceTree = ""; + }; + 29B97323FDCFA39411CA2CEA /* Frameworks */ = { + isa = PBXGroup; + children = ( + 1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */, + 1058C7A2FEA54F0111CA2CBB /* Other Frameworks */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 8D1107260486CEB800E47090 /* Transmission */ = { + isa = PBXNativeTarget; + buildConfigurationList = 4DF0C596089918A300DD8943 /* Build configuration list for PBXNativeTarget "Transmission" */; + buildPhases = ( + 8D1107290486CEB800E47090 /* Resources */, + 8D11072C0486CEB800E47090 /* Sources */, + 8D11072E0486CEB800E47090 /* Frameworks */, + ); + buildRules = ( + ); + buildSettings = { + GCC_GENERATE_DEBUGGING_SYMBOLS = NO; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = Transmission_Prefix.pch; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + INFOPLIST_FILE = Info.plist; + INSTALL_PATH = "$(HOME)/Applications"; + PRODUCT_NAME = Transmission; + WRAPPER_EXTENSION = app; + }; + dependencies = ( + ); + name = Transmission; + productInstallPath = "$(HOME)/Applications"; + productName = Transmission; + productReference = 8D1107320486CEB800E47090 /* Transmission.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 29B97313FDCFA39411CA2CEA /* Project object */ = { + isa = PBXProject; + buildConfigurationList = 4DF0C59A089918A300DD8943 /* Build configuration list for PBXProject "Transmission" */; + buildSettings = { + }; + buildStyles = ( + 4A9504CCFFE6A4B311CA0CBA /* Debug */, + 4A9504CDFFE6A4B311CA0CBA /* Release */, + ); + hasScannedForEncodings = 1; + mainGroup = 29B97314FDCFA39411CA2CEA /* Transmission */; + projectDirPath = ""; + targets = ( + 8D1107260486CEB800E47090 /* Transmission */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 8D1107290486CEB800E47090 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8D11072A0486CEB800E47090 /* MainMenu.nib in Resources */, + 8D11072B0486CEB800E47090 /* InfoPlist.strings in Resources */, + 4DF7500C08A103AD007B0D70 /* Open.tiff in Resources */, + 4DF7500D08A103AD007B0D70 /* Info.tiff in Resources */, + 4DF7500E08A103AD007B0D70 /* Remove.tiff in Resources */, + 4DF7500F08A103AD007B0D70 /* Resume.tiff in Resources */, + 4DF7501008A103AD007B0D70 /* Stop.tiff in Resources */, + 4D813EB508AA43AC00191DB4 /* Progress.tiff in Resources */, + 4D2784370905709500687951 /* Transmission.icns in Resources */, + 4D043A7F090AE979009FEDA8 /* TransmissionDocument.icns in Resources */, + 4D6DAAC6090CE00500F43C22 /* RevealOff.tiff in Resources */, + 4D6DAAC7090CE00500F43C22 /* RevealOn.tiff in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 8D11072C0486CEB800E47090 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 8D11072D0486CEB800E47090 /* main.m in Sources */, + 4DF0C5AB0899190500DD8943 /* Controller.m in Sources */, + 4D096C12089FB4E20091B166 /* NameCell.m in Sources */, + 4D096C13089FB4E20091B166 /* ProgressCell.m in Sources */, + 4D118E1A08CB46B20033958F /* PrefsController.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 089C165CFE840E0CC02AAC07 /* InfoPlist.strings */ = { + isa = PBXVariantGroup; + children = ( + 089C165DFE840E0CC02AAC07 /* English */, + ); + name = InfoPlist.strings; + sourceTree = ""; + }; + 29B97318FDCFA39411CA2CEA /* MainMenu.nib */ = { + isa = PBXVariantGroup; + children = ( + 29B97319FDCFA39411CA2CEA /* English */, + ); + name = MainMenu.nib; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 4DF0C599089918A300DD8943 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = ( + ppc, + i386, + ); + GCC_GENERATE_DEBUGGING_SYMBOLS = YES; + GCC_OPTIMIZATION_LEVEL = 3; + GCC_PRECOMPILE_PREFIX_HEADER = YES; + GCC_PREFIX_HEADER = Transmission_Prefix.pch; + GCC_TREAT_NONCONFORMANT_CODE_ERRORS_AS_WARNINGS = NO; + GCC_UNROLL_LOOPS = YES; + GCC_WARN_ABOUT_MISSING_NEWLINE = YES; + GCC_WARN_ABOUT_MISSING_PROTOTYPES = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES; + GCC_WARN_CHECK_SWITCH_STATEMENTS = YES; + GCC_WARN_EFFECTIVE_CPLUSPLUS_VIOLATIONS = YES; + GCC_WARN_HIDDEN_VIRTUAL_FUNCTIONS = YES; + GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; + GCC_WARN_MISSING_PARENTHESES = YES; + GCC_WARN_NON_VIRTUAL_DESTRUCTOR = YES; + GCC_WARN_SHADOW = YES; + GCC_WARN_SIGN_COMPARE = YES; + GCC_WARN_TYPECHECK_CALLS_TO_PRINTF = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES; + GCC_WARN_UNKNOWN_PRAGMAS = YES; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_LABEL = YES; + GCC_WARN_UNUSED_PARAMETER = NO; + GCC_WARN_UNUSED_VALUE = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + HEADER_SEARCH_PATHS = "\"$(SRCROOT)/../libtransmission\""; + INFOPLIST_FILE = Info.plist; + INSTALL_PATH = "$(HOME)/Applications"; + LIBRARY_SEARCH_PATHS = "\"$(SRCROOT)/../libtransmission\""; + OTHER_LDFLAGS = "-lcrypto"; + PRODUCT_NAME = Transmission; + WRAPPER_EXTENSION = app; + }; + name = Debug; + }; + 4DF0C59D089918A300DD8943 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ARCHS = ( + ppc, + i386, + ); + COPY_PHASE_STRIP = NO; + MACOSX_DEPLOYMENT_TARGET = 10.3; + SDKROOT = /Developer/SDKs/MacOSX10.4u.sdk; + }; + name = Debug; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 4DF0C596089918A300DD8943 /* Build configuration list for PBXNativeTarget "Transmission" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4DF0C599089918A300DD8943 /* Debug */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; + 4DF0C59A089918A300DD8943 /* Build configuration list for PBXProject "Transmission" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4DF0C59D089918A300DD8943 /* Debug */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Debug; + }; +/* End XCConfigurationList section */ + }; + rootObject = 29B97313FDCFA39411CA2CEA /* Project object */; +} diff --git a/macosx/Transmission_Prefix.pch b/macosx/Transmission_Prefix.pch new file mode 100644 index 000000000..aabef477d --- /dev/null +++ b/macosx/Transmission_Prefix.pch @@ -0,0 +1,3 @@ +#ifdef __OBJC__ + #import +#endif diff --git a/macosx/Utils.h b/macosx/Utils.h new file mode 100644 index 000000000..a7212df7d --- /dev/null +++ b/macosx/Utils.h @@ -0,0 +1,74 @@ +/****************************************************************************** + * 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. + *****************************************************************************/ + +static NSString * stringForFileSize( uint64_t size ) +{ + if( size < 1024 ) + { + return [NSString stringWithFormat: @"%lld bytes", size]; + } + if( size < 1048576 ) + { + return [NSString stringWithFormat: @"%lld.%lld KB", + size / 1024, ( size % 1024 ) / 103]; + } + if( size < 1073741824 ) + { + return [NSString stringWithFormat: @"%lld.%lld MB", + size / 1048576, ( size % 1048576 ) / 104858]; + } + return [NSString stringWithFormat: @"%lld.%lld GB", + size / 1073741824, ( size % 1073741824 ) / 107374183]; +} + +static float widthForString( NSString * string, float fontSize ) +{ + NSMutableDictionary * attributes = + [NSMutableDictionary dictionaryWithCapacity: 1]; + [attributes setObject: [NSFont messageFontOfSize: fontSize] + forKey: NSFontAttributeName]; + + return [string sizeWithAttributes: attributes].width; +} + +static NSString * stringFittingInWidth( char * string, float width, + float fontSize ) +{ + NSString * nsString = NULL; + char * foo = strdup( string ); + int i; + + for( i = strlen( string ); i > 0; i-- ) + { + foo[i] = '\0'; + nsString = [NSString stringWithFormat: @"%s%@", + foo, ( i - strlen( string ) ? [NSString + stringWithUTF8String:"\xE2\x80\xA6"] : @"" )]; + + if( widthForString( nsString, fontSize ) <= width ) + { + break; + } + } + free( foo ); + return nsString; +} diff --git a/macosx/main.m b/macosx/main.m new file mode 100644 index 000000000..874d94b40 --- /dev/null +++ b/macosx/main.m @@ -0,0 +1,36 @@ +/****************************************************************************** + * 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 + +int main( int argc, char ** argv ) +{ + if( argc > 1 && !strncmp( argv[1], "-v", 2 ) ) + { + char * env; + int debug = atoi( &argv[1][2] ); + asprintf( &env, "TR_DEBUG=%d", debug ); + putenv( env ); + free( env ); + } + return NSApplicationMain( argc, (const char **) argv ); +} diff --git a/transmissioncli.c b/transmissioncli.c new file mode 100644 index 000000000..5f01b197e --- /dev/null +++ b/transmissioncli.c @@ -0,0 +1,297 @@ +/****************************************************************************** + * 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 +#include +#include +#include +#include +#include +#include + +#define USAGE \ +"Usage: %s [options] file.torrent [options]\n\n" \ +"Options:\n" \ +" -h, --help Print this help and exit\n" \ +" -i, --info Print metainfo and exit\n" \ +" -s, --scrape Print counts of seeders/leechers and exit\n" \ +" -v, --verbose Verbose level (0 to 2, default = 0)\n" \ +" -p, --port Port we should listen on (default = 9090)\n" \ +" -u, --upload Maximum upload rate (-1 = no limit, default = 20)\n" + +static int showHelp = 0; +static int showInfo = 0; +static int showScrape = 0; +static int verboseLevel = 0; +static int bindPort = 9090; +static int uploadLimit = 20; +static char * torrentPath = NULL; +static volatile char mustDie = 0; + +static int parseCommandLine ( int argc, char ** argv ); +static void sigHandler ( int signal ); + +int main( int argc, char ** argv ) +{ + int i, count; + tr_handle_t * h; + tr_stat_t * s; + + printf( "Transmission %s - http://transmission.m0k.org/\n\n", + VERSION_STRING ); + + /* Get options */ + if( parseCommandLine( argc, argv ) ) + { + printf( USAGE, argv[0] ); + return 1; + } + + if( showHelp ) + { + printf( USAGE, argv[0] ); + return 0; + } + + if( verboseLevel < 0 ) + { + verboseLevel = 0; + } + else if( verboseLevel > 9 ) + { + verboseLevel = 9; + } + if( verboseLevel ) + { + static char env[11]; + sprintf( env, "TR_DEBUG=%d", verboseLevel ); + putenv( env ); + } + + if( bindPort < 1 || bindPort > 65535 ) + { + printf( "Invalid port '%d'\n", bindPort ); + return 1; + } + + /* Initialize libtransmission */ + h = tr_init(); + + /* Open and parse torrent file */ + if( tr_torrentInit( h, torrentPath ) ) + { + printf( "Failed opening torrent file `%s'\n", torrentPath ); + goto failed; + } + + if( showInfo ) + { + tr_info_t * info; + + count = tr_torrentStat( h, &s ); + info = &s[0].info; + + /* Print torrent info (quite à la btshowmetainfo) */ + printf( "hash: " ); + for( i = 0; i < SHA_DIGEST_LENGTH; i++ ) + { + printf( "%02x", info->hash[i] ); + } + printf( "\n" ); + printf( "tracker: %s:%d\n", + info->trackerAddress, info->trackerPort ); + printf( "announce: %s\n", info->trackerAnnounce ); + printf( "size: %lld (%lld * %d + %lld)\n", + info->totalSize, info->totalSize / info->pieceSize, + info->pieceSize, info->totalSize % info->pieceSize ); + printf( "file(s):\n" ); + for( i = 0; i < info->fileCount; i++ ) + { + printf( " %s (%lld)\n", info->files[i].name, + info->files[i].length ); + } + + free( s ); + goto cleanup; + } + + if( showScrape ) + { + int seeders, leechers; + + if( tr_torrentScrape( h, 0, &seeders, &leechers ) ) + { + printf( "Scrape failed.\n" ); + } + else + { + printf( "%d seeder(s), %d leecher(s).\n", seeders, leechers ); + } + + goto cleanup; + } + + signal( SIGINT, sigHandler ); + + tr_setBindPort( h, bindPort ); + tr_setUploadLimit( h, uploadLimit ); + + tr_torrentSetFolder( h, 0, "." ); + tr_torrentStart( h, 0 ); + + while( !mustDie ) + { + char string[80]; + int chars = 0; + + sleep( 1 ); + + count = tr_torrentStat( h, &s ); + + if( s[0].status & TR_STATUS_CHECK ) + { + chars = snprintf( string, 80, + "Checking files... %.2f %%", 100.0 * s[0].progress ); + } + else if( s[0].status & TR_STATUS_DOWNLOAD ) + { + chars = snprintf( string, 80, + "Progress: %.2f %%, %d peer%s, dl from %d (%.2f kbps), " + "ul to %d (%.2f kbps)", 100.0 * s[0].progress, + s[0].peersTotal, ( s[0].peersTotal == 1 ) ? "" : "s", + s[0].peersUploading, s[0].rateDownload, + s[0].peersDownloading, s[0].rateUpload ); + } + else if( s[0].status & TR_STATUS_SEED ) + { + chars = snprintf( string, 80, + "Seeding, uploading to %d of %d peer(s), %.2f kbps", + s[0].peersDownloading, s[0].peersTotal, + s[0].rateUpload ); + } + memset( &string[chars], ' ', 79 - chars ); + string[79] = '\0'; + fprintf( stderr, "\r%s", string ); + + if( s[0].status & TR_TRACKER_ERROR ) + { + fprintf( stderr, "\n%s\n", s[0].error ); + } + else if( verboseLevel > 0 ) + { + fprintf( stderr, "\n" ); + } + + free( s ); + } + fprintf( stderr, "\n" ); + + /* Try for 5 seconds to notice the tracker that we are leaving */ + tr_torrentStop( h, 0 ); + for( i = 0; i < 10; i++ ) + { + count = tr_torrentStat( h, &s ); + if( s[0].status & TR_STATUS_PAUSE ) + { + /* The 'stopped' message was sent */ + free( s ); + break; + } + free( s ); + usleep( 500000 ); + } + +cleanup: + tr_torrentClose( h, 0 ); + +failed: + tr_close( h ); + + return 0; +} + +static int parseCommandLine( int argc, char ** argv ) +{ + for( ;; ) + { + static struct option long_options[] = + { { "help", no_argument, NULL, 'h' }, + { "info", no_argument, NULL, 'i' }, + { "scrape", no_argument, NULL, 's' }, + { "verbose", required_argument, NULL, 'v' }, + { "port", required_argument, NULL, 'p' }, + { "upload", required_argument, NULL, 'u' }, + { 0, 0, 0, 0 } }; + + int c, optind = 0; + c = getopt_long( argc, argv, "hisv:p:u:", long_options, &optind ); + if( c < 0 ) + { + break; + } + switch( c ) + { + case 'h': + showHelp = 1; + break; + case 'i': + showInfo = 1; + break; + case 's': + showScrape = 1; + break; + case 'v': + verboseLevel = atoi( optarg ); + break; + case 'p': + bindPort = atoi( optarg ); + break; + case 'u': + uploadLimit = atoi( optarg ); + break; + default: + return 1; + } + } + + if( optind > argc - 1 ) + { + return !showHelp; + } + + torrentPath = argv[optind]; + + return 0; +} + +static void sigHandler( int signal ) +{ + switch( signal ) + { + case SIGINT: + mustDie = 1; + break; + + default: + break; + } +}