Import from 2005-10-26

This commit is contained in:
Eric Petit 2006-01-12 17:43:21 +00:00
commit a6aa884776
64 changed files with 8995 additions and 0 deletions

28
AUTHORS Normal file
View File

@ -0,0 +1,28 @@
Authors:
Eric Petit <titer@m0k.org>
+ About everything until now
Thanks to:
vi@nwr.jp
+ Free SHA1 implementation (sha1.[ch])
Mike Matas <http://www.mikematas.com/>
+ 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

46
Jamfile Normal file
View File

@ -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 ;

85
Jamrules Normal file
View File

@ -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"
}
}

23
LICENSE Normal file
View File

@ -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.
-----

22
NEWS Normal file
View File

@ -0,0 +1,22 @@
NEWS file for Transmission <http://transmission.m0k.org/>
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

6
README Normal file
View File

@ -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/

85
configure vendored Executable file
View File

@ -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 <stdio.h>
#include <openssl/sha.h>
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'."

8
libtransmission/Jamfile Normal file
View File

@ -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__ ;

205
libtransmission/bencode.c Normal file
View File

@ -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: l<item1><item2>e
Dict: d<string1><item1><string2><item2>e
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;
}

58
libtransmission/bencode.h Normal file
View File

@ -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

88
libtransmission/clients.c Normal file
View File

@ -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;
}

23
libtransmission/clients.h Normal file
View File

@ -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 * );

View File

@ -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.<hash>".
*
* 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;
}

279
libtransmission/fdlimit.c Normal file
View File

@ -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 );
}

34
libtransmission/fdlimit.h Normal file
View File

@ -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 * );

601
libtransmission/inout.c Normal file
View File

@ -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
}

33
libtransmission/inout.h Normal file
View File

@ -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

184
libtransmission/internal.h Normal file
View File

@ -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 <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <limits.h>
#include <signal.h>
#include <time.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/resource.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <fcntl.h>
#ifdef BEOS_NETSERVER
# define in_port_t uint16_t
#else
# include <arpa/inet.h>
#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 <openssl/sha.h>
#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 <kernel/OS.h>
# 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 <pthread.h>
# 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

282
libtransmission/metainfo.c Normal file
View File

@ -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 );
}
}

View File

@ -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

225
libtransmission/net.c Normal file
View File

@ -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
}

32
libtransmission/net.h Normal file
View File

@ -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 );

464
libtransmission/peer.c Normal file
View File

@ -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;
}

38
libtransmission/peer.h Normal file
View File

@ -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

View File

@ -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;
}
}
}

827
libtransmission/peerutils.h Normal file
View File

@ -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;
}

235
libtransmission/sha1.c Normal file
View File

@ -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
<ghost@aladdin.com>. Thank him since I'm not a good speaker of English. :)
*/
#include <string.h>
#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]);
}
}

68
libtransmission/sha1.h Normal file
View File

@ -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
<ghost@aladdin.com>. 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

597
libtransmission/tracker.c Normal file
View File

@ -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;
}

36
libtransmission/tracker.h Normal file
View File

@ -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

View File

@ -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 );
}

View File

@ -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 <inttypes.h>
#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

136
libtransmission/upload.c Normal file
View File

@ -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 );
}

32
libtransmission/upload.h Normal file
View File

@ -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 * );

63
libtransmission/utils.c Normal file
View File

@ -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;
}

147
libtransmission/utils.h Normal file
View File

@ -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

78
macosx/Controller.h Normal file
View File

@ -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 <Cocoa/Cocoa.h>
#include <transmission.h>
#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

705
macosx/Controller.m Normal file
View File

@ -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 <IOKit/IOMessage.h>
#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, &notify, 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 <NSDraggingInfo>) 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 <NSDraggingInfo>) 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

View File

@ -0,0 +1,4 @@
/* Localized versions of Info.plist keys */
CFBundleName = "Transmission";
NSHumanReadableCopyright = "Copyright 2005 Eric Petit";

View File

@ -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;
}

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IBDocumentLocation</key>
<string>204 84 361 432 0 0 1440 878 </string>
<key>IBEditorPositions</key>
<dict>
<key>29</key>
<string>105 768 371 44 0 0 1280 832 </string>
</dict>
<key>IBFramework Version</key>
<string>439.0</string>
<key>IBOldestOS</key>
<integer>3</integer>
<key>IBOpenObjects</key>
<array>
<integer>21</integer>
<integer>29</integer>
<integer>273</integer>
<integer>343</integer>
</array>
<key>IBSystem Version</key>
<string>8C46</string>
</dict>
</plist>

Binary file not shown.

BIN
macosx/Images/Info.tiff Normal file

Binary file not shown.

BIN
macosx/Images/Open.tiff Normal file

Binary file not shown.

BIN
macosx/Images/Progress.tiff Normal file

Binary file not shown.

BIN
macosx/Images/Remove.tiff Normal file

Binary file not shown.

BIN
macosx/Images/Resume.tiff Normal file

Binary file not shown.

Binary file not shown.

BIN
macosx/Images/RevealOn.tiff Normal file

Binary file not shown.

BIN
macosx/Images/Stop.tiff Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

45
macosx/Info.plist.in Normal file
View File

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>English</string>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeExtensions</key>
<array>
<string>torrent</string>
</array>
<key>CFBundleTypeIconFile</key>
<string>TransmissionDocument</string>
<key>CFBundleTypeName</key>
<string>BitTorrent Document</string>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSTypeIsPackage</key>
<false/>
<key>NSPersistentStoreTypeKey</key>
<string>XML</string>
</dict>
</array>
<key>CFBundleExecutable</key>
<string>Transmission</string>
<key>CFBundleIconFile</key>
<string>Transmission</string>
<key>CFBundleIdentifier</key>
<string>org.m0k.transmission</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleSignature</key>
<string>TR##</string>
<key>CFBundleVersion</key>
<string>%%VERSION%%</string>
<key>NSMainNibFile</key>
<string>MainMenu</string>
<key>NSPrincipalClass</key>
<string>NSApplication</string>
</dict>
</plist>

33
macosx/NameCell.h Normal file
View File

@ -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 <Cocoa/Cocoa.h>
#include <transmission.h>
@interface NameCell : NSCell
{
tr_stat_t * fStat;
NSRect fRevealRect;
NSPoint fClickPoint;
}
- (void) setStat: (tr_stat_t *) stat;
@end

167
macosx/NameCell.m Normal file
View File

@ -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

49
macosx/PrefsController.h Normal file
View File

@ -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 <Cocoa/Cocoa.h>
#include <transmission.h>
@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

374
macosx/PrefsController.m Normal file
View File

@ -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) */

43
macosx/ProgressCell.h Normal file
View File

@ -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 <Cocoa/Cocoa.h>
#include <transmission.h>
@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

257
macosx/ProgressCell.m Normal file
View File

@ -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

View File

@ -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 = "<group>"; };
1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = /System/Library/Frameworks/Cocoa.framework; sourceTree = "<absolute>"; };
13E42FB307B3F0F600E4EEF1 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = /System/Library/Frameworks/CoreData.framework; sourceTree = "<absolute>"; };
29B97316FDCFA39411CA2CEA /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
29B97319FDCFA39411CA2CEA /* English */ = {isa = PBXFileReference; lastKnownFileType = wrapper.nib; name = English; path = English.lproj/MainMenu.nib; sourceTree = "<group>"; };
29B97324FDCFA39411CA2CEA /* AppKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppKit.framework; path = /System/Library/Frameworks/AppKit.framework; sourceTree = "<absolute>"; };
29B97325FDCFA39411CA2CEA /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = /System/Library/Frameworks/Foundation.framework; sourceTree = "<absolute>"; };
32CA4F630368D1EE00C91783 /* Transmission_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Transmission_Prefix.pch; sourceTree = "<group>"; };
4D043A7E090AE979009FEDA8 /* TransmissionDocument.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; name = TransmissionDocument.icns; path = Images/TransmissionDocument.icns; sourceTree = "<group>"; };
4D096C0E089FB4E20091B166 /* NameCell.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = NameCell.h; sourceTree = "<group>"; };
4D096C0F089FB4E20091B166 /* NameCell.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = NameCell.m; sourceTree = "<group>"; };
4D096C10089FB4E20091B166 /* ProgressCell.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = ProgressCell.h; sourceTree = "<group>"; };
4D096C11089FB4E20091B166 /* ProgressCell.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = ProgressCell.m; sourceTree = "<group>"; };
4D118E1808CB46B20033958F /* PrefsController.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = PrefsController.h; sourceTree = "<group>"; };
4D118E1908CB46B20033958F /* PrefsController.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = PrefsController.m; sourceTree = "<group>"; };
4D2784360905709500687951 /* Transmission.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; name = Transmission.icns; path = Images/Transmission.icns; sourceTree = "<group>"; };
4D3EA0A908AE13C600EA10C2 /* IOKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOKit.framework; path = /System/Library/Frameworks/IOKit.framework; sourceTree = "<absolute>"; };
4D6DAAC4090CE00500F43C22 /* RevealOff.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; name = RevealOff.tiff; path = Images/RevealOff.tiff; sourceTree = "<group>"; };
4D6DAAC5090CE00500F43C22 /* RevealOn.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; name = RevealOn.tiff; path = Images/RevealOn.tiff; sourceTree = "<group>"; };
4D813EB408AA43AC00191DB4 /* Progress.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; name = Progress.tiff; path = Images/Progress.tiff; sourceTree = "<group>"; };
4DF0C5A90899190500DD8943 /* Controller.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = Controller.m; sourceTree = "<group>"; };
4DF0C5AA0899190500DD8943 /* Controller.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Controller.h; sourceTree = "<group>"; };
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 = "<group>"; };
4DF7500808A103AD007B0D70 /* Info.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; name = Info.tiff; path = Images/Info.tiff; sourceTree = "<group>"; };
4DF7500908A103AD007B0D70 /* Remove.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; name = Remove.tiff; path = Images/Remove.tiff; sourceTree = "<group>"; };
4DF7500A08A103AD007B0D70 /* Resume.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; name = Resume.tiff; path = Images/Resume.tiff; sourceTree = "<group>"; };
4DF7500B08A103AD007B0D70 /* Stop.tiff */ = {isa = PBXFileReference; lastKnownFileType = image.tiff; name = Stop.tiff; path = Images/Stop.tiff; sourceTree = "<group>"; };
8D1107310486CEB800E47090 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
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 = "<group>";
};
1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */ = {
isa = PBXGroup;
children = (
1058C7A1FEA54F0111CA2CBB /* Cocoa.framework */,
);
name = "Linked Frameworks";
sourceTree = "<group>";
};
1058C7A2FEA54F0111CA2CBB /* Other Frameworks */ = {
isa = PBXGroup;
children = (
4D3EA0A908AE13C600EA10C2 /* IOKit.framework */,
29B97324FDCFA39411CA2CEA /* AppKit.framework */,
13E42FB307B3F0F600E4EEF1 /* CoreData.framework */,
29B97325FDCFA39411CA2CEA /* Foundation.framework */,
);
name = "Other Frameworks";
sourceTree = "<group>";
};
19C28FACFE9D520D11CA2CBB /* Products */ = {
isa = PBXGroup;
children = (
8D1107320486CEB800E47090 /* Transmission.app */,
);
name = Products;
sourceTree = "<group>";
};
29B97314FDCFA39411CA2CEA /* Transmission */ = {
isa = PBXGroup;
children = (
4DF0C5AD08991C1600DD8943 /* libtransmission.a */,
080E96DDFE201D6D7F000001 /* Classes */,
29B97315FDCFA39411CA2CEA /* Other Sources */,
29B97317FDCFA39411CA2CEA /* Resources */,
29B97323FDCFA39411CA2CEA /* Frameworks */,
19C28FACFE9D520D11CA2CBB /* Products */,
);
name = Transmission;
sourceTree = "<group>";
};
29B97315FDCFA39411CA2CEA /* Other Sources */ = {
isa = PBXGroup;
children = (
32CA4F630368D1EE00C91783 /* Transmission_Prefix.pch */,
29B97316FDCFA39411CA2CEA /* main.m */,
);
name = "Other Sources";
sourceTree = "<group>";
};
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 = "<group>";
};
29B97323FDCFA39411CA2CEA /* Frameworks */ = {
isa = PBXGroup;
children = (
1058C7A0FEA54F0111CA2CBB /* Linked Frameworks */,
1058C7A2FEA54F0111CA2CBB /* Other Frameworks */,
);
name = Frameworks;
sourceTree = "<group>";
};
/* 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 = "<group>";
};
29B97318FDCFA39411CA2CEA /* MainMenu.nib */ = {
isa = PBXVariantGroup;
children = (
29B97319FDCFA39411CA2CEA /* English */,
);
name = MainMenu.nib;
sourceTree = "<group>";
};
/* 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 */;
}

View File

@ -0,0 +1,3 @@
#ifdef __OBJC__
#import <Cocoa/Cocoa.h>
#endif

74
macosx/Utils.h Normal file
View File

@ -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;
}

36
macosx/main.m Normal file
View File

@ -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 <Cocoa/Cocoa.h>
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 );
}

297
transmissioncli.c Normal file
View File

@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <getopt.h>
#include <signal.h>
#include <transmission.h>
#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 <int> Verbose level (0 to 2, default = 0)\n" \
" -p, --port <int> Port we should listen on (default = 9090)\n" \
" -u, --upload <int> 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;
}
}