The script provides useful functions for managing Transmission torrents


#!/usr/bin/perl
 
use strict;
use File::Find;
use File::Spec;
use File::Path;
use File::Copy;
use Getopt::Long;
use Pod::Usage;
 
my $help;
my $list_ids;
my $list_excess;
my $copy_torrents;
my $exec = "transmission-remote";
my $host;
my $auth;
my $content;
my $source;
my $destination;
 
GetOptions ('help' => \$help,
        'list-ids' => \$list_ids,
        'list-excess' => \$list_excess,
        'copy-torrents' => \$copy_torrents,
        'exec:s' => \$exec,
        'host:s' => \$host,
        'auth:s' => \$auth,
        'content:s' => \$content,
        'source:s' => \$source,
        'destination:s' => \$destination);
 
my $cmd = $exec;
if ($host)
{
    $cmd .= " ".$host
}
if ($auth)
{
    $cmd .= " --auth ".$auth;
}
 
if ($help || ($list_ids+$list_excess+$copy_torrents)>1)
{
    pod2usage( { -verbose => 2 } );
}
elsif ($list_ids)
{
    if ($content)
    {
    my @result = list_ids($cmd,$content);
    print(join(",",@result)."\n");    
    }
    else
    {
    pod2usage( { -verbose => 2 } );
    }
}
elsif ($list_excess)
{
    if ($content)
    {
        my @result = list_excess($cmd,$content);
        print(join("\n",@result)."\n");    
    }
    else
    {
    pod2usage( { -verbose => 2 } );
    }
}
elsif ($copy_torrents)
{
    if ($content && $source && $destination)
    {
        copy_torrents($cmd,$content,$source,$destination);
    }
    else
    {
    pod2usage( { -verbose => 2 } );
    }
}
 
sub list_excess #cmd, path
{
    my @ids = list_ids($_[0],$_[1]);
    my $ids_str = join(",",@ids);
    my @output = `$_[0] --torrent $ids_str --info`;
    my @locs = map(/.*Location:\s(.*)\n/,@output);
    @locs = map({File::Spec->canonpath($_).File::Spec->rootdir()} @locs);
    my @all_files = get_all_files($_[0],\@ids);
    my @paths = ();
    for (my $i=0; $i<scalar @all_files; $i++)
    {
    my @torrent_files = @{@all_files[$i]};
    for (my $j=0; $j<scalar @torrent_files; $j++)
    {
        push(@paths,@locs[$i][email protected]_files[$j]);
    }
    }
    my @result = ();
    find({wanted => sub {check_file(\@paths,\@result)}},$_[1]);
    return @result;
}
 
sub copy_torrents #cmd, path, source, destination
{
    my $ids_str = join(",",list_ids($_[0],$_[1]));
    my @output = `$_[0] --torrent $ids_str --info`;
    my @names = map(/.*Name:\s(.*)\n/,@output);
    my @hashes = map(/.*Hash:\s(.*)\n/,@output);
    my $source = File::Spec->canonpath($_[2]).File::Spec->rootdir();
    my $destination = File::Spec->canonpath($_[3]).File::Spec->rootdir();
 
    mkpath($destination);
    for (my $i=0; $i<scalar @names; $i++)
    {
    my $file = @names[$i].".".substr(@hashes[$i],0,16).".torrent";
        copy($source.$file,$destination.$file);
    }
}
 
sub list_ids #cmd, path
{
    my @result = ();
 
    my $path = File::Spec->canonpath($_[1]).File::Spec->rootdir();
    my @all_ids = get_all_torrents($_[0]);
    my $all_ids_str = join(",",@all_ids);
    my @output = `$_[0] --torrent $all_ids_str --info`;
    my @locs = map(/.*Location:\s(.*)\n/,@output);
    @locs = map({File::Spec->canonpath($_).File::Spec->rootdir()} @locs);
    my @check_ids = (); #ids for another check
    my @check_indexes = (); #check_ids element's indices in all_ids
    for (my $i=0; $i<scalar @locs; $i++)
    {
    if (index(@locs[$i],$path)!=-1) #content is in this path entirely
    {
        push(@result,@all_ids[$i]);
    }
    elsif (index($path,@locs[$i])!=-1) #content may be present in this path; need another check
    {
        push(@check_ids,@all_ids[$i]);
        push(@check_indexes,$i);
    }
    }
     
    if (scalar @check_ids == 0) { return @result; }
 
    my @check_files = get_all_files($_[0],\@check_ids);
    for (my $i=0; $i<scalar @check_files; $i++)
    {
    my @torrent_files = @{@check_files[$i]};
    for (my $j=0; $j<scalar @torrent_files; $j++)
    {
        my $file_path = @locs[@check_indexes[$i]][email protected]_files[$j];
        if (index($file_path,$path)!=-1)
        {
        push(@result,@check_ids[$i]);
        last;
        }
    }
    }
 
    return @result;
}
 
sub get_all_files #cmd #ids
{
    my $ids_str = join(",",@{$_[1]});
    my @output = `$_[0] --torrent $ids_str --files`;
    my @result = ();
    my $i = 0;
    while ($i<scalar @output)
    {
    my @files = ();
    @output[$i] =~ /^.*\((\d+)\sfiles\):$/;
    my $count = $1 + 0;
    $i += 2;
    for (my $j=0; $j<$count; $j++)
    {
        @output[$i++] =~ /^\s*\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+\S+\s+(.*)/;
        push(@files,File::Spec->canonpath($1));
    }
    push(@result,\@files);
    }
    return @result;
}
 
sub get_all_torrents #cmd
{
    my @output = `$_[0] --list`;
    my @result = ();
    for (my $i = 1; $i < scalar @output - 1; $i++)
    {
    @output[$i] =~ /\s*(\d+)[\*\s].*/;
    push(@result,$1);
    }
    return @result;
}
 
sub check_file #all, found
{
    if (-f)
    {
    my $file_path = File::Spec->canonpath($File::Find::name);
    if (!grep {$file_path eq $_} @{$_[0]})
    {
        push(@{$_[1]},$file_path);
    }
    }
}
 
=head1 NAME
 
transmission-helper.pl, version 1.0
 
=head1 DESCRIPTION
 
The script offers usefull functions to manage Transmission torrents
 
=head1 SYNOPSIS
 
transmission-helper.pl <command> [option1] [option2] ...
[optionN] = <option name N> <option value N>
 
=head1 COMMANDS
 
=over 8
 
=item --help
 
show help
 
=item --list-ids
 
show IDs of all torrents that have content in a specified directory. Requires "--content" option
 
=item --list-excess
 
list files in a specified directory that do not belong to any torrent. Requires "--content" option
 
=item --copy-torrents
 
make copies of .torrent files of all torrents that have content in a specified directory. Requires "--content", "--source" and "--destination" options
 
=back
 
=head1 OPTIONS
 
=over 8
 
=item --exec
 
a path to transmission-remote executable. Default value is "transmission-remote"
 
=item --host
 
a hostname and a port to connect. Default value is "localhost:9091"
 
=item --auth
 
credentials in the format "username":"password". By default the connection established without authentication
 
=item --content
 
a directory to perform a check
 
=item --source
 
a directory in which Transmission stores .torrent files
 
=item --destination
 
a directory to which the script should copy .torrent files
 
=back
 
=head1 COPYRIGHT
 
(c) 2019 Alexander Galkov, [email protected]
 
=head1 LINK
 
www.galkov.pro/perl_script_for_managing_transmission_torrents
 
=cut

The script can be downloaded here

Leave a Reply