📜 ⬆️ ⬇️

We understand with rtorrent seriously

About installation and basic rtorrent configuration on Habré there are enough articles, as well as disputes about whether it is worth contacting hardcore rtorrent at all or better to get by with something more user-friendly. Personally, many years ago I reviewed all the rocking chairs and as a result, rtorrent turned out to be the most stable and efficient. Its interface is not the most convenient, but clear enough and usable so that it does not become a serious problem. Alternative interfaces like rutorrent I somehow didn’t get accustomed - it’s reluctant to put php just for the sake of rutorrent, and the rest of the options look very weak (and there is not even a single rutorrent in the Gentoo portal).



One of the main advantages of rtorrent is the very flexible options for configuring and automating it . Unfortunately, the ~/.rtorrent.rc rather non-standard, there is no normal documentation, so usually the setup is reduced to searching and copying (an attempt to change something in them except constants / paths to directories usually fails) ready-made recipes or is generally limited to editing constants in basic configuration.
')
The other day I decided that I could not continue like this - we have known each other for many years, he has pumped so much good for me, but I still can’t get to know him better! I will not say that I thoroughly figured out with him, but at least I was able to implement all my rtorrent automation ideas, and did this by understanding what and why I was doing, without shamanism with other people's recipes.



Update


The rtorrent-0.9.2 was configured (with an additional color patch, as in the screenshot above), and if you have an older version, I strongly recommend upgrading. After the upgrade, it is worth running migrate_rtorrent_rc.sh to convert the outdated syntax inside ~/.rtorrent.rc to a more modern one and view migration instructions .

Other documentation


Examining the existing documentation causes only one feeling: deep sadness. I'm lying. It makes you want to curse roughly. And deep sadness is what comes much later, when you realize that you didn’t imagine, and everything is really so bad. Nevertheless, at least something is still better than nothing at all (here! I managed to find something positive), so let's see what we have:


Debugging


For debugging and experimentation, instead of changing the config file and restarting the rtorrent, use the command line built into it. It is called by pressing Ctrl-X , after which you can enter any command, for example:

 command> print="Hello ",World! 

press Enter and get on the screen and in the log:

 (22:13:39) Hello World! 

You can see old log entries unavailable on the screen by pressing l , exit the log viewing screen by pressing the spacebar.

When using commands in the command line that should be applied to a specific object (torrent, file, tracker, peer), the object rtorrent selected in the interface will be used (that is, you need to move the cursor to the desired line before executing the command).

The main disadvantage of this command line is the lack of history, you cannot click on the “up” button to see the previous command, modify it and execute it again.

Config file syntax


The first thing you need to understand about the syntax ~/.rtorrent.rc is not the config file . Yes, it looks like, but this is just a dangerous illusion. In fact, this is a program : a sequence of commands (this is an official term, but I will continue to call them functions , so more familiar and in fact more correct), and actively using the transfer of some commands (along with their own parameters) parameters to others (hello, callback) and in the configs - only you lacked complete happiness!), with several levels of nesting and different screening options. You can (and will) have to create your own functions — it helps a lot to reduce the number of nesting levels and the resulting shielding difficulties.

Thus, when you see a line in the config file like:

 throttle.global_down.max_rate.set_kb = 10000 

then it is actually a function call with one parameter, more traditionally written as:

 throttle.global_down.max_rate.set_kb(10000) 

As far as I understand, dots in function names do not have a special meaning, just an ordinary separator for readability, like an underscore.

Gaps, oddly enough, are not acceptable everywhere where any separators are used. At a minimum, they can be used:


Function call

 #  ,  func() func= #   ,  func("param1","param2") func=param1,param2 

The parameter value is treated as a string. It is necessary to take the value of the parameter in quotes if it contains a space or a separator character like , or ; (to use quotes is always - for clarity - difficult, because very often this parameter is already inside a line containing several commands, so you have to use quotation escaping and visibility as a result will only get worse).

 func="first param, with special chars","second;param" 

To get the result of a function call (to pass it as a parameter to another function), you need to put $ in front of the function name:

 # : system.hostname= print=system.hostname= # : system.hostname= print="system.hostname=" # : powerman.name print=$system.hostname= # : powerman.name print="$system.hostname=" 

In the first and second examples, the print function takes one parameter: the string "system.hostname=" . In the third and fourth print , the parameter receives the result of the function system.hostname without parameters: the string "powerman.name" .

The print function displays all its parameters, and if one of them is a function call, which also has parameters, it is necessary to separate which parameters relate to print and which to another function (in fact, this is not a very urgent problem, since most functions rtorrent parameters either do not accept or accept one):

 # : one  27 23:50:51 EEST 2014-utwo print=one,$execute.capture=date,-u,two # : one  27 20:51:05 UTC 2014two print=one,"$execute.capture=date,-u",two 

The execute.capture function executes any system command (via sh) and returns what this command outputs to STDOUT. Therefore, it can take any number of parameters (command name and parameters for it). As you can see in the first variant, it took only one parameter (and called date ) and the rest (-u, two) went to print . And in the second, we explicitly indicated which parameters belong to execute.capture , so it caused date -u and print only got the last parameter (two).

If you try to run this example, it will not work for you as described. The fact is that the date system command completes the output with a newline, so the rest of the print parameters are already displayed on another line, and rtorrent shows this not very clearly, even in the log. Therefore, in order for the example to work, you need to replace the date command with a variant that does not print a line break, for example, put in ~/bin/date :

 #!/bin/bash echo -n `/bin/date "$@"` 

callback

Some rtorrent functions expect in their parameters the functions passed by the user (which they will call themselves later). There are two alternative syntaxes, how can you pass a function to a parameter to another: either a regular string containing the function call and its parameters is passed in the usual rtorrent syntax, or a list of strings inside (( )) passed, where the function name is the first string and the rest of its parameters .

 # func()    : # -  callback- func1() # -  "param2" func=func1=,param2 # func()    : # -  callback- func1("param11","param12") # -  "param2" func="func1=param11,param12",param2 #   ,      func=((func1,param11,param12)),param2 

Accordingly, if the transferred callback func1 in the parameters must also be passed callback, then you need to either use different shielding styles (quotes / brackets), or shuffle quotes inside the quotes, or use nested brackets.

 #   func1()     param11 # callback func2("param21","param22")  : func="func1=\"func2=param21,param22\",param12",param2 func="func1=((func2,param21,param22)),param12",param2 func=((func1,((func2,param21,param22)),param12)),param2 func=((func1,"func2=param21,param22",param12)),param2 

By the way, in rtorrent there is another way to syntactically transfer the list - inside { } . Where and for what it should be applied I did not understand yet.

User Functions

New functions can be created using the method.insert function. The first parameter is the name for the new function, the second is its type (usually simple , but if you need to create not a function but a variable, then you need to use the types value , bool , string ; you can also make a function / variable private and const and not only - unfortunately deal with these features need to source), the third line with the function body. Unlike the lines with callbacks described above, in this line you can send several commands through ; but you cannot use the separator (( )) .

 #   : method.insert = newfunc, simple, "func=param1,param2;func1=;func2=" #   : newfunc= 

If you need to call the created function with different parameters (up to 4) - they can be accessed inside the function with argument.0= ... argument.3 .

 method.insert = hello, simple, "print = \"Hello \", $argument.0=, !" # : Hello Powerman! hello=Powerman 

Variables


With variables, the work goes essentially also through functions (get / set), and usually there are several alternative recording options.

 #  some.var   "value" some.var.set = value some.set_var = value some.var = value #   some.var $some.var= $some.var.get= $some.get_var= 

As I understand it, the first of the options shown above is considered modern and should work for any variables, and all the others have been used in old rtorrent versions and are now supported for some variables for the sake of backward compatibility.

In order not to bother with method.insert for the sake of creating a pair of variables, there are ready-made functions that allow you to work with variables attached to a specific torrent (which allows you to save information on each torrent separately):

 #   var   value    d.custom.set = var, value #    var    $d.custom=var 

 #  5      d.custom1.set = value1 d.custom2.set = value2 d.custom3.set = value3 d.custom4.set = value4 d.custom5.set = value5 #  5      $d.custom1= $d.custom2= $d.custom3= $d.custom4= $d.custom5= 

I wrote above that dots in function names do not have a special meaning and, in fact, do not differ from underscores. As you can see, this is not quite the case: variables starting with d. associated with a particular torrent, and each torrent has its own values ​​for these variables. Similarly, functions starting with d. - they also operate on the current torrent. Nevertheless, I think that this is a feature of the implementation of built-in functions, and for user functions and variables, the presence or absence of d. the name does not make them magically related to the current torrent (however, I did not check it).

Oddities and bugs


If the callback for schedule transmitted inside (( )) and not " " , then the parameters for this callback are also set inside nested (( )) for some reason are treated as the result of the function, and not its transfer as a callback:

 schedule id,1,1, ((print, ((directory.default)))) #   schedule id,1,1, "print = $directory.default=" 

Some functions are buggy if they are called when rtorrent is started (for example, load.start or ui.current_view.set ) - their execution needs to be postponed through the schedule , and the start time should be greater than 0. Probably this is due to the fact that when rtorrent is started, by the time commands from the config file are executed, not all data structures have been correctly initialized.

Overview of useful features


 print =   

Command line debugging; output to log

 $cat =   

Returns the concatenated lines (analogous to print , only returns the string instead of outputting it to the log).

 $argument.0= $argument.1= $argument.2= $argument.3= 

Get 1/2/3 / 4th parameter of the current function.

 method.insert = _, simple, "; " 

Create a custom function.

In my opinion, only in method.insert and method.set_key can a sequence of several commands be passed through ; - all other functions where you can send commands take them either one by one or as a list (eg load.normal and load.start ).

 method.set_key = event.download., _, "; " 

Create an event handler. Valid values ​​for : inserted , inserted_new , inserted_session , erased , opened , closed , resumed , paused , finished , hash_done , hash_failed , hash_final_failed , hash_removed , hash_queued . Why the _ not clear (it can probably be checked through method.has_key and method.list_keys but it’s not clear where this might be useful), but it is easy to guess that it should be unique.

 schedule = _, , ,   schedule_remove = _ 

Periodically executed , the main tool for automation rtorrent. Values ​​for the and the can be set either in seconds or in the format :: (if the set to 0, it will be executed once). For commands that must be executed when rtorrent is started, the value for the usually set to 5 - it is likely that rtorrent has time to fully start and the bugs I mentioned above haven't occurred.

 load.normal = __torrent_(),   load.start = __torrent_(),   

Usually called from schedule to automatically download files added to the specified directory. load.start automatically starts downloading the file after adding.

 execute = _,    execute.capture = _,    execute.nothrow = _,    execute.nothrow.bg = _,    

Executes an external program. execute.capture returns the output of this command, execute.nothrow ignores the error of the running command, execute.nothrow.bg runs the program in the background.

 if = , __, __ branch = _, __, __ 

Allows you to select the command to be executed depending on the condition. The difference between if and branch is how the condition is defined: as a value ( $func= ) or as a command ( func= ). In addition, instead of the __ if-a- __ you can set the following condition and add two more command parameters, etc.

 false= not =  and =   or =   equal =   less =   greater =   

I think the general meaning is clear. In addition to using the if and branch functions, less and greater are used to set up different sorts.

I haven’t checked the format of these and subsequent commands, so there may be errors (write in a personal, correct).

 import = - 

Connection of an additional config file.

 start_tied= stop_untied= close_untied= remove_untied= 

A rtorrent tied file names that .torrent file that you downloaded and downloaded to rtorrent (usually automatically downloading all .torrent files from the specified directories via schedule and load.start ). Curiously, rtorrent does not use this file for download, but a copy of it, which it saves to its session.path directory. Accordingly, stop_untied , close_untied and remove_untied when executed, determine what to do with the torrent file in the session.path directory if the original torrent file associated (tied) with it has been deleted. What the start_tied team is doing, I still do not understand.

 $d.base_filename= $d.base_path= $d.creation_date= $d.directory= $d.load_date= $d.name= $d.tied_to_file= 

Different properties of the current torrent. In fact, they are several times more, I just mentioned the most useful at first glance. For example, d.directory is the directory where the torrent will be d.tied_to_file , d.tied_to_file name of the original (not from the session.path ) .torrent file, d.name the file or directory the pumped out by this torrent, etc.

 $d.complete= 

Current state - this torrent downloaded or not.

 d.close= d.create_link = , ,  d.delete_link = , ,  d.delete_tied= d.erase= d.open= d.pause= d.resume= d.save_full_session= d.start= d.stop= 

Different operations on the current torrent. The value can be base_path , base_filename and tied and defines the directory for the symlink (from d.base_path , d.base_filename or d.tied_to_file ), the and are added to the name and can be empty.

 d.multicall 

I did not understand this, but as far as I understood, through this function, mass operations are performed on groups of torrents.

An example from my config


When a .torrent file appears in one of the directories /mnt/torrent/ , /mnt/torrent/serials/ and /mnt/torrent/music/ it should be:


  schedule = watch_directory_1, 5, 5, ((load.normal, /mnt/torrent/*.torrent, "d.custom.set = watchdir, / mnt / torrent", "d.directory.set = $ d.custom = watchdir "))
 schedule = watch_directory_2, 5, 5, ((load.normal, /mnt/torrent/serials/*.torrent, "d.custom.set = watchdir, / mnt / torrent / serials", "d.directory.set = $ d.custom = watchdir "))
 schedule = watch_directory_3, 5, 5, ((load.start, /mnt/torrent/music/*.torrent, "d.custom.set = watchdir, / mnt / torrent / music", "d.directory.set = $ d.custom = watchdir ")) 


  method.insert = d.renamed_suffix, simple, "if = $ d.complete =, +++, ---"
 # - WORKAROUND: extra / at beginning needed because $ d.tied_to_file = begins with //
 method.insert = d.renamed_file, simple, "cat = /, $ d.custom = watchdir, /, $ d.name =,., $ d.renamed_suffix =, .torrent"
 method.insert = d.rename_file, simple, "execute = mv, -, $ d.tied_to_file =, $ d.renamed_file =; d.tied_to_file.set = $ d.renamed_file ="
 method.insert = d.safe_rename_file, simple, "branch = ((equal, d.tied_to_file =, d.renamed_file =)),, d.rename_file ="
 method.set_key = event.download.inserted_new, rename_loaded, d.safe_rename_file =
 method.set_key = event.download.resumed, rename_resumed, d.safe_rename_file =
 method.set_key = event.download.finished, rename_finished, d.safe_rename_file = 

If the .torrent file is deleted, then its copy from session.path also needs to be deleted and this torrent removed from rtorrent.

  # Watch a directory for torrents and remove those that have been deleted.
 schedule = watch_untied, 5, 5, (((remove_untied)) 

Show a pop-up notification on the monitor when a torrent with his name is downloaded (a small supporting script is needed).

  # Notify when download finished
 method.set_key = event.download.finished, notify_me, "execute = ~ / bin / rtorrent_finished, $ d.name =" 

After launching rtorrent, open the list of still under-downloaded torrents (analogous to pressing 6manually).

 # Set default view
schedule = default_view, 1, 0, ui.current_view.set = incomplete 

Sort the list of underused ones by the date of addition (this code is taken from the recipe in the wiki and is shown here as an example of use less=).

 method.set_key = event.download.inserted_new, loaded_time, "d.custom.set = tm_loaded, $ system.time =; d.save_full_session ="
view.sort_new = incomplete, less = d.custom = tm_loaded
view.sort_current = incomplete, less = d.custom = tm_loaded 

Conclusion


I hope this description will help automate the work of your rtorrent and solve the problem of the lack of suitable recipes for you personally. Unfortunately, the quality of this article is noticeably lower than my usual articles, I haven’t yet clarified many of the questions, there are certainly errors in the descriptions of some functions, but the amount of time that was not a pity to kill for this task was severely limited. It is probably worth using the information from this article to supplement the official wiki, but I already have no time for it, let's hope someone else does it.

______________________
Text converted using habrahabr backend for AsciiDoc .

Source: https://habr.com/ru/post/238413/


All Articles