Added:
trunk/tatoeba/
trunk/tatoeba/000-basic.pl (contents, props changed)
trunk/tatoeba/001-torrent-info.pl (contents, props changed)
Modified:
trunk/Build.PL
trunk/Changes
trunk/MANIFEST
trunk/MANIFEST.SKIP
trunk/Makefile.PL
trunk/Notes.pod
trunk/TODO.pod
trunk/lib/Net/BitTorrent.pm
trunk/lib/Net/BitTorrent/DHT.pm
trunk/lib/Net/BitTorrent/Peer.pm
trunk/lib/Net/BitTorrent/Torrent.pm
trunk/lib/Net/BitTorrent/Torrent/Tracker.pm
trunk/lib/Net/BitTorrent/Torrent/Tracker/HTTP.pm
trunk/lib/Net/BitTorrent/Torrent/Tracker/UDP.pm
trunk/lib/Net/BitTorrent/Version.pm
trunk/scripts/bittorrent.pl
trunk/t/700_classes/Net/BitTorrent/Torrent/Tracker.t
trunk/t/700_classes/Net/BitTorrent/Torrent/Tracker/HTTP.t
trunk/t/700_classes/Net/BitTorrent/Torrent/Tracker/UDP.t
Log:
Major API-related bug fix and first few example scripts
- [doc] Removed reference to N::B::DHT::Node from N::B::Notes
- [doc] Various documentation changes in N::B::Notes
- [etc] Makefile.pl tries to require Module::Build 0.3
- [api] Separated 'Path' arg tests in Net::BitTorrent::Torrent to provide
more appropriate error messages
- [fix] r45 N::B::Torrent->new failed to set set defaults, generate a
peerid, or create a DHT object when called without arguments.
- [api] N::B::T::Tracker->urls() is now public
- [api] N::B::T::HTTP->url() and N::B::T::UDP->url() are now public
- [etc] tracker-related tests updated to new url() and urls() public status
Modified: trunk/Build.PL
==============================================================================
--- trunk/Build.PL (original)
+++ trunk/Build.PL Tue Dec 30 15:25:17 2008
@@ -59,7 +59,7 @@
if (-e 'nytprof.out') {
my $pm_files =
$self->rscan_dir(File::Spec->catdir($self->blib, 'lib'),
- file_qr('\.pm$'));
+ qr[\.pm$]);
my $cover_files
= $self->rscan_dir('cover_db', sub { -f $_ and not /\.html$/
});
$self->do_system(qw(cover -delete))
@@ -81,9 +81,12 @@
return;
}
require Perl::Tidy;
+ my $demo_files =
+ $self->rscan_dir(File::Spec->catdir('tatoeba'), qr[\.pl$]);
for my $files ([keys(%{$self->script_files})], # scripts first
[values(%{$self->find_pm_files})], # modules
- [@{$self->find_test_files}] # test suite last
+ [@{$self->find_test_files}], # test suite next
+ [@{$demo_files}] # demos last
)
{ $files = [sort map { File::Spec->rel2abs(q[./] . $_) } @{$files}];
@@ -158,11 +161,14 @@
sub ACTION_spellcheck {
my ($self) = @_;
+ my $demo_files =
+ $self->rscan_dir(File::Spec->catdir('tatoeba'), qr[\.pl$]);
for my $files (
[keys(%{$self->script_files})], # scripts first
[values(%{$self->find_pm_files})], # modules
- [@{$self->find_test_files}], # test suite and docs
- [values(%{shift->_find_file_by_type(q[pod], q[.])})]
+ [@{$self->find_test_files}], # test suite
+ [values(%{shift->_find_file_by_type(q[pod], q[.])})], # docs
+ [@{$demo_files}] # demos
)
{ $files = [sort map { File::Spec->rel2abs(q[./] . $_) } @{$files}];
for my $file (@$files) {
@@ -179,12 +185,12 @@
}
SUBCLASS
my $mb = $class->new(
- module_name => q[Net::BitTorrent],
- license => q[artistic_2],
- dist_author => q[Sanko Robinson <sa...@cpan.org>],
- dist_abstract => q[BitTorrent peer-to-peer protocol],
- dist_version_from => q[lib/Net/BitTorrent/Version.pm],
- requires => {
+ module_name => q[Net::BitTorrent],
+ license => q[artistic_2],
+ dist_author => q[Sanko Robinson <sa...@cpan.org>],
+ dist_abstract => q[BitTorrent peer-to-peer protocol],
+ dist_version_from => q[lib/Net/BitTorrent/Version.pm],
+ requires => {
q[Cwd] => 0,
q[Digest::SHA] => 5.45,
q[Errno] => 0,
@@ -226,6 +232,7 @@
script_files => qw[scripts/bittorrent.pl],
test_files => \@tests,
meta_merge => {
+ no_index => {directory => [q[tatoeba]]},
resources => {
bugtracker =>
q[http://code.google.com/p/net-bittorrent/issues/list],
@@ -237,7 +244,7 @@
q[http://code.google.com/p/net-bittorrent/source/browse/]
},
keywords => [qw[BitTorrent client peer p2p torrent socket
dht]],
- generated_by => q[Sanko Robinson <sa...@cpan.org>]
+ generated_by => q[Sanko Robinson version 1.0 (stable ...usually)]
},
);
$mb->notes(okay_tcp => $okay_tcp);
Modified: trunk/Changes
==============================================================================
--- trunk/Changes (original)
+++ trunk/Changes Tue Dec 30 15:25:17 2008
@@ -1,4 +1,26 @@
-Version 0.045 |
+Version 0.046 |
+
+ API Changes/Compatibility Information:
+ - Net::BitTorrent::Torrent::HTTP->url() is now public
+ - Net::BitTorrent::Torrent::UDP->url() is now public
+
+ Resolved Issues/Bugfixes:
+ - In 0.045, if no arguments were passed, Net::BitTorrent->new() failed to
+ set set defaults, generate a peerid, or create a DHT object.
+
+ Protocol/Behavioral Changes:
+ - None
+
+ Documentation/Sample Code/Test Suite:
+ - The first two in a series of demonstration scripts are in the
/tatoeba/ directory
+ - Minor tweaking and clean up in Net::BitTorrent::Notes
+
+ Notes:
+ - This is a major bugfix release with which introduces no
incompatabilities.
+ Upgrade is highly recommended.
+
+---
+Version 0.045 | 2008-12-26 17:17:16 -0500 (Fri, 26 Dec 2008)
API Changes/Compatibility Information:
- [Beta] Torrent resume system (see Net::BitTorrent::Notes).
Modified: trunk/MANIFEST
==============================================================================
--- trunk/MANIFEST (original)
+++ trunk/MANIFEST Tue Dec 30 15:25:17 2008
@@ -43,4 +43,6 @@
t/900_data/950_torrents/954_perl-5.10.0.tar.gz.torrent
t/900_data/950_torrents/955_unicode.torrent
t/900_data/950_torrents/credits.txt
+tatoeba/000-basic.pl
+tatoeba/001-torrent-info.pl
TODO.pod
Modified: trunk/MANIFEST.SKIP
==============================================================================
--- trunk/MANIFEST.SKIP (original)
+++ trunk/MANIFEST.SKIP Tue Dec 30 15:25:17 2008
@@ -50,13 +50,16 @@
scripts/gui\.xrc
scripts/bittorrent_full.pl
scripts/POE\.pl
-scripts/web.*gui\.pl
+scripts/webg?ui\.pl
scripts/x\.pl
scripts/.*\.zip
scripts/.*\.js
scripts/.*\.bencode
scripts/.*\.torrent
scripts/.+/.+
+
+# Don't package silly stuff
+tatoeba/.+/.+
# Don't package downloaded/perif files
\.avi
Modified: trunk/Makefile.PL
==============================================================================
--- trunk/Makefile.PL (original)
+++ trunk/Makefile.PL Tue Dec 30 15:25:17 2008
@@ -4,14 +4,15 @@
BEGIN { exit 0 if $] < 5.008001 }
require 5.8.1;
$|++;
-unless (eval q[use Module::Build::Compat 0.02; 1;]) {
+if (!eval q[use Module::Build::Compat 0.02; 1;]) {
print qq[This module requires Module::Build to install itself.\n];
require ExtUtils::MakeMaker;
my $yn =
ExtUtils::MakeMaker::prompt(q[ Install Module::Build now from
CPAN?],
q[y]);
- unless ($yn =~ m[^y]i) {
- die qq[ *** Cannot install without Module::Build. Exiting ...\n];
+ if ($yn !~ m[^y]i) {
+ warn qq[ *** Cannot install without Module::Build. Exiting ...\n];
+ exit 0;
}
require Cwd;
require File::Spec;
@@ -20,11 +21,19 @@
# Save this 'cause CPAN will chdir all over the place.
my $cwd = Cwd::cwd();
CPAN::Shell->install(q[Module::Build::Compat]);
- CPAN::Shell->expand(q[Module], q[Module::Build::Compat])->uptodate
- or die qq[Couldn't install Module::Build, giving up.\n];
- chdir $cwd or die qq[Cannot chdir() back to $cwd: $!];
+ if (!CPAN::Shell->expand(q[Module],
q[Module::Build::Compat])->uptodate) {
+ warn qq[Couldn't install Module::Build, giving up.\n];
+ exit 0;
+ }
+ if (!chdir $cwd) {
+ warn qq[Cannot chdir() back to $cwd: $!];
+ exit 0;
+ }
+}
+if (!eval q[use Module::Build::Compat 0.02; use Module::Build 0.30; 1]) {
+ warn $@;
+ exit 0;
}
-eval q[use Module::Build::Compat 0.02; 1] or die $@;
Module::Build::Compat->run_build_pl(args => \@ARGV);
require Module::Build;
Module::Build::Compat->write_makefile(build_class => q[Module::Build]);
Modified: trunk/Notes.pod
==============================================================================
--- trunk/Notes.pod (original)
+++ trunk/Notes.pod Tue Dec 30 15:25:17 2008
@@ -21,8 +21,8 @@
.---- Net::BitTorrent::Torrent
/
/ .--- Net::BitTorrent::DHT
- / / \
- Net::BitTorrent `---- N::B::D::Node
+ / /
+ Net::BitTorrent
\
`---- Net::BitTorrent::Peer
@@ -88,11 +88,8 @@
Note: This section describes resume functionality as of C<v0.045>.
C<Net::BitTorrent> has a complete resume system which is capable of
-resuming single torrents, but manually restoring DHT data, or even
-client-wide data and settings is trivial.
-
-The resume data is not checked for tampering, so there are a few things
-to remember when handling it:
+resuming single torrents. The resume data is not checked for tampering,
+so there are a few things to remember when handling it:X<badresumedata>
=over
@@ -115,7 +112,7 @@
To resume a single torrent, use a variation of the following to save the
data...
- my $torrent = $bt->add_torrent( { Path=> 'some.torrent', [...] });
+ my $torrent = $bt->add_torrent( { Path=> 'some.torrent' });
# later...
@@ -126,11 +123,16 @@
To resume, simply load the new .torrent file as usual:
- my $torrent = $bt->add_torrent( { Path=> 'some.torrent', [...] });
+ my $torrent = $bt->add_torrent( { Path=> 'some.torrent' });
-...and unless C<Net::BitTorrent> decides the resume data is bad, you'll
-start right were you left off. I would suggest storing resume data on a
-regular basis while the client runs and again on exit.
+...and unless C<Net::BitTorrent> decides the resume data is
+L<bad|/badresumedata>, you'll start right were you left off. I would
suggest
+storing resume data on a regular basis while the client runs and again on
+exit.
+
+For more on what exactly you're saving and the structure of the data, see
+L<Resume API|/"Resume API"> in the
+<Net::BitTorrent Internals/|"Net::BitTorrent Internals"> section.
=head2 Save and Restore Client-wide State and DHT Data
@@ -217,7 +219,7 @@
found in this distribution but they are noted in the public SVN
repository's log.
-=head2 Set File Priorities
+=head2 Set File Download Priorities
See
L<Net::BitTorrent::Torrent::File|Net::BitTorrent::Torrent::File/"priority(
[NEWVAL] )">.
@@ -233,7 +235,8 @@
=head2 Peer ID Specification
-Please see L<Net::BitTorrent::Version|Net::BitTorrent::Version>.
+Please see
+L<Net::BitTorrent::Version|Net::BitTorrent::Version/"Peer ID
Specification">.
=head2 Handling of Errors and Bad Data
@@ -405,7 +408,7 @@
=head3 Accepted BEPs
These BEPs describe mechanisms that have been deployed in one or more
-BitTorrent implementations and have proven useful. They may require
+BitTorrent implementations and have proved useful. They may require
minor revisions. They await the blessing of the BDFL before they can be
considered Final.
@@ -588,7 +591,18 @@
=head3 Compatibility Notes
-TODO
+This section lists recent major changes in API or behavior between stable
+releases. For older news see the F<Changes> file included with this
+distribution. For detail see the SVN logs.
+
+=over
+
+=item v0.040
+
+Entire distribution was rewritten. Both the internal and the API have
+broken compatibility.
+
+=back
=head1 Giving back
@@ -707,22 +721,20 @@
=head1 See Also
-=over
+=head2 Support and Information Links for C<Net::BitTorrent>
-=item Support and Information Links for C<Net::BitTorrent>
-
-=over 4
+=over
=item The Project's Website
-For updates and links to the wiki and subversion repository access,
-please visit http://sankorobinson.com/net-bittorrent/.
+For updates and info on subversion repository access and the occasional
+long winded rant, please visit http://sankorobinson.com/net-bittorrent/.
=item Receive SVN Commit and Issue Tracker Updates
-The preferred way is to subscribe to one of the feeds of the announce
-and discussion group. Both ATOM 1.0 and RSS 2.0 feeds are provided;
-see http://groups.google.com/group/net-bittorrent/feeds for a list.
+The preferred way is to subscribe to one of the feeds provided by Google.
+ATOM feeds, Gadgets, and CSV files are provided for various data. See
+http://code.google.com/p/net-bittorrent/feeds for a list.
=item Public Mailinglist
@@ -730,7 +742,9 @@
just nice having a searchable, public archive), general questions and
comments should be posted to the C<Net::BitTorrent> mailing list. To
subscribe to the list or view the archive, visit
-http://groups.google.com/group/net-bittorrent.
+http://groups.google.com/group/net-bittorrent. Both ATOM 1.0 and RSS
+2.0 feeds are provided; see
+http://groups.google.com/group/net-bittorrent/feeds for a list.
=item Issue Tracker
@@ -741,7 +755,7 @@
=item Stalk Me While I Tinker
Follow C<Net::BitTorrent>'s development on Twitter:
-http://twitter.com/Net_BitTorrent.
+http://twitter.com/net_bitTorrent.
=item Ohloh
@@ -753,9 +767,9 @@
=back
-=item Other Recommend Open Source BitTorrent Clients
+=head2 Other Recommend Open Source BitTorrent Clients
-=over 4
+=over
=item *
@@ -765,7 +779,7 @@
=item *
Bitflu (L<http://bitflu.workaround.ch/>) is a full client written in
-(*nix oriented) Perl and available under the Perl/Artistic License.
+*nix oriented Perl. It is available under the Perl/Artistic License.
=item *
@@ -779,6 +793,10 @@
libtorrent, written in C++ and released under the MIT License.
=back
+
+=head
+
+=over
=item RFC 3986 (URI: Generic Syntax)
Modified: trunk/TODO.pod
==============================================================================
--- trunk/TODO.pod (original)
+++ trunk/TODO.pod Tue Dec 30 15:25:17 2008
@@ -8,7 +8,24 @@
=over
-=item * Flesh out the test suite
+=item * Flesh out the test suite and example scripts
+
+Create a series of bad .torrent files to test N::B::Torrent with:
+
+=over
+
+=item * invalid pieces string
+
+=over
+
+=item * string length does not % 40 evenly
+
+=item * string is shorter than 40 chars
+
+=item * (piece_length * num_pieces) != total_size_of_torrent
+
+=back
+
=item * Document DHT stuff in N::B::Protocol
@@ -37,33 +54,13 @@
=item * Net::BitTorrent::Notes
-=item * Complete test suite
-
-Create a series of bad .torrent files to test N::B::Torrent with:
-
-=over
-
-=item * invalid pieces string
-
-=over
-
-=item * string length does not % 40 evenly
-
-=item * string is shorter than 40 chars
-
-=item * (piece_length * num_pieces) != total_size_of_torrent
-
-=back
-
-=back
-
=item * Per-torrent transfer limits.
=item * improve file handling
=over
-=item * large torrents (> 4G) typically require 64bit math
+=item * large files (> 4G) typically require 64bit math
=item * intermediate .piece file to store incoming blocks
Modified: trunk/lib/Net/BitTorrent.pm
==============================================================================
--- trunk/lib/Net/BitTorrent.pm (original)
+++ trunk/lib/Net/BitTorrent.pm Tue Dec 30 15:25:17 2008
@@ -21,7 +21,7 @@
use Net::BitTorrent::Version;
use version qw[qv];
our $SVN = q[$Id$];
- our $UNSTABLE_RELEASE = 4; our $VERSION = sprintf(($UNSTABLE_RELEASE ?
q[%.3f_%03d] : q[%.3f]), (version->new((qw$Rev$)[1])->numify / 1000),
$UNSTABLE_RELEASE);
+ our $UNSTABLE_RELEASE = 0; our $VERSION = sprintf(($UNSTABLE_RELEASE ?
q[%.3f_%03d] : q[%.3f]), (version->new((qw$Rev$)[1])->numify / 1000),
$UNSTABLE_RELEASE);
my (@CONTENTS)
= \my (%_tcp, %_udp,
%_schedule, %_tid,
@@ -39,28 +39,28 @@
my ($class, $args) = @_;
my $self = bless \$class, $class;
my ($host, @ports) = (q[0.0.0.0], (0));
+
+ # Defaults
+ $_peers_per_torrent{refaddr $self} = 100;
+ $_connections_per_host{refaddr $self} = 2;
+ $_half_open{refaddr $self} = 8;
+ $_max_dl_rate{refaddr $self} = 0;
+ $_k_dl{refaddr $self} = 0;
+ $_max_ul_rate{refaddr $self} = 0;
+ $_k_ul{refaddr $self} = 0;
+ $_torrents{refaddr $self} = {};
+ $_tid{refaddr $self} = qq[\0] x 5;
+ $_use_dht{refaddr $self} = 1;
+ $_dht{refaddr $self} = Net::BitTorrent::DHT->new({Client =>
$self});
+ $_peerid{refaddr $self} =
Net::BitTorrent::Version::gen_peerid();
+ $_connections{refaddr $self} = {};
+
if (defined $args) {
if (ref($args) ne q[HASH]) {
carp q[Net::BitTorrent->new({}) requires ]
. q[parameters to be passed as a hashref];
return;
}
-
- # Defaults
- $_peers_per_torrent{refaddr $self} = 100;
- $_connections_per_host{refaddr $self} = 2;
- $_half_open{refaddr $self} = 8;
- $_max_dl_rate{refaddr $self} = 0;
- $_k_dl{refaddr $self} = 0;
- $_max_ul_rate{refaddr $self} = 0;
- $_k_ul{refaddr $self} = 0;
- $_torrents{refaddr $self} = {};
- $_tid{refaddr $self} = qq[\0] x 5;
- $_use_dht{refaddr $self} = 1;
- $_dht{refaddr $self}
- = Net::BitTorrent::DHT->new({Client => $self});
- $_peerid{refaddr $self} =
Net::BitTorrent::Version::gen_peerid();
- $_connections{refaddr $self} = {};
$host = $args->{q[LocalHost]}
if defined $args->{q[LocalHost]};
@ports
@@ -169,8 +169,8 @@
}
# Accessors | Public
- sub peerid { return $_peerid{refaddr +shift} }
- sub torrents { return $_torrents{refaddr +shift} }
+ sub peerid { my ($self) = @_; return $_peerid{refaddr $self} }
+ sub torrents { my ($self) = @_; return $_torrents{refaddr $self} }
# Methods | Public
sub do_one_loop {
@@ -436,7 +436,7 @@
q[Net::BitTorrent::Torrent::Tracker::UDP]
)
and $_->_packed_host eq $paddr
- } @{$_tier->_urls};
+ } @{$_tier->urls};
if ( $tracker
&& $tracker->_on_data($paddr, $data))
{ $__UDP_OBJECT_CACHE{refaddr
$self}{$paddr}
@@ -535,7 +535,7 @@
q[Removing .torrent torrent from local
client]);
}
for my $_tracker (@{$torrent->trackers}) {
- $_tracker->_urls->[0]->_announce(q[stopped]);
+ $_tracker->urls->[0]->_announce(q[stopped]);
}
return delete $_torrents{refaddr $self}{$torrent->infohash};
}
Modified: trunk/lib/Net/BitTorrent/DHT.pm
==============================================================================
--- trunk/lib/Net/BitTorrent/DHT.pm (original)
+++ trunk/lib/Net/BitTorrent/DHT.pm Tue Dec 30 15:25:17 2008
@@ -13,7 +13,7 @@
use Net::BitTorrent::Version;
use version qw[qv];
our $SVN = q[$Id$];
- our $UNSTABLE_RELEASE = 4; our $VERSION = sprintf(($UNSTABLE_RELEASE ?
q[%.3f_%03d] : q[%.3f]), (version->new((qw$Rev$)[1])->numify / 1000),
$UNSTABLE_RELEASE);
+ our $UNSTABLE_RELEASE = 0; our $VERSION = sprintf(($UNSTABLE_RELEASE ?
q[%.3f_%03d] : q[%.3f]), (version->new((qw$Rev$)[1])->numify / 1000),
$UNSTABLE_RELEASE);
my @CONTENTS
= \my
(%_client, %tid, %node_id, %outstanding_p, %nodes, %tracking);
my %REGISTRY;
Modified: trunk/lib/Net/BitTorrent/Peer.pm
==============================================================================
--- trunk/lib/Net/BitTorrent/Peer.pm (original)
+++ trunk/lib/Net/BitTorrent/Peer.pm Tue Dec 30 15:25:17 2008
@@ -10,7 +10,7 @@
use Fcntl qw[F_SETFL O_NONBLOCK];
use version qw[qv];
our $SVN = q[$Id$];
- our $UNSTABLE_RELEASE = 6; our $VERSION = sprintf(($UNSTABLE_RELEASE ?
q[%.3f_%03d] : q[%.3f]), (version->new((qw$Rev$)[1])->numify / 1000),
$UNSTABLE_RELEASE);
+ our $UNSTABLE_RELEASE = 0; our $VERSION = sprintf(($UNSTABLE_RELEASE ?
q[%.3f_%03d] : q[%.3f]), (version->new((qw$Rev$)[1])->numify / 1000),
$UNSTABLE_RELEASE);
use lib q[../../../lib];
use Net::BitTorrent::Protocol qw[:build parse_packet :types];
use Net::BitTorrent::Util qw[:bencode];
Modified: trunk/lib/Net/BitTorrent/Torrent.pm
==============================================================================
--- trunk/lib/Net/BitTorrent/Torrent.pm (original)
+++ trunk/lib/Net/BitTorrent/Torrent.pm Tue Dec 30 15:25:17 2008
@@ -25,7 +25,7 @@
use Net::BitTorrent::Torrent::Tracker;
use version qw[qv];
our $SVN = q[$Id$];
- our $UNSTABLE_RELEASE = 10; our $VERSION =
sprintf(($UNSTABLE_RELEASE ? q[%.3f_%03d] : q[%.3f]),
(version->new((qw$Rev$)[1])->numify / 1000), $UNSTABLE_RELEASE);
+ our $UNSTABLE_RELEASE = 0; our $VERSION = sprintf(($UNSTABLE_RELEASE ?
q[%.3f_%03d] : q[%.3f]), (version->new((qw$Rev$)[1])->numify / 1000),
$UNSTABLE_RELEASE);
my %REGISTRY = ();
my @CONTENTS = \my (%_client, %path, %_basedir,
%size, %files, %trackers,
@@ -47,33 +47,40 @@
my ($class, $args) = @_;
my $self = bless \$class, $class;
if ((!$args) || (ref($args) ne q[HASH])) {
- carp q[Net::BitTorrent::Torrent->new({}) requires ]
+ carp q[Net::BitTorrent::Torrent->new({ }) requires ]
. q[parameters to be passed as a hashref];
return;
}
- if ((!$args->{q[Path]}) || (not -f $args->{q[Path]})) {
+ if (!$args->{q[Path]}) {
carp
sprintf(
- q[Net::BitTorrent::Torrent->new({}) requires a 'Path'
parameter]
+ q[Net::BitTorrent::Torrent->new({ }) requires a 'Path'
parameter]
);
return;
}
+ if (not -f $args->{q[Path]}) {
+ carp
+ sprintf(
+ q[Net::BitTorrent::Torrent->new({ }) cannot
find '%s'],
+ $args->{q[Path]});
+ return;
+ }
if (($args->{q[Client]})
&& ( (!blessed $args->{q[Client]})
|| (!$args->{q[Client]}->isa(q[Net::BitTorrent])))
)
- { carp q[Net::BitTorrent::Torrent->new({}) requires a ]
+ { carp q[Net::BitTorrent::Torrent->new({ }) requires a ]
. q[blessed Net::BitTorrent object in the 'Client'
parameter];
return;
}
if ( $args->{q[BlockLength]}
and $args->{q[BlockLength]} !~ m[^\d+$])
- { carp q[Net::BitTorrent::Torrent->new({}) requires an ]
+ { carp q[Net::BitTorrent::Torrent->new({ }) requires an ]
. q[integer 'BlockLength' parameter];
delete $args->{q[BlockLength]};
}
if ($args->{q[Status]} and $args->{q[Status]} !~ m[^\d+$]) {
- carp q[Net::BitTorrent::Torrent->new({}) requires an ]
+ carp q[Net::BitTorrent::Torrent->new({ }) requires an ]
. q[integer 'Status' parameter];
delete $args->{q[Status]};
}
@@ -84,15 +91,15 @@
if (not sysopen($TORRENT_FH, $args->{q[Path]}, O_RDONLY)) {
carp
sprintf(
- q[Net::BitTorrent::Torrent->new({}) could not
open '%s': %s],
- $args->{q[Path]}, $!);
+ q[Net::BitTorrent::Torrent->new({ }) could not
open '%s': %s],
+ $args->{q[Path]}, $!);
return;
}
flock($TORRENT_FH, LOCK_SH);
if (sysread($TORRENT_FH, $TORRENT_RAW, -s $args->{q[Path]})
!= -s $args->{q[Path]})
{ carp sprintf(
- q[Net::BitTorrent::Torrent->new({}) could not read all %d
bytes of '%s' (Read %d instead)],
+ q[Net::BitTorrent::Torrent->new({ }) could not read all %d
bytes of '%s' (Read %d instead)],
-s $args->{q[Path]},
$args->{q[Path]}, length($TORRENT_RAW)
);
@@ -239,8 +246,8 @@
&& $raw_data{refaddr
$self}{q[net-bittorrent]}{q[files]}
[$_index]{q[mtime]}
)
- || ((stat($files{refaddr $self}->[$_index]->path))[9]||
0
- != $raw_data{refaddr $self}{q[net-bittorrent]}
+ || ((stat($files{refaddr $self}->[$_index]->path))[9]
+ || 0 != $raw_data{refaddr $self}{q[net-bittorrent]}
{q[files]}[$_index]{q[mtime]})
)
{ ${$status{refaddr $self}} |= START_AFTER_CHECK;
@@ -1070,32 +1077,46 @@
sub as_string {
my ($self, $advanced) = @_;
my $wanted = $self->_wanted;
- my $dump = !$advanced ? $self->infohash : sprintf <<'END',
+ my $dump
+ = !$advanced ? $self->infohash : sprintf <<'END',
Net::BitTorrent::Torrent
-Path: %s
-Name: %s
-Storage: %s
-Infohash: %s
-Size: %s bytes
-Status: %d
-Progress: %3.2f%% complete (%d bytes up / %d bytes down)
+Path: %s
+Name: %s
+Infohash: %s
+Base Directory: %s
+Size: %s bytes
+Status: %d (%s.)
+DHT Status: %s
+Progress: %3.2f%% complete (%d bytes up / %d bytes down)
[%s]
----------
Pieces: %d x %d bytes
Working: %s
%s
----------
-Files: %s
+ ...has %d file%s:
+ %s
----------
-Trackers: %s
+ ...has %d tracker tier%s:
+ %s
----------
-DHT Status: %s
END
- $self->path(),
- $raw_data{refaddr $self}{q[info]}{q[name]},
- $_basedir{refaddr $self}, $self->infohash(),
- $size{refaddr $self},
- ${$status{refaddr $self}}, # TODO: plain English
+ $self->path, $raw_data{refaddr $self}{q[info]}{q[name]},
+ $self->infohash(), $_basedir{refaddr $self}, $size{refaddr
$self},
+ ${$status{refaddr $self}}, sub {
+ my ($s) = @_;
+ return ucfirst join q[, ],
+ grep {$_} ($s & LOADED) ? q[was loaded okay] : q[],
+ ($s & STARTED) ? q[is started] :
q[],
+ ($s & CHECKING) ? q[is currently hashchecking] :
q[],
+ ($s & START_AFTER_CHECK) ? q[needs hashchecking] :
q[],
+ ($s & CHECKED) ? q[has been checked] :
q[],
+ ($s & PAUSED) ? q[has been paused] :
q[],
+ ($s & QUEUED) ? q[] : q[good for informational use only],
+ ($s & ERROR) ? q[but has an error] : q[];
+ }
+ ->(${$status{refaddr $self}}),
+ ($self->private ? q[Disabled [Private]] : q[Enabled.]),
100 - (grep {$_} split //,
unpack(q[b*], $wanted) / $self->piece_count * 100
),
@@ -1103,11 +1124,10 @@
sprintf q[%s],
join q[],
map {
- vec(${$bitfield{refaddr $self}}, $_, 1)
- ? q[|] # have
- : $_working_pieces{refaddr $self}{$_} ? q[*] #
working
- : vec($wanted, $_, 1) ? q[ ] #
missing
- : q[x] # don't want
+ vec(${$bitfield{refaddr $self}}, $_, 1) ? q[|] # have
+ : $_working_pieces{refaddr $self}{$_} ? q[*] # working
+ : vec($wanted, $_, 1) ? q[ ] # missing
+ : q[x] # don't
want
} 0 .. $self->piece_count - 1
),
$self->piece_count(),
@@ -1143,16 +1163,14 @@
} sort { $a <=> $b }
keys %{$_working_pieces{refaddr $self}}
),
-
- #(map { qq[\n] . $_->as_string(0) } @{$files{refaddr $self}}),
- scalar(@{$files{refaddr $self}}),
- (scalar @{$trackers{refaddr $self}}
- ?
- map { qq[\n] . $_->as_string($advanced) }
- @{$trackers{refaddr $self}}
- : q[]
- ),
- ($self->private ? q[Disabled [Private]] : q[Enabled.]);
+ scalar @{$files{refaddr $self}},
+ @{$files{refaddr $self}} != 1 ? q[s] : q[],
+ join(qq[\n ], map { $_->path } @{$files{refaddr $self}}),
+ scalar @{$trackers{refaddr $self}},
+ @{$trackers{refaddr $self}} != 1 ? q[s] : q[],
+ join(qq[\n ],
+ map { $_->urls->[0]->url } @{$trackers{refaddr $self}}
+ );
return defined wantarray ? $dump : print STDERR qq[$dump\n];
}
@@ -1393,7 +1411,7 @@
data as specified in
L<Net::BitTorrent::Notes|Net::BitTorrent::Notes/"Resume API"> in either
bencoded form or as a raw hash (if you have other plans for the data)
-depending on the boolean value of the optinal C<RAW> parameter.
+depending on the boolean value of the optional C<RAW> parameter.
See also:
L<Resume API|Net::BitTorrent::Notes/"Resume API">
Modified: trunk/lib/Net/BitTorrent/Torrent/Tracker.pm
==============================================================================
--- trunk/lib/Net/BitTorrent/Torrent/Tracker.pm (original)
+++ trunk/lib/Net/BitTorrent/Torrent/Tracker.pm Tue Dec 30 15:25:17 2008
@@ -12,7 +12,7 @@
use version qw[qv];
our $SVN = q[$Id$];
our $UNSTABLE_RELEASE = 0; our $VERSION = sprintf(($UNSTABLE_RELEASE ?
q[%.3f_%03d] : q[%.3f]), (version->new((qw$Rev$)[1])->numify / 1000),
$UNSTABLE_RELEASE);
- my (@CONTENTS) = \my (%torrent, %_urls, %complete, %incomplete);
+ my (@CONTENTS) = \my (%torrent, %urls, %complete, %incomplete);
my %REGISTRY;
sub new {
@@ -42,9 +42,9 @@
weaken $torrent{refaddr $self};
$complete{refaddr $self} = 0;
$incomplete{refaddr $self} = 0;
- $_urls{refaddr $self} = [];
+ $urls{refaddr $self} = [];
for my $_url (@{$args->{q[URLs]}}) {
- push @{$_urls{refaddr $self}},
+ push @{$urls{refaddr $self}},
($_url =~ m[^http://]i
? q[Net::BitTorrent::Torrent::Tracker::HTTP]
: q[Net::BitTorrent::Torrent::Tracker::UDP]
@@ -57,18 +57,18 @@
}
) if $torrent{refaddr $self}->status & 128;
weaken($REGISTRY{refaddr $self} = $self);
- @{$_urls{refaddr $self}} = shuffle(@{$_urls{refaddr $self}});
+ @{$urls{refaddr $self}} = shuffle(@{$urls{refaddr $self}});
return $self;
}
# Accessors | Public
sub incomplete { return $incomplete{refaddr +shift} }
sub complete { return $complete{refaddr +shift} }
+ sub urls { return $urls{refaddr +shift}; }
# Accessors | Private
sub _client { return $torrent{refaddr +shift}->_client; }
sub _torrent { return $torrent{refaddr +shift}; }
- sub _urls { return $_urls{refaddr +shift}; }
# Methods | Private
sub _set_complete {
@@ -84,15 +84,15 @@
sub _shuffle {
my ($self) = @_;
return (
- push(@{$_urls{refaddr $self}}, shift(@{$_urls{refaddr
$self}})));
+ push(@{$urls{refaddr $self}}, shift(@{$urls{refaddr
$self}})));
}
sub _announce {
my ($self, $event) = @_;
return if not defined $self;
- return if not defined $_urls{refaddr $self};
- return if not scalar @{$_urls{refaddr $self}};
- return $_urls{refaddr $self}->[0]->_announce($event ? $event : ());
+ return if not defined $urls{refaddr $self};
+ return if not scalar @{$urls{refaddr $self}};
+ return $urls{refaddr $self}->[0]->_announce($event ? $event : ());
}
sub as_string {
@@ -107,8 +107,8 @@
END
$complete{refaddr $self},
$incomplete{refaddr $self},
- scalar(@{$_urls{refaddr $self}}),
- join qq[\r\n ], map { $_->_url() } @{$_urls{refaddr $self}};
+ scalar(@{$urls{refaddr $self}}),
+ join qq[\r\n ], map { $_->url() } @{$urls{refaddr $self}};
return defined wantarray ? $dump : print STDERR qq[$dump\n];
}
@@ -161,6 +161,13 @@
Returns the number of incomplete peers the tracker says are present in
the swarm.
+
+=item C<urls()>
+
+Returns a list of related
+L<Net::BitTorrent::Torrent::Tracker::HTTP|
Net::BitTorrent::Torrent::Tracker::HTTP>
+and L<Net::BitTorrent::Torrent::Tracker::UDP|
Net::BitTorrent::Torrent::Tracker::UDP>
+objects.
=item C<as_string ( [ VERBOSE ] )>
Modified: trunk/lib/Net/BitTorrent/Torrent/Tracker/HTTP.pm
==============================================================================
--- trunk/lib/Net/BitTorrent/Torrent/Tracker/HTTP.pm (original)
+++ trunk/lib/Net/BitTorrent/Torrent/Tracker/HTTP.pm Tue Dec 30 15:25:17
2008
@@ -41,10 +41,12 @@
return $self;
}
+ # Accessors | Public
+ sub url { my ($self) = @_; return $_url{refaddr $self}; }
+
# Accesors | Private
sub _socket { return $_socket{refaddr +shift}; }
sub _tier { return $_tier{refaddr +shift}; }
- sub _url { return $_url{refaddr +shift}; }
# Methods | Private
sub _announce {
@@ -113,10 +115,10 @@
my $infohash = $_tier{refaddr $self}->_torrent->infohash;
$infohash =~ s|(..)|\%$1|g;
my %query_hash = (
- q[info_hash] => $infohash,
- q[peer_id] => $_tier{refaddr $self}->_client->peerid(),
- q[port] => $_tier{refaddr $self}->_client->_tcp_port() |
| 0,
- q[uploaded] => $_tier{refaddr $self}->_torrent->uploaded(),
+ q[info_hash] => $infohash,
+ q[peer_id] => $_tier{refaddr $self}->_client->peerid(),
+ q[port] => ($_tier{refaddr $self}->_client->_tcp_port() ||
0),
+ q[uploaded] => $_tier{refaddr $self}->_torrent->uploaded(),
q[downloaded] => $_tier{refaddr $self}->_torrent->downloaded(),
q[left] => (
$_tier{refaddr $self}->_torrent->raw_data(1)
@@ -358,6 +360,10 @@
=head1 Methods
=over
+
+=item C<url ( )>
+
+Returns the related HTTP URL according to the original metadata.
=item C<as_string ( [ VERBOSE ] )>
Modified: trunk/lib/Net/BitTorrent/Torrent/Tracker/UDP.pm
==============================================================================
--- trunk/lib/Net/BitTorrent/Torrent/Tracker/UDP.pm (original)
+++ trunk/lib/Net/BitTorrent/Torrent/Tracker/UDP.pm Tue Dec 30 15:25:17 2008
@@ -55,10 +55,12 @@
return $self;
}
+ # Accessors | Public
+ sub url { my ($self) = @_; return $_url{refaddr $self}; }
+
# Accessors | Private
sub _packed_host { return $_packed_host{refaddr +shift} }
sub _tier { return $_tier{refaddr +shift}; }
- sub _url { return $_url{refaddr +shift}; }
sub _client { return $_tier{refaddr +shift}->_client }
# Methods | Private
@@ -341,6 +343,10 @@
=head1 Methods
=over
+
+=item C<url ( )>
+
+Returns the related UDP 'URL' according to the original metadata.
=item C<as_string ( [ VERBOSE ] )>
Modified: trunk/lib/Net/BitTorrent/Version.pm
==============================================================================
--- trunk/lib/Net/BitTorrent/Version.pm (original)
+++ trunk/lib/Net/BitTorrent/Version.pm Tue Dec 30 15:25:17 2008
@@ -5,7 +5,7 @@
use warnings;
use version qw[qv];
our $SVN = q[$Id$];
- our $VERSION_BASE = 45; our $UNSTABLE_RELEASE = 0; our $VERSION =
sprintf(($UNSTABLE_RELEASE ? q[%.3f_%03d] : q[%.3f]),
(version->new(($VERSION_BASE))->numify / 1000), $UNSTABLE_RELEASE);
+ our $VERSION_BASE = 46; our $UNSTABLE_RELEASE = 0; our $VERSION =
sprintf(($UNSTABLE_RELEASE ? q[%.3f_%03d] : q[%.3f]),
(version->new(($VERSION_BASE))->numify / 1000), $UNSTABLE_RELEASE);
our $PRODUCT_TOKEN = qq[Net::BitTorrent $VERSION];
sub gen_peerid {
Modified: trunk/scripts/bittorrent.pl
==============================================================================
--- trunk/scripts/bittorrent.pl (original)
+++ trunk/scripts/bittorrent.pl Tue Dec 30 15:25:17 2008
@@ -1,11 +1,10 @@
-#!/usr/bin/perl
+#!perl -w -I../lib
use strict;
use warnings;
use Getopt::Long;
use Pod::Usage;
use Carp qw[croak carp];
use Time::HiRes qw[sleep];
-use lib q[../lib];
use Net::BitTorrent::Protocol qw[:types];
use Net::BitTorrent::Util qw[:bencode];
use Net::BitTorrent::Torrent qw[:status];
@@ -171,6 +170,10 @@
Guess.
=back
+
+=head1 See Also
+
+For more examples, see the files under the C</tatoeba/> directory.
=head1 Author
Modified: trunk/t/700_classes/Net/BitTorrent/Torrent/Tracker.t
==============================================================================
--- trunk/t/700_classes/Net/BitTorrent/Torrent/Tracker.t (original)
+++ trunk/t/700_classes/Net/BitTorrent/Torrent/Tracker.t Tue Dec 30
15:25:17 2008
@@ -114,12 +114,12 @@
q[Get related N::B::Torrent object]);
is($tracker->_client->isa(q[Net::BitTorrent]),
1, q[Get related Net::BitTorrent object]);
- is_deeply($tracker->_urls,
+ is_deeply($tracker->urls,
[bless(do { \(my $o = "http://example.com/announce") },
"Net::BitTorrent::Torrent::Tracker::HTTP"
)
],
- q[_urls]
+ q[urls]
);
warn(q[TODO: create a fake tracker and connect to it]);
}
Modified: trunk/t/700_classes/Net/BitTorrent/Torrent/Tracker/HTTP.t
==============================================================================
--- trunk/t/700_classes/Net/BitTorrent/Torrent/Tracker/HTTP.t (original)
+++ trunk/t/700_classes/Net/BitTorrent/Torrent/Tracker/HTTP.t Tue Dec 30
15:25:17 2008
@@ -19,7 +19,7 @@
my $verbose = $build->notes(q[verbose]);
$SIG{__WARN__} = ($verbose ? sub { diag shift } : sub { });
my ($flux_capacitor, %peers) = (0, ());
-plan tests => 15;
+plan tests => 17;
SKIP: {
my ($tempdir)
= tempdir(q[~NBSF_test_XXXXXXXX], CLEANUP => 1, TMPDIR => 1);
@@ -82,6 +82,8 @@
isa_ok($_host_address,
q[Net::BitTorrent::Torrent::Tracker::HTTP],
q[{URL=>q[http://localhost/announce/], Tier => [...]}]);
+ is($_ip_address->url, q[http://127.0.0.1/announce/], q[url is
correct]);
+ is($_host_address->url, q[http://localhost/announce/], q[url is
correct]);
skip(
q[HTTP-based tests have been disabled due to system
misconfiguration.],
($test_builder->{q[Expected_Tests]} -
$test_builder->{q[Curr_Test]})
Modified: trunk/t/700_classes/Net/BitTorrent/Torrent/Tracker/UDP.t
==============================================================================
--- trunk/t/700_classes/Net/BitTorrent/Torrent/Tracker/UDP.t (original)
+++ trunk/t/700_classes/Net/BitTorrent/Torrent/Tracker/UDP.t Tue Dec 30
15:25:17 2008
@@ -19,7 +19,7 @@
my $verbose = $build->notes(q[verbose]);
$SIG{__WARN__} = ($verbose ? sub { diag shift } : sub { });
my ($flux_capacitor, %peers) = (0, ());
-plan tests => 15;
+plan tests => 17;
SKIP: {
my ($tempdir)
= tempdir(q[~NBSF_test_XXXXXXXX], CLEANUP => 1, TMPDIR => 1);
@@ -82,6 +82,8 @@
isa_ok($_host_address,
q[Net::BitTorrent::Torrent::Tracker::UDP],
q[{URL=>q[udp://localhost/announce/], Tier => [...]}]);
+ is($_ip_address->url, q[udp://127.0.0.1/announce/], q[url is
correct]);
+ is($_host_address->url, q[udp://localhost/announce/], q[url is
correct]);
skip(
q[UDP-based tests have been disabled due to system
misconfiguration.],
($test_builder->{q[Expected_Tests]} -
$test_builder->{q[Curr_Test]})
Added: trunk/tatoeba/000-basic.pl
==============================================================================
--- (empty file)
+++ trunk/tatoeba/000-basic.pl Tue Dec 30 15:25:17 2008
@@ -0,0 +1,74 @@
+#!perl -w -I../lib
+use strict;
+use warnings;
+use Net::BitTorrent;
+my $client = Net::BitTorrent->new();
+my $torrent = $client->add_torrent({Path => 'a.legal.torrent'}) or exit;
+$client->do_one_loop while 1;
+
+=pod
+
+=head1 NAME
+
+/tatoeba/000-basic.pl - Bare minimum example BitTorrent client
+
+=head1 Description
+
+This is the least amount of code needed to create a full
+C<Net::BitTorrent>-based client.
+
+=head1 Synopsis
+
+ 000-basic.pl
+
+=head1 Lowdown
+
+=over
+
+=item Line 5
+
+Creates a new C<Net::BitTorrent> object. Lets OS pick a random port and
+opens sockets (TCP and UDP) on all available hosts.
+
+=item Line 6
+
+Attempts to create a new C<Net::BitTorrent::Torrent> object. Defaults to
+current working directory for storage.
+
+If there's a problem loading the .torrent, an error will (probably) be
+C<Carp>ed by C<Net::BitTorrent::Torrent>.
+
+=item Line 7
+
+Works forever. C<Net::BitTorrent> will continue to seed the torrent
+after download is complete.
+
+=back
+
+=head1 Author
+
+Sanko Robinson <sa...@cpan.org> - http://sankorobinson.com/
+
+CPAN ID: SANKO
+
+=head1 License and Legal
+
+Copyright (C) 2008 by Sanko Robinson E<lt>sa...@cpan.orgE<gt>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of The Artistic License 2.0. See the F<LICENSE>
+file included with this distribution or
+http://www.perlfoundation.org/artistic_license_2_0. For
+clarification, see http://www.perlfoundation.org/artistic_2_0_notes.
+
+When separated from the distribution, all POD documentation is covered
+by the Creative Commons Attribution-Share Alike 3.0 License. See
+http://creativecommons.org/licenses/by-sa/3.0/us/legalcode. For
+clarification, see http://creativecommons.org/licenses/by-sa/3.0/us/.
+
+Neither this module nor the L<Author|/Author> is affiliated with
+BitTorrent, Inc.
+
+=for svn $Id$
+
+=cut
Added: trunk/tatoeba/001-torrent-info.pl
==============================================================================
--- (empty file)
+++ trunk/tatoeba/001-torrent-info.pl Tue Dec 30 15:25:17 2008
@@ -0,0 +1,94 @@
+#!perl -w -I../lib
+use strict;
+use warnings;
+use Data::Dump;
+use Net::BitTorrent::Torrent;
+my $torrent = Net::BitTorrent::Torrent->new({Path => 'a.legal.torrent'})
+ or exit;
+$torrent->as_string(1);
+dd $torrent->raw_data(1);
+print map { qq[\n] . $_->path } @{$torrent->files};
+
+=pod
+
+=head1 NAME
+
+/tatoeba/001-torrent-info.pl - Use Net::BitTorrent to gather information
+
+=head1 Description
+
+This is a demonstration of how standalone C<Net::BitTorrent::Torrent>
+objects can be created and used to gather information from a .torrent
+file.
+
+=head1 Synopsis
+
+ 001-torrent-info.pl
+
+=head1 Lowdown
+
+=over
+
+=item Line 6
+
+Returns a new C<Net::BitTorrent::Torrent> object. Created this way,
+(without a 'C<Client>' parameter) the new object is not loaded into a
+parent C<Net::BitTorrent> client.
+
+You may use any of the arguments listed in
+L<Net::BitTorrent::Torrent|Net::BitTorrent::Torrent/"new ( { [ARGS] } )">'s
+constructor. If there's a problem loading the .torrent, an error will
+(probably) be C<Carp>ed by C<Net::BitTorrent::Torrent>.
+
+=item Line 8
+
+Calls the debugging method
+L<as_string|Net::BitTorrent::Torrent/"as_string ( [ VERBOSE ] )"> just
+to give you a rundown of what can be parsed from the file.
+
+=item Line 9
+
+Prints a dump of the .torrent file's
+L<metadata|Net::BitTorrent::Torrent/"raw_data ( [ RAW ] )">.
+
+=item Line 10
+
+Prints a list of the .torrent's
+L<files|Net::BitTorrent::Torrent/"files ( )">. This line also uses the
+L<path|Net::BitTorrent::Torrent::File/"path ( )"> method from
+L<Net::BitTorrent::Torrent::File|Net::BitTorrent::Torrent::File>.
+
+=back
+
+=head1 See Also
+
+Please see L<Net::BitTorrent::Torrent|Net::BitTorrent::Torrent>'s
+documentation for a list of methods and accessors.
+
+=head1 Author
+
+Sanko Robinson <sa...@cpan.org> - http://sankorobinson.com/
+
+CPAN ID: SANKO
+
+=head1 License and Legal
+
+Copyright (C) 2008 by Sanko Robinson E<lt>sa...@cpan.orgE<gt>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of The Artistic License 2.0. See the F<LICENSE>
+file included with this distribution or
+http://www.perlfoundation.org/artistic_license_2_0. For
+clarification, see http://www.perlfoundation.org/artistic_2_0_notes.
+
+When separated from the distribution, all POD documentation is covered
+by the Creative Commons Attribution-Share Alike 3.0 License. See
+http://creativecommons.org/licenses/by-sa/3.0/us/legalcode. For
+clarification, see http://creativecommons.org/licenses/by-sa/3.0/us/.
+
+Neither this module nor the L<Author|/Author> is affiliated with
+BitTorrent, Inc.
+
+=for svn $Id$
+
+=cut