When a torrent finishes copy the downloaded files to a new folder for processing.
Script notes
Utorrent has a new feature allowing a command line to be executed when a torrent changes state or finishes. I wanted to test this out and see if I could perform some utorrent automation via powershell.
The desired process is. One a torrent finishes downloading it will move in to seeding. The downloaded files will be copied from the seeding folder to a "new downloads" folder where they can be processed (uncompressing, moving, renaming and so on) without having to stop the torrent from seeding.
When the torrent finishes seeding the files in the seeding folder can be deleted.
To achieve this I have utorrent copy the the downloaded files to a new folder. When I finish seeding the torrent I can remove the files in the seeding folder. I also have another script that runs each day to clean up the seeding folder in case any torrents are removed from utorrent without removing the datafiles.
One tricky part in achieving this was that folder and file torrents are treated differently. There is no single path the the item I want to copy. From testing I came to If the torrent name ($N) is equal to file name ($F) its a file torrent and the file path is D$\$F (file $F in folder path $D). Otherwise we can just use the folder path ($D). I added a check to see if the folder name in $D was the same as the torrent name ($N) to catch any issues for folder torrents, this might not be needed.
The time take to perform the copy and the size of the folders before and after are logged to a file. This was so I could confirm that the copy was successful (this a POC script so testing and logs are needed).
param(
[string]$Reason = "Unknown",
[string]$F = "",
[string]$D = "",
[string]$N = "",
[string]$S = "",
[string]$P = "",
[string]$L = "",
[string]$T = "",
[string]$M = "",
[string]$I = ""
)
#*******************************************************************
# Global Variables
#*******************************************************************
$Script:Version = '0.4.0.5'
<#
Comments
copys completed torrents
Notes
Scheduled Performs (via another script)
Check for removed seeds
Check for dead partial
Inputs to this scripts
$Reason - Default = 'unknown' - Does nothing
Completed - The torrent was completed.
StateChanged - The torrent state changed.
Utorrent Falcon Commands
%F - Name of downloaded file (for single file torrents)
%D - Directory where files are saved
%N - Title of torrent
%S - State of torrent
%P - Previous state of torrent
%L- Label
%T-Tracker
%M - Status message string (same as status column)
%I- hexencoded info-hash
State is one of:
error = 1,
checked = 2
paused = 3
super seeding = 4
seeding = 5
downloading =6
super seed [F] = 7
seeding [F] = 8
downloading [F] = 9
queued seed = 10,
finished = 11
queued = 12
stopped = 13
How to call from utorrent
called from Completed
powershell.exe -noprofile "& 'c:\path\utorrent.ps1' -Reason 'Completed' -F '%F' -D '%D' -N '%N' -S '%S' -P '%P' -L '%L' -T '%T' -M '%M' -I '%I'"
called from StateChange
powershell.exe -noprofile "& 'c:\path\utorrent.ps1' -Reason 'StateChanged' -F '%F' -D '%D' -N '%N' -S '%S' -P '%P' -L '%L' -T '%T' -M '%M' -I '%I'"
#>
#########################################################
#Load config from file
#########################################################
[xml]$configFile = get-ScriptConfig
if ($configFile -eq $null) {
Write-Error "Failed to load config`nExiting"
Exit}
#Location where logs will be written
$GlobalLogPath = $configFile.Configuration.GlobalLogPath
#Location of Utorrent completed folders (Active torrents)
$UtorrentCompletedFolder = $configFile.Configuration.UtorrentCompletedFolder
#Where to place new unsorted downloads
$DownloadsCompletedFolder = $configFile.Configuration.DownloadsCompletedFolder
###########################################################
#functions
###########################################################
function GetState( [string] $intState ) {
#converts the integer state value to its English name
switch ($intState)
{
"1" {return 'error'}
"2" {return 'checked'}
"3" {return 'paused'}
"4" {return 'super seeding'}
"5" {return 'seeding'}
"6" {return 'downloading'}
"7" {return 'super seed [F]'}
"8" {return 'seeding [F]'}
"9" {return 'downloading [F]'}
"10" {return 'queued seed'}
"11" {return 'finished'}
"12" {return 'queued'}
"13" {return 'stopped'}
default {return 'unknown'}
}
}
#creates a dir it is needed
function CreateDirectoryIfNeeded ( [string] $directory ) {
if ((test-path -LiteralPath $directory) -ne $True)
{
New-Item $directory -type directory | out-null
if ((test-path -LiteralPath $directory) -ne $True)
{
return #[boolean]$false
}
else
{
return #[boolean]$true
}
}
else
{
return #[boolean]$true
}
}
#logs a message to file,
#the filename changes every month to keep the file in a usablestate
function Log_Message([string]$Message) {
[datetime]$date = get-date
[String]$filename = "$GlobalLogPath\uTorrent-{1}.log" -f $functionName, $date.ToString( "yyyyMM")
CreateDirectoryIfNeeded $GlobalLogPath
#Writes message to log file
add-content $filename "$date | $Message"
#displays message on console
Write-Host $Message
}
function Get-FolderSize([string]$FolderPath) {
<#
.Synopsis
gets the folder size recursive
.Example
[long]$result = Get-FolderSize "C:\temp"
#>
[long]$FolderLength = (Get-ChildItem -LiteralPath $FolderPath -Recurse | Measure-Object -Property Length -Sum).Sum
return $FolderLength
}
function Get-BytesasString([long]$Bytes) {
<#
.Synopsis
Displays the folder size in a pretty way
.INPUTS
None. You cannot pipe objects to Get-BytesasString.
#>
if ($Bytes -gt 1073741823)
{
[Decimal]$size = $Bytes / 1073741824
return "{0:##.##} GB" -f $size
}
elseif ($Bytes -gt 1048575)
{
[Decimal]$size = $Bytes / 1048576
return "{0:##.##} MB" -f $size
}
elseif ($Bytes -gt 1023)
{
[Decimal]$size = $Bytes / 1024
return "{0:##.##} KB" -f $size
}
elseif ($Bytes -gt 0)
{
[Decimal]$size = $Bytes
return "{0:##.##} bytes" -f $size
}
else
{
return "0 bytes";
}
}
###########################################################
#Main script
###########################################################
#Log Utorrent messages
Log_Message "$Reason | F=$F|D=$D|N=$N|S=$(GetState $S)|P=$(GetState $P)|L=$L|T=$T|M=$M|I=$I|END"
#tasks
#Process completed torrent
if($Reason -eq 'Completed')
{
$Source = $null
#if the name is equal to the file its prob single file torrent
if($N -eq $F)
{
#It's a single file torrent
$Source = join-path -path $D -childpath $F
}
else
{
#It's a folder torrent
$Source = $D
$sourceObj = get-Item -LiteralPath $Source
if ($sourceObj.name -ne $N)
{
Log_Message "ERROR| Folder name does not match %N: $($sourceObj.name)"
$Source = $null
}
}
if ((test-path -LiteralPath $Source) -eq $False)
{
Log_Message "ERROR | File does not exist: $Source. Exiting"
}
else
{
$message = ""
$measure = Measure-Command {
[string]$sizeSourceBefore = Get-BytesasString (Get-FolderSize $Source)
Log_Message "Torrent Completed, copying object '$Source' to '$DownloadsCompletedFolder'"
Copy-Item -LiteralPath $Source -Destination $DownloadsCompletedFolder -Recurse -Force
$sourceObj = get-Item -LiteralPath $Source
$targetFolder = join-path -path $DownloadsCompletedFolder -childpath $sourceObj.name
[string]$sizeTargetAfter = Get-BytesasString (Get-FolderSize $targetFolder)
[string]$sizeSourceAfter = Get-BytesasString (Get-FolderSize $Source)
#log the before and after sizes of the source and destination folders, for debug
$message = "sizeSourceBefore $sizeSourceBefore, sizeSourceAfter $sizeSourceAfter, sizeTarget $sizeTargetAfter"
}
#log how long the copy took
Log_Message "$message - In $($measure.Days) Days $($measure.Hours):$($measure.Minutes):$($measure.Seconds).$($measure.Milliseconds)"
}
}
Happy to receive feedback