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
I'm calling a PS script to run when a download finishes. It emails me to tell me a download has finished. So, far, all I've been able to do is make a script that will do a search in the Utorrent folder, and it will tell me the name of the last file created. This way I can see in my email what file just download. But this won't work properly when I'm using rss feeds that will download multiple files. I'm trying to figure out if I can use the Utorrent parameters i.e, %F,. Ideally, I'm trying to make the script email me when a torrent has finished and tell me what was just download.
ReplyDelete$message=new-object Net.Mail.MailMessage('me@gmail.com', 'me@gmail.com', 'Download Has Finished', 'Message Body')
$filename=(dir C:\Users\Test\AppData\Roaming\uTorrent | ?{!$_.PsIsContainer} | sort CreationTime -desc| select -first 1).Fullname
$message.Body=$filename
$smtp=new-object Net.Mail.SmtpClient('smtp.gmail.com',587)
$smtp.EnableSSL = $true
$smtp.Credentials=New-Object System.Net.NetworkCredential( 'me@gmail.com', 'MyGmailPW' );
$smtp.Send($message)
Hi Rick, you can definitely achieve this, and most of the heavy lifting should be done. What I would suggest is copy the script above change the part inside the Measure-Command with the email code. You should be able to just use the %N, if you want file/folder read the section above regarding how they work, it changes depending on the type of torrent.
DeleteYou will need to make some changes to the config reading, just remove what you don't need and set the rest to constants.
To call the code from utorrent there is a setting to execute a command when and download finishes, Check out the start of the script for more info.
The log_message can help to figure out what is happening when it is called.
For the email password I would consider using a secure string in a config file. I have some code I will post to show how to use that.
Let me know how you go.
Hello,
ReplyDeleteI'm starting some automating of my own (I have a big background in VBscript, but want to use this "home project" as a first step in learning Powershell).
I think there's a new variable in Utorrent (%K) that tells you if the torrent is multifile or single.
Also can you share an example of the XML config file ? (I'm not sure yet how Powershell reads it so an example would be welcome)
Thanks
I'm actually not sure how the get-ScriptConfig (xml part) function works, I havent found anything about that on the web.
DeleteIs it a function that you wrote and isnt included in this code ?
Thanks
Thanks for letting me know about the new parameter %K. Will update the script to support it. get-ScriptConfig is a script I created. I use it so I can change params without needing to re sign the script. I will post the code however you can get work around it. What you need to do is set the three path variables that are set by $configFile.
DeleteHi Chris,
ReplyDeleteI really like your script you have written here.
I use something similar to sort my torrents by TV show and Movies while copying them to different directories using VBS. I'm not quite happy with the way it's doing it right now as it's just calling a very generic script and not basing it on the torrent state changes so it ends up copying all completed files over and over again.
I was wondering if there was any way I could use your code above but have the additional function of checking the torrent name and copying that particular torrent (File or Folder) to the correct directory (Movie or TV Show)?
Would you be able to help me with this? I would much rather use Powershell than VBS.
Thanks,
William.
Hi William, I would be happy to help you with your script. It would prob be easier to do over email. Let me know what you would like to achieve and where you are up to.
DeleteChris